package host import ( "context" "errors" "flag" "log" "os" "os/signal" "path/filepath" "syscall" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/release" ) // checkClaimJournalMigration is root startup's only contact with the legacy // claim journals under the agent's data directory. It never imports and never // quiesces: legacy files the agent changes after the marker are reported and // ignored, so they cannot stop root's control plane. func checkClaimJournalMigration(ctx context.Context, journals *hostbox.JournalService, local string) error { alert, err := journals.CheckLegacyMigration(ctx, local) if alert != nil { log.Printf("claim journal migration: %v", alert) } return err } // RunSystemJournalMigrate is the offline operator step that imports legacy // claim journals into root's journal: carlos system journal-migrate, run as // root with the edge and the agent stopped. SIGTERM and interrupt cancel the // import, so the edge's runtime mask is lifted on the way out. func RunSystemJournalMigrate(args []string) { fs := flag.NewFlagSet("system journal-migrate", flag.ExitOnError) window := fs.String("window", "", journalMigrateWindowFlag) _ = fs.Parse(args) if os.Geteuid() != 0 || fs.NArg() != 0 { log.Fatal(hostwrite.ErrUnavailable) } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) defer stop() ctx, err := claimEntryContext(ctx, os.Getenv, openDeployStore) if err != nil { log.Fatal(err) } if !hostwrite.Enabled(claimEntryEnv(ctx)) { log.Fatal(hostwrite.ErrUnavailable) } reg, err := openServiceRegistry(filepath.Join(dataDir(), "registry.db")) if err != nil { log.Fatal(err) } defer reg.Close() provider := productionClaimOwners() if provider == nil { provider = ProductionClaimOwnerProvider{} } r := ClaimRuntimeResources{Mode: "instance-control", Registry: reg, DataDir: dataDir(), OptDir: release.OptDir(), UnitsDir: "/etc/systemd/system", MigrationWindow: *window} // The window is checked before anything is written; the census checks // its masks again when it walks the unit directory. if _, err = verifyJournalMigrateWindow(r); err != nil { log.Fatal(err) } if err = migrateClaimJournalsWith(ctx, claimStore(ctx), r, provider, true); err != nil { log.Fatal(err) } } // journalMigrateWindowFlag is --window's help. const journalMigrateWindowFlag = "the attended window's directory: its held directory keeps the original of each window unit the window masked, so the migration census accepts those masks" func migrateClaimJournalsWith(ctx context.Context, store release.Store, r ClaimRuntimeResources, provider ClaimOwnerProvider, census bool) (resultErr error) { if store == nil || provider == nil || r.Mode != "instance-control" || !filepath.IsAbs(r.DataDir) { return hostwrite.ErrUnavailable } if census { if err := claimActivationPreflight(r.Registry); err != nil { return err } } if census { if err := claimActivationVerified(ctx, store, r); err != nil { return err } } owners, err := provider.OpenClaimOwners(ctx, store, r) if err != nil { return err } if owners.Close == nil { return hostwrite.ErrUnavailable } defer func() { resultErr = errors.Join(resultErr, owners.Close(context.WithoutCancel(ctx))) }() if owners.RetirementAuthority == nil { return hostwrite.ErrUnavailable } actor, err := owners.actorForConstruction(ctx, r.Mode) if err != nil { return err } launches, err := openClaimRootJournal() if err != nil { return err } defer func() { resultErr = errors.Join(resultErr, launches.Close()) }() journals, err := hostbox.NewJournalService(launches) if err != nil { return err } if census { return migrateRootClaimAuthority(ctx, store, r, owners, actor, journals, launches) } return journals.MigrateLegacy(ctx, filepath.Join(r.DataDir, ".hostclaims"), actor.Box, actor.Enrollment, quiesceLegacyClaimJournals, legacyJournalProof(store, owners.RetirementAuthority.Directory), recoverLegacyClaimEdgeMask) } var openClaimRootJournal = func() (*hostbox.LaunchJournal, error) { if err := hostbox.PrepareRootJournalDirectory("/var/lib/carlos-claims"); err != nil { return nil, err } return hostbox.OpenLaunchJournal("/var/lib/carlos-claims/journals.db") }