Nix Config Secrets logo

Nix Config Secrets

CommunityPopular
ryan4yin
nix-config-secrets

Use when adding, changing, renaming, or removing an agenix secret, deciding whether a secret belongs to agenix or to the encrypted dotfiles sync, wiring a secret into a host, or fixing a decryption or activation failure in this repo.

Overview

Publisherryan4yin
Repositorynix-config
Skill namenix-config-secrets
Stars
2.1K
Forks
104
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by ryan4yin on GitHub. Read the source before you install it.

Installation

Install the Nix Config Secrets AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/ryan4yin/nix-config/tree/main/.agents/skills/nix-config-secrets
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/ryan4yin/nix-config.git /tmp/nix-config
mkdir -p .claude/skills
cp -r /tmp/nix-config/.agents/skills/nix-config-secrets .claude/skills/nix-config-secrets
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Nix Config Secrets in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Nix Config Secrets on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Nix Config Secrets is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Working with secrets

Secrets are age-encrypted files in the private repository ~/codes/nix-secrets (git@github.com:ryan4yin/nix-secrets.git), pulled in as the mysecrets flake input and declared here in secrets/nixos.nix and secrets/darwin.nix. No secret value or ciphertext is ever stored in this public repository.

Read secrets/README.md for the concepts and the recipient rule.

Which flow a file belongs to

Two age flows live in ~/codes/nix-secrets, and a file belongs to exactly one.

  • agenix (secrets.nix): static and one-way, repository → host, decrypted at activation. Consumed as /run/agenix/<name> or an environment.etc copy; needs a flake.lock bump.
  • dotfiles sync (dotfiles/): changes on the host; save/restore are manual, outside Nix. Consumed as a real file under $HOME, edited by hand or by its tool; no lock bump.

agenix is for the secrets that barely change and that a Nix module or service reads from the decrypted path. Everything else is a synced dotfile: Nix does not read it, it changes often, or it is a config file its tool keeps rewriting that happens to carry a few secrets inside. The pin decides it, not the encryption: flake.lock fixes the agenix revision, so every edit costs a rebuild and switch on every desktop.

Nothing in this flake evaluates the sync flow: no secrets.nix entry, no agenix -r, no lock bump, so the three-step order in §2 does not apply to it. Its manifest and file list live in ~/codes/nix-secrets/dotfiles/; this repository carries the pointer, never the inventory. Do not list synced paths here or copy one into a Nix module. The flow covers the desktops only; do not set it up on a server or a cluster node.

nushell-secrets.nu already moved to ~/.secrets/nushell-secrets.nu. It has no agenix entry and must not get one back.

Core rules

  1. Never read a decrypted secret. Reference its path; do not cat, copy, or print it. Prove a change with metadata (mode, owner, timestamps), never with content. The same holds for a file the sync flow owns: it is a live credential under $HOME, and its path is listed in the private repository, not here.
  2. Three steps, in order. The private repository changes first, then this repository's flake.lock moves mysecrets to that commit, then the declaration and consumer change here. file = "${mysecrets}/x.age" resolves against the locked revision, so a declaration that lands before the lock bump points at a file that does not exist yet.
  3. Every secret stays decryptable by the desktops. A recipient set is desktop_keys ++ <the hosts that need it>; recovery_key is a member of desktop_keys. The one exception is restic-password-desktop.age, readable by desktop_keys alone.
  4. A consumer sits behind the same gate as its secret. A secret declared under modules.secrets.desktop only exists on desktops, so only a desktop-gated module may reference it. Desktops and servers are mutually exclusive: secrets/nixos.nix asserts that a host never enables desktop together with a server group, so a value both sides need takes a deliberate choice rather than enabling both groups.
  5. Least privilege on the decrypted file. Pick the narrowest mode that works, and set the owner together with the mode.

1. Where the pieces live

  • Recipient list, encrypted files, agenix CLI: ~/codes/nix-secrets (private; git@github.com:ryan4yin/nix-secrets.git)
  • The pinned revision of that repository: flake.lock, input mysecrets
  • Declaration (file, mode/owner, /etc copy): secrets/nixos.nix, secrets/darwin.nix
  • Which host gets which group: modules.secrets.<group>.enable in outputs/<system>/src/<name>.nix or a host module
  • Consumers: modules reading config.age.secrets."<name>".path (default /run/agenix/<name>)
  • Decryption key: age.identityPaths — the host's SSH host key; /persistent/etc/ssh/... on a preservation host
  • Secret-bearing dotfiles edited in place: ~/codes/nix-secrets/dotfiles/ — private, not a flake input, no entry in secrets.nix

The private repository keeps its .age files in desktop/ (only desktops decrypt them), server/ (any server), and certs/; public/ holds publishable plaintext, not .age.

2. Add or change a secret

