Skip to content

Repository files navigation

nix-components

Reusable Nix modules shared across multiple host configurations (macOS via nix-darwin, NixOS servers, etc.).

Usage

Add this repo as a flake input and import the modules you need:

inputs.nix-components.url = "github:ajmarkow/nix-components";

Then import individual modules in your host or home-manager config:

imports = [
  inputs.nix-components.homeModules.zsh
  inputs.nix-components.homeModules.neovim
  inputs.nix-components.homeModules.claude-code
];

Modules

All files under modules/ are auto-exported as homeModules.<name> (filename without .nix).

Module Description
claude-code.nix Claude Code with statusline, rtk-rewrite hook, plugins, and skill marketplaces
claude-code-claude-md.nix Writes ~/.claude/CLAUDE.md from modules/lib/claude-md-content.nix
opencode.nix opencode with the Muse contributor provider; writes ~/.config/opencode/AGENTS.md from the same modules/lib/claude-md-content.nix
firefox.nix Firefox with dark theme, extensions, and policies
git.nix Git with GPG commit signing and gpg-agent
mcp.nix Central mcpm aggregate client: points every agent at the tailnet endpoint; opt-in server mode runs the aggregate
neovim.nix Nixvim config: LSP, blink-cmp, pulse.nvim, catppuccin, oil, neo-tree, trouble, and more
neovim-wezterm.nix Smart-splits plugin and keybindings for Wezterm pane navigation
packages.nix Common home packages shared across all hosts
restic.nix Restic backups to a shared Backblaze B2 bucket, one repository per host. Linux only
starship.nix Starship prompt
wezterm.nix Wezterm with Catppuccin, smart-splits, and Fira Code
zsh.nix Zsh with oh-my-zsh, vi-mode, direnv, zoxide, and shared aliases

modules/lib/ is not scanned into homeModules (only top-level files under modules/ are) — it holds claude-md-content.nix, the shared CLAUDE.md/AGENTS.md prose imported by both claude-code-claude-md.nix and opencode.nix. Edit prose there, not in either consuming module.

restic.nix

Wraps home-manager's services.restic. Each host backs up to its own subdirectory of one shared B2 bucket, so hosts never contend for a repository lock.

imports = [ inputs.nix-components.homeModules.restic ];

services.resticBackup = {
  enable   = true;
  hostName = "nix-server";          # -> b2:aj-backups:nix-server
  bucket   = "aj-backups";
  paths    = [ "/home/aj/documents" ];
  exclude  = [ "**/node_modules" ];
};

Defaults: daily timer with Persistent = true, repository auto-created on first run, and restic forget --prune after each backup keeping 7 daily, 4 weekly, and 6 monthly snapshots.

Options this module does not re-export are reachable directly, because it writes into the same option tree:

services.restic.backups.b2.runCheck = true;

Restores need no manual environment setup — createWrapper puts restic-b2 on PATH with the repository and credentials already set:

restic-b2 snapshots
restic-b2 restore latest --target /tmp/restore

Linux only. On macOS upstream creates the wrapper script and nothing else, so the module asserts rather than pretending to be scheduled. See home-manager issue #7924.

Required secrets, provisioned out of band on each host. Never generated by Nix, never committed:

File Contents Mode
/etc/nixos/secrets/restic.env B2_ACCOUNT_ID=... and B2_ACCOUNT_KEY=... 0400
/etc/nixos/secrets/restic-password The repository password, alone on one line 0400

Both paths are configurable via services.resticBackup.environmentFile and services.resticBackup.passwordFile.

OS modules

System-level modules are exported as darwinModules.<name> and nixosModules.<name>. They live in os-modules.nix and os-modules/, not modules/, because they set NixOS / nix-darwin system options rather than home-manager ones. Import them in the host module list, next to your other system modules:

modules = [
  inputs.nix-components.darwinModules.home-manager-backup
  inputs.nix-components.darwinModules.determinate
];
Module Platforms Description
home-manager-backup darwin, nixos Shared backupFileExtension + overwriteBackup so activation replaces a stale <file>.backup instead of aborting on it
determinate darwin, nixos Determinate Nix, plus the shared binary caches and trusted users for every host
tailscale darwin, nixos Tailscale client daemon, the tailscaled operator grant the diff-viewer module needs, and an opt-in health watchdog
mcpm-serve nixos Publishes the local mcpm aggregate as the Tailscale Service svc:mcpm (nix-server only)
beszel-agent darwin, nixos Beszel monitoring agent, registering itself with the hub from a universal token read out of a file
restic-backup darwin Restic backups to the shared Backblaze B2 bucket, as a root launchd daemon. Darwin counterpart to homeModules.restic

determinate

Wraps the upstream determinate module and adds the cache baseline. Options:

