README.md
vyx
Note
This is my live, primary repository for deploying all my infrastructure, and laptop! I don’t update the documentation nearly as regularly as I update the repository, but queries are welcome.
For bootstrapping reasons, pulls are made from https://gitlab.com/akhya/vyx, but this repository is updated at the same time as that one and is the canonical one.
You may use any of the contents per the (very permissive) license, noted at the end of this document.
Summary
This is a couple things at once!
- NixOS/nix-darwin configurations for my laptops and VPS.
- Standalone Home Manager configurations for my user on each of those.
- Flux CD repository for a single-node k3s cluster.
A description of the basic layout follows.
flake.nix— entrypoint. I use 25.11 on both NixOS and Darwin. Load common config, our modules, host-specific config, Home Manager, and HEAD jj/Helix builds.modules— root of vyx modules. Host data is loaded fromhosts.toml, which supplies values used all over.modules/secrets— secrets management. Provides a simple interface to sops-nix and installs SSH public and private keys. Secrets are stored inprivate/and encrypted with sops-nix.modules/fish— Fish custom build and baseline configuration. I adjustaliasto let my git aliases tab-complete.modules/nix— configure extra dependencies in nix-darwin like we can in NixOS. (Thanks!)modules/cowsay— custom Cowsay build. For reasons.modules/net— networking configuration for NixOS hosts. This sets the host up based on itshosts.tomldata.modules/nginx— a simple interface to configuring vhosts for Nginx, also allowing inclusion of site content as part of the Nix build, and something of a poor doll’s ingress for Nginx. Also ensures all vhosts get great 502 behaviour.modules/k3s— tooling for building k3s clusters; supports distributed (HA) k3s, as well as a single-server-with-agents approach.modules/remote-builder— configure a system to build Nix derivations with remotes.modules/ghostty— sets XDG_DATA_DIRS on Darwin.modules/borg— configures Borg(matic) baseline.modules/mail— configure OpenSMTPD.modules/desktop— configures system-wide baseline for desktop (actually laptop) use.modules/linux-builder— configuresnix-rosetta-builder.modules/darwin-ssh-patches— patches Nixpkg’s OpenSSH to support the launchd bits from Darwin’s version; I have want a more recent version than bundled in macOS, but the launchd integration is really useful.modules/hammerspoon— installs Hammerspoon on macOS.modules/iosevka— my beloved font.
hosts— host-specific config.hosts/seraphim— configures my Macbook (on nix-darwin).- Homebrew is configured declaratively, which is really nice — I’ll often
brew installsomething manually just to try it out, and let my next rebuild GC it, or add it to the config if I decide to keep it. - Comenzar is enabled.
- Homebrew is configured declaratively, which is really nice — I’ll often
hosts/rebane— configures my work Macbook (on nix-darwin).hosts/akhya— configures my Framework laptop on NixOS.hosts/coer— unused Raspberry Pi configuration.hosts/kala— configureskala, my VPS on NixOS and k3s server.- Hardware config is from
hosts/ovh-vps.nix, originally pulled fromnixos-infect-generatedhardware-configuration.nix. - A baseline vhost for the server’s FQDN itself is set which just returns a 502. (This is important.)
- Tailscale is enabled.
- k3s is started.
- Hardware config is from
home/— Home Manager config, instantiated in the flake’shomeConfigurations. Loads submodules and installs baseline packages.home/fish.nix— sets up Fish for me specifically. Sets prompt git info, addsvyxnixaliases, Git alias passthrough, SSH aliases, jj aliases, kubectl aliases. A prompt is installed, and some aliases to rebuild vyx and use Nix fluently. Nix profiles are added to PATH on Darwin.home/ssh.nix— configures SSH. Generates ssh_config blocks for each host defined inhosts.toml. (These in turn become shell aliases.)home/helix.nix— sets up Helix. (I like typingZZto save and quit, and whyvgldwhenDcould do? Old habits die hard.)home/tmux.nix— configures tmux. The prefix isC-ton non-servers, andC-ron servers, so having a session on your terminal and further sessions on various remotes within that Just Work™. Their statusbars are also coloured differently. (tandrare also nicely placed on Dvorak.)home/git.nix— personal baseline git configuration; pulls in aliases.home/ripgrep.nix— minimal rg config (--hiddento not exclude “hidden” things by default).home/aerc.nix— aerc (and email account) config.home/jj.nix— jj config.home/vyx.nix— bit hacky; reproducesmodules/default.nix’s config for Home Manager modules.home/secrets.nix— as above; manage sops secrets in the home context.home/accounts.nix— email, calendar, contacts accounts.home/less.nix— super useful to haveQto quit without redraw.home/ghostty.nix— configure Ghostty, and install terminfo only on servers.home/hammerspoon/— configures Hammerspoon.home/librewolf.nix— installs and configures Librewolf.home/desktop.nix— installs packages I want ondesklaptop, configures mpd.home/plasma/— configure Plasma (e.g. for Comenzar).home/hosts/— per-host home configuration.
Git aliases
[!NOTE] I’m almost exclusively using jj now, so while the below still represents how I use git, I don’t tend to use it directly all that much any more! Here’s a post about jj.
home/fish.nixincludes jj aliases similar to the below, but they’re undergoing active refinement as my use increases!
Firstly, for all our git aliases, we create a shell alias with a g prefix; i.e. if you have git c defined for commit, then gc at the shell invokes it. (The bare letters are used by jj aliases.)
This isn’t overkill — I talk a bit about this early in Nix revisited, but I’d like to elaborate now: Git is a Swiss Army knife. It’s way better than what came before it in lots of ways, and I’m sure (and glad) folks are improving on it, but I’m very comfy with it, and not because I’ve abstracted it all away from me so I don’t have to think about it, but because I brought it close enough that all its little implements (continuing with the SAK analogy) are like my own fingers. Err. Or something.
Have a look at home/git-aliases.nix. Everything’s split into groups of related aliases. Remember that these are all promoted to shell aliases in their own right, so (as documented in Nix revisited) where necessary I adjust the alias to move out of the way of a common shell utility (like cp ~> pc). There are some utilities I don’t care about and happily displace; that’s what command and builtin are for.
cl:clone. I pull down a lot of new code! (Don’t use this for multiple working trees, or multiple remotes.)co:checkout. Something I find myself doing a lot isco BLAH^ -- x/y/zif I found I removedx/y/zin commitBLAHand want to reintroduce it (but I don’t want to revert all ofBLAH). Of course,co jklorco -to change branches are the main use.cb:checkout -b. Branches are extremely lightweight, use them constantly! Use them to slap temporary names on things while you shift things around (so you don’t have to hunt in the reflog, but you always can do that too), to track remotes, all over.pc:checkout -p. Interactively checkout hunks. I’ll do this when I want to get rid of some debugging stuff I don’t need any more, often cycling betweenpcandato remove and stage alternately. You can give a path to restrict the scope if you know what you want to revert parts of.
s:status -sb. Default to short output, with branch/tracking info. Making the output short is key to ensuring you can call it as freely asls(which I alias to.!); if it’s long, the feeling of loss of context will act as counterpressure, which makes it easy to feel disconnected from the state of your repo/working tree/index.b:branch. Branch ops, but also just to list local branches by itself.ba:branch -a. Include remotes.bd:branch -d. Delete a branch.bdd:branch -D. Really delete a branch. Part of branches being lightweight is not just “easy to create” but “easy to delete” (and recreate again if you need to!). Otherwise they accumulate and become the very opposite.
h:show. (swas taken, so we go to the second letter.)h1:show HEAD^.h2:show HEAD^^.- …
h5:show HEAD^^^^^. If I’ve gotten to this stage I need to find a better point of reference, but they do come in handy for quickly retracing steps.
d:diff. Use it often. Consider alsod -wto ignore whitespace.dch:diff --cached. Shows the diff between the index (staging area) and HEAD; i.e. what you would commit if you hitc.
l:log --show-notes='*' --abbrev-commit --pretty=format:'%Cred%h %Cgreen(%aD)%Creset -%C(bold red)%d%Creset %s %C(bold blue)<%an>% cm%Creset' --graph.- Yes, really.
- This is probably one of the most important ones. For the same reason as “short status” above, actually condensing log info into short lines with the tree structure visible is super important for having a feel about the shape of your repo and where exactly you’re peeking into it from.
lais the same but with--alladded; this means you can see remote tracking branches, branches you’re not on, etc., which gives an even wider overview.lpadds--patch, which includes the full diff of each commit between the lines. I’ll use this pretty often when looking at where references to certain items have been added and removed over time across a repository; if it’s all in one file or subtree,lp x/y/zworks great too.
a:add -p. The default is always to use the patch interface to stage hunks incrementally. You want this to be your default. Know what you’re committing.- Seriously, in a work situation, in a hobby project situation, in a tiny throwaway whatever, know what you’re committing. You can’t accidentally commit a TODO if you look at every line before you put it in a commit.
- Short on time or motivation? Sure,
ad . && cm blahand call it a day. Tomorrow your HEAD^,aand split it out properly. ad:add. For when you want to add whole files or directories.ais my default, but I still needadbecause this is a Swiss Army knife, not a nanny.
mrm:rm. Becausermwas taken.mrc:rm --cached. Remove a file from the index, without touching the working tree; “unstage” a file. You’ll needmrc -fa fair bit of the time.
c:commit. Use this often, since you’ll have interactively staged withaand want to write a long commit message in$EDITOR.cm:commit -m. More often for quick shots:cm wip,cm fixup,cm 'v0.26.0.', etc.cx:commit --amend. You forgot to stage one little hunk of a file?a && cx, no wuckas.pcp:cherry-pick. This is conceptually similar to committing, so I include it here! I use this when I’m doing a drawn-out “manual” rebase, or when I simply have one commit to pull from elsewhere. It’s handy to have at hand so you can easilypcp --abortif you don’t like a conflict.
m:merge.mnf:merge --no-ff. When you’re merging something that could be a fast-forward and you don’t want it to be; this results in a merge commit like a PR merge on GitHub.
r:reset. Use this a lot. By default with no arguments, this “unstages” everything by resetting the index to match HEAD, leaving your working tree untouched. With a commit argument, it resets both the index and the current branch HEAD to the commit given (which can be a branch name or tag), still leaving the working tree untouched. This means you can essentially rewind a branch to a different point in time, without disrupting your work.- In other words, a poor man’s “squash the last 5 commits” looks like
r HEAD~5 && ad . && c. (This also includes your working tree at the time of starting, note.) rh:reset --hard. This is as the above, but also resets the working tree.- You like what you did in subdirectory
plant/as of the current commit, but otherwise you want to drop the last 3 commits, and condense the plant changes?cb yummy-plant && co - && rh HEAD^^^ && co yummy-plant -- plant/ && bdd yummy-plant && c. It’s free real estate. (You can skip the branch creation and just plonk HEAD’s SHA into your clipboard before starting, or even useHEAD@{n}!)
- In other words, a poor man’s “squash the last 5 commits” looks like
en:revert. For introducing a revert commit.rb:rebase. Bringing some changes up to date.ri:rebase -i. For carefully bringing changes up to date, or moreover for rewriting history. Use this to condense commits, drop commits, pull commits apart, anything you so desire.rbc:rebase --continue. Continue a rebase after editing a commit or addressing conflicts. You might also needrb --skipif a commit doesn’t result in any change on the new base.rba:rebase --abort. Rewind all changes made to before you started your last rebase.
w:push. The mnemonic is “write”.wf:push -f. If you enjoy revising history as much as I do, you will find this helpful. All the usual nonsense applies: don’t rewrite history of branches others are working on unless you have a really good thing going.wo:push origin. Particularly useful withwo :xyzto delete branches and tags on the remote namedorigin.wu:push -u. Set the local branch to track the remote.
v:pull. The mnemonic is “vis next towon Dvorak” (:f:fetch.fa:fetch --all. Fetch all remotes.
rv:remote -v. Lists remotes.ra:remote add. Add a new remote. I do this when fetching a new long-term collaborator’s changes, or adding my own fork (or an upstream to my existing fork).rp:remote prune. Prune a remote; i.e. delete tracking branches that don’t exist on it.rpo:remote prune origin. Prune theoriginremote.rpa:fetch --all --prune. Fetches all remotes, pruning them in the process. (The fact that this also fetches is Fine. Fetching is not dangergoose.)
st:stash. Stashes all local changes. You can also just make a temporary commit and use reset shenanigans, but stash is often faster, and by default preserves your index and working tree separately. (Trylaafter a stash to seerefs/stash.)st -pis helpful if you want to partially stash some stuff, before committing (and perhaps reverting) some others.sth:stash show -p. We add-pas, by default,stash showdoesn’t actually show you the diff of the stashed change. (Thehmnemonic is preserved from our top-levelhalias.)stl:stash list.std:stash drop. Drops the latest stash (or stash reference). Note that the commit reference it gives you can be given tostauntil it’s garbage collected, so it’s no big deal if you do this by accident.stp:stash pop. Applies the latest stash (or stash reference), and then drops the stash if it applied without conflict.sta:stash apply. Applies the latest stash (or argument), but the argument doesn’t have to be an explicit stash reference (since it doesn’t need to be a valid stash to drop, unlike withpop).
sms:submodule sync --recursive. Sync submodule remotes with.gitmodules.smu:submodule update --init --recursive. Use this to initially get and update submodules over time.
bl:blame.
SSH aliases
Similar to above, a shell alias is created for every host defined in hosts.toml; i.e. typing kala is enough to ssh kala.
vyxnix
Similar to git, I need the Nix tools closer to hand. vyxnix is the launcher that makes that happen; combine it with a range of aliases and baby you’ve got yourself a stew.
It mostly documents itself, so I’m going to just repeat the header comments:
# Principal nix3 launcher.
#
# "develop" and "shell" are treated specially:
# vyxnix develop -> nix develop --command fish
# vyxnix develop x y z -> nix develop x y z --command fish
# vyxnix develop x -- y z -> nix develop x --command fish -c "y z"
# vyxnix develop x --command y z -> nix develop x --command y z
# "d/s" below refers to these cases.
#
# "run" and "shell" are also treated specially:
# vyxnix run xyz -> nix run nixpkgs#xyz
# vyxnix run abc#def -> nix run abc#def
# vyxnix shell uvw -- nyonk -> nix shell nixpkgs#uvw -- nyonk
#
# !x=y translates to --override-input x y.
# !o -> --offline.
# !n -> don't use `nom` for build/shell/develop.
# $defaultargs: specify --vyx-no-defaults to omit.
set -f defaultargs -L --keep-going
# $dryrun: echo the command that would be executed.
In practice this means (modulo the defaultargs mentioned above):
nd -- makeis equivalent tonix develop --command fish -c "make".nb !nixpkgs=~/g/nixpkgsis equivalent tonix build --override-input nixpkgs ~/g/nixpkgs.nr htopis equivalent tonix run nixpkgs#htop.
The default args are also really helpful — I almost never remember to include -L or --keep-going until a long build has failed and I wish I already had.
The really nice thing about using Fish everywhere, too, is that vyxnix and aliases using it all support tab-completion correctly, including the --vyx options. If they didn’t, I’d want to use them a lot less, and keep forgetting the exact --vyx options since they’re so rarely wanted.
j
This is like vyxnix, but for jj! j is a simple launcher which makes a few options nearer to hand:
# !i -> --ignore-immutable
# !b -> --allow-backwards
# !p -> --no-pager
# !s -> --stat
# !ua -> --update-author
# ^XX -> --remote XX (may be specified more than once)
# %XX -> -r XX
# !U -> sets DELTA_FEATURES=+
This allows for scenarios like the following:
$ Aq
Error: Commit 063ff6e65ac0 is immutable
Hint: Pass `--ignore-immutable` or configure the set of immutable commits via `revset-aliases.immutable_heads()`.
$ Aq !i
Working copy now at: vwsmmryz dde83cf9 (empty) (no description set)
Parent commit : qoqluvpo 4921e89b main* | README: update for jj.
k
This is like vyxnix, but for Kubernetes! Oh my goodness! k abbreviates calls to kubectl, namespace specification, and common tasks.
$ K kv gp
NAME READY STATUS RESTARTS AGE
kv-deploy-d9bb5f75-4glh8 2/2 Running 0 8m21s
kv-deploy-d9bb5f75-gzqmn 2/2 Running 0 8m7s
kv-deploy-d9bb5f75-ksc9k 2/2 Running 0 8m39s
License
Copyright © 2025–2026 Asherah Connor <ashe@kivikakk.ee>
This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the COPYING file for more details.