My literate configurations with Org and Nix

Table of Contents

1. About

2. Philosophy

2.1. 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. Each decision—from high-level architecture to individual package overrides—is accompanied by its rationale: why this approach was chosen, and why alternatives were not.

Configurations are documented with the problem that motivated them, the alternatives considered, and the reasoning behind the final choice. For temporary workarounds, the conditions for removal are also noted.

3. Configuration

3.1. Flake

This flake manages NixOS, nix-darwin, and home-manager configurations for multiple machines across different platforms (macOS, Linux, Android). It uses flake-parts for modular organization and includes tooling for development, code formatting, and pre-commit hooks.

{
  description = "dotfiles";

  inputs = {
    # Core
    nixpkgs.url = "git+https://github.com/nixos/nixpkgs?shallow=1&ref=nixos-unstable-small";
    # "github:NixOS/nixpkgs/trick-renovate-into-working"
    nixpkgs-stable.url = "git+https://github.com/nixos/nixpkgs?shallow=1&ref=nixos-25.05";
    # Flake Infrastructure
    flake-parts = {
      url = "github:hercules-ci/flake-parts";
      inputs.nixpkgs-lib.follows = "nixpkgs";
    };
    # Transitive Dependencies
    flake-utils.url = "github:numtide/flake-utils";
    # System Configuration
    darwin = {
      url = "github:nix-darwin/nix-darwin";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    nixos-wsl = {
      url = "github:nix-community/nixos-wsl";
      inputs.flake-compat.follows = "";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    nix-on-droid = {
      url = "github:nix-community/nix-on-droid";
      inputs.home-manager.follows = "home-manager";
      inputs.nix-formatter-pack.follows = "";
      inputs.nixpkgs-docs.follows = "nixpkgs";
      inputs.nixpkgs-for-bootstrap.follows = "nixpkgs";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.nmd.follows = "";
    };
    disko = {
      url = "github:nix-community/disko";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    impermanence.url = "github:nix-community/impermanence";
    lanzaboote = {
      url = "github:nix-community/lanzaboote";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.pre-commit.follows = "git-hooks";
    };
    nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
    # Infrastructure
    comin = {
      url = "github:nlewo/comin";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    sops-nix = {
      url = "github:Mic92/sops-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    tsnsrv = {
      url = "github:boinkor-net/tsnsrv";
      inputs.flake-parts.follows = "flake-parts";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    # Development Tools
    git-hooks = {
      url = "github:cachix/git-hooks.nix";
      inputs.flake-compat.follows = "";
      inputs.gitignore.follows = "";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    treefmt-nix = {
      url = "github:numtide/treefmt-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    # Desktop & Theming
    nix-colors = {
      url = "github:misterio77/nix-colors";
      inputs.nixpkgs-lib.follows = "nixpkgs";
    };
    nix-wallpaper = {
      url = "github:natsukium/nix-wallpaper/custom-logo";
      inputs.flake-utils.follows = "flake-utils";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.pre-commit-hooks.follows = "git-hooks";
    };
    # Applications
    brew-api = {
      url = "github:BatteredBunny/brew-api";
      flake = false;
    };
    brew-nix = {
      url = "github:BatteredBunny/brew-nix";
      inputs.brew-api.follows = "brew-api";
      inputs.nix-darwin.follows = "darwin";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    claude-desktop = {
      url = "github:k3d3/claude-desktop-linux-flake";
      inputs.flake-utils.follows = "flake-utils";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    edgepkgs = {
      url = "github:natsukium/edgepkgs";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    emacs-overlay = {
      url = "github:nix-community/emacs-overlay";
      inputs.nixpkgs-stable.follows = "nixpkgs-stable";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    firefox-addons = {
      url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    mcp-servers = {
      url = "github:natsukium/mcp-servers-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    niri-flake = {
      url = "github:sodiboo/niri-flake";
      inputs.niri-stable.follows = "";
      inputs.niri-unstable.follows = "";
      inputs.nixpkgs-stable.follows = "nixpkgs-stable";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.xwayland-satellite-stable.follows = "";
      inputs.xwayland-satellite-unstable.follows = "";
    };
    nur-packages = {
      url = "github:natsukium/nur-packages";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    zen-browser = {
      url = "github:0xc000022070/zen-browser-flake";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.home-manager.follows = "home-manager";
    };
  };

  nixConfig = {
    extra-substituters = [
      "https://natsukium.cachix.org"
      "https://nix-community.cachix.org"
    ];

    extra-trusted-public-keys = [
      "natsukium.cachix.org-1:STD7ru7/5+KJX21m2yuDlgV6PnZP/v5VZWAJ8DZdMlI="
      "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
    ];
  };

  outputs =
    { self, flake-parts, ... }@inputs:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [
        "x86_64-linux"
        "aarch64-linux"
        "aarch64-darwin"
      ];

      imports = [
        ./flake-module.nix
        inputs.git-hooks.flakeModule
        inputs.treefmt-nix.flakeModule
      ];

      hosts = {
        # main laptop (m1 macbook air)
        katavi = {
          system = "aarch64-darwin";
        };
        # build server (m1 mac mini)
        mikumi = {
          system = "aarch64-darwin";
        };
        # laptop for work (m4 macbook pro)
        work = {
          system = "aarch64-darwin";
        };
        # main desktop (intel core i5-12400F)
        kilimanjaro = {
          system = "x86_64-linux";
        };
        # WSL (dualboot with kilimanjaro)
        arusha = {
          system = "x86_64-linux";
        };
        # main server (intel N100)
        manyara = {
          system = "x86_64-linux";
        };
        # build server (OCI A1 Flex)
        serengeti = {
          system = "aarch64-linux";
        };
        # build server (Ryzen 9 9950X)
        tarangire = {
          system = "x86_64-linux";
        };
        # phone (pixel 7a)
        android = {
          system = "aarch64-linux";
          platform = "android";
        };
      };

      flake = {
        overlays = import ./overlays { inherit inputs; };
        templates = import ./templates;
      };

      perSystem =
        {
          self',
          config,
          pkgs,
          system,
          ...
        }:
        {
          _module.args.pkgs = import self.inputs.nixpkgs {
            inherit system;
            config.allowUnfree = true;
            overlays = [ self.inputs.nur-packages.overlays.default ] ++ builtins.attrValues self.overlays;
          };

          checks = import ./tests {
            inherit (self.inputs) nixpkgs;
            inherit pkgs;
          };

          packages = {
            fastfetch = pkgs.callPackage ./pkgs/fastfetch { };
            neovim = pkgs.callPackage ./pkgs/neovim-with-config { };
            po4a_0_74 = (
              pkgs.po4a.overrideAttrs (oldAttrs: {
                version = "0.74";
                src = pkgs.fetchurl {
                  url = "https://github.com/mquinson/po4a/releases/download/v0.74/po4a-0.74.tar.gz";
                  hash = "sha256-JfwyPyuje71Iw68Ov0mVJkSw5GgmH5hjPpEhmoOP58I=";
                };
                patches = [ ];
                nativeBuildInputs = oldAttrs.nativeBuildInputs ++ [ pkgs.libxml2 ];
                doCheck = false;
              })
            );
            html =
              with pkgs;
              let
                org-html-themes = fetchurl {
                  url = "https://raw.githubusercontent.com/fniessen/org-html-themes/b3898f4c5b09b3365fd93fd1566f46ecd0a8911f/org/theme-readtheorg.setup";
                  hash = "sha256-+5gy+S6NcuvlV61fudbCNoCKmSrCdA9P5CHeGKlDrSM=";
                };
                org-to-html = writeScript "org-to-html.el" ''
                  (require 'org)
                  (require 'htmlize)

                  (find-file "README.org")
                  (org-html-export-to-html)

                  (find-file "README.ja.org")
                  (org-html-export-to-html)
                '';
              in
              stdenvNoCC.mkDerivation {
                name = "dotfiles";
                src = lib.cleanSource ./.;
                postPatch = ''
                  substituteInPlace README.org \
                    --replace-fail "https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup" "${org-html-themes}"
                '';
                nativeBuildInputs = [
                  (emacs.pkgs.withPackages (epkgs: [ epkgs.htmlize ]))
                  gettext
                  self'.packages.po4a_0_74
                ];
                buildPhase = ''
                  runHook preBuild
                  po4a po4a.cfg
                  emacs --batch -l ${org-to-html}
                  runHook postBuild
                '';

                installPhase = ''
                  runHook preInstall
                  install -Dm644 README.html $out/index.html
                  install -Dm644 README.ja.html $out/ja/index.html
                  runHook postInstall
                '';
              };
          };

          pre-commit = {
            check.enable = true;
            settings = {
              src = ./.;
              hooks = {
                actionlint.enable = true;
                biome.enable = true;
                lua-ls.enable = false;
                nil.enable = true;
                shellcheck.enable = true;
                treefmt.enable = true;
                typos = {
                  enable = true;
                  excludes = [
                    "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;
                  excludes = [
                    "secrets/default.yaml"
                    "secrets.yaml"
                  ];
                  settings.configData = "{rules: {document-start: {present: false}}}";
                };
                # Prefixed with "00-" to ensure this hook runs before all other hooks
                # (hooks are sorted alphabetically when no explicit before/after is specified)
                "00-check-org-tangle" = {
                  enable = true;
                  name = "check-org-tangle";
                  description = "Verify org files are tangled and synchronized";
                  entry =
                    let
                      checkScript = pkgs.writeShellScript "check-org-tangle" ''
                        set -euo pipefail

                        ${pkgs.gnumake}/bin/make -B tangle

                        # Check for differences using git diff
                        changed=$(${pkgs.git}/bin/git diff --name-only)

                        if [ -n "$changed" ]; then
                          echo "Org files were out of sync and have been auto-tangled."
                          echo "Changed files:"
                          echo "$changed"
                          echo ""
                          echo "Please stage the changes and commit again:"
                          echo "  git add $changed"
                          exit 1
                        fi

                        exit 0
                      '';
                    in
                    "${checkScript}";
                  files = "\\.org$";
                  pass_filenames = false;
                };
              };
            };
          };

          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;
            };
          };

          devShells = {
            default = pkgs.mkShell {
              packages = with pkgs; [
                gettext
                nix-fast-build
                self'.packages.po4a_0_74
                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
                ]))
              ];
              shellHook = config.pre-commit.installationScript + ''
                echo "Syncing CLAUDE.md..."
                make CLAUDE.md >/dev/null 2>&1 || echo "Warning: Failed to generate CLAUDE.md"
              '';
            };
          };
        };
    };
}

3.1.1. Inputs

External flake dependencies.

To minimize evaluation time caused by dependency graph bloat, almost all flakes are configured to follow the same nixpkgs and other common inputs wherever possible.

  1. Core
    1. nixpkgs

      https://github.com/NixOS/nixpkgs

      Nix Packages collection & NixOS

      The primary package set. Using nixos-unstable-small instead of nixos-unstable for faster channel updates. The -small variant skips some less critical CI tests, allowing new package versions to propagate faster while maintaining stability for core packages.

      For channel selection guidance, see https://nix.dev/concepts/faq.html#which-channel-branch-should-i-use

      Channel status can be checked at https://status.nixos.org/

      Using git+https:// with shallow=1 instead of github: for slightly faster file extraction on large repositories like nixpkgs.

      nixpkgs.url = "git+https://github.com/nixos/nixpkgs?shallow=1&ref=nixos-unstable-small";
      

      The commented-out line is a workaround for Renovate, which cannot parse certain flake input formats. See https://github.com/renovatebot/renovate/issues/29721

      # "github:NixOS/nixpkgs/trick-renovate-into-working"
      
    2. nixpkgs-stable

      Provides stable packages when unstable has build failures or regressions. Mainly used by the stable overlay (see overlays/configuration.org) for packages that are broken in unstable.

      nixpkgs-stable.url = "git+https://github.com/nixos/nixpkgs?shallow=1&ref=nixos-25.05";
      
  2. Flake Infrastructure
    1. flake-parts

      https://github.com/hercules-ci/flake-parts

      Simplify Nix Flakes with the module system

      Framework for organizing flake outputs. Provides module system for flakes, making complex configurations more maintainable through separation of concerns.

      flake-parts = {
        url = "github:hercules-ci/flake-parts";
        inputs.nixpkgs-lib.follows = "nixpkgs";
      };
      
  3. Transitive Dependencies
    1. flake-utils

      https://github.com/numtide/flake-utils

      Pure Nix flake utility functions

      Common flake utilities. Only used transitively via follows to unify the flake-utils version across inputs that depend on it.

      flake-utils.url = "github:numtide/flake-utils";
      
  4. System Configuration
    1. darwin

      https://github.com/nix-darwin/nix-darwin

      Manage your macOS using Nix

      nix-darwin provides NixOS-style system configuration for macOS. Essential for managing macOS system settings, launchd services, and Homebrew declaratively.

      darwin = {
        url = "github:nix-darwin/nix-darwin";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    2. home-manager

      https://github.com/nix-community/home-manager

      Manage a user environment using Nix

      User environment management. Manages dotfiles, user services, and per-user packages declaratively. The backbone of user-level configuration in this repository.

      home-manager = {
        url = "github:nix-community/home-manager";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    3. nixos-wsl

      https://github.com/nix-community/nixos-wsl

      NixOS on WSL

      NixOS on Windows Subsystem for Linux. Provides NixOS experience within WSL2, useful for Windows machines that need Linux development environments.

      nixos-wsl = {
        url = "github:nix-community/nixos-wsl";
        inputs.flake-compat.follows = "";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    4. nix-on-droid

      https://github.com/nix-community/nix-on-droid

      Nix-enabled environment for your Android device.

      Nix environment for Android via Termux. Enables the same declarative configuration approach on mobile devices.

      nix-on-droid = {
        url = "github:nix-community/nix-on-droid";
        inputs.home-manager.follows = "home-manager";
        inputs.nix-formatter-pack.follows = "";
        inputs.nixpkgs-docs.follows = "nixpkgs";
        inputs.nixpkgs-for-bootstrap.follows = "nixpkgs";
        inputs.nixpkgs.follows = "nixpkgs";
        inputs.nmd.follows = "";
      };
      
    5. disko

      https://github.com/nix-community/disko

      Declarative disk partitioning and formatting using nix

      Used for reproducible NixOS installations with automated partition layout, filesystem creation, and encryption setup.

      disko = {
        url = "github:nix-community/disko";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    6. impermanence

      https://github.com/nix-community/impermanence

      Modules to help you handle persistent state on systems with ephemeral root storage

      Manages stateful paths on systems with ephemeral root filesystems. Used with btrfs snapshots to ensure only explicitly declared state persists across reboots.

      impermanence.url = "github:nix-community/impermanence";
      
    7. lanzaboote

      https://github.com/nix-community/lanzaboote

      Secure Boot for NixOS

      Signs boot components with custom keys, enabling Secure Boot on NixOS machines.

      lanzaboote = {
        url = "github:nix-community/lanzaboote";
        inputs.nixpkgs.follows = "nixpkgs";
        inputs.pre-commit.follows = "git-hooks";
      };
      
    8. nixos-facter-modules

      https://github.com/numtide/nixos-facter-modules

      A series of NixOS modules to be used in conjunction with nixos-facter

      Hardware detection for NixOS. Automatically generates hardware configuration based on detected hardware, simplifying initial system setup.

      nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
      
  5. Infrastructure
    1. comin

      https://github.com/nlewo/comin

      GitOps For NixOS Machines

      Automatically deploys configuration changes when pushed to the repository, enabling continuous deployment for servers without manual nixos-rebuild switch.

      comin = {
        url = "github:nlewo/comin";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    2. sops-nix

      https://github.com/Mic92/sops-nix

      Atomic secret provisioning for NixOS based on sops

      Secrets management using Mozilla SOPS. Encrypts secrets in the repository that are decrypted at activation time using age keys.

      sops-nix = {
        url = "github:Mic92/sops-nix";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    3. tsnsrv

      https://github.com/boinkor-net/tsnsrv

      A reverse proxy that exposes services on your tailnet (as their own tailscale participants)

      Tailscale service proxy. Exposes local services to the Tailscale network with automatic HTTPS certificates.

      tsnsrv = {
        url = "github:boinkor-net/tsnsrv";
        inputs.flake-parts.follows = "flake-parts";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
  6. Development Tools
    1. git-hooks

      https://github.com/cachix/git-hooks.nix

      Seamless integration of pre-commit.com git hooks with Nix.

      Pre-commit hooks as Nix derivations. Ensures code quality checks run consistently across all development environments without requiring global tool installation.

      Inputs that don't affect this flake are removed to prevent lockfile bloat.

      git-hooks = {
        url = "github:cachix/git-hooks.nix";
        inputs.flake-compat.follows = "";
        inputs.gitignore.follows = "";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    2. treefmt-nix

      https://github.com/numtide/treefmt-nix

      treefmt nix configuration

      Unified code formatter configuration. Runs multiple formatters (nixfmt, shfmt, etc.) through a single interface, ensuring consistent formatting across the repository.

      treefmt-nix = {
        url = "github:numtide/treefmt-nix";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
  7. Desktop & Theming
    1. nix-colors

      https://github.com/misterio77/nix-colors

      Modules and schemes to make theming with Nix awesome.

      Base16 color scheme framework for Nix. Provides consistent theming across applications through a single color scheme definition.

      nix-colors = {
        url = "github:misterio77/nix-colors";
        inputs.nixpkgs-lib.follows = "nixpkgs";
      };
      
    2. nix-wallpaper

      https://github.com/natsukium/nix-wallpaper

      A configurable wallpaper for nix systems

      Generates Nix logo wallpapers. Using a custom branch (custom-logo) that supports additional logo variants.

      nix-wallpaper = {
        url = "github:natsukium/nix-wallpaper/custom-logo";
        inputs.flake-utils.follows = "flake-utils";
        inputs.nixpkgs.follows = "nixpkgs";
        inputs.pre-commit-hooks.follows = "git-hooks";
      };
      
  8. Applications
    1. brew-nix

      https://github.com/BatteredBunny/brew-nix

      Experimental nix expression to package all MacOS casks from homebrew automatically

      Provides Homebrew casks as Nix packages for darwin. Useful for proprietary macOS applications not available in nixpkgs.

      The brew-api input is marked as non-flake because it's just a data source (JSON API dump from Homebrew's API).

      brew-api = {
        url = "github:BatteredBunny/brew-api";
        flake = false;
      };
      brew-nix = {
        url = "github:BatteredBunny/brew-nix";
        inputs.brew-api.follows = "brew-api";
        inputs.nix-darwin.follows = "darwin";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    2. claude-desktop

      https://github.com/k3d3/claude-desktop-linux-flake

      Nix Flake for Claude Desktop on Linux

      Provides Claude Desktop for Linux. Using a community flake since Anthropic doesn't officially support Linux yet.

      claude-desktop = {
        url = "github:k3d3/claude-desktop-linux-flake";
        inputs.flake-utils.follows = "flake-utils";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    3. edgepkgs

      https://github.com/natsukium/edgepkgs

      Personal repository for bleeding-edge packages. Contains packages not yet in nixpkgs, those requiring modifications, or packages that wouldn't be accepted upstream (e.g., niche or experimental software).

      edgepkgs = {
        url = "github:natsukium/edgepkgs";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    4. emacs-overlay

      https://github.com/nix-community/emacs-overlay

      Bleeding edge emacs overlay

      Provides latest Emacs builds including native compilation and pure GTK variants. Also includes MELPA packages updated more frequently than nixpkgs. Mainly used for the utility that parses org files and automatically configures dependency packages.

      emacs-overlay = {
        url = "github:nix-community/emacs-overlay";
        inputs.nixpkgs-stable.follows = "nixpkgs-stable";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    5. firefox-addons

      https://gitlab.com/rycee/nur-expressions

      A few Nix expressions suitable for inclusion in Nix User Repository

      Firefox/browser extensions packaged for Nix. Allows declarative browser extension management through home-manager.

      firefox-addons = {
        url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    6. mcp-servers

      https://github.com/natsukium/mcp-servers-nix

      A Nix-based configuration framework for Model Control Protocol (MCP) servers with ready-to-use packages.

      Provides both a configuration framework and packaged MCP servers for Nix. Used with Claude Code and other MCP-compatible AI assistants.

      mcp-servers = {
        url = "github:natsukium/mcp-servers-nix";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    7. niri-flake

      https://github.com/sodiboo/niri-flake

      Nix-native configuration for niri

      Niri is a scrollable-tiling Wayland compositor. This flake provides the compositor and related modules for NixOS/home-manager integration.

      niri-flake = {
        url = "github:sodiboo/niri-flake";
        inputs.niri-stable.follows = "";
        inputs.niri-unstable.follows = "";
        inputs.nixpkgs-stable.follows = "nixpkgs-stable";
        inputs.nixpkgs.follows = "nixpkgs";
        inputs.xwayland-satellite-stable.follows = "";
        inputs.xwayland-satellite-unstable.follows = "";
      };
      
    8. nur-packages

      https://github.com/natsukium/nur-packages

      Personal NUR (Nix User Repository). Contains packages maintained personally that are either too niche for nixpkgs or require customizations.

      nur-packages = {
        url = "github:natsukium/nur-packages";
        inputs.nixpkgs.follows = "nixpkgs";
      };
      
    9. zen-browser

      https://github.com/0xc000022070/zen-browser-flake

      Community-driven Nix Flake for the Zen browser

      Firefox-based browser focused on privacy. Community flake providing Nix packaging with home-manager integration.

      zen-browser = {
        url = "github:0xc000022070/zen-browser-flake";
        inputs.nixpkgs.follows = "nixpkgs";
        inputs.home-manager.follows = "home-manager";
      };
      

3.1.2. Nix Config

Nix settings used by this flake. While these settings are already configured on all managed machines, documenting them here helps with initial setup and allows others to use this flake.

For detailed documentation on each setting, see https://nix.dev/manual/nix/latest/command-ref/conf-file.html

Note: flake.nix uses a restricted subset of the Nix language that prevents code reuse (see https://github.com/NixOS/nix/issues/4945). The values below are currently hardcoded in this section. Once machine configurations are migrated to org-mode, noweb references will allow sharing these values across both the flake and machine-specific settings.

  1. Binary Caches

    Binary cache (substituter) configuration for building this flake. While optional, configuring these caches significantly reduces build times by downloading pre-built binaries instead of compiling from source.

    Using extra-substituters and extra-trusted-public-keys instead of substituters and trusted-public-keys ensures this flake's cache configuration is additive rather than replacing the user's existing settings. This respects any caches the user has already configured in their nix.conf or system configuration.

    extra-substituters = [
      "https://natsukium.cachix.org"
      "https://nix-community.cachix.org"
    ];
    
    extra-trusted-public-keys = [
      "natsukium.cachix.org-1:STD7ru7/5+KJX21m2yuDlgV6PnZP/v5VZWAJ8DZdMlI="
      "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
    ];
    
    1. nix-community.cachix.org

      Community binary cache, primarily used for CUDA-related packages. Since November 2024, CUDA binaries are distributed under the nix-community namespace. Build status can be monitored at https://hydra.nix-community.org/project/nixpkgs

      For more details, see https://discourse.nixos.org/t/cuda-cache-for-nix-community/56038

      Alternatively, Flox's binary cache can be used for CUDA packages. As of September 2025, Flox has partnered with NVIDIA to obtain redistribution rights. See https://discourse.nixos.org/t/nix-flox-nvidia-opening-up-cuda-redistribution-on-nix/69189

    2. natsukium.cachix.org

      Personal binary cache containing outputs from this flake. Packages not available in the official cache.nixos.org or nix-community.cachix.org are built on GitHub Actions and pushed here.

3.1.3. Rest of Flake

outputs =
  { self, flake-parts, ... }@inputs:
  flake-parts.lib.mkFlake { inherit inputs; } {
    systems = [
      "x86_64-linux"
      "aarch64-linux"
      "aarch64-darwin"
    ];

    imports = [
      ./flake-module.nix
      inputs.git-hooks.flakeModule
      inputs.treefmt-nix.flakeModule
    ];

    hosts = {
      # main laptop (m1 macbook air)
      katavi = {
        system = "aarch64-darwin";
      };
      # build server (m1 mac mini)
      mikumi = {
        system = "aarch64-darwin";
      };
      # laptop for work (m4 macbook pro)
      work = {
        system = "aarch64-darwin";
      };
      # main desktop (intel core i5-12400F)
      kilimanjaro = {
        system = "x86_64-linux";
      };
      # WSL (dualboot with kilimanjaro)
      arusha = {
        system = "x86_64-linux";
      };
      # main server (intel N100)
      manyara = {
        system = "x86_64-linux";
      };
      # build server (OCI A1 Flex)
      serengeti = {
        system = "aarch64-linux";
      };
      # build server (Ryzen 9 9950X)
      tarangire = {
        system = "x86_64-linux";
      };
      # phone (pixel 7a)
      android = {
        system = "aarch64-linux";
        platform = "android";
      };
    };

    flake = {
      overlays = import ./overlays { inherit inputs; };
      templates = import ./templates;
    };

    perSystem =
      {
        self',
        config,
        pkgs,
        system,
        ...
      }:
      {
        _module.args.pkgs = import self.inputs.nixpkgs {
          inherit system;
          config.allowUnfree = true;
          overlays = [ self.inputs.nur-packages.overlays.default ] ++ builtins.attrValues self.overlays;
        };

        checks = import ./tests {
          inherit (self.inputs) nixpkgs;
          inherit pkgs;
        };

        packages = {
          fastfetch = pkgs.callPackage ./pkgs/fastfetch { };
          neovim = pkgs.callPackage ./pkgs/neovim-with-config { };
          po4a_0_74 = (
            pkgs.po4a.overrideAttrs (oldAttrs: {
              version = "0.74";
              src = pkgs.fetchurl {
                url = "https://github.com/mquinson/po4a/releases/download/v0.74/po4a-0.74.tar.gz";
                hash = "sha256-JfwyPyuje71Iw68Ov0mVJkSw5GgmH5hjPpEhmoOP58I=";
              };
              patches = [ ];
              nativeBuildInputs = oldAttrs.nativeBuildInputs ++ [ pkgs.libxml2 ];
              doCheck = false;
            })
          );
          html =
            with pkgs;
            let
              org-html-themes = fetchurl {
                url = "https://raw.githubusercontent.com/fniessen/org-html-themes/b3898f4c5b09b3365fd93fd1566f46ecd0a8911f/org/theme-readtheorg.setup";
                hash = "sha256-+5gy+S6NcuvlV61fudbCNoCKmSrCdA9P5CHeGKlDrSM=";
              };
              org-to-html = writeScript "org-to-html.el" ''
                (require 'org)
                (require 'htmlize)

                (find-file "README.org")
                (org-html-export-to-html)

                (find-file "README.ja.org")
                (org-html-export-to-html)
              '';
            in
            stdenvNoCC.mkDerivation {
              name = "dotfiles";
              src = lib.cleanSource ./.;
              postPatch = ''
                substituteInPlace README.org \
                  --replace-fail "https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup" "${org-html-themes}"
              '';
              nativeBuildInputs = [
                (emacs.pkgs.withPackages (epkgs: [ epkgs.htmlize ]))
                gettext
                self'.packages.po4a_0_74
              ];
              buildPhase = ''
                runHook preBuild
                po4a po4a.cfg
                emacs --batch -l ${org-to-html}
                runHook postBuild
              '';

              installPhase = ''
                runHook preInstall
                install -Dm644 README.html $out/index.html
                install -Dm644 README.ja.html $out/ja/index.html
                runHook postInstall
              '';
            };
        };

        pre-commit = {
          check.enable = true;
          settings = {
            src = ./.;
            hooks = {
              actionlint.enable = true;
              biome.enable = true;
              lua-ls.enable = false;
              nil.enable = true;
              shellcheck.enable = true;
              treefmt.enable = true;
              typos = {
                enable = true;
                excludes = [
                  "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;
                excludes = [
                  "secrets/default.yaml"
                  "secrets.yaml"
                ];
                settings.configData = "{rules: {document-start: {present: false}}}";
              };
              # Prefixed with "00-" to ensure this hook runs before all other hooks
              # (hooks are sorted alphabetically when no explicit before/after is specified)
              "00-check-org-tangle" = {
                enable = true;
                name = "check-org-tangle";
                description = "Verify org files are tangled and synchronized";
                entry =
                  let
                    checkScript = pkgs.writeShellScript "check-org-tangle" ''
                      set -euo pipefail

                      ${pkgs.gnumake}/bin/make -B tangle

                      # Check for differences using git diff
                      changed=$(${pkgs.git}/bin/git diff --name-only)

                      if [ -n "$changed" ]; then
                        echo "Org files were out of sync and have been auto-tangled."
                        echo "Changed files:"
                        echo "$changed"
                        echo ""
                        echo "Please stage the changes and commit again:"
                        echo "  git add $changed"
                        exit 1
                      fi

                      exit 0
                    '';
                  in
                  "${checkScript}";
                files = "\\.org$";
                pass_filenames = false;
              };
            };
          };
        };

        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;
          };
        };

        devShells = {
          default = pkgs.mkShell {
            packages = with pkgs; [
              gettext
              nix-fast-build
              self'.packages.po4a_0_74
              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
              ]))
            ];
            shellHook = config.pre-commit.installationScript + ''
              echo "Syncing CLAUDE.md..."
              make CLAUDE.md >/dev/null 2>&1 || echo "Warning: Failed to generate CLAUDE.md"
            '';
          };
        };
      };
  };

3.2. Overlays

3.2.1. Overview

This file defines nixpkgs overlays for patching broken packages, pinning specific versions, and adding local workarounds. Each overlay modifies the package set to address issues that would otherwise block the system build or cause runtime problems.

For details on overlay mechanics (final: prev: pattern, composition order, etc.), see nixpkgs overlay documentation.

Overlays are organized into four categories based on their purpose and expected lifetime:

  • stable: Packages fetched from nixpkgs-stable when broken in unstable and the fix would trigger excessive rebuilds or is too complex to patch locally.
  • temporary-fix: Local overrides (e.g., disabling tests) that don't require a different nixpkgs version. Remove once fixed upstream.
  • pre-release: Alpha, beta, or pre-release packages for testing before they land in nixpkgs.
  • patches: Workarounds not suitable for upstream contribution (e.g., locale-specific fixes, local tooling shims). Expected to remain indefinitely.
{ inputs }:
{
  stable = final: prev: {
    inherit (inputs.nixpkgs-stable.legacyPackages.${final.stdenv.hostPlatform.system})
      hercules-ci-agent
      ;
  };

  temporary-fix = final: prev: {
    python313 = prev.python313.override {
      packageOverrides = pyfinal: pyprev: {
        rapidocr-onnxruntime = pyprev.rapidocr-onnxruntime.overridePythonAttrs (_: {
          doCheck = false;
        });
        lxml-html-clean = pyprev.lxml-html-clean.overridePythonAttrs (_: {
          doCheck = false;
        });
      };
    };
  };

  pre-release = final: prev: { };

  patches = final: prev: {
    gh-dash =
      (final.writeShellApplication {
        name = "gh-dash";
        text = ''
          LANG=C.UTF-8 ${final.lib.getExe prev.gh-dash} "$@"
        '';
      }).overrideAttrs
        { pname = "gh-dash"; };
    inherit (final.callPackage ../pkgs/mkShim { }) mkShim commandLineToolsShim;
  };
}
  1. stable

    Fetch packages from nixpkgs-stable when broken in unstable. This includes cases where the upstream fix would trigger excessive rebuilds, or where the issue is too complex to patch locally.

    stable = final: prev: {
      inherit (inputs.nixpkgs-stable.legacyPackages.${final.stdenv.hostPlatform.system})
        hercules-ci-agent
        ;
    };
    
    1. hercules-ci-agent

      Build is broken on darwin in nixpkgs-unstable due to a dependency issue. A fix is proposed in NixOS/nixpkgs#463879, but as of 2025-11-30, the PR targets the staging branch and is still under review.

      Cherry-picking this fix locally isn't practical because the PR touches core darwin infrastructure, which would trigger rebuilds of numerous packages. Using the stable branch package avoids this rebuild cascade while we wait for the fix to land in master.

      Remove this override once the PR is merged and propagates to the master branch.

      hercules-ci-agent
      
  2. temporary-fix

    Local overrides for packages that build but have failing tests or minor issues. Unlike the stable overlay, these don't require fetching packages from a different nixpkgs branch. We simply override specific attributes (like doCheck) on the existing packages. Remove these overrides once the issues are fixed upstream.

    temporary-fix = final: prev: {
      python313 = prev.python313.override {
        packageOverrides = pyfinal: pyprev: {
          rapidocr-onnxruntime = pyprev.rapidocr-onnxruntime.overridePythonAttrs (_: {
            doCheck = false;
          });
          lxml-html-clean = pyprev.lxml-html-clean.overridePythonAttrs (_: {
            doCheck = false;
          });
        };
      };
    };
    
    1. Python313 package set

      Override problematic Python packages using packageOverrides.

      Python packages in nixpkgs form an interconnected dependency graph. The packageOverrides mechanism ensures that when a package is overridden, all dependent packages automatically see the modified version. This is essential for consistency—a direct overlay override (e.g., python313Packages.foo = ...) would only affect top-level access, leaving internal dependencies using the original broken version.

      See nixpkgs Python documentation for details.

      python313 = prev.python313.override {
        packageOverrides = pyfinal: pyprev: {
          rapidocr-onnxruntime = pyprev.rapidocr-onnxruntime.overridePythonAttrs (_: {
            doCheck = false;
          });
          lxml-html-clean = pyprev.lxml-html-clean.overridePythonAttrs (_: {
            doCheck = false;
          });
        };
      };
      
      1. rapidocr-onnxruntime

        The test suite causes a segmentation fault during execution. The root cause is still under investigation.

        This package is pulled in as a transitive dependency. Runtime functionality has been verified to work correctly in actual use, so disabling the test suite is a safe workaround.

        rapidocr-onnxruntime = pyprev.rapidocr-onnxruntime.overridePythonAttrs (_: {
          doCheck = false;
        });
        
      2. lxml-html-clean

        Tests fail due to breaking changes in libxml2 2.14, which modified how certain DOM operations handle whitespace and entity encoding. These changes cause test assertions to fail even though the actual HTML cleaning functionality works correctly.

        Tracked upstream in fedora-python/lxml_html_clean#24. Remove this override once the test suite is updated for libxml2 2.14 compatibility.

        lxml-html-clean = pyprev.lxml-html-clean.overridePythonAttrs (_: {
          doCheck = false;
        });
        
  3. pre-release

    Overlay for testing alpha, beta, or pre-release versions of packages before they land in nixpkgs. Useful for evaluating release candidates, nightly builds, or packages pending upstream review. Once a package is available in nixpkgs, remove it from this overlay.

    pre-release = final: prev: { };
    
  4. patches

    Workarounds that are not suitable for upstream contribution.

    These patches address issues that upstream would likely not accept—either because they are specific to this configuration (e.g., locale settings), bypass intended behavior, or solve problems in unconventional ways. Unlike temporary-fix, these are expected to remain indefinitely.

    patches = final: prev: {
      gh-dash =
        (final.writeShellApplication {
          name = "gh-dash";
          text = ''
            LANG=C.UTF-8 ${final.lib.getExe prev.gh-dash} "$@"
          '';
        }).overrideAttrs
          { pname = "gh-dash"; };
      inherit (final.callPackage ../pkgs/mkShim { }) mkShim commandLineToolsShim;
    };
    
    1. gh-dash

      The preview pane renders incorrectly when LANG=ja_JP.UTF-8 is set. The issue stems from gh-dash's terminal width calculation, which miscounts the display width of certain UTF-8 characters (particularly CJK characters and some emoji). This causes text wrapping and alignment to break.

      Setting LANG=C.UTF-8 forces ASCII-compatible width calculations while preserving UTF-8 encoding support, which fixes the rendering issue. We use writeShellApplication to create a wrapper that sets this environment variable before invoking the real binary.

      Reported upstream in dlvhdr/gh-dash#316.

      gh-dash =
        (final.writeShellApplication {
          name = "gh-dash";
          text = ''
            LANG=C.UTF-8 ${final.lib.getExe prev.gh-dash} "$@"
          '';
        }).overrideAttrs
          { pname = "gh-dash"; };
      
    2. mkShim

      Shim utility for providing stub implementations of macOS Command Line Tools.

      On darwin systems without Xcode Command Line Tools installed, invoking commands like cc or python3 triggers an annoying system popup prompting installation. These shims intercept such calls and either delegate to Nix-provided tools or return appropriate exit codes, suppressing the popup and preventing spurious build failures.

      See pkgs/mkShim for the implementation details and list of shimmed commands.

      inherit (final.callPackage ../pkgs/mkShim { }) mkShim commandLineToolsShim;
      

3.3. Emacs

3.3.1. early-init.org

  1. disable some bars
    (push '(tool-bar-lines . 0) default-frame-alist)
    (push '(vertical-scroll-bars) default-frame-alist)
    
  2. turn off the annoying bell
    (setq ring-bell-function 'ignore)
    
  3. disable backup files
    (setq make-backup-files nil)
    (setq auto-save-default nil)
    

3.3.2. init.org

  1. basic
    (set-face-attribute 'default nil
                        :family "Liga HackGen Console NF"
                        :height 140)
    (add-to-list 'default-frame-alist
    '(undecorated-round . t))
    (use-package doom-themes
      :ensure t
      :config
      (load-theme 'doom-nord :no-confirm))
    

    Enable visual-line-mode globally instead of using auto-fill-mode here to preserve logical line structure while still wrapping long lines visually for better readability without modifying the actual file.

    (visual-line-mode 1)
    

    Highlight the cursor line.

    (global-hl-line-mode 1)
    
    1. Darwin

      macOSでは環境変数($PATH など)を読み込めないためexec-path-from-shellを使ったワークアランウドが必要。 emacs-plusではEmacsのInfo.plistにPATHを挿入しており、そのパッチを当てればこのパッケージは不要となるかもしれない。

      (use-package exec-path-from-shell
        :ensure t
        :config
        (when (memq window-system '(mac ns x))
          (exec-path-from-shell-initialize)))
      
  2. UI
    1. mode-line
      (use-package moody
        :ensure t
        :config
        (moody-replace-mode-line-front-space)
        (moody-replace-mode-line-buffer-identification)
        (moody-replace-vc-mode))
      
    2. headerline
      (use-package breadcrumb
        :ensure t
        :config
        (breadcrumb-mode))
      
  3. minibuffer

    Referring to https://protesilaos.com/codelog/2024-11-28-basic-emacs-configuration/

    (use-package vertico
      :ensure t
      :hook (after-init . vertico-mode))
    
    (use-package marginalia
      :ensure t
      :hook (after-init . marginalia-mode))
    
    (use-package orderless
      :ensure t
      :config
      (setq completion-styles '(orderless basic))
      (setq completion-category-defaults nil)
      (setq completion-category-overrides nil))
    

    The built-in savehist package keeps a record of user inputs and stores them across sessions. Thus, the user will always see their latest choices closer to the top (such as with M-x).

    (use-package savehist
      :ensure nil ; it is built-in
      :hook (after-init . savehist-mode))
    
    (use-package corfu
      :ensure t
      :hook (after-init . global-corfu-mode)
      :bind (:map corfu-map ("<tab>" . corfu-complete))
      :config
      (setq tab-always-indent 'complete)
      (setq corfu-preview-current nil)
      (setq corfu-min-width 20)
    
      (setq corfu-popupinfo-delay '(1.25 . 0.5))
      (corfu-popupinfo-mode 1) ; shows documentation after `corfu-popupinfo-delay'
    
      ;; Sort by input history (no need to modify `corfu-sort-function').
      (with-eval-after-load 'savehist
        (corfu-history-mode 1)
        (add-to-list 'savehist-additional-variables 'corfu-history)))
    
    1. embark
      (use-package embark
        :ensure t
        :bind
        (("C-." . embark-act)         ;; pick some comfortable binding
         ("C-;" . embark-dwim)        ;; good alternative: M-.
         ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
      
        :init
      
        ;; Optionally replace the key help with a completing-read interface
        (setq prefix-help-command #'embark-prefix-help-command)
      
        ;; Show the Embark target at point via Eldoc. You may adjust the
        ;; Eldoc strategy, if you want to see the documentation from
        ;; multiple providers. Beware that using this can be a little
        ;; jarring since the message shown in the minibuffer can be more
        ;; than one line, causing the modeline to move up and down:
      
        ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
        ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
      
        ;; Add Embark to the mouse context menu. Also enable `context-menu-mode'.
        ;; (context-menu-mode 1)
        ;; (add-hook 'context-menu-functions #'embark-context-menu 100)
      
        :config
      
        ;; Hide the mode line of the Embark live/completions buffers
        (add-to-list 'display-buffer-alist
                     '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                       nil
                       (window-parameters (mode-line-format . none)))))
      
  4. version control system
    1. git
      (use-package magit
        :ensure t
        :bind
        (("C-x g" . magit-status)))
      
      (use-package diff-hl
        :ensure t
        :init
        (global-diff-hl-mode)
        (diff-hl-flydiff-mode)
        (add-hook 'dired-mode-hook 'diff-hl-dired-mode)
        (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))
      
  5. language support
    1. Markdown

      https://github.com/jrblevin/markdown-mode

      markdown-mode is a major mode for editing Markdown-formatted text.

      (use-package markdown-mode
        :ensure t
        :mode ("README\\.md\\'" . gfm-mode)
        :init (setq markdown-command "multimarkdown")
        :bind (:map markdown-mode-map
                    ("C-c C-e" . markdown-do)))
      
    2. Nix

      https://github.com/NixOS/nix-mode

      An Emacs major mode for editing Nix expressions.

      (use-package nix-mode
        :ensure t
        :mode "\\.nix\\'")
      
    3. PO

      po-mode is Emacs's major mode for editing GNU gettext PO (Portable Object) files. PO files store translations for software internationalization.

      Editing PO files as plain text is error-prone because the format has strict requirements for escaping and structure. po-mode provides structured navigation between entries, automatic validation, and prevents common formatting errors.

      1. Common Operations

        PO mode is not derived from text mode. The buffer is read-only and has its own keymap, so standard text editing commands do not work directly. Translations must be edited through the subedit buffer (RET).

        1. Main Commands

          Commands for file operations, validation, and general PO mode management.

          Key Function Description
          _ po-undo Undo last modification
          q po-confirm-and-quit Quit with confirmation
          ? h po-help Show help about PO mode

          Use or q to quit instead of C-x k (kill-buffer), as they properly handle unsaved changes and warn about untranslated entries.

          See Main PO mode Commands for more details.

        2. Entry Positioning

          Commands for navigating between entries in the PO file.

          Key Function Description
          n po-next-entry Move to next entry
          p po-previous-entry Move to previous entry
          < po-first-entry Move to first entry
          > po-last-entry Move to last entry

          See Entry Positioning for more details.

        3. Modifying Translations

          Commands for editing translation strings. Press RET to open a subedit buffer where standard Emacs editing works normally.

          Key Function Description
          RET po-edit-msgstr Open subedit buffer for editing
          C-c C-c po-subedit-exit Finish editing and apply changes
          C-c C-k po-subedit-abort Abort editing and discard changes
          DEL po-fade-out-entry Delete the translation

          See Modifying Translations for more details.

      2. Configuration
        (use-package po-mode
          :ensure t)
        

        The following configuration is based on Completing GNU gettext Installation.

        This registers .po files and files containing .po. in their name to automatically activate PO mode.

        (setq auto-mode-alist
              (cons '("\\.po\\'\\|\\.po\\." . po-mode) auto-mode-alist))
        (autoload 'po-mode "po-mode" "Major mode for translators to edit PO files" t)
        

        This enables automatic detection of the coding system for PO files, which allows Emacs to display international characters correctly.

        (modify-coding-system-alist 'file "\\.po\\'\\|\\.po\\."
                                    'po-find-file-coding-system)
        (autoload 'po-find-file-coding-system "po-mode")
        
  6. org
    1. Semantic Line Breaks

      Semantic Line Breaks (SemBr) is a writing convention where line breaks are placed at logical boundaries in sentences, such as after punctuation marks or between phrases. This makes diffs more meaningful in version control and improves readability without affecting the rendered output.

      The recommended line length is around 80 characters. I set this as an upper limit in the editor to prevent lines from becoming unnecessarily long.

      (add-hook 'text-mode-hook
                (lambda ()
                  (auto-fill-mode 1)
                  (setq fill-column 80)))
      
    2. org-capture
      (global-set-key (kbd "C-c c") 'org-capture)
      
      (setq org-root "~/dropbox/org/")
      
      (setq org-capture-templates
            `(("t" "Todo" entry
               (file+headline ,(concat org-root "todo.org") "Tasks")
               "* TODO %?\n  %i\n  %a")
              ("j" "Journal" entry
               (file+olp+datetree ,(concat org-root "journal.org"))
               "* %U\n%?\n  %i\n  %a")
              ("f" "Fleeting" entry
               (file ,(concat org-root "fleeting.org"))
               "* %?\n  %U\n  %i\n  %a")))
      
    3. org-agenda
      (global-set-key (kbd "C-c a") 'org-agenda)
      
      (setq org-agenda-files '("~/dropbox/org"))
      
    4. org-roam
      (use-package org-roam
        :ensure t
        :custom
        (org-roam-directory "~/dropbox/org-roam")
        (org-roam-db-location "~/.local/share/org-roam.db")
        :bind
        (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n g" . org-roam-graph)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         ("C-c n j" . org-roam-dailies-capture-today))
        :config
        (setq org-roam-capture-templates
              '(("p" "permanent" plain "%?"
                 :target (file+head "permanent/${slug}.org" "#+title: ${title}\n")
                 :unnarrowed t)
                ("l" "literature" plain "%?"
                 :target (file+head "literature/${title}.org" "#+title: ${title}\n")
                 :unnarrowed t)))
        (setq org-roam-node-display-template
              (concat "${title:*} "
                      (propertize "${tags:10}" 'face 'org-tag)))
        (org-roam-db-autosync-mode)
        (require 'org-roam-protocol)
        )
      
      
    5. htmlize

      Used when converting Org files to HTML with syntax highlighting for code blocks.

      C-c C-e h h exports the current Org buffer to HTML.

      (use-package htmlize
        :ensure t)
      
  7. misc
    1. vundo
      (use-package vundo
        :ensure t
        :bind (("C-x u" . vundo))
        :config
        (setq vundo-glyph-alist vundo-unicode-symbols))
      
    2. consult
      ;; Example configuration for Consult
      (use-package consult
        :ensure t
        ;; Replace bindings. Lazily loaded by `use-package'.
        :bind (;; C-c bindings in `mode-specific-map'
               ("C-c M-x" . consult-mode-command)
               ("C-c h" . consult-history)
               ("C-c k" . consult-kmacro)
               ("C-c m" . consult-man)
               ("C-c i" . consult-info)
               ([remap Info-search] . consult-info)
               ;; C-x bindings in `ctl-x-map'
               ("C-x M-:" . consult-complex-command)     ;; orig. repeat-complex-command
               ("C-x b" . consult-buffer)                ;; orig. switch-to-buffer
               ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
               ("C-x 5 b" . consult-buffer-other-frame)  ;; orig. switch-to-buffer-other-frame
               ("C-x t b" . consult-buffer-other-tab)    ;; orig. switch-to-buffer-other-tab
               ("C-x r b" . consult-bookmark)            ;; orig. bookmark-jump
               ("C-x p b" . consult-project-buffer)      ;; orig. project-switch-to-buffer
               ;; Custom M-# bindings for fast register access
               ("M-#" . consult-register-load)
               ("M-'" . consult-register-store)          ;; orig. abbrev-prefix-mark (unrelated)
               ("C-M-#" . consult-register)
               ;; Other custom bindings
               ("M-y" . consult-yank-pop)                ;; orig. yank-pop
               ;; M-g bindings in `goto-map'
               ("M-g e" . consult-compile-error)
               ("M-g f" . consult-flymake)               ;; Alternative: consult-flycheck
               ("M-g g" . consult-goto-line)             ;; orig. goto-line
               ("M-g M-g" . consult-goto-line)           ;; orig. goto-line
               ("M-g o" . consult-outline)               ;; Alternative: consult-org-heading
               ("M-g m" . consult-mark)
               ("M-g k" . consult-global-mark)
               ("M-g i" . consult-imenu)
               ("M-g I" . consult-imenu-multi)
               ;; M-s bindings in `search-map'
               ("M-s d" . consult-find)                  ;; Alternative: consult-fd
               ("M-s c" . consult-locate)
               ("M-s g" . consult-grep)
               ("M-s G" . consult-git-grep)
               ("M-s r" . consult-ripgrep)
               ("M-s l" . consult-line)
               ("M-s L" . consult-line-multi)
               ("M-s k" . consult-keep-lines)
               ("M-s u" . consult-focus-lines)
               ;; Isearch integration
               ("M-s e" . consult-isearch-history)
               :map isearch-mode-map
               ("M-e" . consult-isearch-history)         ;; orig. isearch-edit-string
               ("M-s e" . consult-isearch-history)       ;; orig. isearch-edit-string
               ("M-s l" . consult-line)                  ;; needed by consult-line to detect isearch
               ("M-s L" . consult-line-multi)            ;; needed by consult-line to detect isearch
               ;; Minibuffer history
               :map minibuffer-local-map
               ("M-s" . consult-history)                 ;; orig. next-matching-history-element
               ("M-r" . consult-history))                ;; orig. previous-matching-history-element
      
        ;; Enable automatic preview at point in the *Completions* buffer. This is
        ;; relevant when you use the default completion UI.
        :hook (completion-list-mode . consult-preview-at-point-mode)
      
        ;; The :init configuration is always executed (Not lazy)
        :init
      
        ;; Tweak the register preview for `consult-register-load',
        ;; `consult-register-store' and the built-in commands.  This improves the
        ;; register formatting, adds thin separator lines, register sorting and hides
        ;; the window mode line.
        (advice-add #'register-preview :override #'consult-register-window)
        (setq register-preview-delay 0.5)
      
        ;; Use Consult to select xref locations with preview
        (setq xref-show-xrefs-function #'consult-xref
              xref-show-definitions-function #'consult-xref)
      
        ;; Configure other variables and modes in the :config section,
        ;; after lazily loading the package.
        :config
      
        ;; Optionally configure preview. The default value
        ;; is 'any, such that any key triggers the preview.
        ;; (setq consult-preview-key 'any)
        ;; (setq consult-preview-key "M-.")
        ;; (setq consult-preview-key '("S-<down>" "S-<up>"))
        ;; For some commands and buffer sources it is useful to configure the
        ;; :preview-key on a per-command basis using the `consult-customize' macro.
        (consult-customize
         consult-theme :preview-key '(:debounce 0.2 any)
         consult-ripgrep consult-git-grep consult-grep consult-man
         consult-bookmark consult-recent-file consult-xref
         consult--source-bookmark consult--source-file-register
         consult--source-recent-file consult--source-project-recent-file
         ;; :preview-key "M-."
         :preview-key '(:debounce 0.4 any))
      
        ;; Optionally configure the narrowing key.
        ;; Both < and C-+ work reasonably well.
        (setq consult-narrow-key "<") ;; "C-+"
      
        ;; Optionally make narrowing help available in the minibuffer.
        ;; You may want to use `embark-prefix-help-command' or which-key instead.
        ;; (keymap-set consult-narrow-map (concat consult-narrow-key " ?") #'consult-narrow-help)
      )
      
      ;; Consult users will also want the embark-consult package.
      (use-package embark-consult
        :ensure t ; only need to install it, embark loads it after consult if found
        :hook
        (embark-collect-mode . consult-preview-at-point-mode))
      
    3. Others
      (which-key-mode)
      
      (setq-default indent-tabs-mode nil)
      
      (require 'org-tempo)
      
      (org-babel-do-load-languages
       'org-babel-load-languages
       '((shell . t)))
      
      (setq org-src-preserve-indentation t)
      
      1. project.el

        Register all repositories cloned by ghq (https://github.com/x-motemen/ghq) as projects.

        Specifically, directories under ~/src/$ACCOUNT/$VCS_HOST/$OWNER/$REPO.

        (defun my/sync-project-list ()
          "Find all projects under ~/src and synchronize the project-list-file."
          (interactive)
          (let* (;; 1. Retrieve directory list as a string using find command
                 (command (format "find %s -mindepth 4 -maxdepth 4 -type d"
                                  (expand-file-name "~/src")))
                 (dir-list-string (shell-command-to-string command))
                 ;; 2. Split string by newlines and exclude empty lines to create a list
                 (dirs (split-string dir-list-string "\n" t)))
        
            ;; 3. Build file contents in a temporary buffer
            (with-temp-buffer
              (insert ";;; -*- lisp-data -*-\n")
              (insert "(\n")
              (dolist (dir dirs)
                (insert (format " (\"%s/\")\n" dir)))
              (insert ")\n")
        
              ;; 4. Write the built contents to file
              (write-file project-list-file))))
        
        (my/sync-project-list)
        

4. Development

4.1. Translation

This project uses po4a to manage translations.

4.1.1. Requirements

  • gettext
  • po4a >= 0.74 (required for Org mode support)

4.1.2. Translation workflow

  1. 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] README.org $lang:README.$lang.org opt:"-k 0"
    [type: org] configuration.org $lang:configuration.$lang.org opt:"-k 0"
    [type: org] applications/emacs/init.org $lang:applications/emacs/init.$lang.org opt:"-k 0"
    [type: org] applications/emacs/early-init.org $lang:applications/emacs/early-init.$lang.org opt:"-k 0"
    

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

  2. 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
    
  3. Translate

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

  4. 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
    

Created: 2025-12-04 Thu 01:51

Validate