# Deployment — Phase 0

Operator-runnable steps to install the Phase 0 Command Center binary as a systemd service on a Linux host with Tailscale already running.

> **Threat model recap:** the binary binds only to the Tailscale interface and refuses connections from any other source. There is no public ingress. See [PROJECT.md §5.1](PROJECT.md).

## Prerequisites

On the deployment host:

- Linux with systemd (Debian, Ubuntu, NixOS, etc.).
- Tailscale installed, authenticated, and reachable (`tailscale status` returns OK; the `tailscale0` interface is up).
- An empty `/etc/command-center` directory, owned by root.
- An empty `/var/lib/command-center` directory; systemd will manage permissions via `DynamicUser=yes`.

On the build host (can be the same machine or any other):

- Go 1.22+
- Node.js 20+ with npm
- For a full build (with DuckDB), a C compiler. Omit with `-tags no_duckdb` if you don't have one, but production should include DuckDB.

## 1. Build the binary

From a clean checkout:

```bash
./scripts/build.sh
```

This produces `./command-center` in the repo root. For cross-compilation to Linux from another platform:

```bash
GOOS=linux GOARCH=amd64 ./scripts/build.sh
```

> If you build with `-tags no_duckdb`, the binary still runs but `/api/system/health` will report `duckdb: degraded`. Re-build without the tag once a C toolchain is available.

## 2. Generate the age identity

The age identity encrypts every secret the Command Center stores at rest. It lives outside the repository and outside backups (or inside an out-of-band backup the operator manages).

```bash
sudo install -m 0700 -d /etc/command-center
sudo sh -c 'age-keygen 2>/dev/null > /etc/command-center/age.key'
sudo chmod 0600 /etc/command-center/age.key
# Confirm:
sudo cat /etc/command-center/age.key | grep '^# public key:'
```

> If you don't have `age-keygen` installed, the binary can generate one on first boot when `dev_mode` is true — but DO NOT use dev_mode in production. Install age (`apt install age` / `nix-shell -p age`).

Back up the file the operator way: store the contents in a password manager or a separate offline location. **Without this file, every stored secret is permanently unrecoverable.**

## 3. Place the config

```bash
sudo install -m 0750 -d /etc/command-center/config
sudo cp config/system.example.yaml /etc/command-center/config/system.yaml
sudo $EDITOR /etc/command-center/config/system.yaml
```

Minimum production diff against the example:

```yaml
dev_mode: false
listen:
  tailscale_interface_pattern: "tailscale*"
secrets:
  age_identity_file: "/etc/command-center/age.key"
```

## 4. Install the binary

```bash
sudo install -m 0755 -D ./command-center /usr/local/bin/command-center
```

## 5. Install the systemd unit

```bash
sudo install -m 0644 -D systemd/command-center.service /etc/systemd/system/command-center.service
sudo systemctl daemon-reload
sudo systemctl enable --now command-center
```

The unit uses `DynamicUser=yes`, so systemd materializes a transient `command-center` user. State lives under `/var/lib/command-center/`. Hardening directives (`ProtectSystem=strict`, `MemoryDenyWriteExecute=true`, restrictive `SystemCallFilter`) align with the threat model in [PROJECT.md §5.7](PROJECT.md).

## 6. Verify

From a Tailscale-connected device:

```bash
curl -sS https://<tailscale-hostname>:8443/api/system/health | jq
```

Expected response:

```json
{
  "status": "ok",
  "version": "<git short hash>",
  "uptime_seconds": 12,
  "checks": [
    { "name": "sqlite",  "status": "ok" },
    { "name": "duckdb",  "status": "ok" },
    { "name": "age_key", "status": "ok", "message": "/etc/command-center/age.key" },
    { "name": "config",  "status": "ok" }
  ]
}
```

Visit `https://<tailscale-hostname>:8443/` in a browser. You should see "Hello Command Center" and the same health payload rendered as a list.

Logs are journald:

```bash
journalctl -u command-center -f
```

## 7. Updating

Rebuild and replace the binary, then restart:

```bash
./scripts/build.sh
sudo install -m 0755 ./command-center /usr/local/bin/command-center
sudo systemctl restart command-center
```

Migrations apply automatically on start; new tables are additive (see [PROJECT.md §10.2](PROJECT.md)).

## 8. Backup

Phase 0 has no automated backup yet (Phase 15 ships that). For now:

```bash
sudo systemctl stop command-center
sudo cp /var/lib/command-center/command-center.db ~/cc-backup-$(date +%F).db
sudo systemctl start command-center
```

The age key lives outside `/var/lib/command-center` and must be backed up separately.

## Troubleshooting

| Symptom | Probable cause |
|---|---|
| `tailscale interface (pattern "tailscale*") not present and dev_mode is off` | Tailscale isn't running, or its interface name doesn't match the pattern. Check `ip link`. |
| `no age identity found in any of [...]` | `secrets.age_identity_file` not set and no key at the discovery paths. Re-run step 2. |
| `health` reports `duckdb: degraded` | Binary built with `-tags no_duckdb`. Rebuild without the tag (requires a C compiler). |
| `config reload rejected` in logs | A `system.yaml` edit failed validation. Logs name the offending field. The previous valid config remains active. |
