What is needed for a minimal systemd boot to launch getty on a virtual console?

25,496

Solution 1

First of all, systemd is not a traditional unix init. Systemd is so much more, so it's a bit unfair to compare the two.

To answer the question, what appears to be necessary are some binaries and the following configuration files:

/usr/lib/systemd/system/default.target
/usr/lib/systemd/system/basic.target
/usr/lib/systemd/system/sysinit.target
/usr/lib/systemd/system/getty.target
/usr/lib/systemd/system/[email protected]
/usr/lib/systemd/system/console-getty.service

issuing systemctl enable console-getty.service [email protected] then creates these symlinks:

/etc/systemd/system/default.target.wants/[email protected] -> /lib/systemd/system/getty@service
/etc/systemd/system/getty.target.wants/console-getty.service -> /lib/systemd/system/console-getty.service

NOTE: To utilize systemd's special features for starting agetty dynamically, on-demand when pressing Alt+F3 and so on, it appears that you must also have at least these two files:

/etc/systemd/logind.conf
/lib/systemd/system/[email protected]

where [email protected] is a symlink to [email protected].

Contents of configuration files:

The default.target, getty.target, sysinit.target files can be empty except for the [Unit] tag and (probably) Description=xxx.

basic.target also contains dependency information:

[Unit]
Description=Basic System
Requires=sysinit.target
Wants=sockets.target timers.target paths.target slices.target
After=sysinit.target sockets.target timers.target paths.target slices.target

I'm not sure if the references to targets that don't exist as files are needed or not. They are described on the systemd.special(7) man page.


console-getty.service: (Special case for agetty on the console)

[Unit]
Description=Console Getty
After=systemd-user-sessions.service plymouth-quit-wait.service
Before=getty.target

[Service]
ExecStart=-/sbin/agetty --noclear --keep-baud console 115200,38400,9600 $TERM
Type=idle
Restart=always
RestartSec=0
UtmpIdentifier=cons
TTYPath=/dev/console
TTYReset=yes
TTYVHangup=yes
KillMode=process
IgnoreSIGPIPE=no
SendSIGHUP=yes

[Install]
WantedBy=getty.target

[email protected]: (generic config for all getty services except console)

[Unit]
Description=Getty on %I
After=systemd-user-sessions.service plymouth-quit-wait.service
Before=getty.target
IgnoreOnIsolate=yes
ConditionPathExists=/dev/tty0

[Service]
ExecStart=-/sbin/agetty --noclear %I $TERM
Type=idle
Restart=always
RestartSec=0
UtmpIdentifier=%I
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=no
KillMode=process
IgnoreSIGPIPE=no
SendSIGHUP=yes

[Install]
WantedBy=getty.target
DefaultInstance=tty1

