feat(deploy): one-command update.sh + preserve config on install re-run
Add update.sh (sudo ./update.sh): git pull --ff-only (never discards local changes), self-update via re-exec if the script itself changed, then hand off to the idempotent install.sh non-interactively. Sets safe.directory so a root-run pull works on the clusev-owned tree. Fix install.sh so a non-interactive re-run (an update) no longer wipes a configured domain / ACME e-mail: both now default to the existing .env value when not supplied. First install (empty = bare IP) is unchanged. README documents 'sudo ./update.sh'; the MOTD shows the update command. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>feat/v1-foundation v0.9.8
parent
1c3d3d6a01
commit
fceb0eef77
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -13,6 +13,26 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
|
|||
|
||||
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
|
||||
|
||||
## [0.9.8] - 2026-06-19
|
||||
|
||||
### Hinzugefügt
|
||||
- **`update.sh` — Ein-Befehl-Update (`sudo ./update.sh`).** Holt den neuesten Stand
|
||||
(`git pull --ff-only`, verwirft nie lokale Änderungen), **aktualisiert sich bei Bedarf selbst**
|
||||
(hat sich `update.sh` im Pull geändert, startet es die neue Version neu) und übergibt dann an den
|
||||
idempotenten `install.sh` (Build, Neustart, Migration, MOTD). Setzt für root automatisch
|
||||
`safe.directory` (das Verzeichnis gehört dem `clusev`-Nutzer, sonst verweigert ein root-`git pull`)
|
||||
und ruft `install.sh` **nicht-interaktiv** auf, sodass nichts neu abgefragt wird.
|
||||
|
||||
### Behoben
|
||||
- **Re-Run von `install.sh` bewahrt jetzt Domain & ACME-E-Mail.** Bislang schrieb der Installer
|
||||
`APP_DOMAIN`/`ACME_EMAIL` bei jedem Lauf — ein nicht-interaktiver Re-Run (also ein Update) ohne
|
||||
gesetzte `CLUSEV_DOMAIN` hätte eine konfigurierte Domain **gelöscht**. Beide defaulten jetzt auf den
|
||||
bestehenden `.env`-Wert; Erst-Installation (leer = Bare-IP) unverändert.
|
||||
|
||||
### Geändert
|
||||
- **README-Update-Abschnitt** auf `sudo ./update.sh` umgestellt (der alte Zweischritt
|
||||
`git pull && sudo ./install.sh` funktioniert weiterhin); MOTD zeigt den Update-Befehl an.
|
||||
|
||||
## [0.9.7] - 2026-06-19
|
||||
|
||||
### Behoben
|
||||
|
|
|
|||
|
|
@ -83,10 +83,13 @@ terminal needed; a small, scoped host service performs the restart).
|
|||
|
||||
```bash
|
||||
cd clusev
|
||||
git pull
|
||||
sudo ./install.sh # idempotent: rebuilds, restarts and migrates — secrets are preserved
|
||||
sudo ./update.sh # pulls the latest code, then rebuilds, restarts and migrates
|
||||
```
|
||||
|
||||
`update.sh` fast-forwards the repo (it never discards local changes), re-runs the idempotent
|
||||
installer non-interactively, and updates itself if the script changed. Secrets and the configured
|
||||
domain / e-mail are preserved. (The older two-step `git pull && sudo ./install.sh` still works.)
|
||||
|
||||
## Account recovery
|
||||
|
||||
The **forgot-password** screen offers self-service recovery: an **e-mail reset link** (valid 15
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
return [
|
||||
// First tagged release is v0.1.0 (semantic, not -dev). The live build hash
|
||||
// is resolved from .git at runtime (see App\Livewire\Versions\Index).
|
||||
'version' => '0.9.7',
|
||||
'version' => '0.9.8',
|
||||
|
||||
// Default user channel. Only 'stable' and 'beta' are ever offered to users.
|
||||
'channel' => 'stable',
|
||||
|
|
|
|||
|
|
@ -42,5 +42,6 @@ printf ' %sDashboard%s %s%s%s\n' "$D" "$R" "$O" "$URL" "$R"
|
|||
printf ' %sStack%s %s\n' "$D" "$R" "$(stack_status)"
|
||||
printf ' %sLogin%s %sStandard-Passwort: clusev — beim ersten Login aendern · Reset: clusev:reset-admin%s\n' "$D" "$R" "$D" "$R"
|
||||
printf ' %sVerwalten%s %sdocker compose -f docker-compose.prod.yml ps | logs -f | restart%s\n' "$D" "$R" "$D" "$R"
|
||||
printf ' %sUpdate%s %ssudo ./update.sh (in %s)%s\n' "$D" "$R" "$D" "$(dirname "$COMPOSE")" "$R"
|
||||
printf ' %s──────────────────────────────────────────────────────────%s\n' "$D" "$R"
|
||||
printf '%s\n' ""
|
||||
|
|
|
|||
10
install.sh
10
install.sh
|
|
@ -45,6 +45,9 @@ set_kv() {
|
|||
fi
|
||||
}
|
||||
rand_hex() { openssl rand -hex "${1:-32}"; }
|
||||
# env_get: read a key's current value from .env (empty if the file or key is absent). Lets a
|
||||
# re-run / update default to the EXISTING domain & ACME e-mail instead of wiping them.
|
||||
env_get() { [ -f "$ENV_FILE" ] && grep -E "^${1}=" "$ENV_FILE" | head -n1 | cut -d= -f2- || true; }
|
||||
|
||||
# ── root + OS detection (before everything) ──────────────────────────
|
||||
[ "$(id -u)" = 0 ] || die "Bitte mit sudo ausfuehren: sudo ./install.sh"
|
||||
|
|
@ -106,8 +109,11 @@ chown -R clusev:clusev .
|
|||
info "Installationsverzeichnis gehoert clusev"
|
||||
|
||||
# ── inputs (interactive at a TTY, else env / bare-IP fallback) ───────
|
||||
DOMAIN="${CLUSEV_DOMAIN:-}"
|
||||
ADMIN_EMAIL="${CLUSEV_ADMIN_EMAIL:-}"
|
||||
# Default to the EXISTING .env values so a non-interactive re-run (i.e. an update via
|
||||
# update.sh) preserves the configured domain / ACME e-mail instead of clearing them. On a
|
||||
# first install .env does not exist yet, so these resolve empty → bare-IP, unchanged.
|
||||
DOMAIN="${CLUSEV_DOMAIN:-$(env_get APP_DOMAIN)}"
|
||||
ADMIN_EMAIL="${CLUSEV_ADMIN_EMAIL:-$(env_get ACME_EMAIL)}"
|
||||
HTTP_PORT="${CLUSEV_HTTP_PORT:-80}"
|
||||
if [ -t 0 ]; then
|
||||
echo; bold "Clusev Installer"; echo "================"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
#!/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)"
|
||||
|
||||
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 </dev/null
|
||||
Loading…
Reference in New Issue