Skip to content
This repository was archived by the owner on May 3, 2026. It is now read-only.

Latest commit

 

History

History
153 lines (109 loc) · 5.58 KB

File metadata and controls

153 lines (109 loc) · 5.58 KB

LLMs

General code guidelines

These are instructions for LLMs to write code more to my liking. For general project layout, design, and desires, see ./README.org.

Documents

When authoring new content, make them org-mode files.

Use :exports code if you just want to show off the code, and don’t want it to actually run anything.

No file lists in documentation.

Comments

Formatting and punctuation

Comments must be complete sentences, wrapped at ~80 columns, with standard punctuation. Use two spaces after sentence-ending punctuation for easier sentence matching.

Content

Only comment non-obvious intent, invariants, tradeoffs, or historical constraints; no restating of control flow.

Code

initialisms

Use Pascal Initialisms (Url) instead of Preserved Initialisms (URL) when authoring new identifiers in camel case.

Network and Hosts

The local network uses the .proton domain suffix. Host names declared in nixos-modules/facts.nix are reachable at <hostname>.proton (e.g. rubidium.proton, scandium.proton). When SSH-ing to a host by short name, append .proton — e.g. ssh rubidium.proton.

Nix

Flake Inputs

Try to keep Flake inputs in lexicographical order.

When adding an new input, you don’t need to include it in the outputs section unless it’s specifically required in that section. This is afforded due to flake-inputs being aggregated and passed into the hosts using specialArgs.

File Layout

There are both <system-type>-configs and <system-type>-modules, where <system-type> is one of nixos, darwin, or home. This corresponds with whether or not it belongs to NixOS, macOS, or Home Manager.

configs are inert sets of configuration. They could use functions to calculate or compose settings, but the act of importing the config is what activates said config. These files do not include config or options attributes.

modules are configurable option sets. They use config and options attributes. Importing one of these simply makes the options available, and by itself doesn’t change anything for a host’s configuration.

I do have some inconsistencies wherein some nixos-modules are actually nixos-configs, but I didn’t have the split pattern at time of authorship of those.

Nix Build

A nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' invocation is almost assured to fail because there will likely be a mismatch between architecture and platform. See deploy tools for how to do a deployment, which will also do your build.

Scripts

Scripts that ship as part of a derivation are co-located with their default.nix in a subdirectory under derivations/, following the same convention as nixpkgs. The directory name, the script file name, and the derivation’s name (or pname) must all match.

The script file carries a shebang appropriate to its language so that editors recognise it and it can be executed directly outside of Nix (given the right interpreter and dependencies on PATH). Use the appropriate writers function for the script’s language — no custom installPhase needed. Each writer handles the shebang, installs the binary, and runs a linter at build time (shellcheck for shell, pyflakes for Python): writeShellApplication for shell scripts, writers.writePython3Bin for Python.

derivations/
  my-tool/
    my-tool       <- script with shebang; runnable directly
    default.nix   <- derivation; patches shebang, wires runtime deps

The older pattern of a bare .nix file in derivations/ reading its script from ../scripts/<name> still exists for historical scripts, but new scripts should use the co-located layout above.

Secrets

Secrets are managed with agenix-rekey. Only specify rekeyFile when there is a strong reason to control the output path (e.g. a secret that must live alongside a specific config file). For generated secrets, omit rekeyFile entirely — agenix-rekey will place them under secrets/generated/ automatically, which makes it easy to identify which secrets are generated vs. manually managed.

Secrets for systemd Units

Bind agenix secrets into systemd units using LoadCredential rather than setting owner on the secret. Systemd reads the source file as root and exposes it under /run/credentials/<unit-name>/<credential-name>, readable only by the service — no ownership tuning required. This is also the only correct approach when the service uses DynamicUser = true, where no stable uid exists to assign ownership to.

The consuming application config (e.g. password_file, credentials_file) must reference the /run/credentials/<unit-name>/<credential-name> path, not the raw agenix path.

age.secrets.my-secret = { };  # no owner needed

systemd.services.my-service.serviceConfig.LoadCredential = [
  "my-secret:${config.age.secrets.my-secret.path}"
];