diff --git a/scripts/deploy_static_site.sh b/scripts/deploy_static_site.sh new file mode 100644 index 0000000..7db9051 --- /dev/null +++ b/scripts/deploy_static_site.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# Deploy static files using release directories and an atomic symlink switch. +# +# This pattern reduces partial deployments because files are copied to a new +# release directory first. The live path changes only when the copy succeeds. +# +# Usage: +# bash deploy_static_site.sh --source ./dist --releases /var/www/releases --current /var/www/current +# bash deploy_static_site.sh --source ./dist --releases /var/www/releases --current /var/www/current --keep 10 +# +# Options: +# --source DIR Directory containing built static files. +# --releases DIR Directory that stores timestamped releases. +# --current PATH Symlink path used by the web server. +# --keep N Number of releases to keep. Default: 5. +# --help Show this help message. + +set -Eeuo pipefail + +source_dir="" +releases_dir="" +current_link="" +keep_releases="5" + +usage() { + sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//' +} + +log() { + printf '[%s] %s\n' "$(date -Is)" "$*" +} + +while [[ "$#" -gt 0 ]]; do + case "$1" in + --source) + source_dir="${2:-}" + shift 2 + ;; + --releases) + releases_dir="${2:-}" + shift 2 + ;; + --current) + current_link="${2:-}" + shift 2 + ;; + --keep) + keep_releases="${2:-}" + [[ "$keep_releases" =~ ^[0-9]+$ ]] || { printf 'ERROR: --keep must be an integer.\n' >&2; exit 1; } + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + printf 'ERROR: unknown option: %s\n' "$1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +[[ -d "$source_dir" ]] || { printf 'ERROR: source directory does not exist: %s\n' "$source_dir" >&2; exit 1; } +[[ -n "$releases_dir" ]] || { printf 'ERROR: --releases is required.\n' >&2; exit 1; } +[[ -n "$current_link" ]] || { printf 'ERROR: --current is required.\n' >&2; exit 1; } + +mkdir -p "$releases_dir" + +release_id="$(date +%Y%m%d-%H%M%S)" +release_path="${releases_dir}/${release_id}" + +log "Creating release directory: ${release_path}" +mkdir -p "$release_path" + +log "Copying files from ${source_dir}" +if command -v rsync >/dev/null 2>&1; then + rsync -a --delete "${source_dir}/" "${release_path}/" +else + cp -a "${source_dir}/." "$release_path/" +fi + +log "Switching current symlink to ${release_path}" +ln -sfn "$release_path" "$current_link" + +log "Removing old releases, keeping the newest ${keep_releases}." +find "$releases_dir" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' \ + | sort -nr \ + | awk -v keep="$keep_releases" 'NR > keep {print $2}' \ + | xargs -r rm -rf + +log "Deployment completed successfully." +