Fehler #1: Repository unter /tmp macht den Dienst startunfaehig
Gemeldet wurde ein Abbruch der Einrichtung mit "Der Dienst meldet sich nicht als
betriebsbereit". Der Diagnosebericht enthielt die Ursache woertlich:
syncova-api.service: Failed to set up mount namespacing:
/tmp/syncova-repository: No such file or directory
status=226/NAMESPACE
Die Diensteinheit setzt PrivateTmp=yes. Der Dienst bekommt damit ein eigenes
/tmp, und der in ReadWritePaths genannte Ablageort existiert in seiner Sicht
nicht — systemd bricht ab, bevor das Programm ueberhaupt laeuft.
Der technische Fehler ist der kleinere. Der groessere ist, dass setup.sh einen
Ablageort unter /tmp ueberhaupt angenommen hat: systemd-tmpfiles raeumt dort
regelmaessig auf, und auf einem tmpfs ist nach einem Neustart nichts mehr da.
Ein Backupsystem, das jede Nacht Erfolg meldet und keine Daten hat, ist
schlimmer als gar keines.
Deshalb:
- Fluechtige Ablageorte werden abgelehnt: /tmp, /var/tmp, /dev/shm, /run und
jedes tmpfs oder ramfs. Geprueft wird VOR der Datenbankeinrichtung — ein
unbeaufsichtigter Lauf scheitert damit in Sekunden statt nach Minuten und
einem Rueckbau.
- SYNCOVA_SETUP_ALLOW_VOLATILE_REPOSITORY=ja laesst es ausdruecklich zu; dann
wird PrivateTmp abgeschaltet, sonst startet der Dienst nie. Real geprueft:
Einheit traegt PrivateTmp=no, Dienst laeuft.
- Kommt der Dienst nicht hoch, liefert setup.sh die letzten Journalzeilen gleich
mit und erklaert 226/NAMESPACE. Der Verweis auf journalctl allein war
wertlos: Beim Rueckbau ist der Dienst weg, und der Meldende brauchte einen
zweiten Anlauf, um ueberhaupt zu erfahren, was los war.
Regressionstest vorhanden und als fangend geprueft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0b5ce96c51
commit
a37631c501
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,5 +1,22 @@
|
|||||||
# Änderungen
|
# Änderungen
|
||||||
|
|
||||||
|
## V1 — Release Candidate 3, 17. August 2026
|
||||||
|
|
||||||
|
Behebt [Issue #1](https://git.jfritzsche.de/jf/syncova-backup/issues/1): Die
|
||||||
|
Einrichtung brach ab, wenn das Repository unter `/tmp` liegen sollte.
|
||||||
|
|
||||||
|
- **Ein flüchtiger Ablageort wird abgelehnt** — `/tmp`, `/var/tmp`, `/dev/shm`,
|
||||||
|
`/run` und jedes tmpfs. `systemd-tmpfiles` räumt dort auf, ein tmpfs ist nach
|
||||||
|
einem Neustart leer: Die Sicherungen verschwänden von selbst, ohne Meldung,
|
||||||
|
bis jemand sie braucht. Geprüft wird **vor** der Datenbankeinrichtung, damit
|
||||||
|
ein unbeaufsichtigter Lauf in Sekunden scheitert statt nach Minuten.
|
||||||
|
Für Wegwerf-Umgebungen: `SYNCOVA_SETUP_ALLOW_VOLATILE_REPOSITORY=ja` — dann
|
||||||
|
weicht `PrivateTmp`, sonst könnte der Dienst nicht starten.
|
||||||
|
- **Der Abbruch zeigt jetzt den Grund.** Kommt der Dienst nicht hoch, liefert
|
||||||
|
`setup.sh` die letzten Journalzeilen gleich mit und erklärt `226/NAMESPACE`.
|
||||||
|
Vorher verwies er nur auf `journalctl` — und beim Rückbau war der Dienst dann
|
||||||
|
schon weg.
|
||||||
|
|
||||||
## V1 — Release Candidate 2, 17. August 2026
|
## V1 — Release Candidate 2, 17. August 2026
|
||||||
|
|
||||||
Ergänzt gegenüber `rc1`:
|
Ergänzt gegenüber `rc1`:
|
||||||
|
|||||||
@ -17,6 +17,12 @@ wiederherstellt. Sie beschreibt den Serverteil; die Agenten stehen in
|
|||||||
Datenträgerausfall nähme sonst Original und Sicherung gemeinsam mit. Das ist
|
Datenträgerausfall nähme sonst Original und Sicherung gemeinsam mit. Das ist
|
||||||
keine Feinheit der Einrichtung, sondern der Zweck der Übung.
|
keine Feinheit der Einrichtung, sondern der Zweck der Übung.
|
||||||
|
|
||||||
|
**Und nicht nach `/tmp`, `/var/tmp`, `/run` oder auf ein tmpfs.**
|
||||||
|
`systemd-tmpfiles` räumt die ersten beiden regelmäßig auf, ein tmpfs liegt im
|
||||||
|
Arbeitsspeicher. Die Sicherungen verschwänden dort von selbst — ohne Meldung,
|
||||||
|
bis jemand sie braucht. `setup.sh` lehnt solche Orte ab; die Diensteinheit
|
||||||
|
könnte mit `PrivateTmp=yes` ohnehin nicht starten.
|
||||||
|
|
||||||
## Der kurze Weg: setup.sh
|
## Der kurze Weg: setup.sh
|
||||||
|
|
||||||
Das Paket bringt ein Einrichtungsskript mit. Es geht genau die Schritte dieser
|
Das Paket bringt ein Einrichtungsskript mit. Es geht genau die Schritte dieser
|
||||||
|
|||||||
@ -57,6 +57,26 @@ statt im Klartext zu lauschen.
|
|||||||
**Nichts im Protokoll, Prozess weg** — meist die Datenbank. Der Start wartet 30
|
**Nichts im Protokoll, Prozess weg** — meist die Datenbank. Der Start wartet 30
|
||||||
Sekunden auf sie und endet dann mit einer Meldung.
|
Sekunden auf sie und endet dann mit einer Meldung.
|
||||||
|
|
||||||
|
**`status=226/NAMESPACE`, „Failed to set up mount namespacing"** — ein Pfad in
|
||||||
|
`ReadWritePaths` der Diensteinheit existiert **aus Sicht des Dienstes** nicht.
|
||||||
|
Der häufigste Fall ist ein Repository unterhalb von `/tmp`: Die Einheit setzt
|
||||||
|
`PrivateTmp=yes`, der Dienst bekommt damit ein eigenes `/tmp`, und der
|
||||||
|
Ablageort ist dort nicht vorhanden.
|
||||||
|
|
||||||
|
```text
|
||||||
|
syncova-api.service: Failed to set up mount namespacing: /tmp/… : No such file or directory
|
||||||
|
syncova-api.service: Failed at step NAMESPACE spawning /opt/syncova/bin/syncova-api
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ein Repository gehört ohnehin nicht nach `/tmp`.** `systemd-tmpfiles` räumt
|
||||||
|
dort regelmäßig auf, und auf vielen Systemen ist es ein tmpfs — nach einem
|
||||||
|
Neustart leer. Ein Backupsystem, das jede Nacht Erfolg meldet und keine Daten
|
||||||
|
hat, ist schlimmer als gar keines. `setup.sh` lehnt solche Orte seit `rc3` ab.
|
||||||
|
|
||||||
|
Abhilfe: Repository auf ein dauerhaftes Verzeichnis legen (etwa
|
||||||
|
`/srv/syncova-repository`) und `ReadWritePaths` in
|
||||||
|
`/etc/systemd/system/syncova-api.service` anpassen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Anmeldung schlägt fehl
|
## Anmeldung schlägt fehl
|
||||||
|
|||||||
@ -225,3 +225,60 @@ func TestOperationScriptsAreShipped(testInstance *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSetupRefusesVolatileRepositoryPaths ist der Regressionstest zu Issue #1.
|
||||||
|
//
|
||||||
|
// Gemeldet wurde ein Abbruch der Einrichtung mit "Der Dienst meldet sich nicht
|
||||||
|
// als betriebsbereit". Die Ursache stand im Diagnosebericht: Der Ablageort lag
|
||||||
|
// unter /tmp, die Diensteinheit setzt PrivateTmp=yes, und damit existiert der
|
||||||
|
// Pfad aus Sicht des Dienstes nicht — systemd bricht mit 226/NAMESPACE ab.
|
||||||
|
//
|
||||||
|
// Der technische Fehler ist der kleinere. Der groessere ist, dass ein
|
||||||
|
// Backup-Repository unter /tmp ueberhaupt angenommen wurde: systemd-tmpfiles
|
||||||
|
// raeumt dort auf, und auf einem tmpfs ist nach einem Neustart nichts mehr da.
|
||||||
|
// Ein Backupsystem, das jede Nacht Erfolg meldet und keine Daten hat, ist
|
||||||
|
// schlimmer als gar keines.
|
||||||
|
func TestSetupRefusesVolatileRepositoryPaths(testInstance *testing.T) {
|
||||||
|
setupScript, readError := os.ReadFile(filepath.Join(repositoryRoot, "scripts/setup.sh"))
|
||||||
|
if readError != nil {
|
||||||
|
testInstance.Fatalf("setup.sh ließ sich nicht lesen: %v", readError)
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptText := string(setupScript)
|
||||||
|
|
||||||
|
// Die Orte, die abgelehnt werden muessen.
|
||||||
|
for _, volatilePath := range []string{"/tmp", "/var/tmp", "/dev/shm", "/run"} {
|
||||||
|
if !strings.Contains(scriptText, volatilePath+"|"+volatilePath+"/*") {
|
||||||
|
testInstance.Errorf("setup.sh prüft %s nicht als flüchtigen Ablageort", volatilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Der allgemeine Fall: Was auf einem tmpfs liegt, überlebt keinen Neustart —
|
||||||
|
// gleich wie das Verzeichnis heißt.
|
||||||
|
for _, filesystemName := range []string{"tmpfs", "ramfs"} {
|
||||||
|
if !strings.Contains(scriptText, filesystemName) {
|
||||||
|
testInstance.Errorf("setup.sh erkennt %s nicht als flüchtiges Dateisystem", filesystemName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die Prüfung muss **vor** der Datenbankeinrichtung greifen. Sonst steht am
|
||||||
|
// Ende eine installierte PostgreSQL und ein Rückbau, der zwar funktioniert,
|
||||||
|
// aber Minuten gekostet hat.
|
||||||
|
firstCheckPosition := strings.Index(scriptText, `validateRepositoryPath "${repositoryPath}"`)
|
||||||
|
databaseSectionPosition := strings.Index(scriptText, "PostgreSQL wird installiert")
|
||||||
|
|
||||||
|
if firstCheckPosition < 0 || databaseSectionPosition < 0 {
|
||||||
|
testInstance.Fatal("die Prüfung oder der Datenbankabschnitt wurde nicht gefunden")
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstCheckPosition > databaseSectionPosition {
|
||||||
|
testInstance.Error("der Ablageort wird erst nach der Datenbankeinrichtung geprüft; " +
|
||||||
|
"ein unbeaufsichtigter Lauf soll in Sekunden scheitern, nicht nach Minuten")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Und wenn jemand die Ablehnung ausdrücklich übergeht, muss PrivateTmp
|
||||||
|
// weichen — sonst startet der Dienst nie.
|
||||||
|
if !strings.Contains(scriptText, "privateTmpSetting") {
|
||||||
|
testInstance.Error("PrivateTmp wird nicht abgeschaltet, wenn das Repository unter /tmp liegt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
118
scripts/setup.sh
118
scripts/setup.sh
@ -301,6 +301,75 @@ queryAsPostgres() {
|
|||||||
su - postgres -c "psql -tAc \"$1\"" 2>/dev/null || true
|
su - postgres -c "psql -tAc \"$1\"" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# volatileRepositoryOverride laesst einen fluechtigen Ort ausdruecklich zu.
|
||||||
|
readonly volatileRepositoryOverride="${SYNCOVA_SETUP_ALLOW_VOLATILE_REPOSITORY:-nein}"
|
||||||
|
|
||||||
|
# repositoryIsUnderPrivateTmp meldet einen Ort unterhalb von /tmp.
|
||||||
|
#
|
||||||
|
# Der Dienst laeuft mit PrivateTmp=yes und bekommt damit ein **eigenes** /tmp.
|
||||||
|
# Ein Repository unter /tmp existiert in seiner Sicht nicht; systemd bricht den
|
||||||
|
# Start mit 226/NAMESPACE ab, noch bevor das Programm laeuft.
|
||||||
|
repositoryIsUnderPrivateTmp() {
|
||||||
|
[[ "$1" == /tmp/* || "$1" == "/tmp" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# validateRepositoryPath weist einen fluechtigen Ablageort zurueck.
|
||||||
|
#
|
||||||
|
# **Der wichtigste Punkt dieser ganzen Datei.** Ein Backup-Repository unter /tmp,
|
||||||
|
# /var/tmp oder auf einem tmpfs ist nicht unbequem, sondern falsch:
|
||||||
|
#
|
||||||
|
# - systemd-tmpfiles raeumt /tmp und /var/tmp regelmaessig auf. Die Sicherungen
|
||||||
|
# verschwinden dann von selbst, ohne Meldung und ohne dass jemand es merkt —
|
||||||
|
# bis er sie braucht.
|
||||||
|
# - Ein tmpfs liegt im Arbeitsspeicher. Nach einem Neustart ist es leer.
|
||||||
|
#
|
||||||
|
# Fuer ein Programm, dessen einziger Zweck die Aufbewahrung von Daten ist, waere
|
||||||
|
# das die schlimmste denkbare Voreinstellung: Es meldete jede Nacht erfolgreiche
|
||||||
|
# Sicherungen und haette keine.
|
||||||
|
validateRepositoryPath() {
|
||||||
|
local candidatePath="$1" refusalReason=""
|
||||||
|
|
||||||
|
case "${candidatePath}" in
|
||||||
|
/tmp|/tmp/*) refusalReason="/tmp wird von systemd-tmpfiles regelmaessig aufgeraeumt" ;;
|
||||||
|
/var/tmp|/var/tmp/*) refusalReason="/var/tmp wird von systemd-tmpfiles regelmaessig aufgeraeumt" ;;
|
||||||
|
/dev/shm|/dev/shm/*) refusalReason="/dev/shm liegt im Arbeitsspeicher" ;;
|
||||||
|
/run|/run/*) refusalReason="/run liegt im Arbeitsspeicher" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Der allgemeine Fall: Was auf einem tmpfs liegt, ueberlebt keinen Neustart —
|
||||||
|
# gleich wie das Verzeichnis heisst.
|
||||||
|
if [[ -z "${refusalReason}" ]] && [[ -d "${candidatePath}" ]]; then
|
||||||
|
local filesystemType
|
||||||
|
filesystemType="$(stat -f -c '%T' "${candidatePath}" 2>/dev/null || echo unbekannt)"
|
||||||
|
|
||||||
|
case "${filesystemType}" in
|
||||||
|
tmpfs|ramfs) refusalReason="der Ort liegt auf einem ${filesystemType} (Arbeitsspeicher)" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -z "${refusalReason}" ]] && return 0
|
||||||
|
|
||||||
|
if [[ "${volatileRepositoryOverride}" == "ja" ]]; then
|
||||||
|
writeWarning "Fluechtiger Ablageort ausdruecklich zugelassen: ${refusalReason}."
|
||||||
|
writeDetail "Die Sicherungen dort ueberleben weder Aufraeumlaeufe noch einen Neustart."
|
||||||
|
writeDetail "Das ist ausschliesslich fuer Wegwerf-Umgebungen vertretbar."
|
||||||
|
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
abortWithMessage "Der Ort ${candidatePath} taugt nicht als Repository:
|
||||||
|
${refusalReason}.
|
||||||
|
|
||||||
|
Die Sicherungen wuerden dort verschwinden, ohne dass es jemandem
|
||||||
|
auffaellt — und ein Backupsystem, das jede Nacht Erfolg meldet und
|
||||||
|
keine Daten hat, ist schlimmer als gar keines.
|
||||||
|
|
||||||
|
Nehmen Sie ein dauerhaftes Verzeichnis, etwa /srv/syncova-repository,
|
||||||
|
und moeglichst einen anderen Datentraeger als den der Quelldaten.
|
||||||
|
|
||||||
|
Nur fuer Wegwerf-Umgebungen: SYNCOVA_SETUP_ALLOW_VOLATILE_REPOSITORY=ja"
|
||||||
|
}
|
||||||
|
|
||||||
# detectPackageManager ermittelt das Paketwerkzeug der Distribution.
|
# detectPackageManager ermittelt das Paketwerkzeug der Distribution.
|
||||||
detectPackageManager() {
|
detectPackageManager() {
|
||||||
if command -v apt-get >/dev/null 2>&1; then echo "apt"; return; fi
|
if command -v apt-get >/dev/null 2>&1; then echo "apt"; return; fi
|
||||||
@ -358,6 +427,11 @@ fi
|
|||||||
packageVersion="$("${packageDirectory}/bin/syncova-api" --version 2>/dev/null | awk '{print $2}')"
|
packageVersion="$("${packageDirectory}/bin/syncova-api" --version 2>/dev/null | awk '{print $2}')"
|
||||||
writeSuccess "Paket gefunden: ${packageVersion}"
|
writeSuccess "Paket gefunden: ${packageVersion}"
|
||||||
|
|
||||||
|
# Den vorgesehenen Ablageort sofort pruefen. Im unbeaufsichtigten Lauf soll das
|
||||||
|
# in Sekunden scheitern und nicht erst, nachdem PostgreSQL installiert, das
|
||||||
|
# Schema angelegt und ein Administrator eingerichtet wurde.
|
||||||
|
validateRepositoryPath "${repositoryPath}"
|
||||||
|
|
||||||
# Pruefsummen kontrollieren, sofern das Paket welche mitbringt.
|
# Pruefsummen kontrollieren, sofern das Paket welche mitbringt.
|
||||||
if [[ -f "${packageDirectory}/SHA256SUMS" ]] && command -v sha256sum >/dev/null 2>&1; then
|
if [[ -f "${packageDirectory}/SHA256SUMS" ]] && command -v sha256sum >/dev/null 2>&1; then
|
||||||
if ( cd "${packageDirectory}" && sha256sum --quiet -c SHA256SUMS >/dev/null 2>&1 ); then
|
if ( cd "${packageDirectory}" && sha256sum --quiet -c SHA256SUMS >/dev/null 2>&1 ); then
|
||||||
@ -708,6 +782,8 @@ if [[ "${unattendedMode}" != "ja" ]]; then
|
|||||||
askQuestion "Ort des Repositorys" "${repositoryPath}" repositoryPath
|
askQuestion "Ort des Repositorys" "${repositoryPath}" repositoryPath
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
validateRepositoryPath "${repositoryPath}"
|
||||||
|
|
||||||
if [[ -e "${repositoryPath}/format/repository.json" ]]; then
|
if [[ -e "${repositoryPath}/format/repository.json" ]]; then
|
||||||
writeDetail "Unter ${repositoryPath} liegt bereits ein Repository; es wird verwendet."
|
writeDetail "Unter ${repositoryPath} liegt bereits ein Repository; es wird verwendet."
|
||||||
else
|
else
|
||||||
@ -733,6 +809,20 @@ fi
|
|||||||
printf '\n'
|
printf '\n'
|
||||||
writeStep "Dienst"
|
writeStep "Dienst"
|
||||||
|
|
||||||
|
# PrivateTmp und ein Repository unter /tmp schliessen einander aus.
|
||||||
|
#
|
||||||
|
# Mit PrivateTmp=yes bekommt der Dienst ein eigenes /tmp; der Ablageort
|
||||||
|
# existiert in seiner Sicht dann nicht, und systemd bricht den Start mit
|
||||||
|
# 226/NAMESPACE ab — bevor das Programm ueberhaupt laeuft. Der Fall tritt nur
|
||||||
|
# noch mit ausdruecklicher Freigabe auf; dann muss die Haertung an dieser einen
|
||||||
|
# Stelle weichen, sonst startet der Dienst nie.
|
||||||
|
privateTmpSetting="yes"
|
||||||
|
|
||||||
|
if repositoryIsUnderPrivateTmp "${repositoryPath}"; then
|
||||||
|
privateTmpSetting="no"
|
||||||
|
writeWarning "PrivateTmp wird abgeschaltet, weil das Repository unter /tmp liegt."
|
||||||
|
fi
|
||||||
|
|
||||||
cat > "${serviceUnitFile}" <<UNIT
|
cat > "${serviceUnitFile}" <<UNIT
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Syncova Control Plane
|
Description=Syncova Control Plane
|
||||||
@ -751,7 +841,7 @@ RestartSec=5
|
|||||||
|
|
||||||
# Haertung: Der Dienst braucht Netz und sein Repository, sonst nichts.
|
# Haertung: Der Dienst braucht Netz und sein Repository, sonst nichts.
|
||||||
NoNewPrivileges=yes
|
NoNewPrivileges=yes
|
||||||
PrivateTmp=yes
|
PrivateTmp=${privateTmpSetting}
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ProtectHome=yes
|
ProtectHome=yes
|
||||||
ReadWritePaths=${repositoryPath} ${backupDirectory}
|
ReadWritePaths=${repositoryPath} ${backupDirectory}
|
||||||
@ -789,8 +879,30 @@ done
|
|||||||
if [[ "${serviceIsReady}" != "ja" ]]; then
|
if [[ "${serviceIsReady}" != "ja" ]]; then
|
||||||
printf '\n'
|
printf '\n'
|
||||||
writeWarning "Der Dienst meldet sich nicht als betriebsbereit."
|
writeWarning "Der Dienst meldet sich nicht als betriebsbereit."
|
||||||
writeDetail " systemctl status ${serviceName}"
|
|
||||||
writeDetail " journalctl -u ${serviceName} -n 50 --no-pager"
|
# Die Zeilen gleich mitliefern statt auf sie zu verweisen.
|
||||||
|
#
|
||||||
|
# Ein Abbruch, der nur sagt "sehen Sie ins Journal", verlangt einen zweiten
|
||||||
|
# Anlauf, um ueberhaupt zu erfahren, was los war — und beim Rueckbau ist der
|
||||||
|
# Dienst dann schon weg.
|
||||||
|
if command -v journalctl >/dev/null 2>&1; then
|
||||||
|
printf '\n'
|
||||||
|
writeDetail "Die letzten Zeilen des Dienstes:"
|
||||||
|
printf '\n'
|
||||||
|
journalctl -u "${serviceName}" -n 20 --no-pager 2>/dev/null | sed 's/^/ /' >&2
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
|
# 226/NAMESPACE ist der haeufigste Fall und ohne Erklaerung nicht zu
|
||||||
|
# deuten: Er bedeutet fast immer einen Pfad in ReadWritePaths, den der
|
||||||
|
# Dienst in seiner eigenen Namensraumsicht nicht sieht.
|
||||||
|
if journalctl -u "${serviceName}" -n 20 --no-pager 2>/dev/null | grep -q "226/NAMESPACE"; then
|
||||||
|
writeDetail "226/NAMESPACE bedeutet: Ein Pfad der Diensteinheit ist aus Sicht"
|
||||||
|
writeDetail "des Dienstes nicht vorhanden. Pruefen Sie ReadWritePaths in"
|
||||||
|
writeDetail "${serviceUnitFile} — insbesondere den Ort des Repositorys."
|
||||||
|
printf '\n'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
abortWithMessage "Die Einrichtung wird zurueckgebaut."
|
abortWithMessage "Die Einrichtung wird zurueckgebaut."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user