ibizaman / selfhostblocks

Modular server management based on NixOS modules and focused on best practices.
https://shb.skarabox.com
GNU Affero General Public License v3.0
209 stars 6 forks source link

Are agenix secrets supported? #351

Open tbaumann opened 3 days ago

tbaumann commented 3 days ago

I started with something simple.

  shb.arr = {
    sonarr.enable = true;
    sonarr.settings.ApiKey = config.age.secrets.arr-api-key.path;
    sonarr.subdomain = "radarr";
    sonarr.domain = "rak.baumann.ma";
  };

My secrets are agenix or ragenix to be precise.


         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:413:36:

          412|       # modules recursively. It returns the final list of unique-by-key modules
          413|       filterModules = modulesPath: { disabled, modules }:
             |                                    ^
          414|         let

       … while calling anonymous lambda

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:439:31:

          438|           disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
          439|           keyFilter = filter (attrs: ! elem attrs.key disabledKeys);
             |                               ^
          440|         in map (attrs: attrs.module) (builtins.genericClosure {

       … from call site

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:400:22:

          399|           let
          400|             module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
             |                      ^
          401|             collectedImports = collectStructuredModules module._file module.key module.imports args;

       … while calling anonymous lambda

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:365:11:

          364|         else
          365|           m: m;
             |           ^
          366|

       … from call site

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:400:35:

          399|           let
          400|             module = checkModule (loadModule args parentFile "${parentKey}:anon-${toString n}" x);
             |                                   ^
          401|             collectedImports = collectStructuredModules module._file module.key module.imports args;

       … while calling 'loadModule'

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:336:53:

          335|       # Like unifyModuleSyntax, but also imports paths and calls functions if necessary
          336|       loadModule = args: fallbackFile: fallbackKey: m:
             |                                                     ^
          337|         if isFunction m then

       … from call site

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:354:14:

          353|           throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
          354|         else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
             |              ^
          355|

       … while calling 'unifyModuleSyntax'

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:454:34:

          453|      of ‘options’, ‘config’ and ‘imports’ attributes. */
          454|   unifyModuleSyntax = file: key: m:
             |                                  ^
          455|     let

       … from call site

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:354:59:

          353|           throw "Module imports can't be nested lists. Perhaps you meant to remove one level of lists? Definitions: ${showDefs defs}"
          354|         else unifyModuleSyntax (toString m) (toString m) (applyModuleArgsIfFunction (toString m) (import m) args);
             |                                                           ^
          355|

       … while calling 'applyModuleArgsIfFunction'

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:488:39:

          487|
          488|   applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }:
             |                                       ^
          489|     if isFunction f then applyModuleArgs key f args else f;

       … from call site

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/modules.nix:489:8:

          488|   applyModuleArgsIfFunction = key: f: args@{ config, options, lib, ... }:
          489|     if isFunction f then applyModuleArgs key f args else f;
             |        ^
          490|

       … while calling 'isFunction'

         at /nix/store/9g88fck8ggiah5znz5xn2kxzfr6l7cdq-source/lib/trivial.nix:929:16:

          928|   */
          929|   isFunction = f: builtins.isFunction f ||
             |                ^
          930|     (f ? __functor && isFunction (f.__functor f));

       error: getting status of '/run/agenix/arr-api-key': No such file or directory

Yea, that file doesn't exist at build time.

ibizaman commented 3 days ago

Hi! Sorry for the lack of documentation on this part. The configuration should be:

sonarr.settings.ApiKey.source = config.age.secrets.arr-api-key.path;

With the additional .source option.

This is needed because the type of the APIKey is secretFileType and that type is defined here, it's actually a submodule with a source field. And if you're curious this type is used because when I generate the config file, the .source field is used to identify what values are coming out of band.

Until more doc is there, you may want to get inspiration from the tests.

Now, TBH I'm not sure why you're getting such a convoluted error. That's definitely something that should be improved.

tbaumann commented 3 days ago

Ah thanks for the explanation. I had looked at other places how Secrets Files were used (ldap for example) and it was different.

Thanks for the hint about the tests. I was a bit worried about not finding a single example of how to use it anywhere on the net.

I was using nixarr so far. But I love what you did with the ApiKeys and other settings and I plan to use other selfhosted blocks in the future.

Perhaps if I may ask here, what's the right pattern to use the monitoring stack with multiple machines?

I have a bunch of different machines that should collect logs and metrics and the log host should aggregate them. Ideally with full knowledge of which services export metrics.

tbaumann commented 2 days ago

The media group didn't get created. Huh. In the code it looks like that's not conditional at all.

Also another dumb question, do I need to have a vhost per service? I was using mDNS .local addresses so far. It might be time for real DNS...

ibizaman commented 2 days ago

I had looked at other places how Secrets Files were used (ldap for example) and it was different.

I totally understand the confusion. Sorry about that. I'm slowly updating all places to use the secret contract.

nixarr

TIL about nixarr. That's a really nice project!

I love what you did with the ApiKeys and other settings

Thanks :) The method I'm using is quite universal, I use it in all the services in Self Host Blocks where it is relevant. You might be interested to know that I'm (quite slowly) upstreaming this part to nixpkgs https://github.com/NixOS/nixpkgs/pull/328472 so one day it'll be more widespread, hopefully.

