#!/usr/bin/env bash # Clusev updater — pull the latest code and re-run the idempotent installer (root required). # One command to update an existing install: # # sudo ./update.sh # # In order: # 1. Mark the repo a safe git dir for root. install.sh chowns the tree to the `clusev` user, # so a root-run `git pull` would otherwise refuse with "dubious ownership". # 2. git pull --ff-only on the tracked branch. Aborts cleanly on any conflict / divergence — # it NEVER discards local changes. # 3. Self-update: if update.sh itself changed in the pull, re-exec the NEW copy once (guarded # against loops) so improvements to this very script take effect on the same run. # 4. Hand off to ./install.sh — idempotent: rebuilds the image, restarts the stack, migrates, # refreshes the MOTD and fixes ownership. Run NON-interactively (stdin from /dev/null) so # it never re-prompts; secrets and the configured domain / ACME e-mail are preserved. set -euo pipefail cd "$(dirname "$0")" REPO_DIR="$(pwd)" # set_stage: publish a coarse update stage for the in-browser progress screen (best-effort — the # app container serves run/update-phase.json to it). A write failure must never fail the update. # Written first as "fetch" so the screen leaves the time-guess behind the moment the pull begins, # and so a stale stage from a previous update is reset. set_stage() { mkdir -p "$REPO_DIR/run" 2>/dev/null || true printf '{"stage":"%s","at":%s}\n' "$1" "$(date +%s 2>/dev/null || echo 0)" > "$REPO_DIR/run/update-phase.json" 2>/dev/null || true chmod 644 "$REPO_DIR/run/update-phase.json" 2>/dev/null || true return 0 } set_stage fetch bold() { printf '\033[1m%s\033[0m\n' "$*"; } info() { printf ' %s\n' "$*"; } die() { printf '\033[31m x %s\033[0m\n' "$*" >&2; exit 1; } [ "$(id -u)" = 0 ] || die "Bitte mit sudo ausfuehren: sudo ./update.sh" [ -f docker-compose.prod.yml ] || die "Keine Clusev-Installation hier (docker-compose.prod.yml fehlt)." [ -x ./install.sh ] || [ -f ./install.sh ] || die "install.sh fehlt — Repo unvollstaendig." command -v git >/dev/null 2>&1 || die "git ist nicht installiert." bold "Clusev Update" echo "=============" # Steps 1–3 run only on the first pass; a re-exec (after a self-update) jumps straight to step 4. if [ "${CLUSEV_UPDATE_REEXEC:-0}" != 1 ]; then # 1. let root operate on the clusev-owned working tree git config --global --add safe.directory "$REPO_DIR" 2>/dev/null || true # 2. fast-forward pull only — never auto-merge or throw away local edits before_hash="$(sha256sum -- "$0" | cut -d' ' -f1)" info "Hole Aktualisierungen (git pull --ff-only) ..." git pull --ff-only \ || die "git pull fehlgeschlagen (lokale Aenderungen oder divergierter Branch). Mit 'git status' pruefen und erneut versuchen." after_hash="$(sha256sum -- "$0" | cut -d' ' -f1)" # 3. self-update: relaunch the new update.sh if it changed if [ "$before_hash" != "$after_hash" ]; then info "update.sh wurde aktualisiert — starte die neue Version ..." exec env CLUSEV_UPDATE_REEXEC=1 "$0" "$@" fi fi # 4. apply via the idempotent installer, non-interactively (no prompts; config preserved) info "Wende Update an (install.sh) ..." exec ./install.sh