carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1// carlos is the CARLOS platform binary: edge, host agent, and operator CLI
2// in one artifact, selected by subcommand.
3package main
4
5import (
6 "fmt"
7 "io"
8 "log"
9 "os"
10 "strings"
11
12 "github.com/carlosframework/platform/internal/cgroup"
13 "github.com/carlosframework/platform/internal/edge"
14 "github.com/carlosframework/platform/internal/host"
15 "github.com/carlosframework/platform/internal/instanceexec"
16 "github.com/carlosframework/platform/internal/rootaws"
17 "github.com/carlosframework/platform/internal/version"
18 "github.com/carlosframework/platform/internal/vet"
19)
20
21func main() {
22 // The placement shim (internal/cgroup, #272) must run before anything
23 // else touches the environment or the logger: a shim process carries
24 // none of the tenant's settings and exits from inside this call.
25 cgroup.RunShimIfRequested()
26 rootaws.ScrubEnvironment()
27 // Failure is reported through log.Fatal everywhere in this binary, so
28 // the logger is where the "this went wrong" colour goes — once, rather
29 // than at a few hundred call sites. Off unless stderr is a terminal, so
30 // journald and CI logs see exactly what they saw before.
31 log.SetOutput(newErrorWriter(os.Stderr))
32 if len(os.Args) < 2 {
33 usage(os.Stderr)
34 os.Exit(2)
35 }
36 cmd, args := os.Args[1], os.Args[2:]
37 if cmd == "__registry-child" {
38 if err := host.RunRegistryChild(args); err != nil {
39 log.Fatal(err)
40 }
41 return
42 }
43 switch cmd {
44 // Asking for help is not an error: it prints on stdout and exits 0, so
45 // `carlos help | less` works. A bare `carlos` still fails (stderr, 2).
46 case "help", "-h", "--help":
47 usage(os.Stdout)
48 case "version":
49 // `carlos version` on its own is the build id and nothing else —
50 // unchanged, because every install script and support request in
51 // existence types it. `carlos version target ...` is the only string
52 // that dispatches anywhere new (release versioning spec, 2026-08-08):
53 // a sub-verb rather than a top-level `carlos target`, because what it
54 // sets IS the app's version, and it reads that way at the terminal.
55 if len(args) > 0 && args[0] == "target" {
56 runVersionTarget(args[1:])
57 } else {
58 fmt.Printf("carlos %s\n", version.Build)
59 }
60 case "edge":
61 edge.Run(args)
62 case "add":
63 runAdd(args)
64 case "remove":
65 runRemove(args)
66 case "route":
67 runRoute(args)
68 case "instances":
69 runInstances(args)
70 case "sidecars":
71 runSidecars(args)
72 case "routes":
73 runRoutes(args)
74 case "domains":
75 runDomains(args)
76 case "features":
77 runFeatures(args)
78 case "gate":
79 runGate(args)
80 case "ship":
81 runShip(args)
82 case "promote":
83 runPromote(args)
84 case "deploy":
85 runDeploy(args)
86 case "restart":
87 runRestart(args)
88 case "db":
89 runDB(args)
90 case "run":
91 runRun(args)
92 case "steering":
93 runSteering(args)
94 case "schedule":
95 runSchedule(args)
96 case "economics":
97 runEconomics(args)
98 case "store":
99 runStore(args)
100 case "relay":
101 runRelay(args)
102 case "email":
103 runEmail(args)
104 case "logs":
105 runLogs(args)
106 case "errors":
107 runErrors(args)
108 case "availability":
109 runAvailability(args)
110 case "geo":
111 runGeo(args)
112 case "analytics":
113 runAnalytics(args)
114 case "channels":
115 // Two sub-verbs, resign and digests (#253); bare `carlos channels`
116 // still lists, the `carlos releases retention` precedent.
117 if len(args) > 0 && args[0] == "resign" {
118 runChannelsResign(args[1:])
119 return
120 }
121 if len(args) > 0 && args[0] == "digests" {
122 runChannelsDigests(args[1:])
123 return
124 }
125 runChannels(args)
126 case "pipeline":
127 runPipeline(args)
128 case "skills":
129 runSkills(args)
130 case "releases":
131 // Two sub-verbs, `retention` and `authorize` — the `carlos version
132 // target` judgment call: flags-only verbs stay flat until a word
133 // genuinely names a different action, and a policy write or a
134 // signature is not a listing.
135 if len(args) > 0 && args[0] == "retention" {
136 runReleasesRetention(args[1:])
137 return
138 }
139 if len(args) > 0 && args[0] == "authorize" {
140 runReleasesAuthorize(args[1:])
141 return
142 }
143 runReleases(args)
144 case "rollback":
145 runRollback(args)
146 case "ledger":
147 RunLedger(args)
148 case "auth":
149 RunAuth(args)
150 case "config":
151 RunConfig(args)
152 case "accounts":
153 RunAccounts(args)
154 case "apps":
155 RunApps(args)
156 case "fleets":
157 RunFleets(args)
158 case "services":
159 RunServices(args)
160 case "env":
161 RunEnv(args)
162 case "secrets":
163 RunSecrets(args)
164 case "release-keygen":
165 runKeygen(args)
166 case "proof":
167 runProof(args)
168 case "ops":
169 runOps(args)
170 case "system":
171 runSystem(args)
172 case "move":
173 runMove(args)
174 case "status":
175 RunStatus(args)
176 case "agent":
177 host.RunAgent(args)
178 case "bootstrap":
179 host.RunBootstrap(args)
180 case "adopt":
181 host.RunAdopt(args)
182 case "instance-control":
183 host.RunInstanceControl(args)
184 case "instance-exec":
185 instanceexec.RunExec(args)
186 case "instance-exit":
187 instanceexec.RunExit(args)
188 case "instance-probe":
189 instanceexec.RunProbe(args)
190 case "vet":
191 vet.Run(args)
192 case "update":
193 runUpdate(args)
194 case "canary":
195 runCanary(args)
196 default:
197 usage(os.Stderr)
198 os.Exit(2)
199 }
200 // After a verb that ran to success: the once-a-day newer-release hint
201 // (#161). Verbs that fail never reach here — log.Fatal exits — so a
202 // broken command is never followed by an upsell.
203 maybeUpdateHint(cmd)
204}
205
206// usage prints the top-level command list, grouped by the role you are in
207// when you type the command: shipping your own app, standing up and
208// administering a deployment, or the verbs a host runs on itself. The
209// one-liners here are deliberately thin — every command prints its own full
210// usage (bare, or with -h), and that is where the detail belongs. A flat
211// alphabetical list of every verb with every caveat inline was the previous
212// shape, and it read as a wall rather than as an offer of what to do next.
213// moveUsageLine is the move verb's line of the usage (MV-N363), one literal
214// so the copy gate reads it whole. It prints wrapped like its neighbours.
215const moveUsageLine = "move an instance to another region: status | cut | abort | resolve --retry (operators; off unless the console enables it)"
216
217func moveUsageWrapped() string {
218 return strings.Replace(moveUsageLine, "abort | ", "abort |\n ", 1)
219}
220
221func usage(w io.Writer) {
222 fmt.Fprintln(w, `usage: carlos <command> [flags]
223
224One binary, three roles. "carlos <command> -h" prints a command's own
225flags; --app, the flag on nearly every app verb, shortens to -a.
226
227Your apps — ship and run software on a CARLOS deployment
228 auth log in, see who this terminal is, log out
229 config edit .carlos/config: "config destinations" lists, adds,
230 changes and removes the named places -d deploys to
231 apps create | place | delete | restore an app name — --place
232 fleet/<f> claims and places in one step, and "apps place"
233 repoints an app's routes and instances onto a customer
234 fleet or back to ""
235 ship publish a release of an app — --label and --notes name it,
236 and --version is optional once a version target is set
237 promote move a shipped version onto one of the app's own channels —
238 the channel is optional when the app has only one
239 ("carlos channels" lists them; "carlos pipeline" orders them)
240 deploy ship + promote + wait until the URL serves the new version
241 canary the same, onto a host and channel of this branch's own, so
242 nothing the app already serves changes: deploy | ls | rm
243 restart cycle an app's instances — no new version, no config change;
244 --host or --environment narrows it, --dry-run names the set
245 db read a copy of an instance's database, restored from its
246 replica: pull (save it) | query (one read-only query)
247 run run one of your app's commands against one instance, as that instance
248 schedule recurring or one-shot work per app: ls | set | rm | run
249 store object storage: create | status | apply | approvals | approve | decline | grant | rotate
250 relay the platform STUN/TURN relay: serve (what the unit runs)
251 and keygen (mint a region master)
252 email tenant email sending: enable | domains add | status | test |
253 credentials create/list/revoke | rotate | pause | resume
254 rollback point a channel back at an earlier version
255 channels show what each of an app's channels is serving
256 pipeline show or shape an app's release channels
257 releases every version shipped, newest first, with labels and tags
258 version target the semver ship counts iterations under: print it, or set
259 it — "carlos version target --app <a> 0.5.0"
260 env plain per-app config vars: set | unset | list
261 secrets sealed per-app secrets: set | unset | list | genkey
262 instances an app's instances: enable | create | list | delete |
263 set-upstreams | set-channel — set-channel is the only
264 door onto which channel a route follows
265 sidecars a process nothing dials, one per enrolled member: enable |
266 create | ls | wake | rm | logs
267 steering opt an instance into DNS latency steering, or back off —
268 --app <a> --host <h> latency|off (shared pool only, v1)
269 routes an app's routes and what each is bound to: upstream,
270 channel, config environment, status
271 domains a customer's own hostname attached to an app's route:
272 attach | detach | list
273 features an app's own feature flags, stored and served by the
274 platform, never interpreted: set | list
275 gate a shared password in front of an app's hostnames
276 logs an app's merged log timeline: its own output plus platform
277 events (wakes, restarts, failures); -f follows live
278 errors error groups for an app: log lines at the set level plus
279 500s the edge saw
280 availability requests an app's instances could not answer (502/503/504),
281 grouped by cause, plus failed wakes and exits
282 analytics an app's page views and visitors from a terminal, counted at
283 the edge as it serves your app
284 geo what the platform's GeoIP database says about an address
285 ledger open ledgers: append | head | list | publish | blob | verify
286 vet check a release against the platform contract
287 update update this carlos binary to the latest release (or name
288 the brew/apt command when a package manager owns it);
289 -y skips the prompt, terminal still required
290 skills the platform's published agent skills: list them, or print one —
291 "carlos skills getting-started" pipes the recipe to an agent
292
293Your deployment — stand up and administer boxes, routes and accounts
294 bootstrap prepare a host: units, dirs, user
295 accounts the tenancy primitive: create | list | migrate
296 fleets account-owned groups of remote boxes that dial this
297 console: create | add-box | rotate-token | tag-box |
298 detach-box | ls — box tokens print exactly once, at mint
299 or rotate, and a compromised box needs detach-box, not
300 rotate-token
301 services credentials a SERVER holds, not a person: create | ls |
302 rotate | revoke, owner-only and closed to service
303 credentials themselves — secrets print exactly once, and
304 a compromised one needs revoke, not rotate
305 add route a hostname on this box to an app
306 remove drop one of this box's routes
307 route repoint a route's channel or config environment, grant or
308 revoke a capability, hibernate it, change its backing
309 instances with no sub-verb: list THIS box's registry routes
310 release-keygen mint the root signing key, or a scoped signing key
311 proof dev proofs a box requires before adopting: keygen | get | put
312 economics bill: record the month's AWS bill; backhaul: what region
313 edges cost us this period
314 status --live: what every box last reported; --dry-run: leak check
315 move `+moveUsageWrapped()+`
316
317On the host — what systemd runs; rarely typed by hand
318 edge the mesh front door (router)
319 agent edge + activator + release adoption for this host
320 adopt one release-adoption pass (the hourly timer's verb)
321 ops box operations: litestream | restore-verify
322 system platform self-update: update | status | rollback (apply is the
323 root oneshot); antivirus: deployment-wide object scanning
324 version print the build id`)
325}
326