Do the mechanical work yourself: edit secrets.nix in ~/codes/nix-secrets, run the non-interactive steps, bump the lock, and add the declaration and consumer. For a new secret, or a full replacement, pipe the plaintext into the replace recipe: it removes the target first, so agenix only encrypts and needs no sudo. Hand back only what needs a human: the partial edit and the rekey recipes in secrets/Justfile (they decrypt the current value, so they handle key material, $EDITOR, and sudo) and anything else under sudo. Commits and pushes follow the global git rules.

  1. In ~/codes/nix-secrets, add the file under desktop/ or server/ with a matching secrets.nix entry, keyed by that exact path (e.g. "./desktop/xxx.age"), and the recipient set from core rule 3. Then encrypt it with the replace recipe (§3); ask the user only when the plaintext has to come from an interactive session.
  2. Here: just upp mysecrets (commits the lock) or nix flake update mysecrets (leaves it for you to commit). git diff flake.lock should show only mysecrets moving.
  3. Declare it in secrets/nixos.nix or secrets/darwin.nix under the right modules.secrets.<group> gate, with a mode/owner preset (§4).
  4. Point the consumer at the runtime path, from a module behind the same gate (core rule 4).
  5. just test and just build-host <host>, deploy, then verify as in §5.

Changing only a secret's value is steps 1, 2, and the deploy: use replace for a full replacement (the agent can run it) or edit for a partial change (needs the host key).

3. The private repository

The agenix operations run against ~/codes/nix-secrets. Their single source is secrets/Justfile, which pins the identity, option order, and $EDITOR; run them from this repository's root and keep each path identical to its key in secrets.nix:

bash
just -f secrets/Justfile replace ./desktop/xxx.age < plaintext   # add or fully replace (no sudo)
just -f secrets/Justfile edit ./desktop/xxx.age                  # partial edit (sudo, $EDITOR)
just -f secrets/Justfile rekey                                   # re-encrypt after a recipient change

edit and rekey decrypt the current value with the host key, so they need sudo; replace only encrypts to the recipients in secrets.nix, so it needs no sudo. edit passes EDITOR=hx to the root agenix process, because sudo resets the environment and the invoking user's EDITOR would not reach it; do not add sudo -E or set EDITOR yourself.

4. Modes and ownership

Use one of the presets in secrets/nixos.nix (noaccess, high_security, user_readable); the table and the environment.etc copy trap are in secrets/README.md. The rule to remember: whenever an environment.etc entry sets mode, it also sets user. Never widen a mode to make a root-owned copy readable.

5. Verify without reading

bash
stat -c '%a %U:%G' /run/agenix/<name>    # mode and owner, not content (the default path)
ls -l /run/agenix/                       # /etc/agenix/ holds only the environment.etc copies
journalctl -b | grep -5 agenix                                  # NixOS
tail -n 100 /Library/Logs/org.nixos.activate-agenix.stderr.log  # macOS

A successful activation is silent. Check that the access changed as intended (the mode and owner you set, an old copy gone) instead of assuming activation did it.

6. Remove or rename a secret

  • Search all references before changing it:

    bash
    grep -Rni '<secret-name>' --include='*.nix' --include='*.toml' .

    Check secrets/, home/, modules/, hosts/, outputs, tests, and the private repository.

  • Delete the age.secrets entry, its environment.etc placement, and every consumer in one change; a declaration whose file no longer exists breaks activation. On darwin also drop the path from the chown/chmod list in system.activationScripts.postActivation.

  • Remove it from secrets.nix and delete the file in the private repository, then bump the lock.

  • A rename changes the attribute name and every consumer. The .age filename is separate.

  • Moving a secret out of agenix into the sync flow is that deletion plus what agenix used to guarantee: the consumer points at a $HOME path, and on a preservation host the directory joins preservation.preserveAt, which also sets its mode. A sourced file that may be missing is left to fail: nushell has no conditional source (nushell#8214), and a host without the file is misconfigured, not half-working.

7. Failure modes

  • Decryption fails on one host: its host key is not a recipient. Add its /etc/ssh/ssh_host_ed25519_key.pub to secrets.nix, rekey (§3), push, bump the lock, redeploy.
  • Eval or activation cannot find the file: the lock still points at a mysecrets revision without it (core rule 2).
  • permission denied in a user service: it reads a root-only secret. Fix that secret's owner; do not widen the mode.
  • A preservation host cannot decrypt at boot: age.identityPaths must use the /persistent/etc/ssh/... path, which exists before preservation mounts /etc.
  • The first macOS activation fails on the /etc/agenix copies, because they run before activate-agenix has decrypted anything. Run it again.
  • Nushell exits 1 with File not found and the shell has no aliases: the sourced secret file is missing. agenix guaranteed the path; the sync flow does not, and the failure is left loud on purpose. Restore the file on that host. Never silence it with an empty placeholder: the sync tool reads that as a local edit and reports a conflict.
  • A secret is gone after a reboot on a desktop: it moved out of agenix and its $HOME directory is not in preservation.preserveAt, so the tmpfs root drops it.

Frequently asked questions

What does the Nix Config Secrets AI skill do?

Use when adding, changing, renaming, or removing an agenix secret, deciding whether a secret belongs to agenix or to the encrypted dotfiles sync, wiring a secret into a host, or fixing a decryption or activation failure in this repo.

Why use Nix Config Secrets on TypingMind?

Because you install it once and use it with any model. Nix Config Secrets is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Nix Config Secrets in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/ryan4yin/nix-config/tree/main/.agents/skills/nix-config-secrets. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Nix Config Secrets?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Nix Config Secrets?

As many as you like. As long as a model supports skills, you can use Nix Config Secrets with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Nix Config Secrets AI skill free?

Yes. It is published on GitHub by ryan4yin under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