Skip to content

natsukium/dotfiles

Repository files navigation

My literate configurations with Org and Nix

About

This repository manages system configurations for multiple machines using Nix and Org mode. All configuration is written as literate programs—Org documents where prose explains the reasoning behind each decision, and Nix code blocks are tangled into the actual configuration files.

Documentation

The full configuration document is published at:

Nix

Nix is a purely functional package manager and build system. This repository uses several Nix ecosystem tools:

  • Flakes for reproducible dependency management
  • NixOS for declarative Linux system configuration
  • nix-darwin for declarative macOS system configuration
  • home-manager for user environment management
  • nix-on-droid for Android (Termux) environment

Machines

NamePlatformDeviceRole
kilimanjaroNixOS (x86_64)i5-12400F / RTX 3080Main desktop
tarangireNixOS (x86_64)Ryzen 9 9950XBuild server
manyaraNixOS (x86_64)Beelink Mini S12Home server
arushaNixOS (x86_64)WSL2WSL environment
serengetiNixOS (aarch64)OCI A1 FlexBuild server
katavimacOS (aarch64)M1 MacBook AirMain laptop
workmacOS (aarch64)M4 MacBook ProWork laptop
mikumimacOS (aarch64)M1 Mac miniBuild server
androidnix-on-droidGalaxy S24 FEPhone

Philosophy

Literate Configuration

Nix is declarative. Reading a Nix expression reveals what the system should become, and Nix itself handles how to get there. But neither the code nor the build system captures why a particular configuration exists, or why alternatives were rejected.

Why was fish chosen over zsh or bash? Why does the desktop profile enable this specific set of services? Why was a particular package pinned to an older version? The code shows the decision, but not the reasoning behind it. Without this context, future changes risk undoing intentional tradeoffs or repeating previously rejected approaches.

This repository uses literate programming to preserve intent. Configuration lives in Org mode documents where prose surrounds code.

For most settings, one or two sentences—what it enables and the visible effect—is enough. Reserve the full problem-and-alternatives form for non-obvious trade-offs: architectural choices, package pins, temporary workarounds.

Development

This repository provides a Nix development shell with all the tools needed for working on the configurations. Enter the shell by running:

nix develop

The shell includes infrastructure tools (Terraform, sops, ssh-to-age), translation tools (po4a, gettext), and build utilities (nix-fast-build). On entry, it automatically sets up pre-commit hooks, configures MCP servers, and syncs CLAUDE.md from the literate source.

{ ... }:
{
  perSystem =
    {
      config,
      pkgs,
      ...
    }:
    {
      devShells = {
        default = pkgs.mkShell {
          packages = with pkgs; [
            aws-vault
            nix-fast-build
            sops
            ssh-to-age
            (terraform.withPlugins (p: [
              p.carlpett_sops
              p.cloudflare_cloudflare
              p.determinatesystems_hydra
              p.hashicorp_aws
              p.hashicorp_external
              p.hashicorp_null
              p.integrations_github
              p.oracle_oci
            ]))
            <<translation-packages>>
          ];
          shellHook =
            config.pre-commit.installationScript
            + config.mcp-servers.shellHook
            + ''
              echo "Syncing CLAUDE.md..."
              make CLAUDE.md >/dev/null 2>&1 || echo "Warning: Failed to generate CLAUDE.md"
            '';
        };
      };
    };
}

Pre-commit hooks

Hooks run by git-hooks.nix on every commit. prek is used as the runner because it is the actively-maintained Rust port of pre-commit, with a faster cold start and parallel hook execution out of the box.

check-org-tangle is the only hook with priority = 0 because it must run before everything else: if Org files are out of sync, formatters and linters downstream would otherwise fail against stale tangle output. Every other hook keeps the default priority and runs in parallel under prek.

