Gemeldet wurde ein Abbruch der Einrichtung mit "Der Dienst meldet sich nicht als betriebsbereit". Der Dienst lief einwandfrei — das Protokoll im Bericht zeigt einen vollstaendigen Start, und genau fuenfzehn Sekunden spaeter beendet ihn der Rueckbau. Fuenfzehn Sekunden sind genau fuenfzehn Pruefversuche. Die Ursache stand ebenfalls im Bericht, eine Zeile weiter oben: ## Gesundheit curl ist nicht vorhanden. setup.sh prueft die Betriebsbereitschaft ausschliesslich mit curl. Auf einem schlanken Serverabbild ist der nicht installiert; das ist der Normalfall und nicht die Ausnahme. Die Pruefung kam nicht an den Dienst heran, hielt das fuer einen gescheiterten Start und baute eine funktionierende Anlage zurueck. Ein Einrichtungsskript darf nicht voraussetzen, was es nicht selbst mitbringt. Jetzt drei Wege: curl, sonst wget, sonst /dev/tcp der Bash — letzteres gehoert zur Shell selbst und ist damit ueberall vorhanden. Betrifft setup.sh, update.sh und diagnose.sh; der Diagnosebericht nennt zusaetzlich, womit er gemessen hat. Real nachgewiesen auf einem System ohne curl UND ohne wget: rc3 bricht ab, die korrigierte Fassung laeuft durch. Regressionstest vorhanden und als fangend geprueft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
488 lines
19 KiB
Bash
Executable File
488 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Sammelt den Zustand einer Syncova-Installation fuer eine Fehlermeldung.
|
|
#
|
|
# Der Sinn: **eine** Ausgabe statt zwoelf Rueckfragen. Was hier steht, ist genau
|
|
# das, wonach man sonst einzeln fragen muesste — und was ohne Nachfrage meist
|
|
# fehlt: Fassung, Schemastand, Dateisystem des Repositorys, gemessene
|
|
# Durchsetzungsstufe, die letzten Fehlerzeilen.
|
|
#
|
|
# sudo ./diagnose.sh Ausgabe auf den Bildschirm
|
|
# sudo ./diagnose.sh > bericht.md In eine Datei
|
|
#
|
|
# Das Skript **veraendert nichts**. Es liest, mehr nicht.
|
|
#
|
|
# Geheimnisse werden nicht ausgegeben. Der Weg dahin ist bewusst umgekehrt: Es
|
|
# gibt eine Liste der Werte, die gezeigt werden **duerfen**; alles andere bleibt
|
|
# draussen. Eine Sperrliste vergaesse den naechsten neuen Wert.
|
|
|
|
set -uo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Feste Orte — gleichlautend in setup.sh, update.sh und uninstall.sh
|
|
# ---------------------------------------------------------------------------
|
|
|
|
readonly installationRoot="/opt/syncova"
|
|
readonly configurationDirectory="/etc/syncova"
|
|
readonly configurationFile="${configurationDirectory}/syncova.env"
|
|
readonly serviceName="syncova-api"
|
|
readonly backupDirectory="/var/backups/syncova"
|
|
|
|
# publicSettings sind die Werte, die in einer Fehlermeldung stehen duerfen.
|
|
#
|
|
# Eine Erlaubnisliste und keine Sperrliste: Kommt morgen eine neue Variable mit
|
|
# einem Geheimnis hinzu, faellt sie hier automatisch heraus. Bei einer Sperrliste
|
|
# stuende sie im naechsten Bericht.
|
|
readonly publicSettings=(
|
|
SYNCOVA_ENV
|
|
SYNCOVA_DB_HOST SYNCOVA_DB_PORT SYNCOVA_DB_NAME SYNCOVA_DB_USER SYNCOVA_DB_SSLMODE
|
|
SYNCOVA_DB_MAX_OPEN_CONNECTIONS
|
|
SYNCOVA_ENCRYPTION_CURRENT_KEY
|
|
SYNCOVA_HTTP_LISTEN_ADDRESS SYNCOVA_HTTP_REQUESTS_PER_MINUTE
|
|
SYNCOVA_HTTP_TLS_CERT_FILE SYNCOVA_HTTP_TLS_KEY_FILE
|
|
SYNCOVA_LOG_LEVEL SYNCOVA_LOG_FORMAT
|
|
SYNCOVA_AUTH_REQUIRE_MFA_FOR_PRIVILEGED_USERS
|
|
SYNCOVA_AUTH_MAX_FAILED_LOGIN_ATTEMPTS SYNCOVA_AUTH_LOCKOUT_DURATION
|
|
SYNCOVA_RESTORE_ALLOWED_ROOTS
|
|
SYNCOVA_ALLOW_INTERNAL_NOTIFICATION_TARGETS
|
|
)
|
|
|
|
# secretValues sind die tatsaechlichen Geheimnisse dieser Anlage.
|
|
#
|
|
# Sie werden gelesen, um sie aus **jeder** Ausgabe zu entfernen — auch aus
|
|
# Protokollzeilen, in die sie auf einem Weg geraten sind, den niemand vorhergesehen
|
|
# hat. Die Erlaubnisliste oben schuetzt die Konfiguration; das hier schuetzt alles
|
|
# uebrige.
|
|
secretValues=()
|
|
|
|
|
|
# serviceIsReachable prueft, ob eine Adresse mit HTTP 200 antwortet.
|
|
#
|
|
# Drei Wege, weil keiner ueberall vorhanden ist. Genau daran ist die Einrichtung
|
|
# in Issue #2 gescheitert: Der Dienst lief einwandfrei — Protokoll und
|
|
# Zeitstempel belegen es —, aber auf dem System war kein curl installiert. Die
|
|
# Bereitschaftspruefung hielt das fuer einen gescheiterten Start und baute eine
|
|
# funktionierende Anlage wieder zurueck.
|
|
#
|
|
# Ein Einrichtungsskript darf nicht voraussetzen, was es nicht selbst mitbringt.
|
|
serviceIsReachable() {
|
|
local requestURL="$1"
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fsS --max-time 5 "${requestURL}" >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
# wget endet bei 4xx und 5xx mit einem Fehler — dasselbe Verhalten wie
|
|
# "curl -f".
|
|
if command -v wget >/dev/null 2>&1; then
|
|
wget -q -O /dev/null --timeout=5 "${requestURL}" >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
bashHTTPStatusIsOK "${requestURL}"
|
|
}
|
|
|
|
# bashHTTPStatusIsOK spricht HTTP ohne jedes Zusatzwerkzeug.
|
|
#
|
|
# Der letzte Ausweg: /dev/tcp gehoert zur Bash selbst. Damit laesst sich die
|
|
# Bereitschaft auch auf einem System pruefen, auf dem weder curl noch wget
|
|
# liegen — und das ist bei einem schlanken Serverabbild der Normalfall, nicht
|
|
# die Ausnahme.
|
|
bashHTTPStatusIsOK() {
|
|
local strippedURL="${1#http://}"
|
|
local hostAndPort="${strippedURL%%/*}"
|
|
local requestPath="/${strippedURL#*/}"
|
|
|
|
# Ohne Pfadanteil liefert die Ersetzung oben die Adresse zurueck.
|
|
[[ "${requestPath}" == "/${hostAndPort}" ]] && requestPath="/"
|
|
|
|
local targetHost="${hostAndPort%%:*}"
|
|
local targetPort="${hostAndPort##*:}"
|
|
[[ "${targetPort}" == "${targetHost}" ]] && targetPort=80
|
|
|
|
exec 3<>"/dev/tcp/${targetHost}/${targetPort}" 2>/dev/null || return 1
|
|
|
|
printf 'GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n' \
|
|
"${requestPath}" "${hostAndPort}" >&3
|
|
|
|
local statusLine=""
|
|
read -r -t 5 statusLine <&3
|
|
|
|
exec 3<&- 3>&- 2>/dev/null
|
|
|
|
[[ "${statusLine}" == *" 200 "* ]]
|
|
}
|
|
|
|
# writeHeading schreibt eine Ueberschrift.
|
|
writeHeading() { printf '\n## %s\n\n' "$1"; }
|
|
|
|
# writeBlock rahmt einen Ausgabeblock.
|
|
writeBlock() { printf '```text\n%s\n```\n' "$1"; }
|
|
|
|
# redactSecrets entfernt bekannte Geheimnisse aus einem Text.
|
|
redactSecrets() {
|
|
local text="$1" secretValue
|
|
|
|
for secretValue in "${secretValues[@]}"; do
|
|
[[ ${#secretValue} -lt 8 ]] && continue
|
|
text="${text//${secretValue}/***}"
|
|
done
|
|
|
|
printf '%s' "${text}"
|
|
}
|
|
|
|
# httpToolInUse benennt das verwendete Werkzeug.
|
|
#
|
|
# Es gehoert in den Bericht: Steht dort der Notweg ueber /dev/tcp, weiss der
|
|
# Leser, dass auf diesem System weder curl noch wget liegt — und genau daran ist
|
|
# in Issue #2 eine Einrichtung gescheitert.
|
|
if command -v curl >/dev/null 2>&1; then
|
|
httpToolInUse="curl"
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
httpToolInUse="wget (curl ist nicht vorhanden)"
|
|
else
|
|
httpToolInUse="Bash /dev/tcp (weder curl noch wget vorhanden)"
|
|
fi
|
|
|
|
# fetchHTTPBody holt eine Antwort mit dem vorhandenen Werkzeug.
|
|
fetchHTTPBody() {
|
|
local requestURL="$1"
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -s --max-time 10 -w $'\n(HTTP %{http_code})' "${requestURL}" 2>&1 || echo '(nicht erreichbar)'
|
|
return
|
|
fi
|
|
|
|
if command -v wget >/dev/null 2>&1; then
|
|
wget -q -O - --timeout=10 "${requestURL}" 2>&1 || echo '(nicht erreichbar)'
|
|
return
|
|
fi
|
|
|
|
if bashHTTPStatusIsOK "${requestURL}"; then
|
|
echo "(HTTP 200 — Inhalt ohne curl oder wget nicht lesbar)"
|
|
else
|
|
echo "(nicht erreichbar oder kein HTTP 200)"
|
|
fi
|
|
}
|
|
|
|
# runAndCapture fuehrt ein Kommando aus und gibt seine Ausgabe redigiert zurueck.
|
|
runAndCapture() {
|
|
local commandOutput
|
|
commandOutput="$("$@" 2>&1)" || true
|
|
|
|
[[ -z "${commandOutput}" ]] && commandOutput="(keine Ausgabe)"
|
|
|
|
redactSecrets "${commandOutput}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Konfiguration einlesen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
configurationIsReadable="nein"
|
|
|
|
if [[ -r "${configurationFile}" ]]; then
|
|
configurationIsReadable="ja"
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "${configurationFile}"
|
|
set +a
|
|
|
|
[[ -n "${SYNCOVA_DB_PASSWORD:-}" ]] && secretValues+=("${SYNCOVA_DB_PASSWORD}")
|
|
[[ -n "${SYNCOVA_ENCRYPTION_KEYS:-}" ]] && secretValues+=("${SYNCOVA_ENCRYPTION_KEYS}")
|
|
|
|
# Auch der reine Schluesselwert ohne Versionspraefix, falls er einzeln
|
|
# irgendwo auftaucht.
|
|
if [[ "${SYNCOVA_ENCRYPTION_KEYS:-}" == *:* ]]; then
|
|
secretValues+=("${SYNCOVA_ENCRYPTION_KEYS#*:}")
|
|
fi
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Kopf
|
|
# ---------------------------------------------------------------------------
|
|
|
|
printf '# Syncova — Diagnosebericht\n\n'
|
|
printf 'Erstellt: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
|
|
|
if [[ "$(id -u)" -ne 0 ]]; then
|
|
printf '\n> **Hinweis:** ohne root-Rechte erstellt. Konfiguration, Dienstprotokoll\n'
|
|
printf '> und Datenbankangaben fehlen deshalb. Vollstaendig: `sudo ./diagnose.sh`\n'
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Fassungen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
writeHeading "Fassungen"
|
|
|
|
versionReport=""
|
|
|
|
if [[ -d "${installationRoot}/bin" ]]; then
|
|
for programPath in "${installationRoot}"/bin/*; do
|
|
[[ -x "${programPath}" ]] || continue
|
|
versionReport+="$("${programPath}" --version 2>/dev/null || echo "$(basename "${programPath}") (keine Versionsangabe)")"$'\n'
|
|
done
|
|
else
|
|
versionReport="Unter ${installationRoot}/bin liegt kein Programm."$'\n'
|
|
fi
|
|
|
|
[[ -f "${installationRoot}/VERSION" ]] && \
|
|
versionReport+="VERSION-Datei: $(cat "${installationRoot}/VERSION")"$'\n'
|
|
|
|
[[ -d "${installationRoot}.vorherige" ]] && \
|
|
versionReport+="Vorige Fassung liegt noch: ${installationRoot}.vorherige"$'\n'
|
|
|
|
writeBlock "${versionReport%$'\n'}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. System
|
|
# ---------------------------------------------------------------------------
|
|
|
|
writeHeading "System"
|
|
|
|
systemReport="Betriebssystem: $(. /etc/os-release 2>/dev/null && echo "${PRETTY_NAME:-unbekannt}" || echo unbekannt)
|
|
Kern: $(uname -sr)
|
|
Architektur: $(uname -m)"
|
|
|
|
# Container oder nicht: Er entscheidet ueber den Loeschschutz. In einem Container
|
|
# auf overlayfs gibt es kein Unveraenderlich-Kennzeichen, und ein "advisory" ist
|
|
# dann die richtige Auskunft und kein Fehler.
|
|
if [[ -f /.dockerenv ]] || grep -qa 'container=' /proc/1/environ 2>/dev/null; then
|
|
systemReport+=$'\n'"Umgebung: Container"
|
|
else
|
|
systemReport+=$'\n'"Umgebung: kein Container erkannt"
|
|
fi
|
|
|
|
systemReport+=$'\n'"systemd: $(command -v systemctl >/dev/null 2>&1 && echo vorhanden || echo 'nicht vorhanden')"
|
|
|
|
writeBlock "${systemReport}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Dienst
|
|
# ---------------------------------------------------------------------------
|
|
|
|
writeHeading "Dienst"
|
|
|
|
serviceReport=""
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
serviceState="$(systemctl is-active "${serviceName}" 2>/dev/null)"
|
|
serviceReport+="Zustand: ${serviceState:-nicht aktiv}"$'\n'
|
|
|
|
serviceEnabled="$(systemctl is-enabled "${serviceName}" 2>/dev/null)"
|
|
serviceReport+="Autostart: ${serviceEnabled:-nicht eingerichtet}"$'\n'
|
|
serviceReport+=$'\n'"$(runAndCapture systemctl status "${serviceName}" --no-pager -n 0)"
|
|
else
|
|
serviceReport="systemd ist nicht vorhanden."
|
|
fi
|
|
|
|
writeBlock "$(redactSecrets "${serviceReport}")"
|
|
|
|
# Die Gesundheitsantwort traegt auch bei 503 den vollstaendigen Bericht — sie
|
|
# sagt, welche Komponente fehlt, nicht nur dass etwas fehlt.
|
|
writeHeading "Gesundheit"
|
|
|
|
healthAddress="${SYNCOVA_HTTP_LISTEN_ADDRESS:-127.0.0.1:8080}"
|
|
healthReport=""
|
|
|
|
for endpointPath in /health/live /health/ready /api/v1/health; do
|
|
responseBody="$(fetchHTTPBody "http://${healthAddress}${endpointPath}")"
|
|
healthReport+="${endpointPath}: ${responseBody}"$'\n\n'
|
|
done
|
|
|
|
healthReport+="Abgefragt mit: ${httpToolInUse}"
|
|
|
|
writeBlock "$(redactSecrets "${healthReport%$'\n\n'}")"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Datenbank
|
|
# ---------------------------------------------------------------------------
|
|
|
|
writeHeading "Datenbank und Schema"
|
|
|
|
databaseReport=""
|
|
|
|
if [[ "${configurationIsReadable}" == "ja" ]] && [[ -x "${installationRoot}/bin/syncova-migrate" ]]; then
|
|
databaseReport+="$(runAndCapture "${installationRoot}/bin/syncova-migrate" status)"$'\n'
|
|
else
|
|
databaseReport+="Der Schemastand ist ohne lesbare Konfiguration nicht zu ermitteln."$'\n'
|
|
fi
|
|
|
|
if [[ "${configurationIsReadable}" == "ja" ]] && command -v psql >/dev/null 2>&1; then
|
|
databaseReport+=$'\n'"PostgreSQL: $(PGPASSWORD="${SYNCOVA_DB_PASSWORD:-}" psql -h "${SYNCOVA_DB_HOST:-127.0.0.1}" \
|
|
-p "${SYNCOVA_DB_PORT:-5432}" -U "${SYNCOVA_DB_USER:-syncova}" -d "${SYNCOVA_DB_NAME:-syncova}" \
|
|
-tAc 'SHOW server_version' 2>&1 | head -1)"
|
|
fi
|
|
|
|
writeBlock "$(redactSecrets "${databaseReport%$'\n'}")"
|
|
|
|
# Der Bestand sagt mehr als jede Beschreibung: Eine Anlage ohne einen einzigen
|
|
# erfolgreichen Lauf hat ein anderes Problem als eine mit tausend.
|
|
if [[ "${configurationIsReadable}" == "ja" ]] && command -v psql >/dev/null 2>&1; then
|
|
writeHeading "Bestand"
|
|
|
|
inventoryQuery="
|
|
SELECT 'Repositories: ' || count(*) FROM repositories
|
|
UNION ALL SELECT 'Auftraege: ' || count(*) FROM backup_jobs
|
|
UNION ALL SELECT 'Laeufe gesamt: ' || count(*) FROM backup_job_runs
|
|
UNION ALL SELECT 'davon erfolgreich: ' || count(*) FROM backup_job_runs WHERE status = 'succeeded'
|
|
UNION ALL SELECT 'davon Teilfehler: ' || count(*) FROM backup_job_runs WHERE status = 'partial_failure'
|
|
UNION ALL SELECT 'davon gescheitert: ' || count(*) FROM backup_job_runs WHERE status = 'failed'
|
|
UNION ALL SELECT 'Wiederherstellungsp.: ' || count(*) FROM backups WHERE deleted_at IS NULL
|
|
UNION ALL SELECT 'Agenten: ' || count(*) FROM agents
|
|
UNION ALL SELECT 'Konten (aktiv): ' || count(*) FROM users WHERE status = 'active';"
|
|
|
|
writeBlock "$(redactSecrets "$(PGPASSWORD="${SYNCOVA_DB_PASSWORD:-}" psql \
|
|
-h "${SYNCOVA_DB_HOST:-127.0.0.1}" -p "${SYNCOVA_DB_PORT:-5432}" \
|
|
-U "${SYNCOVA_DB_USER:-syncova}" -d "${SYNCOVA_DB_NAME:-syncova}" \
|
|
-tA -c "${inventoryQuery}" 2>&1)")"
|
|
|
|
# Die letzten gescheiterten Laeufe samt Fehlercode und Klasse. Der Code ist
|
|
# der Vertrag; der Meldungstext ist fuer Menschen und darf sich aendern.
|
|
writeHeading "Letzte nicht erfolgreiche Laeufe"
|
|
|
|
failureQuery="
|
|
SELECT to_char(coalesce(completed_at, started_at), 'YYYY-MM-DD HH24:MI') || ' ' ||
|
|
rpad(status, 16) || ' ' ||
|
|
rpad(coalesce(error_code, '-'), 26) || ' ' ||
|
|
coalesce(failure_class, '-')
|
|
FROM backup_job_runs
|
|
WHERE status <> 'succeeded'
|
|
ORDER BY coalesce(completed_at, started_at) DESC NULLS LAST
|
|
LIMIT 10;"
|
|
|
|
failureOutput="$(PGPASSWORD="${SYNCOVA_DB_PASSWORD:-}" psql \
|
|
-h "${SYNCOVA_DB_HOST:-127.0.0.1}" -p "${SYNCOVA_DB_PORT:-5432}" \
|
|
-U "${SYNCOVA_DB_USER:-syncova}" -d "${SYNCOVA_DB_NAME:-syncova}" \
|
|
-tA -c "${failureQuery}" 2>&1)"
|
|
|
|
[[ -z "${failureOutput}" ]] && failureOutput="(keine — alle Laeufe waren erfolgreich)"
|
|
|
|
writeBlock "$(redactSecrets "${failureOutput}")"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Repositories
|
|
# ---------------------------------------------------------------------------
|
|
|
|
writeHeading "Repositories"
|
|
|
|
repositoryReport=""
|
|
|
|
if [[ "${configurationIsReadable}" == "ja" ]] && command -v psql >/dev/null 2>&1; then
|
|
while IFS='|' read -r repositoryLocation repositoryHardened repositoryLevel; do
|
|
[[ -n "${repositoryLocation}" ]] || continue
|
|
|
|
repositoryReport+="Ort: ${repositoryLocation}"$'\n'
|
|
# PostgreSQL liefert "t"/"f" — das liest sich in einem Bericht wie ein
|
|
# Tippfehler.
|
|
[[ "${repositoryHardened}" == "t" ]] && repositoryHardened="ja" || repositoryHardened="nein"
|
|
repositoryReport+="Gehaertet: ${repositoryHardened}"$'\n'
|
|
repositoryReport+="Durchsetzung: ${repositoryLevel:-nie gemessen}"$'\n'
|
|
|
|
if [[ -d "${repositoryLocation}" ]]; then
|
|
# Das Dateisystem entscheidet ueber den Loeschschutz. Auf overlayfs,
|
|
# NFS oder ohne CAP_LINUX_IMMUTABLE gibt es kein
|
|
# Unveraenderlich-Kennzeichen — "advisory" ist dann richtig.
|
|
repositoryReport+="Dateisystem: $(stat -f -c '%T' "${repositoryLocation}" 2>/dev/null || echo unbekannt)"$'\n'
|
|
repositoryReport+="Belegt: $(du -sh "${repositoryLocation}" 2>/dev/null | cut -f1)"$'\n'
|
|
repositoryReport+="Frei: $(df -h "${repositoryLocation}" 2>/dev/null | awk 'NR==2 {print $4" von "$2}')"$'\n'
|
|
|
|
if [[ -x "${installationRoot}/bin/syncova-repo" ]]; then
|
|
repositoryReport+=$'\n'"$(runAndCapture "${installationRoot}/bin/syncova-repo" health --path "${repositoryLocation}")"$'\n'
|
|
fi
|
|
else
|
|
repositoryReport+="Zustand: NICHT ERREICHBAR"$'\n'
|
|
fi
|
|
|
|
repositoryReport+=$'\n'
|
|
done < <(PGPASSWORD="${SYNCOVA_DB_PASSWORD:-}" psql -h "${SYNCOVA_DB_HOST:-127.0.0.1}" \
|
|
-p "${SYNCOVA_DB_PORT:-5432}" -U "${SYNCOVA_DB_USER:-syncova}" -d "${SYNCOVA_DB_NAME:-syncova}" \
|
|
-tA -F'|' -c "SELECT location, hardened, coalesce(enforcement_level,'') FROM repositories ORDER BY name" 2>/dev/null)
|
|
fi
|
|
|
|
# Ein Repository auf der Platte, das nicht in der Control Plane steht, ist eine
|
|
# haeufige und leicht zu uebersehende Lage: Die Sicherung laeuft nie, weil das
|
|
# Ziel dem Server unbekannt ist. Sie gehoert in den Bericht.
|
|
for candidatePath in /srv/syncova-repository "${SYNCOVA_SETUP_REPOSITORY_PATH:-}"; do
|
|
[[ -n "${candidatePath}" ]] || continue
|
|
[[ -f "${candidatePath}/format/repository.json" ]] || continue
|
|
[[ "${repositoryReport}" == *"${candidatePath}"* ]] && continue
|
|
|
|
repositoryReport+="Ort: ${candidatePath}"$'\n'
|
|
repositoryReport+="Eingetragen: NEIN — die Control Plane kennt dieses Repository nicht."$'\n'
|
|
repositoryReport+=" Ohne Eintrag laeuft keine Sicherung hinein."$'\n'
|
|
repositoryReport+="Dateisystem: $(stat -f -c '%T' "${candidatePath}" 2>/dev/null || echo unbekannt)"$'\n\n'
|
|
done
|
|
|
|
[[ -z "${repositoryReport}" ]] && repositoryReport="(keines gefunden oder nicht ermittelbar)"
|
|
|
|
writeBlock "$(redactSecrets "${repositoryReport%$'\n'}")"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6. Konfiguration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
writeHeading "Konfiguration"
|
|
|
|
configurationReport=""
|
|
|
|
if [[ "${configurationIsReadable}" == "ja" ]]; then
|
|
for settingName in "${publicSettings[@]}"; do
|
|
settingValue="${!settingName:-}"
|
|
[[ -n "${settingValue}" ]] && configurationReport+="${settingName}=${settingValue}"$'\n'
|
|
done
|
|
|
|
# Nur ob gesetzt, niemals der Wert.
|
|
configurationReport+=$'\n'"SYNCOVA_DB_PASSWORD: $( [[ -n "${SYNCOVA_DB_PASSWORD:-}" ]] && echo gesetzt || echo 'NICHT GESETZT' )"$'\n'
|
|
configurationReport+="SYNCOVA_ENCRYPTION_KEYS: $( [[ -n "${SYNCOVA_ENCRYPTION_KEYS:-}" ]] && echo gesetzt || echo 'NICHT GESETZT' )"$'\n'
|
|
configurationReport+=$'\n'"Rechte: $(stat -c '%a %U:%G' "${configurationFile}" 2>/dev/null || echo unbekannt) (erwartet: 600 syncova:syncova)"
|
|
else
|
|
configurationReport="${configurationFile} ist nicht lesbar (root-Rechte noetig?)."
|
|
fi
|
|
|
|
writeBlock "${configurationReport}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7. Protokoll
|
|
# ---------------------------------------------------------------------------
|
|
|
|
writeHeading "Letzte Fehler- und Warnzeilen"
|
|
|
|
logReport=""
|
|
|
|
if command -v journalctl >/dev/null 2>&1; then
|
|
logReport="$(journalctl -u "${serviceName}" --no-pager -n 400 2>/dev/null \
|
|
| grep -iE '"level":"(ERROR|WARN)"|error|fehler|panic' | tail -30)"
|
|
fi
|
|
|
|
[[ -z "${logReport}" ]] && logReport="(keine Fehler- oder Warnzeilen in den letzten 400 Eintraegen)"
|
|
|
|
writeBlock "$(redactSecrets "${logReport}")"
|
|
|
|
writeHeading "Die letzten Protokollzeilen"
|
|
|
|
recentLog=""
|
|
command -v journalctl >/dev/null 2>&1 && \
|
|
recentLog="$(journalctl -u "${serviceName}" --no-pager -n 25 2>/dev/null)"
|
|
|
|
[[ -z "${recentLog}" ]] && recentLog="(kein Journal verfuegbar)"
|
|
|
|
writeBlock "$(redactSecrets "${recentLog}")"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 8. Sicherungen der Aktualisierungen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [[ -d "${backupDirectory}" ]]; then
|
|
writeHeading "Sicherungen frueherer Aktualisierungen"
|
|
writeBlock "$(ls -lh "${backupDirectory}" 2>/dev/null | tail -8)"
|
|
fi
|
|
|
|
printf '\n---\n\n'
|
|
printf 'Erstellt mit `diagnose.sh`. Geheimnisse sind entfernt; sehen Sie den\n'
|
|
printf 'Bericht trotzdem durch, bevor Sie ihn veroeffentlichen.\n'
|