Files
Provisioning/cmd/provisioning/main.go
T
josh a12755522f
build-and-push / build-and-push (push) Has been cancelled
build-and-push / test (push) Has been cancelled
Fix .gitignore excluding cmd/provisioning directory
The pattern `provisioning` matched both the binary and the directory.
Use `/provisioning` to only match at the repo root.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 21:39:20 -04:00

174 lines
3.9 KiB
Go

package main
import (
"context"
"flag"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"provisioning/internal/api"
"provisioning/internal/config"
"provisioning/internal/db"
"provisioning/internal/events"
"provisioning/internal/httpserver"
"provisioning/internal/infra"
"provisioning/internal/orchestrator"
"provisioning/internal/pxe"
"provisioning/internal/store"
)
func main() {
cfgPath := flag.String("config", "provisioning.yaml", "path to config file")
flag.Parse()
cfg, err := config.Load(*cfgPath)
if err != nil {
log.Fatalf("config: %v", err)
}
serverTypes, err := config.LoadServerTypes(cfg.ServerTypePath)
if err != nil {
log.Fatalf("server types: %v", err)
}
if err := os.MkdirAll(filepath.Dir(cfg.Database.Path), 0o755); err != nil {
log.Fatalf("create db dir: %v", err)
}
database, err := db.Open(cfg.Database.Path)
if err != nil {
log.Fatalf("database: %v", err)
}
defer database.Close()
hosts := &store.Hosts{DB: database}
ops := &store.Operations{DB: database}
locks := &store.Locks{DB: database, TTLMinutes: cfg.Locks.TTLMinutes}
images := &store.Images{DB: database}
hub := events.NewHub()
runner := &orchestrator.Runner{
Hosts: hosts,
Ops: ops,
Locks: locks,
Hub: hub,
}
pxeSupervisor := pxe.NewSupervisor(pxe.SupervisorConfig{
Enabled: cfg.PXE.Enabled,
Interface: cfg.PXE.Interface,
Subnet: cfg.PXE.Subnet,
RuntimeDir: cfg.PXE.RuntimeDir,
TFTPRoot: cfg.PXE.TFTPRoot,
DnsmasqBin: cfg.PXE.DnsmasqBin,
PublicURL: cfg.Server.PublicURL,
})
var infraClient *infra.Client
if cfg.Infrastructure.BaseURL != "" {
infraClient = infra.NewClient(cfg.Infrastructure.BaseURL, time.Duration(cfg.Infrastructure.TimeoutSec)*time.Second)
}
clusterJoiner := &orchestrator.ClusterJoiner{
ExistingNode: cfg.Proxmox.ExistingNode,
ClusterName: cfg.Proxmox.ClusterName,
JoinFingerprint: cfg.Proxmox.JoinFingerprint,
}
hostOrch := &orchestrator.HostOrchestrator{
Runner: runner,
Hosts: hosts,
Ops: ops,
Locks: locks,
Cluster: clusterJoiner,
InfraClient: infraClient,
Config: cfg,
ServerTypes: serverTypes,
}
hostAPI := &api.HostAPI{
Hosts: hosts,
Ops: ops,
Locks: locks,
Images: images,
Runner: runner,
Orchestrator: hostOrch,
PXE: pxeSupervisor,
Config: cfg,
ServerTypes: serverTypes,
}
bootAPI := &api.BootAPI{
Hosts: hosts,
Images: images,
Runner: runner,
Orchestrator: hostOrch,
Config: cfg,
ServerTypes: serverTypes,
}
ui := &api.UI{
Hosts: hosts,
Ops: ops,
Locks: locks,
Images: images,
Runner: runner,
Orchestrator: hostOrch,
Hub: hub,
PXE: pxeSupervisor,
Config: cfg,
ServerTypes: serverTypes,
}
router := httpserver.NewRouter(httpserver.Deps{
HostAPI: hostAPI,
BootAPI: bootAPI,
UI: ui,
Hub: hub,
})
srv := &http.Server{
Addr: cfg.Server.Bind,
Handler: router,
}
// Start PXE
allHosts, _ := hosts.List(context.Background())
if err := pxeSupervisor.Start(context.Background(), allHosts); err != nil {
log.Printf("pxe: failed to start: %v", err)
}
// Watch server types for hot reload
stop := make(chan struct{})
serverTypes.Watch(stop)
// Start HTTP server
go func() {
log.Printf("listening on %s", cfg.Server.Bind)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("http: %v", err)
}
}()
// Graceful shutdown
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
<-shutdown
log.Printf("shutting down")
close(stop)
_ = pxeSupervisor.Shutdown()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("http shutdown: %v", err)
}
_ = hub.Shutdown(ctx)
}