Files
vendoo/setup.sh

329 lines
10 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
set -eu
APP_VERSION="1.37.0"
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
MODE_FILE="$ROOT_DIR/.vendoo-install-mode"
ENV_FILE="$ROOT_DIR/.env"
ENV_EXAMPLE="$ROOT_DIR/.env.example"
info() { printf '\033[36m[INFO]\033[0m %s\n' "$*"; }
ok() { printf '\033[32m[OK]\033[0m %s\n' "$*"; }
warn() { printf '\033[33m[WARN]\033[0m %s\n' "$*"; }
err() { printf '\033[31m[ERROR]\033[0m %s\n' "$*" >&2; }
has_command() { command -v "$1" >/dev/null 2>&1; }
get_mode() {
if [ -f "$MODE_FILE" ]; then tr -d '\r\n' < "$MODE_FILE"; else printf 'docker'; fi
}
set_mode() {
printf '%s\n' "$1" > "$MODE_FILE"
chmod 600 "$MODE_FILE" 2>/dev/null || true
}
compose_files() {
case "$(get_mode)" in
nas) printf '%s:%s' "$ROOT_DIR/compose.yaml" "$ROOT_DIR/compose.bind-mounts.example.yaml" ;;
vps) printf '%s' "$ROOT_DIR/compose.vps.yaml" ;;
*) printf '%s' "$ROOT_DIR/compose.yaml" ;;
esac
}
compose() {
COMPOSE_FILE=$(compose_files) docker compose "$@"
}
validate_env_value() {
case "$2" in
*'\n'*|*'\r'*) err "Ungültiger mehrzeiliger Wert für $1."; return 1 ;;
esac
}
set_env() {
key=$1
value=$2
validate_env_value "$key" "$value"
tmp="$ENV_FILE.tmp.$$"
if grep -q "^${key}=" "$ENV_FILE" 2>/dev/null; then
awk -v k="$key" -v v="$value" 'BEGIN{FS="="} $1==k{print k"="v; next} {print}' "$ENV_FILE" > "$tmp"
else
cat "$ENV_FILE" > "$tmp"
printf '\n%s=%s\n' "$key" "$value" >> "$tmp"
fi
chmod 600 "$tmp" 2>/dev/null || true
mv "$tmp" "$ENV_FILE"
}
get_env() {
key=$1
sed -n "s/^${key}=//p" "$ENV_FILE" 2>/dev/null | tail -n 1
}
generate_token() {
if has_command openssl; then openssl rand -hex 32
elif [ -r /dev/urandom ]; then od -An -N32 -tx1 /dev/urandom | tr -d ' \n'
else date +%s | sha256sum | awk '{print $1}'
fi
}
check_docker() {
if ! has_command docker; then
err "Docker ist auf diesem Zielsystem nicht installiert."
printf '%s\n' "Installiere Docker Engine und das Compose-Plugin nach der offiziellen Docker-Dokumentation und starte setup.sh danach erneut."
return 1
fi
if ! docker compose version >/dev/null 2>&1; then
err "Das Docker-Compose-Plugin fehlt."
return 1
fi
if ! docker info >/dev/null 2>&1; then
err "Docker ist installiert, aber der Dienst ist nicht erreichbar oder dein Benutzer hat keine Berechtigung."
return 1
fi
ok "Docker und Docker Compose sind verfügbar."
}
ensure_env() {
if [ ! -f "$ENV_FILE" ]; then
cp "$ENV_EXAMPLE" "$ENV_FILE"
chmod 600 "$ENV_FILE" 2>/dev/null || true
ok ".env wurde automatisch aus der sicheren Vorlage erstellt."
fi
token=$(get_env VENDOO_SETUP_TOKEN)
if [ -z "$token" ]; then
token=$(generate_token)
set_env VENDOO_SETUP_TOKEN "$token"
ok "Ein zufälliger Erst-Admin-Setup-Code wurde erzeugt."
fi
set_env VENDOO_REQUIRE_SETUP_TOKEN true
}
read_default() {
prompt=$1
default=$2
printf '%s [%s]: ' "$prompt" "$default" >&2
read -r value
if [ -z "$value" ]; then printf '%s' "$default"; else printf '%s' "$value"; fi
}
read_yes_no() {
prompt=$1
default=$2
while :; do
printf '%s [%s]: ' "$prompt" "$default"
read -r answer
[ -n "$answer" ] || answer=$default
case "$answer" in
j|J|ja|JA|y|Y|yes|YES) return 0 ;;
n|N|nein|NEIN|no|NO) return 1 ;;
*) warn "Bitte ja oder nein eingeben." ;;
esac
done
}
validate_port() {
case "$1" in ''|*[!0-9]*) return 1 ;; esac
[ "$1" -ge 1024 ] && [ "$1" -le 65535 ]
}
validate_domain() {
case "$1" in
http://*|https://*|*/*|*:*|'') return 1 ;;
*.*) return 0 ;;
*) return 1 ;;
esac
}
configure_wizard() {
ensure_env
printf '\nVendoo %s geführte Installation\n\n' "$APP_VERSION"
printf ' 1 Docker auf Linux/macOS/Docker Desktop (nur lokal erreichbar)\n'
printf ' 2 NAS / Portainer / privates LAN (sichtbare Runtime-Ordner)\n'
printf ' 3 VPS / öffentlicher Server mit automatischem HTTPS\n'
printf ' 0 Abbrechen\n\n'
printf 'Installationsziel: '
read -r choice
case "$choice" in
1) mode=docker ;;
2) mode=nas ;;
3) mode=vps ;;
0) return 1 ;;
*) err "Ungültige Auswahl."; return 1 ;;
esac
set_mode "$mode"
current_port=$(get_env VENDOO_HOST_PORT)
[ -n "$current_port" ] || current_port=8124
port=$(read_default "Port" "$current_port")
validate_port "$port" || { err "Port muss zwischen 1024 und 65535 liegen."; return 1; }
set_env PORT "$port"
set_env VENDOO_HOST_PORT "$port"
set_env VENDOO_COOKIE_SAMESITE lax
set_env VENDOO_ALLOW_ANY_EXTENSION_ORIGIN false
case "$mode" in
docker)
set_env VENDOO_PUBLISH_ADDRESS 127.0.0.1
set_env VENDOO_BIND_HOST 0.0.0.0
set_env VENDOO_TRUST_PROXY false
set_env VENDOO_COOKIE_SECURE auto
set_env PUBLIC_BASE_URL ""
;;
nas)
mkdir -p "$ROOT_DIR/runtime/db" "$ROOT_DIR/runtime/uploads" "$ROOT_DIR/runtime/backups" "$ROOT_DIR/runtime/operations" "$ROOT_DIR/runtime/logs" "$ROOT_DIR/runtime/config"
chmod -R u+rwX "$ROOT_DIR/runtime" 2>/dev/null || true
set_env VENDOO_PUBLISH_ADDRESS 0.0.0.0
set_env VENDOO_BIND_HOST 0.0.0.0
set_env VENDOO_TRUST_PROXY false
set_env VENDOO_COOKIE_SECURE auto
printf 'LAN-Basisadresse für QR-Uploads, z. B. http://192.168.1.20:%s (optional): ' "$port"
read -r public_url
case "$public_url" in
'') set_env PUBLIC_BASE_URL "" ;;
http://*|https://*) set_env PUBLIC_BASE_URL "${public_url%/}" ;;
*) err "Adresse muss mit http:// oder https:// beginnen."; return 1 ;;
esac
warn "NAS-Modus veröffentlicht Port $port im privaten LAN. Nicht direkt aus dem Internet freigeben."
;;
vps)
while :; do
printf 'Öffentliche Domain ohne https://, z. B. vendoo.example.de: '
read -r domain
validate_domain "$domain" && break
warn "Bitte einen vollständigen Domainnamen ohne Protokoll oder Pfad eingeben."
done
set_env VENDOO_DOMAIN "$domain"
set_env PUBLIC_BASE_URL "https://$domain"
set_env VENDOO_PUBLISH_ADDRESS 127.0.0.1
set_env VENDOO_BIND_HOST 0.0.0.0
set_env VENDOO_TRUST_PROXY true
set_env VENDOO_COOKIE_SECURE true
set_env VENDOO_ALLOWED_HOSTS "$domain"
info "Caddy übernimmt HTTPS automatisch. DNS muss auf diesen Server zeigen; Ports 80 und 443 müssen erreichbar sein."
;;
esac
if read_yes_no "Lokales/externes FLUX aktivieren?" "nein"; then
set_env LOCAL_IMAGE_ENABLED true
default_flux=http://host.docker.internal:8188
flux_url=$(read_default "FLUX/ComfyUI-Adresse" "$default_flux")
case "$flux_url" in http://*|https://*) set_env LOCAL_IMAGE_URL "${flux_url%/}" ;; *) err "FLUX-Adresse muss mit http:// oder https:// beginnen."; return 1 ;; esac
else
set_env LOCAL_IMAGE_ENABLED false
fi
token=$(get_env VENDOO_SETUP_TOKEN)
printf '\nERST-ADMIN-SETUP-CODE:\n%s\n\n' "$token"
warn "Code bis zur ersten Anmeldung sicher aufbewahren. AI- und Marktplatz-Schlüssel werden später in Vendoo → Einstellungen gepflegt."
ok "Konfiguration gespeichert; keine Datei musste manuell bearbeitet werden."
}
wait_ready() {
case "$(get_mode)" in
vps) url="https://$(get_env VENDOO_DOMAIN)/readyz" ;;
*) url="http://127.0.0.1:$(get_env VENDOO_HOST_PORT)/readyz" ;;
esac
if ! has_command curl; then warn "curl fehlt; Readiness später im Browser prüfen: $url"; return 0; fi
i=0
while [ "$i" -lt 30 ]; do
if curl -fsS --max-time 5 "$url" >/dev/null 2>&1; then ok "Vendoo ist bereit: $url"; return 0; fi
i=$((i+1)); sleep 2
done
warn "Readiness war noch nicht erfolgreich. Prüfe: compose logs --tail=200"
return 0
}
install_or_update() {
check_docker
configure_wizard || return 0
info "Prüfe Compose-Konfiguration …"
compose config >/dev/null
ok "Compose-Konfiguration ist gültig."
info "Baue und starte Vendoo …"
compose build --pull
compose up -d --remove-orphans
compose ps
wait_ready
}
start_app() { check_docker; compose up -d; compose ps; wait_ready; }
stop_app() { check_docker; compose down; }
update_app() {
check_docker
ensure_env
info "Erstelle vor dem Update automatisch ein Backup …"
backup_app
compose config >/dev/null
compose build --pull
compose up -d --remove-orphans
compose ps
wait_ready
}
status_app() {
check_docker
compose ps
printf '\nReadiness:\n'
case "$(get_mode)" in vps) url="https://$(get_env VENDOO_DOMAIN)/readyz" ;; *) url="http://127.0.0.1:$(get_env VENDOO_HOST_PORT)/readyz" ;; esac
if has_command curl; then curl -fsS "$url" || true; printf '\n'; else warn "curl fehlt; Readiness bitte im Browser prüfen: $url"; fi
}
backup_app() {
check_docker
COMPOSE_FILE=$(compose_files) "$ROOT_DIR/scripts/docker-backup.sh"
}
restore_app() {
printf 'Pfad zum Backup-TAR.GZ: '
read -r backup
[ -f "$backup" ] || { err "Backup nicht gefunden."; return 1; }
check_docker
COMPOSE_FILE=$(compose_files) "$ROOT_DIR/scripts/docker-restore.sh" "$backup" --confirm
wait_ready
}
show_setup_code() {
ensure_env
printf '\nErst-Admin-Setup-Code:\n%s\n\n' "$(get_env VENDOO_SETUP_TOKEN)"
}
menu() {
while :; do
printf '\nVendoo %s Zero-Edit Deployment Assistant\n\n' "$APP_VERSION"
printf ' 1 Installieren / Konfiguration ändern\n'
printf ' 2 Starten\n'
printf ' 3 Stoppen\n'
printf ' 4 Status und Readiness\n'
printf ' 5 Aktualisieren (mit Sicherheitsbackup)\n'
printf ' 6 Backup erstellen\n'
printf ' 7 Backup wiederherstellen\n'
printf ' 8 Erst-Admin-Code anzeigen\n'
printf ' 0 Beenden\n\n'
printf 'Auswahl: '
read -r choice
case "$choice" in
1) install_or_update ;;
2) start_app ;;
3) stop_app ;;
4) status_app ;;
5) update_app ;;
6) backup_app ;;
7) restore_app ;;
8) show_setup_code ;;
0) exit 0 ;;
*) warn "Ungültige Auswahl." ;;
esac
done
}
case "${1:-menu}" in
install|configure) install_or_update ;;
start) start_app ;;
stop) stop_app ;;
status) status_app ;;
update) update_app ;;
backup) backup_app ;;
restore) restore_app ;;
setup-code) show_setup_code ;;
menu) menu ;;
*) err "Unbekannter Befehl: $1"; exit 2 ;;
esac