diff --git a/README.md b/README.md index 133701e..652ccaa 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,26 @@ cp .env.example .env Ersetze anschließend alle Platzhalter und passe mindestens `DOMAIN`, `CONTACT_EMAIL`, Datenbank-, Secret-, SMTP- und S3-Werte an. +Alternativ kannst du die Konfiguration geführt erzeugen lassen: + +```bash +bash scripts/selfhost-setup.sh +``` + +Für den schnellen Standardpfad: + +```bash +bash scripts/selfhost-setup.sh --simple +``` + +Für mehr Rückfragen zu SMTP, API-Schlüsseln und optionalen Diensten: + +```bash +bash scripts/selfhost-setup.sh --advanced +``` + +Der Assistent erklärt zuerst die Selfhost-Verzeichnisstruktur, schreibt anschließend `.env`, legt persistente Verzeichnisse inklusive `traefik/letsencrypt/acme.json` an und kann den Stack optional direkt starten. + ## Beispiel `.env` Diese Datei liegt neben der `docker-compose.yml`: diff --git a/scripts/selfhost-setup.sh b/scripts/selfhost-setup.sh new file mode 100755 index 0000000..eb672fa --- /dev/null +++ b/scripts/selfhost-setup.sh @@ -0,0 +1,469 @@ +#!/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" + +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' &2 + done +} + +prompt_secret() { + local label="$1" + local default_value="${2:-}" + local value + + if [[ -n "$default_value" ]]; then + read -r -s -p "$label [vorbelegt, Enter übernimmt]: " value + echo >&2 + echo "${value:-$default_value}" + else + read -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 -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 -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 <"$ENV_FILE" <")" + + 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 + docker compose -f "$COMPOSE_FILE" up -d + else + echo + echo "Start später mit:" + echo " docker compose -f docker-compose.selfhost.yml up -d" + fi +} + +main