Imported from lytedev/nix (
AGENTS.md). Install upstream withnpx skills add lytedev/nix. Copyright stays with the author.
Agents
Instructions for AI coding agents working in this repository.
Shell
The interactive and login shell on this system — and on the remote hosts, for
both the primary user and root — is fish, not bash. Shell snippets in this
file, and any command you run over SSH or expect to run interactively, must be
fish-compatible.
Key differences from POSIX/bash to watch for:
- Variable assignment:
set var value, notvar=value. - No heredocs: fish has no
<<EOF. Write the file another way, or run the block under bash. - Logic/operators:
and/or/notare commands (not&&/||);;separates statements. Command substitution is(cmd), e.g.nix fmt -- (jj file list). - Running bash on purpose: for multi-statement scripts — especially over SSH,
where
ssh <host> '<cmd>'lands in fish — wrap them:bash -c '...', or pipe a script in withssh <host> bash -s < script.sh.
Repository Structure
lib/modules/nixos/— active NixOS module configurationlib/modules/home/— dead code, do not editpackages/hosts/— per-host NixOS configurationsdotfiles/— config files symlinked into~/.config/secrets/— SOPS-encrypted secretslib/deploy/— deploy-rs configurationlib/doc/— long-form documentationissues/— in-repo issue tracking
Issue Tracking
Issues are tracked as markdown files in issues/open/ and issues/closed/.
See issues/README.md for the convention. One file per issue, status is
directory-based (move between open/ and closed/).
- Before starting work, check
issues/open/for relevant context - When creating a new issue, add a
.mdfile toissues/open/with a# Titleheading - When resolving an issue, move its file from
open/toclosed/ - Use
**Labels**:and**Related**:metadata lines where helpful
Dotfiles Convention
Home-manager has been removed from this flake. User environment (symlinks, dconf
settings, files) is managed by a custom NixOS-native system in
lib/modules/nixos/user-env.nix using system.userActivationScripts.
- Symlinks: Configured via
lyte.userSymlinks(e.g. inlib/modules/nixos/shell-config.nix) - Files: Configured via
lyte.userFiles - dconf: Configured via
lyte.dconfSettings
Actual config content lives in dotfiles/ and is symlinked into ~/.config/ via
lyte.userSymlinks so it can be edited live without rebuilding.
Important: Always symlink individual files, never whole directories. Directory-level
symlinks prevent mutable files (like Helix's runtime/grammars/) from coexisting in
the same config directory.
Note: lib/modules/home/ still exists but contains dead code from the old
home-manager setup. The active configuration is in lib/modules/nixos/.
Version Control
This repo uses jujutsu (jj), not git directly. Use jj commands for all
VCS operations.
Almost always do feature work in a dedicated jj workspace, not the /etc/nixos
default working copy. Daniel edits /etc/nixos live and concurrently, so doing
your own work there churns his on-disk files out from under him, and any jj
operation that moves @ (e.g. jj new, jj edit) can disrupt his in-progress
state. The repo is also operated by multiple agents at once — jj undo/jj redo act on the global op log and can silently clobber another workspace's
operation (e.g. a push), so avoid them; prefer targeted jj edit/jj abandon/jj restore against specific change IDs. A separate workspace isolates all of this.
Only skip the workspace for a very, very good reason — e.g. a tiny read-only inspection, or work that is intrinsically about the default working copy itself — and say why.
Set up workspaces in this format:
- $CODE/workspaces/$REPO_NAME/$WORKSPACE_NAME
- $CODE is the related code directory, usually ~/../code (since $HOME is /home/daniel/.home for clutter reasons and the code directory is usually /home/daniel/code)
- $REPO_NAME would be nix in this case, so going from code/nix to code/workspaces/nix should be obvious
- $WORKSPACE_NAME should probably just be the branch or bookmark name
jj workspace add --revision main $CODE/workspaces/nix/$WORKSPACE_NAME- Note:
fjneeds the forgejo remote, which lives in the/etc/nixoscheckout — runfj pr create ...from/etc/nixos(it operates on the pushed bookmark, not the cwd's working copy).
Deploying (deploy-rs)
Remote hosts are deployed via deploy-rs.
Use nix develop -c deploy (not comma) to ensure the pinned version from the
devshell is used. Node configuration lives in lib/deploy/default.nix. All hosts
are reached over the VPN at <host>.internal.vpn.h.lyte.dev and build remotely
by default.
# Deploy all hosts
nix develop -c deploy .
# Deploy a specific host (skipping checks with -s)
nix develop -c deploy -s --targets ".#beefcake"
# Deploy from remote flake ref without a local clone
nix develop -c deploy -s --targets "git+https://git.lyte.dev/lytedev/nix#beefcake"
Note — deploy beefcake over the LAN, not the VPN. beefcake runs
headscale(the tailnet coordinator), so a deploy reached over the VPN restarts it mid-activation and severs its own connection, leaving the new generation's profile active but services stopped (DNS/VPN/mail/web/git down) and an orphaned deploy-rs magic-rollback waiter. Deploy it over the LAN instead:deploy --hostname 192.168.0.9 …(also reachable asbeefcake.lan). If it wedges anyway, recover fromroot@192.168.0.9: re-run/run/current-system/bin/switch-to-configuration switchto finish the activation, thenkillthe lingeringdeploy-rs … activate waitPID so it can't auto-rollback. For major-release bumps prefernixos-rebuild boot+ reboot.
NEVER deploy a rollback (downgrade) — check versions first
Before deploying any host, verify the branch you are deploying is not OLDER than what the host is currently running. A downgrade silently rolls every package backwards and is dangerous, not neutral:
- Data-format-forward services break: e.g. redis refuses to load a dump written
by a newer redis (
Can't handle RDB format version N). - Services that only exist in the newer config get their users/groups removed and vanish.
- The toolchain change forces a full systemd re-exec mid-switch, which on
beefcake wedges (stops services, drops the SSH connection, deploy-rs does a
messy rollback). See the 2026-06-28 incident and
issues/open/blue-green.md.
main can be BEHIND a host's deployed state. A nixpkgs bump can be deployed
to a host and then dropped/reverted from main, so main's nixpkgs ends up
older than what the host runs. Deploying main to that host is then a
downgrade. Always deploy from an up-to-date workspace (jj git fetch first), not
a stale /etc/nixos checkout.
How to check (do this every deploy):
# What the host runs now (the 26.05.YYYYMMDD.<rev> string gives date + nixpkgs rev):
ssh <host> readlink /run/current-system
# What your branch will build (the primary input may be `nixpkgs_3`, not
# `nixpkgs` — verify which feeds nixosSystem in flake.lock):
nix run nixpkgs#jq -- -r '.nodes.nixpkgs_3.locked | "\(.rev[0:12]) \(.lastModified)"' < flake.lock
If the branch's nixpkgs date is older than the host's, STOP — bump the branch's nixpkgs to >= the host's first, and treat that bump as a version change (boot + reboot, never a live switch).
Forgejo (fj CLI)
Remote is hosted on Forgejo. Use fj (forgejo-cli, available via nix develop) for PRs:
nix develop -c fj pr list
nix develop -c fj pr create "title" --base main --head branch-name --body "description"
nix develop -c fj pr merge <id>
Nix
- Format with
nix fmt - Don't waste time waiting for CI for fast things — format code
nix fmt -- (jj file list) - Let CI handle big builds; don't build host configs locally unless necessary