doc: add a syntax migration guide

The new-format reference describes the block format on its own terms,
which is the wrong lookup direction for someone holding a legacy
one-liner.  Aaron migrated Finix OS from the PR description, proving
the need for a token-in, key-out mapping in the user guide.

One table per part of a stanza, worked conversions for the shapes
that changed structurally -- cgroup selection and the three tty
variants -- and the dropped tokens listed with their replacements.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-08-01 11:25:38 +02:00
parent 3062f49370
commit 057ded451f
3 changed files with 183 additions and 1 deletions
+2 -1
View File
@@ -92,7 +92,8 @@ service [S123456789] env:-/etc/default/sysklogd syslogd -F $SYSLOGD_ARGS -- Syst
That format still works and is not going away. It is frozen at the
Finit 4.x feature set, so new settings appear only in the block format,
and the documentation is written in blocks throughout.
and the documentation is written in blocks throughout. To convert a
file, see the [Syntax Migration Guide](migration.md).
There is no new file extension and no marker line. Every file is still
`*.conf`, and Finit decides which format a file is in by reading it: if
+180
View File
@@ -0,0 +1,180 @@
Syntax Migration Guide
======================
Finit reads both configuration formats. Every file is still `*.conf`,
and the format is detected per file, so a system can be migrated one
file at a time -- a file is one format or the other, never a mix.
This guide maps each part of a line-based stanza to its block key.
The shape of the change:
service [S12345] <pid/syslogd> env:-/etc/default/klogd name:klogd klogd -n $KLOGD_OPTS -- Kernel log daemon
becomes
service klogd {
description = "Kernel log daemon"
runlevel = "S12345"
conditions = { "pid/syslogd" }
envfile = "-/etc/default/klogd"
command = "klogd -n $KLOGD_OPTS"
}
The block title is the service identity, shown by `initctl`. Multiple
instances spell the ID in the title: `service sshd:1 { ... }`. The
legacy bare-ID form, `service :80 ...`, has no equivalent -- the title
carries both name and ID.
Positional parts
----------------
| Line-based | Block key |
|----------------------------------|-------------------------------------------------------|
| `service`, `task`, `run`, `sysv` | same, as block type |
| `[S12345]` | `runlevel = "S12345"` |
| `<pid/a,net/b>` | `conditions = { "pid/a", "net/b" }` |
| `<!pid/a>` on service/sysv | `conditions = { "pid/a" }` + `reload-signal = "none"` |
| `<!pid/a>` on run/task | `conditions = { "pid/a" }` + `required = false` |
| `@user:group,extra` | `user`, `group`, `extra-groups = { "extra" }` |
| `name:foo :1` | block title `foo:1` |
| the command and arguments | `command = "..."` |
| `-- Description text` | `description = "..."` |
The `!` was a flag on the stanza, not a negation, and meant different
things for daemons and one-shots; each meaning is now its own key. A
`~` prefix on a condition is unchanged: `conditions = { "~pid/a" }`.
Service options
---------------
| Line-based | Block key |
|----------------------------------|--------------------------------------------------|
| `env:[-]/path` | `envfile = "[-]/path"` |
| `pid:/path` | `pidfile = "/path"` + `pidfile-create = true` |
| `pid:!/path` | `pidfile = "/path"` |
| `pid` | `pidfile = true` + `pidfile-create = true` |
| `log` | `log { }` |
| `log:/path` | `log { file = "/path" }` |
| `log:null`, `log:console` | `log { file = "/dev/null" }`, `"/dev/console"` |
| `log:prio:p,tag:t` | `log { priority = "p" identity = "t" }` |
| `notify:systemd` | `notify = "systemd"` |
| `type:forking` | `type = "forking"` |
| `manual:yes` | `manual-start = true` |
| `remain:yes` | `remain-after-exit = true` |
| `restart:always` / `restart:NUM` | `restart = "always"` / `restart-max = NUM` |
| `restart_sec:SEC` | `restart-sec = SEC` |
| `norestart` | `restart = "never"` |
| `respawn` | `respawn = true` |
| `oncrash:reboot` | `oncrash = "reboot"` |
| `halt:SIG` | `stop-signal = "SIG"` |
| `kill:SEC` | `stop-timeout = SEC` |
| `pre:[TMO,]/script` | `exec-start-pre`, `exec-start-pre-timeout` |
| `ready:[TMO,]/script` | `exec-start-ready`, and its `-timeout` |
| `stop:[TMO,]/script` | `exec-stop`, and its `-timeout` |
| `post:[TMO,]/script` | `exec-stop-post`, and its `-timeout` |
| `reload:[TMO,]/script` | `exec-reload`, and its `-timeout` |
| `cleanup:[TMO,]/script` | `exec-cleanup`, and its `-timeout` |
| `caps:^cap_a,%cap_b` | `capabilities = { "^cap_a", "%cap_b" }` |
| `conflict:a,b` | `conflicts = { "a", "b" }` |
| `if:svc` / `if:<cond>` | `if = "svc"` / `if = "cond"`, no angle brackets |
| `tty:/dev/x` | `tty = "/dev/x"` |
| `nowarn` | leading `-` on `command` or `envfile` |
| `restarttmo:` | dropped, was already an alias for `restart_sec:` |
Cgroups
-------
The standalone `cgroup.NAME` line selected a group for every stanza
after it in the file, and `cgroup:opts` applied settings to whichever
group was current. Neither survives: every block names its own group,
so nothing depends on what came earlier in the file.
cgroup.maint
service [2345] cgroup:cpu.weight:250 foo -- Foo daemon
becomes
service foo {
description = "Foo daemon"
runlevel = "2345"
cgroup maint { cpu.weight = 250 }
command = "foo"
}
Top-level group declarations keep their name and settings:
cgroup system cpu.weight:9700 mem.max:10M
becomes
cgroup system {
cpu.weight = 9700
memory.max = 10M
}
TTYs
----
The three positional variants become three key choices:
tty [12345] /dev/ttyS0 115200 noclear vt220
tty [12345] /sbin/getty -L ttyS0 115200 vt100
tty [12345] notty
become
tty ttyS0 {
runlevel = "12345"
device = "/dev/ttyS0"
baud = 115200
term = "vt220"
noclear = true
}
tty getty {
runlevel = "12345"
command = "/sbin/getty -L ttyS0 115200 vt100"
}
tty shell {
runlevel = "12345"
notty = true
}
Top-level directives
--------------------
| Line-based | Block key |
|----------------------------------|----------------------------------------------------------|
| `host NAME`, `hostname NAME` | `hostname = "NAME"` |
| `module foo args` (repeated) | `modules = { "foo args", ... }` |
| `network script args` | `network = "script args"` |
| `runlevel N` | `runlevel = N` |
| `rcsd /path` | `rcsd = "/path"` |
| `runparts [progress] [sysv] DIR` | `runparts = "DIR"`, `runparts-progress`, `runparts-sysv` |
| `set KEY=VAL` (repeated) | `environment { KEY = "VAL" }` |
| `log size:100k count:4` | `log { size = 100k count = 4 }` |
| `rlimit [hard\|soft] RES LIM` | `rlimit { hard.res = LIM }` |
| `readiness none` | `readiness = "none"` |
| `reboot-delay N` | `reboot-delay = N` |
| `reboot-watchdog on` | `reboot-watchdog = true` |
| `service-interval SEC` | `service-interval = SEC` |
| `shutdown script` | `shutdown = "script"` |
| `mknod /dev/x c 1 2` | `mknod = { "/dev/x c 1 2" }` |
| `include /path` | `include("/path")` |
Worth knowing
-------------
* Templates work unchanged: `%i` is replaced before the file is
parsed, so `service serv:%i { ... }` in a `serv@.conf` behaves
exactly like its legacy counterpart.
* A list cannot hold comments; the lexer reads entries after a `#`
regardless. Put commented-out candidates above the list.
* `$VAR` in a value is expanded when the file is read, against
Finit's environment. An unset variable expands to nothing.
* New settings only appear in the block format. The first are the
[per-service directories](service-opts.md#service-directories),
`runtime-dir` and friends.
For the full description of every key, see the rest of the
[Configuration](index.md) section.
+1
View File
@@ -21,6 +21,7 @@ nav:
- Building: build.md
- Configuration:
- Overview: config/index.md
- Migration Guide: config/migration.md
- Files & Layout: config/files.md
- Conditions: conditions.md
- Runlevels: config/runlevels.md