Files
docker-podman-installation-…/install.sh
T
duffyduckandClaude Opus 5 b912578f27 Firewall-Pruefung: welche Ports offen sein muessen - und welche nicht
Auf RHEL ist firewalld ab Werk aktiv und gibt in aller Regel nur SSH
frei. Der Stack laeuft dann sauber, ist aber von aussen nicht zu
erreichen: die Agents kommen nicht durch den Receiver auf 8000, und im
Browser passiert nichts. Das war bisher nur eine Handvoll statischer
Beispielzeilen in der README und in install.sh.

Neu ist "cmk-manage.sh firewall". Erkannt werden firewalld, ufw,
nftables und iptables; welche Ports noetig sind, ergibt sich aus der
Konfiguration statt aus einer festen Liste:

  - ohne Proxy Manager wird 5000 zum Pflichtport, mit ihm soll es zu
    bleiben - offenes 5000 wird deshalb ausdruecklich angemerkt
  - der Admin-Port kommt aus NPM_ADMIN_PORT, nicht fest aus 81
  - FW_ROLE unterscheidet single/central/remote: eine Remote-Site
    braucht 6557 eingehend, die Zentrale baut die Verbindungen selbst
    auf und braucht dafuer nichts. Steht FW_ROLE=remote bei
    CMK_LIVESTATUS_TCP=off, ist der Port zwar offen, es lauscht aber
    niemand - auch darauf weist der Bericht hin.
  - Event Console ueber FW_EC_SYSLOG / FW_EC_SNMPTRAP / FW_EC_TCP,
    Agent dieses Hosts ueber FW_AGENT, alles Weitere (Grafana, NSCA)
    ueber FW_EXTRA_PORTS

Syslog und SNMP-Traps laufen ueber UDP, eine TCP-Freigabe nuetzt ihnen
nichts. Ports werden deshalb durchweg als nummer/protokoll gefuehrt -
beim Pruefen wie beim Erzeugen der Regeln. Bestehende Regeln werden
entsprechend gelesen: Portbereiche (6556-6558 bzw. 6556:6558) werden
aufgeloest, firewalld-Dienstnamen und Rich Rules zaehlen mit, bei
firewalld alle aktiven Zonen statt nur der Standardzone, und eine
ufw-Regel ohne Protokoll zaehlt fuer TCP und UDP - so wie ufw sie
anlegt.

"firewall open" zeigt die passenden Kommandos, --apply setzt sie nach
Rueckfrage, --source begrenzt sie auf ein Quellnetz (fuer Port 80 mit
Warnung, weil Lets Encrypt von aussen prueft). Ohne Befund ist der
Rueckgabewert 1, damit sich das auswerten laesst. In der Oberflaeche
steckt derselbe Bericht unter "Firewall pruefen".

Nebenbei: env_set hat Werte mit Leerzeichen samt Anfuehrungszeichen in
die laufende Umgebung exportiert. In der Datei gehoeren sie hin, im
Wert nicht - das fiel erst mit den Beschreibungen in FW_EXTRA_PORTS auf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 19:46:33 +02:00

