#!/usr/bin/env bash # # Manual deploy from a workstation, for use until the Gitea runner is up. # Mirrors .gitea/workflows/deploy.yml: same build, same rsync, same guards. # # ./scripts/deploy.sh build, preview the changes, confirm, deploy # ./scripts/deploy.sh --dry-run stop after the preview # ./scripts/deploy.sh --yes skip the confirmation prompt # ./scripts/deploy.sh --skip-build deploy whatever is already in public/ # # Configuration is read from scripts/deploy.env (gitignored); see # scripts/deploy.env.example. Environment variables take precedence. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" DRY_RUN=0 ASSUME_YES=0 SKIP_BUILD=0 for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=1 ;; --yes|-y) ASSUME_YES=1 ;; --skip-build) SKIP_BUILD=1 ;; -h|--help) sed -n '3,13p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "unknown option: $arg (try --help)" >&2; exit 2 ;; esac done die() { echo "error: $*" >&2; exit 1; } # ---------------------------------------------------------------- config --- CONFIG="${DEPLOY_ENV_FILE:-scripts/deploy.env}" if [ -f "$CONFIG" ]; then # shellcheck disable=SC1090 . "$CONFIG" elif [ -z "${DEPLOY_HOST:-}" ]; then die "no $CONFIG and no DEPLOY_HOST in the environment. Copy scripts/deploy.env.example to $CONFIG and fill it in." fi : "${DEPLOY_HOST:?set DEPLOY_HOST}" : "${DEPLOY_USER:?set DEPLOY_USER}" : "${DEPLOY_PATH:?set DEPLOY_PATH}" SSH_KEY="${SSH_KEY:-$HOME/.ssh/mcg-deploy}" # rsync runs with --delete, so the destination becomes an exact mirror of # public/. Refuse to run against an unset, root or shallow path. case "$DEPLOY_PATH" in ""|"/"|"/root"|"/home"|"/var"|"/etc"|"/usr"|"/srv"|"/opt") die "DEPLOY_PATH is unsafe: '$DEPLOY_PATH'" ;; esac if [ "$(printf '%s' "$DEPLOY_PATH" | tr -cd '/' | wc -c)" -lt 2 ]; then die "DEPLOY_PATH '$DEPLOY_PATH' is too shallow to delete into" fi # --------------------------------------------------------- dependencies --- for cmd in rsync ssh; do command -v "$cmd" >/dev/null 2>&1 || die "'$cmd' is not installed. Run this script from WSL or another Linux shell — Git Bash and PowerShell do not ship rsync." done # ------------------------------------------------------------- ssh key --- [ -f "$SSH_KEY" ] || die "ssh key not found: $SSH_KEY" case "$SSH_KEY" in /mnt/*) die "the ssh key is on the Windows filesystem ($SSH_KEY). WSL cannot hold 0600 there, so ssh will reject the key. Copy it into the WSL home directory instead: cp '$SSH_KEY' ~/.ssh/mcg-deploy && chmod 600 ~/.ssh/mcg-deploy" ;; esac perms="$(stat -c '%a' "$SSH_KEY" 2>/dev/null || echo '')" if [ -n "$perms" ] && [ "$perms" != "600" ] && [ "$perms" != "400" ]; then echo "==> Tightening permissions on $SSH_KEY (was $perms)" chmod 600 "$SSH_KEY" fi # --------------------------------------------------------------- build --- if [ "$SKIP_BUILD" -eq 0 ]; then command -v hugo >/dev/null 2>&1 || die "hugo is not on PATH. Either install Hugo 0.164.0 in WSL, or build on the Windows side and re-run this script with --skip-build." echo "==> Building" rm -rf public hugo --gc --minify fi [ -f public/index.html ] || \ die "public/index.html missing - refusing to deploy an empty build" # -------------------------------------------------------------- deploy --- # No StrictHostKeyChecking override: ssh falls back to its default (ask), so it # uses your personal ~/.ssh/known_hosts and prompts once on first connect. CI # pins it to yes instead, because it has no prompt and a known_hosts secret. SSH_CMD="ssh -i $SSH_KEY -o IdentitiesOnly=yes" TARGET="${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/" RSYNC_OPTS=(-az --delete --checksum) preview="$(mktemp)" trap 'rm -f "$preview"' EXIT echo "==> Previewing changes against $TARGET" rsync "${RSYNC_OPTS[@]}" --itemize-changes --dry-run \ -e "$SSH_CMD" public/ "$TARGET" | tee "$preview" deletions="$(grep -c '^\*deleting' "$preview" || true)" updates="$(grep -cv '^\*deleting' "$preview" || true)" echo echo "==> $updates file(s) to send, $deletions to delete on the server" if [ "$DRY_RUN" -eq 1 ]; then echo "==> Dry run only, nothing was uploaded." exit 0 fi if [ "$ASSUME_YES" -eq 0 ]; then printf 'Continue? [y/N] ' read -r reply case "$reply" in [yY]|[yY][eE][sS]) ;; *) echo "aborted"; exit 1 ;; esac fi echo "==> Deploying" rsync "${RSYNC_OPTS[@]}" --human-readable --stats \ -e "$SSH_CMD" public/ "$TARGET" echo "==> Done"