Skip to content

fix: improve DB setup script#8156

Open
CarolineDenis wants to merge 41 commits into
v7_12_0_7_basefrom
issue-8148-fixes-6121-after-review
Open

fix: improve DB setup script#8156
CarolineDenis wants to merge 41 commits into
v7_12_0_7_basefrom
issue-8148-fixes-6121-after-review

Conversation

@CarolineDenis

@CarolineDenis CarolineDenis commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #8148
Fixes #6121
Fixes #8107

Checklist

  • Self-review the PR after opening it to make sure the changes look good and
    self-explanatory (or properly documented)
  • Add relevant issue to release milestone
  • Add pr to documentation list
  • Add automated tests
  • Add a reverse migration if a migration is present in the PR
  • Add migration function to
    def fix_schema_config(stdout: WriteToStdOut | None = None):

Testing instructions

Summary by CodeRabbit

  • Bug Fixes

    • More reliable DB provisioning, privilege verification and clearer failure diagnostics.
    • Fixed single-use-iterable bug affecting uniqueness rule creation.
    • Improved legacy-permissions detection and safer role/user assignment during initialization.
  • New Features

    • Startup validates DB alias and logs connection fallbacks.
    • Migration initialization now respects override behavior; container startup can skip automatic migrations.
  • Chores

    • Credentials/config now come from environment variables with chained defaults.
    • Setup scripts run in stricter shell mode for safer execution.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 28670215-6c7c-475c-973e-7ca12274b363

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralize env-driven DB credentials, validate DB alias at import, harden sp7_db_setup_check.sh with strict mode and grant parsing/verification, conditionally create/grant users and run migrations, adjust docker/Make wiring, add SP6-permissions detection, and fix iterable consumption in uniqueness rules.

Changes

Database setup, permissions, and startup validation

Layer / File(s) Summary
Settings and environment-driven configuration
specifyweb/settings/specify_settings.py, specifyweb/settings/__init__.py
Credentials derive from chained environment fallbacks (ROOT→MASTER→MIGRATOR→APP); import-time DB alias validation raises on unknown alias.
Entrypoint and Makefile wiring
docker-entrypoint.sh, Makefile
Entrypoint now invokes only ./sp7_db_setup_check.sh in the gunicorn/python branch; Makefile django_migrations target adds --use-override for the migrations DB.
Database setup: strict mode and variable initialization
sp7_db_setup_check.sh (lines 2-46)
Enable set -euo pipefail; initialize master/migrator/app names/passwords/hosts with defaults; default APP user to migrator when unset; expand required-variable validation.
Database setup: privilege parsing and verification helpers
sp7_db_setup_check.sh (lines 60-142)
Add SQL literal/identifier escaping, regex-escape helpers, grant-line detection, and per-role required-privilege arrays to parse SHOW GRANTS output for verification.
Database setup: database existence and creation
sp7_db_setup_check.sh (lines 186-193)
Switch DB existence checks and CREATE DATABASE to mysql CLI against information_schema.schemata.
Database setup: migrator user provisioning and verification
sp7_db_setup_check.sh (lines 203-275)
Host-specific migrator existence check; conditionally CREATE USER; grant ALL PRIVILEGES only on creation; verify migrator privileges via parsed SHOW GRANTS, abort with diagnostics on failure.
Database setup: app user creation and privilege management
sp7_db_setup_check.sh (lines 296-335)
Grant fixed app privileges only when new app user created; verify privileges and fall back to migrator creds on verification failure.
Database setup: migration execution with conditional flags
sp7_db_setup_check.sh (lines 346-365)
Invoke base_specify_migration with --use-override only when DB was not newly created; capture exit code and abort before running Django migrate on non-zero exit.
Permissions system: SP6 legacy table detection and guarded assignment
specifyweb/backend/permissions/initialize.py (lines 35-190, 554)
Add has_sp6_permissions_tables() to detect legacy SP6 tables; early-return when absent; testing path uses with connection.cursor() and get_or_create for UserPolicy when Agent exists.
Management command: base_specify_migration connectivity and table detection
specifyweb/specify/management/commands/base_specify_migration.py (lines 28-40)
Ensure DB alias connection with ensure_connection() and fallback to 'master' if unusable; use Django introspection table_names() to detect django_migrations table.
Uniqueness rules: iterable materialization
specifyweb/backend/businessrules/uniqueness_rules.py (lines 398-406)
create_uniqueness_rule materializes fields and scopes into lists before computing final_discipline and querying existing rules.

Possibly related PRs

  • specify/specify7#8039: Changes to run_key_migration_functions and related migration pipeline overlap with the entrypoint's commented-out migration invocations.

