banger/internal/model/types.go
Thales Maciel 0a0b0b617b
Replace mapdns with daemon DNS
Serve daemon-managed .vm names directly from bangerd on 127.0.0.1:42069 instead of shelling out to mapdns. This keeps DNS state tied to VM lifecycle and lets the daemon rebuild records from running VMs after startup or reconcile.

Add a small in-process authoritative DNS server, register and remove records from the VM start/stop/delete paths, and show the listener in daemon status. Remove the mapdns config and preflight surface, stop helper-flow DNS publishing in customize.sh and interactive.sh, drop dns.sh from the runtime bundle, and update docs/tests for the new local-resolver integration model.

Validated with GOCACHE=/tmp/banger-gocache go test ./..., GOCACHE=/tmp/banger-gocache make build, and bash -n customize.sh interactive.sh.
2026-03-17 15:49:35 -03:00

218 lines
6.2 KiB
Go

package model
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
const (
DefaultBridgeName = "br-fc"
DefaultBridgeIP = "172.16.0.1"
DefaultCIDR = "24"
DefaultDNS = "1.1.1.1"
DefaultSystemOverlaySize = 8 * 1024 * 1024 * 1024
DefaultWorkDiskSize = 8 * 1024 * 1024 * 1024
DefaultMemoryMiB = 1024
DefaultVCPUCount = 2
DefaultStatsPollInterval = 10 * time.Second
DefaultStaleSweepInterval = 1 * time.Minute
DefaultMetricsPollInterval = 15 * time.Second
MaxDiskBytes int64 = 128 * 1024 * 1024 * 1024
)
type VMState string
const (
VMStateCreated VMState = "created"
VMStateRunning VMState = "running"
VMStateStopped VMState = "stopped"
VMStateError VMState = "error"
)
type DaemonConfig struct {
RuntimeDir string
LogLevel string
FirecrackerBin string
SSHKeyPath string
NamegenPath string
CustomizeScript string
AutoStopStaleAfter time.Duration
StatsPollInterval time.Duration
MetricsPollInterval time.Duration
BridgeName string
BridgeIP string
CIDR string
DefaultDNS string
DefaultImageName string
DefaultRootfs string
DefaultBaseRootfs string
DefaultKernel string
DefaultInitrd string
DefaultModulesDir string
DefaultPackagesFile string
}
type Image struct {
ID string `json:"id"`
Name string `json:"name"`
Managed bool `json:"managed"`
ArtifactDir string `json:"artifact_dir,omitempty"`
RootfsPath string `json:"rootfs_path"`
KernelPath string `json:"kernel_path"`
InitrdPath string `json:"initrd_path,omitempty"`
ModulesDir string `json:"modules_dir,omitempty"`
PackagesPath string `json:"packages_path,omitempty"`
BuildSize string `json:"build_size,omitempty"`
Docker bool `json:"docker"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type VMSpec struct {
VCPUCount int `json:"vcpu_count"`
MemoryMiB int `json:"memory_mib"`
SystemOverlaySizeByte int64 `json:"system_overlay_size_bytes"`
WorkDiskSizeBytes int64 `json:"work_disk_size_bytes"`
NATEnabled bool `json:"nat_enabled"`
}
type VMRuntime struct {
State VMState `json:"state"`
PID int `json:"pid,omitempty"`
GuestIP string `json:"guest_ip"`
TapDevice string `json:"tap_device,omitempty"`
APISockPath string `json:"api_sock_path,omitempty"`
LogPath string `json:"log_path,omitempty"`
MetricsPath string `json:"metrics_path,omitempty"`
DNSName string `json:"dns_name,omitempty"`
VMDir string `json:"vm_dir"`
SystemOverlay string `json:"system_overlay_path"`
WorkDiskPath string `json:"work_disk_path"`
BaseLoop string `json:"base_loop,omitempty"`
COWLoop string `json:"cow_loop,omitempty"`
DMName string `json:"dm_name,omitempty"`
DMDev string `json:"dm_dev,omitempty"`
LastError string `json:"last_error,omitempty"`
}
type VMStats struct {
CollectedAt time.Time `json:"collected_at,omitempty"`
CPUPercent float64 `json:"cpu_percent,omitempty"`
RSSBytes int64 `json:"rss_bytes,omitempty"`
VSZBytes int64 `json:"vsz_bytes,omitempty"`
SystemOverlayBytes int64 `json:"system_overlay_bytes,omitempty"`
WorkDiskBytes int64 `json:"work_disk_bytes,omitempty"`
MetricsRaw map[string]any `json:"metrics_raw,omitempty"`
}
type VMRecord struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
State VMState `json:"state"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastTouchedAt time.Time `json:"last_touched_at"`
Spec VMSpec `json:"spec"`
Runtime VMRuntime `json:"runtime"`
Stats VMStats `json:"stats"`
}
type VMCreateRequest struct {
Name string
ImageName string
VCPUCount int
MemoryMiB int
SystemOverlaySizeByte int64
WorkDiskSizeBytes int64
NATEnabled bool
NoStart bool
}
type VMSetRequest struct {
IDOrName string
VCPUCount *int
MemoryMiB *int
WorkDiskSizeBytes *int64
NATEnabled *bool
}
type ImageBuildRequest struct {
Name string
BaseRootfs string
Size string
KernelPath string
InitrdPath string
ModulesDir string
Docker bool
}
func Now() time.Time {
return time.Now().UTC().Truncate(time.Second)
}
func NewID() (string, error) {
buf := make([]byte, 32)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return hex.EncodeToString(buf), nil
}
func ParseSize(raw string) (int64, error) {
if raw == "" {
return 0, errors.New("size is required")
}
raw = strings.TrimSpace(strings.ToUpper(raw))
if raw == "" {
return 0, errors.New("size is required")
}
unit := raw[len(raw)-1]
multiplier := int64(1024 * 1024)
number := raw
switch unit {
case 'K':
multiplier = 1024
number = raw[:len(raw)-1]
case 'M':
multiplier = 1024 * 1024
number = raw[:len(raw)-1]
case 'G':
multiplier = 1024 * 1024 * 1024
number = raw[:len(raw)-1]
default:
if unit < '0' || unit > '9' {
return 0, fmt.Errorf("unsupported size suffix: %q", string(unit))
}
}
value, err := strconv.ParseInt(number, 10, 64)
if err != nil {
return 0, fmt.Errorf("parse size %q: %w", raw, err)
}
result := value * multiplier
if result <= 0 {
return 0, fmt.Errorf("size must be positive: %q", raw)
}
if result > MaxDiskBytes {
return 0, fmt.Errorf("size exceeds max of %d bytes", MaxDiskBytes)
}
return result, nil
}
func FormatSizeBytes(bytes int64) string {
switch {
case bytes%(1024*1024*1024) == 0:
return fmt.Sprintf("%dG", bytes/(1024*1024*1024))
case bytes%(1024*1024) == 0:
return fmt.Sprintf("%dM", bytes/(1024*1024))
case bytes%1024 == 0:
return fmt.Sprintf("%dK", bytes/1024)
default:
return strconv.FormatInt(bytes, 10)
}
}