Files
unitdore/AGENTS.md
T
warkanum 91811b4fa1 feat: order/delay in service files, enable cmd, --all on unit commands
- install/uninstall/start/stop/enable take <name> or --all; *all kept as shortcuts
- add enable (enable at boot + start); startall no longer enables
- order/delay resolved by config.ResolveOrder and written into systemd,
  OpenRC and runit service files (After=/sleep), so they apply at boot
- install --all stops and disables units before removing their files
- update regenerates all installed, enabled units and removes disabled ones
- edit creates the config dir; docs and man page updated
- tests: fake Init for cmd, ResolveOrder, backend ordering output
2026-10-12 21:31:41 +02:00

128 lines
5.6 KiB
Markdown

## Project Overview
**Unitdore** is a Go CLI tool that bridges container runtimes (Podman, Docker) and the init system (systemd, OpenRC, runit). It discovers running containers, stores them in a YAML config, and generates + manages service files for each one.
# Agent Rules
Keep answers short. Question everything, never assume or guess. Ask if unsure.
You must use the AMCS MCP system tools. Set **unitdore** as the active project and always capture summaries of what was done as thoughts using the `capture_thought` tool. If AMCS is not available, write files to `docs/llm/log/yyyymmdd_hh.md`.
# Tools to use
When writing Go: `doc/tools/go-skill.md` (if present), otherwise follow standard Go conventions.
## Commands
```bash
make build # Compile binary: ./unitdore
make install # Install binary to /usr/bin, man page, create /etc/unitdore
make uninstall # Remove binary and man page
make test # go test ./... -v
make test-short # go test ./...
make lint # go vet ./...
make clean # Remove built binary
make release-version # Bump patch version, commit, tag, and push
```
## Architecture
```
cmd/ # Cobra commands (edit, install, enable, list, root, start, status,
# stop, syncup, uninstall, update; each of install/enable/start/
# stop/uninstall takes <name> or --all, *all = shortcuts in
# bulk.go); order.go = ordering; bulk_test.go = fake Init
config/ # Config struct + YAML load/save (units.yaml)
runtime/ # Docker and Podman runtime abstraction, compose labels
sysuser/ # run commands as root/user (runuser), home/linger lookup
testdata/stack # podman-compose test stack + captured `podman ps` fixture
service/ # init-neutral unit resolution: commands, quoting, user env
initsys/ # Init interface, backend selection (config init: / autodetect)
systemd/ # .service generator + systemctl manager (Backend)
openrc/ # /etc/init.d script generator + rc-service manager (Backend)
runit/ # /etc/sv run/finish generator + sv manager (Backend)
docs/ # generated-units.md (unit file examples), unitdore.1 (man page)
```
### Key packages
- **`config`** — `Unit` and `Config` structs; `Load`, `Save`, `FindUnit`, `AddUnit`, `ResolveOrder` (fills `Unit.After`/`StartDelay`, run by `Load`)
- **`runtime`** — `ContainerRuntime` interface; `docker.go`, `podman.go` implementations
- **`service`** — `Resolve(unit)` → start/stop commands, type, workdir; shared by all backends
- **`initsys`** — `Init` interface; `New(cfg)` picks backend; cmd/ only talks to `Init`
- **`systemd`** — `generator.go` builds `.service` file content; `manager.go` calls `systemctl`
- **`openrc`**, **`runit`** — same role; root only; tests stub command execution via package vars
### Config file
**Location:** `/etc/unitdore/units.yaml`
```yaml
init: "" # systemd | openrc | runit (empty = autodetect)
service_dir: "" # runit: supervised dir (empty = autodetect)
prefix: "" # prepended to service file name (e.g. "prod-")
suffix: "" # appended to service file name (e.g. "-svc")
users: [] # root only: rootless users scanned by syncup
units:
- name: nginx
runtime: podman # podman | docker | script | exec
user: "" # empty = system unit; set for rootless user unit
command: "" # override ExecStart entirely
order: 1 # startup order, lower = earlier; same = parallel; written into service files (After=/sleep)
delay: 0s # wait before start, own value, ignored in first group
enabled: true
disabled_reason: "" # auto-set by syncup reconciliation
restart: false # Restart=on-failure
restart_sec: 0 # RestartSec
restart_retries: 0 # StartLimitBurst
kind: container # container | stack (podman-compose project)
project: "" # stack: compose project
working_dir: "" # stack: compose dir (from labels)
compose_files: [] # stack: relative to working_dir
compose_bin: "" # stack: podman-compose path
script: "" # script: file run via shell (ExecStart)
stop_script: "" # script: optional ExecStop
shell: "" # script: interpreter, default /bin/bash
executable: "" # exec: binary run as ExecStart
args: [] # exec: arguments
stop_executable: "" # exec: optional ExecStop
service_type: "" # script/exec: simple (default) | oneshot
```
Non-root runs use `~/.config/unitdore/units.yaml` and manage only own user units.
### Generated service file locations
| Scenario | Path |
|---|---|
| System unit (root) | `/etc/systemd/system/unitdore-<name>.service` |
| User unit | `/home/<user>/.config/systemd/user/unitdore-<name>.service` |
| With prefix `prod-` | `/etc/systemd/system/unitdore-prod-<name>.service` |
| With suffix `-svc` | `/etc/systemd/system/unitdore-<name>-svc.service` |
### Libraries
- `github.com/spf13/cobra` — CLI framework
- `gopkg.in/yaml.v3` — config parsing
## Testing
Tests exist in:
- `config/config_test.go`
- `runtime/runtime_test.go`
- `cmd/syncup_test.go`
- `cmd/bulk_test.go`
- `sysuser/sysuser_test.go`
- `service/spec_test.go`
- `initsys/initsys_test.go`
- `openrc/openrc_test.go`
- `runit/runit_test.go`
- `systemd/generator_test.go`
Always run `make test` before marking a task complete. Add tests for new behaviour in the same package as the code under test.
## Additional documentation
- `docs/generated-units.md` — full annotated examples of every generated unit file combination
- `docs/unitdore.1` — man page