feat: redesign CMS and unify German admin UI

This commit is contained in:
2026-09-17 16:51:24 +02:00
parent a2b3e99174
commit 7ec0c51ff5
129 changed files with 952 additions and 550 deletions
+15 -15
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
# ANOUMA — server-side deployment script.
# ANOUMA - server-side deployment script.
#
# This is NOT a manual update tool. It is invoked exclusively by GitHub
# Actions over SSH as the restricted `anouma-deploy` user (see setup.sh,
# which installs a forced-command SSH key that can only ever run this
# script). The production server never builds an image — it only ever
# script). The production server never builds an image - it only ever
# pulls an already-built, already-tested tag from the registry.
#
# deploy.sh deploy vX.Y.Z
@@ -26,10 +26,10 @@ fail() { log_error "$1"; exit 1; }
# ---------------------------------------------------------------------------
# When invoked through the restricted SSH key (see setup.sh), OpenSSH's
# forced "command=" ignores whatever the SSH client asked for and runs this
# script with NO arguments — the client's actual request arrives in
# script with NO arguments - the client's actual request arrives in
# $SSH_ORIGINAL_COMMAND instead. Re-split it into "$@" so the rest of this
# script doesn't need to care which path it came from. This is intentionally
# unquoted (word-splitting, not `eval`) — no shell metacharacter in
# unquoted (word-splitting, not `eval`) - no shell metacharacter in
# SSH_ORIGINAL_COMMAND is ever interpreted as code, it just becomes literal
# argument text, which the strict version regex below then validates.
# ---------------------------------------------------------------------------
@@ -41,13 +41,13 @@ fi
ACTION="${1:-}"
VERSION="${2:-}"
[ -f .env ] || fail ".env not found — run setup.sh on this server first."
[ -f .env ] || fail ".env not found - run setup.sh on this server first."
COMPOSE="docker compose"
docker compose version >/dev/null 2>&1 || COMPOSE="docker-compose"
# ---------------------------------------------------------------------------
# Deployment lock — never run two deployments concurrently.
# Deployment lock - never run two deployments concurrently.
# ---------------------------------------------------------------------------
LOCK_FILE="$(pwd)/.deploy.lock"
if [ -f "$LOCK_FILE" ] && kill -0 "$(cat "$LOCK_FILE")" 2>/dev/null; then
@@ -58,10 +58,10 @@ echo $$ > "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT
# ---------------------------------------------------------------------------
# .env helpers — same idempotent read/write approach as setup.sh.
# .env helpers - same idempotent read/write approach as setup.sh.
# ---------------------------------------------------------------------------
env_get() { grep -E "^$1=" .env 2>/dev/null | tail -n1 | cut -d'=' -f2- || true; }
# Quoted the same way as setup.sh's env_set — this file is `source`d as a
# Quoted the same way as setup.sh's env_set - this file is `source`d as a
# bash script below, so an unquoted value with shell metacharacters would
# break parsing.
env_set() {
@@ -98,7 +98,7 @@ backup_database() {
$COMPOSE exec -T postgres pg_dump -U "${POSTGRES_USER:-postgres}" "${POSTGRES_DB:-anouma}" | gzip > "$file"
log_success "Backup created ($(du -h "$file" | cut -f1))."
else
log_warn "Postgres is not running yet — skipping backup (first-ever deploy?)."
log_warn "Postgres is not running yet - skipping backup (first-ever deploy?)."
fi
local retention="${BACKUP_RETENTION_DAYS:-14}"
find backups -name 'database-*.sql.gz' -mtime "+${retention}" -not -name "$(basename "$file")" -delete 2>/dev/null || true
@@ -108,7 +108,7 @@ registry_login() {
if [ -n "${REGISTRY_USERNAME:-}" ] && [ -n "${REGISTRY_PASSWORD:-}" ]; then
echo "$REGISTRY_PASSWORD" | docker login "${REGISTRY:-ghcr.io}" -u "$REGISTRY_USERNAME" --password-stdin >/dev/null 2>&1 \
&& log_success "Registry login OK." \
|| fail "Registry login failed — check REGISTRY_USERNAME/REGISTRY_PASSWORD in .env."
|| fail "Registry login failed - check REGISTRY_USERNAME/REGISTRY_PASSWORD in .env."
fi
}
@@ -122,7 +122,7 @@ set_image_version() {
# One full deploy attempt for a given version tag: pull → migrate → start →
# healthcheck. Returns non-zero on any failure without partially cleaning up
# — the caller decides whether to retry with the previous version.
# - the caller decides whether to retry with the previous version.
deploy_version() {
local version="$1"
set_image_version "$version"
@@ -159,18 +159,18 @@ do_deploy() {
log_error "${version} FAILED post-deploy healthcheck."
if [ -z "$previous_version" ]; then
fail "No previous version on record — nothing to roll back to. Check: $COMPOSE logs app"
fail "No previous version on record - nothing to roll back to. Check: $COMPOSE logs app"
fi
log_warn "Rolling back to ${previous_version} …"
if deploy_version "$previous_version"; then
log_warn "Rollback to ${previous_version} succeeded — production is back on the previous version."
log_warn "Database migrations from the failed deploy were NOT reverted (see DEPLOYMENT.md) — restore the backup above manually if ${version} shipped an incompatible migration."
log_warn "Rollback to ${previous_version} succeeded - production is back on the previous version."
log_warn "Database migrations from the failed deploy were NOT reverted (see DEPLOYMENT.md) - restore the backup above manually if ${version} shipped an incompatible migration."
echo "$previous_version" > .current-version
exit 1
fi
fail "Rollback to ${previous_version} ALSO failed — manual intervention required: $COMPOSE logs app"
fail "Rollback to ${previous_version} ALSO failed - manual intervention required: $COMPOSE logs app"
}
case "$ACTION" in