package host import ( "context" "errors" "reflect" "time" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/release" ) // Only a checked root journal reaches this collector. The legacy importer still // requires its independently signed inventory proof before writing that journal. // Offline migration observes closed, settled writers; it never kills from a // legacy candidate or treats reboot alone as producer completion. type migrationStopKernel struct { StopKernelVerifier launches *hostbox.LaunchJournal inventory *hostbox.StopInventoryDB roots map[string]string } func (k migrationStopKernel) RecoverStopped(ctx context.Context, target hostguard.FenceTarget, inventory StopInventory) (StopKernelEvidence, DrainAcknowledgement, error) { fail := func(err error) (StopKernelEvidence, DrainAcknowledgement, error) { return StopKernelEvidence{}, DrainAcknowledgement{}, err } launch, err := k.launches.Read(ctx, target) if err != nil { return fail(err) } before, err := k.launches.ReadStopKernelJournal(ctx, target) if err != nil { return fail(err) } boot, err := recoveryControllerAt("/proc", target, before) if err != nil { return fail(err) } if err := verifyMigrationStopped(ctx, k.launches, k.inventory, launch, k.roots); err != nil { return fail(err) } actual, err := k.inventory.ReadExactStopInventory(ctx, target) if err != nil { return fail(err) } if !reflect.DeepEqual(actual, inventory) { return fail(ErrClaimStopInventory) } var groups []string if boot == target.BootID { groups, err = scanStopCgroups(ctx, "/sys/fs/cgroup", before.Cgroups) } else { groups, err = scanRebootCgroups(ctx, "/sys/fs/cgroup", before.Cgroups, target.BootID, boot) } if err != nil { return fail(err) } after, err := k.launches.ReadStopKernelJournal(ctx, target) if err != nil { return fail(err) } if !reflect.DeepEqual(before, after) { return fail(ErrClaimStopInventory) } ack := DrainClaimAcknowledgement(target.FenceID, target.ControllerID, before.ProcessStart, before.ProducerIDs, before.Completed) return StopKernelEvidence{ObservedBootID: boot, InventorySHA256: digestStop(actual), LocalJournalSHA256: digestStop(after), CgroupIdentities: groups}, ack, nil } func recoverMigrationStop(ctx context.Context, c ClaimStopCoordinator, store release.Store, directory hostactors.Directory, launches *hostbox.LaunchJournal, inventory *hostbox.StopInventoryDB, launch hostbox.LaunchInventory, claimETag string, roots map[string]string) (StopResult, error) { result, err := readMigrationStop(ctx, store, directory, launches, inventory, launch) fresh := errors.Is(err, hostbox.ErrLegacyJournalProof) if err != nil && !fresh { return StopResult{}, err } target := result.Target if fresh { target = launch.Target target.FenceID = c.NewID() } else if err := verifyMigrationStopped(ctx, launches, inventory, launch, roots); err != nil { return StopResult{}, err } target.ClaimETag = claimETag target, err = c.Guard.BeginFence(ctx, target) if err != nil { return StopResult{}, err } if receipt, err := c.Guard.FenceReceiptFor(ctx, target); err == nil { return StopResult{Target: target, Bytes: receipt.Bytes, EvidenceAt: receipt.EvidenceAt}, nil } else if !errors.Is(err, hostguard.ErrNoFenceReceipt) { return StopResult{}, err } if fresh { c.Kernel = migrationStopKernel{launches: launches, inventory: inventory, roots: roots} return c.RecoverStopped(ctx, target) } if err := c.Guard.CompleteFence(ctx, target, result.Bytes, result.EvidenceAt, func(context.Context, hostguard.FenceTarget, []byte, time.Time) error { return nil }); err != nil { return StopResult{}, err } result.Target = target return result, nil }