I keep a golangci-lint config that I share across my Go projects. Sync’ing it around gets old fast: I have to remember which repos use it and update them all.
Devenv already manages my dev shells. Turns out it can also fetch and install config files automatically.
The idea π
pkgs.fetchurl downloads a file at build time and pins it by hash.
Devenv tasks can run before the shell starts.
Combine the two: fetch a remote config, symlink it into the project.
The setup π
Here is a minimal Go project using this pattern.
# devenv.nix
{
pkgs,
...
}:
let
golangci-lint-config = pkgs.fetchurl {
url = "https://example.com/.golangci.yaml";
hash = "sha256-AAAA...";
};
in
{
packages = with pkgs; [
git
go
golangci-lint
];
tasks."config:install" = {
before = [ "devenv:enterShell" ];
exec = "ln -sf ${golangci-lint-config} .golangci.yaml";
};
}What happens:
pkgs.fetchurldownloads the config and stores it in the nix store.- The
config:installtask runs beforedevenv:enterShell. - It symlinks the nix store path to
.golangci.yamlin the project root.
When you enter the shell (via direnv or devenv shell), the config is
already in place. golangci-lint run picks it up.
Updating the config π
When the upstream config changes, the hash no longer matches. The process would be: clear the hash, rebuild, paste the new one.
One could also imagine using a versioning system’s website with a commit in the URL, for even more reproducibility.
Scaling it π
The pattern works for any config file. Fetch multiple files, symlink them all:
let
golangci-config = pkgs.fetchurl { ... };
editorconfig = pkgs.fetchurl { ... };
in
{
tasks."config:install" = {
before = [ "devenv:enterShell" ];
exec = ''
ln -sf ${golangci-config} .golangci.yaml
ln -sf ${editorconfig} .editorconfig
'';
};
}Trade-offs π
The symlink is read-only. You can’t edit the config in the project dir. That is the point: one source of truth. So editing the config must be done at the source.
The hash pin means you get reproducible builds. No surprise config changes. But you do have to update the hash manually when you want the latest version.
CI that would constantly pull a new version would fail. But that’s an acceptable tradeoff.