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