Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
303 linesCodeBlameRaw
1# sudo
2
3g1t's staff console, at <https://sudo.g1t.sh>: the back office g1t is
4building for itself, for sales, support and finance. It is organised the
5way customers know g1t: by **workspace**.
6
7The sidebar (`app/lib/nav.ts`) has Overview and Reach out at the top, then
8sections that fold open to their pages: Customers, Revenue, Platform,
9Support and Team. Each fold is a `<details>`, drawn open for the section
10holding the current page; on a phone the same menu sits behind a "Menu"
11button in the top bar. Pages not built yet are marked **Soon**: each is a
12real page (`routes/soon.tsx`, made from its entry in `nav.ts`) saying what
13it will do, why, and what it will have, so the sidebar doubles as the
14roadmap.
15
16- **Overview** (`/`): this month charged, cost and margin; the last six
17 months as a chart; this month by kind of usage; paying workspaces; how
18 many are stopped, near their limit or declined (each a link into Reach
19 out); open invoices; follow-ups due; and the five most urgent signals.
20 From billing's `admin_overview` and `admin_signals`.
21- **Reach out** (`/reach-out`): every workspace worth a word, most urgent
22 first (at limit, declined, near limit, high spend, growing, established,
23 first payment), with its owners, the reason in a sentence, the figure,
24 and its sales stage and owner at g1t. Filter by why and by whose
25 (everyone's, unassigned, mine), or show only **follow-ups due** (a next
26 step due today or earlier, on a deal not won or lost; one per
27 workspace). Rows show the next step and its day. Each row opens the
28 workspace's Sales.
29- **Invoices** (`/invoices`): every invoice g1t has sent, workspaces' and
30 enterprises', newest first (`admin_invoices`, at most 200). Filter by
31 status and month; totals for what is listed (amount, paid, outstanding);
32 each links to its workspace or enterprise and to Stripe's page (https
33 only).
34- **Audit log** (`/audit`): every change made in sudo, and what Stripe told
35 billing, newest first, 100 a page with "Older" (`admin_audit`). Filter by
36 staff email and kind of change; each line links to its workspace or
37 enterprise.
38- **Workspaces** (`/workspaces`): every workspace, newest first, 50 to a
39 page, with its owners, members, who it is billed to, its terms, this
40 month's usage against its limit, what it was charged and what it cost
41 g1t. Search by workspace, owner, email or enterprise (across the whole
42 list); filter to stopped or warning, comped or custom, or on an
43 enterprise. Billing's figures are fetched for exactly the page shown, so
44 the filters, and the totals over the list, cover that page; the page
45 says so when there is more than one. A workspace's page shows its members;
46 **Sales** (its stage, the staff member who has it, the next step and its
47 date, and notes, newest first; `admin_sales`, `admin_set_sales`,
48 `admin_add_note`); and under **Billing** its limit in words (trust, the
49 owners' own spend limit or the default, the most they may set, how it
50 grows), its last six months as a chart, its invoices (`admin_workspace_invoices`,
51 with Stripe's page and PDF), its terms, who it is billed to (move it onto or off an
52 enterprise), a credit form, a Stripe billing link, its ledger and its
53 audit log. If billing does not answer for sales or invoices, the page
54 still opens and says so in those sections. A protected workspace (one
55 nobody can ever delete: identity's `PROTECTED_WORKSPACES`, and
56 flagon-io always) says so beside its name.
57- **Suspend and clean up** (`/workspaces/<slug>/abuse`, linked from a
58 workspace's page): suspend the workspace, with a reason (identity's
59 `admin_suspend_workspace`): its own tokens stop, and a billing hold
60 (`Suspended: <reason>`) stops its runs, checks, deploys, new
61 self-hosted runners and telemetry. Lifting it lifts that hold, never one
62 staff set otherwise. Remove its self-hosted runners one at a time or all
63 at once, which also ends its registration tokens (actions'
64 `admin_runners`, `admin_remove_runners`), and revoke its projects'
65 telemetry ingest tokens. Every change is in sudo's audit log.
66- **Deleted workspaces** (`/workspaces/deleted`, linked from Workspaces):
67 workspaces their owners deleted, newest first (`admin_deleted_workspaces`),
68 each with who deleted it and when, when it is purged, what went with it
69 (repositories, projects, members, counted at the deletion) and the days
70 left. An owner deletes a workspace with everything in it in one step, and
71 identity keeps it 30 days (`WORKSPACE_RESTORE_DAYS`) so support can undo a
72 deletion that was a mistake or not theirs to make. **Restore**
73 (`admin_restore_workspace`) brings it back with its members and tokens,
74 and its repositories, projects and apps with `workspace.restored`; its
75 plan stays ended, so its owners start it again from Billing. Check that
76 whoever asks is an owner of it before restoring. **Purge now**
77 (`admin_purge_workspace`, the slug typed to confirm) removes it at once,
78 as the sweep does every 15 minutes once its 30 days are up; never for a
79 protected workspace. Both go in the workspace's audit log, as g1t, and in
80 sudo's (`workspace_restored`, `workspace_purged`), naming the staff
81 member.
82- **A person's page** (`/users/<username>`, linked from a workspace's
83 members): their addresses (remove one, with a reason they see), their
84 security log, and **Delete account**. An account made with a shared
85 invite link, before registration opened, says **Joined through
86 <label>** under its name. Delete only when the person asks
87 (from one of the account's confirmed addresses) or for abuse: give the
88 reason, which goes in sudo's audit log (`account_deleted`), and type the
89 username (`admin_delete_account`). It does what deleting their own
90 account from Settings does: signs them out everywhere, ends their tokens,
91 SSH keys, deploy keys they added and applications, takes them out of
92 every workspace, team and repository, and emails their addresses that
93 staff deleted it. While the account is the **only owner of a live
94 workspace**, the page lists those workspaces, each linking to its page,
95 and the form becomes **Delete account and the workspaces it alone
96 owns**: the reason, the username typed, and a box ticked to say the
97 named workspaces go too (`admin_delete_account` with
98 `withSoleWorkspaces`). Use it for an account g1t no longer needs, such
99 as a retired test account and its personal workspace; for a customer,
100 prefer another owner first (an owner makes one under People). Identity
101 checks every one of those workspaces before anything is deleted: one
102 that is protected, or whose billing cannot settle (`close_workspace`:
103 an unpaid invoice, prepaid credit, usage still metering, an enterprise
104 account), refuses the whole deletion, and the page says which and why
105 beside each, instead of the form. Then it deletes each workspace exactly
106 as its owner would (billing closes it, `workspace.deleting`, its
107 repositories and apps go with it, kept 30 days), with the staff member
108 as who deleted it, and the account last. Each workspace is recorded in
109 its own audit log as g1t (rule `staff`) and in sudo's
110 (`workspace_deleted`, with the reason); the account in sudo's
111 (`account_deleted`, naming the workspaces). Should one fail on the way
112 (a card declined that moment), the account is not deleted and the
113 error names the workspace and any that went before; restore those from
114 Deleted workspaces, or try again. Accounts that can never be deleted (`g1t`, `g1t-agent`, `ghost`,
115 and whatever identity's `PROTECTED_ACCOUNTS` names, by username or id)
116 are marked **Protected** and offer no form. A deleted account's page
117 says so, with who deleted it, why, when it is purged, and **Restore** and
118 **Purge now**, as on Deleted accounts; both work at once, with no wait.
119 When workspaces were deleted with it, it lists them too, each with its
120 own **Purge now** (`admin_purge_workspace`, the slug typed; identity
121 purges only a workspace that is still deleted, never a protected one),
122 so staff can remove everything straight away. Purge the workspaces
123 first: once the account is purged its page is gone (they stay on
124 Deleted workspaces). To undo it all, restore the account first, then
125 each workspace, so it comes back with its owner.
126- **Suspending an account**, on a person's page: with a reason, it cannot
127 sign in (with a password or GitHub), is signed out everywhere, and its
128 tokens, SSH keys and git passwords stop until the suspension is lifted
129 (identity's `admin_suspend_user`). Nothing is deleted.
130- **Blocked domains** (`/blocked-domains`, under Platform): email domains,
131 and their subdomains, no new account may sign up with or add, each with
132 a reason (`admin_blocked_domains`, `admin_block_domain`). Accounts
133 already there are not touched.
134- **Deleted accounts** (`/users/deleted`, linked from Workspaces):
135 accounts deleted by the person or by staff, newest first
136 (`admin_deleted_accounts`), each with who deleted it (the person, or the
137 staff member and why), when it is purged, the days left and what it
138 left (workspaces, teams, repositories, tokens, SSH keys). Identity keeps
139 each 30 days (`ACCOUNT_RESTORE_DAYS`). **Restore**
140 (`admin_restore_account`) clears the deletion and puts back the
141 memberships, teams and repository roles it left where they still exist;
142 its sessions, tokens and keys stay ended, and the person signs in with
143 their password. Check that whoever asks owns one of its addresses first.
144 **Purge now** (`admin_purge_account`, the username typed) removes it at
145 once, as the sweep does every 15 minutes once its 30 days are up: its
146 row, addresses, keys, two-factor secret, GitHub link, profile and
147 security log go; its username is kept in `deleted_users` and never given
148 out again; what it wrote shows as `ghost`. Both go in sudo's audit log
149 (`account_restored`, `account_purged`), naming the staff member. There
150 is no API route for deleting an account; only the site and sudo can.
151- **Invites** (`/invites`): anyone may register on g1t.sh, so there is
152 no waitlist or shared link to manage, and an invite is never needed.
153 The page lists every invite code: those sent with an invitation into a
154 workspace or onto a repository, and invites to g1t that people send
155 their friends with Invite a friend (20 each), on their own tab.
156 Searchable by a code's start, an email or a username; a pending one can
157 be revoked (`admin_invites`, `admin_revoke_invite`). The Invites to g1t
158 tab also grants a person more of them, or takes some back, with why
159 (`admin_grant_invites`, kept in `invite_grants`). The old waitlist and
160 shared links stay in identity's database, untouched.
161- **Aliases** (`/aliases`, under Customers): names that lead to a
162 workspace, set by staff only; there is no way for a customer to make
163 one, and nothing user-facing mentions them. `g1t`, the product's name,
164 leads to `flagon-io`, Flagon, Inc. (seeded by identity's migration
165 `0029_workspace_aliases.sql`), so nobody mistakes the trading name for
166 the organization. Every address under an alias leads to the workspace:
167 pages answer with a 301 to the same page (`/g1t/g1t/issues` to
168 `/flagon-io/g1t/issues`), git over HTTPS is answered in place as the
169 workspace's repository (pushes do not follow redirects), the API and MCP
170 run the call again under the workspace's slug, and the package
171 registries answer a 301 (308 for a publish). An alias points at the
172 workspace's id, so it follows a rename; it goes when the workspace is
173 purged. Each row shows the workspace, why the alias exists, and who added
174 it and when. **Add** (`admin_set_alias`) takes the alias, the
175 workspace's slug and why: identity refuses the site's own routes
176 (`settings`, `api`…), anyone's username, a workspace's slug (deleted, or
177 held after a rename for another workspace) and an existing alias.
178 Reserved names such as `g1t` can be aliases, and an alias is nobody's to
179 register or rename a workspace to while it exists. **Remove**
180 (`admin_remove_alias`) needs a reason. Both go in sudo's audit log
181 (`alias_added`, `alias_removed`), naming the staff member. `@g1t` in
182 text still means g1t's agent: it links to how the agent works, never to
183 `/g1t`.
184- **Enterprises**: customers that pay for several workspaces with one
185 bill, one limit and one set of terms. Each has its workspaces (add or
186 remove them), combined usage, terms, credits, ledger and audit log, and
187 **Invoices**: where they go (the billing email, which also makes its
188 Stripe customer), a "Send invoice now" button, and every invoice with
189 its status (open, paid, overdue, void), a line per workspace, and a link
190 to Stripe's hosted invoice page. An invoice also goes out on its own as
191 each month closes: one Stripe invoice, a line per workspace for what it
192 owes, net 30, emailed by Stripe.
193- **Stripe**: whether billing's key is in test or live mode (or off), the
194 webhook Stripe calls (URL, endpoint id, events, who registered it and
195 when), and the events Stripe sent lately with what billing did with
196 each. "Register webhook" (or "Replace") has billing delete the endpoint
197 it made before, create a new one and keep its signing secret, which no
198 one sees. Do it once per mode, and again after switching to live keys.
199
200Sales changes are not money, so they have no confirmation step; they are
201still POSTs from sudo's own pages, recorded with who made them.
202
203Billing's internal account ids (`ws_<slug>` for a workspace's own,
204`ent_…` for an enterprise) are never shown as names; an enterprise's id
205appears only as small "Billing account id" text. Old `/accounts/…` links
206redirect to the workspace or enterprise they meant.
207
208**Cards stay on Stripe.** sudo never shows a card field. To help a customer
209update their card or see invoices, staff make a Stripe billing link on the
210workspace's page (it is recorded) and send it to the owner.
211
212It holds no data. Workspaces, owners and members come from identity's
213staff methods (`admin_workspaces`, `admin_workspace`; `IdentityAdminApi` in
214`packages/contracts/src/identity.ts`); everything about money goes to the
215billing service's (`admin_*`, `BillingAdminApi` in
216`packages/contracts/src/billing.ts`), where each change is recorded with
217the staff member's email. Both are reached over service bindings only, and
218nothing but sudo binds to them.
219
220## How it is locked
221
2221. **Cloudflare Access** sits in front of `sudo.g1t.sh` and signs people in.
2232. **The worker checks Access's work** on every request, the stylesheet
224 included (`run_worker_first`): it verifies the `Cf-Access-Jwt-Assertion`
225 JWT itself (RS256 against the team's published keys, audience, issuer,
226 expiry), then requires its email to be in `STAFF_EMAILS`. That email is
227 who every change is recorded as. See `app/lib/access.ts`.
228 **Service tokens.** A program (Claude, working without a browser) signs
229 in with an Access service token instead: it sends `CF-Access-Client-Id`
230 and `CF-Access-Client-Secret`, the Access application has a policy with
231 the **Service Auth** action that includes the token, and Access sends
232 sudo a JWT with no email whose `common_name` is the token's client id.
233 sudo lets it in only if that client id is in `STAFF_SERVICE_TOKENS`
234 (`<client id>=<name>`, comma separated, in `wrangler.jsonc`), after the
235 same signature, audience, issuer and expiry checks, and records it as
236 `<name>@service.g1t.sh`: `claude@service.g1t.sh` for
237 `claude-sudo`. Its secret lives in `.credentials/sudo-service-token.json`
238 and is used by `scripts/ops/sudo.mjs`, which sends sudo's own `Origin`
239 with each POST so the same-origin check applies to it as to a browser.
240 To take its access away, remove its entry here, or delete or revoke the
241 token in Zero Trust.
2423. **It fails closed.** Until `ACCESS_TEAM_DOMAIN`, `ACCESS_AUD` and
243 `STAFF_EMAILS` are all set, every request gets a 403 saying sudo is not
244 configured.
2454. **Changes** are POSTs only, and only from sudo's own pages (`Origin`, or
246 `Referer`, must be `https://sudo.g1t.sh`). Terms, enterprise moves, new
247 enterprises, Stripe billing links, invoice emails, invoices and the
248 webhook show a confirmation step first; a credit needs the workspace's
249 slug typed out.
2505. **The pages ship no JavaScript.** The content security policy forbids
251 every script and inline style; responses are `no-store`, `noindex` and
252 cannot be framed. The worker has no `workers.dev` address or preview URLs.
253
254## Setting up Access (once, in the Cloudflare dashboard)
255
2561. **Zero Trust → Access → Applications → Add an application → Self-hosted.**
257 - Application name: `sudo`.
258 - Session duration: short, such as 8 hours.
259 - Public hostname: `sudo.g1t.sh` (path empty, so it covers everything).
2602. **Add a policy** (`g1t staff`): action *Allow*, include *Emails* → the
261 owner's address, and *Emails ending in* → `g1t.sh` for everyone with a
262 g1t address (the same entries as `STAFF_EMAILS`, where a domain is
263 written `@g1t.sh`). Add more staff here *and* in `STAFF_EMAILS`; either
264 one alone is not enough.
2653. Save, then open the application's **Overview** (or *Basic information*)
266 and copy the **Application Audience (AUD) tag**.
2674. Find the **team domain** under **Zero Trust → Settings → Custom pages**
268 (or *Team name and domain*): it looks like `<team>.cloudflareaccess.com`.
2695. Put both into `wrangler.jsonc`:
270
271 ```jsonc
272 "vars": {
273 "ACCESS_TEAM_DOMAIN": "<team>.cloudflareaccess.com",
274 "ACCESS_AUD": "<the AUD tag>",
275 "STAFF_EMAILS": "syntaqx@gmail.com, @g1t.sh"
276 }
277 ```
278
2796. Deploy: `scripts/deploy.sh sudo` (after `billing` and `identity`, whose
280 `admin_*` methods it calls).
281
282Visit <https://sudo.g1t.sh>: Access asks you to sign in, then the workspaces
283list opens. Anyone else gets Access's own refusal; anyone Access lets in who
284is not in `STAFF_EMAILS` gets a 403 from the worker.
285
286## Signing in through WARP
287
288Staff signed in to the Zero Trust org in the Cloudflare One agent (WARP)
289reach sudo without the login page: the org allows WARP sessions as Access
290sign-ins (8 hours), the sudo app accepts them, and the `g1t staff` policy
291is also on the WARP enrollment app, so staff can enroll their devices.
292The same two checks still apply: the Access policy, and `STAFF_EMAILS`.
293
294## Working on it
295
296```sh
297npm run typecheck -w @g1t/sudo
298npm test -w @g1t/sudo # JWT verification, forms, money, the workspace join, paging, nav, charts, signals, models
299npm run build -w @g1t/sudo
300```
301
302`npm run dev` serves the pages, but every request is refused without a real
303Access token, by design.