No description
  • Nix 56.1%
  • QML 38.1%
  • Shell 4.4%
  • Python 1.4%
Find a file
Arne Skaar Fismen cf001ff26f Leave root's shell alone
users.defaultUserShell covers root, so shipping fish as the login shell gave
root fish as well — found by deploying it and having "ssh root@host 'a=1'" come
back as a fish syntax error.

Root is the account you reach for when the machine is broken, and it wants the
opposite things from a login shell: POSIX syntax that every script and one-liner
assumes, no plugins, no prompt, nothing that has to build before you can get a
prompt at all. A login shell is a taste; root's shell is a recovery tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxYTTeGcEEEioi5mNXcgnN
2026-08-23 09:32:00 +02:00
dev Stop writing niri config as strings, and take the compositor 2026-08-22 10:23:48 +02:00
docs Draw the documentation site in fjord, and drop mdbook 2026-08-23 09:23:34 +02:00
nix Leave root's shell alone 2026-08-23 09:32:00 +02:00
shell Make the sea green the accent, because it always was 2026-08-22 23:24:52 +02:00
templates/default Ship a shell and a browser, and make the browser default 2026-08-22 22:17:21 +02:00
.envrc M1: Quickshell scaffold, niri event client, and the status-space guard 2026-08-16 08:43:20 +02:00
.gitignore Write the on-ramp, and give the documents a front door 2026-08-22 14:08:37 +02:00
flake.lock Include herdr, and generate its theme rather than copying one 2026-08-22 23:03:28 +02:00
flake.nix Include herdr, and generate its theme rather than copying one 2026-08-22 23:03:28 +02:00
NOTES.md Make the sea green the accent, because it always was 2026-08-22 23:24:52 +02:00
PLAN.md Include herdr, and generate its theme rather than copying one 2026-08-22 23:03:28 +02:00
README.md Declare fjord's options once, on the system side 2026-08-22 21:46:17 +02:00
STYLE.md Generate the palette and layout pages, and make Theme.qml the only copy 2026-08-23 00:05:28 +02:00

fjord

A Linux distribution built on NixOS, with niri as its compositor.

fjord is the system, not an addition to one. It decides the compositor and how it behaves, the keys, the look, the lock screen, the launcher, the login, the firewall, nix policy, boot policy and how applications are declared — the way a distribution decides those things. What is left in a fjord machine's own configuration is its hardware and who it belongs to, and that is close to literally all of it.

NixOS is the substrate rather than the thing you are running, the same way Arch is the substrate under Omarchy. You get NixOS's guarantees for free — every update atomic, every version a generation you can boot back into — without having to write a NixOS configuration to get a desktop.

It looks like a well-set terminal: IBM Plex Mono at every level, dense readouts, a straight value edge, and colour almost never. Terminal typography, not terminal decoration — nothing is drawn with ASCII art, because a frame pinned to a character grid loses every argument it has with a compositor that sizes the window. The reasoning is STYLE.md.

Installing

There is no fjord ISO yet — that is M14 — so you boot NixOS's own installer, partition and mount as usual, and then:

nixos-generate-config --root /mnt
cd /mnt/etc/nixos
nix flake init -t git+https://code.bas.es/bases/fjord.git

That writes four files. Edit the three names at the top of flake.nix — hostname, username, architecture — and install:

nixos-install --flake /mnt/etc/nixos#<hostname>

The account has no password until you give it one: nixos-enter, then passwd <username>. Reboot into a greeter, log in, and press Mod+G.

The template's own README.md covers the rest. Answering questions and partitioning for you is M15; today the NixOS installer does that part.

What you get

Mod+G opens the status space — a workspace holding nothing but fjord's own columns, five of them, each a fixed 380 logical pixels wide so a bigger screen shows more of them rather than wider ones:

glance time, date, user, wm, space, battery, network, audio
notifications the live feed, and everything still held
system machine state
agenda today
settings the interactive column

They are ordinary niri windows, and fjord draws no focus indication on them at all: the compositor already has an answer, so the columns are marked focused however every other window is. That is the rule fjord follows everywhere — where the compositor has an answer, take it, and draw only what it cannot.

Every other workspace carries a 32px rail instead, which says the same things as marks rather than words: a dot per workspace, a battery column that fills from the bottom, wifi bars, a two-row clock. A panel is something you go and read; the rail is something you catch out of the corner of your eye.

Underneath that: niri configured whole — the full keybind set, the layout, the springs, the palette on the focus ring — plus a lock screen, idle handling, notifications, a launcher, a clipboard history, the greeter, portals, audio, GTK and Qt themed dark, a firewall, weekly garbage collection, and a bounded journal. Mod+Shift+/ lists every key.

What stays yours

