---
name: service-kicad-admin
description: >
  Adom-internal operations guide for the service-kicad container.
  SSH access, watchdog architecture, deploy procedures, credential
  rotation, container topology, KiCad upgrades, and known traps.
  NOT for external users — they use the public service-kicad skill.
  Trigger words: service-kicad admin, service-kicad container,
  kicad watchdog, service-kicad ssh, service-kicad deploy,
  service-kicad upgrade, kicad container ops.
---

# service-kicad Admin — Container Operations

Internal guide for Adom engineers operating the service-kicad
infrastructure. External users should use the public `service-kicad`
skill (CLI usage only).

**Repo:** `adom-inc/service-kicad` (private)
**Port:** 8780
**API contract:** `docs/service-kicad-api.md` in the repo.

## Container topology

| Label | SSH | Public host | KiCad | Watchdog | Status |
|---|---|---|---|---|---|
| **v10 default** | `john-service-kicad-ro8mkwui89vi@adom.cloud` | `kicad-rk5ue5pcfemi.adom.cloud` | 10.0.x | source-pull (auto-deploys) | **active** |
| v9 fallback | `john-service-kicad-xqkh3lhfl43f@adom.cloud` | _(no mapping)_ | 9.0.x | health-only | **dormant** |
| v10 secondary | `john-service-kicad-g566fapq3k5v@adom.cloud` | _(no mapping)_ | 10.0.x | health-only | **retired** |

Rediscover current containers:
```bash
adom-cli carbon containers list \
  | jq -r '.[] | select(.repository.name == "service-kicad") | "\(.slug)  \(.ssh_credentials.command)  \(.port_mappings)"'
```

## Modifying the API code

1. Clone: `git clone https://github.com/adom-inc/service-kicad.git`
2. Edit `api/src/main.rs` (the Axum HTTP server)
3. Test locally: `cargo build --release --bin service-kicad-api && ./target/release/service-kicad-api`
4. Push to `origin/main`
5. The v10 default container auto-deploys within 2 minutes via `watchdog.sh`

## Watchdog architecture

### Per-container watchdog (`watchdog.sh`, cron */2)

Runs on the production container:
1. `git fetch origin main` (exits on auth failure)
2. If HEAD moved: `git reset --hard origin/main`, `cargo build --release`
3. `sudo install` binaries to `/usr/local/bin/`
4. `kill_api()` — uses `pgrep -f` + `readlink /proc/PID/exe` to find
   and kill the running API (safe against 15-char comm truncation)
5. `launch_api()` — sources `/etc/default/service-kicad`, nohup launch
6. Health-check; relaunch if down

### Watchdog-of-watchdogs (`sk-watchdog-of-watchdogs.sh`, cron */5)

Runs on the gallia dev box. Monitors remote containers because PID 1
is sshd (not systemd) — cron doesn't survive container restarts:
1. External health check via public URL
2. If down: SSH in, check cron + API status
3. Remediate: `sudo service cron start`, relaunch API
4. Notify via `adom-cli notify`

### Credential watchdog (`sk-credential-watchdog.sh`, cron */5)

Runs on the gallia dev box. Checks git fetch health on the production
container and auto-refreshes the GitHub token from local `gh auth`:
1. Health-check the public URL
2. SSH in, `git fetch --dry-run`
3. If fetch fails: grab `gh auth token` locally, push to
   `~/.git-credentials` on the container
4. Report deployed vs latest commit

## Git credential rotation

The production container uses a `gho_` OAuth token in
`~/.git-credentials`. These tokens expire. Manual refresh:

```bash
gh auth token | ssh john-service-kicad-ro8mkwui89vi@adom.cloud '
  umask 077; tok=$(cat)
  printf "https://x-access-token:%s@github.com\n" "$tok" > ~/.git-credentials
  git config --global credential.helper store
'
```

Automated: `sk-credential-watchdog.sh` (cron */5 on gallia dev box).

## PID-1-is-sshd fragility

