Sydnix

unimplemented! (keyword){:type "keyword", :affiliated {}, :key "TAGS", :value "nix", :position {:start {:line 7, :column 1, :offset 145}, :end {:line 8, :column 1, :offset 157}}}

A second try at NixOS, now that I have a better idea of what I'm doing. The effort is largely inspired by my darling Faye's glorious wishsys.

The Sydnix repo hosts the system and home configurations for my personal computers (the topic of this particular document), my Emacs config, a few oddballs such as my Team Fortress 2 and Quake Live configs, and my home server.

Inbox

Conventions

User configuration

In order of descending preference, user programs should be configured by...

  1. home-manager's modules.

  2. Wrappers, with config files optionally living somewhere under /persist/dots.

  3. home.file and similar.

  4. Mutable symlinks using home.file and mkOutOfStoreSymlink.

doge

Repo structure

hosts/

Each directory hosts/«HOST» is expected to configure a single specific device, where «HOST» is the device's exact hostname. Said directory «HOST» should have at least the following structure:

hosts
└── «HOST»
    ├── configuration.nix
    └── system.nix

Where configuration.nix is the main entry point imported by the top-level flake, and system.nix evaluates to a string corresponding to the system option to be used.

users/

Similarly to the hosts directory, each sub-directory users/«USER» is assumed to have some structure, and that «USER» is the precise username desired.

users
└── «USER»
    └── default.nix

Where default.nix returns an attrset of form

{
  # The NixOS option `users.users.«USER»` will be set to result of
  # `systemConfiguration`.
  systemConfiguration = { config, pkgs, lib, ... }: {
    # isNormalUser = true;
    # extraGroups = [ "wheel" ];
    # ...
  };

  # Home-manager configuration
  homeConfiguration = { config, pkgs, lib, ... }: {
    # home.packages = [ pkgs.hello ];
    # ...
  };
}

modules/

modules
├── home
│   └── «Various home-manager modules»…
└── nixos
    ├── defaults
    │   └── «NixOS modules that are *enabled by default*»…
    ├── deertopia
    │   └── «NixOS modules that are *specific to Deertopia*»…
    └── «Various NixOS modules»…

Impermanence and persistence

I use impermanence to wipe most of my file-system on boot.

Boot process

What follows is an overview of modules/nixos/impermanence/rollback.nix.

On boot, ...

  • The existing subvolume root filesystem will be moved to a 'death row' directory, where it will live for about three days before deletion. Precisely, «btrfs-filesystem»/«root-subvolume» is moved to «btrfs-filesystem»/old-roots/«timestamp». The brief grace period allows for easy recovery in the (very common) case where files are unintentionally deleted due to the user's silly human negligence.

  • A new, blank subvolume is created in place of the previous. Precisely, the subvolume «btrfs-filesystem»/«root-subvolume» is created.

  • Any subvolumes under «btrfs-filesystem»/old-roots older than three days are deleted.

The /persist directory

/persist/root

Persistent files to be linked into the real root, /. These are managed by Impermanence.

/persist/home/«user»

Persistent files to be linked into the analogous location under the real home, /home/«user». These are managed by Impermanence.

/persist/vault/«user»

Persistent files belonging to specific users. This differs from the persistent home directories in that files are not necessarily linked anywhere.

/persist/private-keys

The private key counterparts to the keys listed in this repository's own public-keys directory.

Deferring Emacs packages

Nearly all configuration of Emacs packages happens under the use-package macro. use-package has various keywords with special syntax for common tasks, such as instrumenting hooks, setting keybindings, and customising variables. You may be surprised to learn that these are not just syntactic sugar }:) (I was).

As an example, this

