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 and KV available
- A domain on Cloudflare (or any zone you can point at Workers)
- A GitHub OAuth App whose callback hits your gateway
- Node 22+ and Bun to build and deploy
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 a GitHub OAuth App
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.
4. Set Worker secrets
bun run secret GITHUB_CLIENT_ID bun run secret GITHUB_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. 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.
7. 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.
npx @exeora/cli gateway use https://your.example.com npx @exeora/cli connect
Or in one step, which is the same thing followed immediately by connect.
npx @exeora/cli 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 four secrets plus a development GitHub OAuth App whose callback is http://localhost:8787/oauth/callback/github. 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.