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:
- English: https://natsukium.github.io/dotfiles/
- Japanese: https://natsukium.github.io/dotfiles/ja/
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
| Name | Platform | Device | Role |
|---|---|---|---|
| kilimanjaro | NixOS (x86_64) | i5-12400F / RTX 3080 | Main desktop |
| tarangire | NixOS (x86_64) | Ryzen 9 9950X | Build server |
| manyara | NixOS (x86_64) | Beelink Mini S12 | Home server |
| arusha | NixOS (x86_64) | WSL2 | WSL environment |
| serengeti | NixOS (aarch64) | OCI A1 Flex | Build server |
| katavi | macOS (aarch64) | M1 MacBook Air | Main laptop |
| work | macOS (aarch64) | M4 MacBook Pro | Work laptop |
| mikumi | macOS (aarch64) | M1 Mac mini | Build server |
| android | nix-on-droid | Galaxy S24 FE | Phone |
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. 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.
Development
This repository provides a Nix development shell with all the tools needed for working on the configurations. Enter the shell by running:
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.
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" ''; }; };
Translation
This project uses po4a to manage translations.
Requirements
The required packages are included in the development shell.
gettext self'.packages.po4a_0_74
gettext: provides msgfmt and other internationalization utilitiespo4a_0_74: po4a >= 0.74 is required for Org mode support. Using a pinned derivation since nixpkgs ships an older version (see packages).
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] 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"
[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.