(use-package which-key
  :hook (on-first-input . which-key-mode)
  :custom
  (which-key-allow-evil-operators t)
  (which-key-show-operator-state-maps t)

is not the same as this

(use-package which-key
  :config
  (add-hook 'on-first-input #'which-key-mode)
  (setq which-key-allow-evil-operators t)
  (setq which-key-show-operator-state-maps t)

The difference connects to another silly obsession of the Emacs hacker: startup time. use-package's special keywords will defer the loading of the package ([cite:@systemcrafters2021how]) . E.g., instead of loading which-key on startup, it will be loaded when the on-first-input hook is first called.

on-first-input is one of many useful hooks provided by the package on.el specialised for fine-grained control of package loading.

Naming conventions in my Emacs config

As with the rest of the config, these are largely adapted from Doom's ([cite:@lissner2022contributing]). }:3

syd-[-]«NAME»

Typical ‘namespaced’ Elisp convention ([cite:@gnu2020conventions]).

«NAME»-h

Procedure defined specifically to be added to a hook.

«NAME»-a

Procedure defined specifically as advice.

Hosts

nixos-testbed

Configuration for the VM I'm currently using as a test-bed, before moving to my real desktop.

deertopia

My home server.

Users

crumb

Me }:). My primary user for programming and playing TF2.

lain

A bit on the nose for a transfemme into computers, but my chosen name is also Madeleine.

Used as a server admin account with little configuration.

sydnix-cli

sydnix-cli is a command-line utility written in Clojure wrapping various Sydnix-related scripts.

Tasks

