Files
mcg-website/scripts/deploy.sh
T
luxick 2fcb2fd219 Don't force StrictHostKeyChecking in the local deploy script
The script runs interactively against a host the operator has already
connected to, so pinning the option added nothing. Removing the override
lets ssh use its default (ask): it consults the user's own known_hosts
and prompts once on first connect, and still refuses a changed host key
afterwards.

Left as-is in CI, which has no prompt and pins the key via the
SSH_KNOWN_HOSTS secret.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:13:58 +02:00

140 lines
4.5 KiB
Bash
Executable File

#!/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"