# 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` ```yaml 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 json` and/or `docker 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-.service` then runs `systemctl daemon-reload` - For units with `user: hein` → writes to `/home/hein/.config/systemd/user/unitdore-.service` then runs `systemctl --user daemon-reload` as 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-.service` for system units - Runs `systemctl --user enable --now unitdore-.service` for 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) ```ini # /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 framework - `gopkg.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 `syncup` also accept a `--runtime` flag to limit discovery to one runtime? - [ ] Should `install` imply a `syncup --reconcile` first, or be kept separate? - [ ] Packaging: just a binary, or also a Makefile / install script?