slice 8A (controller half): bootstrap.json ingestion + pinned agent local-API client (v0.35.0)
internal/bootstrap: first-run bootstrap.json ingestion (decision (c)) — seed controller.yaml + skip setup; idempotent + fail-safe. internal/agentapi: minimal pinned local-API client (leaf-cert SHA-256 pin, fails closed). config LocalAPIConfig; startup /storage connectivity probe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,10 +18,12 @@ import (
|
||||
"crypto/subtle"
|
||||
"strings"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/api"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/appexport"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/assets"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/bootstrap"
|
||||
cf "gitea.dooplex.hu/admin/felhom-controller/internal/cloudflare"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/crypto"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
||||
@@ -73,6 +75,13 @@ func main() {
|
||||
|
||||
logger, logBuffer := setupLogger(cfg)
|
||||
|
||||
// --- Bootstrap ingestion (slice 8A, doc 03 §6) ---
|
||||
// On first run, if this controller is not yet configured AND the host agent's provisioning
|
||||
// back-half attached a bootstrap.json config mount, seed controller.yaml from it and come up
|
||||
// CONFIGURED — skipping setup mode. Idempotent (never clobbers an existing controller.yaml)
|
||||
// and fail-safe (a malformed/absent bootstrap leaves us in setup mode).
|
||||
cfg = bootstrap.MaybeIngest(*configPath, cfg, logger)
|
||||
|
||||
// --- Wire system package debug logging ---
|
||||
if cfg.Logging.Level == "debug" {
|
||||
system.DebugLogger = logger
|
||||
@@ -85,6 +94,12 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
// --- Local API connectivity probe (slice 8A) ---
|
||||
// When seeded with a local-API endpoint, prove the controller↔agent channel at startup and
|
||||
// learn this guest's mounts (placement view). Non-fatal — the controller runs regardless; a
|
||||
// failure is logged for diagnosis. The full /backup/due quiesce loop lands in 8B.
|
||||
probeLocalAPI(cfg, logger)
|
||||
|
||||
logger.Printf("[INFO] felhom-controller %s starting (customer: %s, domain: %s)",
|
||||
Version, cfg.Customer.ID, cfg.Customer.Domain)
|
||||
|
||||
@@ -1286,6 +1301,34 @@ func fileExists(path string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// probeLocalAPI proves the controller↔agent local-API channel at startup and logs this guest's
|
||||
// mounts (slice 8A). Non-fatal: it only runs when a local-API endpoint is configured, and any
|
||||
// error is logged for diagnosis without affecting the controller's boot. The leaf SHA-256 from
|
||||
// the bootstrap is pinned by the client (fails closed on mismatch).
|
||||
func probeLocalAPI(cfg *config.Config, logger *log.Logger) {
|
||||
if cfg.LocalAPI.Endpoint == "" || cfg.LocalAPI.Token == "" {
|
||||
return
|
||||
}
|
||||
client, err := agentapi.New(cfg.LocalAPI.Endpoint, cfg.LocalAPI.Token, cfg.LocalAPI.Fingerprint)
|
||||
if err != nil {
|
||||
logger.Printf("[WARN] local-api: client init failed (%v) — channel not verified", err)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.Storage(ctx)
|
||||
if err != nil {
|
||||
logger.Printf("[WARN] local-api: GET /storage failed (%v) — channel not verified", err)
|
||||
return
|
||||
}
|
||||
logger.Printf("[INFO] local-api: channel up (agent %s) — guest %d, %d mount(s) visible",
|
||||
cfg.LocalAPI.Endpoint, resp.VMID, len(resp.Mounts))
|
||||
for _, m := range resp.Mounts {
|
||||
logger.Printf("[INFO] local-api: mount %s → %s (storage=%s, class=%s, backup=%v)",
|
||||
m.Key, m.MountPoint, m.Storage, m.Class, m.Backup)
|
||||
}
|
||||
}
|
||||
|
||||
// runSetupMode starts the setup wizard on dual listeners and blocks until signal.
|
||||
func runSetupMode(cfg *config.Config, logger *log.Logger) {
|
||||
ips := setup.DetectLocalIPs()
|
||||
|
||||
Reference in New Issue
Block a user