v0.5.5: SFTP-over-SSH remote shares (russh, pure-Rust, ring backend)

Adds SFTP as a third remote ISO-library protocol alongside SMB and NFS.
Pure-Rust russh + russh-sftp on the ring crypto backend — no kernel
mount, no subprocess, no OpenSSL, no new C deps. Like NFS (and unlike
SMB), SFTP-sourced ISOs support HTTP Range requests because SFTP opens
a seekable file handle.

- iso-store: SftpShareManager (connect/auth/READDIR/seekable stream),
  IsoSource::Sftp, password OR SSH-key auth, trust-on-first-use host-key
  pinning, 0600 credential sidecar with a restart-safe derived path.
- http-api: /api/sftp-shares routes, Range-aware ISO dispatch arm,
  status/metrics counts, /api/docs entry, `sftp` terminal commands.
- webui: "SFTP (SSH)" protocol option with a password/key auth toggle,
  host-key fingerprint display, dashboard tile, updated copy.

SCP was deliberately rejected: sequential-only (no Range) and its crates
wrap libssh2 (C + OpenSSL), which would break the static-musl build.

russh is pinned to =0.55.0: russh 0.61 needs the stable RustCrypto
generation (pkcs8 0.11), which is API-incompatible with the release-
candidate crates bergshamra-crypto pins (pkcs8 =0.11.0-rc.11). 0.55 is
the newest russh on the prior generation (pkcs8 0.7) that coexists. Do
not bump past 0.55 until bergshamra adopts stable RustCrypto.

252 tests pass, clippy clean, static musl x86_64 binary (ring already
present via rustls + bergshamra, so no new crypto/C deps).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Miles Ward
2026-06-03 11:49:18 -04:00
co-authored by Claude Opus 4.8
parent 674a69f93b
commit 44a2212abe
12 changed files with 2247 additions and 76 deletions
+16 -3
View File
@@ -9,7 +9,7 @@ use openpxe_core::{
};
use openpxe_dhcp_proxy::DhcpProxyServer;
use openpxe_http_api::{build_router, AppState};
use openpxe_iso_store::{IsoStore, NfsShareManager, SmbManager, SmbShareManager};
use openpxe_iso_store::{IsoStore, NfsShareManager, SftpShareManager, SmbManager, SmbShareManager};
use openpxe_tftp::TftpServer;
use std::net::{Ipv4Addr, SocketAddr};
use std::path::PathBuf;
@@ -100,8 +100,7 @@ async fn main() -> anyhow::Result<()> {
iso_store.load_from_disk().await?;
// v0.5.2: unattended answer-file store (Kickstart/Preseed/Autoinstall/
// Windows answer files). Separate directory from the ISO store.
let unattended =
openpxe_iso_store::UnattendedStore::new(config.paths.unattended_dir.clone());
let unattended = openpxe_iso_store::UnattendedStore::new(config.paths.unattended_dir.clone());
if let Err(e) = unattended.load_from_disk().await {
tracing::warn!(
target: "openpxe::unattended",
@@ -155,6 +154,19 @@ async fn main() -> anyhow::Result<()> {
);
}
// v0.5.5: SFTP-over-SSH share manager — pure-Rust in-process
// consumer via `russh` + `russh-sftp` (ring backend, no OpenSSL).
// The third remote-library protocol alongside SMB/NFS; like NFS it
// works in any container (no subprocess, no kernel mount) and
// supports HTTP Range requests because SFTP file handles seek.
let sftp_shares = SftpShareManager::new(&config.paths.work_dir, iso_store.clone());
if let Err(e) = sftp_shares.load_and_rescan().await {
tracing::warn!(
target: "openpxe::sftp",
"could not reload SFTP shares on startup: {e}"
);
}
// Sniff network details for the Network tab. None of these are
// required for PXE to work — they're informational, surfaced in the
// UI so an operator doesn't have to drop to a shell to find their
@@ -183,6 +195,7 @@ async fn main() -> anyhow::Result<()> {
smb: Some(smb.clone()),
smb_shares: smb_shares.clone(),
nfs_shares: nfs_shares.clone(),
sftp_shares: sftp_shares.clone(),
unattended: unattended.clone(),
uploads: openpxe_http_api::uploads::UploadSessions::default(),
log_bus: log_bus.clone(),