DC-021: build pipeline now ships src/ + hygiene for generated artifacts

The release tarball previously omitted dashcaddy-api/src/, which meant the
in-container self-updater had to apply post-deploy patches (dashcaddy-post-
deploy-patches.sh) to work around missing files. That script generates 37
flat copies of src/ files at the dashcaddy-api/ root level to satisfy
broken require() paths. With proper src/ shipping, those files become
obsolete, but they were still being shown as untracked in git.

Changes:
- BUILD-PIPELINE-FIX.md documents the build pipeline fix (in /opt/dashcaddy-release/
  build-release.sh — sibling repo, not tracked here)
- .gitignore now ignores the 37 generated post-deploy artifacts plus the
  backups/ and updates/ runtime directories, so 'git status' stays clean
- scripts/dashcaddy-post-deploy-patches.sh is now tracked so it's preserved
  across rebuilds (still useful as a safety net for transitional installs)
This commit is contained in:
Krystie
2026-07-01 03:09:59 -07:00
parent 2439ed3e85
commit e73bfbb0a1
3 changed files with 410 additions and 63 deletions
+223
View File
@@ -0,0 +1,223 @@
#!/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>
# 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<up_count; i++)); do
up_path="../$up_path"
done
correct_require="${up_path}${mod_path}"
# Apply the fix: require('./X') → require('../X')
sed -i "s|require('\./${mod_path}')|require('${correct_require}')|g" "$src_file"
log " Patched ${rel_dir}/$(basename "$src_file"): './${mod_path}' → '${correct_require}'"
GENERIC_PATCHED=$((GENERIC_PATCHED + 1))
done < <(grep -E "require\(['\"]\./[a-zA-Z0-9_-]+['\"]\)" "$src_file" 2>/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