Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d1a4d3ad7 | ||
|
|
aa25719c0d | ||
|
|
34378998c4 | ||
|
|
4ef0c414fa | ||
|
|
1f3afb1e52 | ||
|
|
5f2040f8bf | ||
|
|
da2ddbd6ab | ||
|
|
46f976b353 |
+25
-4
@@ -5,6 +5,31 @@ Alle nennenswerten Änderungen an diesem Projekt werden in dieser Datei dokument
|
|||||||
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/)
|
Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/)
|
||||||
und dieses Projekt folgt den Richtlinien von [Semantic Versioning](https://semver.org/lang/de/).
|
und dieses Projekt folgt den Richtlinien von [Semantic Versioning](https://semver.org/lang/de/).
|
||||||
|
|
||||||
|
## [0.4.0] - 2026-09-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Distribution & Package Ecosystem**:
|
||||||
|
- Offizielles Scoop-Manifest (`packages/scoop/sanctum.json`) für One-Click CLI-Installation und nahtlose Upgrades unter Windows.
|
||||||
|
- Windows Package Manager (Winget) Manifest (`packages/winget/HaraldPansi.Sanctum.yaml`) zur systemweiten Paketverwaltung.
|
||||||
|
- Vollständiger Installations-Leitfaden (`INSTALL.md`) für Scoop, Winget, Standalone-Binaries und `cargo install`.
|
||||||
|
- **Cross-Platform Architektur (Linux & macOS Vorbereitung)**:
|
||||||
|
- Neues Plattform-Abstraktionsmodul `src/platform/mod.rs` für OS-unabhängiges Speicherschutz- und Mount-Handling.
|
||||||
|
- POSIX-Memory-Hardening mit `mlock` und `munlock` Systemaufrufen für Unix-Systeme.
|
||||||
|
- Plattformübergreifendes Datei- und Ordneröffnen (`explorer.exe` unter Windows, `xdg-open` unter Linux, `open` unter macOS).
|
||||||
|
- Native WebDAV-VFS-Einbindung für Linux GNOME/KDE via `gio mount`.
|
||||||
|
- **Chaos Engineering & Live-Crash-Resilienz**:
|
||||||
|
- `tests/live_crash_resilience_test.rs`: Umfassende automatisierte Stresstests mit Multi-Worker Schreibabbrüchen, asynchronen Panic-Simulationen und Validierung der SQLite WAL-Konsistenz (Null Korruption).
|
||||||
|
- **Legal, Compliance & Software Bill of Materials (SBOM)**:
|
||||||
|
- `LEGAL.md`: Rechtliche Leitlinien, US EAR § 742.15(b) & EU Dual-Use Exportkontroll-Klassifizierung, DSGVO Art. 25 Zero-Data-Erklärung und Haftungsausschluss nach § 521 BGB.
|
||||||
|
- `THIRD_PARTY_LICENSES.md`: Lückenloses Lizenzaudit aller 231 Abhängigkeiten (100% permissive Lizenzen, 0% Copyleft) zur strikten Compliance mit Apache-2.0 Section 4.
|
||||||
|
- Aufnahme von `LEGAL.md` und `THIRD_PARTY_LICENSES.md` in das offizielle Release-Distributionsarchiv (`package-release.ps1`).
|
||||||
|
- **Statisches Linux Single-Binary (`x86_64-unknown-linux-musl`)**:
|
||||||
|
- 100% statisch gelinktes Linux-Binary ohne GLIBC-Abhängigkeiten via Zig-Cross-Toolchain.
|
||||||
|
- Neues Linux-Distributionsarchiv `sanctum-v0.4.0-linux-x86_64.tar.gz` inklusive Dokumentation und Prüfsummen.
|
||||||
|
- Native Freedesktop `.desktop`-Datei und MIME-Type-Integration (`application/x-sanctum`) via `sanctum register`.
|
||||||
|
- **OpSec & Disaster Recovery UX**:
|
||||||
|
- `QUICKSTART.md`: Druckbare 24-Wörter BIP-39 Notfallkarte zur sicheren analogen Verwahrung des Notfallschlüssels (Air-Gapped Vault Paper Backup).
|
||||||
|
|
||||||
## [0.3.1] - 2026-09-10
|
## [0.3.1] - 2026-09-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -20,10 +45,6 @@ und dieses Projekt folgt den Richtlinien von [Semantic Versioning](https://semve
|
|||||||
- **DFIR & Windows Host-Memory Hardening**:
|
- **DFIR & Windows Host-Memory Hardening**:
|
||||||
- Win32 `VirtualLock` Speicherschutz: Physische Verriegelung des Root-DEK im RAM zur Unterbindung von Auslagerungen in `pagefile.sys` oder `swapfile.sys`.
|
- Win32 `VirtualLock` Speicherschutz: Physische Verriegelung des Root-DEK im RAM zur Unterbindung von Auslagerungen in `pagefile.sys` oder `swapfile.sys`.
|
||||||
- Windows Defender Controlled Folder Access (CFA / Ransomware-Schutz) Diagnose mit gezielter Hilfestellung.
|
- Windows Defender Controlled Folder Access (CFA / Ransomware-Schutz) Diagnose mit gezielter Hilfestellung.
|
||||||
- **Legal, Compliance & Export-Control (LEGAL-01 bis LEGAL-03)**:
|
|
||||||
- `LEGAL.md`: Umfassende rechtliche Dokumentation inkl. Exportkontroll-Klassifizierung (US EAR § 742.15(b) & EU Dual-Use GSN), Aufklärung über Plausible Deniability bei Key-Disclosure-Gesetzen, EU/DACH-Haftungsbeschränkung gem. § 521 BGB (Schenkungsrecht) sowie DSGVO Art. 25 Zero-Data-Erklärung.
|
|
||||||
- `THIRD_PARTY_LICENSES.md`: Lückenlose Attribution aller ~230 statisch eingebundenen Crates (Apache-2.0, MIT, BSD, SQLite Public Domain) zur strikten Erfüllung von Section 4 der Apache-2.0-Lizenz.
|
|
||||||
- Automatisierte Aufnahme von `LEGAL.md` und `THIRD_PARTY_LICENSES.md` in das Release-Packaging (`package-release.ps1`).
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **Visuelle Parität & Plausible Deniability beim Mounten (HF-01)**:
|
- **Visuelle Parität & Plausible Deniability beim Mounten (HF-01)**:
|
||||||
|
|||||||
Generated
+1
-1
@@ -1377,7 +1377,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sanctum"
|
name = "sanctum"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sanctum"
|
name = "sanctum"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Harald Pansi <harald@pansi.eu>", "Sanctum Engineering Team"]
|
authors = ["Harald Pansi <harald@pansi.eu>", "Sanctum Engineering Team"]
|
||||||
description = "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV"
|
description = "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV"
|
||||||
@@ -36,6 +36,8 @@ dyn-clone = "1.0"
|
|||||||
lz4_flex = "0.11"
|
lz4_flex = "0.11"
|
||||||
bip39 = { version = "2.2", features = ["zeroize"] }
|
bip39 = { version = "2.2", features = ["zeroize"] }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
tray-item = "0.10"
|
tray-item = "0.10"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
+94
@@ -0,0 +1,94 @@
|
|||||||
|
# Installation & Paketmanager-Guide für Sanctum 🛡️
|
||||||
|
|
||||||
|
Sanctum kann auf verschiedene Arten unter Windows 10/11 installiert und verwaltet werden. Es sind **keine Administratorrechte** erforderlich.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 1. Installation via Scoop (Empfohlen für Entwickler)
|
||||||
|
|
||||||
|
Wenn du den Windows-Paketmanager [Scoop](https://scoop.sh/) nutzt, kannst du Sanctum direkt über das bereitgestellte Manifest installieren:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Direkt über die URL des Manifests installieren:
|
||||||
|
scoop install https://gitea.pansi.eu/harald/sanctum/raw/branch/main/packages/scoop/sanctum.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Oder falls du ein lokales Repository geklont hast:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
scoop install .\packages\scoop\sanctum.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Nach der Installation ist der Befehl `sanctum` sofort global in deiner PowerShell verfügbar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🪟 2. Installation via Windows Package Manager (Winget)
|
||||||
|
|
||||||
|
Sanctum verfügt über ein standardkonformes Winget-Manifest (Schema 1.6.0).
|
||||||
|
|
||||||
|
### Lokale Installation via Manifest:
|
||||||
|
```powershell
|
||||||
|
winget install --manifest .\packages\winget\HaraldPansi.Sanctum.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Winget entpackt das portable Binary automatisch in das Benutzer-Anwendungsverzeichnis und verknüpft das Alias `sanctum`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 3. Manuelle Installation (Standalone Portable)
|
||||||
|
|
||||||
|
1. Lade das aktuelle Release-Paket herunter:
|
||||||
|
* **URL:** [https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.4.0](https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.4.0)
|
||||||
|
* **Datei:** `sanctum-v0.4.0-windows-x86_64.zip`
|
||||||
|
2. Entpacke das Archiv in ein Verzeichnis deiner Wahl (z. B. `C:\Tools\Sanctum\`).
|
||||||
|
3. *(Optional)* Registriere das Windows Explorer Rechtsklick-Kontextmenü:
|
||||||
|
```powershell
|
||||||
|
.\sanctum.exe register
|
||||||
|
```
|
||||||
|
*(Erlaubt das Einhängen per Rechtsklick "In Sanctum öffnen" ohne Adminrechte).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐧 4. Installation unter Linux (x86_64)
|
||||||
|
|
||||||
|
Sanctum wird für Linux als **100 % statisches Single-Binary** (`x86_64-unknown-linux-musl`) ohne jegliche GLIBC- oder Bibliotheksabhängigkeiten bereitgestellt. Es läuft direkt auf Ubuntu, Debian, Alpine, Arch, Fedora und weiteren Distributionen:
|
||||||
|
|
||||||
|
1. Lade das Linux-Release herunter und installiere es:
|
||||||
|
```bash
|
||||||
|
curl -LO https://gitea.pansi.eu/harald/sanctum/releases/download/v0.4.0/sanctum-v0.4.0-linux-x86_64.tar.gz
|
||||||
|
tar -xzf sanctum-v0.4.0-linux-x86_64.tar.gz
|
||||||
|
chmod +x sanctum
|
||||||
|
sudo install -m 755 sanctum /usr/local/bin/sanctum
|
||||||
|
```
|
||||||
|
2. *(Optional)* Freedesktop-Integration einrichten (Doppelklick in GNOME / KDE / Thunar):
|
||||||
|
```bash
|
||||||
|
sanctum register
|
||||||
|
```
|
||||||
|
*Richtet die `.desktop`-Datei und den MIME-Typ `application/x-sanctum` im Userland (`~/.local/share`) ein.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Updates
|
||||||
|
|
||||||
|
* **Scoop:**
|
||||||
|
```powershell
|
||||||
|
scoop update sanctum
|
||||||
|
```
|
||||||
|
* **Winget:**
|
||||||
|
```powershell
|
||||||
|
winget upgrade --manifest .\packages\winget\HaraldPansi.Sanctum.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗑️ Deinstallation
|
||||||
|
|
||||||
|
* **Scoop:**
|
||||||
|
```powershell
|
||||||
|
scoop uninstall sanctum
|
||||||
|
```
|
||||||
|
* **Explorer-Kontextmenü rückstandslos bereinigen:**
|
||||||
|
```powershell
|
||||||
|
sanctum.exe unregister
|
||||||
|
```
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
# Sanctum 🛡️ — Schnellstartanleitung & Notfallhandbuch
|
||||||
|
|
||||||
|
Willkommen bei **Sanctum**! Diese Anleitung führt dich in wenigen Minuten durch die grundlegenden Funktionen zur sicheren Verwaltung deiner verschlüsselten Container unter Windows 10/11 – **100% Userland, ohne Administratorrechte und ohne Treiber**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ In 3 Minuten startklar
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. Neuen Tresor mit Notfallkarte anlegen:
|
||||||
|
sanctum.exe init --path "C:\Users\DeinName\Dokumente\mein_tresor.sanctum"
|
||||||
|
|
||||||
|
# 2. Tresor einbinden (wählt automatisch den nächsten freien Buchstaben, z. B. Z:):
|
||||||
|
sanctum.exe mount --path "C:\Users\DeinName\Dokumente\mein_tresor.sanctum"
|
||||||
|
|
||||||
|
# 3. Sicher trennen:
|
||||||
|
# Entweder Ctrl+C im Konsolenfenster ODER Rechtsklick auf das Schild-Icon im Infobereich (Systray) -> "Aushängen & Beenden".
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Die Kernfunktionen im Überblick
|
||||||
|
|
||||||
|
### 1. Tresor initialisieren (`init`)
|
||||||
|
|
||||||
|
Beim Erstellen eines Tresors wählst du zwischen zwei Sicherheitsstufen:
|
||||||
|
|
||||||
|
* **Standard-Tresor (Single-Vault)**:
|
||||||
|
```powershell
|
||||||
|
sanctum.exe init --path "D:\Tresor\daten.sanctum"
|
||||||
|
```
|
||||||
|
* **Plausible Deniability Tresor (Dual-Vault mit Alibi-Carrier)**:
|
||||||
|
```powershell
|
||||||
|
sanctum.exe init --path "D:\Tresor\daten.sanctum" --with-hidden
|
||||||
|
```
|
||||||
|
* Hier legst du **zwei verschiedene Passwörter** fest:
|
||||||
|
1. **Decoy-Passwort**: Öffnet den äußeren Safe (enthält eine scheinbare Backup-Datei `system_backup.dat`).
|
||||||
|
2. **Hidden-Passwort**: Öffnet den geheimen, unnachweisbaren Safe.
|
||||||
|
* **Wichtig**: Notiere dir die ausgegebenen **24 Wörter des Notfallschlüssels (BIP-39)** auf der untenstehenden Notfallkarte!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Tresor einbinden (`mount`)
|
||||||
|
|
||||||
|
Sanctum erkennt automatisch anhand des eingegebenen Passworts, ob der Decoy- oder Hidden-Safe geöffnet werden soll.
|
||||||
|
|
||||||
|
| Befehl | Zweck |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `sanctum mount --path <DATEI>` | Standard-Mount. Öffnet das Laufwerk automatisch im Explorer. |
|
||||||
|
| `sanctum mount --path <DATEI> --drive S` | Bindet den Tresor fest an den Buchstaben `S:` (statt automatischer Wahl). |
|
||||||
|
| `sanctum mount --path <DATEI> --idle-timeout 300` | Trennt das Laufwerk automatisch nach 5 Minuten (300 Sek.) Inaktivität. |
|
||||||
|
| `sanctum mount --path <DATEI> --no-open` | Verhindert das automatische Öffnen des Windows Explorers (Schutz vor ShellBag-Spuren). |
|
||||||
|
| `sanctum mount --path <DATEI> --stealth` | **Lautloser Stealth-Modus**: Keine Terminal-Ausgaben, keine URLs, kein Explorer-Start. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Notfallrettung & Wartung
|
||||||
|
|
||||||
|
* **Integritätsprüfung (FSCK)**:
|
||||||
|
```powershell
|
||||||
|
sanctum.exe verify --path "D:\Tresor\daten.sanctum"
|
||||||
|
```
|
||||||
|
Überprüft die B-Tree-Struktur der Datenbank und testet sämtliche Chunks gegen ihre kryptografischen AEAD-Authentifizierungs-Tags.
|
||||||
|
|
||||||
|
* **Online-Backup im laufenden Betrieb**:
|
||||||
|
```powershell
|
||||||
|
sanctum.exe backup --path "D:\Tresor\daten.sanctum" --output "E:\Backup\daten_backup.sanctum"
|
||||||
|
```
|
||||||
|
Erzeugt über die SQLite Online Backup API eine konsistente Kopie – selbst während Dateien geöffnet sind.
|
||||||
|
|
||||||
|
* **Passwort vergessen? Wiederherstellung via BIP-39 Notfallschlüssel**:
|
||||||
|
```powershell
|
||||||
|
sanctum.exe passwd --path "D:\Tresor\daten.sanctum" --recovery-key
|
||||||
|
```
|
||||||
|
*(Liest die 24 Wörter maskiert ein, ohne Spuren in der PowerShell-Historie zu hinterlassen, und vergibt ein neues Passwort).*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖨️ Druckvorlage: BIP-39 Notfallkarte
|
||||||
|
|
||||||
|
Drucke diesen Abschnitt aus oder übertrage die Wörter handschriftlich auf ein Blatt Papier. Bewahre diese Karte physisch getrennt von deinem Computer an einem sicheren Ort (z. B. Tresor, Dokumentenmappe) auf.
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ SANCTUM — KRYPTOGRAFISCHE NOTFALL-WIEDERHERSTELLUNGSKARTE (BIP-39) │
|
||||||
|
└──────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Container: __________________________________________________________________
|
||||||
|
Erstelldatum: ____.___.202__ Safe: [ ] Standard [ ] Hidden Vault
|
||||||
|
Hinweis: Alle Wörter sind in Kleinbuchstaben aus der offiziellen BIP-39 Liste.
|
||||||
|
|
||||||
|
┌────┬────────────────────────────┬────┬────────────────────────────┐
|
||||||
|
│ # │ WORT │ # │ WORT │
|
||||||
|
├────┼────────────────────────────┼────┼────────────────────────────┤
|
||||||
|
│ 01 │ __________________________ │ 13 │ __________________________ │
|
||||||
|
│ 02 │ __________________________ │ 14 │ __________________________ │
|
||||||
|
│ 03 │ __________________________ │ 15 │ __________________________ │
|
||||||
|
│ 04 │ __________________________ │ 16 │ __________________________ │
|
||||||
|
│ 05 │ __________________________ │ 17 │ __________________________ │
|
||||||
|
│ 06 │ __________________________ │ 18 │ __________________________ │
|
||||||
|
│ 07 │ __________________________ │ 19 │ __________________________ │
|
||||||
|
│ 08 │ __________________________ │ 20 │ __________________________ │
|
||||||
|
│ 09 │ __________________________ │ 21 │ __________________________ │
|
||||||
|
│ 10 │ __________________________ │ 22 │ __________________________ │
|
||||||
|
│ 11 │ __________________________ │ 23 │ __________________________ │
|
||||||
|
│ 12 │ __________________________ │ 24 │ __________________________ │
|
||||||
|
└────┴────────────────────────────┴────┴────────────────────────────┘
|
||||||
|
|
||||||
|
⚠️ SICHERHEITSHINWEISE:
|
||||||
|
1. Wer im Besitz dieser 24 Wörter ist, kann den Tresor ohne Passwort entschlüsseln!
|
||||||
|
2. Niemals abfotografieren, in Cloud-Notizen speichern oder unverschlüsselt versenden.
|
||||||
|
3. Bei Verlust beider Passwörter und dieser Karte sind die Daten unwiederbringlich verloren.
|
||||||
|
```
|
||||||
@@ -17,6 +17,8 @@ Sanctum ist eine eigenständige, speichersichere und hochperformante CLI-Anwendu
|
|||||||
- **Disaster Recovery**: 24-Wort BIP-39 Mnemonic Seed Phrases, konsistente Online-Backups via SQLite Online Backup API und kryptografische Vollprüfung (`sanctum verify`).
|
- **Disaster Recovery**: 24-Wort BIP-39 Mnemonic Seed Phrases, konsistente Online-Backups via SQLite Online Backup API und kryptografische Vollprüfung (`sanctum verify`).
|
||||||
- **Statisches Single-Binary**: `sanctum.exe` (~5.3 MB) ohne externe DLL-Abhängigkeiten.
|
- **Statisches Single-Binary**: `sanctum.exe` (~5.3 MB) ohne externe DLL-Abhängigkeiten.
|
||||||
|
|
||||||
|
> 💡 **Neu bei Sanctum?** Eine kompakte Schritt-für-Schritt-Anleitung findest du in der [Schnellstartanleitung (QUICKSTART.md)](QUICKSTART.md). Hinweise zur Installation via Scoop oder Winget gibt es im [Installations-Guide (INSTALL.md)](INSTALL.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔐 Kryptografie & Sicherheitsarchitektur
|
## 🔐 Kryptografie & Sicherheitsarchitektur
|
||||||
@@ -209,7 +211,8 @@ powershell -ExecutionPolicy Bypass -File .\scripts\package-release.ps1
|
|||||||
```
|
```
|
||||||
|
|
||||||
Erzeugt:
|
Erzeugt:
|
||||||
- `dist/sanctum-v0.3.1-windows-x86_64.zip`
|
- `dist/sanctum-v0.4.0-windows-x86_64.zip` (Windows x86_64 ZIP)
|
||||||
|
- `dist/sanctum-v0.4.0-linux-x86_64.tar.gz` (Linux x86_64 musl static TAR.GZ via `package-release-linux.ps1`)
|
||||||
- `dist/SHA256SUMS.txt`
|
- `dist/SHA256SUMS.txt`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": "0.4.0",
|
||||||
|
"description": "Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV",
|
||||||
|
"homepage": "https://gitea.pansi.eu/harald/sanctum",
|
||||||
|
"license": "MIT",
|
||||||
|
"architecture": {
|
||||||
|
"64bit": {
|
||||||
|
"url": "https://gitea.pansi.eu/harald/sanctum/releases/download/v0.4.0/sanctum-v0.4.0-windows-x86_64.zip",
|
||||||
|
"hash": "0602173dceeb082fdbe7f64b73a37de1e3f7b021390386a9efba8f43aa65e76d",
|
||||||
|
"bin": "sanctum.exe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkver": {
|
||||||
|
"url": "https://gitea.pansi.eu/api/v1/repos/harald/sanctum/releases/latest",
|
||||||
|
"jsonpath": "$.tag_name",
|
||||||
|
"regex": "v([\\d.]+)"
|
||||||
|
},
|
||||||
|
"autoupdate": {
|
||||||
|
"architecture": {
|
||||||
|
"64bit": {
|
||||||
|
"url": "https://gitea.pansi.eu/harald/sanctum/releases/download/v$version/sanctum-v$version-windows-x86_64.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json
|
||||||
|
PackageIdentifier: HaraldPansi.Sanctum
|
||||||
|
PackageVersion: 0.4.0
|
||||||
|
PackageName: Sanctum
|
||||||
|
Publisher: Harald Pansi
|
||||||
|
PublisherUrl: https://gitea.pansi.eu/harald
|
||||||
|
PublisherSupportUrl: https://gitea.pansi.eu/harald/sanctum/issues
|
||||||
|
Author: Harald Pansi
|
||||||
|
License: MIT
|
||||||
|
LicenseUrl: https://gitea.pansi.eu/harald/sanctum/src/branch/main/LICENSE
|
||||||
|
ShortDescription: Verschlüsselter Ein-Datei-Container unter Windows im reinen Userland via WebDAV
|
||||||
|
Description: Sanctum ist eine eigenständige, speichersichere CLI-Anwendung in Rust, die verschlüsselte Ein-Datei-Container (.sanctum) unter Windows 10/11 im reinen Userland verwaltet. 100% Userland, keine Adminrechte, keine Treiber.
|
||||||
|
Moniker: sanctum
|
||||||
|
Tags:
|
||||||
|
- encryption
|
||||||
|
- security
|
||||||
|
- privacy
|
||||||
|
- webdav
|
||||||
|
- container
|
||||||
|
- plausible-deniability
|
||||||
|
ReleaseNotesUrl: https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.4.0
|
||||||
|
Installers:
|
||||||
|
- Architecture: x64
|
||||||
|
InstallerType: zip
|
||||||
|
NestedInstallerType: portable
|
||||||
|
NestedInstallerFiles:
|
||||||
|
- RelativeFilePath: sanctum.exe
|
||||||
|
PortableCommandAlias: sanctum
|
||||||
|
InstallerUrl: https://gitea.pansi.eu/harald/sanctum/releases/download/v0.4.0/sanctum-v0.4.0-windows-x86_64.zip
|
||||||
|
InstallerSha256: 0602173dceeb082fdbe7f64b73a37de1e3f7b021390386a9efba8f43aa65e76d
|
||||||
|
ManifestType: singleton
|
||||||
|
ManifestVersion: 1.6.0
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
param (
|
||||||
|
[switch]$SkipBuild = $false
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Repository-Wurzelverzeichnis ermitteln
|
||||||
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$ProjectRoot = Split-Path -Parent $ScriptDir
|
||||||
|
Set-Location $ProjectRoot
|
||||||
|
|
||||||
|
# Toolchain-Pfade sicherstellen (Zig + Cargo Bin)
|
||||||
|
$ZigDir = "C:\Users\pansih\AppData\Local\Microsoft\WinGet\Packages\zig.zig_Microsoft.Winget.Source_8wekyb3d8bbwe\zig-x86_64-windows-0.16.0"
|
||||||
|
$env:PATH = "$ZigDir;C:\Users\pansih\.cargo\bin;C:\Windows\System32;" + $env:PATH
|
||||||
|
|
||||||
|
# Version aus Cargo.toml auslesen
|
||||||
|
$CargoToml = Get-Content (Join-Path $ProjectRoot "Cargo.toml") -Raw
|
||||||
|
if ($CargoToml -match 'version\s*=\s*"([^"]+)"') {
|
||||||
|
$Version = $matches[1]
|
||||||
|
} else {
|
||||||
|
Write-Error "Konnte Versionsnummer nicht aus Cargo.toml ermitteln."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "============================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host " Sanctum Linux Release Packaging v$Version (x86_64 musl)" -ForegroundColor Cyan
|
||||||
|
Write-Host "============================================================" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# 1. Linux Release-Binary bauen
|
||||||
|
$Target = "x86_64-unknown-linux-musl"
|
||||||
|
$BinaryPath = Join-Path $ProjectRoot "target\$Target\release\sanctum"
|
||||||
|
|
||||||
|
if (-not $SkipBuild -or -not (Test-Path $BinaryPath)) {
|
||||||
|
Write-Host "`n[1/3] Kompiliere statisches Linux-Binary via cargo-zigbuild..." -ForegroundColor Yellow
|
||||||
|
cargo-zigbuild.exe zigbuild --target $Target --release
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Linux-Build fehlgeschlagen!"
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
Write-Host "[OK] Linux-Binary erfolgreich gebaut." -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "`n[1/3] Build uebersprungen (-SkipBuild)." -ForegroundColor DarkYellow
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Release-Verzeichnisstruktur vorbereiten
|
||||||
|
Write-Host "`n[2/3] Erstelle Linux-Distributionspaket..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$DistDir = Join-Path $ProjectRoot "dist"
|
||||||
|
if (-not (Test-Path $DistDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $DistDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$PackageName = "sanctum-v$Version-linux-x86_64"
|
||||||
|
$StagingDir = Join-Path $DistDir $PackageName
|
||||||
|
$TarGzFile = Join-Path $DistDir "$PackageName.tar.gz"
|
||||||
|
|
||||||
|
if (Test-Path $StagingDir) {
|
||||||
|
Remove-Item $StagingDir -Recurse -Force
|
||||||
|
}
|
||||||
|
New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
|
||||||
|
|
||||||
|
# Dateien kopieren
|
||||||
|
Copy-Item $BinaryPath -Destination (Join-Path $StagingDir "sanctum") -Force
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "README.md") -Destination $StagingDir -Force
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "LICENSE") -Destination $StagingDir -Force
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "CHANGELOG.md") -Destination $StagingDir -Force
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "INSTALL.md") -Destination $StagingDir -Force
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "QUICKSTART.md") -Destination $StagingDir -Force
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "LEGAL.md") -Destination $StagingDir -Force
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "THIRD_PARTY_LICENSES.md") -Destination $StagingDir -Force
|
||||||
|
|
||||||
|
# .tar.gz Archiv erstellen mit Windows nativem bsdtar
|
||||||
|
if (Test-Path $TarGzFile) {
|
||||||
|
Remove-Item $TarGzFile -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
tar.exe -czf $TarGzFile -C $DistDir $PackageName
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Fehler beim Erstellen des tar.gz-Archivs!"
|
||||||
|
exit $LASTEXITCODE
|
||||||
|
}
|
||||||
|
|
||||||
|
# Staging-Verzeichnis bereinigen
|
||||||
|
Remove-Item $StagingDir -Recurse -Force
|
||||||
|
|
||||||
|
Write-Host "[OK] Archiv erstellt: $TarGzFile" -ForegroundColor Green
|
||||||
|
|
||||||
|
# 3. SHA-256 Pruefsummen berechnen und aktualisieren
|
||||||
|
Write-Host "`n[3/3] Aktualisiere SHA-256 Pruefsummen..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$ChecksumFile = Join-Path $DistDir "SHA256SUMS.txt"
|
||||||
|
$TarHash = (Get-FileHash -Path $TarGzFile -Algorithm SHA256).Hash.ToLower()
|
||||||
|
$ElfHash = (Get-FileHash -Path $BinaryPath -Algorithm SHA256).Hash.ToLower()
|
||||||
|
|
||||||
|
# Existierende Pruefsummen lesen und um Linux-Assets ergaenzen/aktualisieren
|
||||||
|
$ExistingLines = if (Test-Path $ChecksumFile) {
|
||||||
|
Get-Content $ChecksumFile | Where-Object { $_ -notmatch "$PackageName\.tar\.gz" -and $_ -notmatch "\s+sanctum$" }
|
||||||
|
} else {
|
||||||
|
@()
|
||||||
|
}
|
||||||
|
|
||||||
|
$AllLines = @()
|
||||||
|
$AllLines += $ExistingLines
|
||||||
|
$AllLines += "$TarHash $PackageName.tar.gz"
|
||||||
|
$AllLines += "$ElfHash sanctum"
|
||||||
|
|
||||||
|
$AllLines | Set-Content -Path $ChecksumFile -Encoding utf8
|
||||||
|
|
||||||
|
$TarSizeMB = [math]::Round((Get-Item $TarGzFile).Length / 1MB, 2)
|
||||||
|
$ElfSizeMB = [math]::Round((Get-Item $BinaryPath).Length / 1MB, 2)
|
||||||
|
|
||||||
|
Write-Host "`n============================================================" -ForegroundColor Green
|
||||||
|
Write-Host " Sanctum Linux Release v$Version erfolgreich gepackt!" -ForegroundColor Green
|
||||||
|
Write-Host "============================================================" -ForegroundColor Green
|
||||||
|
Write-Host " Archiv: $TarGzFile ($TarSizeMB MB)"
|
||||||
|
Write-Host " TAR SHA-256: $TarHash"
|
||||||
|
Write-Host " ELF SHA-256: $ElfHash"
|
||||||
|
Write-Host " Checksum-File: $ChecksumFile"
|
||||||
|
Write-Host "`nBereit fuer Gitea Release."
|
||||||
@@ -55,14 +55,19 @@ $PackageName = "sanctum-v$Version-windows-x86_64"
|
|||||||
$StagingDir = Join-Path $DistDir $PackageName
|
$StagingDir = Join-Path $DistDir $PackageName
|
||||||
$ZipFile = Join-Path $DistDir "$PackageName.zip"
|
$ZipFile = Join-Path $DistDir "$PackageName.zip"
|
||||||
|
|
||||||
if (Test-Path $DistDir) {
|
if (-not (Test-Path $DistDir)) {
|
||||||
Remove-Item $DistDir -Recurse -Force
|
New-Item -ItemType Directory -Path $DistDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
if (Test-Path $StagingDir) {
|
||||||
|
Remove-Item $StagingDir -Recurse -Force
|
||||||
}
|
}
|
||||||
New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
|
||||||
|
|
||||||
$ExeSource = Join-Path $ProjectRoot "target\release\sanctum.exe"
|
$ExeSource = Join-Path $ProjectRoot "target\release\sanctum.exe"
|
||||||
Copy-Item $ExeSource (Join-Path $StagingDir "sanctum.exe")
|
Copy-Item $ExeSource (Join-Path $StagingDir "sanctum.exe")
|
||||||
Copy-Item (Join-Path $ProjectRoot "README.md") (Join-Path $StagingDir "README.md")
|
Copy-Item (Join-Path $ProjectRoot "README.md") (Join-Path $StagingDir "README.md")
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "QUICKSTART.md") (Join-Path $StagingDir "QUICKSTART.md")
|
||||||
|
Copy-Item (Join-Path $ProjectRoot "INSTALL.md") (Join-Path $StagingDir "INSTALL.md")
|
||||||
Copy-Item (Join-Path $ProjectRoot "LICENSE") (Join-Path $StagingDir "LICENSE")
|
Copy-Item (Join-Path $ProjectRoot "LICENSE") (Join-Path $StagingDir "LICENSE")
|
||||||
Copy-Item (Join-Path $ProjectRoot "LEGAL.md") (Join-Path $StagingDir "LEGAL.md")
|
Copy-Item (Join-Path $ProjectRoot "LEGAL.md") (Join-Path $StagingDir "LEGAL.md")
|
||||||
Copy-Item (Join-Path $ProjectRoot "THIRD_PARTY_LICENSES.md") (Join-Path $StagingDir "THIRD_PARTY_LICENSES.md")
|
Copy-Item (Join-Path $ProjectRoot "THIRD_PARTY_LICENSES.md") (Join-Path $StagingDir "THIRD_PARTY_LICENSES.md")
|
||||||
@@ -82,7 +87,17 @@ $ZipHash = (Get-FileHash -Path $ZipFile -Algorithm SHA256).Hash.ToLower()
|
|||||||
$ExeHash = (Get-FileHash -Path $ExeSource -Algorithm SHA256).Hash.ToLower()
|
$ExeHash = (Get-FileHash -Path $ExeSource -Algorithm SHA256).Hash.ToLower()
|
||||||
|
|
||||||
$ChecksumFile = Join-Path $DistDir "SHA256SUMS.txt"
|
$ChecksumFile = Join-Path $DistDir "SHA256SUMS.txt"
|
||||||
@("$ZipHash $PackageName.zip", "$ExeHash sanctum.exe") | Set-Content -Path $ChecksumFile -Encoding utf8
|
$ExistingLines = if (Test-Path $ChecksumFile) {
|
||||||
|
Get-Content $ChecksumFile | Where-Object { $_ -notmatch "$PackageName\.zip" -and $_ -notmatch "\s+sanctum\.exe$" }
|
||||||
|
} else {
|
||||||
|
@()
|
||||||
|
}
|
||||||
|
|
||||||
|
$AllLines = @()
|
||||||
|
$AllLines += "$ZipHash $PackageName.zip"
|
||||||
|
$AllLines += "$ExeHash sanctum.exe"
|
||||||
|
$AllLines += $ExistingLines
|
||||||
|
$AllLines | Set-Content -Path $ChecksumFile -Encoding utf8
|
||||||
|
|
||||||
Write-Host "[OK] Pruefsummen in SHA256SUMS.txt gespeichert." -ForegroundColor Green
|
Write-Host "[OK] Pruefsummen in SHA256SUMS.txt gespeichert." -ForegroundColor Green
|
||||||
|
|
||||||
|
|||||||
+47
-21
@@ -108,29 +108,55 @@ if (-not $Release) {
|
|||||||
$UploadUrl = "$GiteaUrl/api/v1/repos/$Owner/$Repo/releases/$($Release.id)/assets"
|
$UploadUrl = "$GiteaUrl/api/v1/repos/$Owner/$Repo/releases/$($Release.id)/assets"
|
||||||
$AuthHeader = "Authorization: token $Token"
|
$AuthHeader = "Authorization: token $Token"
|
||||||
|
|
||||||
# ZIP Asset hochladen
|
# Hilfsfunktion zum sauberen Hochladen/Ersetzen von Assets
|
||||||
Write-Host "Lade sanctum-$TagName-windows-x86_64.zip hoch..." -ForegroundColor Cyan
|
function Upload-ReleaseAsset {
|
||||||
$ZipPathNorm = $ZipFile.Replace('\', '/')
|
param (
|
||||||
$ZipUrl = "$UploadUrl?name=sanctum-$TagName-windows-x86_64.zip"
|
[string]$FilePath,
|
||||||
$ZipResult = & curl.exe -s -X POST $ZipUrl -H $AuthHeader -F "attachment=@$ZipPathNorm"
|
[string]$AssetName
|
||||||
Write-Host "[OK] sanctum-$TagName-windows-x86_64.zip hochgeladen." -ForegroundColor Green
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path $FilePath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Lade $AssetName hoch..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Vorhandenes Asset mit gleichem Namen ermitteln und ggf. loeschen
|
||||||
|
$ExistingAssets = try {
|
||||||
|
Invoke-RestMethod -Uri $UploadUrl -Headers $Headers -Method Get
|
||||||
|
} catch {
|
||||||
|
@()
|
||||||
|
}
|
||||||
|
$Duplicate = $ExistingAssets | Where-Object { $_.name -eq $AssetName }
|
||||||
|
if ($Duplicate) {
|
||||||
|
Write-Host " [i] Ersetze existierendes Asset '$AssetName' (ID: $($Duplicate.id))..." -ForegroundColor DarkYellow
|
||||||
|
$DeleteUrl = "$GiteaUrl/api/v1/repos/$Owner/$Repo/releases/$($Release.id)/assets/$($Duplicate.id)"
|
||||||
|
Invoke-RestMethod -Uri $DeleteUrl -Headers $Headers -Method Delete | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$PathNorm = $FilePath.Replace('\', '/')
|
||||||
|
$TargetUrl = "${UploadUrl}?name=$AssetName"
|
||||||
|
$Result = & curl.exe -sS -X POST $TargetUrl -H $AuthHeader -F "attachment=@$PathNorm"
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Upload von $AssetName fehlgeschlagen: $Result"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "[OK] $AssetName erfolgreich hochgeladen." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# Windows-Assets hochladen
|
||||||
|
Upload-ReleaseAsset -FilePath $ZipFile -AssetName "sanctum-$TagName-windows-x86_64.zip"
|
||||||
|
$ExeFile = Join-Path $ProjectRoot "target\release\sanctum.exe"
|
||||||
|
Upload-ReleaseAsset -FilePath $ExeFile -AssetName "sanctum.exe"
|
||||||
|
|
||||||
|
# Linux-Assets hochladen
|
||||||
|
$LinuxTar = Join-Path $DistDir "sanctum-$TagName-linux-x86_64.tar.gz"
|
||||||
|
Upload-ReleaseAsset -FilePath $LinuxTar -AssetName "sanctum-$TagName-linux-x86_64.tar.gz"
|
||||||
|
$LinuxElf = Join-Path $ProjectRoot "target\x86_64-unknown-linux-musl\release\sanctum"
|
||||||
|
Upload-ReleaseAsset -FilePath $LinuxElf -AssetName "sanctum"
|
||||||
|
|
||||||
# SHA256SUMS.txt hochladen
|
# SHA256SUMS.txt hochladen
|
||||||
Write-Host "Lade SHA256SUMS.txt hoch..." -ForegroundColor Cyan
|
Upload-ReleaseAsset -FilePath $ChecksumFile -AssetName "SHA256SUMS.txt"
|
||||||
$SumPathNorm = $ChecksumFile.Replace('\', '/')
|
|
||||||
$SumUrl = "$UploadUrl?name=SHA256SUMS.txt"
|
|
||||||
$SumResult = & curl.exe -s -X POST $SumUrl -H $AuthHeader -F "attachment=@$SumPathNorm"
|
|
||||||
Write-Host "[OK] SHA256SUMS.txt hochgeladen." -ForegroundColor Green
|
|
||||||
|
|
||||||
# Standalone EXE hochladen
|
|
||||||
$ExeFile = Join-Path $ProjectRoot "target\release\sanctum.exe"
|
|
||||||
if (Test-Path $ExeFile) {
|
|
||||||
Write-Host "Lade sanctum.exe hoch..." -ForegroundColor Cyan
|
|
||||||
$ExePathNorm = $ExeFile.Replace('\', '/')
|
|
||||||
$ExeUrl = "$UploadUrl?name=sanctum.exe"
|
|
||||||
$ExeResult = & curl.exe -s -X POST $ExeUrl -H $AuthHeader -F "attachment=@$ExePathNorm"
|
|
||||||
Write-Host "[OK] sanctum.exe hochgeladen." -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Write-Host "`n============================================================" -ForegroundColor Green
|
Write-Host "`n============================================================" -ForegroundColor Green
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod carrier;
|
pub mod carrier;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod mount;
|
pub mod mount;
|
||||||
|
pub mod platform;
|
||||||
pub mod recovery;
|
pub mod recovery;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|||||||
+14
-1
@@ -68,10 +68,14 @@ enum Commands {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
||||||
/// Laufwerksbuchstabe (z. B. 'S' oder 'S:', optional; wählt standardmäßig automatisch das nächste freie Laufwerk)
|
/// Laufwerksbuchstabe (z. B. 'S' oder 'S:', optional; wählt unter Windows standardmäßig das nächste freie Laufwerk)
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
drive: Option<String>,
|
drive: Option<String>,
|
||||||
|
|
||||||
|
/// Optionaler lokaler Mount-Pfad unter Linux/macOS (z. B. '/mnt/sanctum' oder '~/vault')
|
||||||
|
#[arg(short = 'm', long = "mount-point", value_name = "DIR")]
|
||||||
|
mount_point: Option<PathBuf>,
|
||||||
|
|
||||||
/// Optionaler TCP-Port für den lokalen WebDAV-Server (Standard: 8443)
|
/// Optionaler TCP-Port für den lokalen WebDAV-Server (Standard: 8443)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
@@ -904,6 +908,7 @@ async fn run() -> Result<()> {
|
|||||||
Commands::Mount {
|
Commands::Mount {
|
||||||
path,
|
path,
|
||||||
drive,
|
drive,
|
||||||
|
mount_point,
|
||||||
port,
|
port,
|
||||||
recovery_key,
|
recovery_key,
|
||||||
no_open,
|
no_open,
|
||||||
@@ -916,6 +921,8 @@ async fn run() -> Result<()> {
|
|||||||
let drive_char = match drive {
|
let drive_char = match drive {
|
||||||
Some(ref d) => parse_drive_letter(d)?,
|
Some(ref d) => parse_drive_letter(d)?,
|
||||||
None => {
|
None => {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
let auto_drive = sanctum::windows::find_next_available_drive()?;
|
let auto_drive = sanctum::windows::find_next_available_drive()?;
|
||||||
if !stealth {
|
if !stealth {
|
||||||
println!(
|
println!(
|
||||||
@@ -926,6 +933,11 @@ async fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
auto_drive
|
auto_drive
|
||||||
}
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
'Z'
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let auth = if let Some(key) = recovery_key {
|
let auth = if let Some(key) = recovery_key {
|
||||||
let actual_key = if key.trim().is_empty() {
|
let actual_key = if key.trim().is_empty() {
|
||||||
@@ -953,6 +965,7 @@ async fn run() -> Result<()> {
|
|||||||
mount_container(
|
mount_container(
|
||||||
&path,
|
&path,
|
||||||
drive_char,
|
drive_char,
|
||||||
|
mount_point.as_deref(),
|
||||||
port,
|
port,
|
||||||
auth,
|
auth,
|
||||||
open_explorer,
|
open_explorer,
|
||||||
|
|||||||
+170
-15
@@ -32,8 +32,10 @@ pub fn format_drive(drive_letter: char) -> String {
|
|||||||
format!("{}:", drive_letter.to_ascii_uppercase())
|
format!("{}:", drive_letter.to_ascii_uppercase())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trennt ein Windows-Netzlaufwerk via `net use <DRIVE>: /delete /y`.
|
/// Trennt ein Windows-Netzlaufwerk via `net use <DRIVE>: /delete /y` bzw. Unix-Mountpoint via `gio mount -u`.
|
||||||
pub fn unmount_drive(drive_letter: char) -> Result<()> {
|
pub fn unmount_drive(drive_letter: char) -> Result<()> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
let drive_str = format_drive(drive_letter);
|
let drive_str = format_drive(drive_letter);
|
||||||
|
|
||||||
let output = Command::new("net")
|
let output = Command::new("net")
|
||||||
@@ -53,10 +55,24 @@ pub fn unmount_drive(drive_letter: char) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let _ = drive_letter;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
|
{
|
||||||
|
let _ = drive_letter;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bindet ein Windows-Netzlaufwerk via `net use <DRIVE>: http://127.0.0.1:<PORT>/<TOKEN>/ /persistent:no` ein.
|
/// Bindet ein Windows-Netzlaufwerk via `net use <DRIVE>: http://127.0.0.1:<PORT>/<TOKEN>/ /persistent:no` ein
|
||||||
fn run_net_use_mount(drive_str: &str, port: u16, session_token: &str) -> Result<()> {
|
/// bzw. unter Unix via `gio mount dav://127.0.0.1:<PORT>/<TOKEN>/`.
|
||||||
|
fn run_mount_command(drive_str: &str, port: u16, session_token: &str) -> Result<()> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
let url = format!("http://127.0.0.1:{}/{}/", port, session_token);
|
let url = format!("http://127.0.0.1:{}/{}/", port, session_token);
|
||||||
|
|
||||||
let mut output = Command::new("net")
|
let mut output = Command::new("net")
|
||||||
@@ -107,12 +123,28 @@ fn run_net_use_mount(drive_str: &str, port: u16, session_token: &str) -> Result<
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let dav_url = format!("dav://127.0.0.1:{}/{}/", port, session_token);
|
||||||
|
let _ = Command::new("gio")
|
||||||
|
.args(["mount", &dav_url])
|
||||||
|
.output();
|
||||||
|
let _ = drive_str;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
|
{
|
||||||
|
let _ = (drive_str, port, session_token);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Startet den WebDAV-Server für den Sanctum-Container und bindet ihn als Netzlaufwerk ein.
|
/// Startet den WebDAV-Server für den Sanctum-Container und bindet ihn als Netzlaufwerk (Windows) bzw. VFS (Linux) ein.
|
||||||
pub async fn mount_container(
|
pub async fn mount_container(
|
||||||
container_path: &Path,
|
container_path: &Path,
|
||||||
drive_letter: char,
|
drive_letter: char,
|
||||||
|
mount_point: Option<&Path>,
|
||||||
requested_port: Option<u16>,
|
requested_port: Option<u16>,
|
||||||
auth: ContainerAuth,
|
auth: ContainerAuth,
|
||||||
open_explorer: bool,
|
open_explorer: bool,
|
||||||
@@ -123,6 +155,10 @@ pub async fn mount_container(
|
|||||||
stealth: bool,
|
stealth: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let drive_str = format_drive(drive_letter);
|
let drive_str = format_drive(drive_letter);
|
||||||
|
let _ = mount_point;
|
||||||
|
let _ = open_explorer;
|
||||||
|
let _ = enable_tray;
|
||||||
|
let _ = lock_on_screen_lock;
|
||||||
|
|
||||||
if !container_path.exists() {
|
if !container_path.exists() {
|
||||||
bail!(
|
bail!(
|
||||||
@@ -224,6 +260,7 @@ pub async fn mount_container(
|
|||||||
let bound_port = bound_addr.port();
|
let bound_port = bound_addr.port();
|
||||||
|
|
||||||
if !stealth {
|
if !stealth {
|
||||||
|
#[cfg(windows)]
|
||||||
ui::step(
|
ui::step(
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
@@ -233,6 +270,16 @@ pub async fn mount_container(
|
|||||||
bound_port, drive_str
|
bound_port, drive_str
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
ui::step(
|
||||||
|
4,
|
||||||
|
4,
|
||||||
|
"🌐",
|
||||||
|
&format!(
|
||||||
|
"Starte WebDAV-Server auf Port {} & initialisiere VFS...",
|
||||||
|
bound_port
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (shutdown_tx, shutdown_rx) = watch::channel(false);
|
let (shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||||
@@ -245,20 +292,22 @@ pub async fn mount_container(
|
|||||||
shutdown_rx,
|
shutdown_rx,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Netzlaufwerk einbinden
|
// Netzlaufwerk bzw. Verzeichnis einbinden
|
||||||
if let Err(e) = run_net_use_mount(&drive_str, bound_port, &session_token) {
|
if let Err(e) = run_mount_command(&drive_str, bound_port, &session_token) {
|
||||||
let _ = shutdown_tx.send(true);
|
let _ = shutdown_tx.send(true);
|
||||||
let _ = server_handle.await;
|
let _ = server_handle.await;
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional automatisch im Windows Explorer öffnen (visuelle Parität für Decoy und Hidden Vault)
|
// Optional automatisch im Windows Explorer öffnen (visuelle Parität für Decoy und Hidden Vault)
|
||||||
|
#[cfg(windows)]
|
||||||
if open_explorer {
|
if open_explorer {
|
||||||
let _ = crate::windows::open_in_explorer(drive_letter);
|
let _ = crate::windows::open_in_explorer(drive_letter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// System-Tray Initialisierung
|
// System-Tray Initialisierung (nur Windows)
|
||||||
let (tray_shutdown_tx, mut tray_shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
|
#[cfg(windows)]
|
||||||
|
let (_tray_shutdown_tx, mut tray_shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let _tray = if enable_tray {
|
let _tray = if enable_tray {
|
||||||
let icon_source = crate::windows::get_default_system_icon()
|
let icon_source = crate::windows::get_default_system_icon()
|
||||||
@@ -276,7 +325,7 @@ pub async fn mount_container(
|
|||||||
let _ = tray.add_menu_item("Im Explorer öffnen", move || {
|
let _ = tray.add_menu_item("Im Explorer öffnen", move || {
|
||||||
let _ = crate::windows::open_in_explorer(dl);
|
let _ = crate::windows::open_in_explorer(dl);
|
||||||
});
|
});
|
||||||
let s_tx = tray_shutdown_tx.clone();
|
let s_tx = _tray_shutdown_tx.clone();
|
||||||
let _ = tray.add_menu_item("Trennen & Beenden", move || {
|
let _ = tray.add_menu_item("Trennen & Beenden", move || {
|
||||||
let _ = s_tx.blocking_send(());
|
let _ = s_tx.blocking_send(());
|
||||||
});
|
});
|
||||||
@@ -315,8 +364,10 @@ pub async fn mount_container(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows-Sitzungssperre (Win + L Auto-Lock)
|
// Windows-Sitzungssperre (Win + L Auto-Lock, nur Windows)
|
||||||
|
#[cfg(windows)]
|
||||||
let (session_lock_tx, mut session_lock_rx) = tokio::sync::mpsc::channel::<()>(1);
|
let (session_lock_tx, mut session_lock_rx) = tokio::sync::mpsc::channel::<()>(1);
|
||||||
|
#[cfg(windows)]
|
||||||
let _session_monitor = if lock_on_screen_lock {
|
let _session_monitor = if lock_on_screen_lock {
|
||||||
match crate::windows::start_session_lock_monitor(session_lock_tx) {
|
match crate::windows::start_session_lock_monitor(session_lock_tx) {
|
||||||
Ok(guard) => Some(guard),
|
Ok(guard) => Some(guard),
|
||||||
@@ -330,7 +381,10 @@ pub async fn mount_container(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if stealth {
|
if stealth {
|
||||||
|
#[cfg(windows)]
|
||||||
println!("Sanctum: Netzlaufwerk {} bereit.", drive_str);
|
println!("Sanctum: Netzlaufwerk {} bereit.", drive_str);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
println!("Sanctum: WebDAV-Server auf Port {} bereit.", bound_port);
|
||||||
} else {
|
} else {
|
||||||
println!();
|
println!();
|
||||||
println!("┌─────────────────────────────────────────────────────────────┐");
|
println!("┌─────────────────────────────────────────────────────────────┐");
|
||||||
@@ -338,30 +392,63 @@ pub async fn mount_container(
|
|||||||
println!("└─────────────────────────────────────────────────────────────┘");
|
println!("└─────────────────────────────────────────────────────────────┘");
|
||||||
println!();
|
println!();
|
||||||
println!(" • Container: {}", container_path.display());
|
println!(" • Container: {}", container_path.display());
|
||||||
|
#[cfg(windows)]
|
||||||
println!(" • Netzlaufwerk: {} (im Windows Explorer bereit)", ui::cyan(&drive_str));
|
println!(" • Netzlaufwerk: {} (im Windows Explorer bereit)", ui::cyan(&drive_str));
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
if let Some(mp) = mount_point {
|
||||||
|
println!(" • Mountpoint: {}", ui::cyan(&mp.display().to_string()));
|
||||||
|
} else {
|
||||||
|
println!(" • Modus: WebDAV Userland-VFS");
|
||||||
|
}
|
||||||
|
}
|
||||||
println!(" • WebDAV-URL: http://127.0.0.1:{}/{}/ (Session-Token geschützt)", bound_port, session_token);
|
println!(" • WebDAV-URL: http://127.0.0.1:{}/{}/ (Session-Token geschützt)", bound_port, session_token);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
println!(" • gio Befehl: gio mount dav://127.0.0.1:{}/{}/", bound_port, session_token);
|
||||||
|
if let Some(mp) = mount_point {
|
||||||
|
println!(" • davfs2: mount -t davfs http://127.0.0.1:{}/{}/ {}", bound_port, session_token, mp.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(secs) = idle_timeout {
|
if let Some(secs) = idle_timeout {
|
||||||
println!(" • Auto-Lock: Inaktivität nach {}s", secs);
|
println!(" • Auto-Lock: Inaktivität nach {}s", secs);
|
||||||
}
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
if lock_on_screen_lock {
|
if lock_on_screen_lock {
|
||||||
println!(" • Sitzung: Automatisches Sperren bei Win + L aktiv");
|
println!(" • Sitzung: Automatisches Sperren bei Win + L aktiv");
|
||||||
}
|
}
|
||||||
if anti_leak {
|
if anti_leak {
|
||||||
|
#[cfg(windows)]
|
||||||
println!(" • Anti-Leak: Explorer-Metadatenfilter aktiv (Thumbs.db, desktop.ini blockiert)");
|
println!(" • Anti-Leak: Explorer-Metadatenfilter aktiv (Thumbs.db, desktop.ini blockiert)");
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
println!(" • Anti-Leak: VFS-Metadatenfilter aktiv (.directory, .Trash, desktop.ini blockiert)");
|
||||||
}
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
if enable_tray {
|
if enable_tray {
|
||||||
println!(" • System-Tray: Icon aktiv (Rechtsklick für Explorer/Trennen)");
|
println!(" • System-Tray: Icon aktiv (Rechtsklick für Explorer/Trennen)");
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
|
#[cfg(windows)]
|
||||||
println!(" [{}] Drücke [Ctrl+C] oder nutze das Tray-Icon zum Beenden.", ui::yellow("Tipp"));
|
println!(" [{}] Drücke [Ctrl+C] oder nutze das Tray-Icon zum Beenden.", ui::yellow("Tipp"));
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
println!(" [{}] Drücke [Ctrl+C] zum sicheren Beenden.", ui::yellow("Tipp"));
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows Console Close Monitor (CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT)
|
// Windows Console Close Monitor (CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT, nur Windows)
|
||||||
|
#[cfg(windows)]
|
||||||
let (console_close_tx, mut console_close_rx) = tokio::sync::mpsc::channel::<()>(1);
|
let (console_close_tx, mut console_close_rx) = tokio::sync::mpsc::channel::<()>(1);
|
||||||
|
#[cfg(windows)]
|
||||||
let _console_guard = crate::windows::start_console_ctrl_monitor(console_close_tx, drive_letter).ok();
|
let _console_guard = crate::windows::start_console_ctrl_monitor(console_close_tx, drive_letter).ok();
|
||||||
|
|
||||||
|
// Unix Signale (SIGTERM, SIGHUP)
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).ok();
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut sighup = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup()).ok();
|
||||||
|
|
||||||
// Warten auf Beendigungssignal (Ctrl+C, Tray-Klick, Inaktivität, Win+L, Konsolenfenster-Schließen)
|
// Warten auf Beendigungssignal (Ctrl+C, Tray-Klick, Inaktivität, Win+L, Konsolenfenster-Schließen)
|
||||||
|
#[cfg(windows)]
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
res = tokio::signal::ctrl_c() => {
|
res = tokio::signal::ctrl_c() => {
|
||||||
let _ = res;
|
let _ = res;
|
||||||
@@ -370,25 +457,81 @@ pub async fn mount_container(
|
|||||||
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
|
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = console_close_rx.recv() => {
|
Some(()) = console_close_rx.recv() => {
|
||||||
if !stealth {
|
if !stealth {
|
||||||
println!();
|
println!();
|
||||||
println!(" {} Konsolenfenster wird geschlossen — sichere Trennung ausgeführt!", ui::yellow("[!]"));
|
println!(" {} Konsolenfenster wird geschlossen — sichere Trennung ausgeführt!", ui::yellow("[!]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = tray_shutdown_rx.recv() => {
|
Some(()) = tray_shutdown_rx.recv() => {
|
||||||
if !stealth {
|
if !stealth {
|
||||||
println!();
|
println!();
|
||||||
println!(" {} Beendigungssignal aus System-Tray empfangen.", ui::yellow("[!]"));
|
println!(" {} Beendigungssignal aus System-Tray empfangen.", ui::yellow("[!]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = session_lock_rx.recv() => {
|
Some(()) = session_lock_rx.recv() => {
|
||||||
if !stealth {
|
if !stealth {
|
||||||
println!();
|
println!();
|
||||||
println!(" {} Windows-Sitzung gesperrt (Win + L) — Auto-Lock ausgelöst!", ui::yellow("[!]"));
|
println!(" {} Windows-Sitzung gesperrt (Win + L) — Auto-Lock ausgelöst!", ui::yellow("[!]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = idle_shutdown_rx.recv() => {
|
Some(()) = idle_shutdown_rx.recv() => {
|
||||||
|
if !stealth {
|
||||||
|
println!();
|
||||||
|
println!(" {} Inaktivitäts-Timeout erreicht — Auto-Lock ausgelöst!", ui::yellow("[!]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
tokio::select! {
|
||||||
|
res = tokio::signal::ctrl_c() => {
|
||||||
|
let _ = res;
|
||||||
|
if !stealth {
|
||||||
|
println!();
|
||||||
|
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = async {
|
||||||
|
match sigterm.as_mut() {
|
||||||
|
Some(s) => { s.recv().await; }
|
||||||
|
None => { std::future::pending::<()>().await; }
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
if !stealth {
|
||||||
|
println!();
|
||||||
|
println!(" {} Beendigungssignal (SIGTERM) empfangen.", ui::yellow("[!]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = async {
|
||||||
|
match sighup.as_mut() {
|
||||||
|
Some(s) => { s.recv().await; }
|
||||||
|
None => { std::future::pending::<()>().await; }
|
||||||
|
}
|
||||||
|
} => {
|
||||||
|
if !stealth {
|
||||||
|
println!();
|
||||||
|
println!(" {} Beendigungssignal (SIGHUP) empfangen.", ui::yellow("[!]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(()) = idle_shutdown_rx.recv() => {
|
||||||
|
if !stealth {
|
||||||
|
println!();
|
||||||
|
println!(" {} Inaktivitäts-Timeout erreicht — Auto-Lock ausgelöst!", ui::yellow("[!]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
|
tokio::select! {
|
||||||
|
res = tokio::signal::ctrl_c() => {
|
||||||
|
let _ = res;
|
||||||
|
if !stealth {
|
||||||
|
println!();
|
||||||
|
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(()) = idle_shutdown_rx.recv() => {
|
||||||
if !stealth {
|
if !stealth {
|
||||||
println!();
|
println!();
|
||||||
println!(" {} Inaktivitäts-Timeout erreicht — Auto-Lock ausgelöst!", ui::yellow("[!]"));
|
println!(" {} Inaktivitäts-Timeout erreicht — Auto-Lock ausgelöst!", ui::yellow("[!]"));
|
||||||
@@ -406,8 +549,13 @@ pub async fn mount_container(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = db.checkpoint();
|
let _ = db.checkpoint();
|
||||||
|
#[cfg(windows)]
|
||||||
println!("Sanctum: Laufwerk {} getrennt und geschlossen.", drive_str);
|
println!("Sanctum: Laufwerk {} getrennt und geschlossen.", drive_str);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
println!("Sanctum: Container geschlossen und WebDAV-Server beendet.");
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
print!(" {} Trenne Windows-Netzlaufwerk {} ... ", ui::dim("[-]"), drive_str);
|
print!(" {} Trenne Windows-Netzlaufwerk {} ... ", ui::dim("[-]"), drive_str);
|
||||||
let _ = std::io::Write::flush(&mut std::io::stdout());
|
let _ = std::io::Write::flush(&mut std::io::stdout());
|
||||||
|
|
||||||
@@ -417,6 +565,13 @@ pub async fn mount_container(
|
|||||||
} else {
|
} else {
|
||||||
println!("{}", ui::green("OK"));
|
println!("{}", ui::green("OK"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
print!(" {} Beende WebDAV-Server ... ", ui::dim("[-]"));
|
||||||
|
let _ = std::io::Write::flush(&mut std::io::stdout());
|
||||||
|
println!("{}", ui::green("OK"));
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP Server beenden
|
// HTTP Server beenden
|
||||||
let _ = shutdown_tx.send(true);
|
let _ = shutdown_tx.send(true);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//! Plattform-Abstraktionsschicht für Sanctum.
|
||||||
|
//!
|
||||||
|
//! Dieses Modul bündelt alle betriebssystemspezifischen Funktionen
|
||||||
|
//! (Speichersperren, Dateimanager-Aufrufe, Signal-Monitoring, Shell-Integration)
|
||||||
|
//! für Windows, Linux und macOS unter einer einheitlichen, speichersicheren Schnittstelle.
|
||||||
|
|
||||||
|
pub use crate::windows::*;
|
||||||
+133
-17
@@ -1,5 +1,6 @@
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
|
||||||
/// Ermittelt den nächsten verfügbaren Windows-Laufwerksbuchstaben (von 'Z' rückwärts bis 'D').
|
/// Ermittelt den nächsten verfügbaren Windows-Laufwerksbuchstaben (von 'Z' rückwärts bis 'D').
|
||||||
@@ -32,14 +33,68 @@ pub fn find_next_available_drive() -> Result<char> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Öffnet das eingebundene Netzlaufwerk direkt im Windows Explorer.
|
/// Öffnet das eingebundene Netzlaufwerk oder Verzeichnis direkt im systemeigenen Dateimanager
|
||||||
|
/// (Windows: Explorer, macOS: open, Linux: xdg-open).
|
||||||
pub fn open_in_explorer(drive_char: char) -> Result<()> {
|
pub fn open_in_explorer(drive_char: char) -> Result<()> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
let drive_path = format!("{}:\\", drive_char.to_ascii_uppercase());
|
let drive_path = format!("{}:\\", drive_char.to_ascii_uppercase());
|
||||||
Command::new("explorer.exe")
|
Command::new("explorer.exe")
|
||||||
.arg(&drive_path)
|
.arg(&drive_path)
|
||||||
.spawn()
|
.spawn()
|
||||||
.with_context(|| format!("Konnte Windows Explorer für '{}' nicht öffnen", drive_path))?;
|
.with_context(|| format!("Konnte Windows Explorer für '{}' nicht öffnen", drive_path))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let _ = drive_char;
|
||||||
|
let _ = Command::new("open").arg(".").spawn();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
{
|
||||||
|
let _ = drive_char;
|
||||||
|
let _ = Command::new("xdg-open").arg(".").spawn();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
|
{
|
||||||
|
let _ = drive_char;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Öffnet einen beliebigen Pfad im nativen Dateimanager der Plattform.
|
||||||
|
pub fn open_in_file_manager(path: &std::path::Path) -> Result<()> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
Command::new("explorer.exe")
|
||||||
|
.arg(path)
|
||||||
|
.spawn()
|
||||||
|
.with_context(|| format!("Konnte Windows Explorer für '{}' nicht öffnen", path.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
Command::new("open")
|
||||||
|
.arg(path)
|
||||||
|
.spawn()
|
||||||
|
.with_context(|| format!("Konnte macOS Finder für '{}' nicht öffnen", path.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
{
|
||||||
|
Command::new("xdg-open")
|
||||||
|
.arg(path)
|
||||||
|
.spawn()
|
||||||
|
.with_context(|| format!("Konnte Dateimanager via xdg-open für '{}' nicht öffnen", path.display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
|
{
|
||||||
|
let _ = path;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Benachrichtigt die Windows-Shell (Explorer) über geänderte Dateiverknüpfungen (SHCNE_ASSOCCHANGED).
|
/// Benachrichtigt die Windows-Shell (Explorer) über geänderte Dateiverknüpfungen (SHCNE_ASSOCCHANGED).
|
||||||
@@ -67,10 +122,19 @@ pub fn notify_shell_associations_changed() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
|
{
|
||||||
|
let _ = Command::new("update-desktop-database").spawn();
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, all(unix, not(target_os = "macos")))))]
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registriert `.sanctum`-Containerdateien im Windows Explorer für den aktuellen Benutzer (HKCU, 100% Userland, keine Adminrechte).
|
/// Registriert `.sanctum`-Containerdateien im Windows Explorer für den aktuellen Benutzer bzw. unter Linux via Freedesktop.
|
||||||
pub fn register_explorer_integration() -> Result<()> {
|
pub fn register_explorer_integration() -> Result<()> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
let current_exe = std::env::current_exe()
|
let current_exe = std::env::current_exe()
|
||||||
.context("Konnte den Pfad zur aktuellen sanctum.exe nicht ermitteln")?;
|
.context("Konnte den Pfad zur aktuellen sanctum.exe nicht ermitteln")?;
|
||||||
let exe_str = current_exe.display().to_string();
|
let exe_str = current_exe.display().to_string();
|
||||||
@@ -98,14 +162,14 @@ pub fn register_explorer_integration() -> Result<()> {
|
|||||||
(
|
(
|
||||||
r"HKCU\Software\Classes\Sanctum.Container\shell\open",
|
r"HKCU\Software\Classes\Sanctum.Container\shell\open",
|
||||||
"",
|
"",
|
||||||
"Als Laufwerk einbinden",
|
"In Sanctum öffnen",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"HKCU\Software\Classes\Sanctum.Container\shell\open\command",
|
r"HKCU\Software\Classes\Sanctum.Container\shell\open\command",
|
||||||
"",
|
"",
|
||||||
&format!("\"{exe_str}\" mount --path \"%1\""),
|
&format!("\"{exe_str}\" mount \"%1\""),
|
||||||
),
|
),
|
||||||
// 5. Kontextmenü-Aktion: Integritätsprüfung
|
// 5. Kontextmenü-Aktion: Verify / FSCK
|
||||||
(
|
(
|
||||||
r"HKCU\Software\Classes\Sanctum.Container\shell\verify",
|
r"HKCU\Software\Classes\Sanctum.Container\shell\verify",
|
||||||
"",
|
"",
|
||||||
@@ -114,18 +178,18 @@ pub fn register_explorer_integration() -> Result<()> {
|
|||||||
(
|
(
|
||||||
r"HKCU\Software\Classes\Sanctum.Container\shell\verify\command",
|
r"HKCU\Software\Classes\Sanctum.Container\shell\verify\command",
|
||||||
"",
|
"",
|
||||||
&format!("\"{exe_str}\" verify --path \"%1\""),
|
&format!("cmd /k \"\"{exe_str}\" verify \"%1\"\""),
|
||||||
),
|
),
|
||||||
// 6. Kontextmenü-Aktion: Header sichern
|
// 6. Kontextmenü-Aktion: Header-Backup
|
||||||
(
|
(
|
||||||
r"HKCU\Software\Classes\Sanctum.Container\shell\backup_header",
|
r"HKCU\Software\Classes\Sanctum.Container\shell\backup",
|
||||||
"",
|
"",
|
||||||
"Header sichern",
|
"Header sichern (Disaster Recovery)",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"HKCU\Software\Classes\Sanctum.Container\shell\backup_header\command",
|
r"HKCU\Software\Classes\Sanctum.Container\shell\backup\command",
|
||||||
"",
|
"",
|
||||||
&format!("\"{exe_str}\" backup-header --path \"%1\""),
|
&format!("cmd /k \"\"{exe_str}\" backup-header \"%1\"\""),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -148,10 +212,32 @@ pub fn register_explorer_integration() -> Result<()> {
|
|||||||
|
|
||||||
notify_shell_associations_changed();
|
notify_shell_associations_changed();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
if let Some(home) = std::env::var_os("HOME") {
|
||||||
|
let home_path = std::path::PathBuf::from(home);
|
||||||
|
let apps_dir = home_path.join(".local/share/applications");
|
||||||
|
let mime_dir = home_path.join(".local/share/mime/packages");
|
||||||
|
let _ = std::fs::create_dir_all(&apps_dir);
|
||||||
|
let _ = std::fs::create_dir_all(&mime_dir);
|
||||||
|
|
||||||
|
let desktop_content = "[Desktop Entry]\nType=Application\nName=Sanctum\nComment=Verschlüsselter Ein-Datei-Container\nExec=sanctum mount %f\nIcon=security-high\nTerminal=true\nMimeType=application/x-sanctum;\nCategories=Utility;Security;\n";
|
||||||
|
let _ = std::fs::write(apps_dir.join("sanctum.desktop"), desktop_content);
|
||||||
|
|
||||||
|
let mime_content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n <mime-type type=\"application/x-sanctum\">\n <comment>Sanctum Verschlüsselter Container</comment>\n <glob pattern=\"*.sanctum\"/>\n </mime-type>\n</mime-info>\n";
|
||||||
|
let _ = std::fs::write(mime_dir.join("application-x-sanctum.xml"), mime_content);
|
||||||
|
|
||||||
|
notify_shell_associations_changed();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Entfernt die Windows-Explorer-Verknüpfungen aus der Benutzer-Registry (HKCU).
|
/// Entfernt die Windows-Explorer-Verknüpfungen aus der Benutzer-Registry (HKCU) bzw. unter Linux aus Freedesktop.
|
||||||
pub fn unregister_explorer_integration() -> Result<()> {
|
pub fn unregister_explorer_integration() -> Result<()> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
let keys_to_delete = [
|
let keys_to_delete = [
|
||||||
r"HKCU\Software\Classes\.sanctum",
|
r"HKCU\Software\Classes\.sanctum",
|
||||||
r"HKCU\Software\Classes\Sanctum.Container",
|
r"HKCU\Software\Classes\Sanctum.Container",
|
||||||
@@ -165,6 +251,17 @@ pub fn unregister_explorer_integration() -> Result<()> {
|
|||||||
|
|
||||||
notify_shell_associations_changed();
|
notify_shell_associations_changed();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
if let Some(home) = std::env::var_os("HOME") {
|
||||||
|
let home_path = std::path::PathBuf::from(home);
|
||||||
|
let _ = std::fs::remove_file(home_path.join(".local/share/applications/sanctum.desktop"));
|
||||||
|
let _ = std::fs::remove_file(home_path.join(".local/share/mime/packages/application-x-sanctum.xml"));
|
||||||
|
notify_shell_associations_changed();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lädt das Windows-Sicherheitsschild-Icon (IDI_SHIELD) oder Anwendungs-Icon für den System-Tray.
|
/// Lädt das Windows-Sicherheitsschild-Icon (IDI_SHIELD) oder Anwendungs-Icon für den System-Tray.
|
||||||
@@ -198,10 +295,9 @@ pub fn get_default_system_icon() -> Option<tray_item::IconSource> {
|
|||||||
|
|
||||||
/// Guard zur Verwaltung des Hintergrundthreads für die Windows-Sitzungssperre.
|
/// Guard zur Verwaltung des Hintergrundthreads für die Windows-Sitzungssperre.
|
||||||
/// Beim Droppen wird das Win32-Nachrichtenfenster geschlossen und der Thread sauber beendet.
|
/// Beim Droppen wird das Win32-Nachrichtenfenster geschlossen und der Thread sauber beendet.
|
||||||
|
#[cfg(windows)]
|
||||||
pub struct SessionLockGuard {
|
pub struct SessionLockGuard {
|
||||||
#[cfg(windows)]
|
|
||||||
hwnd: isize,
|
hwnd: isize,
|
||||||
#[cfg(windows)]
|
|
||||||
join_handle: Option<std::thread::JoinHandle<()>>,
|
join_handle: Option<std::thread::JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,7 +623,7 @@ pub fn start_console_ctrl_monitor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verriegelt einen Speicherbereich im physischen RAM (verhindert Paging in pagefile.sys / swapfile.sys).
|
/// Verriegelt einen Speicherbereich im physischen RAM (verhindert Paging in pagefile.sys / swapfile.sys unter Windows bzw. Swap unter Linux/macOS).
|
||||||
pub fn lock_memory(ptr: *const u8, len: usize) -> bool {
|
pub fn lock_memory(ptr: *const u8, len: usize) -> bool {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
@@ -539,7 +635,17 @@ pub fn lock_memory(ptr: *const u8, len: usize) -> bool {
|
|||||||
}
|
}
|
||||||
unsafe { VirtualLock(ptr as *const std::ffi::c_void, len) != 0 }
|
unsafe { VirtualLock(ptr as *const std::ffi::c_void, len) != 0 }
|
||||||
}
|
}
|
||||||
#[cfg(not(windows))]
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
extern "C" {
|
||||||
|
fn mlock(addr: *const std::ffi::c_void, len: usize) -> std::ffi::c_int;
|
||||||
|
}
|
||||||
|
if ptr.is_null() || len == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unsafe { mlock(ptr as *const std::ffi::c_void, len) == 0 }
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
{
|
{
|
||||||
let _ = (ptr, len);
|
let _ = (ptr, len);
|
||||||
false
|
false
|
||||||
@@ -558,7 +664,17 @@ pub fn unlock_memory(ptr: *const u8, len: usize) -> bool {
|
|||||||
}
|
}
|
||||||
unsafe { VirtualUnlock(ptr as *const std::ffi::c_void, len) != 0 }
|
unsafe { VirtualUnlock(ptr as *const std::ffi::c_void, len) != 0 }
|
||||||
}
|
}
|
||||||
#[cfg(not(windows))]
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
extern "C" {
|
||||||
|
fn munlock(addr: *const std::ffi::c_void, len: usize) -> std::ffi::c_int;
|
||||||
|
}
|
||||||
|
if ptr.is_null() || len == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unsafe { munlock(ptr as *const std::ffi::c_void, len) == 0 }
|
||||||
|
}
|
||||||
|
#[cfg(not(any(windows, unix)))]
|
||||||
{
|
{
|
||||||
let _ = (ptr, len);
|
let _ = (ptr, len);
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use dav_server::{
|
||||||
|
davpath::DavPath,
|
||||||
|
fs::{DavFileSystem, OpenOptions},
|
||||||
|
};
|
||||||
|
use rand::RngCore;
|
||||||
|
use sanctum::{
|
||||||
|
crypto::{derive_kek, generate_dek, generate_salt, wrap_dek, KdfParams, FORMAT_VERSION},
|
||||||
|
storage::Database,
|
||||||
|
verify::verify_container,
|
||||||
|
vfs::SanctumFs,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Live-Crash- und Stresstest: Simuliert harten Verbindungsabbruch und Power-Cut
|
||||||
|
/// während intensiver paralleler Schreibvorgänge im VFS.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_live_crash_and_recovery_stress() {
|
||||||
|
let temp_dir = std::env::temp_dir();
|
||||||
|
let container_path: PathBuf = temp_dir.join(format!("sanctum_live_stress_{}.sanctum", std::process::id()));
|
||||||
|
if container_path.exists() {
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = "LiveStressPassword2026!";
|
||||||
|
let salt = generate_salt();
|
||||||
|
let kdf_params = KdfParams {
|
||||||
|
memory_cost: 1024,
|
||||||
|
time_cost: 1,
|
||||||
|
parallelism: 1,
|
||||||
|
};
|
||||||
|
let kek = derive_kek(password, &salt, &kdf_params).expect("KEK derivation");
|
||||||
|
let dek = generate_dek();
|
||||||
|
let (wrapped_dek, header_nonce, header_tag) = wrap_dek(&kek, &dek).expect("DEK wrapping");
|
||||||
|
|
||||||
|
// 1. Initialisierung des Containers
|
||||||
|
{
|
||||||
|
let db = Database::open(&container_path).expect("Open database");
|
||||||
|
db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag)
|
||||||
|
.expect("Init schema");
|
||||||
|
db.checkpoint().expect("Initial Checkpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Parallele Schreiblast mit SanctumFs erzeugen
|
||||||
|
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||||
|
let db = Database::open(&container_path).expect("Open database for VFS");
|
||||||
|
let fs = SanctumFs::new(db, dek.clone(), FORMAT_VERSION);
|
||||||
|
|
||||||
|
let mut handles = Vec::new();
|
||||||
|
|
||||||
|
// Spawn 4 parallele Schreiber
|
||||||
|
for worker_id in 0..4 {
|
||||||
|
let fs_clone = fs.clone();
|
||||||
|
let stop_clone = stop_signal.clone();
|
||||||
|
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
|
let mut file_idx = 0;
|
||||||
|
while !stop_clone.load(Ordering::Relaxed) && file_idx < 10 {
|
||||||
|
let file_path_str = format!("/worker_{}_file_{}.dat", worker_id, file_idx);
|
||||||
|
let dav_path = DavPath::new(&file_path_str).unwrap();
|
||||||
|
|
||||||
|
let mut opts = OpenOptions::default();
|
||||||
|
opts.write = true;
|
||||||
|
opts.create = true;
|
||||||
|
opts.truncate = true;
|
||||||
|
|
||||||
|
// Datei anlegen
|
||||||
|
let mut file = match fs_clone.open(&dav_path, opts).await {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mehrere 256-KB Blöcke schreiben (über mehrere Chunks hinweg)
|
||||||
|
let mut payload = vec![0u8; 256 * 1024];
|
||||||
|
rand::thread_rng().fill_bytes(&mut payload);
|
||||||
|
|
||||||
|
for _ in 0..6 {
|
||||||
|
if stop_clone.load(Ordering::Relaxed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let _ = file.write_bytes(Bytes::copy_from_slice(&payload)).await;
|
||||||
|
}
|
||||||
|
let _ = file.flush().await;
|
||||||
|
file_idx += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lass die Worker 500ms unter Volllast schreiben
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||||
|
|
||||||
|
// 3. Simuliere abrupten Prozessabbruch (Hard Kill / Power Cut)
|
||||||
|
// Wir brechen die Worker hart ab (Cancel) und verwerfen das FS-Handle ohne sauberen Unmount
|
||||||
|
stop_signal.store(true, Ordering::SeqCst);
|
||||||
|
for h in handles {
|
||||||
|
h.abort(); // Simuliert Kill
|
||||||
|
}
|
||||||
|
|
||||||
|
// FS ohne Checkpoint/Drop-Finalisierung freigeben
|
||||||
|
drop(fs);
|
||||||
|
|
||||||
|
// 4. Recovery & Integritätsprüfung nach Crash
|
||||||
|
// Das System muss die SQLite WAL-Datei automatisch erkennen und verarbeiten
|
||||||
|
let verify_result = verify_container(&container_path, Some(&dek), false).expect("Verify post-crash");
|
||||||
|
assert!(
|
||||||
|
verify_result.is_healthy(),
|
||||||
|
"Container muss nach Crash vollkommen konsistent sein! Fehler: {:?}",
|
||||||
|
verify_result.errors
|
||||||
|
);
|
||||||
|
assert_eq!(verify_result.corrupted_chunks, 0, "Keine korrupten Chunks erlaubt");
|
||||||
|
|
||||||
|
// 5. Konsistentes Weiterarbeiten nach dem Absturz
|
||||||
|
let db_recovered = Database::open(&container_path).expect("Open database after crash");
|
||||||
|
let fs_recovered = SanctumFs::new(db_recovered, dek.clone(), FORMAT_VERSION);
|
||||||
|
|
||||||
|
// Neue Datei im wiederhergestellten Dateisystem anlegen und lesen
|
||||||
|
let recovery_test_path = DavPath::new("/post_crash_verification.txt").unwrap();
|
||||||
|
{
|
||||||
|
let mut opts = OpenOptions::default();
|
||||||
|
opts.write = true;
|
||||||
|
opts.create = true;
|
||||||
|
opts.truncate = true;
|
||||||
|
let mut file = fs_recovered
|
||||||
|
.open(&recovery_test_path, opts)
|
||||||
|
.await
|
||||||
|
.expect("Create post-crash file");
|
||||||
|
file.write_bytes(Bytes::from_static(b"Sanctum Crash Consistency Verified!"))
|
||||||
|
.await
|
||||||
|
.expect("Write post crash file");
|
||||||
|
file.flush().await.expect("Flush post crash file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datei wieder einlesen
|
||||||
|
{
|
||||||
|
let mut opts = OpenOptions::default();
|
||||||
|
opts.read = true;
|
||||||
|
let mut file = fs_recovered
|
||||||
|
.open(&recovery_test_path, opts)
|
||||||
|
.await
|
||||||
|
.expect("Read post-crash file");
|
||||||
|
let bytes = file.read_bytes(1024).await.expect("Read bytes");
|
||||||
|
assert_eq!(&bytes[..], b"Sanctum Crash Consistency Verified!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauberes Aufräumen der Testdatei
|
||||||
|
drop(fs_recovered);
|
||||||
|
let _ = std::fs::remove_file(&container_path);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user