From 2c0b214137499b5c8630665f01c36996b6a8736c Mon Sep 17 00:00:00 2001 From: Anto01 Date: Wed, 8 Apr 2026 19:55:50 -0400 Subject: [PATCH] deploy.sh: add permission pre-flight check with clean remediation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dalidou Claude's second re-deploy (commit b492f5f) reported one remaining friction point: the app dir was root-owned from the previous manual-workaround deploy (when ALTER TABLE was run as root to work around the schema init bug), so deploy.sh's git fetch/reset hit a permission wall. They worked around it with a one-shot docker run chown, but the script itself produced cryptic git errors before that, so the fix wasn't obvious until after the fact. This commit adds a permission pre-flight check that runs BEFORE any git operations and exits cleanly with an explicit remediation message instead of letting git produce half-state on partial failure. The check: 1. Reads the current owner of the app dir via `stat -c '%U:%G'` 2. Reports the current user via `id -un` / `id -u:id -g` 3. Attempts to create a throwaway marker file in the app dir 4. If the marker write fails, prints three distinct remediation commands covering the common environments: a. sudo chown -R 1000:1000 $APP_DIR (if passwordless sudo) b. sudo bash $0 (if running deploy.sh itself as root works) c. docker run --rm -v $APP_DIR:/app alpine chown -R ... (what Dalidou Claude actually did on 2026-04-08) 5. Exits with code 5 so CI / automation can distinguish "no permission" from other deploy failures Dry-run mode skips the check (nothing is mutated in dry-run). A brief WARNING is also printed early if the app dir exists but doesn't appear writable, before the fatal check — this gives operators a heads-up even in the happy-path case. Syntax check: bash -n passes. Full suite: 216 passing (unchanged; no code changes to the app). What this commit does NOT do ---------------------------- - Does NOT automatically fix permissions. chown needs root and we don't want deploy.sh to escalate silently. The operator runs one of the three remediation commands manually. - Does NOT check permissions on nested files (like .git/config) individually. The marker-file test on the app dir root is the cheapest proxy that catches the common case (root-owned dir tree after a previous sudo-based operation). - Does NOT change behavior on first-time deploys where the app dir doesn't exist yet. The check is gated on `-d $APP_DIR`. --- deploy/dalidou/deploy.sh | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/deploy/dalidou/deploy.sh b/deploy/dalidou/deploy.sh index 9e01545..68ab572 100644 --- a/deploy/dalidou/deploy.sh +++ b/deploy/dalidou/deploy.sh @@ -90,6 +90,57 @@ log " branch: $BRANCH" log " health url: $HEALTH_URL" log " dry run: $DRY_RUN" +# --------------------------------------------------------------------- +# Step 0: pre-flight permission check +# --------------------------------------------------------------------- +# +# If $APP_DIR exists but the current user cannot write to it (because +# a previous manual deploy left it root-owned, for example), the git +# fetch / reset in step 1 will fail with cryptic errors. Detect this +# up front and give the operator a clean remediation command instead +# of letting git produce half-state on partial failure. This was the +# exact workaround the 2026-04-08 Dalidou redeploy needed — pre- +# existing root ownership from the pre-phase9 manual schema fix. + +if [ -d "$APP_DIR" ] && [ "$DRY_RUN" != "1" ]; then + if [ ! -w "$APP_DIR" ] || [ ! -r "$APP_DIR/.git" ] 2>/dev/null; then + log "WARNING: app dir exists but may not be writable by current user" + fi + current_owner="$(stat -c '%U:%G' "$APP_DIR" 2>/dev/null || echo unknown)" + current_user="$(id -un 2>/dev/null || echo unknown)" + current_uid_gid="$(id -u 2>/dev/null):$(id -g 2>/dev/null)" + log "Step 0: permission check" + log " app dir owner: $current_owner" + log " current user: $current_user ($current_uid_gid)" + # Try to write a tiny marker file. If it fails, surface a clean + # remediation message and exit before git produces confusing + # half-state. + marker="$APP_DIR/.deploy-permission-check" + if ! ( : > "$marker" ) 2>/dev/null; then + log "FATAL: cannot write to $APP_DIR as $current_user" + log "" + log "The app dir is owned by $current_owner and the current user" + log "doesn't have write permission. This usually happens after a" + log "manual workaround deploy that ran as root." + log "" + log "Remediation (pick the one that matches your setup):" + log "" + log " # If you have passwordless sudo and gitea runs as UID 1000:" + log " sudo chown -R 1000:1000 $APP_DIR" + log "" + log " # If you're running deploy.sh itself as root:" + log " sudo bash $0" + log "" + log " # If neither works, do it via a throwaway container:" + log " docker run --rm -v $APP_DIR:/app alpine \\" + log " chown -R 1000:1000 /app" + log "" + log "Then re-run deploy.sh." + exit 5 + fi + rm -f "$marker" 2>/dev/null || true +fi + # --------------------------------------------------------------------- # Step 1: make sure $APP_DIR is a proper git checkout of the branch # ---------------------------------------------------------------------