Retire legacy implementations and flatten managed layout
This commit is contained in:
+19
-213
@@ -1,229 +1,35 @@
|
||||
# Deployment & Self-Hosting
|
||||
# Server deployment
|
||||
|
||||
## Managed server deployment checkpoint
|
||||
|
||||
The .NET server preserves protocol v2 and the native schema/credentials. Publish a
|
||||
self-contained executable (no installed .NET runtime required):
|
||||
Publish a locked, self-contained server from the repository root:
|
||||
|
||||
```powershell
|
||||
# Publishes both win-x64 and linux-x64 by default.
|
||||
./dotnet/publish-server.ps1
|
||||
# Publish just one target when needed.
|
||||
./dotnet/publish-server.ps1 -Runtime linux-x64
|
||||
./dotnet/artifacts/server/win-x64/VoiceCat.Server.exe --help
|
||||
./dotnet/artifacts/server/win-x64/VoiceCat.Server.exe account add Operator --admin --data-dir ./voicecat-data
|
||||
./dotnet/artifacts/server/win-x64/VoiceCat.Server.exe --data-dir ./voicecat-data --allow-guests false
|
||||
./scripts/publish-server.ps1
|
||||
./scripts/publish-server.ps1 -Runtime linux-x64
|
||||
./artifacts/server/win-x64/VoiceCat.Server.exe --help
|
||||
```
|
||||
|
||||
Account add/reset use a hidden password prompt, redirected standard input, or
|
||||
`VOICECAT_ADMIN_PASSWORD`. Passwords are never accepted as command arguments or logged.
|
||||
Account delete/list work against the same database, including while the server runs.
|
||||
Provisioning grants administrator access only through the local command's `--admin`;
|
||||
in-band account creation remains non-admin. Restrict access to the data directory.
|
||||
|
||||
Defaults are `0.0.0.0:8384` TCP+UDP, guest access enabled, 64 connections, 15-second TLS
|
||||
handshakes, 45-second idle expiry and 15-second sweeps. Override with flags or environment:
|
||||
|
||||
| Flag | Environment variable |
|
||||
|---|---|
|
||||
| `--data-dir` | `VOICECAT_DATA_DIR` |
|
||||
| `--bind` | `VOICECAT_BIND_ADDRESS` |
|
||||
| `--port` | `VOICECAT_BIND_PORT` |
|
||||
| `--name` | `VOICECAT_SERVER_NAME` |
|
||||
| `--allow-guests` | `VOICECAT_ALLOW_GUESTS` |
|
||||
| `--max-connections` | `VOICECAT_MAX_CONNECTIONS` |
|
||||
| `--handshake-seconds` | `VOICECAT_HANDSHAKE_TIMEOUT_SECONDS` |
|
||||
| `--idle-seconds` | `VOICECAT_IDLE_TIMEOUT_SECONDS` |
|
||||
| `--reaper-seconds` | `VOICECAT_REAPER_INTERVAL_SECONDS` |
|
||||
| `--auth-burst` | `VOICECAT_AUTH_BURST` |
|
||||
| `--auth-refill-seconds` | `VOICECAT_AUTH_REFILL_SECONDS` |
|
||||
|
||||
Command arguments override environment values. `--print-config` validates and prints JSON
|
||||
without creating files; `--print-fingerprint` prints the persisted leaf-certificate SHA-256
|
||||
pin. Startup emits one JSON `ready` event with both fingerprints and actual TCP/UDP ports.
|
||||
Bind accepts IP literals; IPv6 listeners are IPv6-only. Open/forward both protocols.
|
||||
An exclusive data-directory instance lock prevents duplicate managed server processes.
|
||||
Ctrl+C and Unix SIGINT/SIGTERM stop all transport tasks; shutdown has a ten-second deadline.
|
||||
Fatal listener/media/reaper failure exits the host rather than leaving a broken listener.
|
||||
`--health-check HOST:PORT` performs a real protocol TLS 1.3 handshake and emits JSON.
|
||||
Automation can add `--expect-fingerprint SHA256` to verify the persisted certificate.
|
||||
|
||||
Password authentication is limited before Argon2 by source address and username across
|
||||
connections: burst 5, refill one attempt per ten seconds. Starting at three failed attempts,
|
||||
backoff grows from one to thirty seconds. Success clears backoff but does not restore
|
||||
tokens. State is bounded to 4096 keys; idle entries retire after ten minutes when full.
|
||||
Throttle and credential failures share the generic auth error. Limits are process-local.
|
||||
|
||||
The publish script produces Windows x64 and Linux x64 self-contained outputs in one command
|
||||
by default. It uses separate per-RID lock files so deployment and development restore graphs
|
||||
remain reproducible. Pass `-Runtime` to publish only one target.
|
||||
The Linux smoke starts the published ELF, validates TLS health and the pin, checks mode 0700
|
||||
data creation, sends SIGTERM and requires a clean exit:
|
||||
|
||||
```bash
|
||||
sh deploy/linux/smoke.sh dotnet/artifacts/server/linux-x64/VoiceCat.Server
|
||||
```
|
||||
|
||||
`deploy/linux/voicecat.service` runs under a systemd dynamic user with a private state
|
||||
directory, no capabilities and filesystem/kernel hardening. Install a published binary and
|
||||
the unit from `deploy/linux/` as root, or use the helper there. The default Dockerfile now
|
||||
builds the managed server with locked packages and runs it as UID 1654 in Microsoft's
|
||||
chiseled .NET runtime-dependencies image. Compose drops all capabilities, sets the root
|
||||
filesystem read-only and persists only `/data`.
|
||||
|
||||
For operational endurance against an already running server:
|
||||
The default endpoint is TCP and UDP port 8384. The server creates its TLS identity and SQLite
|
||||
database in the data directory. Account passwords are accepted only through a hidden prompt,
|
||||
redirected standard input, or `VOICECAT_ADMIN_PASSWORD`; they are never command arguments.
|
||||
|
||||
```powershell
|
||||
./dotnet/soak-server.ps1 -HostName 127.0.0.1 -Port 8384 -Minutes 30 -Pairs 4
|
||||
./artifacts/server/win-x64/VoiceCat.Server.exe account add Operator --admin --data-dir ./voicecat-data
|
||||
./artifacts/server/win-x64/VoiceCat.Server.exe --data-dir ./voicecat-data --allow-guests false
|
||||
```
|
||||
|
||||
Every cycle creates independent managed processes that exchange text and decoded voice.
|
||||
A short 16-session published-server soak is part of this checkpoint. A release candidate
|
||||
still needs the long soak on its target host. TOML/reload and signed image publication remain
|
||||
before broad production rollout.
|
||||
The checked-in `Dockerfile` builds the managed Linux server. `docker-compose.yml` runs it as a
|
||||
non-root user with a read-only filesystem, persistent `/data`, and TCP/UDP 8384 exposed.
|
||||
|
||||
The product goal: someone looks at this and thinks *"oh, I (or my agent) can stand this up
|
||||
in a few minutes."* Everything below is in service of that. Three install paths, all
|
||||
**zero-config and encrypted by default**.
|
||||
|
||||
## 1. The three paths
|
||||
|
||||
### A. Docker (recommended)
|
||||
For a host installation, `deploy/linux/install.sh` installs a published Linux binary and the
|
||||
systemd unit. Validate a published binary with:
|
||||
|
||||
```bash
|
||||
docker build -t voicecat:local .
|
||||
docker run -d --name voicecat \
|
||||
-p 8384:8384/tcp \ # control (TLS 1.3)
|
||||
-p 8384:8384/udp \ # media (encrypted)
|
||||
-v voicecat-data:/data \
|
||||
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m \
|
||||
--cap-drop ALL --security-opt no-new-privileges \
|
||||
voicecat:local
|
||||
sh deploy/linux/smoke.sh artifacts/server/linux-x64/VoiceCat.Server
|
||||
```
|
||||
|
||||
That's the whole thing. On first start it generates its Ed25519 identity + self-signed
|
||||
cert, creates the SQLite database under `/data`, prints the **server fingerprint** (for
|
||||
clients to verify), and listens. Control and media share one port number on TCP+UDP to keep
|
||||
firewall rules trivial.
|
||||
The server exposes `--health-check host:port` for TLS-aware probes. Before release, run the
|
||||
container and the concurrent client soak:
|
||||
|
||||
A `docker-compose.yml` is provided for people who prefer it, but it isn't required.
|
||||
|
||||
### B. Single static binary
|
||||
|
||||
```bash
|
||||
# download for your OS, then:
|
||||
./voicecat-server # uses ./voicecat-data/ , prints fingerprint, runs
|
||||
```powershell
|
||||
./scripts/soak-server.ps1 -HostName 127.0.0.1 -Port 8384 -Minutes 30 -Pairs 4
|
||||
```
|
||||
|
||||
The server is a **single statically linked executable** (mbedTLS, libsodium, opus, sqlite,
|
||||
etc. linked in — all permissive licenses). No runtime, no shared libraries to install, no
|
||||
package manager. Linux (primary), macOS, and Windows builds.
|
||||
|
||||
### C. From source
|
||||
|
||||
```bash
|
||||
git clone … && cd voice-cat
|
||||
cmake --preset server-release # vcpkg fetches & pins all deps; auto-triplet (Linux/macOS/Windows)
|
||||
cmake --build --preset server-release
|
||||
./build/server-release/bin/voicecat-server
|
||||
```
|
||||
|
||||
One `cmake` invocation; vcpkg (manifest mode) resolves the dependency graph reproducibly.
|
||||
The `server-release` preset produces an optimized, **stripped** binary (`-s` linker flag) —
|
||||
smaller executables suitable for distribution. Works on Linux (primary), macOS, and Windows;
|
||||
the vcpkg triplet is auto-resolved by [`cmake/voicecat-toolchain.cmake`](../cmake/voicecat-toolchain.cmake).
|
||||
No system packages to chase.
|
||||
|
||||
## 2. Zero-config defaults
|
||||
|
||||
The server runs with **no config file at all**. Sensible defaults:
|
||||
|
||||
| Setting | Default |
|
||||
|---------|---------|
|
||||
| Encryption | On, always (not configurable off) |
|
||||
| TLS cert / identity | Auto-generated on first run, persisted to the data dir |
|
||||
| Database | Embedded SQLite in the data dir (no external DB) |
|
||||
| Guests | Enabled (so the very first connect "just works"); easily disabled |
|
||||
| Ports | `8384` TCP + UDP |
|
||||
| A default channel | One "Lobby" voice/text channel created on first run |
|
||||
| Opus policy | 48 kHz, 20 ms frames, mono/VOIP defaults; per-channel overrides allowed |
|
||||
| Argon2id cost | Auto-tuned to the host on first run |
|
||||
|
||||
Override only what you care about, via env vars or an optional `server.toml`:
|
||||
|
||||
```toml
|
||||
# server.toml — every key is optional
|
||||
server_name = "Cats United"
|
||||
allow_guests = false
|
||||
bind_port = 8384
|
||||
data_dir = "/data"
|
||||
|
||||
[tls] # only if you want a real CA cert; otherwise self-signed
|
||||
cert_file = "/data/fullchain.pem"
|
||||
key_file = "/data/privkey.pem"
|
||||
|
||||
[opus.defaults] # default Opus policy for new channels
|
||||
mode = "mono"
|
||||
bitrate_bps = 24000
|
||||
frame_ms = 20
|
||||
fec = true
|
||||
dtx = true
|
||||
|
||||
[opus.limits] # server-enforced ceilings (bound bandwidth)
|
||||
max_bitrate_bps = 128000 # channels can't be configured above this
|
||||
```
|
||||
|
||||
Every key also has an `VOICECAT_*` env var form, which is what the Docker path uses.
|
||||
|
||||
## 3. Connecting (client side)
|
||||
|
||||
- **Pure direct-connect.** Enter `host:port` (and a nickname or account). There is no central
|
||||
directory or server browser — you connect to a server you know.
|
||||
- **Saved server list.** The client keeps a local list of saved servers (host:port, pinned
|
||||
fingerprint, nickname/credentials per server) so you can store several and pick one to
|
||||
join. This lives entirely in the client.
|
||||
- On first connect the client shows the server's **fingerprint** and pins it (TOFU). No
|
||||
accounts or certs needed to try it; if the operator disabled guests, the client prompts for
|
||||
the username/password an admin gave you.
|
||||
|
||||
That's the entire flow: run the server, share `host:port` + fingerprint, friends save it and
|
||||
connect.
|
||||
|
||||
## 3a. Provisioning accounts (admin)
|
||||
|
||||
Accounts are **admin-provisioned** — there is no self-serve registration. Two equivalent ways,
|
||||
both writing the same SQLite store:
|
||||
|
||||
```bash
|
||||
# CLI against the server's data dir or a running server
|
||||
voicecat-admin account add <username> # prompts for / generates a password
|
||||
voicecat-admin account reset <username>
|
||||
voicecat-admin account del <username>
|
||||
voicecat-admin account list
|
||||
```
|
||||
|
||||
…or from the **in-app admin interface** (a user with the admin permission), which sends the
|
||||
privileged `CreateAccount`/`ResetPassword`/`DeleteAccount` control messages over TLS
|
||||
(protocol.md §3). Guests need no provisioning; they just pick a nickname (if guests are
|
||||
enabled).
|
||||
|
||||
## 4. Why it stays this easy (design constraints that protect the goal)
|
||||
|
||||
- **No external services.** No separate database, no Redis, no TURN/STUN server, no reverse
|
||||
proxy required. SQLite is embedded; media is plain UDP.
|
||||
- **No certificate chore.** Self-signed + Ed25519 TOFU means encryption needs zero operator
|
||||
action. A domain owner *can* drop in a Let's Encrypt cert, but never *has* to.
|
||||
- **One port pair.** TCP+UDP on the same number; one firewall/port-forward rule.
|
||||
- **Static linking + permissive licenses.** The binary has no install-time dependencies and
|
||||
can be redistributed (including closed-source) without copyleft obligations.
|
||||
- **Agent-friendly.** The run command is a single line with no interactive prompts, the
|
||||
server logs its fingerprint and listen address in machine-readable form, and `--help` /
|
||||
`--print-config` expose everything an automation needs. Health endpoint for liveness checks.
|
||||
|
||||
## 5. Operational niceties (planned, not blocking v1)
|
||||
|
||||
- Signed, multi-architecture image publication.
|
||||
- Graceful reload of `server.toml` on `SIGHUP`.
|
||||
- `voicecat-admin` (see §3a) also handles bans and channel admin, talking to the same SQLite
|
||||
file or a running server.
|
||||
- Prebuilt images for `linux/amd64` + `linux/arm64` (Raspberry Pi / cheap VPS friendly).
|
||||
|
||||
Reference in New Issue
Block a user