Suggested reviewers

  • acwhite211
🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to django_migrations Make target, docker-entrypoint.sh, and base_specify_migration.py support the credential and database setup refactoring; changes to uniqueness_rules.py and permissions/initialize.py appear to address separate code quality/correctness issues unrelated to #6121/#8148 objectives. Remove unrelated changes to uniqueness_rules.py and permissions/initialize.py, or document their connection to the credential refactoring; alternatively, address them in a separate PR focused on those specific improvements.
Automatic Tests ⚠️ Warning PR modifies critical initialization code but unchecked 'Add automated tests' item and no test files were modified. Add automated tests for sp7_db_setup_check.sh, permissions initialization, settings validation, and base_specify_migration command changes.
Testing Instructions ⚠️ Warning PR contains significant database setup and migration changes but provides no specific testing instructions; PR template requires testing instructions to be filled in. Add step-by-step testing instructions covering database user creation, permission validation, credential handling, migration execution, environment variables, fallback mechanisms, and SP6→SP7 migration.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR successfully implements the core requirements from issues #8148 and #6121: environment-driven credential configuration, master/migrator/app user hierarchy, validation of required privileges, and fallback mechanisms for credential initialization.
Title check ✅ Passed The title is clear and related to the main DB setup/script changes, even if it doesn't capture every migration and settings update.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-8148-fixes-6121-after-review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
sp7_db_setup_check.sh (2)

236-250: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing fi for app user creation block and incorrect error message.

The if block starting at line 236 appears to be missing its closing fi before line 269. Additionally, lines 243-249 contain an error message about "Migrator user" lacking access, but this code path is within the block for app user creation failure, which is confusing.

Proposed fix
   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."
-    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}'."
+    exit 1
   fi
 fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sp7_db_setup_check.sh` around lines 236 - 250, The if-block that checks
USER_EXISTS and APP_USER_NAME (starting at the CREATE USER call) is missing its
closing fi and the error branch prints the wrong message about the "Migrator
user"; fix by inserting a closing fi after the else branch that handles CREATE
USER failure so the app-user block is properly terminated, and update the error
text inside that failure branch to reference the app user variables
(APP_USER_NAME, APP_USER_HOST, APP_USER_PASSWORD) and appropriate grant guidance
(or otherwise move the migrator-related reassignment of
MIGRATOR_NAME/MIGRATOR_PASSWORD/MIGRATION_DB_ALIAS out of the app-user block if
it belongs to a separate migrator-check) so the message and variable changes
align with the CREATE USER context.

256-264: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Undefined variables SAME_MASTER_AND_APP and SAME_MIGRATOR_AND_APP.

These variables are referenced but never defined. Only SAME_MASTER_AND_MIGRATOR is defined at lines 61-64. With set -u enabled, this will cause the script to abort with "unbound variable" errors.

Proposed fix - add variable definitions after line 64
 SAME_MASTER_AND_MIGRATOR=false
 if [[ "$MIGRATOR_NAME" == "$MASTER_USER_NAME" && "$MIGRATOR_PASSWORD" == "$MASTER_USER_PASSWORD" ]]; then
   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
+
+SAME_MIGRATOR_AND_APP=false
+if [[ "$APP_USER_NAME" == "$MIGRATOR_NAME" && "$APP_USER_PASSWORD" == "$MIGRATOR_PASSWORD" ]]; then
+  SAME_MIGRATOR_AND_APP=true
+fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sp7_db_setup_check.sh` around lines 256 - 264, The script references
undefined shell variables SAME_MASTER_AND_APP and SAME_MIGRATOR_AND_APP which
will trigger "unbound variable" under set -u; define and initialize these
variables (e.g., export or local assignments to false by default) in the same
section where SAME_MASTER_AND_MIGRATOR is set so they always exist, and set them
appropriately when comparing credentials (the SAME_MASTER_AND_APP and
SAME_MIGRATOR_AND_APP flags should mirror the boolean logic used for
SAME_MASTER_AND_MIGRATOR to indicate when app equals master or migrator).
specifyweb/backend/permissions/initialize.py (1)

45-55: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

migrate_sp6_users is undefined — causes NameError at runtime.

The parameter was removed from the function signature on line 45, but the condition on line 51 still references it. This matches the pipeline failure. Based on the pattern used in assign_users_to_roles() (line 99) and assign_users_to_roles_during_testing() (line 177), the condition should use the new helper instead.

🐛 Proposed fix
 def initialize(wipe: bool=False, apps=apps) -> None:
     with transaction.atomic():
         if wipe:
             wipe_permissions(apps)
         create_admins(apps)
         create_roles(apps)