PROJ [#A] Documentation

Module API/option docs
unimplemented! (statistics-cookie){:type "statistics-cookie", :begin 6397, :end 6403, :value "[2/30]", :post-blank 0, :position {:start {:line 181, :column 33, :offset 6397}, :end {:line 181, :column 39, :offset 6403}}}

Concepts
unimplemented! (statistics-cookie){:type "statistics-cookie", :begin 7932, :end 7935, :value "[/]", :post-blank 0, :position {:start {:line 214, :column 19, :offset 7932}, :end {:line 214, :column 22, :offset 7935}}}

PROJ [#A] Code clean-up

Remove uses of with lib;

Grep for with lib

Refactor modules/ directory

modules
├── home
│   ├── sydnix
│   │   ├── crumb
│   │   │   └── «My configurations»
│   │   └── «Personal Sydnix modules»
│   └── «Home-manager modules that could be upstreamed»
└── nixos
    ├── sydnix
    │   ├── webdav.nix
    │   └── deertopia
    │       └── «Deertopia-specific modules»
    └── «NixOS modules that could be upstreamed»

Import sydnix-cli and port-tools via flake's self.packages

Refactor default modules

All code in default.nix and defaults/ should be moved into individual modules. default.nix's role is then subjugated to enabling those modules and bundling them under a single enable switch.

Deprecate sydnix.deertopia.webdav

I believe Copyparty has a built-in WebDAV server. This would allow more refined permissions, and is one less required service.

sydnix.deertopia.nginx.vhosts should not set a root by default

It's currently only useful for the www and dav vhosts, and otherwise is nothing but a potential problem-causer.

lib.sydnix

  • mkMutableSymlink

  • listNixDirectory

Complete ten Nix TODOs

Grep Sydnix for "TODO"

Log completed TODOs here:

Refactor users/crumb/programs/*.nix into proper modules

IDEA Replace sops-nix with age-nix

Desktop environment

Niri

wbg

Waybar

Swaylock

IDEA Dispatch notification after completion of long-running command

Move this file into Org-roam and out of VC

Emacs

Move Nixcord input back to upsteam

My PR was merged

Firefox

Extensions

Settings

Search engines

Kagi
DuckDuckGo
Wikipedia
YouTube
Hackage
Hoogle
Nixpkgs
Nixopt

Rename user crumb to msyds

Bash

shopt -s histverify

Don’t execute expanded result immediately.

Add passwordFile option to programs.gh module

Add programs.glab module

Move inputs.* imports in nixosConfigurations to their own modules

Syncthing module shouldn't expose devices

Define services.syncthing.settings.devices as constant. Folders can still list devices by name, but not ID.

Dotfile management

This is about the management of configuration files. Nix wrappers, Nix modules, deployment, and the like.

Emacs

Managed by users/crumb/programs/emacs.nix.

See configuration notes here.

Bash

Managed by the Home-manager module.

(neo)vim

Managed by a hand-rolled wrapper. See users/crumb/programs/nvim.nix.

Readline

Declaratively install Jellyfin plugins

Disko

Refactor Disko configs to be more reusable

Replace uses of GPG with age

I don't know anything about either.

pass → passage

Firefox integration

Secret-management

Git config

Convenient way to wrap programs

wrapper-manager looks like it will be really annoying to integrate into my config without a NixOS/HM module.

tf2.nix

  • Declaratively configure tf2

  • tf2Packages?

  • Mastercomfig integration?

  • Define aliases / bindings from nix?

mpd

mpdscribble

syncthing

impermanence

sydnix.defaults module

Split up flake.nix into a file per output

default package sets

Some examples of what I mean:

  • defaultPackages.essential = true to install neovim and git;

  • defaultPackages.development = true for jj and Emacs.

  • defaultPackages.desktop = true for cantata, obs, vlc, vesktop, soulseek;

Naturally, one would expect some way to say "all these, except this and that."

Set environment variables

$EDITOR

See choose-editor.

System maintenance scripts

It might be a good idea to namespace these as subcommands of a single binary.

nix-clean

Collect garbage, clean up boot entries, delete old generations...

https://discourse.nixos.org/t/what-to-do-with-a-full-boot-partition/2049

choose-editor

  • If the Emacs daemon is running...

    • ...then run emacsclient.

    • ...otherwise, prompt the user to choose between Emacs and Vim

      • If Emacs: run emacs.

      • If Vim: In order, try nvim, vim, then vi.

        • If neither are available, use nix run nixpkgs#nvim

          • If this fails, try nano.

  • Support --pager

rage-edit

forget-host HOST

Remove a given host from ~/.ssh/known_hosts. Something like sed -i -e '/192.168.122.54/d' .ssh/known_hosts.

Confirm by printing diff.

scratch-dir [NAME]

Create a new tempdir called [NAME], and cd into it.

doctor / status

Run various checks on the system.

  • Sizes of caches. perhaps those listed by sydnix.impermanence.cache.{files,directories}? Offer to clean them if they're getting old or large.

  • Check for available upgrades. Flake inputs, overlays, emacs packages, etc.

persist status

List persistent files per user, and show their mount strategy.

  • Show info about sydnix.impermanence.cache values. Extend that option to have "description" and "consequences" fields, which are shown to users before clearing them. e.g.

      {
        sydnix.impermanence.cache.directories.".local/share/emacs/cache" = {
          description = "Root of all caches for Emacs and Emacs packages.";
          consequences = [
            "All known projects will be forgotten"
            "All trusted .dir-locals.el files and values will be forgotten"
          ];
        };
      }
    
     $ sydnix cache clear
     Recursively remove `~/.local/share/emacs/cache'?
     - All known projects will be forgotten
     - All trusted .dir-locals.el files and values will be forgotten
    [y/n]
    «more caches…»
    

persist prepare

Command sydnix persist prepare «file or directory» will

  1. Move a directory to /persist

  2. Optionally mount/bind/symlink it back into place.

  3. Print Nix code necessary to persist it.

Pre-references

The section after this one is a subset of the many places I've learnt from. Most important of all are Doom Emacs and Faye's Wishsys.

Doom Emacs was my gateway drug to Emacs, as well as a continually supportive parent as I've begun to move out — of course, that's a flowerism obfuscating the more direct statement "I've stolen a great deal of their code" }:).

Faye's dazzling Wishsys is an incredibly impressive 3-kloc NixOS config with several hosts, users, and a beautiful level of modularity. Her system has a number of quirks that initially raise eyebrows, but the questioning turns to awe once you understand she really knows what she's doing }:). Faye and her config are entirely responsible for inspiring and motivating my effort expended here, as well as being a wonderful reference as I re-learnt Nix from the ground up. In the most nerdy moment of my life, I've genuinely swooned over this damn config.

References