Option Description
nix-components.determinate.substituters Binary caches, added as extra-substituters. Hosts append to the baseline
nix-components.determinate.trustedPublicKeys Matching public keys, added as extra-trusted-public-keys. A cache without its key here is silently skipped
nix-components.determinate.trustedUsers Users granted trusted-users. Needed for nix to honor a flake input's own nixConfig substituters

All three are listOf str and merge across definitions, so a host adds only what is specific to it:

nix-components.determinate.trustedUsers = [ "ajmarkow" ];

Two things differ by platform, both because of upstream:

  • macOS needs the installer first. Determinate manages the Nix installation outside nix-darwin, so run the macOS package on the machine before the first activation. The module sets nix.enable = false, which makes nix-darwin restore whatever unmanaged daemon is in /nix/var/nix/profiles/default and stop managing /etc/nix/nix.conf. Activating first therefore leaves the host with no experimental-features, so no flakes, so no way to rebuild back out. NixOS needs no installer — that module is fully declarative.
  • Garbage collection is not the same setting. On darwin, nix.enable = false makes nix.gc and nix.optimise silently inert, so GC is delegated to determinate-nixd (garbageCollector.strategy = "automatic", free-space driven). On NixOS nix.* still works and upstream exposes no GC option, so the module sets nix.gc to weekly / --delete-older-than 14d. Override per host with nix.gc; every value is a mkDefault.

A host that adopts this module should delete its own nix.settings.experimental-features, nix.gc, nix.optimise, and nix.settings.trusted-users. Determinate enables nix-command and flakes by default, and on darwin the rest is dead code that still reads as live config.

tailscale

Enables services.tailscale and grants a user "operator" rights over the local tailscaled. The grant is what lets the diff-viewer module run tailscale serve as a normal user — without it, diff URLs 404 and tailscaled logs Access denied: serve config denied. Importing the module enables tailscale; there is no enable option.

Option Description
nix-components.tailscale.operator User granted tailscaled operator rights. null (the default) grants nobody
nix-components.tailscale.healthcheck.enable Poll tailscale status every 2 minutes, restart tailscaled when down or hung, alert after 3 consecutive failures. NixOS only, off by default
nix-components.tailscale.healthcheck.secretsFile File defining SHOUTRRR_URL for those alerts. Defaults to /etc/nixos/secrets/tailscale-alert.env, root-owned 0600, optional at run time
nix-components.tailscale.operator = "ajmarkow";

Three things differ by platform:

  • The operator grant uses a different mechanism. NixOS has services.tailscale.extraSetFlags; nix-darwin declares only enable, package and overrideLocalDns, so darwin runs tailscale set --operator= from system.activationScripts instead. Idempotent, and non-fatal if tailscaled is not up yet on first activation.
  • darwin pins package = pkgs.tailscale. The sandboxed Mac App Store Tailscale app cannot run tailscale serve, and homebrew.onActivation.cleanup will not remove a MAS-installed app — so remove it by hand if one is present.
  • The healthcheck is NixOS-only. It is a systemd service plus timer, and the darwin variant asserts the option is off rather than ignoring it. Restart=on-failure / RestartSec=5s on tailscaled is set on NixOS unconditionally; it covers crashes, while the watchdog covers a hung-but-alive daemon and a stalled tailnet link.

Firewall rules, sshd gating, and per-service tailnet port exposure stay host-specific — they name each host's own services and there is nothing shared to factor out.

beszel-agent

Runs the Beszel agent and points it at a hub. Agent only — the hub stays a container in whichever host repo serves it. Importing the module runs the agent; there is no enable option.

The agent authenticates with a universal token, so no host has to be added in the hub's UI first: it opens a WebSocket to hubUrl, presents the token, and registers itself. The same token works on every host, so one Infisical value (BESZEL_UNIVERSAL_TOKEN) covers all of them.

Option Description
nix-components.beszel.agent.hubKey Hub's public key, from its "Add System" dialog. Required, no default
nix-components.beszel.agent.hubUrl Hub URL the agent dials out to. Required, no default
nix-components.beszel.agent.tokenFile File holding the universal token, root-owned 0600. Defaults to /etc/nixos/secrets/beszel-token on NixOS and /etc/beszel-agent/token on darwin
nix-components.beszel.agent.port Listen port, default 45876. Only the inbound hub→agent path uses it
nix-components.beszel.agent.openFirewall Open that port. NixOS only, off by default
nix-components.beszel.agent.smartmon.enable Let the agent read S.M.A.R.T. data via smartctl. NixOS only, off by default
nix-components.beszel.agent.extraEnvironment Extra agent environment variables, merged over the module's. Public — they land in the Nix store
nix-components.beszel.agent = {
  hubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...";
  hubUrl = "https://beszel.example.com";
  smartmon.enable = true;
};