-        if migrate_sp6_users:
+        if has_sp6_permissions_tables():
             if 'test' in ''.join(sys.argv):
                 assign_users_to_roles_during_testing(apps)
             else:
                 assign_users_to_roles(apps)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/backend/permissions/initialize.py` around lines 45 - 55, The
initialize function references an undefined variable migrate_sp6_users causing a
NameError; replace that conditional with a call to the new helper
should_migrate_sp6_users() inside initialize (the block that currently calls
wipe_permissions, create_admins, create_roles, and then decides between
assign_users_to_roles_during_testing and assign_users_to_roles). Update the
if-statement to use should_migrate_sp6_users() so the runtime check matches the
pattern used by assign_users_to_roles and assign_users_to_roles_during_testing.
🧹 Nitpick comments (1)
specifyweb/specify/management/commands/base_specify_migration.py (1)

31-35: ⚡ Quick win

Catch specific database exceptions instead of bare Exception.

Catching Exception (Ruff BLE001) is too broad and may mask unexpected errors. Use Django's specific database exceptions for clearer error handling.

Proposed fix
+from django.db.utils import OperationalError
+
 ...
 
         # Validate the alias exists and is usable; fallback to 'master' if not
         try:
             connections[alias].ensure_connection()
-        except Exception:
+        except (KeyError, OperationalError) as e:
             alias = 'master'
-            logger.warning(f"Falling back to database alias '{alias}'")
+            logger.warning(f"Falling back to database alias 'master': {e}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/specify/management/commands/base_specify_migration.py` around
lines 31 - 35, Replace the broad "except Exception" around
connections[alias].ensure_connection() with a specific Django DB exception (e.g.
catch django.db.DatabaseError or django.db.utils.OperationalError) and import
that exception at top of the module; keep the fallback to alias='master' and
logger.warning(...) unchanged so only database-related connection errors are
handled while other exceptions still propagate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sp7_db_setup_check.sh`:
- Around line 271-272: The echo log and the actual GRANT differ: the echo at the
mysql invocation shows "ALTER, INDEX" but the mysql -e GRANT command executed in
the if-statement omits those privileges; update one side to match the other —
either add ALTER and INDEX to the executed GRANT in the mysql -e string (the
command inside the if) or remove ALTER and INDEX from the echo message so the
logged command accurately reflects the executed GRANT; ensure you update the
SQL_PRIVILEGE list in the echo and the mysql -e GRANT for consistency with
symbols SQL_APP_USER_NAME, SQL_APP_USER_HOST, SQL_DB_IDENTIFIER,
MASTER_USER_NAME and MASTER_USER_PASSWORD referenced in the surrounding code.
- Around line 184-195: The if-block that checks USER_EXISTS and APP_USER_NAME
(creating the migrator user via mysql) is missing its closing fi; close that
conditional right after the else/exit 1 block so the subsequent if [[
"$NEW_MIGRATOR_USER_CREATED" -eq 1 ]] statement is not nested incorrectly.
Locate the block that contains the CREATE USER command (references: USER_EXISTS,
APP_USER_NAME, MIGRATOR_NAME, MIGRATOR_USER_HOST, MIGRATOR_PASSWORD,
NEW_MIGRATOR_USER_CREATED) and insert a single fi immediately after the existing
else/exit 1 end to properly terminate the first if.

In `@specifyweb/backend/businessrules/uniqueness_rules.py`:
- Around line 392-405: In create_uniqueness_rule, materialize the iterables
before using them: call scopes = list(scopes) (and fields = list(fields) if not
already) before invoking rule_is_global(scopes) and computing final_discipline
so a one-shot iterable isn't consumed; update the order near the start of
create_uniqueness_rule (affecting the rule_is_global and final_discipline logic)
so subsequent use of scopes/fields uses the concrete lists.

---

Outside diff comments:
In `@sp7_db_setup_check.sh`:
- Around line 236-250: The if-block that checks USER_EXISTS and APP_USER_NAME
(starting at the CREATE USER call) is missing its closing fi and the error
branch prints the wrong message about the "Migrator user"; fix by inserting a
closing fi after the else branch that handles CREATE USER failure so the
app-user block is properly terminated, and update the error text inside that
failure branch to reference the app user variables (APP_USER_NAME,
APP_USER_HOST, APP_USER_PASSWORD) and appropriate grant guidance (or otherwise
move the migrator-related reassignment of
MIGRATOR_NAME/MIGRATOR_PASSWORD/MIGRATION_DB_ALIAS out of the app-user block if
it belongs to a separate migrator-check) so the message and variable changes
align with the CREATE USER context.
- Around line 256-264: The script references undefined shell variables
SAME_MASTER_AND_APP and SAME_MIGRATOR_AND_APP which will trigger "unbound
variable" under set -u; define and initialize these variables (e.g., export or
local assignments to false by default) in the same section where
SAME_MASTER_AND_MIGRATOR is set so they always exist, and set them appropriately
when comparing credentials (the SAME_MASTER_AND_APP and SAME_MIGRATOR_AND_APP
flags should mirror the boolean logic used for SAME_MASTER_AND_MIGRATOR to
indicate when app equals master or migrator).

In `@specifyweb/backend/permissions/initialize.py`:
- Around line 45-55: The initialize function references an undefined variable
migrate_sp6_users causing a NameError; replace that conditional with a call to
the new helper should_migrate_sp6_users() inside initialize (the block that
currently calls wipe_permissions, create_admins, create_roles, and then decides
between assign_users_to_roles_during_testing and assign_users_to_roles). Update
the if-statement to use should_migrate_sp6_users() so the runtime check matches
the pattern used by assign_users_to_roles and
assign_users_to_roles_during_testing.

---

Nitpick comments:
In `@specifyweb/specify/management/commands/base_specify_migration.py`:
- Around line 31-35: Replace the broad "except Exception" around
connections[alias].ensure_connection() with a specific Django DB exception (e.g.
catch django.db.DatabaseError or django.db.utils.OperationalError) and import
that exception at top of the module; keep the fallback to alias='master' and
logger.warning(...) unchanged so only database-related connection errors are
handled while other exceptions still propagate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b15a511d-c8d8-4f42-bdad-001651b4500b

📥 Commits

Reviewing files that changed from the base of the PR and between 7fc918f and e9f3f40.

📒 Files selected for processing (25)
  • Makefile
  • docker-entrypoint.sh
  • sp7_db_setup_check.sh
  • specifyweb/backend/businessrules/migration_utils.py
  • specifyweb/backend/businessrules/migrations/0004_catnum_uniquerule.py
  • specifyweb/backend/businessrules/migrations/0005_cojo.py
  • specifyweb/backend/businessrules/migrations/0006_storage_uniqueIdentifier.py
  • specifyweb/backend/businessrules/migrations/0007_more_uniqueness_rules.py
  • specifyweb/backend/businessrules/tests/test_component.py
  • specifyweb/backend/businessrules/tests/test_uniqueness_rules.py
  • specifyweb/backend/businessrules/uniqueness_rules.py
  • specifyweb/backend/businessrules/utils.py
  • specifyweb/backend/cache/redis/README.md
  • specifyweb/backend/cache/redis/rqueue.py
  • specifyweb/backend/context/remote_prefs.py
  • specifyweb/backend/context/views.py
  • specifyweb/backend/permissions/initialize.py
  • specifyweb/backend/permissions/permissions.py
  • specifyweb/backend/stored_queries/format.py
  • specifyweb/backend/workbench/upload/auditlog.py
  • specifyweb/settings/__init__.py
  • specifyweb/settings/specify_settings.py
  • specifyweb/specify/management/commands/base_specify_migration.py
  • specifyweb/specify/migration_utils/tectonic_ranks.py
  • specifyweb/specify/tests/test_delete_blockers.py
💤 Files with no reviewable changes (1)
  • specifyweb/specify/tests/test_delete_blockers.py

Comment thread sp7_db_setup_check.sh Outdated
Comment thread sp7_db_setup_check.sh Outdated
Comment thread specifyweb/backend/businessrules/uniqueness_rules.py Outdated
@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board Jun 3, 2026
@CarolineDenis CarolineDenis changed the base branch from main to v7_12_0_7_base June 3, 2026 10:54
@CarolineDenis

Copy link
Copy Markdown
Contributor Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sp7_db_setup_check.sh (1)

267-281: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

App user creation bypasses prepared escaping while GRANT uses it.

Lines 271-272 create the app user with literal ${APP_USER_NAME}, ${APP_USER_HOST}, and ${APP_USER_PASSWORD} interpolation, but the subsequent GRANT statement at line 303 correctly uses the pre-escaped SQL_APP_USER_* variables. This inconsistency creates an injection risk in the CREATE USER path.

🛡️ Proposed fix to use escaped variables consistently
 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=\"<hidden>\" -e \"CREATE USER '${APP_USER_NAME}'@'${APP_USER_HOST}' IDENTIFIED BY '<hidden>';\""
   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

Also update line 265 to use escaped variables:

-"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;")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sp7_db_setup_check.sh` around lines 267 - 281, The CREATE USER invocation and
its diagnostic echo use unescaped variables (APP_USER_NAME, APP_USER_HOST,
APP_USER_PASSWORD) which is inconsistent with the GRANT path that uses the
pre-escaped SQL_APP_USER_* variables; update the CREATE USER command and the
preceding “Executing:” echo to use SQL_APP_USER_NAME, SQL_APP_USER_HOST and
SQL_APP_USER_PASSWORD instead of the raw APP_* variables so both creation and
GRANT paths use the same escaped identifiers and avoid injection risk, leaving
the surrounding conditional and flags (NEW_APP_USER_CREATED, MIGRATOR_NAME
fallback) unchanged.
🧹 Nitpick comments (2)
specifyweb/backend/permissions/initialize.py (1)

