Dear all,
I’m learning Nix and my config gets better every day. I use it to manage remote machines with GitHub - serokell/deploy-rs: A simple multi-profile Nix-flake deploy tool.. The machines share some config in JSON files, which is more or less the same. So I made some functions that can take one or more parameters and generate the config files. In a machine config I now have:
{ config, ... }:
let
signalKSelf = "vessels.urn:mrn:imo:mmsi:244770688";
tools = import
../../../../modules/tools; # TODO: must be a better way to import the functions
in {
imports = [ ./postgresql.nix ];
sops.secrets = {
mosquittoPassword = { };
postgresqlGoskPassword = { };
};
services.gosk = {
enable = true;
processors = rec {
# connectors
connectAis = {
args = "connect";
config = {
name = "AIS";
protocol = "nmea0183";
url = "tcp://127.0.0.1:10110";
};
after = [ "ais-catcher.service" ];
};
connectAmpero = {
args = "connect";
config = {
name = "Ampero modules";
protocol = "modbus";
url = "rtu:///dev/ttyUSBCom2";
baudRate = 19200;
dataBits = 8;
stopBits = 1;
parity = "N";
registerGroups = tools.gosk.registerGroupsForAmpero8AI 1 "5s";
};
};
connectAutoPilot = {
args = "connect";
config = {
name = "Auto Pilot";
protocol = "nmea0183";
url = "file:///dev/ttyUSBCom3";
baudRate = 4800;
dataBits = 8;
stopBits = 1;
parity = "N";
};
};
connectShaftPowerMeter = {
args = "connect";
config = {
name = "Shaft Power Meter";
protocol = "canbus";
url = "sock://slcan0";
};
};
connectEmuFwd = {
args = "connect";
config = {
name = "EMU modules";
protocol = "modbus";
url = "tcp://192.168.5.1:502";
registerGroups = tools.gosk.registerGroupsForEMU 1 "5s";
};
};
connectEmuAft = {
args = "connect";
config = {
name = "EMU modules";
protocol = "modbus";
url = "tcp://192.168.4.1:502";
registerGroups = tools.gosk.registerGroupsForEMU 1 "5s";
};
};
connectSygo = {
args = "connect";
config = {
name = "Sygo";
protocol = "csv";
url = "file:///dev/ttyUSBCom1";
baudRate = 19200;
dataBits = 8;
stopBits = 1;
parity = "N";
};
};
# mappers
mapAis = {
args = "map";
subscribeTo = [ connectAis ];
config = {
context = "${signalKSelf}";
protocol = "nmea0183";
};
};
mapAmpero = {
args = "map";
subscribeTo = [ connectAmpero ];
config = {
context = "${signalKSelf}";
protocol = "modbus";
mappings = [
{
slave = 1;
functionCode = 4;
address = 52;
numberOfCoilsOrRegisters = 1;
expression = "deltas[52] / 500000.0 / (timedeltas[52] / 1000.0)";
path = "propulsion.mainEngine.fuel.rate.supply";
}
{
slave = 1;
functionCode = 4;
address = 54;
numberOfCoilsOrRegisters = 1;
expression = "deltas[54] / 500000.0 / (timedeltas[54] / 1000.0)";
path = "propulsion.mainEngine.fuel.rate.return";
}
];
};
};
mapAutoPilot = {
args = "map";
subscribeTo = [ connectAutoPilot ];
config = {
context = "${signalKSelf}";
protocol = "nmea0183";
};
};
mapShaftPowerMeter = {
args = "map";
subscribeTo = [ connectShaftPowerMeter ];
config = {
context = "${signalKSelf}";
protocol = "canbus";
dbcFile = ./TelMA_1_shaft.dbc;
mappings = [
{
name = "Temperature";
origin = "TelMA_Data";
expression = "value + 273.15";
path = "environment.inside.engineRoom.temperature";
}
{
name = "RPM";
origin = "TelMA_Data";
expression = "value / 60";
path = "propulsion.mainEngine.drive.revolutions";
}
{
name = "Torque_Sum_Raw_Data";
origin = "Raw_Data";
expression = "value";
path = "propulsion.mainEngine.drive.torque";
}
];
};
};
mapEmuAft = {
args = "map";
subscribeTo = [ connectEmuAft ];
config = {
context = "${signalKSelf}";
protocol = "modbus";
protocolOptions = { skipFaultDetection = "true"; };
mappings = tools.gosk.mappingsForEMU 1 "generatorAft";
};
};
mapEmuFwd = {
args = "map";
subscribeTo = [ connectEmuFwd ];
config = {
context = "${signalKSelf}";
protocol = "modbus";
protocolOptions = { skipFaultDetection = "true"; };
mappings = tools.gosk.mappingsForEMU 1 "generatorFwd";
};
};
mapSygo = {
args = "map";
subscribeTo = [ connectSygo ];
config = {
context = "${signalKSelf}";
protocol = "csv";
separator = ",";
mappings = tools.gosk.mappingsForSYGO ++ [
{
beginsWith = "B118,";
path = "tanks.fuel.portAft.currentVolume";
expression =
"heightToVolume(floatValues[0] / 100.0, sensorheight, heights, volumes)";
}
{
beginsWith = "B119,";
path = "tanks.fuel.starboardAft.currentVolume";
expression =
"heightToVolume(floatValues[0] / 100.0, sensorheight, heights, volumes)";
}
];
};
};
# proxies
proxyRaw = {
args = "proxy";
subscribeTo = [
connectAis
connectAmpero
connectAutoPilot
connectEmuAft
connectEmuFwd
connectShaftPowerMeter
connectSygo
];
};
proxyMapped = {
args = "proxy";
subscribeTo = [
mapAis
mapAmpero
mapAutoPilot
mapEmuAft
mapEmuFwd
mapShaftPowerMeter
mapSygo
];
};
# database writers
writeDatabaseRaw = {
args = "write database mapped";
subscribeTo = [ proxyRaw ];
config = {
url =
"postgres://gosk:${config.sops.placeholder.postgresqlGoskPassword}@127.0.0.1:5432/gosk?sslmode=disable&application_name=gosk_writer_postgresql";
batch_flush_length = 100;
batch_flush_interval = "1m";
buffer_size = 100;
number_of_workers = 1;
timeout = "10s";
};
# after = [ "container@postgresql-15-test.service" ];
};
writeDatabaseMapped = {
args = "write database mapped";
subscribeTo = [ proxyMapped ];
config = {
url =
"postgres://gosk:${config.sops.placeholder.postgresqlGoskPassword}@127.0.0.1:5432/gosk?sslmode=disable&application_name=gosk_writer_postgresql";
batch_flush_length = 100;
batch_flush_interval = "1m";
buffer_size = 100;
number_of_workers = 1;
timeout = "10s";
};
# after = [ "container@postgresql-15-test.service" ];
};
};
};
}
I don’t like the tools = import ...
line in this configuration. I need to repeat that for each host. How can I make the functions I created available like the function in e.g. lib
? The flake I’m using right now is:
{
# inspired by https://github.com/Misterio77/nix-config
description = "Sustainable Motion servers and edge computers";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default-linux";
disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs";
deploy-rs.url = "github:serokell/deploy-rs";
deploy-rs.inputs.nixpkgs.follows = "nixpkgs";
crowdsec.url = "git+https://codeberg.org/kampka/nix-flake-crowdsec.git";
crowdsec.inputs.nixpkgs.follows = "nixpkgs";
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs";
home-manager.url = "github:nix-community/home-manager/release-24.11";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
programsdb.url = "github:wamserma/flake-programs-sqlite";
programsdb.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, deploy-rs, home-manager, nixos-facter-modules, ...
}@inputs:
let
inherit (self) outputs;
inherit (nixpkgs) lib;
users = [ "munnik" "smit" ];
systems = {
"hetzner-otap01" = {
inherit users;
hostname = "hetzner-otap01.sustainablemotion.io";
};
"hetzner-prod01" = {
inherit users;
hostname = "hetzner-prod01.sustainablemotion.io";
};
"node0001" = {
inherit users;
hostname = "192.168.122.53";
};
"node0002" = {
inherit users;
hostname = "192.168.122.159";
};
};
in {
inherit lib;
nixosModules = import ./modules/nixos;
overlays = import ./overlays { inherit inputs outputs; };
formatter.x86_64-linux =
nixpkgs.legacyPackages.x86_64-linux.nixfmt-rfc-style;
homeConfigurations = builtins.listToAttrs (map (userName:
lib.nameValuePair "${userName}"
(home-manager.lib.homeManagerConfiguration {
pkgs = import nixpkgs { system = "x86_64-linux"; };
modules = [ ./home/common ./home/${userName} ];
})) users);
nixosConfigurations = builtins.mapAttrs (systemName: _:
lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/${systemName}
nixos-facter-modules.nixosModules.facter
{ config.facter.reportPath = ./hosts/${systemName}/facter.json; }
];
specialArgs = { inherit inputs outputs; };
}) systems;
deploy = {
nodes = builtins.mapAttrs (systemName: systemConfig: {
inherit (systemConfig) hostname;
profiles = {
system = {
user = "root";
path = deploy-rs.lib.x86_64-linux.activate.nixos
self.nixosConfigurations."${systemName}";
};
} // builtins.listToAttrs (map (userName:
lib.nameValuePair "${userName}" {
user = "${userName}";
path = deploy-rs.lib.x86_64-linux.activate.home-manager
self.homeConfigurations."${userName}";
}) systemConfig.users);
}) systems;
};
# This is highly advised, and will prevent many possible mistakes
checks =
builtins.mapAttrs (_: deployLib: deployLib.deployChecks self.deploy)
deploy-rs.lib;
};
}