Add daemon support to init(8).

This commit is contained in:
Jonas 'Sortie' Termansen 2018-10-15 00:37:27 +02:00
parent 9b80a3dfca
commit d708a9d32b
18 changed files with 3045 additions and 350 deletions

View File

@ -334,7 +334,7 @@ $(LIVE_INITRD): sysroot
mkdir -p $(LIVE_INITRD).d
mkdir -p $(LIVE_INITRD).d/etc
mkdir -p $(LIVE_INITRD).d/etc/init
echo single-user > $(LIVE_INITRD).d/etc/init/target
echo require single-user exit-code > $(LIVE_INITRD).d/etc/init/default
echo "root::0:0:root:/root:sh" > $(LIVE_INITRD).d/etc/passwd
echo "root::0:root" > $(LIVE_INITRD).d/etc/group
mkdir -p $(LIVE_INITRD).d/home

View File

@ -6,16 +6,14 @@
.Nd system initialization
.Sh SYNOPSIS
.Nm init
.Op Fl \-target Ns "=" Ns Ar init-target
.Op Fl \-target Ns "=" Ns Ar default-daemon
.Op Fl \-
.Op Ar chain-init ...
.Sh DESCRIPTION
.Nm
is the first program run after system startup and is responsible for
initializing the operating system and starting the specified
.Ar init-target .
This is normally a login screen, a root shell, or a dedicated special purpose
program.
initializing the operating system constituted by the default daemon (system
background process) and its dependencies.
.Pp
The
.Xr kernel 7
@ -34,56 +32,8 @@ If the system is installed on a harddisk, then the initrd is a minimal system
made with
.Xr update-initrd 8
that will search for the actual root filesystem and chain init it.
The next stage init will recognize it as the intended system and complete the
system startup.
.Ss Initialization Target
.Nm
first determines its target from the
.Fl \-target
option if specified or
.Pa /etc/init/target
otherwise.
Supported targets are:
.Pp
.Bl -tag -width "single-user" -compact -offset indent
.It Sy chain
mount real root filesystem and run its
.Nm .
.It Sy chain-merge
like
.Sy chain
but run
.Pa /sysmerge/sbin/init
with the
.Sy merge
target.
.It Sy merge
finish a
.Xr sysmerge 8
upgrade and then execute the real
.Nm
with its default target.
.It Sy multi-user
boot to
.Xr login 8 .
.It Sy single-user
boot to root shell without password (not secure).
.It Sy sysinstall
boot to operating system installer (not secure).
.It Sy sysupgrade
boot to operating system upgrader (not secure).
.El
.Pp
It is a full system compromise if unauthenticated users are able to boot the
wrong target.
The kernel command line can specify the path to
.Nm
and its arguments.
Unprivileged users can change the kernel command line from the bootloader
command line if it hasn't been password protected.
Likewise unprivileged users can use their own replacement bootloader by booting
a portable device under their control if the firmware configuration has not been
password protected.
The next stage init will recognize itself as the intended system and complete
the system startup.
.Ss Cleanup of /tmp and /var/run
.Nm
deletes everything inside of
@ -104,6 +54,7 @@ will scan every block device for valid partition tables and create the
corresponding partition devices in
.Pa /dev .
.Ss Chain Initialization
.\" TODO: Clarify this under the new daemon model.
The
.Sy chain
target mounts the root filesystem as in
@ -113,9 +64,9 @@ target mounts the root filesystem as in
and runs the next
.Nm
program.
This is used by
This configuration file is a copy of the real file made by
.Xr update-initrd 8
to make a bootstrap
when it makes a bootstrap
.Xr initrd 7 .
.Pp
Every block device and partition is scanned to determine if it is the root
@ -164,6 +115,7 @@ on the next boot.
The file is also written on system shutdown where the system has the most
entropy.
.Ss Merge
.\" TODO: Clarify this under the new daemon model.
The
.Sy merge
target completes a delayed system upgrade by invoking the
@ -178,7 +130,20 @@ If the upgrade succeeds, the temporary
deinitializes the system and invokes the real (now upgraded)
.Nm
which will restart system initialization in the normal fashion.
.Ss Session
.Ss Daemons
TODO: This section.
.Pp
It is a full system compromise if unauthenticated users are able to boot the
wrong target.
The kernel command line can specify the path to
.Nm
and its arguments.
Unprivileged users can change the kernel command line from the bootloader
command line if it hasn't been password protected.
Likewise unprivileged users can use their own replacement bootloader by booting
a portable device under their control if the firmware configuration has not been
password protected.
.Pp
Finally
.Nm
will start the target program according to its initialization target.
@ -213,21 +178,44 @@ root
.Sh FILES
.Bl -tag -width "/boot/random.seed" -compact
.It Pa /boot/random.seed
initial kernel entropy
.It Pa /etc/init/target
default initialization target
Initial kernel entropy
.It Pa /etc/init/
Daemon configuration for the local system (first in search path) (see
.Xr init 5 )
.It Pa /etc/init/default
Configuration for the default daemon (see
.Xr init 5 )
.It Pa /etc/fstab
filesystem table (see
Filesystem table (see
.Xr fstab 5 )
.It Pa /etc/hostname
hostname (see
Hostname (see
.Xr hostname 5 )
.It Pa /etc/kblayout
keyboard layout (see
Keyboard layout (see
.Xr kblayout 5 )
.It Pa /etc/videomode
graphics resolution (see
Graphics resolution (see
.Xr videomode 5 )
.It Pa /share/init/
Default daemon configuration provided by the operating system (second in
search path) (see
.Xr init 5 )
.It Pa /var/log/
Daemon log files (see
.Xr init 5 )
.It Pa /var/log/init.log
.Nm Ns 's
own log.
.El
.Sh ASYNCHRONOUS EVENTS
.Bl -tag -width "SIGUSR1"
.It Dv SIGTERM
Request system poweroff.
.It Dv SIGINT
Request system reboot.
.It Dv SIGQUIT
Request system halt.
.El
.Sh EXIT STATUS
.Nm

File diff suppressed because it is too large Load Diff

1
share/init/base Normal file
View File

@ -0,0 +1 @@
require time

0
share/init/local Normal file
View File

8
share/init/multi-user Normal file
View File

@ -0,0 +1,8 @@
require base no-await
require local no-await
tty tty0
need tty
exit-code-meaning poweroff-reboot
exec login

2
share/init/no-user Normal file
View File

@ -0,0 +1,2 @@
require base no-await
require local no-await exit-code

9
share/init/single-user Normal file
View File

@ -0,0 +1,9 @@
require base no-await
require local no-await
tty tty0
need tty
cd "$HOME"
exit-code-meaning poweroff-reboot
exec "$SHELL"

11
share/init/sysinstall Normal file
View File

@ -0,0 +1,11 @@
require base no-await
require local no-await
tty tty0
need tty
# TODO:
#program=sysinstall
#exec "$program"
exec sysinstall
exit-code-meaning poweroff-reboot

11
share/init/sysupgrade Normal file
View File

@ -0,0 +1,11 @@
require base no-await
require local no-await
tty tty0
need tty
# TODO:
#program=sysupgrade
#exec "$program"
exec sysupgrade
exit-code-meaning poweroff-reboot

0
share/init/time Normal file
View File

506
share/man/man5/init.5 Normal file
View File

@ -0,0 +1,506 @@
.Dd July 29, 2018
.Dt INIT 5
.Os
.Sh NAME
.Nm init
.Nd system initialization configuration
.Sh SYNOPSIS
.Nm /etc/init/
.Nm /share/init/
.Sh DESCRIPTION
.Xr init 8
starts each daemon (system background process) according to the daemon's
configuration file, which specifies the daemon's dependencies and how to run the
daemon.
.Pp
Configuration files are searched for in the
.Pa /etc/init/
directory (local initialization configuration owned by the system administrator,
which may be modified) and the
.Pa /share/init/
directory (default initialization configuration owned by the operating system,
which should not be modified).
The file name of each configuration file is that of the daemon without any file
extension.
For instance, the daemon
.Sy exampled
might come with the default configuration file
.Pa /share/init/exampled
that the system administrator can override in
.Pa /etc/init/exampled .
.Pp
.Xr init 8
initially starts the
.Sy default
daemon which is configured in
.Pa /etc/init/default ,
which then depends on the daemons constituting the operating system (which in
turn depend on the
.Sy local
daemon).
Local system daemons should be started by overriding the
.Sy local
daemon in
.Pa /etc/init/local ,
which then depends on the locally required daemons.
.\" TODO: Daemon model.
.\" TODO: Log files.
.\" TODO: Readyness.
.\" TODO: chroot /var/empty
.\" TODO: Environment variables.
.\" TODO: No restart. exit 0 means finished. One stops.share/man/man5/init.5
.\" TODO: Run daemon as specific user.
.\" TODO: /etc/environment or /etc/environ.
.\" TODO: Environment variables.
.\" TODO: Method for including/extending files in /share/init
.\" TODO: Reference counting. No cycles.
.\" TODO: Init reload support?
.Sh DAEMONS
The
.Sy default
daemon should depend on exactly one top level daemon and nothing else.
.Pp
The following daemons are top level daemons that start the operating system.
They are mutually exclusive and only a single one should be depended on:
.Bl -tag -width "12345678"
.It Sy multi-user
Starts the operating system in the multi-user mode.
It starts the
.Sy login
foreground daemon that provides a login screen and exits with login's exit code
when login exits.
This is a secure operating system mode where only authorized users have access.
It depends on the
.Sy base
and
.Sy local
daemons.
.It Sy no-user
Starts the operating system in the no-user mode.
This is a secure operating system mode where no user is granted access.
Additional daemons can be started by configuring the
.Sy local
daemon.
It depends on the
.Sy base
and
.Sy local
daemons.
The dependency on
.Sy local
is marked
.Sy exit-code ,
letting the system administrator fully control the
.Sy default
daemon's exit code and when the system completes.
.It Sy single-user
Starts the operating system in the single user mode.
It starts the
.Sy sh
foreground daemon that directly provides a root shell and exits with the shell's
exit code when the shell exits.
This operating system mode is insecure because it boots straight to root access
without a password.
It depends on the
.Sy base
and
.Sy local
daemons.
.It Sy sysinstall
Starts the operating system installer.
It starts the
.Sy sysinstall
foreground daemon that provides the operating system installer and exits with
the installer's exit code when the installer exits.
This operating system mode is insecure because it boots straight to root access
without a password.
It depends on the
.Sy base
and
.Sy local
daemons.
.It Sy sysupgrade
Starts the operating system upgrader.
It starts the
.Sy sysupgrade
foreground daemon that provides the operating system upgrader and exits with
the upgrader's exit code when the upgrader exits.
This operating system mode is insecure because it boots straight to root access
without a password.
It depends on the
.Sy base
and
.Sy local
daemons.
.El
.Pp
The following daemons are provided by the system:
.Bl -tag -width "12345678"
.It Sy base
Virtual daemon that depends on the core operating system daemons.
It depends on the
.Sy time
daemon.
.It Sy local
Virtual daemon that starts daemons pertinent to the local system.
The system provides a default implementation that does nothing.
The system administrator is meant to override the daemon in
.Pa /etc/init/local
by depending on daemons outside of the base system that should run on the local
system.
.It Sy time
Virtual daemon that becomes ready when the current date and time has been
established.
The system provides a default implementation that does nothing, as the base
system does not contain a daemon that obtains the current date and time.
The system administrator is meant to override the daemon in
.Pa /etc/init/time
by depending on a daemon that obtains the current date and time and sets the
system time.
Daemons can depend on this daemon if they need the current date and time to have
been established before they start.
.El
.Sh FORMAT
Daemon configuration files are processed line by line.
The # character starts a comment and the rest of the line is ignored.
Each line specifies a property of the daemon.
.\" TODO: White space, tokenization.
.Bl -tag -width "12345678"
.It Sy cd Ar directory
The working directory to run the deamon inside.
(Default is
.Pa / )
.It Sy exec Ar command
The command line that starts the daemon.
.Pp
If this property isn't specified, then the daemon is a virtual daemon.
Virtual deamons become ready well all their dependencies are ready and finish
when all their dependencies are finished.
Virtual daemons exit 0 (success) if every dependency finished successfully,
otherwise they exit 2 (failed).
.It Sy exit-code-meaning Oo Sy default "|" poweroff-reboot Oc
This property specifies how to interpret the exit code.
.Pp
The
.Sy default
meaning is that exiting 0 is successful.
Any other exit means the daemon failed.
.Pp
The
.Sy poweroff-reboot
meaning is that exiting 0 means the system should power off, exiting 1 means the
system should reboot, and any other exit means the daemon failed (exit 2 should
preferably be used if the system should panic).
.Pp
Daemons are considered successful if they exit by
.Sy SIGTERM
if
.Xr init 8
stopped the daemon by sending
.Sy SIGTERM.
.It Sy furthermore
The current daemon configuration file extends an existing daemon that is defined
in a configuration file by the same name later in the search path.
The later configuration file is included into the current configuration file.
This statement can only be used once per configuration file, any subsequent uses
are silently ignored, but it can be used recursively.
Customizing an existing daemon should be done by adding a new daemon file
earlier in the search path that starts with the
.Sy furthermore
statement, followed by additional configuration.
.Pp
This is not a property and cannot be
.Sy unset .
.\" TODO: How are the default log options set?
.It Sy log-control-messages Oo Sy false "|" true Oc
Includes control messages such as the start and stop of the daemon and loss of
log data.
Control messages are inserted as entries from the daemon
.Sy init .
.Pp
The default is to include control messages.
.It Sy log-exec Ar program Oo Ar argument ... Oc
When using the
.Sy pipe
log method, start the log process using this program and arguments.
.\" TODO: The name of the daemon should be inserted as an argument?
.It Sy log-format
Selects the format of the log:
.Bl -tag -width "nanoseconds"
.It Sy none
The log is exactly as written by the daemon with no additional formatting.
.It Sy seconds
"YYYY-dd-mm HH:MM:SS +0000: "
.Pp
Each line is prefixed with a timestamp with second precision and the timezone
offset.
.It Sy nanoseconds
"YYYY-dd-mm HH:MM:SS.nnnnnnnnn +0000: "
.Pp
Each line is prefixed with a timestamp with nanosecond precision and the
timezone offset.
.It Sy basic
"YYYY-dd-mm HH:MM:SS.nnnnnnnnn +0000 daemon: "
.Pp
Each line is prefixed with a timestamp with nanosecond precision and the
timezone offset followed by the name of the daemon.
.It Sy full
"YYYY-dd-mm HH:MM:SS.nnnnnnnnn +0000 hostname daemon: "
.Pp
Each line is prefixed with a timestamp with nanosecond precision and the
timezone offset followed
by the hostname and name of the daemon.
.It Sy syslog
"<ppp>1 YYYY-dd-mmTHH:MM:SS.uuuuuuZ hostname daemon - - - "
.Pp
Each line is prefixed in the RFC 5424 syslog version 1 format with the priority,
the timestamp with microsecond precision and the timezone offset, the hostname,
and the daemon name.
.El
.Pp
The default format is
.Sy nanoseconds .
.It Sy log-line-size Ar line-size
When using the
.Sy rotate
log method, log files are cut at newlines if the lines don't exceed
.Ar line-size
bytes.
.Pp
The default value is 4096 bytes.
.It Sy log-method Oo Sy append "|" rotate "|" pipe Oc
Selects the method for logging (the default is
.Sy rotate ) :
.Bl -tag -width "12345678"
.It Sy append
Always append the log data to the log file without any rotation.
.Pp
This method does not lose log data but it will fail when disk space is
exhausted.
.It Sy rotate
Append lines to the log file until it becomes too large, in which case the
daemon's logs are rotated.
.Pp
Rotation is done by deleting the oldest log (if there are too many), each of the
remaining log files are renamed with the subsequent number, and a new log file
is begun.
The logs are cut on a newline boundary if the lines doesn't exceed
.Sy log-line-size .
.Pp
For instance,
.Pa example.log.2
is deleted,
.Pa example.log.1
becomes
.Pa example.log.2 ,
.Pa example.log.1
becomes
.Pa example.log.2 ,
and a new
.Pa example.log
is begun.
.Pp
This is the default log method.
This method will lose old log data.
.It Sy pipe
The daemon's logs are piped to the standard input of its own log process running
the
.Sy log-exec
program.
.Pp
After the daemon has stopped, the log daemon receives an end of file condition
on its input.
If the log daemon fails to exit after a timeout, it is sent
.Sy SIGTERM.
If it still hasn't exited after another timeout, it is killed with
.Sy SIGKILL.
.Pp
The daemon will not start if the log process could not be started, and the
daemon will stop with a failure if the log process fails.
.\" TODO: This is harsh, a single log failure can bring down the system. The
.\" log program needs to be written especially failsafe too. Maybe have a
.\" mode that just loses log messages and tries again?
.\" TODO: Have some sort of readiness signal in case the the process wants to
.\" initialize buffers (e.g. connect) and not lose any messages if the log
.\" fails?
.Pp
This method does not lose log data, always forwarding it to the spawned process,
but the spawned process might of course lose it.
.El
.It Sy log-rotate-on-start Oo Sy false "|" true Oc
When starting the daemon, rotate the logs (when using the
.Sy rotate
log method) or empty the log (when using the
.Sy append
log method), such that the daemon starts out with a new log.
.It Sy log-size Ar size
When using the
.Sy rotate
log method, keep each log file below
.Ar size
bytes.
.Pp
The default value is 1048576 bytes.
.It Sy need tty
.\" TODO: Only a single daemon can be this.
Specifies that the daemon is not a background daemon, but instead a foreground
daemon that requires a terminal.
.\" TODO: Make session leader.
The daemon is made a process group leader.
The terminal's foreground process group is set to that of the daemon.
The terminal is enabled by setting
.Sy CREAD .
The daemon is not logged, and the standard input, output, and error are instead
connected to the terminal.
.\" TODO: Automatically being ready could be another independent property.
Foreground daemons are automatically considered ready and don't participate in
the
.Ev READYFD
daemon readiness protocol.
Upon exit, the original terminal settings are restored and
.Xr init 8
regains ownership of the terminal.
.It Sy require Ar dependency Oo Ar flag ... Oc
.\" TODO: 'suggest dependency' which doesn't fail if it can't be provided,
.\" and is just best-effort.
When the daemon is needed, start the
.Ar dependency
first.
The daemon starts when all its dependencies have become ready or have finished.
Dependencies are started in parallel whenever possible.
.\" TODO: Whether all dependencies are waited for in the eror case.
If the daemon hasn't started yet, and any dependency finishes unsuccessfully,
the daemon doesn't start and instead directly finishes unsuccessfully.
If the daemon has started, it is the daemon's responsibility to detect failures
in its dependencies.
.Pp
The dependency can be customized with zero or more flags:
.Bl -tag -width "12345678"
.It Sy exit-code
If the daemon is a virtual daemon, then the daemon's exit code is that of the
specific
.Ar dependency
rather than whether all dependencies succeeded.
The daemon exits as soon as the
.Ar dependency
exits, rather than waiting for all dependencies to exit.
The
.Sy exit-code-meaning
field is set to that of the dependency.
.Sy exit-code
can at most be used on a single dependency for a daemon.
.It Sy no-await
Don't wait for the
.Ar dependency
to become ready before starting this daemon.
This flag is meant for dependencies that the daemon can make use of, but isn't
essential to the daemon itself becoming ready.
It shouldn't be used if the daemon polls for the the dependency to come online,
as it is more efficient to only start the daemon once the dependency is ready.
.It Sy optional
.\" TODO: What about a dependency that succeeds if the dependency isn't
.\" installed?
Start the daemon even if the
.Ar dependency
fails.
.\" TODO: Warning or error?
The dependency is assumed to exist and a warning occurs if it doesn't exist.
.El
.Pp
Dependencies can be forgotten using
.Sy unset require Ar dependency .
Flags on a dependency can be be unset using
.Sy unset require Ar dependency flag ... .
.It Sy unset Ar property
Reset the given property to its default value.
.It Sy tty Ar device
.\" TODO: Do something with this property.
If the daemon is a foreground daemon
.Sy ( need tty
is set), then connect the daemon to the terminal named
.Ar device .
This property is currently ignored and the daemon is always connected to
.Pa tty1 .
.El
.Sh FILES
.\" TODO: Interesting daemons: default, local, base, time, single-user, etc.
.Bl -tag -width /share/init/ -compact
.It Pa /etc/init/
Daemon configuration for the local system (first in search path).
.It Pa /share/init/
Default daemon configuration provided by the operating system (second in search
path).
.It Pa /var/log/
Daemon log files.
.El
.Sh EXAMPLES
.Ss Configuring a daemon to start on boot
The local system can be configured to start the
.Sy exampled
daemon by creating
.Pa /etc/init/local
with the following contents:
.Bd -literal
require exampled optional
.Ed
.Pp
Additional lines can be included for any daemon you wish to start.
The
.Sy optional
flag means the
.Sy local
daemon doesn't fail if the daemon fails.
The top level daemons
.Sy ( multi-user , single-user , ... )
fails if the
.Sy local
daemon fails.
The
.Sy optional
flag should only be omitted if a local daemon is critical and the boot should
fail if the daemon fails.
.Ss Creating a new virtual daemon
The
.Sy exampled
daemon, which depends on the
.Sy food , bard ,
and
.Sy quxd
daemons and whose program file is called
.Pa exampled ,
can then be configured by creating
.Pa /etc/init/exampled
with the following contents:
.Bd -literal
require food
require bard
require quxd
exec exampled
.Ed
.Ss Configuring a multi-user system
The system can be configured to boot into multi-user mode by creating
.Pa /etc/init/default
with the following contents:
.Bd -literal
require multi-user exit-code
.Ed
.Ss Configuring an unattended system
A fully unattended system that only starts the base system and the
.Sy exampled
daemon, shutting down when the
.Sy exampled
daemon finishes, can be done by first creating
.Pa /etc/init/default
with the following contents:
.Bd -literal
require no-user exit-code
.Ed
.Pp
And then secondly creating
.Pa /etc/init/local
with the following contents:
.Bd -literal
require exampled exit-code
.Ed
.Sh SEE ALSO
.Xr init 8

View File

@ -69,6 +69,11 @@ releasing Sortix x.y, foo." to allow the maintainer to easily
.Xr grep 1
for it after a release.
.Sh CHANGES
.Ss Add daemon support to init(8)
TODO:
.Pa /etc/init/target
has become
.Pa /etc/init/default .
.Ss Implement file descriptor passing
The
.Dv SCM_RIGHTS

View File

View File

@ -54,6 +54,7 @@ install: all
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-random-seed
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-tix-manifest-mode
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-leaked-files
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-init
sysinstall: $(SYSINSTALL_OBJS)
$(CC) $(SYSINSTALL_OBJS) -o $@ -lmount

View File

@ -257,6 +257,45 @@ void upgrade_prepare(const struct release* old_release,
free(installed);
}
}
// TODO: After releasing Sortix 1.1, remove this compatibility.
if ( hook_needs_to_be_run(source_prefix, target_prefix, "sortix-1.1-init") )
{
char* init_target_path = join_paths(target_prefix, "/etc/init/target");
char* init_default_path =
join_paths(target_prefix, "/etc/init/default");
if ( !init_target_path || !init_default_path )
{
warn("malloc");
_exit(2);
}
char* line = read_string_file(init_target_path);
if ( line )
{
printf(" - Converting /etc/init/target to /etc/init/default...\n");
FILE* init_default_fp = fopen(init_default_path, "w");
if ( !init_default_fp ||
fprintf(init_default_fp, "require %s exit-code\n", line) < 0 ||
fclose(init_default_fp) == EOF )
{
warn("%s", init_default_path);
_exit(2);
}
free(line);
if ( unlink(init_target_path) < 0 )
{
warn("unlink: %s", init_target_path);
_exit(2);
}
}
else if ( errno != ENOENT )
{
warn("%s", init_target_path);
_exit(2);
}
free(init_target_path);
free(init_default_path);
}
}
void upgrade_finalize(const struct release* old_release,

View File

@ -1033,7 +1033,8 @@ int main(void)
else
warn("mkdir: etc/init");
}
install_configurationf("etc/init/target", "w", "multi-user\n");
install_configurationf("etc/init/default", "w",
"require multi-user exit-code\n");
text("Congratulations, the system is now functional! This is a good time "
"to do further customization of the system.\n\n");

View File

@ -77,9 +77,13 @@ mkdir "$tmp/etc"
cp "$sysroot/etc/fstab" "$tmp/etc/fstab"
mkdir "$tmp/etc/init"
if $sysmerge; then
echo chain-merge > "$tmp/etc/init/target"
cat > "$tmp/etc/init/default" << EOF
require chain-merge exit-code
EOF
else
echo chain > "$tmp/etc/init/target"
cat > "$tmp/etc/init/default" << EOF
require chain exit-code
EOF
fi
mkdir -p "$sysroot/boot"
mkinitrd --format=sortix-initrd-2 "$tmp" -o "$sysroot/boot/sortix.initrd" > /dev/null