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
@@ -94,6 +94,17 @@ pub fn build_router(state: AppState) -> Router {
|
||||
// JSON API.
|
||||
.route("/api/isos", get(api_list_isos).post(api_upload_iso))
|
||||
.route("/api/isos/{id}", delete(api_delete_iso))
|
||||
// v0.8.1: add ISO by URL — server-side streaming download + progress
|
||||
// polling. Static `fetch` coexists with `{id}` above (matchit
|
||||
// prioritizes the literal), same as `/api/queue/join` vs `{entry_id}`.
|
||||
.route(
|
||||
"/api/isos/fetch",
|
||||
get(crate::fetch::api_iso_fetch_list).post(crate::fetch::api_iso_fetch_start),
|
||||
)
|
||||
.route(
|
||||
"/api/isos/fetch/{id}",
|
||||
delete(crate::fetch::api_iso_fetch_cancel),
|
||||
)
|
||||
.route("/api/uploads", post(api_upload_begin))
|
||||
.route(
|
||||
"/api/uploads/{upload_id}",
|
||||
@@ -1857,6 +1868,12 @@ async fn api_docs() -> Json<serde_json::Value> {
|
||||
"summary": "Clear an ISO's boot password."},
|
||||
{"method": "PUT", "path": "/api/isos/{id}/category",
|
||||
"summary": "Set the menu category. Body: { \"category\": \"os\" | \"tools\" }."},
|
||||
{"method": "POST", "path": "/api/isos/fetch",
|
||||
"summary": "Add an ISO by URL. Body: { \"url\", \"filename\"? }. The server streams the .iso into storage and introspects it. Returns { fetch_id }."},
|
||||
{"method": "GET", "path": "/api/isos/fetch",
|
||||
"summary": "Poll URL-fetch progress — [{ id, filename, url, downloaded, total, state }]. Completed jobs are returned once."},
|
||||
{"method": "DELETE", "path": "/api/isos/fetch/{id}",
|
||||
"summary": "Cancel an in-flight URL fetch, or dismiss a finished/failed one."},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user