Finally you probably need a few of these special binaries (I haven't tried which ones are crucial):

/lib/systemd/systemd (/sbin/init usually points to this)
/lib/systemd/systemd-logind
/lib/systemd/systemd-cgroups-agent
/lib/systemd/systemd-user-sessions
/lib/systemd/systemd-vconsole-setup
/lib/systemd/systemd-update-utmp
/lib/systemd/systemd-sleep
/lib/systemd/systemd-sysctl
/lib/systemd/systemd-initctl
/lib/systemd/systemd-reply-password
/lib/systemd/systemd-ac-power
/lib/systemd/systemd-activate
/lib/systemd/systemd-backlight
/lib/systemd/systemd-binfmt
/lib/systemd/systemd-bootchart
/lib/systemd/systemd-bus-proxyd
/lib/systemd/systemd-coredump
/lib/systemd/systemd-cryptsetup
/lib/systemd/systemd-fsck
/lib/systemd/systemd-hostnamed
/lib/systemd/systemd-journald
/lib/systemd/systemd-journal-gatewayd
/lib/systemd/systemd-journal-remote
/lib/systemd/systemd-localed
/lib/systemd/systemd-machined
/lib/systemd/systemd-modules-load
/lib/systemd/systemd-multi-seat-x
/lib/systemd/systemd-networkd
/lib/systemd/systemd-networkd-wait-online
/lib/systemd/systemd-quotacheck
/lib/systemd/systemd-random-seed
/lib/systemd/systemd-readahead
/lib/systemd/systemd-remount-fs
/lib/systemd/systemd-resolved
/lib/systemd/systemd-rfkill
/lib/systemd/systemd-shutdown
/lib/systemd/systemd-shutdownd
/lib/systemd/systemd-socket-proxyd
/lib/systemd/systemd-timedated
/lib/systemd/systemd-timesyncd
/lib/systemd/systemd-udevd
/lib/systemd/systemd-update-done

To summarize the systemd start process, I think it works something like this:

  1. systemd locates basic.target (or all *.target files?)
  2. dependencies are resolved based on WantedBy=, Wants=, Before=, After=... directives in the [Install] section of the *.service and *.target configuration files.
  3. *.services that should start (that are not "special" services), have a [Service] section with a ExecStart= directive, that points out the executable to start.

Solution 2

systemd automatically creates a getty when you switch to the terminals, up to a certain maximum number. The default is 6 (so you get automatically a getty for alt+f1 to alt+f6). If you want to change this parameter you can edit /etc/systemd/logind.conf to change NAutoVTs parameter to some other number (max 12)

If you want a getty to spawn even if you don't manually switch you can add a symlink to /usr/lib/systemd/system/[email protected] to the /etc/systemd/system/getty.target.wants/ directory:

ln -sf /usr/lib/systemd/system/[email protected] /etc/systemd/system/getty.target.wants/[email protected]

this will result in the getty.target requiring one more getty@ service. A target is a collection of services that need to be spawned, replacement of runlevels which supports dependencies. The default target depends on getty.target

See at systemd FAQ in ArchWiki

edit: I researched a bit more in the documentation.

At boot the systemd daemon loads all systems in the default target and their dependencies. A target is defined by the files

/etc/systemd/system/default.target
/usr/lib/systemd/system/default.target

A target has a list of attached services specified by symlinks in the directories

/etc/systemd/system/default.target.wants
/usr/lib/systemd/system/default.target.wants

The /etc version overrides the distribution defaults in /usr/lib. Only one of the .target files is required, while none of the directory is required

getty is just one of the services among others which can be run by init scripts. In the distribution I checked (fedora, arch) getty is run in two different ways:

  1. Started by specific scripts for each terminal (links to the /usr/lib/systemd/system/[email protected] file in which the tty name is substituted in by systemd from the link filename)
  2. Automatically brought up as needed by the logind when the user switch to a virtual terminal (similar to the way old inetd brought up services only when a request arrives). logind is a different daemon distributed with systemd, and reads its configuration from the /etc/systemd/logind.conf file.

Hope this is satisfying.

Solution 3

After some experiments I found that one target-service pair is enough to boot into. Copied from emergency.service:

[Unit]
DefaultDependencies=no
Description=shell.service: Console and Login

[Service]
Environment=HOME=/root
WorkingDirectory=-/root
ExecStart=-/usr/lib/systemd/systemd-sulogin-shell
ExecStartPost=/usr/bin/openvt -f -c 16 agetty tty16
ExecStartPost=/usr/bin/openvt -f -c 17 -- agetty -p tty17
ExecStartPost=/usr/bin/openvt -f -c 18 -- agetty -p -a USER tty18
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
#KillMode=process
KillMode=control-group
IgnoreSIGPIPE=no
SendSIGHUP=yes

For testing: renaming the distro's unit dir to /lib/systemd/systemDEACT is a simple but radical way to reduce the number of units/unit-files from 200 to 10; there are still some false ones:

  UNIT                    LOAD      ACTIVE     SUB       DESCRIPTION
  dev-sda3.device         loaded    activating tentative /dev/sda3
  -.mount                 loaded    active     mounted   Root Mount
  tmp.mount               loaded    active     mounted   /tmp
  init.scope              loaded    active     running   System and Service Manager
  io.service              loaded    inactive   dead      io: usbhid kmod for keyboard
  remount.service         loaded    inactive   dead      remount: rw for / and tmpfs for /tmp
  shell.service           loaded    active     running   shell.service: Console and Login
  -.slice                 loaded    active     active    Root Slice
  system.slice            loaded    active     active    System Slice
* systemd-journald.socket not-found inactive   dead      systemd-journald.socket
* local-fs-pre.target     not-found inactive   dead      local-fs-pre.target
* local-fs.target         not-found inactive   dead      local-fs.target
  mini.target             loaded    active     active    "mini": Minimal Boot and Shell
* swap.target             not-found inactive   dead      swap.target
* umount.target           not-found inactive   dead      umount.target

Beside shell.service (see top) I made a remount.service and io.service; the modprobe usbhid is crucial for my (any?) USB keyboard. The remount service needs a sync with the fsck in arch's initrd script for the remount and with tmp.mount for /tmp. I also have to find out where to place that rw correctly on the KCL...in the meantime I get a couple of green [OK] at bootup from two oneshot and one idle services.

The dependency tree is:

mini.target
* |-io.service
* |-remount.service
* `-shell.service

With my remount redundant/conflicting with KCL and io just doing modprobe usbhid (which can be done with a ExecStart... in shell.service also), this is just one target/service pair. Plus the default.target link if you don't add systemd.unit=mini.target on the kernel CL.

I spent hours chasing the tentative dev-sdaX.device (the only, root partition); now I think this is halfways normal.

-.mount and -.slice and init.scope are internal and make sense.

tmp.mount I think is generated in /run.

The not-found journald, local-fs, swap and umount units don't seem to hurt systemd; with --state loaded they are not shown. They probably should be created as first additional units. But how define local-fs when root partition comes already checked from initrd? Can a boot with initrd be minimal?

systemctl status is also minimal. There is a slice, but only one (no user-X slices):

* archlinux
    State: running
     Jobs: 0 queued
   Failed: 0 units
    Since: Fri 2021-10-01 15:24:16 UTC; 3min 2s ago
   CGroup: /
           |-init.scope 
           | `-1 /sbin/init arch\x5cvmlinuz-linux
           `-system.slice 
             `-shell.service 
               |-215 /usr/lib/systemd/systemd-sulogin-shell
               |-219 bash
               |-220 agetty tty16
               |-222 agetty -p tty17
               |-225 agetty -p -a USER tty18
               `-238 systemctl status

With KillMode you can control what happens to the processes inside shell.service. The corresponding ps axf is:

    1 ?        Ss     0:00 /sbin/init arch\vmlinuz-linux
  215 tty1     Ss     0:00 /usr/lib/systemd/systemd-sulogin-shell
  219 tty1     S      0:00  \_ bash
  245 tty1     R+     0:00      \_ ps axf
  246 tty1     S+     0:00      \_ tail -8
  220 tty16    Ss+    0:00 agetty tty16
  222 tty17    Ss+    0:00 agetty -p tty17
  225 tty18    Ss+    0:00 agetty -p -a USER tty18

The agettys have no parent - thanks to openvt ? - but still PID1/init/systemd keeps track of the pids. I was able to restart shell.service with different KillModes.

A ctrl-D or exit on that sulogin-shell is not recommended...

And how can I stop all this?

After that radical dir-rename the reboot command "can not talk to init daemon" or so. A (copy of) reboot.service with action reboot-force (not reboot, not reboot-immediate) is the most direct way out, and sufficient for this test bootup (no journald running etc.). Sysvinit shutdown is not really elegant either, nor fast. systemctl start reboot then does the PID signalling and killing and a sync (I guess), but no otherwise ordered shutdown. That would be with a "final" or "shutdown" target, from where you can go to CPU reboot, poweroff or halt. With or w/o shutdown-initramfs.


So systemd can start and run (and stop!) without journald, udevd and dbus and all these unit files, and --user instances. But it is hard to "supermask" (i.e. make go away) the 200 distro-units, and then there still are some traces of built-in units and built-in logic.

TL;DR: emergency.target, DefaultDependencies=no, openvt

Share:
25,496

Related videos on Youtube

MattBianco
Author by

MattBianco

Updated on September 18, 2022

Comments

  • MattBianco
    MattBianco over 1 year

    For SysV init, I need /etc/inittab respawning getty entries, the /sbin/init binary, the binaries and shared libraries for the shell, login, the getty, the PAM/security/shadow stuff, and a few device files.

    For upstart I need pretty much the same requirements, but instead of /etc/inittab, I have a few *.conf files under /etc/init: one *.conf that start on startup that sets a runlevel with telinit, and one *.conf for each tty that start/respawn getty on that tty on the appropriate runlevels.

    What configuration and binaries do I need for systemd init?

    The documentation I find all seems to be focused on how to use an already-installed system to start and stop services.

    A minimal list of files to copy (except the kernel/initrd) from a running Arch or fedora installation would do fine, but I cannot seem to find that kind of information about systemd.


    What I would like to know is, for systemd, what files are required, and what must they contain, to start a login shell after an initramfs does it's switch_root call to the systemd /sbin/init.


    Example for upstart, the binaries and two *.conf files:

    File /etc/init/whatever.conf:

    start on startup
    emits runlevel
    task
    script
      telinit 2
    end script
    

    File /etc/init/tty1.conf:

    start on runlevel [12345]
    respawn
    exec /sbin/agetty -8 --noclear 38400 tty1 linux
    

    Example for sysvinit, the binaries and 1 conf file named /etc/inittab:

    id:2:initdefault:
    c1:12345:respawn:/sbin/agetty 38400 tty1 linux
    

    Now I'm after the systemd equivalent.

    I assume at least 1 *.service file is needed somewhere, with a [Service] entry containing ExecStart=-/sbin/agetty --noclear %I linux and Restart=always, but what else is needed?

    • MattBianco
      MattBianco over 9 years
      Now there is a recent RedHat knowledge base article (754933) describing the systemd boot at this URL: Overview of systemd for RHEL 7
    • ceving
      ceving almost 7 years
      It is so depressingly to see how people blow up a single configuration line into a big mess and call it improvement.
  • MattBianco
    MattBianco over 9 years
    I want to know which files are needed, and what they shall contain. Could you summarize your answer with a list of files that are required and what causes them to be read in what order? I'm missing info about what needs to be found in that directory. I'll try to elaborate my question a bit. Thanks!
  • MattBianco
    MattBianco over 9 years
    Yes, I'm spoiled by documentation from other open source projects. I'm sorry for sounding hostile. It's just frustrating that there seems to be no simple document explaining the boot process. (I understand now that it is because systemd is not simple.) In a humorous way I'd like to comment that it is perhaps systemd that is hostile, as in performing a hostile takeover of the way an open system starts. It turns GNU/Linux away from Unix. Not saying that that's a bad thing, but it is very different from how things have traditionally been. And googling around a bit indicates that I'm not alone.
  • Stefan Majewsky
    Stefan Majewsky over 9 years
    AFAIK the [Install] section is not used by the boot sequence, only by systemctl enable. What the boot looks at is the symlinks in /etc/systemd/system/basic.target.wants/, which are created by systemctl enable.