Files
Labyricorn ec6e21bb25
Deploy production / deploy (push) Successful in 4s
Add repository-owned project publishing
2026-08-11 23:24:55 -07:00

115 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
deploy_user="labyricorn-deploy"
deploy_home="/srv/labyricorn"
if [[ "$(id -u)" -eq 0 ]]; then
exec sudo -u "$deploy_user" env \
HOME="$deploy_home" \
PATH="$deploy_home/.local/bin:/usr/local/bin:/usr/bin:/bin" \
"$0" "$@"
fi
if [[ "$(id -un)" != "$deploy_user" ]]; then
printf 'deploy-labyricorn must run as root or %s\n' "$deploy_user" >&2
exit 1
fi
repo="$deploy_home/repo"
releases="$deploy_home/releases"
current="$deploy_home/current"
logs="$deploy_home/logs"
project_cache="$deploy_home/project-cache"
lektor="$deploy_home/.local/bin/lektor"
lektor_python="$deploy_home/.local/pipx/venvs/lektor/bin/python"
lock_file="$logs/deploy.lock"
log_file="$logs/deploy.log"
release_dir=""
log() {
printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*" | tee -a "$log_file"
}
cleanup() {
status=$?
if [[ "$status" -ne 0 && -n "$release_dir" && -d "$release_dir" ]]; then
rm -rf -- "$release_dir"
fi
if [[ "$status" -ne 0 ]]; then
log "deployment failed status=$status"
fi
exit "$status"
}
trap cleanup EXIT
mkdir -p "$releases" "$logs" "$project_cache"
exec 9>"$lock_file"
if ! flock -n 9; then
log "deployment skipped reason=lock-held"
exit 0
fi
if [[ -n "$(git -C "$repo" status --porcelain)" ]]; then
log "deployment refused reason=dirty-working-tree"
exit 1
fi
log "deployment started"
git -C "$repo" fetch --prune origin
git -C "$repo" rev-parse --verify --quiet refs/remotes/origin/main >/dev/null
git -C "$repo" switch main
git -C "$repo" reset --hard origin/main
site_commit="$(git -C "$repo" rev-parse HEAD)"
release_name="$(date -u +'%Y%m%dT%H%M%SZ')-${site_commit:0:12}"
release_dir="$releases/$release_name"
if [[ ! -x "$lektor" || ! -x "$lektor_python" ]]; then
log "deployment refused reason=missing-lektor-runtime"
exit 1
fi
"$lektor_python" "$repo/scripts/build_with_projects.py" \
--site-root "$repo" \
--cache-path "$project_cache" \
--output-path "$release_dir" \
--lektor "$lektor"
test -f "$release_dir/index.html"
test -f "$release_dir/.labyricorn-projects.json"
printf '%s\n' "$site_commit" >"$release_dir/.labyricorn-commit"
manifest_signature() {
"$lektor_python" -c 'import json,sys; d=json.load(open(sys.argv[1], encoding="utf-8")); p={k:{"commit":v["commit"],"metadata_digest":v["metadata_digest"]} for k,v in d.get("projects",{}).items()}; print(json.dumps({"site_commit":d.get("site_commit"),"projects":p},sort_keys=True,separators=(",",":")))' "$1"
}
if [[ -f "$current/.labyricorn-projects.json" ]]; then
new_signature="$(manifest_signature "$release_dir/.labyricorn-projects.json")"
current_signature="$(manifest_signature "$current/.labyricorn-projects.json")"
if [[ "$new_signature" == "$current_signature" ]]; then
rm -rf -- "$release_dir"
release_dir=""
log "deployment unchanged commit=$site_commit"
exit 0
fi
fi
chmod -R u=rwX,go=rX "$release_dir"
ln -sfn "releases/$release_name" "$deploy_home/current.next"
mv -Tf "$deploy_home/current.next" "$current"
release_dir=""
mapfile -t old_releases < <(
find "$releases" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' \
| sort -nr \
| awk 'NR > 5 {sub(/^[^ ]+ /, ""); print}'
)
for old_release in "${old_releases[@]}"; do
if [[ "$(readlink -f "$old_release")" != "$(readlink -f "$current")" ]]; then
rm -rf -- "$old_release"
fi
done
log "deployment succeeded release=$release_name commit=$site_commit"