115 lines
3.4 KiB
Bash
Executable File
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"
|