Handbook

01

Overview

krypt/sh is a source-based Linux distribution built on a pure LLVM/musl toolchain. The entire system is compiled with clang/clang++ against musl libc — there is no GCC and no glibc anywhere in the base system.

Requirements

Architecture: x86_64 only.
Firmware: UEFI only — legacy BIOS is not supported.
RAM: 16 GB recommended for building packages from source.
Disk: 20 GB minimum for a base system. More for a full ports tree with sources.

Toolchain

The core toolchain consists of LLVM (clang, lld, llvm-tools), musl (libc), compiler-rt (builtins and sanitizers), libc++ (C++ standard library), and libunwind (stack unwinding). GCC compatibility shims are provided where needed — libgcc_s.so symlinks to libunwind and libstdc++.so symlinks to libc++ — so software expecting GNU toolchain components works without modification.

LLVM GPU targets

The llvm-toolchain port is built with AMDGPU as the default GPU backend target. This is required by Mesa for AMD Radeon shader compilation (radeonsi, radv) and by the LLVM-based software rasterizer (llvmpipe). If your system uses different hardware, you can change the targets and rebuild:

customizing LLVM targets
# Edit the llvm-toolchain MAKEPKG
# cd /ports/core/llvm-toolchain
# vi MAKEPKG

# Find the -DLLVM_TARGETS_TO_BUILD line and adjust:
# AMDGPU — AMD Radeon (required for radeonsi/radv)
# NVPTX — NVIDIA (compute/CUDA)
# X86 — always required

# Rebuild and upgrade
# mkpkg -F
# pkg.add -u llvm-toolchain#*.pkg.tar.gz

# Then rebuild Mesa to pick up the new backend
# cd /usr/ports/xorg/mesa
# mkpkg -F
# pkg.add -u mesa#*.pkg.tar.gz
Intel GPUs do not need an LLVM backend target — Intel's Mesa drivers (iris, anv) use their own compiler stack. If you run Intel-only graphics, you can remove AMDGPU entirely to reduce the LLVM build time and binary size.

Installation model

krypt/sh uses a hybrid installation model. During initial installation, pre-built binary packages are fetched over the network and installed with pkg.add. Once the system is running, you transition to source-based updates and customization through the ports system — syncing port trees, building packages with mkpkg, and managing dependencies with tux.

Getting help

Join #krypt/sh on Libera.Chat for support, questions, and discussion.

02

Filesystem Layout

krypt/sh uses a flat library layout — there is no lib32, lib64, or multilib. Libraries live in /lib and /usr/lib, period. This reflects the single-arch, single-libc nature of the system.

The filesystem splits into two install prefixes. The ports tree mirrors this split directly:

# Root prefix — core system (packages from /ports/core) / ├── bin/ # essential binaries ├── boot/ │ └── efi/ # EFI system partition mount ├── etc/ │ ├── boot.d/ # boot-time setup scripts │ ├── ports/ # ports sync configuration │ ├── profile.d/ # shell environment scripts │ ├── ssl/ # certificates │ └── sv/ # runit service definitions ├── home/ ├── include/ # core headers ├── lib/ # core libraries (no lib32, no lib64) │ └── firmware/ ├── media/ ├── mnt/ ├── ports/ │ └── core/ # core ports → install to / ├── root/ # root home directory ├── sbin/ # essential system binaries ├── service/ # supervised services (symlinks → /etc/sv) ├── src/ ├── usr/ │ ├── bin/ # user binaries │ ├── include/ # user headers │ ├── lib/ # user libraries + pkgconfig │ ├── ports/ # non-core ports → install to /usr │ │ ├── devel/ # development tools │ │ ├── libs/ # shared libraries │ │ ├── opt/ # optional apps │ │ ├── perl/ # perl modules │ │ ├── python/ # python modules │ │ └── xorg/ # graphical stack │ └── share/ # man pages, data, etc. └── var/ └── lib/ ├── pkg/ # package database └── tux/ # dependency database

