Self-hosting
The whole product is one static binary: amadan-hub. Front door, web UI, and per-repo instance servers, all self-contained, assets embedded, CGO_ENABLED=0, and no external requests from any page.
Run it on your own box, under your own domain, with an outgoing mail server for sign-in links.
Try it locally first#
hack/local-deploy-demo.shBuilds every binary and drives a real end-to-end journey — sign-in, key registration, an encrypted push and clone, a plaintext push and clone with a real token — as separate OS processes under a scratch directory that cleans itself up. No credentials needed, no mocks, nothing left behind.
Set AMADAN_DEMO_KEEP_RUNNING=1 to leave the stack up and poke at it in a browser afterwards.
Configuration#
The hub is configured entirely by environment variables.
Required#
AMADAN_ORIGIN— the hub's public origin, e.g.https://amadan.example.com. Keymail treats it as the hub's OAuthclient_id, so there is nothing to register with Keymail beforehand. It is also the base of every emailed sign-in link and of public repos' Go module paths, and passkeys belong to it, so changing it later means everyone adds their passkeys again. It must behttpsunless the hub runs on localhost, and the hub will not start without it.AMADAN_AUTH_INSTANCE_KEY— a random secret (openssl rand -hex 32). It seals each sign-in while it is in progress. The hub will not start without it.AMADAN_DATA_DIR— where the hub's own SQLite database, every repo instance's SQLite file, and the materialized git state live. Defaults to./amadan-data, which is not a production-safe default.AMADAN_INTERNAL_KEY— read the section below before deploying. This is the one people miss.
Optional#
AMADAN_SMTP_HOST,AMADAN_SMTP_PORT,AMADAN_SMTP_USER,AMADAN_SMTP_PASS,AMADAN_SMTP_FROM— outbound mail, all five or none. Without them, only addresses with a Keymail inbox can sign in, and invitations are shared as links.AMADAN_KEYMAIL_SERVERS— a comma-separated list of the Keymail servers this hub will use, eachhostorhost:port. Leave it unset and each address's own domain says where its Keymail lives. Set it, and an address whose server isn't on the list gets an emailed link instead. Set it to nothing, and every address gets an emailed link.AMADAN_GIT_RL_RPS— requests per second per client IP for unauthenticated public-tier git reads. Token bucket, burst around 10. Defaults to 5. An unset or unparseable value falls back to the default rather than disabling the limiter.AMADAN_MINTER—local(the default) orplatform. Uselocal. See the known gap below.AMADAN_REPO_BIN— path to a standaloneamadan-repobinary for the local minter to spawn. Leave it unset in production: the hub then self-execs its own binary asamadan-hub repo …, so one shipped artifact is both the front door and the repo server, and nothing has to be co-located on the box.
Set for you, unless you run instances yourself#
AMADAN_REPO_PATH tells a repo instance which repo it is, as the {ns}/{repo} people address it by. Every signed request to the encrypted tier is verified under a context bound to that name, so an instance that does not know it authenticates nobody.
The hub sets it when it spawns an instance, so there is nothing to do here. It only becomes yours if you run amadan-repo by hand — a standalone instance with no AMADAN_REPO_PATH will serve public reads and refuse every signature.
AMADAN_INTERNAL_KEY — read this before deploying#
This key authenticates the hub to its own repo instances. Set it explicitly, to 32 bytes as hex (64 characters) or base64:
openssl rand -hex 32If you don't set it, the hub generates one and persists it to a file under AMADAN_DATA_DIR — or, if that is also unset, under the system temp directory.
That fallback exists so a first run works, and it is not safe to rely on in production. On ephemeral storage the key vanishes on restart and the hub can no longer talk to the instances it created. Set it explicitly before going live.
Known gap: AMADAN_MINTER=platform#
Only AMADAN_MINTER=local is implemented. platform is a documented stub that fails the hub at startup, by design, rather than half-working.
With local, the hub is one long-lived process that spawns a repo instance per repo on its own machine. That is the recommended — and currently the only — configuration.
The consequence worth knowing: you cannot yet keep your ciphertext on hardware you own while somebody else's hub handles names and membership. The architecture leans that way (every repo is its own hibernating instance server the hub reaches by URL), but today a hub only mints instances locally.
Your hub hosts Go modules#
Any public-tier repo on your deployment is a Go module host, with no configuration. The hub answers Go's ?go-get=1 probe with a go-import meta tag built from AMADAN_ORIGIN:
go install your-hub.example.com/<ns>/<repo>/cmd/<tool>@latestOnly public repos. Private and encrypted repos fall through untouched — the tag would otherwise both leak that a repo exists and offer closed source as a module.
Related#
docs/deploy.mdin the repository is the full operator's guide, including production checklists and the CARLOS deployment shape.- Tiers and encryption — what your server will and won't be able to read.
- CI — runners are member machines, so there is no CI infrastructure for you to operate.