release gate / check (push) Failing after 6m43s
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.
382 lines
15 KiB
Markdown
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.
|