360 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
# =====================================================================
# install.sh - Installation des Checkmk-/NGINX-Proxy-Manager-Stacks
#
# Debian/Ubuntu : Docker CE + Compose-Plugin
# RHEL/Rocky/Alma/CentOS/Fedora : Podman + podman-compose
#
# Zusaetzlich: dialog (ncurses-Oberflaeche), openssl, tar, curl
# =====================================================================
set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$PROJECT_DIR/scripts/lib/common.sh"
WITH_SYSTEMD=""
WITH_QUADLET=""
SKIP_RUNTIME=""
ROOTLESS=""
usage() {
cat <<'EOF'
install.sh - Installation des Checkmk-Container-Stacks
OPTIONEN
--with-systemd systemd-Unit einrichten, damit der Stack nach einem
Neustart des Hosts automatisch wieder startet
(Vorgabe bei Podman)
--quadlet statt der Compose-Unit Podman-Quadlet-Dateien
(/etc/containers/systemd/) einrichten
--rootless Podman rootless vorbereiten (linger + Ports ab 80)
--skip-runtime Docker/Podman nicht installieren (nur Projekt einrichten)
-h, --help Hilfe
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
--with-systemd) WITH_SYSTEMD=1; shift ;;
--quadlet) WITH_QUADLET=1; WITH_SYSTEMD=1; shift ;;
--rootless) ROOTLESS=1; shift ;;
--skip-runtime) SKIP_RUNTIME=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "Unbekannte Option: $1" ;;
esac
done
[ "$(id -u)" -eq 0 ] || die "Bitte als root ausfuehren: sudo ./install.sh"
# ---------------------------------------------------------------------
# Distribution erkennen
# ---------------------------------------------------------------------
[ -r /etc/os-release ] || die "/etc/os-release nicht lesbar - Distribution unbekannt."
# shellcheck disable=SC1091
. /etc/os-release
OS_ID="${ID:-unknown}"
OS_LIKE="${ID_LIKE:-}"
OS_NAME="${PRETTY_NAME:-$OS_ID}"
FAMILY=""
case "$OS_ID" in
debian|ubuntu|linuxmint|raspbian|pop) FAMILY=debian ;;
rhel|centos|rocky|almalinux|fedora|ol|oracle|scientific) FAMILY=rhel ;;
*)
case " $OS_LIKE " in
*debian*) FAMILY=debian ;;
*rhel*|*fedora*|*centos*) FAMILY=rhel ;;
esac
;;
esac
[ -n "$FAMILY" ] || die "Nicht unterstuetzte Distribution: $OS_NAME"
printf '\n%s=== Checkmk Container-Stack - Installation ===%s\n\n' "$C_BLD" "$C_RST"
log "System : $OS_NAME"
log "Familie : $FAMILY"
log "Projektpfad : $PROJECT_DIR"
# ---------------------------------------------------------------------
# Runtime installieren
# ---------------------------------------------------------------------
install_debian() {
log "Installiere Docker CE aus dem offiziellen Docker-Repository ..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y ca-certificates curl gnupg tar dialog openssl
if [ "$OS_ID" = debian ] || [ "$OS_ID" = ubuntu ]; then
install -m 0755 -d /etc/apt/keyrings
if [ ! -f /etc/apt/keyrings/docker.asc ]; then
curl -fsSL "https://download.docker.com/linux/$OS_ID/gpg" -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
fi
local codename="${VERSION_CODENAME:-}"
[ -n "$codename" ] || codename="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-stable}")"
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$OS_ID $codename stable" \
>/etc/apt/sources.list.d/docker.list
apt-get update -qq
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
else
warn "Abgeleitete Distribution ($OS_NAME) - verwende die Distributionspakete."
apt-get install -y docker.io docker-compose
fi
systemctl enable --now docker
ok "Docker installiert: $(docker --version)"
}
# Prueft, ob ueberhaupt ein nutzbares Repository vorhanden ist. Auf einem
# nicht registrierten RHEL ist das nicht der Fall - dnf meldet dann nur
# "This system is not registered with an entitlement server".
rhel_repo_check() {
local PM="$1"
$PM -q list --available podman >/dev/null 2>&1 && return 0
err "Es ist kein nutzbares Paket-Repository vorhanden."
cat >&2 <<'EOF'
Auf Red Hat Enterprise Linux liefert dnf ohne Subskription keine Pakete.
Drei Wege fuehren weiter (ausfuehrlich in der README, Kapitel 5.1):
1) Kostenlose Developer-Subskription (empfohlen, bis 16 Systeme):
Konto auf https://developers.redhat.com anlegen, dann
subscription-manager register --username <benutzer>
subscription-manager status
2) Installations-DVD als lokales Repository (ohne Konto, offline):
mount -o loop,ro /pfad/rhel-9.x-x86_64-dvd.iso /mnt/rhel
(Repo-Datei anlegen - siehe README, Kapitel 5.1)
3) Auf Rocky Linux oder AlmaLinux wechseln (binaerkompatibel, frei):
dieses Projekt laeuft dort unveraendert.
Pruefen, was aktiv ist: subscription-manager status ; dnf repolist
EOF
return 1
}
install_rhel() {
log "Installiere Podman ..."
local PM=dnf
command -v dnf >/dev/null 2>&1 || PM=yum
rhel_repo_check "$PM" || die "Abbruch - bitte zuerst den Paketzugang einrichten."
$PM install -y podman tar openssl || die "Podman-Installation fehlgeschlagen."
# dialog ist optional: liegt in AppStream. Fehlt es, genuegt whiptail
# aus dem Paket "newt" (BaseOS) - die Skripte koennen beides.
if ! $PM install -y dialog 2>/dev/null; then
warn "'dialog' nicht verfuegbar - versuche 'newt' (whiptail)."
$PM install -y newt 2>/dev/null \
|| warn "Weder dialog noch newt installierbar - die ncurses-Oberflaeche
steht dann nicht zur Verfuegung, die Kommandozeile funktioniert weiterhin."
fi
# podman-compose besorgen (Reihenfolge: Distro-Repo -> EPEL -> pip)
if ! command -v podman-compose >/dev/null 2>&1; then
log "Suche podman-compose ..."
if ! $PM install -y podman-compose 2>/dev/null; then
if [ "$OS_ID" != fedora ]; then
local rel; rel="${VERSION_ID%%.*}"
log "Aktiviere EPEL ${rel} ..."
$PM install -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-${rel}.noarch.rpm" 2>/dev/null || true
$PM install -y podman-compose 2>/dev/null || true
fi
fi
fi
if ! command -v podman-compose >/dev/null 2>&1; then
log "Versuche podman-compose per pip ..."
$PM install -y python3-pip 2>/dev/null || true
pip3 install --quiet podman-compose 2>/dev/null || true
fi
if command -v podman-compose >/dev/null 2>&1; then
ok "podman-compose vorhanden: $(podman-compose --version 2>&1 | head -n1)"
else
warn "podman-compose konnte nicht installiert werden.
Alternative: Installation ueber Quadlet ( ./install.sh --quadlet ),
dafuer wird kein Compose benoetigt."
fi
ok "Podman installiert: $(podman --version)"
# SELinux: Bind-Mounts brauchen den passenden Kontext (:Z ist im Compose gesetzt)
if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" = "Enforcing" ]; then
log "SELinux ist aktiv - die Bind-Mounts sind im Compose mit ':Z' versehen."
fi
}
if [ -z "$SKIP_RUNTIME" ]; then
case "$FAMILY" in
debian) install_debian ;;
rhel) install_rhel ;;
esac
else
log "Runtime-Installation uebersprungen (--skip-runtime)."
fi
# ---------------------------------------------------------------------
# Projekt vorbereiten
# ---------------------------------------------------------------------
log "Bereite Projektverzeichnis vor ..."
mkdir -p "$PROJECT_DIR/data/checkmk/sites" \
"$PROJECT_DIR/data/npm/data" \
"$PROJECT_DIR/data/npm/letsencrypt" \
"$PROJECT_DIR/data/npm/data/nginx/custom" \
"$PROJECT_DIR/data/npm/custom-certs" \
"$PROJECT_DIR/backups" \
"$PROJECT_DIR/images"
chmod +x "$PROJECT_DIR"/scripts/*.sh "$PROJECT_DIR/install.sh" 2>/dev/null || true
if [ ! -f "$PROJECT_DIR/.env" ]; then
cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env"
chmod 600 "$PROJECT_DIR/.env"
ok ".env aus .env.example erzeugt - bitte Passwort und Site-Namen anpassen!"
else
log ".env ist bereits vorhanden - bleibt unveraendert."
fi
load_env "$PROJECT_DIR/.env"
detect_runtime
ok "Verwendete Runtime: $RUNTIME"
# Podman: Autostart nach Reboot ohne systemd-Unit funktioniert nicht ->
# Unit standardmaessig einrichten.
if [ "$RUNTIME" = podman ] && [ -z "$WITH_SYSTEMD" ]; then
warn "Podman hat keinen Daemon: ohne systemd-Unit starten die Container
nach einem Reboot NICHT automatisch. Die Unit wird jetzt eingerichtet."
WITH_SYSTEMD=1
fi
# ---------------------------------------------------------------------
# Rootless-Vorbereitung (optional)
# ---------------------------------------------------------------------
if [ -n "$ROOTLESS" ]; then
log "Bereite rootless-Betrieb vor ..."
echo 'net.ipv4.ip_unprivileged_port_start=80' >/etc/sysctl.d/99-checkmk-rootless.conf
sysctl -q --system || true
ok "Unprivilegierte Ports ab 80 erlaubt."
warn "Zusaetzlich noetig: loginctl enable-linger <benutzer>
und die Unit als '--user'-Unit einrichten (siehe README)."
fi
# ---------------------------------------------------------------------
# systemd
# ---------------------------------------------------------------------
install_compose_unit() {
detect_compose
log "Verwendetes Compose-Kommando: ${COMPOSE_CMD[*]}"
# Die Unit ruft bewusst cmk-manage.sh auf statt compose direkt: nur so
# werden NPM_ENABLED und der Start aller Sites beruecksichtigt.
log "Richte systemd-Unit 'checkmk-stack.service' ein ..."
cat >/etc/systemd/system/checkmk-stack.service <<EOF
[Unit]
Description=Checkmk + NGINX Proxy Manager (Container-Stack)
Documentation=file://$PROJECT_DIR/README.md
Wants=network-online.target
After=network-online.target
$([ "$RUNTIME" = docker ] && echo "Requires=docker.service" || true)
$([ "$RUNTIME" = docker ] && echo "After=docker.service" || true)
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=$PROJECT_DIR
EnvironmentFile=$PROJECT_DIR/.env
TimeoutStartSec=0
ExecStart=$PROJECT_DIR/scripts/cmk-manage.sh -y up
ExecStartPost=$PROJECT_DIR/scripts/cmk-manage.sh start-all-sites
ExecStop=$PROJECT_DIR/scripts/cmk-manage.sh -y down
ExecReload=$PROJECT_DIR/scripts/cmk-manage.sh -y up
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable checkmk-stack.service
ok "Unit aktiviert. Steuerung: systemctl start|stop|status checkmk-stack"
}
install_quadlet_units() {
log "Richte Podman-Quadlet-Units ein (/etc/containers/systemd/) ..."
[ -d /etc/containers/systemd ] || mkdir -p /etc/containers/systemd
local f
for f in "$PROJECT_DIR"/systemd/quadlet/*.container; do
# Proxy Manager abgeschaltet? Dann auch keine Unit dafuer.
if [ "$(basename "$f")" = "nginx-proxy-manager.container" ] && ! npm_enabled; then
log "NPM_ENABLED=off - ueberspringe nginx-proxy-manager.container"
rm -f /etc/containers/systemd/nginx-proxy-manager.container
continue
fi
sed -e "s|__PROJECT_DIR__|$PROJECT_DIR|g" \
-e "s|__CMK_IMAGE__|${CMK_IMAGE:-docker.io/checkmk/check-mk-raw:2.3.0-latest}|g" \
-e "s|__NPM_IMAGE__|${NPM_IMAGE:-docker.io/jc21/nginx-proxy-manager:2}|g" \
-e "s|__CMK_SITE_ID__|${CMK_SITE_ID:-cmk}|g" \
-e "s|__CMK_CONTAINER_NAME__|${CMK_CONTAINER_NAME:-checkmk}|g" \
-e "s|__NPM_CONTAINER_NAME__|${NPM_CONTAINER_NAME:-nginx-proxy-manager}|g" \
"$f" >"/etc/containers/systemd/$(basename "$f")"
done
systemctl daemon-reload
ok "Quadlet-Units geschrieben. Start: systemctl start checkmk nginx-proxy-manager"
log "Die Units starten durch 'WantedBy=multi-user.target' automatisch nach einem Reboot."
}
if [ -n "$WITH_SYSTEMD" ]; then
if [ -n "$WITH_QUADLET" ]; then
[ "$RUNTIME" = podman ] || die "--quadlet ist nur mit Podman moeglich."
install_quadlet_units
else
install_compose_unit
fi
fi
# ---------------------------------------------------------------------
# Firewall-Hinweise
# ---------------------------------------------------------------------
print_firewall_hint() {
local be=""
if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
be=firewalld
elif command -v ufw >/dev/null 2>&1 \
&& ( set +o pipefail; ufw status 2>/dev/null | grep -qi '^Status: active' ); then
be=ufw
elif command -v nft >/dev/null 2>&1 && [ -n "$(nft list ruleset 2>/dev/null)" ]; then
be=nftables
fi
[ -n "$be" ] || return 0
cat <<EOF
Firewall: ${be} ist aktiv. Auf RHEL ist ab Werk nur SSH freigegeben - dann
kommen weder Browser noch Agents durch. Welche Ports dieser Stack braucht und
ob sie offen sind, sagt:
$PROJECT_DIR/scripts/cmk-manage.sh firewall
sudo $PROJECT_DIR/scripts/cmk-manage.sh firewall open --apply
Port 5000 (Checkmk direkt) bleibt dabei zu, solange der Proxy Manager laeuft.
EOF
}
print_firewall_hint
cat <<EOF
${C_BLD}Installation abgeschlossen.${C_RST}
Naechste Schritte:
1) Konfiguration anpassen:
\$EDITOR $PROJECT_DIR/.env (CMK_SITE_ID, CMK_PASSWORD, TZ ...)
2) Stack starten:
$PROJECT_DIR/scripts/cmk-manage.sh up
3) NGINX Proxy Manager oeffnen:
http://$(primary_ip):81 Erstlogin: admin@example.com / changeme
4) Ist Port 81 im Netz gesperrt, laesst sich die Adminoberflaeche auf einen
anderen Port legen:
$PROJECT_DIR/scripts/cmk-manage.sh npm-port 10443
Oder ganz ohne Netzzugriff per Terminal-Browser auf dieser Maschine:
$PROJECT_DIR/scripts/cmk-manage.sh browser
5) Verwaltung / Backup / Migration - ncurses-Oberflaeche:
$PROJECT_DIR/scripts/cmk-manage.sh gui
Alles Weitere steht in der README.md.
EOF