kdos

Administration

This chapter is for the person who looks after a KDOS machine once it is installed or booted from the stick. The first half is the machine as a whole: services and periodic jobs, the login on tty1, users and groups, storage, networking and the firewall, hardware and device access, printing, media and time, updates, diagnosis and logs, copying and rebuilding the medium, and tuning builds for this CPU. The second half is what each user sets up in their own home: backups, mail, passwords and one-time codes, calendars and contacts, finding files by name, the language, and input methods. Each task is given as the command or the file that does it, with the reason the system is arranged that way where the reason affects what you do.

If you are new to KDOS, read Getting started first; this chapter assumes you can open a terminal and use sudo. The mechanisms behind it are in Boot and init and The daemons. Two facts shape everything below:

When something is wrong, start with Diagnosing.

Services

A service is a script in /etc/init.d named NN_name.sh, where NN is two digits that fix its place in the boot order. At boot, init runs /etc/init.d/rcS as its sysinit entry (Boot and init). rcS runs the enabled scripts in numeric order with the argument start, and ksvc, the KDOS service supervisor, keeps each long-running daemon under a process of its own and restarts it five seconds after it exits. You manage services with service:

service list                  # every service, whether it starts at boot, and its state
service status <name>
service start   <name>
service stop    <name>
service restart <name>        # stop, wait one second, start
service enable  <name>        # start it at boot
service disable <name>        # do not start it at boot

service is a link to ksvc, so ksvc status sshd and service status sshd do the same thing. ksvc check <name> asks the supervisor directly whether the process it started under that name is alive, which is how a single periodic job is checked (see Periodic jobs).

<name> is the part of the file name between the number and .sh: sshd for 70_sshd.sh. For status, start, stop and restart, an exact name is tried first and then any service whose name contains what you typed, so service status ssh finds sshd. enable and disable check the same way that the service exists, but record the name exactly as you typed it, so type the full name from service list: service disable ssh writes a marker that rcS never looks for.

Enabling and disabling is a marker file, not an edit: service disable bluetooth creates the empty file /etc/service.disabled/bluetooth, and service enable bluetooth deletes it. Nothing in /etc/init.d is rewritten. The live image ships one marker, sshd, so the SSH server is the one installed service that does not start until you enable it; opening ssh in the firewall (below) enables it. The installer writes markers for the services you turn off, and turns off CUPS and sshd by default (see Installation).

During boot, each script’s output goes to /run/kdos-init.<name>.log and is then copied to the console, so the first thing to read about a service that failed at boot is that file. It lives in /run, a tmpfs, and is gone after the next reboot.

The scripts that ship, in boot order. Scripts marked (package) come with the package named and are absent if that package is removed.

Script What it starts
01_udev Device management, and the coldplug that loads drivers for the hardware already present. It also creates device nodes such as /dev/uinput, /dev/uhid, /dev/snd/seq and /dev/vhost-net, whose driver loads only when a program first opens the node, so they exist before anything asks for them
02_modules Modules listed in /etc/modules-load.d
03_lvm LVM volume groups, activated once at boot, then any fstab entry on a logical volume checked and mounted. On a disk boot the initramfs has already activated the groups it saw; this pass catches the rest — every group on a live boot, and any on a disk that appeared after the initramfs ran. A group plugged in later is not activated automatically; run sudo vgchange -aay
05_hostname The hostname
10_sysctl Kernel parameters from /etc/sysctl.conf: unprivileged ping; the name of the fatal signal in the kernel log when a program crashes; the fs.protected_* link protections, which stop a user redirecting a root job’s write in /tmp onto another file; per-task delay accounting, which iotop’s IO% and htop’s delay columns read, at a small cost on every wait the kernel accounts; no ICMP redirect accepted or sent; the loose reverse-path filter; and kernel addresses hidden from every account but root
12_zram Compressed swap in RAM — see Storage
15_userdirs /run/user/<uid> for every account with a UID from 1000 to 65533, and a delegated cgroup subtree for each with the cpu, memory and pids controllers, which is what lets rootless podman apply a box’s memory and CPU limits (a box is the rootless container one application runs in; see the glossary)
18_timers Periodic jobs: one supervised snooze per line of /etc/kdos/timers.d — see Periodic jobs
20_dmesg, 22_syslog Kernel and system logging. 20_dmesg lowers the console log level to 3, so only kernel errors reach the screen. 22_syslog runs syslogd, whose /etc/syslog.conf sets secure_mode 2, so it opens no network socket: it neither accepts messages from other hosts nor forwards to one. secure_mode 1 allows forwarding to an @host action without listening. See Logs
25_nftables The firewall, loaded before the network comes up — see The firewall
30_network dhcpcd, the fallback DHCP client. It stays down when NetworkManager is installed and not disabled, because NetworkManager’s own DHCP client does not defer to it: two clients on one link means two leases, two default routes and two writers of /etc/resolv.conf
31_babeld (babeld) Babel mesh routing, skipped until a non-empty /etc/babeld.conf exists. Open the babel firewall name for the neighbours
35_chrony Time synchronisation
40_dbus The system message bus. On its first start it generates /var/lib/dbus/machine-id and links /etc/machine-id to it
41_polkitd polkit, the privilege broker. It starts before NetworkManager, which asks it on its first privileged call
42_modemmanager ModemManager: mobile broadband modems, for NetworkManager and mmcli. Started at boot rather than on demand for the same reason as polkit
42_networkmanager NetworkManager
43_boltd (bolt) Thunderbolt. Only when /sys/bus/thunderbolt/devices is non-empty, it calls org.freedesktop.bolt so the bus starts boltd. A device plugged in later is handled by the bolt package’s udev rule
44_switcheroo-control (switcheroo-control) net.hadess.SwitcherooControl on the system bus, which tells a launcher which GPU is discrete and what environment starts a program on it; KIO’s “run on the discrete GPU” and switcherooctl launch read it. Skipped when the machine has fewer than two GPUs
45_avahi mDNS: finding printers and other machines on the local network by name
45_seatd Seat management, which the desktop needs
46_hostapd A wireless access point, only when /etc/hostapd/hostapd.conf exists — see An access point with hostapd
47_pcscd Smart cards and security keys: the PC/SC daemon, with readers arriving through udev
50_alsa Saves and restores the sound card’s mixer levels
51_mdmonitor mdadm --monitor --scan --syslog: a failed member, a degraded array or a missing spare is written to the system log. Skipped when /proc/mdstat lists no array; a MAILADDR or PROGRAM line in /etc/mdadm.conf adds a route of your own
51_lsmd (libstoragemgmt) The libstoragemgmt plugin daemon, which udisks2’s LSM module and lsmcli reach over /run/lsm/ipc; plugins that do not need root run as the libstoragemgmt account
52_smartd Disk health: smartd polls every disk that answers SMART every half hour and logs a failing health check, bad sectors, drive errors and failed self-tests. Where no disk answers SMART — any virtual machine — it logs why and stops
53_xfs_healer One xfs_healer per XFS filesystem mounted at boot. It logs the metadata damage the kernel reports and, where the filesystem’s autofsck property asks for it, has it repaired online. Skipped on a kernel without the XFS health monitor
54_thermald Intel thermal management
55_powerd kdos-powerd: suspend, power-off and reboot for the desktop, and a few system settings (time zone, accent, autologin, firewall names)
55_tlp Laptop power management: tlp init start at boot and tlp init stop at shutdown, which apply the startup and shutdown radio settings in /etc/tlp.d. kdos-powerd runs tlp suspend and tlp resume around a suspend
56_energyd Per-application energy use
56_nut (nut) The UPS tools: the drivers, upsd and upsmon, or upsmon alone as a network client. Skipped unless MODE in /etc/nut/nut.conf is standalone, netserver or netclient. Open the nut firewall name to serve UPS status to other machines
57_oomd Memory-pressure protection: ends the heaviest application before the machine stalls, sparing the desktop itself
58_mountd Removable media for the desktop
59_packd kdos-packd, which mounts application packs: signed EROFS images holding an application, runtime, base or dataset, which boxes are built over. See Packs and boxes
60_bluetooth Bluetooth
61_spacenavd (spacenavd) The 3D mouse daemon. It finds 3Dconnexion devices as they are plugged in and serves them on /run/spnav.sock, the socket every libspnav client (FreeCAD, OpenSCAD, KiCad, Blender) connects to. /etc/spnavrc, absent by default, sets sensitivity, axis mapping and dead zones; /usr/share/doc/spacenavd/example-spnavrc is the template. It serves no X11 client, so a program that speaks only the proprietary 3Dconnexion X protocol sees no device
62_virtlogd (libvirt) libvirt’s log daemon, which keeps each guest’s console log
63_gssd (nfs-utils) rpc.gssd, the Kerberos half of an NFS client, which a sec=krb5 mount needs. It mounts rpc_pipefs at /var/lib/nfs/rpc_pipefs first. Skipped until /etc/krb5.keytab exists
63_libvirtd (libvirt) libvirt’s management daemon for qemu:///system. Its polkit rule lets members of the libvirt group manage guests
64_lircd (lirc) The infrared remote daemon, skipped until a receiver is present (a /dev/lirc* node or an rc-core device). Its socket /run/lirc/lircd is what Kodi, VLC and other LIRC clients read
65_brltty (brltty) The console screen reader, skipped until /etc/brltty.conf exists. Its speech goes straight to the sound card, so it cannot speak while a desktop session’s PipeWire holds the card. See Accessibility
70_sshd The SSH server, disabled until you enable it (the image ships /etc/service.disabled/sshd). Opening ssh in the firewall enables and starts it, and closing ssh disables and stops it. Before the first start it generates every missing host key type with ssh-keygen -A
71_snmpd (net-snmp) The SNMP agent, as root, skipped until a non-empty /etc/snmp/snmpd.conf exists; it logs to syslog. Open the snmp firewall name for other machines
72_nfsd (nfs-utils) The NFSv4 server, skipped until /etc/exports names a share. It loads nfsd, supervises nfsdcld (the client records a restarted server needs) and nfsv4.exportd, runs exportfs -r, then starts the kernel threads with rpc.nfsd -N 3 -V 4. NFSv3 is off, so no portmapper, rpc.mountd or lock daemon runs. After editing /etc/exports, run sudo exportfs -r. Open the nfs firewall name for other machines
73_mosquitto (mosquitto) The MQTT broker, as the mosquitto account, skipped until /etc/mosquitto/mosquitto.conf exists. With no configuration the broker listens on loopback only; a listener 1883 line there, plus the mqtt firewall name, lets devices on the LAN reach it
74_prosody (prosody) The XMPP chat server, as the prosody account, skipped until it has an account: sudo prosodyctl adduser <user>@<host> makes the first. Clients are refused until the host has a certificate; sudo prosodyctl cert generate <host> makes a self-signed one. Open the xmpp firewall name for other machines
75_mumble-server (mumble) The Mumble voice server, as the mumble-server account, skipped until its database exists: running mumble-server -supw <password> as that account makes it. Open the mumble firewall name for other machines
76_postgresql (postgresql) One shared PostgreSQL server, as the postgres account, skipped until a cluster exists: sudo -u postgres initdb -D /var/lib/postgres/data makes it. It listens on loopback and on a socket in /tmp
77_radicale (radicale) Radicale, the CalDAV and CardDAV server, as the radicale account, skipped until /etc/radicale/users has an account line (name: followed by a hash from openssl passwd -6). Open the caldav firewall name for other machines
78_maddy (maddy) The maddy mail server, as the maddy account, skipped until /var/lib/maddy/credentials.db holds an account (running maddy creds create <address> and maddy imap-acct create <address> as the maddy account, with sudo -u maddy, makes one). Open the mail firewall name for other machines
79_ngircd (ngircd) The ngIRCd chat server, as the ngircd account, skipped until a non-empty /etc/ngircd.conf exists; /usr/share/doc/ngircd/sample-ngircd.conf is the template. Open the irc firewall name for other machines
80_cups Printing
81_cups-browsed Printers shared on the network, added to CUPS as they appear
82_ipp-usb Driverless printing and scanning over USB: each IPP-over-USB device is served on localhost as it is plugged in
83_samba (samba) smbd, skipped until a non-empty /etc/samba/smb.conf exists. Open the smb firewall name for other machines
84_minidlna (minidlna) MiniDLNA, the media server for televisions and players on the LAN, skipped until a non-empty /etc/minidlna.conf exists; the template is in /usr/share/doc/minidlna. Open the dlna firewall name for other machines
85_gnuhealth (gnuhealth) The GNU Health server (trytond), as the gnuhealth account, skipped until /etc/gnuhealth/trytond.conf names a database uri. Open the tryton firewall name for other machines
86_kiwix-serve The offline library on 127.0.0.1:8080, skipped until /var/lib/kiwix/library.xml lists an archive — see The local servers
87_kolibri Kolibri, the offline curriculum, on 127.0.0.1:8081, skipped until a channel is installed
88_llama-server The local language-model server on 127.0.0.1:8082, skipped until a .gguf model is under /usr/share/llama.cpp/models
89_step-ca (step-ca) The step-ca certificate authority, as root, from /etc/step-ca. Skipped until /etc/step-ca/config/ca.json (from sudo STEPPATH=/etc/step-ca step ca init) and /etc/step-ca/password.txt (the CA password, mode 600) both exist. Open the https firewall name for other machines when the CA listens on 443

