Files
FEDEO/scripts/selfhost-setup.sh
florianfederspiel 9c1d3bc04c
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 19s
Build and Push Docker Images / build-frontend (push) Successful in 53s
Build and Push Docker Images / build-website (push) Successful in 20s
Build and Push Docker Images / build-docs (push) Successful in 11s
KI-AGENT: Selfhost Setup für Node Exporter ergänzen
2026-05-20 21:03:12 +02:00

497 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env"
ENV_EXAMPLE="$ROOT_DIR/.env.example"
COMPOSE_FILE="$ROOT_DIR/docker-compose.selfhost.yml"
MODE=""
START_STACK="ask"
FORCE="false"
TTY_INPUT="${FEDEO_TTY_INPUT:-/dev/tty}"
TTY_FD=""
if { exec 3<"$TTY_INPUT"; } 2>/dev/null; then
TTY_FD="3"
fi
read_interactive() {
if [[ -n "$TTY_FD" ]]; then
read -u "$TTY_FD" "$@"
else
read "$@"
fi
}
compose() {
if [[ "${FEDEO_USE_SUDO_DOCKER:-false}" == "true" ]]; then
sudo docker compose "$@"
else
docker compose "$@"
fi
}
usage() {
cat <<'USAGE'
FEDEO Selfhost Setup
Nutzung:
bash scripts/selfhost-setup.sh
bash scripts/selfhost-setup.sh --simple
bash scripts/selfhost-setup.sh --advanced
bash scripts/selfhost-setup.sh --simple --start
Optionen:
--simple Kurzer Assistent mit lokalen Diensten und sicheren Defaults
--advanced Fragt zusätzliche Integrationen wie SMTP, OpenAI und DATEV-nahes Umfeld ab
--start Startet den Stack nach dem Schreiben der Konfiguration
--no-start Schreibt nur Konfiguration und Verzeichnisse
--force Überschreibt eine vorhandene .env ohne Rückfrage
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--simple)
MODE="simple"
;;
--advanced)
MODE="advanced"
;;
--start)
START_STACK="yes"
;;
--no-start)
START_STACK="no"
;;
--force)
FORCE="true"
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unbekannte Option: $1" >&2
usage
exit 1
;;
esac
shift
done
need_file() {
if [[ ! -f "$1" ]]; then
echo "Fehlt: $1" >&2
exit 1
fi
}
random_secret() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -base64 48 | tr -d '\n'
else
LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 64
fi
}
env_quote() {
local value="$1"
value="${value//\'/\'\\\'\'}"
printf "'%s'" "$value"
}
prompt() {
local label="$1"
local default_value="${2:-}"
local value
if [[ -n "$default_value" ]]; then
read_interactive -r -p "$label [$default_value]: " value
echo "${value:-$default_value}"
else
read_interactive -r -p "$label: " value
echo "$value"
fi
}
prompt_required() {
local label="$1"
local default_value="${2:-}"
local value
while true; do
value="$(prompt "$label" "$default_value")"
if [[ -n "$value" ]]; then
echo "$value"
return
fi
echo "Bitte einen Wert eintragen." >&2
done
}
prompt_secret() {
local label="$1"
local default_value="${2:-}"
local value
if [[ -n "$default_value" ]]; then
read_interactive -r -s -p "$label [vorbelegt, Enter übernimmt]: " value
echo >&2
echo "${value:-$default_value}"
else
read_interactive -r -s -p "$label: " value
echo >&2
echo "$value"
fi
}
yes_no() {
local label="$1"
local default_value="${2:-n}"
local answer
while true; do
read_interactive -r -p "$label [$default_value]: " answer
answer="${answer:-$default_value}"
case "$answer" in
y|Y|j|J|yes|Yes|ja|Ja)
return 0
;;
n|N|no|No|nein|Nein)
return 1
;;
*)
echo "Bitte ja oder nein eingeben."
;;
esac
done
}
choose_mode() {
if [[ -n "$MODE" ]]; then
return
fi
echo
echo "Setup-Modus"
echo " 1) einfach - Domain, Admin, lokale Datenbank, MinIO, Matrix"
echo " 2) advanced - zusätzlich SMTP, externe Schlüssel und optionale Dienste"
echo
local choice
while true; do
read_interactive -r -p "Modus wählen [1]: " choice
case "${choice:-1}" in
1|einfach|simple)
MODE="simple"
return
;;
2|advanced|erweitert)
MODE="advanced"
return
;;
*)
echo "Bitte 1 oder 2 wählen."
;;
esac
done
}
print_structure_guide() {
cat <<EOF
FEDEO Selfhost Setup
Dieses Script führt dich durch die lokale Betriebsstruktur:
$ROOT_DIR/
docker-compose.selfhost.yml Docker Stack für FEDEO, Traefik, PostgreSQL, MinIO, Matrix und Monitoring
.env Zielkonfiguration, wird von diesem Script geschrieben
postgres/ persistente FEDEO-Datenbank
minio/ lokaler S3-kompatibler Dateispeicher
matrix/postgres/ persistente Synapse-Datenbank
matrix/synapse/ generierte Synapse-Konfiguration und Matrix-Daten
traefik/letsencrypt/ Let's-Encrypt-Zertifikate
traefik/logs/ Traefik-Logs
Öffentliche Endpunkte auf einer Domain:
https://DOMAIN/ FEDEO Frontend
https://DOMAIN/backend FEDEO API
https://DOMAIN/_matrix Matrix Homeserver
https://DOMAIN/.well-known Matrix Discovery
https://DOMAIN/livekit/sfu LiveKit
https://DOMAIN/livekit/jwt LiveKit JWT-Service
https://DOMAIN/element Element Web
EOF
}
write_env() {
local domain="$1"
local contact_email="$2"
local db_password="$3"
local minio_password="$4"
local cookie_secret="$5"
local jwt_secret="$6"
local encryption_key="$7"
local m2m_key="$8"
local admin_email="$9"
local admin_password="${10}"
local admin_first_name="${11}"
local admin_last_name="${12}"
local tenant_name="${13}"
local tenant_short="${14}"
local matrix_db_password="${15}"
local matrix_turn_secret="${16}"
local matrix_registration_secret="${17}"
local livekit_secret="${18}"
local mailer_host="${19}"
local mailer_port="${20}"
local mailer_ssl="${21}"
local mailer_user="${22}"
local mailer_pass="${23}"
local mailer_from="${24}"
local web_push_public="${25}"
local web_push_private="${26}"
local pdf_license="${27}"
local openai_key="${28}"
local stirling_key="${29}"
local gocardless_secret_id="${30}"
local gocardless_secret_key="${31}"
local dokubox_host="${32}"
local dokubox_port="${33}"
local dokubox_secure="${34}"
local dokubox_user="${35}"
local dokubox_password="${36}"
cat >"$ENV_FILE" <<EOF
# FEDEO Selfhosting
DOMAIN=$(env_quote "$domain")
CONTACT_EMAIL=$(env_quote "$contact_email")
DB_NAME=$(env_quote "fedeo")
DB_USER=$(env_quote "fedeo")
DB_PASSWORD=$(env_quote "$db_password")
DATABASE_URL=$(env_quote "postgres://fedeo:$db_password@db:5432/fedeo")
MINIO_ROOT_USER=$(env_quote "fedeo-minio")
MINIO_ROOT_PASSWORD=$(env_quote "$minio_password")
MINIO_BUCKET=$(env_quote "fedeo")
HOST=$(env_quote "0.0.0.0")
PORT=$(env_quote "3100")
FEDEO_RUN_MIGRATIONS=$(env_quote "true")
COOKIE_SECRET=$(env_quote "$cookie_secret")
JWT_SECRET=$(env_quote "$jwt_secret")
ENCRYPTION_KEY=$(env_quote "$encryption_key")
MAILER_SMTP_HOST=$(env_quote "$mailer_host")
MAILER_SMTP_PORT=$(env_quote "$mailer_port")
MAILER_SMTP_SSL=$(env_quote "$mailer_ssl")
MAILER_SMTP_USER=$(env_quote "$mailer_user")
MAILER_SMTP_PASS=$(env_quote "$mailer_pass")
MAILER_FROM=$(env_quote "$mailer_from")
WEB_PUSH_PUBLIC_KEY=$(env_quote "$web_push_public")
WEB_PUSH_PRIVATE_KEY=$(env_quote "$web_push_private")
WEB_PUSH_SUBJECT=$(env_quote "mailto:$contact_email")
S3_ENDPOINT=$(env_quote "http://minio:9000")
S3_REGION=$(env_quote "eu-central-1")
S3_ACCESS_KEY=$(env_quote "fedeo-minio")
S3_SECRET_KEY=$(env_quote "$minio_password")
S3_BUCKET=$(env_quote "fedeo")
M2M_API_KEY=$(env_quote "$m2m_key")
API_BASE_URL=$(env_quote "https://$domain/backend")
GOCARDLESS_BASE_URL=$(env_quote "https://api.gocardless.com")
GOCARDLESS_SECRET_ID=$(env_quote "$gocardless_secret_id")
GOCARDLESS_SECRET_KEY=$(env_quote "$gocardless_secret_key")
DOKUBOX_IMAP_HOST=$(env_quote "$dokubox_host")
DOKUBOX_IMAP_PORT=$(env_quote "$dokubox_port")
DOKUBOX_IMAP_SECURE=$(env_quote "$dokubox_secure")
DOKUBOX_IMAP_USER=$(env_quote "$dokubox_user")
DOKUBOX_IMAP_PASSWORD=$(env_quote "$dokubox_password")
OPENAI_API_KEY=$(env_quote "$openai_key")
STIRLING_API_KEY=$(env_quote "$stirling_key")
NUXT_PUBLIC_PDF_LICENSE=$(env_quote "$pdf_license")
NODE_EXPORTER_URL=$(env_quote "http://node-exporter:9100")
FEDEO_BOOTSTRAP_ADMIN_EMAIL=$(env_quote "$admin_email")
FEDEO_BOOTSTRAP_ADMIN_PASSWORD=$(env_quote "$admin_password")
FEDEO_BOOTSTRAP_ADMIN_FIRST_NAME=$(env_quote "$admin_first_name")
FEDEO_BOOTSTRAP_ADMIN_LAST_NAME=$(env_quote "$admin_last_name")
FEDEO_BOOTSTRAP_TENANT_NAME=$(env_quote "$tenant_name")
FEDEO_BOOTSTRAP_TENANT_SHORT=$(env_quote "$tenant_short")
MATRIX_SERVER_NAME=$(env_quote "$domain")
MATRIX_POSTGRES_DB=$(env_quote "synapse")
MATRIX_POSTGRES_USER=$(env_quote "synapse")
MATRIX_POSTGRES_PASSWORD=$(env_quote "$matrix_db_password")
MATRIX_TURN_SHARED_SECRET=$(env_quote "$matrix_turn_secret")
LIVEKIT_KEY=$(env_quote "fedeo-livekit")
LIVEKIT_SECRET=$(env_quote "$livekit_secret")
MATRIX_HOMESERVER_URL=$(env_quote "http://matrix-synapse:8008")
MATRIX_RTC_HOST=$(env_quote "$domain")
MATRIX_RTC_JWT_URL=$(env_quote "https://$domain/livekit/jwt")
MATRIX_LIVEKIT_URL=$(env_quote "wss://$domain/livekit/sfu")
MATRIX_REGISTRATION_SHARED_SECRET=$(env_quote "$matrix_registration_secret")
MATRIX_SERVICE_USER_LOCALPART=$(env_quote "fedeo_service")
NUXT_PUBLIC_MATRIX_ELEMENT_URL=$(env_quote "https://$domain/element")
EOF
}
prepare_directories() {
mkdir -p \
"$ROOT_DIR/traefik/letsencrypt" \
"$ROOT_DIR/traefik/logs" \
"$ROOT_DIR/postgres" \
"$ROOT_DIR/minio" \
"$ROOT_DIR/matrix/postgres" \
"$ROOT_DIR/matrix/synapse"
touch "$ROOT_DIR/traefik/letsencrypt/acme.json"
chmod 600 "$ROOT_DIR/traefik/letsencrypt/acme.json"
}
main() {
need_file "$COMPOSE_FILE"
need_file "$ENV_EXAMPLE"
print_structure_guide
choose_mode
if [[ -f "$ENV_FILE" && "$FORCE" != "true" ]]; then
if yes_no ".env existiert bereits. Backup erstellen und überschreiben?" "n"; then
cp "$ENV_FILE" "$ENV_FILE.$(date +%Y%m%d-%H%M%S).bak"
else
echo "Abgebrochen, .env bleibt unverändert."
exit 0
fi
fi
echo
echo "Basis"
local domain contact_email admin_email admin_password admin_first_name admin_last_name tenant_name tenant_short
domain="$(prompt_required "Domain für FEDEO" "app.example.com")"
contact_email="$(prompt_required "Kontakt-E-Mail für Let's Encrypt" "admin@example.com")"
admin_email="$(prompt_required "Erster Admin-Login" "$contact_email")"
admin_password="$(prompt_secret "Erstes Admin-Passwort" "$(random_secret)")"
admin_first_name="$(prompt_required "Admin-Vorname" "Admin")"
admin_last_name="$(prompt_required "Admin-Nachname" "Benutzer")"
tenant_name="$(prompt_required "Name des ersten Mandanten" "Mein Unternehmen")"
tenant_short="$(prompt_required "Kurzname des ersten Mandanten" "MEIN")"
echo
echo "Secrets werden automatisch erzeugt."
local db_password minio_password cookie_secret jwt_secret encryption_key m2m_key
local matrix_db_password matrix_turn_secret matrix_registration_secret livekit_secret
db_password="$(random_secret)"
minio_password="$(random_secret)"
cookie_secret="$(random_secret)"
jwt_secret="$(random_secret)"
encryption_key="$(random_secret)"
m2m_key="$(random_secret)"
matrix_db_password="$(random_secret)"
matrix_turn_secret="$(random_secret)"
matrix_registration_secret="$(random_secret)"
livekit_secret="$(random_secret)"
local mailer_host="smtp.example.com"
local mailer_port="587"
local mailer_ssl="false"
local mailer_user="mailer@example.com"
local mailer_pass="change-this-mail-password"
local mailer_from="FEDEO <no-reply@example.com>"
local web_push_public="replace-this-web-push-public-key"
local web_push_private="replace-this-web-push-private-key"
local pdf_license="replace-with-your-pdf-license"
local openai_key="replace-this"
local stirling_key="replace-this"
local gocardless_secret_id="replace-this"
local gocardless_secret_key="replace-this"
local dokubox_host="imap.example.com"
local dokubox_port="993"
local dokubox_secure="true"
local dokubox_user="dokubox@example.com"
local dokubox_password="change-this-imap-password"
if [[ "$MODE" == "advanced" ]]; then
echo
echo "Advanced: SMTP"
mailer_host="$(prompt "SMTP Host" "$mailer_host")"
mailer_port="$(prompt "SMTP Port" "$mailer_port")"
mailer_ssl="$(prompt "SMTP SSL true/false" "$mailer_ssl")"
mailer_user="$(prompt "SMTP Benutzer" "$mailer_user")"
mailer_pass="$(prompt_secret "SMTP Passwort" "$mailer_pass")"
mailer_from="$(prompt "Absender" "FEDEO <no-reply@$domain>")"
echo
echo "Advanced: optionale Schlüssel"
pdf_license="$(prompt "PDF-Lizenz" "$pdf_license")"
openai_key="$(prompt_secret "OpenAI API Key" "$openai_key")"
stirling_key="$(prompt_secret "Stirling API Key" "$stirling_key")"
web_push_public="$(prompt "Web Push Public Key" "$web_push_public")"
web_push_private="$(prompt_secret "Web Push Private Key" "$web_push_private")"
echo
echo "Advanced: Banking und Dokubox"
gocardless_secret_id="$(prompt "GoCardless Secret ID" "$gocardless_secret_id")"
gocardless_secret_key="$(prompt_secret "GoCardless Secret Key" "$gocardless_secret_key")"
dokubox_host="$(prompt "Dokubox IMAP Host" "$dokubox_host")"
dokubox_port="$(prompt "Dokubox IMAP Port" "$dokubox_port")"
dokubox_secure="$(prompt "Dokubox IMAP Secure true/false" "$dokubox_secure")"
dokubox_user="$(prompt "Dokubox IMAP Benutzer" "$dokubox_user")"
dokubox_password="$(prompt_secret "Dokubox IMAP Passwort" "$dokubox_password")"
fi
write_env \
"$domain" "$contact_email" "$db_password" "$minio_password" \
"$cookie_secret" "$jwt_secret" "$encryption_key" "$m2m_key" \
"$admin_email" "$admin_password" "$admin_first_name" "$admin_last_name" \
"$tenant_name" "$tenant_short" "$matrix_db_password" "$matrix_turn_secret" \
"$matrix_registration_secret" "$livekit_secret" "$mailer_host" "$mailer_port" \
"$mailer_ssl" "$mailer_user" "$mailer_pass" "$mailer_from" "$web_push_public" \
"$web_push_private" "$pdf_license" "$openai_key" "$stirling_key" \
"$gocardless_secret_id" "$gocardless_secret_key" "$dokubox_host" \
"$dokubox_port" "$dokubox_secure" "$dokubox_user" "$dokubox_password"
prepare_directories
echo
echo "Konfiguration geschrieben:"
echo " $ENV_FILE"
echo
echo "Vor dem Start prüfen:"
echo " 1. DNS zeigt auf diesen Server: $domain"
echo " 2. Ports 80, 443, 3478/tcp, 3478/udp, 5349/tcp, 49160-49200/udp sind offen"
echo " 3. Platzhalter für optionale Dienste in .env bei Bedarf ersetzen"
if [[ "$START_STACK" == "ask" ]]; then
if yes_no "Stack jetzt mit Docker Compose starten?" "n"; then
START_STACK="yes"
else
START_STACK="no"
fi
fi
if [[ "$START_STACK" == "yes" ]]; then
compose -f "$COMPOSE_FILE" up -d
else
echo
echo "Start später mit:"
if [[ "${FEDEO_USE_SUDO_DOCKER:-false}" == "true" ]]; then
echo " sudo docker compose -f $COMPOSE_FILE up -d"
else
echo " docker compose -f $COMPOSE_FILE up -d"
fi
fi
}
main