Anchor the .gitignore binary entry so pkg/*/unitdore is tracked. Document Gentoo and Void installs in the README.
301 lines
9.2 KiB
Markdown
301 lines
9.2 KiB
Markdown
# unitdore
|
|
|
|
> *A door you open and close for container units.*
|
|
|
|
Unitdore bridges your container runtime (Podman, Docker) and your init system (systemd, OpenRC, runit). It discovers running containers and podman-compose stacks, stores them in a config file, and generates + manages a service for each one.
|
|
|
|
## Install
|
|
|
|
**Arch Linux (AUR)**
|
|
|
|
```bash
|
|
yay -S unitdore
|
|
```
|
|
|
|
**Debian / Ubuntu**
|
|
|
|
Download the `.deb` from the [releases page](https://git.warky.dev/wdevs/unitdore/releases) and install:
|
|
|
|
```bash
|
|
sudo dpkg -i unitdore_*.deb
|
|
```
|
|
|
|
**CentOS / AlmaLinux / Rocky Linux**
|
|
|
|
Download the `.rpm` from the [releases page](https://git.warky.dev/wdevs/unitdore/releases) and install:
|
|
|
|
```bash
|
|
sudo rpm -i unitdore-*.rpm
|
|
# or with dnf:
|
|
sudo dnf install unitdore-*.rpm
|
|
```
|
|
|
|
**Gentoo (OpenRC / systemd)**
|
|
|
|
Ebuild in `pkg/gentoo/app-containers/unitdore/`. Copy it into a local overlay, then:
|
|
|
|
```bash
|
|
ebuild unitdore-<ver>.ebuild manifest # once per version
|
|
sudo emerge app-containers/unitdore
|
|
```
|
|
|
|
**Void Linux (runit)**
|
|
|
|
Template in `pkg/void/srcpkgs/unitdore/`. Copy it into a `void-packages` checkout, then:
|
|
|
|
```bash
|
|
./xbps-src pkg unitdore
|
|
sudo xbps-install --repository=hostdir/binpkgs unitdore
|
|
```
|
|
|
|
Set `init: runit` / `init: openrc` in the config if autodetect picks the wrong one.
|
|
|
|
**From source**
|
|
|
|
```bash
|
|
make build
|
|
sudo make install
|
|
```
|
|
|
|
Or manually:
|
|
|
|
```bash
|
|
go build -o unitdore .
|
|
sudo cp unitdore /usr/local/bin/
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Discover running containers
|
|
|
|
```bash
|
|
sudo unitdore syncup
|
|
```
|
|
|
|
Queries Podman and Docker for running containers, adds new ones to the config, and reconciles any that have disappeared (marking them disabled).
|
|
|
|
- Podman first; Docker only for names Podman doesn't have
|
|
- podman-compose containers → one `kind: stack` unit per project; existing member units disabled (`part of stack <project>`)
|
|
- Root: root's containers + rootless Podman containers of each user in `users:`
|
|
- Normal user: own Podman containers, config `~/.config/unitdore/units.yaml`, user units only
|
|
|
|
### Edit the config
|
|
|
|
```bash
|
|
sudo unitdore edit
|
|
```
|
|
|
|
Opens `/etc/unitdore/units.yaml` in `$EDITOR` (falls back to `vi`).
|
|
|
|
### Install systemd unit files
|
|
|
|
```bash
|
|
sudo unitdore install
|
|
```
|
|
|
|
Generates `.service` files for all enabled units and writes them to systemd. Use `--dry-run` to preview without writing.
|
|
|
|
```bash
|
|
sudo unitdore install --dry-run
|
|
```
|
|
|
|
### Enable and start units
|
|
|
|
```bash
|
|
sudo unitdore active
|
|
```
|
|
|
|
Runs `systemctl enable --now` for all installed, enabled units in startup order.
|
|
|
|
### Check status
|
|
|
|
```bash
|
|
sudo unitdore status
|
|
```
|
|
|
|
Prints a summary table:
|
|
|
|
```
|
|
NAME RUNTIME USER ENABLED INSTALLED STATE REASON
|
|
nginx podman root yes yes active
|
|
myapp docker hein yes yes active
|
|
oldthing podman hein no no — container not found
|
|
```
|
|
|
|
## Config file
|
|
|
|
**Location:** `/etc/unitdore/units.yaml`
|
|
|
|
```yaml
|
|
init: "" # optional: systemd | openrc | runit (empty = autodetect)
|
|
service_dir: "" # optional: runit supervised dir (empty = /var/service or /etc/service)
|
|
prefix: "" # optional: prepended to all service names, e.g. "prod-"
|
|
suffix: "" # optional: appended to all service names, e.g. "-svc"
|
|
users: [hein] # root only: rootless users scanned by syncup
|
|
|
|
units:
|
|
- name: nginx
|
|
runtime: podman # podman | docker
|
|
user: "" # empty = root/system unit
|
|
command: "" # optional: override ExecStart entirely
|
|
order: 1 # startup order (lower = earlier)
|
|
delay: 0s # delay after previous order group
|
|
enabled: true
|
|
disabled_reason: "" # auto-set by syncup reconciliation
|
|
|
|
- name: myapp
|
|
runtime: docker
|
|
user: hein # rootless: generates user unit for this user
|
|
order: 2
|
|
delay: 5s
|
|
enabled: true
|
|
|
|
- name: shop # = compose project name
|
|
kind: stack # container (default) | stack
|
|
runtime: podman
|
|
user: hein
|
|
project: shop
|
|
working_dir: /srv/shop # from compose labels (refreshed by syncup)
|
|
compose_files: [compose.yaml]
|
|
compose_bin: /usr/bin/podman-compose
|
|
order: 3
|
|
enabled: true
|
|
|
|
- name: backup # shell script unit
|
|
runtime: script
|
|
script: /opt/scripts/backup.sh # required
|
|
stop_script: /opt/scripts/stop.sh # optional → ExecStop
|
|
shell: /bin/bash # optional, default /bin/bash
|
|
service_type: simple # simple (default) | oneshot
|
|
working_dir: /opt/scripts # optional
|
|
order: 4
|
|
enabled: true
|
|
|
|
- name: agent # executable unit
|
|
runtime: exec
|
|
executable: /opt/agent/bin/agent # required
|
|
args: [--config, /etc/agent.toml] # optional
|
|
stop_executable: /opt/agent/bin/stop # optional → ExecStop
|
|
order: 5
|
|
enabled: true
|
|
```
|
|
|
|
### Init systems
|
|
|
|
Set `init:` in the config, or leave empty to autodetect (`/run/systemd/system` → systemd, `/run/openrc` → openrc, `/etc/runit` → runit, else systemd).
|
|
|
|
| | systemd | openrc | runit |
|
|
|---|---|---|---|
|
|
| Service file | `/etc/systemd/system/unitdore-<n>.service` | `/etc/init.d/unitdore-<n>` | `/etc/sv/unitdore-<n>/{run,finish}` + symlink in `service_dir` |
|
|
| Start / stop | `systemctl` | `rc-service` | `sv` |
|
|
| Enable at boot | `systemctl enable` | `rc-update add … default` | remove `down` file |
|
|
| Installed state | disabled until enabled | not in runlevel | symlinked with `down` file (no autostart) |
|
|
| Rootless `user:` | `systemctl --user` unit (needs linger) | system service, `command_user` | system service, `chpst -u` |
|
|
| Non-root use | yes (own user units) | **root only** | **root only** |
|
|
| `restart*` | `Restart=on-failure`, `StartLimitBurst` | `supervise-daemon` `respawn_*` (always respawns) | `finish`: on-failure, `RestartSec`; **no retry limit** |
|
|
| `docker` dependency | `Requires=docker.service` | `need docker` | none |
|
|
| `oneshot` | `RemainAfterExit` | `start()`/`stop()` functions | run once, then `tail -f /dev/null` |
|
|
|
|
- Rootless units on openrc/runit set `HOME` and `XDG_RUNTIME_DIR=/run/user/<uid>` and create that dir if missing.
|
|
- `command` / `args` use systemd quoting; they are re-quoted for sh on openrc/runit.
|
|
- No log redirection is generated on openrc; use the runtime's own logs.
|
|
- `docs/generated-units.md` shows systemd output only.
|
|
|
|
### Script / executable units
|
|
|
|
Added manually (`unitdore edit`); syncup never creates or disables them.
|
|
|
|
| Key | Runtime | Meaning |
|
|
|---|---|---|
|
|
| `script` | script | Required. `ExecStart=<shell> <script>` |
|
|
| `stop_script` | script | Optional. `ExecStop=-<shell> <stop_script>` |
|
|
| `shell` | script | Interpreter, default `/bin/bash` |
|
|
| `executable` | exec | Required. `ExecStart=<executable> <args>` |
|
|
| `args` | exec | Optional list, quoted as needed |
|
|
| `stop_executable` | exec | Optional. `ExecStop=-<stop_executable>` |
|
|
| `service_type` | both | `simple` (default) or `oneshot` (RemainAfterExit=yes) |
|
|
| `working_dir` | both | Optional `WorkingDirectory` |
|
|
| `command` | both | If set, overrides ExecStart |
|
|
| `user` | both | Set for a user unit |
|
|
|
|
### Stacks (podman-compose)
|
|
|
|
| Condition | Unit |
|
|
|---|---|
|
|
| `compose_bin`, `working_dir`, `compose_files` exist | `podman-compose -p <proj> -f <files> up` / `stop`, `Type=simple` |
|
|
| any missing | `podman pod start/stop pod_<proj>`, `Type=oneshot` |
|
|
| compose missing and no pod | `syncup` disables: `stack not found` |
|
|
|
|
### Users
|
|
|
|
| Run as | Config | Discovers | Units |
|
|
|---|---|---|---|
|
|
| root | `/etc/unitdore/units.yaml` | root + `users:` (Podman only for users) | system + user units |
|
|
| user | `~/.config/unitdore/units.yaml` | own Podman | own user units |
|
|
|
|
User units need linger to start at boot: `sudo loginctl enable-linger <user>` (`install` warns).
|
|
|
|
### Prefix and suffix
|
|
|
|
Set `prefix` and/or `suffix` to namespace your service files:
|
|
|
|
```yaml
|
|
prefix: prod-
|
|
suffix: ""
|
|
```
|
|
|
|
Generates: `unitdore-prod-nginx.service`, `unitdore-prod-myapp.service`, etc.
|
|
Prefix/suffix apply to the **service file name only** — container names are unchanged.
|
|
|
|
### Unnamed containers
|
|
|
|
If a container has no name, `syncup` uses its short container ID (first 12 chars) as the unit name.
|
|
You can rename it later with `unitdore edit`.
|
|
|
|
## Generated unit files
|
|
|
|
See **[docs/generated-units.md](docs/generated-units.md)** for full examples covering:
|
|
- System units (root)
|
|
- User units (rootless)
|
|
- Docker vs Podman
|
|
- Custom commands
|
|
- Compose stacks (compose + pod fallback)
|
|
- Prefix/suffix naming
|
|
- Startup ordering
|
|
- Unnamed containers (short ID fallback)
|
|
|
|
Quick 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
|
|
```
|
|
|
|
## Flags
|
|
|
|
```
|
|
--config string path to units config file (default "/etc/unitdore/units.yaml"; non-root: ~/.config/unitdore/units.yaml)
|
|
```
|
|
|
|
## Requirements
|
|
|
|
- Go 1.21+
|
|
- systemd
|
|
- Podman and/or Docker (only what you have installed is used)
|
|
- podman-compose for compose-mode stacks (pod fallback without it)
|
|
- Root for system units; the relevant user for rootless units
|