v0.8.1: add ISO by URL, zero-touch admin bootstrap
Ease-of-use pass inspired by Bootimus (Dnsmasq-PXE is a manual dnsmasq setup guide — nothing to adopt; OpenPXE already replaces that stack). Add ISO by URL: - New http-api `fetch` module: a small FetchJobs registry + a background streaming download (reqwest) that pipes a remote .iso through the same UploadHandle + introspection path as an upload, so a URL-fetched image classifies and gains boot entries identically. Progress is polled by the Storage view and rendered as rows, mirroring uploads. - Routes POST/GET/DELETE /api/isos/fetch. http/https only; .iso-only filename derived from Content-Disposition / URL basename with path traversal stripped; 16 GiB cap; cancel; credential-stripped URL display. Operator-gated, no boot-time outbound — offline boot is untouched. - Storage upload card gains an "Or add by URL" field with progress + cancel. Zero-touch admin bootstrap: - OPENPXE_ADMIN_USERNAME + OPENPXE_ADMIN_PASSWORD (or _PASSWORD_FILE for Docker/K8s secrets) auto-create the admin on first run, so a fresh container is usable with no setup wizard. Seeds the first run only — a lingering env var can't reset a rotated password. Tests: URL parse / filename / Content-Disposition unit tests + a wiremock end-to-end fetch-into-store integration test. clippy/fmt/node clean. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1c262a6d61
commit
5f98e6e03f
@@ -120,6 +120,38 @@ async fn main() -> anyhow::Result<()> {
|
||||
let boot_log = openpxe_core::BootLog::load_or_default(&config.paths.work_dir);
|
||||
let branding = openpxe_core::BrandingStore::load_or_default(&config.paths.work_dir);
|
||||
let admin = openpxe_core::AdminStore::load_or_default(&config.paths.work_dir);
|
||||
// v0.8.1: zero-touch first-run bootstrap. If no admin exists yet and the
|
||||
// operator supplied OPENPXE_ADMIN_USERNAME + OPENPXE_ADMIN_PASSWORD (or
|
||||
// …_PASSWORD_FILE, for Docker/K8s secrets), create the admin now so a
|
||||
// fresh container is usable without the web setup wizard. Seeds the
|
||||
// first run only — once an admin exists (including one made in the UI)
|
||||
// this is a no-op, so a lingering env var can't reset a rotated password.
|
||||
if !admin.is_configured() {
|
||||
if let Ok(username) = std::env::var("OPENPXE_ADMIN_USERNAME") {
|
||||
let password = std::env::var("OPENPXE_ADMIN_PASSWORD_FILE")
|
||||
.ok()
|
||||
.and_then(|p| std::fs::read_to_string(p).ok())
|
||||
.map(|s| s.trim_end_matches(['\n', '\r']).to_string())
|
||||
.or_else(|| std::env::var("OPENPXE_ADMIN_PASSWORD").ok());
|
||||
if let Some(password) = password {
|
||||
match admin.bootstrap(&username, &password) {
|
||||
Ok(p) => tracing::info!(
|
||||
target: "openpxe::auth", username = %p.username,
|
||||
"admin bootstrapped from environment"
|
||||
),
|
||||
Err(e) => tracing::warn!(
|
||||
target: "openpxe::auth",
|
||||
"env admin bootstrap failed ({e}); use the web setup wizard"
|
||||
),
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: "openpxe::auth",
|
||||
"OPENPXE_ADMIN_USERNAME set without OPENPXE_ADMIN_PASSWORD[_FILE]; skipping bootstrap"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let sso = openpxe_core::SsoStore::load_or_default(&config.paths.work_dir);
|
||||
let notify = openpxe_core::NotifyStore::load_or_default(&config.paths.work_dir);
|
||||
let api_key = openpxe_core::ApiKeyStore::load_or_init(&config.paths.work_dir);
|
||||
@@ -213,6 +245,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
sftp_shares: sftp_shares.clone(),
|
||||
unattended: unattended.clone(),
|
||||
uploads: openpxe_http_api::uploads::UploadSessions::default(),
|
||||
fetch_jobs: openpxe_http_api::fetch::FetchJobs::default(),
|
||||
log_bus: log_bus.clone(),
|
||||
started_at: time::OffsetDateTime::now_utc(),
|
||||
public_base_url: public_base_url.clone(),
|
||||
|
||||
Reference in New Issue
Block a user