I plan to use other selfhosted blocks in the future

That's really pleasing to hear. :heart: I'm working on mostly documentation right now so you might get better docs when you get to it. Otherwise, feel free to open an issue or join the matrix channel to ask question. I mean instead of guessing the options is they're undocumented. Oh that makes me think this page lists all options, even those that don't have an accompanying example or manual. https://shb.skarabox.com/options.html You might get some inspiration there already.

monitoring stack with multiple machines

That's something I've not yet well investigated. I mean, since Self Host Blocks uses the same machinery as nixpkgs, it won't help you to manage a cluster of machines. I've seen projects manage IPs of servers and other cluster related values but don't remember their names right now.

The best I could tell you right now is to add yourself a scraper like so:

services.prometheus.scrapeConfigs = [
  {
    job_name = "my other server - netdata";
    metrics_path = "/api/v1/allmetrics?format=prometheus&help=yes&source=as-collected";
    static_configs = [
      {
        targets = ["192.168.1.150:19999"];
      }
    ];
  }
];

You'll need to hardcode the IP in some way. It could be done more dynamically by having a DNS server you control.

There is a monitoring block in Self Host Blocks. What I do on my server is enable it and add some scrapers for custom jobs.

The media group didn't get created.

Yeah that's dumb. I made it work for my use case a while ago and didn't go back in that module for a while. I suppose it's failing because it's telling you the media group does not exist?

Actually in my own config there's no mention of creating the media group. Neither in the tests. What error do you get exactly?

do I need to have a vhost per service

Not necessarily. On my DNS provider, I have a two AAAA records. One from the my domain name, say example.com, to my IP address and one for all subdomains, say *.example.com, to my IP address. That's enough for the external config.

Then, that will reach at some point your router and Nginx will pick up the request and redirect it to the correct service using the subdomain.

tbaumann commented 2 days ago

monitoring stack with multiple machines

That's something I've not yet well investigated. I mean, since Self Host Blocks uses the same machinery as nixpkgs, it won't help you to manage a cluster of machines. I've seen projects manage IPs of servers and other cluster related values but don't remember their names right now.

The best I could tell you right now is to add yourself a scraper like so:

I think it can be done with NixOps. I did once try to get the ball rolling with this question https://discourse.nixos.org/t/datacenter-abstraction-service-discovery/54802/

The nix-topology module does seem to do a similar task. They even attempt to discover what services are exposed on a machine. (in a very ugly way)

My scrape config is hardcoded as well at the moment. https://github.com/tbaumann/nix-conf/blob/ada81cf338e9cc2d41664d356da9096ef1b750dd/hosts/nas/default.nix#L92

I will use the selfhost blocks to set up the server I guess. That would improve a lot for me already, especially the grafana bootstrap stuff.

The media group didn't get created.

Yeah that's dumb. I made it work for my use case a while ago and didn't go back in that module for a while. I suppose it's failing because it's telling you the media group does not exist?

Actually in my own config there's no mention of creating the media group. Neither in the tests. What error do you get exactly?

Specifically agenix complains that the group media isn't valid for the secrets file. I can see why

› getent group media

~ 
› getent passwd sonarr
sonarr:x:274:274::/var/lib/sonarr:/run/current-system/sw/bin/nologin

But I also think exraGroups should declare the group. I need to think a bit more...

tbaumann commented 2 days ago

yup, group needs to be declared not just referenced.

users.groups.media = {}; 
› getent group media
media:x:974:sonarr