Self-hosted Corotum Cloud
Self-hosted Corotum Cloud is free under AGPLv3. Hosted Corotum billing is not required for self-hosted Cloud. Creem is not required. Do not configure Creem. Auth and OAuth are configured independently from hosted billing.
Devices pair in a browser and apply or report state through the CLI on that device. Automatic Cloud sync is an opt-in native per-user service. There is no remote forced sync.
What you deploy
Section titled “What you deploy”Deploy the apps/events Cloudflare Worker with its SQLite-backed workspace Durable Object, then the apps/web Worker (vinext / Next.js on workerd), D1, and R2. The main app serves the landing page, dashboard, WebMCP, and Cloud API, including /api/v2/workspaces/:workspaceId/revision and /api/v2/workspaces/:workspaceId/events. Devices connect only to the main app origin; the app authenticates and proxies event subscriptions through a private service binding. No public events hostname, route, or separate client origin is needed.
Leave COROTUM_HOSTED unset or set it to false. Only true or 1 turns on hosted corotum.com billing. Self-hosted Cloud paths do not check Creem entitlement.
Prerequisites
Section titled “Prerequisites”- A Cloudflare account and Wrangler authenticated for that account (
npx wrangler login) - Bun 1.3 or newer
- A GitHub OAuth App
- A Google OAuth App
- Public HTTPS origin for the Worker (
*.workers.devor a custom domain) - An independently configured transactional email service/binding for magic-link delivery
Creem, a Creem account, and hosted corotum.com subscription products are not prerequisites. GitHub and Google sign-in work without email. Magic links need the EMAIL binding, AUTH_EMAIL_FROM, and a matching allowed sender.
AGPL obligations
Section titled “AGPL obligations”This software is licensed under GNU AGPLv3. If you run a modified version as a network service, you must offer the corresponding source to users who interact with it over the network. Keep the license text, copyright notices, and a way to obtain the source you actually deploy.
Create D1 and bindings
Section titled “Create D1 and bindings”From apps/web:
npx wrangler d1 create corotumPut the returned database_id in apps/web/wrangler.jsonc under d1_databases for binding DB, database name corotum, migrations_dir migrations. Use your own database id rather than the repository’s configured deployment id.
Apply migrations:
npx wrangler d1 migrations apply corotum --remoteLocal development:
bun run db:migratewrangler.jsonc already binds:
| Binding | Name | Purpose |
|---|---|---|
DB | D1 corotum | Auth, workspaces, revisions, devices, reports, artifact metadata |
ARTIFACTS | R2 corotum | Artifact-backed skill archives only; change to your own bucket |
ASSETS | dist/client | Built web assets |
COROTUM_TELEMETRY | Analytics Engine dataset corotum_telemetry | Optional anonymous CLI telemetry ingest |
EVENTS | Worker service corotum-events | Private revision hints via workspace Durable Objects |
CLOUD_READ_LIMITER | Rate limiter namespace 2101 | 120 reads per 60 seconds per application key |
CLOUD_WRITE_LIMITER | Rate limiter namespace 2102 | 30 mutations per 60 seconds per application key |
CLOUD_AUTH_LIMITER | Rate limiter namespace 2103 | 10 pairing/auth requests per 60 seconds per application key |
Create the R2 bucket before wrangler deploy (or change bucket_name to a bucket you own):
npx wrangler r2 bucket create corotumD1 stores no archive bytes. Retention keeps the current artifact plus one previous artifact per skill; GC deletes an object only when it is absent from both references. See skills.md.
The COROTUM_TELEMETRY binding is already in wrangler.jsonc. Cloudflare creates the Analytics Engine dataset on first write. You do not create it by hand. CLI devices send events only if they opt in.
The shipped send_email binding allows only auth@corotum.com. Change allowed_sender_addresses in apps/web/wrangler.jsonc to your own sender before production deploy. It must match AUTH_EMAIL_FROM.
You do not need a Creem webhook route configuration for self-hosting. Hosted billing routes return that billing is unavailable when the deployment is not hosted.
Auth and OAuth
Section titled “Auth and OAuth”Production (anything other than COROTUM_ENVIRONMENT=development) requires:
BETTER_AUTH_SECRETat least 32 charactersBETTER_AUTH_URLequal to the public origin, for examplehttps://cloud.example.com- Both GitHub and Google OAuth client id and secret
Create the OAuth apps with:
| Field | Value |
|---|---|
| Homepage | https://cloud.example.com |
| GitHub callback | https://cloud.example.com/api/auth/callback/github |
| Google callback | https://cloud.example.com/api/auth/callback/google |
Replace the origin with your BETTER_AUTH_URL. Partial OAuth (id without secret, or only one provider) is rejected.
OAuth and email magic-link sign-in are independent of Creem and of hosted corotum.com billing.
Environment variables
Section titled “Environment variables”Required for a production self-host:
| Name | How to set | Notes |
|---|---|---|
BETTER_AUTH_SECRET | npx wrangler secret put BETTER_AUTH_SECRET | ≥ 32 characters |
GITHUB_CLIENT_SECRET | npx wrangler secret put GITHUB_CLIENT_SECRET | |
GOOGLE_CLIENT_SECRET | npx wrangler secret put GOOGLE_CLIENT_SECRET | |
BETTER_AUTH_URL | vars in wrangler.jsonc | Public origin, no trailing path |
GITHUB_CLIENT_ID | vars | |
GOOGLE_CLIENT_ID | vars | |
COROTUM_ENVIRONMENT | vars | production |
COROTUM_HOSTED | vars | false or omit |
AUTH_EMAIL_FROM | vars in production; .dev.vars locally | Sender address from your own sending domain; must match send_email.allowed_sender_addresses |
Example vars (do not put secrets here):
"vars": { "BETTER_AUTH_URL": "https://cloud.example.com", "GITHUB_CLIENT_ID": "your-github-client-id", "GOOGLE_CLIENT_ID": "your-google-client-id", "COROTUM_ENVIRONMENT": "production", "COROTUM_HOSTED": "false", "AUTH_EMAIL_FROM": "auth@cloud.example.com"}Do not set hosted billing variables. Hosted Corotum billing is not required for self-hosted Cloud. Official GitHub Actions secrets (R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET) publish Corotum CLI builds; a self-host does not need them.
Optional cookieless website analytics (self-hosted Umami). Omit both to leave analytics off. Separate from COROTUM_TELEMETRY CLI ingest.
| Name | How to set | Notes |
|---|---|---|
UMAMI_HOST | vars | Umami origin, no trailing slash, for example https://stats.example.com |
UMAMI_WEBSITE_ID | vars | Umami website id |
Both must be set. The Worker then loads script.js and recorder.js in the document head. Product clicks use data-umami-event; the UI does not send emails, skill names, or workspace ids.
Email magic links
Section titled “Email magic links”The /sign-in page supports GitHub, Google, and passwordless email links. Email requests always show the same confirmation for new and existing addresses, so the flow does not disclose account existence. Links are hashed at rest, expire, can be used once, and reject unsafe redirects.
A self-hosted deployment must provide its own email-delivery configuration. It must not use Corotum-owned Cloudflare Email Service resources, sender domains, or hosted credentials, and it does not require hosted entitlement or Creem. The shipped Worker integration expects an EMAIL Cloudflare send_email binding and an AUTH_EMAIL_FROM sender address; configure both in your own Cloudflare account and for your own onboarded sending domain, or replace the application email boundary with your independently operated transport.
For local development, put only the email sender setting in apps/web/.dev.vars:
AUTH_EMAIL_FROM=auth@corotum.comUse your own sender address in a real self-host. Set AUTH_EMAIL_FROM in vars and the same address in send_email.allowed_sender_addresses. EMAIL is not a .dev.vars secret: it is the Worker send_email binding declared in wrangler.jsonc. That binding path does not require an email API key. Enable Cloudflare Email Sending on your account, onboard your sending domain, and publish the DNS/authentication records Cloudflare supplies before testing real delivery.
Authentication and pairing remain available without a subscription. In contrast, the hosted corotum.com deployment uses its own Cloudflare Email Service binding and separately gates paid Cloud operations with Creem; self-hosted Cloud stays usable without Creem.
Optional CLI-side variables, used on devices rather than the Worker:
| Name | Purpose |
|---|---|
COROTUM_CLOUD_ORIGIN | Cloud origin for login, init cloud, and migrate. Also corotum config set origin |
COROTUM_RELEASE_BASE | CLI release origin for installers and cli-update |
Local development
Section titled “Local development”From the repository root:
bun installbun run db:migratebun run web:devLocal values go in apps/web/.dev.vars (email sender is documented below). COROTUM_ENVIRONMENT=development (the default when unset in local Worker dev) does not require BETTER_AUTH_URL or both OAuth providers. Production does.
For automatic-sync development, also run the events service in another terminal:
bunx wrangler dev --config apps/events/wrangler.jsoncThe web development Worker resolves its EVENTS service binding through Wrangler’s local service registry. Loopback HTTP/WebSocket origins are allowed by the CLI; non-loopback automatic sync requires HTTPS. The focused bun run --cwd apps/events test verifies real workerd sockets and hibernation. bun run --cwd apps/events test:clients builds the production web app and proves convergence using two standalone CLI processes, isolated homes, local D1, and the real events Worker.
Deploy
Section titled “Deploy”Deploy the events Worker first. From the repository root:
bun installbun run --cwd apps/events deploybun run web:buildapps/events/wrangler.jsonc names the Worker corotum-events, binds WORKSPACES to WorkspaceEvents, and declares the initial SQLite Durable Object migration. Keep that migration when deploying for the first time. The Worker disables workers.dev and preview URLs and needs no public route or hostname.
The main app must have this service binding in apps/web/wrangler.jsonc (use your deployed events Worker name if renamed):
"services": [ { "binding": "EVENTS", "service": "corotum-events" }]This is a Worker service binding, not an environment URL or secret. Keep the configured Cloud API rate-limiter bindings as well. The public app authenticates /api/v2/workspaces/:workspaceId/events upgrades and /api/v2/workspaces/:workspaceId/revision reads before consulting the private events service. Do not expose the events Worker directly or put device tokens in URLs.
From apps/web:
npx wrangler d1 migrations apply corotum --remotenpx wrangler deploywrangler deploy prints a *.workers.dev URL. That is a valid public origin. For a custom domain, attach it in the Cloudflare dashboard (Workers → this worker → Custom Domains) or add routes in wrangler.jsonc. BETTER_AUTH_URL and the OAuth callback URLs must be that exact origin, with no trailing slash.
Confirm the origin serves the site. Unauthenticated users go to /sign-in (GitHub, Google, or email magic link) and reach /dashboard after a session is created.
Operational setup
Section titled “Operational setup”- Install the official CLI on each device (install.md).
- Sign in at
/sign-inwith GitHub, Google, or an email magic link. A default workspace is created for the user. - Pair a device:
corotum login --origin https://cloud.example.comOr initialize Cloud and adopt selected local skills in one step:
corotum init cloud --origin https://cloud.example.cominit cloud opens the pairing browser flow when the device is not already logged in. Hosted entitlement is not required.
- Open
/dashboardfor skills, devices, target reports, and settings. The dashboard is a full product surface. Self-hosted billing UI states that Cloud functionality is free and has no billing portal. - Mutate Cloud desired state with the same CLI skill commands as Git Sync (
add,adopt,remove,unmanage,restore,update,set-ref) or with WebMCP / the same-origin dashboard mutation API. Then runcorotum syncon each device, or opt in withcorotum auto enable; the device reports the applied revision. There is no remote forced sync. Zero agents is valid. - Revoke a device from
/dashboard/devices. Revoke invalidates only that device token and keeps remote machine data. corotum logout --origin https://cloud.example.comrevokes the local token.
Pairing codes expire after 10 minutes. Cloud may return 426 Upgrade Required when the CLI is older than 0.1.0. Git Sync is independent of that check.
Supported agents and migration
Section titled “Supported agents and migration”Supported agents are listed in the CLI reference. v0.5 manages global/user-level skills only.
Git ↔ Cloud migration:
corotum migrate cloud --strategy replace --origin https://cloud.example.comcorotum migrate git git@github.com:example/corotum-state.git --strategy merge --origin https://cloud.example.comSee migration.md. Identity (skill id, source, ref, lock revision, hash, targets) is preserved. The canonical local store is not rewritten by migrate.
Limitations
Section titled “Limitations”- v0.5 binaries are unsigned. Official installers are the supported install path.
- Automatic sync is Cloud-only and opt-in; no remote forced sync or automatic upstream skill updates.
- No project-level skills, teams/RBAC, or Windows arm64.
- Production auth requires both GitHub and Google OAuth.
- Self-hosted Cloud does not offer a Creem checkout or billing portal.
- Manual binary download is not an officially supported installation method.