Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
58f1c0b
Add duplicate unit tests to cover create_user with the UUID user ID a…
jonasbardino May 28, 2026
26f6229
Add unit test to verify expected FS paths are created for UUID users.
jonasbardino May 28, 2026
1c6961d
Extend unit tests of expected FS contents after user creation both wi…
jonasbardino May 28, 2026
ae02139
Add short_id and verification of corresponding link in FS from create…
jonasbardino May 28, 2026
1591938
Add delete user test and verify that it cleans up in FS, too.
jonasbardino May 28, 2026
3325a30
More thorough clean up verification.
jonasbardino May 28, 2026
742cc25
Split tests into more logical parts with delete_user tested separately.
jonasbardino May 28, 2026
4bb7f6d
Rework delete_user unit tests to rely on _provision_test_user for sim…
jonasbardino May 29, 2026
c944f7e
Drop unused import of `lookup_client_id` helper. May need reintroduct…
jonasbardino Jun 1, 2026
1d3a7b9
Non-functional change to clarify the role and use of `lookup_client_i…
jonasbardino Jun 1, 2026
d4f0250
Add unit tests for the recently renamed `lookup_client_id_from_uuid` …
jonasbardino Jun 1, 2026
51f9ac2
Add unit tests for get_any_oid_user_dn in the case of UUID user ID fo…
jonasbardino Jun 1, 2026
ef5c47b
Add similar unit tests for get_any_oid_user_dn with the default user …
jonasbardino Jun 1, 2026
45c366e
A bit of consistency clean up and addition of flush user as clean up …
jonasbardino Jun 1, 2026
5ebefc7
Try harder to make sure that the required X509 symlink to UUID user h…
jonasbardino Jun 3, 2026
e5effe6
Further test fixes and adjustments to make them pass.
jonasbardino Jun 3, 2026
4f7bdf2
Adjust warning to error and make unit tests more robust with actual e…
jonasbardino Jun 9, 2026
de3f88f
Tighten the reverse lookup in lookup_client_id_from_uuid to prevent c…
jonasbardino Jun 9, 2026
eb4dd7f
Tighten edit_user tests to verify not just changes but also that noth…
jonasbardino Jun 9, 2026
66a3c94
Establish griddaemons unit tests with just some simple login helper t…
jonasbardino Jun 10, 2026
2b18634
Minor clean up.
jonasbardino Jun 10, 2026
a8a1088
Split tests into individual classes to make it easier to target requi…
jonasbardino Jun 10, 2026
c00cfcd
Add tests for refresh_share_creds.
jonasbardino Jun 10, 2026
3da033d
Simplify setup with shared helper vars and extend test coverage for the
jonasbardino Jun 10, 2026
bcc0472
Add test coverage of refresh_user_creds in griddaemons suite and extend
jonasbardino Jun 10, 2026
f444a94
Add UUID user id version of refresh_user_creds unit tests.
jonasbardino Jun 12, 2026
58e220a
Add UUID user id version of get_creds_changes unit tests.
jonasbardino Jun 12, 2026
8542b0a
Add unit tests for login_map_lookup helper.
jonasbardino Jun 12, 2026
fdb61cc
Add update_login_map unit tests.
jonasbardino Jun 12, 2026
0f1af2a
Add add_user_object unit tests.
jonasbardino Jun 12, 2026
deea53f
Add add_share_object unit tests.
jonasbardino Jun 12, 2026
ef85598
Add update_user_objects unit tests.
jonasbardino Jun 13, 2026
a7eb90d
Add unit tests for add_jupyter_object and add_jupyter_object . Begin …
jonasbardino Jun 15, 2026
0f00a25
Sort unit test classes and refactor out the duplicate FakeLock class.
jonasbardino Jun 15, 2026
c2f848e
Comment-only changes.
jonasbardino Jun 15, 2026
1b05d1a
Complete fundamental unit test coverage of griddaemons.login module.
jonasbardino Jun 15, 2026
02314b8
Rename test suite to fit griddaemons login coverage.
jonasbardino Jun 15, 2026
cc765c0
Reformat griddaemons unit test suite with make python-format to sync …
jonasbardino Jun 15, 2026
3053cf1
Pull in some minor adjustments to useramd test suite from #576.
jonasbardino Jun 15, 2026
8009fe1
Initial unit tests for accountstate.
jonasbardino Jun 15, 2026
a99675d
Extend accountstate unit tests to cover get_account_expire_cache,
jonasbardino Jun 15, 2026
622fb5d
Extend with unit tests for get_account_status_cache and
jonasbardino Jun 15, 2026
5b0454e
Add unit tests for check_account_expire .
jonasbardino Jun 15, 2026
965db1c
Reformat with `make format-python`.
jonasbardino Jun 15, 2026
7610865
Fix test naming for default_account_valid_days and default_account_ex…
jonasbardino Jun 15, 2026
00280f5
Remove debug message.
jonasbardino Jun 15, 2026
71c0f7b
Pull in minor fixes from #575.
jonasbardino Jun 16, 2026
21c869d
Pull in fix from #576.
jonasbardino Jun 16, 2026
cb72471
Fix various merge and rebase fallout.
jonasbardino Jun 16, 2026
cffc38f
Reformat with make format-python .
jonasbardino Jun 16, 2026
e745d9d
Add unit tests for account_expire_info and detect_special_login.
jonasbardino Jun 18, 2026
2b39980
Add tests for check_account_status.
jonasbardino Jun 18, 2026
5f3bfba
Format unit tests with make format-python helper.
jonasbardino Jun 18, 2026
c82823d
Add tests for check_update_account_expire.
jonasbardino Jun 18, 2026
15379fb
Add unit tests for check_update_account_expire and sync back some con…
jonasbardino Jun 19, 2026
8ebfce3
Rework handling of UUID user home in check_account_accessible to corr…
jonasbardino Jun 23, 2026
720c455
Resync with test suite in #580 and extend coverage to some of the UUI…
jonasbardino Jun 23, 2026
c47547d
Remove try/except wrapping of calls were the backend function was mea…
jonasbardino Jun 23, 2026
9c86f29
Bump copyright year.
jonasbardino Jun 23, 2026
86dc2fe
Merge branch 'next' into adjust/support-for-uuid-user-id-as-home-dir
jonasbardino Jun 24, 2026
c2fbbeb
Expose `user_id_format` in generateconfs but with explicit default of…
jonasbardino Jun 24, 2026
0033349
Fix a corner-case bug in the lookup_client_id_from_uuid where it woul…
jonasbardino Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mig/install/MiGserver-template.conf
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ update_interval = __ACCOUNTING_UPDATE_INTERVAL__
# Web site appearance
# Whether to use Python 3 for all Python invocations
prefer_python3 = __PREFER_PYTHON3__
# Which user home dir format to use (default is X509; only experimental UUID)
user_id_format = __USER_ID_FORMAT__
# Dynamic entry page to pick user default with fallback to site landing page
autolaunch_page = __AUTOLAUNCH_PAGE__
# Entry page if not explictly provided or overriden by user
Expand Down
1 change: 1 addition & 0 deletions mig/install/generateconfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def main(argv, _generate_confs=generate_confs, _print=print):
'listen_clause',
'serveralias_clause',
'distro',
'user_id_format',
'autolaunch_page',
'landing_page',
'skin',
Expand Down
40 changes: 33 additions & 7 deletions mig/shared/accountstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# --- BEGIN_HEADER ---
#
# accountstate - various user account state helpers
# Copyright (C) 2003-2025 The MiG Project by the Science HPC Center at UCPH
# Copyright (C) 2003-2026 The MiG Project by the Science HPC Center at UCPH
#
# This file is part of MiG.
#
Expand All @@ -20,7 +20,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

Check warning on line 23 in mig/shared/accountstate.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
#
# -- END_HEADER ---
#
Expand Down Expand Up @@ -62,7 +62,7 @@
return valid_days


def default_account_expire(configuration, auth_type, start_time=int(time.time())):

Check warning on line 65 in mig/shared/accountstate.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (82 > 80 characters)
"""Lookup default account expire value (epoch) based on start_time"""

valid_days = default_account_valid_days(configuration, auth_type)
Expand Down Expand Up @@ -144,7 +144,7 @@
if not status_key:
_logger.info("no status set for user: %s" % user_dict)
return True
if not status_key in valid_account_status:

Check warning on line 147 in mig/shared/accountstate.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'
_logger.error("invalid account status for user: %s" % user_dict)
return False
if delete:
Expand Down Expand Up @@ -263,7 +263,7 @@
if environ.get('REMOTE_ADDR', '') == '127.0.0.1' or \
environ.get('HTTP_USER_AGENT', '') == 'grid cron daemon':
try_renew = False
elif account_expire and account_expire < time.time() + min_days_left * 86400:

Check warning on line 266 in mig/shared/accountstate.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
try_renew = True
else:
try_renew = False
Expand Down Expand Up @@ -392,7 +392,7 @@
real_path = os.path.realpath(os.path.join(
configuration.sharelink_home, mode, username))
if os.path.exists(real_path):
_logger.info("%s sharelink %s detected - always accessible" %

Check warning on line 395 in mig/shared/accountstate.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
(mode, username))
return True
if proto in ('sftp', 'sftp-subsys') and \
Expand Down Expand Up @@ -453,17 +453,43 @@
# Use client_id_dir to make it work even if already expanded
home_dir = os.path.join(configuration.user_home,
client_id_dir(client_id))
# Expand to actual home dir and proceed from there
expanded_home = os.path.realpath(home_dir)
if configuration.site_user_id_format == X509_USER_ID_FORMAT:
# Expand to actual home dir
expanded_home = os.path.realpath(home_dir)
client_dir = os.path.basename(expanded_home)
elif configuration.site_user_id_format == UUID_USER_ID_FORMAT:
# Only follow first username -> client_id_dir symlink with UUID
expanded_home = os.readlink(home_dir)
# Traverse home_dir links to X509 dir name linking to expanded_home
next_link = home_dir
client_dir = None
expansions, max_expansions = 0, 3
while os.path.islink(next_link) and expansions < max_expansions:
_logger.debug("compare next link %s to expanded %s" %
(next_link, expanded_home))
if os.readlink(next_link) == expanded_home:
# Next link is the wanted X509 link to expanded UUID home
_logger.debug("found %s account ID dir: %s -> %s" %
(client_id, next_link, expanded_home))
client_dir = os.path.basename(next_link)
break
else:
# follow next_link and prepend user_home if relative
next_link = os.readlink(next_link)
max_expansions += 1
if os.path.basename(next_link) == next_link:
next_link = os.path.join(configuration.user_home,
next_link)
if client_dir is None:
_logger.debug("link traversal for %s ended at %s" %
(client_id, next_link))
_logger.warning("failed to lookup home and ID for %s" %
username)
return False
else:
raise ValueError("invalid user ID format requested: %s" %
configuration.site_user_id_format)
real_id = os.path.basename(expanded_home)
client_id = client_dir_id(real_id)

# Now we can extract the desired X509 user ID
client_id = client_dir_id(client_dir)

(account_accessible, account_status, _) = check_account_status(
configuration, client_id)
Expand All @@ -484,8 +510,8 @@
if __name__ == "__main__":
from mig.shared.conf import get_configuration_object
conf = get_configuration_object()
active_user = '/C=DK/ST=NA/L=NA/O=NBI/OU=NA/CN=Jonas Bardino/emailAddress=bardino@nbi.ku.dk'

Check warning on line 513 in mig/shared/accountstate.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (96 > 80 characters)
suspended_user = '/C=DK/ST=NA/L=NA/O=FAKSEK/OU=NA/CN=Jonas Bardino/emailAddress=bardino@science.ku.dk'

Check warning on line 514 in mig/shared/accountstate.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (106 > 80 characters)
dummy_user = "No_Such_User"
active_user_email = active_user.split('=')[-1]
suspended_user_email = suspended_user.split('=')[-1]
Expand Down
16 changes: 7 additions & 9 deletions mig/shared/griddaemons/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# --- BEGIN_HEADER ---
#
# login - grid daemon login helper functions
# Copyright (C) 2003-2025 The MiG Project by the Science HPC Center at UCPH
# Copyright (C) 2003-2026 The MiG Project by the Science HPC Center at UCPH
#
# This file is part of MiG.
#
Expand All @@ -20,7 +20,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

Check warning on line 23 in mig/shared/griddaemons/login.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
#
# -- END_HEADER ---
#
Expand All @@ -47,8 +47,7 @@
https_authkeys, get_authkeys, ssh_authpasswords, davs_authpasswords, \
ftps_authpasswords, https_authpasswords, get_authpasswords, \
ssh_authdigests, davs_authdigests, ftps_authdigests, https_authdigests, \
generate_password_hash, generate_password_digest, load_user_dict, \
lookup_client_id
generate_password_hash, generate_password_digest, load_user_dict
from mig.shared.validstring import possible_sharelink_id, possible_job_id, \
possible_jupyter_mount_id

Expand Down Expand Up @@ -399,7 +398,7 @@
all_digests = []
# Clean up all old key entries for this user
conf['users'] = [i for i in conf['users']
if not i.username in user_logins or

Check warning on line 401 in mig/shared/griddaemons/login.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'
i.public_key is None]
elif auth_file == proto_authpasswords:
all_keys = []
Expand All @@ -415,7 +414,7 @@
all_digests = []
# Clean up all old password entries for this user
conf['users'] = [i for i in conf['users']
if not i.username in user_logins or

Check warning on line 417 in mig/shared/griddaemons/login.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

test for membership should be 'not in'
i.password is None]
else:
all_keys = []
Expand Down Expand Up @@ -636,8 +635,7 @@
sessionid = username
job_dict = unpickle(link_path, logger)

# We only allow connections from executing jobs that
# has a public key
# We only allow connections from executing jobs with a public key
if job_dict is not None and isinstance(job_dict, dict) and \
'STATUS' in job_dict and \
job_dict['STATUS'] == 'EXECUTING' and \
Expand Down Expand Up @@ -852,15 +850,15 @@
logger.error("invalid protocol: %s" % protocol)
return (conf, active_jupyter_creds)
if not possible_jupyter_mount_id(configuration, username):
# logger.debug("ruled out %s as a possible jupyter_mount ID" \
# % username)
# logger.debug("ruled out %s as a possible jupyter_mount ID" %
# username)
return (conf, active_jupyter_creds)

logger.info("Getting active jupyter mount creds")
link_path = os.path.join(configuration.sessid_to_jupyter_mount_link_home,
"%s" % username + ".jupyter_mount")
# logger.debug("jupyter linkpath: %s jupyter path exists: %s" % \
# (os.path.islink(link_path), os.path.exists(link_path)))
# logger.debug("jupyter linkpath: %s jupyter path exists: %s" %
# (os.path.islink(link_path), os.path.exists(link_path)))

jupyter_dict, sessionid = None, None
if os.path.islink(link_path) and os.path.exists(link_path):
Expand Down
3 changes: 3 additions & 0 deletions mig/shared/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ def generate_confs(
listen_clause='#Listen',
serveralias_clause='#ServerAlias',
distro='Debian',
user_id_format='X509',
autolaunch_page=None,
landing_page=None,
skin='migrid-basic',
Expand Down Expand Up @@ -833,6 +834,7 @@ def _generate_confs_prepare(
listen_clause,
serveralias_clause,
distro,
user_id_format,
autolaunch_page,
landing_page,
skin,
Expand Down Expand Up @@ -1094,6 +1096,7 @@ def _generate_confs_prepare(
user_dict['__LISTEN_CLAUSE__'] = listen_clause
user_dict['__SERVERALIAS_CLAUSE__'] = serveralias_clause
user_dict['__DISTRO__'] = distro
user_dict['__USER_ID_FORMAT__'] = user_id_format
user_dict['__SKIN__'] = skin
user_dict['__TITLE__'] = title
user_dict['__SHORT_TITLE__'] = short_title
Expand Down
20 changes: 14 additions & 6 deletions mig/shared/useradm.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,24 +284,30 @@
return alias_links


def lookup_client_id(configuration, user_id):
"""Lookup the X509 format client_id linked to user_id based on the helper
symlink in user_id_alias_dir in configured mig_system_run location.
def lookup_client_id_from_uuid(configuration, user_id):
"""Lookup the X509 format client_id linked to UUID user_id based on the
helper symlink in user_id_alias_dir in configured mig_system_run location.
Automatic fallback to reverse lookup based on symlinks in user_home if
helper symlink is missing with write-through to mig_system_run cache.
"""
_logger = configuration.logger
alias_link = os.path.join(configuration.mig_system_run, user_id_alias_dir,
user_id)
_logger.debug("looking for alias link %r" % alias_link)
# NOTE: use islink rather than exists here because it is a dead link if so
if os.path.islink(alias_link):
target_path = os.path.realpath(alias_link)
# Only follow first chain even if mix of UUID and X509 links overlap
target_path = os.readlink(alias_link)
client_dir = os.path.basename(target_path)
_logger.debug("found linked alias %r for %r" % (client_dir, user_id))
else:
# Try a reverse lookup directly in user_home with writeback to
# mig_system_run, which is only scratch space.
client_dir = False
for name in listdir(configuration.user_home):
# Skip if not a DN-dir name to avoid hit e.g. on short alias links
if '=' not in name or '+' not in name:
continue
link_path = os.path.join(configuration.user_home, name)
if os.path.islink(link_path):
target = os.path.basename(os.path.realpath(link_path))
Expand All @@ -312,8 +318,9 @@
makedirs_rec(os.path.dirname(alias_link), configuration)
make_symlink(client_dir, alias_link, _logger)
break

if not client_dir:
_logger.warning("found no alias %r" % user_id)
_logger.error("found no alias %r" % user_id)
return user_id
client_id = client_dir_id(client_dir)
return client_id
Expand Down Expand Up @@ -650,7 +657,7 @@
if verbose:
print("""Renewal request supplied a different
password and you didn't accept change anyway - nothing more to do""")
err = """Cannot renew account using a new password!

Check failure on line 660 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
Please tell user to use the original password, request password reset or go
through renewal using Xgi-bin with proper authentication to authorize the
change."""
Expand Down Expand Up @@ -1182,7 +1189,7 @@
for (share_id, share_dict) in sharelinks.items():
# Update owner and use generic update helper to replace symlink
share_dict['owner'] = client_id
(mod_status, err) = update_share_link(share_dict, client_id,

Check failure on line 1192 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
configuration, sharelinks)
if verbose:
if mod_status:
Expand Down Expand Up @@ -1466,14 +1473,14 @@
res_map = get_resource_map(configuration)
for (res_id, res) in res_map.items():
if client_id in res[OWNERS]:
(add_status, err) = resource_add_owners(configuration, res_id,

Check failure on line 1476 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
[new_id])
if not add_status:
if verbose:
print('Could not add new %s owner of %s: %s'
% (new_id, res_id, err))
continue
(del_status, err) = resource_remove_owners(configuration, res_id,

Check failure on line 1483 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
[client_id])
if not del_status:
if verbose:
Expand All @@ -1494,14 +1501,14 @@
for (vgrid_name, vgrid) in vgrid_map[VGRIDS].items():
if client_id in vgrid[OWNERS]:

(add_status, err) = vgrid_add_owners(configuration, vgrid_name,

Check failure on line 1504 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
[new_id])
if not add_status:
if verbose:
print('Could not add new %s owner of %s: %s'
% (new_id, vgrid_name, err))
continue
(del_status, err) = vgrid_remove_owners(configuration, vgrid_name,

Check failure on line 1511 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
[client_id])
if not del_status:
if verbose:
Expand All @@ -1513,14 +1520,14 @@
client_id,
new_id))
elif client_id in vgrid[MEMBERS]:
(add_status, err) = vgrid_add_members(configuration, vgrid_name,

Check failure on line 1523 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
[new_id])
if not add_status:
if verbose:
print('Could not add new %s member of %s: %s'
% (new_id, vgrid_name, err))
continue
(del_status, err) = vgrid_remove_members(configuration, vgrid_name,

Check failure on line 1530 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
[client_id])
if not del_status:
if verbose:
Expand All @@ -1537,7 +1544,7 @@
(re_status, re_list) = list_runtime_environments(configuration)
if re_status:
for re_name in re_list:
(re_status, err) = update_runtimeenv_owner(re_name, client_id,

Check failure on line 1547 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
new_id, configuration)
if verbose:
if re_status:
Expand All @@ -1559,7 +1566,7 @@
for (share_id, share_dict) in sharelinks.items():
# Update owner and use generic update helper to replace symlink
share_dict['owner'] = new_id
(mod_status, err) = update_share_link(share_dict, new_id,

Check failure on line 1569 in mig/shared/useradm.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Assignment to variable "err" outside except: block [misc]
configuration, sharelinks)
if verbose:
if mod_status:
Expand Down Expand Up @@ -1875,7 +1882,8 @@
(native_dir, raw_login))
if configuration.site_user_id_format == UUID_USER_ID_FORMAT:
user_id = native_dir
distinguished_name = lookup_client_id(configuration, user_id)
distinguished_name = lookup_client_id_from_uuid(configuration,
user_id)
elif configuration.site_user_id_format == X509_USER_ID_FORMAT:
distinguished_name = client_dir_id(native_dir)
else:
Expand Down
2 changes: 2 additions & 0 deletions tests/fixture/confs-stdlocal/MiGserver.conf
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ update_interval = 3600
# Web site appearance
# Whether to use Python 3 for all Python invocations
prefer_python3 = False
# Which user home dir format to use (default is X509; only experimental UUID)
user_id_format = X509
# Dynamic entry page to pick user default with fallback to site landing page
autolaunch_page = /wsgi-bin/autolaunch.py
# Entry page if not explictly provided or overriden by user
Expand Down
Loading
Loading