deploy/lib/preflight.sh prüft Python, venv, Platz, Betriebssystem, Umgebung (VM/Container), pypi und optional Checkmk/Ollama per TCP. first-run.sh und setup-lxc.sh rufen ihn auf (--skip-preflight, --preflight-only). LXC-Teile nur bei erkanntem Container; Texte neutral; README um VM-Abschnitt ergänzt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
279 lines
9.6 KiB
Bash
Executable File
279 lines
9.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Erstinstallation von checkbot in einer frischen Debian-12/13-VM oder einem LXC (auch Ubuntu ab 22.04). Als root aus dem Git-Checkout starten.
|
|
set -euo pipefail
|
|
|
|
SELF=$(readlink -f "${BASH_SOURCE[0]}")
|
|
# shellcheck source=lib/common.sh
|
|
source "$(dirname "$SELF")/lib/common.sh"
|
|
# shellcheck source=lib/caddy.sh
|
|
source "$COMMON_DIR/caddy.sh"
|
|
# shellcheck source=lib/timezone.sh
|
|
source "$COMMON_DIR/timezone.sh"
|
|
# shellcheck source=lib/preflight.sh
|
|
source "$COMMON_DIR/preflight.sh"
|
|
|
|
PACKAGES=(python3 python3-venv python3-pip ca-certificates curl rsync tzdata)
|
|
CLI_LINK=/usr/local/sbin/checkbot-cli
|
|
RELAXED_DROPIN_SRC=$DEPLOY_DIR/systemd/checkbot-lxc-relaxed.conf
|
|
RELAXED_DROPIN_DST=$SYSTEMD_DIR/checkbot.service.d/lxc-relaxed.conf
|
|
BACKUP_RELAXED_DROPIN_DST=$SYSTEMD_DIR/checkbot-backup.service.d/lxc-relaxed.conf
|
|
SECRET_FILES=(master.key session.secret)
|
|
HOSTNAME_RE='^[A-Za-z0-9]([A-Za-z0-9.-]*[A-Za-z0-9])?$|^[0-9A-Fa-f:]+$'
|
|
IP_LIST_RE='^[0-9A-Fa-f:./ ,]*$'
|
|
PATH_RE='^[A-Za-z0-9_./-]+$'
|
|
|
|
FQDN=${CHECKBOT_FQDN:-}
|
|
MONITOR_IP=${CHECKBOT_MONITOR_IP:-}
|
|
TLS_MODE=${CHECKBOT_TLS_MODE:-internal}
|
|
TLS_CERT=${CHECKBOT_TLS_CERT:-}
|
|
TLS_KEY=${CHECKBOT_TLS_KEY:-}
|
|
TIMEZONE=${CHECKBOT_TZ:-Europe/Berlin}
|
|
ASSUME_NO_PROMPT=0
|
|
RELAXED=0
|
|
SKIP_PREFLIGHT=0
|
|
PREFLIGHT_ONLY=0
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Aufruf: sudo deploy/setup-lxc.sh [Optionen]
|
|
|
|
Installiert checkbot (Code, venv, Schlüssel, systemd, Caddy) in einer Debian-12/13-VM oder einem LXC.
|
|
Mehrfaches Ausführen ist sicher; Vorhandenes (Schlüssel, checkbot.env) wird nicht überschrieben.
|
|
|
|
Optionen (Umgebungsvariable in Klammern):
|
|
--fqdn NAME Hostname der UI, Standard: hostname -f (CHECKBOT_FQDN)
|
|
--monitor-ip IPS Monitoring-Server für /health, mehrere mit Komma (CHECKBOT_MONITOR_IP)
|
|
--tls-mode MODUS internal (Caddy-interne CA, Standard) oder files (CHECKBOT_TLS_MODE)
|
|
--tls-cert DATEI Zertifikat (PEM) für --tls-mode files (CHECKBOT_TLS_CERT)
|
|
--tls-key DATEI privater Schlüssel (PEM) für --tls-mode files (CHECKBOT_TLS_KEY)
|
|
--tz ZONE Systemzeitzone, Standard Europe/Berlin (CHECKBOT_TZ)
|
|
--lxc-relaxed gelockertes systemd-Drop-In für unprivilegierte LXCs installieren
|
|
(nur bei Exit-Code 218/226/228; in einer VM nicht nötig)
|
|
--skip-preflight Preflight-Prüfungen überspringen
|
|
--preflight-only nur die Preflight-Prüfungen ausführen und beenden
|
|
--no-prompt nie nachfragen (z.B. wegen Admin-Anlage)
|
|
-h, --help diese Hilfe
|
|
|
|
Zusätzlich: CHECKBOT_CADDY_KEY_FPR pinnt den Fingerabdruck des Caddy-Repo-Schlüssels (nur falls Caddy
|
|
nicht im Debian-Repository liegt).
|
|
USAGE
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--fqdn) FQDN=${2:?--fqdn braucht einen Wert}; shift 2 ;;
|
|
--monitor-ip) MONITOR_IP=${2:?--monitor-ip braucht einen Wert}; shift 2 ;;
|
|
--tls-mode) TLS_MODE=${2:?--tls-mode braucht einen Wert}; shift 2 ;;
|
|
--tls-cert) TLS_CERT=${2:?--tls-cert braucht einen Wert}; shift 2 ;;
|
|
--tls-key) TLS_KEY=${2:?--tls-key braucht einen Wert}; shift 2 ;;
|
|
--tz) TIMEZONE=${2:?--tz braucht einen Wert}; shift 2 ;;
|
|
--lxc-relaxed) RELAXED=1; shift ;;
|
|
--skip-preflight) SKIP_PREFLIGHT=1; shift ;;
|
|
--preflight-only) PREFLIGHT_ONLY=1; shift ;;
|
|
--no-prompt) ASSUME_NO_PROMPT=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) usage >&2; die "Unbekannte Option: $1" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
validate_args() {
|
|
(( ! SKIP_PREFLIGHT || ! PREFLIGHT_ONLY )) || die "--skip-preflight und --preflight-only schließen sich aus."
|
|
if [[ -z $FQDN ]]; then
|
|
FQDN=$(hostname -f 2>/dev/null || hostname)
|
|
fi
|
|
[[ $FQDN =~ $HOSTNAME_RE ]] || die "Ungültiger Hostname für die UI: '$FQDN'"
|
|
validate_timezone_name "$TIMEZONE"
|
|
MONITOR_IP=${MONITOR_IP//,/ }
|
|
[[ $MONITOR_IP =~ $IP_LIST_RE ]] || die "Ungültige Monitoring-IP: '$MONITOR_IP'"
|
|
case $TLS_MODE in
|
|
internal) ;;
|
|
files)
|
|
[[ -f $TLS_CERT && -f $TLS_KEY ]] || die "TLS-Modus files braucht --tls-cert und --tls-key (lesbare Dateien)."
|
|
[[ $TLS_CERT =~ $PATH_RE && $TLS_KEY =~ $PATH_RE ]] || die "Zertifikatspfade dürfen nur Buchstaben, Ziffern und ._/- enthalten."
|
|
;;
|
|
*) die "Ungültiger TLS-Modus '$TLS_MODE' (internal oder files)." ;;
|
|
esac
|
|
}
|
|
|
|
run_preflight() {
|
|
(( ! SKIP_PREFLIGHT )) || { pf_detect_env; return 0; }
|
|
PF_PACKAGES_PENDING=1
|
|
preflight_run "$APP_DIR" "$DATA_DIR" || die "Preflight fehlgeschlagen (FEHLER-Zeilen oben). --skip-preflight übergeht die Prüfung."
|
|
}
|
|
|
|
install_packages() {
|
|
log "Installiere Pakete."
|
|
apt-get update -qq
|
|
apt_install "${PACKAGES[@]}"
|
|
}
|
|
|
|
ensure_user_and_dirs() {
|
|
if ! getent passwd "$APP_USER" >/dev/null; then
|
|
log "Lege Systembenutzer $APP_USER an."
|
|
useradd --system --user-group --home-dir "$DATA_DIR" --no-create-home --shell /usr/sbin/nologin "$APP_USER"
|
|
fi
|
|
install -d -m 0750 -o root -g "$APP_GROUP" "$CONF_DIR"
|
|
install -d -m 0750 -o "$APP_USER" -g "$APP_GROUP" "$DATA_DIR"
|
|
install -d -m 0700 -o "$APP_USER" -g "$APP_GROUP" "$BACKUP_DIR"
|
|
}
|
|
|
|
# Das Konfigurationsverzeichnis gehört root; checkbot kann dort keine Dateien anlegen, daher als root erzeugen und übergeben.
|
|
ensure_keys() {
|
|
local name
|
|
(
|
|
umask 077
|
|
cd "$APP_DIR"
|
|
CHECKBOT_CONFIG_DIR=$CONF_DIR PYTHONDONTWRITEBYTECODE=1 "$VENV_DIR/bin/python" -m app.cli gen-keys
|
|
)
|
|
for name in "${SECRET_FILES[@]}"; do
|
|
[[ -f $CONF_DIR/$name ]] || die "$CONF_DIR/$name fehlt nach gen-keys."
|
|
chown "$APP_USER:$APP_GROUP" "$CONF_DIR/$name"
|
|
chmod 0600 "$CONF_DIR/$name"
|
|
done
|
|
}
|
|
|
|
ensure_env_file() {
|
|
if [[ -f $ENV_FILE ]]; then
|
|
log "$ENV_FILE existiert, bleibt unverändert."
|
|
else
|
|
install -m 0640 -o root -g "$APP_GROUP" "$DEPLOY_DIR/checkbot.env.example" "$ENV_FILE"
|
|
log "$ENV_FILE angelegt."
|
|
fi
|
|
chown root:"$APP_GROUP" "$ENV_FILE"
|
|
chmod 0640 "$ENV_FILE"
|
|
}
|
|
|
|
install_dropins() {
|
|
if (( RELAXED )); then
|
|
if ! pf_is_container; then
|
|
warn "--lxc-relaxed ohne erkannten Container (Umgebung: $PF_VIRT_KIND); das Drop-In schwächt die Sandbox und wird nur auf ausdrücklichen Wunsch installiert."
|
|
fi
|
|
install -D -m 0644 "$RELAXED_DROPIN_SRC" "$RELAXED_DROPIN_DST"
|
|
install -D -m 0644 "$RELAXED_DROPIN_SRC" "$BACKUP_RELAXED_DROPIN_DST"
|
|
systemctl daemon-reload
|
|
log "Gelockertes systemd-Drop-In installiert."
|
|
elif [[ -e $RELAXED_DROPIN_DST ]]; then
|
|
warn "$RELAXED_DROPIN_DST existiert noch (frühere Installation mit --lxc-relaxed) und lockert die Sandbox weiter."
|
|
fi
|
|
}
|
|
|
|
install_cli_link() {
|
|
ln -sfn "$APP_DIR/deploy/checkbot-cli" "$CLI_LINK"
|
|
}
|
|
|
|
configure_caddy() {
|
|
local port host upstream rendered
|
|
ensure_caddy
|
|
port=$(env_value CHECKBOT_BIND_PORT "$DEFAULT_BIND_PORT")
|
|
host=$(env_value CHECKBOT_BIND_HOST "$DEFAULT_BIND_HOST")
|
|
case $host in 0.0.0.0|::|"") host=127.0.0.1 ;; esac
|
|
[[ $host == *:* ]] && host="[$host]"
|
|
upstream="$host:$port"
|
|
if [[ $TLS_MODE == files ]]; then
|
|
install_tls_files "$TLS_CERT" "$TLS_KEY"
|
|
fi
|
|
rendered=$(render_caddyfile "$FQDN" "$TLS_MODE" "$MONITOR_IP" "$upstream")
|
|
apply_caddyfile "$rendered"
|
|
}
|
|
|
|
start_service() {
|
|
systemctl enable "$SERVICE_NAME" >/dev/null
|
|
systemctl enable --now checkbot-backup.timer >/dev/null
|
|
systemctl restart "$SERVICE_NAME"
|
|
if ! wait_health; then
|
|
warn "Dienst antwortet nicht innerhalb von ${HEALTH_WAIT_SECONDS} s."
|
|
warn "Logs: journalctl -u $SERVICE_NAME -n 100 --no-pager."
|
|
if pf_is_container; then
|
|
warn "Bei Exit-Code 218/226/228 im Container: --lxc-relaxed (siehe deploy/README.md)."
|
|
fi
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
has_admin() {
|
|
local db
|
|
db=$(db_file)
|
|
[[ -f $db ]] || return 1
|
|
runuser -u "$APP_USER" -- python3 -I -c '
|
|
import sqlite3, sys
|
|
try:
|
|
n = sqlite3.connect(sys.argv[1]).execute("SELECT COUNT(*) FROM users WHERE role = ? AND disabled = 0", ("admin",)).fetchone()[0]
|
|
except sqlite3.Error:
|
|
n = 0
|
|
sys.exit(0 if n else 1)
|
|
' "$db"
|
|
}
|
|
|
|
offer_admin() {
|
|
local answer=""
|
|
if has_admin; then
|
|
log "Ein Admin existiert bereits."
|
|
return 0
|
|
fi
|
|
if (( ! ASSUME_NO_PROMPT )) && [[ -t 0 && -t 1 ]]; then
|
|
read -r -p "Ersten Admin jetzt anlegen? [J/n] " answer || answer=n
|
|
if [[ ! $answer =~ ^[nN] ]]; then
|
|
"$APP_DIR/deploy/checkbot-cli" create-admin && return 0
|
|
warn "Admin-Anlage fehlgeschlagen. Später: sudo checkbot-cli create-admin"
|
|
fi
|
|
fi
|
|
ADMIN_PENDING=1
|
|
}
|
|
|
|
summary() {
|
|
cat <<SUMMARY
|
|
|
|
checkbot ist installiert.
|
|
|
|
UI: https://$FQDN/
|
|
Dienst: systemctl status $SERVICE_NAME
|
|
Logs: journalctl -u $SERVICE_NAME -f
|
|
Backups: täglich nach $BACKUP_DIR (Timer checkbot-backup.timer)
|
|
|
|
SUMMARY
|
|
if (( ${ADMIN_PENDING:-0} )); then
|
|
printf 'Noch offen: Admin anlegen mit\n sudo checkbot-cli create-admin\n\n'
|
|
fi
|
|
cat <<SUMMARY
|
|
Nächste Schritte:
|
|
1. In der UI anmelden und den Setup-Assistenten durchlaufen.
|
|
2. Checkmk: Automation-User mit Lese-Rolle anlegen und in der UI eintragen.
|
|
3. Telegram: Bot bei @BotFather anlegen, Token und Chat-ID in der UI eintragen (optional Matrix).
|
|
4. Ollama-Host (und ggf. Token/Proxy) in der UI eintragen.
|
|
5. master.key und session.secret aus $CONF_DIR getrennt von der DB sichern.
|
|
SUMMARY
|
|
if [[ $TLS_MODE == internal ]]; then
|
|
printf ' 6. Root-Zertifikat der Caddy-CA an die Clients verteilen: /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt\n'
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
case ${1:-} in -h|--help) usage; return 0 ;; esac
|
|
parse_args "$@"
|
|
require_root
|
|
validate_args
|
|
run_preflight
|
|
if (( PREFLIGHT_ONLY )); then return 0; fi
|
|
umask 022
|
|
install_packages
|
|
ensure_timezone "$TIMEZONE"
|
|
check_python_version
|
|
ensure_user_and_dirs
|
|
sync_code
|
|
ensure_venv
|
|
ensure_keys
|
|
ensure_env_file
|
|
install_units
|
|
install_dropins
|
|
install_cli_link
|
|
configure_caddy
|
|
start_service
|
|
ADMIN_PENDING=0
|
|
offer_admin
|
|
summary
|
|
}
|
|
|
|
main "$@"
|