nixos/evcc: support passing secrets with envsubst

and apply some newer best practices.
This commit is contained in:
Martin Weinelt 2025-03-07 09:54:59 +01:00
parent 369eaeefa3
commit 067732921f
No known key found for this signature in database
GPG key ID: 87C1E9888F856759
2 changed files with 64 additions and 20 deletions

View file

@ -1,10 +1,19 @@
{ {
config,
lib, lib,
pkgs, pkgs,
config, utils,
... ...
}: }:
let let
inherit (lib)
getExe
mkEnableOption
mkIf
mkOption
mkPackageOption
;
cfg = config.services.evcc; cfg = config.services.evcc;
format = pkgs.formats.yaml { }; format = pkgs.formats.yaml { };
@ -17,27 +26,40 @@ in
meta.maintainers = with lib.maintainers; [ hexa ]; meta.maintainers = with lib.maintainers; [ hexa ];
options.services.evcc = with lib.types; { options.services.evcc = with lib.types; {
enable = lib.mkEnableOption "EVCC, the extensible EV Charge Controller with PV integration"; enable = mkEnableOption "EVCC, the extensible EV Charge Controller and Home Energy Management System";
extraArgs = lib.mkOption { package = mkPackageOption pkgs "evcc" { };
extraArgs = mkOption {
type = listOf str; type = listOf str;
default = [ ]; default = [ ];
description = '' description = ''
Extra arguments to pass to the evcc executable. Extra arguments to pass to the `evcc` executable.
''; '';
}; };
settings = lib.mkOption { environmentFile = mkOption {
type = nullOr path;
default = null;
example = /run/keys/evcc;
description = ''
File with environment variables to pass into the runtime environment.
Useful to pass secrets into the configuration, that get applied using `envsubst`.
'';
};
settings = mkOption {
type = format.type; type = format.type;
description = '' description = ''
evcc configuration as a Nix attribute set. evcc configuration as a Nix attribute set. Supports substitution of secrets using `envsubst` from the `environmentFile`.
Check for possible options in the sample [evcc.dist.yaml](https://github.com/andig/evcc/blob/${package.version}/evcc.dist.yaml). Check for possible options in the sample [evcc.dist.yaml](https://github.com/andig/evcc/blob/${package.version}/evcc.dist.yaml).
''; '';
}; };
}; };
config = lib.mkIf cfg.enable { config = mkIf cfg.enable {
systemd.services.evcc = { systemd.services.evcc = {
wants = [ "network-online.target" ]; wants = [ "network-online.target" ];
after = [ after = [
@ -52,7 +74,21 @@ in
getent getent
]; ];
serviceConfig = { serviceConfig = {
ExecStart = "${package}/bin/evcc --config ${configFile} ${lib.escapeShellArgs cfg.extraArgs}"; EnvironmentFile = lib.optionals (cfg.environmentFile != null) [ cfg.environmentFile ];
ExecStartPre = utils.escapeSystemdExecArgs [
(getExe pkgs.envsubst)
"-i"
configFile
"-o"
"/run/evcc/config.yaml"
];
ExecStart = utils.escapeSystemdExecArgs (
[
(getExe cfg.package)
"--config=/run/evcc/config.yaml"
]
++ cfg.extraArgs
);
CapabilityBoundingSet = [ "" ]; CapabilityBoundingSet = [ "" ];
DeviceAllow = [ DeviceAllow = [
"char-ttyUSB" "char-ttyUSB"
@ -61,14 +97,6 @@ in
DynamicUser = true; DynamicUser = true;
LockPersonality = true; LockPersonality = true;
MemoryDenyWriteExecute = true; MemoryDenyWriteExecute = true;
Restart = "on-failure";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
PrivateTmp = true; PrivateTmp = true;
PrivateUsers = true; PrivateUsers = true;
ProcSubset = "pid"; ProcSubset = "pid";
@ -80,6 +108,15 @@ in
ProtectKernelModules = true; ProtectKernelModules = true;
ProtectKernelTunables = true; ProtectKernelTunables = true;
ProtectProc = "invisible"; ProtectProc = "invisible";
Restart = "on-failure";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RuntimeDirectory = "evcc";
StateDirectory = "evcc"; StateDirectory = "evcc";
SystemCallArchitectures = "native"; SystemCallArchitectures = "native";
SystemCallFilter = [ SystemCallFilter = [

View file

@ -1,5 +1,8 @@
{ pkgs, lib, ... }: { pkgs, lib, ... }:
let
port = "1234";
in
{ {
name = "evcc"; name = "evcc";
meta.maintainers = with lib.maintainers; [ hexa ]; meta.maintainers = with lib.maintainers; [ hexa ];
@ -8,11 +11,15 @@
machine = { machine = {
services.evcc = { services.evcc = {
enable = true; enable = true;
# This is NOT a safe way to deal with secrets in production
environmentFile = pkgs.writeText "evcc-secrets" ''
PORT=${toString port}
'';
settings = { settings = {
network = { network = {
schema = "http"; schema = "http";
host = "localhost"; host = "localhost";
port = 7070; port = "$PORT";
}; };
log = "info"; log = "info";
@ -82,14 +89,14 @@
start_all() start_all()
machine.wait_for_unit("evcc.service") machine.wait_for_unit("evcc.service")
machine.wait_for_open_port(7070) machine.wait_for_open_port(${port})
with subtest("Check package version propagates into frontend"): with subtest("Check package version propagates into frontend"):
machine.fail( machine.fail(
"curl --fail http://localhost:7070 | grep '0.0.1-alpha'" "curl --fail http://localhost:${port} | grep '0.0.1-alpha'"
) )
machine.succeed( machine.succeed(
"curl --fail http://localhost:7070 | grep '${pkgs.evcc.version}'" "curl --fail http://localhost:${port} | grep '${pkgs.evcc.version}'"
) )
with subtest("Check journal for errors"): with subtest("Check journal for errors"):