- 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
183 lines
5.4 KiB
Markdown
183 lines
5.4 KiB
Markdown
# 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-<name>.service`
|
|
then runs `systemctl daemon-reload`
|
|
- For units with `user: hein` → writes to `/home/hein/.config/systemd/user/unitdore-<name>.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-<name>.service` for system units
|
|
- Runs `systemctl --user enable --now unitdore-<name>.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?
|