feat: add download/checksum and burn execution stub

This commit is contained in:
Brad Stein 2026-01-11 02:35:24 -03:00
parent b500241b8c
commit 910d01d22a
4 changed files with 154 additions and 4 deletions

View File

@ -72,17 +72,17 @@ func burnCmd(args []string) {
log.Fatalf("--node and --device are required")
}
inv := loadInventory(*invPath)
p, err := plan.Build(inv, *node, *device, *cache)
p, err := plan.Execute(inv, *node, *device, *cache, *confirm)
if err != nil {
log.Fatalf("build plan: %v", err)
log.Fatalf("burn: %v", err)
}
fmt.Printf("Plan for %s to %s:\n", p.Node, p.Device)
for _, a := range p.Actions {
fmt.Printf("- [%s] %s\n", a.Type, a.Detail)
}
if !*confirm {
fmt.Printf("\nDry run. Re-run with --yes to execute (not yet implemented).\n")
fmt.Printf("\nDry run. Re-run with --yes to execute.\n")
return
}
log.Fatalf("burn execution not yet implemented; follow plan commands manually")
fmt.Println("\nBurn complete. Safely eject and insert the SD into the node.")
}

79
pkg/image/download.go Normal file
View File

@ -0,0 +1,79 @@
package image
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
// Download fetches url into dest if dest does not exist.
func Download(url, dest string) error {
if _, err := os.Stat(dest); err == nil {
return nil
}
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
return err
}
if strings.HasPrefix(url, "file://") {
src := strings.TrimPrefix(url, "file://")
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("download failed: %s", resp.Status)
}
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
// VerifyChecksum checks sha256 in the form "sha256:<hex>".
func VerifyChecksum(path, checksum string) error {
if checksum == "" {
return nil
}
parts := strings.SplitN(checksum, ":", 2)
if len(parts) != 2 || parts[0] != "sha256" {
return errors.New("unsupported checksum format; use sha256:<hex>")
}
expected := strings.ToLower(parts[1])
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return err
}
sum := hex.EncodeToString(h.Sum(nil))
if sum != expected {
return fmt.Errorf("checksum mismatch: expected %s got %s", expected, sum)
}
return nil
}

47
pkg/plan/burn.go Normal file
View File

@ -0,0 +1,47 @@
package plan
import (
"fmt"
"os/exec"
"path/filepath"
"metis/pkg/image"
"metis/pkg/inventory"
)
// Execute performs a burn if confirm is true. With confirm=false, it only downloads/verifies and returns the plan.
func Execute(inv *inventory.Inventory, nodeName, device, cacheDir string, confirm bool) (*Plan, error) {
p, err := Build(inv, nodeName, device, cacheDir)
if err != nil {
return nil, err
}
cacheImage := filepath.Join(cacheDir, filepath.Base(p.Image))
if err := image.Download(p.Image, cacheImage); err != nil {
return p, fmt.Errorf("download image: %w", err)
}
if err := image.VerifyChecksum(cacheImage, checksumFromInventory(inv, nodeName)); err != nil {
return p, err
}
if !confirm {
return p, nil
}
if device == "" || device == "/dev/sdX" {
return p, fmt.Errorf("refusing to write to placeholder device")
}
ddCmd := []string{"dd", fmt.Sprintf("if=%s", cacheImage), fmt.Sprintf("of=%s", device), "bs=4M", "status=progress", "conv=fsync"}
cmd := exec.Command(ddCmd[0], ddCmd[1:]...)
cmd.Stdout = nil
cmd.Stderr = nil
if err := cmd.Run(); err != nil {
return p, fmt.Errorf("dd failed: %w", err)
}
return p, nil
}
func checksumFromInventory(inv *inventory.Inventory, node string) string {
_, cls, err := inv.FindNode(node)
if err != nil || cls == nil {
return ""
}
return cls.Checksum
}

24
pkg/util/run.go Normal file
View File

@ -0,0 +1,24 @@
package util
import (
"fmt"
"os/exec"
)
// Run executes a command and returns combined output or error.
func Run(cmd string, args ...string) error {
c := exec.Command(cmd, args...)
c.Stdout = nil
c.Stderr = nil
return c.Run()
}
// RunLogged returns stdout/stderr for logging while failing on error.
func RunLogged(cmd string, args ...string) (string, error) {
c := exec.Command(cmd, args...)
out, err := c.CombinedOutput()
if err != nil {
return string(out), fmt.Errorf("%s %v failed: %w: %s", cmd, args, err, string(out))
}
return string(out), nil
}