Hyprland drops the layer-shell surfaces on the remaining output when a monitor is removed and does not recreate them, so waybar and the awww wallpaper vanish on unplug. The socket2 watcher only reacted to `monitoradded`, which masked it — reconnecting re-fired the add and restored both. Handle `monitorremoved` too, and on either event re-push the wallpaper and reload waybar (SIGUSR2 rebuilds its per-output bars). Renamed `wallpaper-hotplug` to `monitor-hotplug` since it now does both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NixOS + home-manager flake
A flake-based NixOS and home-manager configuration: a set of reusable modules plus a ready-to-run example host that assembles them. Use it either way:
- as a starting template — clone, swap in your hardware, rebuild; or
- as modules — import them into your own (private) flake.
Quick start (template)
nix flake init -t github:<owner>/<repo> # or: git clone …
# generate hardware config for THIS machine (the one file you must replace):
sudo nixos-generate-config --show-hardware-config > hosts/example/hardware-configuration.nix
# set your hostName + username in hosts/example/default.nix, then:
sudo nixos-rebuild switch --flake .#example
nixosConfigurations.example is a real, buildable machine assembled from the modules in
modules/. It doubles as living documentation: it shows exactly how the modules wire
together, so there's nothing to reverse-engineer. The only genuinely machine-specific file
is hardware-configuration.nix (NixOS generates it); everything else works from defaults
you can tweak.
Import the modules into your own flake
Add this repo as an input and assemble your machines in a private flake. Below is a
realistic example layout. Structurally only flake.nix is required — every other path
is just one way to organize your config; rename, merge, inline, or drop them freely. The one
bit of content a bootable machine can't skip is its hardware-configuration.nix
(nixos-generate-config output); the hosts/<host>/ directory is only where this example
keeps it.
your-inventory/
├── flake.nix # inputs (baseline + your nixpkgs) + nixosConfigurations.<host>
├── flake.lock
├── system/ # system-level modules
│ ├── regional.nix # time zone / keymap / locale (see Overriding)
│ ├── packages.nix # extra system packages
│ └── tailscale.nix # VPN / network topology
├── home/ # home-manager modules, one file per concern
│ ├── identity.nix # git identity, name/email
│ ├── ssh.nix # SSH hosts (topology — stays private)
│ ├── syncthing.nix # Syncthing peers
│ └── … # personal apps: browser, editor, chat, …
└── hosts/
└── myhost/
├── default.nix # hostname, user account, host-specific system bits
└── hardware-configuration.nix # `nixos-generate-config` output
The flake.nix that wires it together:
{
inputs = {
# You own nixpkgs, so your update cadence is yours — the baseline modules
# build against the versions you pin here.
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
baseline.url = "github:<owner>/<repo>";
baseline.inputs.nixpkgs.follows = "nixpkgs"; # baseline builds against YOUR nixpkgs
baseline.inputs.home-manager.follows = "home-manager";
};
outputs = inputs@{ self, nixpkgs, baseline, home-manager, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
baseline.nixosModules.default # generic system baseline
./system/regional.nix # your regional values (see below)
./hosts/myhost # hardware + hostname
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
# Inject your own inputs into your home modules; the public ones ignore them.
home-manager.extraSpecialArgs = { inherit inputs; };
home-manager.users.me.imports = [
baseline.homeModules.default # generic home baseline
./home/identity.nix # your git identity, name/email, …
./home/ssh.nix # your SSH hosts (topology stays private)
# … more personal home modules
];
}
];
};
};
}
This repo ships modules, not pinned packages: they build against the nixpkgs you
declare above, so you decide when packages move. Quickstart alternative: drop your own
nixpkgs and write nixpkgs.follows = "baseline/nixpkgs" to pin to this repo's tested
versions instead — one less thing to think about, but then your update cadence is whatever
this repo pins, not the reverse.
Your machine list, hostnames, and any private specifics stay in your flake — you pull only the generic modules from here.
Overriding the baseline
The public modules are a baseline you extend without forking. Three ways to shape them from your private flake, in rough order of how often you reach for them:
-
Neutral default → your real value. Region-agnostic settings ship as
mkDefaultplaceholders (console.keyMap = "us",time.timeZone = "UTC",en_USlocale). Set the real ones in a private module — nomkForceneeded:# system/regional.nix { time.timeZone = "Europe/Berlin"; console.keyMap = "de"; } -
local.*options. A module that exposes a knob puts it under thelocal.*namespace with a default. Override in one line, e.g. a fuller TeX Live:local.texlive.package = pkgs.texlive.withPackages (ps: [ ps.scheme-full ]);A few
local.*options exist precisely because a module pins a package to a nixpkgs other than the one you declare (a deliberate freeze that would otherwise reach you regardless of your own pin); the option is how you pull it back onto your nixpkgs. -
Extra modules. Anything with no public counterpart is just another module in the import lists above — personal apps, SSH/Syncthing topology, secrets wiring.
Architecture: three layers
Kept apart on purpose. When adding config, decide which layer it belongs to:
- Modules — generic, public (this repo). Reusable building blocks under
modules/, exposed asnixosModules.*/homeModules.*. Anything machine- or user-specific is a module option with a sane default — never hard-code personal values into a module. - Inventory — topology, private (your flake). The actual machines:
nixosConfigurations.<host>, per-hosthosts/<host>/(hardware, hostname), and topology-revealing config (SSH hosts, Syncthing peers, internal IPs). This is what you deploy from; it imports the modules above. Publish the modules, keep the inventory in a private repo. Theexamplehost here is a placeholder-filled stand-in for it. - Secrets — neither repo. Private keys, tokens, passwords never enter any repo.
Provide them as runtime files (
0600, out-of-band) or via a secrets manager that decrypts to a runtime path; nix references only the path.
Layout
flake.nix # inputs + outputs (nixosModules, homeModules, example host, devShells)
modules/ # the reusable library
hosts/example/ # the template host (placeholder hardware-configuration.nix)
Shared *.nix / *.lock live at the top level; per-host files under hosts/<host>/.
Chasing a shutdown/boot hang? modules/shutdown-debug.nix exposes local.shutdownDebug.*
(verbose systemd logging to kmsg, a tty9 debug shell, a shortened stop-timeout) — see its
header comment for how to read the evidence.