#!/usr/bin/env bash # DashCaddy Post-Deploy Patch Script # Runs AFTER the host-side update script copies staging files into the API source # directory, but BEFORE the Docker build. Fixes upstream bugs in the released # tarball so the build succeeds and the container starts cleanly. # # Why this exists: # v1.14.4 (commit d2a48b1) shipped with broken relative require paths: # - Root server.js: `require('../src/...')` instead of `require('./src/...')` # - Many src/**/*.js: `require('./module-name')` instead of # `require('../module-name')` (files were moved into src/ but requires # not updated to point at root-level modules) # - Missing license-keygen.js at root # Without these patches, every auto-update results in a crash-looping container. # # Idempotent: safe to run multiple times, only changes files that match the # broken pattern. Reports what was already OK so you can confirm health. # # Usage: dashcaddy-post-deploy-patches.sh # api_source_dir: e.g. /opt/dashcaddy/dashcaddy-api set -uo pipefail API_DIR="${1:-/opt/dashcaddy/dashcaddy-api}" log() { echo "[dashcaddy-patch] $(date '+%Y-%m-%d %H:%M:%S') $*"; } if [[ ! -d "$API_DIR" ]]; then log "ERROR: API source directory not found: $API_DIR" exit 1 fi cd "$API_DIR" || exit 1 TOTAL_PATCHED=0 TOTAL_ALREADY_OK=0 # ──────────────────────────────────────────────────────────────────────────── # Patch 1: Root-level server.js — fix '../src/...' requires to './src/...' # v1.14.4 was tagged with broken relative paths. server.js sits at the API # root, so any `require('../src/...')` is one directory too high. # ──────────────────────────────────────────────────────────────────────────── SERVER_JS="$API_DIR/server.js" if [[ -f "$SERVER_JS" ]]; then if grep -q "require('\.\./src/" "$SERVER_JS"; then BAD_COUNT=$(grep -c "require('\.\./src/" "$SERVER_JS" || true) sed -i "s|require('\.\./src/|require('./src/|g" "$SERVER_JS" if ! grep -q "require('\.\./src/" "$SERVER_JS"; then log "Patched server.js: rewrote ${BAD_COUNT} '../src/...' requires to './src/...'" TOTAL_PATCHED=$((TOTAL_PATCHED + BAD_COUNT)) else log "WARNING: server.js sed did not remove all bad requires" fi else TOTAL_ALREADY_OK=$((TOTAL_ALREADY_OK + 1)) log "server.js: already correct (no '../src/...' requires)" fi else log "WARNING: $SERVER_JS not found" fi # ──────────────────────────────────────────────────────────────────────────── # Patch 2: src/managers/license-manager.js — fix './license-keygen' require # The license-keygen module lives at the API root, so from src/managers/ # the correct relative path is '../../license-keygen'. # ──────────────────────────────────────────────────────────────────────────── LICENSE_MGR="$API_DIR/src/managers/license-manager.js" if [[ -f "$LICENSE_MGR" ]]; then if grep -q "require('\./license-keygen')" "$LICENSE_MGR"; then sed -i "s|require('\./license-keygen')|require('../../license-keygen')|g" "$LICENSE_MGR" if grep -q "require('\.\./\.\./license-keygen')" "$LICENSE_MGR"; then log "Patched license-manager.js: './license-keygen' → '../../license-keygen'" TOTAL_PATCHED=$((TOTAL_PATCHED + 1)) else log "WARNING: license-manager.js sed did not apply" fi else TOTAL_ALREADY_OK=$((TOTAL_ALREADY_OK + 1)) log "license-manager.js: already correct" fi else log "WARNING: $LICENSE_MGR not found" fi # ──────────────────────────────────────────────────────────────────────────── # Patch 3: Generic src/**/*.js require path fix # For every file in any src/ subdir, find `require('./module-name')` patterns # where module-name.js exists at API root but NOT in the same subdir, and # rewrite them to `require('../module-name')`. # # This catches the bulk of v1.14.4's broken paths that the upstream refactor # left behind (files moved into src/ but requires not updated). # ──────────────────────────────────────────────────────────────────────────── log "Scanning src/ for broken root-level requires..." GENERIC_PATCHED=0 GENERIC_ALREADY_OK=0 # Build a list of all .js files in src/ (excluding tests) while IFS= read -r -d '' src_file; do # Get the directory containing this file relative to API_DIR rel_dir=$(dirname "${src_file#$API_DIR/}") # e.g. "src/docker" depth=$(echo "$rel_dir" | tr '/' '\n' | wc -l) # depth=1 means src/foo.js (parent is "src") # depth=2 means src/docker/foo.js (parent is "src/docker"), need ../ # Find all `require('./name')` patterns in this file while IFS= read -r require_line; do # Extract the module path from inside the quotes mod_path=$(echo "$require_line" | grep -oE "require\(['\"]\./[a-zA-Z0-9_-]+['\"]\)" | head -1 | sed -E "s|require\(['\"]\./||; s|['\"]\)||") if [[ -z "$mod_path" ]]; then continue; fi # Compute the absolute path Node would resolve `./mod_path` to from this file # Candidate 1: same dir, .js file candidate="$rel_dir/$mod_path.js" if [[ -f "$candidate" ]]; then # File exists in same subdir → require is correct as-is continue fi # Candidate 2: same dir, directory with index.js if [[ -d "$rel_dir/$mod_path" && -f "$rel_dir/$mod_path/index.js" ]]; then continue fi # Check if it exists at the root (one level above src/, or at the # appropriate depth for nested src/ subdirs) # For a file at $rel_dir/$file.js, './mod' resolves to $rel_dir/mod.js # We need to find where mod.js actually exists. found_path="" # Walk up from the same-dir candidate, checking each parent dir. # Start by checking the file's own dir (already done above), then # dirname(rel_dir), dirname(dirname(rel_dir)), ..., until we hit ".". # rel_dir is relative to API_DIR, so when test_dir becomes ".", we # should check API_DIR/$mod_path.js (the root), THEN break. test_dir="$rel_dir" while true; do test_dir=$(dirname "$test_dir") # Check this directory for the module: either .js file or dir/index.js if [[ -f "$test_dir/$mod_path.js" || ( -d "$test_dir/$mod_path" && -f "$test_dir/$mod_path/index.js" ) ]]; then found_path="$test_dir/$mod_path" break fi # Stop when we've gone past root [[ "$test_dir" == "." || "$test_dir" == "/" ]] && break done if [[ -z "$found_path" ]]; then # Module not found anywhere — leave it alone, would need investigation continue fi # Found at root. Compute the correct relative path from this file to root. # For src/docker/self-updater.js requiring platform-paths (at root): # need: '../../platform-paths' file_dir=$(dirname "$src_file") file_dir_rel="${file_dir#$API_DIR/}" # e.g. "src/docker" # Number of dirs to go up: count slashes + 1 # "src/docker" → 2 dirs → go up 2: ../../platform-paths up_count=$(echo "$file_dir_rel" | awk -F'/' '{print NF}') up_path="" for ((i=0; i/dev/null || true) done < <(find "$API_DIR/src" -type f -name "*.js" -not -path "*/node_modules/*" -not -path "*/__tests__/*" -print0 2>/dev/null) if [[ $GENERIC_PATCHED -gt 0 ]]; then log "Generic src/ require patches: ${GENERIC_PATCHED} fixed" else log "Generic src/ require patches: 0 needed (all correct)" fi TOTAL_PATCHED=$((TOTAL_PATCHED + GENERIC_PATCHED)) TOTAL_ALREADY_OK=$((TOTAL_ALREADY_OK + GENERIC_ALREADY_OK)) # ──────────────────────────────────────────────────────────────────────────── # Patch 4: Ensure license-keygen.js exists at API root # v1.14.4's tarball didn't ship the root-level license-keygen.js. If missing, # restore from src/managers/license-keygen.js or a backup. # ──────────────────────────────────────────────────────────────────────────── LICENSE_ROOT="$API_DIR/license-keygen.js" LICENSE_SRC="$API_DIR/src/managers/license-keygen.js" if [[ ! -f "$LICENSE_ROOT" ]]; then BACKUP_FILE="" # Prefer the v1.13.x backup if it exists (the version that had it at root) if [[ -d "$API_DIR/../updates/backups" ]]; then BACKUP_FILE=$(find "$API_DIR/../updates/backups" -name "license-keygen.js" 2>/dev/null | head -1) fi # Fall back to src/managers/ if newer refactor put it there if [[ -z "$BACKUP_FILE" && -f "$LICENSE_SRC" ]]; then BACKUP_FILE="$LICENSE_SRC" fi # Last resort: search elsewhere if [[ -z "$BACKUP_FILE" ]]; then BACKUP_FILE=$(find /opt/dashcaddy -name "license-keygen.js" -not -path "*/node_modules/*" -not -path "*/updates/*" -not -path "*/backups/staging-*" 2>/dev/null | head -1) fi if [[ -n "$BACKUP_FILE" && -f "$BACKUP_FILE" ]]; then cp -f "$BACKUP_FILE" "$LICENSE_ROOT" log "Restored missing license-keygen.js from $BACKUP_FILE" TOTAL_PATCHED=$((TOTAL_PATCHED + 1)) else log "ERROR: license-keygen.js missing at root and no backup available — build may fail" fi else TOTAL_ALREADY_OK=$((TOTAL_ALREADY_OK + 1)) fi # ──────────────────────────────────────────────────────────────────────────── # Summary # ──────────────────────────────────────────────────────────────────────────── log "=== Post-deploy patch summary: ${TOTAL_PATCHED} require fixes applied, ${TOTAL_ALREADY_OK} components already OK ===" exit 0