PID 1 on every service-kicad container is `/bin/sh -c sudo /usr/sbin/sshd -D`.
Cron never auto-starts on container restart. If the container restarts:
- Watchdog.sh stops → no auto-deploy, no health recovery
- Any API crash is permanent until someone SSHes in

Fix from outside: `ssh ... 'sudo service cron start'`

Hardening (add to `~/.bashrc`):
```bash
pgrep -x cron >/dev/null || sudo service cron start
```

## Crontab setup

The production container's crontab MUST have the PATH line:
```
PATH=/home/adom/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/2 * * * * $HOME/service-kicad/watchdog.sh >> $HOME/.cache/service-kicad-watchdog.log 2>&1
```

Without the PATH line, `cargo build` fails with `cargo: command not found`.

## KiCad version upgrade

```bash
ssh john-service-kicad-ro8mkwui89vi@adom.cloud "
  sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
  sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq kicad kicad-libraries
  kicad-cli version
"
```

No restart needed — `kicad-cli` is a subprocess, so the running API
picks up the new binary immediately.

## Provisioning a new container

```bash
adom-cli carbon user repos create --slug service-kicad \
  --description "shared headless KiCad + kicad-packages3d"
REPO_ID=$(adom-cli carbon user repos get service-kicad --format json | jq -r '.id')
adom-cli carbon containers create \
  --image-id 69b43b3e58d13e5ce628cdc5 \
  --name service-kicad \
  --repo-id "$REPO_ID"
# SSH in and run deploy.sh
```

## Cross-container deploy (for revived fallbacks)

Only needed if v9 or a new fallback is brought online:
```bash
# Pull binaries from v10 default
scp john-service-kicad-ro8mkwui89vi@adom.cloud:/usr/local/bin/service-kicad-api /tmp/
scp john-service-kicad-ro8mkwui89vi@adom.cloud:/usr/local/bin/service-kicad /tmp/
# Push to fallback
ssh TARGET@adom.cloud '
  sudo install -m 0755 /tmp/service-kicad-api /usr/local/bin/
  PID=$(pgrep -f /usr/local/bin/service-kicad-api | head -1)
  [ -n "$PID" ] && kill "$PID"; sleep 2
  setsid -f nohup /usr/local/bin/service-kicad-api >> /var/log/service-kicad.log 2>&1 < /dev/null
'
```

## Reviving the v9 fallback

```bash
adom-cli carbon containers port-add xqkh3lhfl43f --port 8780 --prefix kicad-v9
ssh john-service-kicad-xqkh3lhfl43f@adom.cloud '
  sudo service cron start
  [ -f /etc/default/service-kicad ] && { set -a; . /etc/default/service-kicad; set +a; }
  setsid -f nohup /usr/local/bin/service-kicad-api >> /var/log/service-kicad.log 2>&1 < /dev/null
'
# Add to TARGETS in sk-watchdog-of-watchdogs.sh
```

## Known traps

- **pkill -x never matches** — binary name is 17 chars, comm truncates
  to 15. Watchdog uses `readlink /proc/PID/exe` instead.
- **git reset overwrites watchdog.sh** — local patches survive at most
  2 min. All watchdog changes MUST land upstream.
- **Drill export bug** — `--generate-map false` rejected by kicad-cli;
  the `let _ = run_kicad(...)` swallows the error silently.
- **Log locations** — watchdog: `~/.cache/service-kicad-watchdog.log`;
  service: `/var/log/service-kicad.log`.

## Decommissioning history (2026-05-05)

| Container | Action | Why |
|---|---|---|
| v10 default (ro8mkwui89vi) | **kept active** | All clients hit this |
| v10 secondary (g566fapq3k5v) | **retired** | Accidental duplicate |
| v9 fallback (xqkh3lhfl43f) | **dormant, recoverable** | Legacy board compat |

Steps taken: killed API, stopped cron, wrote marker at
`/etc/service-kicad-decommissioned`, removed port mappings.
