Writing a feature
A feature is how a package does more than put files in place: define a service, change a setting, make something its files rely on. Reach for one only when the package can't do without it. A feature's scripts run as whoever turns it on, with that person's authority and no more, and each one is code an Administrator has to trust.
Its directory #
A feature is a directory your package installs under
/usr/libexec/features/<name>/, read at /libexec/features/<name>/. Its
name is letters, digits, -, _ and .. In it, any of:
| File | Runs |
|---|---|
install.sh | To install it: set up what it needs, not yet in use. |
enable.sh | To turn it on. |
disable.sh | To turn it off, leaving what install set up. |
uninstall.sh | To remove it: take away what install set up. |
feature.toml | Nothing: it says what the feature is. |
Ship only the scripts that have something to do. A step with no script just records the new state. Other files may sit beside them for the scripts to use.
What a script is given #
Each script is run directly, so it needs a #! line and to be executable.
It runs:
- as whoever asked, with their token:
featadds no authority; - in the feature's directory, so it can name the files beside it;
- with
FEAT_NAME,FEAT_DIRandFEAT_PHASE(install,enable,disableoruninstall) set, beside the rest of the caller's environment; - with the caller's standard output and error, or, under a program such as Feature Manager, with its output read line by line and shown, and its standard input empty.
A script that exits with anything but 0 has failed: the feature is left interrupted, and the person is shown what the script said.
The rules a script keeps #
- Be safe to run again. Asking again after an interruption reruns the script from the start, whatever it had done. Set values rather than add to them; treat "already there" and "already gone" as done.
- Be safe to run first. An interrupted uninstall can be followed by an install, and an interrupted turn-on by a turn-off, so a script can't assume the step before it finished.
- Say what you did, briefly. Each line is shown to the person, under Feature Manager as the change runs. Start lines with the feature's name.
- Fail with words. Write why to standard error before exiting non-zero. It is what the person sees.
- Ask nothing. There may be no terminal, and standard input may be empty.
- Undo what you do.
uninstall.shtakes away everythinginstall.shmade;disable.shreversesenable.sh.
feature.toml #
= "Dynamic Boot"
= """
Keeps this machine's boot image up to date. Two services, mkirf-watch and
mkuki-watch, make it again whenever the kernel, the initramfs or the kernel
command line changes, so a change to any of them is used at the next boot
without making the boot image by hand.
Turning it on or off takes effect at the next boot."""
| Member | Is |
|---|---|
title | Its name, in Title Case, for people. Without one, its directory's name is shown. |
description | What it does, in a sentence or a few. Wrap lines as you like: they are joined, and a blank line starts a new paragraph. Say when a change takes effect, if not at once. |
Both are optional, and members feat doesn't know are ignored. A
feature.toml that can't be read doesn't hide the feature: it is listed,
with the problem said beside it.
Packaging it #
Map the files into place in your package's [files]:
[]
= "usr/libexec/features/dynamic-boot/feature.toml"
= "usr/libexec/features/dynamic-boot/install.sh"
= "usr/libexec/features/dynamic-boot/enable.sh"
= "usr/libexec/features/dynamic-boot/disable.sh"
= "usr/libexec/features/dynamic-boot/uninstall.sh"
and depend on dev.peios.peiosutils, which ships feat, and on whatever
the scripts run. Installing the package runs none of them: the feature
waits, not installed, until someone turns it on.
A feature's package is named for it, feat-<name>, as Peios's own
dev.peios.feat-dynamic-boot is: a whole example.