From c28ccf10ac12a326aa842b9a79a4cd7ee9b2eb11 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 1 Jun 2026 15:06:56 +0200 Subject: [PATCH 01/27] Fix: Typo in comment --- docker-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index e52a68dc66e..4ccd59d7bdb 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -10,7 +10,7 @@ if [ "$1" = 've/bin/gunicorn' ] || [ "$1" = 've/bin/python' ]; then cd /opt/specify7 echo "Applying Django migrations." set +e - ./sp7_db_setup_check.sh # Setup db users and run mirgations + ./sp7_db_setup_check.sh # Setup db users and run migrations # ve/bin/python manage.py base_specify_migration # ve/bin/python manage.py migrate ve/bin/python manage.py run_key_migration_functions # Uncomment if you want the key migration functions to run on startup. From 1213dc2344bb03e3ac079c2506fd402d6ad042f2 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 1 Jun 2026 15:27:59 +0200 Subject: [PATCH 02/27] Fix: Improve DB setup script --- sp7_db_setup_check.sh | 189 ++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 127 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 652d8514f3b..1833315a404 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -euo pipefail echo "Starting MariaDB database and user creation script..." @@ -40,7 +41,7 @@ if [[ -z "$MIGRATOR_NAME" ]]; then MIGRATOR_PASSWORD="$DB_ROOT_PASSWORD" fi -# If app user name is not set, set it to migrator +# If target user name is not set, set it to migrator if [[ -z "$APP_USER_NAME" ]]; then APP_USER_NAME="$MIGRATOR_NAME" APP_USER_PASSWORD="$MIGRATOR_PASSWORD" @@ -121,14 +122,14 @@ else fi # Create database if it doesn't exist -DB_EXISTS=$(mariadb -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -sse "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '$DB_NAME';") +DB_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ +"SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = '$DB_NAME';") if [[ "$DB_EXISTS" -eq 0 ]]; then echo "Creating database '$DB_NAME'..." - echo "Executing: mariadb -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE DATABASE \`$DB_NAME\`;\"" - if mariadb -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "CREATE DATABASE \`$DB_NAME\`;"; then + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE DATABASE \`$DB_NAME\`;\"" + if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ + -e "CREATE DATABASE \`$DB_NAME\`;"; then NEW_DATABASE_CREATED=1 else echo "Error: Failed to create database." @@ -138,125 +139,45 @@ else echo "Database '$DB_NAME' already exists." fi -######################################## -# MIGRATOR USER -######################################## - -if [[ "$SAME_MASTER_AND_MIGRATOR" == true ]]; then - echo "Migrator user '$MIGRATOR_NAME' uses the same credentials as master." - echo "Skipping creation/grant steps for a separate migrator account and using master connection for migrations." - MIGRATION_DB_ALIAS="master" -else - echo "Ensuring migrator user '$MIGRATOR_NAME' exists for relevant hosts..." - - MIGRATOR_HOSTS_SEEN="" - # Ensure user exists for both MIGRATOR_USER_HOST and CLIENT_HOST - for h in "$MIGRATOR_USER_HOST" "$CLIENT_HOST"; do - [[ -z "$h" ]] && continue - if [[ " $MIGRATOR_HOSTS_SEEN " == *" $h "* ]]; then - continue - fi - MIGRATOR_HOSTS_SEEN+=" $h" +# Create migrator user if it doesn't exist +USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ +"SELECT COUNT(*) FROM mysql.user WHERE user = '$MIGRATOR_NAME' AND host = '$MIGRATOR_USER_HOST';") - USER_EXISTS_HOST=$(mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -sse "SELECT COUNT(*) FROM mysql.user WHERE user = '$MIGRATOR_NAME' AND host = '$h';") - - if [[ "$USER_EXISTS_HOST" -eq 0 ]]; then - echo "Creating user '${MIGRATOR_NAME}'@'${h}'..." - echo "Executing: CREATE USER '${MIGRATOR_NAME}'@'${h}' IDENTIFIED BY ''" - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "CREATE USER '${MIGRATOR_NAME}'@'${h}' IDENTIFIED BY '${MIGRATOR_PASSWORD}';"; then - echo "Error: Failed to create migrator user '${MIGRATOR_NAME}'@'${h}'." - exit 1 - fi - NEW_MIGRATOR_USER_CREATED=1 - else - echo "Migrator user '${MIGRATOR_NAME}'@'${h}' already exists." - fi - done - - echo "Existing hosts for '$MIGRATOR_NAME' in mysql.user:" - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SELECT CONCAT(\"'\", user, \"'@'\", host, \"'\") FROM mysql.user WHERE user = '$MIGRATOR_NAME';"; then - echo "Warning: Could not list existing hosts for '$MIGRATOR_NAME'." +if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then + echo "Creating migrator user '$MIGRATOR_NAME'..." + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE USER '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}' IDENTIFIED BY '';\"" + if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ + -e "CREATE USER '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}' IDENTIFIED BY '${MIGRATOR_PASSWORD}';"; then + NEW_MIGRATOR_USER_CREATED=1 + else + echo "Error: Failed to create user." + exit 1 fi - # Grant privileges on DB_NAME for all relevant migrator hosts - echo "Granting privileges to migrator user '$MIGRATOR_NAME'..." - MIGRATOR_GRANT_HOSTS_SEEN="" - for h in "$MIGRATOR_USER_HOST" "$CLIENT_HOST"; do - [[ -z "$h" ]] && continue - if [[ " $MIGRATOR_GRANT_HOSTS_SEEN " == *" $h "* ]]; then - continue - fi - MIGRATOR_GRANT_HOSTS_SEEN+=" $h" - - echo "Granting ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${h}'..." - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${h}';"; then - echo "Error: Failed to grant privileges to migrator user '${MIGRATOR_NAME}'@'${h}'." - echo "--------------" - echo "GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${h}'" - echo "--------------" - exit 1 - fi - done - - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "FLUSH PRIVILEGES;"; then - echo "Error: Failed to FLUSH PRIVILEGES for migrator user." +if [[ "$NEW_MIGRATOR_USER_CREATED" -eq 1 ]]; then + echo "Granting privileges to new user..." + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}'; FLUSH PRIVILEGES;\"" + if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -e "GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}'; FLUSH PRIVILEGES;"; then + echo "Error: Failed to grant privileges to new user." exit 1 fi - # Verify migrator access for MIGRATOR_USER_HOST - GRANTS_OUTPUT="$(mariadb -N -B -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SHOW GRANTS FOR '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}';" 2>/dev/null || true)" +GRANTS_OUTPUT="$(mysql -N -B -h "$DB_HOST" -P "$DB_PORT" \ + -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ + -e "SHOW GRANTS FOR '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}';" 2>/dev/null || true)" - if [[ -z "$GRANTS_OUTPUT" ]]; then - echo "Error: Could not retrieve grants for '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}'." - echo "Check whether this user exists with a different host (e.g. 'localhost' instead of '$MIGRATOR_USER_HOST')." - exit 1 - fi +GRANTS_PARSED="$(echo "$GRANTS_OUTPUT" | tr -s '[:space:]' ' ')" - migrator_has_access=false - while IFS= read -r raw_line; do - line="$(echo "$raw_line" | tr -s '[:space:]' ' ')" - if echo "$line" | grep -Eiq " ON (\*\.\*|(\`?${DB_NAME}\`?)\.\*) "; then - privs="$(echo "$line" | sed -E 's/^GRANT (.+) ON .+$/\1/I')" - if echo "$privs" | grep -Eiq '^[[:space:]]*USAGE[[:space:]]*$'; then - continue - fi - migrator_has_access=true - break - fi - done <<< "$GRANTS_OUTPUT" - - # Also verify for CLIENT_HOST if available and different - if [[ -n "$CLIENT_HOST" && "$CLIENT_HOST" != "$MIGRATOR_USER_HOST" ]]; then - CLIENT_GRANTS_OUTPUT="$(mariadb -N -B -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SHOW GRANTS FOR '${MIGRATOR_NAME}'@'${CLIENT_HOST}';" 2>/dev/null || true)" - while IFS= read -r raw_line; do - line="$(echo "$raw_line" | tr -s '[:space:]' ' ')" - if echo "$line" | grep -Eiq " ON (\*\.\*|(\`?${DB_NAME}\`?)\.\*) "; then - privs="$(echo "$line" | sed -E 's/^GRANT (.+) ON .+$/\1/I')" - if echo "$privs" | grep -Eiq '^[[:space:]]*USAGE[[:space:]]*$'; then - continue - fi - migrator_has_access=true - break - fi - done <<< "$CLIENT_GRANTS_OUTPUT" - fi +# Create app user if it doesn't exist +USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ +"SELECT COUNT(*) FROM mysql.user WHERE user = '$APP_USER_NAME' AND host = '$APP_USER_HOST';") - if [[ "$migrator_has_access" == true ]]; then - echo "Verified: migrator user '${MIGRATOR_NAME}' has usable access to '${DB_NAME}'." +if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then + echo "Creating app user '$APP_USER_NAME'..." + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE USER '${APP_USER_NAME}'@'${APP_USER_HOST}' IDENTIFIED BY '';\"" + if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ + -e "CREATE USER '${APP_USER_NAME}'@'${APP_USER_HOST}' IDENTIFIED BY '${APP_USER_PASSWORD}';"; then + NEW_APP_USER_CREATED=1 else echo "Notice: Migrator user '${MIGRATOR_NAME}' lacks usable access to '${DB_NAME}'." echo "Make corrections to the intended MIGRATOR user permissions to resolve." @@ -284,13 +205,16 @@ elif [[ "$SAME_MIGRATOR_AND_APP" == true ]]; then else echo "Ensuring app user '$APP_USER_NAME' exists for relevant hosts..." - APP_HOSTS_SEEN="" - for h in "$APP_USER_HOST" "$CLIENT_HOST"; do - [[ -z "$h" ]] && continue - if [[ " $APP_HOSTS_SEEN " == *" $h "* ]]; then - continue - fi - APP_HOSTS_SEEN+=" $h" +if [[ "$NEW_APP_USER_CREATED" -eq 1 ]]; then + echo "Granting privileges to new user..." + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON \`${DB_NAME}\`.* TO '${APP_USER_NAME}'@'${APP_USER_HOST}'; FLUSH PRIVILEGES;\"" + if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -e "GRANT SELECT, INSERT, UPDATE, ALTER, INDEX, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON \`${DB_NAME}\`.* TO '${APP_USER_NAME}'@'${APP_USER_HOST}'; FLUSH PRIVILEGES;"; then + echo "Error: Failed to grant privileges to new user." + exit 1 + fi +else + echo "Skipping privilege grant for app user: user already exists. Verifying privileges on '${DB_NAME}'..." +fi USER_EXISTS_HOST=$(mariadb -h "$DB_HOST" -P "$DB_PORT" \ -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ @@ -423,13 +347,24 @@ echo "New app user created: $([[ "$NEW_APP_USER_CREATED" -eq 1 ]] && echo True | echo "--------------------------------------------------" # Run the base_specify_migration script +echo "Running base_specify_migration..." + if [[ "$NEW_DATABASE_CREATED" -eq 0 ]]; then - echo "Existing database detected." + set +e ve/bin/python manage.py base_specify_migration --use-override --database=${MIGRATION_DB_ALIAS} + BASE_MIGRATION_EXIT_CODE=$? + set -e else - echo "New database detected." + set +e ve/bin/python manage.py base_specify_migration --database=${MIGRATION_DB_ALIAS} + BASE_MIGRATION_EXIT_CODE=$? + set -e +fi + +if [[ $BASE_MIGRATION_EXIT_CODE -ne 0 ]]; then + echo "Error: base_specify_migration failed (exit code $BASE_MIGRATION_EXIT_CODE). Aborting." + exit 1 fi -# Run Django migrations -ve/bin/python manage.py migrate --database=${MIGRATION_DB_ALIAS} +echo "Running Django migrations..." +ve/bin/python manage.py migrate --database=${MIGRATION_DB_ALIAS} \ No newline at end of file From 7a19f7f80ef66d6579d672a071702262fe186d4c Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 1 Jun 2026 15:43:11 +0200 Subject: [PATCH 03/27] Fix: Validate master credentials explicitly in the required-env checkt --- sp7_db_setup_check.sh | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 1833315a404..225de02bbeb 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -5,13 +5,8 @@ echo "Starting MariaDB database and user creation script..." DB_HOST="${DATABASE_HOST}" DB_PORT="${DATABASE_PORT}" - -DB_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" - -MASTER_USER_NAME="${MASTER_NAME:-root}" -MASTER_USER_PASSWORD="${MASTER_PASSWORD:-$DB_ROOT_PASSWORD}" -MASTER_USER_HOST="${MASTER_HOST}" - +MASTER_USER_NAME="${MASTER_NAME:-${MASTER_USER_NAME:-}}" +MASTER_USER_PASSWORD="${MASTER_PASSWORD:-${MASTER_USER_PASSWORD:-}}" MIGRATOR_NAME="${MIGRATOR_NAME}" MIGRATOR_PASSWORD="${MIGRATOR_PASSWORD}" MIGRATOR_USER_HOST="${MIGRATOR_HOST}" @@ -48,8 +43,8 @@ if [[ -z "$APP_USER_NAME" ]]; then fi # Validate required variables -if [[ -z "$DB_HOST" || -z "$DB_PORT" || -z "$DB_NAME" ]]; then - echo "Error: DB_HOST, DB_PORT, and DB_NAME must be set." +if [[ -z "$DB_HOST" || -z "$DB_PORT" || -z "$MASTER_USER_NAME" || -z "$MASTER_USER_PASSWORD" || -z "$MIGRATOR_PASSWORD" || -z "$DB_NAME" || -z "$APP_USER_NAME" || -z "$APP_USER_PASSWORD" ]]; then + echo "Error: One or more required environment variables are missing or empty." exit 1 fi @@ -367,4 +362,4 @@ if [[ $BASE_MIGRATION_EXIT_CODE -ne 0 ]]; then fi echo "Running Django migrations..." -ve/bin/python manage.py migrate --database=${MIGRATION_DB_ALIAS} \ No newline at end of file +ve/bin/python manage.py migrate --database=${MIGRATION_DB_ALIAS} From efd66caf77bd0bfb55cbb033b2d20e09e62d36da Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 1 Jun 2026 15:53:27 +0200 Subject: [PATCH 04/27] Fix: Require migration-capable privileges --- sp7_db_setup_check.sh | 222 +++++++++++++++++++----------------------- 1 file changed, 98 insertions(+), 124 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 225de02bbeb..5bb287f8154 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -64,15 +64,59 @@ if [[ "$MIGRATOR_NAME" == "$MASTER_USER_NAME" && "$MIGRATOR_PASSWORD" == "$MASTE SAME_MASTER_AND_MIGRATOR=true fi -SAME_MASTER_AND_APP=false -if [[ "$APP_USER_NAME" == "$MASTER_USER_NAME" && "$APP_USER_PASSWORD" == "$MASTER_USER_PASSWORD" ]]; then - SAME_MASTER_AND_APP=true -fi +has_all_privs_in_line() { + local line="$1" + shift + local missing=() + local p + for p in "$@"; do + # match whole words; spaces already canonicalized + if ! grep -qiE "(^|[, ])${p}(,| |$)" <<<"$line"; then + missing+=("$p") + fi + done + if [[ ${#missing[@]} -eq 0 ]]; then + return 0 + else + return 1 + fi +} -SAME_MIGRATOR_AND_APP=false -if [[ "$APP_USER_NAME" == "$MIGRATOR_NAME" && "$APP_USER_PASSWORD" == "$MIGRATOR_PASSWORD" ]]; then - SAME_MIGRATOR_AND_APP=true -fi +grant_line_has_required_privs() { + local line="$1" + shift + + if grep -qiE "^GRANT .*ALL PRIVILEGES.* ON \*\.\* TO " <<<"$line"; then + return 0 + fi + + if grep -qiE "^GRANT .*ALL PRIVILEGES.* ON (${SQL_DB_IDENTIFIER_REGEX}|${DB_NAME_REGEX})\.\* TO " <<<"$line"; then + return 0 + fi + + if grep -qiE " ON \*\.\* TO " <<<"$line" && has_all_privs_in_line "$line" "$@"; then + return 0 + fi + + if grep -qiE " ON (${SQL_DB_IDENTIFIER_REGEX}|${DB_NAME_REGEX})\.\* TO " <<<"$line" && has_all_privs_in_line "$line" "$@"; then + return 0 + fi + + return 1 +} + +SQL_DB_NAME=$(sql_string_literal "$DB_NAME") +SQL_DB_IDENTIFIER=$(sql_identifier "$DB_NAME") +SQL_MIGRATOR_NAME=$(sql_string_literal "$MIGRATOR_NAME") +SQL_MIGRATOR_PASSWORD=$(sql_string_literal "$MIGRATOR_PASSWORD") +SQL_MIGRATOR_USER_HOST=$(sql_string_literal "$MIGRATOR_USER_HOST") +SQL_APP_USER_NAME=$(sql_string_literal "$APP_USER_NAME") +SQL_APP_USER_PASSWORD=$(sql_string_literal "$APP_USER_PASSWORD") +SQL_APP_USER_HOST=$(sql_string_literal "$APP_USER_HOST") +DB_NAME_REGEX=$(regex_escape "$DB_NAME") +SQL_DB_IDENTIFIER_REGEX=$(regex_escape "$SQL_DB_IDENTIFIER") +MIGRATION_REQUIRED_PRIVS=("SELECT" "INSERT" "UPDATE" "DELETE" "CREATE" "ALTER" "INDEX" "DROP") +APP_REQUIRED_PRIVS=("SELECT" "INSERT" "UPDATE" "ALTER" "INDEX" "DELETE" "CREATE TEMPORARY TABLES" "LOCK TABLES" "EXECUTE") echo "--------------------------------------------------" echo "DB Configuration:" @@ -161,7 +205,30 @@ GRANTS_OUTPUT="$(mysql -N -B -h "$DB_HOST" -P "$DB_PORT" \ -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ -e "SHOW GRANTS FOR '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}';" 2>/dev/null || true)" -GRANTS_PARSED="$(echo "$GRANTS_OUTPUT" | tr -s '[:space:]' ' ')" +if [[ -z "$GRANTS_OUTPUT" ]]; then + echo "Error: Could not retrieve grants for '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}'." + exit 1 +fi + +mapfile -t MIGRATOR_GRANTS_LINES < <(echo "$GRANTS_OUTPUT" | tr -s '[:space:]' ' ') + +migrator_has_required_permissions=false + +for g in "${MIGRATOR_GRANTS_LINES[@]}"; do + if grant_line_has_required_privs "$g" "${MIGRATION_REQUIRED_PRIVS[@]}"; then + migrator_has_required_permissions=true; break + fi +done + +if [[ "$migrator_has_required_permissions" == true ]]; then + echo "Verified: '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}' has migration privileges on '${DB_NAME}'." +else + echo "Error: '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}' lacks migration privileges on '${DB_NAME}'." + echo "Required for migrations (any one GRANT must include all of): ${MIGRATION_REQUIRED_PRIVS[*]}" + echo "Grants found:" + echo "$GRANTS_OUTPUT" + exit 1 +fi # Create app user if it doesn't exist USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ @@ -211,127 +278,34 @@ else echo "Skipping privilege grant for app user: user already exists. Verifying privileges on '${DB_NAME}'..." fi - USER_EXISTS_HOST=$(mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -sse "SELECT COUNT(*) FROM mysql.user WHERE user = '$APP_USER_NAME' AND host = '$h';") - - if [[ "$USER_EXISTS_HOST" -eq 0 ]]; then - echo "Creating user '${APP_USER_NAME}'@'${h}'..." - echo "Executing: CREATE USER '${APP_USER_NAME}'@'${h}' IDENTIFIED BY ''" - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "CREATE USER '${APP_USER_NAME}'@'${h}' IDENTIFIED BY '${APP_USER_PASSWORD}';"; then - echo "Error: Failed to create app user '${APP_USER_NAME}'@'${h}'." - exit 1 - fi - NEW_APP_USER_CREATED=1 - else - echo "App user '${APP_USER_NAME}'@'${h}' already exists." - fi - done +APP_GRANTS_RAW="$(mysql -N -B -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ + -e "SHOW GRANTS FOR ${SQL_APP_USER_NAME}@${SQL_APP_USER_HOST};" 2>/dev/null || true)" - echo "Existing hosts for '$APP_USER_NAME' in mysql.user:" - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SELECT CONCAT(\"'\", user, \"'@'\", host, \"'\") FROM mysql.user WHERE user = '$APP_USER_NAME';"; then - echo "Warning: Could not list existing hosts for '$APP_USER_NAME'." - fi - - REQUIRED_PRIVS=("SELECT" "INSERT" "UPDATE" "ALTER" "INDEX" "DELETE" "CREATE TEMPORARY TABLES" "LOCK TABLES" "EXECUTE") - - echo "Granting required privileges to app user '$APP_USER_NAME' for relevant hosts..." - APP_GRANT_HOSTS_SEEN="" - for h in "$APP_USER_HOST" "$CLIENT_HOST"; do - [[ -z "$h" ]] && continue - if [[ " $APP_GRANT_HOSTS_SEEN " == *" $h "* ]]; then - continue - fi - APP_GRANT_HOSTS_SEEN+=" $h" - - echo "Granting app privileges on \`${DB_NAME}\`.* to '${APP_USER_NAME}'@'${h}'..." - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "GRANT SELECT, INSERT, UPDATE, ALTER, INDEX, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON \`${DB_NAME}\`.* TO '${APP_USER_NAME}'@'${h}';"; then - echo "Error: Failed to grant privileges to app user '${APP_USER_NAME}'@'${h}'." - echo "--------------" - echo "GRANT SELECT, INSERT, UPDATE, ALTER, INDEX, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON \`${DB_NAME}\`.* TO '${APP_USER_NAME}'@'${h}';" - echo "--------------" - exit 1 - fi - done - - if ! mariadb -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "FLUSH PRIVILEGES;"; then - echo "Error: Failed to FLUSH PRIVILEGES for app user." - exit 1 - fi - - APP_GRANTS_RAW="$(mariadb -N -B -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SHOW GRANTS FOR '${APP_USER_NAME}'@'${APP_USER_HOST}';" 2>/dev/null || true)" - - if [[ -z "$APP_GRANTS_RAW" ]]; then - echo "Error: Could not retrieve grants for '${APP_USER_NAME}'@'${APP_USER_HOST}'." - echo "Check whether this user exists only with different hosts (e.g. 'localhost' instead of '$APP_USER_HOST')." - exit 1 - fi - - mapfile -t APP_GRANTS_LINES < <(printf '%s\n' "$APP_GRANTS_RAW" | sed 's/[[:space:]]\+/ /g') - - app_has_required_permissions=false +if [[ -z "$APP_GRANTS_RAW" ]]; then + echo "Error: Could not retrieve grants for '${APP_USER_NAME}'@'${APP_USER_HOST}'." + exit 1 +fi - has_all_privs_in_line() { - local line="$1" - local missing=() - for p in "${REQUIRED_PRIVS[@]}"; do - if ! grep -qiE "(^|[, ])${p}(,| |$)" <<<"$line"; then - missing+=("$p") - fi - done - [[ ${#missing[@]} -eq 0 ]] - } +mapfile -t APP_GRANTS_LINES < <(echo "$APP_GRANTS_RAW" | tr -s '[:space:]' ' ') - for g in "${APP_GRANTS_LINES[@]}"; do - if grep -qiE "^GRANT .*ALL PRIVILEGES.* ON \*\.\* TO " <<<"$g"; then - app_has_required_permissions=true; break - fi - if grep -qiE " ON \*\.\* TO " <<<"$g" && has_all_privs_in_line "$g"; then - app_has_required_permissions=true; break - fi - if grep -qiE " ON (\`?${DB_NAME}\`?)\.\* TO " <<<"$g" && has_all_privs_in_line "$g"; then - app_has_required_permissions=true; break - fi - done +app_has_required_permissions=false - # Verify at CLIENT_HOST if different - if [[ "$app_has_required_permissions" != true && -n "$CLIENT_HOST" && "$CLIENT_HOST" != "$APP_USER_HOST" ]]; then - CLIENT_APP_GRANTS_RAW="$(mariadb -N -B -h "$DB_HOST" -P "$DB_PORT" \ - -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SHOW GRANTS FOR '${APP_USER_NAME}'@'${CLIENT_HOST}';" 2>/dev/null || true)" - mapfile -t CLIENT_APP_GRANTS_LINES < <(printf '%s\n' "$CLIENT_APP_GRANTS_RAW" | sed 's/[[:space:]]\+/ /g') - for g in "${CLIENT_APP_GRANTS_LINES[@]}"; do - if grep -qiE "^GRANT .*ALL PRIVILEGES.* ON \*\.\* TO " <<<"$g"; then - app_has_required_permissions=true; break - fi - if grep -qiE " ON \*\.\* TO " <<<"$g" && has_all_privs_in_line "$g"; then - app_has_required_permissions=true; break - fi - if grep -qiE " ON (\`?${DB_NAME}\`?)\.\* TO " <<<"$g" && has_all_privs_in_line "$g"; then - app_has_required_permissions=true; break - fi - done +# Evaluate each grant line +for g in "${APP_GRANTS_LINES[@]}"; do + if grant_line_has_required_privs "$g" "${APP_REQUIRED_PRIVS[@]}"; then + app_has_required_permissions=true; break fi +done - if [[ "$app_has_required_permissions" == true ]]; then - echo "Verified: app user '${APP_USER_NAME}' has required privileges on '${DB_NAME}'." - else - echo "Error: '${APP_USER_NAME}' lacks required privileges on '${DB_NAME}'." - echo "Required (any one GRANT must include all of): ${REQUIRED_PRIVS[*]}" - echo "Grants found (APP_USER_HOST):" - echo "$APP_GRANTS_RAW" - APP_USER_NAME="$MIGRATOR_NAME" - APP_USER_PASSWORD="$MIGRATOR_PASSWORD" - fi +if [[ "$app_has_required_permissions" == true ]]; then + echo "Verified: '${APP_USER_NAME}'@'${APP_USER_HOST}' has required privileges on '${DB_NAME}'." +else + echo "Error: '${APP_USER_NAME}'@'${APP_USER_HOST}' lacks required privileges on '${DB_NAME}'." + echo "Required (any one GRANT must include all of): ${APP_REQUIRED_PRIVS[*]}" + echo "Grants found:" + echo "$APP_GRANTS_RAW" + APP_USER_NAME="$MIGRATOR_NAME" + APP_USER_PASSWORD="$MIGRATOR_PASSWORD" fi echo "--------------------------------------------------" From 47cc4c80768247b5079b79ed9f89298b01043f21 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 1 Jun 2026 15:55:12 +0200 Subject: [PATCH 05/27] Fix: Validate DJANGO_DB_ALIAS before indexing DATABASES --- specifyweb/settings/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/specifyweb/settings/__init__.py b/specifyweb/settings/__init__.py index 990c5ed8ce0..cb04dbccf38 100644 --- a/specifyweb/settings/__init__.py +++ b/specifyweb/settings/__init__.py @@ -83,6 +83,12 @@ } DB_ALIAS = os.getenv("DJANGO_DB_ALIAS", "default") # Might want to set to "app" in the future +if DB_ALIAS not in DATABASES: + valid_aliases = ", ".join(sorted(DATABASES)) + raise ValueError( + f"Invalid DJANGO_DB_ALIAS '{DB_ALIAS}'. " + f"Expected one of: {valid_aliases}." + ) if DB_ALIAS != "default": from copy import deepcopy DATABASES['default'] = deepcopy(DATABASES[DB_ALIAS]) From f78c9e12962db802a20caa73fba621891b3b2d8d Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 1 Jun 2026 15:57:33 +0200 Subject: [PATCH 06/27] Fix: Read the DB role credentials from the environment --- specifyweb/settings/specify_settings.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/specifyweb/settings/specify_settings.py b/specifyweb/settings/specify_settings.py index 127874067f4..f780068ea0f 100644 --- a/specifyweb/settings/specify_settings.py +++ b/specifyweb/settings/specify_settings.py @@ -55,21 +55,15 @@ # https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-OPTIONS DATABASE_OPTIONS = {} -# The master user login. This is the MySQL user used to connect to the -# database. This can be the same as the Specify 6 master user. -MASTER_NAME = 'MasterUser' -MASTER_PASSWORD = 'MasterPassword' -MIGRATOR_NAME = 'MasterUser' -MIGRATOR_PASSWORD = 'MasterPassword' -APP_USER_NAME = 'MasterUser' -APP_USER_PASSWORD = 'MasterPassword' - -# MASTER_NAME = os.environ.get('MASTER_NAME', 'root') -# MASTER_PASSWORD = os.environ.get('MASTER_NAME', 'password') -# MIGRATOR_NAME = os.environ.get('MIGRATOR_NAME', MASTER_NAME) -# MIGRATOR_PASSWORD = os.environ.get('MIGRATOR_PASSWORD', MASTER_PASSWORD) -# APP_USER_NAME = os.environ.get('APP_USER_NAME', MIGRATOR_NAME) -# APP_USER_PASSWORD = os.environ.get('APP_USER_PASSWORD', MIGRATOR_PASSWORD) +# The master, migrator, and application user logins. The migrator and app +# credentials fall back through the more privileged roles for compatibility. +ROOT_PASSWORD = os.getenv('MYSQL_ROOT_PASSWORD', 'password') +MASTER_NAME = os.getenv('MASTER_NAME', 'root') +MASTER_PASSWORD = os.getenv('MASTER_PASSWORD', ROOT_PASSWORD) +MIGRATOR_NAME = os.getenv('MIGRATOR_NAME', MASTER_NAME) +MIGRATOR_PASSWORD = os.getenv('MIGRATOR_PASSWORD', MASTER_PASSWORD) +APP_USER_NAME = os.getenv('APP_USER_NAME', MIGRATOR_NAME) +APP_USER_PASSWORD = os.getenv('APP_USER_PASSWORD', MIGRATOR_PASSWORD) # The Specify web attachment server URL. WEB_ATTACHMENT_URL = None From 7d9e8c9665475693b1d85858d0ba19a3a80b4154 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 1 Jun 2026 16:01:46 +0200 Subject: [PATCH 07/27] Fix: Trigger fallback mechanism --- .../specify/management/commands/base_specify_migration.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/specifyweb/specify/management/commands/base_specify_migration.py b/specifyweb/specify/management/commands/base_specify_migration.py index 7d8d57c176f..8134218171f 100644 --- a/specifyweb/specify/management/commands/base_specify_migration.py +++ b/specifyweb/specify/management/commands/base_specify_migration.py @@ -25,14 +25,16 @@ def add_arguments(self, parser): def handle(self, *args, **options): use_override = bool(options.get('use_override', False)) alias = options["database"] - conn = connections[alias] logger.info(f"Running base_specify_migration using database alias '{alias}'") + # Validate the alias exists and is usable; fallback to 'master' if not try: - transaction.atomic(using=alias) - except: + connections[alias].ensure_connection() + except Exception: alias = 'master' + logger.warning(f"Falling back to database alias '{alias}'") + conn = connections[alias] with transaction.atomic(using=alias): with conn.cursor() as cursor: # Check django table From 926fd439d3db632b8ef38ac3d663e71d65f14f27 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Tue, 2 Jun 2026 12:27:15 +0200 Subject: [PATCH 08/27] Fix: Fail entrypoint fast when ./sp7_db_setup_check.sh fails --- docker-entrypoint.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 4ccd59d7bdb..5cdd9251f8b 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -9,11 +9,9 @@ if [ "$1" = 've/bin/gunicorn' ] || [ "$1" = 've/bin/python' ]; then rsync -a --delete specifyweb/frontend/static/ /volumes/static-files/frontend-static cd /opt/specify7 echo "Applying Django migrations." - set +e ./sp7_db_setup_check.sh # Setup db users and run migrations # ve/bin/python manage.py base_specify_migration # ve/bin/python manage.py migrate - ve/bin/python manage.py run_key_migration_functions # Uncomment if you want the key migration functions to run on startup. - set -e + # ve/bin/python manage.py run_key_migration_functions # Uncomment if you want the key migration functions to run on startup. fi exec "$@" From a9fe052c18c57cc423ac0a21bf9037402f9754c1 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Tue, 2 Jun 2026 12:30:13 +0200 Subject: [PATCH 09/27] Fix: Use override for startup migration flow --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 863c2b158da..ae4dcc733c7 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ pip_requirements: $(PIP) install --upgrade -r requirements.txt django_migrations: - $(PYTHON) manage.py base_specify_migration --database=migrations + $(PYTHON) manage.py base_specify_migration --use-override --database=migrations $(PYTHON) manage.py migrate --database=migrations specifyweb/settings/build_version.py: .FORCE From 1e5737c8206fdebbb46c0e565b999f2c8a1ad0c7 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Tue, 2 Jun 2026 12:32:43 +0200 Subject: [PATCH 10/27] Fix: Fix set -u --- sp7_db_setup_check.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 5bb287f8154..3bf6e134261 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -7,15 +7,14 @@ DB_HOST="${DATABASE_HOST}" DB_PORT="${DATABASE_PORT}" MASTER_USER_NAME="${MASTER_NAME:-${MASTER_USER_NAME:-}}" MASTER_USER_PASSWORD="${MASTER_PASSWORD:-${MASTER_USER_PASSWORD:-}}" -MIGRATOR_NAME="${MIGRATOR_NAME}" -MIGRATOR_PASSWORD="${MIGRATOR_PASSWORD}" -MIGRATOR_USER_HOST="${MIGRATOR_HOST}" - +MIGRATOR_NAME="${MIGRATOR_NAME:-}" +MIGRATOR_PASSWORD="${MIGRATOR_PASSWORD:-}" +MIGRATOR_USER_HOST="%" +DB_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" DB_NAME="${DATABASE_NAME}" - -APP_USER_NAME="${APP_USER_NAME}" -APP_USER_PASSWORD="${APP_USER_PASSWORD}" -APP_USER_HOST="${APP_HOST}" +APP_USER_NAME="${APP_USER_NAME:-}" +APP_USER_PASSWORD="${APP_USER_PASSWORD:-}" +APP_USER_HOST="%" MASTER_USER_HOST="${MASTER_USER_HOST:-%}" MIGRATOR_USER_HOST="${MIGRATOR_USER_HOST:-%}" From 841bf2921a26af545d8acfabfadf9550fa939fbe Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Tue, 2 Jun 2026 12:36:15 +0200 Subject: [PATCH 11/27] Fix: Remove schema-changing privileges --- sp7_db_setup_check.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 3bf6e134261..6861ae0f533 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -115,7 +115,7 @@ SQL_APP_USER_HOST=$(sql_string_literal "$APP_USER_HOST") DB_NAME_REGEX=$(regex_escape "$DB_NAME") SQL_DB_IDENTIFIER_REGEX=$(regex_escape "$SQL_DB_IDENTIFIER") MIGRATION_REQUIRED_PRIVS=("SELECT" "INSERT" "UPDATE" "DELETE" "CREATE" "ALTER" "INDEX" "DROP") -APP_REQUIRED_PRIVS=("SELECT" "INSERT" "UPDATE" "ALTER" "INDEX" "DELETE" "CREATE TEMPORARY TABLES" "LOCK TABLES" "EXECUTE") +APP_REQUIRED_PRIVS=("SELECT" "INSERT" "UPDATE" "DELETE" "CREATE TEMPORARY TABLES" "LOCK TABLES" "EXECUTE") echo "--------------------------------------------------" echo "DB Configuration:" @@ -268,8 +268,8 @@ else if [[ "$NEW_APP_USER_CREATED" -eq 1 ]]; then echo "Granting privileges to new user..." - echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON \`${DB_NAME}\`.* TO '${APP_USER_NAME}'@'${APP_USER_HOST}'; FLUSH PRIVILEGES;\"" - if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -e "GRANT SELECT, INSERT, UPDATE, ALTER, INDEX, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON \`${DB_NAME}\`.* TO '${APP_USER_NAME}'@'${APP_USER_HOST}'; FLUSH PRIVILEGES;"; then + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT SELECT, INSERT, UPDATE, ALTER, INDEX, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON ${SQL_DB_IDENTIFIER}.* TO ${SQL_APP_USER_NAME}@${SQL_APP_USER_HOST}; FLUSH PRIVILEGES;\"" + if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -e "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON ${SQL_DB_IDENTIFIER}.* TO ${SQL_APP_USER_NAME}@${SQL_APP_USER_HOST}; FLUSH PRIVILEGES;"; then echo "Error: Failed to grant privileges to new user." exit 1 fi From 4ad9becd70fddac28dbe0ce6a9363eeaf4c3bd3c Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Tue, 2 Jun 2026 12:41:26 +0200 Subject: [PATCH 12/27] Fix: Check for sp6 tables only when it's not a new db --- specifyweb/backend/permissions/initialize.py | 54 +++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/specifyweb/backend/permissions/initialize.py b/specifyweb/backend/permissions/initialize.py index a9cf2c62d5b..6447d1a13ad 100644 --- a/specifyweb/backend/permissions/initialize.py +++ b/specifyweb/backend/permissions/initialize.py @@ -32,12 +32,17 @@ def is_sp6_user_permissions_migrated(user, apps=apps) -> bool: return UserRole.objects.filter(specifyuser=user).exists() or \ UserPolicy.objects.filter(specifyuser=user).exists() -def initialize( - wipe: bool = False, - apps=apps, - *, - migrate_sp6_users: bool = True, -) -> None: +def has_sp6_permissions_tables() -> bool: + with connection.cursor() as cursor: + cursor.execute(""" + SELECT COUNT(*) + FROM information_schema.tables + WHERE table_name IN ('specifyuser_spprincipal', 'spuserrole') + AND table_schema = DATABASE(); + """) + return cursor.fetchone()[0] == 2 + +def initialize(wipe: bool=False, apps=apps) -> None: with transaction.atomic(): if wipe: wipe_permissions(apps) @@ -91,15 +96,10 @@ def assign_users_to_roles(apps=apps) -> None: } results = [] + if not has_sp6_permissions_tables(): + return # Newly created sp7 databases don't have these sp6 specific tables. + with connection.cursor() as cursor: - cursor.execute(""" - SELECT COUNT(*) - FROM information_schema.tables - WHERE table_name IN ('specifyuser_spprincipal', 'spuserrole') - AND table_schema = DATABASE(); - """) - if cursor.fetchone()[0] < 2: - return # Newly created sp7 databases don't have these sp6 specific tables. cursor.execute(""" SELECT u.SpecifyUserID as user_id, @@ -165,7 +165,6 @@ def assign_users_to_roles_during_testing(apps=apps) -> None: Specifyuser = apps.get_model('specify', 'Specifyuser') Agent = apps.get_model('specify', 'Agent') - cursor = connection.cursor() for user in Specifyuser.objects.all(): for collection in Collection.objects.all(): if user.usertype == 'Manager': @@ -175,16 +174,21 @@ def assign_users_to_roles_during_testing(apps=apps) -> None: if user.usertype in ('LimitedAccess', 'Guest'): user.roles.create(role=Role.objects.get(collection=collection, name="Read Only - Legacy")) - for colid, _ in users_collections_for_sp6(cursor, user.id): - # Does the user has an agent for the collection? - if Agent.objects.filter(specifyuser=user, division__disciplines__collections__id=colid).exists(): - # Give them access to the collection. - UserPolicy.objects.create( - collection_id=colid, - specifyuser_id=user.id, - resource=CollectionAccessPT.access.resource(), - action=CollectionAccessPT.access.action(), - ) + if not has_sp6_permissions_tables(): + return + + with connection.cursor() as cursor: + for user in Specifyuser.objects.all(): + for colid, _ in users_collections_for_sp6(cursor, user.id): + # Does the user has an agent for the collection? + if Agent.objects.filter(specifyuser=user, division__disciplines__collections__id=colid).exists(): + # Give them access to the collection. + UserPolicy.objects.create( + collection_id=colid, + specifyuser_id=user.id, + resource=CollectionAccessPT.access.resource(), + action=CollectionAccessPT.access.action(), + ) def create_roles(apps = apps) -> None: LibraryRole = apps.get_model('permissions', 'LibraryRole') From e9f3f4018d503c8c10d3704161093919a71d2ba9 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Tue, 2 Jun 2026 12:44:25 +0200 Subject: [PATCH 13/27] Fix: Remove useless try/except --- .../management/commands/base_specify_migration.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/specifyweb/specify/management/commands/base_specify_migration.py b/specifyweb/specify/management/commands/base_specify_migration.py index 8134218171f..f15af436992 100644 --- a/specifyweb/specify/management/commands/base_specify_migration.py +++ b/specifyweb/specify/management/commands/base_specify_migration.py @@ -37,18 +37,7 @@ def handle(self, *args, **options): conn = connections[alias] with transaction.atomic(using=alias): with conn.cursor() as cursor: - # Check django table - try: - cursor.execute(""" - SELECT 1 - FROM django_migrations - LIMIT 1; - """) - exists = True - except: - exists = False - - if not exists: + if "django_migrations" not in conn.introspection.table_names(cursor): # Check if the django_migrations table exists and create it if it doesn't cursor.execute(""" CREATE TABLE IF NOT EXISTS `django_migrations` ( From 999cf164bd9b341204cd8e145b75d6ff113756b0 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 13:14:59 +0200 Subject: [PATCH 14/27] Fix: Closed Fi before the grant block starts --- sp7_db_setup_check.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 6861ae0f533..b249b31cc07 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -191,6 +191,9 @@ if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then echo "Error: Failed to create user." exit 1 fi +else + echo "User '$MIGRATOR_NAME' already exists." +fi if [[ "$NEW_MIGRATOR_USER_CREATED" -eq 1 ]]; then echo "Granting privileges to new user..." @@ -199,6 +202,9 @@ if [[ "$NEW_MIGRATOR_USER_CREATED" -eq 1 ]]; then echo "Error: Failed to grant privileges to new user." exit 1 fi +else + echo "Skipping privilege grant for migrator user: user already exists. Verifying privileges on '${DB_NAME}'..." +fi GRANTS_OUTPUT="$(mysql -N -B -h "$DB_HOST" -P "$DB_PORT" \ -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ @@ -306,6 +312,7 @@ else APP_USER_NAME="$MIGRATOR_NAME" APP_USER_PASSWORD="$MIGRATOR_PASSWORD" fi +fi echo "--------------------------------------------------" echo "Database and user setup complete." From eb7c2e4ea6302516fdc1a6531d8e95b13ba8df96 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 13:16:23 +0200 Subject: [PATCH 15/27] Fix: Fix log message --- sp7_db_setup_check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index b249b31cc07..467ccf8c3ef 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -274,7 +274,7 @@ else if [[ "$NEW_APP_USER_CREATED" -eq 1 ]]; then echo "Granting privileges to new user..." - echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT SELECT, INSERT, UPDATE, ALTER, INDEX, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON ${SQL_DB_IDENTIFIER}.* TO ${SQL_APP_USER_NAME}@${SQL_APP_USER_HOST}; FLUSH PRIVILEGES;\"" + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON ${SQL_DB_IDENTIFIER}.* TO ${SQL_APP_USER_NAME}@${SQL_APP_USER_HOST}; FLUSH PRIVILEGES;\"" if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -e "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE ON ${SQL_DB_IDENTIFIER}.* TO ${SQL_APP_USER_NAME}@${SQL_APP_USER_HOST}; FLUSH PRIVILEGES;"; then echo "Error: Failed to grant privileges to new user." exit 1 From 08ee353599cc6f79049662fba4b3ba08a72be034 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 13:18:08 +0200 Subject: [PATCH 16/27] Fix: Materialize scopes before computing final_discipline --- specifyweb/backend/businessrules/uniqueness_rules.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specifyweb/backend/businessrules/uniqueness_rules.py b/specifyweb/backend/businessrules/uniqueness_rules.py index 34ec4af7857..9cd79b4c50c 100644 --- a/specifyweb/backend/businessrules/uniqueness_rules.py +++ b/specifyweb/backend/businessrules/uniqueness_rules.py @@ -395,15 +395,15 @@ def create_uniqueness_rule(model_name: str, discipline, is_database_constraint: UniquenessRuleField = registry.get_model( 'businessrules', 'UniquenessRuleField') if registry else models.UniquenessRuleField + fields = list(fields) + scopes = list(scopes) + final_discipline = None if rule_is_global(scopes) else discipline candidate_rules = UniquenessRule.objects.filter(modelName=model_name, isDatabaseConstraint=is_database_constraint, discipline=final_discipline) - fields = list(fields) - scopes = list(scopes) - for rule in candidate_rules: # If the rule already exists, skip creating the rule if _rule_fields_match( From 05868603f1b84a814c9052efe27b3a67fc04b02a Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 13:22:36 +0200 Subject: [PATCH 17/27] Fix: Fix import --- specifyweb/backend/permissions/initialize.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/specifyweb/backend/permissions/initialize.py b/specifyweb/backend/permissions/initialize.py index 6447d1a13ad..74a79ff01f6 100644 --- a/specifyweb/backend/permissions/initialize.py +++ b/specifyweb/backend/permissions/initialize.py @@ -48,11 +48,10 @@ def initialize(wipe: bool=False, apps=apps) -> None: wipe_permissions(apps) create_admins(apps) create_roles(apps) - if migrate_sp6_users: - if 'test' in ''.join(sys.argv): - assign_users_to_roles_during_testing(apps) - else: - assign_users_to_roles(apps) + if 'test' in ''.join(sys.argv): + assign_users_to_roles_during_testing(apps) + else: + assign_users_to_roles(apps) def create_admins(apps=apps) -> None: UserPolicy = apps.get_model('permissions', 'UserPolicy') @@ -552,4 +551,4 @@ def create_roles(apps = apps) -> None: ) if is_new: for lp in collection_admin.policies.all(): - ca.policies.get_or_create(resource=lp.resource, action=lp.action) + ca.policies.get_or_create(resource=lp.resource, action=lp.action) \ No newline at end of file From 0361cc39487c5bff8aca1ab6f10ee9d33047b86b Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 13:40:18 +0200 Subject: [PATCH 18/27] fix: add helper fct --- sp7_db_setup_check.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 467ccf8c3ef..d9d3b3455c2 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -63,6 +63,23 @@ if [[ "$MIGRATOR_NAME" == "$MASTER_USER_NAME" && "$MIGRATOR_PASSWORD" == "$MASTE SAME_MASTER_AND_MIGRATOR=true fi +sql_string_literal() { + local value="$1" + value=$(printf '%s' "$value" | sed -e 's/\\/\\\\/g' -e "s/'/''/g") + printf "'%s'" "$value" +} + +sql_identifier() { + local value="$1" + value=$(printf '%s' "$value" | sed -e 's/`/``/g') + printf "\`%s\`" "$value" +} + +regex_escape() { + local value="$1" + printf '%s' "$value" | sed -e 's/[][\/.^$*+?{}()|\\]/\\&/g' +} + has_all_privs_in_line() { local line="$1" shift From 7277f66a100d1ee2f8fc87e27001bc4c44ecc19a Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 13:43:51 +0200 Subject: [PATCH 19/27] fix: Fix syntax in setup_check --- sp7_db_setup_check.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index d9d3b3455c2..85684f38762 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -59,9 +59,17 @@ fi # Relationship flags between the three users SAME_MASTER_AND_MIGRATOR=false +SAME_MASTER_AND_APP=false +SAME_MIGRATOR_AND_APP=false if [[ "$MIGRATOR_NAME" == "$MASTER_USER_NAME" && "$MIGRATOR_PASSWORD" == "$MASTER_USER_PASSWORD" ]]; then SAME_MASTER_AND_MIGRATOR=true fi +if [[ "$APP_USER_NAME" == "$MASTER_USER_NAME" && "$APP_USER_PASSWORD" == "$MASTER_USER_PASSWORD" ]]; then + SAME_MASTER_AND_APP=true +fi +if [[ "$APP_USER_NAME" == "$MIGRATOR_NAME" && "$APP_USER_PASSWORD" == "$MIGRATOR_PASSWORD" ]]; then + SAME_MIGRATOR_AND_APP=true +fi sql_string_literal() { local value="$1" From da2532b7de8a583eb788156913267f95484794ef Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 14:58:37 +0200 Subject: [PATCH 20/27] fix: Use SQL_MIGRATOR_* --- sp7_db_setup_check.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 85684f38762..ff1c8105320 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -204,13 +204,13 @@ fi # Create migrator user if it doesn't exist USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ -"SELECT COUNT(*) FROM mysql.user WHERE user = '$MIGRATOR_NAME' AND host = '$MIGRATOR_USER_HOST';") +"SELECT COUNT(*) FROM mysql.user WHERE user = $SQL_MIGRATOR_NAME AND host = $SQL_MIGRATOR_USER_HOST;") if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then echo "Creating migrator user '$MIGRATOR_NAME'..." echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE USER '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}' IDENTIFIED BY '';\"" if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "CREATE USER '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}' IDENTIFIED BY '${MIGRATOR_PASSWORD}';"; then + -e "CREATE USER $SQL_MIGRATOR_NAME@$SQL_MIGRATOR_USER_HOST IDENTIFIED BY $SQL_MIGRATOR_PASSWORD;"; then NEW_MIGRATOR_USER_CREATED=1 else echo "Error: Failed to create user." @@ -222,8 +222,8 @@ fi if [[ "$NEW_MIGRATOR_USER_CREATED" -eq 1 ]]; then echo "Granting privileges to new user..." - echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}'; FLUSH PRIVILEGES;\"" - if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -e "GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}'; FLUSH PRIVILEGES;"; then + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"GRANT ALL PRIVILEGES ON ${SQL_DB_IDENTIFIER}.* TO ${SQL_MIGRATOR_NAME}@${SQL_MIGRATOR_USER_HOST}; FLUSH PRIVILEGES;\"" + if ! mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -e "GRANT ALL PRIVILEGES ON ${SQL_DB_IDENTIFIER}.* TO ${SQL_MIGRATOR_NAME}@${SQL_MIGRATOR_USER_HOST}; FLUSH PRIVILEGES;"; then echo "Error: Failed to grant privileges to new user." exit 1 fi From b46fe0956526bf87a1dcc2f50ed5d380b349bfea Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 21:38:59 +0200 Subject: [PATCH 21/27] fix: Make SQL escaping --- sp7_db_setup_check.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index ff1c8105320..09ed495f4a0 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -262,13 +262,13 @@ fi # Create app user if it doesn't exist USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ -"SELECT COUNT(*) FROM mysql.user WHERE user = '$APP_USER_NAME' AND host = '$APP_USER_HOST';") +"SELECT COUNT(*) FROM mysql.user WHERE user = $SQL_APP_USER_NAME AND host = $SQL_APP_USER_HOST;") if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then echo "Creating app user '$APP_USER_NAME'..." echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE USER '${APP_USER_NAME}'@'${APP_USER_HOST}' IDENTIFIED BY '';\"" if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "CREATE USER '${APP_USER_NAME}'@'${APP_USER_HOST}' IDENTIFIED BY '${APP_USER_PASSWORD}';"; then + -e "CREATE USER $SQL_APP_USER_NAME@$SQL_APP_USER_HOST IDENTIFIED BY $SQL_APP_USER_PASSWORD;"; then NEW_APP_USER_CREATED=1 else echo "Notice: Migrator user '${MIGRATOR_NAME}' lacks usable access to '${DB_NAME}'." From 0b2e4b8a9f9bc23f4d040240eb55a7e24263d8d0 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Wed, 3 Jun 2026 21:41:46 +0200 Subject: [PATCH 22/27] fix: Use get_or_create() for idempotency to match production behavior --- specifyweb/backend/permissions/initialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/backend/permissions/initialize.py b/specifyweb/backend/permissions/initialize.py index 74a79ff01f6..28814171dd0 100644 --- a/specifyweb/backend/permissions/initialize.py +++ b/specifyweb/backend/permissions/initialize.py @@ -182,7 +182,7 @@ def assign_users_to_roles_during_testing(apps=apps) -> None: # Does the user has an agent for the collection? if Agent.objects.filter(specifyuser=user, division__disciplines__collections__id=colid).exists(): # Give them access to the collection. - UserPolicy.objects.create( + UserPolicy.objects.get_or_create( collection_id=colid, specifyuser_id=user.id, resource=CollectionAccessPT.access.resource(), From fa20860a945145f9e2eacc8581613c3e8c3baa61 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Fri, 5 Jun 2026 09:30:01 +0200 Subject: [PATCH 23/27] fix: do not fail on missing variable for test panel --- sp7_db_setup_check.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 09ed495f4a0..798dbdea762 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -10,7 +10,7 @@ MASTER_USER_PASSWORD="${MASTER_PASSWORD:-${MASTER_USER_PASSWORD:-}}" MIGRATOR_NAME="${MIGRATOR_NAME:-}" MIGRATOR_PASSWORD="${MIGRATOR_PASSWORD:-}" MIGRATOR_USER_HOST="%" -DB_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" +DB_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-}" DB_NAME="${DATABASE_NAME}" APP_USER_NAME="${APP_USER_NAME:-}" APP_USER_PASSWORD="${APP_USER_PASSWORD:-}" @@ -206,7 +206,7 @@ fi USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ "SELECT COUNT(*) FROM mysql.user WHERE user = $SQL_MIGRATOR_NAME AND host = $SQL_MIGRATOR_USER_HOST;") -if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then +if [[ "$USER_EXISTS" -eq 0 && "$MIGRATOR_NAME" != "root" ]]; then echo "Creating migrator user '$MIGRATOR_NAME'..." echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE USER '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}' IDENTIFIED BY '';\"" if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ @@ -264,7 +264,7 @@ fi USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ "SELECT COUNT(*) FROM mysql.user WHERE user = $SQL_APP_USER_NAME AND host = $SQL_APP_USER_HOST;") -if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then +if [[ "$USER_EXISTS" -eq 0 && "$MIGRATOR_NAME" != "root" ]]; then echo "Creating app user '$APP_USER_NAME'..." echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE USER '${APP_USER_NAME}'@'${APP_USER_HOST}' IDENTIFIED BY '';\"" if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ @@ -309,7 +309,7 @@ else fi APP_GRANTS_RAW="$(mysql -N -B -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SHOW GRANTS FOR ${SQL_APP_USER_NAME}@${SQL_APP_USER_HOST};" 2>/dev/null || true)" + -e "SHOW GRANTS FOR '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}';" 2>/dev/null || true)" if [[ -z "$APP_GRANTS_RAW" ]]; then echo "Error: Could not retrieve grants for '${APP_USER_NAME}'@'${APP_USER_HOST}'." From 756c6d3ffe65526d262886a9c1ed7138de0b621e Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Fri, 5 Jun 2026 12:53:49 +0200 Subject: [PATCH 24/27] fix: Fix wrong user name --- sp7_db_setup_check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 798dbdea762..4ec10ca560a 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -264,7 +264,7 @@ fi USER_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" -sse \ "SELECT COUNT(*) FROM mysql.user WHERE user = $SQL_APP_USER_NAME AND host = $SQL_APP_USER_HOST;") -if [[ "$USER_EXISTS" -eq 0 && "$MIGRATOR_NAME" != "root" ]]; then +if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then echo "Creating app user '$APP_USER_NAME'..." echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE USER '${APP_USER_NAME}'@'${APP_USER_HOST}' IDENTIFIED BY '';\"" if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ From d9c0ae7e0c8b9f0b751269d2b72302b049a06fda Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Fri, 5 Jun 2026 12:54:38 +0200 Subject: [PATCH 25/27] fix: Fix wrong user name --- sp7_db_setup_check.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 4ec10ca560a..1893a98a999 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -309,7 +309,7 @@ else fi APP_GRANTS_RAW="$(mysql -N -B -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "SHOW GRANTS FOR '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}';" 2>/dev/null || true)" + -e "SHOW GRANTS FOR '${APP_USER_NAME}'@'${APP_USER_HOST}';" 2>/dev/null || true)" if [[ -z "$APP_GRANTS_RAW" ]]; then echo "Error: Could not retrieve grants for '${APP_USER_NAME}'@'${APP_USER_HOST}'." From aaf0498fec9d166376a2435f47bbdd2cf39f4b10 Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Fri, 5 Jun 2026 12:57:24 +0200 Subject: [PATCH 26/27] fix: Improve error logs --- .../specify/management/commands/base_specify_migration.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specifyweb/specify/management/commands/base_specify_migration.py b/specifyweb/specify/management/commands/base_specify_migration.py index f15af436992..d1a05eb6d7f 100644 --- a/specifyweb/specify/management/commands/base_specify_migration.py +++ b/specifyweb/specify/management/commands/base_specify_migration.py @@ -28,11 +28,12 @@ def handle(self, *args, **options): logger.info(f"Running base_specify_migration using database alias '{alias}'") # Validate the alias exists and is usable; fallback to 'master' if not + original_alias = alias try: connections[alias].ensure_connection() - except Exception: + except Exception as e: alias = 'master' - logger.warning(f"Falling back to database alias '{alias}'") + logger.warning(f"Database alias '{original_alias}' unavailable ({e}), falling back to 'master'") conn = connections[alias] with transaction.atomic(using=alias): From 8c2ef7db5622be16f9b08107d1194017282f85ca Mon Sep 17 00:00:00 2001 From: CarolineDenis Date: Mon, 8 Jun 2026 14:45:12 +0200 Subject: [PATCH 27/27] fix: Fix host variable initialization order that prevents environment override --- sp7_db_setup_check.sh | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/sp7_db_setup_check.sh b/sp7_db_setup_check.sh index 1893a98a999..cfc60d1e4cd 100644 --- a/sp7_db_setup_check.sh +++ b/sp7_db_setup_check.sh @@ -9,12 +9,10 @@ MASTER_USER_NAME="${MASTER_NAME:-${MASTER_USER_NAME:-}}" MASTER_USER_PASSWORD="${MASTER_PASSWORD:-${MASTER_USER_PASSWORD:-}}" MIGRATOR_NAME="${MIGRATOR_NAME:-}" MIGRATOR_PASSWORD="${MIGRATOR_PASSWORD:-}" -MIGRATOR_USER_HOST="%" DB_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-}" DB_NAME="${DATABASE_NAME}" APP_USER_NAME="${APP_USER_NAME:-}" APP_USER_PASSWORD="${APP_USER_PASSWORD:-}" -APP_USER_HOST="%" MASTER_USER_HOST="${MASTER_USER_HOST:-%}" MIGRATOR_USER_HOST="${MIGRATOR_USER_HOST:-%}" @@ -190,9 +188,9 @@ DB_EXISTS=$(mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password= if [[ "$DB_EXISTS" -eq 0 ]]; then echo "Creating database '$DB_NAME'..." - echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE DATABASE \`$DB_NAME\`;\"" + echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"\" -e \"CREATE DATABASE ${SQL_DB_IDENTIFIER};\"" if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \ - -e "CREATE DATABASE \`$DB_NAME\`;"; then + -e "CREATE DATABASE ${SQL_DB_IDENTIFIER};"; then NEW_DATABASE_CREATED=1 else echo "Error: Failed to create database." @@ -271,12 +269,10 @@ if [[ "$USER_EXISTS" -eq 0 && "$APP_USER_NAME" != "root" ]]; then -e "CREATE USER $SQL_APP_USER_NAME@$SQL_APP_USER_HOST IDENTIFIED BY $SQL_APP_USER_PASSWORD;"; then NEW_APP_USER_CREATED=1 else - echo "Notice: Migrator user '${MIGRATOR_NAME}' lacks usable access to '${DB_NAME}'." - echo "Make corrections to the intended MIGRATOR user permissions to resolve." - echo " GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${MIGRATOR_NAME}'@'${MIGRATOR_USER_HOST}'; FLUSH PRIVILEGES;" - MIGRATOR_NAME="$MASTER_USER_NAME" - MIGRATOR_PASSWORD="$MASTER_USER_PASSWORD" - MIGRATION_DB_ALIAS="master" + echo "Error: Failed to create app user '${APP_USER_NAME}'@'${APP_USER_HOST}'." + echo "Falling back to migrator credentials for app user." + APP_USER_NAME="$MIGRATOR_NAME" + APP_USER_PASSWORD="$MIGRATOR_PASSWORD" fi fi