Add a workstation deploy script
The Gitea runner is not up yet, so deploys need to be possible by hand. scripts/deploy.sh mirrors the workflow: same hugo build, same rsync invocation, same DEPLOY_PATH guards. It previews every change and reports the number of server-side deletions before asking to continue, since rsync runs with --delete. Credentials live in the gitignored scripts/deploy.env. Two Windows-specific guards, both hit in practice: it refuses an SSH key under /mnt (WSL cannot hold 0600 there, so ssh rejects it) and it fails with a clear message when run from Git Bash, which has no rsync. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,3 +5,6 @@
|
|||||||
|
|
||||||
# Local scratch space
|
# Local scratch space
|
||||||
/.cache/
|
/.cache/
|
||||||
|
|
||||||
|
# Local deploy credentials
|
||||||
|
/scripts/deploy.env
|
||||||
|
|||||||
@@ -102,6 +102,28 @@ the VPS webroot.
|
|||||||
There is one deploy target. For development, run the site locally with
|
There is one deploy target. For development, run the site locally with
|
||||||
`hugo server`.
|
`hugo server`.
|
||||||
|
|
||||||
|
### Deploying from a workstation
|
||||||
|
|
||||||
|
`scripts/deploy.sh` does the same thing without CI — useful while the Gitea
|
||||||
|
runner is not set up. It needs `rsync`, so run it from WSL or another Linux
|
||||||
|
shell, not Git Bash or PowerShell.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp scripts/deploy.env.example scripts/deploy.env # then fill it in (gitignored)
|
||||||
|
./scripts/deploy.sh --dry-run # preview, upload nothing
|
||||||
|
./scripts/deploy.sh # build, preview, confirm, deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Effect |
|
||||||
|
|---|---|
|
||||||
|
| `--dry-run` | Stop after the preview |
|
||||||
|
| `--yes` | Skip the confirmation prompt |
|
||||||
|
| `--skip-build` | Deploy the existing `public/`, e.g. after building on Windows |
|
||||||
|
|
||||||
|
The script previews every change and reports how many files would be **deleted**
|
||||||
|
on the server before asking to continue. Keep the SSH key inside the WSL
|
||||||
|
filesystem — a key under `/mnt/c` cannot hold `0600`, and ssh will reject it.
|
||||||
|
|
||||||
### Required secrets
|
### Required secrets
|
||||||
|
|
||||||
Set these in the Gitea repository settings:
|
Set these in the Gitea repository settings:
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Copy to scripts/deploy.env (gitignored) and fill in.
|
||||||
|
# Must match the values used by the Gitea Actions secrets.
|
||||||
|
|
||||||
|
DEPLOY_HOST=server.example.de
|
||||||
|
DEPLOY_USER=www-deploy
|
||||||
|
DEPLOY_PATH=/var/www/mcg
|
||||||
|
|
||||||
|
# Private half of the deploy key. Keep it inside the WSL filesystem —
|
||||||
|
# a key under /mnt/c or /mnt/d cannot hold 0600 and ssh will reject it.
|
||||||
|
SSH_KEY=$HOME/.ssh/mcg-deploy
|
||||||
Executable
+136
@@ -0,0 +1,136 @@
|
|||||||
|
#!/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 ---
|
||||||
|
|
||||||
|
SSH_CMD="ssh -i $SSH_KEY -o IdentitiesOnly=yes -o StrictHostKeyChecking=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"
|
||||||
Reference in New Issue
Block a user