Run your own Starbridge server instead of starbridge.run. The server is one Bun process and a
SQLite file. It stores encrypted items and relays notifications; it holds no key that opens
them. Its routes are in PROTOCOL.md.
What you run
- The server, which answers
/v1/*and/healthz. - The web app, which serves every other path.
- A reverse proxy in front of both, on one origin, such as
https://starbridge.example. The session cookie and the server's cross-site check need the API and the web app on the same origin.
Set it up
With Docker installed, clone the latest release, here
v0.1.6:git clone --branch v0.1.6 https://github.com/T0mSIlver/starbridge cd starbridgeWrite the server's settings in
server/.env. The owner token signs you in to the server's one account; make it long and random, for example withopenssl rand -hex 32.PUBLIC_URL=https://starbridge.example OWNER_TOKEN=change-me RELAY_URL=https://starbridge.runRELAY_URLsends Android and browser notifications through starbridge.run, which sees what Notifications lists. To keep every notification on your own servers, leave it out and follow Without the relay. Environment lists the other settings.Build and start the server, on
127.0.0.1:8080, and the web app, on127.0.0.1:3000:docker compose -f server/compose.yaml up -d --buildRoute both through your reverse proxy. The web app sends its pages and scripts uncompressed, so have the proxy compress them. With Caddy, which also gets the TLS certificate:
starbridge.example { # The web image leaves compression to the proxy. encode zstd gzip # The web image sets the page's Content-Security-Policy; the proxy sets the rest. header { Strict-Transport-Security "max-age=31536000" X-Content-Type-Options nosniff Referrer-Policy same-origin defer } @server path /v1/* /healthz /healthz/* handle @server { reverse_proxy localhost:8080 } handle { reverse_proxy localhost:3000 } }Sign in. On the web, open your origin, pick "Sign in" and enter the owner token. In the Android app, pick "Use your own server", enter your origin, then the owner token. With GitHub sign-in set up, both can use it on your server too, and the web page shows "Continue with GitHub" first, with the owner token behind "Use your own server".
Install the CLI on each machine that runs your harnesses. Its install script carries its
PUBLIC_URL, so setup pairs the machine with your server:curl -fsSL https://starbridge.example/install.sh | shOn Windows:
irm https://starbridge.example/install.ps1 | iex. Your landing page shows the same commands. A CLI installed another way pairs withstarbridge setup --server https://starbridge.example, or withSTARBRIDGE_SERVERset. Setup ends with a test question: close the app first to check that notifications reach the phone.
Notifications
Android notifications go through Firebase, whose credentials belong to the app's project. With
RELAY_URL, your server sends them through starbridge.run's relay; Web Push goes through it too,
unless you set your own VAPID keys. So a self-hosted server still sends its pushes through
starbridge.run unless it uses UnifiedPush for Android and its own VAPID keys for browsers.
For each push, the relay sees the phone's FCM token or the browser's push endpoint and the payload: the item's kind, its id, the sender's id, the id of the item it answers, when a snoozed question comes back, and the item sealed to that device, which it cannot open. That is the same activity timeline your server sees, tied to a push address. It stores none of it, and counts relayed pushes only by type and result.
Without the relay, browsers get notifications only with your own VAPID keys, and the Android app only through UnifiedPush. Without UnifiedPush, the Android app checks for new items every 10 seconds while it is open, and nothing reaches the phone while it is closed. The web page still updates on its own.
Without the relay
UnifiedPush delivers to the Android app through your own ntfy, with nothing through Google's Firebase or starbridge.run. Your VAPID keys send browser notifications without starbridge.run; each browser's own push service still carries them, encrypted, such as Google's for Chrome.
In
server/.env, removeRELAY_URLand add Web Push keys. Make them withbunx web-push generate-vapid-keys:VAPID_PUBLIC_KEY=... VAPID_PRIVATE_KEY=...If your ntfy is on a private address or plain HTTP, add its host, such as
ALLOW_PRIVATE_PUSH_ENDPOINTS=ntfy.lanorALLOW_PRIVATE_PUSH_ENDPOINTS=192.168.1.20. Then restart:docker compose -f server/compose.yaml up -d. A browser that turned notifications on through the relay subscribes again with your key the next time it opens the page.On an ntfy server with access control, let anyone publish to UnifiedPush topics, which the ntfy app names
upand a random id, and sign the ntfy app in as a user who can read them:ntfy access everyone 'up*' write-onlyOn the phone, install the ntfy app and set its default server to yours. In Starbridge, pick UnifiedPush under Settings → Notifications → Delivered through.
The phone must reach your ntfy, and your Starbridge server must reach it too.
Upgrade
A new server brings its database's schema up to date when it starts. Copy the database first, with the server running:
sudo sqlite3 "$(docker volume inspect -f '{{.Mountpoint}}' starbridge_data)/starbridge.db" ".backup 'starbridge-before.db'"Then check out the new release's tag, such as v0.1.6, and rebuild:
git fetch --tags && git checkout v0.1.6
docker compose -f server/compose.yaml up -d --buildA server refuses a database a newer release has already upgraded, so to go back to an older release, restore the copy you made before upgrading.
Environment
Basics
PUBLIC_URL- The origin people open, such as
https://starbridge.example. The server uses it for sign-in redirects and the cross-site check, and anhttps://origin makes the session cookie Secure. Default:http://localhost:$PORT. PORT- Default: 8080.
DB_PATH- The SQLite file. Default:
./data/starbridge.db, or/data/starbridge.dbin the image. TRUST_PROXY- Read the client's IP from the last
X-Forwarded-Forhop. Turn it on behind a reverse proxy. Default: off.
Sign-in
OWNER_TOKEN- Signs you in to the server's one owner account. Set it, GitHub sign-in, or both. Unset by default.
GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET- GitHub sign-in, where each GitHub user gets their own account, from a GitHub OAuth app. Set both. Register two callback URLs in the OAuth app:
$PUBLIC_URL/v1/auth/github/callbackfor the web and$PUBLIC_URL/v1/auth/github/callback/appfor the Android app. GitHub refuses the app's sign-in when only the first is listed. Unset by default. SIGNUP_GITHUB_IDS- Comma-separated numeric GitHub user ids. When set, only these users can make an account, and neither Sign in with Apple nor
OWNER_TOKENmakes one; accounts that exist sign in as before. A value that is empty or not a list of ids stops the server at start. Unset by default: anyone can sign up. APP_REDIRECT_URI- Where GitHub sign-in sends the Android app. Only starbridge.run can vouch for the app, so its server sets
https://starbridge.run/app/auth. Default:starbridge://auth.
Notifications
RELAY_URL- The relay's origin, such as
https://starbridge.run. Notifications says what goes out without it. Unset by default. FCM_*,APNS_*,APPLE_*- Keys of the apps' own Firebase project and Apple team, for Android and iPhone pushes and Sign in with Apple. Only starbridge.run holds them; a self-hosted server uses the relay or UnifiedPush instead. The names are in
server/src/config.ts. Unset by default. VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY,VAPID_SUBJECT- Web Push keys, to send browser notifications without the relay. Make them with
bunx web-push generate-vapid-keys. Unset by default. ALLOW_PRIVATE_PUSH_ENDPOINTS- Hosts on your own network that push endpoints may name, over plain HTTP too, such as an ntfy: comma-separated names or addresses, without ports. Any account on the server can point its pushes at them.
1still allows every private host, but the server warns at startup: every account could make it call any host on your network. Default: none. RELAY_MODE- Serve
POST /v1/relayfor other servers, as starbridge.run does. The server refuses it withALLOW_PRIVATE_PUSH_ENDPOINTS, since the relay pushes for anyone who asks. Default: off. PUSH_TIMEOUT_MS- How long one request to a push service may take. Default: 10000.
PUSH_INLINE_LIMIT- The largest push payload that carries the encrypted item itself; larger ones carry only its id. Default: 3072 bytes.
Limits
MAX_MACHINES- Machines per account; phones and browsers don't count. Default: 5. starbridge.run sets 3.
MAX_WAIT_SECONDS- The longest an answer or pairing long-poll waits. Default: 300.
Run from source
The server alone, for development; web/README.md adds the web app.
pnpm installOWNER_TOKEN=change-me PORT=8080 bun run server/src/main.tspnpm --filter @starbridge/server testusage [days] prints the daily usage counts from DB_PATH and exits; the image runs it as
bun server.js usage. What it counts: src/usage.ts and /privacy.
DB_PATH=./data/starbridge.db bun run server/src/main.ts usage 14