From a12755522fd514d9a44ff49f7349257033721cf4 Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 3 May 2026 21:39:20 -0400 Subject: [PATCH] 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 --- .gitignore | 4 +- cmd/provisioning/main.go | 173 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 cmd/provisioning/main.go diff --git a/.gitignore b/.gitignore index 1cf3019..2b01037 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Binary -provisioning -provisioning.exe +/provisioning +/provisioning.exe # Runtime data data/ diff --git a/cmd/provisioning/main.go b/cmd/provisioning/main.go new file mode 100644 index 0000000..195debf --- /dev/null +++ b/cmd/provisioning/main.go @@ -0,0 +1,173 @@ +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) +}