carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "flag"
7 "io"
8 "log"
9 "os"
10 "os/signal"
11 "os/user"
12 "path/filepath"
13 "strconv"
14 "syscall"
15 "time"
16
17 "github.com/carlosframework/platform/internal/activator"
18 "github.com/carlosframework/platform/internal/cgroup"
19 "github.com/carlosframework/platform/internal/controlsock"
20 "github.com/carlosframework/platform/internal/hostbox"
21 "github.com/carlosframework/platform/internal/hostclaim"
22 "github.com/carlosframework/platform/internal/hostwrite"
23 "github.com/carlosframework/platform/internal/release"
24)
25
26var errFilesSeedDelegation = errors.New("run through carlos-edge.service with delegation enabled; a standalone shell cannot prepare this host")
27
28func parseFilesSeed(args []string) (string, error) {
29 if len(args) == 0 || args[0] != "seed" {
30 return "", hostbox.ErrUnsupported
31 }
32 fs := flag.NewFlagSet("system files seed", flag.ContinueOnError)
33 fs.SetOutput(io.Discard)
34 host := fs.String("host", "", "")
35 if err := fs.Parse(args[1:]); err != nil {
36 return "", err
37 }
38 if fs.NArg() != 0 {
39 return "", hostbox.ErrUnsupported
40 }
41 if *host != "" {
42 if _, err := hostclaim.Key(*host); err != nil {
43 return "", err
44 }
45 }
46 return *host, nil
47}
48
49func RunSystemFiles(args []string) {
50 signalCtx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt)
51 defer cancel()
52 ctx, finish := activator.WithCleanupBudget(signalCtx, 30*time.Second)
53 defer finish()
54 if err := runSystemFiles(ctx, args, os.Stdout); err != nil {
55 log.Printf("Files seed failed: %s.", err)
56 os.Exit(filesSeedExitCode(err))
57 }
58}
59
60func filesSeedExitCode(err error) int {
61 if err == nil {
62 return 0
63 }
64 var incomplete filesSeedIncompleteError
65 if errors.As(err, &incomplete) {
66 return 2
67 }
68 return 1
69}
70
71func runSystemFiles(ctx context.Context, args []string, out io.Writer) error {
72 host, err := parseFilesSeed(args)
73 if err != nil {
74 return err
75 }
76 if !hostwrite.Enabled(os.Getenv) || os.Getenv("CARLOS_HOST_CLAIM_ORIGINS") != "1" {
77 return errors.New("CARLOS_HOST_CLAIM and CARLOS_HOST_CLAIM_ORIGINS must both be set; CARLOS_MOVES_PHASE1 is not required")
78 }
79 u, err := user.Lookup("carlos")
80 if err != nil {
81 return errFilesSeedDelegation
82 }
83 uid, err := strconv.Atoi(u.Uid)
84 if err != nil || uid == 0 || os.Geteuid() != uid {
85 return errFilesSeedDelegation
86 }
87 pid, err := filesSeedEdgePID(ctx)
88 if err != nil {
89 return errFilesSeedDelegation
90 }
91 if pid != os.Getpid() {
92 return filesSeedClient(ctx, host, out, uid)
93 }
94 manager, err := filesSeedManager(ctx)
95 if err != nil {
96 return err
97 }
98 // This capability runs root's peer and delegated-cgroup validation before
99 // registry construction, classification, stop, lease or retry mutation.
100 root := NewClaimRoleRootClient(controlsock.Path)
101 if err := root.KindCapability(ctx, "files-seed"); err != nil {
102 return errors.Join(errFilesSeedDelegation, err)
103 }
104 return runFilesSeedOneShot(ctx, host, out, manager)
105}
106
107func runFilesSeedOneShot(ctx context.Context, host string, out io.Writer, manager *cgroup.Manager) (resultErr error) {
108 if manager == nil {
109 return errFilesSeedDelegation
110 }
111 ctx, err := claimEntryContext(ctx, os.Getenv, openDeployStore)
112 if err != nil {
113 return err
114 }
115 dd := dataDir()
116 reg, err := openServiceRegistry(filepath.Join(dd, "registry.db"))
117 if err != nil {
118 return err
119 }
120 safeToClose := true
121 defer func() {
122 if safeToClose {
123 resultErr = errors.Join(resultErr, reg.Close())
124 }
125 }()
126 if err := registerProductionClaimFiles(claimEntryEnv(ctx), dd, reg); err != nil {
127 return err
128 }
129 resources := ClaimRuntimeResources{Mode: "agent", Registry: reg, Manager: manager, DataDir: dd, OptDir: release.OptDir(), UnitsDir: "/etc/systemd/system"}
130 ctx = withClaimRuntimeResources(ctx, resources)
131 ctx, err = ensureProductionClaimAssembly(ctx)
132 if err != nil {
133 return err
134 }
135 entry, _ := ctx.Value(claimEntryKey{}).(claimEntry)
136 a := entry.Assembly
137 if a == nil {
138 return hostbox.ErrUnsupported
139 }
140 var executor *filesSeedExecutor
141 defer func() {
142 cleanup, cancel := activator.CleanupContext(ctx, 30*time.Second)
143 defer cancel()
144 if executor != nil {
145 if err := executor.close(cleanup); err != nil {
146 safeToClose = false
147 resultErr = errors.Join(resultErr, err)
148 }
149 }
150 if safeToClose {
151 closeErr := a.Close(cleanup)
152 resultErr = errors.Join(resultErr, closeErr)
153 if closeErr != nil {
154 safeToClose = false
155 }
156 }
157 }()
158 if _, err := a.Build(ctx); err != nil {
159 return err
160 }
161 source, ok := productionClaimFilesSource().(*movesFilesSource)
162 if !ok {
163 return hostbox.ErrUnsupported
164 }
165 executor = &filesSeedExecutor{assembly: a, service: &activator.Service{}, registry: reg, source: source}
166 return executor.run(ctx, host, out)
167}
168