A fjord machine's configuration is short, and this is the whole of what is in it:

  • hardware-configuration.nix and the disk layout
  • the boot loader and the timezone
  • your outputs, keyboard and touchpad
  • who you are — a username and a hostname, the two values fjord refuses to guess, so leaving either unset is an evaluation error rather than a machine that quietly belongs to somebody else
  • which applications you want on it
  • anything you decide fjord got wrong

That last line is not a courtesy. Every opinion fjord holds is a mkDefault, because when a distribution supplies most of your configuration, a default you cannot override is not a strong opinion — it is a bug.

Changing fjord's mind

fjord invents no names for things NixOS already has, so disagreeing with it means setting the ordinary option:

# the look
programs.niri.settings.layout.gaps = 8;
programs.fjord.theme.fontSize = 15;      # type was chosen against a HiDPI panel

# one key — binds are an attrset, so yours merges with fjord's
programs.niri.settings.binds."Mod+T".action.spawn = "alacritty";

# system policy — no fjord name to learn, just the NixOS option
nix.gc.dates = "daily";
services.journald.extraConfig = "SystemMaxUse=2G";

# reaching the machine: off by default, correct when switched on
programs.fjord.remote.ssh.enable = true;        # key-only, no root; opens 22
programs.fjord.remote.tailscale.enable = true;  # direct connections, not relayed
programs.fjord.remote.mdns.enable = true;       # <hostname>.local actually resolves

# all of fjord's taste, keeping only what the shell needs to work
programs.fjord.niri.defaults = false;

The rule the system side follows: intent is yours, mechanism is fjord's. Whether this machine listens on the network is a fact about you; what a correct sshd looks like is a fact about NixOS. fjord answers the second and leaves the first alone — so sshd is off and already hardened, and unfree packages just evaluate instead of teaching you why they would not.

Colours are the one exception: not tunable, because a palette is an identity rather than a preference.

Declaring an application

One statement, five outputs that cannot disagree with each other:

programs.fjord.workspaces = [ "web" ];

programs.fjord.apps.browser = {
  package = pkgs.firefox;
  appId = "^firefox$";
  workspace = "web";
  bind = "Mod+B";
};

That installs it, writes the window rule that catches its windows, creates the workspace they open on, binds the key, and puts it in the launcher. Doing the same by hand means four files that have to agree, and every way of getting it wrong is silent — a mistyped app-id matches nothing and the window opens where it always did; an undeclared workspace makes the rule a no-op. Here the workspace is checked at evaluation, the rule is generated from the same declaration the launcher runs, and the bind spawns a store path rather than a hopeful name.

Already running NixOS?

You do not have to reinstall. fjord's profiles are ordinary NixOS modules, and taking a slice is supported — import fjord.nixosModules.desktop alone if nix policy, the login and the firewall are already somebody's job, or nixosModules.default for all of it:

fjord.nixosModules.default

programs.fjord.enable = true;          # the shell
programs.fjord.desktop.enable = true;  # compositor, greeter, portals, audio
programs.fjord.system.enable = true;   # nix policy, the login, networking
programs.fjord.identity.username = "ada";
programs.fjord.identity.hostName = "kestrel";

All of it is one option surface, on the system side. fjord's home-manager module declares nothing and is imported for the files it writes — the shell's user service, niri's config, GTK and Qt — reading everything above through osConfig. That means home-manager has to run as a NixOS module, which was already true: niri's own settings tree is only in scope that way.

What it costs

Honestly, and in the order you will meet it:

  • nixos-unstable. fjord needs Quickshell 0.3.0, which the stable channel does not have.
  • niri. fjord is not portable to another compositor and is not trying to be. A scrollable-tiling workspace that fjord holds row 1 of is not a theme.
  • Three columns are not finished. glance and notifications read real data. system reports what niri tells it and not yet cpu, memory or disk; agenda has no data source decided; settings is placeholders for shape.
  • No releases yet. No tags and no stable channel, so pinning means pinning a commit.
  • No screen reader. niri's stock config binds one; fjord does not ship orca, and that is a gap rather than a decision anyone should be happy with.

Where it is

PLAN.md what fjord is, why the boundary sits where it does, and the milestones
STYLE.md the visual language and the reasoning behind it
NOTES.md the audit trail — what was verified, on what, and what surprised us
nix build .#docs the documentation site, with every option generated from the module

Status

Working: the compositor and its whole configuration, the five columns, notifications, launcher, clipboard, lock and idle, the greeter, portals, audio, GTK/Qt theming, programs.fjord.apps, the system profile, and remote access.

Outstanding: real data in three columns (M5), multi-output (M7), a live ISO (M14), an installer (M15), releases and an update command (M17), the terminal half — shell, editor, multiplexer — and a settings surface (M20).

fjord is developed against two machines and installed on a third that is nothing but a consumer of it, with no configuration around it to paper over what fjord forgets to ship. Most of what this repo knows was found that way rather than designed.