Skip to content

fix(deploy): harden legacy cutover rollback - #409

Merged
corrin merged 4 commits into
mainfrom
shared-disk-space-saving
Jun 26, 2026
Merged

fix(deploy): harden legacy cutover rollback#409
corrin merged 4 commits into
mainfrom
shared-disk-space-saving

Conversation

@corrin

@corrin corrin commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Summary

Hardens the remaining legacy-to-shared-release cutover path for KAN-197.

  • skips shared-release rollback build attempts for legacy checkouts and points failed legacy migrations at legacy_rollback.sh
  • adds one-off legacy cutover and rollback scripts with restorable code, unit, nginx, and DB artifact checks
  • fails snapshot creation when required rollback assets are missing instead of producing an incomplete snapshot
  • restores the executable bit on scripts/cleanup_backups.py
  • updates deploy docs to distinguish normal shared-release rollback from first legacy cutover snapshot rollback

Why

The shared immutable release model superseded the original per-instance frontend/node_modules symlink approach. The remaining risk is the one-time msm-prod cutover from a legacy checkout, where the old SHA cannot be rebuilt as a shared release and rollback must use a pre-cutover snapshot.

Validation

  • shellcheck scripts/server/deploy.sh scripts/server/cutover_legacy_instance.sh scripts/legacy_rollback.sh scripts/backup_db.sh scripts/predeploy_backup.sh scripts/predeploy_rollback.sh
  • bash -n scripts/server/deploy.sh scripts/server/cutover_legacy_instance.sh scripts/legacy_rollback.sh
  • git diff --check
  • pre-commit run --files scripts/server/deploy.sh scripts/server/cutover_legacy_instance.sh scripts/legacy_rollback.sh scripts/cleanup_backups.py docs/updating.md scripts/server/README.md
  • commit hooks passed
  • pre-push hooks passed, including backend type-check gate, frontend unit tests, lint, type-check, production build, typed-router check, and workflow formatting

Summary by CodeRabbit

  • New Features
    • Added a one-time legacy instance cutover flow that creates per-instance rollback artifacts.
    • Added a root-only legacy rollback script to restore an instance from a legacy snapshot prefix.
  • Bug Fixes
    • Improved deploy validation (instance credentials/required nginx config) and tightened legacy vs shared-release behavior.
    • Updated migration failure rollback guidance to use the correct legacy vs predeploy rollback path.
  • Documentation
    • Refreshed runbook guidance for rollback availability and clarified “previous release” behavior.
    • Documented both legacy rollback paths (including first legacy checkout guidance).
  • Tests
    • Expanded assertions for legacy rollback artifact creation and updated deploy-cleanup and immutable artifact expectations.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f90363ff-2d0e-44ac-aa53-f3d4d7cb1400

📥 Commits

Reviewing files that changed from the base of the PR and between b8addd7 and 3bd29e9.

📒 Files selected for processing (3)
  • apps/workflow/tests/test_xero_instance_templates.py
  • scripts/legacy_rollback.sh
  • scripts/predeploy_backup.sh

📝 Walkthrough

Walkthrough

The PR adds legacy cutover and rollback scripts, updates deploy behavior for legacy versus shared-release instances, changes rollback backup output handling, and revises related docs and tests.

Changes

Legacy cutover and rollback flow

Layer / File(s) Summary
Cutover snapshot capture
scripts/server/cutover_legacy_instance.sh
The cutover script validates a legacy checkout, captures rollback artifacts, records a manifest, and execs deploy.sh after saving unit and nginx backups.
Deploy validation and legacy branching
scripts/server/deploy.sh, docs/updating.md, scripts/server/README.md, apps/workflow/tests/test_xero_instance_templates.py
deploy.sh validates instance credentials and nginx config, classifies legacy checkouts, narrows previous-release handling, and selects the matching rollback command; the docs and tests update the corresponding rollback and cleanup expectations.
Legacy rollback restore
scripts/legacy_rollback.sh
scripts/legacy_rollback.sh restores the selected legacy snapshot and predeploy database dump, then reloads the restored systemd and nginx state.
Predeploy backup routing
scripts/predeploy_backup.sh
scripts/predeploy_backup.sh writes backups into the legacy rollback directory when a matching manifest exists and enforces the directory and dump permissions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • corrin/docketworks#407: Introduces the shared immutable release model and the deploy/rollback control flow that this PR extends for legacy cutover and rollback.
  • corrin/docketworks#408: Adjusts previous-release handling in scripts/server/deploy.sh, which is the same rollback branch this PR further narrows for legacy instances.

Poem

I hopped through cutovers, soft-paw neat,
and stashed a legacy snapshot beneath my feet.
Then I booped rollback paths, tidy and bright,
while nginx hummed back to moonbeam light.
🐇✨ Old worlds return, then spring anew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: hardening legacy cutover rollback.
Description check ✅ Passed The description covers summary, rationale, and validation, and includes the Jira key, though it omits the template's explicit Jira section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch shared-disk-space-saving

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

@corrin
corrin marked this pull request as ready for review June 26, 2026 07:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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 `@scripts/legacy_rollback.sh`:
- Around line 144-166: The sync_sequences step is currently picking the first
complete shared release venv, which may not match the restored snapshot
environment. Update the legacy_rollback.sh flow around the SHARED_VENV lookup
and the manage.py sync_sequences invocation so it uses the restored legacy
environment associated with the rollback target, or explicitly validates that
the selected venv matches the restored runtime before proceeding. Keep the
change localized to the shared venv selection logic and the sync_sequences
execution block.
- Line 141: The ownership change in the rollback script is too broad and
recurses into the backups area, which should remain root-owned. Update the
ownership logic around the chown in legacy_rollback.sh so it only applies to the
restored checkout/runtime paths used by the rollback flow, and explicitly
exclude the backups directory (including snapshots, saved unit files, nginx
backups, and dumps) from any recursive ownership changes. Keep the
backup-related paths owned by root while still assigning the restored app files
to the instance user.