{ inputs, ... }:
{
  imports = [ inputs.git-hooks.flakeModule ];

  perSystem =
    { pkgs, ... }:
    {
      pre-commit = {
        check.enable = true;
        settings = {
          package = pkgs.prek;
          src = ../../..;
          hooks =
            let
              check-git-changes = pkgs.writeShellApplication {
                name = "check-git-changes";
                runtimeInputs = [ pkgs.git ];
                text = builtins.readFile ../../../scripts/check-git-changes.sh;
              };
              emacs-with-org = (pkgs.emacsPackagesFor pkgs.emacs).emacsWithPackages (epkgs: [ epkgs.org ]);
            in
            {
              actionlint = {
                enable = true;
                priority = 10;
              };
              biome = {
                enable = true;
                priority = 10;
              };
              lua-ls = {
                enable = false;
                priority = 10;
              };
              nil = {
                enable = true;
                priority = 10;
              };
              shellcheck = {
                enable = true;
                priority = 10;
              };
              treefmt = {
                enable = true;
                priority = 10;
              };
              typos = {
                enable = true;
                priority = 10;
                excludes = [
                  ".sops.yaml"
                  "homes/shared/gpg/keys.txt"
                  "secrets.yaml"
                  "secrets/default.yaml"
                  "systems/nixos/tarangire/facter.json"
                  "systems/shared/hercules-ci/binary-caches.json"
                ];
                settings.configPath = "typos.toml";
              };
              yamllint = {
                enable = true;
                priority = 10;
                excludes = [
                  "secrets/default.yaml"
                  "secrets.yaml"
                ];
                settings.configData = "{rules: {document-start: {present: false}}}";
              };
              po4a = {
                enable = true;
                name = "po4a";
                description = "Update translations with po4a";
                priority = 10;
                entry = pkgs.lib.getExe (
                  pkgs.writeShellApplication {
                    name = "check-po4a";
                    runtimeInputs = [
                      pkgs.po4a
                      pkgs.gettext
                      check-git-changes
                    ];
                    text = builtins.readFile ../../../scripts/check-po4a.sh;
                  }
                );
                files = "(\\.org|po/.*\\.po)$";
                pass_filenames = false;
              };
              "check-org-tangle" = {
                enable = true;
                name = "check-org-tangle";
                description = "Verify org files are tangled and synchronized";
                # Ensure this hook runs before all other hooks
                priority = 0;
                entry = pkgs.lib.getExe (
                  pkgs.writeShellApplication {
                    name = "check-org-tangle";
                    runtimeInputs = [
                      emacs-with-org
                      pkgs.gnumake
                      check-git-changes
                    ];
                    text = builtins.readFile ../../../scripts/check-org-tangle.sh;
                  }
                );
                files = "\\.org$";
                pass_filenames = false;
              };
            };
        };
      };
    };
}

Formatting

Formatters are aggregated via treefmt-nix and invoked from the treefmt pre-commit hook, so a single declaration covers both editor integrations and the commit-time check.

{ inputs, ... }:
{
  imports = [ inputs.treefmt-nix.flakeModule ];

  perSystem = _: {
    treefmt = {
      projectRootFile = "flake.nix";
      programs = {
        biome.enable = true;
        nixfmt.enable = true;
        shfmt.enable = true;
        stylua.enable = true;
        taplo.enable = true;
        terraform.enable = true;
        yamlfmt.enable = true;
      };
    };
  };
}

MCP Servers

Configuration for mcp-servers-nix, enabled in the development shell. The flavors.claude-code preset generates a .mcp.json file compatible with Claude Code’s expected format.

Enabled servers:

  • nixos: NixOS package/option search and Home Manager documentation
  • terraform: Terraform registry lookup for providers, modules, and policies
  • grafana: Query dashboards, datasources, and metrics from the home server’s Grafana instance

The passwordCommand option retrieves secrets at runtime using rbw (Bitwarden CLI), avoiding plaintext credentials in the repository.

{ inputs, ... }:
{
  imports = [ inputs.mcp-servers.flakeModule ];

  perSystem = _: {
    mcp-servers = {
      flavors.claude-code.enable = true;
      programs = {
        nixos.enable = true;
        terraform.enable = true;
        grafana = {
          enable = true;
          env = {
            GRAFANA_URL = "http://manyara:3001";
            GRAFANA_USERNAME = "admin";
          };
          passwordCommand = {
            GRAFANA_PASSWORD = [
              "rbw"
              "get"
              "grafana"
            ];
          };
        };
      };
    };
  };
}

Translation

This project uses po4a to manage translations.

Requirements

The required packages are included in the development shell.

gettext
po4a
  • gettext: provides msgfmt and other internationalization utilities
  • po4a: po4a >= 0.74 is required for Org mode support.

Translation workflow

Create po4a configuration

Configure the target language, location for generated po files, and documents to translate as follows. The -k 0 option forces output of translated files even if the translation is incomplete (default threshold is 80%).

[po4a_langs] ja
[po4a_paths] po/dotfiles.pot $lang:po/$lang.po
[type: org] configuration.org $lang:configuration.$lang.org opt:"-k 0"
[type: org] .github/README.org $lang:.github/README.$lang.org opt:"-k 0"
[type: org] modules/flake/features/emacs/init.org $lang:modules/flake/features/emacs/init.$lang.org opt:"-k 0"
[type: org] modules/flake/features/emacs/early-init.org $lang:modules/flake/features/emacs/early-init.$lang.org opt:"-k 0"
[type: org] overlays/configuration.org $lang:overlays/configuration.$lang.org opt:"-k 0"
[type: org] modules/configuration.org $lang:modules/configuration.$lang.org opt:"-k 0"

For detailed information about po4a.cfg configuration, see man po4a.

Create/Update po

When documents are updated and you need to create/update po files, run the following command. This generates template (pot) and po files for each language at the paths configured in po4a.cfg.

po4a --no-translations po4a.cfg

Translate

Edit the target language po using a po editor. Popular options include Emacs po-mode, poedit, GNOME’s Gtranslator, and KDE’s Lokalize.

Create/Update translation file

After completing translations, generate files with the following command. Since po files are also updated at this time, in practice you only need to run this command.

po4a po4a.cfg

About

Nix dotfiles for NixOS, Darwin and WSL

Topics

Resources

License

Stars

Watchers

Forks

Contributors