Extract init-neutral unit resolution into service/, add initsys.Init with systemd, openrc and runit backends, selected by 'init:' config or autodetect. OpenRC and runit are root only; rootless units run as system services as that user. Add docs and generated examples for all three.
664 lines
13 KiB
Markdown
664 lines
13 KiB
Markdown
# Generated Unit Files (systemd, OpenRC, runit)
|
|
|
|
Unitdore generates `.service` files from your `units.yaml` config.
|
|
This document shows every combination with annotated examples.
|
|
|
|
---
|
|
|
|
## System Unit (root, no user)
|
|
|
|
**Config:**
|
|
```yaml
|
|
units:
|
|
- name: nginx
|
|
runtime: podman
|
|
order: 1
|
|
enabled: true
|
|
```
|
|
|
|
**Written to:** `/etc/systemd/system/unitdore-nginx.service`
|
|
|
|
```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
|
|
```
|
|
|
|
**Notes:**
|
|
- `After=network.target` ensures the network is up before starting
|
|
- `WantedBy=multi-user.target` means it starts in normal boot runlevel
|
|
- `Restart=on-failure` restarts the container if it exits unexpectedly
|
|
|
|
---
|
|
|
|
## System Unit (Docker)
|
|
|
|
**Config:**
|
|
```yaml
|
|
units:
|
|
- name: redis
|
|
runtime: docker
|
|
order: 1
|
|
enabled: true
|
|
```
|
|
|
|
**Written to:** `/etc/systemd/system/unitdore-redis.service`
|
|
|
|
```ini
|
|
# /etc/systemd/system/unitdore-redis.service
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
[Unit]
|
|
Description=Unitdore: redis (docker)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/bin/docker start redis
|
|
ExecStop=/usr/bin/docker stop redis
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
**Notes:**
|
|
- Docker uses `docker start <name>` (no `-a` flag needed)
|
|
|
|
---
|
|
|
|
## User Unit (rootless container)
|
|
|
|
**Config:**
|
|
```yaml
|
|
units:
|
|
- name: myapp
|
|
runtime: podman
|
|
user: hein
|
|
order: 2
|
|
enabled: true
|
|
```
|
|
|
|
**Written to:** `/home/hein/.config/systemd/user/unitdore-myapp.service`
|
|
|
|
```ini
|
|
# ~/.config/systemd/user/unitdore-myapp.service
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
[Unit]
|
|
Description=Unitdore: myapp (podman)
|
|
After=default.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/bin/podman start -a myapp
|
|
ExecStop=/usr/bin/podman stop myapp
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
```
|
|
|
|
**Notes:**
|
|
- User units use `After=default.target` and `WantedBy=default.target`
|
|
- The file is written to the user's own systemd directory
|
|
- Unitdore calls `runuser -u hein -- systemctl --user` for enable/start/status
|
|
|
|
---
|
|
|
|
## Custom Command
|
|
|
|
**Config:**
|
|
```yaml
|
|
units:
|
|
- name: myscript
|
|
runtime: podman
|
|
command: /opt/myapp/start.sh
|
|
order: 3
|
|
enabled: true
|
|
```
|
|
|
|
**Written to:** `/etc/systemd/system/unitdore-myscript.service`
|
|
|
|
```ini
|
|
# /etc/systemd/system/unitdore-myscript.service
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
[Unit]
|
|
Description=Unitdore: myscript (podman)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/opt/myapp/start.sh
|
|
ExecStop=
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
**Notes:**
|
|
- When `command` is set, it is used verbatim as `ExecStart`
|
|
- `ExecStop` is left empty — systemd will send SIGTERM to the process group
|
|
|
|
---
|
|
|
|
## With Prefix and Suffix
|
|
|
|
**Config:**
|
|
```yaml
|
|
prefix: prod-
|
|
suffix: -svc
|
|
|
|
units:
|
|
- name: nginx
|
|
runtime: podman
|
|
order: 1
|
|
enabled: true
|
|
- name: api
|
|
runtime: docker
|
|
order: 2
|
|
enabled: true
|
|
```
|
|
|
|
**Written to:**
|
|
- `/etc/systemd/system/unitdore-prod-nginx-svc.service`
|
|
- `/etc/systemd/system/unitdore-prod-api-svc.service`
|
|
|
|
```ini
|
|
# /etc/systemd/system/unitdore-prod-nginx-svc.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
|
|
```
|
|
|
|
**Notes:**
|
|
- `prefix` and `suffix` apply to the **service file name only** — the container name stays unchanged
|
|
- Useful for distinguishing environments (`prod-`, `staging-`) or roles (`-web`, `-worker`)
|
|
|
|
---
|
|
|
|
## Container Named by Short ID
|
|
|
|
If a container has no name (e.g. started with `docker run` without `--name`), unitdore uses its short container ID (first 12 characters) as the unit name.
|
|
|
|
**Example:** Container ID `a3f2c1b9e4d7` with no name →
|
|
|
|
**Config entry added by `syncup`:**
|
|
```yaml
|
|
units:
|
|
- name: a3f2c1b9e4d7
|
|
runtime: docker
|
|
order: 1
|
|
enabled: true
|
|
```
|
|
|
|
**Written to:** `/etc/systemd/system/unitdore-a3f2c1b9e4d7.service`
|
|
|
|
**Tip:** After `syncup`, use `unitdore edit` to give it a proper name before running `install`.
|
|
|
|
---
|
|
|
|
## Startup Ordering Example
|
|
|
|
**Config:**
|
|
```yaml
|
|
units:
|
|
- name: postgres
|
|
runtime: podman
|
|
order: 1
|
|
enabled: true
|
|
|
|
- name: redis
|
|
runtime: podman
|
|
order: 1 # same order = starts in parallel
|
|
enabled: true
|
|
|
|
- name: api
|
|
runtime: podman
|
|
order: 2 # waits until order 1 group is started
|
|
enabled: true
|
|
|
|
- name: nginx
|
|
runtime: podman
|
|
order: 3
|
|
enabled: true
|
|
```
|
|
|
|
`unitdore active` starts units in ascending `order` value, lowest first.
|
|
Units with the **same order** are started together (parallel).
|
|
|
|
---
|
|
|
|
## Disabled Unit
|
|
|
|
**Config:**
|
|
```yaml
|
|
units:
|
|
- name: oldthing
|
|
runtime: podman
|
|
order: 1
|
|
enabled: false
|
|
disabled_reason: container not found
|
|
```
|
|
|
|
- `unitdore install` will **remove** the `.service` file if it exists
|
|
- `unitdore active` will **skip** disabled units
|
|
- `unitdore status` shows the reason in the REASON column
|
|
|
|
---
|
|
|
|
## Stack Unit (podman-compose, compose mode)
|
|
|
|
Used when `compose_bin`, `working_dir` and all `compose_files` exist.
|
|
|
|
```yaml
|
|
- name: shop
|
|
kind: stack
|
|
runtime: podman
|
|
project: shop
|
|
working_dir: /srv/shop
|
|
compose_files: [compose.yaml]
|
|
compose_bin: /usr/bin/podman-compose
|
|
enabled: true
|
|
```
|
|
|
|
```ini
|
|
# /etc/systemd/system/unitdore-shop.service
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
[Unit]
|
|
Description=Unitdore: shop (podman-compose stack)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=/srv/shop
|
|
ExecStart=/usr/bin/podman-compose -p shop -f compose.yaml up
|
|
ExecStop=-/usr/bin/podman-compose -p shop -f compose.yaml stop
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
---
|
|
|
|
## Stack Unit (pod fallback)
|
|
|
|
Used when the compose binary, working dir or a compose file is missing. Requires pod `pod_<project>`.
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Unitdore: shop (podman pod)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
ExecStart=/usr/bin/podman pod start pod_shop
|
|
ExecStop=-/usr/bin/podman pod stop pod_shop
|
|
```
|
|
|
|
---
|
|
|
|
## Config Behind the Init System Examples
|
|
|
|
```yaml
|
|
init: systemd # systemd | openrc | runit
|
|
units:
|
|
- {name: nginx, runtime: podman, order: 1, enabled: true}
|
|
- {name: redis, runtime: docker, order: 2, enabled: true, restart: true, restart_sec: 5, restart_retries: 3}
|
|
- {name: myapp, runtime: podman, user: alice, order: 3, enabled: true}
|
|
- {name: backup, runtime: script, script: /opt/scripts/backup.sh, stop_script: /opt/scripts/stop.sh, shell: /bin/sh, working_dir: /opt/scripts, order: 4, enabled: true}
|
|
- {name: agent, runtime: exec, executable: /opt/agent/bin/agent, args: [--config, /etc/agent.toml], service_type: oneshot, order: 5, enabled: true}
|
|
- {name: shop, runtime: podman, kind: stack, project: shop, order: 6, enabled: true}
|
|
```
|
|
|
|
---
|
|
|
|
## Script and Executable Units (systemd)
|
|
|
|
`script` runs `<shell> <script>` (default shell `/bin/bash`); `exec` runs the binary directly.
|
|
|
|
```ini
|
|
# /etc/systemd/system/unitdore-backup.service
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
[Unit]
|
|
Description=Unitdore: backup (sh script)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=/opt/scripts
|
|
ExecStart=/bin/sh /opt/scripts/backup.sh
|
|
ExecStop=-/bin/sh /opt/scripts/stop.sh
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
```ini
|
|
# /etc/systemd/system/unitdore-agent.service
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
[Unit]
|
|
Description=Unitdore: agent (executable)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
ExecStart=/opt/agent/bin/agent --config /etc/agent.toml
|
|
ExecStop=
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
---
|
|
|
|
## OpenRC
|
|
|
|
Root only. Files in `/etc/init.d/`. Rootless `user:` units run as a system service via `command_user`.
|
|
|
|
### nginx
|
|
|
|
```sh
|
|
#!/sbin/openrc-run
|
|
# /etc/init.d/unitdore-nginx
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
description="Unitdore: nginx (podman)"
|
|
|
|
command_background=yes
|
|
pidfile="/run/${RC_SVCNAME}.pid"
|
|
command="/usr/bin/podman"
|
|
command_args="start -a nginx"
|
|
|
|
depend() {
|
|
need net
|
|
}
|
|
|
|
stop_pre() {
|
|
/usr/bin/podman stop nginx || true
|
|
}
|
|
```
|
|
|
|
### redis
|
|
|
|
```sh
|
|
#!/sbin/openrc-run
|
|
# /etc/init.d/unitdore-redis
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
description="Unitdore: redis (docker)"
|
|
|
|
supervisor=supervise-daemon
|
|
respawn_delay=5
|
|
respawn_max=3
|
|
command="/usr/bin/docker"
|
|
command_args="start -a redis"
|
|
|
|
depend() {
|
|
need net
|
|
need docker
|
|
}
|
|
|
|
stop_pre() {
|
|
/usr/bin/docker stop redis || true
|
|
}
|
|
```
|
|
|
|
### myapp
|
|
|
|
```sh
|
|
#!/sbin/openrc-run
|
|
# /etc/init.d/unitdore-myapp
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
description="Unitdore: myapp (podman)"
|
|
export HOME=/home/alice
|
|
export XDG_RUNTIME_DIR=/run/user/1000
|
|
|
|
command_background=yes
|
|
pidfile="/run/${RC_SVCNAME}.pid"
|
|
command="/usr/bin/podman"
|
|
command_args="start -a myapp"
|
|
command_user="alice"
|
|
|
|
depend() {
|
|
need net
|
|
}
|
|
|
|
start_pre() {
|
|
checkpath -d -m 0700 -o alice /run/user/1000
|
|
}
|
|
|
|
stop_pre() {
|
|
su -m -s /bin/sh -c '/usr/bin/podman stop myapp' alice || true
|
|
}
|
|
```
|
|
|
|
### backup
|
|
|
|
```sh
|
|
#!/sbin/openrc-run
|
|
# /etc/init.d/unitdore-backup
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
description="Unitdore: backup (sh script)"
|
|
|
|
command_background=yes
|
|
pidfile="/run/${RC_SVCNAME}.pid"
|
|
command="/bin/sh"
|
|
command_args="/opt/scripts/backup.sh"
|
|
directory="/opt/scripts"
|
|
|
|
depend() {
|
|
need net
|
|
}
|
|
|
|
stop_pre() {
|
|
/bin/sh /opt/scripts/stop.sh || true
|
|
}
|
|
```
|
|
|
|
### agent
|
|
|
|
```sh
|
|
#!/sbin/openrc-run
|
|
# /etc/init.d/unitdore-agent
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
description="Unitdore: agent (executable)"
|
|
|
|
start() {
|
|
ebegin "Starting $RC_SVCNAME"
|
|
/opt/agent/bin/agent --config /etc/agent.toml
|
|
eend $?
|
|
}
|
|
|
|
depend() {
|
|
need net
|
|
}
|
|
```
|
|
|
|
### shop
|
|
|
|
```sh
|
|
#!/sbin/openrc-run
|
|
# /etc/init.d/unitdore-shop
|
|
# Generated by unitdore — do not edit manually
|
|
|
|
description="Unitdore: shop (podman pod)"
|
|
|
|
start() {
|
|
ebegin "Starting $RC_SVCNAME"
|
|
/usr/bin/podman pod start pod_shop
|
|
eend $?
|
|
}
|
|
|
|
stop() {
|
|
ebegin "Stopping $RC_SVCNAME"
|
|
/usr/bin/podman pod stop pod_shop
|
|
eend $?
|
|
}
|
|
|
|
depend() {
|
|
need net
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## runit
|
|
|
|
Root only. Each unit is `/etc/sv/unitdore-<name>/{run,finish}` symlinked into the service dir. Install adds a `down` file (no autostart until started/enabled). Both files are shown per unit.
|
|
|
|
### nginx
|
|
|
|
```sh
|
|
# ===== /etc/sv/unitdore-nginx/run =====
|
|
#!/bin/sh
|
|
# unitdore-nginx — generated by unitdore, do not edit manually
|
|
exec 2>&1
|
|
exec /usr/bin/podman start -a nginx
|
|
|
|
# ===== /etc/sv/unitdore-nginx/finish =====
|
|
#!/bin/sh
|
|
# unitdore-nginx — generated by unitdore, do not edit manually
|
|
/usr/bin/podman stop nginx || true
|
|
exec sv down .
|
|
```
|
|
|
|
### redis
|
|
|
|
```sh
|
|
# ===== /etc/sv/unitdore-redis/run =====
|
|
#!/bin/sh
|
|
# unitdore-redis — generated by unitdore, do not edit manually
|
|
exec 2>&1
|
|
exec /usr/bin/docker start -a redis
|
|
|
|
# ===== /etc/sv/unitdore-redis/finish =====
|
|
#!/bin/sh
|
|
# unitdore-redis — generated by unitdore, do not edit manually
|
|
/usr/bin/docker stop redis || true
|
|
[ "$1" = 0 ] && exec sv down .
|
|
sleep 5
|
|
```
|
|
|
|
### myapp
|
|
|
|
```sh
|
|
# ===== /etc/sv/unitdore-myapp/run =====
|
|
#!/bin/sh
|
|
# unitdore-myapp — generated by unitdore, do not edit manually
|
|
exec 2>&1
|
|
[ -d /run/user/1000 ] || install -d -m 0700 -o alice /run/user/1000
|
|
export HOME=/home/alice
|
|
export XDG_RUNTIME_DIR=/run/user/1000
|
|
exec chpst -u alice /usr/bin/podman start -a myapp
|
|
|
|
# ===== /etc/sv/unitdore-myapp/finish =====
|
|
#!/bin/sh
|
|
# unitdore-myapp — generated by unitdore, do not edit manually
|
|
chpst -u alice /usr/bin/podman stop myapp || true
|
|
exec sv down .
|
|
```
|
|
|
|
### backup
|
|
|
|
```sh
|
|
# ===== /etc/sv/unitdore-backup/run =====
|
|
#!/bin/sh
|
|
# unitdore-backup — generated by unitdore, do not edit manually
|
|
exec 2>&1
|
|
cd /opt/scripts || exit 1
|
|
exec /bin/sh /opt/scripts/backup.sh
|
|
|
|
# ===== /etc/sv/unitdore-backup/finish =====
|
|
#!/bin/sh
|
|
# unitdore-backup — generated by unitdore, do not edit manually
|
|
cd /opt/scripts 2>/dev/null
|
|
/bin/sh /opt/scripts/stop.sh || true
|
|
exec sv down .
|
|
```
|
|
|
|
### agent
|
|
|
|
```sh
|
|
# ===== /etc/sv/unitdore-agent/run =====
|
|
#!/bin/sh
|
|
# unitdore-agent — generated by unitdore, do not edit manually
|
|
exec 2>&1
|
|
/opt/agent/bin/agent --config /etc/agent.toml || exit 1
|
|
exec tail -f /dev/null
|
|
|
|
# ===== /etc/sv/unitdore-agent/finish =====
|
|
#!/bin/sh
|
|
# unitdore-agent — generated by unitdore, do not edit manually
|
|
exec sv down .
|
|
```
|
|
|
|
### shop
|
|
|
|
```sh
|
|
# ===== /etc/sv/unitdore-shop/run =====
|
|
#!/bin/sh
|
|
# unitdore-shop — generated by unitdore, do not edit manually
|
|
exec 2>&1
|
|
/usr/bin/podman pod start pod_shop || exit 1
|
|
exec tail -f /dev/null
|
|
|
|
# ===== /etc/sv/unitdore-shop/finish =====
|
|
#!/bin/sh
|
|
# unitdore-shop — generated by unitdore, do not edit manually
|
|
/usr/bin/podman pod stop pod_shop || true
|
|
exec sv down .
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Reference: Service File Locations
|
|
|
|
| Scenario | Path |
|
|
|---|---|
|
|
| System unit (root) | `/etc/systemd/system/unitdore-<name>.service` |
|
|
| User unit | `/home/<user>/.config/systemd/user/unitdore-<name>.service` |
|
|
| With prefix `prod-` | `/etc/systemd/system/unitdore-prod-<name>.service` |
|
|
| With suffix `-svc` | `/etc/systemd/system/unitdore-<name>-svc.service` |
|
|
| With both | `/etc/systemd/system/unitdore-prod-<name>-svc.service` |
|
|
| OpenRC | `/etc/init.d/unitdore-<name>` |
|
|
| runit | `/etc/sv/unitdore-<name>/` (+ symlink in `/var/service` or `/etc/service`) |
|