In `@scripts/server/cutover_legacy_instance.sh`:
- Around line 73-109: The snapshot creation flow in cutover_legacy_instance.sh
currently writes the tarball in the create snapshot step before the required
systemd unit files and nginx config are validated, which can leave behind an
incomplete legacy_<sha> snapshot. Move the asset validation ahead of the tarball
write, or stage the archive and backup outputs in temporary locations and only
finalize them after all checks pass; use the existing log, unit_path loop, and
NGINX_CONF checks to gate the snapshot creation.
- Line 86: The rollback snapshot ownership is being changed to the instance
user, which allows the payload consumed by the root-executed rollback path to be
tampered with. In the cutover flow in cutover_legacy_instance.sh, keep the
$SNAPSHOT archive root-owned instead of chowning it to $INST_USER, or relocate
the snapshot to a root-owned location outside the instance-owned tree so
legacy_rollback.sh can safely extract the trusted archive.

In `@scripts/server/deploy.sh`:
- Around line 331-334: The deploy flow is using a different legacy-checkout test
in the previous-sha handling than the shared predicate used earlier, so it can
misclassify instances that still have .git alongside current after cutover.
Update the condition in the previous shared-release path to reuse the same
legacy-layout predicate already used around the earlier branch, and apply the
same fix anywhere else the legacy rollback/build decision is made so both paths
make the same .git and current check.

In `@scripts/server/README.md`:
- Line 118: The deployment step description in the README only mentions the
explicit rollback path for normal deploys, but it also needs to document the
legacy checkout rollback flow. Update the text around the deploy/rollback
instructions to mention both `predeploy_rollback.sh` for standard cases and
`legacy_rollback.sh` for legacy checkouts, keeping the wording aligned with the
operator flow described by the deploy script.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c981e2fa-a5b3-43c9-8b6c-adac20e585ae

📥 Commits

Reviewing files that changed from the base of the PR and between e240881 and 1ddb5bb.

📒 Files selected for processing (7)
  • apps/workflow/tests/test_xero_instance_templates.py
  • docs/updating.md
  • scripts/cleanup_backups.py
  • scripts/legacy_rollback.sh
  • scripts/server/README.md
  • scripts/server/cutover_legacy_instance.sh
  • scripts/server/deploy.sh

Comment thread scripts/legacy_rollback.sh Outdated
Comment thread scripts/legacy_rollback.sh Outdated
Comment thread scripts/server/cutover_legacy_instance.sh
Comment thread scripts/server/cutover_legacy_instance.sh Outdated
Comment thread scripts/server/deploy.sh Outdated
Comment thread scripts/server/README.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@scripts/server/cutover_legacy_instance.sh`:
- Around line 65-66: The rollback dump placement in cutover_legacy_instance.sh
is unsafe because the predeploy_*.sql.gz file is being kept under the
app-writable backups directory and later consumed by legacy_rollback.sh as
postgres. Update the cutover flow to store the rollback-consumed dump in the
root-owned rollback directory (or make the dump file/directory root-owned and
immutable to the instance user), and ensure the restore path in
legacy_rollback.sh only reads from that protected location after validating the
expected file ownership and contents. Use the existing
cutover_legacy_instance.sh backup setup and legacy_rollback.sh restore logic to
keep the predeploy dump inaccessible to $INST_USER.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f395058f-30e7-413c-b172-8655ce84d483

📥 Commits

Reviewing files that changed from the base of the PR and between 1ddb5bb and b8addd7.

📒 Files selected for processing (5)
  • apps/workflow/tests/test_xero_instance_templates.py
  • scripts/legacy_rollback.sh
  • scripts/server/README.md
  • scripts/server/cutover_legacy_instance.sh
  • scripts/server/deploy.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/server/deploy.sh

Comment on lines +65 to +66
chown "$INST_USER:$INST_USER" "$BACKUP_DIR"
chmod 700 "$BACKUP_DIR"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Keep rollback-consumed DB dumps out of the app-writable backup directory.

legacy_rollback.sh later restores predeploy_*_${OLD_SHORT}.sql.gz from $INSTANCE_DIR/backups, but this script makes that directory writable by $INST_USER. A compromised instance user can replace the gzip with arbitrary psql input that rollback runs as postgres. Store the paired predeploy dump in the root-owned $ROLLBACK_DIR, or make the dump directory/file root-owned and validate both immediately before restore.

🤖 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 `@scripts/server/cutover_legacy_instance.sh` around lines 65 - 66, The rollback
dump placement in cutover_legacy_instance.sh is unsafe because the
predeploy_*.sql.gz file is being kept under the app-writable backups directory
and later consumed by legacy_rollback.sh as postgres. Update the cutover flow to
store the rollback-consumed dump in the root-owned rollback directory (or make
the dump file/directory root-owned and immutable to the instance user), and
ensure the restore path in legacy_rollback.sh only reads from that protected
location after validating the expected file ownership and contents. Use the
existing cutover_legacy_instance.sh backup setup and legacy_rollback.sh restore
logic to keep the predeploy dump inaccessible to $INST_USER.

@corrin
corrin merged commit 04b7b43 into main Jun 26, 2026
8 of 9 checks passed
@corrin
corrin deleted the shared-disk-space-saving branch June 26, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant