- 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
5.6 KiB
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
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—UnitandConfigstructs;Load,Save,FindUnit,AddUnit,ResolveOrder(fillsUnit.After/StartDelay, run byLoad)runtime—ContainerRuntimeinterface;docker.go,podman.goimplementationsservice—Resolve(unit)→ start/stop commands, type, workdir; shared by all backendsinitsys—Initinterface;New(cfg)picks backend; cmd/ only talks toInitsystemd—generator.gobuilds.servicefile content;manager.gocallssystemctlopenrc,runit— same role; root only; tests stub command execution via package vars
Config file
Location: /etc/unitdore/units.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 frameworkgopkg.in/yaml.v3— config parsing
Testing
Tests exist in:
config/config_test.goruntime/runtime_test.gocmd/syncup_test.gocmd/bulk_test.gosysuser/sysuser_test.goservice/spec_test.goinitsys/initsys_test.goopenrc/openrc_test.gorunit/runit_test.gosystemd/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 combinationdocs/unitdore.1— man page