How to edit variables - Updating templating configuration

So, move on in my configuration, I need to write down the configuration file in a way to keep the immutability of the file. However, on this configuration file, I need to use a variable that will be defined only when my activation script runs.
Let me share the code example to be more clear.

let
  someServiceConfigFile = nixpkgs.writeText "someService.conf"
  ''
    <some configurations here>
    <some other configuration> $variable_A
    
  '';
in
{
  system.activationScripts.script.text = ''
   variable_A=${variable_B}
    if [ "$(cat /etc/ec2-tags/name)" == "instance-1" ]; then
      variable_A="''${variable_A/0./1.}"
    else
      variable_A="''${variable_A/0./2.}"
    fi
  '';

  someService.config = "config ${someServiceConfigFile}";

So, the system.activationScripts is working fine (thanks guys for help), but as the code shows i need to user the variable_A on my someServiceConfigFile, however this variable will be created when the activation scripts runs, and when the activation script runs, the someServiceConfigFile was already compiled.
I’m stuck on this, any ideas?

The simple answer is that you can’t do that :slight_smile: Nix roughly works in three stages:

  • Evaluation, which takes your Nix code and turns it into build recipes, derivations
  • Building, which takes your derivations and builds them, turning them into store paths containing useful stuff
  • Running, which takes the built stuff and runs it, generally a binary, or NixOS in your case (or an activation script more specifically)

Notably there’s no way for a later stage to influence an earlier one.

Typically requiring this hints at the potential at a better solution. In your case this could be:

  • Making variable_B be known at evaluation time or build time, so that it can influence the build result of someService.conf
  • Making the contents of someService.conf only be required at runtime, e.g. by building a script that returns the configuration file contents
  • Making the activation script calculation happen either at evaluation time or build time

I also want to point out there’s only very few cases where extending the activation script is a good idea. I can’t judge your use since you redacted it, but if you need something to happen at runtime, systemd services work really well for that too.

1 Like

This sounds like an A/B problem :wink: What is it you’re actually trying to achieve here?

In the last thread it looked like you were trying to set an IP address based on the contents of /etc/ec2-tags/name. I’m guessing this is intended for some kind of AWS deployment, and that that file only becomes available at runtime.

If so your best bet is indeed to write a systemd service that does whatever that variable is intended to be used for at runtime.

But it’s worth thinking about whether you can determime what will be needed before you deploy.

1 Like

Yes, the ā€˜variable_B’ is available at the earliest stage, however this variable at the earliest stage was not evaluated and update yet. This variable will be only evaluated and updated at the activation stage, this because I just have the host name available at the activation stage. So I need to make the configuration file immutable, however I need a parameter (a variable) that will be available only at the activation stage (due the constraint of set this variable based on the host name using activationScripts).

What Im trying to achieve is make a immutable configuration file the uses a custom variable which is defined based on the host name. Like, if the hostname is equal instance-1 then the ip on the variable is going to be 10.10.1.0 else the variable is going to be 10.10.2.0, this logic to set this variable was achieved with you guys help on my previous posts, the challenge now is make a imutable configuration file that uses this variable, however this variable 8s set only at the activation stage.

Yep, but taking a step away from the details of the variable, and looking at the higher level, what are you trying to do with the variable? What is its purpose?

If this is some kind of networking setup based on a tag it sounds like it should really be handled on the network level, rather than in an individual host’s configuration.

1 Like

No, its not network configuration, This will be used to set configuration to a vpn service.

So each host needs to talk to a different VPN server based on what its hostname is? Then this should probably be handled in whatever service starts the VPN connection.

Can you share your draft for that? Do you intend to use services.openvpn.servers?

1 Like

Yes, Im going to use service.openvpn.server. And this config is to set the ip range for each server, not for the clients.

server variable_A subenetMask

I do not have draft in a repo.

Right, this is indeed a bit awkward. Nixpkgs modules won’t do things like this without a bit of downstream massaging.

I basically see two high level options:

  1. Just deploy two different configurations to the hosts. This is particularly conveniently doable with flakes/nixops & co. You just need to specify which deployment goes where at deploy time, which moves this logic out of the server config and into something you can probably better control.
  2. Deploy both configurations to both servers as different openvpn servers, but modify the openvpn systemd services so they only start conditionally.

Can’t help with the former, since that requires doing things in your deploymemt system, but for the latter you’d do something like:

let
  make-config = ip: ''
    # <all kinds of settings here>
    server ${ip} subenetMask
  '';
in {
  services.openvpn.servers = {
    instance-1 = {
      config = make-config "10.10.1.0";
    };

    instance-2 = {
      config = make-config "10.10.2.0";
    };
  };

  # Very verbose; you can probably write a really easy function to map these based on the openvpn servers, instead of repeating those host names, but I'm lazy.
  systemd.services.openvpn-instance-1.serviceConfig.ConditionHost = "instance-1";
  systemd.services.openvpn-instance-2.serviceConfig.ConditionHost = "instance-2";
}

Or something along those lines. I’m not sure how ec2 tags exactly work off the top of my head, but if they don’t actually set the hostname obviously ConditonHost won’t work.

If so, maybe you can use your activation script to touch a file in /run and check for it using ConditionPathExists instead. Or you pass them in as an EnvironmentFile.

Alternatively you could of course modify the ExecStart and put a script in there that first checks the file or something else along those lines. The possibilities are as endless as the systemd service options :wink: I think using Condition* is less invasive, though, which should make maintaining it easier.

Either way, propagating a variable from the activation scripts is probably not the way to go, and you definitely can’t modify a string nix has already evaluated at run time with just nix evaluation.

1 Like

Unfortunately did not work.

Yep, it’s more of a draft, I don’t imagine it will just work, figuring it out properly will take a bit of trial and error. What does ā€œnot workā€ mean specifically?

Still get a bunch of errors, I’ve tested all the scenarios and got a bunch of different errors.
So, its simple impossible to make a immutable file with custom information on it?
Just asking because looks like so ā€œsimpleā€ do do in theory… Its just a file, immutable file, that contains a variable on it.

That’s easy, that can be done with pkgs.writeText. What you’re asking for is modifying an immutable file based on runtime information, which is by definition of ā€œimmutableā€ impossible.

If you want the application to treat something in a file as a variable, you need application support for parsing that variable, and a different mechanism of supplying it. Openvpn does not support this, and even if it did would still require systemd config.

I suppose you could make a template file? But that still requires following something roughly like one of my sketches, so you wouldn’t be happy with that either?

If you could design your dream API, what would it look like? Maybe it is possible, or if it is indeed not I can point out where the gap is.

How can i make and use a template file? This looks like one option.
Can I use the fiel creation block after the ā€œINā€ statement?
Something like this:

let
  
in
{
  system.activationScripts.script.text = ''
   variable_A=${variable_B}
    if [ "$(cat /etc/ec2-tags/name)" == "instance-1" ]; then
      variable_A="''${variable_A/0./1.}"
    else
      variable_A="''${variable_A/0./2.}"
    fi
  '';

   someServiceConfigFile = nixpkgs.writeText "someService.conf"
    ''
    <some configurations here>
    <some other configuration> $variable_A
    
   '';

  someService.config = "config ${someServiceConfigFile}";

I,ve tried but a error rises saying someServiceConfigFile variable not declared

To be more precise this is the error:

 error: The option 'someServiceConfigFile' does not exist. Definition values:
          - In '<nix path>': <derivation '<nix path>'-someServiceConfigFile.conf.drv>