Files
brain/RUNNING.md
T
Andrew S Erwin 4e421f6ea2
release gate / check (push) Failing after 6m43s
Address code review findings across API, web and deployment
High severity:
- wire TRUSTED_PROXY through Compose/deploy/docs and warn at web startup
- bucket public rate limits on effective client IP, add per-bookmark budget,
  and forward the signed browser identity from the share page
- disable first-run bootstrap in server deployments and document seeding
- serialize last-admin demotion in one advisory-locked transaction

Medium/low:
- cap and batch share recipient inserts
- add a shared outbound budget for preview/CardDAV/IMAP/signal work
- pin CardDAV dials to 443 and bound IMAP logout
- fix rightward kanban column drags and normalize cards on column delete
- classify backend outages in auth guards, add request timeouts and an
  error boundary, remove the dead login route, surface throttling
- enforce pinned/unpinned page reorder grouping server-side
- avoid preview work for stale bookmark versions; drop duplicate dialog fetch
- sequence-guard collection/tag/calendar/quick-find loads
- validate property config and fix MULTI_SELECT/DATE filter inputs
- document index maintenance; apply same-origin check to sign-out
- trim signup names, unique shared dates and kanban keys (migration 010),
  reset reference cache on identity changes, hide OIDC issuer, return 413
  for oversized direct bodies, escape Markdown link destinations
- remove dead template files and duplicate test helper

Verified with go build/vet, full PostgreSQL race suite, db convergence,
tsc, both ESLint configs and a Next production build.
2026-10-20 00:13:30 -05:00

382 lines
15 KiB
Markdown

