package host import ( "context" "errors" "github.com/carlosframework/platform/internal/hostbox" "log" "os" "sort" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/rootstate" ) var openClaimUnitBindings = func() (rootstate.Store, error) { return rootstate.Open("/var/lib/carlos-claims/unit-bindings", true, false) } var openClaimUnitMaterializations = func() (rootstate.Store, error) { return rootstate.Open("/var/lib/carlos-claims/unit-materializations", true, false) } var productionClaimUnitShapes = func() (map[string]claimUnitShape, error) { shapes, err := readClaimUnitShapes("/etc/carlos/claim-units.json") if errors.Is(err, os.ErrNotExist) { return map[string]claimUnitShape{}, nil } return shapes, err } // Called only in the census-gated root control startup. The root configuration // chooses unit placement; the existing root installation chooses its owner. func bindProductionClaimUnits(ctx context.Context, h *ClaimRootWithLaunch, a *ClaimAssembly) error { guard := h.Launch.Guard if guard.Root == nil || guard.Claims == nil { return os.ErrPermission } base, ok := h.Launch.Units.(instancectl.ClaimUnits) if !ok { return os.ErrPermission } shapes, configErr := productionClaimUnitShapes() if configErr != nil { log.Print(configErr) shapes = map[string]claimUnitShape{} } state, err := openClaimUnitBindings() if err != nil { return err } admit := func() error { if !claimNamedUnit4aComplete { return hostbox.ErrUnsupported } return nil } b := &claimUnitBindings{state: state, admit: admit} b.canonical = func(ctx context.Context, host string) (claimUnitShape, error) { current, err := productionClaimUnitShapes() if err != nil { return claimUnitShape{}, err } shape, ok := current[host] if !ok { return claimUnitShape{}, os.ErrNotExist } return shape, nil } b.installation = func(ctx context.Context, host string) (hostguard.RootInstallation, error) { captured, err := guard.Capture(host) if err != nil { return hostguard.RootInstallation{}, err } in, err := guard.Root.Installation(ctx, host, captured) if err != nil { return in, err } observed, err := guard.Claims.Read(ctx, host) if err != nil { return in, err } if observed.Absent || observed.Claim == nil || observed.Claim.Holder == nil { return in, os.ErrPermission } holder := observed.Claim.Holder if holder.Incarnation != in.Incarnation || holder.Stamp != in.Stamp || holder.Account != in.Account || holder.App != in.App || holder.Key != in.Record { return in, os.ErrPermission } return in, nil } materialState, err := openClaimUnitMaterializations() if err != nil { state.Close() return err } files := claimUnitFiles{dir: "/etc/systemd/system"} material := claimUnitMaterializer{admit: admit, idle: func(host string) error { binding, err := b.read(host) if err != nil { return err } unit := base unit.BoundHost, unit.BoundUnit = host, binding.Shape.Unit return unit.Gone(ctx, host, "") }, state: materialState, read: files.read, create: files.create, replace: files.replace} predecessor := claimPredecessorKernel{ctl: base.Systemctl, group: claimPredecessorGroup, boot: claimUnitBoot} material.predecessor, material.stopPredecessor = predecessor.capture, predecessor.stop material.socketCapture, material.socketPark = captureProductionClaimUnitSocket, parkProductionClaimUnitSocket b.handover = material.handover dispatch := claimUnitDispatch{base: base, bindings: b, shapes: productionClaimUnitShapes, effective: func(ctx context.Context, shape claimUnitShape) error { return claimEffectiveUnit(ctx, shape, base.Systemctl) }, definition: func(shape claimUnitShape) error { binding, err := b.read(shape.Host) if err != nil { return err } if binding.Shape != shape { return os.ErrPermission } return material.verify(binding) }} h.Launch.Units, h.Launch.unitBindings = dispatch, b h.Launch.unitProgram = readClaimUnitProgram h.Launch.boundReady = func(ctx context.Context, host string, started instancectl.ClaimUnitStarted) error { binding, err := b.read(host) if errors.Is(err, os.ErrNotExist) { return nil } if err != nil { return err } if binding.Shape.Unit != started.Unit { return os.ErrPermission } binding, err = b.check(ctx, host, nil) if err != nil { return err } return claimBoundUnitReady(ctx, started, binding.Shape) } h.Root.retireBoundUnit = func(ctx context.Context, target hostguard.FenceTarget) error { if err := retireClaimBoundUnit(ctx, target, b, material, dispatch.Gone); err != nil { return err } _, err := base.Systemctl(ctx, "daemon-reload") return err } kernel, ok := h.Root.Stop.Coordinator.Kernel.(claimUnitStopKernel) if !ok { state.Close() materialState.Close() return os.ErrPermission } kernel.Units = dispatch h.Root.Stop.Coordinator.Kernel = kernel if h.Root.executionRuntime != nil { h.Root.executionRuntime.root = &h.Root } closePrevious := a.Close a.Close = func(ctx context.Context) error { var err error if closePrevious != nil { err = closePrevious(ctx) } return errors.Join(err, state.Close(), materialState.Close()) } if h.Root.Reconcile.Retirement.JournalDir != "" { journals, e := hostbox.SavedRetirementJournals(h.Root.Reconcile.Retirement.JournalDir) if e != nil && !errors.Is(e, os.ErrNotExist) { log.Print(e) } for _, journal := range journals { if journal.Target != nil { if e := h.Root.retire(ctx, *journal.Target); e != nil { log.Print(e) } } } } hosts := make([]string, 0, len(shapes)) for host := range shapes { hosts = append(hosts, host) } sort.Strings(hosts) for _, host := range hosts { shape := shapes[host] // Named services must keep their operator definition until a reserved // boot/adoption controller can keep them running independently of edge. if !claimNamedUnit4aComplete || shape.Backing == "" { log.Print(hostbox.ErrUnsupported) continue } if !claimHexDigest(shape.Program) { log.Print(os.ErrPermission) continue } if _, e := readClaimUnitProgram(shape); e != nil { log.Print(e) continue } next, e := b.current(ctx, host) if e != nil { log.Print(e) continue } if e = guard.WithLocal(ctx, host, func() error { current, e := guard.Root.Installation(ctx, host, hostguard.Captured{Incarnation: next.Owner.Incarnation, Stamp: next.Owner.Stamp}) if e != nil { return e } if unitBindingOwner(current) != next.Owner { return os.ErrPermission } binding, e := b.establish(ctx, host, next) if e != nil { return e } if e = material.install(ctx, binding); e != nil { return e } if _, e = base.Systemctl(ctx, "daemon-reload"); e != nil { return e } if e = claimEffectiveUnit(ctx, binding.Shape, base.Systemctl); e != nil { return e } return projectClaimBoundSocket(binding.Shape) }); e != nil { log.Print(e) } } if len(hosts) != 0 { if _, e := base.Systemctl(ctx, "daemon-reload"); e != nil { log.Print(e) } } return nil }