I am looking for a way to have a public flake that is reusable as inputs in other flakes, but some parts of the flake are private. I think this is a very common thing, people sharing their config, but don’t want to share their secrets of course or some network settings. If those private parts are just plain files, then agenix or sops can be used, encrypted versions of those secrets are published (still kinda meh, but okay). The problem arises if some nix code should be concealed as well.
Here are the partial solutions I see to this problem:
Git Annex
Git Annex allows you to mark certain files as ‘large’, causing only a pointer to be stored in git instead of the actual content. By default that pointer is a symlink into .git/annex/objects/..., but that doesn’t work with nix, so one needs to git annex unlock the file into a pointer file with content /annex/objects/<hash>.ext that a git smudge filter turns into the proper content on checkout.
This is what I do currently: https://gitlab.com/nobodyinperson/nixconfig
Good:: Any file can be selectively concealed. Only the hash of the file is public. Adding garbage comments to the file helps the brute-forcing paranoia. Already-encrypted public files such as age secrets get another layer of obscurity, which doesn’t harm.
Good: Git annex provides many other benefits, most notably a convenience command git annex assist [-m "these changes"] that will pull, merge, push to all configured remotes and ‘do the git stuff’ in one command.
Bad: If the git tree is clean (everything nice and committed), nix only sees the pointer files as it just copies the git-tracked content into the nix store… That means one needs to (remember to) manually dirtify the tree before doing anything. Very annoying, but workable.
Bad: People reusing the flake will hit eval errors because the private nix are not present on their machine (which is good!). Maybe this is handleable with something like this:
nix-repl> isAnnexedPointerFile = path: (builtins.substring 0 15 (builtins.readFile path)) == "/annex/objects/"
nix-repl> isAnnexedPointerFile ./modules/secrets/setup.nix
true
and then conditionally importing or doing graceful fallbacks where the secrets are used. I’d have to try though.
Private git repo as input, git submodule or fetched via builtins.fetchGit
{
inputs.privateStuff.url = "git+ssh://myserver.com/...";
# or as submodule
self.submodules = true; # include submodules (build with nix>=2.27!)
}
# or manual builtins.fetchGit
Good: This would allow private parts to stay non-public completely (still ending up verbatim in the local nix store of course). No manual git-tree-dirtifying shenanigans necessary as for git annex.
Bad: Flake is not reusable. People will hit fetch errors. Apparently builtins.tryEval also cannot catch errors like this to handle gracefully?
The public-private git subtree stunt
While writing this up, I just found this approach, with a funny flake.lock-symlinking trick and including a public flake as git subtree (i.e. duplicating it the git way) in a private repo. Maybe I don’t understand it fully, but I don’t really see how that differs from just having one public and one private flake.
Good: Public flake is nicely reusable, private stuff stays private
Bad: Manual work to keep public and private flake pushed and synced, either with subtree or submodule approach. E.g. with the git annex approach, it’s really just git annex assist -m "these changes" and the state is synced to every configured remote. EDIT:
Although, git annex assist in the private all-encompassing flake should still work as usual, right? And one could selectively push the subtree to the public repo if one feels like it, this reconsideration might also help prevent accidental leakage of stuff that should have landed in the private part…
Bad?: The public part might need massaging, e.g. a function that produces a certain nixosConfiguration with options for customization. Or maybe just recursive merging can work.
Thinking about it, this last approach might be good. Subtree shouldn’t be necessary, a submodule should be fine or even just using the public repo as url input (kills interactivity though…). And input following setup will be a mess, which is why they symlinked the flake.lock, makes sense…
Any other ideas or approaches I might be missing?