221 lines
4.9 KiB
Go
221 lines
4.9 KiB
Go
package pxe
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/wild-cloud/wild-central/daemon/internal/storage"
|
|
)
|
|
|
|
// Manager handles PXE boot asset management
|
|
type Manager struct {
|
|
dataDir string
|
|
}
|
|
|
|
// NewManager creates a new PXE manager
|
|
func NewManager(dataDir string) *Manager {
|
|
return &Manager{
|
|
dataDir: dataDir,
|
|
}
|
|
}
|
|
|
|
// Asset represents a PXE boot asset
|
|
type Asset struct {
|
|
Type string `json:"type"` // kernel, initramfs, iso
|
|
Version string `json:"version"`
|
|
Path string `json:"path"`
|
|
Size int64 `json:"size"`
|
|
SHA256 string `json:"sha256,omitempty"`
|
|
Downloaded bool `json:"downloaded"`
|
|
}
|
|
|
|
// GetPXEDir returns the PXE directory for an instance
|
|
func (m *Manager) GetPXEDir(instanceName string) string {
|
|
return filepath.Join(m.dataDir, "instances", instanceName, "pxe")
|
|
}
|
|
|
|
// ListAssets returns available PXE assets for an instance
|
|
func (m *Manager) ListAssets(instanceName string) ([]Asset, error) {
|
|
pxeDir := m.GetPXEDir(instanceName)
|
|
|
|
// Ensure PXE directory exists
|
|
if err := storage.EnsureDir(pxeDir, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
assets := []Asset{}
|
|
|
|
// Check for common assets
|
|
assetTypes := []struct {
|
|
name string
|
|
path string
|
|
}{
|
|
{"kernel", "kernel"},
|
|
{"initramfs", "initramfs.xz"},
|
|
{"iso", "talos.iso"},
|
|
}
|
|
|
|
for _, at := range assetTypes {
|
|
assetPath := filepath.Join(pxeDir, at.path)
|
|
info, err := os.Stat(assetPath)
|
|
|
|
asset := Asset{
|
|
Type: at.name,
|
|
Path: assetPath,
|
|
Downloaded: err == nil,
|
|
}
|
|
|
|
if err == nil {
|
|
asset.Size = info.Size()
|
|
// Calculate SHA256 if file exists
|
|
if hash, err := calculateSHA256(assetPath); err == nil {
|
|
asset.SHA256 = hash
|
|
}
|
|
}
|
|
|
|
assets = append(assets, asset)
|
|
}
|
|
|
|
return assets, nil
|
|
}
|
|
|
|
// DownloadAsset downloads a PXE asset
|
|
func (m *Manager) DownloadAsset(instanceName, assetType, version, url string) error {
|
|
pxeDir := m.GetPXEDir(instanceName)
|
|
|
|
// Ensure PXE directory exists
|
|
if err := storage.EnsureDir(pxeDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Determine filename based on asset type
|
|
var filename string
|
|
switch assetType {
|
|
case "kernel":
|
|
filename = "kernel"
|
|
case "initramfs":
|
|
filename = "initramfs.xz"
|
|
case "iso":
|
|
filename = "talos.iso"
|
|
default:
|
|
return fmt.Errorf("unknown asset type: %s", assetType)
|
|
}
|
|
|
|
assetPath := filepath.Join(pxeDir, filename)
|
|
|
|
// Check if asset already exists (idempotency)
|
|
if storage.FileExists(assetPath) {
|
|
return nil // Already downloaded
|
|
}
|
|
|
|
// Download file
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to download %s: %w", url, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("failed to download %s: status %d", url, resp.StatusCode)
|
|
}
|
|
|
|
// Create temporary file
|
|
tmpFile := assetPath + ".tmp"
|
|
out, err := os.Create(tmpFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create file: %w", err)
|
|
}
|
|
defer out.Close()
|
|
|
|
// Copy data
|
|
_, err = io.Copy(out, resp.Body)
|
|
if err != nil {
|
|
os.Remove(tmpFile)
|
|
return fmt.Errorf("failed to write file: %w", err)
|
|
}
|
|
|
|
// Move to final location
|
|
if err := os.Rename(tmpFile, assetPath); err != nil {
|
|
os.Remove(tmpFile)
|
|
return fmt.Errorf("failed to move file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAssetPath returns the local path for an asset
|
|
func (m *Manager) GetAssetPath(instanceName, assetType string) (string, error) {
|
|
pxeDir := m.GetPXEDir(instanceName)
|
|
|
|
var filename string
|
|
switch assetType {
|
|
case "kernel":
|
|
filename = "kernel"
|
|
case "initramfs":
|
|
filename = "initramfs.xz"
|
|
case "iso":
|
|
filename = "talos.iso"
|
|
default:
|
|
return "", fmt.Errorf("unknown asset type: %s", assetType)
|
|
}
|
|
|
|
assetPath := filepath.Join(pxeDir, filename)
|
|
|
|
if !storage.FileExists(assetPath) {
|
|
return "", fmt.Errorf("asset %s not found", assetType)
|
|
}
|
|
|
|
return assetPath, nil
|
|
}
|
|
|
|
// VerifyAsset checks if an asset exists and is valid
|
|
func (m *Manager) VerifyAsset(instanceName, assetType string) (bool, error) {
|
|
assetPath, err := m.GetAssetPath(instanceName, assetType)
|
|
if err != nil {
|
|
return false, nil // Asset doesn't exist, but that's not an error for verification
|
|
}
|
|
|
|
// Check if file is readable
|
|
info, err := os.Stat(assetPath)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Check if file has size
|
|
if info.Size() == 0 {
|
|
return false, fmt.Errorf("asset %s is empty", assetType)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// DeleteAsset removes an asset
|
|
func (m *Manager) DeleteAsset(instanceName, assetType string) error {
|
|
assetPath, err := m.GetAssetPath(instanceName, assetType)
|
|
if err != nil {
|
|
return nil // Asset doesn't exist, idempotent
|
|
}
|
|
|
|
return os.Remove(assetPath)
|
|
}
|
|
|
|
// calculateSHA256 computes the SHA256 hash of a file
|
|
func calculateSHA256(filePath string) (string, error) {
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
hash := sha256.New()
|
|
if _, err := io.Copy(hash, file); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
|
}
|