# Running computer
This guide takes you from cloning the repository to signing in on your own
computer. Docker runs the web app, Go API, and PostgreSQL database together.
You do not need to install Go, Node.js, PostgreSQL, Make, or OpenSSL separately.
## Before you start
- **Docker with Docker Compose v2 or newer.** Docker Desktop includes Compose
on Windows and macOS. On Linux, install Docker Engine and the Compose plugin.
- **Git** to clone the repository. Alternatively, download and extract the
repository ZIP from GitHub.
- An internet connection for the first build to download images and packages.
- Local ports **3000** and **8080** available for the app.
Start Docker Desktop, or make sure the Docker service is running on Linux.
Open a terminal (PowerShell on Windows) and check:
```sh
docker info
docker compose version
```
Both commands should succeed. On Windows, Docker must be using Linux containers.
## 1. Download the app
```sh
git clone https://github.com/andrewthecodertx/computer.git
cd computer
```
If you downloaded a ZIP, open a terminal in the extracted folder instead.
Run the remaining commands from the folder containing `docker-compose.yml`.
## 2. Create your configuration
Copy the supplied example to a file named `.env` in the repository root.
Do this once on a new installation; keep an existing `.env` when updating.
**macOS / Linux:**
```sh
cp .env.example .env
```
**Windows PowerShell:**
```powershell
Copy-Item .env.example .env
```
Generate an authentication secret using Docker. This command works in either
terminal:
```sh
docker run --rm node:22-alpine node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
```
The final line of output is a 64-character random value. Open `.env` in a text
editor and replace `replace-with-a-random-secret` on the `AUTH_SECRET` line
with that value. Save the file.
For a new local installation, keep these other settings:
```dotenv
NEXTAUTH_URL=http://localhost:3000
COOKIE_SECURE=0
ADMIN_EMAILS=
POSTGRES_VOLUME=computer_pgdata
POSTGRES_VOLUME_EXTERNAL=false
```
The supplied `POSTGRES_PASSWORD=computer` works with the bundled database.
If you choose a different password, set it before the first startup; use a
random hexadecimal value to avoid special characters in the connection URL.
Keep the `OIDC_*` placeholders as supplied: use email/password login for now.
Keep `.env` for future starts and upgrades. It is ignored by Git. **Keep
`AUTH_SECRET` unchanged:** changing it invalidates sessions, and — unless a
separate `ENCRYPTION_KEY` is set — makes saved IMAP passwords unreadable.
Setting `ENCRYPTION_KEY` (a random hex string, backed up with the database)
decouples integration-credential encryption from session signing: you can
then rotate `AUTH_SECRET` without losing stored mailbox passwords, and rows
encrypted under the old derivation are re-encrypted transparently on first
use. Back up `.env` with your database backups: without the secrets it holds,
encrypted integration data in a database backup cannot be decrypted.
Mailbox connections always use verified TLS; plaintext IMAP logins are
rejected. Use the normal IMAP ports: **993** (the default) or **143**.
## 3. Build and start
```sh
docker compose up -d --build
```
The first build can take several minutes. Docker automatically:
1. Starts PostgreSQL and initializes a new database.
2. Applies any pending additive database migrations.
3. Starts the Go API.
4. Starts the Next.js web app.
Check startup status:
```sh
docker compose ps -a
```
Expected results:
| Service | Expected state |
|---|---|
| `db` | Running, healthy |
| `migrate` | Exited with code **0** — normal; it runs once per startup |
| `app` | Running, healthy |
| `web` | Running, healthy |
If `db`, `app`, or `web` still says `health: starting`, wait a few seconds and
check again. The `tests` service is optional and does not start during normal use.
## 4. Open the app and create your account
Open **http://localhost:3000** in your browser.
1. Choose **Sign up** and create an account with your email and a password.
2. Sign in using those credentials.
3. Add a bookmark to check that saving works.
With the default empty `ADMIN_EMAILS`, the first account whose email does not
end in `@example.com` becomes the administrator. Create your own account first.
Later accounts are regular users; an administrator can manage their roles
from the Admin screen.
Addresses listed in `ADMIN_EMAILS` are reserved and cannot be claimed through
public signup. To create an account for one of those addresses, use the
operator-provisioning command in [README.md](README.md#developer-commands).
The default setup is accessible only from this computer. Public bookmark
links are also local until you configure externally accessible hosting.
### What to test
- Create, edit, and delete a bookmark, including its URL, description, and tags.
- Organize bookmarks using tags, calendar dates, contacts, and kanban columns.
- Create a collection, add an existing bookmark to it (nothing is duplicated),
add a select property such as Status, and switch between the table and a
board view over the same membership.
- Write a Markdown page, switch between pages, and download its Markdown export.
- Share a bookmark, tag, or date with a second account and revoke the share.
- Open a public bookmark link in a signed-out browser window.
Pages autosave after a short pause and finish saving before switching page
tabs. If saving fails, the page remains **Unsaved** and a draft is kept in the
current browser tab. Reconnect and click **Save now**; tab-local drafts are
not a replacement for database backups. If another tab or device saved the
same page first, autosave pauses with a conflict banner — choose explicitly
between overwriting, saving a copy, or discarding; typing alone never
overwrites the newer version.
## Everyday commands
Run these from the repository root:
| Task | Command |
|---|---|
| Stop the app and keep your data | `docker compose down` |
| Start it again | `docker compose up -d` |
| Restart the web app and API | `docker compose restart app web` |
| Check service status | `docker compose ps -a` |
| Follow application logs | `docker compose logs -f app web` |
Press **Ctrl+C** to stop following logs; the app keeps running.
After changing `.env`, use `docker compose up -d` to recreate affected
containers with the new settings. A simple `restart` does not load changed
environment variables.
### Where your data lives
Bookmarks, accounts, pages, and settings live in the Docker PostgreSQL volume
named `computer_pgdata` by default. They survive normal stops, container
recreation, and image rebuilds.
**Do not use `docker compose down -v` or delete that volume if you want to
keep your data.** The volume is not a backup; back up the database separately.
### Backups and restore
Take dumps with the database running (review OPS-03: a dump is only a backup
once a restore from it has been proven):
```sh
docker compose exec -T db pg_dump -U computer -d computer | gzip > computer-$(date -u +%Y%m%d).sql.gz
cp .env env-$(date -u +%Y%m%d).bak # AUTH_SECRET / ENCRYPTION_KEY / POSTGRES_PASSWORD
```
Keep the dump **and** the `.env` snapshot together and copy both off-host:
stored IMAP passwords are AES-GCM ciphertext, and without the secret they
were encrypted under, a restored database cannot decrypt them. The
deployment entry point (`scripts/deploy.sh`, which runs
`scripts/server-deploy.sh` on the server) does this automatically before every
deploy into `/var/backups/computer` (0600 dumps, 15 retained); it starts a
stopped database rather than skipping the backup silently.
Restore into an isolated environment first to verify a dump before trusting
it. Use a separate checkout (or stop the live stack first) and a scratch
volume so the live `computer_pgdata` volume is never touched:
```sh
POSTGRES_VOLUME=computer_restore docker compose up -d db
gunzip -c computer-YYYYMMDD.sql.gz | docker compose exec -T db psql -U computer -d computer
# inspect the restored data, then remove the scratch container and volume
docker compose rm -sf db
docker volume rm computer_restore
```
Then restore the matching `.env` so the secrets can decrypt integration
data, and start the stack against the real volume. Migrations are additive,
so any current or newer application release reads an older dump; reverting to
an older data state means restoring that dump, not reverting SQL.
### Updating the app
Back up your database and `.env` before updating. Then run:
```sh
git pull --ff-only
docker compose up -d --build
docker compose ps -a
```
Keep your existing `.env` and database volume. Database migrations run
automatically during startup. The bundled PostgreSQL image already includes
the `pg_trgm` extension used by page search; the database owner role must be
allowed to create extensions if you point the app at an external database.
## Optional integrations
The core app works without any external services.
- **IMAP email:** In **Settings → IMAP**, enter your mail server, port
(**993** or **143**), TLS setting, username, password, and folder. Test the
connection, then save.
Add an email watcher from a bookmark's Signals panel. Enabled watchers are
checked every five minutes while the API is running.
- **Nextcloud contacts:** In **Settings → Nextcloud**, enter your Nextcloud
URL, username, and app password, then sync contacts. You can associate a
synced contact with a bookmark.
- **Browser alerts:** Allow notifications when prompted. Alert checks run
while the app is open in your browser.
- **Authelia:** This integration is intentionally unfinished. Setting OIDC
environment variables alone does not enable sign-in. See
[web/docs/AUTHELIA.md](web/docs/AUTHELIA.md) for integration details.
## Troubleshooting
### Docker cannot connect to its daemon
Start Docker Desktop or the Linux Docker service, then retry `docker info`.
On Linux, ensure your account has permission to use Docker.
### `docker compose` is not recognized
Install the Docker Compose plugin or update Docker Desktop. These instructions
use `docker compose` with a space, rather than the older `docker-compose`.
### `Set AUTH_SECRET in .env`
Confirm `.env` exists beside `docker-compose.yml`, has a nonempty `AUTH_SECRET`,
and was not saved as `.env.txt`. Follow step 2 to generate the value.
### Port 3000 or 8080 is already in use
Stop the other application or Docker stack using that port, then retry
`docker compose up -d --build`. Alternatively move this stack's host ports in
`.env` (`WEB_HOST_PORT`, `APP_HOST_PORT`). When you shift `WEB_HOST_PORT`,
`NEXTAUTH_URL` must be updated to the same browser-facing origin (for example
`http://localhost:3100`): the API proxy rejects requests whose Origin matches
neither, so sign-in works but saving fails with "Invalid request origin".
Recreate the stack with `docker compose up -d` after changing `.env`.
### The app does not open, or sign-in fails
Use **http://localhost:3000** consistently; keep `NEXTAUTH_URL` set to that
address and `COOKIE_SECURE=0` for local HTTP. Then check:
```sh
docker compose ps -a
docker compose logs --tail=100 db migrate app web
```
The API's database readiness check is available in your browser at
**http://localhost:8080/readyz**. A ready API returns `{"status":"ready"}`.
If PostgreSQL reports a password mismatch on an existing volume, restore
the original `POSTGRES_PASSWORD` in `.env`. Changing that variable does not
change the password of a database that was already initialized.
When asking for help, include service status and relevant error messages.
Remove passwords, secrets, and personal data from anything you share.
## Running on a remote server
The default Compose ports bind to `127.0.0.1`. For remote access, configure an
HTTPS reverse proxy to the web app on port 3000, set `NEXTAUTH_URL` to your
public HTTPS URL, and set `COOKIE_SECURE=1`. The database remains internal to
Docker.
### Rate-limit identity behind a proxy
Set `TRUSTED_PROXY=1` in `.env` when (and only when) the proxy in front of the
web container **overwrites** `X-Real-IP` with the browser's address — the
bundled nginx template does, and `scripts/server-deploy.sh` writes the flag
for server deployments. The Next server then signs that address and forwards
it to Go, so each browser gets its own login/signup/public-endpoint bucket.
Without it every client shares the web container's bucket (20 logins/minute
for the whole instance).
Do **not** set `TRUSTED_PROXY=1` while port 3000 is reachable directly:
clients could then choose their own rate-limit identity. The web process logs
a startup warning when `NEXTAUTH_URL` is HTTPS but the flag is missing.
### First-run administrator on an exposed server
The default empty `ADMIN_EMAILS` plus bootstrap lets the first signup become
administrator. That is fine locally, but on a public host it is a race with
whoever registers first after DNS goes live. For exposed deployments:
1. Put `ALLOW_BOOTSTRAP_SIGNUP=0` in `.env` (server-deploy.sh writes it).
2. List the operator in `ADMIN_EMAILS=you@example.com` and restart the app.
3. Provision the account with `SEED_EMAIL=you@example.com SEED_PASSWORD='...' make seed`.
`SIGNUP_ENABLED=0` additionally closes self-service registration. Public
signup can never grant `ADMIN` after bootstrap is disabled; recovery is
operator-driven.
### Large-install index maintenance
Migration `007` builds GIN trigram indexes on `Page(title)` and
`Page(content)`. The migration runner wraps each file in one transaction, so
`CREATE INDEX CONCURRENTLY` is unavailable and the build holds locks that
block writes for its duration. Fresh and small installations are fine; on a
large existing `Page` table, schedule a maintenance window, or pre-build the
indexes yourself before deploying (the migration uses `IF NOT EXISTS` and
becomes a no-op):
```sql
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX CONCURRENTLY IF NOT EXISTS "Page_title_trgm_idx" ON "Page" USING gin (title gin_trgm_ops);
CREATE INDEX CONCURRENTLY IF NOT EXISTS "Page_content_trgm_idx" ON "Page" USING gin (content gin_trgm_ops);
```
Run those through `make db-shell` (they cannot run inside a transaction).
### Bundled deployment script
The repository ships a target-agnostic deployment for exactly this setup —
no host or domain is hardcoded. Prerequisites: a Debian/Ubuntu server with
ssh access (an ssh config alias works well), DNS for your domain already
pointing at it, and `rsync` + `git` on your workstation. Then, from the
repository root:
```sh
HOST=app-server DOMAIN=computer.example.tld CERTBOT_EMAIL=ops@example.tld ./scripts/deploy.sh
```
The script deploys a **known git revision** (dirty working trees are refused
unless `ALLOW_DIRTY=1`) and on the server provisions Docker/nginx/certbot,
writes `.env` on first deploy, takes a pre-deploy database dump plus `.env`
snapshot into `/var/backups/computer` (0600, 15 retained), builds
revision-tagged images, renders the nginx + Let's Encrypt proxy from
`deploy/nginx/computer.conf.template`, and verifies `https://DOMAIN/login`
end to end.
Rollback: re-deploy an older revision (`git checkout <rev> && ./scripts/deploy.sh`),
or restore one of the pre-deploy dumps together with its `.env` snapshot.
Database migrations are additive, so an older application release keeps
working against a migrated database; rolling the data itself back means the
dump, not reverting SQL.