# Mountaineer Stack This document defends every default Mountaineer ships. One paragraph per pick. Each pick must be in Alpine's `main/` or `community/` repositories. This first pass covers the **easy wins** — picks where the design debate is not really live, where a thoughtful operator in 2026 reaches for the same answer most of the time, and where Mountaineer's job is to lock the choice in and move on. The spicier picks (init, shell, editor, multiplexer, container runtime, log router, filesystem) are deferred to a second pass; they each need a longer defense. ## Compatibility contract Mountaineer ships modern, named-replacement tools under conventional command names while keeping a POSIX-conformant base reachable for scripts. The contract is small and worth stating once, here, before the per-pick defenses that implement it. - `/bin/sh` is busybox ash. Always. Scripts and `#!/bin/sh` shebangs get POSIX behavior, nothing more. - Busybox coreutils are the system-glue layer. The full applet set remains reachable via `/bin/busybox ` regardless of what interactive PATH resolves a given name to. - Interactive replacements (rg, fd, bat, eza, dust, procs, bottom, sd) own the conventional names (`grep`, `find`, `cat`, `ls`, `du`, `ps`, `top`, `sed`) in interactive PATH order. Scripts that need busybox- or GNU-specific behavior call the underlying tool by its real name or full path. - Available-on-request, not installed: bash (`apk add bash`), OpenRC (`apk add openrc`), neovim, mako, waybar. Escape hatches, not hidden. - `docker` is a shell alias for `podman`. Habit accommodated; no second runtime installed. - `sendmail` is one name owned by one of: msmtp (default) or OpenSMTPD (operator upgrade for relay use). Mutually exclusive — installing one removes the other. Principle 12 is the through-line: when a third-party install script breaks because it assumed GNU coreutils, systemd, or Docker, the script is the bug. The contract above says exactly which assumptions Mountaineer will and will not honor. ## Foundation ### apk Alpine's own package manager and one of the best-designed in mainstream use. Fast, deterministic, parseable output, atomic transactions, simple repository format. `/etc/apk/world` is a declarative list of installed packages — Mountaineer treats it as a first-class configuration file rather than an implementation detail. There is no alternative worth considering for an Alpine-based system, and no alternative worth wanting. ### busybox ash (`/bin/sh`) Alpine's default `/bin/sh` stays. Busybox ash is a small, POSIX-conformant shell that handles the role `/bin/sh` actually has — running short scripts, system glue, package install hooks — without the accumulated weight of bash. Anything that needs bash features explicitly is either rewritten or runs bash directly via shebang. Mountaineer does not promote ash to the *interactive* shell; that decision lives in the spicy-picks document. ### busybox coreutils (system glue) Busybox stays as the coreutils layer — `cat`, `cp`, `mv`, `rm`, `mkdir`, `chmod`, `ln`, `install`, `find`, `ps`, `du`, and the rest. It is POSIX-conformant, small, the path every Alpine package install hook and shell script already assumes, and the layer the rest of the system is glued together with. We do not swap it for uutils: uutils is a rewrite of *GNU* coreutils, and Mountaineer does not have a GNU-compatibility problem to solve (principle 12 — scripts assuming GNU are the bug, not the system). Backing a rewrite of GNU when the project we are actually standing on is busybox would be principle 15 applied to the wrong rebuild. The interactive replacements listed below — ripgrep, fd, bat, eza, dust, procs, bottom, sd — sit *on top* of busybox under conventional names (`grep`, `find`, `cat`, `ls`, `du`, `ps`, `top`, `sed`). The busybox applets remain reachable under their full paths (`/bin/busybox find`, etc.) for scripts and system glue that need the POSIX-canonical behavior. The contract: interactive shells get the modern tool, scripts and system code get busybox. ### OpenSSH The reference implementation, in active development, with a security record that has earned the field's trust. Mountaineer ships a hardened default `sshd_config`: no password authentication, no root login, modern KEX/cipher/MAC list only, `AuthenticationMethods publickey`, and a documented pattern for adding keys via `authorized_keys` or via a managed file. There is no second SSH server worth considering. ## Time and DNS ### chrony The current best-designed NTP implementation for a single-host system. More accurate than `ntpd` on the bursty, virtualized hardware Mountaineer machines are likely to run on, more honest about its sync state than busybox ntpd, and the standard choice on every serious distribution that isn't pinned to a legacy daemon. Ships with a sane default config pointing at the public NTP pool with iburst, and a documented swap-in for organizational time sources. ### unbound A local validating, recursive, caching DNS resolver running on 127.0.0.1 and `::1`. Mountaineer's default config forwards upstream over DNS-over-TLS to a documented pair of resolvers and validates DNSSEC. This replaces the "edit `/etc/resolv.conf` and hope" pattern with a real local resolver that is fast, private, and inspectable. `dnsmasq` is a fine tool for a different job (DHCP + DNS on a LAN gateway); unbound is the right pick for a host resolver. ## Web ingress and TLS ### Caddy The reverse proxy whose design every other reverse proxy will eventually adopt. Auto-HTTPS via ACME is the default and not a feature you configure. The Caddyfile syntax is the clearest configuration language in the category. HTTP/2 and HTTP/3 work without ceremony. For the single-host and small-fleet workloads Mountaineer targets, Caddy is the correct answer; nginx remains the right answer at a scale Mountaineer does not target. Mountaineer ships an example Caddyfile demonstrating reverse-proxying to a podman container, serving static files, and a documented pattern for non-HTTP services that need TLS termination. ## Secrets and backup ### age A small, modern file-encryption tool with a single sensible algorithm choice, ssh-key compatibility, and no configuration surface to misuse. Replaces gpg for the file-encryption use case for which gpg was always overkill and underspecified. Mountaineer's documentation treats age as the canonical "encrypt this file to a recipient" primitive. ### sops A secret-management layer that uses age (or other backends) to encrypt values inside otherwise-readable structured configuration files. The killer property: diffs of an encrypted file remain meaningful because only values are encrypted, not the whole document. Mountaineer documents a sops + age workflow for storing service secrets in version control without accident. ### restic A deduplicating, encrypted, repository-based backup tool with broad backend support (local, S3, B2, SFTP, rclone-anything). Snapshots are content-addressed, restores are precise, and the on-disk format is documented. Mountaineer ships a `restic` stub with a commented-out repository URL, a scheduled job (one-shot service on the chosen init), and a documented restore drill. Kopia is a credible competitor and may overtake restic on design; restic is in Alpine's repositories, kopia is not, and our thesis governs. ## Interactive userland These are the tools a Mountaineer user runs by hand all day. Per manifesto principles 1 and 9, they are **defaults, not aliases** — `ls` invokes `eza`, `grep` invokes `ripgrep`. Busybox applets remain present for scripts and system glue; PATH order and a small set of replacement symlinks shape the interactive experience. ### ripgrep (`rg`, and as `grep`) Faster than grep, smarter about ignore files, sane defaults for recursive search, structured output available when scripts want it. The design question "what should grep do by default in a source tree" was answered by ripgrep and the answer is correct. The compatibility cost is real and acceptable: a small set of grep flags have different spellings, and a small set of scripts will need adjustment. ### fd (and as `find`) Replaces `find` for the 95% of use cases that are not "execute arbitrary actions on matching files." Defaults are humane (regex by default, smart case, skip ignored directories), syntax is short, output is colored and structured where appropriate. For the remaining 5% (`-exec` patterns, complex predicates), busybox `find` is still available under its full path. ### sd (and as `sed`) A regex-based find-and-replace tool covering the 90% of `sed` invocations operators actually type — `sd 'foo' 'bar' file` — with PCRE-style regex (not basic-regex), in-place editing as a normal flag, and no addresses, line ranges, or `s///g` ceremony. The remaining 10% (stream editing, address blocks, hold-buffer tricks) is what busybox `sed` retains, reachable under its full path. The split is the same one we draw for `find` and `ps`: the common-case interactive tool wins the conventional name, the legacy tool stays available for the edge cases that need it. ### bat (and as `cat`) Adds syntax highlighting, paging when output exceeds a screen, and line numbers — all defeatable when output is piped, which is the property that makes it a safe `cat` replacement. The design choice "act like cat when piped, act like a viewer when on a tty" is exactly right. ### eza (and as `ls`) A from-scratch `ls` with consistent column behavior, git-status integration, tree output, and sane handling of extended attributes. The flag surface is smaller and more orthogonal than GNU ls. Mountaineer's default invocation includes file type indicators and human-readable sizes. ### dust (and as `du`) A `du` that shows you what you actually want — biggest directories first, tree-shaped, with proportional bars. The interactive use of `du` is almost always "what is eating my disk," and dust answers that question in one command. ### procs (and as `ps`) A `ps` that does not require memorizing the BSD-vs-SysV flag dialect. Colored output, sensible defaults, structured columns, and tree mode. For scripts, busybox `ps` remains available under its full path. ### bottom (`btm`) A `top`/`htop` replacement with better defaults, mouse support, and a sensible layout out of the box. Network, disk, and process views in one tool. We pick bottom over btop on the grounds that bottom's keybinding model is more discoverable; both are in Alpine repos and the choice is close. ### zoxide Augments `cd` with a frecency-ranked jump list. `z foo` goes to the directory matching `foo` you most recently or most often used. Once internalized, this is one of those tools whose absence becomes painful within hours. Mountaineer ships it integrated with the interactive shell from first boot. ### lnav The log file navigator. Understands common log formats out of the box, gives you SQL over your logs, handles compressed and rotated files transparently, and is the right tool for the "what just happened" question on a single machine. Pairs naturally with whatever structured-log routing we land on in the spicy-picks pass. ### starship The prompt. Fast, configured by a single TOML file, presents only context that exists (a git segment when in a repo, a kubernetes segment when a kubeconfig is active, etc.). Ships with a Mountaineer-flavored default that surfaces hostname, user, working directory, git status, and the exit code of the last command. No other prompt is worth the maintenance. ## Spicy picks These are the picks with real downstream consequences. Each one is a position Mountaineer has to defend on first contact with operators who already have habits. ### s6 + s6-rc (init and service management) Mountaineer ships s6 as PID 1 and s6-rc as the service manager, replacing Alpine's default OpenRC. This is the single most consequential pick in the stack: it shapes every service definition Mountaineer writes, every package integration we do, and the dogfood story for the project itself. The design case is straightforward. **s6 is a process supervisor by construction**, not by add-on. Every long-running service runs under a dedicated supervisor whose only job is to keep it alive and respond to its state changes. OpenRC, by contrast, is a service *starter* that hands off and stops paying attention; supervision is bolted on via `supervise-daemon` or external tools, which means every operator ends up with a slightly different "did the service come back up after that crash?" answer. systemd has supervision but couples it to an enormous surface area; s6 is the supervision, the readiness notification model, and the dependency graph — and nothing else. The pieces are small, separately documented, and composable. The cost is honest and bounded. Almost no upstream package ships an s6-rc service definition; nearly every Alpine package ships an OpenRC init script. Mountaineer therefore owns an s6-rc service-definition library covering every daemon it ships — Caddy, Podman, Vector, unbound, chrony, and so on — and we ship the canonical examples for tools the broader s6 community has not yet packaged. This is core project work (principle 2: absorb the adoption cost), not a side effort. The bounded part: Mountaineer's stack is small by design. The full set of services we need to define is in the low tens, and a service definition is a directory of small text files that rarely changes. We are committed to a hard gate on this pick: **v0.1 ships only when the Mountaineer project's own infrastructure is hosted on s6.** Build server, docs site, package mirror — whatever ends up being "the Mountaineer project's machines" runs s6 from the start. If we cannot dogfood s6 for our own use, we have no business asking users to. OpenRC remains available via `apk add openrc` for users who need to run a package whose ecosystem assumes it. We do not promote this; we do not hide it. ### helix (editor) Helix is the editor Mountaineer ships and integrates with shell, file picker, and language servers from first boot. We pick it over neovim, vim, and emacs. The design case is structural, not aesthetic. Helix's selection-then-action model — inherited from Kakoune — is a genuine UX improvement over vim's action-then-motion. The selection is visible before the action commits; you see what you are about to operate on. Vim's model is a 1970s constraint (you could not draw a highlighted selection on a VT100) that became a cultural artifact. Modal editors built from scratch in the 2020s work the way helix works because the constraint that produced vim's model no longer exists. Helix's second structural property is that it ships as a finished editor. LSP, tree-sitter, fuzzy file picking, diagnostics, and language support are present out of the box; there is no `init.lua`, no plugin manager, no pre-baked config distribution to choose between. This matches Mountaineer's posture exactly — the editor we ship is the editor a Mountaineer user uses, not a kit they assemble. Neovim is a fine editor, but it is also a substrate for editor-configuration projects (LazyVim, NvChad, AstroNvim), and shipping neovim means picking one of those or building our own. Helix has nothing to pick. The honest weaknesses: no plugin system yet (Steel/Scheme work is in progress), thinner DAP/debugger support than neovim, intentionally minimal workspace-navigation features. For Mountaineer's "minimize the configuration surface" thesis the first two are *features*, not bugs. For an operator who wants 17 customizations and a full IDE, `apk add neovim` remains one command away — and they will not be the median Mountaineer user anyway. ### zellij (terminal multiplexer) Zellij replaces tmux as Mountaineer's default multiplexer. Tmux is the consensus pick across most of the Linux world and Mountaineer's rejection of it is principle 14 at work. Tmux is a 2007 tool whose interface presumes you will spend a weekend writing a `.tmux.conf` before it is comfortable. Its default keybindings, default status line, default scrolling behavior, and default copy-mode are all artifacts of an era when configuration was the point. The tmux community responded with a plugin manager (tpm), a culture of dotfiles repos, and a tradition of new users adopting someone else's config wholesale. None of this is the *idea* of a terminal multiplexer; it is accumulated workaround. Zellij ships with discoverable keybindings printed on screen, sensible scrollback and copy behavior, and a layouts-as-config model where complex pane arrangements are reproducible KDL files rather than runtime invocations. Sessions, panes, and tabs work the way you would design them today if nothing existed before. There is a plugin system (WASM-based, deliberately constrained) but the base tool is fully usable with no plugins and no configuration — which is what Mountaineer ships. The cost: smaller user base, fewer Stack Overflow answers, and occasional rough edges on TUI applications that probe terminal capabilities aggressively. Acceptable tax for a tool that doesn't require a config to be pleasant. ### nushell (interactive shell) Nushell is Mountaineer's interactive shell. `/bin/sh` remains busybox ash (principle 12 and reality: too much of the system assumes a POSIX shell at that path); nushell is what the operator types at. This is the most controversial pick in the stack and deserves a clean defense. The argument: **text-stream pipelines were a brilliant 1970s compromise that we have stopped questioning, and structured pipelines are simply the correct design now that the constraints that produced text streams no longer hold.** Every conventional shell pipeline is a sequence of programs guessing at each other's output format — awk and cut and sed are workarounds for the absence of a type system between programs. Nushell makes the type system explicit: tables and records flow between commands, columns have names, filters and projections are operators rather than text-massaging exercises. Once internalized, `ls | where size > 1mb | sort-by modified` is not a flex, it is what you wished `ls -lah | sort` had always been. The cost is significant and we pay it on purpose. Almost no copy-pasted Stack Overflow answer works in nushell. Subshell substitution, command substitution, and process redirection syntaxes are different. Scripts written for bash do not run; nushell does not pretend to be bash-compatible. Mountaineer's response: we document the nushell idioms for the things sysadmins actually do — restart a service, inspect logs, walk a directory tree, run a one-liner against a CSV — and we ship a small `nu`-flavored library of helper functions for common operations. The investment is real; it is also what principle 2 ("absorb the adoption cost") commits us to. We pick nushell over the structured-shell alternatives (PowerShell, Elvish, Murex, Oils) on three grounds: it is in Alpine `community/` (principle 11), it is actively developed with a stable-enough core for daily use as of late 2025, and its data model is the cleanest. PowerShell carries Windows baggage we do not want; Elvish has design instincts we admire but a smaller ecosystem; Oils is interesting but still self-describes as a research project. Bash remains available via `apk add bash` and is not hidden; users who need it for a specific script can have it without ceremony. ### podman (container runtime) Mountaineer's default container runtime is podman, configured for rootless operation and integrated with s6-rc for long-running services. Docker is not installed and is not promoted. The design case has two halves. The first is architectural: **podman is a CLI, not a daemon.** It invokes `runc` (or `crun`) directly per container and exits. There is no always-on root process mediating every operation, no socket exposed to the local user, and no single point of failure whose crash takes every container with it. Docker's design — a long-running root daemon that every `docker` invocation talks to — has been the source of an unreasonable share of the historical container security incidents on Linux. Podman's design eliminates the category. This is principle 6 ("rootless and least-privileged by default") as architecture rather than configuration. The second half is rootless containers as a first-class mode. Podman runs containers in user namespaces by default, with the operator's UID mapped to root *inside* the container and to an unprivileged subuid range *outside*. This is the operating mode Mountaineer ships and documents; root-mode podman is available but not the demonstrated path. The container in your homelab does not need to be able to escape into your host's actual root, and on Mountaineer it cannot. Podman is OCI-compliant and the CLI is intentionally Docker-compatible — most `docker` commands work verbatim as `podman` commands, and we ship `docker` as a shell alias for `podman` so habit doesn't fight us. The image registry, the build tooling (`buildah` is shipped alongside), and the pod abstraction (`podman pod`) are all designed to be what you reach for when you would have reached for Docker, Docker Compose, or a tiny Kubernetes. **The honest cost: the upstream "run a container as a service" story assumes systemd.** Podman ships `quadlet`, a system that takes declarative container files and generates systemd unit files. Mountaineer is on s6-rc, so we cannot use quadlet directly. Instead, Mountaineer ships a small `podman-s6` integration: a directory of s6-rc service templates that take a podman quadlet-style declaration and produce a supervised long-running container under s6, with restart policy, dependency declarations, and log routing into Vector. This is core project work (principle 2, again), and it is one of the canonical artifacts Mountaineer produces that the broader Alpine + s6 ecosystem will benefit from. The rejected alternatives: - **Docker** — the daemon model is the failure we are correcting; daemon-as-root is the security posture we refuse. - **containerd directly** — designed as a building block for orchestrators (kubelet, etc.), not for direct human use. Wrong tool for the job. - **CRI-O** — Kubernetes-specific. Wrong tool for the job. - **LXC/LXD** — different abstraction (system containers, not application containers). Useful for a different problem; not what an operator reaching for "I want to run this image" wants. ### ZFS on root (filesystem) Mountaineer installs to ZFS on root by default. Ext4 is the choice you make when you do not want to think about your filesystem; Mountaineer's posture is the opposite, and the operator's answer for "filesystem on a machine I care about" is not ext4. The design case has three load-bearing parts. **First, ZFS is the filesystem an actual sysadmin chooses.** Snapshots, send/receive for backup, native encryption, transparent compression, on-disk integrity via checksums, scheduled scrubbing, and a coherent admin CLI (`zfs`, `zpool`) where every operation has an obvious command. This is not a list of features; it is a posture — the filesystem actively defends data and gives the operator the tools to recover from their own mistakes. **Second, ZFS operationalizes principle 5 at the filesystem layer.** Principle 5 says "atomic configuration, cheap rollback. The system's state is derived from files we can version. A bad change is reverted, not debugged in production." ZFS makes this real without Mountaineer inventing a generation model. Pre-upgrade snapshot → bad upgrade → reboot into the snapshot → done. The NixOS rollback story implemented at a layer that does not require us to also be NixOS. The `sysop upgrade` workflow is built around this primitive: every upgrade is bracketed by an automatic snapshot, retained on a default schedule, and exposed in the boot menu via zfsbootmenu. **Third, it works on both server and laptop**, which principle 13 requires. ZFS native encryption gives full-disk crypto on the laptop without a separate dm-crypt layer. The same pool layout, the same snapshot tools, and the same backup primitive (`zfs send | ssh host zfs recv`) apply on a VPS, a homelab box, and a workstation. **Honest costs:** - **ZFS-on-root is not Alpine's default install path.** Mountaineer ships its own installer (or post-install steps invoked by the Alpine installer) that handles bootloader integration, an initramfs with the zfs module, the zpool cache, and the canonical dataset layout. Several of these are Mountaineer-canonical artifacts in their own right: the default dataset hierarchy (separating `/var/log`, `/home`, container storage, and root for distinct snapshot and compression policies) and the zfsbootmenu integration that surfaces snapshots as boot targets. - **The kernel module is out-of-tree.** Mountaineer follows Alpine's `linux-lts` + `zfs-lts` pairing; Alpine is responsible for keeping these in sync and we do not run kernel versions Alpine does not have a matching zfs module for. The brief windows during Alpine kernel bumps are handled the way Alpine handles them — wait, do not force. - **Memory.** ZFS's ARC will use a lot of RAM by default. On a 1GB VPS this is the difference between "works fine" and "swaps constantly." Mountaineer ships with a tuned default `zfs_arc_max` (512MB for small machines, sized appropriately for larger ones via an install-time check) and documents the adjustment. - **It is a commitment.** Picking ZFS shapes the installer, the upgrade tooling, the backup recipes in `ROUTES.md`, and the documentation. We do not unpick this casually in v2. **Rejected alternatives:** - **btrfs** — same conceptual feature set, in-tree, but multi-device and RAID5/6 remain flagged as not-for-production in the upstream kernel docs. We cannot recommend a filesystem whose advice changes the moment the operator adds a second disk. - **bcachefs** — the rebuild we would back if it were ready. As of late 2025 it is still flagged experimental in mainline and Alpine's `bcachefs-tools` says so. The "Alpine packages it as production" condition (principle 11 in spirit) is not met. Revisit on a multi-year horizon; in the meantime we watch it. - **ext4** — the safe answer; the wrong answer under this manifesto. Gives us none of the snapshot, rollback, integrity, encryption, or send/recv story principle 5 implies. - **xfs** — solid for the workloads it targets, but gives us none of the snapshot/rollback story either. Worse than ZFS on every dimension Mountaineer cares about. ### Vector (log routing) Vector is Mountaineer's log router. Every service that produces logs — system daemons via syslog, podman containers via their stdout/stderr, application processes via direct sink — flows through Vector to filtered, structured destinations on disk, with lnav as the canonical viewer. The design case: most Unix log infrastructure is text-stream-shaped (rsyslog, syslog-ng) and treats structure as an afterthought. Vector inverts this — its data model is structured events with typed fields, its config language is a real DSL (VRL — Vector Remap Language) for transforming events, and its sources and sinks are first-class connectors rather than custom log formats. This is the same shift from text-streams-to-structured-data that nushell is making at the shell layer, applied to log routing. The tools agree on the model. Practically: a single `vector.toml` declares which sources feed in (journal/syslog, container logs, file tails, HTTP endpoints), which transforms apply (parse, filter, enrich, sample, redact), and which sinks receive the output (files partitioned by service and date, an upstream aggregator for fleets, or an alerting webhook). The Mountaineer-canonical config establishes a sensible default: container logs go to `/var/log/containers//.log` (compressed automatically by the ZFS dataset), system daemons go to `/var/log/system/`, and a small "critical" tap can be configured to email or alert when patterns match. Vector handles rotation, compression, and back-pressure correctly without ad-hoc shell scripts. Rejected: rsyslog and syslog-ng (treat structure as bolted on), Fluent Bit (smaller, less expressive transform language, less integrated with the structured-data thesis), Logstash (heavy JVM, not in Alpine). ### nftables (firewall, directly) Mountaineer's firewall is nftables, configured via a Mountaineer-canonical `/etc/nftables.d/` ruleset that the install ships. We do not use `awall` (Alpine's nftables front-end), `ufw`, or `firewalld`. The case is principle 14: nftables' own configuration syntax *is* the modern, well-designed answer to the firewall-config question. It is declarative, readable, supports named sets and maps, and replaces iptables' positional flag salad with structured rule definitions. Wrapping it in awall introduces a second layer to learn, debug, and version — solving a problem (iptables' awful syntax) that nftables already solved. The wrapper is the legacy mental model trying to survive. Mountaineer ships a default ruleset: default-deny inbound on both v4 and v6, allow established/related, allow SSH (rate-limited), allow ICMP/ICMPv6 with sane filtering, and a documented pattern for opening additional services either by editing the ruleset directly or by dropping a file into `/etc/nftables.d/`. The ruleset is heavily commented; reading it is how you learn what the firewall does. ### msmtp (outbound mail) Mountaineer ships msmtp as the default outbound-mail relay, configured to fail loudly with an informative message until the operator points `/etc/msmtprc` at a real SMTP provider. msmtp reads a message from stdin, hands it to a configured upstream over authenticated SMTP, exits. That is the whole tool. It is the right size for the "cron needs to email me when the backup fails" use case that the rest of the stack assumes works. We do not install a full MTA. Postfix, Exim, and Sendmail are aimed at machines that *receive* mail; the median Mountaineer machine does not. For operators who genuinely run a mail relay, OpenSMTPD is the documented upgrade path — it is the best-designed full MTA in active use, it is in Alpine, and a recipe in `ROUTES.md` will cover the canonical relay setup. Installing OpenSMTPD removes msmtp; the two are mutually exclusive aliases for `/usr/sbin/sendmail`. ### ifupdown-ng + iwd (network configuration) Mountaineer's network configuration is **ifupdown-ng** for wired interfaces (all profiles) and **iwd** for wireless interfaces (laptop profile). dhcpcd is the DHCP client for wired interfaces; iwd handles DHCP on the wireless side via its built-in client. No meta-layer wraps these tools — their own config files are the canonical source of truth. The case: the modern declarative network-config front-ends (netplan, nmstate, systemd-networkd) are either not in Alpine or tied to systemd. The Alpine team's own answer to "what is the modern, well-designed replacement for legacy ifupdown" is ifupdown-ng — actively maintained, plugin-based (bridges, bonds, VLANs, wireguard all expressible as small stanzas in `/etc/network/interfaces.d/*`), and tightly scoped. iwd is what wpa_supplicant would look like if rewritten with two decades of hindsight — small, designed around modern WPA standards, with a sane CLI (`iwctl`) and its own integrated DHCP. Both pass principles 11 (Alpine packages it) and 15 (tight scope). We considered and rejected writing a Mountaineer-authored declarative meta-layer (a single `/etc/mountaineer/network.toml` that compiles into ifupdown-ng and iwd configs). The defense for refusing: ifupdown-ng's config language was authored by people who thought hard about it and is genuinely humane; wrapping it would be the failure mode principle 15 warns against — a wrapper that re-does design work already done. Mountaineer is a curation, not a wrapper distribution. What we *do* author, per principle 14 (wrap UX, not configuration): a small set of ratatui-based `sysop` subcommands that smooth the operational UX without owning any config files. The committed set: - `sysop wifi` — scan, connect, forget, list known networks. A TUI in front of `iwctl`. The files iwd writes to `/var/lib/iwd/` are unchanged and remain authoritative. - `sysop net` — show current interface state, link/address summary, default route, DNS resolver state. Read-only inspector; the underlying tools (`ip`, `iwctl`, `resolvectl` equivalent) are still the answer for anything beyond status. - `sysop wg` — add/remove/show wireguard peers, with the underlying config landing in the standard `/etc/wireguard/*.conf` files that the ifupdown-ng wireguard plugin already consumes. These helpers are bounded — they exist for the common operator paths and refuse to grow into config managers. The ifupdown-ng `interfaces.d/` files are still the source of truth for anything structural (a new bridge, a bonded interface, a VLAN topology); operators edit those files directly, the same way they would on any Alpine machine. Rejected: - **NetworkManager** — heavy, dbus-driven, GUI-shaped; wrong posture for Mountaineer. - **ConnMan / wicd** — less maintained, less coherent. - **wpa_supplicant** — the legacy wifi daemon iwd is the rebuild of (principle 15: we back the rebuild). - **systemd-networkd** — unavailable. - **A Mountaineer-authored declarative meta-layer** — rejected on principle 14 grounds, as discussed above. If practical operator experience reveals consistent gaps that the underlying tools cannot express, this decision is reopenable. ### Nebula (overlay mesh) Mountaineer ships **Nebula** as the default overlay-mesh tool — installed on every build, started once the operator has signed a host certificate and pointed the host at a lighthouse. The mesh is opt-in to *run* (no daemon starts on a host without a cert + config) but default to *exist* (the package, service unit, and `sysop mesh` UX are installed everywhere). This is principle 13 as a build-shape constraint applied to networking: the Sysadmin laptop and the Server box ship the same mesh tool, so the operator's mesh is one consistent thing across their fleet rather than something they bolt on per host. The case is principle 14 applied to a real reachability problem. Single-operator fleets with a public-IP bastion can live on hardened sshd alone (ROUTES.md covers this floor), but the moment a laptop roams onto an arbitrary network, or a server lives behind CGNAT, or a second site appears, raw SSH becomes "manage firewall holes and dynamic DNS forever." A mesh is the boring answer to that ongoing-work problem. Mountaineer picks one. The case for Nebula specifically, over the more famous alternatives: - **Certificate-based identity.** Nebula uses a small CA (`nebula-cert`) that signs host certificates with embedded groups, IPs, and subnets. The operator already understands certificates from Caddy (TLS) and age (file encryption); Nebula's identity model slots into that mental space rather than introducing a parallel one with users, preauth keys, and SSO. Revocation is cert-expiry plus a CRL — the same shape the rest of the operator's world uses. - **Stateless lighthouses.** A lighthouse is a public-IP host that helps other nodes find each other and punch through NAT. It holds no authoritative state. Kill it, restart it, replace it, run two of them — the mesh keeps working. Compare to a control-plane database (Headscale's SQLite, Netbird's Postgres) that becomes a backup-and-restore problem the moment it exists. - **Cert-embedded ACLs.** Authorization is `groups: [laptop, dev]` baked into the certificate at sign time, plus a small `firewall` stanza in each host's config that says which groups can reach which ports. No central policy server, no policy-reload step, no "did the ACL apply" guard. The policy travels with the cert. - **Single project, single binary.** Nebula is one Go binary on each host, one config file, one service. No client-vs-control-plane split, no second vendor, no question of whether the open-source piece will keep working when the closed piece changes. - **Production-proven.** Slack built Nebula for their own fleet and runs it at scale. It is not a young project trying to find its shape. - **In Alpine community.** Principle 11. The tradeoff to be honest about: Nebula uses its own Noise-based protocol rather than WireGuard. That means we are *not* layering on top of a kernel primitive every Linux operator already knows, which is a real cost (WireGuard is itself well-designed, audited, in-kernel, and culturally familiar). The defense for accepting the cost: WireGuard is still available on every Mountaineer host (it is a kernel module plus `wg-tools`, used by `sysop wg` for raw point-to-point peers — see the ifupdown-ng section); Nebula and raw WireGuard coexist, addressing different jobs. The mesh job is what Nebula does well. The "two boxes, one private link" job is what WireGuard does well. Mountaineer does not pretend one tool is both. Rejected: - **Headscale + Tailscale.** The most popular self-hosted mesh, and a legitimate pick. Rejected for Mountaineer on three grounds: (1) two-piece system (Headscale + the Tailscale client), creating a second-vendor dependency where Tailscale-the-company has to keep shipping a client that works with the open control plane — they have committed to this, but it is structurally fragile compared to a single project; (2) Headscale is a stateful control plane with a SQLite database that must be backed up and survives as a load-bearing piece of infrastructure, whereas Nebula's lighthouses are stateless; (3) Tailscale's strongest case is its mobile-client UX (iOS/macOS/Android), which is not Mountaineer's audience — Mountaineer targets Mountaineer-on-both-ends. The ACL hujson language is nicer than Nebula's cert-embedded groups; that is the one thing we are genuinely giving up. - **Netbird.** Fully open-source including the client (an honest improvement over the Tailscale split), WireGuard underneath, single-vendor stack. Rejected for now on operational-surface grounds — Netbird's control plane is heavier than Headscale, let alone a Nebula lighthouse, and the project is younger. Reopenable if Nebula's protocol-not-WireGuard cost turns out to bite in practice. - **Innernet.** Philosophically closest to Mountaineer's stance — CRDT-distributed config, no control-plane daemon. Rejected on community size: it is a small project and we do not want the mesh on the critical path of "is this still maintained in three years." Worth watching. - **Raw WireGuard with hand-rolled key distribution.** The artisanal answer. Mountaineer refuses this default for the same reason it refuses hand-rolled init systems: a documented, boring tool that solves the ongoing-work problem is the principled pick. - **Tailscale with Tailscale's hosted control plane.** Out of scope — Mountaineer does not default to SaaS dependencies for primitives. - **ZeroTier.** Older model, less Alpine-friendly historically, not the design shape we want. What Mountaineer authors on top: a `sysop mesh` subcommand cluster (CA init, sign, join, status, lighthouse mode toggle) that wraps `nebula-cert` and the daemon's config file in a UX that matches the rest of `sysop`. The Nebula config file at `/etc/nebula/config.yml` remains the source of truth — the wrapper exists for the common operator paths and refuses to grow into a config manager, same posture as `sysop wifi` over `iwctl`. ## Mountaineer Sysadmin (laptop / workstation build) Mountaineer ships two builds: **Server** (everything in the foundational and spicy-picks sections above) and **Sysadmin** (Server plus the graphical layer below). The relationship is strict subset — Sysadmin is Server plus exactly these additions; nothing in Server is omitted or substituted for Sysadmin. A sysadmin installing the Server build on a workstation gets a perfectly functional headless system; a sysadmin installing the Sysadmin build on a server has a graphical layer they will not start. This is principle 13 as a build-shape constraint, not just a target-audience statement. Cultural through-line for the graphical layer: most of these are by the same small set of authors (Daniel Eklöf for terminal/launcher/notifications; emersion and the wlroots team for compositor primitives; Drew DeVault for lock/idle/background). One design voice across the surfaces the operator stares at all day. The laptop profile builds on top of the base server stack with a Wayland desktop, wifi, audio, and a small constellation of tightly-scoped UX tools. Cultural through-line: most of these are by the same small set of authors (Daniel Eklöf for terminal/launcher/notifications; emersion and the wlroots team for compositor primitives; Drew DeVault for lock/idle/background). One design voice across the surfaces the operator stares at all day. ### river (Wayland compositor) A from-scratch Wayland-native tiling compositor whose layout engine is an external process — exactly the principle-16 architecture (the compositor refuses to know about layouts; it ships a protocol). Chosen over Sway because Sway is a faithful i3 reimplementation and i3's design is 2009; we back the rebuild. The river binary is small, the configuration is a shell script (`init` file), and the layout strategy is decoupled from the compositor — `rivertile` is the default but any program speaking the layout protocol works. ### foot (terminal) Daniel Eklöf. Wayland-native, single binary, fast startup, INI config, server-side decorations (works cleanly with river), sixel support for images. The alternative is alacritty (Rust, GPU-accelerated, also excellent) — foot is smaller, has no GPU dependency, and shares an author with the launcher and notification daemon we ship, giving the laptop UX a single design sensibility across the three things you stare at most. ### yambar (status bar) A small status bar by Daniel Eklöf's neighbor in the ecosystem (Aleksey Bavshin). Sized like Mountaineer wants its bar to be — date, network, battery, load, time, maybe a wifi indicator — without the feature-creep gravity of waybar. The default config Mountaineer ships is about 30 lines of YAML and is the kind of thing an operator reads once and never touches. We accept YAML here per the manifesto's "tool's own DSL" carve-out; the bar itself is the right shape. Rejected: waybar (larger, JSONC config, broader module set that invites contributor drift toward "let's add a Spotify module"). Waybar is a fine tool for someone who wants a feature-rich bar; Mountaineer's laptop user doesn't. ### fuzzel (launcher) + cliphist (clipboard history) Daniel Eklöf, again. fuzzel is the app launcher and command runner — single-purpose, Wayland-native, fast, no plugin system. Bound to the standard Super+D. cliphist stores the clipboard history; fuzzel is reused as the picker via a small wrapper. Bound separately to Super+V. We deliberately do not ship an all-in-one launcher (rofi, anyrun). Principle 16 (tight scope) says we use small tools for distinct jobs: window switching is native to river, application launching is fuzzel, clipboard history is cliphist + fuzzel as picker. An operator who wants the rofi-everything experience installs it themselves; the default keeps each role clean. ### fnott (notifications) Daniel Eklöf. Wayland-native, dbus-driven, deliberately minimal. Three pieces of the laptop UX (terminal, launcher, notifications) by the same author, with the same design sensibility — small, fast, sane defaults, no configuration required to be pleasant. Rejected: mako (slightly more featureful, supports per-app criteria-based rules, is the consensus pick on Sway setups). The mako case is real but the principle-16 case is stronger for our default — fnott ships with defaults that already do the right thing, and a laptop user who wants per-app notification rules installs mako themselves. ### grim + slurp (screenshots) The canonical Wayland screenshot tools, both by emersion. grim captures, slurp selects regions, piped together for region screenshots. swappy (also emersion) handles annotation when shipped as part of a screenshot flow. No real alternatives that aren't worse. ### swaylock + swayidle + swaybg (lock, idle, background) Drew DeVault's tools, all wlroots-compatible and working on river. swaylock locks the screen, swayidle handles idle timeouts (dim, lock, suspend), swaybg sets the wallpaper. Three small binaries with one config file each. ### pipewire + wireplumber (audio) The modern Linux audio stack. Replaces pulseaudio and jack, handles both. No real competition in 2025. wireplumber is the session manager that pipewire needs to do anything useful. Both in Alpine. ### Laptop integration - **seatd** (session/seat management) — smaller than elogind, does not drag systemd-ish assumptions. - **brightnessctl** — small, well-scoped brightness control, no daemon. - **tlp** — battery/power tuning, with Mountaineer's tuned default erring toward battery life over peak performance. Operator knobs (`tlp-stat`, `tlp setcharge`) remain available. ### Browser Explicitly no default. The laptop install script presents the browser packages Alpine ships (using each package's own `apk info` description) and runs `apk add` on the user's choice. Mountaineer authors no editorialized descriptions; we delegate the curation of "what counts as a browser" to Alpine. w3m is preinstalled as an emergency text fallback so the first-boot user can read documentation before picking. This is the only category where Mountaineer refuses to default — the browser landscape is broken in ways a curation choice cannot fix, and inventing editorial language for browsers we would not endorse is dishonest.