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 "encoding/json"
6 "errors"
7 "github.com/carlosframework/platform/internal/hostops"
8 "github.com/carlosframework/platform/internal/hostwrite"
9 "github.com/carlosframework/platform/internal/instancectl"
10 "github.com/carlosframework/platform/internal/provision"
11 "github.com/carlosframework/platform/internal/registry"
12 "github.com/carlosframework/platform/internal/release"
13 "github.com/carlosframework/platform/internal/rootstate"
14 "io/fs"
15 "os"
16 "path/filepath"
17 "slices"
18 "strings"
19)
20
21const claimNamedUnit4aComplete = false
22
23var openClaimActivationState = rootstate.Open
24
25func claimActivationPreflight(reg *registry.Registry) error {
26 if reg == nil {
27 return hostwrite.ErrUnavailable
28 }
29 if claimNamedUnit4aComplete {
30 return nil
31 }
32 routes, err := reg.List()
33 if err != nil {
34 return err
35 }
36 for _, route := range routes {
37 if route.Unit != "" || route.Backing == "unit" {
38 return hostwrite.ErrUnavailable
39 }
40 }
41 return nil
42}
43
44func claimActivationVerified(ctx context.Context, store release.Store, r ClaimRuntimeResources) error {
45 if claimNamedUnit4aComplete {
46 return nil
47 }
48 apps, err := hostops.Apps(ctx, store)
49 if err != nil {
50 return err
51 }
52 cfg := provisionTickConfig(os.Getenv)
53 for _, app := range apps {
54 records, err := hostops.Records(ctx, store, app.Account, app.Name)
55 if err != nil {
56 return err
57 }
58 for _, row := range records {
59 if row.Holder.Family != "instance" {
60 continue
61 }
62 var rec provision.Record
63 if json.Unmarshal(row.Body, &rec) != nil {
64 return hostwrite.ErrUnavailable
65 }
66 if rec.Region != "" && rec.Region != cfg.Region || rec.Pool != "" && !slices.Contains(cfg.Pools, rec.Pool) {
67 continue
68 }
69 if rec.Unit != "" {
70 return hostwrite.ErrUnavailable
71 }
72 }
73 }
74 if err := claimActivationLocalUnits(r); err != nil {
75 return err
76 }
77 if r.Mode == "agent" {
78 return nil
79 }
80 shapes, err := productionClaimUnitShapes()
81 if err != nil {
82 return err
83 }
84 if len(shapes) != 0 {
85 return hostwrite.ErrUnavailable
86 }
87 for _, dir := range []string{"/var/lib/carlos-claims/unit-bindings", "/var/lib/carlos-claims/unit-materializations"} {
88 state, err := openClaimActivationState(dir, false, false)
89 if errors.Is(err, os.ErrNotExist) {
90 continue
91 }
92 if err != nil {
93 return err
94 }
95 names, err := state.Names()
96 state.Close()
97 if err != nil {
98 return err
99 }
100 for _, name := range names {
101 if !strings.HasSuffix(name, ".lock") {
102 return hostwrite.ErrUnavailable
103 }
104 }
105 }
106 return nil
107}
108
109func claimActivationLocalUnits(r ClaimRuntimeResources) error {
110 if r.Mode != "agent" && os.Geteuid() == 0 {
111 for _, dir := range []string{"/var/lib/carlos-app", "/var/lib/private/carlos-app"} {
112 entries, err := os.ReadDir(dir)
113 if errors.Is(err, os.ErrNotExist) {
114 continue
115 }
116 if err != nil {
117 return err
118 }
119 if len(entries) != 0 {
120 return hostwrite.ErrUnavailable
121 }
122 }
123 }
124 for _, dir := range []string{r.UnitsDir, "/etc/systemd/system", "/run/systemd/system", "/usr/lib/systemd/system", "/lib/systemd/system", "/etc/carlos/apps"} {
125 if dir == "" {
126 continue
127 }
128 err := filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error {
129 if errors.Is(err, os.ErrNotExist) && path == dir {
130 return nil
131 }
132 if err != nil {
133 return err
134 }
135 name := entry.Name()
136 if strings.HasPrefix(name, "carlos-app@") && name != "carlos-app@.service" && name != "carlos-app@.socket" {
137 return hostwrite.ErrUnavailable
138 }
139 if entry.IsDir() {
140 if r.Mode == "agent" && filepath.Dir(path) == "/run/systemd/system" && strings.HasSuffix(name, ".d") {
141 role, _, ordinary := instancectl.ParseUnitName(strings.TrimSuffix(name, ".d"))
142 if ordinary && role == instancectl.RoleRun {
143 return fs.SkipDir
144 }
145 }
146 return nil
147 }
148 if dir == "/etc/carlos/apps" {
149 return hostwrite.ErrUnavailable
150 }
151 if !strings.HasSuffix(name, ".service") && !strings.HasSuffix(name, ".conf") {
152 return nil
153 }
154 if _, err := unitsFS.ReadFile("units/" + name); err == nil {
155 return nil
156 }
157 raw, err := os.ReadFile(path)
158 if err != nil {
159 return err
160 }
161 for _, marker := range []string{filepath.Join(r.OptDir, "live") + "/", "/opt/carlos/live/", "/etc/carlos/apps/", "User=carlos", "User = carlos"} {
162 if strings.Contains(string(raw), marker) {
163 return hostwrite.ErrUnavailable
164 }
165 }
166 return nil
167 })
168 if err != nil {
169 return err
170 }
171 }
172 return nil
173}
174