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 "os"
7
8 "github.com/carlosframework/platform/internal/caps"
9 "github.com/carlosframework/platform/internal/controlsock"
10 "github.com/carlosframework/platform/internal/hostbox"
11 "github.com/carlosframework/platform/internal/hostclaim"
12 "github.com/carlosframework/platform/internal/hostwrite"
13 "github.com/carlosframework/platform/internal/registry"
14 "github.com/carlosframework/platform/internal/rootstate"
15)
16
17var openClaimUnitBindingsRead = func() (rootstate.Store, error) {
18 return rootstate.Open("/var/lib/carlos-claims/unit-bindings", false, false)
19}
20
21type claimUnitShapeReader func(context.Context, registry.Route, hostbox.Selection) error
22
23func claimUnitShapePolicy(route func(string) (*registry.Route, error), read claimUnitShapeReader) hostbox.PolicyRefusal {
24 return func(ctx context.Context, _ *hostclaim.Claim, s hostbox.Selection, _ hostwrite.Identity) error {
25 return claimUnitShapeCheck(ctx, route, read, s, s.Installation())
26 }
27}
28
29// claimUnitShapeCheck is the unit-shape refusal's decision for selection s.
30// installation is s.Installation(): whether s is the work an installation
31// selected for itself (hostbox's installWithWork), the only work that may
32// go on without a registry row.
33func claimUnitShapeCheck(ctx context.Context, route func(string) (*registry.Route, error), read claimUnitShapeReader, s hostbox.Selection, installation bool) error {
34 if route == nil || read == nil {
35 return os.ErrPermission
36 }
37 rt, err := route(s.Host)
38 if err != nil {
39 return err
40 }
41 if rt == nil {
42 // A fresh install has no row until registerRemote's second
43 // work.With writes it, and every work.With before that runs this
44 // policy (harness G10, 2026-10-21). Only the installation's own work
45 // may go on without a row. Everything else on a host whose row is
46 // absent (a timer delivering to its cached route, a restart, a
47 // retired or deleted host) refuses as before (fix round 2). The
48 // installation checks an empty route for the host, as
49 // rootClaimRestartShape does: a host with a configured shape or a
50 // saved unit binding still refuses through checkClaimUnitShape
51 // (root's boundInputs in agent mode), and registerRemote refuses to
52 // recreate a row its inventory already records as materialized.
53 if !installation {
54 return hostbox.ErrChanged
55 }
56 rt = &registry.Route{Host: s.Host}
57 }
58 if rt.Host != s.Host {
59 return hostbox.ErrChanged
60 }
61 return read(ctx, *rt, s)
62}
63
64func checkClaimUnitShape(ctx context.Context, b *claimUnitBindings, shapes map[string]claimUnitShape, rt registry.Route, s hostbox.Selection) error {
65 saved, err := b.read(rt.Host)
66 if errors.Is(err, os.ErrNotExist) {
67 if _, configured := shapes[rt.Host]; configured || rt.Unit != "" || rt.Backing != "" || rt.Tunnel != "" {
68 return hostbox.ErrUnsupported
69 }
70 return nil
71 }
72 if err != nil {
73 return err
74 }
75 if saved.Retiring || saved.Retired || saved.Shape != shapes[rt.Host] || !saved.Shape.matches(rt) {
76 return hostbox.ErrChanged
77 }
78 if s.Host != "" && (saved.Owner.Host != s.Host || saved.Owner.Box != s.Box || saved.Owner.Incarnation != s.Captured.Incarnation || saved.Owner.Stamp != s.Captured.Stamp) {
79 return hostbox.ErrChanged
80 }
81 return ctx.Err()
82}
83
84func withClaimUnitShape(ctx context.Context, rt registry.Route, s hostbox.Selection, fn func(claimUnitBinding) error) error {
85 state, err := openClaimUnitBindingsRead()
86 shapes, configErr := productionClaimUnitShapes()
87 if errors.Is(err, os.ErrNotExist) && rt.Unit == "" && rt.Backing == "" && rt.Tunnel == "" && shapes[rt.Host].Host == "" {
88 return fn(claimUnitBinding{})
89 }
90 if err != nil {
91 return err
92 }
93 defer state.Close()
94 b := &claimUnitBindings{state: state}
95 if configErr != nil {
96 _, e := b.read(rt.Host)
97 if !errors.Is(e, os.ErrNotExist) || rt.Unit != "" || rt.Backing != "" || rt.Tunnel != "" {
98 return configErr
99 }
100 shapes = map[string]claimUnitShape{}
101 }
102 if err = checkClaimUnitShape(ctx, b, shapes, rt, s); err != nil {
103 return err
104 }
105 saved, err := b.read(rt.Host)
106 if errors.Is(err, os.ErrNotExist) {
107 return fn(claimUnitBinding{})
108 }
109 if err != nil {
110 return err
111 }
112 return fn(saved)
113}
114
115func productionClaimUnitShapePolicy(mode string, route func(string) (*registry.Route, error)) hostbox.PolicyRefusal {
116 return claimUnitShapePolicy(route, func(ctx context.Context, rt registry.Route, s hostbox.Selection) error {
117 if mode == "agent" {
118 _, err := NewClaimRoleRootClient(controlsock.Path).UnitInputs(ctx, rt)
119 return err
120 }
121 if mode != "adopt" && mode != "instance-control" {
122 return os.ErrPermission
123 }
124 return withClaimUnitShape(ctx, rt, s, func(claimUnitBinding) error { return nil })
125 })
126}
127
128func rootClaimUnitInputs(ctx context.Context, rt registry.Route) (*hostbox.UnitLaunchInputs, error) {
129 var out *hostbox.UnitLaunchInputs
130 err := withClaimUnitShape(ctx, rt, hostbox.Selection{}, func(b claimUnitBinding) error {
131 if b == (claimUnitBinding{}) {
132 return nil
133 }
134 p, err := readClaimUnitProgram(b.Shape)
135 if err != nil {
136 return err
137 }
138 out = &hostbox.UnitLaunchInputs{Unit: b.Shape.Unit, Socket: b.Shape.Socket, Addr: b.Shape.Addr, DBPath: b.Shape.DBPath, Program: b.Shape.Program, Args: p.Args, Env: p.Env}
139 return nil
140 })
141 return out, err
142}
143
144func rootClaimRestartShape(ctx context.Context, host string, rt *registry.Route) error {
145 if rt == nil {
146 rt = &registry.Route{Host: host}
147 }
148 if rt.Host != host {
149 return os.ErrPermission
150 }
151 return withClaimUnitShape(ctx, *rt, hostbox.Selection{}, func(b claimUnitBinding) error {
152 if b != (claimUnitBinding{}) {
153 return hostbox.ErrUnsupported
154 }
155 return nil
156 })
157}
158
159func (c rootRestartChecks) checkUnitShape(ctx context.Context, host string, rt *registry.Route) error {
160 if c.unitShape == nil {
161 return nil
162 }
163 legacy, err := c.allowLegacy()
164 if err != nil {
165 return err
166 }
167 if legacy {
168 return nil
169 }
170 return c.unitShape(ctx, host, rt)
171}
172
173func productionClaimCapsOps(o caps.Ops, census func() (bool, error)) caps.Ops {
174 previous := o.CheckHost
175 o.CheckHost = func(host string) error {
176 if census == nil {
177 return os.ErrPermission
178 }
179 legacy, err := census()
180 if err != nil {
181 return err
182 }
183 if !legacy {
184 if err := claimBoundCapsRefusal(host); err != nil {
185 return err
186 }
187 }
188 if previous != nil {
189 return previous(host)
190 }
191 return nil
192 }
193 return o
194}
195
196func claimBoundCapsRefusal(host string) error {
197 shapes, e := productionClaimUnitShapes()
198 if e == nil {
199 if _, bound := shapes[host]; bound {
200 return hostbox.ErrUnsupported
201 }
202 }
203 state, e := openClaimUnitBindingsRead()
204 if e != nil && !errors.Is(e, os.ErrNotExist) {
205 return e
206 }
207 if e == nil {
208 b := &claimUnitBindings{state: state}
209 _, e = b.read(host)
210 state.Close()
211 if e == nil {
212 return hostbox.ErrUnsupported
213 }
214 if !errors.Is(e, os.ErrNotExist) {
215 return e
216 }
217 }
218 return nil
219}
220