paper-dynasty-database/scripts/deploy.sh
Cal Corum 900f9723e5 fix: address PR review — unknown flag guard, local var scope, container map
- Reject unknown --flags with error instead of silently treating as commit SHA
- Declare remote_hash as local to prevent stale values across loop iterations
- Use associative array for container names (consistent with DEPLOY_HOST pattern)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 20:32:12 -05:00

204 lines
5.8 KiB
Bash
Executable File

#!/bin/bash
# Deploy Paper Dynasty Database API
# Usage: ./scripts/deploy.sh <dev|prod> [--sync-templates] [commit]
#
# Dev: Force-updates the "dev" git tag → CI builds :dev Docker image
# Prod: Creates CalVer tag + force-updates "latest" and "production" git tags
# → CI builds :<calver>, :latest, :production Docker images
#
# Options:
# --sync-templates Upload changed templates to the target server via scp
#
# Templates are volume-mounted (not in the Docker image). The script always
# checks for template drift and warns if local/remote differ. Pass
# --sync-templates to actually push the changed files.
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
REMOTE="origin"
SYNC_TEMPLATES=false
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMPLATE_DIR="$SCRIPT_DIR/../storage/templates"
# Server config
declare -A DEPLOY_HOST=([dev]="pd-database" [prod]="akamai")
declare -A TEMPLATE_PATH=(
[dev]="/home/cal/container-data/dev-pd-database/storage/templates"
[prod]="/root/container-data/paper-dynasty/storage/templates"
)
usage() {
echo "Usage: $0 <dev|prod> [--sync-templates] [commit]"
echo ""
echo " dev [commit] Force-update 'dev' tag on HEAD or specified commit"
echo " prod [commit] Create CalVer + 'latest' + 'production' tags on HEAD or specified commit"
echo ""
echo "Options:"
echo " --sync-templates Upload changed templates to the target server"
exit 1
}
[[ $# -lt 1 ]] && usage
ENV="$1"
shift
# Parse optional flags
COMMIT="HEAD"
while [[ $# -gt 0 ]]; do
case "$1" in
--sync-templates)
SYNC_TEMPLATES=true
shift
;;
--*)
echo -e "${RED}Unknown option: $1${NC}"
usage
;;
*)
COMMIT="$1"
shift
;;
esac
done
SHA=$(git rev-parse "$COMMIT" 2>/dev/null) || {
echo -e "${RED}Invalid commit: $COMMIT${NC}"
exit 1
}
SHA_SHORT="${SHA:0:7}"
git fetch --tags "$REMOTE"
if ! git branch -a --contains "$SHA" 2>/dev/null | grep -qE '(^|\s)(main|remotes/origin/main)$'; then
echo -e "${RED}Commit $SHA_SHORT is not on main. Aborting.${NC}"
exit 1
fi
# --- Template drift check ---
check_templates() {
local host="${DEPLOY_HOST[$ENV]}"
local remote_path="${TEMPLATE_PATH[$ENV]}"
echo -e "${CYAN}Checking templates against ${host}:${remote_path}...${NC}"
local local_hashes remote_hashes
local_hashes=$(cd "$TEMPLATE_DIR" && md5sum *.html 2>/dev/null | sort -k2)
remote_hashes=$(ssh "$host" "cd '$remote_path' && md5sum *.html 2>/dev/null | sort -k2" 2>/dev/null) || {
echo -e "${YELLOW} Could not reach ${host} — skipping template check.${NC}"
return 0
}
local changed=()
local missing_remote=()
while IFS= read -r line; do
local hash file remote_hash
hash=$(echo "$line" | awk '{print $1}')
file=$(echo "$line" | awk '{print $2}')
remote_hash=$(echo "$remote_hashes" | awk -v f="$file" '$2 == f {print $1}')
if [[ -z "$remote_hash" ]]; then
missing_remote+=("$file")
elif [[ "$hash" != "$remote_hash" ]]; then
changed+=("$file")
fi
done <<<"$local_hashes"
if [[ ${#changed[@]} -eq 0 && ${#missing_remote[@]} -eq 0 ]]; then
echo -e "${GREEN} Templates in sync.${NC}"
return 0
fi
echo -e "${YELLOW} Template drift detected:${NC}"
for f in "${changed[@]+"${changed[@]}"}"; do
[[ -n "$f" ]] && echo -e " ${YELLOW}CHANGED${NC} $f"
done
for f in "${missing_remote[@]+"${missing_remote[@]}"}"; do
[[ -n "$f" ]] && echo -e " ${YELLOW}MISSING${NC} $f (not on server)"
done
if [[ "$SYNC_TEMPLATES" == true ]]; then
echo -e "${CYAN} Syncing templates...${NC}"
for f in "${changed[@]+"${changed[@]}"}" "${missing_remote[@]+"${missing_remote[@]}"}"; do
[[ -n "$f" ]] && scp "$TEMPLATE_DIR/$f" "${host}:${remote_path}/$f"
done
echo -e "${GREEN} Templates synced to ${host}.${NC}"
else
echo -e "${YELLOW} Run with --sync-templates to push changes.${NC}"
fi
}
check_templates
# --- Cached image report ---
declare -A API_CONTAINER=([dev]="dev_pd_database" [prod]="pd_api")
report_cache() {
local host="${DEPLOY_HOST[$ENV]}"
local container="${API_CONTAINER[$ENV]}"
echo -e "${CYAN}Cached card images on ${host} (${container}):${NC}"
ssh "$host" "
png_count=\$(docker exec $container find /app/storage/cards -name '*.png' 2>/dev/null | wc -l)
apng_count=\$(docker exec $container find /app/storage/cards -name '*.apng' 2>/dev/null | wc -l)
echo \" PNG: \${png_count} files\"
echo \" APNG: \${apng_count} files\"
echo \" Total: \$((\${png_count} + \${apng_count})) files\"
" 2>/dev/null || echo -e "${YELLOW} Could not reach ${host} — skipping cache report.${NC}"
}
report_cache
case "$ENV" in
dev)
echo -e "${YELLOW}Deploying to dev...${NC}"
echo -e " Commit: ${SHA_SHORT}"
git tag -f dev "$SHA"
git push "$REMOTE" dev --force
echo -e "${GREEN}Tagged ${SHA_SHORT} as 'dev' and pushed. CI will build :dev image.${NC}"
;;
prod)
echo -e "${YELLOW}Deploying to prod...${NC}"
YEAR=$(date -u +%Y)
MONTH=$(date -u +%-m)
PREFIX="${YEAR}.${MONTH}."
LAST_BUILD=$(git tag -l "${PREFIX}*" | sed "s/^${PREFIX}//" | sort -n | tail -1)
BUILD=$((${LAST_BUILD:-0} + 1))
CALVER="${PREFIX}${BUILD}"
echo -e " Commit: ${SHA_SHORT}"
echo -e " Version: ${CALVER}"
echo -e " Tags: ${CALVER}, latest, production"
read -rp "Proceed? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || {
echo "Aborted."
exit 0
}
git tag "$CALVER" "$SHA"
git tag -f latest "$SHA"
git tag -f production "$SHA"
git push "$REMOTE" "$CALVER"
git push "$REMOTE" latest --force
git push "$REMOTE" production --force
echo -e "${GREEN}Tagged ${SHA_SHORT} as '${CALVER}', 'latest', 'production' and pushed.${NC}"
echo -e "${GREEN}CI will build :${CALVER}, :latest, :production images.${NC}"
;;
*)
usage
;;
esac