From da2ddbd6ab7ee8c06d4d94d70fc8cd5fde573969 Mon Sep 17 00:00:00 2001 From: harald Date: Thu, 10 Sep 2026 16:51:24 +0200 Subject: [PATCH] feat(distribution): add Scoop/Winget manifests, INSTALL.md, and Unix mount integration --- INSTALL.md | 75 +++++++++++ README.md | 2 +- packages/scoop/sanctum.json | 25 ++++ packages/winget/HaraldPansi.Sanctum.yaml | 32 +++++ scripts/package-release.ps1 | 1 + src/mount.rs | 159 ++++++++++++++--------- 6 files changed, 229 insertions(+), 65 deletions(-) create mode 100644 INSTALL.md create mode 100644 packages/scoop/sanctum.json create mode 100644 packages/winget/HaraldPansi.Sanctum.yaml diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..3d6af6d --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,75 @@ +# 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.3.1](https://gitea.pansi.eu/harald/sanctum/releases/tag/v0.3.1) + * **Datei:** `sanctum-v0.3.1-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).* + +--- + +## 🔄 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 + ``` diff --git a/README.md b/README.md index 332edab..a69552f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ 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`). - **Statisches Single-Binary**: `sanctum.exe` (~5.3 MB) ohne externe DLL-Abhängigkeiten. -> 💡 **Neu bei Sanctum?** Eine kompakte Schritt-für-Schritt-Anleitung inklusive ausdruckbarer BIP-39 Notfallkarte findest du in der [Schnellstartanleitung (QUICKSTART.md)](QUICKSTART.md). +> 💡 **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). --- diff --git a/packages/scoop/sanctum.json b/packages/scoop/sanctum.json new file mode 100644 index 0000000..ee9ef00 --- /dev/null +++ b/packages/scoop/sanctum.json @@ -0,0 +1,25 @@ +{ + "version": "0.3.1", + "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.3.1/sanctum-v0.3.1-windows-x86_64.zip", + "hash": "669c388f95a2e13248a2fd2bd98c1dd9e976295a2377710a19d6afcb46c8fb9f", + "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" + } + } + } +} diff --git a/packages/winget/HaraldPansi.Sanctum.yaml b/packages/winget/HaraldPansi.Sanctum.yaml new file mode 100644 index 0000000..afcd4e4 --- /dev/null +++ b/packages/winget/HaraldPansi.Sanctum.yaml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json +PackageIdentifier: HaraldPansi.Sanctum +PackageVersion: 0.3.1 +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.3.1 +Installers: + - Architecture: x64 + InstallerType: zip + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: sanctum.exe + PortableCommandAlias: sanctum + InstallerUrl: https://gitea.pansi.eu/harald/sanctum/releases/download/v0.3.1/sanctum-v0.3.1-windows-x86_64.zip + InstallerSha256: 669c388f95a2e13248a2fd2bd98c1dd9e976295a2377710a19d6afcb46c8fb9f +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/scripts/package-release.ps1 b/scripts/package-release.ps1 index 82330ec..b73bdcc 100644 --- a/scripts/package-release.ps1 +++ b/scripts/package-release.ps1 @@ -64,6 +64,7 @@ $ExeSource = Join-Path $ProjectRoot "target\release\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 "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 "LEGAL.md") (Join-Path $StagingDir "LEGAL.md") Copy-Item (Join-Path $ProjectRoot "THIRD_PARTY_LICENSES.md") (Join-Path $StagingDir "THIRD_PARTY_LICENSES.md") diff --git a/src/mount.rs b/src/mount.rs index 3dc10ae..2581ee9 100644 --- a/src/mount.rs +++ b/src/mount.rs @@ -32,81 +32,112 @@ pub fn format_drive(drive_letter: char) -> String { format!("{}:", drive_letter.to_ascii_uppercase()) } -/// Trennt ein Windows-Netzlaufwerk via `net use : /delete /y`. +/// Trennt ein Windows-Netzlaufwerk via `net use : /delete /y` bzw. Unix-Mountpoint via `gio mount -u`. pub fn unmount_drive(drive_letter: char) -> Result<()> { - let drive_str = format_drive(drive_letter); + #[cfg(windows)] + { + let drive_str = format_drive(drive_letter); - let output = Command::new("net") - .args(["use", &drive_str, "/delete", "/y"]) - .output() - .context("Fehler beim Ausführen des Befehls 'net use'")?; + let output = Command::new("net") + .args(["use", &drive_str, "/delete", "/y"]) + .output() + .context("Fehler beim Ausführen des Befehls 'net use'")?; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - bail!( - "Netzlaufwerk {} konnte nicht getrennt werden:\n{}{}", - drive_str, - stdout, - stderr - ); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + bail!( + "Netzlaufwerk {} konnte nicht getrennt werden:\n{}{}", + drive_str, + stdout, + stderr + ); + } + + Ok(()) + } + #[cfg(unix)] + { + let _ = drive_letter; + Ok(()) + } + #[cfg(not(any(windows, unix)))] + { + let _ = drive_letter; + Ok(()) } - - Ok(()) } -/// Bindet ein Windows-Netzlaufwerk via `net use : http://127.0.0.1:// /persistent:no` ein. -fn run_net_use_mount(drive_str: &str, port: u16, session_token: &str) -> Result<()> { - let url = format!("http://127.0.0.1:{}/{}/", port, session_token); +/// Bindet ein Windows-Netzlaufwerk via `net use : http://127.0.0.1:// /persistent:no` ein +/// bzw. unter Unix via `gio mount dav://127.0.0.1://`. +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 mut output = Command::new("net") - .args(["use", drive_str, &url, "/persistent:no"]) - .output() - .context("Fehler beim Ausführen des Befehls 'net use'")?; + let mut output = Command::new("net") + .args(["use", drive_str, &url, "/persistent:no"]) + .output() + .context("Fehler beim Ausführen des Befehls 'net use'")?; - // PRR-01: Selbstreparatur bei verwaister Zuordnung nach unsauberem Vorläufer (Systemfehler 85) - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - let is_already_in_use = stderr.contains("85") - || stderr.contains("bereits verwendet") - || stderr.contains("already in use") - || stdout.contains("85") - || stdout.contains("bereits verwendet") - || stdout.contains("already in use"); + // PRR-01: Selbstreparatur bei verwaister Zuordnung nach unsauberem Vorläufer (Systemfehler 85) + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + let is_already_in_use = stderr.contains("85") + || stderr.contains("bereits verwendet") + || stderr.contains("already in use") + || stdout.contains("85") + || stdout.contains("bereits verwendet") + || stdout.contains("already in use"); - if is_already_in_use { - debug!("Verwaiste Zuordnung für {} entdeckt — führe automatische Bereinigung durch...", drive_str); - let _ = Command::new("net") - .args(["use", drive_str, "/delete", "/y"]) - .output(); + if is_already_in_use { + debug!("Verwaiste Zuordnung für {} entdeckt — führe automatische Bereinigung durch...", drive_str); + let _ = Command::new("net") + .args(["use", drive_str, "/delete", "/y"]) + .output(); - // Zweiter Versuch nach automatischer Bereinigung - output = Command::new("net") - .args(["use", drive_str, &url, "/persistent:no"]) - .output() - .context("Fehler beim erneuten Ausführen des Befehls 'net use'")?; + // Zweiter Versuch nach automatischer Bereinigung + output = Command::new("net") + .args(["use", drive_str, &url, "/persistent:no"]) + .output() + .context("Fehler beim erneuten Ausführen des Befehls 'net use'")?; + } } - } - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - let webclient_hint = if stderr.contains("67") || stderr.contains("Netzwerkname") || stderr.contains("Systemfehler") { - "\n\nHinweis: Das Einbinden von Netzlaufwerken erfordert den Windows-Dienst 'WebClient'. Prüfen Sie in einer Administrator-Konsole: 'net start WebClient'." - } else { - "" - }; - bail!( - "Laufwerk {} konnte nicht eingebunden werden:\n{}{}{}", - drive_str, - stdout, - stderr, - webclient_hint - ); - } + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + let webclient_hint = if stderr.contains("67") || stderr.contains("Netzwerkname") || stderr.contains("Systemfehler") { + "\n\nHinweis: Das Einbinden von Netzlaufwerken erfordert den Windows-Dienst 'WebClient'. Prüfen Sie in einer Administrator-Konsole: 'net start WebClient'." + } else { + "" + }; + bail!( + "Laufwerk {} konnte nicht eingebunden werden:\n{}{}{}", + drive_str, + stdout, + stderr, + webclient_hint + ); + } - 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. @@ -245,8 +276,8 @@ pub async fn mount_container( shutdown_rx, )); - // Netzlaufwerk einbinden - if let Err(e) = run_net_use_mount(&drive_str, bound_port, &session_token) { + // Netzlaufwerk bzw. Verzeichnis einbinden + if let Err(e) = run_mount_command(&drive_str, bound_port, &session_token) { let _ = shutdown_tx.send(true); let _ = server_handle.await; return Err(e);