Add boot image management with ISO extraction and serving
build-and-push / test (push) Successful in 34s
build-and-push / build-and-push (push) Successful in 1m7s

Upload Proxmox ISOs via API or dashboard UI, extract kernel+initrd
using pure-Go iso9660 library, store on disk, and serve over HTTP
for PXE booting. Dynamic kernel/initrd filenames per image replace
the previous hardcoded paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 21:26:31 -04:00
parent da2d72e95d
commit 4774600040
13 changed files with 486 additions and 20 deletions
+38 -6
View File
@@ -129,18 +129,50 @@ func imagesPage(images []model.Image) string {
for _, img := range images {
def := ""
if img.IsDefault {
def = "✓"
def = `<span class="badge state-green">default</span>`
} else {
def = fmt.Sprintf(`<form method="POST" action="/images/%d/default" class="inline"><button class="btn" style="font-size:0.75rem;padding:0.2rem 0.5rem">Set Default</button></form>`, img.ID)
}
rows.WriteString(fmt.Sprintf(`<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>`,
html.EscapeString(img.Name), img.Kind, img.Version, def, img.CreatedAt.Format("2006-01-02")))
deleteBtn := fmt.Sprintf(`<form method="POST" action="/images/%d/delete" class="inline" onsubmit="return confirm('Delete image %s?')"><button class="btn btn-danger" style="font-size:0.75rem;padding:0.2rem 0.5rem">Delete</button></form>`, img.ID, html.EscapeString(img.Name))
rows.WriteString(fmt.Sprintf(`<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>`,
html.EscapeString(img.Name), img.Kind, img.Version, def, img.CreatedAt.Format("2006-01-02"), deleteBtn))
}
if len(images) == 0 {
rows.WriteString(`<tr><td colspan="6" style="text-align:center;color:var(--text-muted)">No images uploaded yet.</td></tr>`)
}
return layout("Images", fmt.Sprintf(`
<h2>Boot Images</h2>
<div class="actions">
<a href="/images/new" class="btn">Upload Image</a>
<span class="count">%d images</span>
</div>
<table class="ops-table">
<thead><tr><th>Name</th><th>Kind</th><th>Version</th><th>Default</th><th>Added</th></tr></thead>
<thead><tr><th>Name</th><th>Kind</th><th>Version</th><th>Default</th><th>Added</th><th></th></tr></thead>
<tbody>%s</tbody>
</table>
`, rows.String()))
`, len(images), rows.String()))
}
func imageUploadForm(errMsg string) string {
errHTML := ""
if errMsg != "" {
errHTML = fmt.Sprintf(`<div class="error">%s</div>`, html.EscapeString(errMsg))
}
return layout("Upload Image", fmt.Sprintf(`
<h2>Upload Boot Image</h2>
%s
<form method="POST" action="/images/upload" enctype="multipart/form-data" class="form">
<label>Name<input type="text" name="name" placeholder="proxmox-8.2" required pattern="[a-z0-9][a-z0-9.\-]*"></label>
<label>Version<input type="text" name="version" placeholder="8.2-1" required></label>
<label>Kind
<select name="kind">
<option value="proxmox" selected>Proxmox VE</option>
</select>
</label>
<label>ISO File<input type="file" name="iso" accept=".iso" required></label>
<p style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.5rem">Upload may take several minutes for large ISOs. The kernel and initrd will be extracted automatically.</p>
<button type="submit" class="btn">Upload &amp; Extract</button>
</form>
`, errHTML))
}
func stateColor(s model.HostState) string {