├── config ├── helix │ ├── helix.scm │ ├── .gitignore │ ├── languages.toml │ └── config.toml ├── niri │ ├── .gitignore │ ├── private.kdl │ ├── config.kdl │ ├── common │ │ ├── config.kdl │ │ ├── input.kdl │ │ ├── shell.kdl │ │ ├── visuals.kdl │ │ ├── window-rule.kdl │ │ └── binds.kdl │ └── hosts │ │ └── pc3 │ │ └── config.kdl ├── kitty │ ├── .gitignore │ ├── kitty.conf │ └── current-theme.conf ├── nvim │ ├── lazy-lock.json │ ├── plugins.nix │ └── init.lua ├── zed │ └── hosts │ │ ├── pc3 │ │ ├── keymap.json │ │ └── settings.json │ │ └── work-macbookpro │ │ ├── keymap.json │ │ └── settings.json ├── npm │ └── npmrc ├── zsh │ ├── .gitignore │ ├── .zshenv │ ├── home_zshenv │ └── .zshrc ├── fish │ ├── functions │ │ ├── ipv4.fish │ │ ├── ipv6.fish │ │ ├── shell.fish │ │ ├── pathify.fish │ │ ├── poke.fish │ │ ├── clean-store.fish │ │ ├── mvcd.fish │ │ ├── mkblueprint.fish │ │ ├── paths.fish │ │ ├── announce.fish │ │ ├── sayresult.fish │ │ ├── ls.fish │ │ ├── abbred.fish │ │ ├── fish_greeting.fish │ │ ├── nix.fish │ │ ├── fish_job_summary.fish │ │ ├── trash.fish │ │ ├── fish_user_key_bindings.fish │ │ └── run.fish │ ├── .gitignore │ ├── completions │ │ └── run.fish │ ├── conf.d │ │ ├── init.fish │ │ ├── ghostty-shell-integration.fish │ │ ├── man.fish │ │ ├── options.fish │ │ ├── 999_fix_path.fish │ │ ├── eza.fish │ │ ├── abbreviations.fish │ │ └── environment.fish │ └── config.fish ├── jj │ └── config.toml ├── direnv │ └── direnv.toml ├── git │ ├── ignore │ └── config ├── tmux │ └── tmux.conf └── ghostty │ ├── config │ ├── os-config-linux │ └── os-config-darwin ├── .envrc ├── .gitignore ├── hosts ├── macmini │ ├── id_ed25519.pub │ ├── users │ │ └── robert │ │ │ ├── id_ed25519.pub │ │ │ └── home-configuration.nix │ └── darwin-configuration.nix ├── homeserver1 │ ├── id_ed25519.pub │ ├── clouddns-config.json.age │ ├── tailscale-homeserver1.age │ ├── minecraft │ │ ├── default.nix │ │ └── servers │ │ │ └── family.nix │ ├── clouddns.nix │ ├── vrising.nix │ ├── disko.nix │ └── configuration.nix ├── macbook-air │ ├── id_ed25519.pub │ └── users │ │ └── robert │ │ ├── id_ed25519.pub │ │ └── home-configuration.nix ├── pc3 │ └── users │ │ ├── work │ │ ├── gitconfig.age │ │ ├── id_ed25519.pub │ │ └── home-configuration.nix │ │ └── robert │ │ ├── id_ed25519.pub │ │ └── home-configuration.nix ├── work-macbookpro │ ├── users │ │ └── robert │ │ │ ├── id_ed25519.pub │ │ │ ├── work-gitconfig.age │ │ │ └── home-configuration.nix │ └── darwin-configuration.nix └── legacy │ └── users │ └── robert │ └── home-configuration.nix ├── modules ├── common │ └── nixpkgs-unstable.nix ├── darwin │ ├── system-defaults.nix │ └── fish-environment.nix └── home │ ├── my-config.nix │ ├── my-programs-neovim.nix │ └── my-programs-fish.nix ├── packages ├── homeserver1-install.nix ├── rcon-cli.nix ├── helix-clo4.nix ├── ccase.nix ├── mrpack-install.nix ├── run.nix ├── helix.nix └── schemat.nix ├── users └── robert │ ├── darwin.nix │ ├── work-configuration.nix │ └── home-configuration.nix ├── .github └── workflows │ └── update-flake.yml ├── devshell.nix ├── secrets.nix ├── LICENSE ├── flake.nix ├── README.md ├── run.fish └── flake.lock /config/helix/helix.scm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/helix/.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | -------------------------------------------------------------------------------- /config/niri/.gitignore: -------------------------------------------------------------------------------- 1 | !*.kdl 2 | -------------------------------------------------------------------------------- /config/kitty/.gitignore: -------------------------------------------------------------------------------- 1 | dank-*.conf 2 | -------------------------------------------------------------------------------- /config/nvim/lazy-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /config/zed/hosts/pc3/keymap.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | watch_file devshell.nix 2 | use flake 3 | -------------------------------------------------------------------------------- /config/npm/npmrc: -------------------------------------------------------------------------------- 1 | prefix = ${HOME}/.local 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | result-* 3 | .direnv 4 | _* 5 | -------------------------------------------------------------------------------- /config/zed/hosts/work-macbookpro/keymap.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /config/zsh/.gitignore: -------------------------------------------------------------------------------- 1 | .zcompdump* 2 | .zsh_history 3 | -------------------------------------------------------------------------------- /config/fish/functions/ipv4.fish: -------------------------------------------------------------------------------- 1 | function ipv4 2 | curl -4 https://api.ipify.org 3 | end 4 | -------------------------------------------------------------------------------- /config/fish/functions/ipv6.fish: -------------------------------------------------------------------------------- 1 | function ipv6 2 | curl -6 https://api6.ipify.org 3 | end 4 | -------------------------------------------------------------------------------- /config/niri/private.kdl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clo4/nix-dotfiles/HEAD/config/niri/private.kdl -------------------------------------------------------------------------------- /config/niri/config.kdl: -------------------------------------------------------------------------------- 1 | include "common/config.kdl" 2 | include "host/config.kdl" 3 | include "private.kdl" 4 | -------------------------------------------------------------------------------- /hosts/macmini/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL7jCIKZyfKrAYIDzsiBOLf5/JgSRtNtXeBOjIG3CVNi 2 | -------------------------------------------------------------------------------- /hosts/homeserver1/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL3hbWmccdfoYplE/PZ251CMUrCiTJJd9ON37/RR2JkP 2 | -------------------------------------------------------------------------------- /hosts/macbook-air/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcpkHfo1d9l7WDahmib2sBuhdsSpllCGPDiBjcq757d 2 | -------------------------------------------------------------------------------- /config/fish/functions/shell.fish: -------------------------------------------------------------------------------- 1 | function shell 2 | NIXPKGS_ALLOW_UNFREE=1 nix shell --impure nixpkgs#$argv 3 | end 4 | -------------------------------------------------------------------------------- /hosts/pc3/users/work/gitconfig.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clo4/nix-dotfiles/HEAD/hosts/pc3/users/work/gitconfig.age -------------------------------------------------------------------------------- /config/fish/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything except for directories and .fish files 2 | * 3 | !*/ 4 | !*.fish 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /config/fish/functions/pathify.fish: -------------------------------------------------------------------------------- 1 | function pathify 2 | for p in $argv 3 | set --path $p $$p 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /hosts/macmini/users/robert/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILAWNw2z4swcjAkPPwO1evXclnlIYta1jaJKPKWsrOoo 2 | -------------------------------------------------------------------------------- /hosts/macbook-air/users/robert/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO4GO2AYXejanlgjDDg3C9K2IG8WhB+Bp8up785b3IP5 2 | -------------------------------------------------------------------------------- /hosts/pc3/users/robert/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFQZ33imCWLzYj9ZsMOs+SqnNNQVLhsrs1laY5tImq8D robert@pc3 2 | -------------------------------------------------------------------------------- /hosts/pc3/users/work/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBpv/AINUV6zJZDtJAn13pbNJEz7ImAs52nTOX0+JgBQ work@pc3 2 | -------------------------------------------------------------------------------- /hosts/homeserver1/clouddns-config.json.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clo4/nix-dotfiles/HEAD/hosts/homeserver1/clouddns-config.json.age -------------------------------------------------------------------------------- /hosts/homeserver1/tailscale-homeserver1.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clo4/nix-dotfiles/HEAD/hosts/homeserver1/tailscale-homeserver1.age -------------------------------------------------------------------------------- /config/fish/functions/poke.fish: -------------------------------------------------------------------------------- 1 | function poke 2 | for arg in $argv 3 | mkdir -p (path dirname $arg); and touch $arg 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/jj/config.toml: -------------------------------------------------------------------------------- 1 | "$schema" = "https://jj-vcs.github.io/jj/latest/config-schema.json" 2 | 3 | [user] 4 | name = "clo4" 5 | email = "robert@clo4.net" 6 | -------------------------------------------------------------------------------- /config/niri/common/config.kdl: -------------------------------------------------------------------------------- 1 | include "binds.kdl" 2 | include "input.kdl" 3 | include "visuals.kdl" 4 | include "window-rule.kdl" 5 | include "shell.kdl" 6 | -------------------------------------------------------------------------------- /hosts/work-macbookpro/users/robert/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHw37HYeSngFDNoNaiLo0rZcwA/BjHNebH8nzOPQ3LTs robert@work-macbookpro 2 | -------------------------------------------------------------------------------- /hosts/work-macbookpro/users/robert/work-gitconfig.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clo4/nix-dotfiles/HEAD/hosts/work-macbookpro/users/robert/work-gitconfig.age -------------------------------------------------------------------------------- /config/fish/functions/clean-store.fish: -------------------------------------------------------------------------------- 1 | function clean-store 2 | announce nix store gc --verbose 3 | echo 4 | announce nix store optimise --verbose 5 | end 6 | -------------------------------------------------------------------------------- /config/fish/functions/mvcd.fish: -------------------------------------------------------------------------------- 1 | function mvcd 2 | set cwd $PWD 3 | set newcwd $argv[1] 4 | cd .. 5 | mv $cwd $newcwd 6 | cd $newcwd 7 | pwd 8 | end 9 | -------------------------------------------------------------------------------- /config/fish/functions/mkblueprint.fish: -------------------------------------------------------------------------------- 1 | function mkblueprint 2 | nix flake init -t blueprint 3 | nix flake update 4 | git init 5 | git add . 6 | direnv allow 7 | end 8 | -------------------------------------------------------------------------------- /config/niri/common/input.kdl: -------------------------------------------------------------------------------- 1 | input { 2 | keyboard { 3 | xkb { 4 | layout "us,us(colemak_dh)" 5 | } 6 | 7 | numlock 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /config/direnv/direnv.toml: -------------------------------------------------------------------------------- 1 | [global] 2 | load_dotenv = true 3 | strict_env = true 4 | warn_timeout = 0 5 | 6 | [whitelist] 7 | prefix = ["~/Developer/clo4", "~/Developer/Work", "~/Repos"] 8 | -------------------------------------------------------------------------------- /config/git/ignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | result 4 | result-* 5 | .DS_Store 6 | .direnv 7 | /.helix 8 | .flake 9 | .pkgs 10 | .jj 11 | /.envrc 12 | 13 | **/.claude/settings.local.json 14 | -------------------------------------------------------------------------------- /config/fish/functions/paths.fish: -------------------------------------------------------------------------------- 1 | function paths 2 | set --append --local argv PATH 3 | set --local --path paths $$argv[1] 4 | for p in $paths 5 | echo $p 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/fish/completions/run.fish: -------------------------------------------------------------------------------- 1 | set -l commands (run 2>/dev/null) 2 | complete -c run -s h -l help -fx 3 | complete -c run -n "not __fish_seen_subcommand_from $commands" -fa "(__COMPLETE_RUN_DESCRIPTIONS=1 run 2>/dev/null)" 4 | -------------------------------------------------------------------------------- /modules/common/nixpkgs-unstable.nix: -------------------------------------------------------------------------------- 1 | { inputs, pkgs, ... }: 2 | { 3 | _module.args.pkgs' = import inputs.nixpkgs-unstable { 4 | system = pkgs.stdenv.hostPlatform.system; 5 | config.allowUnfree = true; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /config/fish/functions/announce.fish: -------------------------------------------------------------------------------- 1 | function announce 2 | set colored_command (string escape -- $argv | string join ' ' | fish_indent --ansi) 3 | echo "$(set_color magenta)~~>$(set_color normal) $colored_command" 4 | $argv 5 | end 6 | -------------------------------------------------------------------------------- /config/zsh/.zshenv: -------------------------------------------------------------------------------- 1 | # Executing hm-session-vars is designed to be idempotent, safe to execute 2 | # multiple times 3 | local session_vars="$HOME/.local/share/zsh/hm-session-vars.sh" 4 | if [[ -f $session_vars && -r $session_vars ]]; then 5 | source $session_vars 6 | fi 7 | -------------------------------------------------------------------------------- /config/kitty/kitty.conf: -------------------------------------------------------------------------------- 1 | # clear_all_shortcuts 2 | 3 | # map command+t new_tab_with_cwd 4 | # map command+n new_os_window 5 | 6 | # map control+t next_tab 7 | # map control+shift+t previous_tab 8 | 9 | # map command+d new_window_with_cwd 10 | 11 | include current-theme.conf 12 | -------------------------------------------------------------------------------- /config/fish/conf.d/init.fish: -------------------------------------------------------------------------------- 1 | function __source 2 | # Using type instead of command allows for functions too 3 | if type -q $argv[1] 4 | $argv | source 5 | end 6 | end 7 | 8 | __source zoxide init fish 9 | __source direnv hook fish 10 | __source fzf --fish 11 | 12 | functions -e __source 13 | -------------------------------------------------------------------------------- /packages/homeserver1-install.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | pname, 4 | perSystem, 5 | flake, 6 | }: 7 | if pkgs.stdenv.hostPlatform.system != "x86_64-linux" then 8 | pkgs.emptyFile 9 | else 10 | pkgs.writeShellScriptBin pname '' 11 | ${perSystem.disko.disko-install}/bin/disko-install --flake path:${flake}#homeserver1 --disk main /dev/nvme0n1 12 | '' 13 | -------------------------------------------------------------------------------- /hosts/legacy/users/robert/home-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | flake, 4 | config, 5 | ... 6 | }: 7 | { 8 | imports = [ "${flake}/users/robert/home-configuration.nix" ]; 9 | 10 | home.stateVersion = "25.05"; 11 | 12 | # Config fails to build without this. 13 | nix.package = pkgs.nix; 14 | 15 | my.config.directory = "${config.home.homeDirectory}/Developer/clo4/nix-dotfiles"; 16 | } 17 | -------------------------------------------------------------------------------- /hosts/homeserver1/minecraft/default.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | imports = [ 4 | ./servers/family.nix 5 | ]; 6 | assertions = [ 7 | { 8 | assertion = config.virtualisation.oci-containers.backend == "podman"; 9 | message = '' 10 | You can't change to Docker without also updating minecraft/servers/*.nix as these 11 | files explicitly invoke podman. 12 | ''; 13 | } 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /config/tmux/tmux.conf: -------------------------------------------------------------------------------- 1 | set -g default-terminal "tmux-256color" 2 | set -g base-index 1 3 | setw -g pane-base-index 1 4 | set -g status-keys emacs 5 | set -g mode-keys emacs 6 | set -g mouse on 7 | set -g focus-events off 8 | setw -g aggressive-resize off 9 | setw -g clock-mode-style 12 10 | set -s escape-time 50 11 | set -g history-limit 10000 12 | 13 | set-option -sa terminal-overrides ",tmux-256color:RGB" 14 | -------------------------------------------------------------------------------- /users/robert/darwin.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | { 3 | config = lib.mkIf pkgs.stdenv.isDarwin { 4 | # Notably, in my configuration, the *value* of this variable is never checked. 5 | # The only important thing is whether or not it's set at all. 6 | home.sessionVariables.IS_DARWIN = ""; 7 | 8 | # On macOS, this is intended to suppress the login welcome message. 9 | home.file.".hushlogin".source = pkgs.emptyFile; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /config/fish/functions/sayresult.fish: -------------------------------------------------------------------------------- 1 | function sayresult 2 | announce $argv 3 | set -l cmd_status $status 4 | 5 | if not command -q say 6 | return $cmd_status 7 | end 8 | 9 | if test $CMD_DURATION -lt 3000 10 | return $cmd_status 11 | end 12 | 13 | if test $cmd_status -eq 0 14 | say "Command succeeded" 15 | else 16 | say "Command failed" 17 | end 18 | 19 | return $cmd_status 20 | end 21 | -------------------------------------------------------------------------------- /packages/rcon-cli.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | pkgs.buildGoModule rec { 3 | pname = "rcon-cli"; 4 | # current as of 2025-02-20 5 | version = "1.6.11"; 6 | 7 | src = pkgs.fetchFromGitHub { 8 | owner = "itzg"; 9 | repo = "rcon-cli"; 10 | rev = "${version}"; 11 | hash = "sha256-RfcmAF2lj/huQNxxQFS1GUsqCS1eVfF5jTpXVGvykFE="; 12 | }; 13 | 14 | vendorHash = "sha256-b9mWhrsHyXPhUm/9v9Oj72O4VEnlYMnieJiahE/9k1k="; 15 | 16 | doCheck = false; 17 | } 18 | -------------------------------------------------------------------------------- /config/ghostty/config: -------------------------------------------------------------------------------- 1 | scrollback-limit = 100000000 2 | window-padding-color = extend 3 | font-family = RobotoMono Nerd Font 4 | font-size = 12 5 | window-height = 60 6 | window-width = 170 7 | window-theme = auto 8 | theme = Gruvbox Dark 9 | auto-update-channel = tip 10 | 11 | # This file will be linked to the appropriate os-config file depending on the current platform 12 | config-file = os-config 13 | 14 | # Keybind for Claude Code 15 | keybind = shift+enter=text:\n 16 | -------------------------------------------------------------------------------- /config/zed/hosts/work-macbookpro/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_font_size": 14.0, 3 | "buffer_font_size": 14.0, 4 | "buffer_font_weight": 400.0, 5 | "ui_font_weight": 390.0, 6 | "icon_theme": "JetBrains New UI Icons (Dark)", 7 | "features": { 8 | "edit_prediction_provider": "zed" 9 | }, 10 | "theme": "Fleet Dark", 11 | "vim_mode": true, 12 | "buffer_font_family": "RobotoMono Nerd Font", 13 | "ui_font_family": "RobotoMono Nerd Font", 14 | "format_on_save": "off" 15 | } 16 | -------------------------------------------------------------------------------- /config/fish/conf.d/ghostty-shell-integration.fish: -------------------------------------------------------------------------------- 1 | # If using distrobox, it's quite possible that the variables could be set 2 | # but inaccessible. 3 | if set -q GHOSTTY_RESOURCES_DIR GHOSTTY_BIN_DIR 4 | and test -d $GHOSTTY_RESOURCES_DIR -a -d $GHOSTTY_BIN_DIR 5 | source "$GHOSTTY_RESOURCES_DIR/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish" 6 | set --append fish_complete_path "$GHOSTTY_RESOURCES_DIR/shell-integration/fish/vendor_completions.d" 7 | set --append PATH $GHOSTTY_BIN_DIR 8 | end 9 | -------------------------------------------------------------------------------- /config/fish/conf.d/man.fish: -------------------------------------------------------------------------------- 1 | # This function can't be lazy loaded because it needs to wrap another fish function, 2 | # not a builtin or command. In a normal fish distribution, `man` is guaranteed to be 3 | # a function. 4 | 5 | functions --copy man __fish_man 6 | functions --erase man 7 | 8 | function man --wraps man 9 | if not set -q MANWIDTH 10 | set --function MANWIDTH 80 11 | end 12 | set --function --export MANWIDTH (math "min($MANWIDTH, $(tput cols))") 13 | __fish_man --no-justification $argv 14 | end 15 | -------------------------------------------------------------------------------- /packages/helix-clo4.nix: -------------------------------------------------------------------------------- 1 | # This is a helix setup on either the latest (or close to the latest) version of 2 | # the plugin system fork, which is usually quite stable and only a couple of weeks 3 | # behind upstream. This wraps my existing Helix configuration, allowing me to use 4 | # it on all systems. 5 | { 6 | perSystem, 7 | pkgs, 8 | flake, 9 | }: 10 | pkgs.writeShellScriptBin "hx" '' 11 | export PATH=${perSystem.self.ccase}/bin:$PATH 12 | exec ${perSystem.self.helix}/bin/hx -c ${flake}/config/helix/config.toml "$@" 13 | '' 14 | -------------------------------------------------------------------------------- /config/zsh/home_zshenv: -------------------------------------------------------------------------------- 1 | local session_vars="$HOME/.local/share/zsh/hm-session-vars.sh" 2 | if [[ -f $session_vars && -r $session_vars ]]; then 3 | source $session_vars 4 | fi 5 | 6 | # ~/.zshenv is always sourced first, so when this file sets the ZDOTDIR, 7 | # it finishes executing and ZSH moves on to the next item in the source order. 8 | # We need to manually source the appropriate file in the correct directory, if 9 | # it exists and is readable. 10 | if [[ -n $ZDOTDIR && -f $ZDOTDIR/.zshenv && -r $ZDOTDIR/.zshenv ]]; then 11 | source $ZDOTDIR/.zshenv 12 | fi 13 | -------------------------------------------------------------------------------- /hosts/macmini/users/robert/home-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | flake, 3 | config, 4 | ... 5 | }: 6 | { 7 | home.stateVersion = "24.11"; 8 | 9 | imports = [ "${flake}/users/robert/home-configuration.nix" ]; 10 | 11 | home.sessionVariables = { 12 | # My fish configuration uses this to check whether it should check if 13 | # the Touch ID PAM module is enabled. See: config/fish/functions/fish_greeting.fish 14 | FISH_GREETING_CHECK_SUDO_TOUCHID = "1"; 15 | }; 16 | 17 | my.config.directory = "${config.home.homeDirectory}/Developer/clo4/nix-dotfiles"; 18 | } 19 | -------------------------------------------------------------------------------- /config/git/config: -------------------------------------------------------------------------------- 1 | [init] 2 | defaultBranch = "main" 3 | 4 | [push] 5 | autoSetupRemote = true 6 | 7 | [user] 8 | email = robert@clo4.net 9 | name = "clo4" 10 | 11 | [alias] 12 | root = rev-parse --show-toplevel 13 | 14 | [includeIf "gitdir:~/Developer/Work/**"] 15 | path = ~/Developer/Work/.gitconfig 16 | 17 | [includeIf "gitdir:~/Repos/Work/**"] 18 | path = ~/Repos/Work/.gitconfig 19 | 20 | [core] 21 | pager = delta 22 | 23 | [interactive] 24 | diffFilter = delta --color-only 25 | 26 | [delta] 27 | navigate = true 28 | 29 | [merge] 30 | conflictStyle = zdiff3 31 | -------------------------------------------------------------------------------- /config/fish/functions/ls.fish: -------------------------------------------------------------------------------- 1 | # When used interactively, wraps eza with some nice coloring and good default options. 2 | # When given options or used in a command substitution, delegates to system ls. 3 | 4 | function ls --wraps ls 5 | if string match --quiet -- '-*' $argv; or not isatty stdout 6 | command ls $argv 7 | return 8 | end 9 | 10 | set cmd eza --long --group-directories-first --sort=Name --follow-symlinks --git --almost-all 11 | if path is -d .git 12 | and not path is -d node_modules 13 | $cmd --total-size $argv 14 | else 15 | $cmd $argv 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config/zed/hosts/pc3/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui_font_size": 14.0, 3 | "buffer_font_size": 14.0, 4 | "buffer_font_weight": 400.0, 5 | "ui_font_weight": 390.0, 6 | "icon_theme": "JetBrains New UI Icons (Dark)", 7 | "features": { 8 | "edit_prediction_provider": "zed" 9 | }, 10 | "theme": "Fleet Dark", 11 | "vim_mode": true, 12 | "buffer_font_family": "RobotoMono Nerd Font", 13 | "ui_font_family": "RobotoMono Nerd Font", 14 | "format_on_save": "off", 15 | "languages": { 16 | "Go": { 17 | "format_on_save": "on" 18 | }, 19 | "Go Mod": { 20 | "format_on_save": "on" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/niri/hosts/pc3/config.kdl: -------------------------------------------------------------------------------- 1 | output "Samsung Electric Company S90D 0x01000E00" { 2 | mode "3840x2160@120.000" 3 | scale 2 4 | } 5 | 6 | input { 7 | touchpad { 8 | natural-scroll 9 | 10 | // I'm using a Magic Trackpad, which seems to scroll exactly twice as fast as is reasonable by default. 11 | scroll-factor 0.5 12 | } 13 | 14 | mouse { 15 | accel-profile "flat" 16 | accel-speed -0.2 17 | } 18 | } 19 | 20 | // Testing if this will help with the occasional flicker I get 21 | debug { 22 | wait-for-frame-completion-before-queueing 23 | keep-max-bpc-unchanged 24 | } 25 | -------------------------------------------------------------------------------- /hosts/macbook-air/users/robert/home-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | flake, 4 | config, 5 | ... 6 | }: 7 | { 8 | imports = [ "${flake}/users/robert/home-configuration.nix" ]; 9 | 10 | home.stateVersion = "24.05"; 11 | 12 | my.config.directory = "${config.home.homeDirectory}/.config/nix-dotfiles"; 13 | 14 | # Config fails to build without this. 15 | nix.package = pkgs.nix; 16 | 17 | # FIXME: This isn't working, need to figure out why 18 | targets.darwin.currentHostDefaults = { 19 | NSGlobalDomain = { 20 | NSUserKeyEquivalents = { 21 | "\\033Window\\033Zoom" = "@$\\\\U21a9"; 22 | }; 23 | }; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/ccase.nix: -------------------------------------------------------------------------------- 1 | { pname, pkgs, ... }: 2 | pkgs.rustPlatform.buildRustPackage { 3 | inherit pname; 4 | version = "0.4.1"; 5 | src = pkgs.fetchFromGitHub { 6 | owner = "rutrum"; 7 | repo = pname; 8 | rev = "7ca56557d0cc69641e0d0c5ae9370c48f4cce09d"; 9 | hash = "sha256-TQJkvANms/5Mzh1J4qsEYOrlML17dVv7MYEoN4Z/gm0="; 10 | }; 11 | cargoHash = "sha256-RLjwLr1IF1T3QR5t8i2dGEWs72YY49Ib1l8QlaFkcqg="; 12 | 13 | meta = { 14 | description = "Command line interface to convert strings into any case"; 15 | homepage = "https://github.com/rutrum/ccase"; 16 | license = pkgs.lib.licenses.mit; 17 | maintainers = [ ]; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/mrpack-install.nix: -------------------------------------------------------------------------------- 1 | { pkgs }: 2 | # Requires specifying the go version because it uses the 'tool' directive. 3 | # At the time of writing, the version used by buildGoModule is 1.23.5, but the 4 | # latest available is 1.24 5 | pkgs.buildGo124Module rec { 6 | pname = "mrpack-install"; 7 | # current as of 2025-02-20 8 | version = "v0.20.0-beta"; 9 | 10 | src = pkgs.fetchFromGitHub { 11 | owner = "nothub"; 12 | repo = "mrpack-install"; 13 | rev = "${version}"; 14 | hash = "sha256-vMueeK9iLr4W7LFJ+FQxATpB4s7QXazFVOmZ4SQ9B+M="; 15 | }; 16 | 17 | vendorHash = "sha256-GA3dbl+Rld6xlW5is3SINEhYIjfm00Sy8B51hgOcfCw="; 18 | 19 | doCheck = false; 20 | } 21 | -------------------------------------------------------------------------------- /config/ghostty/os-config-linux: -------------------------------------------------------------------------------- 1 | window-padding-x = 6 2 | window-padding-y = 4 3 | 4 | font-size = 8 5 | gtk-toolbar-style = raised-border 6 | gtk-titlebar-style = tabs 7 | gtk-wide-tabs = false 8 | 9 | 10 | # I'm trying to reserve ctrl+shift for window or tab-level stuff, 11 | # and use ctrl+alt for surfaces. 12 | 13 | keybind = ctrl+shift+r=prompt_surface_title 14 | keybind = ctrl+shift+comma=reload_config 15 | 16 | keybind = ctrl+alt+d=new_split:right 17 | keybind = ctrl+shift+d=new_split:down 18 | 19 | keybind = ctrl+alt+w=close_surface 20 | 21 | keybind = page_up=scroll_page_fractional:-0.5 22 | keybind = page_down=scroll_page_fractional:0.5 23 | 24 | keybind = ctrl+alt+s=write_scrollback_file:paste 25 | -------------------------------------------------------------------------------- /.github/workflows/update-flake.yml: -------------------------------------------------------------------------------- 1 | name: "Update nightly" 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 18 * * *" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: cachix/install-nix-action@v27 14 | with: 15 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | - name: Update flake.lock 18 | run: | 19 | git config --global user.name 'github-actions[bot]' 20 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 21 | nix flake update nixpkgs-unstable --commit-lock-file 22 | 23 | - name: Push to main 24 | run: git push 25 | -------------------------------------------------------------------------------- /packages/run.nix: -------------------------------------------------------------------------------- 1 | { pkgs, flake }: 2 | pkgs.stdenv.mkDerivation { 3 | name = "run"; 4 | 5 | src = flake; 6 | 7 | buildInputs = [ pkgs.fish ]; 8 | 9 | installPhase = '' 10 | mkdir -p $out/bin 11 | echo "#!${pkgs.fish}/bin/fish" > $out/bin/run 12 | echo "" >> $out/bin/run 13 | cat config/fish/functions/run.fish >> $out/bin/run 14 | echo "" >> $out/bin/run 15 | echo "run \$argv" >> $out/bin/run 16 | chmod +x $out/bin/run 17 | 18 | mkdir -p $out/share/fish/vendor_completions.d 19 | cp config/fish/completions/run.fish $out/share/fish/vendor_completions.d/ 20 | 21 | mkdir -p $out/share/fish/vendor_functions.d 22 | cp config/fish/functions/run.fish $out/share/fish/vendor_functions.d/ 23 | ''; 24 | } 25 | -------------------------------------------------------------------------------- /packages/helix.nix: -------------------------------------------------------------------------------- 1 | # This is a helix setup on either the latest (or close to the latest) version of 2 | # the plugin system fork, which is usually quite stable and only a couple of weeks 3 | # behind upstream. This wraps my existing Helix configuration, allowing me to use 4 | # it on all systems. 5 | { 6 | perSystem, 7 | pkgs, 8 | flake, 9 | }: 10 | let 11 | steelWithLsp = perSystem.steel.default.overrideAttrs (oldAttrs: { 12 | cargoBuildFlags = "-p cargo-steel-lib -p steel-interpreter -p steel-language-server"; 13 | }); 14 | in 15 | pkgs.writeShellScriptBin "hx" '' 16 | export PATH=${steelWithLsp}/bin:$PATH 17 | export STEEL_HOME=${perSystem.helix.helix-cogs} 18 | export STEEL_LSP_HOME=${perSystem.helix.helix-cogs}/steel-language-server 19 | exec ${perSystem.helix.default}/bin/hx -c ${flake}/config/helix/config.toml "$@" 20 | '' 21 | -------------------------------------------------------------------------------- /packages/schemat.nix: -------------------------------------------------------------------------------- 1 | { pname, pkgs, ... }: 2 | pkgs.rustPlatform.buildRustPackage { 3 | inherit pname; 4 | version = "0.2.14"; 5 | 6 | src = pkgs.fetchFromGitHub { 7 | owner = "raviqqe"; 8 | repo = pname; 9 | rev = "ea9bcb6545214a70ce93a5c49c229f430ab58c2e"; # as of 2025-01-22 10 | hash = "sha256-h8JjT1cspfpuLII2hBMVRiPMDoC2hV8e8SVykWVkkys="; 11 | }; 12 | 13 | # FIXME: This should really be removed in favor of using a proper nightly 14 | # toolchain, but it works for now. Might need to use rust-overlay? 15 | RUSTC_BOOTSTRAP = true; 16 | 17 | cargoHash = "sha256-ZKy+voOLROK1S5YD8b8i5/pXZXnQn2ZBarFsUjYThPY="; 18 | 19 | meta = { 20 | description = "Code formatter for Scheme, Lisp, and any S-expressions"; 21 | homepage = "github.com/raviqqe/schemat"; 22 | license = pkgs.lib.licenses.unlicense; 23 | maintainers = [ ]; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /devshell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | inputs, 4 | perSystem, 5 | }: 6 | pkgs.mkShellNoCC { 7 | packages = [ 8 | perSystem.home-manager.default 9 | perSystem.agenix.default 10 | # In my normal shell, run is a function. In any other shell, 11 | # or on a system without my configuration, it will instead 12 | # be the "packaged" version of that function. 13 | perSystem.self.run 14 | pkgs.nixos-rebuild 15 | pkgs.nixos-anywhere 16 | pkgs.nixd 17 | pkgs.taplo 18 | pkgs.age 19 | pkgs.deno 20 | ] 21 | ++ pkgs.lib.optional pkgs.stdenv.isDarwin [ 22 | perSystem.nix-darwin.default 23 | ]; 24 | # buildInputs = 25 | # [ ] 26 | # ++ pkgs.lib.optional pkgs.stdenv.isDarwin [ 27 | # pkgs.clang 28 | # pkgs.darwin.cctools # I've been burned in the past by not having this 29 | # ]; 30 | shellHook = '' 31 | export IN_NIX_CONFIG_DEVSHELL=1 32 | ''; 33 | } 34 | -------------------------------------------------------------------------------- /modules/darwin/system-defaults.nix: -------------------------------------------------------------------------------- 1 | { 2 | system.defaults.NSGlobalDomain = { 3 | ApplePressAndHoldEnabled = false; 4 | AppleShowAllExtensions = true; 5 | NSAutomaticCapitalizationEnabled = false; 6 | NSAutomaticPeriodSubstitutionEnabled = false; 7 | NSAutomaticSpellingCorrectionEnabled = false; 8 | NSWindowShouldDragOnGesture = true; 9 | InitialKeyRepeat = 15; 10 | KeyRepeat = 2; 11 | # Explicitly enabling media keys because the media keycodes themselves are 12 | # used for some shortcuts 13 | "com.apple.keyboard.fnState" = false; 14 | }; 15 | 16 | system.defaults.dock.autohide = true; 17 | 18 | system.defaults.finder = { 19 | ShowPathbar = true; 20 | # This magic string makes it search the current folder by default 21 | FXDefaultSearchScope = "SCcf"; 22 | # Use the column view by default (the obviously correct and best view) 23 | FXPreferredViewStyle = "clmv"; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /hosts/pc3/users/robert/home-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | flake, 4 | config, 5 | inputs, 6 | ... 7 | }: 8 | { 9 | imports = [ 10 | "${flake}/users/robert/home-configuration.nix" 11 | inputs.agenix.homeManagerModules.default 12 | ]; 13 | 14 | home.stateVersion = "25.05"; 15 | 16 | # Config fails to build without this. 17 | nix.package = pkgs.nix; 18 | 19 | my.config.directory = "${config.home.homeDirectory}/Developer/clo4/nix-dotfiles"; 20 | my.config.source = { 21 | ".config/zed" = "config/zed/hosts/pc3"; 22 | 23 | ".config/niri/config.kdl" = "config/niri/config.kdl"; 24 | ".config/niri/common" = "config/niri/common"; 25 | ".config/niri/host" = "config/niri/hosts/pc3"; 26 | }; 27 | 28 | age.secrets.niri-private = { 29 | file = "${flake}/config/niri/private.kdl"; 30 | path = "$HOME/.config/niri/private.kdl"; 31 | }; 32 | 33 | home.packages = [ 34 | pkgs.systemd-lsp 35 | ]; 36 | } 37 | -------------------------------------------------------------------------------- /hosts/work-macbookpro/users/robert/home-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | flake, 3 | config, 4 | pkgs, 5 | inputs, 6 | ... 7 | }: 8 | { 9 | home.stateVersion = "24.11"; 10 | 11 | imports = [ 12 | "${flake}/users/robert/work-configuration.nix" 13 | inputs.agenix.homeManagerModules.default 14 | ]; 15 | 16 | my.config.directory = "${config.home.homeDirectory}/Developer/clo4/nix-dotfiles"; 17 | my.config.source = { 18 | ".config/zed" = "config/zed/hosts/work-macbookpro"; 19 | }; 20 | 21 | age.secrets.work-gitconfig = { 22 | file = ./work-gitconfig.age; 23 | # This can't be inside my ~/.config/git directory because the entire 24 | # directory is symlinked non-recursively, and we need to symlink the 25 | # decrypted copy of this file to this path at login-time. 26 | path = "$HOME/Developer/Work/.gitconfig"; 27 | }; 28 | 29 | home.sessionVariables = { 30 | FISH_GREETING_CHECK_SUDO_TOUCHID = "1"; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /config/fish/functions/abbred.fish: -------------------------------------------------------------------------------- 1 | function abbred 2 | set abbrfile ~/.config/fish/conf.d/abbreviations.fish 3 | pushd ~/.config/fish 4 | # If $EDITOR is set by basically anything other than fish, 5 | # it will be a single string that could contain multiple space-separated 6 | # arguments. Using 'read -at' will tokenize it like the shell would, and 7 | # and store that in a list. 8 | set -l editor vim 9 | if set -q EDITOR 10 | echo -- $EDITOR | read -at editor 11 | end 12 | $editor $abbrfile 13 | set editor_status $status 14 | popd 15 | if test $editor_status -gt 0 16 | echo "$EDITOR exited with a non-zero status code, not executing abbreviations" 17 | return $editor_status 18 | end 19 | echo "removing and reapplying abbreviations" 20 | abbr --erase (abbr --list) 21 | # This file can be sourced unconditionally because it only contains abbreviations, 22 | # which are idempotent if executed with the same name. 23 | source $abbrfile 24 | end 25 | -------------------------------------------------------------------------------- /modules/darwin/fish-environment.nix: -------------------------------------------------------------------------------- 1 | # This module is loosely translated from 2 | # https://github.com/LnL7/nix-darwin/blob/6a1fdb2a1204c0de038847b601cff5012e162b5e/modules/programs/fish.nix 3 | # to resolve some issues with the environment not being set correctly. 4 | { pkgs, config, ... }: 5 | let 6 | babelfishTranslate = 7 | path: name: 8 | pkgs.runCommand "${name}.fish" { } '' 9 | ${pkgs.babelfish}/bin/babelfish < ${path} > $out 10 | ''; 11 | in 12 | { 13 | # Fish from Nix has a hook to setup the environment correctly, which 14 | # requires this exact path: /etc/fish/nixos-env-preinit.fish 15 | # The fish module sets this up correctly, but since we're not using 16 | # that, we have to do it manually. 17 | environment.etc."fish/nixos-env-preinit.fish".text = '' 18 | if [ -z "$__NIX_DARWIN_SET_ENVIRONMENT_DONE" ] 19 | source /etc/fish/setEnvironment.fish 20 | end 21 | ''; 22 | environment.etc."fish/setEnvironment.fish".source = 23 | babelfishTranslate config.system.build.setEnvironment "setEnvironment"; 24 | } 25 | -------------------------------------------------------------------------------- /config/fish/conf.d/options.fish: -------------------------------------------------------------------------------- 1 | # All of these options need to be exported for fish_indent to colorize correctly. 2 | 3 | set -gx fish_color_normal normal 4 | set -gx fish_color_command brcyan 5 | set -gx fish_color_keyword magenta 6 | set -gx fish_color_quote normal 7 | set -gx fish_color_redirection yellow --bold 8 | set -gx fish_color_end white 9 | set -gx fish_color_error red 10 | set -gx fish_color_param normal 11 | set -gx fish_color_valid_path cyan 12 | set -gx fish_color_option brblue 13 | set -gx fish_color_comment brblack 14 | set -gx fish_color_selection --background=brblack 15 | set -gx fish_color_operator yellow 16 | set -gx fish_color_escape cyan 17 | set -gx fish_color_autosuggestion brblack 18 | 19 | set -gx fish_color_cwd blue 20 | set -gx fish_color_cwd_root red 21 | set -gx fish_color_user green 22 | set -gx fish_color_host blue 23 | set -gx fish_color_host_remote yellow 24 | set -gx fish_color_status red 25 | set -gx fish_color_cancel red 26 | 27 | set -gx fish_color_search_match --background=brblack 28 | set -gx fish_color_history_current blue 29 | -------------------------------------------------------------------------------- /config/kitty/current-theme.conf: -------------------------------------------------------------------------------- 1 | ## name: Gruvbox Dark 2 | ## author: Pavel Pertsev 3 | ## license: MIT/X11 4 | ## upstream: https://raw.githubusercontent.com/gruvbox-community/gruvbox-contrib/master/kitty/gruvbox-dark.conf 5 | 6 | selection_foreground #ebdbb2 7 | selection_background #d65d0e 8 | 9 | background #282828 10 | foreground #ebdbb2 11 | 12 | color0 #3c3836 13 | color1 #cc241d 14 | color2 #98971a 15 | color3 #d79921 16 | color4 #458588 17 | color5 #b16286 18 | color6 #689d6a 19 | color7 #a89984 20 | color8 #928374 21 | color9 #fb4934 22 | color10 #b8bb26 23 | color11 #fabd2f 24 | color12 #83a598 25 | color13 #d3869b 26 | color14 #8ec07c 27 | color15 #fbf1c7 28 | 29 | cursor #bdae93 30 | cursor_text_color #665c54 31 | 32 | url_color #458588 33 | -------------------------------------------------------------------------------- /config/fish/functions/fish_greeting.fish: -------------------------------------------------------------------------------- 1 | function fish_greeting 2 | if test -n "$FISH_GREETING_CHECK_SUDO_TOUCHID" 3 | and not grep -qE '^auth\\s+sufficient\\s+pam_tid\\.so' /etc/pam.d/sudo_local 4 | gum style \ 5 | --foreground 3 \ 6 | --border-foreground 1 \ 7 | --bold \ 8 | --border rounded \ 9 | --align center \ 10 | --width 50 \ 11 | --margin "1 3" \ 12 | --padding "1 4" \ 13 | 'Touch ID will not work with sudo until the system configuration has been reapplied.' 14 | echo 15 | end 16 | 17 | if string match -q "/nix/store/*" "$NIX_CONFIG_DIR" 18 | gum style \ 19 | --foreground 3 \ 20 | --border-foreground 1 \ 21 | --bold \ 22 | --border rounded \ 23 | --align center \ 24 | --width 50 \ 25 | --margin "1 3" \ 26 | --padding "1 4" \ 27 | 'Configuration symlinked to /nix/store. 28 | You should set a config directory.' 29 | echo 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /hosts/pc3/users/work/home-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | perSystem, 4 | flake, 5 | config, 6 | inputs, 7 | ... 8 | }: 9 | { 10 | imports = [ 11 | "${flake}/users/robert/work-configuration.nix" 12 | inputs.agenix.homeManagerModules.default 13 | ]; 14 | 15 | home.stateVersion = "25.05"; 16 | 17 | # Config fails to build without this. 18 | nix.package = pkgs.nix; 19 | 20 | my.config.directory = "${config.home.homeDirectory}/Repos/nix-dotfiles"; 21 | 22 | age.secrets.work-gitconfig = { 23 | file = ./gitconfig.age; 24 | path = "$HOME/Repos/Work/.gitconfig"; 25 | }; 26 | 27 | my.config.source = { 28 | ".config/zed" = "config/zed/hosts/pc3"; 29 | ".config/niri/config.kdl" = "config/niri/config.kdl"; 30 | ".config/niri/common" = "config/niri/common"; 31 | ".config/niri/host" = "config/niri/hosts/pc3"; 32 | }; 33 | 34 | age.secrets.niri-private = { 35 | file = "${flake}/config/niri/private.kdl"; 36 | path = "$HOME/.config/niri/private.kdl"; 37 | }; 38 | 39 | home.packages = [ 40 | perSystem.winapps.winapps 41 | pkgs.systemd-lsp 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /config/fish/functions/nix.fish: -------------------------------------------------------------------------------- 1 | function nix --wraps nix 2 | if status is-interactive 3 | and test (count $argv) -gt 0 4 | and not contains -- --command $argv 5 | and not contains -- -c $argv 6 | and not contains -- --help $argv 7 | switch $argv[1] 8 | case develop 9 | announce nix develop $argv[2..] --command (status fish-path) 10 | case shell 11 | # eelco has stated that IN_NIX_SHELL will not be added to 'nix shell' 12 | # because its behavior differs from 'nix-shell' and 'nix develop', but 13 | # from a user's perspective, I'm still entering a subshell launched by 14 | # Nix, so I want to know about that. 15 | # Nix sets IN_NIX_SHELL to either 'pure' or 'impure'. I'm using an 16 | # alternative that differentiates it while still setting it. 17 | IN_NIX_SHELL=shell announce nix shell $argv[2..] --command (status fish-path) 18 | case '*' 19 | command nix $argv 20 | end 21 | else 22 | command nix $argv 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /config/niri/common/shell.kdl: -------------------------------------------------------------------------------- 1 | // dms is being managed by systemd: 2 | // systemctl --user add-wants niri.service dms 3 | // So, the following line is no longer necessary: 4 | // spawn-at-startup "dms" "run" 5 | 6 | // spawn-sh-at-startup "/usr/lib/polkit-kde-authentication-agent-1" 7 | 8 | spawn-at-startup "vicinae" "server" 9 | 10 | layer-rule { 11 | match namespace="^wallpaper$" 12 | place-within-backdrop true 13 | } 14 | 15 | layer-rule { 16 | match namespace="^notifications$" 17 | block-out-from "screencast" 18 | } 19 | 20 | hotkey-overlay { 21 | skip-at-startup 22 | } 23 | 24 | cursor { 25 | hide-when-typing 26 | xcursor-size 24 27 | xcursor-theme "capitaine-cursors" 28 | } 29 | 30 | screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" 31 | 32 | environment { 33 | XDG_SESSION_TYPE "wayland" 34 | XDG_CURRENT_DESKTOP "niri" 35 | 36 | ELECTRON_OZONE_PLATFORM_HINT "auto" 37 | 38 | QT_QPA_PLATFORM "wayland" 39 | QT_WAYLAND_DISABLE_WINDOWDECORATION "1" 40 | QT_QPA_PLATFORMTHEME "gtk3" 41 | QT_QPA_PLATFORMTHEME_QT6 "gtk3" 42 | 43 | _JAVA_AWT_WM_NONREPARENTING "1" 44 | } 45 | -------------------------------------------------------------------------------- /config/fish/conf.d/999_fix_path.fish: -------------------------------------------------------------------------------- 1 | # Standalone Home Manager leaves Fish to reconstruct its own PATH, which 2 | # takes the existing path from ZSH, puts the contents of /etc/paths and 3 | # /etc/paths.d first, then appends the existing non-duplicate entries to 4 | # the end. This results in the Nix entries being at the tail of the PATH, 5 | # which isn't ideal. The solution is to add all profile bin directories 6 | # to the front and deduplicate them. 7 | # 8 | # Without the check before executing, the NIX_PROFILES directories will 9 | # always be moved to the front, even when entering a nested `nix develop` 10 | # shell, which could result in the wrong program being executed if specified 11 | # by both the user environment and dev shell. 12 | if set -q IS_DARWIN; and not set -q __NIX_PROFILES_HAVE_BEEN_MOVED 13 | for path in (string split ' ' $NIX_PROFILES) 14 | set -g PATH $path/bin $PATH 15 | end 16 | set -gx __NIX_PROFILES_HAVE_BEEN_MOVED 17 | end 18 | 19 | # Deduplicate PATH because it might contain dupes after the handoff from ZSH. 20 | set -l new_path 21 | for path in $PATH 22 | if not contains -- $path $new_path 23 | set new_path $new_path $path 24 | end 25 | end 26 | set -gx PATH $new_path 27 | -------------------------------------------------------------------------------- /config/fish/config.fish: -------------------------------------------------------------------------------- 1 | if status is-interactive 2 | # Tide hasn't been set up yet but it is installed, so this must be a first-time setup. 3 | # This isn't critical to be in config.fish, but it's good to know that all other configuration 4 | # has already been applied before any setup is done. 5 | if not set -q tide_left_prompt_items; and type -q tide 6 | tide configure --auto --style='Lean' --prompt_colors='16 colors' --show_time='No' --lean_prompt_height='One line' --prompt_spacing='Compact' --icons='Few icons' --transient='No' 7 | 8 | set -g __setup__clear_screen_prompt_count 0 9 | function __setup__clear_screen --on-event fish_prompt 10 | if test "$__setup__clear_screen_prompt_count" = 0 11 | # Clearing the screen here gets rid of the greeting if it was displayed 12 | clear 13 | echo " Tide has been set up." 14 | echo " Press enter to begin." 15 | set -e __setup__clear_screen_prompt_count 16 | else 17 | clear 18 | functions -e __setup__clear_screen 19 | fish_greeting 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /secrets.nix: -------------------------------------------------------------------------------- 1 | let 2 | system-homeserver1 = builtins.readFile ./hosts/homeserver1/id_ed25519.pub; 3 | 4 | robert-macmini = builtins.readFile ./hosts/macmini/users/robert/id_ed25519.pub; 5 | robert-macbook-air = builtins.readFile ./hosts/macbook-air/users/robert/id_ed25519.pub; 6 | robert-pc3 = builtins.readFile ./hosts/pc3/users/robert/id_ed25519.pub; 7 | robert-personal = [ 8 | robert-pc3 9 | robert-macmini 10 | robert-macbook-air 11 | ]; 12 | 13 | robert-work-macbookpro = builtins.readFile ./hosts/work-macbookpro/users/robert/id_ed25519.pub; 14 | work-pc3 = builtins.readFile ./hosts/pc3/users/work/id_ed25519.pub; 15 | robert-work = [ 16 | robert-work-macbookpro 17 | work-pc3 18 | ]; 19 | 20 | robert = robert-personal ++ robert-work; 21 | in 22 | { 23 | "hosts/homeserver1/tailscale-homeserver1.age".publicKeys = [ system-homeserver1 ] ++ robert; 24 | "hosts/homeserver1/clouddns-config.json.age".publicKeys = [ system-homeserver1 ] ++ robert; 25 | "hosts/work-macbookpro/users/robert/work-gitconfig.age".publicKeys = [ 26 | robert-work-macbookpro 27 | ] 28 | ++ robert; 29 | "hosts/pc3/users/work/gitconfig.age".publicKeys = robert; 30 | "config/niri/private.kdl".publicKeys = robert; 31 | } 32 | -------------------------------------------------------------------------------- /users/robert/work-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | flake, 3 | config, 4 | pkgs, 5 | inputs, 6 | ... 7 | }: 8 | { 9 | imports = [ 10 | ./home-configuration.nix 11 | ]; 12 | 13 | # The work setups are managed much more imperatively than any of my personal 14 | # machines, mainly because it isn't worth my time while I'm working to set 15 | # up Nix correctly (without buy-in from anyone else on the team), and it 16 | # isn't worth my time when I'm not working because there are other things I'd 17 | # rather be doing. Instead, I can do as much as is reasonable in Nix, but 18 | # fall back to plugins and homebrew when stuff doesn't work right. 19 | 20 | my.programs.fish.plugins = [ 21 | (pkgs.fetchFromGitHub { 22 | owner = "jorgebucaran"; 23 | repo = "nvm.fish"; 24 | rev = "846f1f20b2d1d0a99e344f250493c41a450f9448"; # current as of 2025-11-18 25 | hash = "sha256-u3qhoYBDZ0zBHbD+arDxLMM8XoLQlNI+S84wnM3nDzg="; 26 | }) 27 | ]; 28 | 29 | home.packages = [ 30 | pkgs.awscli2 31 | pkgs.python3 32 | pkgs.nss_latest 33 | pkgs.ngrok 34 | pkgs.vtsls 35 | pkgs.mongosh 36 | pkgs.typos 37 | pkgs.typos-lsp 38 | pkgs.glow 39 | pkgs.deno 40 | pkgs._1password-cli 41 | pkgs.mongodb-tools 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /config/fish/functions/fish_job_summary.fish: -------------------------------------------------------------------------------- 1 | function fish_job_summary -a job_id is_foreground cmd_line signal_or_end_name signal_desc proc_pid proc_name 2 | if test "$signal_or_end_name" = SIGINT; and test $is_foreground -eq 1 3 | return 4 | end 5 | 6 | set -l max_cmd_len 32 7 | set cmd_line (string shorten -m$max_cmd_len -- $cmd_line) 8 | 9 | set -l message 10 | switch $signal_or_end_name 11 | # case STOPPED 12 | # set message (printf ( _ "fish: Job %s, '%s' has stopped\n" ) $job_id $cmd_line) 13 | case ENDED 14 | set message (printf ( _ "fish: Job %s, '%s' has ended\n" ) $job_id $cmd_line) 15 | case 'SIG*' 16 | if test -n "$proc_pid" 17 | set message (printf ( _ "fish: Process %s, '%s' from job %s, '%s' terminated by signal %s (%s)\n" ) \ 18 | $proc_pid $proc_name $job_id $cmd_line $signal_or_end_name $signal_desc) 19 | else 20 | set message (printf ( _ "fish: Job %s, '%s' terminated by signal %s (%s)\n" ) \ 21 | $job_id $cmd_line $signal_or_end_name $signal_desc) 22 | end 23 | end 24 | 25 | if test $is_foreground -eq 0; and test $signal_or_end_name != STOPPED 26 | __fish_echo string join \n -- $message 27 | else 28 | string join >&2 \n -- $message 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /hosts/homeserver1/clouddns.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | perSystem, 4 | ... 5 | }: 6 | { 7 | age.secrets.clouddns-config = { 8 | file = ./clouddns-config.json.age; 9 | owner = "clouddns"; 10 | group = "clouddns"; 11 | mode = "400"; 12 | }; 13 | 14 | users.users.clouddns = { 15 | description = "System user for clouddns"; 16 | isSystemUser = true; 17 | group = "clouddns"; 18 | }; 19 | 20 | users.groups.clouddns = { }; 21 | 22 | systemd.services.clouddns = { 23 | description = "Update Cloudflare DNS records with the current IP address"; 24 | after = [ "network-online.target" ]; 25 | wants = [ "network-online.target" ]; 26 | 27 | serviceConfig = { 28 | Type = "oneshot"; 29 | NoNewPrivileges = true; 30 | PrivateDevices = true; 31 | MemoryDenyWriteExecute = true; 32 | User = "clouddns"; 33 | Group = "clouddns"; 34 | Environment = [ 35 | "DDNS_CONFIG_PATH=${config.age.secrets.clouddns-config.path}" 36 | "DDNS_CACHE_PATH=/var/tmp" 37 | ]; 38 | ExecStart = "${perSystem.clouddns.default}/bin/clouddns"; 39 | }; 40 | }; 41 | 42 | systemd.timers.clouddns = { 43 | description = "Timer for clouddns"; 44 | wantedBy = [ "timers.target" ]; 45 | 46 | timerConfig = { 47 | OnBootSec = "1m"; 48 | OnCalendar = "*:0/20"; 49 | }; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /config/fish/functions/trash.fish: -------------------------------------------------------------------------------- 1 | function trash 2 | set -l rm_rf_targets 3 | set -l trash_targets 4 | 5 | for target in $argv 6 | set name (path basename -- $target) 7 | # Well, this is horrifying. Fish doesn't really have sane grouping, 8 | # so we need this utterly deranged nested if statement with two grouped 9 | # conditions in `begin; ...; end` blocks. 10 | # The idea is that all symlinks should be rm'd, and any files/dirs 11 | # in their respective kill-lists. 12 | if path is -l -- $target 13 | or begin 14 | path is -f -- $target 15 | and contains -- $name $skip_trash_files 16 | end 17 | or begin 18 | path is -d -- $target 19 | and begin 20 | contains -- $name $skip_trash_directories 21 | or test (count (command ls -A $target)) -eq 0 22 | end 23 | end 24 | or begin 25 | path is -x -- $target 26 | and test (head -c 2 $target) != "#!" 27 | end 28 | set -a rm_rf_targets $target 29 | else 30 | set -a trash_targets $target 31 | end 32 | end 33 | 34 | set total_rm (count $rm_rf_targets) 35 | if test "$total_rm" -gt 0 36 | echo "Permanently deleting $total_rm item(s)" 37 | command rm -rf $rm_rf_targets 38 | end 39 | 40 | set total_trash (count $trash_targets) 41 | if test "$total_trash" -gt 0 42 | echo "Trashing $total_trash item(s)" 43 | command trash $trash_targets 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /hosts/homeserver1/vrising.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | ... 5 | }: 6 | { 7 | systemd.tmpfiles.rules = [ 8 | "d /srv/vrising 0700 root root -" 9 | "d /srv/vrising/server 0700 root root -" 10 | "d /srv/vrising/data 0700 root root -" 11 | ]; 12 | 13 | virtualisation.oci-containers.containers.vrising = { 14 | image = "docker.io/trueosiris/vrising"; 15 | autoStart = true; 16 | ports = [ 17 | "9876:9876/udp" 18 | "9877:9877/udp" 19 | ]; 20 | volumes = [ 21 | "/srv/vrising/server:/mnt/vrising/server" 22 | "/srv/vrising/data:/mnt/vrising/persistentdata" 23 | ]; 24 | environment = { 25 | TZ = config.time.timeZone; 26 | SERVERNAME = "vrising-clo4"; 27 | }; 28 | extraOptions = [ 29 | "--network=bridge" 30 | ]; 31 | }; 32 | 33 | systemd.services.podman-vrising = { 34 | after = [ "network.target" ]; 35 | requires = [ "network.target" ]; 36 | }; 37 | 38 | # systemd.services.vrising-restart = { 39 | # description = "Restart V-Rising Server"; 40 | # requires = [ "podman-vrising.service" ]; 41 | # after = [ "podman-vrising.service" ]; 42 | # script = '' 43 | # ${pkgs.systemd}/bin/systemctl restart podman-vrising.service 44 | # ''; 45 | # serviceConfig = { 46 | # Type = "oneshot"; 47 | # User = "root"; 48 | # }; 49 | # }; 50 | 51 | # systemd.timers.vrising-restart = { 52 | # description = "Timer for daily Minecraft server restart"; 53 | # wantedBy = [ "timers.target" ]; 54 | # timerConfig = { 55 | # OnCalendar = "04:00:00"; 56 | # Unit = "vrising-restart.service"; 57 | # }; 58 | # }; 59 | } 60 | -------------------------------------------------------------------------------- /hosts/homeserver1/disko.nix: -------------------------------------------------------------------------------- 1 | { 2 | disko.devices.disk = { 3 | main = { 4 | type = "disk"; 5 | device = "/dev/nvme0n1"; 6 | content = { 7 | type = "gpt"; 8 | partitions = { 9 | ESP = { 10 | priority = 1; 11 | name = "ESP"; 12 | start = "1M"; 13 | end = "512M"; 14 | type = "EF00"; 15 | content = { 16 | type = "filesystem"; 17 | format = "vfat"; 18 | mountpoint = "/boot"; 19 | mountOptions = [ 20 | "defaults" 21 | "umask=0077" 22 | ]; 23 | }; 24 | }; 25 | swap = { 26 | size = "4G"; 27 | content = { 28 | type = "swap"; 29 | }; 30 | }; 31 | root = { 32 | size = "100%"; 33 | content = { 34 | type = "btrfs"; 35 | extraArgs = [ "-f" ]; 36 | subvolumes = { 37 | root = { 38 | mountOptions = [ "compress=zstd" ]; 39 | mountpoint = "/"; 40 | }; 41 | home = { 42 | mountOptions = [ "compress=zstd" ]; 43 | mountpoint = "/home"; 44 | }; 45 | nix = { 46 | mountOptions = [ 47 | "compress=zstd" 48 | "noatime" 49 | ]; 50 | mountpoint = "/nix"; 51 | }; 52 | }; 53 | }; 54 | }; 55 | }; 56 | }; 57 | }; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /config/fish/functions/fish_user_key_bindings.fish: -------------------------------------------------------------------------------- 1 | function fish_user_key_bindings 2 | fish_default_key_bindings 3 | bind ctrl-z 'fg 2>/dev/null; commandline -f repaint' 4 | bind alt-z 'zi; commandline -f repaint' 5 | 6 | # c-g for git status 7 | bind ctrl-g _fzf_git_status_modified 8 | 9 | # Not sure why but the order of these is broken by default. 10 | # expand-abbr needs to happen first so the cursor is 11 | # still over the abbreviation when it tries to expand. 12 | bind ' ' expand-abbr self-insert 13 | bind ';' expand-abbr self-insert 14 | bind '|' expand-abbr self-insert 15 | bind '&' expand-abbr self-insert 16 | bind '>' expand-abbr self-insert 17 | bind '<' expand-abbr self-insert 18 | bind ')' expand-abbr self-insert 19 | 20 | # This isn't bound by default because of the fzf keybindings. 21 | bind ctrl-r history-pager 22 | 23 | command -q trash && bind enter _execute_no_rm 24 | end 25 | 26 | function _fzf_git_status_modified 27 | set -l selected (git status --porcelain | sed 's/^.. //' | fzf --query (commandline --current-token) --multi --layout=reverse --height=40% --cycle) 28 | commandline --current-token --replace -- (string join ' ' -- $selected) 29 | commandline -f repaint 30 | end 31 | 32 | function _execute_no_rm 33 | if string match --quiet "rm *" -- (commandline) 34 | echo 35 | echo " $(set_color --background red --bold) ERROR $(set_color normal) Interactive usage of 'rm' is disabled. Use 'trash' to prevent data loss." 36 | echo " (if you $(set_color --italics)need$(set_color normal) to run 'rm', use 'command rm' instead)" 37 | commandline "" 38 | commandline -f repaint 39 | else 40 | commandline -f execute 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /modules/home/my-config.nix: -------------------------------------------------------------------------------- 1 | # A very simple Home Manager module that allows me to easily create out-of-store symlinks. 2 | { 3 | pkgs, 4 | config, 5 | lib, 6 | flake, 7 | ... 8 | }: 9 | let 10 | inherit (lib) types; 11 | cfg = config.my.config; 12 | mkConfigSymlink = 13 | relativePath: config.lib.file.mkOutOfStoreSymlink "${cfg.directory}/${relativePath}"; 14 | in 15 | { 16 | options.my.config = { 17 | directory = lib.mkOption { 18 | default = flake; 19 | type = types.path; 20 | description = '' 21 | Path to the directory that the configuration will be linked to. 22 | This is an absolute path on the system, which will be the flake's 23 | path in the store if not specified. 24 | ''; 25 | example = lib.literalExpression '' 26 | ''${config.home.homeDirectory}/.config/system-configuration 27 | ''; 28 | }; 29 | 30 | source = lib.mkOption { 31 | default = { }; 32 | type = with types; attrsOf (nullOr (either str path)); 33 | description = '' 34 | Mapping from system path to path relative to the source directory. 35 | The value can be either a directory or a file. 36 | To reference your XDG_CONFIG_HOME, use Home Manager's `xdg.configHome` 37 | value. 38 | ''; 39 | }; 40 | 41 | force = lib.mkOption { 42 | default = true; 43 | type = types.bool; 44 | description = '' 45 | Whether the configuration links should override whatever exists already. 46 | ''; 47 | }; 48 | }; 49 | 50 | config = lib.mkIf (cfg.source != { }) { 51 | home.file = 52 | let 53 | nonNull = lib.filterAttrs (n: v: v != null) cfg.source; 54 | in 55 | builtins.mapAttrs (_: v: { 56 | source = mkConfigSymlink v; 57 | force = cfg.force; 58 | }) nonNull; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /modules/home/my-programs-neovim.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | config, 5 | ... 6 | }: 7 | 8 | let 9 | cfg = config.my.programs.neovim; 10 | 11 | mkPluginLink = 12 | group: p: 13 | let 14 | subdir = if p.start or false then "start" else "opt"; 15 | in 16 | { 17 | "${config.xdg.dataHome}/nvim/site/pack/${group}/${subdir}/${p.name}" = { 18 | source = p.src; 19 | recursive = p.recursive; 20 | }; 21 | }; 22 | 23 | links = lib.foldlAttrs ( 24 | acc: group: plugins: 25 | lib.foldl' (acc2: pl: acc2 // mkPluginLink group pl) acc plugins 26 | ) { } cfg.packages; 27 | 28 | pluginType = lib.types.submodule ( 29 | { ... }: 30 | { 31 | options = { 32 | name = lib.mkOption { 33 | type = lib.types.str; 34 | description = "Directory name used with :packadd."; 35 | }; 36 | src = lib.mkOption { 37 | type = lib.types.path; 38 | description = "Plugin source derivation."; 39 | }; 40 | start = lib.mkOption { 41 | type = lib.types.bool; 42 | default = false; 43 | description = "Install under start/ when true, opt/ when false."; 44 | }; 45 | recursive = lib.mkOption { 46 | type = lib.types.bool; 47 | default = false; 48 | description = "Symlink the plugin files recursively when true, or the entire directory when false."; 49 | }; 50 | }; 51 | } 52 | ); 53 | 54 | in 55 | { 56 | options.my.programs.neovim = with lib; { 57 | enable = mkEnableOption "Install NeoVim plugins via packpath" // { 58 | default = true; 59 | }; 60 | packages = mkOption { 61 | type = types.attrsOf (types.listOf pluginType); 62 | default = { }; 63 | description = "Mapping from pack group to plugin list."; 64 | }; 65 | }; 66 | 67 | config = lib.mkIf cfg.enable { 68 | home.file = links; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /config/helix/languages.toml: -------------------------------------------------------------------------------- 1 | [[language]] 2 | name = "go" 3 | auto-format = true 4 | formatter.command = "goimports" 5 | 6 | 7 | [[language]] 8 | name = "typescript" 9 | language-servers = ["vtsls"] 10 | 11 | [language-server.vtsls] 12 | command = "vtsls" 13 | args = ["--stdio"] 14 | 15 | 16 | [[language]] 17 | name = "nix" 18 | auto-format = true 19 | language-servers = ["nil"] 20 | formatter.command = "nixfmt" 21 | formatter.args = ["-"] 22 | 23 | 24 | [[language]] 25 | name = "fish" 26 | auto-format = true 27 | formatter.command = "fish_indent" 28 | language-servers = ["fish-lsp"] 29 | 30 | [language-server.fish-lsp] 31 | command = "fish-lsp" 32 | # Helix doesn't support popups at the moment, which 33 | # causes the language server to crash 34 | args = ["start"] 35 | environment = { "fish_lsp_show_client_popups" = "false" } 36 | 37 | 38 | [[language]] 39 | name = "markdown" 40 | auto-format = false 41 | language-servers = ["ltex-ls"] 42 | formatter.command = "deno" 43 | formatter.args = ["--ext=md", "-"] 44 | 45 | 46 | [[language]] 47 | name = "git-commit" 48 | language-servers = ["ltex-ls"] 49 | 50 | 51 | [[language]] 52 | name = "scheme" 53 | formatter = { command = "schemat", args = ["/dev/stdin"] } 54 | # auto-format = true 55 | language-servers = ["steel-language-server"] 56 | 57 | [language-server.steel-language-server] 58 | command = "steel-language-server" 59 | 60 | 61 | [language-server.deno] 62 | command = "deno" 63 | args = ["lsp"] 64 | config.enable = true 65 | config.lint = true 66 | config.unstable = true 67 | 68 | 69 | [language-server.ltex-ls] 70 | command = "ltex-ls" 71 | 72 | 73 | [language-server.rust-analyzer] 74 | command = "rust-analyzer" 75 | 76 | 77 | [language-server.rust-analyzer.config.check] 78 | command = "clippy" 79 | 80 | 81 | [language-server.svelteserver] 82 | command = "svelteserver" 83 | 84 | 85 | [language-server.tailwindcss] 86 | command = "tailwindcss-language-server" 87 | args = ["--stdio"] 88 | language-id = "tailwindcss" 89 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 5 | 6 | # blueprint.url = "path:/Users/robert/Developer/blueprint"; 7 | # blueprint.url = "github:numtide/blueprint"; 8 | blueprint.url = "github:clo4/blueprint/generic-users"; 9 | blueprint.inputs.nixpkgs.follows = "nixpkgs"; 10 | 11 | nix-darwin.url = "github:LnL7/nix-darwin/master"; 12 | nix-darwin.inputs.nixpkgs.follows = "nixpkgs"; 13 | 14 | # helix.url = "github:clo4/helix/helix-cogs-steel-language-server"; 15 | # helix.inputs.nixpkgs.follows = "nixpkgs"; 16 | # helix.inputs.crane.follows = "crane"; 17 | # crane.url = "github:ipetkov/crane"; 18 | # steel.url = "github:mattwparas/steel"; 19 | # steel.inputs.nixpkgs.follows = "nixpkgs"; 20 | 21 | helix.url = "github:helix-editor/helix"; 22 | helix.inputs.nixpkgs.follows = "nixpkgs"; 23 | 24 | home-manager.url = "github:nix-community/home-manager/master"; 25 | home-manager.inputs.nixpkgs.follows = "nixpkgs"; 26 | 27 | nix-homebrew.url = "github:zhaofengli/nix-homebrew"; 28 | 29 | disko.url = "github:nix-community/disko"; 30 | disko.inputs.nixpkgs.follows = "nixpkgs"; 31 | 32 | srvos.url = "github:nix-community/srvos"; 33 | srvos.inputs.nixpkgs.follows = "nixpkgs"; 34 | 35 | agenix.url = "github:ryantm/agenix"; 36 | agenix.inputs.nixpkgs.follows = "nixpkgs"; 37 | agenix.inputs.darwin.follows = "nix-darwin"; 38 | agenix.inputs.home-manager.follows = "home-manager"; 39 | 40 | clouddns.url = "github:clo4/clouddns"; 41 | clouddns.inputs.nixpkgs.follows = "nixpkgs"; 42 | clouddns.inputs.blueprint.follows = "blueprint"; 43 | 44 | winapps.url = "github:winapps-org/winapps"; 45 | winapps.inputs.nixpkgs.follows = "nixpkgs"; 46 | }; 47 | 48 | outputs = 49 | inputs: 50 | inputs.blueprint { 51 | inherit inputs; 52 | nixpkgs.config.allowUnfree = true; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /hosts/work-macbookpro/darwin-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | lib, 5 | flake, 6 | ... 7 | }: 8 | { 9 | imports = [ 10 | inputs.self.darwinModules.system-defaults 11 | inputs.self.darwinModules.fish-environment 12 | 13 | inputs.nix-homebrew.darwinModules.nix-homebrew 14 | ]; 15 | 16 | users.users.robert = { 17 | description = "Robert"; 18 | home = "/Users/robert"; 19 | openssh.authorizedKeys.keyFiles = [ 20 | "${flake}/hosts/pc3/users/work/id_ed25519.pub" 21 | ]; 22 | }; 23 | 24 | services.openssh.enable = true; 25 | environment.etc."ssh/sshd_config.d/999-disable-password-auth.conf".text = '' 26 | PermitRootLogin no 27 | PasswordAuthentication no 28 | KbdInteractiveAuthentication no 29 | UsePAM no 30 | ''; 31 | 32 | # This needs to be reapplied after each system update. My Fish configuration 33 | # will warn about this if it detects the line it adds to sudo_local is absent. 34 | security.pam.services.sudo_local.touchIdAuth = true; 35 | 36 | networking.hostName = "work-macbookpro"; 37 | 38 | system.stateVersion = 6; 39 | system.primaryUser = "robert"; 40 | 41 | home-manager.backupFileExtension = "hm-backup"; 42 | 43 | nix-homebrew.enable = true; 44 | # A user needs to own the prefix, so we'll make it my account 45 | nix-homebrew.user = "robert"; 46 | 47 | environment.systemPackages = [ 48 | pkgs.fish 49 | ]; 50 | 51 | # Not sure about adding in vendor_conf.d because tools can just dump their init into it, 52 | # and because it will be included in a directory in $NIX_PROFILES, therefore 53 | # also $XDG_DATA_DIRS, it will be sourced during startup. This is probably fine, but 54 | # I want total control over what runs in my fish config. 55 | environment.pathsToLink = [ 56 | # "/share/fish/vendor_conf.d" 57 | "/share/fish/vendor_completions.d" 58 | "/share/fish/vendor_functions.d" 59 | ]; 60 | 61 | nixpkgs.hostPlatform = "aarch64-darwin"; 62 | 63 | nix.enable = true; 64 | nix.nixPath = lib.mkForce [ 65 | "nixpkgs=${inputs.nixpkgs}" 66 | "home-manager=${inputs.home-manager}" 67 | ]; 68 | nix.settings.experimental-features = [ 69 | "nix-command" 70 | "flakes" 71 | ]; 72 | nix.settings.trusted-users = [ "@admin" ]; 73 | nix.channel.enable = false; 74 | } 75 | -------------------------------------------------------------------------------- /config/niri/common/visuals.kdl: -------------------------------------------------------------------------------- 1 | window-rule { 2 | geometry-corner-radius 12 3 | clip-to-geometry true 4 | tiled-state true 5 | draw-border-with-background false 6 | } 7 | 8 | window-rule { 9 | match is-floating=true 10 | 11 | geometry-corner-radius 16 12 | 13 | shadow { 14 | on 15 | spread -6 16 | softness 48 17 | offset x=0 y=8 18 | color "#00000090" 19 | inactive-color "#00000040" 20 | } 21 | 22 | border { 23 | on 24 | width 0.5 25 | active-color "#ffffffa0" 26 | inactive-color "#ffffff60" 27 | } 28 | } 29 | 30 | window-rule { 31 | match is-floating=true is-active=true 32 | 33 | shadow { 34 | spread -8 35 | softness 32 36 | } 37 | } 38 | 39 | layout { 40 | gaps 6 41 | 42 | empty-workspace-above-first 43 | 44 | always-center-single-column 45 | 46 | default-column-display "tabbed" 47 | tab-indicator { 48 | hide-when-single-tab 49 | // place-within-column 50 | length total-proportion=0.5 51 | gap -6 52 | width 3 53 | corner-radius 6 54 | active-color "#ffffffc0" 55 | inactive-color "#ffffff80" 56 | gaps-between-tabs 12 57 | } 58 | 59 | default-column-width { 60 | proportion 0.6 61 | } 62 | preset-column-widths { 63 | proportion 0.4 64 | proportion 0.6 65 | } 66 | 67 | focus-ring { 68 | off 69 | } 70 | border { 71 | on 72 | width 1 73 | active-color "#5da8dc" 74 | inactive-color "#77777b" 75 | } 76 | shadow { 77 | on 78 | softness 0 79 | spread 0.5 80 | offset x=0 y=0 81 | color "000000c0" 82 | inactive-color "000000a0" 83 | } 84 | 85 | background-color "#000000" 86 | } 87 | 88 | overview { 89 | backdrop-color "#000000" 90 | 91 | // since the background is black, no point rendering the shadow 92 | workspace-shadow { 93 | off 94 | } 95 | } 96 | 97 | prefer-no-csd 98 | 99 | animations { 100 | slowdown 0.3 101 | } 102 | 103 | recent-windows { 104 | highlight { 105 | corner-radius 6 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /config/zsh/.zshrc: -------------------------------------------------------------------------------- 1 | # In order to keep ZSH as the login shell but still use 2 | # Fish interactively, this snippet will execute Fish if 3 | # none of the parent processes were Fish. Unless I run into 4 | # some serious issues, I've consolidated on all shells 5 | # launching ZSH, which does some amount of generic init, 6 | # then puts me into Fish if the session is interactive. 7 | # 8 | # zshenv is the first file that ZSH executes, but a standalone 9 | # Home Manager installation adds the Nix setup to zshrc, 10 | # so the necessary setup would be skipped in that system. 11 | # Everything that zshenv does can also be done in zshrc, 12 | # unless it has to be done unconditionally for non-interactive 13 | # shells - in which case that's also the best spot for it, 14 | # since the spawned and exec'd fish shell will inherit 15 | # the variables. 16 | # 17 | # NOTE: this doesn't handle `nix shell` at all because 18 | # it's difficult to determine if the current shell was 19 | # directly invoked by it. Instead, I use a wrapper function 20 | # in Fish that appends `--command $(status fish-path)`. 21 | 22 | if [[ -o interactive && ! ( "$TERM_PROGRAM" = "WarpTerminal" ) ]]; then 23 | found_fish=0 24 | if type fish >/dev/null; then 25 | ppid=$$ 26 | while [[ $ppid -gt 1 ]]; do 27 | if [[ $(ps -o comm= -p $ppid) =~ fish$ ]]; then 28 | found_fish=1 29 | break 30 | fi 31 | ppid=$(ps -o ppid= -p $ppid) 32 | ppid=${ppid##*[[:space:]]} # Strip leading whitespace 33 | done 34 | if [[ $found_fish -eq 0 ]]; then 35 | local shell=$(command -v fish) 36 | exec -l "$shell" -l 37 | fi 38 | else 39 | echo 40 | echo " WARNING: fish not found in PATH - falling back to zsh" 41 | echo 42 | fi 43 | fi 44 | 45 | # Beyond this point, we're setting up ZSH. If Fish doesn't exist on PATH 46 | # then I'll know that by now, so just set things up nicely. 47 | 48 | if [[ -n $GHOSTTY_RESOURCES_DIR ]]; then 49 | source "$GHOSTTY_RESOURCES_DIR"/shell-integration/zsh/ghostty-integration 50 | fi 51 | 52 | # Removes duplicates by keeping the first occurrence of each item, left to right. 53 | typeset -U path cdpath fpath manpath 54 | 55 | for profile in ${(z)NIX_PROFILES}; do 56 | fpath+=($profile/share/zsh/site-functions $profile/share/zsh/$ZSH_VERSION/functions $profile/share/zsh/vendor-completions) 57 | done 58 | 59 | HISTFILE=$HOME/.local/share/zsh/.zsh_history 60 | -------------------------------------------------------------------------------- /config/ghostty/os-config-darwin: -------------------------------------------------------------------------------- 1 | command = /bin/sh -c "/bin/wait4path /nix/store && exec -l $SHELL" 2 | 3 | auto-update-channel = tip 4 | 5 | macos-option-as-alt = true 6 | macos-titlebar-style = tabs 7 | 8 | window-padding-x = 6 9 | window-padding-y = 4 10 | 11 | keybind = clear 12 | 13 | # On my Mac, I use native 4k (no scaling) which results in almost unreadable 14 | # text at the default size. 15 | font-size = 16 16 | 17 | # All keybinds are "command-first" - if it's possible to bind just using 18 | # command, that should be the preference. Nothing in the terminal will use 19 | # command, so it's safe, and simpler is easier to remember + use. 20 | 21 | keybind = command+shift+comma=reload_config 22 | 23 | # Splits are controlled by D. 24 | # preference for vertical, shift becomes horizontal. 25 | # --- 26 | keybind = command+d=new_split:right 27 | keybind = command+shift+d=new_split:down 28 | 29 | # Moving between splits is an arrow key. 30 | # Enter will "fullscreen" a split, for full focus. 31 | # --- 32 | keybind = command+enter=toggle_split_zoom 33 | keybind = command+left=goto_split:left 34 | keybind = command+right=goto_split:right 35 | keybind = command+up=goto_split:top 36 | keybind = command+down=goto_split:bottom 37 | 38 | # Resizing is the same as moving, but you add shift. 39 | # --- 40 | keybind = command+shift+left=resize_split:left,40 41 | keybind = command+shift+right=resize_split:right,40 42 | keybind = command+shift+up=resize_split:up,40 43 | keybind = command+shift+down=resize_split:down,40 44 | keybind = command+equal=equalize_splits 45 | 46 | # macOS controls. 47 | # Some things are implemented by the GUI toolkit, e.g. control+tab 48 | # --- 49 | keybind = command+t=new_tab 50 | keybind = command+n=new_window 51 | keybind = command+w=close_surface 52 | keybind = command+shift+w=close_window 53 | keybind = command+q=quit 54 | keybind = command+c=copy_to_clipboard 55 | keybind = command+v=paste_from_clipboard 56 | keybind = command+shift+v=paste_from_selection 57 | keybind = command+p=toggle_command_palette 58 | 59 | 60 | # Misc functionality 61 | # --- 62 | keybind = page_up=scroll_page_fractional:-0.5 63 | keybind = page_down=scroll_page_fractional:0.5 64 | # The s can be a mnemonic for scrollback or search 65 | keybind = command+shift+s=write_scrollback_file:paste 66 | 67 | 68 | # Quick terminal settings 69 | quick-terminal-animation-duration = 0 70 | quick-terminal-position = right 71 | quick-terminal-size = 50% 72 | keybind = global:command+shift+option+control+grave_accent=toggle_quick_terminal 73 | -------------------------------------------------------------------------------- /config/fish/conf.d/eza.fish: -------------------------------------------------------------------------------- 1 | set -gx EZA_COLORS "reset:fi=0:di=1;34:ln=36:or=31:ex=32:pi=90:so=90:bd=90:cd=90:oc=2:ur=2:uw=2:ux=2:ue=2:gr=2:gw=2:gx=2:tr=2:tw=2:tx=2:su=1;37:sf=1;37:xa=90:sn=2:sb=2;90:uu=2:un=2:uR=2;37:gu=2:gn=2:gR=2;37:xx=90:da=2:in=2:bl=2:hd=1;37:lp=36:mp=34" 2 | 3 | # ---------------------------------------------------- 4 | # The rest of this file is to make it easy to generate 5 | # ---------------------------------------------------- 6 | # 7 | 8 | # set -gx --path EZA_COLORS reset 9 | 10 | # # Basic file types 11 | # set -a EZA_COLORS "fi=0" # Regular files (normal) 12 | # set -a EZA_COLORS "di=1;34" # Directories (bold blue) 13 | # set -a EZA_COLORS "ln=36" # Symlinks (cyan) 14 | # set -a EZA_COLORS "or=31" # Broken symlinks (red) 15 | # set -a EZA_COLORS "ex=32" # Executable files (green) 16 | 17 | # # Special files 18 | # set -a EZA_COLORS "pi=90" # Named pipes (dark gray) 19 | # set -a EZA_COLORS "so=90" # Sockets (dark gray) 20 | # set -a EZA_COLORS "bd=90" # Block devices (dark gray) 21 | # set -a EZA_COLORS "cd=90" # Character devices (dark gray) 22 | 23 | # # Permissions (dimmed) 24 | # set -a EZA_COLORS "oc=2" # Octal permissions 25 | # set -a EZA_COLORS "ur=2" # User read 26 | # set -a EZA_COLORS "uw=2" # User write 27 | # set -a EZA_COLORS "ux=2" # User execute 28 | # set -a EZA_COLORS "ue=2" # User execute (other file types) 29 | # set -a EZA_COLORS "gr=2" # Group read 30 | # set -a EZA_COLORS "gw=2" # Group write 31 | # set -a EZA_COLORS "gx=2" # Group execute 32 | # set -a EZA_COLORS "tr=2" # Others read 33 | # set -a EZA_COLORS "tw=2" # Others write 34 | # set -a EZA_COLORS "tx=2" # Others execute 35 | # set -a EZA_COLORS "su=1;37" # Setuid/setgid (bold white) 36 | # set -a EZA_COLORS "sf=1;37" # Setuid/setgid/sticky (other file types) 37 | # set -a EZA_COLORS "xa=90" # Extended attributes (dark gray) 38 | 39 | # # File size (dimmed) 40 | # set -a EZA_COLORS "sn=2" # File size numbers 41 | # set -a EZA_COLORS "sb=2;90" # File size units (dimmed dark gray) 42 | 43 | # # User/Group (dimmed) 44 | # set -a EZA_COLORS "uu=2" # Current user 45 | # set -a EZA_COLORS "un=2" # Other users 46 | # set -a EZA_COLORS "uR=2;37" # Root user (dimmed white) 47 | # set -a EZA_COLORS "gu=2" # Your group 48 | # set -a EZA_COLORS "gn=2" # Other groups 49 | # set -a EZA_COLORS "gR=2;37" # Root group (dimmed white) 50 | 51 | # # UI elements 52 | # set -a EZA_COLORS "xx=90" # UI punctuation (dark gray) 53 | # set -a EZA_COLORS "da=2" # Date (dimmed) 54 | # set -a EZA_COLORS "in=2" # Inode (dimmed) 55 | # set -a EZA_COLORS "bl=2" # Blocks (dimmed) 56 | # set -a EZA_COLORS "hd=1;37" # Table header (bold white) 57 | # set -a EZA_COLORS "lp=36" # Symlink path (cyan) 58 | 59 | # # Mount points 60 | # set -a EZA_COLORS "mp=34" # Mount points (blue) 61 | -------------------------------------------------------------------------------- /config/nvim/plugins.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | let 3 | fromGitHub = 4 | { 5 | owner, 6 | repo, 7 | name ? repo, 8 | rev, 9 | hash, 10 | opt ? false, 11 | recursive ? false, 12 | }: 13 | { 14 | inherit name recursive; 15 | start = !opt; 16 | src = pkgs.fetchFromGitHub { 17 | inherit 18 | owner 19 | repo 20 | rev 21 | hash 22 | ; 23 | }; 24 | }; 25 | in 26 | { 27 | my.programs.neovim.packages.nix-managed = [ 28 | (fromGitHub { 29 | owner = "echasnovski"; 30 | repo = "mini.surround"; 31 | rev = "1a2b59c77a0c4713a5bd8972da322f842f4821b1"; 32 | hash = "sha256-khhvGI4aWVgdTeBabxncVNWPI5vouSTpHAUYfEhgISs="; 33 | }) 34 | 35 | (fromGitHub { 36 | owner = "sainnhe"; 37 | repo = "gruvbox-material"; 38 | rev = "66cfeb7050e081a746a62dd0400446433e802368"; 39 | hash = "sha256-EPz9jIbyext4WEjzh5V8JKMeMBVgUzmgeBPqiWf0dc4="; 40 | }) 41 | 42 | (fromGitHub { 43 | owner = "nvim-treesitter"; 44 | repo = "nvim-treesitter"; 45 | rev = "1f069f1bc610493d38276023165075f7424ca7b4"; 46 | hash = "sha256-PABUBcr0QFROpO3WHi1BwHpXk4Pe+cm4vr3otiyEK/4="; 47 | }) 48 | 49 | # Oil depends on at least one library to do icons 50 | (fromGitHub { 51 | owner = "stevearc"; 52 | repo = "oil.nvim"; 53 | rev = "08c2bce8b00fd780fb7999dbffdf7cd174e896fb"; 54 | hash = "sha256-fbRbRT9VJdppOs4hML1J113qLHdj7YRuSDQgZkt34cM="; 55 | }) 56 | (fromGitHub { 57 | owner = "echasnovski"; 58 | repo = "mini.icons"; 59 | rev = "94848dad1589a199f876539bd79befb0c5e3abf0"; 60 | hash = "sha256-2S9w8OGfV0QFs814cYMOzYiZwCZmyDl6n0TMsNWuIKA="; 61 | }) 62 | 63 | # Telescope has a few dependencies. 64 | (fromGitHub { 65 | owner = "nvim-telescope"; 66 | repo = "telescope.nvim"; 67 | rev = "b4da76be54691e854d3e0e02c36b0245f945c2c7"; 68 | hash = "sha256-JpW0ehsX81yVbKNzrYOe1hdgVMs6oaaxMLH6lECnOJg="; 69 | }) 70 | (fromGitHub { 71 | owner = "nvim-lua"; 72 | repo = "plenary.nvim"; 73 | rev = "857c5ac632080dba10aae49dba902ce3abf91b35"; 74 | hash = "sha256-8FV5RjF7QbDmQOQynpK7uRKONKbPRYbOPugf9ZxNvUs="; 75 | }) 76 | 77 | { 78 | name = "telescope-fzf-native.nvim"; 79 | src = pkgs.stdenv.mkDerivation { 80 | name = "telescope-fzf-native"; 81 | src = pkgs.fetchFromGitHub { 82 | owner = "nvim-telescope"; 83 | repo = "telescope-fzf-native.nvim"; 84 | rev = "1f08ed60cafc8f6168b72b80be2b2ea149813e55"; 85 | hash = "sha256-Zyv8ikxdwoUiDD0zsqLzfhBVOm/nKyJdZpndxXEB6ow="; 86 | }; 87 | installPhase = '' 88 | runHook preInstall 89 | all_files=$(ls) 90 | mkdir -p $out 91 | cp -R $all_files $out 92 | runHook postInstall 93 | ''; 94 | }; 95 | } 96 | ]; 97 | } 98 | -------------------------------------------------------------------------------- /config/niri/common/window-rule.kdl: -------------------------------------------------------------------------------- 1 | workspace "games" 2 | 3 | window-rule { 4 | // These display media, the media should dictate how wide the column should be 5 | match app-id=r#"^org\.gnome\.(Loupe|Showtime)$"# 6 | default-column-display "normal" 7 | default-column-width {} 8 | } 9 | 10 | window-rule { 11 | match app-id=r#"^org\.gnome\.Calculator$"# 12 | open-floating true 13 | default-floating-position x=100 y=0 relative-to="right" 14 | } 15 | 16 | window-rule { 17 | match app-id=r#"^XEyes$"# 18 | 19 | default-column-width { fixed 340; } 20 | default-window-height { fixed 240; } 21 | open-floating true 22 | default-floating-position x=30 y=30 relative-to="top-right" 23 | } 24 | 25 | // Indicate screencasted windows with red colors. 26 | window-rule { 27 | match is-window-cast-target=true 28 | 29 | focus-ring { 30 | off 31 | } 32 | 33 | border { 34 | width 4 35 | active-color "#7bff44aa" 36 | inactive-color "#4f9b05aa" 37 | } 38 | 39 | tab-indicator { 40 | active-color "#4f9b05" 41 | inactive-color "#4f9b05" 42 | } 43 | } 44 | 45 | // I use the following apps in full-width, pretty much exclusively 46 | window-rule { 47 | match app-id=r#"^firefox$"# 48 | match app-id=r#"^code$"# 49 | match app-id=r#"^spotify$"# 50 | 51 | default-column-width { 52 | proportion 1.0 53 | } 54 | } 55 | 56 | window-rule { 57 | match app-id=r#"^firefox$"# title=r#"^Picture-in-Picture$"# 58 | 59 | open-floating true 60 | geometry-corner-radius 8 61 | default-floating-position x=12 y=12 relative-to="bottom-right" 62 | default-column-width { fixed 480; } 63 | default-window-height { fixed 270; } 64 | border { 65 | inactive-color "#ffffff40" 66 | } 67 | } 68 | 69 | window-rule { 70 | match app-id=r#"^org\.keepassxc\.KeePassXC$"# 71 | match app-id=r#"^org\.gnome\.World\.Secrets$"# 72 | match app-id=r#"^1password$"# 73 | 74 | block-out-from "screen-capture" 75 | 76 | // Use this instead if you want them visible on third-party screenshot tools. 77 | // block-out-from "screencast" 78 | } 79 | 80 | window-rule { 81 | match app-id=r#"^com\.discordapp\.Discord$"# 82 | open-on-workspace "games" 83 | default-column-width { 84 | proportion 0.8 85 | } 86 | } 87 | 88 | window-rule { 89 | match app-id=r#"^steam$"# 90 | exclude title=r#"^notificationtoasts_\d+_desktop$"# 91 | open-on-workspace "games" 92 | default-column-width { 93 | proportion 0.8 94 | } 95 | shadow { off; } 96 | border { off; } 97 | focus-ring { off; } 98 | clip-to-geometry false 99 | } 100 | 101 | window-rule { 102 | match app-id=r#"^steam$"# title=r#"^notificationtoasts_\d+_desktop$"# 103 | shadow { off; } 104 | border { off; } 105 | focus-ring { off; } 106 | clip-to-geometry false 107 | default-floating-position x=0 y=0 relative-to="bottom-right" 108 | } 109 | 110 | -------------------------------------------------------------------------------- /config/fish/conf.d/abbreviations.fish: -------------------------------------------------------------------------------- 1 | # General shell helpers. 2 | # These are commands that I type enough that I need them 3 | # to be faster to type. 4 | abbr -a - "cd -" 5 | abbr -a .. "cd .." 6 | abbr -a cpr "cp -r" 7 | abbr -a % "xargs -I % --" 8 | abbr -a f "fzf |" 9 | abbr -a fm "fzf --multi |" 10 | abbr -a rmf "rm -rf" 11 | abbr -a md "mkdir -p" 12 | abbr -a c cp 13 | abbr -a cmv "command -v" 14 | command -q trash; and abbr -a rm trash 15 | abbr -a j jj 16 | abbr -a fns functions 17 | abbr -a p pnpm 18 | abbr -a pr "pnpm run" 19 | abbr -a pa "pnpm add" 20 | abbr -a pi "pnpm install" 21 | abbr -a px pnpx 22 | abbr -a nx "npx nx" 23 | abbr -a nxr "npx nx run" 24 | abbr -a nrsl "npm run start-local" 25 | 26 | abbr -a r run 27 | 28 | if set -q IS_DARWIN 29 | abbr -a p pbcopy 30 | abbr -a pp pbpaste 31 | abbr -a b brew 32 | end 33 | 34 | abbr -a gb "go build" 35 | abbr -a gb. "go build ./..." 36 | abbr -a gf "go fmt" 37 | abbr -a gf. "go fmt ./..." 38 | abbr -a gt "go test" 39 | abbr -a gt. "go test ./..." 40 | abbr -a glint "golangci-lint run" 41 | 42 | abbr -a n nix 43 | abbr -a nxi nix 44 | abbr -a nd "nix develop" 45 | abbr -a nb "nix build" 46 | abbr -a nr "nix run" 47 | abbr -a ns "nix shell" 48 | abbr -a nre "nix repl" 49 | abbr -a nf "nix fmt" 50 | abbr -a nfs "nix flake show" 51 | abbr -a nfl "nix flake lock" 52 | abbr -a nfu "nix flake update" 53 | abbr -a nfuc "nix flake update --commit-lock-file" 54 | abbr -a nfc "nix flake check" 55 | abbr -a nfi "nix flake init" 56 | abbr -a nfit "nix flake init --template" 57 | 58 | # Random abbreviations that are easier to type on some layouts, because I hop 59 | # around a lot. 60 | abbr -a nv nvim 61 | abbr -a he hx 62 | 63 | abbr -a t tmux 64 | abbr -a ta "tmux attach; or tmux" 65 | abbr -a tk "tmux kill-session" 66 | abbr -a tl "tmux list-sessions" 67 | 68 | abbr -a ts tailscale 69 | abbr -a tsd tailscaled 70 | abbr -a tss "tailscale status" 71 | abbr -a tf terraform # not installed globally, used in projects 72 | 73 | abbr -a co cargo 74 | abbr -a cob "cargo build" 75 | abbr -a cor "cargo run" 76 | abbr -a corr "cargo run --release" 77 | abbr -a cot "cargo test" 78 | abbr -a coa "cargo add" 79 | abbr -a coc "cargo check" 80 | 81 | abbr -a d docker 82 | abbr -a dp "docker ps" 83 | abbr -a dc "docker compose" 84 | abbr -a dcu "docker compose up" 85 | abbr -a dcud "docker compose up --detach" 86 | abbr -a dcs "docker compose stop" 87 | 88 | abbr -a g lazygit 89 | abbr -a ",a" "git add" 90 | abbr -a ",ap" "git add --patch" 91 | abbr -a ",aa" "git add -A" 92 | abbr -a ",r" "git restore" 93 | abbr -a ",rs" "git restore --staged" 94 | abbr -a ",re" "git reset" 95 | abbr -a ",rv" "git remote -v" 96 | abbr -a ",ra" "git rebase --abort" 97 | abbr -a ",c" "git commit" 98 | abbr -a ",ca" "git commit --amend" 99 | abbr -a ",d" "git diff" 100 | abbr -a ",dc" "git diff --cached" 101 | abbr -a ",m" "git merge" 102 | abbr -a ",s" "git status" 103 | abbr -a ",p" "git push" 104 | abbr -a ",pf" "git push --force-with-lease" 105 | abbr -a ",pu" "git pull" 106 | abbr -a ",f" "git fetch" 107 | abbr -a ",fu" "git fetch upstream" 108 | abbr -a ",sw" "git switch" 109 | abbr -a ",sc" "git switch -c" 110 | abbr -a ",b" "git branch" 111 | abbr -a ",l" "git log" 112 | 113 | abbr -a yay paru 114 | -------------------------------------------------------------------------------- /hosts/homeserver1/configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | flake, 3 | inputs, 4 | config, 5 | pkgs, 6 | ... 7 | }: 8 | { 9 | imports = [ 10 | inputs.disko.nixosModules.default 11 | inputs.agenix.nixosModules.default 12 | 13 | inputs.srvos.nixosModules.server 14 | inputs.srvos.nixosModules.mixins-systemd-boot 15 | 16 | ./disko.nix 17 | ./clouddns.nix 18 | # ./minecraft 19 | # ./vrising.nix 20 | ]; 21 | 22 | system.stateVersion = "24.11"; 23 | nixpkgs.hostPlatform = "x86_64-linux"; 24 | 25 | boot.loader.efi.canTouchEfiVariables = true; 26 | boot.initrd.enable = true; 27 | boot.initrd.availableKernelModules = [ 28 | "xhci_pci" 29 | "ahci" 30 | "nvme" 31 | "usbhid" 32 | "usb_storage" 33 | "sd_mod" 34 | ]; 35 | boot.initrd.kernelModules = [ ]; 36 | boot.kernelModules = [ "kvm-intel" ]; 37 | boot.kernelPackages = pkgs.linuxPackages_latest; 38 | boot.extraModulePackages = [ ]; 39 | boot.supportedFilesystems = [ "btrfs" ]; 40 | boot.initrd.supportedFilesystems = [ "btrfs" ]; 41 | 42 | hardware.cpu.intel.updateMicrocode = true; 43 | hardware.enableRedistributableFirmware = true; 44 | 45 | networking.hostName = "homeserver1"; 46 | networking.hostId = "027fb931"; 47 | networking.useDHCP = true; 48 | 49 | nix.registry.nixpkgs.flake = inputs.nixpkgs; 50 | nix.registry.self.flake = inputs.self; 51 | 52 | # The srvos server profile disables documentation by default, but it's 53 | # useful to have it enabled so I can reference things quickly in an 54 | # SSH session instead of trying to find the documentation online. 55 | srvos.server.docs.enable = true; 56 | 57 | # Srvos also sets the timezone to UTC automatically. I want this to be 58 | # my current location because it will also handle DST changes smoothly. 59 | # A timer set for "04:00" should go off when my wall clock says it's 4. 60 | time.timeZone = "Australia/Sydney"; 61 | 62 | age.secrets.tailscale-homeserver1.file = ./tailscale-homeserver1.age; 63 | services.tailscale = { 64 | enable = true; 65 | authKeyFile = config.age.secrets.tailscale-homeserver1.path; 66 | openFirewall = true; 67 | extraUpFlags = [ 68 | "--advertise-exit-node" 69 | "--exit-node-allow-lan-access" 70 | ]; 71 | }; 72 | 73 | virtualisation.podman.enable = true; 74 | virtualisation.oci-containers.backend = "podman"; 75 | 76 | environment.etc."configuration-revision".text = flake.rev or flake.dirtyRev; 77 | 78 | users.users.robert = { 79 | isNormalUser = true; 80 | extraGroups = [ 81 | "wheel" 82 | "podman" 83 | "systemd-journal" 84 | ]; 85 | hashedPassword = "!"; 86 | 87 | # TODO: Think of a better place to store these keys. Maybe in users/robert/? 88 | openssh.authorizedKeys.keys = [ 89 | # My iPhone, blink terminal 90 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFkVAe4iwrprDibMgY1m0BeUPgrKBRErKRfLfxjVl+lu" 91 | # My iPad, blink terminal 92 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEGcz3Qiqix5lJPsDeE+RY2q64Bpl+jY0tLO/fUM5TNr" 93 | ]; 94 | 95 | # Allows each system's root and user to connect to the server, which 96 | # is necessary to use it as a builder. 97 | openssh.authorizedKeys.keyFiles = [ 98 | "${flake}/hosts/macbook-air/id_ed25519.pub" 99 | "${flake}/hosts/macbook-air/users/robert/id_ed25519.pub" 100 | 101 | "${flake}/hosts/macmini/id_ed25519.pub" 102 | "${flake}/hosts/macmini/users/robert/id_ed25519.pub" 103 | ]; 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /hosts/macmini/darwin-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | lib, 5 | flake, 6 | ... 7 | }: 8 | { 9 | imports = [ 10 | inputs.self.darwinModules.system-defaults 11 | inputs.self.darwinModules.fish-environment 12 | 13 | inputs.nix-homebrew.darwinModules.nix-homebrew 14 | ]; 15 | 16 | users.users.robert = { 17 | description = "Robert"; 18 | home = "/Users/robert"; 19 | openssh.authorizedKeys.keys = [ 20 | # My iPhone, blink terminal 21 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFkVAe4iwrprDibMgY1m0BeUPgrKBRErKRfLfxjVl+lu" 22 | 23 | # My iPad, blink terminal 24 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEGcz3Qiqix5lJPsDeE+RY2q64Bpl+jY0tLO/fUM5TNr" 25 | ]; 26 | openssh.authorizedKeys.keyFiles = [ 27 | # Allows both my user account and system root to SSH into the robert account. 28 | # This is so I can use macmini as a remote builder to offload some work from 29 | # the more thermally constrained laptop, but unfortunately because the laptop 30 | # isn't managed with nix-darwin, I have to manually configure the builder :( 31 | "${flake}/hosts/macbook-air/id_ed25519.pub" 32 | "${flake}/hosts/macbook-air/users/robert/id_ed25519.pub" 33 | # Allows me to SSH into myself, which is useful sometimes 34 | "${flake}/hosts/macmini/users/robert/id_ed25519.pub" 35 | ]; 36 | }; 37 | 38 | # This needs to be reapplied after each system update. My Fish configuration 39 | # will warn about this if it detects the line it adds to sudo_local is absent. 40 | security.pam.services.sudo_local.touchIdAuth = true; 41 | 42 | networking.hostName = "macmini"; 43 | 44 | system.stateVersion = 6; 45 | system.primaryUser = "robert"; 46 | 47 | home-manager.backupFileExtension = "hm-backup"; 48 | 49 | nix-homebrew.enable = true; 50 | # A user needs to own the prefix, so we'll make it my account 51 | nix-homebrew.user = "robert"; 52 | 53 | environment.systemPackages = [ 54 | pkgs.fish 55 | pkgs.mosh 56 | ]; 57 | 58 | # Not sure about adding in vendor_conf.d because tools can just dump their init into it, 59 | # and because it will be included in a directory in $NIX_PROFILES, therefore 60 | # also $XDG_DATA_DIRS, it will be sourced during startup. This is probably fine, but 61 | # I want total control over what runs in my fish config. 62 | environment.pathsToLink = [ 63 | # "/share/fish/vendor_conf.d" 64 | "/share/fish/vendor_completions.d" 65 | "/share/fish/vendor_functions.d" 66 | ]; 67 | 68 | nixpkgs.hostPlatform = "aarch64-darwin"; 69 | 70 | nix.enable = true; 71 | nix.nixPath = lib.mkForce [ 72 | "nixpkgs=${inputs.nixpkgs}" 73 | "home-manager=${inputs.home-manager}" 74 | ]; 75 | nix.settings.experimental-features = [ 76 | "nix-command" 77 | "flakes" 78 | ]; 79 | nix.settings.trusted-users = [ "@admin" ]; 80 | nix.channel.enable = false; 81 | 82 | nix.distributedBuilds = true; 83 | nix.buildMachines = [ 84 | { 85 | hostName = "homeserver1"; 86 | sshUser = "robert"; 87 | system = "x86_64-linux"; 88 | maxJobs = 8; 89 | supportedFeatures = [ 90 | "kvm" 91 | "benchmark" 92 | "big-parallel" 93 | ]; 94 | } 95 | ]; 96 | 97 | services.tailscale.enable = true; 98 | services.openssh.enable = true; 99 | 100 | # Good example for how to disable SSH password authentication with nix-darwin. 101 | # I want to use the builtin macOS SSH server, but with declarative config. 102 | environment.etc."ssh/sshd_config.d/999-disable-password-auth.conf".text = '' 103 | PermitRootLogin no 104 | PasswordAuthentication no 105 | KbdInteractiveAuthentication no 106 | UsePAM no 107 | ''; 108 | } 109 | -------------------------------------------------------------------------------- /config/fish/conf.d/environment.fish: -------------------------------------------------------------------------------- 1 | # These variables will not be pathified by Fish by default, but 2 | # they behave like path variables, so it makes sense to treat them 3 | # as such. 4 | for p in XDG_DATA_DIRS XDG_CONFIG_DIRS TERMINFO_DIRS 5 | # @fish-lsp-disable-next-line 3003 6 | set --path $p $$p 7 | end 8 | 9 | # It's possible that `hx` isn't available if I totally bungle my $PATH 10 | if command -q hx 11 | set -x EDITOR hx 12 | else 13 | set -x EDITOR vim 14 | end 15 | 16 | # Setting this to an empty string makes direnv silent 17 | set -x DIRENV_LOG_FORMAT 18 | 19 | # SQLite and Postgres both store their history files in a stupid location 20 | # by default, so this moves the histories to the data dir where they belong. 21 | set -x PSQL_HISTORY $HOME/.local/share/psql/psql_history 22 | set -l psql_history_dir (path dirname $PSQL_HISTORY) 23 | test -d $psql_history_dir; or mkdir -p $psql_history_dir 24 | 25 | set -x SQLITE_HISTORY $HOME/.local/share/sqlite3/sqlite_history 26 | set -l sqlite_history_dir (path dirname $SQLITE_HISTORY) 27 | test -d $sqlite_history_dir; or mkdir -p $sqlite_history_dir 28 | 29 | if test -d $HOME/.local/bin; and not contains -- $HOME/.local/bin $PATH 30 | set --append PATH $HOME/.local/bin 31 | end 32 | 33 | # Homebrew should be available, but I don't want anything installed by 34 | # it to take precendence over anything installed by Nix. 35 | if test -d /opt/homebrew/bin; and not contains -- /opt/homebrew/bin $PATH 36 | set --append PATH /opt/homebrew/bin 37 | end 38 | 39 | # Fish comes with some builtin aliases for ls, which we don't want. 40 | # Instead, `l` is an abbreviation defined in the abbreviations.fish file. 41 | functions -e la ll 42 | 43 | if set -q SSH_CLIENT; or set -q SSH_TTY 44 | set -x BROWSER echo 45 | end 46 | 47 | set -x FZF_CTRL_T_COMMAND "fd --type file --strip-cwd-prefix --follow" 48 | set -x FZF_ALT_C_COMMAND "fd --type directory --strip-cwd-prefix" 49 | 50 | set -g skip_trash_directories .git node_modules target .direnv .nx venv .venv dist result build 51 | set -g skip_trash_files .DS_Store 52 | 53 | # Something has gone wrong if the first item in these paths is not the 54 | # user configuration directories, but *if* it isn't, correct it so that 55 | # user functions will always win. 56 | # Shells are not pleasant to debug, so I want to give myself a head start 57 | # by making it hard to ignore errors. 58 | if test $fish_function_path[1] != $HOME/.config/fish/functions 59 | set --prepend fish_function_path $HOME/.config/fish/functions 60 | echo "$(set_color brred)WARNING:$(set_color normal) $(set_color --bold)fish_function_path[1]$(set_color normal) was not $(set_color --italics)'$HOME/.config/fish/functions'$(set_color normal), fixing for this shell." 61 | echo " You should take some time to figure out the root cause - something is misconfigured". 62 | echo 63 | for line in $fish_function_path 64 | if test $line = $HOME/.config/fish/functions 65 | set_color --bold 66 | echo -- " -> "$line 67 | set_color normal 68 | else 69 | echo -- " "$line 70 | end 71 | end 72 | echo 73 | end 74 | if test $fish_complete_path[1] != $HOME/.config/fish/completions 75 | set --prepend fish_complete_path $HOME/.config/fish/completions 76 | echo "$(set_color brred)WARNING:$(set_color normal) $(set_color --bold)fish_complete_path[1]$(set_color normal) was not $(set_color --italics)'$HOME/.config/fish/completions'$(set_color normal), fixing for this shell." 77 | echo " You should take some time to figure out the root cause - something is misconfigured". 78 | echo 79 | for line in $fish_complete_path 80 | if test $line = $HOME/.config/fish/completions 81 | set_color --bold 82 | echo -- " -> "$line 83 | set_color normal 84 | else 85 | echo -- " "$line 86 | end 87 | end 88 | echo 89 | end 90 | -------------------------------------------------------------------------------- /config/helix/config.toml: -------------------------------------------------------------------------------- 1 | theme = "gruvbox" 2 | 3 | [editor] 4 | bufferline = "multiple" 5 | color-modes = true 6 | completion-trigger-len = 1 7 | idle-timeout = 0 8 | line-number = "relative" 9 | true-color = true 10 | 11 | [editor.cursor-shape] 12 | insert = "bar" 13 | normal = "block" 14 | select = "underline" 15 | 16 | [editor.indent-guides] 17 | character = "▏" 18 | render = true 19 | skip-levels = 1 20 | 21 | [editor.inline-diagnostics] 22 | cursor-line = "hint" 23 | other-lines = "hint" 24 | 25 | [editor.lsp] 26 | display-inlay-hints = true 27 | display-messages = true 28 | 29 | [editor.statusline] 30 | right = ["diagnostics", "selections", "position", "position-percentage", "file-encoding"] 31 | 32 | [editor.whitespace.characters] 33 | newline = "↵" 34 | 35 | [editor.whitespace.render] 36 | newline = "all" 37 | 38 | [keys.insert] 39 | C-h = ":toggle-option lsp.display-inlay-hints" 40 | C-space = "completion" 41 | 42 | [keys.normal] 43 | "*" = ["trim_selections", "search_selection", "select_mode"] 44 | C-R = ":reload-all" 45 | C-d = ["half_page_down", "goto_window_center"] 46 | C-h = ":toggle-option lsp.display-inlay-hints" 47 | C-q = ":quit-all" 48 | C-r = ":reload" 49 | C-u = ["half_page_up", "goto_window_center"] 50 | C-w = ":quit" 51 | D = "goto_word" 52 | S-down = "jump_view_down" 53 | S-left = "jump_view_left" 54 | S-right = "jump_view_right" 55 | S-up = "jump_view_up" 56 | a = ["append_mode", "collapse_selection"] 57 | i = ["insert_mode", "collapse_selection"] 58 | C-t = ["delete_selection", "paste_after"] 59 | 60 | [keys.normal.Z] 61 | C-d = ["half_page_down", "goto_window_center"] 62 | C-u = ["half_page_up", "goto_window_center"] 63 | D = ["scroll_down", "scroll_down", "scroll_down", "scroll_down", "scroll_down"] 64 | E = ["scroll_down", "scroll_down", "scroll_down", "scroll_down", "scroll_down"] 65 | J = ["scroll_down", "scroll_down", "scroll_down", "scroll_down", "scroll_down"] 66 | K = ["scroll_up", "scroll_up", "scroll_up", "scroll_up", "scroll_up"] 67 | U = ["scroll_up", "scroll_up", "scroll_up", "scroll_up", "scroll_up"] 68 | Y = ["scroll_up", "scroll_up", "scroll_up", "scroll_up", "scroll_up"] 69 | d = "scroll_down" 70 | e = "scroll_down" 71 | u = "scroll_up" 72 | y = "scroll_up" 73 | 74 | [keys.normal."`"] 75 | C = ["trim_selections", ":pipe ccase --to uppercamel"] 76 | K = ["trim_selections", ":pipe ccase --to upperkebab"] 77 | S = ["trim_selections", ":pipe ccase --to screamingsnake"] 78 | c = ["trim_selections", ":pipe ccase --to camel"] 79 | k = ["trim_selections", ":pipe ccase --to kebab"] 80 | r = ["trim_selections", ":pipe ccase --to pseudorandom"] 81 | s = ["trim_selections", ":pipe ccase --to snake"] 82 | t = ["trim_selections", ":pipe ccase --to title"] 83 | 84 | [keys.normal.g] 85 | down = "move_line_down" 86 | left = "goto_line_start" 87 | right = "goto_line_end" 88 | up = "move_line_up" 89 | 90 | [keys.normal.space] 91 | D = "hsplit" 92 | d = "vsplit" 93 | u = ":reset-diff-change" 94 | B = ":echo %sh{git blame -L %{cursor_line},+1 %{buffer_name}}" 95 | 96 | [keys.normal.space.w] 97 | S = ["hsplit_new", "file_picker"] 98 | V = ["vsplit_new", "file_picker"] 99 | 100 | [keys.select] 101 | ";" = ["collapse_selection", "normal_mode"] 102 | C-d = ["half_page_down", "goto_window_center"] 103 | C-h = ":toggle-option lsp.display-inlay-hints" 104 | C-u = ["half_page_up", "goto_window_center"] 105 | D = "extend_to_word" 106 | S-down = "jump_view_down" 107 | S-left = "jump_view_left" 108 | S-right = "jump_view_right" 109 | S-up = "jump_view_up" 110 | a = ["append_mode", "collapse_selection"] 111 | i = ["insert_mode", "collapse_selection"] 112 | C-t = ["delete_selection", "paste_after"] 113 | 114 | [keys.select."`"] 115 | C = ["trim_selections", ":pipe ccase --to uppercamel"] 116 | K = ["trim_selections", ":pipe ccase --to upperkebab"] 117 | S = ["trim_selections", ":pipe ccase --to screamingsnake"] 118 | c = ["trim_selections", ":pipe ccase --to camel"] 119 | k = ["trim_selections", ":pipe ccase --to kebab"] 120 | r = ["trim_selections", ":pipe ccase --to pseudorandom"] 121 | s = ["trim_selections", ":pipe ccase --to snake"] 122 | t = ["trim_selections", ":pipe ccase --to title"] 123 | 124 | [keys.select.g] 125 | down = "move_line_down" 126 | left = "goto_line_start" 127 | right = "goto_line_end" 128 | up = "move_line_up" 129 | 130 | [keys.select.space] 131 | D = "hsplit" 132 | d = "vsplit" 133 | 134 | -------------------------------------------------------------------------------- /config/fish/functions/run.fish: -------------------------------------------------------------------------------- 1 | # There are an annoying amount of steps required for proper isolation 2 | # since this function is initially executed in our current environment. 3 | # When 'run.fish' is found, it needs to be executed in its own fish 4 | # (and you can't rely on fish being in $PATH because this function is 5 | # used for bootstrapping). 6 | # Constraints: 7 | # - Fish syntax is too complicated to reasonably grep function definitions 8 | # - Don't want to execute twice 9 | # Which means the script *has* to be sourced to bring its functions into 10 | # the local scope. 11 | # If it were just sourced normally, it would have access to local variables 12 | # such as pre_functions, which is undesirable, so it has to be sourced 13 | # from a different function with no locals. 14 | # All the functions defined will be in scope for 'run.fish' too, which 15 | # means a name collision would be possible. The functions names have to be 16 | # prefixed to avoid this. 17 | # Two extra requirements: 1) must preserve exit status, 2) executes in same 18 | # directory as the file. 19 | 20 | function run 21 | # 'test' is unable to test for '-h' because it sees it as a flag. 22 | switch $argv[1] 23 | case -h --help 24 | echo "Usage: run [function [args...]] 25 | 26 | Run fish functions defined in a single file. It's a command runner, 27 | similar to just, but you get use the best shell scripting language. 28 | 29 | Create a file named 'run.fish' and write some functions in it. 30 | Invoking 'run' will source the file in an isolated fish environment 31 | and invoke whichever function was named. 32 | 33 | Run 'run' without any arguments to get a list of public functions. 34 | Function names beginning with an underscore are private. 35 | 36 | The script itself, and all functions, will be invoked from the 37 | directory containing the file. All output from the top level of the 38 | script will be silenced, functions will not be." 39 | return 40 | end 41 | 42 | set -l dir (pwd) 43 | set -l file_path 44 | while test "$dir" != / 45 | if test -e "$dir/run.fish" 46 | set file_path "$dir/run.fish" 47 | break 48 | end 49 | set dir (dirname "$dir") 50 | end 51 | 52 | if not set -q file_path[1] 53 | echo (set_color brred)"error:"(set_color normal)" run.fish not found in current or parent directories" 54 | return 1 55 | end 56 | 57 | set -l fish_path (status fish-path) 58 | 59 | $fish_path --no-config -c " 60 | function __private_main 61 | set -l pre_functions (functions --names) 62 | 63 | cd \"$dir\" 64 | # Not all errors from sourcing can be detected. Instead, we're just 65 | # going to assume that everything went okay, because partial failure 66 | # won't actually impact the execution at all. 67 | __private_source \"$file_path\" &>/dev/null 68 | 69 | set -l post_functions (functions --names) 70 | 71 | set -l new_functions 72 | for function in \$post_functions 73 | if not contains -- \$function \$pre_functions; and not string match \"_*\" -- \$function 74 | set --append new_functions \$function 75 | end 76 | end 77 | 78 | if not set -q argv[1] 79 | if test \"\$__COMPLETE_RUN_DESCRIPTIONS\" != 1 80 | for function in \$new_functions 81 | echo -- \$function 82 | end 83 | else 84 | for function in \$new_functions 85 | set desc (functions -vD \$function | sed -n -e \"5{p;q}\") 86 | echo -- \$function\\t\$desc 87 | end 88 | end 89 | else if contains -- \$argv[1] \$new_functions 90 | # The directory could have changed when the script was sourced, 91 | # e.g. top-level 'cd' job 92 | cd \"$dir\" 93 | \$argv 94 | return \$status 95 | else 96 | echo (set_color brred)\"error:\"(set_color normal)\" did not match a function name: \$argv[1]\" 97 | end 98 | end 99 | 100 | function __private_source 101 | source \$argv 102 | end 103 | 104 | __private_main $argv 105 | " $argv 106 | set -l result_status $status 107 | 108 | return $result_status 109 | end 110 | -------------------------------------------------------------------------------- /modules/home/my-programs-fish.nix: -------------------------------------------------------------------------------- 1 | # Declarative fish plugin installation 2 | { 3 | pkgs, 4 | config, 5 | lib, 6 | ... 7 | }: 8 | let 9 | cfg = config.my.programs.fish; 10 | 11 | fishIndent = 12 | name: text: 13 | pkgs.runCommand name { 14 | nativeBuildInputs = [ pkgs.fish ]; 15 | inherit text; 16 | passAsFile = [ "text" ]; 17 | } "env HOME=$(mktemp -d) fish_indent < $textPath > $out"; 18 | 19 | babelfishTranslate = 20 | path: name: 21 | pkgs.runCommand "${name}.fish" { } '' 22 | ${pkgs.babelfish}/bin/babelfish < ${path} > $out 23 | ''; 24 | in 25 | { 26 | options = { 27 | my.programs.fish.plugins = lib.mkOption { 28 | type = lib.types.listOf lib.types.path; 29 | description = '' 30 | Plugins that will be installed and activated. 31 | ''; 32 | }; 33 | }; 34 | 35 | config = { 36 | # FIXME: This is no longer necessary now that ZSH handles sourcing it. 37 | # xdg.dataFile."fish/vendor_conf.d/00_hm-session-vars.fish".source = 38 | # let 39 | # sessionVars = "${config.home.sessionVariablesPackage}/etc/profile.d/hm-session-vars.sh"; 40 | # in 41 | # babelfishTranslate sessionVars "hm-session-vars"; 42 | 43 | xdg.dataFile."fish/vendor_conf.d/00_source_plugins.fish".source = lib.mkIf (cfg.plugins != [ ]) ( 44 | fishIndent "source_plugins.fish" '' 45 | for plugin in ${lib.concatStringsSep " " cfg.plugins} 46 | if test -d $plugin/functions 47 | set fish_function_path $fish_function_path[1] $plugin/functions $fish_function_path[2..] 48 | end 49 | 50 | if test -d $plugin/completions 51 | set fish_complete_path $fish_complete_path[1] $plugin/completions $fish_complete_path[2..] 52 | end 53 | 54 | # Sourcing files in both places allows me to support OMF plugins too, just in case. 55 | for file in $plugin/conf.d/*.fish $plugin/*.fish 56 | test -f $file -a -r $file 57 | and source $file 58 | end 59 | end 60 | '' 61 | ); 62 | 63 | # The following has been adapted from the Fish module of home-manager: 64 | # https://github.com/nix-community/home-manager/blob/0d7908bd09165db6699908b7e3970f137327cbf0/modules/programs/fish.nix#L4 65 | # This is temporary and will be cleaned up in the future. 66 | 67 | programs.man.generateCaches = true; 68 | 69 | xdg.dataFile."fish/generated_completions".source = 70 | let 71 | # paths later in the list will overwrite those already linked 72 | destructiveSymlinkJoin = 73 | args_@{ 74 | name, 75 | paths, 76 | preferLocalBuild ? true, 77 | allowSubstitutes ? false, 78 | postBuild ? "", 79 | ... 80 | }: 81 | let 82 | args = 83 | removeAttrs args_ [ 84 | "name" 85 | "postBuild" 86 | ] 87 | // { 88 | # pass the defaults 89 | inherit preferLocalBuild allowSubstitutes; 90 | }; 91 | in 92 | pkgs.runCommand name args '' 93 | mkdir -p $out 94 | for i in $paths; do 95 | if [ -z "$(find $i -prune -empty)" ]; then 96 | cp -srf $i/* $out 97 | fi 98 | done 99 | ${postBuild} 100 | ''; 101 | 102 | generateCompletions = 103 | let 104 | getName = 105 | attrs: attrs.name or "${attrs.pname or "«pname-missing»"}-${attrs.version or "«version-missing»"}"; 106 | in 107 | package: 108 | pkgs.runCommand "${getName package}-fish-completions" 109 | { 110 | srcs = [ 111 | package 112 | ] 113 | ++ lib.filter (p: p != null) ( 114 | builtins.map (outName: package.${outName} or null) config.home.extraOutputsToInstall 115 | ); 116 | nativeBuildInputs = [ pkgs.python3 ]; 117 | buildInputs = [ pkgs.fish ]; 118 | preferLocalBuild = true; 119 | } 120 | '' 121 | mkdir -p $out 122 | for src in $srcs; do 123 | if [ -d $src/share/man ]; then 124 | find -L $src/share/man -type f \ 125 | | xargs python ${pkgs.fish}/share/fish/tools/create_manpage_completions.py --directory $out \ 126 | > /dev/null 127 | fi 128 | done 129 | ''; 130 | in 131 | destructiveSymlinkJoin { 132 | name = "${config.home.username}-fish-completions"; 133 | paths = 134 | let 135 | cmp = (a: b: (a.meta.priority or 0) > (b.meta.priority or 0)); 136 | in 137 | map generateCompletions (lib.sort cmp config.home.packages); 138 | }; 139 | 140 | xdg.dataFile."fish/vendor_conf.d/99_generated_completions.fish".source = 141 | fishIndent "99_generated_completions.fish" '' 142 | set -l genpath ${config.xdg.dataHome}/fish/generated_completions 143 | 144 | if test -d $genpath; and not contains -- genpath $PATH 145 | set --append fish_complete_path $genpath 146 | end 147 | ''; 148 | }; 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clo4's configuration 2 | 3 | This is my dead-simple configuration. Maybe something to take inspiration from 4 | if you're getting stuck configuring your own Nix flake setup! I currently 5 | actively maintain my nix-darwin and standalone Home Manager configurations, but 6 | supporting NixOS or WSL would be as simple as adding another entry to `hosts`. 7 | 8 | Traditional Nix system configuration requires a rebuild to apply, but I found 9 | that this was slowing me down and disincentivising me from changing things on my 10 | system. This time around, everything about the way my config is structured is to 11 | allow me to iterate raplidly: 12 | 13 | - Home Manager symlinks my config to the right location. This results in the 14 | normal instant feedback loop of editing your dotfiles, but with the certainty 15 | that you can reapply it at any point exactly the same way it is now. 16 | - The flake uses [Blueprint](https://github.com/numtide/blueprint), which gets 17 | rid of all the glue code I would otherwise need. Creating hosts is as simple 18 | as creating a directory in `hosts`, or making a package, creating a file in 19 | `packages`. 20 | 21 | Program configuration is all stored in [config](/config). 22 | 23 | My shared Home Manager config applies this configuration: 24 | [users/robert/home-configuration.nix](/users/robert/home-configuration.nix) 25 | 26 | This configuration is applied per-host with tweaks on top of it: [hosts](/hosts) 27 | 28 | ## Custom stuff 29 | 30 | I keep finding yaks to shave. 31 | 32 | - Fish is my interactive shell, but all _initial_ environment setup is delegated 33 | to ZSH. This means the login shell is always guaranteed to be POSIX-enough to 34 | work, but I still get to benefit from using the _best_ shell. I'm particularly 35 | proud that the `$PATH` works flawlessly on my Darwin systems, which has been a 36 | known issue with Fish + Nix. 37 | - I implemented a Fish plugin manager in Nix for declarative plugins with 38 | imperative configuration. Plugins don't clutter up my config, nor can they 39 | accidentally clobber any files. Updates are done by updating the Nix plugin 40 | package definitions. 41 | - This config uses 42 | [mattwparas' fork of Helix](https://github.com/mattwparas/helix/tree/steel-event-system) 43 | with support for plugins, though I haven't set up or written any plugins yet. 44 | I had to fork it to get the Steel language server integration working. A build 45 | that uses this version and has working a working scheme language server for 46 | building plugins is available by running 47 | `nix run github:clo4/nix-dotfiles#helix`. 48 | - Homebrew is installed automatically and managed declaratively with 49 | [nix-homebrew](https://github.com/zhaofengli/nix-homebrew) 50 | - Since fish is the best language for shell scripting, I wrote a custom command 51 | runner for fish functions. It replaces tools like `just` and common abuse of 52 | `make`. You just write fish functions in `run.fish`, then run them with `run`. 53 | This is also usable as `nix run github:clo4/nix-dotfiles#run`. 54 | - My server hosts a Minecraft server using `virtualisation.oci-containers` with 55 | podman. The container is launched as root, but switched to a system 56 | user/group. Its data is stored in `/srv/minecraft/family`. It restarts every 57 | day at 4 am local-time. It has a DDNS client. I think this may be the best 58 | reference configuration for setting up a Minecraft server on NixOS. 59 | - I built a simple DDNS client for Cloudflare to keep my DNS record up to date 60 | with my home internet's IP address. It's a simple Go program, it compiles 61 | quickly, it caches IP to minimise useless updates. The credentials are stored 62 | encrypted with Agenix. It's been moved into its own repository: 63 | [clo4/clouddns](https://github.com/clo4/clouddns) 64 | 65 | More of my custom things will be documented in the future. 66 | 67 | ## Hosts 68 | 69 | - `macmini` 70 | - This is my main dev machine. Most configuration will be up-to-date for it. 71 | It's a nix-darwin system that also configures my user using Home Manager. 72 | - `macbook-air` 73 | - This is my secondary computer. It's owned by my partner, so I haven't 74 | installed nix-darwin. Instead, this is a standalone configuration. 75 | - `homeserver1` 76 | - This is my personal server. It's a Minisforum NAB6 Lite running an Intel 77 | Core i5 12600, so it uses <9w at idle. 78 | - Hopefully not the first of many, but knowing me, it's best to start adding 79 | versioning to the names. 80 | 81 | ### Building and switching 82 | 83 | As a way to make sure I always get the commands right, there's a command runner 84 | included in the developer environment named `run`. Run `run` with no arguments 85 | to print the available commands. 86 | 87 | Switching `macmini` and `macbook-air` will attempt to switch the currently 88 | active device, but switching `homeserver1` will cause the server to rebuild and 89 | switch remotely, allowing the command to be run from whatever device is being 90 | used without an SSH connection. The only requirement is that Tailscale is up and 91 | connected. 92 | 93 | `run` can be used from any shell, but is intended for use within Fish. 94 | 95 | ### Bootstrapping homeserver1 96 | 97 | This isn't included in `run.fish` because it has to be executed from the system 98 | itself or over SSH. 99 | 100 | ```bash 101 | sudo nix --extra-experimental-features 'nix-command flakes' run github:clo4/nix-dotfiles/vps#homeserver1-install 102 | ``` 103 | 104 | ## Personal notes 105 | 106 | ### Moving configuration directories 107 | 108 | To migrate from one directory to another, create an exact copy of the 109 | configuration in the new destination **without removing the existing 110 | configuration.** Once created, edit the new configuration, changing 111 | `my.config.directory` to whatever the new path is. Now you can reapply the 112 | configuration from the new directory. 113 | -------------------------------------------------------------------------------- /users/robert/home-configuration.nix: -------------------------------------------------------------------------------- 1 | # This is the shared user configuration applied and customised by each 2 | # host's `robert` user. 3 | { 4 | pkgs, 5 | pkgs', 6 | perSystem, 7 | inputs, 8 | config, 9 | flake, 10 | ... 11 | }@args: 12 | let 13 | steelWithLsp = perSystem.steel.default.overrideAttrs (oldAttrs: { 14 | cargoBuildFlags = "-p cargo-steel-lib -p steel-interpreter -p steel-language-server"; 15 | }); 16 | 17 | neovimWithDependencies = pkgs.symlinkJoin { 18 | name = "neovim-with-dependencies"; 19 | paths = [ pkgs.neovim ]; 20 | buildInputs = [ pkgs.makeWrapper ]; 21 | postBuild = '' 22 | wrapProgram $out/bin/nvim \ 23 | --prefix PATH : ${ 24 | pkgs.lib.makeBinPath [ 25 | pkgs.curl 26 | pkgs.tree-sitter 27 | pkgs.ripgrep 28 | ] 29 | } 30 | ''; 31 | }; 32 | in 33 | { 34 | imports = [ 35 | inputs.self.homeModules.my-config 36 | inputs.self.homeModules.my-programs-fish 37 | inputs.self.homeModules.my-programs-neovim 38 | inputs.self.modules.common.nixpkgs-unstable 39 | ./darwin.nix 40 | 41 | "${flake}/config/nvim/plugins.nix" 42 | ]; 43 | 44 | home.packages = [ 45 | perSystem.helix.helix 46 | # perSystem.helix.helix-cogs 47 | # steelWithLsp 48 | perSystem.self.schemat 49 | perSystem.self.ccase # Case conversion used in my Helix keybindings (TODO: port to scheme plugin?) 50 | pkgs.curl 51 | pkgs'.stripe-cli 52 | pkgs.gh 53 | pkgs.delta 54 | pkgs.direnv 55 | pkgs.eza 56 | pkgs.fd 57 | pkgs.fish 58 | pkgs.fish-lsp 59 | pkgs.fzf 60 | pkgs.git 61 | pkgs.git-open 62 | pkgs.gum 63 | pkgs.home-manager 64 | pkgs.jq 65 | pkgs.jujutsu 66 | pkgs.just 67 | pkgs.lazygit 68 | neovimWithDependencies 69 | pkgs.nix-direnv 70 | pkgs.nix-output-monitor 71 | pkgs.nixfmt-rfc-style 72 | pkgs.nushell 73 | pkgs.ripgrep 74 | pkgs.tealdeer 75 | pkgs.tmux 76 | pkgs.tree 77 | pkgs.vim 78 | pkgs.wget 79 | pkgs.zoxide 80 | 81 | # Fonts 82 | pkgs.nerd-fonts.roboto-mono 83 | ]; 84 | 85 | fonts.fontconfig.enable = !pkgs.stdenv.isDarwin; 86 | 87 | my.config.source = 88 | let 89 | # Some tools prefer to place their configuration in the correct directory 90 | # for the platform. On Linux, that's XDG_CONFIG_HOME, which defaults to 91 | # ~/.config if unset. On macOS, the configuration directory is 92 | # '~/Library/Application Support'. 93 | # Unfortunately, this isn't common - most tools simply use ~/.config 94 | # regardless of platform conventions. 95 | platformConfig = if pkgs.stdenv.isDarwin then "Library/Application Support" else ".config"; 96 | in 97 | { 98 | ".config/ghostty/config" = "config/ghostty/config"; 99 | ".config/ghostty/os-config" = 100 | if pkgs.stdenv.isDarwin then 101 | "config/ghostty/os-config-darwin" 102 | else 103 | "config/ghostty/os-config-linux"; 104 | 105 | ".config/kitty" = "config/kitty"; 106 | 107 | ".config/helix" = "config/helix"; 108 | 109 | ".config/nvim" = "config/nvim"; 110 | 111 | ".config/tmux" = "config/tmux"; 112 | 113 | ".config/git" = "config/git"; 114 | "${platformConfig}/jj" = "config/jj"; 115 | 116 | ".config/direnv/direnv.toml" = "config/direnv/direnv.toml"; 117 | 118 | # Fish can't just link the config directory because if the flake directory 119 | # is used as my.config.directory (which is only true on new home manager 120 | # systems during bootstrapping) then it will try to write to the fish_variables 121 | # file repeatedly and fail each time, spamming the terminal with errors. 122 | # It's better to link each of the directories individually to avoid this. 123 | ".config/fish/conf.d" = "config/fish/conf.d"; 124 | ".config/fish/functions" = "config/fish/functions"; 125 | ".config/fish/completions" = "config/fish/completions"; 126 | ".config/fish/config.fish" = "config/fish/config.fish"; 127 | 128 | # There needs to be two zshenv files because when the top-level 129 | # zshenv is executed, it would not normally execute the zshenv in the ZDOTDIR. 130 | ".zshenv" = "config/zsh/home_zshenv"; 131 | ".config/zsh" = "config/zsh"; 132 | 133 | # Allows imperative NPM package installation and management, low friction way 134 | # to install and manage things like Claude Code 135 | ".npmrc" = "config/npm/npmrc"; 136 | }; 137 | 138 | # This needs to be in a known location so it can be sourced regardless 139 | # of whether we're in standalone HM or as a system module. 140 | home.file.".local/share/zsh/hm-session-vars.sh".source = 141 | "${config.home.sessionVariablesPackage}/etc/profile.d/hm-session-vars.sh"; 142 | 143 | # Since hm-session-vars is consistent and will be sourced, ZDOTDIR can 144 | # be set declaratively and will be used by ZSH during init. 145 | home.sessionVariables.ZDOTDIR = "$HOME/.config/zsh"; 146 | 147 | # Ordinarily, the direnv module would set this automatically. 148 | home.file.".config/direnv/lib/nix-direnv.sh".source = 149 | "${pkgs.nix-direnv}/share/nix-direnv/direnvrc"; 150 | 151 | my.programs.fish.plugins = [ 152 | (pkgs.fetchFromGitHub { 153 | owner = "IlanCosman"; 154 | repo = "tide"; 155 | rev = "44c521ab292f0eb659a9e2e1b6f83f5f0595fcbd"; # as of 2025-01-01 156 | hash = "sha256-85iU1QzcZmZYGhK30/ZaKwJNLTsx+j3w6St8bFiQWxc="; 157 | }) 158 | ]; 159 | 160 | home.sessionVariables.NIX_CONFIG_REV = flake.rev or flake.dirtyRev; 161 | home.sessionVariables.NIX_CONFIG_DIR = config.my.config.directory; 162 | home.sessionVariables.NIX_CONFIG_LAST_MODIFIED = builtins.toString flake.lastModified; 163 | 164 | nix.registry = { 165 | nixpkgs.flake = inputs.nixpkgs; 166 | nixpkgs-unstable.flake = inputs.nixpkgs-unstable; 167 | blueprint.flake = inputs.blueprint; 168 | home-manager.flake = inputs.home-manager; 169 | nix-darwin.flake = inputs.nix-darwin; 170 | helix.flake = inputs.helix; 171 | }; 172 | nix.settings.log-lines = 25; 173 | } 174 | -------------------------------------------------------------------------------- /hosts/homeserver1/minecraft/servers/family.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | ... 4 | }: 5 | let 6 | uid = 399; 7 | gid = 398; 8 | in 9 | { 10 | users.users.minecraft-family = { 11 | inherit uid; 12 | isSystemUser = true; 13 | group = "minecraft-family"; 14 | home = "/srv/minecraft/family"; 15 | 16 | # I'm not certain if these are necessary any more, but they don't hurt. 17 | linger = true; 18 | autoSubUidGidRange = true; 19 | }; 20 | 21 | # The group needs its own GID because the container references it directly. 22 | users.groups.minecraft-family = { 23 | inherit gid; 24 | }; 25 | 26 | users.users.robert.extraGroups = [ "minecraft-family" ]; 27 | 28 | # rwxrwx--- so the minecraft-family group can also make changes. 29 | systemd.tmpfiles.rules = [ 30 | "d /srv/minecraft/family 0770 minecraft-family minecraft-family -" 31 | ]; 32 | 33 | virtualisation.oci-containers.containers.minecraft-family = { 34 | image = "docker.io/itzg/minecraft-server"; 35 | autoStart = true; 36 | 37 | # There are multiple ways to connect to a Minecraft server without entering 38 | # any port information. I'm choosing to use SRV records instead of A records 39 | # because I want to add more servers in the future and doing so means I'd 40 | # have to use a routing solution like mc-router to inspect incoming packets 41 | # and route them to the right port based on the address the client was 42 | # connected to. This way, the client connects right to the desired server 43 | # instance. 44 | ports = [ 45 | # Minecraft server, using SRV record so clients connect to the right port. 46 | # This also has the advantage that the server scanners don't see the server, 47 | # since it's not hosted on the default port. 48 | "25580:25565" 49 | # UDP is used for server queries 50 | "25580:25565/udp" 51 | # Simple voice chat. It has to be configured in the mod settings to use 52 | # this port. 53 | "24480:24480/udp" 54 | ]; 55 | 56 | # Launched by root, then changed to minecraft-family:minecraft-family. 57 | # Because the user has been set, it's also respected inside the container. 58 | user = "${toString uid}:${toString gid}"; 59 | 60 | # All the actual server configuration is done manually in server.properties. 61 | environment = { 62 | EULA = "TRUE"; 63 | TYPE = "FABRIC"; 64 | VERSION = "1.21.4"; 65 | 66 | # Security 67 | ENABLE_WHITELIST = "TRUE"; 68 | WHITELIST = "clo4_"; 69 | OPS = "clo4_"; 70 | 71 | # Performance settings 72 | MEMORY = "6G"; 73 | USE_AIKAR_FLAGS = "TRUE"; 74 | 75 | # Auto-pause configuration 76 | ENABLE_AUTOPAUSE = "TRUE"; 77 | # Wait 1 minute after server initialisation before pausing 78 | AUTOPAUSE_TIMEOUT_INIT = "60"; 79 | # Wait 30 minutes after last player disconnection before pausing 80 | AUTOPAUSE_TIMEOUT_EST = "1800"; 81 | # This fixes the "unable to start knockd" issue 82 | SKIP_SUDO = "TRUE"; 83 | 84 | # Disable unnecessary watchdog timers 85 | MAX_TICK_TIME = "-1"; 86 | WATCHDOG = "-1"; 87 | 88 | MODRINTH_PROJECTS = '' 89 | almanac 90 | appleskin 91 | balm 92 | blossomlib 93 | blossomtpa 94 | blossomwarps 95 | c2me-fabric 96 | continents 97 | convenient-mobgriefing 98 | clumps 99 | datapack:afk-sleep 100 | datapack:detect-afk 101 | datapack:no-free-deaths 102 | datapack:pause-day-cycle 103 | distanthorizons:beta 104 | fabric-api 105 | ferrite-core 106 | forge-config-api-port 107 | geophilic 108 | ksyxis 109 | lithium 110 | leaves-be-gone 111 | lmd 112 | modernfix 113 | netherportalfix 114 | no-shield-delay 115 | noisium 116 | puzzles-lib 117 | scalablelux 118 | simple-voice-chat:beta 119 | sit! 120 | ''; 121 | }; 122 | 123 | volumes = [ 124 | "/srv/minecraft/family:/data" 125 | ]; 126 | 127 | extraOptions = [ 128 | "--cap-add=CAP_NET_RAW" # Required for autopause 129 | "--no-healthcheck" 130 | "--tty" 131 | ]; 132 | }; 133 | 134 | systemd.services.podman-minecraft-family = { 135 | after = [ "network.target" ]; 136 | requires = [ "network.target" ]; 137 | }; 138 | 139 | # Service to restart the Minecraft container 140 | systemd.services.minecraft-family-restart = { 141 | description = "Restart Minecraft Family Server"; 142 | requires = [ "podman-minecraft-family.service" ]; 143 | after = [ "podman-minecraft-family.service" ]; 144 | script = '' 145 | # If the server is currently paused, the autopause daemon creates .paused 146 | # in the /data mount, so if this file is present then don't wake the server 147 | # up to send a message to nobody. 148 | function is_paused() { 149 | [ -f /srv/minecraft/family/.paused ] 150 | } 151 | is_paused || ${pkgs.podman}/bin/podman exec minecraft-family rcon-cli "say Server will restart in 5 minutes." 152 | sleep 240 153 | is_paused || ${pkgs.podman}/bin/podman exec minecraft-family rcon-cli "say Server will restart in 1 minute." 154 | sleep 50 155 | is_paused || ${pkgs.podman}/bin/podman exec minecraft-family rcon-cli "say Server will restart in 10 seconds." 156 | sleep 5 157 | is_paused || ${pkgs.podman}/bin/podman exec minecraft-family rcon-cli "say Server will restart in 5 seconds." 158 | sleep 5 159 | 160 | ${pkgs.systemd}/bin/systemctl restart podman-minecraft-family.service 161 | ''; 162 | serviceConfig = { 163 | Type = "oneshot"; 164 | User = "root"; 165 | }; 166 | }; 167 | 168 | systemd.timers.minecraft-family-restart = { 169 | description = "Timer for daily Minecraft server restart"; 170 | wantedBy = [ "timers.target" ]; 171 | timerConfig = { 172 | # Since the script starts warning players at 5 minutes before restart, 173 | # it needs to be scheduled for 5 minutes before the intended time. 174 | OnCalendar = "03:55:00"; 175 | Unit = "minecraft-family-restart.service"; 176 | }; 177 | }; 178 | } 179 | -------------------------------------------------------------------------------- /config/niri/common/binds.kdl: -------------------------------------------------------------------------------- 1 | binds { 2 | // Mod+Space hotkey-overlay-title="Application Launcher" { 3 | // spawn "dms" "ipc" "call" "spotlight" "toggle"; 4 | // } 5 | Mod+Space hotkey-overlay-title="Application Launcher" { 6 | spawn "vicinae" "toggle" 7 | } 8 | 9 | Mod+Ctrl+Space hotkey-overlay-title="Emoji Picker" { 10 | spawn "vicinae" "vicinae://extensions/vicinae/vicinae/search-emojis" 11 | } 12 | 13 | Mod+V hotkey-overlay-title="Clipboard Manager" { 14 | spawn "vicinae" "vicinae://extensions/vicinae/clipboard/history" 15 | } 16 | 17 | Mod+N hotkey-overlay-title="Notification Center" { 18 | spawn "dms" "ipc" "call" "notifications" "toggle" 19 | } 20 | 21 | Mod+X hotkey-overlay-title="Power Menu" { 22 | spawn "dms" "ipc" "call" "powermenu" "open" 23 | } 24 | 25 | // Audio control 26 | Mod+WheelScrollUp cooldown-ms=20 { 27 | spawn "dms" "ipc" "call" "audio" "increment" "6" 28 | } 29 | XF86AudioRaiseVolume allow-when-locked=true { 30 | spawn "dms" "ipc" "call" "audio" "increment" "3" 31 | } 32 | Mod+WheelScrollDown cooldown-ms=20 { 33 | spawn "dms" "ipc" "call" "audio" "decrement" "6" 34 | } 35 | XF86AudioLowerVolume allow-when-locked=true { 36 | spawn "dms" "ipc" "call" "audio" "decrement" "3" 37 | } 38 | XF86AudioMute allow-when-locked=true { 39 | spawn "dms" "ipc" "call" "audio" "mute" 40 | } 41 | XF86AudioMicMute allow-when-locked=true { 42 | spawn "dms" "ipc" "call" "audio" "micmute" 43 | } 44 | 45 | // Media player 46 | XF86AudioNext allow-when-locked=true { 47 | spawn "dms" "ipc" "call" "mpris" "next" 48 | } 49 | XF86AudioPrev allow-when-locked=true { 50 | spawn "dms" "ipc" "call" "mpris" "previous" 51 | } 52 | XF86AudioPlay allow-when-locked=true { 53 | spawn "dms" "ipc" "call" "mpris" "playPause" 54 | } 55 | XF86AudioPause allow-when-locked=true { 56 | spawn "dms" "ipc" "call" "mpris" "pause" 57 | } 58 | 59 | Mod+Shift+Slash { show-hotkey-overlay; } 60 | 61 | Mod+B hotkey-overlay-title="Open Browser: Firefox" { spawn "firefox"; } 62 | Mod+E hotkey-overlay-title="File Manager: Nautilus" { spawn "nautilus"; } 63 | Mod+T hotkey-overlay-title="Open Terminal: Foot" { spawn "foot"; } 64 | Mod+Ctrl+Alt+Shift+Grave hotkey-overlay-title="Open Terminal: Foot" { spawn "foot"; } 65 | 66 | // Mod+T hotkey-overlay-title="Open Terminal: Ghostty" { spawn "ghostty" "+new-window"; } 67 | // Mod+Ctrl+Alt+Shift+Grave hotkey-overlay-title="Open Terminal: Ghostty" { spawn "ghostty" "+new-window"; } 68 | 69 | Mod+Return { maximize-column; } 70 | Mod+Ctrl+Return { maximize-window-to-edges; } 71 | Mod+Shift+Return { fullscreen-window; } 72 | 73 | Mod+Q repeat=false { close-window; } 74 | 75 | // Regular directional keybinds just move the focus around the current workspace. 76 | Mod+Left { focus-column-left; } 77 | Mod+Down { focus-window-down; } 78 | Mod+Up { focus-window-up; } 79 | Mod+Right { focus-column-right; } 80 | 81 | Mod+Next { focus-workspace-down; } 82 | Mod+Prior { focus-workspace-up; } 83 | 84 | // Control is the "window" modifier. Adding in control moves the window instead of the focus. 85 | Mod+Ctrl+Left { move-column-left; } 86 | Mod+Ctrl+Down { move-window-down; } 87 | Mod+Ctrl+Up { move-window-up; } 88 | Mod+Ctrl+Right { move-column-right; } 89 | 90 | Mod+Ctrl+Next { move-column-to-workspace-down; } 91 | Mod+Ctrl+Prior { move-column-to-workspace-up; } 92 | 93 | 94 | // Home and end naturally move focus or window to the start or end of the list. 95 | Mod+Home { focus-column-first; } 96 | Mod+End { focus-column-last; } 97 | Mod+Ctrl+Home { move-column-to-first; } 98 | Mod+Ctrl+End { move-column-to-last; } 99 | 100 | // Shift is the "monitor" modifier. Adding in shift changes the movement from the current 101 | // monitor to the other monitors. 102 | Mod+Shift+Left { focus-monitor-left; } 103 | Mod+Shift+Down { focus-monitor-down; } 104 | Mod+Shift+Up { focus-monitor-up; } 105 | Mod+Shift+Right { focus-monitor-right; } 106 | 107 | // Naturally, shift and control move the column to another monitor, instead 108 | // of moving the focus to another monitor. 109 | Mod+Shift+Ctrl+Left { move-column-to-monitor-left; } 110 | Mod+Shift+Ctrl+Down { move-column-to-monitor-down; } 111 | Mod+Shift+Ctrl+Next { move-column-to-monitor-down; } 112 | Mod+Shift+Ctrl+Up { move-column-to-monitor-up; } 113 | Mod+Shift+Ctrl+Prior { move-column-to-monitor-up; } 114 | Mod+Shift+Ctrl+Right { move-column-to-monitor-right; } 115 | 116 | Mod+1 { focus-workspace 1; } 117 | Mod+2 { focus-workspace 2; } 118 | Mod+3 { focus-workspace 3; } 119 | Mod+4 { focus-workspace 4; } 120 | Mod+5 { focus-workspace 5; } 121 | Mod+6 { focus-workspace 6; } 122 | Mod+7 { focus-workspace 7; } 123 | Mod+8 { focus-workspace 8; } 124 | Mod+9 { focus-workspace 9; } 125 | Mod+Ctrl+1 { move-column-to-workspace 1; } 126 | Mod+Ctrl+2 { move-column-to-workspace 2; } 127 | Mod+Ctrl+3 { move-column-to-workspace 3; } 128 | Mod+Ctrl+4 { move-column-to-workspace 4; } 129 | Mod+Ctrl+5 { move-column-to-workspace 5; } 130 | Mod+Ctrl+6 { move-column-to-workspace 6; } 131 | Mod+Ctrl+7 { move-column-to-workspace 7; } 132 | Mod+Ctrl+8 { move-column-to-workspace 8; } 133 | Mod+Ctrl+9 { move-column-to-workspace 9; } 134 | 135 | Mod+Alt+Ctrl+Left { consume-or-expel-window-left; } 136 | Mod+Alt+Ctrl+Right { consume-or-expel-window-right; } 137 | 138 | Mod+BracketLeft { consume-window-into-column; } 139 | Mod+BracketRight { expel-window-from-column; } 140 | 141 | Mod+R { switch-preset-column-width; } 142 | Mod+Shift+R { switch-preset-window-height; } 143 | Mod+Ctrl+R { reset-window-height; } 144 | 145 | Mod+C { center-column; } 146 | Mod+Ctrl+C { center-visible-columns; } 147 | 148 | Mod+Minus { set-column-width "-10%"; } 149 | Mod+Shift+Minus { set-window-height "-10%"; } 150 | Mod+Equal { set-column-width "+10%"; } 151 | Mod+Shift+Equal { set-window-height "+10%"; } 152 | 153 | Mod+Ctrl+V { toggle-window-floating; } 154 | Mod+Shift+V { switch-focus-between-floating-and-tiling; } 155 | 156 | Mod+W { toggle-column-tabbed-display; } 157 | 158 | Mod+S { screenshot; } 159 | Mod+Shift+S { screenshot-screen; } 160 | Mod+Ctrl+S { screenshot-window; } 161 | 162 | Mod+Escape { toggle-overview; } 163 | 164 | Mod+Shift+Ctrl+Alt+O { debug-toggle-opaque-regions; } 165 | Mod+Shift+Ctrl+Alt+T { toggle-debug-tint; } 166 | } 167 | -------------------------------------------------------------------------------- /config/nvim/init.lua: -------------------------------------------------------------------------------- 1 | vim.cmd.colorscheme("gruvbox-material") 2 | 3 | vim.g.mapleader = " " 4 | 5 | -- Uses ripgrep for grep 6 | vim.opt.grepprg = "rg --vimgrep" 7 | 8 | -- How long before the swap file is written to? 9 | vim.opt.updatetime = 200 10 | 11 | -- Makes visual block mode able to select empty space 12 | vim.opt.virtualedit = "block" 13 | 14 | -- TODO: Find out more about this option 15 | vim.opt.wildmode = "longest:full,full" 16 | 17 | -- Absolute line number with relative numbers above/below 18 | vim.opt.number = true 19 | vim.opt.relativenumber = true 20 | 21 | -- Set highlight on search 22 | vim.opt.hlsearch = false 23 | 24 | -- Substitute always uses global flag 25 | vim.opt.gdefault = true 26 | 27 | -- Enable mouse mode 28 | vim.opt.mouse = "a" 29 | 30 | -- Enable break indent 31 | vim.opt.breakindent = true 32 | 33 | -- Save undo history 34 | vim.opt.undofile = true 35 | vim.opt.undolevels = 10000 36 | 37 | -- Case insensitive searching UNLESS /C or capital in search 38 | vim.opt.ignorecase = true 39 | vim.opt.smartcase = true 40 | 41 | -- Set colorscheme 42 | vim.opt.termguicolors = true 43 | vim.g.gruvbox_material_enable_italic = 0 44 | vim.g.gruvbox_material_disable_italic_comment = 1 45 | vim.g.gruvbox_material_sign_column_background = "none" 46 | vim.g.gruvbox_material_palette = "original" 47 | vim.cmd([[colorscheme gruvbox-material]]) 48 | 49 | -- Set completeopt to have a better completion experience 50 | vim.opt.completeopt = { "menuone", "noselect" } 51 | 52 | -- Show line and col 53 | vim.opt.ruler = true 54 | 55 | -- Show certain hidden characters 56 | vim.opt.list = true 57 | vim.opt.listchars = { tab = "→ ", lead = "·", trail = "·", nbsp = "•" } 58 | 59 | -- Delete comment characters when joining lines 60 | vim.opt.formatoptions:append("j") 61 | 62 | -- Recognize numbered lists 63 | vim.opt.formatoptions:append("n") 64 | 65 | -- Read changes from disk 66 | vim.opt.autoread = true 67 | 68 | -- Highlight the line with the cursor on it 69 | vim.opt.cursorline = true 70 | 71 | -- Sane line wrapping settings 72 | vim.opt.wrap = false 73 | vim.opt.linebreak = true 74 | vim.opt.showbreak = "↪" 75 | vim.opt.breakindent = true 76 | vim.opt.breakindentopt = "list:-1" 77 | 78 | -- Sane tab settings 79 | vim.opt.expandtab = false 80 | vim.opt.smarttab = true 81 | vim.opt.tabstop = 2 82 | vim.opt.shiftwidth = 0 83 | 84 | -- Splits will open on the right/below instead of left/above 85 | vim.opt.splitright = true 86 | vim.opt.splitbelow = true 87 | vim.opt.splitkeep = "screen" 88 | 89 | -- Vertical diffs 90 | vim.opt.diffopt:append("vertical") 91 | 92 | -- 3 lines/cols buffer when scrolling 93 | vim.opt.scrolloff = 3 94 | vim.opt.sidescrolloff = 3 95 | 96 | -- Always draw the sign column 97 | vim.opt.signcolumn = "yes" 98 | 99 | -- Wait 300ms for a sequence to complete 100 | vim.opt.timeout = true 101 | vim.opt.timeoutlen = 300 102 | 103 | -- Ignore files matching these patterns while expanding a wildcard 104 | vim.opt.wildignore = { "*.o", "*.obj", "*.bak", "*.exe", "*.pyc", ".DS_Store" } 105 | 106 | -- Hide the welcome message 107 | vim.opt.shortmess = "I" 108 | 109 | -- Ask to write instead of failing when using :q 110 | vim.opt.confirm = true 111 | 112 | -- Use tree-sitter folds 113 | vim.opt.foldmethod = "expr" 114 | vim.opt.foldexpr = "nvim_treesitter#foldexpr()" 115 | vim.opt.foldlevelstart = 69 116 | 117 | -- Display a window title 118 | vim.opt.title = true 119 | 120 | -- Use the + register for operations that would normally use _ 121 | vim.opt.clipboard = "unnamedplus" 122 | 123 | -- Don't display the ~ chars at the end of the buffer 124 | vim.opt.fillchars = { eob = " " } 125 | 126 | -- 127 | 128 | -- Highlight the yanked region briefly 129 | local highlight_group = vim.api.nvim_create_augroup("YankHighlight", { 130 | clear = true, 131 | }) 132 | vim.api.nvim_create_autocmd("TextYankPost", { 133 | callback = function() 134 | vim.highlight.on_yank() 135 | end, 136 | group = highlight_group, 137 | pattern = "*", 138 | }) 139 | 140 | -- If the file's parent directory doesn't exist, create it first 141 | local mkdirp_on_write = vim.api.nvim_create_augroup("MkdirpOnWrite", { 142 | clear = true, 143 | }) 144 | vim.api.nvim_create_autocmd("BufWritePre", { 145 | callback = function() 146 | local dir = vim.fn.expand(":p:h") 147 | if vim.fn.isdirectory(dir) == 0 then 148 | vim.fn.mkdir(dir, "p") 149 | end 150 | end, 151 | group = mkdirp_on_write, 152 | pattern = "*", 153 | }) 154 | 155 | 156 | vim.api.nvim_create_user_command("Term", ":vsp | terminal", { desc = "Open a new terminal in a split" }) 157 | 158 | 159 | -- Make Y behave consistently, like D and C 160 | vim.keymap.set("n", "Y", "y$", { silent = true }) 161 | 162 | -- Paste without overwriting the unnamed register 163 | vim.keymap.set("v", "P", '"_dP', { silent = true }) 164 | 165 | -- dD deletes all the characters in the line without removing the line itself 166 | vim.keymap.set("n", "dD", "0D", { desc = "Delete all the characters in the line" }) 167 | 168 | -- Space does nothing in normal or visual mode 169 | vim.keymap.set({ "n", "v" }, "", "", { silent = true }) 170 | 171 | -- Up and down navigate through wrapped lines sensibly 172 | vim.keymap.set("n", "", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) 173 | vim.keymap.set("n", "", "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) 174 | 175 | -- Delete a buffer without deleting the split 176 | vim.keymap.set("n", "bd", "bp|bd #", { desc = "Delete buffer without closing pane" }) 177 | 178 | -- Telescope mappings 179 | vim.keymap.set("n", "f", require("telescope.builtin").find_files, { desc = "Find files by name" }) 180 | vim.keymap.set("n", "/", require("telescope.builtin").live_grep, { desc = "Find files with grep" }) 181 | vim.keymap.set("n", "b", require("telescope.builtin").buffers, { desc = "Find buffers" }) 182 | vim.keymap.set("n", "d", require("telescope.builtin").diagnostics, { desc = "Find diagnostics" }) 183 | vim.keymap.set("n", "o", require("telescope.builtin").jumplist, { desc = "View jumplist" }) 184 | 185 | -- Diagnostic pairs 186 | vim.keymap.set("n", "]d", vim.diagnostic.goto_next) 187 | vim.keymap.set("n", "[d", vim.diagnostic.goto_prev) 188 | vim.keymap.set("n", "dn", vim.diagnostic.goto_next) 189 | vim.keymap.set("n", "dp", vim.diagnostic.goto_prev) 190 | 191 | -- View diagnostic information 192 | vim.keymap.set("n", "ge", vim.diagnostic.open_float) 193 | 194 | -- Hit escape twice to exit the terminal 195 | vim.keymap.set("t", "", "") 196 | 197 | require("mini.surround").setup() 198 | 199 | require("telescope").setup({ 200 | defaults = { 201 | mappings = { 202 | i = { 203 | [""] = false, 204 | [""] = false, 205 | }, 206 | }, 207 | }, 208 | extensions = { 209 | ["ui-select"] = { 210 | require("telescope.themes").get_dropdown({}), 211 | }, 212 | }, 213 | }) 214 | require("telescope").load_extension("fzf") 215 | --require("telescope").load_extension("ui-select") 216 | -------------------------------------------------------------------------------- /run.fish: -------------------------------------------------------------------------------- 1 | # @fish-lsp-disable 2002 4004 2 | 3 | # If the devshell isn't active, the functiond defined in this file cannot be 4 | # used, so ensure that the dev environment is always active. 5 | if not set -q IN_NIX_CONFIG_DEVSHELL 6 | alias devshell "nix develop" 7 | return 1 8 | end 9 | 10 | # 11 | # --- Private utility functions 12 | # 13 | 14 | function _pretty_print 15 | set colored_command (string escape -- $argv | string join ' ' | fish_indent --ansi) 16 | echo "$(set_color brgreen --bold)~~>$(set_color normal) $colored_command" 17 | end 18 | 19 | function _run 20 | _pretty_print $argv 21 | $argv 22 | end 23 | 24 | function _require 25 | for program in $argv 26 | if not command -vq $program 27 | echo "Required program '$program' is missing. Ensure it is in your environment, then try again." 28 | end 29 | end 30 | end 31 | 32 | # 33 | # --- Commands 34 | # 35 | 36 | set -g this_host (hostname -s) 37 | 38 | function dry -d "Dry-run a function (replaces _run with _pretty_print)" 39 | functions --erase _run 40 | functions --copy _pretty_print _run 41 | 42 | # Mistakenly ran this function without any arguments, print functions. 43 | if not set -q argv[1] 44 | run 45 | return 46 | end 47 | 48 | $argv 49 | end 50 | 51 | function mc-rcon -d "Connect to homeserver1 and begin an interactive RCON session" 52 | echo (set_color --italics)"connecting to homeserver1 and executing rcon-cli..."(set_color normal) 53 | _run ssh robert@homeserver1 "sudo podman exec -i minecraft-family rcon-cli" 54 | end 55 | 56 | function mc-stop 57 | echo (set_color --italics)"connecting to homeserver1 and stopping server..."(set_color normal) 58 | _run ssh robert@homeserver1 "sudo systemctl stop podman-minecraft-family.service" 59 | end 60 | 61 | function mc-start 62 | echo (set_color --italics)"connecting to homeserver1 and stopping server..."(set_color normal) 63 | _run ssh robert@homeserver1 "sudo systemctl start podman-minecraft-family.service" 64 | end 65 | 66 | function mc-logs 67 | echo (set_color --italics)"connecting to homeserver1 and stopping server..."(set_color normal) 68 | _run ssh robert@homeserver1 "journalctl -xefu podman-minecraft-family.service" 69 | end 70 | 71 | function check-applied -d "Check if the currently applied configuration needs to be updated" 72 | set last_commit_timestamp (git log -1 --format=%at) 73 | set current_commit_pretty (set_color --dim --italics)$NIX_CONFIG_REV(set_color normal) 74 | if test $NIX_CONFIG_LAST_MODIFIED -lt $last_commit_timestamp 75 | echo "Configuration is out of date." 76 | echo $current_commit_pretty 77 | # TODO: Prompt to apply configuration if out of date? 78 | return 1 79 | else 80 | set commit_hash (git log -1 --format=%H) 81 | if test $NIX_CONFIG_REV = $commit_hash 82 | echo "Configuration is the most recent commit." 83 | echo $current_commit_pretty 84 | else 85 | echo "Current configuration is dirty (new)." 86 | echo $current_commit_pretty 87 | end 88 | end 89 | end 90 | 91 | function edit-age -d "Edit the encrypted files stored in this repository" 92 | set file (fd --type file --glob '*.age' | fzf --height=40% --layout=reverse) 93 | or return 94 | agenix -e $file $argv 95 | end 96 | 97 | # 98 | # --- Functions for building/switching hosts 99 | # 100 | 101 | function homeserver1 -a verb 102 | set rebuild_args 103 | set maybe_sudo 104 | 105 | if test $this_host != homeserver1 106 | # If not building on homeserver1, set homeserver1 to be the target. 107 | # Otherwise, it is definitely a mistake to switch on the local system. 108 | set --append rebuild_args --use-remote-sudo --target-host robert@homeserver1 109 | echo (set_color --dim --italics)"not on homeserver1, targeting remote host..."(set_color normal) 110 | 111 | # If the current system isn't x86-64 Linux, then homeserver1 needs to build 112 | # its own configuration. --fast (--no-build-nix) is also required because 113 | # the local system will attempt to execute a version of `nix` that it can't 114 | # run. 115 | if test (nix eval --impure --raw --expr 'builtins.currentSystem') != x86_64-linux 116 | set --append rebuild_args --build-host robert@homeserver1 --fast 117 | echo (set_color --dim --italics)"not on x86_64-linux, performing remote build..."(set_color normal) 118 | end 119 | 120 | else 121 | # If, for whatever reason, we *are* on homeserver1, then we need to use sudo. 122 | set maybe_sudo sudo 123 | echo (set_color --dim --italics)"on homeserver1, using sudo..."(set_color normal) 124 | end 125 | 126 | _run $maybe_sudo nixos-rebuild $verb --flake .#homeserver1 $rebuild_args $argv[2..] 127 | end 128 | 129 | function macmini -a verb 130 | set maybe_sudo 131 | if test $verb = switch 132 | set maybe_sudo sudo 133 | end 134 | _run $maybe_sudo darwin-rebuild $verb --flake .#macmini --max-jobs 8 $argv[2..] 135 | end 136 | 137 | function work-macbookpro -a verb 138 | set maybe_sudo 139 | if test $verb = switch 140 | set maybe_sudo sudo 141 | end 142 | _run $maybe_sudo darwin-rebuild $verb --flake .#work-macbookpro $argv[2..] 143 | end 144 | 145 | function macbook-air -a verb 146 | _require pmset timeout home-manager 147 | 148 | set jobs 8 149 | 150 | if test $this_host = macbook-air; and pmset -g batt | grep -q "Battery Power" 151 | echo (set_color --dim --italics)"on battery power, testing connection to macmini..."(set_color normal) 152 | if timeout 3 nix store info --store ssh-ng://robert@macmini &>/dev/null 153 | echo (set_color --dim --italics)"delegating to macmini..."(set_color normal) 154 | set jobs 0 155 | else 156 | echo (set_color --dim --italics)"running on this machine..."(set_color normal) 157 | end 158 | end 159 | 160 | _run home-manager $verb --flake ".#$USER@macbook-air" --max-jobs $jobs $argv[2..] 161 | end 162 | 163 | function legacy -a verb 164 | _require home-manager 165 | _run home-manager $verb --flake ".#$USER@legacy" --max-jobs 8 $argv[2..] 166 | end 167 | 168 | function pc3 -a verb 169 | _require home-manager 170 | set jobs 8 171 | if test $this_host = pc3 172 | set jobs 32 173 | end 174 | _run home-manager $verb --flake ".#$USER@pc3" --max-jobs $jobs $argv[2..] 175 | end 176 | 177 | # The logic below defines the commands used to build/switch configurations for 178 | # the hosts above. This requires some amount of metaprogramming, which Fish has 179 | # decent support for. 180 | 181 | set hosts (ls hosts) 182 | set verbs build switch 183 | 184 | for host in $hosts 185 | functions -q $host; or continue 186 | functions --copy $host _$host 187 | functions --erase $host 188 | 189 | for verb in $verbs 190 | echo "function $verb-$host -d '$verb the configuration for $host'; _$host $verb \$argv; end" | source 191 | # This line is visually confusing. Essentially, if there is a config 192 | # for the current $hostname, we create an alias for it so I can refer 193 | # to it as "host" rather than the actual hostname. It's easier to type 194 | # and remember, since most of the time I'll want to build the config 195 | # for the system I'm using at the time, regardless of what it is. 196 | test $this_host = $host; and alias $verb-host $verb-$host 197 | end 198 | end 199 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "agenix": { 4 | "inputs": { 5 | "darwin": [ 6 | "nix-darwin" 7 | ], 8 | "home-manager": [ 9 | "home-manager" 10 | ], 11 | "nixpkgs": [ 12 | "nixpkgs" 13 | ], 14 | "systems": "systems" 15 | }, 16 | "locked": { 17 | "lastModified": 1762618334, 18 | "narHash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ=", 19 | "owner": "ryantm", 20 | "repo": "agenix", 21 | "rev": "fcdea223397448d35d9b31f798479227e80183f6", 22 | "type": "github" 23 | }, 24 | "original": { 25 | "owner": "ryantm", 26 | "repo": "agenix", 27 | "type": "github" 28 | } 29 | }, 30 | "blueprint": { 31 | "inputs": { 32 | "nixpkgs": [ 33 | "nixpkgs" 34 | ], 35 | "systems": "systems_2" 36 | }, 37 | "locked": { 38 | "lastModified": 1740813264, 39 | "narHash": "sha256-LIO3+CMw9yLVLK+8TjcaQoLmEgCUkXfRonzFguoLZZk=", 40 | "owner": "clo4", 41 | "repo": "blueprint", 42 | "rev": "b033f96062fd91c024f6a04ee2cbf7c168a28a6e", 43 | "type": "github" 44 | }, 45 | "original": { 46 | "owner": "clo4", 47 | "ref": "generic-users", 48 | "repo": "blueprint", 49 | "type": "github" 50 | } 51 | }, 52 | "brew-src": { 53 | "flake": false, 54 | "locked": { 55 | "lastModified": 1763638478, 56 | "narHash": "sha256-n/IMowE9S23ovmTkKX7KhxXC2Yq41EAVFR2FBIXPcT8=", 57 | "owner": "Homebrew", 58 | "repo": "brew", 59 | "rev": "fbfdbaba008189499958a7aeb1e2c36ab10c067d", 60 | "type": "github" 61 | }, 62 | "original": { 63 | "owner": "Homebrew", 64 | "ref": "5.0.3", 65 | "repo": "brew", 66 | "type": "github" 67 | } 68 | }, 69 | "clouddns": { 70 | "inputs": { 71 | "blueprint": [ 72 | "blueprint" 73 | ], 74 | "nixpkgs": [ 75 | "nixpkgs" 76 | ] 77 | }, 78 | "locked": { 79 | "lastModified": 1754186948, 80 | "narHash": "sha256-Iuu0ZTyZcPBGN1P8n8s70uNLONGm1XBLEEmUoouZfQM=", 81 | "owner": "clo4", 82 | "repo": "clouddns", 83 | "rev": "074c4096fbf842383190d10c4f47b44b34b366f4", 84 | "type": "github" 85 | }, 86 | "original": { 87 | "owner": "clo4", 88 | "repo": "clouddns", 89 | "type": "github" 90 | } 91 | }, 92 | "disko": { 93 | "inputs": { 94 | "nixpkgs": [ 95 | "nixpkgs" 96 | ] 97 | }, 98 | "locked": { 99 | "lastModified": 1765688338, 100 | "narHash": "sha256-MjrytR2kiHYUnzX11cXaD31tS7kKdhM1KFaac0+KAig=", 101 | "owner": "nix-community", 102 | "repo": "disko", 103 | "rev": "be1a6b8a05afdd5d5fa69fcaf3c4ead7014c9fd8", 104 | "type": "github" 105 | }, 106 | "original": { 107 | "owner": "nix-community", 108 | "repo": "disko", 109 | "type": "github" 110 | } 111 | }, 112 | "flake-compat": { 113 | "locked": { 114 | "lastModified": 1696426674, 115 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 116 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 117 | "revCount": 57, 118 | "type": "tarball", 119 | "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" 120 | }, 121 | "original": { 122 | "type": "tarball", 123 | "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" 124 | } 125 | }, 126 | "flake-utils": { 127 | "inputs": { 128 | "systems": "systems_3" 129 | }, 130 | "locked": { 131 | "lastModified": 1710146030, 132 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 133 | "owner": "numtide", 134 | "repo": "flake-utils", 135 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 136 | "type": "github" 137 | }, 138 | "original": { 139 | "owner": "numtide", 140 | "repo": "flake-utils", 141 | "type": "github" 142 | } 143 | }, 144 | "helix": { 145 | "inputs": { 146 | "nixpkgs": [ 147 | "nixpkgs" 148 | ], 149 | "rust-overlay": "rust-overlay" 150 | }, 151 | "locked": { 152 | "lastModified": 1765575289, 153 | "narHash": "sha256-zvFdyJO+n+myu3t5DQ9gqoXyfWzvmUn1zsDElQtqWXU=", 154 | "owner": "helix-editor", 155 | "repo": "helix", 156 | "rev": "e709298cfb6db7a222c433b75e1a4f950d4a4a15", 157 | "type": "github" 158 | }, 159 | "original": { 160 | "owner": "helix-editor", 161 | "repo": "helix", 162 | "type": "github" 163 | } 164 | }, 165 | "home-manager": { 166 | "inputs": { 167 | "nixpkgs": [ 168 | "nixpkgs" 169 | ] 170 | }, 171 | "locked": { 172 | "lastModified": 1765682243, 173 | "narHash": "sha256-yeCxFV/905Wr91yKt5zrVvK6O2CVXWRMSrxqlAZnLp0=", 174 | "owner": "nix-community", 175 | "repo": "home-manager", 176 | "rev": "58bf3ecb2d0bba7bdf363fc8a6c4d49b4d509d03", 177 | "type": "github" 178 | }, 179 | "original": { 180 | "owner": "nix-community", 181 | "ref": "master", 182 | "repo": "home-manager", 183 | "type": "github" 184 | } 185 | }, 186 | "nix-darwin": { 187 | "inputs": { 188 | "nixpkgs": [ 189 | "nixpkgs" 190 | ] 191 | }, 192 | "locked": { 193 | "lastModified": 1765684049, 194 | "narHash": "sha256-svCS2r984qEowMT0y3kCrsD/m0J6zaF5I/UusS7QaH0=", 195 | "owner": "LnL7", 196 | "repo": "nix-darwin", 197 | "rev": "9b628e171bfaea1a3d1edf31eee46251e0fe4a33", 198 | "type": "github" 199 | }, 200 | "original": { 201 | "owner": "LnL7", 202 | "ref": "master", 203 | "repo": "nix-darwin", 204 | "type": "github" 205 | } 206 | }, 207 | "nix-filter": { 208 | "locked": { 209 | "lastModified": 1731533336, 210 | "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", 211 | "owner": "numtide", 212 | "repo": "nix-filter", 213 | "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", 214 | "type": "github" 215 | }, 216 | "original": { 217 | "owner": "numtide", 218 | "repo": "nix-filter", 219 | "type": "github" 220 | } 221 | }, 222 | "nix-homebrew": { 223 | "inputs": { 224 | "brew-src": "brew-src" 225 | }, 226 | "locked": { 227 | "lastModified": 1764473698, 228 | "narHash": "sha256-C91gPgv6udN5WuIZWNehp8qdLqlrzX6iF/YyboOj6XI=", 229 | "owner": "zhaofengli", 230 | "repo": "nix-homebrew", 231 | "rev": "6a8ab60bfd66154feeaa1021fc3b32684814a62a", 232 | "type": "github" 233 | }, 234 | "original": { 235 | "owner": "zhaofengli", 236 | "repo": "nix-homebrew", 237 | "type": "github" 238 | } 239 | }, 240 | "nixpkgs": { 241 | "locked": { 242 | "lastModified": 1765472234, 243 | "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=", 244 | "owner": "nixos", 245 | "repo": "nixpkgs", 246 | "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b", 247 | "type": "github" 248 | }, 249 | "original": { 250 | "owner": "nixos", 251 | "ref": "nixos-unstable", 252 | "repo": "nixpkgs", 253 | "type": "github" 254 | } 255 | }, 256 | "nixpkgs-unstable": { 257 | "locked": { 258 | "lastModified": 1765644376, 259 | "narHash": "sha256-yqHBL2wYGwjGL2GUF2w3tofWl8qO9tZEuI4wSqbCrtE=", 260 | "owner": "nixos", 261 | "repo": "nixpkgs", 262 | "rev": "23735a82a828372c4ef92c660864e82fbe2f5fbe", 263 | "type": "github" 264 | }, 265 | "original": { 266 | "owner": "nixos", 267 | "ref": "nixpkgs-unstable", 268 | "repo": "nixpkgs", 269 | "type": "github" 270 | } 271 | }, 272 | "root": { 273 | "inputs": { 274 | "agenix": "agenix", 275 | "blueprint": "blueprint", 276 | "clouddns": "clouddns", 277 | "disko": "disko", 278 | "helix": "helix", 279 | "home-manager": "home-manager", 280 | "nix-darwin": "nix-darwin", 281 | "nix-homebrew": "nix-homebrew", 282 | "nixpkgs": "nixpkgs", 283 | "nixpkgs-unstable": "nixpkgs-unstable", 284 | "srvos": "srvos", 285 | "winapps": "winapps" 286 | } 287 | }, 288 | "rust-overlay": { 289 | "inputs": { 290 | "nixpkgs": [ 291 | "helix", 292 | "nixpkgs" 293 | ] 294 | }, 295 | "locked": { 296 | "lastModified": 1759631821, 297 | "narHash": "sha256-V8A1L0FaU/aSXZ1QNJScxC12uP4hANeRBgI4YdhHeRM=", 298 | "owner": "oxalica", 299 | "repo": "rust-overlay", 300 | "rev": "1d7cbdaad90f8a5255a89a6eddd8af24dc89cafe", 301 | "type": "github" 302 | }, 303 | "original": { 304 | "owner": "oxalica", 305 | "repo": "rust-overlay", 306 | "type": "github" 307 | } 308 | }, 309 | "srvos": { 310 | "inputs": { 311 | "nixpkgs": [ 312 | "nixpkgs" 313 | ] 314 | }, 315 | "locked": { 316 | "lastModified": 1765415765, 317 | "narHash": "sha256-DNEUksb+s7DbwahAlIZ4v/BUFUacOqGklCbjgAHZb4k=", 318 | "owner": "nix-community", 319 | "repo": "srvos", 320 | "rev": "a9e46dc439591c67337a0caf0beebb5a73ed9a86", 321 | "type": "github" 322 | }, 323 | "original": { 324 | "owner": "nix-community", 325 | "repo": "srvos", 326 | "type": "github" 327 | } 328 | }, 329 | "systems": { 330 | "locked": { 331 | "lastModified": 1681028828, 332 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 333 | "owner": "nix-systems", 334 | "repo": "default", 335 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 336 | "type": "github" 337 | }, 338 | "original": { 339 | "owner": "nix-systems", 340 | "repo": "default", 341 | "type": "github" 342 | } 343 | }, 344 | "systems_2": { 345 | "locked": { 346 | "lastModified": 1681028828, 347 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 348 | "owner": "nix-systems", 349 | "repo": "default", 350 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 351 | "type": "github" 352 | }, 353 | "original": { 354 | "owner": "nix-systems", 355 | "repo": "default", 356 | "type": "github" 357 | } 358 | }, 359 | "systems_3": { 360 | "locked": { 361 | "lastModified": 1681028828, 362 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 363 | "owner": "nix-systems", 364 | "repo": "default", 365 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 366 | "type": "github" 367 | }, 368 | "original": { 369 | "owner": "nix-systems", 370 | "repo": "default", 371 | "type": "github" 372 | } 373 | }, 374 | "winapps": { 375 | "inputs": { 376 | "flake-compat": "flake-compat", 377 | "flake-utils": "flake-utils", 378 | "nix-filter": "nix-filter", 379 | "nixpkgs": [ 380 | "nixpkgs" 381 | ] 382 | }, 383 | "locked": { 384 | "lastModified": 1765228041, 385 | "narHash": "sha256-+h7yJqGfTbNy0xAUaGWtMxm8REcHG4SMucVSt3D6vZQ=", 386 | "owner": "winapps-org", 387 | "repo": "winapps", 388 | "rev": "44342c34b839547be0b2ea4f94ed00293fa7cc38", 389 | "type": "github" 390 | }, 391 | "original": { 392 | "owner": "winapps-org", 393 | "repo": "winapps", 394 | "type": "github" 395 | } 396 | } 397 | }, 398 | "root": "root", 399 | "version": 7 400 | } 401 | --------------------------------------------------------------------------------