Fix SELinux backup execution context and probe source metadata before downtime
This commit is contained in:
1 parent
5c8a7a30be
commit
c0a24b63fc
9 files changed
+193
-14
No files matched your search
+34
-2
@@ -54,6 +54,35 @@ flowchart TD
|
||||
|
||||
Dateikopien erhalten mit `rsync -aHAX --numeric-ids` Eigentümer, Modus, Symlinks, Hardlinks innerhalb einer Komponente, ACLs und Extended Attributes. Tar erhält diese Metadaten einschließlich SELinux. Plattform-/Dateisystemunterstützung ist Voraussetzung. Externe Symlink-Ziele werden nicht automatisch gesichert; absolute Symlinks als einzelne Einträge bleiben erhalten. Geräte, FIFOs und andere Spezialdateien werden von der Archivprüfung abgelehnt. Zusätzliche Pfade sind deshalb Datenverzeichnisse, kein Ersatz für ein vollständiges Systemimage.
|
||||
|
||||
Vor jedem Backup und bei `--check` prüft rsync das Auflisten der Quellwurzel sowie
|
||||
das Übertragen ihrer Verzeichnisattribute in einen privaten temporären Prüfbereich
|
||||
unter `STAGING_ROOT`. Alle Kindobjekte sind von der Attributprobe ausgeschlossen;
|
||||
Datei-Inhalte werden nicht kopiert. Die Probe wird danach entfernt. Das erkennt
|
||||
beispielsweise eine SELinux-Ablehnung von `opendir` oder `relabelfrom` vor dem
|
||||
Wartungsmodus. Es belegt nicht den Zugriff auf jede untergeordnete Datei oder
|
||||
jedes dort abweichende Attribut. Der echte Kopierlauf bleibt strikt fehlergeprüft.
|
||||
Die rsync-Kindprozesse erhalten keine geerbten Update-/Backup-Sperrdeskriptoren;
|
||||
der übergeordnete Prozess hält seine Sperren unverändert weiter.
|
||||
|
||||
Backup-Dienst und abgekoppelter Update-Auftrag starten über `/bin/bash` mit
|
||||
`SELinuxContext=system_u:system_r:unconfined_service_t:s0`. Das erlaubt dem
|
||||
privilegierten Backup, unterschiedliche Web-, Mail- und Home-Labels zu lesen
|
||||
und Sicherheitsattribute zu erhalten. **Diese Prozesse und ihre Kindprozesse
|
||||
sind damit bewusst nicht durch SELinux eingeschränkt**; die übrigen Dienste
|
||||
bleiben ihrer bestehenden Policy unterworfen. Das ist keine eigens eingeschränkte
|
||||
Backup-Policy. Es werden keine globalen rsync-Booleans, Quell-Labels oder
|
||||
Dateizugriffsrechte geändert. Ein verweigerter Kontext wird nicht mit dem
|
||||
systemd-Präfix `-` übergangen. Bei deaktiviertem SELinux ignoriert systemd die
|
||||
Kontexteinstellung.
|
||||
|
||||
Auf Gemini wurde am 10.09.2026 unter Enforcing die Startkette Bash → rsync mit
|
||||
diesem Kontext und erfolgreichem Auflisten von `/var/www/html` bestätigt. Der
|
||||
direkte Start von rsync als Unit-Einstiegspunkt scheiterte dagegen mit 203/EXEC;
|
||||
deshalb verwenden beide Units ausdrücklich Bash. Dieser Lesetest bestätigt
|
||||
noch keine vollständige Attributübertragung oder Sicherung. Referenzen:
|
||||
[systemd SELinuxContext](https://github.com/systemd/systemd/blob/v256/man/systemd.exec.xml),
|
||||
[Fedora-SELinux-Domain und Einstiegspunkte](https://github.com/fedora-selinux/selinux-policy/blob/rawhide/policy/modules/system/unconfined.te).
|
||||
|
||||
Die Kompression schreibt direkt aus tar in `.partial`-Dateien. Erst erfolgreiche Pipelines werden umbenannt. zstd nutzt standardmäßig Stufe 6 und zwei Threads. `sync -f` vor Fortsetzung bzw. Retention fordert die Persistierung des lokalen Satzes an; Hardware-/Storagefehler bleiben eine eigene Ebene.
|
||||
|
||||
## Format und Komponenten
|
||||
@@ -331,10 +360,13 @@ Die Entfernung der drei DB-Dateien aus dem aktuellen Baum löscht deren Historie
|
||||
|
||||
Reproduzierbar mit `tests/Dockerfile` und `tests/run.sh`. Der Container bekommt das Repository nur lesbar und benötigt während der Tests kein Netzwerk. Die MariaDB läuft ohne TCP-Port in einem temporären Verzeichnis. systemd/occ werden simuliert; alle eigentlichen Archive, Dateikopien, Dumps/Imports und Crypt-Operationen sind real.
|
||||
|
||||
Ergebnis des lokalen Abschlusslaufs am 10.09.2026: **41 Tests erfolgreich**, Shell-Syntax und ShellCheck ohne Befund. Testumgebung: Debian 13.6, MariaDB 11.8.6, rclone 1.60.1-DEV (Distributionspaket), ShellCheck 0.10.0. Diese Versionsangaben beschreiben die Testumgebung, keine Empfehlung für die Produktivversion.
|
||||
Ergebnis des lokalen Abschlusslaufs am 10.09.2026: **44 Tests erfolgreich**, Shell-Syntax und ShellCheck ohne Befund. Testumgebung: Debian 13.6, MariaDB 11.8.6, rclone 1.60.1-DEV (Distributionspaket), ShellCheck 0.10.0. Diese Versionsangaben beschreiben die Testumgebung, keine Empfehlung für die Produktivversion.
|
||||
|
||||
Geprüfte Fälle umfassen:
|
||||
|
||||
- Verweigerter rsync-Quellzugriff bricht das Update vor Installation und Dienst-Stopp ab.
|
||||
- Verweigerte Verzeichnisattribute werden vor Wartungsmodus erkannt; der private Prüfbereich wird entfernt.
|
||||
- Attributprobe kopiert keine Kindobjekte; rsync erbt bei Prüfung, Backup und Restore keine Update-/Backup-Sperrdeskriptoren.
|
||||
- Fehlendes globales `RELOAD` bei eingeschränktem Konto: Vorprüfungsabbruch ohne Installation/Writer-Stopp, anschließend erfolgreicher Paketlauf nach gezieltem Grant. Aktive verschachtelte MariaDB-Rollen werden anerkannt, inaktive Rollen abgewiesen.
|
||||
- Fehlender Zugriff auf MariaDB-Routinen-Metadaten vor Wartungsmodus erkannt; nach gezielter Berechtigung tatsächlicher Dump mit gespeicherter Prozedur geprüft.
|
||||
- Fehlender Mailstore ohne Live-Änderung, explizite Pfadkorrektur, Homes-only-Update samt tatsächlichem Maildatei-Restore und Ablehnung unvollständiger Home-Konfigurationen.
|
||||
@@ -354,4 +386,4 @@ Geprüfte Fälle umfassen:
|
||||
- Abgewiesene systemd-Übergabe, zurückgezogene Freigabe bei bereits eingereihtem Auftrag und persistente Fehlerprotokolle bei Sperr-Timeout; jeweils ohne Installationsänderung oder Timer-Stopp.
|
||||
- Ausdrückliche Cron-Policy und Writer-Wartezeit/Timeout.
|
||||
|
||||
**Serverabnahme steht separat aus:** echte Rocky-Linux-10-Umgebung, SELinux-/ACL-Verhalten auf den verwendeten Dateisystemen, installierte Nextcloud-/Gitea-Versionen, vollständige Writer-/Pfadliste, Berechtigungen des echten DB-Backupaccounts, tatsächlicher OneDrive-Upload großer Archive, Laufzeit/Speicherbedarf und fachliche Restore-Probe. Es wurde kein Produktivbackup ausgeführt und keine Serverinstallation verändert.
|
||||
**Serverabnahme steht separat aus:** SELinux-/ACL-Verhalten beim vollständigen Kopieren auf den verwendeten Dateisystemen, installierte Nextcloud-/Gitea-Versionen, vollständige Writer-/Pfadliste, tatsächlicher OneDrive-Upload großer Archive, Laufzeit/Speicherbedarf und fachliche Restore-Probe. Die Entwicklungstests verändern keinen Produktivserver. Vom Betreiber ausgeführte Paketläufe haben die Installation auf Gemini bereits aktualisiert; alle drei DB-Dumps waren erfolgreich. Eine vollständige erfolgreiche Sicherung und fachliche Wiederherstellung sind noch nicht bestätigt.
|
||||
@@ -37,6 +37,13 @@ abfragbar. Diagnose und Details stehen in der Update-Anleitung.
|
||||
|
||||
Die folgende manuelle Anleitung bleibt als Alternative für individuelle Updates.
|
||||
|
||||
Auf Rocky/RHEL starten Backup-Dienst und Update-Auftrag über Bash mit
|
||||
`SELinuxContext=system_u:system_r:unconfined_service_t:s0`. SELinux bleibt systemweit
|
||||
aktiv; **diese privilegierten Backup-Prozesse sind bewusst nicht durch SELinux
|
||||
eingeschränkt**. Dadurch können sie Web-/Mail-/Home-Daten samt Sicherheitsattributen
|
||||
sichern. Der Installer ändert keine globalen rsync-Booleans und keine Quell-Labels.
|
||||
Details und geprüfte Grenzen stehen in der Update-Anleitung.
|
||||
|
||||
## Wesentliche Änderungen
|
||||
|
||||
- Funktionierender Restore mit Prüflauf, Entpackmodus, Komponentenauswahl und ausdrücklichem `--apply --yes`.
|
||||
@@ -96,8 +103,11 @@ sudo cat /var/backups/app-backup/last-backup.json
|
||||
`--check` prüft Werkzeuge, Quellpfade, Dateirechte, DB-Verbindungen, das globale
|
||||
DB-Recht `RELOAD` für die Dump-Sperre, bei MariaDB den Zugriff auf Routinen-Metadaten,
|
||||
Speicherreserve und Crypt-Zugriff.
|
||||
Die Rechteprüfung liest nur Metadaten; sie stoppt keine Anwendungen, sperrt keine
|
||||
Datenbank und führt weder Backup noch Upload aus. Sie ersetzt keinen vollständigen
|
||||
Die Prüfung liest Quellverzeichnisse über rsync und überträgt nur deren
|
||||
Verzeichnisattribute in einen privaten temporären Prüfbereich im Staging.
|
||||
Dabei werden keine Datei-Inhalte kopiert. Die DB-Rechteprüfung liest Metadaten;
|
||||
es werden keine Anwendungen gestoppt, keine globale DB-Sperre genommen und kein
|
||||
Backup oder Upload ausgeführt. Die Vorprüfung ersetzt keinen vollständigen
|
||||
Probelauf. Fehlt `RELOAD`, die tatsächlich verwendeten Backup-Konten ermitteln und
|
||||
gezielt berechtigen; siehe [DB-Rechte korrigieren](UPDATE_ANLEITUNG.md#fehlendes-db-recht-reload).
|
||||
|
||||
|
||||
+42
-3
@@ -24,7 +24,9 @@ Backups mit zeitweiliger Unterbrechung der konfigurierten Anwendungen.
|
||||
5. Crypt-Konfiguration, DB-Dateien, globales DB-Recht `RELOAD` und bei MariaDB
|
||||
Zugriff auf Routinen-Metadaten prüfen;
|
||||
vorhandene Schlüssel/Passwörter erhalten. Keine automatischen DB-Grants.
|
||||
6. Vorprüfung mit geschützten Kopien durchführen. Bei Fehlern bleibt das installierte
|
||||
6. Vorprüfung mit geschützten Kopien durchführen. rsync prüft außerdem den Zugriff
|
||||
auf Quellverzeichnisse und deren Attribute in einem privaten temporären
|
||||
Prüfbereich, ohne Datei-Inhalte zu kopieren. Bei Fehlern bleibt das installierte
|
||||
Paket samt Anwendungskonfiguration und Timer unverändert.
|
||||
7. Einen von SSH unabhängigen systemd-Auftrag `app-backup-upgrade.service` mit
|
||||
`--no-block` einreihen und die Sperren an ihn übergeben.
|
||||
@@ -274,6 +276,41 @@ im Journal protokolliert. Eine längere Pause ohne CPU-Last lässt sich so einem
|
||||
konkreten Dienst zuordnen; ohne dessen Journal lässt sich die Ursache der Pause
|
||||
nicht aus ihrer Dauer allein bestimmen.
|
||||
|
||||
## SELinux: rsync-Zugriff verweigert (Exit 23)
|
||||
|
||||
Der Gemini-Lauf vom 10.09.2026 um 14:58 Uhr scheiterte nach drei erfolgreichen
|
||||
DB-Dumps beim Kopieren von `/var/www/html`. Das Audit bestätigt rsync im Kontext
|
||||
`rsync_t`: Lesen von `httpd_sys_content_t` sowie Attributänderungen und Relabeling
|
||||
im Staging wurden verweigert. Zusätzlich wurden geerbte Sperrdeskriptoren beanstandet.
|
||||
Die gestoppten Dienste wurden laut Journal wieder gestartet und Nextcloud verließ
|
||||
den Wartungsmodus. Es entstand kein vollständiger neuer Sicherungssatz.
|
||||
|
||||
Das aktuelle Paket setzt für den Update-Auftrag und den installierten Backup-Dienst
|
||||
`SELinuxContext=system_u:system_r:unconfined_service_t:s0` und startet beide über
|
||||
`/bin/bash`. **Diese privilegierten Prozesse und ihre Kindprozesse laufen damit
|
||||
ohne SELinux-Einschränkung.** SELinux bleibt systemweit aktiv; andere Dienste
|
||||
behalten ihre bestehende Policy. Der Installer ändert keine globalen rsync-Booleans,
|
||||
keine Quell-Labels und keine Quellberechtigungen. Ein nicht zugelassener Kontext
|
||||
führt zu einem sichtbaren Startfehler.
|
||||
|
||||
Auf Gemini ist die Bash-Startkette mit diesem Kontext und erfolgreichem
|
||||
rsync-Auflisten des Webverzeichnisses unter Enforcing geprüft. Der direkte
|
||||
Unit-Start von `/usr/bin/rsync` scheiterte dagegen mit 203/EXEC und bildet die
|
||||
Startkette des Pakets nicht ab. Manuelle Unit-Anpassungen sind nicht erforderlich:
|
||||
das aktuelle Paket mit denselben Gitea-/Cron-/Mail-Optionen erneut starten.
|
||||
|
||||
Vor dem Wartungsmodus prüft das Paket über rsync die Quellverzeichnisse und die
|
||||
Übernahme ihrer Verzeichnisattribute in einen privaten temporären Prüfbereich.
|
||||
Dabei werden keine Dateiinhalte kopiert. Die Probe erfasst nicht jede untergeordnete
|
||||
Datei; der vollständige Kopierlauf und die Remote-Prüfung bleiben erforderlich.
|
||||
rsync erhält keine geerbten Sperrdeskriptoren; die Elternprozesse halten die Sperren
|
||||
weiter. Der SELinux-Kontext des Backup-Prozesses erscheint im Protokoll.
|
||||
|
||||
Bei einem erneuten Fehler `update.log`, die ausgegebene Ergebnisdatei und die
|
||||
passenden AVC-Meldungen prüfen. Maßgeblich für den erfolgreichen Abschluss bleibt
|
||||
`SUCCESS` in `result.txt`; erst danach aktiviert der Installer den Backup-Timer.
|
||||
Eine Wiederherstellungsprobe auf einem separaten System steht weiterhin aus.
|
||||
|
||||
## Paket reproduzieren
|
||||
|
||||
Aus einem sauberen, eingecheckten Repository:
|
||||
@@ -290,12 +327,14 @@ Das ausführbare Paket ausschließlich über euren vertrauenswürdigen Kanal bez
|
||||
|
||||
## Verifikation der Lieferung
|
||||
|
||||
41 Container-Tests sowie ShellCheck und Shell-Syntaxprüfung erfolgreich.
|
||||
44 Container-Tests sowie ShellCheck und Shell-Syntaxprüfung erfolgreich.
|
||||
Die Tests enthalten echte MariaDB-Dumps/Imports und lokale rclone-Crypt-Roundtrips;
|
||||
systemd/occ und die systemd-run-Übergabe sind simuliert. Das Übergabemodell
|
||||
berücksichtigt jetzt das Warten auf den oneshot-Startauftrag: Der Regressionstest
|
||||
scheiterte mit dem alten Code und war nach dem Fix erfolgreich. Geprüft sind auch
|
||||
abgewiesene Übergaben, eingereihte Worker trotz fehlgeschlagener Antwort und
|
||||
persistente Fehlerprotokolle bei belegter Update-Sperre. Die tatsächliche
|
||||
Server-/OneDrive-Prüfung erfolgt beim ersten Paketlauf. Ein fachlicher Restore
|
||||
SELinux-Durchsetzung wird im Debian-Testcontainer nicht nachgebildet; der erfolgreiche
|
||||
Bash-/rsync-Lesetest auf Gemini wurde separat vom Betreiber durchgeführt. Die vollständige
|
||||
Server-/OneDrive-Prüfung erfolgt beim Paketlauf. Ein fachlicher Restore
|
||||
auf einem separaten Testsystem bleibt erforderlich.
|
||||
@@ -118,6 +118,8 @@ init_runtime() {
|
||||
# Journald already captures service stdout. No dependency on systemd-cat in terminals.
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
echo "== $OPERATION $RUN_ID =="
|
||||
local security_context
|
||||
if security_context=$(id -Z 2>/dev/null); then echo "-- SELinux process context: $security_context"; fi
|
||||
need rsync; need tar; need "$COMPRESSOR"
|
||||
STOPPED_SERVICES=(); NC_CHANGED=false; NC_WAS_ON=false; MUTATION_STARTED=false
|
||||
trap 'exit 130' INT
|
||||
@@ -190,8 +192,31 @@ check_db_credentials() {
|
||||
done
|
||||
}
|
||||
|
||||
check_source_access() (
|
||||
# This subshell owns only a fresh private probe directory. Keep source data
|
||||
# untouched and clean probe objects even if an attribute operation fails.
|
||||
local probe p
|
||||
probe=$(mktemp -d "$STAGING_ROOT/.access-probe-XXXXXXXX")
|
||||
trap 'rm -rf -- "$probe"' EXIT
|
||||
# Test through rsync itself: under SELinux it can enter a different domain
|
||||
# than this root shell. A shell test -r / realpath alone misses that denial.
|
||||
# --dirs lists one directory level only, without reading file contents,
|
||||
# recursing through live mailboxes, or creating a destination.
|
||||
for p in "${SOURCES[@]}"; do
|
||||
rsync --list-only --dirs -- "$p/" 8>&- 9>&- >/dev/null || die "rsync cannot list source: $p; check permissions and SELinux audit records before retrying"
|
||||
mkdir "$probe/root"
|
||||
# Exclude ALL children: copy only root-directory metadata into the private
|
||||
# probe. Exercise chmod/chown/times/ACLs/xattrs, including SELinux labels,
|
||||
# before maintenance. This is not a recursive/full content access test.
|
||||
rsync -aHAX --numeric-ids --exclude='/*' -- "$p/" "$probe/root/" 8>&- 9>&- || die "rsync cannot preserve directory metadata for: $p; inspect SELinux/filesystem permissions"
|
||||
rm -rf -- "$probe/root"
|
||||
done
|
||||
echo '-- Source directory access and staging metadata probe passed'
|
||||
)
|
||||
|
||||
preflight() {
|
||||
for p in "${SOURCES[@]}"; do check_source_path "$p"; done
|
||||
if [[ $OPERATION == backup ]]; then check_source_access; fi
|
||||
check_db_credentials
|
||||
if (( ${#DB_LABELS[@]} )); then
|
||||
need "$DB_DUMP_BIN"; need "$DB_CLIENT_BIN"
|
||||
|
||||
@@ -5,7 +5,14 @@ After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/app-backup.sh
|
||||
# Bash is an allowed entry point for this SELinux service domain. Running rsync
|
||||
# directly as the entry point can fail even when child execution is permitted.
|
||||
ExecStart=/bin/bash /usr/local/sbin/app-backup.sh
|
||||
# A privileged backup crosses web/mail/user labels and preserves those labels.
|
||||
# Keep enforcing globally; scope unconfined execution to this backup service.
|
||||
# This deliberately removes SELinux confinement for this unit and its children.
|
||||
# No '-' prefix: an unavailable/denied context must fail visibly.
|
||||
SELinuxContext=system_u:system_r:unconfined_service_t:s0
|
||||
# Timeout sends TERM: Bash runs its recovery trap; grace time permits service restart.
|
||||
TimeoutStartSec=12h
|
||||
TimeoutStopSec=5min
|
||||
|
||||
+66
-2
@@ -251,6 +251,67 @@ QUIESCE_SERVICES=(nc-cron)
|
||||
self.assertFalse(list((self.base/'work/archives').glob('appbackup_*')))
|
||||
self.assertEqual(json.loads(self.state.read_text()),self.initial)
|
||||
|
||||
def test_rsync_source_denial_fails_preflight_before_stopping_services(self):
|
||||
canonical=self.configure_upgrade()
|
||||
original=canonical.read_bytes()
|
||||
wrapper=self.bin/'rsync'
|
||||
wrapper.write_text('''#!/usr/bin/env bash
|
||||
if [[ " $* " == *" --list-only "* ]]; then
|
||||
echo 'rsync: simulated source opendir permission denied' >&2
|
||||
exit 23
|
||||
fi
|
||||
exec /usr/bin/rsync "$@"
|
||||
''')
|
||||
wrapper.chmod(0o755)
|
||||
package=self.build_package()
|
||||
result=subprocess.run(['bash',str(package),'--foreground'],env=self.env,capture_output=True,text=True,timeout=60)
|
||||
self.assertNotEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
self.assertIn('rsync cannot list source',result.stdout+result.stderr)
|
||||
self.assertEqual(canonical.read_bytes(),original)
|
||||
self.assertEqual(json.loads(self.state.read_text())['services']['app-backup.timer'],'active')
|
||||
self.assertNotIn('systemctl stop',self.events.read_text())
|
||||
self.assertNotIn('maintenance:mode --on',self.events.read_text())
|
||||
|
||||
def test_directory_attribute_denial_is_caught_before_maintenance(self):
|
||||
wrapper=self.bin/'rsync'
|
||||
wrapper.write_text('''#!/usr/bin/env bash
|
||||
if [[ " $* " == *" --exclude=/* "* ]]; then
|
||||
echo 'rsync: simulated security.selinux relabel denial' >&2
|
||||
exit 23
|
||||
fi
|
||||
exec /usr/bin/rsync "$@"
|
||||
''')
|
||||
wrapper.chmod(0o755)
|
||||
failed=self.run_script('app-backup.sh','--check',ok=False)
|
||||
self.assertIn('cannot preserve directory metadata',failed.stdout)
|
||||
self.assertNotIn('systemctl stop',self.events.read_text())
|
||||
self.assertEqual(json.loads(self.state.read_text()),self.initial)
|
||||
self.assertFalse(list((self.base/'work/staging').iterdir()))
|
||||
|
||||
def test_metadata_probe_copies_no_children_and_rsync_inherits_no_locks(self):
|
||||
wrapper=self.bin/'rsync'
|
||||
wrapper.write_text('''#!/usr/bin/env python3
|
||||
import os,subprocess,sys
|
||||
from pathlib import Path
|
||||
for fd in (8,9):
|
||||
try: os.fstat(fd)
|
||||
except OSError: pass
|
||||
else: sys.exit('rsync inherited a coordinator lock descriptor')
|
||||
result=subprocess.run(['/usr/bin/rsync',*sys.argv[1:]])
|
||||
if result.returncode: sys.exit(result.returncode)
|
||||
if '--exclude=/*' in sys.argv:
|
||||
destination=Path(sys.argv[-1])
|
||||
assert not list(destination.iterdir()), 'probe copied source contents'
|
||||
source=Path(sys.argv[-2])
|
||||
assert destination.stat().st_mode == source.stat().st_mode
|
||||
''')
|
||||
wrapper.chmod(0o755)
|
||||
self.run_script('app-backup.sh','--check')
|
||||
self.assertFalse(list((self.base/'work/staging').iterdir()))
|
||||
self.assertNotIn('systemctl stop',self.events.read_text())
|
||||
run=self.backup()
|
||||
self.run_script('app-restore.sh','--local-run',run,'--components','wordpress','--apply','--yes')
|
||||
|
||||
def test_maintenance_and_inactive_service_preserved(self):
|
||||
state=dict(services=dict(self.initial['services'],gitea='inactive'),maintenance=True)
|
||||
self.state.write_text(json.dumps(state))
|
||||
@@ -296,10 +357,12 @@ QUIESCE_SERVICES=(nc-cron)
|
||||
|
||||
def test_term_recovers_services(self):
|
||||
wrapper=self.bin/'rsync'
|
||||
wrapper.write_text('#!/usr/bin/env bash\necho waiting > "$TEST_EVENTS.waiting"\nsleep 2\nexec /usr/bin/rsync "$@"\n')
|
||||
# Signal only during the real copy, after services have stopped. The
|
||||
# new access probes run earlier and must not trigger this recovery test.
|
||||
wrapper.write_text('#!/usr/bin/env bash\nif [[ " $* " != *" --list-only "* && " $* " != *" --exclude=/* "* ]]; then\n echo waiting > "$TEST_EVENTS.waiting"\n sleep 2\nfi\nexec /usr/bin/rsync "$@"\n')
|
||||
proc=subprocess.Popen(['bash',str(REPO/'usr-local-sbin/app-backup.sh'),'--config',str(self.config)],env=self.env,text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
|
||||
try:
|
||||
for _ in range(100):
|
||||
for _ in range(200):
|
||||
if Path(str(self.events)+'.waiting').exists(): break
|
||||
time.sleep(.05)
|
||||
else: self.fail('Backup did not reach rsync')
|
||||
@@ -612,6 +675,7 @@ from pathlib import Path
|
||||
args=sys.argv[1:]
|
||||
assert '--unit=app-backup-upgrade' in args
|
||||
assert '--property=Type=oneshot' in args
|
||||
assert '--property=SELinuxContext=system_u:system_r:unconfined_service_t:s0' in args
|
||||
command=args[args.index('/bin/bash'):]
|
||||
state=Path(command[-1])
|
||||
if os.environ.get('TEST_SYSTEMD_SUBMIT') == 'reject':
|
||||
|
||||
@@ -255,6 +255,7 @@ if [[ $UP_WORKER == false ]]; then
|
||||
# Only enqueue here; result.txt, not systemd-run's exit code, proves success.
|
||||
# The launcher must not keep copies of our locks; systemd starts a fresh worker.
|
||||
systemd-run --no-block --unit=app-backup-upgrade --collect --property=Type=oneshot \
|
||||
--property=SELinuxContext=system_u:system_r:unconfined_service_t:s0 \
|
||||
--property=TimeoutStartSec=24h --property=TimeoutStopSec=5min --property=KillMode=mixed \
|
||||
/bin/bash "$UPDATE_ROOT/update.sh" --worker "$UP_STATE" 8>&- 9>&- || {
|
||||
# Revoke the queued state while still holding the coordinator lock. Even
|
||||
|
||||
@@ -24,7 +24,7 @@ init_runtime
|
||||
build_plan
|
||||
preflight
|
||||
if [[ $CHECK == true ]]; then
|
||||
echo 'Preflight passed. No source copy, service stop, dump, upload or deletion performed.'
|
||||
echo 'Preflight passed. Directory metadata probed; no application backup, service stop or upload performed.'
|
||||
exit 0
|
||||
fi
|
||||
STAGING_DIR=$(mktemp -d "$STAGING_ROOT/${RUN_ID}.XXXXXX")
|
||||
@@ -58,7 +58,8 @@ for i in "${!LABELS[@]}"; do
|
||||
exclusions "$label"
|
||||
echo "-- Copy: $label ($src)"
|
||||
# Never accept vanished-file (24) or partial-copy (23) as success.
|
||||
rsync -aHAX --numeric-ids "${EXCLUDES[@]}" -- "$src/" "$STAGING_DIR/$root/"
|
||||
# Parent retains both locks; rsync itself needs neither inherited descriptor.
|
||||
rsync -aHAX --numeric-ids "${EXCLUDES[@]}" -- "$src/" "$STAGING_DIR/$root/" 8>&- 9>&-
|
||||
printf '%s\t%s\n' "$label" "$src" >> "$PLAN"
|
||||
done
|
||||
# The full consistency window includes all dumps AND copies. Compression/upload
|
||||
|
||||
@@ -53,7 +53,7 @@ else
|
||||
input_real=$(realpath -e "$LOCAL_RUN")
|
||||
stage_real=$(realpath -e "$STAGING_DIR")
|
||||
[[ $stage_real != "$input_real" && $stage_real != "$input_real/"* ]] || die 'Backup source contains restore workspace'
|
||||
rsync -a -- "$LOCAL_RUN/" "$INPUT/"
|
||||
rsync -a -- "$LOCAL_RUN/" "$INPUT/" 8>&- 9>&-
|
||||
fi
|
||||
if [[ $LEGACY == true ]]; then
|
||||
echo 'WARNING: Legacy backup has no trusted manifest/completeness proof.'
|
||||
@@ -172,7 +172,7 @@ for i in "${!SELECTED[@]}"; do
|
||||
else
|
||||
exclusions "$label"
|
||||
if [[ $RESTORE_STRICT_DELETE == true ]]; then EXCLUDES+=(--delete-delay); fi
|
||||
rsync -aHAX --numeric-ids "${EXCLUDES[@]}" -- "$source_path/" "${TARGETS[i]}/"
|
||||
rsync -aHAX --numeric-ids "${EXCLUDES[@]}" -- "$source_path/" "${TARGETS[i]}/" 8>&- 9>&-
|
||||
fi
|
||||
done
|
||||
# A backup captured maintenance=true. Enforce the recorded pre-restore state even
|
||||
|
||||
Reference in new issue
Block a user