Tangle ships as two long-lived Bun processes plus Postgres and (optionally) an S3-compatible bucket. Repository content lives on disk as plain bare git repos.
| Process | Entry | Port | What it serves |
|---|---|---|---|
| API | src/server.ts |
3000 | All JSON routes (/login, /repos/..., /me/...) and git Smart-HTTP (/<owner>/<repo>.git/...). |
| Web | src/web/serve.ts |
3001 | The SPA (src/web/app.tsx); proxies /api/* through to the API. |
In production, Caddy fronts both. *.git/* paths bypass the SPA proxy and go straight to the API; everything else flows through the web container.
| Concern | Where it lives |
|---|---|
| User accounts, repos, issues, comments | Postgres (the system of record) |
| Bare git repositories | Disk, under REPO_DIR/<owner>/<repo>.git |
| Avatars, release assets | Pluggable: s3 driver or local driver (single host) |
The git Smart-HTTP route module is the only code that reads or writes the bare repos. Everything else (issues, comments, etc.) goes through Postgres.
src/server.ts is the composition root. It:
- Builds a typed config via
@atlas/config - Opens a Postgres connection via
@atlas/db - Creates a
StorageHandlefor attachment blobs - Resolves and creates
REPO_DIR - Runs migrations from
./migrations - Registers routes from each feature module
- Starts
Bun.servewith security headers wired around the router
Each feature module under src/<feature>/index.ts exports a single factory — authRoutes, userRoutes, repoRoutes, gitRoutes, etc. — that takes (db, secret, ...) and returns an array of routes.
users(usernameis unique and shares a namespace withorgs.login)sessions— server-side row for every issued JWT, keyed byjti; revocation flips a columnapps— personal access tokens; stored as SHA-256 hashssh_keys— OpenSSH public keys, deduped by SHA256 fingerprintorgs,org_membersrepos—(owner_kind, owner_id, owner_login, name)withUNIQUE(owner_login, name)repo_collaborators— explicit per-repo grants; can be byuser_idor by pendingemailissues,pulls— share a per-repo numbering pool (mirrors GitHub UX)comments— polymorphic on(subject_kind, subject_id)covering both issues and pullsstars,releases,release_assets,webhooks,webhook_deliveriesaudit_events,rate_limits,invites,password_resets,webauthn_*
Schema lives in two places — src/schema/index.ts (for @atlas/db query builders) and migrations/<n>_<name>/{up,down}.sql. Migrations are the source of truth at runtime.
Three credential types, all funneled through requireAuth (src/auth/guard.ts):
| Channel | Identifier | Notes |
|---|---|---|
| Web session | JWT with jti claim |
Backed by a sessions row; revocable |
| Personal access token | tangle_pat_… opaque |
Scoped (repo, repo:read, repo:write, admin); used by CLI git |
| Git over HTTP Basic | PAT in password field | Decoded inline by src/git/index.ts — passwords are not accepted |
src/permissions/index.ts resolves effective access for (user, repo) to a RepoAccess of { read, write, admin, role }. The rules:
- Repo owner (user-owned) →
owner(full) - Org owner (org-owned) →
admin - Org member →
readerbaseline - Explicit
repo_collaboratorsrow promotes (never demotes) above the baseline - Public repos grant
readto everyone, including unauthenticated callers - Archived repos drop
writeandadminto read-only
src/git/index.ts mounts three routes on the API (note: not under /api):
GET /<owner>/<repo>.git/info/refs?service=git-upload-pack
GET /<owner>/<repo>.git/info/refs?service=git-receive-pack
POST /<owner>/<repo>.git/git-upload-pack
POST /<owner>/<repo>.git/git-receive-pack
src/git/protocol.ts shells out to the upstream git-upload-pack / git-receive-pack binaries with --stateless-rpc and pipes stdin/stdout. Authentication is HTTP Basic with a PAT in the password slot.