Serve HTTPS by default with a local CA
The installer generates a CA and a 397-day certificate into $COBBLE_HOME/certs (in the app image, with the host's addresses), mounts the certificate, key and CA into the container and serves TLS from Bun on the published port. --no-https opts out; --https-cert/--https-key use a real certificate, --https-host adds names and --https-regen replaces the CA. The installer and the image health check use curl -k when TLS is on. The CA is served at /ca.crt and as an iOS/macOS profile at /ca.mobileconfig for one-tap install. Development picks up certs/ automatically, `bun run dev:phone` generates and starts, and a plain-HTTP listener on PORT + 1 redirects to HTTPS. Port sharing is off, so a stale dev server fails instead of silently answering on the same port.
This commit is contained in:
@@ -26,10 +26,13 @@ missing):
|
||||
curl -fsSL https://url.moduletrace.com/cobble | sudo bash
|
||||
```
|
||||
|
||||
Open the address it prints, usually `http://<your-ip>:7227`, and create the
|
||||
super admin account. The first signup also creates the coordinator and the
|
||||
main computer, which pulls the sandbox image in the background. Give it a
|
||||
few minutes.
|
||||
Open the address it prints, usually `https://<your-ip>:7227`. The
|
||||
certificate is signed by a local CA the installer made, so the browser warns
|
||||
until you trust it: open `https://<your-ip>:7227/ca.crt` on the device and
|
||||
install it (see [HTTPS](#https)), then reload and create the super admin
|
||||
account. The first signup also creates the coordinator and the main
|
||||
computer, which pulls the sandbox image in the background. Give it a few
|
||||
minutes.
|
||||
|
||||
Then open **Settings > Models**, add an OpenRouter key (or another provider)
|
||||
and wait for the model list to sync. This is what the bots think with.
|
||||
@@ -43,13 +46,47 @@ and wait for the model list to sync. This is what the bots think with.
|
||||
- Docker Desktop on macOS works for development and small installs, but
|
||||
Linux is the supported host.
|
||||
- Port 7227, or another port with `--port`.
|
||||
- HTTPS in front of the app before you expose it to the internet. Web Push
|
||||
and installable PWA features need it. A Cloudflare Tunnel or Caddy in
|
||||
front of `127.0.0.1:7227` is enough.
|
||||
- HTTPS is on by default, with a certificate for this machine. Exposing the
|
||||
app publicly? Use `--https-cert` and `--https-key` with a real
|
||||
certificate, or `--no-https` behind a proxy or tunnel.
|
||||
|
||||
The installer binds to `0.0.0.0` so the app is reachable from your phone on
|
||||
the same network. Use `--bind 127.0.0.1` when a reverse proxy is the only
|
||||
thing that should reach it.
|
||||
thing that should reach it (with `--no-https`).
|
||||
|
||||
## HTTPS
|
||||
|
||||
The installer serves HTTPS by default. It generates a local CA and a
|
||||
certificate for this machine into `$COBBLE_HOME/certs` and mounts them into
|
||||
the container read only. The CA is kept across updates; the certificate is
|
||||
re-issued when the machine's addresses change or it is within 30 days of
|
||||
expiring, and a rerun of the updater does both.
|
||||
|
||||
Trust the CA once on each device. Until it is trusted the browser warns, and
|
||||
the installed app, its service worker and Web Push stay off:
|
||||
|
||||
- **Download:** open `https://<this machine>:7227/ca.crt` on the device, or
|
||||
copy `$COBBLE_HOME/certs/ca.crt`.
|
||||
- **iPhone or iPad:** open `https://<this machine>:7227/ca.mobileconfig`
|
||||
instead. iOS offers the profile in one tap; install it, then turn it on
|
||||
under Settings > General > About > Certificate Trust Settings.
|
||||
- **Android:** Settings > Security > Encryption & credentials > Install a
|
||||
certificate > CA certificate.
|
||||
- **macOS:** add it to the login keychain and set it to Always Trust.
|
||||
- **Linux:** copy it into `/usr/local/share/ca-certificates/` and run
|
||||
`sudo update-ca-certificates`.
|
||||
|
||||
Options for the certificate:
|
||||
|
||||
- A VPN name or address the installer did not detect: `--https-host NAME`,
|
||||
repeatable.
|
||||
- Your own certificate (a public CA, an internal CA, `tailscale cert`):
|
||||
`--https-cert FILE --https-key FILE`. Rerun the installer when it is
|
||||
renewed.
|
||||
- Replace the local CA, for example after trusting the wrong one:
|
||||
`--https-regen`. Every device must trust the new CA.
|
||||
- Plain HTTP with a proxy or tunnel in front: `--no-https`.
|
||||
- Turn HTTPS back on after `--no-https`: `--https`.
|
||||
|
||||
## Update and uninstall
|
||||
|
||||
@@ -80,6 +117,12 @@ curl -fsSL https://url.moduletrace.com/cobble | sudo bash -s -- --uninstall --pu
|
||||
| `--image IMAGE` | Container image, default `ghcr.io/moduletrace/cobble` |
|
||||
| `--version TAG` | Image tag, default `latest` |
|
||||
| `--extra-network NET` | Join another Docker network, for a tunnel or reverse proxy |
|
||||
| `--no-https` | Serve plain HTTP, for a proxy or tunnel in front |
|
||||
| `--https` | Serve HTTPS again after `--no-https` |
|
||||
| `--https-host VALUE` | Names or IPs the certificate covers, repeatable |
|
||||
| `--https-cert FILE` | Use your own certificate instead of the local CA |
|
||||
| `--https-key FILE` | Its private key, given with `--https-cert` |
|
||||
| `--https-regen` | Replace the local CA and the certificate |
|
||||
| `--force` | Recreate the container even when it is current |
|
||||
| `--no-pull` | Use an image that is already on the machine |
|
||||
| `--skip-backup` | Update without a database backup |
|
||||
@@ -96,6 +139,7 @@ Everything that must survive an update lives under the install directory:
|
||||
.env # generated once, mode 600
|
||||
install.conf # installer state
|
||||
backups/ # pre-update database snapshots
|
||||
certs/ # the local CA and the certificate (HTTPS)
|
||||
data/
|
||||
db/cobble
|
||||
db/.backups/
|
||||
@@ -103,13 +147,15 @@ Everything that must survive an update lives under the install directory:
|
||||
computers/<id>/ # each computer's home directory
|
||||
```
|
||||
|
||||
`.env` holds the port and the encryption keys. The keys encrypt sessions and
|
||||
settings, so keep a copy and do not rotate them without re-entering provider
|
||||
keys and other secrets. Provider keys, MCP servers, tools and app settings
|
||||
are managed in the app, not in `.env`.
|
||||
`.env` holds the port, the encryption keys and the `TLS_*` certificate
|
||||
paths. The keys encrypt sessions and settings, so keep a copy and do not
|
||||
rotate them without re-entering provider keys and other secrets. Provider
|
||||
keys, MCP servers, tools and app settings are managed in the app, not in
|
||||
`.env`.
|
||||
|
||||
Edit `.env` and run `docker restart cobble` to apply changes. The installer
|
||||
only appends missing values and keeps what is already there.
|
||||
only appends missing values and keeps what is already there, apart from the
|
||||
port and the `TLS_*` entries it manages.
|
||||
|
||||
The app container is started with the Docker socket mounted and
|
||||
`MAIN_SERVER=1`, so it can create computer containers and run cron, the
|
||||
@@ -137,16 +183,42 @@ bun run db:schema
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Open `http://localhost:7227`. With no users, every page goes to
|
||||
`/auth/signup` to create the super admin. For a headless setup:
|
||||
Open `http://localhost:7227`, or `https://localhost:7227` once `certs/`
|
||||
exists. With no users, every page goes to `/auth/signup` to create the super
|
||||
admin. For a headless setup:
|
||||
|
||||
```bash
|
||||
SEED_SUPER_ADMIN_PASSWORD=... bun run seed
|
||||
```
|
||||
|
||||
The development server reads the same `TLS_*` variables as the container, so
|
||||
it can serve HTTPS too. That is how to try the app from a phone over a VPN,
|
||||
where the secure context (installed app, Web Push, voice) needs it:
|
||||
|
||||
```bash
|
||||
bun run dev:phone -- --host cobble-laptop.tailnet-name.ts.net
|
||||
```
|
||||
|
||||
That generates `certs/` and starts the dev server with it, with no `.env`
|
||||
edits. Afterwards a plain `bun run dev` reuses the certificate whenever
|
||||
`certs/server.crt` and `certs/server.key` exist (production never does).
|
||||
`bun run gen:certs` on its own regenerates them if you need to add a name.
|
||||
|
||||
Open `https://<the name you passed>:7227` on the phone and install the CA
|
||||
from `https://<the name you passed>:7227/ca.mobileconfig` first. On the
|
||||
laptop, trust the same `certs/ca.crt` once, so `https://localhost:7227`
|
||||
stops warning.
|
||||
|
||||
With `certs/` present the dev server also listens one port above (`7228`)
|
||||
for plain HTTP and redirects to the HTTPS page, so a stray
|
||||
`http://localhost:7228/...` link still lands. The HTTPS port itself cannot
|
||||
answer plain HTTP, so `http://localhost:7227` still fails; use
|
||||
`DEV_HTTP_PORT` to move the redirect.
|
||||
|
||||
| Command | Purpose |
|
||||
| -------------------- | ------------------------------------ |
|
||||
| `bun run dev` | Development server |
|
||||
| `bun run dev:phone` | Dev server over HTTPS for a phone |
|
||||
| `bun run start` | Production server on the host |
|
||||
| `bun run typecheck` | Type-check |
|
||||
| `bun run test` | Run the test suite |
|
||||
@@ -154,6 +226,7 @@ SEED_SUPER_ADMIN_PASSWORD=... bun run seed
|
||||
| `bun run seed:bots` | Add a sample crew |
|
||||
| `bun run db:schema` | Sync the schema and types |
|
||||
| `bun run gen:favicon` | Regenerate favicons and PWA assets |
|
||||
| `bun run gen:certs` | Generate a local HTTPS certificate |
|
||||
|
||||
The app is Bun, [`@moduletrace/bunext`](https://github.com/Moduletrace/bunext),
|
||||
SQLite via [`@moduletrace/bun-sqlite`](https://github.com/Moduletrace/bun-sqlite),
|
||||
|
||||
Reference in New Issue
Block a user