Shutdown

Shutdown runs the same set backwards. poweroff, reboot and shutdown ask init for runlevel 0 or 6, and /etc/init.d/rcK is the first entry of those runlevels in /etc/inittab; it runs each enabled script with stop, in reverse order, before the remaining processes are ended, swap is turned off and filesystems are unmounted, so a service can still save its state. A service that fails to stop does not hold up the rest. The firewall is not stopped: it stays loaded until power-off, so the machine is never on the network unfiltered. See Boot and init.

Services that skip themselves

A daemon that cannot do its job on this machine is skipped at boot, with a [SKIP] line saying why, instead of being started and left to fail:

Script Skipped when
44_switcheroo-control Fewer than two GPUs under /sys/class/drm
54_thermald The processor is not Intel
56_energyd No readable CPU energy counter (/sys/class/powercap/*/energy_uj)
57_oomd /proc/pressure/memory is not writable (pressure stall information is off)
59_packd The kernel cannot mount EROFS, the pack filesystem

The check runs before the daemon starts, because a daemon that refuses to run, restarted every five seconds by its supervisor, keeps the boot from ever settling.

Some daemons can only tell for themselves that they have nothing to do: thermald knows which Intel models it supports, and smartd finds out only by asking each disk. When one of these — thermald, smartd, mdadm --monitor or xfs_healer — exits with a status that restarting cannot change, the supervisor logs one line such as [KDOS] (thermald) Exited with code 2, a final status: not restarting and leaves it stopped, and service status reports it as not running. Each script names those final statuses when it starts its daemon (supervise --final-exit 2 …); how the supervisor tells that exit from a crash is in The daemons.

Services with no daemon

Twelve scripts leave nothing for the supervisor to watch: 01_udev, 02_modules, 03_lvm, 05_hostname, 10_sysctl, 12_zram, 15_userdirs, 20_dmesg, 25_nftables, 43_boltd, 50_alsa and 55_tlp. Most apply a setting and exit; 25_nftables and 12_zram hand their result to the kernel, which keeps it. For these, service status reports what the script applied rather than a running process: sysctl: applied, the zram device and its nominal size, whether a firewall ruleset is loaded, the logical volumes (lvs), or TLP’s own summary (tlp-stat -s). 02_modules and 15_userdirs print nothing. Two report a process after all: service status boltd says whether the bus has boltd running, and service status udev says whether udevd is running, because udevd runs on its own once the script has started it.

Periodic jobs

There is no cron daemon. A periodic job is one line in a file ending .timer under /etc/kdos/timers.d/:

NAME  TIMESPEC...  --  COMMAND...

Blank lines and lines starting with # are ignored. For example, the shipped update check:

update-check  -H 4 -M 17 -s 8h  --  kdos update check --fetch --out /var/lib/kdos/update.json

snooze is a small program that sleeps until the clock matches its options, runs a command once and exits. At boot 18_timers starts one snooze per line under the supervisor, as the service kdos-timer-NAME; when snooze exits after a run, the supervisor starts it again and it sleeps until the next slot. That gives one process per job and no shared schedule file to corrupt. service status timers shows every job, and ksvc check kdos-timer-update-check shows one.

The timespec is snooze’s own options, unchanged: -H 4 -M 17 is 04:17, -d /2 is every second day. snooze(1) is the full reference.

Missed runs. -s sets the slack. A machine that was asleep at the slot runs the job once when it wakes, if it wakes within the slack — once, not once per missed slot, so a laptop closed for a fortnight does not run fourteen catch-up jobs.

-s does not cover a machine that was off. A snooze that has just started looks forward from now, so a slot that passed before boot is missed until the next one. -t FILE changes that: snooze then counts from that file’s modification time, and the slack covers the gap. A job that must run even when the machine was off across its slot needs both -s and -t.

The command is a list of words, not a shell line. There is no pipe, no redirection and no &&; a program that writes a file takes a flag naming it, which is why kdos update check has --out. Quotes are not interpreted either: the line is split on spaces, so -- kdos notify "Good morning" passes "Good and morning" as two arguments. A program that needs a sentence reads it from a file of its own, which is what kdos remind does. This is what lets the table be parsed rather than run by a shell, so a line cannot run anything its author did not write.

Errors. A line with no --, no command, or a name containing anything but letters, digits, _ and - is reported at boot and skipped. A line whose command is not installed is skipped with a [SKIP] line. Nothing is guessed at: a job that silently never started looks exactly like one that has not fired yet.

The jobs that ship in the system table:

File Runs When Why then
10-update-check.timer kdos update check --fetch --out /var/lib/kdos/update.json 04:17, slack 8 h Not midnight, when every other machine runs its jobs and asks the same server
20-fstrim.timer fstrim -a 12:47, slack 20 h See below
30-fwupd-refresh.timer (added by the fwupd package) fwupdmgr refresh 13:43, slack 20 h The firmware metadata, so fwupdmgr get-updates has something to compare against

fstrim tells an SSD which blocks are free. The installer mounts ext4 and XFS without the discard option, so without this job the drive’s write speed and wear degrade as it fills; btrfs and f2fs discard on their own, and a device that cannot discard is skipped. The slot is at midday because fstrim has no timefile to pass to -t, so a slot the machine was off for is missed — and a desktop switched off every night would never reach an overnight one.

Your own jobs

Put your own jobs in ~/.config/kdos/timers.d/*.timer, in the same format. Your desktop session starts them when you log in and stops them when you log out: a job that writes into your home directory should not outlive the login that started it. While you are logged in they repeat like the system ones — each line runs in a loop that restarts snooze after every run.

Differences from the system table:

Give each job a minute of its own. A laptop that wakes after its slots have passed runs every job whose slack covers the gap at the same moment, and jobs that start together compete for one disk. That is why the backup job below sits at 03:40 and not beside the 04:17 update check.

Two jobs ship in the skeleton copied into each new home:

File Runs State
20-updatedb.timer kdos-updatedb at 03:05 Enabled — see Finding a file by name
10-backup.timer kdos-backup --once at 03:40 Commented out: a first backup of a home directory can take hours, and should be something you chose — see Backups

The session on tty1

Logging in on the first console, tty1, starts the desktop. /etc/inittab gives tty1 to kdos-getty, which loads the console font and colours and then runs kdos-login. kdos-login reads /etc/kdos/login.conf and either logs in the account it names without asking or shows the normal password prompt. tty2 is a plain login prompt: the recovery console to use when the desktop does not come up.

/etc/kdos/login.conf has one key:

Key Does
autologin The account tty1 logs in without a password. Shipped as autologin = kdos. Commented out, tty1 asks for a password

Change it with kdos-users, or kdos-power autologin <user> or kdos-power autologin off (members of wheel). Those refuse an account that cannot log in, which would leave a machine that boots to a login nobody can complete. This is the only place the desktop account is named, and the installer writes it when you choose a user name. If kdos-login is missing, kdos-getty starts a plain autologin prompt itself, using the same autologin key, so a broken login program still leaves you a console.

Users and groups

One account ships: kdos (UID 1000), a member of wheel, seat, users and the hardware groups below. The installer asks for its password; on the live image it is kdos.

The hardware groups of the kdos account, and what removing each would silently stop:

Group Makes usable without root
dialout Serial adapters, debug probes, SDRs, instruments, security keys, cameras, scanners, phones — see the udev rules
audio Sound devices
video The GPU’s display nodes, screen and keyboard backlight, and a monitor’s DDC/CI line
render GPU rendering and compute
input Input devices and game controllers’ raw HID
kvm Hardware virtualisation for QEMU
cdrom Optical drives
lpadmin Adding and managing printers in CUPS
tty Opening a virtual terminal from a backgrounded session: /dev/tty0 is 0620 root:tty, and a backgrounded session has no controlling terminal of its own, so without this group the desktop is refused a terminal

Adding a user. kdos-users lists accounts and their groups and sets autologin; it does not create accounts. Use the shadow tools:

sudo useradd -m -G wheel,seat,audio,video,render,input -s /bin/bash alice
sudo passwd alice

Add the other groups from the table as needed. Two things to check afterwards:

Storage

Filesystem checks at boot. Before anything is mounted, rcS runs fsck -A -R -T -a: every /etc/fstab filesystem with a non-zero pass number is checked and repaired where that is safe, except the root, which the initramfs checked before mounting it. The report goes to /run/kdos-fsck.log. A filesystem left with uncorrected errors is still mounted, and the boot splash says so, rather than the machine stopping behind the splash; read the log and repair it by hand from tty2. A filesystem on an LVM volume group that the initramfs did not activate is checked and mounted later by 03_lvm, which appends to the same log.

Swap. rcS runs swapon -a at boot, after mount -a, so any swap partition or swapfile in /etc/fstab is used. The installer can create a swapfile and writes its fstab line.

zram is compressed swap held in RAM, set up by 12_zram from /etc/kdos/zram.conf:

size = 50
algorithm = zstd
Key Means Default
size How much swap the device may claim to hold, as a percentage of RAM (1–90). It is not the memory the device occupies, since compressed pages live in that same RAM. A value outside 1–90 is reported and replaced with 50 50
algorithm The compression algorithm. One the running kernel lacks is reported, and the kernel’s default is used zstd

The device is used ahead of disk swap (priority 100). Once it is up, 12_zram changes two kernel settings, and service stop zram puts both back:

Removable media are mounted by the kdos-mountd root daemon, which you reach through kdos-devices (Super+F6) or the kdos-mount command. A mounted device appears under /media/<user>/<label> (the device name when it has no label, with any character other than letters, digits, ., _ and - removed), private to you and removed again on unmount. Every removable mount is nosuid,nodev, and noexec by default: a setuid program on someone else’s USB stick would otherwise be a way to become root. Create /etc/kdos/mountd.conf (not shipped) to change that:

Key Effect
exec = yes Mount removable media without noexec, so programs on them can run
format = yes Allow formatting a removable device
write = yes Allow writing a disk image over a whole removable disk

The daemon never offers an internal disk, a filesystem the kernel cannot mount, anything already mounted, anything named in /etc/fstab, or the medium the system booted from. Members of seat or wheel may use it.

The client never names a device or a mount point: it asks for a row number from the list the daemon published, and the daemon decides the rest.

kdos-mount list              # the removable devices, one numbered row each
kdos-mount mount 0           # mount row 0; prints the mount point
kdos-mount unmount 0
kdos-mount smart 0           # the disk's SMART health verdict (smartctl -H)
kdos-mount write 0 kdos.iso sdb   # kdos.iso over all of sdb, read back and compared
kdos-mount shares            # the network shares connected now
kdos-mount browse            # file servers that answered an mDNS or NetBIOS broadcast

The same daemon unlocks a LUKS-encrypted stick, formats a device when format = yes allows it, writes an image over a whole stick when write = yes allows it (the image is opened as you and handed to the daemon open, and the disk’s name is typed back to confirm), and connects SMB shares, including with a Kerberos ticket from kinit (kdos-mount krb5 <server> <share> <user|-> <domain|->). The passphrase or share password is typed into kdos-disks or kdos-connect, never on a command line. Every verb and refusal is in The daemons.

Disks in the native applications. The applications ported natively (Dolphin’s device list, GNOME Disks, K3b, the Impression image writer, the file choosers of GTK and Qt) do not use kdos-mountd. They reach disks through udisks2, with gvfs on top for GTK. udisksd has no service script: the system bus starts it on the first request. It mounts under /run/media/<user>, and every request is decided by polkit, whose rule in /etc/polkit-1/rules.d/50-kdos.rules grants mounting, unlocking, ejecting, formatting and raw device access to members of wheel without a password. With no authentication agent to ask for a password, an account outside wheel is refused. kdos-mountd remains the authority for the panel and kdos-devices.

Networking

NetworkManager manages the network, with wpa_supplicant for wireless, ModemManager for mobile broadband and a local dnsmasq for DNS. The polkit rule in /etc/polkit-1/rules.d/50-kdos.rules lets members of wheel scan, connect, forget networks, switch the radio and share a connection without a password. It deliberately leaves out setting the hostname, setting machine-wide DNS and reloading NetworkManager’s configuration; those need sudo.

Tool For
kdos-net (Super+F4) The desktop network manager: networks, connecting, forgetting, the hotspot
kdos-netagent The passphrase box NetworkManager raises; started with the session
nmtui The full text interface
nmcli Scripting

Connecting to wireless. Open kdos-net, choose the network and type the passphrase; it is saved in the connection profile.

Any later question comes from kdos-netagent. NetworkManager never prompts by itself: when a saved secret is missing or refused, it asks the password agents registered with it, and fails the connection silently if none answers. So a changed wireless key, 802.1X enterprise wireless and a VPN one-time code all arrive as a box from the agent, not from the window you started in. The agent stores nothing; only a request that allows interaction raises a box.

DNS. NetworkManager starts a caching dnsmasq on 127.0.0.1 and ::1 and feeds it the servers of each connection, as set by /usr/lib/NetworkManager/conf.d/10-kdos-dns.conf. DNS is split: a VPN’s servers answer for the VPN’s own domains rather than replacing every server the machine has. A file of the same name in /etc/NetworkManager/conf.d overrides the shipped one. /etc/resolv.conf has one writer, resolvconf (openresolv), which NetworkManager, the fallback dhcpcd and wg-quick all go through, so none of them overwrites another’s entries.

VPNs. OpenVPN works through NetworkManager with certificate and password authentication; PKCS#11 hardware tokens are not supported by the OpenVPN build. WireGuard works two ways:

Web servers that ask for a Windows sign-in. libsoup, the HTTP library under WebKitGTK, GStreamer’s network sources, GeoClue and gvfs, answers both challenges an Active Directory web service sends. Negotiate is answered with the Kerberos ticket kinit left in your credential cache. NTLM first tries single sign-on through Samba’s /usr/bin/ntlm_auth, which is why every libsoup consumer installs samba; that helper signs on only from winbindd’s cached credentials, and Samba here is built without winbind, so the attempt fails and libsoup answers NTLM itself with the password the application asks for. libsoup’s TLS is glib-networking’s GIO module, loaded at run time, so a system without glib-networking fails every https request at the handshake with “TLS/SSL support not available”.

Mobile broadband is a NetworkManager connection like any other. ModemManager finds a built-in WWAN card or a USB modem (MBIM, QMI, QRTR or AT). Connect it with nmcli connection add type gsm ifname '*' apn <apn>, or with nmtui’s mobile broadband entry, which offers carriers from mobile-broadband-provider-info. kdos-netagent asks for a SIM PIN. mmcli -m any shows the modem as ModemManager sees it; members of wheel can unlock and enable a modem and read its text messages there without sudo.

Other connection types:

Connection Command
PPPoE (DSL), carried by pppd nmcli connection add type pppoe ifname ppp0 pppoe.parent <ethernet> username <user> password <password>
A phone’s Bluetooth tethering nmcli connection add type bluetooth bt-type panu bluetooth.bdaddr <phone>, after pairing the phone in kdos-bt
A phone that offers only dial-up over Bluetooth The same with bt-type dun, plus an APN

An access point with hostapd

kdos-net’s hotspot is the quick way to share a connection, and needs none of this. hostapd is for a machine that is the network — an access point that stays up across reboots, with or without an uplink:

  1. Write /etc/hostapd/hostapd.conf, starting from hostapd.conf.example beside it. 46_hostapd starts hostapd at boot only when that file exists.
  2. Tell NetworkManager to leave the wireless card alone, or both will try to drive it: create a file in /etc/NetworkManager/conf.d containing

    [keyfile]
    unmanaged-devices=interface-name:<interface>
    
  3. Give the interface an address, and run a dnsmasq of your own on it for DHCP and DNS (interface=, bind-interfaces, dhcp-range=), from an init script of your own in /etc/init.d. bind-interfaces is required: NetworkManager’s dnsmasq already answers on 127.0.0.1:53 and ::1:53, and a second one without it tries the wildcard address, fails with the port in use, and serves no clients.
  4. Let the clients through the firewall. The shipped rules open DHCP and DNS only for NetworkManager’s hotspot range 10.42.0.0/16, so add a file in /etc/nftables.d that accepts udp dport { 53, 67 } and tcp dport 53 with iifname "<interface>" in the input chain — and, if clients should reach anything beyond this machine, accepts forwarding from that interface. See Adding your own rules.

Syncing and serving on the network

Three programs answer other machines. Each has a name in the firewall that must be turned on first; the shipped policy blocks them all.

Program Start it with Firewall name
Syncthing File Sync in the menu, which runs kdos-syncthing in a terminal and opens the web interface on 127.0.0.1:8384. Closing the terminal stops syncing syncthing, on both machines
mosh mosh <user>@<host> from the other machine; mosh-server is started over SSH ssh and mosh
Caddy caddy run --config /etc/caddy/Caddyfile, which serves kiwix-serve (expected on port 8080) over HTTPS on port 8443, with a certificate from Caddy’s own local authority caddy

kdos-syncthing gives a user who has no Syncthing configuration one with local discovery on, and global discovery, relays, NAT traversal and usage reporting off: two machines on one network find each other, and nothing reaches a server outside it. An existing configuration is left as it is.

The firewall

/etc/nftables.conf holds the firewall policy, and 25_nftables loads it before the network starts, so the machine is never on the network unfiltered.

What the shipped policy does:

Traffic Rule
Incoming Dropped unless a rule below accepts it
Forwarded Dropped unless a rule below accepts it
Outgoing Accepted
Replies to connections this machine opened Accepted, incoming and forwarded
Invalid packets Dropped
Loopback Accepted
ICMPv6 Accepted: destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, router advertisements, neighbour solicitation and advertisement
ICMP Accepted: destination-unreachable, time-exceeded, parameter-problem, echo-request
mDNS (UDP 5353), DHCPv6 client (UDP 546) Accepted
NetBIOS name replies (UDP 137 to 137) Accepted
DNS (UDP and TCP 53) and DHCP (UDP 67) from 10.42.0.0/16 Accepted
Anything to or from 10.42.0.0/16 Forwarded
DNS (UDP and TCP 53) from a podman* bridge Accepted
From a podman* bridge, or to one through a published port Forwarded

Why the less obvious rows are there:

Opening a service

Anything that should be reachable from another machine needs a rule — sshd, a shared printer and a served offline library are all blocked by default. There are two ways, and they do not interfere with each other.

By name, with kdos-firewall (the Firewall tool in Settings, under System) or the command kdos-power firewall:

kdos-power firewall list          # every name, and whether it is on
kdos-power firewall ssh on
kdos-power firewall ssh off
Name Opens
ssh TCP 22
http TCP 80
https TCP 443
ipp TCP 631 (sharing a printer)
smb TCP 445
kiwix TCP 8080 (kiwix-serve)
mdns UDP 5353
mqtt TCP 1883 and 8883
xmpp TCP 5222
nfs TCP 2049
babel UDP 6696 (Babel mesh routing, babeld)
nut TCP 3493 (UPS status for other machines)
snmp UDP 161 (the SNMP agent, snmpd)
mumble TCP and UDP 64738 (a Mumble voice server)
caldav TCP 5232 (Radicale’s calendars and contacts)
mail TCP 25, 143, 465, 587 and 993 (Maddy)
irc TCP 6667 and 6697 (ngIRCd)
dlna TCP 8200, and UDP 1900 for discovery (MiniDLNA)
tryton TCP 8000 (the GNU Health server)
caddy TCP 8443 (the shipped Caddyfile’s site)
mosh UDP 60000–61000; also needs ssh
syncthing TCP and UDP 22000, and UDP 21027 for discovery
kdeconnect TCP and UDP 1714–1764 (KDE Connect with a phone)
vnc TCP 5900 (wayvnc; it binds 127.0.0.1 unless given an address)
xonotic UDP 26000 (hosting a Xonotic game)

Turning a name on or off needs membership of wheel. The meaning of each name is fixed inside kdos-powerd, not in the tool you click, so no client can open a port that is not on this list. kdos-powerd rewrites /etc/nftables.d/50-kdos-services.nft completely on every change and reloads the firewall, so do not put your own rules in that file: they disappear at the next toggle.

Adding your own rules

Anything else is an edit. /etc/nftables.conf carries commented examples in its input chain, and its last line, include "/etc/nftables.d/*.nft", reads every .nft file in that directory. A file of your own there is never touched by kdos-firewall. For example, /etc/nftables.d/60-local.nft, opening TCP 3000 for a development server, a port no firewall name covers:

table inet filter {
    chain input {
        tcp dport 3000 accept
    }
}

Add to table inet filter and do not declare a table of your own. Reloading deletes and rebuilds only inet filter, so that netavark’s inet netavark table (rootful containers’ address translation and published ports) and NetworkManager’s nm-shared-* tables (the hotspot) survive; a table of your own would survive too, keeping its old rules beside the new ones. Name the chain without a type … hook line — that re-opens the existing chain, while repeating the hook declares a second chain and the load fails.

Check, then apply:

sudo nft -c -f /etc/nftables.conf     # check only
sudo service nftables start           # apply

25_nftables runs the same check before loading, so a file with a mistake is refused as a whole and the rules already loaded stay in force. sudo service nftables stop removes the inet filter table, which leaves the machine unfiltered until the next start.

Hardware

Firmware

linux-firmware is installed with upstream’s own script so that the alias links drivers ask for exist. It ships every blob an x86 kernel can request: only firmware for hardware this kernel cannot drive is left out (see the ports catalogue). Trimming by guessing at your hardware would fail silently, so nothing an x86 driver names is removed.

sof-firmware is separate and is needed for audio on Intel Tiger Lake and newer. It carries both the DSP firmware and the topology files; firmware without its topology loads and produces no sound.

wireless-regdb, the table of which radio channels and power levels each country allows, is built from upstream’s db.txt and ships with upstream’s signature. The kernel checks that signature, and rejects an unsigned or altered database silently, leaving the radio on the restrictive world settings: no 5 GHz DFS channels and reduced transmit power. The build verifies the signature against the database it produced and fails on a mismatch, so an edited db.txt never ships.

The country comes from your time zone. The installer and kdos-power timezone <Area/City> both write the zone’s country code to /etc/modprobe.d/kdos-regdom.conf as cfg80211’s ieee80211_regdom. kdos doctor warns when the radio is still on the world domain 00, and sudo iw reg set <CC> changes it until the next boot.

Microcode

CPU microcode is applied by the kernel’s early loader, which runs before any filesystem exists, so it is carried as an uncompressed archive in front of the initramfs: Intel’s from the intel-ucode package and AMD’s from linux-firmware. Late loading is off in the kernel, so this is the only way microcode is applied.

kdos doctor        # whether the initramfs carries this CPU's microcode, and the running revision

The check exists because an initramfs rebuilt without microcode has no symptom: the processor keeps whatever revision the firmware loaded.

The device groups and the udev rules

Opening a device as a normal user takes two things: membership of a group, and a udev rule that gives that group the device. Neither works alone. The KDOS rules live in /etc/udev/rules.d:

Rules file Devices Group
70-kdos-serial.rules USB serial adapters — FTDI, CP210x, CH341, CDC-ACM dialout
70-kdos-debug.rules In-circuit debuggers and programmers, raw USB and HID: CMSIS-DAP from any vendor, ST-Link, J-Link, Atmel-ICE, PICkit, USBasp, USB-Blaster, XDS110, Nu-Link, KitProg and the rest of openocd’s and openFPGALoader’s cables dialout
70-kdos-sdr.rules Software-defined radio front ends: every RTL2832U stick librtlsdr knows, HackRF, Airspy, bladeRF, SDRplay and Mirics, LimeSDR, the USRP B-series, ADALM-Pluto, the FUNcube Dongles (their HID node too), Perseus, RFNM and Fobos dialout
70-kdos-usbtmc.rules USB Test & Measurement: scopes, meters, function generators — /dev/usbtmc* and the raw USB device pyvisa-py opens dialout
70-kdos-sigrok.rules Every logic analyser, scope and meter libsigrok’s 60-libsigrok.rules marks dialout
70-kdos-gpio.rules /dev/gpiochip*, for libgpiod’s tools and the GPIO cables of openocd and openFPGALoader dialout
70-kdos-fido.rules FIDO2/U2F security keys, for ssh-keygen -t ed25519-sk and the fido2-* tools dialout
70-kdos-camera.rules PTP/MTP cameras, for gphoto2 dialout
70-kdos-scanner.rules Flatbed and sheet-fed scanners, for SANE dialout
70-kdos-ptouch.rules Brother P-touch label printers, which ptouch-print drives over raw USB lpadmin
70-kdos-gamepad.rules Game controllers’ raw HID, for SDL’s HIDAPI drivers input
70-kdos-i2c.rules The DDC/CI line of a display controller, for ddcutil video
70-kdos-backlight.rules The panel’s brightness for kdos-osd, and the keyboard backlight where there is one video

One more file there grants no group: 99-qemu-tablet.rules marks QEMU’s emulated USB tablet as a plain pointer, so in a virtual machine the pointer is handled as a mouse rather than as a graphics tablet or a joystick.

Packages add two more: libmtp’s rules give phones to dialout, and gpsd’s /usr/lib/udev/rules.d/70-kdos-gpsd.rules handles GPS receivers (below).

Every rule that names a device node sets MODE="0660", readable only by the owner and group, so other accounts on a shared machine cannot read these devices.

Some rules are deliberately narrow:

Upstream rules that grant access through a plugdev group or uaccess are not installed: there is no plugdev group, udev turns an unknown group into root, and nothing here uses uaccess. That is why libfido2’s 70-u2f.rules and the rules of rtl-sdr, hackrf, airspy, bladeRF, openocd and openFPGALoader are absent, and the KDOS files above cover their devices. stlink’s are removed because they make every ST-Link writable by every account (MODE:="0666"). Where upstream separates identifying a device from granting it, the identification is kept and the grant is KDOS’s: libsigrok’s 60-libsigrok.rules sets ID_SIGROK, sane-backends’ 20-sane.hwdb and 65-libsane.rules set libsane_matched, and eudev’s fido_id sets ID_SECURITY_TOKEN; each 70-kdos-* file turns its mark into the group. So the FIDO rule covers every key whose HID descriptor declares FIDO, and the scanner rule every scanner a SANE backend supports.

Modules loaded by name. Some drivers declare nothing udev can match, so files in /etc/modules-load.d load them at boot through 02_modules:

File Loads Without it
kdos-i2c.conf i2c-dev No /dev/i2c-*, and the i2c rule has nothing to grant
kdos-hwmon.conf nct6775 (Nuvoton), it87 (ITE), drivetemp (SATA disk temperature) sensors and kdos-res show CPU and GPU temperatures only — no fans, voltages or board temperatures
kdos-containers.conf tun, overlay Container networking and overlay storage
kdos-drm.conf virtio_gpu, qxl, bochs, vmwgfx Virtual machines’ display drivers

Each hwmon driver binds only a chip it finds; on other hardware, or where the firmware has claimed the chip, the load fails and 02_modules logs a skip. sensors-detect is not shipped, because it probes the SMBus by writing to it. fancontrol is shipped, and nothing starts it.

One blacklist. /etc/modprobe.d/kdos-sdr.conf blacklists dvb_usb_rtl28xxu. Otherwise the kernel claims an RTL2832U dongle as a TV tuner when it is plugged in, and SDR programs cannot open it even though lsusb lists it.

Software-defined radio. Each radio the SDR rule covers has its own library and tools — rtl-sdr, hackrf, airspy and bladerf — and a SoapySDR module: soapyrtlsdr, soapyhackrf, soapyairspy and soapybladerf, in /usr/lib/SoapySDR/modules0.8. SoapySDRUtil --find lists what is plugged in, and a SoapySDR program reaches it, for example rtl_433 -d driver=hackrf. A bladeRF streams nothing until its FPGA image is loaded; that image is Nuand’s and is not shipped, so put it in ~/.config/Nuand/bladeRF/ or /etc/Nuand/bladeRF/, or let a bladeRF 2.0 load it from its own flash. SDRplay receivers are granted but have no driver, because SDRplay’s API is a closed library.

Drawing tablets need no group. The compositor opens them through libinput, which uses libwacom’s database to pair the stylus with its pad, group the pad’s buttons and rings, and tell a screen tablet from a desk one. libwacom-list-local-devices shows what it recognised. A model missing from the database works as a plain absolute pointer. To add it, describe it in a .tablet file under /etc/libwacom, then run libwacom-update-db --skip-systemd-hwdb-update followed by sudo udevadm hwdb --update (the tool’s own last step would call systemd-hwdb, which this system does not have).

GPS receivers. Plugging one in starts gpsd; no init script runs it at boot. gpsd’s 70-kdos-gpsd.rules runs gpsd.hotplug for receivers whose USB ID identifies them — u-blox, Garmin, DeLorme, the Holux MediaTek, the Telit module — and for the kernel’s gnss devices, and gpsdctl adds the device to a running gpsd or starts one. Upstream’s rule also matches the generic CP2102 and ATEN serial chips, which would make gpsd probe every ESP32 board and USB serial adapter, so it is not installed. Hand over a receiver behind a generic chip with sudo gpsdctl add /dev/ttyUSB0; until something is added, localhost:2947 refuses connections.

Without the group and the rule, a serial programmer, development board, instrument or GPS receiver looks like a broken cable rather than a permission problem. kdos doctor checks the serial (/dev/ttyUSB*, /dev/ttyACM*), video capture (/dev/video*) and USBTMC (/dev/usbtmc*) nodes that are present, lists the ones you cannot open, and names the group that owns each.

Bluetooth

bluetoothd is started by 60_bluetooth. kdos-bt (Super+F5, or Bluetooth in Settings, under Hardware) scans, pairs, trusts and connects devices, and registers the pairing agent: without an agent to confirm the passkey, the service refuses to pair a keyboard. kdos-audio (Super+F3) has a Bluetooth pane as well, for connecting a headset. bluetoothctl is the same control from a terminal, for scripting. Which profile and codec a headset uses is PipeWire’s decision, not the pairing tool’s; see kdos-shell.

Thunderbolt, phones, smart cards and firmware updates

None of these runs under the supervisor. boltd and fwupd are started by the system bus on first use and run as root through dbus-daemon-launch-helper; libmtp and opensc are libraries; a phone’s storage is mounted by a FUSE process of your own.

Thunderbolt. Where the firmware’s Thunderbolt security level is user or secure — the default on most business laptops — a TB3 or TB4 dock is detected and then never authorised, so its ports stay dead until something approves it. boltd approves a dock already enrolled: 43_boltd starts it at boot on a machine with a Thunderbolt controller, and a udev rule starts it when a Thunderbolt device appears later. Enrol a new dock with sudo boltctl enroll <uuid>. boltd is built to trust wheel and ships a polkit rule for it, but that rule also requires an active session, which nothing on KDOS provides, so the command needs sudo — see Security model.

Firmware updates. fwupd updates the firmware of SSDs, docks and peripherals from the LVFS, the Linux Vendor Firmware Service. Run sudo fwupdmgr get-updates to see what is available and sudo fwupdmgr update to apply it; the metadata is refreshed daily by /etc/kdos/timers.d/30-fwupd-refresh.timer.

System firmware — the BIOS, the embedded controller, and anything the vendor ships as a UEFI capsule — is applied at the next boot. fwupd places the capsule on the EFI system partition, which it expects at /boot/efi (EspLocation in /etc/fwupd/fwupd.conf, matching the installer’s fstab). Then either the firmware picks it up from disk, or the machine boots once into fwupdx64.efi (from fwupd-efi, placed in EFI/kdos beside the kernel), which hands it over. fwupd’s usual way of finding that partition is to ask udisks; the fwupd package is patched to describe the FAT filesystem mounted at EspLocation from sysfs and the udev database instead. The live image mounts no EFI partition, so there only devices updated over USB are offered. The loader is unsigned, which is no extra restriction, since KDOS already requires Secure Boot to be off (see Known gaps).

Phones. libmtp recognises a phone and gives its USB device to dialout. To browse its storage, unlock the phone, set it to file transfer, and open Android File Transfer from the Start menu, which copies to and from the phone with no mount. For a directory instead, run aft-mtp-mount ~/Phone (from android-file-transfer); any file manager can then read ~/Phone, and fusermount3 -u ~/Phone unmounts it. kdos-mountd does not offer MTP devices, so this is always by hand. libmtp’s own mtp-* tools reach files by numeric ID only. gphoto2 and libgphoto2 cover still cameras in PTP mode.

Smart cards. pcscd and ccid reach the reader, and opensc-pkcs11.so speaks PIV, CAC, OpenPGP and national ID cards. Its opensc.module registers it with p11-kit, so openconnect accepts a pkcs11: URI and GnuTLS programs see the card’s certificates; ssh -I /usr/lib/opensc-pkcs11.so uses it directly. pkcs11-tool --list-slots tells you whether the card is seen at all. Members of wheel reach smart cards without sudo.

Printing

CUPS is the print system, started by 80_cups. The installer turns it off by default, so on an installed machine enable it before the first print job:

sudo service enable cups
sudo service start cups

kdos-print (Printers in Settings, under System) lists the queues and their jobs, and adds a printer CUPS has discovered. It runs as you, not through a root daemon: CUPS is built with lpadmin as its system group, so membership of lpadmin is the authority to manage printers, and the kdos account has it. It sets a printer up only as IPP Everywhere, the driverless protocol a printer advertises and CUPS configures without a PPD file; a printer that needs a vendor driver is refused, and the window says so. The same from a terminal:

lpinfo -v                                        # the device URIs CUPS can reach
lpadmin -p <name> -v <uri> -m everywhere -E      # add and enable a driverless queue
lpstat -p -d                                     # the queues, and the default

81_cups-browsed adds printers shared on the network to CUPS as they appear, and 82_ipp-usb serves each IPP-over-USB printer on localhost as it is plugged in, so a USB printer is discovered the same way. Sharing a printer with other machines needs the ipp name in the firewall. How kdos-print is built is in kdos-shell.

Media, colour and time

ffmpeg is built with the full codec set: H.264, HEVC, VP8/VP9, AV1 encode and decode, JPEG XL, MP3, Opus, Vorbis, FLAC, LC3, subtitle burn-in, HDR tone mapping (zscale, libplacebo), high-quality resampling (soxr), time-stretch (rubberband), SVG input (librsvg), text recognition (the ocr filter, through tesseract), QR codes (qrencode and qrencodesrc), the OpenCL filters (tonemap_opencl, nlmeans_opencl, unsharp_opencl, overlay_opencl and the rest) and hardware acceleration through VA-API and Vulkan. There is one encoder per format. There is no ffplay, because it needs SDL, and SDL depends back on ffmpeg through PipeWire.

The OpenCL filters run on Mesa’s rusticl, which offers an Intel or AMD GPU because /etc/profile.d/50-opencl.sh sets RUSTICL_ENABLE; a machine with neither has no OpenCL device.

The build passes --enable-gpl (which x264, x265 and rubberband require) together with --enable-version3, and ffmpeg’s configure licenses that combination as GPL version 3 or later. Everything that links it inherits that licence. LICENSE.notice in the ffmpeg port (ports/core/media-frameworks/ffmpeg/) is the notice a redistributor should read.

Hardware video decoding goes through VA-API, and the driver depends on the GPU:

GPU Driver
AMD (r600, radeonsi), NVIDIA through nouveau, virgl Mesa’s gallium-va
Intel, Broadwell and newer intel-media-driver (iHD), over intel-gmmlib
Intel Sandy Bridge, Ivy Bridge, Haswell libva-intel-driver (i965)

libva tries iHD first and falls back to i965, so each machine loads the one that serves it. With no driver, ffmpeg and mpv silently fall back to software decoding. vainfo, from libva-utils, lists the profiles the loaded driver offers on this machine.

mpv is the native player. Its --vo=gpu-next renderer uses Vulkan where a device answers and OpenGL through EGL otherwise. An external subtitle in a legacy encoding — CP1251, GBK, Shift-JIS — is detected by uchardet and converted, and libass breaks long subtitle lines where Unicode allows (through libunibreak), not only at spaces. The mpv-mpris plugin, loaded from /etc/mpv/scripts for every user, puts mpv on the session bus, so the media keys and the panel’s now-playing display control it.

Optical discs. mpv opens dvd://, bd:// and cdda://, and plays a DVD’s titles and chapters without its menus. mpd and cmus play audio CDs; cd-paranoia rips one with verified reads, and ffmpeg -f dvdvideo -i /dev/sr0 rips a DVD title with its chapters. GStreamer has cdiocddasrc, dvdreadsrc and rsndvdbin, and rsndvdbin plays DVD menus. CSS-encrypted DVDs and AACS-encrypted Blu-rays do not open, and Blu-ray Java menus do not run — see Known gaps.

Other media abilities:

Colour. lcms2 is the colour management engine, and the only thing on the host that can apply an ICC profile.

Time zones. The zone data is a compiled zoneinfo tree that musl reads directly, built in the “fat” format rather than “slim”. Applications in containers read the same tree, and a format they misread would make the host and its containers disagree about local time. The tree also carries tzdata.zi, the text form of the same release, which C++ std::chrono time zones read; without it libstdc++ falls back to the older copy compiled into it.

The zone is set with kdos-time (Date and time in Settings, under System) or kdos-power timezone <Area/City>, both of which need membership of wheel. kdos-powerd then rewrites two files together: /etc/localtime, the link programs follow into the zoneinfo tree, and /etc/profile.d/20-timezone.sh, which sets TZ for every login shell. A shell opened before the change keeps the old TZ until it is restarted. The same change writes the wireless regulatory country (see Firmware). On a machine where nothing has chosen a zone, rcS links /etc/localtime to UTC at boot. The clock itself is kept by chrony (35_chrony), and kdos-time shows whether it is synchronised.

Keeping it current

kdos cve                  # which installed versions have known vulnerabilities, offline
kdos cve --json           # the same, as JSON
kdos update fetch         # the newest signed ports tree, over the network, as /ports
kdos update check         # what the ports tree pins that is not installed, and what went wrong
kdos update apply         # fetch the sources and build it all here (see A/B root slots)
kdos app install <id>     # install a boxed application (to update one, see Applications below)

Vulnerabilities. kdos cve compares your installed versions against a copy of Alpine’s security database shipped in /usr/share/kdos/secdb.txt, with no network. It exits 1 when any package is behind a recorded fix and 0 otherwise. A package that database does not carry is counted as unknown, not clean, and the summary says how many there are. A library a program compiles into itself, such as the AWS-LC inside six Rust programs, is listed under bundled copies with its version, and checked like a package. Every run prints the database’s date, and warns when it is more than 180 days old.

Host packages are built from ports — the recipes in the KDOS source tree (see the glossary). What counts as “an update” is a newer ports tree, so kdos update needs one on the machine, and every image carries one at /ports: each port’s kpkgbuild, build.sh and patches, ports/shelves, ports/sources.idx, KDOS’s own programs under /ports/src and the book they render under /ports/docs, with no source archive. PORT_REPO in /etc/kpkg.conf names where kpkg looks: a space-separated list of recipe directories, by default the five of that tree, /ports/core /ports/src/system /ports/src/art /ports/src/desktop /ports/src/daemons. The KDOS source tree keeps its recipes in the same five directories — ports/core for upstream software, each port one shelf down in ports/core/<shelf>/<name>/, and src/system, src/art, src/desktop and src/daemons for KDOS’s own programs. A repository is searched for a port at <name>/ and one shelf down, never deeper; the shelves are not listed separately. For a complete checkout of the KDOS repository at DIR:

PORT_REPO="DIR/ports/core DIR/src/system DIR/src/art DIR/src/desktop DIR/src/daemons"

A stick built with KDOS_ISO_SOURCES=1 also carries the whole tree, fetched sources included, under /mnt/iso/sources (see kdos rebuild), whose five directories can be named the same way. Without a ports tree kdos update check says so and exits 2. The ports catalogue lists every port by shelf, with the phase that builds it, and How KDOS is built follows a port from recipe to installed package.

Command Does
kdos update fetch Downloads the latest release’s ports tree, checks its signature against /etc/kdos/keys/tree, and installs it as /ports. A tree older than the installed one is refused. --from URL or --from DIR (a stick holding kdos-ports.tar.xz and its .sig) reads it from elsewhere
kdos update check Lists installed packages that differ from the ports tree, and the last problem: no tree, no network, a fetch or a build that failed. --json prints JSON; --out PATH writes that JSON to a file; --fetch runs fetch first (the nightly timer uses both, for the panel’s update badge)
kdos update apply Downloads the sources of every out-of-date package, then builds and installs each one. --dry-run lists what would be built; --root DIR installs into another root
kdos update theme Re-runs the theme generators for your home directory after an artwork upgrade

Updates are built here, from source, and need the network. There is no binary package server. kdos update fetch downloads the newer recipe tree, and kdos update apply downloads every source it names, from the KDOS source archive first and from upstream second, each checked against the recipe’s sha256 = before use. It fetches them all before it compiles anything, so a machine that is offline finds out in minutes. Then it compiles every package that is behind on this machine: a few packages take minutes, a large set (a new compiler, a browser, Qt) takes hours. fetch and apply run as root. tree = URL or tree = DIR in /etc/kdos/update.conf sets where trees come from instead of the latest release. The panel’s update badge counts the packages behind, and turns to the warning colour, or to ! when nothing is behind, when the last fetch or apply failed or the machine has no tree; hovering it says which.

A/B root slots. On a machine installed with two root slots, kdos update apply installs into the inactive slot, which must be mounted, checks it with kpkg check (a missing file, or a changed one outside /etc, stops it; configuration edited under /etc is listed and does not), copies /ports into it, then installs that slot’s kernel with kdos-bootctl deploy and marks the slot to be tried on the next boot. The running system is not touched, so a bad update can be rolled back by booting the other slot. If the inactive slot is not mounted it refuses; --in-place updates the running root instead and gives up that rollback. See Boot and init for how a tried slot is confirmed, and Known gaps for what the updater does not do.

Applications have no separate update command. An application is a stack of images this machine built, so rebuilding it is the update: kdos app remove <id> and then kdos app install <id> rebuild the application’s own image. Under the shipped snapshot = auto the rebuild reads the same Debian snapshot as before, because the pulled debian:trixie-slim is kept; its packages move to a newer snapshot only when the catalogue names a later date or this machine pulls a newer debian:trixie-slim. Installing an application that is already installed builds nothing and is reported as a failure: every image is already there, kdos-box create refuses because the box exists, and the command ends with 1 of 1 did not install. See Applications.

Diagnosing

kdos doctor        # checks kernel, hardware, session, container and desktop set-up
kdos doctor --json # the same, as JSON
kdos status        # what this machine is and what it is running
kdos restarts      # which running processes still use code an upgrade replaced
kdos why <thing>   # what provides something, and why it is that way
kdos stutter       # why the desktop hiccuped, naming the application
kdos cve           # known vulnerabilities (kdos doctor --cve runs the same report)

Run kdos doctor first when something is wrong. Each line is ok, warn, or skip with a reason: much of what it checks cannot be answered in a virtual machine, and reporting those as ok would claim something never tested. It exits 0 when nothing warned and non-zero otherwise, in both the text and the JSON form, so a script can act on the status alone. --cve runs the vulnerability report instead (see Keeping it current).

The checks are grouped into sections:

Section Checks
Kernel Landlock, the kernel’s unprivileged file and network sandbox that kdos sandbox uses, is present and enabled; the initramfs carries this CPU’s microcode, and the running revision
Hardware The wireless regulatory database is signed and loaded, and the domain is not the world domain 00; Intel audio firmware (sof-firmware); GPU firmware; device nodes you cannot open, naming the group that owns each (see the udev rules); the boot medium
Boxes The application packs
Session WAYLAND_DISPLAY names a socket that exists; XDG_RUNTIME_DIR is set; kdos-comp, kdos-shell and the wlr desktop portal are running
Containers The mount namespace root is /, and /etc/subuid and /etc/subgid carry your range
Desktop The accent, the foot theme and the KDE palette (kdeglobals) are applied; Xwayland is reachable; the setuid bits on kdos-checkpass, kdos-resctl, newuidmap and newgidmap, and on dbus-daemon-launch-helper with group messagebus; /etc/subuid and /etc/subgid exist; kdos-powerd, kdos-energyd (or the absence of any RAPL energy counter) and kdos-oomd are running, and fcitx5 when it is installed; the compositor’s frame-timing and command sockets exist; kdos-keys and the screen-capture portal with kdos-portals.conf are installed; ~/.local/bin is on PATH
Security Run as root only: whether the kdos account still has the shipped password kdos

Without its setuid bit, kdos-checkpass cannot read /etc/shadow, so the lock screen refuses every password and locks you out of your own session, recoverable only from tty2; the fix it prints is chown root and chmod 4755. The shipped-password check needs to read /etc/shadow, so an ordinary user’s run has no Security section at all rather than a check that pretends to have looked; run sudo kdos doctor to get it, and expect the session and container checks of that run to warn, since root has no desktop session.

service list and service status <name> show each service’s state. After an upgrade, kdos restarts lists the running processes that still use code the upgrade replaced or removed; each keeps the old version until it restarts. See kdos restarts.

Logs

syslogd (from sysklogd) writes the system log. It reads the kernel’s messages itself, so there is no separate klogd. /etc/syslog.conf routes them:

File Holds
/var/log/messages Everything except authentication
/var/log/auth.log Logins, sudo and other authentication
/var/log/kern.log Kernel messages
/var/log/cron The cron facility; nothing on the image logs there by default

syslogd rotates each file itself at 1 MB and keeps five old copies, so no logrotate job exists. The limit matters on the live medium, where /var/log is in RAM and an unbounded log would take memory rather than disk. Emergency messages are also written to every logged-in terminal.

Three more places hold what the system log does not:

Where Holds
dmesg The kernel’s ring buffer, including messages from before syslogd started
/run/kdos-init.<name>.log The output of each service script at boot (see Services)
/run/kdos-fsck.log The boot-time filesystem check (see Storage)

A supervised daemon’s restarts and final exits ([KDOS] (name) Exited with code …) appear in its service’s boot log.

Copying and rebuilding the medium

sudo kdos clone /dev/sdb        # write this medium to another stick
kdos rebuild /mnt/disk/work     # run the build from a source tree on this machine

kdos clone

kdos clone copies the medium this system booted from onto another device, byte for byte, then reads it back to verify. The boot arrangement is whatever the medium already carries, so the copy boots exactly what the original boots. With no device named, it lists the devices it would accept.

Option Does
--source <path> Clone an image file or another device instead of the boot medium. Required on an installed system, which has no boot medium to copy
--extent Print the image’s exact length and stop
-y, --yes Do not ask before overwriting. Required when not run from a terminal
--no-probe Skip the counterfeit-capacity test (f3probe)
--no-verify Skip the read-back, and say what that costs

The length copied comes from the image’s own description, not from the device, so a 3 GB image on a 64 GB stick copies 3 GB. To confirm, you type the device’s name, not y. Before writing anything it refuses the medium the system booted from, any disk with a filesystem mounted, anything named in /etc/fstab, and any device smaller than the image.

Before writing, f3probe tests whether the stick really has the capacity it reports — a counterfeit stick silently wraps around and loses data. The verify drops the page cache before reading back: otherwise the kernel would return the bytes it just wrote from memory, not what the flash stored, and a counterfeit stick would pass.

kdos rebuild

kdos rebuild <work-directory> runs the KDOS build on a booted system, with no network. It looks for a source tree wherever KDOS_SOURCES points, then at /mnt/iso/sources, /kdos and the current directory, and accepts one that has script/kdosbuild.sh, ports/core, fs/etc and src/devtools/kdosbuild.

A stick built with make build KDOS_ISO_SOURCES=1 carries a copy under /mnt/iso/sources: the ports tree with every fetched source beside its recipe, src/, script/ and the fs/ overlay of system files. The Makefile and the Dockerfile are not copied. kdos rebuild copies the tree to the work directory and runs the build there, with KDOS_WORKSPACE naming the copy so that the host phases read fs/ from it rather than from /workspace. No rebuild from a medium has yet been run through every phase; see Why KDOS and The kdos command.

Option Does
--dry-run Run the checks and stop
--iso-only Rebuild only the image phase, 70_image, which produces the ISO

The checks are what make it safe to start. The work directory needs about 25 GB, and is refused if it is on a temporary or overlay filesystem: a live stick’s root is RAM, so a rebuild started there reports gigabytes free, fills memory and dies hours in. It also stops if a compiler, make, bash, tar or xz is missing, and warns that no ISO will be produced if mksquashfs, xorriso or mkfs.fat is.

Tuning for this machine

kdos march probe          # which instruction set levels this CPU has
kdos march run lz4        # build lz4 twice and measure
kdos march report         # the ledger: kept, reverted, unmeasurable

kdos march probe reports which of the x86-64 instruction set levels beyond the baseline (x86-64-v2, -v3 and -v4) this CPU supports. kdos march run <port> builds a port with and without -march= set to the highest of them, runs the benchmark named by the bench = line in that port’s recipe against both builds (the median of five runs by default), and keeps the faster flags only where the gain exceeds 3 per cent plus this machine’s own measured noise. A difference inside the noise is recorded as reverted, and a port with no bench = line is reported as unmeasurable. The decisions are kept in /var/lib/kdos/march.ledger, and kdos march report lists reverted ports as prominently as kept ones. On a CPU with only the baseline level there is nothing to measure, and it says so.

Backups

kdos-backup backs up with restic. Opened from the menu it is a window listing the snapshots in your repository, newest first: b starts a backup (it does not wait for it to finish) and r re-reads the repository. kdos-backup --once backs up without a window, waits for restic, and exits 0 on success, 1 on any failure and 2 on a bad argument; that is what the timer runs.

Set it up once:

  1. Create the repository: restic -r <repo> init. It asks for a password twice and does not store it anywhere.
  2. Put that password, and nothing else, in ~/.config/kdos/backup.pass, and make it private with chmod 600 ~/.config/kdos/backup.pass. kdos-backup refuses to run while the file is readable by anyone else.
  3. Edit ~/.config/kdos/backup.conf (one key = value per line, # starts a comment):

    Key Means Repeats
    repo Where the repository is No; the last line wins
    include A path to back up Up to eight; more are ignored
    exclude A path to leave out Up to eight; more are ignored
    password-file The password file, when it is not ~/.config/kdos/backup.pass No; the last line wins
  4. Run kdos-backup --once and check that it succeeds.
  5. To run it nightly, uncomment the backup line in ~/.config/kdos/timers.d/10-backup.timer.

--once stops with a message, before touching restic, if backup.conf names no repo or no include, or if the password file is missing or readable by others. An enabled timer with no configuration therefore does nothing harmful.

Restoring is a restic command, run by hand so its options are in front of you: restic -r <repo> restore <snapshot> --target <dir>. restic help restore lists the options.

Mail

Mail is four programs sharing one directory. ~/Mail is a Maildir (one file per message), and each program is pointed at it:

Program Does
mbsync Fetches: makes an IMAP mailbox and ~/Mail the same, in both directions
notmuch Indexes and searches what is there. It never fetches and never sends
aerc Reads and writes
msmtp Sends. /usr/sbin/sendmail and /usr/bin/sendmail both link to it

notmuch new is the one command you type. Its pre-new hook runs mbsync -a before the scan and its post-new hook removes the new tag from what arrived, so one command fetches and indexes.

The pre-new hook steps aside (exit 0) if mbsync is not installed, ~/.mbsyncrc is unreadable, or that file names no Channel — so an unconfigured machine still indexes. Otherwise a failed fetch fails notmuch new too, because indexing after a fetch that did not happen would report “0 new messages” and look like an empty inbox.

Setting up an account

Three files to fill in, all shipped with commented examples:

File Holds Mode
~/.mbsyncrc The incoming (IMAP) server 600
~/.msmtprc The outgoing (SMTP) server 600
~/.config/notmuch/default/config Your own address 644

The two mode-600 files hold, or fetch, a password: msmtp refuses to send through an .msmtprc that carries a password line and is readable by others. The image build sets both modes in the skeleton copied into each new home.

~/.config/aerc/accounts.conf is not shipped. aerc refuses to start with one that anyone else can read, and its own setup wizard writes it at mode 600 the first time you run aerc, so let the wizard do it.

Keep passwords out of all of them. pass is installed, and the templates show PassCmd "pass show …" (mbsync) and passwordeval "pass show …" (msmtp).

Rendering attachments

Five kinds of message part are shown as text. ~/.config/aerc/aerc.conf sends each to one script, kdos-part; plain text, delivery reports and attached messages go through aerc’s own colorize. A type not listed gets aerc’s No filter configured card, with its :open, :save and :pipe hints. The [filters] section replaces aerc’s whole default set rather than adding to it, which is why the plain-text rows are written out.

Part Shown as
HTML w3m -dump, which lays tables out on the grid, so marketing mail is readable
An invitation (text/calendar) aerc’s calendar filter, run through gawk. It only reads: a filter runs every time a message scrolls past, and one that imported invitations would accept every meeting you looked at
PDF mutool draw -F txt
An image chafa, as coloured symbols, the same in every terminal. With no filter for images, aerc would instead draw a JPEG or PNG through the terminal’s graphics protocol, which kdos-term supports
A .docx docx2txt

kdos-part saves the part to a temporary file first, because mutool opens a document by path and cannot read a pipe.

HTML is rendered where it cannot reach the network, so a tracking image has nowhere to report to. kdos-part first tries unshare --map-root-user --net, which runs w3m with no network at all; where the system refuses a new user namespace, it runs w3m behind an unreachable proxy instead. It tests whether the namespace works rather than whether the unshare program exists — aerc’s own HTML filter checks only the program, and where namespaces are refused it shows unshare: Operation not permitted in place of the message.

OAuth

XOAUTH2 sign-in works in the three programs that talk to a server: aerc speaks it itself, msmtp has it built in, and mbsync reaches it through cyrus-sasl and its libxoauth2.so plugin, which the image carries. pizauth obtains and refreshes the token. The plugin takes whatever SASL returns as the password and sends it as the token, so PassCmd is the only configuration mbsync needs:

IMAPAccount work
AuthMechs XOAUTH2
PassCmd   "pizauth show work"

OAUTHBEARER is not available to mbsync: the plugin implements XOAUTH2 only. A provider that offers only OAUTHBEARER can be read in aerc and sent to through msmtp, but has no local Maildir — and with it no notmuch index and no offline search.

Passwords and one-time codes

pass is the password store: one file per entry in a git repository, each encrypted with gpg. There is no database and no format to migrate; even without pass, gpg -d reads every entry.

For a site that asks for a six-digit code, save the otpauth:// link its QR code contains, then ask for a code when you need one:

pass otp insert site/example        # paste the otpauth:// link
pass otp site/example               # the code for right now
pass otp -c site/example            # and copy it to the clipboard

Nothing needs enabling: pass loads its installed extensions automatically. The code comes from oathtool, which also works on its own: oathtool --totp -b <secret>.

There is no one-time code for logging in to this machine. oath-toolkit’s PAM module is not built: a wrong line in the login stack locks everyone out, including whoever is trying to fix it. These codes are for other people’s websites.

Calendar and contacts

Calendars and contacts are plain files, like the mail. ~/.local/share/calendars holds calendars and ~/.local/share/contacts holds address books, each as a vdir: one directory per collection, one .ics or .vcf file per item. That can be searched with grep, compared with diff and backed up by copying. A file holding two events is not a valid vdir entry; use khal import to bring one in.

Program Does
khal Prints what is on: khal list today 7d, khal import invite.ics
ikhal The same calendar, full screen, to move around in. This is the Calendar menu entry
khard The address book: khard list, khard show, khard new
vdirsyncer Keeps a server’s collection and the local vdir the same, as mbsync does for mail. Unconfigured, vdirsyncer sync does nothing and exits 0

khal and khard refuse to start with no store configured, so the shipped configuration names empty ones, and the image creates both directories in each new home: khal reads every directory under ~/.local/share/calendars/, and khard reads the one address book ~/.local/share/contacts/personal. To add a calendar, make a directory under ~/.local/share/calendars and put .ics files in it; khal discovers it with no registration.

The panel’s calendar reads the same store: days with something on are marked, and today’s events are listed under the month. It asks khal when it opens and when you change month, and runs nothing between those times.

Syncing with a server is vdirsyncer, configured in ~/.config/vdirsyncer/config. A pair is two storages plus the rule for settling conflicts. There is no safe default: a wins discards the server’s edit and b wins discards yours, so the shipped example makes you choose. Run vdirsyncer discover once, then vdirsyncer sync whenever you like. Keep the password out of the file with password.fetch = ["command", "pass", "show", "…"].

khard list exits 1 on an empty address book and prints Found no contacts. That is its answer for “nothing matched”, not a failure, so a script using it must treat that exit as an empty result.

Finding a file by name

plocate searches an index instead of the disk, so a search by name across your whole home reads one index file instead of walking the tree, as fd does.

The index is per user. A timer in ~/.config/kdos/timers.d/ rebuilds it nightly at 03:05, covering only $HOME, and writes it to ~/.cache/kdos/plocate.db, so it can only contain paths you could already list. LOCATE_PATH, set in /etc/profile.d/40-plocate.sh, points plocate at it, and locate is the same program under its usual name. Run kdos-updatedb to rebuild it now.

updatedb is plocate’s, in /usr/sbin. findutils’ own locate and updatedb are not installed, because they read and write a different index format.

Upstream plocate is a setgid program reading one shared index for the whole machine; KDOS ships neither the setgid bit nor the shared index. The reasoning is in the security model.

Language

KDOS is in English, and only English. Every package is made without its translations: the message catalogs, the Qt translations, the translated KDE handbooks and the translated manual pages are removed as it is built (see the size and language policy), so there is no other language for a program to switch to. /etc/profile sets LANG=C.UTF-8; setting LANG or LC_MESSAGES to another language changes no program’s menus or messages.

Writing in another language is unaffected. The fonts cover every script, the keyboard layout is yours to choose, and the input methods below type Chinese, Japanese and Korean. What is missing is listed in Known gaps.

Input methods

fcitx5 is the input method engine, for typing languages that need more than a keyboard layout: Chinese (pinyin, shuangpin and the table methods), Japanese and Korean. The session starts it by name if it is installed. It talks to kdos-comp through input-method-v2, and is configured through text files under ~/.config/fcitx5/ — its graphical configuration tool is Qt and is not shipped. Ctrl+Space switches method; that is fcitx5’s own binding, and the compositor does not claim it.

The candidate window is KDOS’s. kdos-ime starts just before fcitx5 and owns org.kde.impanel, so the text being composed and the list of candidates are drawn like every other KDOS surface. fcitx5 prefers that panel over its own as soon as it appears; without kdos-ime, fcitx5 draws its own window.

Cloud pinyin is compiled out: it would send what you type to a remote service.

See also


Part II — Using KDOS, chapter 10. Previous: 9. Theming · Contents · Next: 11. Accessibility