Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7853e42
[Bugfix:SubminiPolls] Multiple Poll Response Edits Not Saving (#12351)
John-Roy123 Jan 29, 2026
0bfd288
[Bugfix:Developer] Fix Broken CI Tests (#12358)
IDzyre Jan 30, 2026
a362c30
[DevDependency] Bump phpunit/phpunit from 10.5.46 to 10.5.62 in /site…
dependabot[bot] Jan 31, 2026
185cfab
[Dependency] Bump doctrine/orm from 3.6.0 to 3.6.2 in /site (#12364)
dependabot[bot] Feb 1, 2026
01202b3
[DevDependency] Bump phpstan/phpstan-strict-rules from 2.0.6 to 2.0.8…
dependabot[bot] Feb 1, 2026
6c5e2c5
[DevDependency] Bump cypress from 15.8.1 to 15.9.0 in /site (#12367)
dependabot[bot] Feb 2, 2026
515d162
[DevDependency] Bump phpstan/phpstan from 2.1.33 to 2.1.38 in /site (…
dependabot[bot] Feb 2, 2026
421b544
[Dependency] Bump psutil from 7.2.1 to 7.2.2 in /.setup/pip (#12400)
dependabot[bot] Feb 2, 2026
d14c780
[Dependency] Bump jsonschema from 4.25.1 to 4.26.0 in /python_submitt…
dependabot[bot] Feb 2, 2026
ce342dd
[DevDependency] Bump cypress-io/github-action from 6 to 7 (#12392)
dependabot[bot] Feb 2, 2026
6fc2abd
[Dependency] Bump @codemirror/view from 6.39.8 to 6.39.12 in /site (#…
dependabot[bot] Feb 2, 2026
53c3042
[DevDependency] Bump globals from 17.0.0 to 17.3.0 in /site (#12376)
dependabot[bot] Feb 2, 2026
164bb7f
[Dependency] Bump twig/markdown-extra from 3.22.0 to 3.23.0 in /site …
dependabot[bot] Feb 2, 2026
e06c225
[Dependency] Bump sqlalchemy from 2.0.45 to 2.0.46 in /.setup/pip (#1…
dependabot[bot] Feb 2, 2026
35028b0
[Dependency] Bump @codemirror/state from 6.5.3 to 6.5.4 in /site (#12…
dependabot[bot] Feb 2, 2026
58420f0
Working to add defaults
IDzyre Feb 3, 2026
3938f55
Fix boolean
IDzyre Feb 3, 2026
43043af
Add debugging statements
IDzyre Feb 3, 2026
639a70c
Add more debugging statements
IDzyre Feb 3, 2026
893f97f
Fix incorrect location of UseDefault
IDzyre Feb 3, 2026
76e3fe5
specify defaults for those that don't
IDzyre Feb 3, 2026
1b3d333
More fixes for use_default
IDzyre Feb 3, 2026
fa2104a
use file database by default
IDzyre Feb 3, 2026
16a6f53
Final fix of use_default
IDzyre Feb 3, 2026
a62d991
CI
IDzyre Feb 3, 2026
db02762
Use default password CHANGEME
IDzyre Feb 3, 2026
d991e90
Use original passwords
IDzyre Feb 4, 2026
d515fa8
Fix calls
IDzyre Feb 9, 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
21 changes: 10 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,6 @@ jobs:
echo "http://localhost" # submitty url
echo "" # vcs url
echo "" # institution name
echo "y" # user create account
echo "" # sysadmin email
echo "" # where to report
echo "1" # PamAuth
Expand Down Expand Up @@ -683,7 +682,7 @@ jobs:
run: sudo sed -ie "s/Database/Pam/g" ${SUBMITTY_INSTALL_DIR}/config/authentication.json

- name: Run accessibility test
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
spec: cypress/e2e/Cypress-System/accessibility.spec.js
Expand All @@ -702,7 +701,7 @@ jobs:
sudo systemctl stop submitty_autograding_shipper

- name: Run autograding status test
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
spec: cypress/e2e/Cypress-System/autograding_status_1.spec.js
Expand All @@ -715,7 +714,7 @@ jobs:
sudo systemctl restart submitty_autograding_shipper

- name: Run autograding status test
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
spec: cypress/e2e/Cypress-System/autograding_status_2.spec.js
Expand All @@ -728,7 +727,7 @@ jobs:
sudo systemctl restart submitty_autograding_worker

- name: Run cypress e2e login tests with PAM auth
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost,chromeWebSecurity=false
spec: cypress/e2e/Cypress-System/login.spec.js
Expand All @@ -739,15 +738,15 @@ jobs:
run: sudo sed -ie "s/Pam/Database/g" ${SUBMITTY_INSTALL_DIR}/config/authentication.json

- name: Run cypress e2e login tests with database auth
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
spec: cypress/e2e/Cypress-System/login.spec.js
working-directory: ${{env.SUBMITTY_REPOSITORY}}/site
browser: chrome

- name: Run self-account-creation tests.
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
spec: cypress/e2e/Cypress-System/self_account_creation.spec.js
Expand All @@ -758,7 +757,7 @@ jobs:
run: sudo sed -ie "s/Database/Ldap/g" ${SUBMITTY_INSTALL_DIR}/config/authentication.json

- name: Run cypress e2e login tests with LDAP auth
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
spec: cypress/e2e/Cypress-System/login.spec.js
Expand All @@ -774,7 +773,7 @@ jobs:
run: sudo sed -ie "s/Ldap/Saml/g" ${SUBMITTY_INSTALL_DIR}/config/authentication.json

- name: Run cypress e2e login tests with SAML auth
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost,chromeWebSecurity=false
spec: cypress/e2e/Cypress-System/login.spec.js
Expand Down Expand Up @@ -879,7 +878,7 @@ jobs:
npm ci

- name: Run cypress e2e tests with pam auth
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
working-directory: ${{env.SUBMITTY_REPOSITORY}}/site
Expand Down Expand Up @@ -987,7 +986,7 @@ jobs:
curl --show-error --fail --include http://localhost/authentication/login

- name: Run Ansible cypress test
uses: cypress-io/github-action@v6
uses: cypress-io/github-action@v7
with:
config: baseUrl=http://localhost
spec: cypress/e2e/Cypress-Ansible/ansible-course.spec.js
Expand Down
124 changes: 50 additions & 74 deletions .setup/CONFIGURE_SUBMITTY.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import pwd
import secrets
import shutil
import string
import tzlocal
import string
import tempfile


Expand All @@ -28,7 +28,10 @@ def get_ids(user):
raise SystemExit("ERROR: Could not find user: " + user)


def get_input(question, default=""):
def get_input(question, default="", use_default=False):
print(use_default)
if use_default:
return default
add = "[{}] ".format(default) if default != "" else ""
user = input("{}: {}".format(question, add)).strip()
if user == "":
Expand Down Expand Up @@ -61,9 +64,10 @@ def __call__(self, parser, namespace, values, option_string=None):
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--debug', action='store_true', default=False, help='Configure Submitty to be in debug mode. '
'This should not be used in production!')
parser.add_argument('--setup-for-sample-courses', action='store_true', default=False,
help="Sets up Submitty for use with the sample courses. This is a Vagrant convenience "
"flag and should not be used in production!")
parser.add_argument('--dev-vm', action='store_true', default=False,
help="Sets up submitty for use with Vagrant for developers, not to be used for production")
parser.add_argument('--ci', action='store_true', default=False,
help="Sets up Submitty with parameters for CI, This should not be used in production.")
parser.add_argument('--worker', action='store_true', default=False, help='Configure Submitty with autograding only')
parser.add_argument('--install-dir', default='/usr/local/submitty', help='Set the install directory for Submitty')
parser.add_argument('--data-dir', default='/var/local/submitty', help='Set the data directory for Submitty')
Expand Down Expand Up @@ -157,6 +161,7 @@ def __call__(self, parser, namespace, values, option_string=None):
SETUP_INSTALL_DIR = os.path.join(SUBMITTY_INSTALL_DIR, '.setup')
SETUP_REPOSITORY_DIR = os.path.join(SUBMITTY_REPOSITORY, '.setup')

DEFAULTS_FILE = os.path.join(SETUP_REPOSITORY_DIR, 'defaults.json')
INSTALL_FILE = os.path.join(SETUP_INSTALL_DIR, 'INSTALL_SUBMITTY.sh')
CONFIGURATION_JSON = os.path.join(SETUP_INSTALL_DIR, 'submitty_conf.json')
SITE_CONFIG_DIR = os.path.join(SUBMITTY_INSTALL_DIR, "site", "config")
Expand All @@ -174,41 +179,12 @@ def __call__(self, parser, namespace, values, option_string=None):
'SamlAuthentication'
]

defaults = {
'database_host': 'localhost',
'database_port': 5432,
'database_user': 'submitty_dbuser',
'database_course_user': 'submitty_course_dbuser',
'submission_url': '',
'supervisor_user': 'submitty',
'vcs_url': '',
'authentication_method': 0,
'institution_name' : '',
'institution_homepage' : '',
'user_create_account' : False,
'timezone' : str(tzlocal.get_localzone()),
'submitty_admin_username': '',
'email_user': '',
'email_password': '',
'email_sender': 'submitty@myuniversity.edu',
'email_reply_to': 'submitty_do_not_reply@myuniversity.edu',
'email_server_hostname': 'mail.myuniversity.edu',
'email_server_port': 25,
'email_internal_domain': 'example.com',
'course_code_requirements': "Please follow your school's convention for course code.",
'sys_admin_email': '',
'sys_admin_url': '',
'ldap_options': {
'url': '',
'uid': '',
'bind_dn': ''
},
'saml_options': {
'name': '',
'username_attribute': ''
},
'course_material_file_upload_limit_mb': 100
}
defaults = {}
with open(DEFAULTS_FILE, 'r') as defaults_file:
defaults = json.load(defaults_file)
defaults['timezone'] = str(tzlocal.get_localzone())
if args.ci:
defaults['database_host'] = 'localhost'

loaded_defaults = {}
if os.path.isfile(CONFIGURATION_JSON):
Expand Down Expand Up @@ -249,81 +225,81 @@ def __call__(self, parser, namespace, values, option_string=None):

print('Hit enter to use default in []')
print()

USE_DEFAULT = args.dev_vm is True
if args.worker:
SUPERVISOR_USER = get_input('What is the id for your submitty user?', defaults['supervisor_user'])
SUPERVISOR_USER = get_input('What is the id for your submitty user?', defaults['supervisor_user'], USE_DEFAULT)
print('SUPERVISOR USER : {}'.format(SUPERVISOR_USER))
else:
DATABASE_HOST = get_input('What is the database host?', defaults['database_host'])
DATABASE_HOST = get_input('What is the database host?', defaults['database_host'], USE_DEFAULT)
print()

if not os.path.isdir(DATABASE_HOST):
DATABASE_PORT = int(get_input('What is the database port?', defaults['database_port']))
DATABASE_PORT = int(get_input('What is the database port?', defaults['database_port'], USE_DEFAULT))
print()
else:
DATABASE_PORT = defaults['database_port']

DATABASE_USER = get_input('What is the global database user/role?', defaults['database_user'])
DATABASE_USER = get_input('What is the global database user/role?', defaults['database_user'], USE_DEFAULT)
print()

default = ''
if 'database_password' in defaults and DATABASE_USER == defaults['database_user']:
default = '(Leave blank to use same password)'
DATABASE_PASS = get_input('What is the password for the global database user/role {}? {}'.format(DATABASE_USER, default))
DATABASE_PASS = get_input('What is the password for the global database user/role {}? {}'.format(DATABASE_USER, default), "", USE_DEFAULT)
if DATABASE_PASS == '' and DATABASE_USER == defaults['database_user'] and 'database_password' in defaults:
DATABASE_PASS = defaults['database_password']
print()

DATABASE_COURSE_USER = get_input('What is the course database user/role?', defaults['database_course_user'])
DATABASE_COURSE_USER = get_input('What is the course database user/role?', defaults['database_course_user'], USE_DEFAULT)
print()

default = ''
if 'database_course_password' in defaults and DATABASE_COURSE_USER == defaults['database_course_user']:
default = '(Leave blank to use same password)'
DATABASE_COURSE_PASSWORD = get_input('What is the password for the course database user/role {}? {}'.format(DATABASE_COURSE_USER, default))
DATABASE_COURSE_PASSWORD = get_input('What is the password for the course database user/role {}? {}'.format(DATABASE_COURSE_USER, default), "", USE_DEFAULT)
if DATABASE_COURSE_PASSWORD == '' and DATABASE_COURSE_USER == defaults['database_course_user'] and 'database_course_password' in defaults:
DATABASE_COURSE_PASSWORD = defaults['database_course_password']
print()

TIMEZONE = get_input('What timezone should Submitty use? (for a full list of supported timezones see http://php.net/manual/en/timezones.php)', defaults['timezone'])
TIMEZONE = get_input('What timezone should Submitty use? (for a full list of supported timezones see http://php.net/manual/en/timezones.php)', defaults['timezone'], USE_DEFAULT)
print()

DEFAULT_LOCALE = get_input('What default language should the Submitty site use?', 'en_US')
DEFAULT_LOCALE = get_input('What default language should the Submitty site use?', 'en_US', USE_DEFAULT)
print()

COURSE_MATERIAL_UPLOAD_LIMIT_MB = get_input('What is the maximum file upload size for course materials (in MB)?', defaults['course_material_file_upload_limit_mb'])
COURSE_MATERIAL_UPLOAD_LIMIT_MB = get_input('What is the maximum file upload size for course materials (in MB)?', defaults['course_material_file_upload_limit_mb'], USE_DEFAULT)
print()

SUBMISSION_URL = get_input('What is the url for submission? (ex: http://192.168.56.101 or '
'https://submitty.cs.rpi.edu)', defaults['submission_url']).rstrip('/')
'https://submitty.cs.rpi.edu)', defaults['submission_url'], USE_DEFAULT).rstrip('/')
print()

VCS_URL = get_input('What is the url for VCS? (Leave blank to default to submission url + {$vcs_type}) (ex: http://192.168.56.101/{$vcs_type} or https://submitty-vcs.cs.rpi.edu/{$vcs_type}', defaults['vcs_url']).rstrip('/')
VCS_URL = get_input('What is the url for VCS? (Leave blank to default to submission url + {$vcs_type}) (ex: http://192.168.56.101/{$vcs_type} or https://submitty-vcs.cs.rpi.edu/{$vcs_type}', defaults['vcs_url'], USE_DEFAULT).rstrip('/')
print()

INSTITUTION_NAME = get_input('What is the name of your institution? (Leave blank/type "none" if not desired)',
defaults['institution_name'])
defaults['institution_name'], USE_DEFAULT)
print()

if INSTITUTION_NAME == '' or INSTITUTION_NAME.isspace():
INSTITUTION_HOMEPAGE = ''
else:
INSTITUTION_HOMEPAGE = get_input("What is the url of your institution\'s homepage? "
'(Leave blank/type "none" if not desired)', defaults['institution_homepage'])
'(Leave blank/type "none" if not desired)', defaults['institution_homepage'], USE_DEFAULT)
if INSTITUTION_HOMEPAGE.lower() == "none":
INSTITUTION_HOMEPAGE = ''
print()

SYS_ADMIN_EMAIL = get_input("What is the email for system administration?", defaults['sys_admin_email'])
SYS_ADMIN_URL = get_input("Where to report problems with Submitty (url for help link)?", defaults['sys_admin_url'])
SYS_ADMIN_EMAIL = get_input("What is the email for system administration?", defaults['sys_admin_email'], USE_DEFAULT)
SYS_ADMIN_URL = get_input("Where to report problems with Submitty (url for help link)?", defaults['sys_admin_url'], USE_DEFAULT)

print('What authentication method to use:')
for i in range(len(authentication_methods)):
print(f"{i + 1}. {authentication_methods[i]}")

while True:
try:
auth = int(get_input('Enter number?', defaults['authentication_method'])) - 1
auth = int(get_input('Enter number?', defaults['authentication_method'], USE_DEFAULT)) - 1
except ValueError:
auth = -1
if auth in range(len(authentication_methods)):
Expand All @@ -341,13 +317,13 @@ def __call__(self, parser, namespace, values, option_string=None):
}
USER_CREATE_ACCOUNT = False
if AUTHENTICATION_METHOD == 'DatabaseAuthentication':
user_create_account = get_input("Allow users to create their own accounts? [y/n]", 'n')
user_create_account = get_input("Allow users to create their own accounts? [y/n]", 'n', USE_DEFAULT)
USER_CREATE_ACCOUNT = user_create_account.lower() in ['yes', 'y']
print()
if AUTHENTICATION_METHOD == 'LdapAuthentication':
LDAP_OPTIONS['url'] = get_input('Enter LDAP url?', LDAP_OPTIONS['url'])
LDAP_OPTIONS['uid'] = get_input('Enter LDAP UID?', LDAP_OPTIONS['uid'])
LDAP_OPTIONS['bind_dn'] = get_input('Enter LDAP bind_dn?', LDAP_OPTIONS['bind_dn'])
LDAP_OPTIONS['url'] = get_input('Enter LDAP url?', LDAP_OPTIONS['url'], USE_DEFAULT)
LDAP_OPTIONS['uid'] = get_input('Enter LDAP UID?', LDAP_OPTIONS['uid'], USE_DEFAULT)
LDAP_OPTIONS['bind_dn'] = get_input('Enter LDAP bind_dn?', LDAP_OPTIONS['bind_dn'], USE_DEFAULT)

default_auth_options = defaults.get('saml_options', dict())
SAML_OPTIONS = {
Expand All @@ -356,28 +332,28 @@ def __call__(self, parser, namespace, values, option_string=None):
}

if AUTHENTICATION_METHOD == 'SamlAuthentication':
SAML_OPTIONS['name'] = get_input('Enter name you would like shown to user for authentication?', SAML_OPTIONS['name'])
SAML_OPTIONS['username_attribute'] = get_input('Enter SAML username attribute?', SAML_OPTIONS['username_attribute'])
SAML_OPTIONS['name'] = get_input('Enter name you would like shown to user for authentication?', SAML_OPTIONS['name'], USE_DEFAULT)
SAML_OPTIONS['username_attribute'] = get_input('Enter SAML username attribute?', SAML_OPTIONS['username_attribute'], USE_DEFAULT)


CGI_URL = SUBMISSION_URL + '/cgi-bin'

SUBMITTY_ADMIN_USERNAME = get_input("What is the submitty admin username (optional)?", defaults['submitty_admin_username'])
SUBMITTY_ADMIN_USERNAME = get_input("What is the submitty admin username (optional)?", defaults['submitty_admin_username'], USE_DEFAULT)

while True:
is_email_enabled = get_input("Will Submitty use email notifications? [y/n]", 'y')
is_email_enabled = get_input("Will Submitty use email notifications? [y/n]", 'y', USE_DEFAULT)
if (is_email_enabled.lower() in ['yes', 'y']):
EMAIL_ENABLED = True
EMAIL_USER = get_input("What is the email user?", defaults['email_user'])
EMAIL_PASSWORD = get_input("What is the email password",defaults['email_password'])
EMAIL_SENDER = get_input("What is the email sender address (the address that will appear in the From: line)?",defaults['email_sender'])
EMAIL_REPLY_TO = get_input("What is the email reply to address?", defaults['email_reply_to'])
EMAIL_SERVER_HOSTNAME = get_input("What is the email server hostname?", defaults['email_server_hostname'])
EMAIL_USER = get_input("What is the email user?", defaults['email_user'], USE_DEFAULT)
EMAIL_PASSWORD = get_input("What is the email password",defaults['email_password'], USE_DEFAULT)
EMAIL_SENDER = get_input("What is the email sender address (the address that will appear in the From: line)?",defaults['email_sender'], USE_DEFAULT)
EMAIL_REPLY_TO = get_input("What is the email reply to address?", defaults['email_reply_to'], USE_DEFAULT)
EMAIL_SERVER_HOSTNAME = get_input("What is the email server hostname?", defaults['email_server_hostname'], USE_DEFAULT)
try:
EMAIL_SERVER_PORT = int(get_input("What is the email server port?", defaults['email_server_port']))
EMAIL_SERVER_PORT = int(get_input("What is the email server port?", defaults['email_server_port'], USE_DEFAULT))
except ValueError:
EMAIL_SERVER_PORT = defaults['email_server_port']
EMAIL_INTERNAL_DOMAIN = get_input("What is the internal email address format?", defaults['email_internal_domain'])
EMAIL_INTERNAL_DOMAIN = get_input("What is the internal email address format?", defaults['email_internal_domain'], USE_DEFAULT)
break

elif (is_email_enabled.lower() in ['no', 'n']):
Expand Down Expand Up @@ -541,7 +517,7 @@ def write(x=''):
if not args.worker:
if not os.path.isfile(WORKERS_JSON):
capabilities = ["default"]
if args.setup_for_sample_courses:
if args.dev_vm:
capabilities.extend(["cpp", "python", "et-cetera", "notebook", "unsupported"])

worker_dict = {
Expand Down
Loading
Loading