StarbridgeDocs
Self-host

Self-host

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

  1. With Docker installed, clone the latest release, here v0.1.6:

    git clone --branch v0.1.6 https://github.com/T0mSIlver/starbridge
    cd starbridge
  2. Write 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 with openssl rand -hex 32.

    PUBLIC_URL=https://starbridge.example
    OWNER_TOKEN=change-me
    RELAY_URL=https://starbridge.run

    RELAY_URL sends 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.

  3. Build and start the server, on 127.0.0.1:8080, and the web app, on 127.0.0.1:3000:

    docker compose -f server/compose.yaml up -d --build
  4. Route 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
      }
    }
  5. 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".

  6. 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 | sh

    On Windows: irm https://starbridge.example/install.ps1 | iex. Your landing page shows the same commands. A CLI installed another way pairs with starbridge setup --server https://starbridge.example, or with STARBRIDGE_SERVER set. 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.

  1. In server/.env, remove RELAY_URL and add Web Push keys. Make them with bunx 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.lan or ALLOW_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.

  2. On an ntfy server with access control, let anyone publish to UnifiedPush topics, which the ntfy app names up and a random id, and sign the ntfy app in as a user who can read them:

    ntfy access everyone 'up*' write-only
  3. On 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 --build

A 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 an https:// 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.db in the image.
TRUST_PROXY
Read the client's IP from the last X-Forwarded-For hop. 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/callback for the web and $PUBLIC_URL/v1/auth/github/callback/app for 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_TOKEN makes 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. 1 still 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/relay for other servers, as starbridge.run does. The server refuses it with ALLOW_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 install
OWNER_TOKEN=change-me PORT=8080 bun run server/src/main.ts
pnpm --filter @starbridge/server test

usage [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