hubKey and hubUrl deliberately have no defaults. This repo is public; the host repos that consume it are not. hubKey is a public key so committing it would leak no credential, but both values identify the hub, so they are set per host. The token — the one value that is a credential — never enters the Nix store at all.

Everything else about the two platforms differs, because only one of them has an upstream module:

  • NixOS wraps nixpkgs' services.beszel.agent; darwin is a hand-rolled launchd daemon. nix-darwin has no beszel module, so the darwin variant runs pkgs.beszel's beszel-agent from launchd.daemons, as root, replacing a hand-installed henrygd/beszel Homebrew formula. It also sets DATA_DIR, which the NixOS module leaves to the agent's own default — a launchd daemon starts in / with no writable cwd.
  • The token reaches the agent differently. On darwin the daemon is root and reads tokenFile directly. On NixOS the agent is a DynamicUser under ProtectSystem=strict and cannot read a root-owned 0600 file, so the module passes it through LoadCredential and sets TOKEN_FILE to the staged copy under /run/credentials/. Chowning the secret to beszel-agent instead would need that user to exist when the secret is provisioned, and on a fresh host provisioning runs before the first rebuild creates it.
  • openFirewall is not upstream's. nixpkgs' own openFirewall picks its port from environment.PORT, but the agent's option is LISTEN — a non-default port would open 45876 while the agent listened elsewhere. This module pins upstream's to false and writes the firewall rule itself.
  • openFirewall and smartmon.enable are NixOS-only. The darwin variant asserts both are off rather than ignoring them: macOS has no nix-darwin-managed firewall, and smartmon is a udev rule plus the disk group plus two Linux capabilities.

restic-backup

Darwin counterpart to homeModules.restic. nix-darwin has no restic module at all to wrap — unlike beszel-agent/tailscale/determinate, there is no upstream implementation on this platform, Linux or otherwise — so this is a plain root launchd daemon around the restic binary, same idiom as beszel-agent's darwin variant: root reads the secret files directly off disk.

nix-components.resticBackup = {
  enable   = true;
  hostName = "nix-mac";          # -> b2:aj-backups:nix-mac
  bucket   = "aj-backups";
  paths    = [ "/Users/aj/Documents" ];
};
Option Description
nix-components.resticBackup.hostName This host's subdirectory in the shared bucket. Required, no default
nix-components.resticBackup.bucket Shared Backblaze B2 bucket name. Required, no default
nix-components.resticBackup.paths Paths to back up. Required, no default
nix-components.resticBackup.exclude Exclude patterns, default [ ]
nix-components.resticBackup.pruneOpts restic forget --prune policy, default 7 daily / 4 weekly / 6 monthly
nix-components.resticBackup.environmentFile File holding B2_ACCOUNT_ID/B2_ACCOUNT_KEY. Default /etc/nix-darwin/secrets/restic.env
nix-components.resticBackup.passwordFile File holding the repository password. Default /etc/nix-darwin/secrets/restic-password
nix-components.resticBackup.calendar launchd's StartCalendarInterval, default { Hour = 3; Minute = 0; }

restic-b2 lands on PATH for manual restores, same as the Linux module's wrapper:

restic-b2 snapshots
restic-b2 restore latest --target /tmp/restore

launchd's StartCalendarInterval has no Persistent-style catch-up: a Mac asleep at the scheduled time simply misses that day's backup rather than running it late once it wakes.

Packages

Custom packages under pkgs/ are exported as packages.<system>.<name>:

Package Description
summarize CLI tool for summarizing and transcribing URLs, videos, podcasts, and files
organize-tool File management automation tool. Not yet in nixpkgs, so this repo packages it
semble Semantic code search tool, built from the semble-workspace uv2nix project

Skills

Claude Code skills under skills/ are bundled into claude-code.nix at build time via the paseo-skills flake input:

Skill Description
adding-to-claude-md Add new rules or guidance to modules/lib/claude-md-content.nix
c-and-p Commit all staged and unstaged changes with a conventional commit message and push
debug-with-llms Debug AI-assisted code without looping, patching symptoms, or writing slop
deploy-nix-components Deploy a nix-components change and bump the flake input on nix-server
paseo-send Find a running paseo agent by repo name and send it a message
ponytail Push for the simplest, shortest working solution on any coding task
serve Serve a local file or directory publicly through an ngrok tunnel
ste-writing Write or edit documentation in Simplified Technical English
summarize Summarize or transcribe URLs, YouTube videos, podcasts, PDFs, and local files
taste-skill Design landing pages and portfolios that avoid a templated look
test-driven-development Follow test-driven development before writing implementation code
tighten Refactor AGENTS.md following progressive disclosure principles

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages