Self-hosting
Run your own Exeora gateway on Cloudflare Workers.
Exeora is open source under the AGPL-3.0. You can run the gateway yourself on Cloudflare Workers with your own domain, database and OAuth app. This page is the setup path; the repository at github.com/leynier/exeora is the source of truth for every file named below.
What you need
- A Cloudflare account with Workers, D1, KV, Pipelines and R2 Data Catalog
- A domain on Cloudflare (or any zone you can point at Workers)
- A GitHub OAuth App, Google OAuth client, or both, whose callbacks hit your gateway
- Node 22+ and Bun to build and deploy
- A recurring runner for archive maintenance; GitHub Actions is included
1. Clone and install
git clone https://github.com/leynier/exeora.git cd exeora bun install
2. Create the Cloudflare resources
Put the returned ids into apps/gateway/wrangler.jsonc. Ids are not secrets.
bunx wrangler d1 create exeora bunx wrangler kv namespace create OAUTH_KV
Also set EXEORA_BASE_URL and the routes pattern in that file to your hostname, and create a proxied DNS record for it (a single AAAA on @ pointing at 100:: with the proxy on is enough).
3. Create identity provider credentials
Configure at least one provider. A provider appears only when both of its secrets are present.
GitHub
Homepage and callback must match your hostname. Callback path: /oauth/callback/github. An OAuth App admits one callback URL, so keep a separate app for local development if you need one.
Create a Web application client in Google Auth Platform, request only openid email profile, and register /oauth/callback/google on your hostname. Use a separate client with http://localhost:8787/oauth/callback/google for local development.
4. Set Worker secrets
bun run secret GITHUB_CLIENT_ID bun run secret GITHUB_CLIENT_SECRET bun run secret GOOGLE_CLIENT_ID bun run secret GOOGLE_CLIENT_SECRET bun run secret COOKIE_SECRET bun run secret REQUEST_STATE_SECRET
Generate the two secrets with openssl rand -hex 32, and use different values than development. REQUEST_STATE_SECRET signs approvals that travel through an AI client; keep it separate from COOKIE_SECRET on purpose.
5. Administrators
On a fresh database, the first account to sign in becomes the admin. That person can open the administration panel and act on every account. Protect that first sign-in the same way you would protect any root account.
To name operators ahead of time instead, set the Worker var ADMIN_EMAILS to a comma-separated list:
# apps/gateway/wrangler.jsonc, under "vars": "ADMIN_EMAILS": "you@example.com,ops@example.com"
Matching addresses are promoted when they register; everyone else stays ordinary. It is a var, not a secret. Leave it unset to keep the first-user rule.
6. Provision the required audit archive
Audit storage is part of the operating contract, not optional analytics. D1 first persists a bounded outbox intent; the durable history, Activity queries and usage rollups live in an Iceberg table written through Cloudflare Pipelines. If the intent cannot be written, Exeora does not execute the tool. A temporary Pipeline failure stays queued for retry.
Follow apps/gateway/pipelines/readme.md to create the Pipeline, R2 Data Catalog table and three separately held catalog tokens. Copy the stream binding and archive coordinates into wrangler.jsonc, then set the two secrets the Worker holds:
bun run secret AUDIT_R2_SQL_TOKEN bun run secret AUDIT_MAINTENANCE_SECRET
Run .github/workflows/audit-maintenance.yml nightly with repository secrets AUDIT_CATALOG_URI, AUDIT_R2_WAREHOUSE, AUDIT_R2_MAINTENANCE_TOKEN, GATEWAY_URL and the same AUDIT_MAINTENANCE_SECRET. It drains leased erasure work twice, at least 24 hours apart, and refuses to prune retention while the usage rollup is behind.
The repository's complete self-hosting guide contains the exact token permissions and maintenance setup.
7. Migrate and deploy
bun run db:migrate bun run deploy
deploy builds the landing and dashboard, then deploys the Worker. The Worker serves the built site through its ASSETS binding.
8. Point the CLI at your gateway
The published CLI talks to https://exeora.dev until you tell it otherwise. Tell it once, and the choice is stored: every later command talks to your gateway with no flag and no variable.
exeora gateway use https://your.example.com exeora connect
Or in one step, which is the same thing followed immediately by connect.
exeora connect --gateway https://your.example.com
exeora gateway prints the active one and exeora gateway reset goes back to the hosted one. One gateway is active at a time: switching forgets the machine registration, the projects and the session belonging to the previous one, because a device id issued by one gateway's database means nothing to another. The CLI says what it is about to forget and asks first.
EXEORA_GATEWAY_URL still works and outranks the stored value, which makes it the right tool for one shell or one command rather than for living on a self-hosted gateway.
Local development
Copy apps/gateway/.dev.vars.example to .dev.vars and fill in the same provider credentials plus the two signing secrets. Development callbacks are http://localhost:8787/oauth/callback/github and http://localhost:8787/oauth/callback/google. Then:
bun run db:migrate:local bun run dev
License
Self-hosting is permitted under the AGPL-3.0. If you modify Exeora and offer it as a network service, you must offer the corresponding source to the users of that service.