Skip to main content

max / mountaineer

17.1 KB · 156 lines History Blame Raw
1 # Mountaineer
2
3 *A slightly nicer path up the mountain.*
4
5 An operating system for people who would rather configure once than configure often.
6
7 ## What this is
8
9 Mountaineer is a curated Alpine Linux distribution with a sysadmin-first userland. The base system, default tools, and service configurations are picked, wired together, and documented so that a competent operator can sit down at a fresh install and have a coherent, modern environment without spending a weekend assembling one.
10
11 Mountaineer is not a new distro. It is an **opinion**, expressed as an `apkovl` and a small set of scripts on top of Alpine.
12
13 ## What this isn't
14
15 - Not a consumer desktop. Mountaineer runs on a sysadmin's laptop as well as on their servers (see principle 13), but it is not aiming to replace macOS or Ubuntu for someone who wants an app store, a proprietary GPU stack, or an Electron-first experience.
16 - Not a beginner's Linux. We assume you know what a shell is and why you'd want one.
17 - Not a "no learning required" system. We replace tools you know with better-designed ones, and we expect you to learn them. The payoff is that you only learn them once.
18 - Not bleeding edge. We do not ship git HEAD. We do not chase release candidates.
19 - Not backwards-compatible with your muscle memory. `ls` is not GNU ls. `grep` is not GNU grep. Scripts from the internet will sometimes break. This is fine.
20
21 ## The thesis
22
23 > **The best-designed tool Alpine is willing to package, configured the way its designers would configure it if they ran your machine.**
24
25 Two halves, both load-bearing.
26
27 **"Best-designed"** means we do not default to the most familiar option. We default to the option that, in 2026, a thoughtful person designing from scratch would pick. Familiarity is not a design property. Ubiquity is not a design property. Age is not a design property.
28
29 **"That Alpine is willing to package"** is the governor. Alpine's repositories are a maturity filter we did not build and do not maintain. If a tool is in `main/` or `community/`, it has survived contact with a packaging maintainer, builds reproducibly across releases, and has not been abandoned. If a tool is *not* in Alpine, we wait. The filter chooses for us, and it chooses conservatively enough that we never ship an experiment as a default.
30
31 We call this **the dull edge**: ahead of the consensus, behind the frontier, riding Alpine's release train without fighting it.
32
33 ## Principles
34
35 1. **Defaults are arguments.** Every default in Mountaineer is a position we are willing to defend in writing. If we cannot explain why a default is the default, it is not a default; it is a leftover.
36
37 2. **Configuration is documentation.** The `/etc` tree is meant to be read. Files are commented. Examples are checked in. Reading the system teaches the system.
38
39 3. **One way to do each thing.** Two tools that do the same job means we have not made a decision. We make the decision.
40
41 4. **Structured data over text streams, where the tool exists.** Pipes that carry typed data are better than pipes that carry whitespace-separated guesses. We adopt structured tools where Alpine packages them.
42
43 5. **Atomic configuration, cheap rollback.** The system's state is derived from files we can version. A bad change is reverted, not debugged in production.
44
45 6. **Rootless and least-privileged by default.** Containers are rootless. Services run as their own users. The firewall is default-deny. Surprises here are bugs.
46
47 7. **IPv6 is not an afterthought.** The default ruleset, the default resolver, the default service bindings all assume dual-stack with v6 as the primary.
48
49 8. **Logs are data.** Plain-text journals that need `grep` rituals to read are a design failure. We route structured logs to structured tools.
50
51 9. **No GNU where a smaller or better-designed option exists in repos.** This is not ideology; it is the consequence of principle 1. Most of the time, the better option is also smaller.
52
53 10. **YAML is not a configuration language.** TOML, KDL, or a tool's own well-designed DSL. We will write wrappers before we will write YAML.
54
55 11. **Alpine's release train is our release train.** When Alpine cuts a release, Mountaineer cuts a release. When Alpine drops a package, we drop the feature. We do not maintain orphans.
56
57 12. **If something breaks, the script is wrong.** When a third-party installer assumes bash, GNU coreutils, or systemd, the assumption is the bug. We document the fix; we do not retrofit the assumption.
58
59 13. **One OS for the server and the sysadmin.** Mountaineer runs on a rented VPS, on the homelab box in the closet, and on the laptop the operator uses every day. We do not maintain parallel forks. We ship two builds with a strict subset relationship: **Mountaineer Sysadmin** is the canonical build, and **Mountaineer Server** is Sysadmin minus the graphical layer. Same kernel, same init, same userland, same defaults — the only delta is the Wayland compositor and the laptop-profile peripherals around it. This forces the interactive userland to be good (the sysadmin feels it daily) and keeps the server install lean (the sysadmin does not want a Java runtime they did not ask for). It also keeps the operator's muscle memory portable end-to-end: the laptop they SSH from runs the same shell, editor, multiplexer, and tools as the server they SSH to.
60
61 14. **Wrap UX, not configuration.** When an underlying tool is well-designed but its operational UX has rough edges, Mountaineer ships a small, Mountaineer-specific TUI (the convention is ratatui) that smooths the rough edges *without* taking ownership of the tool's configuration. `sysop wifi add` is a TUI in front of `iwctl`, not a generator that compiles a Mountaineer file into iwd's profile directory. Operators can still use the underlying tool directly and the files they produce are unchanged; our tooling is additive, never substitutive. This is the bright line that keeps Mountaineer a curation rather than a wrapper distribution — and the rule that tells us when a small Mountaineer-authored helper is justified versus when we are reaching for an abstraction we should refuse.
62
63 Within these helpers, design for tool-agnostic backends. The TUI's user-facing surface should not couple tightly to the current underlying tool. If iwd is replaced in three years by a better wifi daemon Alpine packages, `sysop wifi` should swap backends without changing what the operator sees. Internal abstraction that preserves swappability is good engineering; external abstraction that hides the underlying tool's config from the operator is the failure mode this principle exists to prevent. The two are different and easy to confuse.
64
65 15. **Stable vocabulary, findable names.** A Mountaineer user should not need to memorize that the notification daemon is fnott, the bar is yambar, or the launcher is fuzzel. The roles are stable; the tools filling them are implementation detail. Common operations go through `sysop` vocabulary (`sysop wifi connect`, `sysop notifications mute`) that does not change when the underlying tool does. The tool's actual name is findable — `sysop info <role>` prints what fills the role today and where its config lives — and configuration edits still happen in the underlying tool's native files (principle 14 forbids wrapping those). What we eliminate is the trivia tax: the cognitive overhead of remembering which specific package fills which role on this specific OS. The role names are the contract; the package names are documentation.
66
67 16. **Tight scope beats extensibility.** When a project ships a thriving plugin ecosystem and an unconfigured base, it has usually pushed its design decisions onto its users — and every operator ends up with a slightly different, slightly broken assemblage. Mountaineer prefers tools whose authors made the calls themselves — helix over neovim, zellij over tmux-plus-tpm — and uses them close to their default configuration. The deeper bet: in an era where the meaningful contribution of most projects is the *idea*, a clean re-expression of an idea (helix, nushell, s6) is more valuable than a legacy project patched to bolt the idea on. We back the rebuilds. This applies to Mountaineer itself: someday someone with better taste will come along and express this idea more clearly, and the people running Mountaineer will move on. That is the project working as intended.
68
69 17. **Backwards compatible with hardware, not with software.** Operators are expected to run the most recent Mountaineer release. We do not maintain LTS branches, support shims, or migration scaffolding for users on stale versions; if a better default arrives and Alpine packages it, we adopt it and the old behavior goes. Principle 11 is the upstream face of this: Alpine's release train is our release train. The downstream face is that we ask operators to stay current and we make staying current cheap by writing the migration notes for every break. Hardware is the opposite. We do not narrow Alpine's architecture support, we do not require recent silicon to use the system, and we resist any default that would obsolete a machine that physically still works. The cost surfaces are different — software backcompat costs maintainer time per release, hardware backcompat costs runtime detection code we write once and amortize — and the asymmetric stance is how a small team ships forward motion without leaving operators stranded on yesterday's hardware.
70
71 ## What we pick, and why (summary — defenses live in `STACK.md`)
72
73 - **Package manager**: apk. One of the best-designed in existence. `/etc/apk/world` treated as a first-class config file.
74 - **Init**: s6 + s6-rc. Smaller, more composable, and more honestly designed than either OpenRC or systemd. Mountaineer ships its own s6-rc service definitions for every daemon in the stack — core project work, not a side effort.
75 - **Shell (`/bin/sh`)**: busybox ash. Already the Alpine default. Stays.
76 - **Shell (interactive)**: nushell. Structured pipelines are the actual future. The cost is real and we pay it on purpose.
77 - **Coreutils userland**: busybox stays as system glue; ripgrep + fd + sd + bat + eza + dust + procs + bottom + zoxide + lnav + starship sit on top under conventional names. Not aliases — defaults.
78 - **Editor**: helix. Modal, batteries-included, no plugin culture. Tree-sitter and LSP are not optional features.
79 - **Multiplexer**: zellij. Discoverable, layouts as config, no thirty-year keybinding archaeology.
80 - **Container runtime**: podman, rootless, with a Mountaineer-authored `podman-s6` template library (quadlet analog for s6-rc).
81 - **Reverse proxy**: Caddy. Auto-HTTPS is the correct default.
82 - **Firewall**: nftables directly, with a documented default ruleset. No `awall`.
83 - **Resolver**: unbound, local validating recursive, DoT upstream.
84 - **Time**: chrony.
85 - **Network config**: ifupdown-ng (wired) + iwd (wireless). No NetworkManager.
86 - **Logs**: Vector as the router, structured files on disk, lnav for browsing.
87 - **Secrets**: age + sops.
88 - **Backup**: restic.
89 - **Outbound mail**: msmtp. OpenSMTPD is the documented upgrade for relay use cases; they share `/usr/sbin/sendmail` and are mutually exclusive.
90 - **Filesystem**: ZFS on root. Native encryption, snapshot/rollback, send/recv. Mountaineer ships the installer, dataset layout, and zfsbootmenu integration. Largest engineering commitment in the stack.
91 - **SSH**: OpenSSH, hardened defaults (no passwords, no root login, modern KEX/cipher/MAC only).
92 - **Graphical layer (Sysadmin build only)**: river (compositor) + foot (terminal) + yambar (bar) + fuzzel/cliphist (launcher/clipboard) + fnott (notifications) + grim/slurp/swappy (screenshots) + swaylock/swayidle/swaybg + pipewire/wireplumber (audio) + seatd + brightnessctl + tlp. Browser: explicitly no default.
93
94 Each pick must satisfy: (a) Alpine packages it, (b) we can defend the choice in a paragraph, (c) we can configure it once well enough that most users never touch the config.
95
96 ## A second purpose: adoption is the bottleneck
97
98 For a meaningful number of the tools Mountaineer ships, the design debate is effectively over. s6 is a better-designed init than its competitors. nushell's structured pipelines are not a controversial idea anymore. Caddy's auto-HTTPS is the position every reverse proxy will eventually adopt. What blocks these tools from being defaults is not technical merit; it is **the cost of being the first person on your team to use them**.
99
100 Mountaineer's second job is to absorb that cost on behalf of its users.
101
102 When we ship s6 as the init, every Mountaineer user is an s6 user. When we ship nushell as the interactive shell, every Mountaineer user writes nushell. The service definitions, the configuration patterns, the documentation, the day-two operational knowledge — we generate it, refine it, and publish it. The friction that keeps good tools at single-digit adoption is the friction we eat.
103
104 This is not advocacy. It is a design choice with a downstream effect we welcome. If Mountaineer ends up nudging the broader Alpine ecosystem toward s6 service definitions, or contributing nushell configurations that get upstreamed, that is the project working as intended.
105
106 ## A third purpose: leave the route better than we found it
107
108 Mountaineer is built for people who *can* build good systems. That capability comes with a corresponding responsibility to the tools we rely on.
109
110 Real climbing communities operate on a shared ethos: you maintain the trail, you replace the worn anchor, you carry out more than you carry in, you write up the route so the next party has better information than you did. The skill of the community is what keeps the mountain climbable.
111
112 The Mountaineering community holds itself to the same standard with software. The tools we ship — s6, nushell, helix, Caddy, podman, Vector, Alpine itself — are mostly maintained by small groups of people working with limited resources on work that benefits everyone downstream. We use their work directly. We benefit from it disproportionately.
113
114 In return, we contribute back. Patches upstream. Documentation written and offered to the projects whose docs we found wanting. Bug reports filed carefully, with reproductions. s6-rc service definitions submitted to Alpine packaging when ours are good enough. Maintainer time funded when the project accepts it.
115
116 This is not a code of conduct or a contribution quota. It is a description of the kind of community Mountaineer expects to attract and the kind of work that community is expected to be doing alongside running the OS. If you are using Mountaineer, you are probably the kind of person upstream wants a patch from.
117
118 ## Where we refuse to default
119
120 We understand that there are some tools, like web browsers, where almost every user will want one and yet there is no default we feel comfortable endorsing. In these situations, Mountaineer will encourage users to research the options provided by the Alpine package manager rather than invent editorial guidance for tools we ourselves would not endorse.
121
122 This is rare and deliberately so. Most categories have a defensible pick, and refusal is reserved for the cases where the entire category is broken in ways curation cannot fix. Where it applies, we say so plainly in the install flow and in `STACK.md`, and we hand off to Alpine's repositories as the place to look. Refusing to default is not the same as having no opinion — it is the opinion that we should not pretend to have one.
123
124 ## What we will not do
125
126 - We will not ship Docker. Podman is the design we agree with.
127 - We will not ship bash as the interactive shell. It is available via `apk add` for the people who insist.
128 - We will not ship systemd-anything. We are on OpenRC's cousin by design.
129 - We will not write a configuration GUI. The configuration *is* the interface.
130 - We will not maintain backwards compatibility with our own past defaults across major releases. If a better option emerges and Alpine packages it, we switch and document the migration.
131 - We will not chase users who want a familiar system. Other distributions exist and are good.
132
133 ## Who this is for
134
135 - Operators who run a small fleet of servers and are tired of yak-shaving every fresh install.
136 - Sysadmins who want a personal infrastructure machine — homelab, VPS, bastion — that comes pre-wired correctly.
137 - People who have read enough man pages to know that "the way it's always been done" is usually a fossil.
138 - People who would rather learn one well-designed tool than memorize three legacy ones.
139
140 ## Who this is not for
141
142 - Anyone whose primary requirement is "my existing scripts must keep working."
143 - Anyone running production workloads who cannot tolerate occasional sharp edges from being on a smaller distribution.
144 - Desktop users who need Electron apps, proprietary drivers, or the latest GPU stack.
145 - Anyone who finds the above tone insufferable. The tone is the product.
146
147 ## Versioning and release channels
148
149 Mountaineer's version is `<alpine-release>.<our-iteration>`. Example: `3.21.0`, `3.21.1`. When Alpine moves to 3.22, Mountaineer moves to 3.22. Between Alpine releases, we ship overlay-only iterations.
150
151 Release builds are called **Dull Edge** — ahead of the consensus, behind the frontier. A Dull Edge build is what you install on a machine you care about. Pre-release and development builds use other channel names; only Dull Edge is supported.
152
153 ## Status
154
155 Pre-v0. This document is the project. Code follows.
156