185-190: 💤 Low value

Consider using get_or_create() for consistency.

The non-testing assign_users_to_roles() (lines 151-156) uses UserPolicy.objects.get_or_create(), while this testing version uses .create(). Using get_or_create() here would maintain consistency and prevent potential IntegrityError if the function is ever invoked multiple times without a database wipe.

Suggested change
-                    UserPolicy.objects.create(
+                    UserPolicy.objects.get_or_create(
                         collection_id=colid,
                         specifyuser_id=user.id,
                         resource=CollectionAccessPT.access.resource(),
                         action=CollectionAccessPT.access.action(),
                     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/backend/permissions/initialize.py` around lines 185 - 190, The
test-only block uses UserPolicy.objects.create(...) which can raise
IntegrityError on repeated runs; change it to use
UserPolicy.objects.get_or_create(...) to match the non-test
assign_users_to_roles() behavior. Replace the create call for UserPolicy with a
get_or_create call using the same identifying kwargs (collection_id=colid,
specifyuser_id=user.id, resource=CollectionAccessPT.access.resource(),
action=CollectionAccessPT.access.action()) so the record is reused if it already
exists.
sp7_db_setup_check.sh (1)

188-203: 💤 Low value

Consider using pre-escaped SQL_DB_IDENTIFIER for consistency.

Line 195 uses backticks directly around $DB_NAME for the CREATE DATABASE statement, while line 133 precomputes the escaped form as SQL_DB_IDENTIFIER. For consistency with the rest of the script's SQL injection prevention pattern, consider using the pre-escaped variable.

♻️ Proposed change
-    -e "CREATE DATABASE \`$DB_NAME\`;"; then
+    -e "CREATE DATABASE $SQL_DB_IDENTIFIER;"; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sp7_db_setup_check.sh` around lines 188 - 203, The CREATE DATABASE branch
uses raw backticks around $DB_NAME instead of the pre-escaped SQL_DB_IDENTIFIER
used elsewhere, so replace the literal backticked `\`$DB_NAME\`` occurrences
(the echoed "Executing: ..." string and the mysql -e "CREATE DATABASE ..."
command) with the precomputed "$SQL_DB_IDENTIFIER" variable to keep
escaping/SQL-injection protection consistent and still set
NEW_DATABASE_CREATED=1 on success; update both the echo and the mysql invocation
that currently reference $DB_NAME so they use SQL_DB_IDENTIFIER instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sp7_db_setup_check.sh`:
- Around line 206-232: The CREATE USER/GRANT/SELECT commands use raw variables
($MIGRATOR_NAME, $MIGRATOR_USER_HOST, $MIGRATOR_PASSWORD) which risks injection
and is inconsistent with the escaped forms; update the mysql invocations and the
USER_EXISTS query to use the pre-escaped variables (SQL_MIGRATOR_NAME,
SQL_MIGRATOR_USER_HOST, SQL_MIGRATOR_PASSWORD) instead, keeping the same logic
around NEW_MIGRATOR_USER_CREATED and the conditional branches so the CREATE
USER, GRANT ALL PRIVILEGES and the initial SELECT COUNT(*) all substitute the
SQL_MIGRATOR_* variants rather than the unescaped originals.

---

Outside diff comments:
In `@sp7_db_setup_check.sh`:
- Around line 267-281: The CREATE USER invocation and its diagnostic echo use
unescaped variables (APP_USER_NAME, APP_USER_HOST, APP_USER_PASSWORD) which is
inconsistent with the GRANT path that uses the pre-escaped SQL_APP_USER_*
variables; update the CREATE USER command and the preceding “Executing:” echo to
use SQL_APP_USER_NAME, SQL_APP_USER_HOST and SQL_APP_USER_PASSWORD instead of
the raw APP_* variables so both creation and GRANT paths use the same escaped
identifiers and avoid injection risk, leaving the surrounding conditional and
flags (NEW_APP_USER_CREATED, MIGRATOR_NAME fallback) unchanged.

---

Nitpick comments:
In `@sp7_db_setup_check.sh`:
- Around line 188-203: The CREATE DATABASE branch uses raw backticks around
$DB_NAME instead of the pre-escaped SQL_DB_IDENTIFIER used elsewhere, so replace
the literal backticked `\`$DB_NAME\`` occurrences (the echoed "Executing: ..."
string and the mysql -e "CREATE DATABASE ..." command) with the precomputed
"$SQL_DB_IDENTIFIER" variable to keep escaping/SQL-injection protection
consistent and still set NEW_DATABASE_CREATED=1 on success; update both the echo
and the mysql invocation that currently reference $DB_NAME so they use
SQL_DB_IDENTIFIER instead.

In `@specifyweb/backend/permissions/initialize.py`:
- Around line 185-190: The test-only block uses UserPolicy.objects.create(...)
which can raise IntegrityError on repeated runs; change it to use
UserPolicy.objects.get_or_create(...) to match the non-test
assign_users_to_roles() behavior. Replace the create call for UserPolicy with a
get_or_create call using the same identifying kwargs (collection_id=colid,
specifyuser_id=user.id, resource=CollectionAccessPT.access.resource(),
action=CollectionAccessPT.access.action()) so the record is reused if it already
exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c2d4e7d0-78f6-4625-9e50-7e02e539bc50

📥 Commits

Reviewing files that changed from the base of the PR and between 3202d36 and 7277f66.

📒 Files selected for processing (8)
  • Makefile
  • docker-entrypoint.sh
  • sp7_db_setup_check.sh
  • specifyweb/backend/businessrules/uniqueness_rules.py
  • specifyweb/backend/permissions/initialize.py
  • specifyweb/settings/__init__.py
  • specifyweb/settings/specify_settings.py
  • specifyweb/specify/management/commands/base_specify_migration.py

Comment thread sp7_db_setup_check.sh
@CarolineDenis

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
sp7_db_setup_check.sh (3)

274-280: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Error message and fallback logic don't match the failure condition.

This block executes when CREATE USER for the app user fails, but:

  1. The error message references the migrator user lacking access
  2. The fallback swaps migrator credentials to master, not app credentials

This appears to be misplaced error handling. If app user creation fails, the message should indicate that, and the appropriate fallback would be to use migrator or master as the app user.

Proposed fix
   if mysql -h "$DB_HOST" -P "$DB_PORT" -u "$MASTER_USER_NAME" --password="$MASTER_USER_PASSWORD" \
     -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
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sp7_db_setup_check.sh` around lines 274 - 280, The error message and fallback
in the branch that handles a failed CREATE USER for the app user are incorrect:
update the echo lines and variable assignments so they reference the app user
(not MIGRATOR) and fall back to a sensible app credential (e.g., set APP_*
variables to MIGRATOR or MASTER credentials as intended); specifically change
the message that currently mentions MIGRATOR_NAME and the GRANT example to
mention the APP user and database, and change the assignments that overwrite
MIGRATOR_NAME/MIGRATOR_PASSWORD/MIGRATION_DB_ALIAS (the lines touching
MIGRATOR_NAME, MIGRATOR_PASSWORD, MASTER_USER_NAME, MASTER_USER_PASSWORD, and
MIGRATION_DB_ALIAS) so they set the corresponding
APP_USER_NAME/APP_USER_PASSWORD/APP_DB_ALIAS fallback values instead.

12-21: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Host variable initialization order prevents environment override.

Lines 12 and 17 unconditionally assign MIGRATOR_USER_HOST and APP_USER_HOST to % before lines 20-21 attempt to read them from the environment. Since the variables are already set, the ${VAR:-default} expansion on lines 20-21 becomes a no-op, ignoring any user-supplied environment values.

Remove lines 12 and 17 to allow the env-var fallback logic on lines 19-21 to work correctly:

Proposed fix
 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:-%}"
 APP_USER_HOST="${APP_USER_HOST:-%}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sp7_db_setup_check.sh` around lines 12 - 21, The early unconditional
assignments for MIGRATOR_USER_HOST and APP_USER_HOST (the lines setting
MIGRATOR_USER_HOST="%" and APP_USER_HOST="%") prevent environment overrides;
remove those two assignments so the later defaulting lines using
MIGRATOR_USER_HOST="${MIGRATOR_USER_HOST:-%}" and
APP_USER_HOST="${APP_USER_HOST:-%}" (and similar MASTER_USER_HOST handling) can
correctly fall back to "%" only when the env var is unset, preserving
user-supplied values.

188-195: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use pre-escaped variables for database operations.

For consistency with user operations (which use $SQL_* variables), the database existence check and creation should use the pre-escaped forms. If DB_NAME contains special characters, the current raw interpolation could break.

Proposed fix
 # Create database if it doesn't exist
 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';")
+"SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name = $SQL_DB_NAME;")

 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=\"<hidden>\" -e \"CREATE DATABASE \`$DB_NAME\`;\""
+  echo "Executing: mysql -h \"$DB_HOST\" -P \"$DB_PORT\" -u \"$MASTER_USER_NAME\" --password=\"<hidden>\" -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
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sp7_db_setup_check.sh` around lines 188 - 195, The DB existence check and
creation use raw DB_NAME/MASTER_USER_PASSWORD interpolation which can break with
special chars; change the mysql invocations and DB_EXISTS assignment to use the
pre-escaped SQL_* variables (e.g., replace DB_NAME with SQL_DB_NAME,
MASTER_USER_NAME with SQL_MASTER_USER_NAME and MASTER_USER_PASSWORD with
SQL_MASTER_USER_PASSWORD) in the SELECT COUNT(...) and CREATE DATABASE
statements and in the mysql command arguments (and keep the echoed command
showing --password="<hidden>"); update the DB_EXISTS assignment and the
conditional mysql -e "CREATE DATABASE \`...\`;" calls to reference those SQL_*
variables so names are properly escaped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@sp7_db_setup_check.sh`:
- Around line 274-280: The error message and fallback in the branch that handles
a failed CREATE USER for the app user are incorrect: update the echo lines and
variable assignments so they reference the app user (not MIGRATOR) and fall back
to a sensible app credential (e.g., set APP_* variables to MIGRATOR or MASTER
credentials as intended); specifically change the message that currently
mentions MIGRATOR_NAME and the GRANT example to mention the APP user and
database, and change the assignments that overwrite
MIGRATOR_NAME/MIGRATOR_PASSWORD/MIGRATION_DB_ALIAS (the lines touching
MIGRATOR_NAME, MIGRATOR_PASSWORD, MASTER_USER_NAME, MASTER_USER_PASSWORD, and
MIGRATION_DB_ALIAS) so they set the corresponding
APP_USER_NAME/APP_USER_PASSWORD/APP_DB_ALIAS fallback values instead.
- Around line 12-21: The early unconditional assignments for MIGRATOR_USER_HOST
and APP_USER_HOST (the lines setting MIGRATOR_USER_HOST="%" and
APP_USER_HOST="%") prevent environment overrides; remove those two assignments
so the later defaulting lines using
MIGRATOR_USER_HOST="${MIGRATOR_USER_HOST:-%}" and
APP_USER_HOST="${APP_USER_HOST:-%}" (and similar MASTER_USER_HOST handling) can
correctly fall back to "%" only when the env var is unset, preserving
user-supplied values.
- Around line 188-195: The DB existence check and creation use raw
DB_NAME/MASTER_USER_PASSWORD interpolation which can break with special chars;
change the mysql invocations and DB_EXISTS assignment to use the pre-escaped
SQL_* variables (e.g., replace DB_NAME with SQL_DB_NAME, MASTER_USER_NAME with
SQL_MASTER_USER_NAME and MASTER_USER_PASSWORD with SQL_MASTER_USER_PASSWORD) in
the SELECT COUNT(...) and CREATE DATABASE statements and in the mysql command
arguments (and keep the echoed command showing --password="<hidden>"); update
the DB_EXISTS assignment and the conditional mysql -e "CREATE DATABASE \`...\`;"
calls to reference those SQL_* variables so names are properly escaped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 477eb1b6-2bc9-46da-9efa-544082746367

📥 Commits

Reviewing files that changed from the base of the PR and between fa20860 and aaf0498.

📒 Files selected for processing (2)
  • sp7_db_setup_check.sh
  • specifyweb/specify/management/commands/base_specify_migration.py

@CarolineDenis

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
specifyweb/specify/management/commands/base_specify_migration.py (1)

31-36: 💤 Low value

Consider narrowing the exception type for clarity.

The fallback logic and logging look good. The broad Exception catch works correctly for graceful fallback, but narrowing to django.db.Error (or a tuple of OperationalError, DatabaseError) would make the intent clearer and avoid accidentally swallowing unrelated exceptions.

🔧 Optional refinement
 from django.core.management.base import BaseCommand
-from django.db import connections, transaction
+from django.db import connections, transaction, Error as DatabaseError
 import logging

...

         original_alias = alias
         try:
             connections[alias].ensure_connection()
-        except Exception as e:
+        except DatabaseError as e:
             alias = 'master'
             logger.warning(f"Database alias '{original_alias}' unavailable ({e}), falling back to 'master'")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/specify/management/commands/base_specify_migration.py` around
lines 31 - 36, The current except Exception in the
connections[alias].ensure_connection() block is too broad; change it to catch
Django DB errors (e.g., from django.db import DatabaseError, OperationalError)
or the base django.db.Error so you only handle database connection failures.
Update the except clause that currently references original_alias and sets
alias='master' to catch DatabaseError or (OperationalError, DatabaseError) and
keep the existing alias fallback and logger.warning message unchanged
(referencing connections[alias].ensure_connection(), original_alias, alias, and
logger.warning).

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@specifyweb/specify/management/commands/base_specify_migration.py`:
- Around line 31-36: The current except Exception in the
connections[alias].ensure_connection() block is too broad; change it to catch
Django DB errors (e.g., from django.db import DatabaseError, OperationalError)
or the base django.db.Error so you only handle database connection failures.
Update the except clause that currently references original_alias and sets
alias='master' to catch DatabaseError or (OperationalError, DatabaseError) and
keep the existing alias fallback and logger.warning message unchanged
(referencing connections[alias].ensure_connection(), original_alias, alias, and
logger.warning).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ac12d1f6-2646-403a-a139-f160bfcc7220

📥 Commits

Reviewing files that changed from the base of the PR and between fa20860 and 8c2ef7d.

📒 Files selected for processing (2)
  • sp7_db_setup_check.sh
  • specifyweb/specify/management/commands/base_specify_migration.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • sp7_db_setup_check.sh

@grantfitzsimmons grantfitzsimmons left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All sp7_db_setup_check.sh functions present. APP_USER_NAME fallback chain functions correctly.

Databases Tested: (Number of tables, discipline)

  • aumnh_fish_2026_06_26 (254, Ichthyology)
  • alfmuseum_2026_06_26 (250, Multi)
  • aumnh_herbarium_2026_06_26 (253, Botany)
  • aumnh_inverts_2026_06_26 (267, Invertebrate)
  • bohart_entomology_2026_06_26 (248, Entomology)
  • calvertmarinemuseum_2026_06_26 (249, Multi)
  • sfmuseumofearthscience_2026_06_26 (228, EarthScience)
  • sp7demofish_2026_06_26 (256, Ichthyology)
  • turtleconservancy_2026_06_26 (236, Herpetology)
  • uiowapaleo_2026_06_26 (249, Paleontology)
  • unitec_2026_06_26 (248, Multi)
  • unsm_vp_2026_06_26 (251, VertPaleo)
  • uoregon_2026_06_26 (252, Multi)
  • uwfc_2026_06_26 (260, Ichthyology)
  • vimsfish_2026_06_26 (290, Ichthyology)

All exit 0. Every DB reported "No migrations to apply". Privilege verification was done.

When I restarted the container with a fresh DATABASE_NAME, the full chain (sp7_db_setup_check.shbase_specify_migrationmigrate) completed cleanly with the database created, user privileges verified, and all 70+ migrations applied (in under 2 minutes on MariaDB via Docker).

manage.py test test_delete_blockers all 5 passed in 3.661s.

No issues found!

melton-jason and others added 11 commits July 1, 2026 09:21
Remove the 'restart: unless-stopped' line from the development service in docker-compose.yml
Replace the previous tolerant invocation of sp7_db_setup_check.sh (which used set +e / set -e) with an explicit if-check. If the DB setup script succeeds, continue to run key migration functions; if it fails, print an error and exit with status 1 to avoid continuing startup with an incomplete DB setup.
@grantfitzsimmons

Copy link
Copy Markdown
Member

Added my changes from #8108. Waiting on @melton-jason's review

@grantfitzsimmons grantfitzsimmons changed the title Issue 8148 fixes 6121 after review fix: improve DB setup script Jul 2, 2026
fixes #8107

This resulted in the following workflow:
1. MariaDB would create the database on startup
2. The sp7_db_setup_check.sh would see the existing database
   and assume it was a Specify 6 DB, inserting the fake specify/0001
   migration
3. When running the accounts/0001 migration, the operation would fail
   because there is no Specifyuser table
Find some way to use a router to always use the right database
for migrations.
See https://docs.djangoproject.com/en/4.2/topics/db/multi-db/ and
#8154 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Dev Attention Needed

3 participants