Initial commit: unitdore scaffold + syncup/edit commands
- 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
This commit is contained in:
182
PLAN.md
Normal file
182
PLAN.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# 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?
|
||||
Reference in New Issue
Block a user