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._
|
_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
|
## [0.9.7] - 2026-06-19
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,13 @@ terminal needed; a small, scoped host service performs the restart).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd clusev
|
cd clusev
|
||||||
git pull
|
sudo ./update.sh # pulls the latest code, then rebuilds, restarts and migrates
|
||||||
sudo ./install.sh # idempotent: rebuilds, restarts and migrates — secrets are preserved
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`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
|
## Account recovery
|
||||||
|
|
||||||
The **forgot-password** screen offers self-service recovery: an **e-mail reset link** (valid 15
|
The **forgot-password** screen offers self-service recovery: an **e-mail reset link** (valid 15
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
return [
|
return [
|
||||||
// First tagged release is v0.1.0 (semantic, not -dev). The live build hash
|
// 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).
|
// 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.
|
// Default user channel. Only 'stable' and 'beta' are ever offered to users.
|
||||||
'channel' => 'stable',
|
'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 ' %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 ' %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 ' %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──────────────────────────────────────────────────────────%s\n' "$D" "$R"
|
||||||
printf '%s\n' ""
|
printf '%s\n' ""
|
||||||
|
|
|
||||||
10
install.sh
10
install.sh
|
|
@ -45,6 +45,9 @@ set_kv() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
rand_hex() { openssl rand -hex "${1:-32}"; }
|
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) ──────────────────────────
|
# ── root + OS detection (before everything) ──────────────────────────
|
||||||
[ "$(id -u)" = 0 ] || die "Bitte mit sudo ausfuehren: sudo ./install.sh"
|
[ "$(id -u)" = 0 ] || die "Bitte mit sudo ausfuehren: sudo ./install.sh"
|
||||||
|
|
@ -106,8 +109,11 @@ chown -R clusev:clusev .
|
||||||
info "Installationsverzeichnis gehoert clusev"
|
info "Installationsverzeichnis gehoert clusev"
|
||||||
|
|
||||||
# ── inputs (interactive at a TTY, else env / bare-IP fallback) ───────
|
# ── inputs (interactive at a TTY, else env / bare-IP fallback) ───────
|
||||||
DOMAIN="${CLUSEV_DOMAIN:-}"
|
# Default to the EXISTING .env values so a non-interactive re-run (i.e. an update via
|
||||||
ADMIN_EMAIL="${CLUSEV_ADMIN_EMAIL:-}"
|
# 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}"
|
HTTP_PORT="${CLUSEV_HTTP_PORT:-80}"
|
||||||
if [ -t 0 ]; then
|
if [ -t 0 ]; then
|
||||||
echo; bold "Clusev Installer"; echo "================"
|
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