# Optimize update.sh — Conditional Rebuild Only When Needed ## TL;DR > **Quick Summary**: Modify `update.sh` to skip Docker rebuild, restart, and migration when no git/submodule/`.env` changes are detected. Prevents unnecessary 2-5 minute downtime on every run. > > **Deliverables**: > - `update.sh` — refactored with conditional rebuild logic > - `.update-state` — persistent state file (gitignored) > - `.gitignore` — add `.update-state` entry > > **Estimated Effort**: Quick > **Parallel Execution**: N/A (single file) > **Critical Path**: N/A --- ## Context ### Original Request User noticed `./update.sh` always runs `docker compose down`, `docker compose build`, `docker compose up -d`, and migration — even when no code changes exist. This wastes time (2-5 min) and causes unnecessary downtime. ### Metis Review — Key Findings **Critical Gap #1**: `.env` changes (especially `VITE_API_BASE_URL` which is a `--build-arg`) are NOT tracked by git. Must hash `.env` content alongside git state. **Critical Gap #2**: State file location must be `.gitignore`'d. Use `./.update-state` with atomic write (tmp + mv). **Critical Gap #3**: No force-rebuild mechanism. Must add `--force` flag. **Minor Gap #4**: `md5sum` not portable to macOS. Use `openssl sha256`. **Minor Gap #5**: On `git pull` or submodule failure, should ALWAYS rebuild (safe fallback). --- ## Work Objectives ### Core Objective Skip Docker rebuild/restart/migration cycle when git state, submodule state, and `.env` are unchanged from last successful update. ### Concrete Deliverables - `update.sh` — refactored with state comparison + conditional rebuild - `.update-state` — persistent state file (auto-created, never committed) - `.gitignore` — add `.update-state` entry ### Definition of Done - [x] Second consecutive run with no changes prints "No changes detected. Skipping." and exits in <5s - [x] First run (or after any change) executes full cycle (pull, build, up, migrate) - [x] `bash update.sh --force` always executes full cycle - [x] `.env` change (esp. `VITE_API_BASE_URL`) triggers rebuild even without git change - [x] Git pull failure triggers rebuild (safe fallback) - [x] Corrupted state file treated as first run → always builds ### Must Have - Conditional rebuild: only when git HEAD, submodules, or `.env` changed - `--force` flag to bypass state check - `.update-state` properly gitignored - Clean output: clear `[+]` / `[-]` indicators for skip vs rebuild paths ### Must NOT Have (Guardrails) - Do NOT change the `down → build → up` cycle pattern when rebuild IS needed - Do NOT add per-submodule selective build (always build all or nothing) - Do NOT add Docker health-check polling or auto-rollback - Do NOT modify any file other than `update.sh` and `.gitignore` - Do NOT use `docker-compose` (v1) anywhere --- ## Verification Strategy > **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed. ### Test Decision - **Infrastructure exists**: YES (bash on Linux server) - **Automated tests**: None (shell script test suite doesn't exist) - **Primary verification**: Run on remote server, verify behavior with: - `ssh root@172.20.8.191` — execute updated script - First run: full cycle - Second run (no changes): skip - After `.env` edit: rebuild - With `--force`: rebuild --- ## Execution Strategy Single task, no waves needed — one file change. --- ## TODOs _All tasks completed in commit `0b943ae`_ - [x] 1. Refactor `update.sh` — Add State Comparison & Conditional Rebuild Logic **What to do**: - Add at top of script (after `set -e`): define `STATE_FILE=".update-state"` path - After `git pull` + `git submodule update --init --recursive --remote`: 1. Compute combined hash: `CURRENT_HASH=$(echo "$(git rev-parse HEAD)$(git submodule status)$(sha256sum .env)" | sha256sum | cut -d' ' -f1)` 2. Read previous hash from `$STATE_FILE` (if exists) 3. If `$CURRENT_HASH` matches previous AND `--force` not passed → skip rebuild 4. Otherwise → execute full `down → build → up -d → migrate → backfill` cycle - Write new hash atomically: `echo "$CURRENT_HASH" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"` - Handle `--force` flag: `if [ "$1" = "--force" ]; then ...` - Handle missing/corrupt state file (treat as first run → build) - Handle `git pull` failure (always build as safe fallback) - Handle `git submodule update` failure (always build as safe fallback) - Print clear `[+]`/`[-]` output for skip vs rebuild paths **Must NOT do**: - Do NOT change the `down → build → up` cycle pattern (preserve existing) - Do NOT add per-service selective build - Do NOT modify any existing command flags or environment sourcing **Recommended Agent Profile**: - **Category**: `quick` - Reason: Single file, well-defined logic, no external dependencies - **Skills**: `[]` **Parallelization**: - **Can Run In Parallel**: N/A (single task) **References**: - `update.sh` — Current file to refactor (52 lines) - `.gitignore:41` — `connect_remote.txt` already listed; add `.update-state` nearby **Acceptance Criteria**: - [x] Script runs full cycle on first invocation (no `.update-state`) - [x] Script skips full cycle on second invocation (no changes) - [x] `bash update.sh --force` always runs full cycle - [x] `.env` change triggers rebuild (hash detects difference) - [x] Corrupted `.update-state` treated as first run - [x] `git pull` failure → rebuild triggered (safe fallback) - [x] All output clear and actionable **QA Scenarios**: ``` Scenario A: Second run skips rebuild (no changes) ✅ Tool: Bash (interactive_bash via tmux on server) Preconditions: State file created with correct hash Steps: 1. ssh root@172.20.8.191 2. cd /root/Nexus-Guard-Suite 3. hash=$(echo "$(git rev-parse HEAD)$(git submodule status)$(sha256sum .env)" | sha256sum | cut -d' ' -f1) 4. echo "$hash" > .update-state 5. bash update.sh Result: "No changes detected. Skipping build and restart." in <2s, exit 0 Evidence: .sisyphus/evidence/f1-update-sh-optimization.md Scenario B: --force triggers rebuild even without changes ✅ Tool: Bash (interactive_bash via tmux on server) Preconditions: State file exists Steps: 1. bash update.sh --force Result: "--force flag detected. Will rebuild." → full cycle Evidence: .sisyphus/evidence/f1-update-sh-optimization.md Scenario C: .env change triggers rebuild ✅ Tool: Bash (interactive_bash via tmux on server) Preconditions: State file exists Steps: 1. echo "# test change" >> .env 2. bash update.sh Result: "State hash changed. Rebuilding." → full cycle Evidence: .sisyphus/evidence/f1-update-sh-optimization.md Scenario D: First run (no state) triggers rebuild ✅ Tool: Bash (interactive_bash via tmux on server) Preconditions: No .update-state Steps: 1. rm -f .update-state 2. bash update.sh Result: "First run (no state file found). Full cycle required." → full cycle Evidence: .sisyphus/evidence/f1-update-sh-optimization.md ``` **Evidence to Capture**: - [x] Task 1 — skip-rebuild output - [x] Task 1 — force-rebuild output - [x] Task 1 — env-change rebuild output - [x] Task 1 — corrupt-state output > Evidence consolidated in `.sisyphus/evidence/f1-update-sh-optimization.md` **Commit**: YES - Message: `chore(ops): optimize update.sh to skip rebuild when no changes detected` - Files: `update.sh`, `.gitignore` - Pre-commit: review diff - [x] 2. Add `.update-state` to `.gitignore` **What to do**: - Edit root `.gitignore` to add `.update-state` entry (alongside `connect_remote.txt` on line 41 or nearby) - This prevents accidental commit of machine-local build state **Must NOT do**: - Do NOT change any existing `.gitignore` entries - Do NOT add `.update-state` to submodule `.gitignore` files **Recommended Agent Profile**: - **Category**: `quick` - Reason: Trivial one-line addition - **Skills**: `[]` **Parallelization**: - **Can Run In Parallel**: YES (independent of Task 1's logic, but logically grouped in same commit) - **Blocked By**: Commit groups with Task 1 **References**: - `.gitignore:41` — Current state, `connect_remote.txt` already listed there **Acceptance Criteria**: - [x] `git check-ignore .update-state` returns the path (file is ignored) - [x] No existing entries modified **Evidence to Capture**: - [x] git check-ignore verification **Commit**: YES (group with Task 1) --- ## Final Verification - [x] F1. **Behavioral Verification** — Run on server across all scenarios ## Commit Strategy - **1**: `chore(ops): optimize update.sh to skip rebuild when no changes detected` ## Success Criteria ```bash # First run (or after changes): full cycle bash update.sh # Expected: git pull, down, build, up -d, migrate # Second run (no changes): skip bash update.sh # Expected: "No changes detected. Skipping build and restart." in <5s # Force rebuild bash update.sh --force # Expected: full cycle regardless # After .env edit bash update.sh # Expected: rebuild detected (new .env hash) ```