#!/bin/bash # Deploy Paper Dynasty Database API # Usage: ./scripts/deploy.sh [--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 :, :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 [--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