# Deploy Configuration

**Parent**: [/CLAUDE.md](/CLAUDE.md) | **Related**: [/scripts/systemd/CLAUDE.md](/scripts/systemd/CLAUDE.md)

Environment files, monit watchdog configuration, and deployment scripts for bigblack.

---

## Environment Files (SSoT)

| File                         | Git-tracked | Purpose                               | Deployed To                     |
| ---------------------------- | ----------- | ------------------------------------- | ------------------------------- |
| `opendeviationbar.env`       | Yes         | Non-secret config (SSoT for bigblack) | `~/opendeviationbar-py/deploy/` |
| `opendeviationbar.local.env` | **No**      | Secrets: Telegram TOKEN, Redis pw     | `~/opendeviationbar-py/deploy/` |
| `sidecar.env`                | Yes         | Streaming-specific vars               | `~/opendeviationbar-py/deploy/` |

Services load both env files via `EnvironmentFile=` in each `.service` unit. The `-` prefix (`EnvironmentFile=-path`) means "ignore if missing" — used for `local.env` so services start even without secrets.

**To edit bigblack config**: Edit `deploy/opendeviationbar.env` locally, then `mise run deploy:bigblack` to sync. Never edit on bigblack directly — it will be overwritten on next deploy.

---

## opendeviationbar.env — Key Variables

```ini
# Connection mode
OPENDEVIATIONBAR_MODE=local
OPENDEVIATIONBAR_CH_HOSTS=localhost

# Ouroboros (day-mode for atomic daily resets)
OPENDEVIATIONBAR_OUROBOROS_MODE=day

# Redis write lock (Issue #266: short timeout, fall through to L2 dedup)
OPENDEVIATIONBAR_REDIS_HOST=localhost
OPENDEVIATIONBAR_REDIS_LOCK_BLOCKING_TIMEOUT=2

# Telegram alert destination
OPENDEVIATIONBAR_TELEGRAM_CHAT_ID=90417581

# Kintsugi parallelism (see scripts/CLAUDE.md for details)
OPENDEVIATIONBAR_KINTSUGI_MAX_REPAIRS_PER_PASS=500
OPENDEVIATIONBAR_KINTSUGI_MAX_CONCURRENT_REPAIRS=24  # empirical sweet spot; 32 = cliff (0 repairs/30s)
OPENDEVIATIONBAR_KINTSUGI_MAX_CONCURRENT_PARQUET=64
OPENDEVIATIONBAR_KINTSUGI_BATCH_WORKERS=4
OPENDEVIATIONBAR_KINTSUGI_BATCH_DAYS=365

# Parquet seeder (Issue #270)
OPENDEVIATIONBAR_SEEDER_HISTORICAL_START=2018-01-01
OPENDEVIATIONBAR_SEEDER_MAX_CONCURRENT_DOWNLOADS=2
```

**Kintsugi parallelism SSoT**: All kintsugi parallelism values live in this file. See [/scripts/CLAUDE.md](/scripts/CLAUDE.md#kintsugi-parallelism-tuning-bigblack) for the meaning of each value and tuning guidance.

---

## Monit Configuration (`monit/`)

| File                             | Deployed To                                         | Purpose                                              |
| -------------------------------- | --------------------------------------------------- | ---------------------------------------------------- |
| `opendeviationbar.conf`          | `/etc/monit/conf.d/opendeviationbar.conf`           | 10 monitors: processes, ports, filesystem, system    |
| `monit-override.conf`            | `/etc/systemd/system/monit.service.d/override.conf` | systemd drop-in: allow uid/gid exec (CAP_SETUID etc) |
| `obdpy-monit-alert.sh`           | `/usr/local/bin/obdpy-monit-alert.sh`               | Telegram alert on monit events                       |
| `obdpy-monit-heartbeat.sh`       | `/usr/local/bin/obdpy-monit-heartbeat.sh`           | Self-report every 10 monit cycles                    |
| `obdpy-check-heartbeat-timer.sh` | `/usr/local/bin/obdpy-check-heartbeat-timer.sh`     | Exit-code check: did timer run on schedule?          |

### Syncing Monit Config (Requires sudo)

```bash
scp deploy/monit/opendeviationbar.conf bigblack:/tmp/
ssh bigblack 'sudo cp /tmp/opendeviationbar.conf /etc/monit/conf.d/ && sudo monit reload'
```

### Why the systemd Drop-in (`monit-override.conf`)

Monit runs as root but needs to exec commands `as uid "tca"`. systemd 247+ hardening flags block this by default. The drop-in sets:

- `NoNewPrivileges=false`
- `CapabilityBoundingSet` includes `CAP_SETUID`, `CAP_SETGID`, `CAP_SYS_PTRACE`
- `ProtectHome=no`
- `PrivateTmp=no`

Without these:

- **Missing `NoNewPrivileges=false` + caps**: monit alert scripts silently fail (no Telegram message).
- **Missing `PrivateTmp=no`**: monit runs with a private `/tmp`+`/var/tmp` namespace, making files written by services to the real `/var/tmp/` invisible to `check file` monitors. Symptom: `seeder-state` permanently shows "Does not exist" even though the file exists on disk. Diagnosed 2026-03-13 via `sudo nsenter -t $(monit PID) -m -- ls /var/tmp/`.

### Alert Script Pattern

`obdpy-monit-alert.sh` sources both env files to get `TOKEN` (from `local.env`) and `TELEGRAM_CHAT_ID` (from `opendeviationbar.env`), then calls the Telegram API. This keeps secrets out of the monit config file (which is root-readable).

---

## Deploy Workflow

Full pipeline: `mise run deploy:bigblack`

What it does:

1. Syncs git repo on bigblack (`git fetch && git reset --hard origin/main`)
2. Installs new wheel into `.venv` (from PyPI or local wheel SCP fallback)
3. Copies systemd units to `~/.config/systemd/user/`
4. `systemctl --user daemon-reload && enable --now` timers
5. Restarts all services (monit unmonitor first, start sidecar then kintsugi)
6. `sudo monit monitor sidecar && sudo monit monitor kintsugi`

For Python-only hotfixes (no Rust rebuild): use `/odb-ship hotfix` skill — rsyncs Python files to site-packages directly.

**Full deploy procedure**: [/.claude/skills/odb-ship/SKILL.md](/.claude/skills/odb-ship/SKILL.md)

---

## local.env (Secrets — Not in Git)

```ini
OPENDEVIATIONBAR_TELEGRAM_TOKEN=<bot token>
OPENDEVIATIONBAR_REDIS_PASSWORD=<redis pw>
```

This file lives at `~/opendeviationbar-py/deploy/opendeviationbar.local.env` on bigblack. It survives `git reset --hard` (not tracked). Never commit it.

---

## Related

- [/scripts/systemd/CLAUDE.md](/scripts/systemd/CLAUDE.md) — systemd service units, dependency chain, OOM protection
- [/scripts/CLAUDE.md](/scripts/CLAUDE.md) — kintsugi parallelism tuning, monitoring
- [/CLAUDE.md](/CLAUDE.md) — project hub
