carlos / platform Public

docs: h2 groups over h3 commands, and four corrections

The three role groups were sharing heading rank with the 36 commands
under them, so the website's sub-nav would have rendered 39 flat entries
with three of them quietly acting as separators. Groups are h2 now and
commands h3. `carlos help` and `carlos canary` belong to no role, so
they moved into a fourth group of their own instead of floating at h2.

The index linked to carloku.com/docs, which does not exist yet — that
site is a later plan. It points at carloku.com, which is live.

carlos ledger was missing `blob`; it now describes the upload, its
create-only 4MB cap, and the fact that ledgers have no direct-bucket
mode at all. The example ledger name is `carbon`, which is the one
internal/ledger's own tests use.

carlos deploy's zero-argument path and carlos store's operator gate were
both written from usage text and flagged as untraced. Both now match the
code: deploy is silent with saved defaults, asks once at a terminal, and
refuses in CI; store's grant is the only operator verb, and its 403 sits
behind the account gate so a stranger still gets the 404.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 6652c4dab7d1e0e6ad676a7edbd80876252ef77a parent 184baac
2 files changed, +74 −55
  • docs/site/cli.md +71 −52
  • docs/site/index.md +3 −3
diff --git a/docs/site/cli.md b/docs/site/cli.md
index c9a3f412f..c192e0149 100644
--- a/docs/site/cli.md
+++ b/docs/site/cli.md
@@ -23,19 +23,13 @@ credentials gets their work done. With `CARLOS_DEPLOYMENT_BUCKET` or
`CARLOS_DEPLOYMENT_DIR` set, they write to the bucket directly, which is the
operator's path. Where a command only works one way, its section says so.
-## carlos help
-
-Prints the command list, grouped the way this page is. It goes to stdout and
-exits 0, so `carlos help | less` works. A bare `carlos` with no arguments
-prints the same text on stderr and exits 2, because that one is a mistake.
-
## Your apps
Everything from claiming a name to watching a deploy land. None of it needs
access to a box, a cloud console, or the deployment's bucket; that is a rule
the platform holds itself to, and a command that broke it would be a bug.
-## carlos auth
+### carlos auth
Logs this terminal in to a console. `carlos auth login` prints a short code
that you approve in a browser already signed in to that console, and the
@@ -57,7 +51,7 @@ shows the console this machine talks to by default, and sets it when you give
it a URL. With `-project` it writes `./.carlos/config`, which you can commit
so a checkout pins its own deployment.
-## carlos apps
+### carlos apps
Claims an app name, moves an app onto a customer fleet, and deletes or
restores one. Creating goes through the same door the console's New app form
@@ -80,7 +74,7 @@ later, puts the app's routes and instances on a fleet your account owns.
in that empty spelling: leaving it off is a usage error, never a silent
clear.
-## carlos ship
+### carlos ship
Publishes a release. The artifact is hashed, stored under its content
address, and recorded in a manifest that never changes afterwards. Shipping
@@ -97,7 +91,7 @@ off is the point of having one: ship mints the next iteration under it. Use
is one line for humans and shows up in `carlos releases` and in the console,
so write it for the person doing a rollback at 2am.
-## carlos promote
+### carlos promote
Points a channel at a version you have already shipped. The ladder is
`canary → edge → beta → stable`: beta is entered from edge and stable from
@@ -113,7 +107,7 @@ Promoting the same version twice succeeds and does nothing. `-hotfix` skips
the ladder and is an operator act: it works on the direct-bucket path only,
and the console refuses it by name, so the flag never quietly does nothing.
-## carlos deploy
+### carlos deploy
Ship, promote, and then wait until the app's URL actually serves the new
build. It watches the `X-Carlos-Version` header on the live host and does not
@@ -125,9 +119,12 @@ between them.
carlos deploy -app hello ./hello
```
-Signed-in only. In a project with saved defaults you can run `carlos deploy`
-with no arguments at all, and the first run at a terminal asks what to deploy
-and offers to remember the answer. `-channel` overrides the channel it picks
+Signed-in only. With saved project defaults you can run `carlos deploy` with
+no arguments and no questions. The first run at a terminal asks which
+artifact to ship, works out from what you point it at whether that is a
+binary or a site, and offers to save the answer as the project's default.
+With neither arguments nor defaults and no terminal to ask at — CI, usually —
+it refuses instead of guessing. `-channel` overrides the channel it picks
(normally the one the app's instances already follow), and `-host` scopes the
watch to a single instance. A static site needs that second flag, having no
instances to resolve a channel from.
@@ -136,7 +133,7 @@ instances to resolve a channel from.
carlos deploy -app website -kind static -host www.example.com ./dist
```
-## carlos restart
+### carlos restart
Cycles an app's processes with no new version and no config change. This is
the answer to a wedged instance.
@@ -152,7 +149,7 @@ stop within seconds and respawn on their next request, which for an idle app
may be a while. Nothing is stuck: the instance comes back with the next
request that needs it. Hibernating instances are left asleep.
-## carlos rollback
+### carlos rollback
Points a channel back at the version it was serving before.
@@ -160,7 +157,7 @@ Points a channel back at the version it was serving before.
carlos rollback -app hello stable
```
-## carlos channels
+### carlos channels
What each of the app's channels is serving right now, with the tag if the
release earned one at stable.
@@ -169,7 +166,7 @@ release earned one at stable.
carlos channels -app hello
```
-## carlos releases
+### carlos releases
Every version shipped, newest first, with the label its author typed and the
tag it earned. Channels answer "what is running"; this answers "what is there
@@ -186,7 +183,7 @@ rollback history, tagged releases, anything inside the bake window, and each
box's own adopted versions are never pruned, so nothing promoted or recent
disappears by policy. `-off` goes back to keeping everything.
-## carlos version
+### carlos version
`carlos version` prints the build id of the binary in front of you. It is the
first thing to check when a command behaves unlike this page describes.
@@ -199,7 +196,7 @@ next one for you.
carlos version target -app hello 0.5.0
```
-## carlos env
+### carlos env
Plain per-app config vars: `set`, `unset`, `list`.
@@ -212,7 +209,7 @@ bundle instead, layered on top of the default when config is materialized,
and `carlos route -environment` is what binds a route to one.
`carlos env environments -app hello` lists the bundle names an app has.
-## carlos secrets
+### carlos secrets
The same shape as `carlos env`, sealed. `list` prints key names and never
values.
@@ -225,7 +222,7 @@ Sealing uses a public key; the private half lives on the box that decrypts
them and never leaves it, in either mode. `carlos secrets genkey` mints the
pair, and it is always local to your terminal.
-## carlos instances
+### carlos instances
An app's instances are the places it actually runs. `carlos instances enable`
is the app's opt-in to provisioning, and until it is set the API refuses to
@@ -247,7 +244,7 @@ it lists that box's own registry routes, backing and owning unit included.
Off the box, [carlos routes](/docs/cli#carlos-routes) answers the same
question through the console.
-## carlos routes
+### carlos routes
The app's routes on this deployment: where each one sends traffic, which
channel it follows, and which config environment it is bound to. That last
@@ -261,7 +258,7 @@ carlos routes -app hello
It reads the console's own box, so on a multi-box fleet a short answer is not
necessarily a complete one. The command says so every time it prints.
-## carlos domains
+### carlos domains
Attaches a customer's own hostname to one of the app's routes, detaches it,
or lists what is claimed.
@@ -276,7 +273,7 @@ that route at its next domains pass, certificate and all. The `list` verb
joins the claims to the fleet's own readings sweep, so DNS state, certificate
expiry and delegation are readable without leaving the terminal.
-## carlos logs
+### carlos logs
The app's own stdout and stderr, merged with the platform's events about it:
wakes, restarts, and failures with the reason. A site served from files has
@@ -290,7 +287,7 @@ carlos logs -app hello -since 1h -f
an RE2 pattern, `-stream app` drops the platform commentary, and `-host`
narrows to one instance.
-## carlos store
+### carlos store
Object storage for an app. `create` declares that the app wants a bucket,
which is a request and not a provision — nothing exists in any cloud account
@@ -304,25 +301,36 @@ carlos store status -app hello
`carlos store rotate` mints a fresh credential and leaves the old one live
until you run it again with `-finish`, so the app has a window to pick the
-new one up. Granting needs a deployment operator; the other three are member
-verbs.
+new one up. Rotating, declaring and reading status are member verbs and want
+nothing but your bearer token. `grant` is the one that spends money in the
+deployment's own cloud account, so it also wants an address on
+`CARLOS_STORE_OPERATORS`; that check runs after the account gate, so a
+stranger still meets the same 404 as everyone else rather than a 403 that
+would confirm the app exists.
-## carlos ledger
+### carlos ledger
-Append-only hash-chained ledgers an app can publish: `append` adds an entry,
-`head` prints the chain's current head, `list` shows what the app has, and
-`publish` decides which of them it serves publicly.
+Append-only hash-chained ledgers an app can publish. `append` adds one JSON
+entry to a chain, `head` prints that chain's current head, `list` shows the
+app's ledgers and what it publishes, and `publish` decides which of them are
+served publicly.
```sh
-carlos ledger append -app hello events entry.json
+carlos ledger append -app hello carbon entry.json
```
-`carlos ledger verify` walks a published chain over plain HTTPS and re-hashes
-every entry. It needs no credentials at all, which is the whole point:
+`blob` uploads files to a chain as content-addressed blobs and prints each
+one's sha, so an entry can point at it. Uploads are create-only and capped at
+4 MB a file, and re-uploading identical bytes does nothing.
+
+Ledgers have no direct-bucket mode at all: every sub-verb except `verify`
+goes through the console, because a contributor's machine is never given
+bucket credentials. `carlos ledger verify` walks a published chain over plain
+HTTPS and re-hashes every entry. It needs no credentials at all, which is the whole point:
anybody can check a ledger you publish, including you, from a machine that
has never been logged in.
-## carlos vet
+### carlos vet
Checks a shipped release against the platform contract before anyone promotes
it. The manifest has to exist and parse, and every artifact's stored bytes
@@ -337,7 +345,7 @@ serve HTTP on that socket, and answer `GET /healthz` with a 200 within ten
seconds. That is the entire app contract, and this is the cheapest place to
find out you have broken it.
-## carlos update
+### carlos update
Replaces the `carlos` binary on your workstation with the latest published
release, checking the signed checksums before it swaps anything. Where a
@@ -358,7 +366,7 @@ The verbs for the person who owns the boxes. Some of these run against a
console like the app commands above; others open a box's registry directly
and only make sense while you are standing on it.
-## carlos bootstrap
+### carlos bootstrap
Prepares a host to be a CARLOS box: the service user, the data directories,
the systemd units. Run it once per host, as root.
@@ -372,7 +380,7 @@ users and running no systemctl, which is how you read what it would do before
it does it. `-offcloud` is for a box outside EC2, where credentials come from
a staged 0600 file instead of an instance profile.
-## carlos accounts
+### carlos accounts
Accounts are the tenancy primitive: apps, fleets, credentials and bucket
prefixes all hang off one. `create` mints an account and prints the sqid
@@ -390,7 +398,7 @@ logged-in one, which is why it exists.
prefix and re-stamps its routes. Run it with `-dry-run` first; it prints the
plan and touches nothing.
-## carlos fleets
+### carlos fleets
A fleet is a group of remote boxes an account owns, dialing in to this
console over a channel named `<fleet>/<label>`. Create one against the
@@ -410,7 +418,7 @@ the old one, but it does not evict a channel the box is already holding. If a
credential may be compromised, `detach-box` is the command that actually
stops it.
-## carlos services
+### carlos services
Credentials a server holds rather than a person: a CI job that ships, a
sidecar that reads instances. Reach is fixed at mint, so adding you to
@@ -430,7 +438,7 @@ The secret prints once, same as a fleet token, and the same distinction
applies at the other end: `rotate` refuses the credential's next call with
the old secret, `revoke` stops it whatever secret it holds.
-## carlos add
+### carlos add
Writes a route straight into a box's registry: this hostname, served by this
app, following this channel. It is the box-local, operator-side counterpart
@@ -446,7 +454,7 @@ tenant. `-unit console.service` names the systemd unit that owns the route's
process, which is what lets adoption cycle it; leave it off for an ordinary
exec child.
-## carlos remove
+### carlos remove
Drops one of this box's routes. It stops routing the hostname and never
touches the instance's database.
@@ -455,7 +463,7 @@ touches the instance's database.
carlos remove hello.example.com
```
-## carlos route
+### carlos route
Changes one thing about a route that already exists. Each axis is its own
operation and the command refuses to combine them, because a capability grant
@@ -481,7 +489,7 @@ environment belong to its instance record: `carlos route` refuses both axes
on such a row and names the record, because the reconciler would revert you
within seconds anyway.
-## carlos release-keygen
+### carlos release-keygen
Mints the deployment's root signing key, or a scoped key that can sign only
certain rungs of the ladder.
@@ -496,7 +504,7 @@ a grant. The grant carries no secret material but has to travel with the key,
because a scoped key on its own signs pointers nothing will accept. Only the
root key can mint a grant, which is what stops a scoped key widening itself.
-## carlos economics
+### carlos economics
Records one month's AWS bill so the console's economics dashboard has a real
number to divide by.
@@ -509,7 +517,7 @@ Operator only, and the console answers everyone else with the same bare 404
it gives an unknown route. A "not found" here almost always means your
account lacks the fleet-operator bit, not that you typed the path wrong.
-## carlos status
+### carlos status
`-live` prints what every box in the fleet last reported about itself:
release, tunnel state, memory, load, free disk. A box that has stopped
@@ -532,7 +540,7 @@ box's own agent tick.
These are what systemd starts. You will rarely type them, but knowing what
they do explains most of what happens between a promote and a URL changing.
-## carlos edge
+### carlos edge
The front door. TLS and ACME, and the proxy that maps a hostname to whatever
is serving it. One per box, started by `carlos-edge.service`.
@@ -545,13 +553,13 @@ carlos edge -dev -http :8080
laptop. `-fallback` redirects unknown hostnames somewhere (an apex marketing
site, usually) instead of returning the plain 404.
-## carlos agent
+### carlos agent
Edge, the hibernation activator, release adoption and the status tick, in one
process per host. This is what a box actually runs; `carlos edge` alone is
the proxy without any of the rest.
-## carlos adopt
+### carlos adopt
One release-adoption pass: read each route's channel, resolve what that
channel points at, fetch it if the box does not have it, and swap the
@@ -573,7 +581,7 @@ build the process is not serving.
root-side unit to run for it, since the agent can request a pass but cannot
write to `/etc` or restart units itself.
-## carlos ops
+### carlos ops
Box operations, run by a timer or by an operator standing on the box.
`litestream` regenerates the replication config from the registry and the
@@ -588,7 +596,18 @@ the deployment bucket so you can read it off-box.
carlos ops restore-verify
```
-## carlos canary
+## Everything else
+
+`carlos help` belongs to all three roles above; `carlos canary` belongs
+to none of them yet.
+
+### carlos help
+
+Prints the command list, grouped the way this page is. It goes to stdout and
+exits 0, so `carlos help | less` works. A bare `carlos` with no arguments
+prints the same text on stderr and exits 2, because that one is a mistake.
+
+### carlos canary
Reserved, and not implemented: typing it exits with `not implemented`.
Canary releases today are ordinary channels named `canary/<something>`,
diff --git a/docs/site/index.md b/docs/site/index.md
index 16a159564..fe9fd6eca 100644
--- a/docs/site/index.md
+++ b/docs/site/index.md
@@ -12,6 +12,6 @@ either running a deployment or shipping onto one. They do not teach Go, and
they are not a tutorial.
If you are a Carloku customer, the docs you want are at
-[carloku.com/docs](https://carloku.com/docs). Carloku is CARLOS run for you,
-and its pages cover the parts you actually touch without repeating the
-box-side machinery below.
+[carloku.com](https://carloku.com). Carloku is CARLOS run for you, and it
+covers the parts you actually touch without the box-side machinery these
+pages spend most of their time on.