The rule is simple: packages in /ports/core install into the root prefix (/bin, /sbin, /lib). Packages in /usr/ports/* install into the /usr prefix (/usr/bin, /usr/lib). This is set by --prefix=/ vs --prefix=/usr in each port's build function.

The setup-filesystem script creates this entire skeleton, including /var/lib/pkg/db (the empty package database), default /etc/passwd, /etc/group, /etc/profile, and other base configuration files. It must be run before any packages are installed.

XDG runtime directories

krypt/sh sets up XDG base directories through two scripts rather than relying on elogind, dumb_runtime_dir, or session managers. This means Wayland compositors work out of the box with no extra session infrastructure.

/etc/profile.d/xdg.sh exports the environment variables on login:

/etc/profile.d/xdg.sh
#!/bin/sh
export XDG_DATA_HOME="$HOME/.local/share"
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_CACHE_HOME="$HOME/.cache"
export XDG_RUNTIME_DIR=/run/user/$(id -u)

/etc/boot.d/xdg-runtime creates the runtime directories at boot for each user:

/etc/boot.d/xdg-runtime
#!/bin/sh
# Create XDG_RUNTIME_DIR for root and regular users
for user in root $(awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd); do
    uid=$(id -u "$user")
    dir=/run/user/$uid
    mkdir -p "$dir"
    chmod 0700 "$dir"
    chown "$user":"$user" "$dir"
done

No PAM, no elogind

krypt/sh does not use Linux-PAM or elogind. Authentication is handled by traditional shadow utilities. Session setup — XDG runtime directories, seat management via seatd — is done with minimal, purpose-built scripts that interface directly with the system rather than through abstraction layers.

03

Installation

krypt/sh ships as a UEFI-bootable disk image (disk.img). Write it to a USB drive and boot it — you get a live shell with networking, package tools, and everything needed to install the system. Type setup-help at any time for a quick reference.

Note: The disk image is UEFI only. krypt/sh does not ship GRUB or syslinux. If you install to a spare partition on an existing system, you can chainload it from your existing bootloader as long as /etc/fstab is correct.

Write the image

Download from krypt.sh/download. For help, join #krypt/sh on Libera.Chat.

write image
# verify checksum
$ sha256sum disk.img
73457cf67bac1b7012eb0f2d67170e4157b7a6d7e17169cdbbb1c984003c4dba disk.img

# write to USB
$ dd if=disk.img of=/dev/sdX bs=4M status=progress

Boot the USB drive from your UEFI firmware. You will land at a root shell.

Network

network
# Ethernet starts automatically

# WiFi (built-in Ralink/Realtek)
# wpa_passphrase "SSID" "password" > /tmp/wpa.conf
# wpa_supplicant -B -i wlan0 -c /tmp/wpa.conf
# dhcpcd wlan0

Partition and mount

partition
# fdisk /dev/sdX

# Create a GPT label, an EFI system partition, and a root partition

# mkfs.fat -F32 /dev/sdX1
# mkfs.ext4 /dev/sdX2

# mount /dev/sdX2 /mnt
# mkdir -p /mnt/boot/efi
# mount /dev/sdX1 /mnt/boot/efi

Install packages

install
# Create the filesystem skeleton
# setup-filesystem /mnt

# Install core system + build toolchain
# install --target /mnt core devel

The install script fetches pre-built binary packages from rsync://krypt.sh/bulk and installs them with pkg.add.

core — the base system (~57 packages):

acl attr bash bzip2 coreutils dash-shell dhcpcd diffutils dosfstools e2fsprogs efibootmgr efivar file findutils gmp grep gzip iproute2 kmod libcap libelf libmnl libnl libressl libseccomp lz4 mawk mkpkg musl ncurses openresolv openvi patch pcre2 pkgtools popt procps readline revdep rsync runit sed shadow signify-openbsd tar tux tzdata util-linux wget wpa-supplicant xxhash xz zlib zstd

devel — LLVM toolchain and build tools:

llvm-toolchain=(llvm clang compiler-rt libunwind lld libcxx) rust gcc-compat cmake python3 make meson samurai pkgconf autoconf automake libtool m4 perl

Generate fstab and chroot

chroot
# genfstab /mnt > /mnt/etc/fstab
# cat /mnt/etc/fstab

# enter-chroot /mnt

Configure the system

inside chroot
# passwd root
# echo myhostname > /etc/hostname
# ln -sf /usr/share/zoneinfo/Region/City /etc/localtime

Boot with EFISTUB

krypt/sh boots the kernel directly via UEFI — no bootloader required. Copy the kernel to the EFI system partition and create a boot entry:

efistub
# cp /boot/vmlinuz-X.Y.Z /boot/efi/vmlinuz-X.Y.Z.efi

# efibootmgr -c -d /dev/sdX -p 1 \
   -L 'krypt/sh X.Y.Z' \
   -l '\vmlinuz-X.Y.Z.efi' \
   -u 'root=/dev/sdXn rw quiet net.ifnames=0'
Existing bootloader: If you installed krypt/sh to a spare partition alongside another distribution, you can skip EFISTUB and add an entry to your existing GRUB configuration instead. As long as /etc/fstab is correct, grub-mkconfig or os-prober from the host system can detect and boot it.
04

Package Management

krypt/sh has three layers of package management: low-level tools (pkg.add, pkg.del, pkg.info) that operate on binary archives and the package database, the build tool (mkpkg) that compiles ports into packages, and the high-level manager (tux) that resolves dependencies and orchestrates everything.

Binary package tools

CommandDescription
pkg.add pkg.tar.gzInstall a binary package archive
pkg.add -u pkg.tar.gzUpgrade an installed package
pkg.del nameRemove an installed package
pkg.info -iList all installed packages
pkg.info -l nameList files owned by a package
pkg.info -o patternFind which package owns a file

Configuration files under /etc are preserved on upgrade by default. See pkg.add.conf(5) for details.

The tux package manager

CommandDescription
tux install pkgInstall a package and all dependencies
tux update pkgUpdate a package to the ports tree version
tux remove pkgRemove an installed package
tux sysupUpdate all outdated packages
tux diffShow version differences (installed vs ports)
tux depends pkgShow the dependency tree
tux rdepends pkgShow reverse dependencies
tux autoremoveRemove orphaned dependency packages
tux lock pkgPrevent a package from being updated

tux searches port directories in the order defined in /etc/tux.conf. When multiple ports provide the same package name, the last match wins. See tux(8) and tux.conf(5).

Typical workflow

daily usage
# Sync the ports tree
# ports -u

# See what's outdated
# tux diff

# Update everything
# tux sysup

# Install something new
# tux install firefox

# Clean up
# tux autoremove

Reverse dependency checking (revdep)

When a shared library is updated, binaries linked against the old version will break — the dynamic linker cannot find the previous .so name. The revdep tool scans every installed binary and reports packages with broken library links.

Here is a real-world example: upgrading libvpx and nettle bumps their soname versions. Any package linked against the old libraries will fail to start:

broken library example
$ firefox
XPCOMGlueLoad error for file /usr/lib/firefox/libxul.so:
Error loading shared library libvpx.so.11: No such file or directory
Couldn't load XPCOM.

Instead of guessing which packages are affected, run revdep to find them all:

revdep workflow
# 1. Update the libraries
# pkg.add -u libvpx#1.15.1-1.pkg.tar.gz
# pkg.add -u nettle#3.10.1-1.pkg.tar.gz

# 2. Find every package with broken links
# revdep
firefox
thunderbird
ffmpeg
mpv
rdfind

# 3. Force-rebuild and upgrade each one
# cd /usr/ports/opt/firefox && mkpkg -F && pkg.add -u firefox#*.pkg.tar.gz
# cd /usr/ports/opt/thunderbird && mkpkg -F && pkg.add -u thunderbird#*.pkg.tar.gz
# cd /usr/ports/opt/ffmpeg && mkpkg -F && pkg.add -u ffmpeg#*.pkg.tar.gz
# cd /usr/ports/opt/mpv && mkpkg -F && pkg.add -u mpv#*.pkg.tar.gz
# cd /usr/ports/opt/rdfind && mkpkg -F && pkg.add -u rdfind#*.pkg.tar.gz

# 4. Verify — should produce no output
# revdep

Note the use of mkpkg -F (force) rather than mkpkg -u — the package version has not changed, only its library dependencies have. Without -F, mkpkg would see the same version string and skip the build.

Always run revdep after upgrading core libraries such as libressl, libvpx, nettle, icu, libpng, or anything in /lib. A single library bump can silently break dozens of packages — revdep catches them all in one pass.
05

Building Ports

Every package in krypt/sh is defined by a MAKEPKG file — a shell-like recipe that describes how to fetch, build, and package software. See MAKEPKG(5) for the full specification.

Syncing ports

ports
# Sync all port collections
# ports -u

# Sync a specific collection
# ports -u core

# List all available ports
# ports -l

Port collections are configured via /etc/ports/*.rsync files and synced from rsync.krypt.sh. See ports(8).

Building a package

mkpkg
# Navigate to a port
# cd /usr/ports/opt/nftables

# Install build dependencies
# tux prepare

# Download and build
# mkpkg -d

# Build and install directly
# mkpkg -d -i

# Build and upgrade an existing package
# mkpkg -d -u

MAKEPKG anatomy

MAKEPKG example
#!/bin/mkpkg
# *** krypt/sh ***
# description: Shows the full path of shell commands
# url: https://www.gnu.org/software/which/

name=which
version=2.21
release=1
depends=()
source=(https://ftp.gnu.org/gnu/$name/$name-$version.tar.gz)
sha256sums=(abc123...)

build() {
    cd $name-$version
    ./configure --prefix=/
    make
    make DESTDIR=$PKG install
}

Key arrays: depends=() for runtime dependencies, makedeps=() for build-time dependencies, groups=() for declarative user/group creation, services=() for runit service enablement, and permissions=() for file ownership. Note: krypt/sh uses makedeps=(), not makedepends=().

Build configuration

Compiler flags and build settings live in /etc/mkpkg.conf. Only the shell(), build(), and post_build() hooks source this file — extract() and patch() do not need build flags so they skip it. See mkpkg.conf(5).

06

Services & Init

krypt/sh uses runit as its init system with a vanilla setup. The supervised service directory is /service — not /var/service like some other distributions. Service definitions live in /etc/sv/ and are activated by symlinking into /service/.

That said, krypt/sh is not bound to runit. The ports tree ships it as the default, but you are free to build and use the init system of your choice — the system has no hard dependency on any specific init.

Managing services

runit
# Enable a service
# ln -s /etc/sv/dhcpcd /service/

# Check status
# sv status dhcpcd

# Stop / start / restart
# sv stop dhcpcd
# sv start dhcpcd
# sv restart dhcpcd

# Disable a service
# rm /service/dhcpcd

# List enabled services
# ls /service/

# List available services
# ls /etc/sv/

Service file structure

Each service directory under /etc/sv/ contains a run script and optionally a log/run for logging via svlogd. Packages that ship runit services declare them in the services=() array of their MAKEPKG file, and pkg.add creates the /service symlink automatically.

Example: simple daemon

A typical service run script for a daemon like dhcpcd:

/etc/sv/dhcpcd/run
#!/bin/sh
exec 2>&1
exec dhcpcd -B -M

The corresponding log script sends output to svlogd:

/etc/sv/dhcpcd/log/run
#!/bin/sh
mkdir -p /var/log/dhcpcd
exec svlogd -tt /var/log/dhcpcd

Example: nftables (one-shot)

Services that load configuration and exit (like a firewall) use a finish script and sv once:

/etc/sv/nftables/run
#!/bin/sh
exec 2>&1
nft -f /etc/nftables.conf
exec pause

Example: seatd with cleanup

Services that create runtime files can use a finish script for cleanup:

/etc/sv/seatd/run
#!/bin/sh
exec 2>&1
exec seatd -g seat
/etc/sv/seatd/finish
#!/bin/sh
rm -f /run/seatd.sock
Key points: All run, finish, and log/run scripts must be executable (chmod 755). Daemons should run in the foreground — runit supervises the process directly. Use exec so the daemon replaces the shell process.
07

Kernel

The kernel is installed as part of the linux group during installation. The kernel image lands at /boot/vmlinuz-X.Y.Z and modules go into /lib/modules/X.Y.Z.

Updating the kernel

After building or installing a new kernel version, copy it to the EFI system partition and create a new boot entry:

kernel update
# cp /boot/vmlinuz-X.Y.Z /boot/efi/vmlinuz-X.Y.Z.efi

# efibootmgr -c -d /dev/sdX -p 1 \
   -L 'krypt/sh X.Y.Z' \
   -l '\vmlinuz-X.Y.Z.efi' \
   -u 'root=/dev/sdXn rw quiet net.ifnames=0'

# Optionally remove old entries
# efibootmgr
# efibootmgr -b XXXX -B

Since krypt/sh uses EFISTUB directly, each kernel version gets its own EFI boot entry. You can keep multiple versions and select between them from the UEFI firmware boot menu — no bootloader configuration needed.

Building a custom kernel

krypt/sh ships a default kernel via the linux port, but building a custom kernel is straightforward. Download the sources, copy the running config as a starting point, and build with LLVM:

custom kernel
# cd /usr/src
# tar xf linux-X.Y.Z.tar.xz
# cd linux-X.Y.Z

# Start from the running config
# zcat /proc/config.gz > .config
# make LLVM=1 olddefconfig

# Customize
# make LLVM=1 menuconfig

# Build
# make LLVM=1 -j$(nproc)
# make LLVM=1 modules_install
# cp arch/x86/boot/bzImage /boot/vmlinuz-X.Y.Z
LLVM=1 tells the kernel build system to use clang, lld, and LLVM tools for the entire build. This is the only supported method on krypt/sh since GCC is not available.

Loading modules

krypt/sh boots without an initramfs — there is no dracut, mkinitramfs, or initial ramdisk of any kind. The kernel mounts the root filesystem directly. This means any kernel modules your hardware needs at boot must either be compiled into the kernel (=y) or loaded early from /etc/rc.modules.

The /etc/rc.modules script is executed at boot and is the place to modprobe drivers that are built as modules (=m) rather than built-in. A typical setup:

/etc/rc.modules
#!/bin/sh
modprobe iwlmvm
modprobe iwlwifi
modprobe amdgpu
Must be executable. The script will not run unless you set the execute bit: chmod +x /etc/rc.modules. If you add new hardware or enable new module-only drivers, add the corresponding modprobe line here.

Since there is no initramfs, drivers that are critical for reaching the root filesystem — your storage controller, filesystem driver, and root disk encryption if used — must be built into the kernel with =y, not as modules. If they are only modules, the kernel cannot mount root and will panic. A safe rule of thumb:

Driver typeRecommendation
NVMe / AHCI / SCSIBuilt-in (=y) — needed to mount root
ext4 / XFS / btrfsBuilt-in (=y) — your root filesystem
dm-crypt / LUKSBuilt-in (=y) — if using encryption
GPU (amdgpu, i915)Module (=m) — loaded via rc.modules
WiFi (iwlwifi, ath)Module (=m) — loaded via rc.modules
BluetoothModule (=m) — loaded via rc.modules
Sound (snd-hda)Module (=m) — loaded via rc.modules
USB HID / inputBuilt-in (=y) — keyboard/mouse at boot

To check what modules are currently loaded on a running system, use lsmod. To see which module handles a specific device, use lspci -k or lsusb -t.

EFISTUB configuration

krypt/sh boots the kernel directly from UEFI firmware without a bootloader. The following options must be enabled for EFISTUB to work:

.config — EFISTUB
# Required for EFISTUB boot
CONFIG_EFI=y
CONFIG_EFI_STUB=y
CONFIG_EFI_MIXED=y

# EFI filesystem access
CONFIG_EFIVAR_FS=y
CONFIG_EFI_VARS=y

# EFI framebuffer (console before GPU driver loads)
CONFIG_FB_EFI=y
CONFIG_FRAMEBUFFER_CONSOLE=y

# GPT partition table support
CONFIG_EFI_PARTITION=y

Without CONFIG_EFI_STUB=y, the kernel image cannot be loaded directly by the UEFI firmware and you would need a bootloader like GRUB or systemd-boot. The EFI_MIXED option allows a 64-bit kernel to boot on 32-bit EFI firmware, which some older machines have.

Security & sandboxing

These options enable the kernel features that sandboxing tools, browsers (Firefox, Chromium), and container runtimes depend on. They are strongly recommended for any system running untrusted code:

.config — security & sandboxing
# Seccomp — syscall filtering (required by browsers, containers)
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y

# User namespaces — unprivileged sandboxing (Flatpak, Chromium)
CONFIG_USER_NS=y

# PID, network, mount namespaces — process isolation
CONFIG_PID_NS=y
CONFIG_NET_NS=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y

# Landlock — unprivileged filesystem sandboxing (kernel 5.13+)
CONFIG_SECURITY_LANDLOCK=y

# Yama — ptrace scope restrictions
CONFIG_SECURITY_YAMA=y

# Kernel hardening
CONFIG_RANDOMIZE_BASE=y
CONFIG_RANDOMIZE_MEMORY=y
CONFIG_STACKPROTECTOR=y
CONFIG_STACKPROTECTOR_STRONG=y
CONFIG_HARDENED_USERCOPY=y
CONFIG_FORTIFY_SOURCE=y
Browsers need seccomp. Both Firefox and Chromium will refuse to start or will run without sandboxing if CONFIG_SECCOMP_FILTER=y is missing. This is the single most important security option on a desktop system.

Cgroups & containers

If you plan to run containers, lightweight VMs, or resource-limited services, enable cgroups v2 and the relevant controllers:

.config — cgroups
# Cgroups v2 (unified hierarchy)
CONFIG_CGROUPS=y
CONFIG_CGROUP_V2=y

# Resource controllers
CONFIG_MEMCG=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_BPF=y

# Overlay filesystem (container image layers)
CONFIG_OVERLAY_FS=y

# Virtual ethernet pairs (container networking)
CONFIG_VETH=y
CONFIG_BRIDGE=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y

To boot with cgroups v2 as default, add cgroup_no_v1=all systemd.unified_cgroup_hierarchy=1 to your kernel command line in the efibootmgr -u parameters. Since krypt/sh uses runit (not systemd), only the cgroup_no_v1=all part is strictly needed.

08

Networking

krypt/sh uses simple, traditional networking tools — no NetworkManager, no systemd-networkd. Network interfaces are configured through dhcpcd for DHCP, manual ip commands or boot scripts for static addressing, and wpa_supplicant for WiFi. All network services run under runit supervision.

Static IP

For a static IP configuration, create a boot script that brings up the interface. This runs at boot via the /etc/boot.d/ mechanism:

/etc/boot.d/network
#!/bin/sh
ip addr add 192.168.1.100/24 dev eth0
ip link set eth0 up
ip route add default via 192.168.1.1

Make it executable with chmod +x /etc/boot.d/network. Note that krypt/sh uses net.ifnames=0 on the kernel command line by default, so interfaces use classic names like eth0 and wlan0 rather than predictable names like enp3s0.

DHCP

dhcpcd handles DHCP for both wired and wireless interfaces. Enable it as a runit service:

dhcpcd service
# Enable the dhcpcd service
# ln -s /etc/sv/dhcpcd /service/

# Check it's running
# sv status dhcpcd

# Or run manually for a specific interface
# dhcpcd eth0

By default, dhcpcd will configure all available interfaces. To restrict it to a specific interface, edit /etc/dhcpcd.conf and add allowinterfaces eth0.

WiFi (wpa_supplicant)

WiFi is handled by wpa_supplicant. For a persistent setup that survives reboots, create a configuration file and a runit service.

First, generate the configuration:

wpa_supplicant setup
# Generate a config with your network credentials
# wpa_passphrase "MyNetwork" "MyPassword" > /etc/wpa_supplicant.conf

# Verify it looks right
# cat /etc/wpa_supplicant.conf

Then create a runit service to start it at boot:

/etc/sv/wpa_supplicant/run
#!/bin/sh
exec 2>&1
exec wpa_supplicant -i wlan0 -c /etc/wpa_supplicant.conf
enable service
# chmod 755 /etc/sv/wpa_supplicant/run
# ln -s /etc/sv/wpa_supplicant /service/

With both wpa_supplicant and dhcpcd running as services, WiFi will connect and obtain an address automatically at boot. For multiple networks, add additional network={} blocks to /etc/wpa_supplicant.confwpa_supplicant will choose the best available one.

WiFi firmware: Most WiFi chipsets require firmware files in /lib/firmware. The linux-firmware package provides these. Intel chips (iwlwifi) also need the driver loaded via /etc/rc.modules — see the Loading modules section.

DNS resolution

DNS is configured in /etc/resolv.conf. When using dhcpcd, this file is updated automatically. For static setups, set it manually:

/etc/resolv.conf
nameserver 1.1.1.1
nameserver 9.9.9.9

If dhcpcd overwrites your manual settings, you can prevent this by adding nohook resolv.conf to /etc/dhcpcd.conf, or simply make the file immutable with chattr +i /etc/resolv.conf.

Firewall (nftables)

krypt/sh uses nftables for firewalling — not iptables. A sensible default configuration that blocks incoming connections while allowing outbound traffic:

/etc/nftables.conf
#!/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # Allow established/related connections
        ct state established,related accept

        # Allow loopback
        iif lo accept

        # Allow ICMP (ping)
        meta l4proto icmp accept

        # Allow SSH
        tcp dport 22 accept

        # Drop everything else
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

The nftables service loads this configuration at boot as a one-shot service (see Services & Init):

enable firewall
# Enable the nftables service
# ln -s /etc/sv/nftables /service/

# Verify rules are loaded
# nft list ruleset

# Reload after editing nftables.conf
# nft -f /etc/nftables.conf
Adding services: To expose a web server, add tcp dport { 80, 443 } accept to the input chain. For more complex setups, nftables supports sets, maps, rate limiting, and logging — see man nft.
09

Users & Permissions

krypt/sh does not use Linux-PAM. User management is handled by traditional shadow utilities — useradd, usermod, userdel, passwd, and groupadd. Authentication goes through /etc/shadow directly.

Adding users

Create a regular user with a home directory and login shell:

user management
# Create a user with home directory
# useradd -m -s /bin/bash username

# Set password
# passwd username

# Add to supplementary groups
# usermod -aG wheel,audio,video,seat username

# Verify
# id username

The -m flag creates /home/username. The XDG runtime directory at /run/user/UID is created at boot by the /etc/boot.d/xdg-runtime script for any user with a UID ≥ 1000 (see Filesystem Layout).

Groups & privileges

Group membership controls hardware and service access. The important groups on an krypt/sh system:

GroupPurpose
wheelCan use su / doas for root access
audioDirect access to ALSA sound devices
videoDirect access to GPU and framebuffer devices
seatAccess to seatd for Wayland compositors
inputAccess to /dev/input devices
kvmAccess to /dev/kvm for virtualization
diskRaw disk device access (use with caution)

Packages that need system groups declare them in the groups=() array of their MAKEPKG file. pkg.add creates the groups automatically during installation.

Privilege escalation

Without PAM, sudo does not work out of the box. krypt/sh provides two alternatives:

su — the traditional tool. Members of the wheel group can switch to root:

su
# Switch to root (enter root password)
$ su -

# Run a single command as root
$ su -c 'tux sysup'

doas — a minimal sudo replacement from OpenBSD. Install it from the ports tree and create a configuration:

doas setup
# tux install doas

# Allow wheel group members to run commands as root
# echo 'permit :wheel' > /etc/doas.conf

# Optionally persist authentication for 5 minutes
# echo 'permit persist :wheel' > /etc/doas.conf

# Set restrictive permissions
# chmod 600 /etc/doas.conf
Why not sudo? The standard sudo package has a hard dependency on PAM for session and authentication handling. Since krypt/sh does not ship PAM, doas is the recommended alternative — it works directly with /etc/shadow and has a much smaller attack surface.

Login shell & environment

The default login shell is set per-user in /etc/passwd and can be changed with usermod -s /bin/bash username. Environment setup happens through /etc/profile and drop-in scripts in /etc/profile.d/.

krypt/sh ships two profile scripts by default: /etc/profile.d/xdg.sh (XDG base directories) and /etc/profile.d/path.sh (PATH additions). You can add your own by dropping executable .sh files in /etc/profile.d/ — they are sourced in alphabetical order on login.

10

Graphics & Wayland

krypt/sh is a pure Wayland system — X11 is not part of the core. The graphics stack consists of kernel DRM drivers, Mesa for userspace OpenGL/Vulkan, seatd for seat management, and a Wayland compositor of your choice.

The graphics stack

The layers involved in getting pixels on screen:

LayerComponent
KernelDRM driver (amdgpu, i915, nouveau)
Userspace driverMesa (radeonsi, iris, nouveau, llvmpipe)
Seat managementseatd — grants GPU/input access
Wayland protocolwayland, wayland-protocols
Compositorlabwc, sway, or other wlroots-based
ToolkitGTK4, Qt6 (with Wayland backend)

There is no display manager — you launch the compositor directly from a TTY login. The compositor handles window management, input, and output all in one process.

Seat management (seatd)

seatd is a minimal seat management daemon that replaces elogind and systemd-logind. It grants unprivileged users access to GPU, input, and DRM devices through the seat group.

seatd setup
# Ensure your user is in the seat group
# usermod -aG seat username

# Enable the seatd service
# ln -s /etc/sv/seatd /service/

# Verify it's running
# sv status seatd

The seatd runit service runs with -g seat, restricting access to members of the seat group. The socket at /run/seatd.sock is cleaned up by the service's finish script.

seatd must be running before the compositor starts. If you see errors about Failed to connect to seat or libseat: Could not connect, check that seatd is active and your user is in the seat group.

GPU drivers

GPU drivers are kernel modules loaded via /etc/rc.modules (see Loading modules). Mesa provides the userspace OpenGL and Vulkan implementations:

GPUKernel moduleMesa driver
AMD Radeon (GCN+)amdgpuradeonsi (GL), radv (Vulkan)
Intel (Gen 8+)i915iris (GL), anv (Vulkan)
Intel (Arc)xe / i915iris (GL), anv (Vulkan)
NVIDIA (open)nouveaunouveau (GL, limited)
Softwarellvmpipe (CPU fallback)

Add the appropriate modprobe line to /etc/rc.modules. AMD and Intel GPUs work well with the open-source stack. For NVIDIA, the proprietary driver is not available on musl — nouveau provides basic support but lacks reclocking on most cards.

Hybrid graphics (AMD + Intel): On laptops with both a discrete and integrated GPU, both kernel modules should be loaded. The compositor will typically use the discrete GPU. Power management for the discrete GPU can be controlled via /sys/bus/pci/devices/.../power/control.

Compositors (labwc / sway)

krypt/sh ships labwc and sway in the ports tree — both are wlroots-based Wayland compositors. Launch them directly from a TTY after logging in:

launching a compositor
# Option A: labwc (stacking compositor, openbox-like)
$ labwc

# Option B: sway (tiling compositor, i3-like)
$ sway

labwc is a stacking window manager similar to Openbox. Configuration lives in ~/.config/labwc/ with files for rc.xml (keybinds, window rules), menu.xml (right-click menu), and autostart (programs to launch). It is a good choice if you prefer a traditional floating window experience.

sway is an i3-compatible tiling compositor. Configuration lives in ~/.config/sway/config. If you are familiar with i3, the transition is seamless — most configuration directives work identically.

Both compositors need the XDG_RUNTIME_DIR environment variable set and seatd running. If you followed the installation guide, both are already in place.

Auto-start from login: To launch the compositor automatically when you log in on TTY1, add this to the end of your ~/.profile or ~/.bash_profile:

[ "$(tty)" = "/dev/tty1" ] && exec labwc

Troubleshooting

Common graphics issues and how to diagnose them:

SymptomCause / fix
Permission denied on /dev/driUser not in video and seat groups
Failed to connect to seatseatd not running — check sv status seatd
No XDG_RUNTIME_DIRMissing /etc/profile.d/xdg.sh or /etc/boot.d/xdg-runtime
Black screen on launchGPU module not loaded — check /etc/rc.modules
Software rendering onlyMesa not finding GPU — run LIBGL_DEBUG=verbose glxinfo
Compositor crashes on exitNormal if seatd.sock isn't cleaned — the finish script handles it

For debugging, run the compositor with verbose output: WLR_RENDERER=vulkan sway or WAYLAND_DEBUG=1 labwc. Check dmesg for DRM/GPU errors and lsmod to confirm the right driver is loaded.

11

Encryption (LUKS2)

krypt/sh uses LUKS2 via cryptsetup for full disk encryption. The stack is: cryptsetupdevice-mapperargon2 (key derivation). No initramfs — the kernel must have dm-crypt built in.

Setting up LUKS2

luks2 setup
# Format a partition with LUKS2 + argon2id KDF
# cryptsetup luksFormat --type luks2 --pbkdf argon2id /dev/nvme0n1p7

# Open the container
# cryptsetup open /dev/nvme0n1p7 cryptroot

# Format the plaintext device
# mkfs.ext4 /dev/mapper/cryptroot

# Mount and install as usual
# mount /dev/mapper/cryptroot /mnt

Opening at boot

Without an initramfs, LUKS volumes must be opened by a boot script before the root filesystem is mounted — or, for non-root volumes, by a runit service early in the boot sequence.

/etc/boot.d/luks
#!/bin/sh
cryptsetup open /dev/nvme0n1p7 whonix-gw
cryptsetup open /dev/nvme0n1p8 whonix-ws
cryptsetup open /dev/nvme0n1p9 fedora
Root partition encryption requires the kernel to have CONFIG_DM_CRYPT=y built in — not as a module. If dm-crypt is a module (=m), the kernel cannot open the LUKS container before mounting root and will panic.

The example above prompts for a passphrase for each volume separately. For systems with many encrypted partitions there is a cleaner pattern: dedicate one small partition as a keys partition. Open it with a single passphrase, mount it, then open every other volume using a keyfile read from inside it. Once all volumes are open, unmount and close the keys partition. By the time runit stage 2 starts, the keys partition is gone from the device mapper — no keyfiles are reachable from a running shell, even as root. You type one password at boot and all volumes open automatically from that point. The implementation belongs in /etc/boot.d/ as a stage 1 script alongside an /etc/crypttab that maps each volume name to its device and keyfile path. Adapt the logic to your own partition layout — do not copy it verbatim from any example.

Kernel requirements

.config — dm-crypt
# Device mapper core
CONFIG_BLK_DEV_DM=y

# dm-crypt — LUKS encryption
CONFIG_DM_CRYPT=y

# Crypto algorithms
CONFIG_CRYPTO_AES=y
CONFIG_CRYPTO_XTS=y
CONFIG_CRYPTO_SHA256=y
CONFIG_CRYPTO_SHA512=y
CONFIG_CRYPTO_ARGON2=y
12

QEMU

krypt/sh runs QEMU fully rootless. Networking is provided by passt — a userspace network stack that requires no privileges and no tun/tap devices. Display is via -display gtk using the Wayland backend.

Installation

install
# tux install qemu passt

Your user needs to be in the kvm group for hardware acceleration: usermod -aG kvm username.

Running a VM

kali linux example
# Start passt first
$ passt --socket /tmp/passt-kali.sock &

# Launch VM
$ qemu-system-x86_64 \
   -enable-kvm \
   -m 2048 \
   -cpu host -smp 4 \
   -drive file=kali.qcow2,format=qcow2,if=virtio \
   -netdev stream,id=net0,server=off,addr.type=unix,addr.path=/tmp/passt-kali.sock \
   -device virtio-net-pci,netdev=net0 \
   -device virtio-tablet-pci \
   -device virtio-vga,xres=1920,yres=1080 \
   -display gtk &

Rootless networking (passt)

passt provides NAT networking to QEMU VMs without root, tun/tap, or kernel modules. For an internal network between two VMs (such as Whonix Gateway and Workstation), use a multicast socket:

whonix internal LAN
# Gateway — internal interface (second -netdev)
-netdev socket,id=internal,mcast=230.0.0.1:1234,localaddr=127.0.0.1 \
-device virtio-net-pci,netdev=internal \

# Workstation — same mcast address
-netdev socket,id=internal,mcast=230.0.0.1:1234,localaddr=127.0.0.1 \
-device virtio-net-pci,netdev=internal \
localaddr=127.0.0.1 is required. Without it, QEMU binds the multicast socket to the wireless interface and the VMs cannot see each other.

Clipboard sharing

Clipboard sharing between the krypt/sh host and QEMU guests uses the qemu-vdagent chardev — the agent protocol without the SPICE display stack. Build QEMU with --enable-gtk-clipboard and add to the launch command:

clipboard setup
# Add to QEMU launch flags
-device virtio-serial,packed=on,ioeventfd=on \
-device virtserialport,name=com.redhat.spice.0,chardev=vdagent0 \
-chardev qemu-vdagent,id=vdagent0,name=vdagent,clipboard=on,mouse=off \

# Inside the guest — install and start spice-vdagent
$ systemctl enable --now spice-vdagentd
13

Podman

Podman runs containers rootlessly on krypt/sh. The runtime stack is podmancrun (OCI runtime) → aardvark-dns + netavark (networking) → catatonit (init). No Docker daemon. No root required.

Installation

install
# tux install podman crun aardvark-dns netavark catatonit

The kernel must have user namespaces, cgroups v2, and overlay filesystem enabled — see the Security & sandboxing and Cgroups sections.

Rootless containers

Configure subuid and subgid ranges for your user — required for rootless operation:

subuid/subgid
# Add to /etc/subuid and /etc/subgid
# echo "username:100000:65536" >> /etc/subuid
# echo "username:100000:65536" >> /etc/subgid

# Initialize the podman user configuration
$ podman system migrate

Running containers

basic usage
# Run a container
$ podman run --rm -it alpine sh

# Run detached with a name
$ podman run -d --name myapp -p 8080:80 nginx

# List running containers
$ podman ps

# Stop and remove
$ podman stop myapp && podman rm myapp
14

Xen

krypt/sh supports Xen as a paravirtualised hypervisor for hard VM isolation — each domU runs in its own hardware-enforced trust boundary. The boot path is limine → multiboot2 → xen.gz + vmlinuz. Direct xen.efi EFI boot is not supported on all firmware — limine is the correct and tested bootloader.

dom0 kernel

The Linux kernel running as dom0 requires Xen-specific options. Build the kernel with make LLVM=1 and enable:

.config — Xen dom0
CONFIG_XEN=y
CONFIG_XEN_DOM0=y
CONFIG_XEN_PRIVILEGED_GUEST=y
CONFIG_XEN_BALLOON=y
CONFIG_XEN_SCRUB_PAGES_DEFAULT=y
CONFIG_XEN_DEV_EVTCHN=y
CONFIG_XEN_BACKEND=y
CONFIG_XEN_NETDEV_BACKEND=m
CONFIG_XEN_BLKDEV_BACKEND=m
CONFIG_XEN_PCIDEV_BACKEND=m
CONFIG_XEN_GNTDEV=y
CONFIG_XEN_GRANT_DEV_ALLOC=y
CONFIG_SWIOTLB_XEN=y
CONFIG_XEN_FBDEV_FRONTEND=y
CONFIG_XEN_SAVE_RESTORE=y
CONFIG_XEN_ACPI_PROCESSOR=y

Booting with limine

Install limine as BOOTX64.EFI and configure it to load Xen via multiboot2. Place xen.gz and the dom0 kernel on the ESP:

/boot/efi/limine.conf
timeout: 5
default_entry: 1

/Xen dom0 krypt/sh
    protocol: multiboot2
    path: boot():/xen.gz
    cmdline: dom0_mem=6144M,max:6144M dom0_max_vcpus=4 iommu=no noreboot
    module_path: boot():/vmlinuz-6.12.77
    module_cmdline: root=/dev/nvme0n1p6 rw quiet
xen.gz must be on the ESP. Copy it from /boot/xen.gz to /boot/efi/xen.gz after every Xen package update. limine reads from the ESP only.

Xen runit services

Three runit services are required for Xen toolstack operation. All services guard against non-dom0 boots — they exit cleanly if /proc/xen/capabilities does not contain control_d. Do not symlink them into /service/ until after a confirmed first Xen boot.

ServicePurpose
xenstoredXenStore daemon — required first, all other tools depend on it
xencommonsXencommons setup — hotplug and backend services
xenconsoledConsole multiplexer — access domU consoles via xl console
enable after confirmed boot
# ln -s /etc/sv/xenstored /service/
# ln -s /etc/sv/xencommons /service/
# ln -s /etc/sv/xenconsoled /service/

Creating domUs

domUs are created with xl. A basic PVH domU configuration using a LUKS-encrypted partition:

fedora.cfg
name = "fedora"
type = "pvh"
memory = 4096
vcpus = 4
disk = [ "/dev/mapper/fedora,raw,xvda,rw" ]
vif = [ "bridge=xenbr0" ]
bootloader = "pygrub"
xl commands
# Open LUKS partition first
# cryptsetup open /dev/nvme0n1p9 fedora

# Create and start the domU
# xl create fedora.cfg

# List running domains
# xl list

# Connect to console
# xl console fedora

# Shutdown
# xl shutdown fedora

Pure LLVM musl libc Source-Based Independent