warkanum 91811b4fa1 feat: order/delay in service files, enable cmd, --all on unit commands
- install/uninstall/start/stop/enable take <name> or --all; *all kept as shortcuts
- add enable (enable at boot + start); startall no longer enables
- order/delay resolved by config.ResolveOrder and written into systemd,
  OpenRC and runit service files (After=/sleep), so they apply at boot
- install --all stops and disables units before removing their files
- update regenerates all installed, enabled units and removes disabled ones
- edit creates the config dir; docs and man page updated
- tests: fake Init for cmd, ResolveOrder, backend ordering output
2026-10-12 21:31:41 +02:00

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)

yay -S unitdore

Debian / Ubuntu

Download the .deb from the releases page and install:

sudo dpkg -i unitdore_*.deb

CentOS / AlmaLinux / Rocky Linux

Download the .rpm from the releases page and install:

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:

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:

./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

make build
sudo make install

Or manually:

go build -o unitdore .
sudo cp unitdore /usr/local/bin/

Usage

Discover running containers

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

sudo unitdore edit

Opens /etc/unitdore/units.yaml in $EDITOR (falls back to nvim, nano, vi; creates the config dir if missing).

Install service files

sudo unitdore install <name>

Generates the service file for one enabled unit (error if unknown or disabled). Does not enable or start it.

sudo unitdore install <name> --dry-run   # preview, write nothing
sudo unitdore install --all              # all enabled units; installed + disabled ones are stopped and removed
sudo unitdore installall                 # same as install --all

Start / stop / enable / uninstall

sudo unitdore start <name>       # start only
sudo unitdore enable <name>      # enable at boot + start
sudo unitdore stop <name>
sudo unitdore uninstall <name>   # stop, disable, remove service file

Each takes --all instead of a name to act on every unit (shortcuts: startall, enableall, stopall, uninstallall, installall).

  • start/enable --all: installed, enabled units in order (lowest first); same order → parallel
  • stop/uninstall --all: all installed units, reverse order, one at a time

Order and delay at boot

order/delay are written into the generated service files, so they also apply at boot.

Backend order delay
systemd After= units of the previous group ExecStartPre=sleep N, TimeoutStartSec=N+90
OpenRC depend() { after … } start_pre() { sleep N }
runit sv -w 30 check <prev> in run sleep N in run
  • Previous group = nearest lower order among enabled units of the same user
  • delay is the unit's own, ignored in the first group
  • Changing order/enabled via update regenerates all installed, enabled units
  • Delay also applies to manual start and to restarts
  • delay must be a valid non-negative duration (5s, 1m); an invalid value fails config load

Check status

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

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                # wait before start (own delay, not first 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:

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 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:

# /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
S
Description
A door you open and close for container units. Manages systemd units for Podman/Docker containers.
Readme MIT
306 KiB
v1.0.0
Latest
2026-10-12 20:03:16 +00:00
Languages
Go 96.3%
Shell 2.2%
Makefile 1.5%