From systemd
runwisp import systemd converts .service and .timer units into
runwisp.toml. A Type=oneshot unit becomes a task
that runs when the daemon starts. Any other unit becomes a
service. A timer and the service it starts become
one scheduled task, see Timers.
Convert a unit
Section titled “Convert a unit”runwisp import systemd /etc/systemd/system/myapp.service -o runwisp.tomlrunwisp validateA bare unit name such as myapp.service also works: it is looked up where
systemd keeps units.
Given tempoledger.service:
[Unit]Description=Tempo Ledger API
[Service]Type=simpleExecStart=/usr/local/bin/bun run startWorkingDirectory=/srv/tempoledgerUser=deployGroup=deployEnvironment=NODE_ENV=productionEnvironment=PORT=3000Restart=alwaysTimeoutStopSec=30KillSignal=SIGTERM
[Install]WantedBy=multi-user.targetyou get:
[services.tempoledger]description = "Tempo Ledger API"run = "/usr/local/bin/bun run start"working_dir = "/srv/tempoledger"graceful_stop = "30s"stop_signal = "SIGTERM"user = "deploy:deploy"
[services.tempoledger.env]NODE_ENV = "production"PORT = "3000"The name comes from the file name. A piped-in unit has no file name, so it gets a placeholder name and a note to rename it. A summary on stderr lists every unit and anything that needs a look.
If you already have a runwisp.toml, use --write instead of -o; see
Staging and promoting. All flags are
in the CLI reference.
Disable the unit
Section titled “Disable the unit”The import doesn’t stop or disable anything. If systemd and RunWisp both run the same process, you get two copies fighting over ports or queues. Disable the unit before starting it under RunWisp:
sudo systemctl disable --now myapp.service--now stops it immediately; disable keeps it from starting on boot. You can
move a few units at a time, but never leave one enabled in both. There is no
takeover for systemd units.
How the pieces map
Section titled “How the pieces map”| systemd | runwisp.toml | Notes |
|---|---|---|
Type=oneshot |
[tasks.NAME] with run_on_start = "boot" |
Runs once per boot. |
any other Type |
[services.NAME] |
See Restart= below. |
ExecStart |
run |
One command. @ - + ! prefixes are removed, with a note. Arguments with shell characters such as * or ; are quoted. |
WorkingDirectory |
working_dir |
|
User (+ Group) |
user |
Combined as user:group. The daemon must run as root to switch users. |
Environment |
env |
Every line is kept. |
EnvironmentFile |
env_file |
Only the first file. A - file that doesn’t exist is left out. |
TimeoutStopSec |
graceful_stop |
0 (no timeout) is left out with a note. |
Restart |
restart |
See below. |
KillSignal |
stop_signal |
|
[Unit] Description |
description |
|
[Install] |
(not needed) | Services start with the daemon. See below. |
RunWisp starts services when the daemon starts. To keep one stopped at boot,
set autostart = false. For start order,
use depends_on.
Timers
Section titled “Timers”since: 1.8
Pass the .timer file. The import reads the service it starts from the timer’s
Unit=, or else NAME.service next to it, and writes one task named after the
timer:
runwisp import systemd /etc/systemd/system/backup.timer -o runwisp.tomlGiven backup.timer and backup.service:
# backup.timer[Timer]OnCalendar=Mon..Fri *-*-* 03:30:00Persistent=true
# backup.service[Service]Type=oneshotExecStart=/usr/local/bin/backup --allyou get:
[tasks.backup]run = "/usr/local/bin/backup --all"cron = "30 3 * * 1-5"If you also pass the service, it isn’t imported a second time. Disable the
timer, not the service: sudo systemctl disable --now backup.timer.
| systemd | runwisp.toml | Notes |
|---|---|---|
OnCalendar |
cron |
weekly is Monday. A trailing zone becomes timezone. |
OnUnitActiveSec, OnUnitInactiveSec |
cron = "@every …" |
Counted from the last scheduled run, not from when the unit ran. |
OnBootSec, OnStartupSec |
run_on_start = "boot" |
The delay is dropped. |
Persistent=true |
(default) | Without it, the import sets catch_up = 0. |
RandomizedDelaySec |
jitter |
The closest match, with a note. |
OnActiveSec, OnClockChange |
(dropped) | With a note. |
AccuracySec, WakeSystem, [Install] |
(not needed) |
A timer-run service becomes a task whatever its Type=, and Restart= is
ignored, as systemd ignores it. These need a human:
- A year or a weekday plus a date.
OnCalendar=2027-*-* 00:00andMon *-*-01..07have no cron form. The value is kept under a# TODO. - More than one
OnCalendar. Only the first is imported. Add a task for each other schedule. - A missing service. The task gets an empty
runand a# TODO. A timer piped on stdin has no folder to look in, so it always gets one.
What needs a human
Section titled “What needs a human”Anything that can’t be mapped gets a # TODO in the TOML and a note on the
unit’s row.
Restart=.alwaysis RunWisp’s defaultrestart.on-failurebecomesrestart = "on_failure", andno(or none) becomesrestart = "never".on-success,on-abnormal,on-abort, andon-watchdoghave no exact equal, so they land on the closest value with a note.- More than one command. RunWisp runs one
runcommand. Only the firstExecStartis imported; otherExecStart,ExecStartPre,ExecStartPost, andExecStoplines are flagged. Put them into therunscript, or setup steps into a separate task. Type=notifyandType=forking. RunWisp watches the process it starts and treats it as up at once. Run the program in the foreground if you can.- Sandboxing.
ProtectSystem,PrivateTmp,NoNewPrivileges,CapabilityBoundingSet, and similar directives are dropped. The process gets more access than the unit allowed. If that matters, use a container or a restricted user. - Socket activation. Not supported. The program must open its own port.
- Template units.
%i,%I,%n, and similar are not filled in. Replace them in therunline.
Learn more
Section titled “Learn more”[services.*]- Tasks vs services
- Autostart, to run RunWisp itself under systemd
- From cron, for cron jobs