Skip to content

Publish an age key

Nerthus.Core (until cutover). This page describes the frozen system that runs today and is deleted at cutover. Replaced by: Instance secrets.

The secret tree cannot hold a secret until at least one real recipient exists, and publishing one is an act only its holder can perform: the private half must never be transmitted, so nobody can do it for you.

The four steps

  1. Generate the keypair on your own device.
age-keygen -o ~/.config/sops/age/nerthus-sejf.key
chmod 600 ~/.config/sops/age/nerthus-sejf.key

The file starts with a comment line carrying the public half — # public key: age1….

  1. Keep the private half. It never leaves that device, never goes in a repository, never goes in a message, never goes in a password manager shared with anybody else. Losing it loses every value encrypted to it, permanently.

  2. Publish the public half yourself, as one line under your own block in the contributor store:

- @klucz_szyfr: age1…
  1. Ask for it to be added to .sops.yaml and the tree re-keyed. That is sops updatekeys over every file under ci/, edge/, hosts/ and services/.

Two things about that last step

Adding or removing a recipient rewrites every ciphertext in the tree. The diff is large and has no content change in it — which is exactly the diff nobody reviews carefully. Review the recipient: lines and the creation_rules block; the selftest checks the rest, and it fails if any file was left behind by an updatekeys that did not reach it.

Removing a recipient is not revocation. A person removed from .sops.yaml can still read every value they ever decrypted. Removing them stops them reading new ciphertext and takes nothing back. A credential a departing holder could read has to be re-minted, and whether it must be is a policy question rather than a mechanic. #/sejf/dostepy says the same sentence to a reader.

Provenance is a fact about how the key was made

A key is real when its holder generated it on their own device and published the public half themselves. That is not something a bech32 check can tell you and it is not something the grammar records, so it has to be asked.

The estate currently publishes nine @klucz_szyfr lines and not one of them is a real recipient: one in the lore contributor store and eight in the moderator store, each labelled a development seed by its own comment. Treating one of them as a recipient would encrypt production credentials to a key somebody generated on a laptop for a test.

(Counted 2026-08-21. Earlier pages said eight, from a count taken before the moderator store's last roster edit; the total is what a grep over both files answers today.)

And no fixture may use one. Until 2026-08-21 the dashboard's test fixtures carried the private halves of three of those published keys, which made a development seed a committed secret as well as a weak one. They are synthetic now, and Nerthus.Core.Dashboard/tests/fixtures-throwaway.test.ts asserts that no key in a fixture appears in any roster file — the rule was stated long before anything checked it.

How many recipients

At least two, and not two people who share a household or a device. A vault with one recipient is a vault one lost laptop empties — permanently, because nothing else can decrypt it. 17-sejf H5 rules on the archive-key shares separately; this is the ordinary case.

Getting a value back out

Being a recipient is what lets you read the tree, and The secret tree · Consume a credential is how. Short version: sejf/Invoke-SejfEnv.ps1 puts a credential in a child process's environment and nothing on disk; a tool that already uses Import-NerthusEnv.ps1 finds it automatically. Without an identity on the machine those tools say «key X: not in .env and no age identity» rather than failing vaguely — which is the message this page is the answer to.