package host import ( "context" "errors" "flag" "io" "log" "os" "os/signal" "os/user" "path/filepath" "strconv" "syscall" "time" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/cgroup" "github.com/carlosframework/platform/internal/controlsock" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/release" ) var errFilesSeedDelegation = errors.New("run through carlos-edge.service with delegation enabled; a standalone shell cannot prepare this host") func parseFilesSeed(args []string) (string, error) { if len(args) == 0 || args[0] != "seed" { return "", hostbox.ErrUnsupported } fs := flag.NewFlagSet("system files seed", flag.ContinueOnError) fs.SetOutput(io.Discard) host := fs.String("host", "", "") if err := fs.Parse(args[1:]); err != nil { return "", err } if fs.NArg() != 0 { return "", hostbox.ErrUnsupported } if *host != "" { if _, err := hostclaim.Key(*host); err != nil { return "", err } } return *host, nil } func RunSystemFiles(args []string) { signalCtx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt) defer cancel() ctx, finish := activator.WithCleanupBudget(signalCtx, 30*time.Second) defer finish() if err := runSystemFiles(ctx, args, os.Stdout); err != nil { log.Printf("Files seed failed: %s.", err) os.Exit(filesSeedExitCode(err)) } } func filesSeedExitCode(err error) int { if err == nil { return 0 } var incomplete filesSeedIncompleteError if errors.As(err, &incomplete) { return 2 } return 1 } func runSystemFiles(ctx context.Context, args []string, out io.Writer) error { host, err := parseFilesSeed(args) if err != nil { return err } if !hostwrite.Enabled(os.Getenv) || os.Getenv("CARLOS_HOST_CLAIM_ORIGINS") != "1" { return errors.New("CARLOS_HOST_CLAIM and CARLOS_HOST_CLAIM_ORIGINS must both be set; CARLOS_MOVES_PHASE1 is not required") } u, err := user.Lookup("carlos") if err != nil { return errFilesSeedDelegation } uid, err := strconv.Atoi(u.Uid) if err != nil || uid == 0 || os.Geteuid() != uid { return errFilesSeedDelegation } pid, err := filesSeedEdgePID(ctx) if err != nil { return errFilesSeedDelegation } if pid != os.Getpid() { return filesSeedClient(ctx, host, out, uid) } manager, err := filesSeedManager(ctx) if err != nil { return err } // This capability runs root's peer and delegated-cgroup validation before // registry construction, classification, stop, lease or retry mutation. root := NewClaimRoleRootClient(controlsock.Path) if err := root.KindCapability(ctx, "files-seed"); err != nil { return errors.Join(errFilesSeedDelegation, err) } return runFilesSeedOneShot(ctx, host, out, manager) } func runFilesSeedOneShot(ctx context.Context, host string, out io.Writer, manager *cgroup.Manager) (resultErr error) { if manager == nil { return errFilesSeedDelegation } ctx, err := claimEntryContext(ctx, os.Getenv, openDeployStore) if err != nil { return err } dd := dataDir() reg, err := openServiceRegistry(filepath.Join(dd, "registry.db")) if err != nil { return err } safeToClose := true defer func() { if safeToClose { resultErr = errors.Join(resultErr, reg.Close()) } }() if err := registerProductionClaimFiles(claimEntryEnv(ctx), dd, reg); err != nil { return err } resources := ClaimRuntimeResources{Mode: "agent", Registry: reg, Manager: manager, DataDir: dd, OptDir: release.OptDir(), UnitsDir: "/etc/systemd/system"} ctx = withClaimRuntimeResources(ctx, resources) ctx, err = ensureProductionClaimAssembly(ctx) if err != nil { return err } entry, _ := ctx.Value(claimEntryKey{}).(claimEntry) a := entry.Assembly if a == nil { return hostbox.ErrUnsupported } var executor *filesSeedExecutor defer func() { cleanup, cancel := activator.CleanupContext(ctx, 30*time.Second) defer cancel() if executor != nil { if err := executor.close(cleanup); err != nil { safeToClose = false resultErr = errors.Join(resultErr, err) } } if safeToClose { closeErr := a.Close(cleanup) resultErr = errors.Join(resultErr, closeErr) if closeErr != nil { safeToClose = false } } }() if _, err := a.Build(ctx); err != nil { return err } source, ok := productionClaimFilesSource().(*movesFilesSource) if !ok { return hostbox.ErrUnsupported } executor = &filesSeedExecutor{assembly: a, service: &activator.Service{}, registry: reg, source: source} return executor.run(ctx, host, out) }