Authoring background service scripts
Overview
Turn ad‑hoc “run this in another terminal” workflows into a small gum-driven CLI that owns process lifecycle, logs, and health signals. One service → one directory under ~/.server/<server_name> for PID and logs; stop and restart must consult PID file and listening port before treating a process as stopped or safe to replace.
When to use
- Script wraps a daemon or dev server that should outlive the launching shell.
- Operators need predictable subcommands: at minimum start, stop, restart; usually also install (deps/binary) and update (pull/build/restart).
- UX should be interactive menus/prompts in the terminal (gum).
- You must avoid zombie processes and false “stopped” states when PID files are stale.
When not to use
- One-off cron jobs or foreground-only tools with no daemon semantics.
- Production orchestration (systemd/Kubernetes) — different contracts.
- Requirements that are fully enforceable by a Makefile or package manager alone.
Gum bootstrap
- All interactive UX goes through gum (choose, confirm, input, spin, style).
- If
gum is missing, install via:
curl -LsSf https://raw.githubusercontent.com/farfarfun/fundeploy/master/scripts/tools/utils/setup.sh | bash -s -- gum --force
Re-check command -v gum after install; fail fast with a clear message if still unavailable.
Required CLI surface
Ship a single entrypoint (e.g. mysvc or scripts/server) that implements at least:
| Command |
Purpose |
start |
Launch service in background; write PID; tee or append logs. |
stop |
Stop using PID file; confirm with port check when configured. |
restart |
stop then start (reuse the same rules). |
update |
Refresh artifacts (git pull/build); typically ends with restart. |
install |
First-time setup (deps, dirs, defaults) — may call start after. |
Add status, logs, or doctor only if they reduce support load; document them in Quick reference.
Data layout
Per service server_name:
- Root:
~/.server/<server_name>/
pid file: single line, numeric PID written atomically after successful background start.
- Logs: e.g.
logs/stdout.log, logs/stderr.log or a single rotating file; include timestamps.
- Optional:
port file or config fragment if port is dynamic but must be tracked for checks.
Never scatter PID/logs across /tmp without namespacing — the ~/.server/<server_name> contract is the stable contract.
Stop and restart rules
- Read PID from
~/.server/<server_name>/pid (if missing, treat as not running; still run port check if port known).
- Signal process (
TERM, then short wait, then KILL if needed).
- Port check: if the service is known to bind a TCP port, verify nothing listens after stop (e.g.
lsof/platform-specific). If port still open, surface error via gum and exit non‑zero.
- Cleanup: remove or invalidate PID file only after success criteria (process gone and port free when applicable).
- restart: must not start a second instance until stop succeeds.
Quick reference
- Start:
nohup/setsid or background & + disown — ensure controlling TTY does not kill the service; redirect logs under ~/.server/.../logs/.
- Idempotence:
start should refuse or offer “already running” when PID+port indicate health.
- Observability: log rotation or truncation policy; document where to
tail.
- Failures: non‑zero exit; gum message states what failed (missing binary, port busy, stale PID).
Common mistakes
- Writing PID before the child process is definitely running (race → wrong PID).
- Trusting PID alone when wrappers spawn children that hold the port.
- Using gum only in
install but falling back to naked echo/read elsewhere (inconsistent UX).
- Leaving logs only on stdout of the parent shell (lost on disconnect).
Verification
Before merging or publishing the CLI:
- Cold machine: no
gum → installer path runs → gum works.
start: service runs detached; PID file matches live process; port responds (if applicable); logs append.
stop: process ends; port free; PID file cleared or marked inactive.
restart: never two listeners on the same port; logs show cycle.
update/install: simulate failure (bad network) — script exits non‑zero, no partial PID state, gum shows cause.
Org note: Treat major edits like code: run at least one baseline scenario without this doc and one with it when changing enforcement-heavy sections (same discipline as superpowers:writing-skills).
1---2name: authoring-background-service-scripts3description: Use when building shell CLIs that manage long-running local services (start/stop/restart/update/install), interactive prompts via Charm gum, PID files under ~/.server/<name>, and port-based liveness checks alongside PID.4---56# Authoring background service scripts78## Overview910Turn ad‑hoc “run this in another terminal” workflows into a **small gum-driven CLI** that owns process lifecycle, logs, and health signals. One service → one directory under `~/.server/<server_name>` for PID and logs; **stop** and **restart** must consult **PID file** and **listening port** before treating a process as stopped or safe to replace.1112## When to use1314- Script wraps a daemon or dev server that should **outlive the launching shell**.15- Operators need predictable subcommands: at minimum **start**, **stop**, **restart**; usually also **install** (deps/binary) and **update** (pull/build/restart).16- UX should be **interactive menus/prompts** in the terminal (gum).17- You must avoid zombie processes and false “stopped” states when PID files are stale.1819## When not to use2021- One-off cron jobs or foreground-only tools with no daemon semantics.22- Production orchestration (systemd/Kubernetes) — different contracts.23- Requirements that are fully enforceable by a Makefile or package manager alone.2425## Gum bootstrap2627- **All** interactive UX goes through **gum** (choose, confirm, input, spin, style).28- If `gum` is missing, install via:2930```bash31curl -LsSf https://raw.githubusercontent.com/farfarfun/fundeploy/master/scripts/tools/utils/setup.sh | bash -s -- gum --force32```3334Re-check `command -v gum` after install; fail fast with a clear message if still unavailable.3536## Required CLI surface3738Ship a single entrypoint (e.g. `mysvc` or `scripts/server`) that implements at least:3940| Command | Purpose |41|-----------|---------|42| `start` | Launch service **in background**; write PID; tee or append logs. |43| `stop` | Stop using **PID file**; confirm with **port** check when configured. |44| `restart` | `stop` then `start` (reuse the same rules). |45| `update` | Refresh artifacts (git pull/build); typically ends with `restart`. |46| `install` | First-time setup (deps, dirs, defaults) — may call `start` after. |4748Add `status`, `logs`, or `doctor` only if they reduce support load; document them in **Quick reference**.4950## Data layout5152Per service `server_name`:5354- Root: `~/.server/<server_name>/`55- **`pid` file**: single line, numeric PID written atomically after successful background start.56- **Logs**: e.g. `logs/stdout.log`, `logs/stderr.log` or a single rotating file; include timestamps.57- Optional: `port` file or config fragment if port is dynamic but must be tracked for checks.5859Never scatter PID/logs across `/tmp` without namespacing — the `~/.server/<server_name>` contract is the stable contract.6061## Stop and restart rules62631. **Read PID** from `~/.server/<server_name>/pid` (if missing, treat as not running; still run port check if port known).642. **Signal process** (`TERM`, then short wait, then `KILL` if needed).653. **Port check**: if the service is known to bind a TCP port, verify nothing listens after stop (e.g. `lsof`/platform-specific). If port still open, surface error via gum and exit non‑zero.664. **Cleanup**: remove or invalidate PID file only after success criteria (process gone **and** port free when applicable).675. **restart**: must not start a second instance until stop succeeds.6869## Quick reference7071- **Start**: `nohup`/`setsid` or background `&` + disown — ensure controlling TTY does not kill the service; redirect logs under `~/.server/.../logs/`.72- **Idempotence**: `start` should refuse or offer “already running” when PID+port indicate health.73- **Observability**: log rotation or truncation policy; document where to `tail`.74- **Failures**: non‑zero exit; gum message states *what* failed (missing binary, port busy, stale PID).7576## Common mistakes7778- Writing PID before the child process is definitely running (race → wrong PID).79- Trusting PID alone when wrappers spawn children that hold the port.80- Using gum only in `install` but falling back to naked `echo/read` elsewhere (inconsistent UX).81- Leaving logs only on stdout of the parent shell (lost on disconnect).8283## Verification8485Before merging or publishing the CLI:86871. **Cold machine**: no `gum` → installer path runs → `gum` works.882. **`start`**: service runs detached; PID file matches live process; port responds (if applicable); logs append.893. **`stop`**: process ends; port free; PID file cleared or marked inactive.904. **`restart`**: never two listeners on the same port; logs show cycle.915. **`update`/`install`**: simulate failure (bad network) — script exits non‑zero, no partial PID state, gum shows cause.9293**Org note:** Treat major edits like code: run at least one **baseline scenario without** this doc and one **with** it when changing enforcement-heavy sections (same discipline as `superpowers:writing-skills`).