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.
computer
A multi-user bookmark workspace: URLs with Markdown notes, tags, calendar dates, editable kanban boards, project collections with typed properties and saved views, pages, previews, sharing, contacts and email signals.
Run the whole app
Client setup guide: Running computer covers cloning, setup, first login, everyday commands, updates, and troubleshooting on Windows, macOS, and Linux.
Requires Docker with Compose v2 or newer. On a new installation:
git clone https://github.com/andrewthecodertx/computer.git
cd computer
cp .env.example .env
docker run --rm node:22-alpine node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
Put the generated value in AUTH_SECRET in .env, save the file, then run:
docker compose up -d --build # builds and starts PostgreSQL, Go and Next.js
On Windows PowerShell, use Copy-Item .env.example .env instead of cp.
- Web interface: http://localhost:3000. Sign up, then sign in.
- Go API: http://localhost:8080 (local access; endpoints in API.md).
- PostgreSQL:
db:5432inside Docker; not published on the host. - With
ADMIN_EMAILSempty, the first non-@example.comaccount becomes administrator and completes a one-shot bootstrap: afterwards signup never auto-promotes again, even if the last administrator is demoted or removed — recovery is operator-driven (ADMIN_EMAILS+ seed, or promotion by an existing admin). Addresses inADMIN_EMAILSare reserved for operator-provisioned accounts; automatic promotion requires a verified email. Existing accounts are preserved on restart. docker compose downstops the stack. It does not delete the database volume.
make run and make stop are optional shortcuts for the Compose commands.
docker-compose.full.yml is a compatibility alias for the same complete stack.
Existing Docker database
Set POSTGRES_VOLUME to its existing Docker volume name and
POSTGRES_VOLUME_EXTERNAL=true. The volume must contain the computer
database and computer role. Stop the previous PostgreSQL container before
mounting that volume in this stack.
db/schema.sql initializes a new, empty database once. db/migrations/
holds the ordered, checksummed additive migrations that db/migrate.sh runs
(ledger table _migration) before Go starts. Each migration is applied and
recorded in a single transaction under an advisory lock, so a crash never
leaves an applied file unrecorded (or vice versa) and a second concurrent
runner aborts instead of double-applying. Neither path drops a database.
Do not run the initial schema against an already initialized database, and do
not edit a migration that has been applied — add a new numbered file.
Migrations are additive (new columns/tables/indexes with defaults), so the
previous application release keeps working against a migrated database;
rolling the data itself back means restoring the pre-deploy dump (see
RUNNING.md), not reverting SQL. Because schema.sql and the chain are two
paths to the same schema, every schema change needs both a schema.sql edit
and a new migration; make db-converge (included in make check) fails
when they drift apart. The page-search migration enables the pg_trgm
extension (bundled with PostgreSQL and trusted, so the database owner can
install it); the index-backed page search depends on it.
Architecture
internal/: Go API, persistence, authorization and integrations. SQL values are parameterized; camelCase JSON matches the Next.js client contract.web/: Next.js UI and NextAuth browser sessions. Credentials are verified by Go. A server-side API proxy signs 60-second identity assertions; Go reloads roles and resolves admin impersonation on every data request. Signing out in the browser also revokes backend sessions ("sign out everywhere"): it bumps the user's token version, so prior assertions and Go cookie sessions stop working.- The frontend has no runtime Prisma client or direct database connection.
- Go also supports its own HttpOnly
computer_sessioncookie for direct API consumers. Administration uses the real user; data uses the effective user. - Containers share the Compose backend network. The app runs as non-root
with a read-only filesystem; the web container has temporary
/tmpstorage.
Features and integrations
- Bookmarks: create/edit/delete, tags, contacts, previews, calendar dates, alerts, Markdown downloads and public read-only links.
- Bookmark collections use cursor pagination; the UI follows every page so collections larger than 200 remain visible on all organization screens.
- Notion-style workspace shell: a Quick Find command palette (Ctrl/Cmd+K) jumps to any page, bookmark, tag or view and runs commands (new page, add bookmark, theme toggle); the slim top bar shows a breadcrumb path (workspace / section / page); sidebar sections collapse and page rows expose hover actions (pin, inline rename, delete).
- Pages: Markdown editing/autosave with a Notion-style slash-command block
menu (type
/for headings, to-dos, lists, quotes, dividers and code) and the[]+ space to-do shortcut, link collections, pinning, reorder/export. Saves are queued per page; unsaved drafts are kept in the current browser tab for recovery after a failed save, together with the server version they were based on. Page switching waits for pending saves. When another tab or device saved first, autosave pauses in an explicit conflict state — the editor offers overwrite, save-as-copy and discard, and typing alone never overwrites the newer version. - Kanban: custom columns, card moves, reorder; deleting a column moves cards.
- Collections: per-project workflow state over your existing bookmarks. The same link joins many collections without duplication, each with its own typed properties (text, number, select, multi-select, date, checkbox, URL), saved table/board views, server-side filters with typed operator allowlists, and manual ordering. Membership adds context — it never owns or publishes the bookmark: removing an item or a whole collection keeps the bookmark, and a bookmark whose share was revoked disappears from collections everywhere. Every mutating PATCH carries an expected version, so concurrent editors get a conflict instead of a silent overwrite; reordering and property deletion advance the affected items' and views' revisions too, and deletions are idempotent removals.
- Sharing: bookmarks, tags and calendar dates; recipients read shared content. Owners can revoke shares. Shared tags and dates expose the associated URLs. Tag sharing is group-wide: every recipient of a tag sees all bookmarks carrying it, including ones other recipients add. Shared readers get a narrowed response: the link and its reader-facing content only — never the owner's contact record, the other recipients, alerts, or mailbox watches.
- Public bookmark links deliberately publish the title, description, notes, due date, tags and preview; contacts, alerts, mailbox watches and the owner identity stay private. The share panel states this before you toggle.
- Remote preview images and favicons are loaded by your browser directly
from the third-party host, with
referrerPolicy="no-referrer"so the app URL is not disclosed; opening a page can still let those hosts observe the request. Server-side fetches (previews, CardDAV, IMAP) never touch private network ranges, including 6to4/Teredo/NAT64-wrapped addresses. Previews are pinned to HTTP/HTTPS ports 80/443 and mailbox connections to IMAP ports 143/993; CardDAV uses the explicitly configured HTTPS port. - Text fields have per-field caps in addition to the request-body limit, so an oversized bookmark title, tag name, note or page body is rejected instead of bloating every list and search.
- Alerts: browser notifications and in-app dismissal, polled every five minutes.
- Nextcloud: CardDAV REPORT sync using each user's app password. Credentials are used for that request only, not stored, and never travel plaintext: the Nextcloud URL must use https (redirects are not followed).
- IMAP: encrypted per-user settings, real login tests, sender/subject searches, email watchers, and Message-ID deduplication. Verified TLS is required — plaintext mailbox logins are rejected. Watcher ingestion is incremental: a durable per-source UID checkpoint processes bounded batches until caught up instead of re-scanning the last 50 matches. Configure integrations in Settings, then check watchers from a bookmark. Enabled email watchers also run every five minutes in the Go process, including watches enabled with the original mailbox-watch switch. The scheduler runs users with bounded concurrency, per-user failure backoff, and a database-backed lease so extra instances never double-poll a mailbox.
Connecting Authelia (OIDC)
Authelia sign-in is currently stubbed. Connecting it requires both server configuration and developer integration; setting environment variables alone does not enable it. Email/password sign-in works while that integration is pending. The full Authelia connection guide includes the client registration example and the implementation handoff.
1. Register the app in Authelia
Use an Authelia installation with OIDC enabled and a valid HTTPS issuer URL.
Register computer as a confidential client with:
- Scopes:
openid,profile,email. - Grant type:
authorization_code; response type:code. - Token endpoint authentication:
client_secret_basic. - Redirect URI:
<PUBLIC_APP_URL>/api/auth/callback/authelia, for examplehttps://computer.company.tld/api/auth/callback/authelia.
Generate a client secret as described in the connection guide. Put its
hashed digest in Authelia and its plain value in the app's
OIDC_CLIENT_SECRET. The redirect URI must exactly match the public web URL
and callback path. It points to the Next.js server, not the Go API on port 8080.
2. Configure computer
Edit the root .env, replacing the sample values with your deployment values:
NEXTAUTH_URL=https://computer.company.tld
COOKIE_SECURE=1
OIDC_ISSUER=https://auth.company.tld
OIDC_CLIENT_ID=computer
OIDC_CLIENT_SECRET=<plain-client-secret>
Serve the web app through an HTTPS reverse proxy; the default Docker ports
bind to localhost. Keep your existing AUTH_SECRET unchanged. Apply the
environment changes with docker compose up -d, then check Settings → OIDC.
It will report that the variables are configured but integration is pending.
The login button remains disabled at this stage.
3. Complete the developer integration
The trusted Next.js server owns OIDC verification and the callback. The remaining work is to:
- Implement Go-backed lookup/provisioning of a local
UserandAccountfor the verified Authelia identity. A provisioning endpoint is not yet supplied; any new endpoint must authenticate the trusted Next server. - Update
web/auth.tsto resolve the verified identity to the localUser.idand permit sign-in only after that succeeds. The session and API assertion must use this local ID, not Authelia'ssubclaim. Existing accounts need an explicit, verified account-linking policy. - Enable the readiness gate in
web/app/login/client.tsxafter mapping works, rebuild withdocker compose up -d --build, and verify sign-in, first-login provisioning, returning users, and Go-backed data access.
The Go OIDC login/callback handlers in internal/oidc/provider.go deliberately
return 501. They are not the NextAuth callback implementation. Config status
(including an enabled flag) indicates that values are present, not that an
end-to-end login has been implemented.
Developer commands
make run # build and start the full Compose stack
make stop # stop the stack, keep the PostgreSQL volume
make build # Go compile
make vet # Go vet
make test # Go unit tests with race detection
make integration # Docker PostgreSQL + CardDAV/IMAP fixture tests
make db-shell # psql inside the database container
make logs # Go and Next.js logs
make migrate # ordered additive database migrations
make db-converge # schema.sql vs migration-chain drift check
make check # release validation (fails without DB coverage)
make audit # dependency advisory scans (needs govulncheck)
The same release gate runs automatically on every push and pull request
(.github/workflows/check.yml), including the browser smoke tests against
the composed stack. Pass YARN="corepack yarn" to make check when yarn
comes through corepack.
Integration tests create/drop random test schemas; they never clear app data. They exercise CRUD, sharing, ownership boundaries, impersonation, encryption, CardDAV sync, IMAP login/search and signal deduplication.
Frontend checks (from web/): yarn lint, yarn build, and
yarn eslint -c eslint.ssr.config.mjs .. Builds enforce TypeScript checks.
Browser smoke tests (with the stack running):
npm --prefix scripts ci
CHROMIUM_PATH=/usr/bin/chromium make e2e
These verify login, UI bookmark creation, Go-backed persistence, public links, Quick Find deep links, kanban, the collections workflow (membership without duplication, typed properties, table+board over one membership), page autosave/slash commands/exports and every application screen. Test accounts have unique names and only those accounts are deleted afterward.
Targeted browser regressions (after yarn build in web/):
npm --prefix scripts run test:regressions
These run an isolated Next server with API fixtures and test login CSRF, browser sign-out revocation, autosave switching/ordering/recovery (including drafts whose base version is unknown, which pause for review), pagination, and bookmark editing without creating or deleting application users.
Optional operator-provisioned account: set its address in ADMIN_EMAILS in
the root .env, run docker compose up -d, then:
docker compose exec -e SEED_EMAIL=admin@company.tld -e SEED_PASSWORD='<chosen-password>' app /computer --seed
This trusted command creates an admin for an allowlisted address. It never changes an existing account's password or role, or deletes data. Additional existing users can be promoted from the Admin screen.
Deploying to a server
Deployment is target-agnostic — there are no built-in host or domain defaults. Point DNS for your domain at the server, have an ssh alias (or host) ready, then run from the repository root:
HOST=app-server DOMAIN=computer.example.tld CERTBOT_EMAIL=ops@example.tld ./scripts/deploy.sh
(make deploy runs the same script with the same variables.) It records the
exact git revision, refuses dirty trees unless ALLOW_DIRTY=1, rsyncs the
source, 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 (15 retained), builds revision-tagged images, and
installs the nginx + Let's Encrypt reverse proxy from
deploy/nginx/computer.conf.template. Rollback means re-deploying an older
revision or restoring a pre-deploy dump; migrations are additive. See
RUNNING.md → "Running on a remote server" for the operator checklist.
Client test build
Use RUNNING.md for the client handoff. Test login, bookmark editing, tag/contact organization, calendar dates, kanban, a collections workflow (create a collection, add the same bookmark to two collections, give each a status, move a card on a board view), Markdown pages/exports, Quick Find (Ctrl/Cmd+K), slash commands in the page editor, and sharing with separate accounts.
Current integration boundaries:
- Authelia still requires the developer identity-mapping work described above; use email/password for this test build.
- IMAP and Nextcloud require the client's own service credentials.
- Alerts are browser-only and checked every five minutes while the app is open.
- Page autosave needs an API connection. Check the save indicator; use Save now after a failed save. Tab-local drafts are recovery aids, not backups.