├── .cirrus.yml ├── .envrc ├── .github ├── dependabot.yml └── workflows │ ├── bisect.yml │ ├── rescue-build.yml │ └── update.yml ├── .gitignore ├── Justfile ├── README.md ├── bin ├── , ├── _lib.fish ├── flakeup-logs ├── git-peek ├── imgcat ├── mark ├── mount-backup ├── nix-lastgoodrev ├── nixfetch ├── pywith └── show ├── flake.lock ├── flake.nix ├── flake ├── devshell.nix ├── hosts.nix ├── pkgs.nix └── rescue.nix ├── hosts ├── venusaur │ ├── brew.nix │ ├── default.nix │ └── home.nix └── workmac │ ├── brew.nix │ ├── default.nix │ ├── home.nix │ └── ssl.nix └── modules ├── broken-overlay-data.json ├── broken-overlay.nix ├── darwin ├── 1password.nix ├── brew.nix ├── default.nix ├── fish.nix └── wm.nix ├── home ├── 1password.nix ├── default.nix ├── direnv.nix ├── fish.nix ├── fzf.nix ├── ghostty.nix ├── git.nix ├── k8s.nix ├── neovim │ ├── completion.nix │ ├── default.nix │ ├── lsp.nix │ └── plugins.nix ├── ptpython.nix ├── wm.nix └── xdg.nix └── nix-settings.nix /.cirrus.yml: -------------------------------------------------------------------------------- 1 | env: 2 | CIRRUS_SHELL: bash -il 3 | CACHIX_AUTH_TOKEN: ENCRYPTED[!5b9226401f61dbae39295591ce5115a7f499fa0c24fd6e8a46df6188eda5e03de499c8adfea6a86dd4c0e284e107f3e0!] 4 | CACHIX_NAME: i077 5 | DARWIN_BUILD_IMAGE: ghcr.io/cirruslabs/macos-runner:sonoma 6 | LINUX_BUILD_IMAGE: nixpkgs/cachix-flakes:latest 7 | NIX_BUILD_FLAGS: -j auto --accept-flake-config 8 | 9 | build_template: &BUILD_TEMPLATE 10 | only_if: $CIRRUS_BRANCH == $CIRRUS_DEFAULT_BRANCH || $CIRRUS_TAG != '' || $CIRRUS_PR != '' || $CIRRUS_BUILD_SOURCE == "" 11 | name: build_${CIRRUS_OS}_${ARCH} 12 | timeout_in: 120m 13 | use_cachix_script: cachix use $CACHIX_NAME 14 | build_script: cachix watch-exec $CACHIX_NAME -- nix build $NIX_BUILD_FLAGS $OUTPUTS --system ${ARCH}-${CIRRUS_OS} --show-trace 15 | print_diff_script: | 16 | if [ $CIRRUS_BRANCH = 'update_flake_lock_action' ]; then 17 | for out in $OUTPUTS; do 18 | nix build nixpkgs#nvd 19 | nix build $NIX_BUILD_FLAGS $out -o result --quiet; 20 | nix build $NIX_BUILD_FLAGS github:i077/system#$(echo $out | cut -d# -f2) -o result-old --quiet; 21 | echo "===== $out" 22 | nix run nixpkgs#nvd -- --color always diff ./result-old ./result; 23 | echo -e "=====\n" 24 | done 25 | fi 26 | 27 | build_darwin_profiles_task: 28 | macos_instance: 29 | image: $DARWIN_BUILD_IMAGE 30 | matrix: 31 | - env: 32 | ARCH: aarch64 33 | OUTPUTS: 34 | - .#darwinConfigurations.Venusaur-ci.config.system.build.toplevel 35 | - .#darwinConfigurations.workmac-ci.config.system.build.toplevel 36 | - .#devShells.aarch64-darwin.default 37 | install_rosetta_script: softwareupdate --install-rosetta --agree-to-license 38 | install_nix_script: curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm 39 | install_cachix_script: nix profile install --impure github:nixos/nixpkgs/nixpkgs-unstable#cachix 40 | trust_user_script: echo "trusted-users = root admin" | sudo tee -a /etc/nix/nix.conf && sudo pkill nix-daemon 41 | <<: *BUILD_TEMPLATE 42 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | watch_file flake/devshell.nix 3 | eval "$shellHook" 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/bisect.yml: -------------------------------------------------------------------------------- 1 | name: Bisect nixpkgs input 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | bisect: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | hostname: [giratina, dialga] 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | path: system 16 | - name: Checkout nixpkgs 17 | uses: actions/checkout@v4 18 | with: 19 | repository: NixOS/nixpkgs 20 | fetch-depth: 0 21 | path: nixpkgs 22 | - name: Cleanup disk 23 | uses: curoky/cleanup-disk-action@v2.0 24 | - uses: cachix/install-nix-action@v31.4.0 25 | with: 26 | extra_nix_config: | 27 | experimental-features = nix-command flakes 28 | - name: Configure Cachix 29 | uses: cachix/cachix-action@v16 30 | with: 31 | name: i077 32 | # No need to push useless builds to the cache 33 | skipPush: true 34 | - name: Bisect nixpkgs flake input 35 | run: | 36 | # Assumes that HEAD points to a flake update commit and extract nixpkgs refs 37 | GOOD_REF=$(git -C system log --format=%B -1 | grep "'nixpkgs'" | sed "s/\* Updated '.*': '.*\([0-9a-f]\{40\}\)' -> '.*[0-9a-f]\{40\}'/\1/") 38 | BAD_REF=$(git -C system log --format=%B -1 | grep "'nixpkgs'" | sed "s/\* Updated '.*': '.*[0-9a-f]\{40\}' -> '.*\([0-9a-f]\{40\}\)'/\1/") 39 | git -C nixpkgs bisect start 40 | git -C nixpkgs bisect good $GOOD_REF 41 | git -C nixpkgs bisect bad $BAD_REF 42 | # Automate git bisect with nix build command 43 | git -C nixpkgs bisect run nix build $GITHUB_WORKSPACE/system#nixosConfigurations.${{ matrix.hostname }}.config.system.build.toplevel --override-input nixpkgs $GITHUB_WORKSPACE/nixpkgs 44 | -------------------------------------------------------------------------------- /.github/workflows/rescue-build.yml: -------------------------------------------------------------------------------- 1 | name: "Build rescue image" 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - flake.lock 8 | - flake/rescue.nix 9 | workflow_dispatch: 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | arch: 17 | - x86_64-linux 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | # Shallow clones won't work 22 | fetch-depth: 0 23 | - uses: DeterminateSystems/nix-installer-action@v17 24 | with: 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | - name: Configure Cachix 27 | uses: cachix/cachix-action@v16 28 | with: 29 | name: i077 30 | authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" 31 | - name: Build rescue image 32 | run: nix build --accept-flake-config .#packages.${{ matrix.arch }}.rescueImage 33 | - uses: actions/upload-artifact@v4 34 | with: 35 | name: rescue-image 36 | path: result/iso/*.iso 37 | retention-days: 7 38 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: Update flake inputs 2 | 3 | on: 4 | schedule: 5 | - cron: "0 4 * * 2,5" 6 | workflow_dispatch: 7 | 8 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 9 | jobs: 10 | update: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: DeterminateSystems/nix-installer-action@v17 15 | with: 16 | github-token: ${{ secrets.GITHUB_TOKEN }} 17 | - name: Update flake.lock 18 | uses: DeterminateSystems/update-flake-lock@v25 19 | with: 20 | token: ${{ secrets.GH_UPDATER_TOKEN }} 21 | nix-options: --accept-flake-config 22 | pr-body: | 23 | Automated changes by the [update-flake-lock](https://github.com/DeterminateSystems/update-flake-lock) GitHub Action. 24 | 25 | ``` 26 | {{ env.GIT_COMMIT_MESSAGE }} 27 | ``` 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result* 2 | .direnv 3 | .hostname 4 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | # Build & activate this host's profile 2 | switch: check fmt fast-switch 3 | 4 | # Switch without running checks/formatting, for rapid iteration 5 | fast-switch: 6 | darwin-rebuild --flake .#$(just get-name) switch 7 | 8 | # Deploy server profiles 9 | deploy host='': check fmt 10 | deploy .#{{ host }} 11 | 12 | # Format files, and ask to amend last commit if changes are present 13 | fmt: 14 | #!/usr/bin/env fish 15 | source bin/_lib.fish 16 | if not treefmt --fail-on-change 17 | git update-index 18 | set -l reformatted_files (git ls-files -m -o -d --exclude-standard) 19 | if test (count $reformatted_files) -gt 0 20 | log_warn "The files below were re-formatted:" 21 | for f in $reformatted_files 22 | echo "- $f" 23 | end 24 | if ask_yesno "Amend the last commit with these changes?" 25 | git add $reformatted_files 26 | git commit --amend --no-edit 27 | else 28 | exit 1 29 | end 30 | end 31 | end 32 | 33 | # Diff this host's closure in the flake against its current one 34 | diff: 35 | nix build ".#darwinConfigurations.$(just get-name).system" -o ./new-system 36 | nvd --color always diff /run/current-system ./new-system | less -FR 37 | rm ./new-system 38 | 39 | # Start a REPL with flake outputs 40 | repl: 41 | @nix repl --expr 'let \ 42 | flake = builtins.getFlake "'$(pwd)'"; \ 43 | pkgs = flake.inputs.nixpkgs.legacyPackages.${builtins.currentSystem}; in \ 44 | { inherit flake pkgs; inherit (pkgs) lib; configs = flake.darwinConfigurations // flake.nixosConfigurations; }' 45 | 46 | # Build this host's profile 47 | build: 48 | darwin-rebuild --flake .#$(just get-name) build 49 | 50 | # Check for a clean working tree 51 | check: 52 | #!/usr/bin/env fish 53 | source bin/_lib.fish 54 | set -l unclean_files (git ls-files -m -o -d --exclude-standard) 55 | if test -n "$unclean_files" 56 | log_error "Git working tree is unclean. Commit or stash changes to these files:" 57 | for f in $unclean_files 58 | echo "- $f" 59 | end 60 | exit 1 61 | end 62 | 63 | # Merge the latest flake update commit 64 | up: check 65 | #!/usr/bin/env fish 66 | source bin/_lib.fish 67 | ./bin/flakeup-logs 68 | if ask_yesno "Merge the latest update commit?" 69 | git fetch 70 | if ! git merge-base --is-ancestor master origin/update_flake_lock_action 71 | log_warn "Cannot merge using fast-forward." 72 | if ask_yesno "Cherry-pick commit instead?" 73 | git cherry-pick origin/update_flake_lock_action 74 | end 75 | end 76 | end 77 | 78 | # Garbage-collect the Nix store 79 | gc age='60': 80 | sudo nix-collect-garbage --delete-older-than {{ age }}d 81 | nix-collect-garbage 82 | 83 | # Get name of config to apply, overriden with .hostname file 84 | [private] 85 | @get-name: 86 | [[ -f .hostname ]] && cat .hostname || hostname 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # System configuration 2 | 3 | This repository hosts system/user configuration for my macOS machines managed through [nix-darwin](https://github.com/LnL7/nix-darwin) 4 | and their user environments managed through [home-manager](https://github.com/nix-community/home-manager). 5 | 6 | ## Structure 7 | 8 | This repository is a [flake](https://www.tweag.io/blog/2020-05-25-flakes/). Dependencies for this 9 | flake are specified in [`flake.nix`](./flake.nix) in the `inputs` set. The output set is configured 10 | using [flake-parts](https://flake.parts), which adds NixOS' module system for added, uh, modularity. 11 | 12 | - [`flake`](./flake) is where flake outputs are declared. Outputs are split into files by 13 | functionality. 14 | - [`modules`](./modules) stores snippets of configuration for nix-darwin and home-manager. 15 | - [`hosts`](./hosts) stores configuration for each machine managed in this repo. 16 | User environment config is stored in a `home.nix` file or `home` directory for each host (if at all). 17 | - [`bin`](./bin) is a collection of shell scripts I find useful. These are usually written for [fish](https://fishshell.com/). 18 | 19 | ## Maintenance 20 | 21 | Common maintenance tasks, like updating the flake, switching configurations, and garbage-collecting 22 | the Nix store are stored in a [Justfile](./Justfile) and run using 23 | [just](https://github.com/casey/just). 24 | -------------------------------------------------------------------------------- /bin/,: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Like https://github.com/Shopify/comma, but using nix flakes. 4 | 5 | set _dir (dirname (status filename)) 6 | source $_dir/_lib.fish 7 | 8 | check_progs nix-locate 9 | 10 | set dict _comma_choice 11 | 12 | function usage 13 | echo ", (comma) - Search for and run a command from nixpkgs 14 | 15 | "$bold"Usage:"$resf" 16 | , [-y | --yes] [-n | --dry-run] [args...] 17 | , (-k | --forget) 18 | 19 | "$bold"Options:"$resf" 20 | -h, --help Print this help message 21 | -k, --forget cmd Forget package used for cmd (or all if cmd is '!') 22 | -y, --yes Use remembered choice for cmd 23 | -n, --dry-run Just print what would be run 24 | 25 | "$bold"Examples:"$resf" 26 | , fd foo # Run 'fd' from nixpkgs#fd with argument 'foo' 27 | , -k dog # Forgot what package was chosen for command 'dog' 28 | , -y dog google.com # Run 'dog', using the package chosen from before" 29 | end 30 | 31 | function locate_attr 32 | # Use nix-locate to find the right package 33 | set -g attr (nix-locate --top-level --minimal --at-root --whole-name "/bin/$argv[1]") 34 | if test (count $attr) -eq 0 35 | log_error "No match." 36 | exit 1 37 | else if test (count $attr) -gt 1 38 | # If there are multiple candidates, first check if we've tried to run this binary before 39 | # Useful for running the same command several times 40 | if dict_query $dict $input 41 | if not test -z $yes; or ask_yesno -y "Use nixpkgs#"(dict_get $dict $input)" from before?" 42 | set -g attr (dict_get $dict $input) 43 | end 44 | else 45 | log_minor "Found multiple packages. Select one." 46 | set -g attr (for a in $attr; echo $a; end | fzf --reverse --height 10) 47 | # Remember this selection for next time 48 | not test -z $attr; and dict_set $dict $input $attr 49 | end 50 | end 51 | if test -z $attr 52 | log_error "No package selected." 53 | exit 1 54 | end 55 | end 56 | 57 | function exec_attr 58 | log_ok "Running with nixpkgs#$attr..." 59 | if test -z $dry_run 60 | nix shell "nixpkgs#$attr" -c $argv 61 | else 62 | echo nix shell "nixpkgs#$attr" -c $argv 63 | end 64 | end 65 | 66 | function _run 67 | set -g input $argv[1] 68 | log_step "Looking for packages with /bin/$input..." 69 | locate_attr $input 70 | exec_attr $argv 71 | end 72 | 73 | # Entry point 74 | argparse -s n/dry-run 'k/forget=' y/yes h/help -- $argv 75 | or return 76 | set -g dry_run $_flag_dry_run 77 | set -g yes $_flag_yes 78 | 79 | if not test -z $_flag_help 80 | usage 81 | exit 0 82 | end 83 | 84 | if not test -z $_flag_forget 85 | if test $_flag_forget = '!' 86 | dict_clear $dict 87 | else 88 | dict_clear $dict $_flag_forget 89 | end 90 | exit 0 91 | end 92 | 93 | switch $argv[1] 94 | case '' 95 | usage 96 | exit 127 97 | case '*' 98 | _run $argv 99 | end 100 | -------------------------------------------------------------------------------- /bin/_lib.fish: -------------------------------------------------------------------------------- 1 | # Some helper functions used throughout the other fish scripts. 2 | 3 | # ----- FORMATTING ----- 4 | set red (tput setaf 1) 5 | set green (tput setaf 2) 6 | set white (tput setaf 15) 7 | set purple (tput setaf 5) 8 | set yellow (tput setaf 3) 9 | set bold (tput bold) 10 | set resf (tput sgr0) 11 | 12 | # ----- LOGGING ----- 13 | function log_step 14 | echo $bold$white">> $argv[1]"$resf 1>&2 15 | end 16 | 17 | function log_minor 18 | echo $white":: "$argv[1]$resf 1>&2 19 | end 20 | 21 | function log_error 22 | echo $bold$red":: error: "$resf$red$argv[1]$resf 1>&2 23 | end 24 | 25 | function log_warn 26 | echo $bold$yellow":: warning: "$resf$yellow$argv[1]$resf 1>&2 27 | end 28 | 29 | function log_ok 30 | echo $green":: "$argv[1]$resf 1>&2 31 | end 32 | 33 | # ----- PROMPTING ----- 34 | # Ask a yes/no question, with no being the default answer (unless --yes is passed) 35 | # Typical usage: 36 | # if ask_yesno "Do something?" 37 | # action_if_yes 38 | # else 39 | # action_if_no 40 | # end 41 | function ask_yesno 42 | argparse y/yes -- $argv 43 | # Workaround to remove the mode indicator 44 | function fish_mode_prompt 45 | end 46 | 47 | if set -q _flag_yes 48 | set yesno '(Y/n)' 49 | else 50 | set yesno '(y/N)' 51 | end 52 | 53 | read -n 1 -P $purple"?? "$argv[1]" "$yesno" "$resf reply 54 | if contains $reply y Y 55 | return 0 56 | else if contains $reply n N 57 | return 1 58 | else if set -q _flag_yes 59 | return 0 60 | end 61 | return 1 62 | end 63 | 64 | function ask_silent 65 | # Workaround to remove the mode indicator 66 | function fish_mode_prompt 67 | end 68 | 69 | read --silent -P $purple"?? "$argv[1]" "$resf 70 | end 71 | 72 | # ----- UTILITY ----- 73 | # Check if a list of programs is in $PATH 74 | function check_progs 75 | for p in $argv 76 | if not command -q $p 77 | log_error "Command '$p' not found in PATH." 78 | exit 127 79 | end 80 | end 81 | end 82 | 83 | # Utils for dictionaries stored as universal variables 84 | function dict_init --argument-names dict 85 | if not set -Uq $dict 86 | set -U $dict '{}' 87 | end 88 | end 89 | 90 | function dict_set --argument-names dict key value 91 | dict_init $dict 92 | set -U $dict (echo $$dict | jq '. * {"'$key'": "'$value'"}') 93 | end 94 | 95 | function dict_get --argument-names dict key 96 | dict_init $dict 97 | echo $$dict | jq -r '.["'$key'"]' 98 | end 99 | 100 | function dict_query --argument-names dict key 101 | dict_init $dict 102 | echo $$dict | jq -e 'has("'$key'")' >/dev/null 103 | end 104 | 105 | function dict_list_keys --argument-names dict 106 | dict_init $dict 107 | echo $$dict | jq -r '. | keys[]' 108 | end 109 | 110 | function dict_clear --argument-names dict key 111 | set -Ue $dict 112 | end 113 | -------------------------------------------------------------------------------- /bin/flakeup-logs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Retrieve the logs of the last CI run against the flake update branch. 4 | # Useful for quickly seeing what packages in each system's closure were updated. 5 | 6 | set _dir (dirname (status filename)) 7 | source $_dir/_lib.fish 8 | 9 | log_step "Contacting Cirrus CI..." 10 | set cirrus_json \ 11 | (curlie --silent POST \ 12 | "https://api.cirrus-ci.com/graphql" \ 13 | query='{ 14 | ownerRepository(owner: "i077", name: "system", platform: "github") { 15 | builds(last: 1, branch: "update_flake_lock_action") { 16 | edges { 17 | node { 18 | id 19 | status 20 | changeIdInRepo 21 | tasks { name status commandLogsTail(name: "print_diff") } 22 | } 23 | } 24 | } 25 | } 26 | }' | jq .data.ownerRepository.builds.edges[0].node) 27 | set cirrus_status (echo $cirrus_json | jq -r .status) 28 | if test -n "$cirrus_status"; and test $cirrus_status != COMPLETED 29 | log_warn "Build status is currently '$cirrus_status'." 30 | log_warn "See https://cirrus-ci.com/build/"(echo $cirrus_json | jq -r .id)" for build logs." 31 | if not ask_yesno "Continue?" 32 | exit 0 33 | end 34 | end 35 | set ci_lines (echo $cirrus_json | jq -r .tasks[].commandLogsTail[]\?) 36 | 37 | # Only capture output between nvd delimiters 38 | set IS_DIFF 0 39 | for line in $ci_lines 40 | if string match -q "===== *" $line 41 | set IS_DIFF 1 42 | set -a difflines "" "" 43 | else if string match -q "=====" $line 44 | set IS_DIFF 0 45 | end 46 | 47 | if test "$IS_DIFF" -gt 0 48 | if string match -q "===== *" $line 49 | set -a difflines $bold$line$resf 50 | else 51 | set -a difflines $line 52 | end 53 | end 54 | tput sgr0 55 | end 56 | 57 | string join \n $difflines | less -FR 58 | -------------------------------------------------------------------------------- /bin/git-peek: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Quickly clone a git repo in a temporary place and open it in $EDITOR 4 | # Inspired by https://github.com/Jarred-Sumner/git-peek 5 | 6 | set _dir (dirname (status filename)) 7 | source $_dir/_lib.fish 8 | 9 | function usage 10 | echo "git-peek - Quickly open a git repository in \$EDITOR 11 | 12 | "$bold"Usage:"$resf" git peek [options...] 13 | 14 | "$bold"Options:"$resf" 15 | -h, --help Print this help message 16 | -e, --editor Open repo in instead of \$EDITOR 17 | -d, --depth Only clone commits, or entire tree if 0 (default: 1) 18 | -k, --keep Don't delete the clone after exiting 19 | (implied if existing directory is passed) 20 | -b, --branch Clone instead of the default branch 21 | -o, --dest Path to clone repo into 22 | -s, --shell Open a new shell in the clone instead of \$EDITOR 23 | 24 | "$bold"Examples:"$resf" 25 | git peek https://github.com/torvalds/linux 26 | git peek -s -b nixpkgs-unstable https://github.com/NixOS/nixpkgs" 27 | end 28 | 29 | function _clean 30 | if test -z $_flag_keep 31 | dirs -c 32 | log_step "Cleaning up..." 33 | rm -rf $clone_dir 34 | end 35 | end 36 | 37 | argparse -s h/help 'e/editor=+' k/keep 'd/depth=' 'b/branch=' 'o/dest=' s/shell -- $argv 38 | 39 | if test -z $argv[1]; or ! test -z $_flag_help 40 | usage 41 | exit 0 42 | end 43 | 44 | if ! test (count $_flag_editor) -eq 0 45 | set -g EDITOR $_flag_editor 46 | end 47 | 48 | # Parse arguments to git clone 49 | if ! test -z $_flag_branch 50 | set branch_arg "--branch=$_flag_branch" 51 | end 52 | 53 | if ! test -z $_flag_depth 54 | echo $_flag_depth 55 | if test $_flag_depth -gt 0 56 | set depth_arg "--depth=$_flag_depth" 57 | end 58 | else 59 | set depth_arg "--depth=1" 60 | end 61 | 62 | if test -z $_flag_dest 63 | set -g clone_dir (mktemp -d /tmp/git-peek.XXXXXX) 64 | else 65 | set -g clone_dir $_flag_dest 66 | if test -e $clone_dir 67 | log_error "Path '$clone_dir' already exists." 68 | exit 1 69 | end 70 | end 71 | 72 | set -g _flag_keep $_flag_keep 73 | 74 | # Main procedure 75 | log_step "Cloning $argv[1] into $clone_dir..." 76 | if ! git clone $argv[1] $clone_dir $branch_arg $depth_arg 77 | log_error "Cloning was not successful." 78 | _clean 79 | exit 1 80 | end 81 | 82 | pushd $clone_dir 83 | if test -z $_flag_shell 84 | log_step "Opening with $EDITOR..." 85 | $EDITOR $clone_dir 86 | else 87 | log_step "Opening a new shell at $clone_dir..." 88 | if test -z $_flag_keep 89 | log_minor "Will clean up after the shell exits." 90 | end 91 | $SHELL 92 | end 93 | popd 94 | _clean 95 | -------------------------------------------------------------------------------- /bin/imgcat: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Display images using iTerm2's inline protocol. 4 | # Taken from https://iterm2.com/utilities/imgcat 5 | 6 | set -o pipefail 7 | 8 | # tmux requires unrecognized OSC sequences to be wrapped with DCS tmux; 9 | # ST, and for all ESCs in to be replaced with ESC ESC. It 10 | # only accepts ESC backslash for ST. We use TERM instead of TMUX because TERM 11 | # gets passed through ssh. 12 | function print_osc() { 13 | if [[ $TERM == screen* ]]; then 14 | printf "\033Ptmux;\033\033]" 15 | else 16 | printf "\033]" 17 | fi 18 | } 19 | 20 | # More of the tmux workaround described above. 21 | function print_st() { 22 | if [[ $TERM == screen* ]]; then 23 | printf "\a\033\\" 24 | else 25 | printf "\a" 26 | fi 27 | } 28 | 29 | function load_version() { 30 | if [ -z ${IMGCAT_BASE64_VERSION+x} ]; then 31 | IMGCAT_BASE64_VERSION=$(base64 --version 2>&1) 32 | export IMGCAT_BASE64_VERSION 33 | fi 34 | } 35 | 36 | function b64_encode() { 37 | load_version 38 | if [[ $IMGCAT_BASE64_VERSION =~ GNU ]]; then 39 | # Disable line wrap 40 | base64 -w0 41 | else 42 | base64 43 | fi 44 | } 45 | 46 | function b64_decode() { 47 | load_version 48 | if [[ $IMGCAT_BASE64_VERSION =~ fourmilab ]]; then 49 | BASE64ARG=-d 50 | elif [[ $IMGCAT_BASE64_VERSION =~ GNU ]]; then 51 | BASE64ARG=-di 52 | else 53 | BASE64ARG=-D 54 | fi 55 | base64 $BASE64ARG 56 | } 57 | 58 | # print_image filename inline base64contents print_filename width height preserve_aspect_ratio 59 | # filename: Filename to convey to client 60 | # inline: 0 or 1, if set to 1, the file will be displayed inline, otherwise, it will be downloaded 61 | # base64contents: Base64-encoded contents 62 | # print_filename: 0 or 1, if set to 1, print the filename after outputting the image 63 | # width: set output width of the image in character cells, pixels or percent 64 | # height: set output height of the image in character cells, pixels or percent 65 | # preserve_aspect_ratio: 0 or 1, if set to 1, fill the specified width and height as much as possible without stretching the image 66 | function print_image() { 67 | print_osc 68 | printf "1337;File=inline=%s" "$2" 69 | printf ";size=%d" $(printf "%s" "$3" | b64_decode | wc -c) 70 | [ -n "$1" ] && printf ";name=%s" "$(printf "%s" "$1" | b64_encode)" 71 | [ -n "$5" ] && printf ";width=%s" "$5" 72 | [ -n "$6" ] && printf ";height=%s" "$6" 73 | [ -n "$7" ] && printf ";preserveAspectRatio=%s" "$7" 74 | printf ":%s" "$3" 75 | print_st 76 | printf '\n' 77 | [ "$4" == "1" ] && echo "$1" 78 | has_image_displayed=t 79 | } 80 | 81 | function error() { 82 | errcho "ERROR: $*" 83 | } 84 | 85 | function errcho() { 86 | echo "$@" >&2 87 | } 88 | 89 | function show_help() { 90 | errcho 91 | errcho "Usage: imgcat [-p] [-n] [-W width] [-H height] [-r] [-s] [-u] [-f] filename ..." 92 | errcho " cat filename | imgcat [-W width] [-H height] [-r] [-s]" 93 | errcho 94 | errcho "Display images inline in the iTerm2 using Inline Images Protocol" 95 | errcho 96 | errcho "Options:" 97 | errcho 98 | errcho " -h, --help Display help message" 99 | errcho " -p, --print Enable printing of filename or URL after each image" 100 | errcho " -n, --no-print Disable printing of filename or URL after each image" 101 | errcho " -u, --url Interpret following filename arguments as remote URLs" 102 | errcho " -f, --file Interpret following filename arguments as regular Files" 103 | errcho " -r, --preserve-aspect-ratio When scaling image preserve its original aspect ratio" 104 | errcho " -s, --stretch Stretch image to specified width and height (this option is opposite to -r)" 105 | errcho " -W, --width N Set image width to N character cells, pixels or percent (see below)" 106 | errcho " -H, --height N Set image height to N character cells, pixels or percent (see below)" 107 | errcho 108 | errcho " If you don't specify width or height an appropriate value will be chosen automatically." 109 | errcho " The width and height are given as word 'auto' or number N followed by a unit:" 110 | errcho " N character cells" 111 | errcho " Npx pixels" 112 | errcho " N% percent of the session's width or height" 113 | errcho " auto the image's inherent size will be used to determine an appropriate dimension" 114 | errcho 115 | errcho "Examples:" 116 | errcho 117 | errcho " $ imgcat -W 250px -H 250px -s avatar.png" 118 | errcho " $ cat graph.png | imgcat -W 100%" 119 | errcho " $ imgcat -p -W 500px -u http://host.tld/path/to/image.jpg -W 80 -f image.png" 120 | errcho " $ cat url_list.txt | xargs imgcat -p -W 40 -u" 121 | errcho 122 | } 123 | 124 | function check_dependency() { 125 | if ! (builtin command -V "$1" >/dev/null 2>&1); then 126 | echo "imgcat: missing dependency: can't find $1" 1>&2 127 | exit 1 128 | fi 129 | } 130 | 131 | # verify that value is in the image sizing unit format: N / Npx / N% / auto 132 | function validate_size_unit() { 133 | if [[ ! "$1" =~ ^(:?[0-9]+(:?px|%)?|auto)$ ]]; then 134 | error "Invalid image sizing unit - '$1'" 135 | show_help 136 | exit 1 137 | fi 138 | } 139 | 140 | ## Main 141 | 142 | if [ -t 0 ]; then 143 | has_stdin=f 144 | else 145 | has_stdin=t 146 | fi 147 | 148 | # Show help if no arguments and no stdin. 149 | if [ $has_stdin = f ] && [ $# -eq 0 ]; then 150 | show_help 151 | exit 152 | fi 153 | 154 | check_dependency base64 155 | check_dependency wc 156 | 157 | # Look for command line flags. 158 | while [ $# -gt 0 ]; do 159 | case "$1" in 160 | -h | --h | --help) 161 | show_help 162 | exit 163 | ;; 164 | -p | --p | --print) 165 | print_filename=1 166 | ;; 167 | -n | --n | --no-print) 168 | print_filename=0 169 | ;; 170 | -W | --W | --width) 171 | validate_size_unit "$2" 172 | width="$2" 173 | shift 174 | ;; 175 | -H | --H | --height) 176 | validate_size_unit "$2" 177 | height="$2" 178 | shift 179 | ;; 180 | -r | --R | --preserve-aspect-ratio) 181 | preserve_aspect_ratio=1 182 | ;; 183 | -s | --S | --stretch) 184 | preserve_aspect_ratio=0 185 | ;; 186 | -f | --F | --file) 187 | has_stdin=f 188 | is_url=f 189 | ;; 190 | -u | --u | --url) 191 | check_dependency curl 192 | has_stdin=f 193 | is_url=t 194 | ;; 195 | -*) 196 | error "Unknown option flag: $1" 197 | show_help 198 | exit 1 199 | ;; 200 | *) 201 | if [ "$is_url" == "t" ]; then 202 | encoded_image=$(curl -fs "$1" | b64_encode) || { 203 | error "Could not retrieve image from URL $1, error_code: $?" 204 | exit 2 205 | } 206 | elif [ -r "$1" ]; then 207 | encoded_image=$(cat "$1" | b64_encode) 208 | else 209 | error "imgcat: $1: No such file or directory" 210 | exit 2 211 | fi 212 | has_stdin=f 213 | print_image "$1" 1 "$encoded_image" "$print_filename" "$width" "$height" "$preserve_aspect_ratio" 214 | ;; 215 | esac 216 | shift 217 | done 218 | 219 | # Read and print stdin 220 | if [ $has_stdin = t ]; then 221 | print_image "" 1 "$(cat | b64_encode)" 0 "$width" "$height" "$preserve_aspect_ratio" 222 | fi 223 | 224 | if [ "$has_image_displayed" != "t" ]; then 225 | error "No image provided. Check command line options." 226 | show_help 227 | exit 1 228 | fi 229 | 230 | exit 0 231 | -------------------------------------------------------------------------------- /bin/mark: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # CLI for my private shortlinking service, mark. 4 | 5 | set _dir (dirname (status filename)) 6 | source $_dir/_lib.fish 7 | 8 | function usage 9 | echo "mark - Shorten a URL or open a shortened one 10 | 11 | "$bold"Usage:"$resf" 12 | mark 13 | mark [-p | --print] 14 | 15 | "$bold"Options:"$resf" 16 | -h, --help Print this help message 17 | -p, --print Print the URL to stdout instead of opening it" 18 | end 19 | 20 | argparse -s h/help o/open p/print -- $argv 21 | 22 | if test -z $argv[1]; or ! test -z $_flag_help 23 | usage 24 | exit 0 25 | end 26 | 27 | # Helper function to check if argument is a valid URL 28 | function is_url 29 | string match --quiet --regex '^https?://' $argv[1] 30 | return $status 31 | end 32 | 33 | if is_url $argv[1] 34 | # Get the authentication key first 35 | log_step "Getting authkey from 1Password..." 36 | set AUTHKEY (op item get mark --field password) 37 | if test -z $AUTHKEY 38 | log_error "Couldn't get authkey." 39 | exit 1 40 | end 41 | 42 | set response (curl --silent -X POST \ 43 | --data '{ "authkey": "'$AUTHKEY'", "url": "'$argv[1]'"}' -H 'Content-Type: application/json'\ 44 | "https://mark.imranh.org/new") 45 | 46 | if is_url $response 47 | echo $response 48 | else 49 | log_error $response 50 | exit 1 51 | end 52 | else 53 | set link (curl --silent --dump-header - "https://mark.imranh.org/"$argv[1] | \ 54 | # Get the location header and strip the trailing newline 55 | grep -i "^location:" | cut -d " " -f 2 | tr -d '\r') 56 | 57 | if test -z $link 58 | log_error "No link with this key." 59 | exit 1 60 | end 61 | 62 | if test $_flag_print 63 | echo $link 64 | else 65 | log_ok "Opening $link in browser..." 66 | open $link 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /bin/mount-backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Mount backup repo to a temporary directory and open a new shell. 4 | # This is hardcoded to my restic backup repo. 5 | 6 | set _dir (dirname (status filename)) 7 | source $_dir/_lib.fish 8 | 9 | function usage 10 | echo "mount-backup - Mount restic backup repository and open a shell 11 | 12 | "$bold"Usage:"$resf" mount-backup [options...] 13 | 14 | "$bold"Options:"$resf" 15 | -h, --help Print this help message 16 | -a, --ask-pass Prompt for restic password" 17 | end 18 | 19 | argparse -s h/help a/ask-pass -- $argv 20 | 21 | if ! test -z $_flag_help 22 | usage 23 | exit 0 24 | end 25 | 26 | check_progs restic 27 | 28 | if test -z $_flag_ask_pass 29 | check_progs rbw 30 | log_step "Retrieving password for restic repo..." 31 | set -x RESTIC_PASSWORD (rbw get restic) 32 | else 33 | set -x RESTIC_PASSWORD (ask_silent "Enter your restic repo's password:") 34 | end 35 | 36 | set -x RESTIC_REPOSITORY "rest:https://minidepot.imranh.xyz:6053" 37 | 38 | set mountpoint (mktemp -d /tmp/restic.XXXXXX) 39 | log_step "Mounting backup repo to $mountpoint..." 40 | test -d $mountpoint || mkdir -p $mountpoint 41 | restic mount $mountpoint & 42 | 43 | # No need to keep the password around anymore 44 | set -e RESTIC_PASSWORD 45 | set restic_pid (jobs -p %1) 46 | 47 | # Wait for mountpoint to be created 48 | log_minor "Waiting for restic to mount..." 49 | while not mountpoint -q $mountpoint 50 | sleep 1 51 | # Check if restic exited 52 | if not jobs -q 53 | log_error "Restic couldn't mount the repo." 54 | exit 1 55 | end 56 | end 57 | log_ok "Repository was mounted." 58 | 59 | log_step "Opening a new shell at $mountpoint..." 60 | log_minor "Will clean up after this shell exits." 61 | pushd $mountpoint 62 | 63 | # Open a new fish shell with a custom right prompt 64 | set right_prompt_cmd \ 65 | "function fish_right_prompt; echo '"$bold$yellow"Restic repo is mounted"$resf"'; end" 66 | fish -C $right_prompt_cmd 67 | popd 68 | 69 | # Clean up 70 | log_step "Cleaning up..." 71 | kill -2 $restic_pid 72 | wait $restic_pid 73 | rmdir $mountpoint 74 | -------------------------------------------------------------------------------- /bin/nix-lastgoodrev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Get the last known good evaluation of a package in nixpkgs, according to Hydra. 4 | 5 | set _dir (dirname (status filename)) 6 | source $_dir/_lib.fish 7 | 8 | set _flake (dirname $_dir) 9 | set supported_systems {x86_64,aarch64}-{darwin,linux} 10 | 11 | set HYDRA_URL "https://hydra.nixos.org" 12 | 13 | function usage 14 | echo "nix-lastgoodrev - Get the nixpkgs commit with the last good eval of a package, according to Hydra 15 | 16 | "$bold"Usage:"$resf" 17 | nix-lastgoodrev [options...] 18 | 19 | "$bold"Options:"$resf" 20 | -h, --help Print this help message 21 | -u, --update Update system's list of broken packages 22 | -s, --system= Get revision for system (repeat for multiple systems) 23 | (default: builtins.currentSystem)" 24 | end 25 | 26 | argparse h/help s/system=+ u/update -- $argv; or exit 127 27 | 28 | if set -q _flag_help; or test (count $argv) -lt 1 29 | usage 30 | exit 0 31 | else if not set -q _flag_system 32 | set _flag_system (nix eval --impure --raw --expr builtins.currentSystem) 33 | end 34 | 35 | check_progs curl jo 36 | 37 | set attr $argv[1] 38 | 39 | function getrev -a double 40 | log_step "Getting last good revision for nixpkgs#$double.$attr..." 41 | # Get Hydra project & jobset 42 | set hydra_proj nixpkgs 43 | set hydra_jobset trunk 44 | 45 | # Get latest successful build info 46 | set hydra_job $attr.$double 47 | set latest_build_json \ 48 | (curl -sL -H "accept: application/json" "$HYDRA_URL/job/$hydra_proj/$hydra_jobset/$hydra_job/latest") 49 | set latest_build_eval_id (echo $latest_build_json | jq -r ".jobsetevals[0]") 50 | if string match -q null $latest_build_eval_id 51 | log_error "Couldn't find a successful build." 52 | return 53 | end 54 | set latest_build_date (date --date="@"(echo $latest_build_json | jq -r .timestamp) -I) 55 | 56 | set pname (echo $latest_build_json | jq -r ".nixname") 57 | log_minor "Found a successful build in Hydra from $latest_build_date. Getting nixpkgs revision..." 58 | 59 | # Determine which revision of nixpkgs was used to build latest successful build 60 | # The eval info is quite large, so we take what we need immediately without storing the entire 61 | # output of /eval/:id 62 | set nixpkgs_rev \ 63 | (curl -s -H "accept: application/json" "$HYDRA_URL/eval/$latest_build_eval_id" | jq -r ".jobsetevalinputs.nixpkgs.revision") 64 | set nixpkgs_hash (nix flake prefetch --json "github:NixOS/nixpkgs/$nixpkgs_rev" | jq -r ".hash") 65 | 66 | # Build a JSON object with all collected info 67 | jo -- \ 68 | -s nixpkgs_rev="$nixpkgs_rev" \ 69 | -s nixpkgs_hash="$nixpkgs_hash" \ 70 | -s pname="$pname" \ 71 | -n hydra_build=$latest_build_eval_id \ 72 | -s build_date=$latest_build_date 73 | end 74 | 75 | # Build up a JSON object with info for each system 76 | for double in $_flag_system 77 | if not contains $double $supported_systems 78 | log_error "Unsupported system: $double" 79 | continue 80 | end 81 | set hydra_info (getrev $double) 82 | if test -z $hydra_info 83 | continue 84 | end 85 | set -a jo_opts $double=$hydra_info 86 | end 87 | 88 | set result_json (jo -e -- $jo_opts) 89 | if set -q _flag_update 90 | set json_path $_flake/modules/broken-overlay-data.json 91 | # Check if there's already an existing key for $attr, since jo doesn't merge objects 92 | # If we don't do this, we'll lose information about the last good revision for another system. 93 | set old_attr_result (cat $json_path | jq -r .$attr) 94 | if not string match -q null "$old_attr_result" 95 | set result_json (echo $old_attr_result | jo -e -f - -- $jo_opts) 96 | end 97 | 98 | # Update file with new results 99 | set tmpfile (mktemp) 100 | jo -p -D -f $json_path -- $attr=$result_json >$tmpfile 101 | mv $tmpfile $json_path 102 | log_ok "File updated." 103 | else 104 | jo -p -e -- $jo_opts 105 | end 106 | -------------------------------------------------------------------------------- /bin/nixfetch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Fetch a file, add it to the nix store, and output its path. 4 | # Essentially a wrapper around `nix store prefetch-file`. 5 | # The nix store is used to avoid storing duplicates of the same file. 6 | 7 | set _dir (dirname (status filename)) 8 | source $_dir/_lib.fish 9 | 10 | function usage 11 | echo "nixfetch - Add a file to /nix/store and output its path 12 | 13 | "$bold"Usage:"$resf" nixfetch [options..] 14 | 15 | "$bold"Options:"$resf" 16 | -h, --help Print this help message 17 | -x, --executable Set the file's executable bit 18 | --unpack Unpack the file (url can also be a flakeref)" 19 | end 20 | 21 | argparse -s h/help x/executable unpack -- $argv 22 | 23 | if test -z $argv[1]; or set -q _flag_help 24 | usage 25 | exit 125 26 | end 27 | 28 | # -x and --unpack cannot be used together 29 | if set -q _flag_executable; and set -q _flag_unpack 30 | log_error $_flag_executable" and "$_flag_unpack" cannot be used together" 31 | exit 125 32 | end 33 | 34 | # Prefetching a tarball is exposed under `nix flake prefetch` 35 | if set -q _flag_unpack 36 | set nixargs flake prefetch 37 | else 38 | set nixargs store prefetch-file 39 | if set -q _flag_executable 40 | set -a nixargs --executable 41 | end 42 | end 43 | 44 | # Fetch (+unpack) the file! 45 | set storepath (nix $nixargs --json $argv[1] | jq -r .storePath) 46 | echo $storepath 47 | -------------------------------------------------------------------------------- /bin/pywith: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | 3 | # Spawn a python environment (and run it) with the given list of packages. 4 | 5 | set _dir (dirname (status filename)) 6 | source $_dir/_lib.fish 7 | 8 | function usage 9 | echo "pywith - Quickly spawn Python environments 10 | 11 | "$bold"Usage:"$resf" 12 | pywith [options...] ... 13 | pywith [options...] (-r | --requirements) 14 | 15 | "$bold"Options:"$resf" 16 | -h, --help Print this help message 17 | -c, --command= Run in the built shell (default: ptpython) 18 | -s, --shell Same as '-c $SHELL' 19 | -f, --file= Interpret with built Python env 20 | -r, --requirements Read packages from a text file 21 | -R Same as '-r ./requirements.txt' 22 | 23 | "$bold"Examples:"$resf" 24 | pywith numpy scipy # Open a python REPL with the numpy and scipy packages available 25 | pywith -R # Open a python REPL with packages from ./requirements.txt 26 | pywith -s requests # Open a shell with a python that has the requests library 27 | pywith -f script.py numpy # Interpret script.py using a python with numpy available 28 | " 29 | end 30 | 31 | argparse h/help 'c/command=' s/shell 'f/file=' 'r/requirements=' R -- $argv; or exit 127 32 | 33 | if set -q _flag_help 34 | usage 35 | exit 0 36 | end 37 | 38 | # Hardcode Python 3.13 for now 39 | set -a uv_args --python '3.13' --native-tls 40 | 41 | # Give a default command if none was given 42 | if set -q _flag_command; and begin 43 | set -q _flag_file; or set -q _flag_shell 44 | end 45 | log_error "Options --command, --file, and --shell are mutually exclusive." 46 | exit 127 47 | else if set -q _flag_command 48 | set cmd $_flag_command 49 | else if set -q _flag_shell 50 | set cmd $SHELL 51 | else if set -q _flag_file 52 | if not stat $_flag_file >/dev/null 53 | log_error "Couldn't read file: "$_flag_file 54 | exit 2 55 | end 56 | set cmd python $_flag_file 57 | else 58 | set -a uv_args --with ptpython 59 | set cmd ptpython 60 | end 61 | 62 | # If -r or -R was passed, read from the given requirements file 63 | if set -q _flag_requirements; or set -q _flag_R 64 | set -a uv_args --with-requirements 65 | 66 | if set -q _flag_R 67 | set -a uv_args './requirements.txt' 68 | else 69 | set -a uv_args $_flag_requirements 70 | end 71 | end 72 | 73 | for pkg in $argv 74 | set -a uv_args --with $pkg 75 | end 76 | 77 | # Run uv run 78 | uv run $uv_args $cmd 79 | 80 | # vim: set ft=fish sw=4: 81 | -------------------------------------------------------------------------------- /bin/show: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fish 2 | set _dir (dirname (status filename)) 3 | source $_dir/_lib.fish 4 | 5 | # Show the contents of $argv[1], depending on the type. 6 | # I wrote this because I didn't want to switch between `ls` and `cat` when previewing things in 7 | # the terminal. 8 | 9 | function help 10 | echo "show: Show the contents of a file, depending on the type 11 | 12 | "$bold"Usage:"$resf" show [args...] 13 | 14 | All ARGS after FILE get passed to the program used to show FILE. 15 | For example, if FILE is a directory, the resulting command run becomes: 16 | 17 | ls -l FILE [ARGS...]" 18 | end 19 | 20 | if test -z $argv[1] 21 | # If no argument is passed and we're in a pipe, act like a pager. 22 | if test -t 0 # Check if stdout is a terminal (heuristic for pipe) 23 | help 24 | exit 127 25 | else 26 | bat 27 | end 28 | else if not test -e $argv[1] 29 | # Maybe I pressed ENTER a little too soon after TAB... 30 | # If there were multiple completions, prompt to pick one 31 | if test (count $argv[1]*) -gt 1 32 | set newarg1 (for f in $argv[1]*; echo $f; end | fzf --reverse --height 10) 33 | if test -z $newarg1 34 | exit 1 35 | end 36 | log_minor "Showing '"$newarg1"'..." 37 | show $newarg1 $argv[2..-1] 38 | else 39 | log_error "No such file: '"$argv[1]"'." 40 | exit 2 41 | end 42 | else if test -L $argv[1] 43 | set linkto (readlink -f $argv[1]) 44 | log_minor "File is a link to "$linkto 45 | show $linkto $argv[2..-1] 46 | else if test -d $argv[1] 47 | # If the listing ends up being long, use less as a pager 48 | ls --color=always -l $argv | less -FR 49 | else if test -f $argv[1] 50 | # If the file is a regular file, let's look at the mimetype... 51 | set mimetype (file --brief --mime-type $argv[1]) 52 | set mimeencoding (file --brief --mime-encoding $argv[1]) 53 | 54 | # For a file with binary data, try to display its contents using other programs 55 | if test $mimeencoding = binary 56 | # Display images if we're in iTerm 57 | if string match -q "image/*" $mimetype; and set -q TERM_PROGRAM; and test $TERM_PROGRAM = "iTerm.app" 58 | $_dir/imgcat --preserve-aspect-ratio --width 100% --height 90% --file $argv[1] 59 | else if test $mimetype = application/gzip 60 | file --uncompress --brief $argv 61 | else 62 | file --brief $argv 63 | end 64 | else if test $mimetype = application/json; and check_progs jq 65 | cat $argv[1] | jq --color-output $argv[2..-1] | less -FR 66 | else 67 | bat $argv 68 | end 69 | else 70 | # Not sure how to handle other types at this point, so just pass them to stat 71 | stat $argv[1] 72 | end 73 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "darwin": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1748352827, 11 | "narHash": "sha256-sNUUP6qxGkK9hXgJ+p362dtWLgnIWwOCmiq72LAWtYo=", 12 | "owner": "LnL7", 13 | "repo": "nix-darwin", 14 | "rev": "44a7d0e687a87b73facfe94fba78d323a6686a90", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "LnL7", 19 | "repo": "nix-darwin", 20 | "type": "github" 21 | } 22 | }, 23 | "devshell": { 24 | "inputs": { 25 | "nixpkgs": "nixpkgs" 26 | }, 27 | "locked": { 28 | "lastModified": 1741473158, 29 | "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=", 30 | "owner": "numtide", 31 | "repo": "devshell", 32 | "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "numtide", 37 | "repo": "devshell", 38 | "type": "github" 39 | } 40 | }, 41 | "fdbPkg": { 42 | "inputs": { 43 | "flakeUtils": "flakeUtils", 44 | "nixpkgs": "nixpkgs_2" 45 | }, 46 | "locked": { 47 | "lastModified": 1721791780, 48 | "narHash": "sha256-ET3+7Jdl8MMmFPjsR6meACMpLHa8hwjDYHBXfBN38FY=", 49 | "owner": "shopstic", 50 | "repo": "nix-fdb", 51 | "rev": "ae67770ac3cb7fa14f307bd92fa86a43ff888b36", 52 | "type": "github" 53 | }, 54 | "original": { 55 | "owner": "shopstic", 56 | "ref": "7.1.61", 57 | "repo": "nix-fdb", 58 | "type": "github" 59 | } 60 | }, 61 | "flake-parts": { 62 | "inputs": { 63 | "nixpkgs-lib": "nixpkgs-lib" 64 | }, 65 | "locked": { 66 | "lastModified": 1743550720, 67 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 68 | "owner": "hercules-ci", 69 | "repo": "flake-parts", 70 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "hercules-ci", 75 | "repo": "flake-parts", 76 | "type": "github" 77 | } 78 | }, 79 | "flake-parts_2": { 80 | "inputs": { 81 | "nixpkgs-lib": [ 82 | "nixvim", 83 | "nixpkgs" 84 | ] 85 | }, 86 | "locked": { 87 | "lastModified": 1743550720, 88 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 89 | "owner": "hercules-ci", 90 | "repo": "flake-parts", 91 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 92 | "type": "github" 93 | }, 94 | "original": { 95 | "owner": "hercules-ci", 96 | "repo": "flake-parts", 97 | "type": "github" 98 | } 99 | }, 100 | "flake-utils": { 101 | "inputs": { 102 | "systems": "systems_3" 103 | }, 104 | "locked": { 105 | "lastModified": 1710146030, 106 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 107 | "owner": "numtide", 108 | "repo": "flake-utils", 109 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 110 | "type": "github" 111 | }, 112 | "original": { 113 | "owner": "numtide", 114 | "repo": "flake-utils", 115 | "type": "github" 116 | } 117 | }, 118 | "flake-utils_2": { 119 | "inputs": { 120 | "systems": "systems_4" 121 | }, 122 | "locked": { 123 | "lastModified": 1731533236, 124 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 125 | "owner": "numtide", 126 | "repo": "flake-utils", 127 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 128 | "type": "github" 129 | }, 130 | "original": { 131 | "owner": "numtide", 132 | "repo": "flake-utils", 133 | "type": "github" 134 | } 135 | }, 136 | "flakeUtils": { 137 | "inputs": { 138 | "systems": "systems" 139 | }, 140 | "locked": { 141 | "lastModified": 1710146030, 142 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 143 | "owner": "numtide", 144 | "repo": "flake-utils", 145 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 146 | "type": "github" 147 | }, 148 | "original": { 149 | "owner": "numtide", 150 | "repo": "flake-utils", 151 | "type": "github" 152 | } 153 | }, 154 | "flakeUtils_2": { 155 | "inputs": { 156 | "systems": "systems_2" 157 | }, 158 | "locked": { 159 | "lastModified": 1731533236, 160 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 161 | "owner": "numtide", 162 | "repo": "flake-utils", 163 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 164 | "type": "github" 165 | }, 166 | "original": { 167 | "owner": "numtide", 168 | "repo": "flake-utils", 169 | "type": "github" 170 | } 171 | }, 172 | "home-manager": { 173 | "inputs": { 174 | "nixpkgs": [ 175 | "nixpkgs" 176 | ] 177 | }, 178 | "locked": { 179 | "lastModified": 1748668774, 180 | "narHash": "sha256-fYk/vk4ClmvHIgnGv/5GNRiDLtNCwXo9aLq36L/x+P4=", 181 | "owner": "nix-community", 182 | "repo": "home-manager", 183 | "rev": "60e4624302d956fe94d3f7d96a560d14d70591b9", 184 | "type": "github" 185 | }, 186 | "original": { 187 | "owner": "nix-community", 188 | "repo": "home-manager", 189 | "type": "github" 190 | } 191 | }, 192 | "ixx": { 193 | "inputs": { 194 | "flake-utils": [ 195 | "nixvim", 196 | "nuschtosSearch", 197 | "flake-utils" 198 | ], 199 | "nixpkgs": [ 200 | "nixvim", 201 | "nuschtosSearch", 202 | "nixpkgs" 203 | ] 204 | }, 205 | "locked": { 206 | "lastModified": 1748294338, 207 | "narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=", 208 | "owner": "NuschtOS", 209 | "repo": "ixx", 210 | "rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85", 211 | "type": "github" 212 | }, 213 | "original": { 214 | "owner": "NuschtOS", 215 | "ref": "v0.0.8", 216 | "repo": "ixx", 217 | "type": "github" 218 | } 219 | }, 220 | "nix-hot-pot": { 221 | "inputs": { 222 | "fdbPkg": "fdbPkg", 223 | "flakeUtils": "flakeUtils_2", 224 | "nix2containerPkg": "nix2containerPkg", 225 | "nixpkgs": "nixpkgs_3", 226 | "npmlock2nixPkg": "npmlock2nixPkg" 227 | }, 228 | "locked": { 229 | "lastModified": 1747687418, 230 | "narHash": "sha256-/3Us9qkzmMIBL00NLsaEW+5X30mGtyvFlzqT/p2lgN8=", 231 | "owner": "shopstic", 232 | "repo": "nix-hot-pot", 233 | "rev": "020c62ee81b923ff41db4b1f86c1181a606dc155", 234 | "type": "github" 235 | }, 236 | "original": { 237 | "owner": "shopstic", 238 | "repo": "nix-hot-pot", 239 | "type": "github" 240 | } 241 | }, 242 | "nix2containerPkg": { 243 | "inputs": { 244 | "flake-utils": "flake-utils", 245 | "nixpkgs": [ 246 | "nix-hot-pot", 247 | "nixpkgs" 248 | ] 249 | }, 250 | "locked": { 251 | "lastModified": 1738222190, 252 | "narHash": "sha256-MRrL8IQ4NMM8NfOsL7BwBzz+TkN7VzJwKXTowYIHkg4=", 253 | "owner": "nktpro", 254 | "repo": "nix2container", 255 | "rev": "64ba2c169fd283fef92449d4cc3c881859f9a8d0", 256 | "type": "github" 257 | }, 258 | "original": { 259 | "owner": "nktpro", 260 | "repo": "nix2container", 261 | "type": "github" 262 | } 263 | }, 264 | "nixpkgs": { 265 | "locked": { 266 | "lastModified": 1722073938, 267 | "narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=", 268 | "owner": "NixOS", 269 | "repo": "nixpkgs", 270 | "rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae", 271 | "type": "github" 272 | }, 273 | "original": { 274 | "owner": "NixOS", 275 | "ref": "nixpkgs-unstable", 276 | "repo": "nixpkgs", 277 | "type": "github" 278 | } 279 | }, 280 | "nixpkgs-lib": { 281 | "locked": { 282 | "lastModified": 1743296961, 283 | "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", 284 | "owner": "nix-community", 285 | "repo": "nixpkgs.lib", 286 | "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", 287 | "type": "github" 288 | }, 289 | "original": { 290 | "owner": "nix-community", 291 | "repo": "nixpkgs.lib", 292 | "type": "github" 293 | } 294 | }, 295 | "nixpkgs_2": { 296 | "locked": { 297 | "lastModified": 1720553833, 298 | "narHash": "sha256-IXMiHQMtdShDXcBW95ctA+m5Oq2kLxnBt7WlMxvDQXA=", 299 | "owner": "nixos", 300 | "repo": "nixpkgs", 301 | "rev": "249fbde2a178a2ea2638b65b9ecebd531b338cf9", 302 | "type": "github" 303 | }, 304 | "original": { 305 | "owner": "nixos", 306 | "repo": "nixpkgs", 307 | "rev": "249fbde2a178a2ea2638b65b9ecebd531b338cf9", 308 | "type": "github" 309 | } 310 | }, 311 | "nixpkgs_3": { 312 | "locked": { 313 | "lastModified": 1744168086, 314 | "narHash": "sha256-S9M4HddBCxbbX1CKSyDYgZ8NCVyHcbKnBfoUXeRu2jQ=", 315 | "owner": "nixos", 316 | "repo": "nixpkgs", 317 | "rev": "60e405b241edb6f0573f3d9f944617fe33ac4a73", 318 | "type": "github" 319 | }, 320 | "original": { 321 | "owner": "nixos", 322 | "ref": "nixos-24.11", 323 | "repo": "nixpkgs", 324 | "type": "github" 325 | } 326 | }, 327 | "nixpkgs_4": { 328 | "locked": { 329 | "lastModified": 1748601998, 330 | "narHash": "sha256-XbIdzeWUL6Htz3/EbeyY71qMxYizMUWYltWYGg6qfcI=", 331 | "owner": "NixOS", 332 | "repo": "nixpkgs", 333 | "rev": "8ca7ec685bbee55d6dcb326abe23945c0806c39e", 334 | "type": "github" 335 | }, 336 | "original": { 337 | "owner": "NixOS", 338 | "ref": "nixpkgs-unstable", 339 | "repo": "nixpkgs", 340 | "type": "github" 341 | } 342 | }, 343 | "nixpkgs_5": { 344 | "locked": { 345 | "lastModified": 1748406211, 346 | "narHash": "sha256-B3BsCRbc+x/d0WiG1f+qfSLUy+oiIfih54kalWBi+/M=", 347 | "owner": "NixOS", 348 | "repo": "nixpkgs", 349 | "rev": "3d1f29646e4b57ed468d60f9d286cde23a8d1707", 350 | "type": "github" 351 | }, 352 | "original": { 353 | "owner": "NixOS", 354 | "ref": "nixpkgs-unstable", 355 | "repo": "nixpkgs", 356 | "type": "github" 357 | } 358 | }, 359 | "nixpkgs_6": { 360 | "locked": { 361 | "lastModified": 1747958103, 362 | "narHash": "sha256-qmmFCrfBwSHoWw7cVK4Aj+fns+c54EBP8cGqp/yK410=", 363 | "owner": "nixos", 364 | "repo": "nixpkgs", 365 | "rev": "fe51d34885f7b5e3e7b59572796e1bcb427eccb1", 366 | "type": "github" 367 | }, 368 | "original": { 369 | "owner": "nixos", 370 | "ref": "nixpkgs-unstable", 371 | "repo": "nixpkgs", 372 | "type": "github" 373 | } 374 | }, 375 | "nixvim": { 376 | "inputs": { 377 | "flake-parts": "flake-parts_2", 378 | "nixpkgs": "nixpkgs_5", 379 | "nuschtosSearch": "nuschtosSearch", 380 | "systems": "systems_5" 381 | }, 382 | "locked": { 383 | "lastModified": 1748564405, 384 | "narHash": "sha256-uCmQLJmdg0gKWBs+vhNmS9RIPJW8/ddo6TvQ/a4gupc=", 385 | "owner": "nix-community", 386 | "repo": "nixvim", 387 | "rev": "8b3a69cfea5ba2fa008c6c57ab79c99c513a349b", 388 | "type": "github" 389 | }, 390 | "original": { 391 | "owner": "nix-community", 392 | "repo": "nixvim", 393 | "type": "github" 394 | } 395 | }, 396 | "npmlock2nixPkg": { 397 | "flake": false, 398 | "locked": { 399 | "lastModified": 1673447413, 400 | "narHash": "sha256-sJM82Sj8yfQYs9axEmGZ9Evzdv/kDcI9sddqJ45frrU=", 401 | "owner": "nix-community", 402 | "repo": "npmlock2nix", 403 | "rev": "9197bbf397d76059a76310523d45df10d2e4ca81", 404 | "type": "github" 405 | }, 406 | "original": { 407 | "owner": "nix-community", 408 | "repo": "npmlock2nix", 409 | "rev": "9197bbf397d76059a76310523d45df10d2e4ca81", 410 | "type": "github" 411 | } 412 | }, 413 | "nuschtosSearch": { 414 | "inputs": { 415 | "flake-utils": "flake-utils_2", 416 | "ixx": "ixx", 417 | "nixpkgs": [ 418 | "nixvim", 419 | "nixpkgs" 420 | ] 421 | }, 422 | "locked": { 423 | "lastModified": 1748298102, 424 | "narHash": "sha256-PP11GVwUt7F4ZZi5A5+99isuq39C59CKc5u5yVisU/U=", 425 | "owner": "NuschtOS", 426 | "repo": "search", 427 | "rev": "f8a1c221afb8b4c642ed11ac5ee6746b0fe1d32f", 428 | "type": "github" 429 | }, 430 | "original": { 431 | "owner": "NuschtOS", 432 | "repo": "search", 433 | "type": "github" 434 | } 435 | }, 436 | "root": { 437 | "inputs": { 438 | "darwin": "darwin", 439 | "devshell": "devshell", 440 | "flake-parts": "flake-parts", 441 | "home-manager": "home-manager", 442 | "nix-hot-pot": "nix-hot-pot", 443 | "nixpkgs": "nixpkgs_4", 444 | "nixvim": "nixvim", 445 | "treefmt-nix": "treefmt-nix" 446 | } 447 | }, 448 | "systems": { 449 | "locked": { 450 | "lastModified": 1681028828, 451 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 452 | "owner": "nix-systems", 453 | "repo": "default", 454 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 455 | "type": "github" 456 | }, 457 | "original": { 458 | "owner": "nix-systems", 459 | "repo": "default", 460 | "type": "github" 461 | } 462 | }, 463 | "systems_2": { 464 | "locked": { 465 | "lastModified": 1681028828, 466 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 467 | "owner": "nix-systems", 468 | "repo": "default", 469 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 470 | "type": "github" 471 | }, 472 | "original": { 473 | "owner": "nix-systems", 474 | "repo": "default", 475 | "type": "github" 476 | } 477 | }, 478 | "systems_3": { 479 | "locked": { 480 | "lastModified": 1681028828, 481 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 482 | "owner": "nix-systems", 483 | "repo": "default", 484 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 485 | "type": "github" 486 | }, 487 | "original": { 488 | "owner": "nix-systems", 489 | "repo": "default", 490 | "type": "github" 491 | } 492 | }, 493 | "systems_4": { 494 | "locked": { 495 | "lastModified": 1681028828, 496 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 497 | "owner": "nix-systems", 498 | "repo": "default", 499 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 500 | "type": "github" 501 | }, 502 | "original": { 503 | "owner": "nix-systems", 504 | "repo": "default", 505 | "type": "github" 506 | } 507 | }, 508 | "systems_5": { 509 | "locked": { 510 | "lastModified": 1681028828, 511 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 512 | "owner": "nix-systems", 513 | "repo": "default", 514 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 515 | "type": "github" 516 | }, 517 | "original": { 518 | "owner": "nix-systems", 519 | "repo": "default", 520 | "type": "github" 521 | } 522 | }, 523 | "treefmt-nix": { 524 | "inputs": { 525 | "nixpkgs": "nixpkgs_6" 526 | }, 527 | "locked": { 528 | "lastModified": 1748243702, 529 | "narHash": "sha256-9YzfeN8CB6SzNPyPm2XjRRqSixDopTapaRsnTpXUEY8=", 530 | "owner": "numtide", 531 | "repo": "treefmt-nix", 532 | "rev": "1f3f7b784643d488ba4bf315638b2b0a4c5fb007", 533 | "type": "github" 534 | }, 535 | "original": { 536 | "owner": "numtide", 537 | "repo": "treefmt-nix", 538 | "type": "github" 539 | } 540 | } 541 | }, 542 | "root": "root", 543 | "version": 7 544 | } 545 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "My Nix configurations"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | 7 | nix-hot-pot.url = "github:shopstic/nix-hot-pot"; 8 | 9 | # Manage macOS systems with Nix 10 | darwin = { 11 | url = "github:LnL7/nix-darwin"; 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | }; 14 | 15 | # Manage user environment with Nix 16 | home-manager = { 17 | url = "github:nix-community/home-manager"; 18 | inputs.nixpkgs.follows = "nixpkgs"; 19 | }; 20 | 21 | # Module for neovim configuration 22 | nixvim.url = "github:nix-community/nixvim"; 23 | 24 | # Dev-environment stuff 25 | flake-parts.url = "github:hercules-ci/flake-parts"; 26 | treefmt-nix.url = "github:numtide/treefmt-nix"; 27 | devshell.url = "github:numtide/devshell"; 28 | }; 29 | 30 | nixConfig = { 31 | substituters = [ 32 | "https://i077.cachix.org" 33 | "https://cache.nixos.org" 34 | "https://nix-community.cachix.org/" 35 | ]; 36 | trusted-public-keys = [ 37 | "i077.cachix.org-1:v28tOFUfUjtVXdPol5FfEO/6wC/VKWnHkD32/aMJJBk=" 38 | "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 39 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 40 | ]; 41 | }; 42 | 43 | outputs = inputs: 44 | inputs.flake-parts.lib.mkFlake {inherit inputs;} { 45 | systems = ["x86_64-darwin" "aarch64-darwin" "x86_64-linux" "aarch64-linux"]; 46 | 47 | imports = [ 48 | ./flake/devshell.nix 49 | ./flake/hosts.nix 50 | ./flake/rescue.nix 51 | ./flake/pkgs.nix 52 | ]; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /flake/devshell.nix: -------------------------------------------------------------------------------- 1 | # This part defines the development environment experience as built by `nix develop`. 2 | {inputs, ...}: { 3 | imports = [ 4 | inputs.treefmt-nix.flakeModule 5 | inputs.devshell.flakeModule 6 | ]; 7 | 8 | perSystem = { 9 | config, 10 | inputs', 11 | pkgs, 12 | ... 13 | }: { 14 | devshells.default = { 15 | name = "system"; 16 | packages = with pkgs; [ 17 | # Wrap nix to support flakes 18 | (writeShellScriptBin "nix" '' 19 | ${lib.getExe nixVersions.latest} --extra-experimental-features "nix-command flakes" "$@" 20 | '') 21 | cachix 22 | jo 23 | nvd 24 | ]; 25 | 26 | commands = [ 27 | {package = pkgs.just;} 28 | {package = config.treefmt.build.wrapper;} 29 | ]; 30 | }; 31 | 32 | treefmt = { 33 | projectRootFile = ".git/config"; 34 | programs.alejandra.enable = true; 35 | programs.prettier.enable = true; 36 | programs.stylua.enable = true; 37 | settings.formatter.fish = { 38 | command = "${pkgs.fish}/bin/fish_indent"; 39 | options = ["--write"]; 40 | includes = 41 | ["*.fish"] 42 | ++ 43 | # Exclude non-fish scripts in ./bin from formatter 44 | (pkgs.lib.pipe ../bin [ 45 | builtins.readDir # Read the ./bin directory 46 | (pkgs.lib.flip builtins.removeAttrs ["imgcat"]) # Remove non-fish scripts 47 | builtins.attrNames # Just get the names of the files 48 | (map (x: "bin/${x}")) # Map names to their actual paths relative to repo's root 49 | ]); 50 | }; 51 | settings.formatter.just = { 52 | command = pkgs.lib.getExe pkgs.just; 53 | options = ["--fmt" "--unstable" "-f"]; 54 | includes = ["Justfile"]; 55 | }; 56 | }; 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /flake/hosts.nix: -------------------------------------------------------------------------------- 1 | # This part is responsible for defining all host configurations (NixOS & nix-darwin) & deploy-rs 2 | # options for the NixOS hosts. 3 | {inputs, ...}: let 4 | inherit (inputs) nixpkgs darwin home-manager; 5 | inherit (nixpkgs.lib) mkMerge; 6 | 7 | mkDarwinSystem = name: system: let 8 | systemArgs = isCi: { 9 | inherit system; 10 | modules = [ 11 | home-manager.darwinModules.default 12 | ../modules/darwin 13 | ../hosts/${name} 14 | {lib.env.isCi = isCi;} 15 | ]; 16 | specialArgs = {inherit inputs;}; 17 | }; 18 | in { 19 | # For each Darwin host, create two darwinSystems: 20 | # - one that targets the actual system, and 21 | darwinConfigurations.${name} = darwin.lib.darwinSystem (systemArgs false); 22 | # - one that is meant for CI, so that options defined with pkgs.requireFile can be skipped 23 | darwinConfigurations."${name}-ci" = darwin.lib.darwinSystem (systemArgs true); 24 | }; 25 | in { 26 | imports = let 27 | inherit (inputs.nixpkgs.lib) mkOption types; 28 | in [ 29 | {options.flake.darwinConfigurations = mkOption {type = types.lazyAttrsOf types.raw;};} 30 | ]; 31 | 32 | flake = mkMerge [ 33 | (mkDarwinSystem "Venusaur" "aarch64-darwin") 34 | (mkDarwinSystem "workmac" "aarch64-darwin") 35 | { 36 | # Add my SSH keys to the lib set 37 | lib.mySshKeys = [ 38 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHQRxhrUwCg/DcNQfG8CwIMdJsHu0jZWI2BZV/T6ka5N" 39 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBsq7jgT0egpEZ4QpgaFHRRxrwk7vzWVvZE0w7Bhk9hK" 40 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGTgmWqiXS1b+l8KhvdrjZtbXXCh5UuBnbnase5601p2" 41 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG13aSroi6VPpZII3u+0XkJyfE7ldbC6ovvMr3Fl6tMn" 42 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID1YIyqTUGvH71i6MWCsYPVoijYLZWfapmuMSR4aGAh9" 43 | ]; 44 | } 45 | ]; 46 | } 47 | -------------------------------------------------------------------------------- /flake/pkgs.nix: -------------------------------------------------------------------------------- 1 | {inputs, ...}: { 2 | perSystem = { 3 | pkgs, 4 | system, 5 | ... 6 | }: { 7 | _module.args.pkgs = import inputs.nixpkgs { 8 | inherit system; 9 | # The pkgs set used to instantiate the berkeley-mono package must have allowUnfree set 10 | # so others can use it. 11 | config.allowUnfree = true; 12 | }; 13 | 14 | packages.berkeley-mono = let 15 | inherit (pkgs) lib; 16 | pname = "berkeley-mono"; 17 | in 18 | pkgs.stdenvNoCC.mkDerivation { 19 | inherit pname; 20 | version = "2.000"; 21 | 22 | src = pkgs.requireFile rec { 23 | name = "${pname}-typeface.zip"; 24 | sha256 = "0iakm2ga73k1ia81zg20ws1dk2ssmdb257aqx7nbq0pkv1nxdr1c"; 25 | 26 | message = '' 27 | Log in to https://usgraphics.com/auth/login/, and 28 | download the Berkeley Mono typeface with: 29 | - TrueType format 30 | - Normal width 31 | - All widths 32 | - All slants 33 | - Glyph alternatives: dotted zero, standard seven 34 | - No contextual alternatives 35 | 36 | Zip up all .ttf files and add the zip to the Nix store with: 37 | $ nix store add-file /path/to/${name} 38 | ''; 39 | }; 40 | sourceRoot = "."; 41 | 42 | nativeBuildInputs = with pkgs; [unzip parallel]; 43 | 44 | dontConfigure = true; 45 | dontBuild = true; 46 | 47 | installPhase = '' 48 | runHook preInstall 49 | 50 | installDir=$out/share/fonts/truetype 51 | mkdir -p $installDir 52 | find . -name "*.ttf" -exec cp -a {} $installDir \; 53 | 54 | runHook postInstall 55 | ''; 56 | 57 | meta = { 58 | homepage = "https://usgraphics.com/products/berkeley-mono"; 59 | license = lib.licenses.unfree; 60 | maintainers = [lib.maintainers.i077]; 61 | platforms = lib.platforms.all; 62 | }; 63 | }; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /flake/rescue.nix: -------------------------------------------------------------------------------- 1 | # This part defines a NixOS rescue/installation ISO that can be used to bootstrap or repair systems. 2 | { 3 | config, 4 | inputs, 5 | ... 6 | }: let 7 | inherit (inputs) nixpkgs; 8 | inherit (nixpkgs.lib) mkIf; 9 | 10 | flakeConfig = config; 11 | 12 | isLinux = system: builtins.elem system nixpkgs.lib.systems.doubles.linux; 13 | 14 | # Define the NixOS configuration for the rescue ISO 15 | rescueConfig = system: 16 | nixpkgs.lib.nixosSystem { 17 | inherit system; 18 | modules = [ 19 | ({ 20 | config, 21 | lib, 22 | modulesPath, 23 | pkgs, 24 | ... 25 | }: { 26 | imports = [ 27 | # Build on top of the GNOME installer ISO 28 | "${modulesPath}/installer/cd-dvd/installation-cd-graphical-gnome.nix" 29 | "${modulesPath}/installer/cd-dvd/channel.nix" 30 | ]; 31 | 32 | isoImage.isoBaseName = lib.mkForce "nixos-rescue"; 33 | 34 | system.stateVersion = "20.09"; 35 | time.timeZone = "America/New_York"; 36 | 37 | # Use latest kernel 38 | boot.kernelPackages = pkgs.linuxPackages_latest; 39 | 40 | boot.supportedFilesystems = lib.mkForce [ 41 | "btrfs" 42 | "cifs" 43 | "exfat" 44 | "ext2" 45 | "ext4" 46 | "f2fs" 47 | "jfs" 48 | "ntfs" 49 | "reiserfs" 50 | "vfat" 51 | "xfs" 52 | # "zfs" Commented out since zfs is broken on latest kernel 53 | ]; 54 | networking.hostId = null; 55 | 56 | # Support building for other linux systems supported by this flake 57 | boot.binfmt.emulatedSystems = 58 | builtins.filter 59 | (supportedSystem: supportedSystem != system && isLinux supportedSystem) 60 | flakeConfig.systems; 61 | 62 | networking = { 63 | hostName = "rescue"; 64 | wireless = { 65 | enable = true; 66 | userControlled.enable = true; 67 | }; 68 | }; 69 | 70 | # Add additional rescue tools 71 | environment.systemPackages = with pkgs; [ 72 | coreutils 73 | efitools 74 | fd 75 | file 76 | inetutils 77 | jq 78 | lsof 79 | neovim 80 | ripgrep 81 | wget 82 | ]; 83 | environment.variables.EDITOR = "nvim"; 84 | 85 | # Use fish shell 86 | programs.fish.enable = true; 87 | environment.shells = [pkgs.fish]; 88 | users.defaultUserShell = pkgs.fish; 89 | 90 | # Define root user 91 | users.users.root = { 92 | password = "rescue"; 93 | openssh.authorizedKeys.keys = inputs.self.lib.mySshKeys; 94 | }; 95 | users.mutableUsers = false; 96 | }) 97 | ]; 98 | }; 99 | in { 100 | flake.nixosConfigurations.rescue = rescueConfig "x86_64-linux"; 101 | 102 | perSystem = {system, ...}: 103 | mkIf (isLinux system) { 104 | packages.rescueImage = (rescueConfig system).config.system.build.isoImage; 105 | }; 106 | } 107 | -------------------------------------------------------------------------------- /hosts/venusaur/brew.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | homebrew = { 3 | casks = [ 4 | "altserver" 5 | "copilot" 6 | "discord" 7 | "fmail2" 8 | "minecraft" 9 | "mullvadvpn" 10 | "onedrive" 11 | "steam" 12 | "skim" 13 | "sony-ps-remote-play" 14 | "swiftformat-for-xcode" 15 | "tailscale" 16 | "the-unarchiver" 17 | ]; 18 | 19 | masApps = { 20 | "Windows App" = 1295203466; 21 | "Reeder." = 6475002485; 22 | "Things 3" = 904280696; 23 | WhatsApp = 310633997; 24 | Xcode = 497799835; 25 | }; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /hosts/venusaur/default.nix: -------------------------------------------------------------------------------- 1 | {...}: let 2 | hostName = "Venusaur"; 3 | in { 4 | imports = [./brew.nix ./home.nix ../../modules/darwin/fish.nix ../../modules/darwin/1password.nix]; 5 | 6 | # User config 7 | users.users.imran.home = "/Users/imran"; 8 | system.primaryUser = "imran"; 9 | 10 | networking = { 11 | computerName = hostName; 12 | inherit hostName; 13 | }; 14 | 15 | # Enable touch ID for sudo 16 | security.pam.services.sudo_local.touchIdAuth = true; 17 | 18 | # Enable x86 builds 19 | nix.extraOptions = '' 20 | extra-platforms = x86_64-darwin aarch64-darwin 21 | ''; 22 | } 23 | -------------------------------------------------------------------------------- /hosts/venusaur/home.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | home-manager.users.imran = { 3 | imports = [ 4 | ../../modules/home 5 | ../../modules/home/fish.nix 6 | ../../modules/home/direnv.nix 7 | ../../modules/home/fzf.nix 8 | ../../modules/home/k8s.nix 9 | ../../modules/home/git.nix 10 | ../../modules/home/neovim 11 | ../../modules/home/1password.nix 12 | ]; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /hosts/workmac/brew.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | homebrew = { 3 | casks = [ 4 | "apache-directory-studio" 5 | "navicat-premium" 6 | "slack" 7 | ]; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /hosts/workmac/default.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | imports = [ 3 | ./brew.nix 4 | ./home.nix 5 | ./ssl.nix 6 | ../../modules/darwin/fish.nix 7 | ../../modules/darwin/1password.nix 8 | ]; 9 | 10 | # User config 11 | users.users.hossaini.home = "/Users/hossaini"; 12 | system.primaryUser = "hossaini"; 13 | 14 | # Enable x86 builds 15 | nix.extraOptions = '' 16 | extra-platforms = x86_64-darwin aarch64-darwin 17 | ''; 18 | } 19 | -------------------------------------------------------------------------------- /hosts/workmac/home.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: { 6 | home-manager.users.hossaini = { 7 | imports = [ 8 | ../../modules/home 9 | ../../modules/home/fish.nix 10 | ../../modules/home/direnv.nix 11 | ../../modules/home/fzf.nix 12 | ../../modules/home/git.nix 13 | ../../modules/home/k8s.nix 14 | ../../modules/home/neovim 15 | ../../modules/home/1password.nix 16 | ]; 17 | 18 | programs.git = { 19 | lfs.enable = true; 20 | userEmail = lib.mkForce ("imran.hossain" + "@" + "saic.com"); 21 | }; 22 | 23 | home.packages = with pkgs; [awscli2 terraform terragrunt poetry ssm-session-manager-plugin]; 24 | 25 | # Add completions for docker 26 | xdg.configFile."fish/completions/docker.fish".source = "${pkgs.docker.src}/contrib/completion/fish/docker.fish"; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /hosts/workmac/ssl.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: { 7 | # Add Private CA 8 | security.pki.certificateFiles = 9 | lib.optional (!config.lib.env.isCi) 10 | (pkgs.requireFile { 11 | name = "private-ca.cer"; 12 | hash = "sha256-VNdLlz0lEC7cQhEr+DXAHqkWWpPKtZDUjgI8vSSSmPU="; 13 | 14 | message = '' 15 | Export the private root CA's certificate from Keychain Access, and add with 16 | $ nix store add-file /path/to/private-ca.cer 17 | ''; 18 | }); 19 | 20 | # Configure various CLIs to use the built CA bundle 21 | home-manager.users.hossaini.home.sessionVariables = builtins.listToAttrs (map 22 | (var: { 23 | name = var; 24 | value = "/etc/ssl/certs/ca-certificates.crt"; 25 | }) 26 | ["AWS_CA_BUNDLE" "CURL_CA_BUNDLE" "REQUESTS_CA_BUNDLE"]); 27 | } 28 | -------------------------------------------------------------------------------- /modules/broken-overlay-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "neovide": { 3 | "x86_64-darwin": { 4 | "nixpkgs_rev": "844ffa82bbe2a2779c86ab3a72ff1b4176cec467", 5 | "nixpkgs_hash": "sha256-D21ctOBjr2Y3vOFRXKRoFr6uNBvE8q5jC4RrMxRZXTM=", 6 | "pname": "neovide-0.10.4", 7 | "hydra_build": 1798360, 8 | "build_date": "2023-07-24" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /modules/broken-overlay.nix: -------------------------------------------------------------------------------- 1 | # This module loads in an overlay that replaces currently broken packages with ones from 2 | # a previous revision of nixpkgs that is known to build successfully (according to hydra.nixos.org). 3 | # It's useful when a minor package (e.g. a small CLI tool) in the system's closure is broken and 4 | # prevents the entire profile from building. 5 | # 6 | # The data containing the actual broken packages are contained in ./broken-overlay-data.json and is 7 | # updated using the nix-lastgoodrev script in the bin directory. 8 | # It comes in the following form: 9 | # { 10 | # "attr": { 11 | # "system-double": { 12 | # "nixpkgs_rev": "", 13 | # "nixpkgs_hash": "" 14 | # "pname": "-", 15 | # "hydra_build": , 16 | # "build_date": "YYYY-MM-DD" 17 | # } 18 | # } 19 | # } 20 | # I could have the data be in the form "system-double" -> "attr" to make it easier to work with, 21 | # but it would be harder to tell at a glance which packages are being replaced across all systems. 22 | # Perhaps this isn't as useful, so maybe I'll change this in the future. 23 | { 24 | config, 25 | lib, 26 | pkgs, 27 | ... 28 | }: let 29 | currentSystem = config.nixpkgs.system; 30 | 31 | overlayData = builtins.fromJSON (builtins.readFile ./broken-overlay-data.json); 32 | # Filter overlay data by current system, so this will look like 33 | # { "attr": { "nixpkgs_rev": ..., }, ... } 34 | overlayDataForSystem = 35 | builtins.mapAttrs (n: v: v.${currentSystem}) 36 | (lib.filterAttrs (n: builtins.hasAttr currentSystem) overlayData); 37 | 38 | # Function to import nixpkgs at a specified commit (I know, IFD...) 39 | nixpkgsRev = rev: hash: 40 | import (pkgs.fetchFromGitHub { 41 | name = "nixpkgs-src-" + rev; 42 | owner = "NixOS"; 43 | repo = "nixpkgs"; 44 | inherit rev hash; 45 | }) {inherit (config.nixpkgs) config system;}; 46 | 47 | # Function to check if a package is still broken in this flake's nixpkgs input 48 | # Note: This is a really ugly hack using IFD and an unsafe function, 49 | # but ideally there isn't more than one or two broken packages at a time so does it really matter? 50 | packageStillBroken = prevpkgs: attr: let 51 | inherit (prevpkgs.${attr}) drvPath; 52 | in 53 | import (pkgs.runCommand "try-build-${attr}" {buildInputs = [pkgs.nix];} '' 54 | set +e 55 | nix build ${builtins.unsafeDiscardStringContext drvPath}^out 56 | if [ $? -eq 0 ]; then 57 | echo false > $out 58 | else 59 | echo true > $out 60 | fi 61 | ''); 62 | in { 63 | nixpkgs.overlays = [ 64 | (final: prev: 65 | # Map each package's last good revision data to an actual derivation from that revision 66 | builtins.mapAttrs 67 | (attr: data: 68 | if packageStillBroken prev attr 69 | then (nixpkgsRev data.nixpkgs_rev data.nixpkgs_hash).${attr} 70 | else throw "${attr} builds successfully, but is still in broken overlay") 71 | overlayDataForSystem) 72 | ]; 73 | } 74 | -------------------------------------------------------------------------------- /modules/darwin/1password.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | homebrew = { 3 | # The GUI app insists on being in /Applications, and the CLI insists that the integration requires it to be in 4 | # /usr/local/bin, so we just install both via Homebrew 5 | casks = ["1password" "1password-cli"]; 6 | }; 7 | 8 | # Source fish completions for 1password cli 9 | programs.fish.interactiveShellInit = '' 10 | command -q op; and op completion fish | source 11 | ''; 12 | } 13 | -------------------------------------------------------------------------------- /modules/darwin/brew.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | homebrew = { 3 | # Enable homebrew & global bundle management 4 | enable = true; 5 | global = { 6 | brewfile = true; 7 | autoUpdate = false; 8 | }; 9 | 10 | onActivation = { 11 | cleanup = "uninstall"; 12 | autoUpdate = false; 13 | }; 14 | 15 | taps = [ 16 | "homebrew/bundle" 17 | "homebrew/cask-versions" 18 | ]; 19 | 20 | # Some default packages 21 | casks = [ 22 | "betterdisplay" 23 | "firefox" 24 | "ghostty" 25 | "hyperkey" 26 | "jetbrains-toolbox" 27 | "jordanbaird-ice" 28 | "kopiaui" 29 | "mac-mouse-fix" 30 | "mediamate" 31 | "plexamp" 32 | "pocket-casts" 33 | "raycast" 34 | "stats" 35 | "visual-studio-code" 36 | "vivaldi" 37 | "zed" 38 | ]; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /modules/darwin/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | inputs, 4 | lib, 5 | pkgs, 6 | ... 7 | }: let 8 | selfPkgs = inputs.self.packages.${config.nixpkgs.system}; 9 | in { 10 | imports = [./brew.nix ./wm.nix ../broken-overlay.nix ../nix-settings.nix]; 11 | 12 | system.stateVersion = 4; 13 | 14 | # macOS Sequoia requires using a new set of user/group IDs for nixbld users 15 | ids.gids.nixbld = 350; 16 | 17 | nix = { 18 | # Enable flakes 19 | extraOptions = '' 20 | build-users-group = nixbld 21 | max-jobs = auto 22 | ''; 23 | 24 | settings = { 25 | # Add administrators to trusted users 26 | trusted-users = ["@admin"]; 27 | }; 28 | 29 | # Add inputs to registry & nix path 30 | nixPath = lib.mkForce [ 31 | "nixpkgs=${inputs.nixpkgs}" 32 | "home-manager=${inputs.home-manager}" 33 | "darwin=${inputs.darwin}" 34 | ]; 35 | registry = let 36 | # Helper to copy a list of given flake inputs to the registry 37 | copyFlakeInputs = inputList: 38 | lib.genAttrs inputList (name: { 39 | from = { 40 | id = name; 41 | type = "indirect"; 42 | }; 43 | flake = inputs.${name}; 44 | }); 45 | in 46 | copyFlakeInputs ["self" "nixpkgs" "darwin" "home-manager"]; 47 | }; 48 | 49 | nixpkgs.config.allowUnfree = true; 50 | 51 | programs.zsh.enable = true; 52 | 53 | environment.systemPackages = with pkgs; [coreutils obsidian]; 54 | 55 | # Fonts 56 | fonts.packages = 57 | if config.lib.env.isCi 58 | then [] 59 | else [selfPkgs.berkeley-mono]; 60 | 61 | # Enable home-manager 62 | home-manager = { 63 | extraSpecialArgs = {inherit inputs;}; 64 | useGlobalPkgs = true; 65 | useUserPackages = true; 66 | backupFileExtension = "bak"; 67 | sharedModules = [inputs.nixvim.homeManagerModules.nixvim]; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /modules/darwin/fish.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: let 2 | inherit (lib) mkAfter mkBefore mkMerge; 3 | in { 4 | programs.fish.enable = true; 5 | # Workaround for $PATH ordering in fish 6 | # /usr/libexec/path_helper prepends system paths before anything else 7 | environment.etc."fish/nixos-env-preinit.fish".text = mkMerge [ 8 | (mkBefore '' 9 | set -l oldPath $PATH 10 | '') 11 | (mkAfter '' 12 | for elt in $PATH 13 | if not contains -- $elt $oldPath /usr/local/bin /usr/bin /bin /usr/sbin /sbin 14 | set -ag fish_user_paths $elt 15 | end 16 | end 17 | set -el oldPath 18 | '') 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /modules/darwin/wm.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | system.defaults.NSGlobalDomain.NSWindowShouldDragOnGesture = true; 3 | } 4 | -------------------------------------------------------------------------------- /modules/home/1password.nix: -------------------------------------------------------------------------------- 1 | {config, ...}: let 2 | home = config.home.homeDirectory; 3 | darwinSockPath = "${home}/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"; 4 | in { 5 | home.sessionVariables.SSH_AUTH_SOCK = darwinSockPath; 6 | programs.ssh = { 7 | enable = true; 8 | extraConfig = '' 9 | IdentityAgent "${darwinSockPath}" 10 | ''; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /modules/home/default.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | programs.home-manager.enable = true; 3 | 4 | imports = [./ghostty.nix ./xdg.nix ./wm.nix ./ptpython.nix]; 5 | 6 | home = { 7 | stateVersion = "20.09"; 8 | 9 | sessionVariables = { 10 | EDITOR = "nvim"; 11 | HOMEBREW_NO_AUTO_UPDATE = 1; 12 | 13 | # Disable Python virtualenv prompt updates, since tide takes care of that 14 | VIRTUAL_ENV_DISABLE_PROMPT = 1; 15 | }; 16 | 17 | packages = with pkgs; [ 18 | colima 19 | curlie 20 | dasel 21 | docker 22 | libqalculate 23 | just 24 | jq 25 | mosh 26 | nil 27 | nixd 28 | nix-index 29 | nix-output-monitor 30 | ripgrep 31 | unixtools.watch 32 | ]; 33 | }; 34 | 35 | programs.bat = { 36 | enable = true; 37 | config = { 38 | pager = "less -FR"; 39 | theme = "gruvbox-dark"; 40 | }; 41 | }; 42 | 43 | programs.eza.enable = true; 44 | 45 | programs.broot.enable = true; 46 | 47 | # Include other SSH config files that I don't want to check in here 48 | programs.ssh = { 49 | forwardAgent = true; 50 | includes = ["~/.ssh/config.d/*"]; 51 | }; 52 | 53 | programs.go.enable = true; 54 | 55 | xdg.enable = true; 56 | } 57 | -------------------------------------------------------------------------------- /modules/home/direnv.nix: -------------------------------------------------------------------------------- 1 | {config, ...}: { 2 | programs.direnv = { 3 | enable = true; 4 | nix-direnv = {enable = true;}; 5 | 6 | stdlib = '' 7 | # Store .direnv in cache instead of project dir 8 | declare -A direnv_layout_dirs 9 | direnv_layout_dir() { 10 | echo "''${direnv_layout_dirs[$PWD]:=$( 11 | echo -n ${config.xdg.cacheHome}/direnv/layouts/ 12 | echo -n "$PWD" | sha256sum | cut -d ' ' -f 1 13 | )}" 14 | } 15 | ''; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /modules/home/fish.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | inputs, 4 | lib, 5 | pkgs, 6 | ... 7 | }: { 8 | programs.fish = { 9 | enable = true; 10 | 11 | shellAbbrs = { 12 | g = "git"; 13 | 14 | l = "eza"; 15 | ls = "eza"; 16 | la = "eza -a"; 17 | ll = "eza -l"; 18 | lL = "eza -algiSH --git"; 19 | lt = "eza -lT"; 20 | 21 | m = "make"; 22 | py = "ptpython"; 23 | t = "tmux"; 24 | ta = "tmux a -t"; 25 | 26 | nv = "neovide"; 27 | v = "nvim"; 28 | 29 | # Apparently I mix these up all the time. 30 | ":q" = "exit"; 31 | }; 32 | 33 | functions = { 34 | lwhich = { 35 | description = "Show the full path of a command, resolving links along the way"; 36 | body = '' 37 | if test (count $argv) -ne 1 38 | echo "lwhich: Expected exactly one argument." 39 | return 127 40 | end 41 | which $argv[1] > /dev/null; or return 1 42 | readlink -f (which $argv[1]) 43 | ''; 44 | }; 45 | 46 | mkcd = { 47 | description = "Make and enter a directory"; 48 | body = '' 49 | if test (count $argv) -ne 1 50 | echo "mkcd: Expected exactly one argument." 51 | return 127 52 | end 53 | mkdir $argv[1] && cd $argv[1] 54 | ''; 55 | }; 56 | 57 | # Greeting taken from bobthefish 58 | fish_greeting = { 59 | body = '' 60 | set_color $fish_color_autosuggestion 61 | uname -nmsr 62 | command -s uptime >/dev/null 63 | and command uptime 64 | set_color normal 65 | ''; 66 | }; 67 | }; 68 | 69 | plugins = [ 70 | { 71 | name = "z"; 72 | src = pkgs.fishPlugins.z.src; 73 | } 74 | { 75 | name = "tide"; 76 | src = pkgs.fishPlugins.tide.src; 77 | } 78 | ]; 79 | 80 | shellInit = lib.optionalString pkgs.stdenvNoCC.isDarwin '' 81 | test -d /opt/homebrew/bin; and eval (/opt/homebrew/bin/brew shellenv) 82 | ''; 83 | 84 | interactiveShellInit = '' 85 | test -d ~/system/bin; and set -p PATH ~/system/bin 86 | # Add 1Password shell plugins support 87 | test -f ${config.xdg.configHome}/op/plugins.sh; and source ${config.xdg.configHome}/op/plugins.sh 88 | 89 | # Vi keybindings 90 | set -g fish_key_bindings fish_vi_key_bindings 91 | set -g fish_cursor_default block 92 | set -g fish_cursor_insert line 93 | 94 | # Custom bindings 95 | bind -M insert \cs accept-autosuggestion repaint 96 | bind -M insert \cc kill-whole-line repaint 97 | 98 | # Add proper completion for aws cli: github.com/aws/aws-cli/issues/1079 99 | test -x (which aws_completer); and complete --command aws --no-files --arguments '(begin; set --local --export COMP_SHELL fish; set --local --export COMP_LINE (commandline); aws_completer | sed \'s/ $//\'; end)' 100 | ''; 101 | }; 102 | } 103 | -------------------------------------------------------------------------------- /modules/home/fzf.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | programs.fzf = { 3 | enable = true; 4 | 5 | fileWidgetOptions = [ 6 | # Preview the contents of the selected file 7 | "--preview 'bat --color=always --plain {}'" 8 | ]; 9 | 10 | changeDirWidgetOptions = [ 11 | # Preview the contents of the selected directory 12 | "--preview 'exa -l --tree --level=2 --color=always {}'" 13 | ]; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /modules/home/ghostty.nix: -------------------------------------------------------------------------------- 1 | {lib, ...}: let 2 | mkCfg = lib.generators.toKeyValue {listsAsDuplicateKeys = true;}; 3 | in { 4 | xdg.configFile."ghostty/config".text = mkCfg { 5 | font-family = "Berkeley Mono"; 6 | font-size = 14; 7 | font-thicken = true; 8 | 9 | theme = "GruvboxDark"; 10 | mouse-hide-while-typing = true; 11 | 12 | macos-option-as-alt = "left"; 13 | 14 | cursor-style-blink = false; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /modules/home/git.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | ... 5 | }: { 6 | programs.git = { 7 | enable = true; 8 | 9 | userName = "Imran Hossain"; 10 | userEmail = "hi" + "@" + "imranh.org"; 11 | 12 | aliases = let 13 | # A nicer-looking one-line log format. Thanks @malob! 14 | logformat = "--format=format:'%C(blue)%h%C(reset) - %C(green)(%ar)%C(reset) %s %C(italic)- %an%C(reset)%C(magenta bold)%d%C(reset)'"; 15 | # Small function to get the default branch in origin 16 | defaultBranch = remote: ''!b() { git remote show ${remote} | grep "HEAD branch" | sed 's/.*: //' ;};''; 17 | in { 18 | # Staging/diffs 19 | a = "add"; 20 | d = "diff"; 21 | dc = "diff --cached"; 22 | st = "status"; 23 | 24 | # Commit 25 | c = "commit --verbose"; 26 | ca = "commit --amend --verbose"; 27 | call = "commit --all"; 28 | cm = "commit --message"; 29 | credo = "commit --amend --no-edit"; 30 | credoall = "commit --amend --no-edit --all"; 31 | 32 | # Checkout 33 | co = "checkout"; 34 | cob = "checkout -b"; 35 | com = "${defaultBranch "origin"} git checkout $(b)"; 36 | 37 | # Log 38 | l = "log"; 39 | lg = "log --oneline --graph --decorate ${logformat}"; 40 | lga = "log --oneline --graph --decorate --all ${logformat}"; 41 | 42 | # Push/pull/fetch 43 | f = "fetch"; 44 | pd = "pull"; 45 | pdu = "pull upstream"; 46 | pdum = "${defaultBranch "upstream"} git pull upstream $(b)"; 47 | pu = "push"; 48 | puf = "push --force-with-lease"; # as in "poof" go your old commits! 49 | puu = "!head() { git rev-parse --abbrev-ref HEAD ;}; git push --set-upstream origin $(head)"; 50 | 51 | # Rebase 52 | rb = "rebase"; 53 | rba = "rebase --abort"; 54 | rbc = "rebase --continue"; 55 | rbi = "rebase --interactive"; 56 | 57 | # Stash 58 | sh = "stash"; 59 | sha = "stash apply"; 60 | shd = "stash drop"; 61 | shl = "stash list"; 62 | shp = "stash pop"; 63 | shs = "stash show --patch"; 64 | 65 | whohas = "branch -a --contains"; 66 | ignore = "!gi() { curl -sL https://www.toptal.com/developers/gitignore/api/$@ ;}; gi"; 67 | }; 68 | 69 | ignores = [".DS_Store"]; 70 | 71 | extraConfig = { 72 | pull.rebase = false; 73 | init.defaultBranch = "main"; 74 | 75 | # Sign with SSH key 76 | user.signingkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHQRxhrUwCg/DcNQfG8CwIMdJsHu0jZWI2BZV/T6ka5N"; 77 | gpg.format = "ssh"; 78 | gpg.ssh.allowedSignersFile = "${config.xdg.configHome}/git/allowed-signers"; 79 | commit.gpgsign = true; 80 | }; 81 | 82 | # Difftastic, a syntactic diff tool 83 | difftastic.enable = true; 84 | }; 85 | 86 | # GitHub CLI 87 | programs.gh = { 88 | enable = true; 89 | 90 | settings = { 91 | aliases = { 92 | co = "pr checkout"; 93 | cl = "repo clone"; 94 | }; 95 | git_protocol = "ssh"; 96 | }; 97 | }; 98 | 99 | # Add git extensions & utilities 100 | home.packages = with pkgs; [lazygit]; 101 | } 102 | -------------------------------------------------------------------------------- /modules/home/k8s.nix: -------------------------------------------------------------------------------- 1 | # Kubneretes-related tooling 2 | { 3 | inputs, 4 | pkgs, 5 | ... 6 | }: let 7 | kubesess = inputs.nix-hot-pot.packages.${pkgs.system}.kubesess.overrideAttrs (old: { 8 | postInstall = '' 9 | mkdir -p $out/share/fish/vendor_completions.d $out/share/fish/vendor_functions.d 10 | cp scripts/fish/completions/*.fish $out/share/fish/vendor_completions.d 11 | cp scripts/fish/functions/*.fish $out/share/fish/vendor_functions.d 12 | ''; 13 | }); 14 | in { 15 | home.packages = with pkgs; [kubectl kubectl-node-shell kubernetes-helm kubesess fluxcd stern vcluster]; 16 | 17 | # Add kubectl & friends as aliases 18 | programs.fish = { 19 | shellAbbrs = { 20 | k = "kubectl"; 21 | }; 22 | }; 23 | 24 | programs.k9s.enable = true; 25 | } 26 | -------------------------------------------------------------------------------- /modules/home/neovim/completion.nix: -------------------------------------------------------------------------------- 1 | {...}: { 2 | programs.nixvim = { 3 | # Use icons insteead of text in completion menu when showing kind 4 | plugins.lspkind = { 5 | enable = true; 6 | preset = "codicons"; 7 | mode = "symbol"; 8 | }; 9 | 10 | plugins.cmp = { 11 | enable = true; 12 | 13 | settings.sources = [ 14 | { 15 | name = "nvim_lsp"; 16 | groupIndex = 1; 17 | } 18 | { 19 | name = "path"; 20 | groupIndex = 1; 21 | } 22 | { 23 | name = "buffer"; 24 | groupIndex = 2; 25 | } 26 | ]; 27 | 28 | settings.view.entries = { 29 | name = "custom"; 30 | selection_order = "near_cursor"; 31 | }; 32 | 33 | settings.mapping = { 34 | "" = "cmp.mapping.complete()"; 35 | "" = "cmp.mapping.abort()"; 36 | "" = "cmp.mapping.confirm({ select = false })"; # Accept only explicitly selected items 37 | "" = "cmp.mapping.select_next_item()"; 38 | "" = "cmp.mapping.select_prev_item()"; 39 | "" = "cmp.mapping.scroll_docs(-4)"; 40 | "" = "cmp.mapping.scroll_docs(4)"; 41 | }; 42 | }; 43 | 44 | # Include other sources not in main setup table & setup other filetypes 45 | plugins.cmp-git.enable = true; 46 | plugins.cmp-cmdline.enable = true; 47 | plugins.cmp-fish.enable = true; 48 | 49 | extraConfigLua = '' 50 | local cmp = require('cmp') 51 | -- Set up git commit completions 52 | cmp.setup.filetype("gitcommit", { 53 | sources = cmp.config.sources({ { name = "cmp_git" } }, { { name = "buffer" }, { name = "path" } }), 54 | }) 55 | 56 | cmp.setup.filetype("fish", { 57 | sources = cmp.config.sources({ { name = "fish" } }, { { name = "buffer" }, { name = "path" } }), 58 | }) 59 | 60 | -- Use buffer source for `/` 61 | cmp.setup.cmdline("/", { mapping = cmp.mapping.preset.cmdline(), sources = { { name = "buffer" } } }) 62 | 63 | -- Use cmdline & path source for ':' 64 | cmp.setup.cmdline(":", { 65 | mapping = cmp.mapping.preset.cmdline(), 66 | sources = cmp.config.sources({ { name = "path" } }, { { name = "cmdline" } }), 67 | }) 68 | ''; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /modules/home/neovim/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | pkgs, 4 | ... 5 | }: { 6 | imports = [./plugins.nix ./completion.nix ./lsp.nix]; 7 | 8 | # Add configuration for Neovide 9 | xdg.configFile."neovide/config.toml".source = (pkgs.formats.toml {}).generate "neovide-config.toml" { 10 | multigrid = true; 11 | }; 12 | 13 | lib.nvim = { 14 | # Helper function to create leader mappings under a prefix 15 | mkLeaderMappings = prefix: 16 | lib.mapAttrsToList (key: action: { 17 | key = "${prefix}${key}"; 18 | mode = ["n"]; 19 | options.silent = true; 20 | inherit action; 21 | }); 22 | }; 23 | 24 | programs.nixvim = { 25 | enable = true; 26 | 27 | viAlias = true; 28 | vimAlias = true; 29 | 30 | # Set colorscheme 31 | extraConfigLuaPre = '' 32 | require('gruvbox').setup({ 33 | italic = { 34 | strings = false, 35 | }, 36 | }) 37 | ''; 38 | 39 | colorschemes.gruvbox.enable = true; 40 | 41 | opts = { 42 | # Mouse interaction 43 | mouse = "a"; 44 | 45 | # Indentation -- 4 spaces per 46 | tabstop = 4; 47 | expandtab = true; 48 | shiftwidth = 4; 49 | 50 | # Word wrap 51 | wrap = true; 52 | linebreak = true; 53 | 54 | # Show results of :substitute 55 | inccommand = "nosplit"; 56 | 57 | # Searches are case-insensitive unless capital letter is present 58 | smartcase = true; 59 | 60 | # Preserve undo history across sessions 61 | undofile = true; 62 | 63 | # Don't start with all folds closed 64 | foldlevelstart = 99; 65 | 66 | # Font for GUI editors 67 | guifont = "Berkeley Mono:10"; 68 | }; 69 | 70 | # Show cursorline, but only on current window 71 | # via https://stackoverflow.com/a/12018552 72 | autoGroups.CursorLine = {}; 73 | autoCmd = [ 74 | { 75 | group = "CursorLine"; 76 | event = ["VimEnter" "WinEnter" "BufWinEnter"]; 77 | pattern = "*"; 78 | command = "setlocal cursorline"; 79 | } 80 | { 81 | group = "CursorLine"; 82 | event = "WinLeave"; 83 | pattern = "*"; 84 | command = "setlocal nocursorline"; 85 | } 86 | ]; 87 | 88 | # Neovide options 89 | globals.neovide_cursor_trail_size = 0.3; 90 | globals.neovide_hide_mouse_when_typing = true; 91 | globals.neovide_input_macos_alt_is_meta = true; 92 | 93 | # Set leader 94 | globals.mapleader = " "; 95 | 96 | keymaps = 97 | [ 98 | { 99 | key = " "; 100 | mode = ["n" "v"]; 101 | options.silent = true; 102 | action = ""; 103 | } 104 | 105 | # Go to last buffer 106 | { 107 | key = ""; 108 | mode = "n"; 109 | action = ""; 110 | } 111 | 112 | # :write with ZW 113 | { 114 | key = "ZW"; 115 | mode = "n"; 116 | action = ":w"; 117 | } 118 | 119 | # Quickly correct typos with 120 | { 121 | key = ""; 122 | mode = "i"; 123 | action = "u[s1z=`]au"; 124 | } 125 | ] 126 | ++ 127 | # Mappings to work with the OS clipboard 128 | (map (k: { 129 | key = "g${k}"; 130 | mode = ["n" "v"]; 131 | action = "\"+${k}"; 132 | }) ["y" "p" "P"]); 133 | 134 | # Different indentation for various filetypes 135 | extraFiles = let 136 | indentTwoSpaces = '' 137 | setlocal tabstop=2 138 | setlocal shiftwidth=2 139 | ''; 140 | in { 141 | "after/ftplugin/nix.vim".text = indentTwoSpaces; 142 | "after/ftplugin/terraform.vim".text = indentTwoSpaces; 143 | "after/ftplugin/typescript.vim".text = indentTwoSpaces; 144 | }; 145 | }; 146 | } 147 | -------------------------------------------------------------------------------- /modules/home/neovim/lsp.nix: -------------------------------------------------------------------------------- 1 | {config, ...}: let 2 | inherit (config.lib.nvim) mkLeaderMappings; 3 | in { 4 | programs.nixvim = { 5 | plugins.lsp = { 6 | enable = true; 7 | 8 | servers = { 9 | # Go 10 | gopls.enable = true; 11 | 12 | # Haskell 13 | hls = { 14 | enable = true; 15 | installGhc = false; 16 | }; 17 | 18 | # JSON 19 | jsonls.enable = true; 20 | 21 | # Nix 22 | nil_ls = { 23 | enable = true; 24 | settings = { 25 | formatting.command = ["alejandra"]; 26 | }; 27 | }; 28 | 29 | # Python 30 | pylsp.enable = true; 31 | 32 | # Rust 33 | rust_analyzer = { 34 | enable = true; 35 | installRustc = false; 36 | installCargo = false; 37 | }; 38 | 39 | # Terraform 40 | terraformls.enable = true; 41 | 42 | # LaTeX 43 | texlab.enable = true; 44 | 45 | # TypeScript 46 | ts_ls.enable = true; 47 | }; 48 | }; 49 | 50 | plugins.lspsaga = { 51 | enable = true; 52 | 53 | # Don't show action icon in signcolumn 54 | lightbulb.sign = false; 55 | 56 | # Turn off breadcrumbs 57 | symbolInWinbar.enable = false; 58 | }; 59 | 60 | keymaps = 61 | [ 62 | { 63 | key = "K"; 64 | mode = ["n"]; 65 | options.silent = true; 66 | action = ":Lspsaga hover_doc"; 67 | } 68 | { 69 | key = "gd"; 70 | mode = ["n"]; 71 | options.silent = true; 72 | action = ":Lspsaga peek_definition"; 73 | } 74 | ] 75 | ++ mkLeaderMappings "l" { 76 | "r" = ":Lspsaga rename"; 77 | "o" = ":Lspsaga outline"; 78 | "f" = ":Lspsaga finder"; 79 | "a" = ":Lspsaga code_action"; 80 | "d" = ":Lspsaga show_buf_diagnostics"; 81 | "D" = ":Lspsaga show_workspace_diagnostics"; 82 | }; 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /modules/home/neovim/plugins.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | inherit (config.lib.nvim) mkLeaderMappings; 8 | in { 9 | programs.nixvim = lib.mkMerge [ 10 | { 11 | # Syntax highlighting using tree-sitter 12 | plugins.treesitter = { 13 | enable = true; 14 | folding = true; 15 | nixvimInjections = true; 16 | settings = { 17 | incremental_selection.enable = true; 18 | indent.enable = true; 19 | }; 20 | }; 21 | 22 | # Show code context, e.g. what function current line is under 23 | plugins.treesitter-context = { 24 | enable = true; 25 | settings = { 26 | max_lines = 2; 27 | min_window_height = 100; 28 | }; 29 | }; 30 | 31 | # Comments 32 | plugins.comment.enable = true; 33 | 34 | # Automatic session management 35 | plugins.auto-session = { 36 | enable = true; 37 | settings.cwd_change_handling = false; 38 | }; 39 | # Map to :Telescope for sessions 40 | keymaps = [ 41 | { 42 | key = "fs"; 43 | options.silent = true; 44 | action.__raw = "require('auto-session.session-lens').search_session"; 45 | } 46 | ]; 47 | 48 | # f/t but with two characters 49 | plugins.leap.enable = true; 50 | 51 | # Surrounds 52 | plugins.vim-surround.enable = true; 53 | 54 | # Better folds 55 | plugins.nvim-ufo.enable = true; 56 | 57 | # Keymap hints 58 | plugins.which-key.enable = true; 59 | 60 | # Autopairs 61 | plugins.nvim-autopairs = { 62 | enable = true; 63 | settings = { 64 | check_ts = true; 65 | map_bs = true; 66 | map_c_w = true; 67 | }; 68 | }; 69 | 70 | # Focused writing 71 | plugins.goyo = { 72 | enable = true; 73 | settings.width = 120; 74 | }; 75 | 76 | # Statusline 77 | plugins.lualine = { 78 | enable = true; 79 | 80 | settings = { 81 | options = { 82 | componentSeparators = { 83 | left = ""; 84 | right = ""; 85 | }; 86 | sectionSeparators = { 87 | left = ""; 88 | right = ""; 89 | }; 90 | iconsEnabled = false; 91 | }; 92 | 93 | sections = { 94 | # Only show the first char of the current mode 95 | lualine_a = [ 96 | { 97 | __unkeyed-1 = "mode"; 98 | fmt = "function(str) return str:sub(1,1) end"; 99 | } 100 | "selectioncount" 101 | ]; 102 | lualine_b = ["branch" "diff"]; 103 | lualine_c = [ 104 | { 105 | __unkeyed-1 = "filename"; 106 | path = 1; 107 | } 108 | ]; 109 | 110 | lualine_x = ["diagnostics" "filetype"]; 111 | lualine_y = ["progress" "searchcount"]; 112 | lualine_z = ["location"]; 113 | }; 114 | 115 | tabline = { 116 | lualine_a = [ 117 | { 118 | __unkeyed-1 = "windows"; 119 | windows_color = { 120 | active = "lualine_a_normal"; 121 | inactive = "lualine_b_inactive"; 122 | }; 123 | } 124 | ]; 125 | lualine_z = [ 126 | { 127 | __unkeyed-1 = "tabs"; 128 | tabs_color = { 129 | active = "lualine_a_normal"; 130 | inactive = "lualine_b_inactive"; 131 | }; 132 | } 133 | ]; 134 | }; 135 | }; 136 | }; 137 | 138 | # Extra plugins that aren't yet in nixvim 139 | extraPlugins = with pkgs.vimPlugins; [ 140 | sensible # Sensible defaults 141 | repeat # Repeatable plugin actions 142 | easy-align # Align text around symbols 143 | direnv-vim # Direnv integration 144 | ]; 145 | } 146 | 147 | # mini.nvim 148 | { 149 | plugins.mini.enable = true; 150 | plugins.mini.modules = { 151 | # Enhanced textobjects 152 | ai = {}; 153 | # Text alignment 154 | align = {}; 155 | # Window-preserving buffer deletion 156 | bufremove = {}; 157 | }; 158 | } 159 | 160 | # Telescope picker 161 | { 162 | plugins.telescope = { 163 | enable = true; 164 | keymaps = { 165 | "" = "find_files"; 166 | "ff" = "find_files"; 167 | "fg" = "live_grep"; 168 | "fb" = "buffers"; 169 | "fh" = "help_tags"; 170 | "f:" = "commands"; 171 | "fq" = "quickfix"; 172 | "fr" = "oldfiles"; 173 | "fd" = "diagnostics"; 174 | }; 175 | }; 176 | 177 | plugins.web-devicons.enable = true; 178 | } 179 | 180 | # Visualize the undo tree 181 | { 182 | plugins.undotree.enable = true; 183 | keymaps = [ 184 | { 185 | key = ""; 186 | mode = ["n"]; 187 | action = ":UndotreeToggle"; 188 | } 189 | ]; 190 | } 191 | 192 | # File tree 193 | { 194 | plugins.nvim-tree.enable = true; 195 | 196 | # to open/close file tree 197 | keymaps = [ 198 | { 199 | key = ""; 200 | mode = ["n"]; 201 | options.silent = true; 202 | action = ":NvimTreeToggle"; 203 | } 204 | ]; 205 | } 206 | 207 | # Git integration w/ vim-fugitive 208 | { 209 | plugins.fugitive.enable = true; 210 | 211 | # Create mappings under g 212 | keymaps = mkLeaderMappings "g" { 213 | "s" = ":Git"; 214 | "l" = ":Git log --oneline --graph"; 215 | "L" = ":Gclog"; 216 | "d" = ":Gdiffsplit"; 217 | "b" = ":Git blame"; 218 | 219 | "c" = ":Git commit"; 220 | "C" = ":Git commit %"; 221 | "A" = ":Git commit --amend"; 222 | 223 | "f" = ":Git fetch"; 224 | "p" = ":Git push"; 225 | "P" = ":Git pull"; 226 | 227 | "w" = ":Gwrite"; 228 | }; 229 | } 230 | 231 | # Live LaTeX editing 232 | { 233 | plugins.vimtex = { 234 | enable = true; 235 | # TeXLive will come from a project's devshell 236 | texlivePackage = null; 237 | settings = { 238 | view_method = "skim"; 239 | }; 240 | }; 241 | plugins.texpresso.enable = true; 242 | } 243 | ]; 244 | } 245 | -------------------------------------------------------------------------------- /modules/home/ptpython.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | inherit (lib) concatStringsSep isBool isFloat isInt isString mapAttrsToList; 8 | 9 | boolToPyBool = b: 10 | if b 11 | then "True" 12 | else "False"; 13 | 14 | rawPy = value: {__raw = value;}; 15 | 16 | attrsToPyStmts = attrs: 17 | concatStringsSep "\n " (mapAttrsToList (n: v: "repl.${n} = ${ 18 | if (isBool v) 19 | then (boolToPyBool v) 20 | else if (isInt v || isFloat v) 21 | then "${toString v}" 22 | else if (isString (v.__raw or null)) 23 | then v.__raw 24 | else ''"${v}"'' 25 | }") 26 | attrs); 27 | 28 | # See https://github.com/prompt-toolkit/ptpython/blob/main/examples/ptpython_config/config.py 29 | # for available options 30 | ptpythonCfg = { 31 | show_signature = true; 32 | show_docstring = true; 33 | 34 | show_line_numbers = true; 35 | highlight_matching_parenthesis = true; 36 | 37 | enable_mouse_support = true; 38 | 39 | vi_mode = true; 40 | cursor_shape_config = "Modal (vi)"; 41 | enable_open_in_editor = true; 42 | 43 | completion_visualization = rawPy "CompletionVisualisation.POP_UP"; 44 | }; 45 | 46 | colorscheme = "native"; 47 | in { 48 | home.packages = [pkgs.uv]; 49 | home.sessionVariables.PTPYTHON_CONFIG_HOME = "${config.xdg.configHome}/ptpython"; 50 | 51 | xdg.configFile."ptpython/config.py".text = '' 52 | from prompt_toolkit.styles import Style 53 | 54 | from ptpython.layout import CompletionVisualisation 55 | 56 | __all__ = ["configure"] 57 | 58 | 59 | def configure(repl): 60 | ${attrsToPyStmts ptpythonCfg} 61 | repl.use_code_colorscheme("${colorscheme}") 62 | ''; 63 | } 64 | -------------------------------------------------------------------------------- /modules/home/wm.nix: -------------------------------------------------------------------------------- 1 | {...}: let 2 | mod = "ctrl-cmd-alt"; 3 | in { 4 | programs.aerospace = { 5 | enable = true; 6 | userSettings = { 7 | start-at-login = true; 8 | enable-normalization-opposite-orientation-for-nested-containers = true; 9 | 10 | gaps = { 11 | inner.horizontal = 4; 12 | inner.vertical = 4; 13 | outer.left = 4; 14 | outer.right = 4; 15 | outer.top = 4; 16 | outer.bottom = 4; 17 | }; 18 | 19 | mode.main.binding = { 20 | # Focus 21 | "${mod}-h" = "focus left"; 22 | "${mod}-j" = "focus down"; 23 | "${mod}-k" = "focus up"; 24 | "${mod}-l" = "focus right"; 25 | 26 | # Move 27 | "${mod}-shift-h" = "move left"; 28 | "${mod}-shift-j" = "move down"; 29 | "${mod}-shift-k" = "move up"; 30 | "${mod}-shift-l" = "move right"; 31 | 32 | # Resize 33 | "${mod}-minus" = "resize smart -50"; 34 | "${mod}-equal" = "resize smart +50"; 35 | "${mod}-shift-minus" = "resize smart-opposite -50"; 36 | "${mod}-shift-equal" = "resize smart-opposite +50"; 37 | "${mod}-enter" = "fullscreen"; 38 | 39 | # Layout mode 40 | "${mod}-slash" = "layout tiles horizontal vertical"; 41 | "${mod}-comma" = "layout accordion horizontal vertical"; 42 | 43 | # Workspaces 44 | "${mod}-1" = "workspace 1"; 45 | "${mod}-2" = "workspace 2"; 46 | "${mod}-3" = "workspace 3"; 47 | "${mod}-4" = "workspace 4"; 48 | "${mod}-5" = "workspace 5"; 49 | "${mod}-6" = "workspace 6"; 50 | "${mod}-7" = "workspace 7"; 51 | "${mod}-8" = "workspace 8"; 52 | "${mod}-9" = "workspace 9"; 53 | "${mod}-0" = "workspace 10"; 54 | "${mod}-shift-1" = "move-node-to-workspace 1"; 55 | "${mod}-shift-2" = "move-node-to-workspace 2"; 56 | "${mod}-shift-3" = "move-node-to-workspace 3"; 57 | "${mod}-shift-4" = "move-node-to-workspace 4"; 58 | "${mod}-shift-5" = "move-node-to-workspace 5"; 59 | "${mod}-shift-6" = "move-node-to-workspace 6"; 60 | "${mod}-shift-7" = "move-node-to-workspace 7"; 61 | "${mod}-shift-8" = "move-node-to-workspace 8"; 62 | "${mod}-shift-9" = "move-node-to-workspace 9"; 63 | "${mod}-shift-0" = "move-node-to-workspace 10"; 64 | 65 | "${mod}-r" = "mode resize"; 66 | }; 67 | 68 | mode.resize.binding = { 69 | h = "resize width -50"; 70 | j = "resize height +50"; 71 | k = "resize height -50"; 72 | l = "resize width +50"; 73 | esc = "mode main"; 74 | }; 75 | }; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /modules/home/xdg.nix: -------------------------------------------------------------------------------- 1 | # Force all macOS utilities, e.g. zsh, to conform to the XDG Base Directory spec 2 | {config, ...}: let 3 | # Use ~/Library/XDG for storing app files, I don't want any dotfiles cluttering up ~. 4 | xdgSubdir = "Library/XDG"; 5 | xdgDir = "${config.home.homeDirectory}/Library/XDG"; 6 | xdg = { 7 | configHome = "${xdgDir}/config"; 8 | cacheHome = "${xdgDir}/cache"; 9 | dataHome = "${xdgDir}/data"; 10 | runtimeDir = "$TMPDIR"; 11 | stateHome = "${xdgDir}/state"; 12 | }; 13 | in { 14 | xdg = { 15 | inherit (xdg) configHome cacheHome dataHome stateHome; 16 | }; 17 | home.sessionVariables.XDG_RUNTIME_DIR = xdg.runtimeDir; 18 | 19 | home.file."${xdgSubdir}/.keep".text = ""; 20 | } 21 | -------------------------------------------------------------------------------- /modules/nix-settings.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: { 2 | nix.package = pkgs.nixVersions.latest; 3 | 4 | nix.settings = { 5 | # Add other binary caches 6 | substituters = ["https://i077.cachix.org" "https://nix-community.cachix.org/"]; 7 | trusted-public-keys = [ 8 | "i077.cachix.org-1:v28tOFUfUjtVXdPol5FfEO/6wC/VKWnHkD32/aMJJBk=" 9 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 10 | ]; 11 | }; 12 | 13 | # Use nix flakes for local flake evaluation 14 | nix.extraOptions = '' 15 | experimental-features = nix-command flakes 16 | builders-use-substitutes = true 17 | ''; 18 | } 19 | --------------------------------------------------------------------------------