Overhaul deployment to Gitea Actions CI/CD, remove update.sh
Production no longer builds from source or self-updates via cron/git pull:
setup.sh now only provisions the server once (Docker, /opt/anouma, a
restricted `anouma-deploy` SSH user whose key can only ever run
deploy.sh, generated secrets). All future deployments run through
.gitea/workflows/ci.yml (lint/typecheck/test/build on every push) and
release.yml (on a vX.Y.Z tag: build the image, push it to the Gitea
registry, then SSH-trigger deploy.sh on the server), which pulls,
migrates, restarts, healthchecks, backs up the database first, and
automatically rolls back the code on a failed healthcheck.
docker-compose.yml's app service now runs a registry image
(${ANOUMA_IMAGE}) instead of building locally.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# ANOUMA — automated setup.
|
||||
# ANOUMA — production server installer.
|
||||
#
|
||||
# Fresh install:
|
||||
# git clone https://git.maro.run/maro/anouma.git && cd anouma && ./setup.sh
|
||||
# git clone https://git.maro.run/maro/anouma.git && cd anouma
|
||||
# chmod +x setup.sh && ./setup.sh
|
||||
#
|
||||
# Safe to re-run on an existing installation: never overwrites secrets that
|
||||
# already exist in .env, never deletes volumes/networks/containers it
|
||||
# doesn't own, and never touches Nginx Proxy Manager (or any other existing
|
||||
# container) beyond optionally joining its Docker network.
|
||||
# Prepares a fresh Ubuntu/Debian server for ANOUMA: installs Docker if
|
||||
# needed, creates /opt/anouma, writes a production .env with generated
|
||||
# secrets, creates a restricted `anouma-deploy` SSH user for Gitea Actions,
|
||||
# and installs the server-side deploy.sh + docker-compose.yml.
|
||||
#
|
||||
# From then on, Gitea Actions owns all deployments (see
|
||||
# .gitea/workflows/release.yml) — there is no update.sh. Pushing a release
|
||||
# tag (vX.Y.Z) is the only way production gets updated.
|
||||
#
|
||||
# Safe to re-run: never regenerates an existing secret or SSH key, never
|
||||
# deletes a volume/database/user, never touches Nginx Proxy Manager (or any
|
||||
# other existing container) beyond optionally joining its Docker network.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
REPO_DIR="$(pwd)"
|
||||
PROD_DIR="/opt/anouma"
|
||||
DEPLOY_USER="anouma-deploy"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -19,12 +30,8 @@ log_info() { printf "${COLOR_INFO}[INFO]${COLOR_RESET} %s\n" "$1"; }
|
||||
log_warn() { printf "${COLOR_WARN}[WARN]${COLOR_RESET} %s\n" "$1"; }
|
||||
log_error() { printf "${COLOR_ERROR}[ERROR]${COLOR_RESET} %s\n" "$1" >&2; }
|
||||
log_success() { printf "${COLOR_OK}[SUCCESS]${COLOR_RESET} %s\n" "$1"; }
|
||||
|
||||
fail() {
|
||||
log_error "$1"
|
||||
[ -n "${2:-}" ] && log_error " → $2"
|
||||
exit 1
|
||||
}
|
||||
step() { printf "\n${COLOR_INFO}== %s ==${COLOR_RESET}\n" "$1"; }
|
||||
fail() { log_error "$1"; [ -n "${2:-}" ] && log_error " → $2"; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Args
|
||||
@@ -42,19 +49,40 @@ for arg in "$@"; do
|
||||
esac
|
||||
done
|
||||
|
||||
# ask PROMPT DEFAULT [ENV_VAR_NAME] -> echoes the answer.
|
||||
# In --non-interactive mode: uses a real environment variable of the given
|
||||
# name if the caller exported one (e.g. SMTP_HOST=... ./setup.sh
|
||||
# --non-interactive), otherwise falls back to DEFAULT.
|
||||
ask() {
|
||||
# ask "Prompt" "default" -> echoes the answer
|
||||
local prompt="$1" default="$2" answer
|
||||
local prompt="$1" default="$2" env_name="${3:-}" answer
|
||||
if [ "$NON_INTERACTIVE" = true ] || [ ! -t 0 ]; then
|
||||
if [ -n "$env_name" ]; then
|
||||
answer="$(eval "echo \"\${$env_name:-}\"")"
|
||||
[ -n "$answer" ] && { echo "$answer"; return; }
|
||||
fi
|
||||
echo "$default"
|
||||
return
|
||||
fi
|
||||
read -r -p "$prompt [$default]: " answer || true
|
||||
if [ -n "$default" ]; then
|
||||
read -r -p "$prompt [Default: $default]: " answer || true
|
||||
else
|
||||
read -r -p "$prompt: " answer || true
|
||||
fi
|
||||
echo "${answer:-$default}"
|
||||
}
|
||||
|
||||
ask_secret() {
|
||||
local prompt="$1" env_name="${2:-}" answer=""
|
||||
if [ "$NON_INTERACTIVE" = true ] || [ ! -t 0 ]; then
|
||||
[ -n "$env_name" ] && answer="$(eval "echo \"\${$env_name:-}\"")"
|
||||
echo "$answer"
|
||||
return
|
||||
fi
|
||||
read -r -s -p "$prompt: " answer || true; echo "" >&2
|
||||
echo "$answer"
|
||||
}
|
||||
|
||||
ask_yn() {
|
||||
# ask_yn "Prompt" "Y|N" -> echoes true/false
|
||||
local prompt="$1" default="$2" answer
|
||||
if [ "$NON_INTERACTIVE" = true ] || [ ! -t 0 ]; then
|
||||
[ "$default" = "Y" ] && echo true || echo false
|
||||
@@ -75,52 +103,141 @@ echo "============"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1–4: detect tooling
|
||||
# 3. System check
|
||||
# ---------------------------------------------------------------------------
|
||||
command -v docker >/dev/null 2>&1 || fail "Docker wurde nicht gefunden." "Installiere Docker: https://docs.docker.com/engine/install/"
|
||||
log_success "Docker erkannt: $(docker --version)"
|
||||
step "System check"
|
||||
|
||||
docker info >/dev/null 2>&1 || fail "Der Docker-Daemon läuft nicht (oder fehlende Berechtigung)." "Starte Docker bzw. führe dieses Skript mit einem Nutzer aus, der Docker verwenden darf."
|
||||
OS_NAME="$(uname -s)"
|
||||
ARCH_NAME="$(uname -m)"
|
||||
log_info "OS: $OS_NAME, Architecture: $ARCH_NAME"
|
||||
[ "$OS_NAME" = "Linux" ] || log_warn "Dieses Script wurde für Linux (Ubuntu/Debian) gebaut — andere Betriebssysteme werden nicht offiziell unterstützt."
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
SUDO=""
|
||||
else
|
||||
command -v sudo >/dev/null 2>&1 || fail "Dieses Script braucht root-Rechte (Systembenutzer, /opt, Paketinstallation)." "Als root ausführen oder sudo installieren."
|
||||
SUDO="sudo"
|
||||
log_info "Läuft ohne root — verwende sudo für privilegierte Schritte."
|
||||
fi
|
||||
|
||||
PKG_INSTALL=""
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
PKG_INSTALL="$SUDO apt-get install -y"
|
||||
fi
|
||||
|
||||
ensure_tool() {
|
||||
local bin="$1" pkg="$2"
|
||||
if command -v "$bin" >/dev/null 2>&1; then
|
||||
log_success "$bin gefunden."
|
||||
return
|
||||
fi
|
||||
if [ -n "$PKG_INSTALL" ]; then
|
||||
log_warn "$bin nicht gefunden — installiere Paket '$pkg' …"
|
||||
$SUDO apt-get update -y >/dev/null 2>&1 || true
|
||||
$PKG_INSTALL "$pkg" || fail "$bin konnte nicht automatisch installiert werden." "Installiere '$pkg' manuell und starte setup.sh erneut."
|
||||
command -v "$bin" >/dev/null 2>&1 || fail "$bin fehlt weiterhin nach der Installation."
|
||||
log_success "$bin installiert."
|
||||
else
|
||||
fail "$bin wurde nicht gefunden und kein unterstützter Paketmanager (apt) ist vorhanden." "Installiere '$pkg' manuell."
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_tool git git
|
||||
ensure_tool openssl openssl
|
||||
ensure_tool curl curl
|
||||
ensure_tool ssh-keygen openssh-client
|
||||
command -v wget >/dev/null 2>&1 || log_info "wget nicht gefunden — wird hier nicht benötigt."
|
||||
|
||||
if systemctl is-active --quiet ssh 2>/dev/null || systemctl is-active --quiet sshd 2>/dev/null; then
|
||||
log_success "SSH-Server läuft."
|
||||
else
|
||||
log_warn "Kein laufender SSH-Server erkannt (ssh/sshd) — Gitea Actions kann ohne einen laufenden SSH-Server nicht deployen. Installiere/starte z. B. 'openssh-server'."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Docker
|
||||
# ---------------------------------------------------------------------------
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "Docker ist nicht installiert."
|
||||
INSTALL_DOCKER="$(ask_yn "Soll Docker automatisch installiert werden?" "Y")"
|
||||
if [ "$INSTALL_DOCKER" = true ]; then
|
||||
log_info "Installiere Docker (offizielles Installationsscript get.docker.com) …"
|
||||
curl -fsSL https://get.docker.com | $SUDO sh || fail "Docker-Installation fehlgeschlagen." "Installiere Docker manuell: https://docs.docker.com/engine/install/"
|
||||
$SUDO systemctl enable --now docker >/dev/null 2>&1 || true
|
||||
log_success "Docker installiert."
|
||||
else
|
||||
fail "Docker wird benötigt." "Installiere Docker manuell: https://docs.docker.com/engine/install/"
|
||||
fi
|
||||
else
|
||||
log_success "Docker erkannt: $(docker --version)"
|
||||
fi
|
||||
|
||||
# Figure out whether docker commands need sudo in this session (e.g. right
|
||||
# after a fresh install, before the invoking user's docker-group membership
|
||||
# has taken effect).
|
||||
DOCKER="docker"
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
if $SUDO docker info >/dev/null 2>&1; then
|
||||
DOCKER="$SUDO docker"
|
||||
log_warn "Docker braucht hier noch sudo (Gruppenmitgliedschaft wirkt erst nach erneutem Login) — setup.sh verwendet sudo für diesen Lauf."
|
||||
else
|
||||
fail "Docker-Daemon läuft nicht oder ist nicht erreichbar." "Starte Docker: sudo systemctl start docker"
|
||||
fi
|
||||
fi
|
||||
|
||||
COMPOSE=""
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
COMPOSE="docker compose"
|
||||
if $DOCKER compose version >/dev/null 2>&1; then
|
||||
COMPOSE="$DOCKER compose"
|
||||
elif command -v docker-compose >/dev/null 2>&1; then
|
||||
COMPOSE="docker-compose"
|
||||
fi
|
||||
[ -n "$COMPOSE" ] || fail "Docker Compose wurde nicht gefunden." "Installiere das Compose-Plugin: https://docs.docker.com/compose/install/"
|
||||
log_success "Docker Compose erkannt: $($COMPOSE version | head -n1)"
|
||||
|
||||
if command -v git >/dev/null 2>&1; then
|
||||
log_success "Git erkannt: $(git --version)"
|
||||
else
|
||||
log_warn "Git wurde nicht gefunden — für update.sh (Release-Erkennung) empfohlen, aber für den Betrieb selbst nicht zwingend nötig."
|
||||
fi
|
||||
|
||||
# Node/npm are NOT required on the host — everything runs inside the app
|
||||
# container. This is purely informational.
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
log_info "Node.js auf dem Host erkannt: $(node --version) (wird nicht benötigt — ANOUMA läuft in Docker)"
|
||||
log_info "Node.js auf dem Host erkannt: $(node --version) (wird nicht benötigt — ANOUMA läuft und wird ausschließlich in Docker gebaut/betrieben)."
|
||||
else
|
||||
log_info "Node.js nicht auf dem Host installiert — kein Problem, ANOUMA läuft vollständig in Docker."
|
||||
log_info "Node.js nicht auf dem Host installiert — das ist beabsichtigt, npm wird nur innerhalb von Docker (CI-Build) verwendet."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5–6: detect existing Docker networks, look for a reverse proxy (e.g. NPM)
|
||||
# 4. /opt/anouma
|
||||
# ---------------------------------------------------------------------------
|
||||
log_info "Prüfe vorhandene Docker-Netzwerke …"
|
||||
EXISTING_NETWORKS="$(docker network ls --format '{{.Name}}' 2>/dev/null || true)"
|
||||
step "Projektverzeichnis"
|
||||
|
||||
FRESH_INSTALL=false
|
||||
if [ -d "$PROD_DIR" ] && [ -f "$PROD_DIR/.env" ]; then
|
||||
echo "ANOUMA installation already exists at $PROD_DIR."
|
||||
echo ""
|
||||
echo "1. Bestehende Installation verwenden"
|
||||
echo "2. Setup abbrechen"
|
||||
CHOICE="$(ask "Auswahl" "1")"
|
||||
case "$CHOICE" in
|
||||
2) log_info "Setup abgebrochen. Keine Änderungen vorgenommen."; exit 0 ;;
|
||||
*) log_info "Verwende bestehende Installation in $PROD_DIR — bestehende Daten bleiben unangetastet." ;;
|
||||
esac
|
||||
else
|
||||
FRESH_INSTALL=true
|
||||
$SUDO mkdir -p "$PROD_DIR"
|
||||
log_success "$PROD_DIR erstellt."
|
||||
fi
|
||||
$SUDO mkdir -p "$PROD_DIR/backups" "$PROD_DIR/media" "$PROD_DIR/.deploy"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Docker network / Nginx Proxy Manager detection
|
||||
# ---------------------------------------------------------------------------
|
||||
step "Docker-Netzwerk"
|
||||
|
||||
EXISTING_NETWORKS="$($DOCKER network ls --format '{{.Name}}' 2>/dev/null || true)"
|
||||
|
||||
detect_npm_network() {
|
||||
# 1) Look for a running container that looks like Nginx Proxy Manager and
|
||||
# read the network it's actually attached to.
|
||||
local npm_container
|
||||
npm_container="$(docker ps --format '{{.Names}}\t{{.Image}}' 2>/dev/null | grep -iE 'nginx-proxy-manager|jc21/nginx-proxy-manager' | head -n1 | cut -f1 || true)"
|
||||
npm_container="$($DOCKER ps --format '{{.Names}}\t{{.Image}}' 2>/dev/null | grep -iE 'nginx-proxy-manager|jc21/nginx-proxy-manager' | head -n1 | cut -f1 || true)"
|
||||
if [ -n "$npm_container" ]; then
|
||||
docker inspect "$npm_container" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' 2>/dev/null | grep -v '^bridge$' | head -n1
|
||||
$DOCKER inspect "$npm_container" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' 2>/dev/null | grep -v '^bridge$' | head -n1
|
||||
return
|
||||
fi
|
||||
# 2) Fall back to a name heuristic among existing networks.
|
||||
echo "$EXISTING_NETWORKS" | grep -iE 'nginx-proxy-manager|^npm' | head -n1 || true
|
||||
}
|
||||
|
||||
@@ -134,57 +251,78 @@ else
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7–10: .env — create if missing, fill in only missing keys otherwise,
|
||||
# generate secrets, never overwrite existing ones
|
||||
# 6–8. .env — full generation, secrets, interactive questions
|
||||
# ---------------------------------------------------------------------------
|
||||
step ".env"
|
||||
|
||||
ENV_FILE="$PROD_DIR/.env"
|
||||
gen_secret() { openssl rand -hex 32; }
|
||||
|
||||
FRESH_ENV=false
|
||||
if [ ! -f .env ]; then
|
||||
FRESH_ENV=true
|
||||
cp .env.example .env
|
||||
log_info ".env aus .env.example erstellt."
|
||||
fi
|
||||
|
||||
env_get() { grep -E "^$1=" .env 2>/dev/null | tail -n1 | cut -d'=' -f2- || true; }
|
||||
env_get() { $SUDO grep -E "^$1=" "$ENV_FILE" 2>/dev/null | tail -n1 | cut -d'=' -f2- || true; }
|
||||
# Rewrites the file via a local temp file + `printf` (normal argument
|
||||
# passing, never re-interpolated into a nested shell string) so arbitrary
|
||||
# characters in $value — quotes, backslashes, anything a typed SMTP/registry
|
||||
# password might contain — can never break quoting or be misinterpreted.
|
||||
env_set() {
|
||||
# env_set KEY VALUE — replaces an existing (possibly empty) line, or
|
||||
# appends the key if it's missing entirely. Never touches other keys.
|
||||
local key="$1" value="$2"
|
||||
if grep -qE "^$key=" .env; then
|
||||
local tmp; tmp="$(mktemp)"
|
||||
awk -v k="$key" -v v="$value" 'BEGIN{FS=OFS="="} $1==k{$0=k "=" v} {print}' .env > "$tmp" && mv "$tmp" .env
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value" >> .env
|
||||
local key="$1" value="$2" tmp
|
||||
tmp="$(mktemp)"
|
||||
if $SUDO test -f "$ENV_FILE"; then
|
||||
$SUDO grep -vE "^${key}=" "$ENV_FILE" > "$tmp" 2>/dev/null || true
|
||||
fi
|
||||
printf '%s=%s\n' "$key" "$value" >> "$tmp"
|
||||
$SUDO cp "$tmp" "$ENV_FILE"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
# Only fills the key in if it is currently missing or blank — this is what
|
||||
# makes secret generation safe to re-run (existing secrets survive updates).
|
||||
# Only fills a key in if currently missing/blank — makes generation safe to
|
||||
# re-run (existing secrets/config survive every future setup.sh run).
|
||||
env_ensure() {
|
||||
local key="$1" value="$2"
|
||||
local current; current="$(env_get "$key")"
|
||||
if [ -z "$current" ]; then
|
||||
env_set "$key" "$value"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
[ -z "$(env_get "$key")" ] || return 1
|
||||
env_set "$key" "$value"
|
||||
}
|
||||
|
||||
if [ "$FRESH_ENV" = true ]; then
|
||||
log_info "Domain für diese Installation (z. B. https://anouma.org):"
|
||||
APP_URL="$(ask "Domain" "http://localhost:3000")"
|
||||
env_set NEXT_PUBLIC_SERVER_URL "$APP_URL"
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
log_info "Erstelle neue $ENV_FILE …"
|
||||
$SUDO tee "$ENV_FILE" >/dev/null <<'EOF'
|
||||
# Generated by setup.sh — see DEPLOYMENT.md for the full reference.
|
||||
DATABASE_URI=
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_DB=anouma
|
||||
PAYLOAD_SECRET=
|
||||
MEETING_SESSION_SECRET=
|
||||
CRON_SECRET=
|
||||
NEXT_PUBLIC_SERVER_URL=
|
||||
APP_PORT=3000
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM=
|
||||
STUN_SERVER=stun:stun.l.google.com:19302
|
||||
TURN_SERVER=
|
||||
TURN_USERNAME=
|
||||
TURN_PASSWORD=
|
||||
GOOGLE_SITE_VERIFICATION=
|
||||
DOCKER_NETWORK=anouma-network
|
||||
NPM_NETWORK=
|
||||
BACKUP_RETENTION_DAYS=14
|
||||
REGISTRY=git.maro.run
|
||||
REGISTRY_REPO=maro/anouma
|
||||
REGISTRY_USERNAME=
|
||||
REGISTRY_PASSWORD=
|
||||
ANOUMA_IMAGE=
|
||||
AUTO_DEPLOY=true
|
||||
EOF
|
||||
fi
|
||||
|
||||
# --- secrets --------------------------------------------------------------
|
||||
env_ensure PAYLOAD_SECRET "$(gen_secret)" && log_info "PAYLOAD_SECRET generiert." || log_info "PAYLOAD_SECRET bereits vorhanden — unverändert."
|
||||
env_ensure MEETING_SESSION_SECRET "$(gen_secret)" && log_info "MEETING_SESSION_SECRET generiert." || log_info "MEETING_SESSION_SECRET bereits vorhanden — unverändert."
|
||||
env_ensure CRON_SECRET "$(gen_secret)" && log_info "CRON_SECRET generiert." || log_info "CRON_SECRET bereits vorhanden — unverändert."
|
||||
|
||||
if env_ensure POSTGRES_PASSWORD "$(gen_secret)"; then
|
||||
log_info "POSTGRES_PASSWORD generiert."
|
||||
# Keep DATABASE_URI in sync with the freshly generated password so the
|
||||
# app can actually connect — only rewritten when we just generated the
|
||||
# password ourselves, never when POSTGRES_PASSWORD already existed.
|
||||
PG_USER="$(env_get POSTGRES_USER)"; PG_USER="${PG_USER:-postgres}"
|
||||
PG_DB="$(env_get POSTGRES_DB)"; PG_DB="${PG_DB:-anouma}"
|
||||
PG_PASSWORD="$(env_get POSTGRES_PASSWORD)"
|
||||
@@ -193,62 +331,211 @@ else
|
||||
log_info "POSTGRES_PASSWORD bereits vorhanden — unverändert."
|
||||
fi
|
||||
|
||||
if [ "$FRESH_ENV" = true ]; then
|
||||
echo ""
|
||||
# --- interactive questions (only for values with no safe automatic value) -
|
||||
if [ -z "$(env_get NEXT_PUBLIC_SERVER_URL)" ]; then
|
||||
DOMAIN="$(ask "Domain" "anouma.org" "DOMAIN")"
|
||||
APP_URL="$(ask "Application URL" "https://$DOMAIN" "APP_URL")"
|
||||
env_set NEXT_PUBLIC_SERVER_URL "$APP_URL"
|
||||
[ -z "$(env_get SMTP_FROM)" ] && env_set SMTP_FROM "ANOUMA <no-reply@${DOMAIN}>"
|
||||
else
|
||||
log_info "NEXT_PUBLIC_SERVER_URL bereits vorhanden — unverändert."
|
||||
fi
|
||||
|
||||
if [ -z "$(env_get SMTP_HOST)" ]; then
|
||||
SETUP_SMTP="$(ask_yn "SMTP-Zugangsdaten jetzt eintragen? (sonst später manuell in .env)" "N")"
|
||||
if [ "$SETUP_SMTP" = true ]; then
|
||||
env_set SMTP_HOST "$(ask "SMTP-Host" "")"
|
||||
env_set SMTP_PORT "$(ask "SMTP-Port" "587")"
|
||||
env_set SMTP_USER "$(ask "SMTP-Benutzer" "")"
|
||||
read -r -s -p "SMTP-Passwort: " smtp_pw || true; echo ""
|
||||
env_set SMTP_PASSWORD "$smtp_pw"
|
||||
env_set SMTP_HOST "$(ask "SMTP Host" "" "SMTP_HOST")"
|
||||
env_set SMTP_PORT "$(ask "SMTP Port" "587" "SMTP_PORT")"
|
||||
env_set SMTP_USER "$(ask "SMTP Username" "" "SMTP_USER")"
|
||||
env_set SMTP_PASSWORD "$(ask_secret "SMTP Password" "SMTP_PASSWORD")"
|
||||
FROM_DEFAULT="$(env_get SMTP_FROM)"
|
||||
env_set SMTP_FROM "$(ask "SMTP From Address" "${FROM_DEFAULT:-ANOUMA <no-reply@example.org>}" "SMTP_FROM")"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$NPM_DETECTED" = true ]; then
|
||||
USE_NPM="$(ask_yn "Nginx Proxy Manager verwenden (Netzwerk beitreten)?" "Y")"
|
||||
if [ "$USE_NPM" = true ]; then
|
||||
NPM_NETWORK_NAME="$(ask "NPM-Docker-Netzwerk" "$DETECTED_NPM_NETWORK")"
|
||||
env_set NPM_NETWORK "$NPM_NETWORK_NAME"
|
||||
if [ -z "$(env_get NPM_NETWORK)" ] && [ "$NPM_DETECTED" = true ]; then
|
||||
USE_NPM="$(ask_yn "Nginx Proxy Manager Network verwenden (Netzwerk beitreten)?" "Y")"
|
||||
if [ "$USE_NPM" = true ]; then
|
||||
env_set NPM_NETWORK "$(ask "Nginx Proxy Manager Network" "$DETECTED_NPM_NETWORK" "NPM_NETWORK")"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- registry --------------------------------------------------------------
|
||||
REGISTRY_HOST="$(env_get REGISTRY)"; REGISTRY_HOST="${REGISTRY_HOST:-git.maro.run}"
|
||||
log_info "Prüfe Registry-Erreichbarkeit ($REGISTRY_HOST) …"
|
||||
REGISTRY_REACHABLE=false
|
||||
REGISTRY_HTTP_CODE="$(curl -fsS -o /dev/null -w '%{http_code}' --max-time 5 "https://${REGISTRY_HOST}/v2/" 2>/dev/null || echo "000")"
|
||||
if [ "$REGISTRY_HTTP_CODE" = "200" ] || [ "$REGISTRY_HTTP_CODE" = "401" ]; then
|
||||
REGISTRY_REACHABLE=true
|
||||
log_success "Registry erreichbar (HTTP $REGISTRY_HTTP_CODE)."
|
||||
else
|
||||
log_warn "Registry unter https://${REGISTRY_HOST}/v2/ nicht erreichbar (HTTP $REGISTRY_HTTP_CODE) — ggf. später erneut prüfen."
|
||||
fi
|
||||
|
||||
if [ -z "$(env_get REGISTRY_USERNAME)" ]; then
|
||||
DOCKER_CONFIG_FILE="${HOME}/.docker/config.json"
|
||||
REUSED_AUTH=false
|
||||
if [ -f "$DOCKER_CONFIG_FILE" ] && command -v grep >/dev/null 2>&1 && grep -q "\"$REGISTRY_HOST\"" "$DOCKER_CONFIG_FILE" 2>/dev/null; then
|
||||
log_info "Vorhandene Docker-Login-Daten für $REGISTRY_HOST gefunden (~/.docker/config.json)."
|
||||
REUSE="$(ask_yn "Diese Zugangsdaten für die Registry verwenden?" "Y")"
|
||||
if [ "$REUSE" = true ]; then
|
||||
REUSED_AUTH=true
|
||||
log_info "Registry-Zugangsdaten werden zur Laufzeit über die bestehende Docker-Session verwendet — trage sie zusätzlich unten ein, damit auch deploy.sh (als $DEPLOY_USER) einloggen kann."
|
||||
fi
|
||||
fi
|
||||
|
||||
AUTO_UPDATE_ANSWER="$(ask_yn "Automatische Updates aktivieren?" "N")"
|
||||
env_set AUTO_UPDATE "$AUTO_UPDATE_ANSWER"
|
||||
NEED_REGISTRY_AUTH="$(ask_yn "Werden Zugangsdaten für die Container Registry benötigt (privates Repository)?" "Y")"
|
||||
if [ "$NEED_REGISTRY_AUTH" = true ]; then
|
||||
REG_USER="$(ask "Registry Username" "" "REGISTRY_USERNAME")"
|
||||
REG_PASS="$(ask_secret "Registry Password/Token (Gitea Personal Access Token empfohlen)" "REGISTRY_PASSWORD")"
|
||||
if [ -z "$REG_USER" ] || [ -z "$REG_PASS" ]; then
|
||||
if [ "$NON_INTERACTIVE" = true ]; then
|
||||
fail "Registry-Zugangsdaten fehlen." "Setze die Umgebungsvariablen REGISTRY_USERNAME und REGISTRY_PASSWORD und starte setup.sh erneut, oder führe es interaktiv aus."
|
||||
fi
|
||||
log_warn "Keine Registry-Zugangsdaten eingegeben — kann später manuell in $ENV_FILE ergänzt werden (REGISTRY_USERNAME / REGISTRY_PASSWORD)."
|
||||
else
|
||||
env_set REGISTRY_USERNAME "$REG_USER"
|
||||
env_set REGISTRY_PASSWORD "$REG_PASS"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_info "REGISTRY_USERNAME bereits vorhanden — unverändert."
|
||||
fi
|
||||
|
||||
chmod 600 .env || true
|
||||
log_success ".env ist bereit."
|
||||
if [ -z "$(env_get AUTO_DEPLOY)" ] || [ "$FRESH_INSTALL" = true ]; then
|
||||
AUTO_DEPLOY_ANSWER="$(ask_yn "Enable automatic deployment (Gitea Actions deployt automatisch bei neuen Releases)?" "Y")"
|
||||
env_set AUTO_DEPLOY "$AUTO_DEPLOY_ANSWER"
|
||||
fi
|
||||
|
||||
$SUDO chmod 600 "$ENV_FILE"
|
||||
log_success ".env ist bereit ($ENV_FILE)."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reverse-proxy network override — generated only when configured/detected,
|
||||
# never required. See docker-compose.override.yml.example for the manual
|
||||
# equivalent.
|
||||
# 9. ENV summary (secrets masked)
|
||||
# ---------------------------------------------------------------------------
|
||||
step "Konfigurierte Umgebung"
|
||||
|
||||
mask() { [ -n "$1" ] && echo "********" || echo "(nicht gesetzt)"; }
|
||||
echo "NEXT_PUBLIC_SERVER_URL: $(env_get NEXT_PUBLIC_SERVER_URL)"
|
||||
echo "APP_PORT: $(env_get APP_PORT)"
|
||||
echo "POSTGRES_DB: $(env_get POSTGRES_DB)"
|
||||
echo "POSTGRES_PASSWORD: $(mask "$(env_get POSTGRES_PASSWORD)")"
|
||||
echo "PAYLOAD_SECRET: $(mask "$(env_get PAYLOAD_SECRET)")"
|
||||
echo "MEETING_SESSION_SECRET: $(mask "$(env_get MEETING_SESSION_SECRET)")"
|
||||
echo "CRON_SECRET: $(mask "$(env_get CRON_SECRET)")"
|
||||
echo "SMTP_HOST: $(env_get SMTP_HOST)"
|
||||
echo "SMTP_PASSWORD: $(mask "$(env_get SMTP_PASSWORD)")"
|
||||
echo "NPM_NETWORK: $(env_get NPM_NETWORK)"
|
||||
echo "REGISTRY: $(env_get REGISTRY)/$(env_get REGISTRY_REPO)"
|
||||
echo "REGISTRY_USERNAME: $(env_get REGISTRY_USERNAME)"
|
||||
echo "REGISTRY_PASSWORD: $(mask "$(env_get REGISTRY_PASSWORD)")"
|
||||
echo "AUTO_DEPLOY: $(env_get AUTO_DEPLOY)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Compose files + deploy.sh — always refreshed from the repo checkout
|
||||
# (generated artifacts, not secrets/user data — safe to overwrite every run).
|
||||
# ---------------------------------------------------------------------------
|
||||
step "Deployment-Dateien"
|
||||
|
||||
$SUDO cp "$REPO_DIR/docker-compose.yml" "$PROD_DIR/docker-compose.yml"
|
||||
$SUDO cp "$REPO_DIR/docker-compose.override.yml.example" "$PROD_DIR/docker-compose.override.yml.example"
|
||||
$SUDO cp "$REPO_DIR/deploy.sh" "$PROD_DIR/deploy.sh"
|
||||
$SUDO chmod +x "$PROD_DIR/deploy.sh"
|
||||
log_success "docker-compose.yml + deploy.sh nach $PROD_DIR übertragen."
|
||||
|
||||
NPM_NETWORK_VALUE="$(env_get NPM_NETWORK)"
|
||||
if [ -n "$NPM_NETWORK_VALUE" ]; then
|
||||
if ! echo "$EXISTING_NETWORKS" | grep -qx "$NPM_NETWORK_VALUE"; then
|
||||
log_warn "NPM_NETWORK=$NPM_NETWORK_VALUE ist in .env gesetzt, aber kein Docker-Netzwerk mit diesem Namen existiert — das Override wird trotzdem geschrieben, `docker compose up` wird aber fehlschlagen, bis das Netzwerk existiert."
|
||||
fi
|
||||
sed "s/\${NPM_NETWORK}/$NPM_NETWORK_VALUE/g" docker-compose.override.yml.example > docker-compose.override.yml
|
||||
sed "s/\${NPM_NETWORK}/$NPM_NETWORK_VALUE/g" "$PROD_DIR/docker-compose.override.yml.example" | $SUDO tee "$PROD_DIR/docker-compose.override.yml" >/dev/null
|
||||
log_success "docker-compose.override.yml für Reverse-Proxy-Netzwerk '$NPM_NETWORK_VALUE' erstellt."
|
||||
elif [ -f docker-compose.override.yml ]; then
|
||||
log_info "NPM_NETWORK ist leer, aber docker-compose.override.yml existiert bereits — wird unverändert gelassen (nicht automatisch gelöscht)."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 9–11: build, start database, wait for healthy, migrate
|
||||
# 10. Deploy user
|
||||
# ---------------------------------------------------------------------------
|
||||
step "Deployment-User"
|
||||
|
||||
# Waits for a compose service's container healthcheck to report "healthy".
|
||||
# Uses `docker inspect` on the container ID (via `compose ps -q`) rather than
|
||||
# `compose ps --format`, since Go-template support in `ps --format` varies
|
||||
# across Compose versions while `docker inspect` is stable everywhere.
|
||||
if id "$DEPLOY_USER" >/dev/null 2>&1; then
|
||||
log_info "Benutzer '$DEPLOY_USER' existiert bereits — unverändert."
|
||||
else
|
||||
DEPLOY_SHELL="/bin/bash"
|
||||
command -v bash >/dev/null 2>&1 || DEPLOY_SHELL="/bin/sh"
|
||||
# A real shell is required here — OpenSSH executes a forced authorized_keys
|
||||
# "command=" via the account's login shell (`$SHELL -c "command"`). A
|
||||
# nologin shell would swallow that command and refuse to run it, breaking
|
||||
# deployment entirely. "No normal interactive login" is instead enforced
|
||||
# by locking the password (below) and by the SSH key's forced command
|
||||
# (see step 11) — never by the shell field.
|
||||
$SUDO useradd --system --create-home --shell "$DEPLOY_SHELL" "$DEPLOY_USER"
|
||||
$SUDO usermod -L "$DEPLOY_USER" >/dev/null 2>&1 || true
|
||||
log_success "Benutzer '$DEPLOY_USER' erstellt (kein Passwort, kein normaler Login möglich)."
|
||||
fi
|
||||
|
||||
if $DOCKER info >/dev/null 2>&1 && getent group docker >/dev/null 2>&1; then
|
||||
$SUDO usermod -aG docker "$DEPLOY_USER"
|
||||
log_success "'$DEPLOY_USER' zur docker-Gruppe hinzugefügt (notwendig für docker compose)."
|
||||
fi
|
||||
# Deliberately NO sudo rights of any kind for this user — see DEPLOYMENT.md
|
||||
# ("Security") for the docker-group-is-root-equivalent trade-off and how the
|
||||
# restricted SSH key + fixed deploy.sh logic mitigate it.
|
||||
|
||||
DEPLOY_HOME="$(getent passwd "$DEPLOY_USER" | cut -d: -f6)"
|
||||
$SUDO chown -R "$DEPLOY_USER:$DEPLOY_USER" "$PROD_DIR" \
|
||||
"$PROD_DIR/backups" "$PROD_DIR/media" "$PROD_DIR/docker-compose.yml" "$PROD_DIR/deploy.sh" "$ENV_FILE" 2>/dev/null || true
|
||||
# .deploy/ (the SSH keypair) stays out of that blanket chown — see step 11.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 11–13. SSH deployment key + authorized_keys restriction
|
||||
# ---------------------------------------------------------------------------
|
||||
step "SSH Deployment Key"
|
||||
|
||||
KEY_PATH="$PROD_DIR/.deploy/id_ed25519"
|
||||
SSH_KEY_FRESH=false
|
||||
if [ -f "$KEY_PATH" ]; then
|
||||
echo "Existing deployment key found."
|
||||
log_info "SSH-Key wird NICHT neu generiert."
|
||||
else
|
||||
$SUDO ssh-keygen -t ed25519 -N "" -f "$KEY_PATH" -C "anouma-deploy@$(hostname -f 2>/dev/null || hostname)" >/dev/null
|
||||
SSH_KEY_FRESH=true
|
||||
log_success "Neuer SSH-Deployment-Key erzeugt."
|
||||
fi
|
||||
$SUDO chmod 700 "$PROD_DIR/.deploy"
|
||||
$SUDO chmod 600 "$KEY_PATH"
|
||||
$SUDO chmod 644 "$KEY_PATH.pub"
|
||||
|
||||
PUBLIC_KEY="$($SUDO cat "$KEY_PATH.pub")"
|
||||
$SUDO mkdir -p "$DEPLOY_HOME/.ssh"
|
||||
AUTH_KEYS="$DEPLOY_HOME/.ssh/authorized_keys"
|
||||
$SUDO touch "$AUTH_KEYS"
|
||||
|
||||
FORCED_LINE="restrict,command=\"$PROD_DIR/deploy.sh\" $PUBLIC_KEY"
|
||||
if $SUDO grep -qF "$PUBLIC_KEY" "$AUTH_KEYS" 2>/dev/null; then
|
||||
log_info "Public Key ist bereits in authorized_keys eingetragen — unverändert."
|
||||
else
|
||||
printf '%s\n' "$FORCED_LINE" | $SUDO tee -a "$AUTH_KEYS" >/dev/null
|
||||
log_success "Public Key mit eingeschränktem Forced-Command in authorized_keys eingetragen."
|
||||
fi
|
||||
$SUDO chmod 700 "$DEPLOY_HOME/.ssh"
|
||||
$SUDO chmod 600 "$AUTH_KEYS"
|
||||
$SUDO chown -R "$DEPLOY_USER:$DEPLOY_USER" "$DEPLOY_HOME/.ssh"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Start Postgres now (harmless, needed regardless of whether a release has
|
||||
# ever been deployed yet). The app image itself is only ever started by
|
||||
# deploy.sh once at least one release has been pushed — a fresh install has
|
||||
# no image to run yet.
|
||||
# ---------------------------------------------------------------------------
|
||||
step "Datenbank"
|
||||
|
||||
cd "$PROD_DIR"
|
||||
|
||||
# Relies on cwd (now $PROD_DIR) for compose file discovery — same as every
|
||||
# other compose call below, so it consistently picks up
|
||||
# docker-compose.override.yml too when one exists.
|
||||
wait_healthy() {
|
||||
local service="$1" timeout_iterations="$2" id status
|
||||
for _ in $(seq 1 "$timeout_iterations"); do
|
||||
id="$($COMPOSE ps -q "$service" 2>/dev/null || true)"
|
||||
id="$($COMPOSE --env-file "$ENV_FILE" ps -q "$service" 2>/dev/null || true)"
|
||||
if [ -n "$id" ]; then
|
||||
status="$(docker inspect --format '{{.State.Health.Status}}' "$id" 2>/dev/null || true)"
|
||||
status="$($DOCKER inspect --format '{{.State.Health.Status}}' "$id" 2>/dev/null || true)"
|
||||
[ "$status" = "healthy" ] && return 0
|
||||
fi
|
||||
sleep 2
|
||||
@@ -256,83 +543,125 @@ wait_healthy() {
|
||||
return 1
|
||||
}
|
||||
|
||||
log_info "Baue Docker-Images (das kann beim ersten Mal einige Minuten dauern) …"
|
||||
$COMPOSE build
|
||||
|
||||
log_info "Starte Datenbank …"
|
||||
$COMPOSE up -d postgres
|
||||
|
||||
log_info "Warte auf Datenbank-Healthcheck …"
|
||||
log_info "Starte Postgres …"
|
||||
$COMPOSE --env-file "$ENV_FILE" up -d postgres
|
||||
DB_READY=false
|
||||
wait_healthy postgres 60 && DB_READY=true
|
||||
[ "$DB_READY" = true ] || fail "Datenbank wurde nicht rechtzeitig healthy." "Prüfe die Logs: $COMPOSE logs postgres"
|
||||
log_success "Datenbank ist bereit."
|
||||
|
||||
log_info "Führe Datenbank-Migrationen aus …"
|
||||
$COMPOSE run --rm app npm run migrate || fail "Migration fehlgeschlagen." "Prüfe die Ausgabe oben. Die Datenbank wurde nicht verändert, wenn die Migration atomar fehlgeschlagen ist."
|
||||
log_success "Migrationen angewendet."
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 13: seed
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$SKIP_SEED" = false ]; then
|
||||
DO_SEED="$(ask_yn "Vorhandene ANOUMA-Inhalte (Angebote, Seitentexte) jetzt einspielen?" "Y")"
|
||||
if [ "$DO_SEED" = true ]; then
|
||||
log_info "Spiele Seed-Daten ein …"
|
||||
$COMPOSE run --rm app npm run seed || log_warn "Seed ist fehlgeschlagen — die Installation läuft trotzdem weiter, Inhalte können später im Admin-Bereich gepflegt werden."
|
||||
fi
|
||||
if [ "$DB_READY" = true ]; then
|
||||
log_success "Datenbank ist bereit."
|
||||
else
|
||||
log_warn "Datenbank wurde nicht rechtzeitig healthy — prüfe: $COMPOSE logs postgres"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 14–15: start app, healthcheck
|
||||
# ---------------------------------------------------------------------------
|
||||
log_info "Starte Anwendung …"
|
||||
$COMPOSE up -d app
|
||||
|
||||
log_info "Warte auf Healthcheck der Anwendung …"
|
||||
EXISTING_IMAGE="$(env_get ANOUMA_IMAGE)"
|
||||
APP_READY=false
|
||||
wait_healthy app 60 && APP_READY=true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AUTO_UPDATE cron
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$(env_get AUTO_UPDATE)" = "true" ] && command -v crontab >/dev/null 2>&1; then
|
||||
CRON_MARKER="# anouma-auto-update"
|
||||
if ! (crontab -l 2>/dev/null | grep -qF "$CRON_MARKER"); then
|
||||
( crontab -l 2>/dev/null; echo "*/30 * * * * cd $(pwd) && ./update.sh --auto >> $(pwd)/update.log 2>&1 $CRON_MARKER" ) | crontab -
|
||||
log_success "Cronjob für automatische Updates eingerichtet (alle 30 Minuten)."
|
||||
else
|
||||
log_info "Cronjob für automatische Updates ist bereits eingerichtet."
|
||||
if [ -n "$EXISTING_IMAGE" ]; then
|
||||
log_info "ANOUMA_IMAGE ist bereits gesetzt ($EXISTING_IMAGE) — starte Anwendung …"
|
||||
if [ -n "$(env_get REGISTRY_USERNAME)" ]; then
|
||||
echo "$(env_get REGISTRY_PASSWORD)" | $DOCKER login "$(env_get REGISTRY)" -u "$(env_get REGISTRY_USERNAME)" --password-stdin >/dev/null 2>&1 || log_warn "Registry-Login fehlgeschlagen — 'docker compose pull' könnte fehlschlagen."
|
||||
fi
|
||||
if $COMPOSE --env-file "$ENV_FILE" pull app 2>/dev/null && $COMPOSE --env-file "$ENV_FILE" run --rm app npm run migrate && $COMPOSE --env-file "$ENV_FILE" up -d; then
|
||||
wait_healthy app 60 && APP_READY=true
|
||||
fi
|
||||
if [ "$APP_READY" = false ]; then
|
||||
log_warn "Anwendung konnte nicht (neu) gestartet werden — prüfe: $COMPOSE logs app"
|
||||
fi
|
||||
else
|
||||
log_info "Noch kein Release deployt — die Anwendung wird beim ersten Git-Tag automatisch über Gitea Actions gestartet."
|
||||
if [ "$SKIP_SEED" = false ]; then
|
||||
log_info "Hinweis: Inhalte können nach dem ersten Deploy mit 'docker compose run --rm app npm run seed' eingespielt werden."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$(git rev-parse --short HEAD 2>/dev/null || echo unknown)" > .installed-version 2>/dev/null || true
|
||||
cd "$REPO_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 16: result
|
||||
# 31. Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "ANOUMA installation complete"
|
||||
echo ""
|
||||
echo "Version:"
|
||||
echo " $(cat .installed-version 2>/dev/null || echo unknown)"
|
||||
echo "========================================"
|
||||
echo "ANOUMA SETUP COMPLETE"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Application:"
|
||||
echo " $(env_get NEXT_PUBLIC_SERVER_URL)"
|
||||
echo ""
|
||||
echo "Project:"
|
||||
echo " maro/anouma"
|
||||
echo ""
|
||||
echo "Docker:"
|
||||
echo " ✓ Running"
|
||||
echo " ✓"
|
||||
echo ""
|
||||
echo "Docker Compose:"
|
||||
echo " ✓"
|
||||
echo ""
|
||||
echo "Database:"
|
||||
if [ "$DB_READY" = true ]; then echo " ✓ Healthy"; else echo " ✗ Not healthy — check: $COMPOSE logs postgres"; fi
|
||||
if [ "$DB_READY" = true ]; then echo " ✓"; else echo " ✗ (siehe oben)"; fi
|
||||
echo ""
|
||||
echo "Application healthcheck:"
|
||||
if [ "$APP_READY" = true ]; then echo " ✓ Running"; else echo " ✗ Not healthy yet — check: $COMPOSE logs app"; fi
|
||||
echo "Docker Network:"
|
||||
echo " ✓ ($(env_get DOCKER_NETWORK))"
|
||||
echo ""
|
||||
echo "Nginx Proxy Manager:"
|
||||
if [ "$NPM_DETECTED" = true ]; then echo " ✓ Detected ($DETECTED_NPM_NETWORK)"; else echo " - Not detected"; fi
|
||||
if [ "$NPM_DETECTED" = true ]; then echo " ✓ ($DETECTED_NPM_NETWORK)"; else echo " not detected"; fi
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Visit ${NPM_DETECTED:+your configured domain, or }http://localhost:${APP_PORT:-3000}/admin to create the first admin account."
|
||||
echo " 2. See DEPLOYMENT.md for reverse-proxy setup, backups, updates and troubleshooting."
|
||||
echo "Production User:"
|
||||
echo " $DEPLOY_USER"
|
||||
echo ""
|
||||
echo "Deployment:"
|
||||
echo " ✓"
|
||||
echo ""
|
||||
echo "Registry:"
|
||||
if [ "$REGISTRY_REACHABLE" = true ]; then echo " ✓ ($(env_get REGISTRY))"; else echo " ⚠ nicht erreichbar geprüft ($(env_get REGISTRY))"; fi
|
||||
echo ""
|
||||
echo "Gitea Actions:"
|
||||
echo " Ready"
|
||||
echo ""
|
||||
echo "Automatic Deployment:"
|
||||
if [ "$(env_get AUTO_DEPLOY)" = "true" ]; then echo " Enabled"; else echo " Disabled"; fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "GITEA SECRETS"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
if [ "$SSH_KEY_FRESH" = true ]; then
|
||||
echo "Trage folgende Werte EINMALIG unter Gitea → maro/anouma → Settings → Secrets → Actions ein."
|
||||
echo "Der private Key wird nach diesem Lauf nicht erneut angezeigt."
|
||||
echo ""
|
||||
echo "DEPLOY_HOST:"
|
||||
echo " $(curl -fsS --max-time 3 https://ifconfig.me 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}' || echo "<Server-IP eintragen>")"
|
||||
echo ""
|
||||
echo "DEPLOY_PORT:"
|
||||
echo " 22"
|
||||
echo ""
|
||||
echo "DEPLOY_USER:"
|
||||
echo " $DEPLOY_USER"
|
||||
echo ""
|
||||
echo "DEPLOY_SSH_KEY:"
|
||||
echo "----------------------------------------"
|
||||
$SUDO cat "$KEY_PATH"
|
||||
echo "----------------------------------------"
|
||||
echo ""
|
||||
echo "REGISTRY_USERNAME:"
|
||||
echo " $(env_get REGISTRY_USERNAME)"
|
||||
echo ""
|
||||
echo "REGISTRY_PASSWORD:"
|
||||
echo " $(env_get REGISTRY_PASSWORD)"
|
||||
else
|
||||
echo "Existing deployment key found — Gitea Secrets wurden bereits bei der ersten Einrichtung ausgegeben."
|
||||
echo "Falls sie nicht mehr vorliegen: DEPLOY_SSH_KEY kann sicher eingesehen werden mit:"
|
||||
echo " sudo cat $KEY_PATH"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "NEXT STEP"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Add these values to:"
|
||||
echo " Gitea → maro/anouma → Settings → Secrets → Actions"
|
||||
echo ""
|
||||
echo "Danach reicht für jedes zukünftige Deployment:"
|
||||
echo " git tag v1.0.0 && git push origin v1.0.0"
|
||||
echo ""
|
||||
echo "Siehe DEPLOYMENT.md für den vollständigen Ablauf."
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user