- config: load/save/add unit, Unit struct - runtime: podman + docker discovery and exists check - cmd: syncup (discover + reconcile), edit, cobra root - PLAN.md: full project plan
5.4 KiB
Unitdore — Project Plan
A door you open and close for container units. Unitdore bridges your container runtime and systemd.
What It Is
A Go CLI tool that bridges your container runtime (Podman, Docker, or custom) and systemd.
It discovers running containers, stores them in a config file, and generates + manages systemd
.service units for each one.
Think of it as: config → systemd units → running services.
Goals
- Unified config for containers across runtimes and users
- Auto-generate systemd service files (no manual unit writing)
- Reconcile: disable units whose containers no longer exist
- Simple CLI — no daemon, no background process, no magic
Non-Goals
- Not a container runtime (doesn't replace Podman/Docker)
- Not Kubernetes / Compose / Quadlet
- Not a daemon — runs on demand
Config File
Location: /etc/unitdore/units.yaml
Owned by: root
Modified by: unitdore edit (opens in $EDITOR) or automatically by syncup
units:
- name: nginx
runtime: podman # podman | docker | exec
user: hein # empty = root/system unit; set = user unit for this user
command: "" # optional: override the ExecStart command entirely
order: 1 # startup order (lower = earlier)
delay: 0s # delay after previous order group finishes
enabled: true # false = unit exists but is not installed/started
disabled_reason: "" # auto-set by reconcile: "container not found", etc.
Commands
unitdore syncup
Discovers all currently running containers across configured runtimes and adds any new ones to the config. Does not remove existing entries.
- Runs
podman ps --format jsonand/ordocker ps --format json - Compares against existing config entries by name
- Appends new units with
enabled: true - Writes updated config back to disk
- Also reconciles: checks if containers for existing units still exist.
If not → sets
enabled: false+disabled_reason
unitdore edit
Opens /etc/unitdore/units.yaml in $EDITOR (fallback: vi).
unitdore install
Generates and installs systemd .service files for all enabled units.
- For units with
user: ""→ writes to/etc/systemd/system/unitdore-<name>.servicethen runssystemctl daemon-reload - For units with
user: hein→ writes to/home/hein/.config/systemd/user/unitdore-<name>.servicethen runssystemctl --user daemon-reloadas that user - Does not enable or start — just installs the unit files
- Removes unit files for disabled units and reloads
unitdore active
Enables and starts all installed, enabled units.
- Runs
systemctl enable --now unitdore-<name>.servicefor system units - Runs
systemctl --user enable --now unitdore-<name>.servicefor user units - Reports success/failure per unit
unitdore status
Prints a summary table of all managed units.
NAME RUNTIME USER ENABLED SYSTEMD STATUS REASON
nginx podman — yes active (running)
myapp docker hein yes active (running)
oldthing podman hein no inactive container not found
Generated Service File (example)
# /etc/systemd/system/unitdore-nginx.service
# Generated by unitdore — do not edit manually
[Unit]
Description=Unitdore: nginx (podman)
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/podman start -a nginx
ExecStop=/usr/bin/podman stop nginx
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
For user units, User= and Group= are set and it targets default.target.
Project Structure
unitdore/
├── main.go
├── go.mod
├── cmd/
│ ├── root.go # cobra root, version, config path flag
│ ├── syncup.go # discover + reconcile
│ ├── edit.go # open $EDITOR
│ ├── install.go # generate + write unit files
│ ├── active.go # enable + start units
│ └── status.go # status table
├── config/
│ └── config.go # load, save, Unit struct, YAML marshal/unmarshal
├── runtime/
│ ├── runtime.go # interface: ListContainers() []Container
│ ├── podman.go # podman ps --format json
│ └── docker.go # docker ps --format json
└── systemd/
├── generator.go # build .service file content from a Unit
└── manager.go # install, reload, enable, start, status via systemctl
Dependencies
github.com/spf13/cobra— CLI frameworkgopkg.in/yaml.v3— config file- Standard library for everything else (exec, os, text/template)
Phased Build
| Phase | Deliverable |
|---|---|
| 1 | Scaffold: project, go.mod, cobra root, config load/save |
| 2 | syncup — discover containers, update config |
| 3 | install — generate + write service files |
| 4 | active + status |
| 5 | Reconcile (auto-disable missing units) in syncup |
| 6 | User unit support (rootless containers) |
| 7 | Polish: edit, error handling, --dry-run flag |
Open Questions
- Should
syncupalso accept a--runtimeflag to limit discovery to one runtime? - Should
installimply asyncup --reconcilefirst, or be kept separate? - Packaging: just a binary, or also a Makefile / install script?