From 60bbb633eff46c0959fe6dcf11325271860c2304 Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 11 Mar 2026 12:05:31 +0000 Subject: [PATCH] Add wiki pages --- Daemon-Lifecycle.md | 57 ++++++++++++++++++++++++++++++++++++ Home.md | 71 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 Daemon-Lifecycle.md create mode 100644 Home.md diff --git a/Daemon-Lifecycle.md b/Daemon-Lifecycle.md new file mode 100644 index 0000000..6a2ce40 --- /dev/null +++ b/Daemon-Lifecycle.md @@ -0,0 +1,57 @@ +# Daemon Lifecycle + +Module: `forge.lthn.ai/core/go-process` + +## Overview + +The `Daemon` struct orchestrates three concerns: single-instance enforcement (PID file), health monitoring (HTTP server), and fleet tracking (registry). + +## Lifecycle + +``` +NewDaemon(opts) -> Start() -> SetReady(true) -> Run(ctx) -> [ctx cancelled] -> Stop() +``` + +### Start() + +1. Acquire PID file (fails if another instance holds it) +2. Start health server (HTTP `/health` + `/ready`) +3. Auto-register in daemon registry (if configured) + +### Run(ctx) + +Blocks until `ctx.Done()`, then calls `Stop()`. + +### Stop() + +1. Set readiness to false (health server returns 503) +2. Graceful shutdown of health server (with timeout) +3. Release PID file +4. Unregister from daemon registry + +## Registry + +`Registry` tracks daemons via JSON files in a directory (default `~/.core/daemons/`). + +File naming: `{code}-{daemon}.json` (slashes replaced with dashes). + +Each file contains: +```json +{ + "code": "myapp", + "daemon": "server", + "pid": 12345, + "health": "127.0.0.1:9090", + "started": "2026-03-10T12:00:00Z" +} +``` + +`List()` and `Get()` automatically prune entries whose PIDs are no longer alive (checked via `kill -0`). + +## Health Server + +Endpoints: +- `GET /health` — Runs all registered `HealthCheck` functions. Returns 200 if all pass, 503 if any fail. +- `GET /ready` — Returns 200 when `SetReady(true)`, 503 otherwise. + +`WaitForHealth(addr, timeoutMs)` polls `/health` every 200ms until 200 or timeout. diff --git a/Home.md b/Home.md new file mode 100644 index 0000000..5d3dd4a --- /dev/null +++ b/Home.md @@ -0,0 +1,71 @@ +# go-process + +Module: `forge.lthn.ai/core/go-process` + +Process management with Core IPC integration. Provides spawning, monitoring, and controlling external processes with output streaming via the Core ACTION system. Includes PID file management, health check servers, daemon lifecycle, and a daemon registry. + +## Architecture + +| File | Purpose | +|------|---------| +| `types.go` | `Status`, `Stream`, `RunOptions`, `Info` types | +| `process.go` | `Process` struct — managed external process with output capture | +| `runner.go` | Process runner — starts processes with ring buffer capture | +| `service.go` | Core service integration — registers with Core DI container | +| `pidfile.go` | `PIDFile` — single-instance enforcement via lock files | +| `health.go` | `HealthServer` — HTTP `/health` and `/ready` endpoints | +| `daemon.go` | `Daemon` — lifecycle manager combining PID file + health + registry | +| `registry.go` | `Registry` — tracks running daemons via JSON files in `~/.core/daemons/` | +| `buffer.go` | `RingBuffer` — fixed-size circular buffer for output capture | +| `actions.go` | IPC action types for process events | +| `exec/` | `exec.Exec` — simple command execution helper | + +## Key Types + +### Process Management + +- **`Process`** — Running process: `ID`, `Command`, `Args`, `Dir`, `Env`, `Status`, `ExitCode`. Methods: `Wait()`, `Done()`, `Kill()`, `Signal()`, `SendInput()`, `CloseStdin()`, `Output()`, `Info()`, `IsRunning()` +- **`Status`** — `StatusPending`, `StatusRunning`, `StatusExited`, `StatusFailed`, `StatusKilled` +- **`RunOptions`** — `Command`, `Args`, `Dir`, `Env`, `DisableCapture` +- **`Info`** — Snapshot of process state (serialisable): `ID`, `Command`, `Args`, `PID`, `Status`, `ExitCode`, `Duration` +- **`RingBuffer`** — Circular output buffer: `Write()`, `String()`, `Bytes()` + +### Daemon Lifecycle + +- **`Daemon`** — Manages PID file + health server + registry: `Start()`, `Run()` (blocks until context cancelled), `Stop()`, `SetReady()`, `HealthAddr()` +- **`DaemonOptions`** — `PIDFile`, `ShutdownTimeout` (default 30s), `HealthAddr`, `HealthChecks`, `Registry`, `RegistryEntry` +- **`PIDFile`** — `Acquire()`, `Release()`, `IsLocked()`, `ReadPID()` +- **`HealthServer`** — HTTP server at `/health` (checks) and `/ready` (readiness): `Start()`, `Stop()`, `AddCheck()`, `SetReady()`, `Addr()` +- **`HealthCheck`** — `func() error` — returns nil if healthy + +### Daemon Registry + +- **`Registry`** — JSON file-based daemon tracker: `Register()`, `Unregister()`, `Get()`, `List()` +- **`DaemonEntry`** — `Code`, `Daemon`, `PID`, `Health`, `Project`, `Binary`, `Started` +- **`DefaultRegistry()`** — Uses `~/.core/daemons/` +- Stale entries (dead PIDs) are automatically pruned on `Get()` and `List()` + +## Usage + +```go +import "forge.lthn.ai/core/go-process" + +// Daemon mode +daemon := process.NewDaemon(process.DaemonOptions{ + PIDFile: "/var/run/myapp.pid", + HealthAddr: ":9090", + Registry: process.DefaultRegistry(), + RegistryEntry: process.DaemonEntry{Code: "myapp", Daemon: "server"}, +}) +daemon.Start() +daemon.SetReady(true) +daemon.Run(ctx) // blocks until ctx cancelled + +// Health check polling +ok := process.WaitForHealth("localhost:9090", 5000) // 5s timeout +``` + +## Dependencies + +- `github.com/stretchr/testify` — Tests only +- No core ecosystem dependencies