* docs(b3-1): record PR #1449 =71d867cbin the status line, step table and evidence block Pre-squash SHAs completed with the coverage commita0356ee1and the three Codex rounds (head8614603b). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo * refactor(b3-2): auth_deps.go — the consumer-owned AuthService interface Eight methods beside the handlers that need them: Register, Login, VerifyTOTP, Logout, DeleteAccount, EnableTOTP, ConfirmTOTP, DisableTOTP — fewer than the ten *db.DB methods the two handlers call today. The input and result types they name (Principal, RegisterInput, LoginInput, AuthResult, TOTPChangeResult) and the AuthBroadcaster the delete path needs live in service/auth.go. Nothing implements or calls the interface yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo * refactor(b3-2): service.AuthService — the auth orchestration, moved verbatim Register, Login, VerifyTOTP, Logout, DeleteAccount, EnableTOTP, ConfirmTOTP, DisableTOTP and the RegistrationPolicy gate two characterization rows pin ahead of the body read. The enumeration guard, the F3 reserve-before-compare, the audit writes, the best-effort custom-status clear and the 200+warning partial-success contract move line for line; persistence stays in db behind Store. Each refusal is a named service.Err* whose Error() is the exact public message the handler wrote and whose category (ErrUnauthorized and ErrInvalidInput join the message.go set) the transport maps to a status. The auth rate multiplier moves to auth/ratescale.go so the route mounts and the login failure accounting read one value; api keeps its wrappers. Nothing calls the service yet — the handlers still own their copies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo * refactor(b3-2): thin auth handlers — decode, call AuthService, encode *db.DB leaves every handler signature in auth_handler.go and totp_handler.go; MountAuthRoutes takes the interface and the AuthMiddleware the caller builds, and router.go constructs the service after the hub. Each refusal is encoded by one writeAuthError switch on the service's error categories. The principal helper in middleware.go hands the handlers the caller as service.Principal, and userResponse moves next to the profile handler, so neither auth file names db any more: their two DBImportAllow rows go in this commit (TestDBImportAllowIsLive proves the rows could not outlive the import) and the boundary fixture points at middleware.go instead. The auth-slice limits leave api/constants.go with the code that reads them; profile_handler.go reads the shared pw_confirm budget from the service. Test files change only where they mount the routes (four helper lines + two direct mounts); no assertion or row moves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo * docs(b3-2): after-state boundary inventory — api db importers 12 → 10 Regenerated table (49 files; move 28 → 26), the auth slice's after-state dependency rows, and the honest reading of the plan's "neither db nor service" target: met for db, not for service — the handlers import service for the interface's types and Err* categories. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo * docs(b3-2): evidence block — pre-squash SHAs, graph deltas, gates, coverage Characterization green at each SHA in a detached worktree with the frozen files byte-identical to 71d867cb; nine-method interface vs ten db methods; api db importers 12 → 10; slice coverage 392/433 = 90.5% → 392/427 = 91.8%; the five behaviour notes (decode-before-gate corner cases, shared AuthMiddleware, folded confirmation block, moved limits, moved converter). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo * docs(hp-3): scorecard draft and the D4 vertical-slice pattern in server.md Five questions answered with commands and outputs at fe1d11b8/3f0d24ec; owner sign-off line left blank. server.md gains D4 — the eight-step interface/service/handler rule for B3-8 with the awkward step (gate-before-decode) named — and its D3 deviation note drops the auth routes. Plans README indexes the scorecard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo * docs(b3-2): record PR #1450 in the evidence block and the HP-3 fetch line Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A17Uq3d2C36rN82Jitf3wo --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
OwnCord
A self-hosted chat app I build for me and my friends — text channels, voice and video, and a server you actually own.
Alpha, and a hobby project. This is something I build for fun and run for a small group of friends. It isn't a product, it comes with no support commitment, and it isn't production-ready. Expect rough edges, rapid changes, and the occasional breaking change.
Don't use it for anything sensitive.
It's a Go server plus a Tauri desktop client: real-time messaging, voice/video via LiveKit, file sharing, and a web admin panel. Run the server on a spare box or a VPS, hand your friends an invite code, and that's the whole thing.
How it's built
Most of the implementation is generated with AI tooling, with quality held up by automated checks — CI, tests, linting — and by me and my friends actually using it. That keeps iteration fast, and it also means behaviour can change quickly between releases.
What works right now
| Area | Status |
|---|---|
| Core chat flow | Working in alpha |
| Voice/video | Working in alpha |
| Admin panel | Working in alpha |
| Security hardening | Ongoing review passes; findings and their statuses are tracked in the dated audits in docs/ (see the Docs Index below) |
Platform Support (Current Releases)
| Component | Windows x64 | Linux x64 | Linux ARM64 |
|---|---|---|---|
| Server binary | Yes | Yes | Not yet |
| Desktop client | Yes (NSIS installer) | Yes (AppImage, .deb) | Yes (AppImage, .deb) |
| Docker server | N/A | Build from source (compose) | Not yet |
Start Here
- New user quick path: docs/quick-start.md
- Linux Docker deployment: docs/deployment.md
- Remote access without router config: docs/tailscale.md
- Manual router/network setup: docs/port-forwarding.md
Quick Start
Option A: Prebuilt binaries
- Download assets from Releases (binaries, checksums, signatures, and a full source snapshot per release).
- Run the server binary:
- Windows:
chatserver.exe - Linux:
./chatserver
- Windows:
- Open
https://localhost:8443/adminand complete the setup wizard — it creates your Owner account and configures the server for you (settings are saved toconfig.yamlautomatically). - Generate invite codes in the admin panel and share them with friends.
Option B: Docker (Linux server)
cd Server
cp .env.example .env
cp livekit.yaml.example livekit.yaml
# Edit both files before starting
docker compose up -d
See the full setup guide in docs/deployment.md.
The client uses TOFU (Trust On First Use) for self-signed certificates: it prompts once, then pins the certificate for future connections.
What OwnCord Already Has
- Real-time channels and direct messages over WebSocket
- Voice/video channels via LiveKit — the LiveKit server binary is downloaded and managed for you
- Invite-only registration and role-based permissions
- Web admin panel with logs, backups, and update tooling
- File uploads and inline media rendering
- TOTP 2FA support and API rate limiting
- Desktop client auto-update with signature verification
- WASM plugin system (slash commands; sandboxed, default-disabled — enable via
plugins.enabledand build with-tags wazero) - GIF picker — off by default; each server supplies its own
Klipy key via
gif.api_key(setup)
See deeper feature and architecture docs in docs/architecture/ and docs/protocol.md.
Architecture
Two main components:
- Go server (REST API, WebSocket hub, SQLite, admin panel)
- Tauri v2 desktop client (Rust backend + TypeScript frontend)
+---------------------+ +---------------------+
| OwnCord Client | | OwnCord Server |
| (Tauri v2) | | (Go) |
| | | |
| +---------------+ | WSS | +---------------+ |
| | Chat UI |--+------->| | WebSocket Hub| |
| +---------------+ | | +---------------+ |
| +---------------+ | HTTPS | +---------------+ |
| | REST Client |--+------->| | REST API | |
| +---------------+ | | +---------------+ |
| +---------------+ | LiveKit | +---------------+ |
| | Voice/Video |--+------->| | LiveKit SFU | |
| +---------------+ | | +---------------+ |
+---------------------+ | +---------------+ |
| | SQLite DB | |
| +---------------+ |
+---------------------+
Build and Test
Prerequisites
- Go 1.26+
- Node.js 24+ (see
Client/.nvmrc) - Rust stable (client builds)
Build from source
# Server (Windows)
cd Server
go build -o chatserver.exe -ldflags "-s -w -X main.version=1.2.0-alpha.4" .
# Server (Linux)
cd Server
CGO_ENABLED=0 go build -o chatserver -ldflags "-s -w -X main.version=1.2.0-alpha.4" .
# Client
cd Client
npm install
npm run tauri build
Core verification commands
Everything CI gates on, from the repository root:
npm run check # server + client + Rust
npm run check:server # or one stack at a time
node scripts/run.mjs --list # exactly what each task runs, and where
Or run the stacks directly — the facade is a convenience, not the only path, and server work needs no Node at all:
# Server
cd Server
go test ./...
# Client
cd Client
npm run typecheck
npm run lint
npm test
For the full command set, use docs/contributing.md.
Configuration
On first run, the server generates config.yaml and a local data/ directory:
data/
├── chatserver.db
├── certs/
├── uploads/
└── backups/
Key options include TLS mode, upload limits, LiveKit settings, and admin CIDR restrictions. See docs/server-configuration.md.
Security and Vulnerability Reporting
- For vulnerabilities, use GitHub Security Advisories (private disclosure flow).
- Do not open public issues for security bugs.
- Read full policy and hardening notes in docs/security.md.
Update Signing Notes (Maintainers)
Client and server update signing keys are intentionally separate.
Required Actions secrets for release signing:
TAURI_SIGNING_PRIVATE_KEYTAURI_SIGNING_PRIVATE_KEY_PASSWORDSERVER_UPDATE_SIGNING_PRIVATE_KEYSERVER_UPDATE_SIGNING_PRIVATE_KEY_PASSWORD
When rotating the server updater key, update Server/updater/server_update_public_key.txt and use staged rollover for live fleets.
Docs Index
docs/README.md is the complete index — every document in
docs/, grouped by whether it is guidance, a reference contract, a dated audit,
or a plan. The most-used entries:
- docs/quick-start.md — get a server running
- docs/deployment.md — production deployment
- docs/contributing.md — setup, branch model, how to run the checks CI runs
- docs/security.md — reporting a vulnerability
- docs/architecture/ — system blueprints (diagrams + flows)
- docs/architecture/ux/ — client UX specification (target-state flows, per-view states, event→reaction maps)
- docs/api.md, docs/protocol.md, docs/schema.md, docs/server-configuration.md — reference contracts
- docs/plans/README.md — plan index; records each plan's state and is the authority over a plan's own header
Audits are dated snapshots and are not maintained after the fact — read them as history. docs/README.md lists all nine, newest first.
Contributing
- Create a branch from
dev(the active development branch). - Keep changes focused and tested.
- Open a PR targeting
dev—devis merged tomainfor releases.
See docs/contributing.md for the full process.
Getting Help and Reporting Problems
Nothing here is a support promise — see the note at the top — but there is a right place for each kind of message:
| Kind | Where |
|---|---|
| A reproducible bug | Issues |
| A question about setup or usage | Discussions → Q&A |
| An idea or feature suggestion | Discussions → Ideas |
| A security vulnerability | Private advisory — never an issue |
License
AGPL-3.0


