nixos/glance: allow specifying secret settings

This commit is contained in:
Gutyina Gergő 2025-04-02 20:49:22 +02:00
parent 5e5402ecbc
commit 27d160b60b
No known key found for this signature in database
2 changed files with 99 additions and 14 deletions

View file

@ -8,15 +8,27 @@ let
cfg = config.services.glance; cfg = config.services.glance;
inherit (lib) inherit (lib)
mkEnableOption catAttrs
mkPackageOption concatMapStrings
mkOption
mkIf
getExe getExe
mkEnableOption
mkIf
mkOption
mkPackageOption
types types
; ;
inherit (builtins)
concatLists
isAttrs
isList
attrNames
getAttr
;
settingsFormat = pkgs.formats.yaml { }; settingsFormat = pkgs.formats.yaml { };
settingsFile = settingsFormat.generate "glance.yaml" cfg.settings;
mergedSettingsFile = "/run/glance/glance.yaml";
in in
{ {
options.services.glance = { options.services.glance = {
@ -69,7 +81,9 @@ in
{ type = "calendar"; } { type = "calendar"; }
{ {
type = "weather"; type = "weather";
location = "Nivelles, Belgium"; location = {
_secret = "/var/lib/secrets/glance/location";
};
} }
]; ];
} }
@ -84,6 +98,13 @@ in
Configuration written to a yaml file that is read by glance. See Configuration written to a yaml file that is read by glance. See
<https://github.com/glanceapp/glance/blob/main/docs/configuration.md> <https://github.com/glanceapp/glance/blob/main/docs/configuration.md>
for more. for more.
Settings containing secret data should be set to an
attribute set containing the attribute
<literal>_secret</literal> - a string pointing to a file
containing the value the option should be set to. See the
example in `services.glance.settings.pages` at the weather widget
with a location secret to get a better picture of this.
''; '';
}; };
@ -102,13 +123,41 @@ in
description = "Glance feed dashboard server"; description = "Glance feed dashboard server";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "network.target" ]; after = [ "network.target" ];
path = [ pkgs.replace-secret ];
serviceConfig = { serviceConfig = {
ExecStart = ExecStartPre =
let let
glance-yaml = settingsFormat.generate "glance.yaml" cfg.settings; findSecrets =
data:
if isAttrs data then
if data ? _secret then
[ data ]
else
concatLists (map (attr: findSecrets (getAttr attr data)) (attrNames data))
else if isList data then
concatLists (map findSecrets data)
else
[ ];
secretPaths = catAttrs "_secret" (findSecrets cfg.settings);
mkSecretReplacement = secretPath: ''
replace-secret ${
lib.escapeShellArgs [
"_secret: ${secretPath}"
secretPath
mergedSettingsFile
]
}
'';
secretReplacements = concatMapStrings mkSecretReplacement secretPaths;
in in
"${getExe cfg.package} --config ${glance-yaml}"; # Use "+" to run as root because the secrets may not be accessible to glance
"+"
+ pkgs.writeShellScript "glance-start-pre" ''
install -m 600 -o $USER ${settingsFile} ${mergedSettingsFile}
${secretReplacements}
'';
ExecStart = "${getExe cfg.package} --config ${mergedSettingsFile}";
WorkingDirectory = "/var/lib/glance"; WorkingDirectory = "/var/lib/glance";
StateDirectory = "glance"; StateDirectory = "glance";
RuntimeDirectory = "glance"; RuntimeDirectory = "glance";

View file

@ -5,19 +5,47 @@
nodes = { nodes = {
machine_default = machine_default =
{ pkgs, ... }: { ... }:
{ {
services.glance = { services.glance = {
enable = true; enable = true;
}; };
}; };
machine_custom_port = machine_configured =
{ pkgs, ... }: { pkgs, ... }:
let
# Do not use this in production. This will make the secret world-readable
# in the Nix store
secrets.glance-location.path = builtins.toString (
pkgs.writeText "location-secret" "Nivelles, Belgium"
);
in
{ {
services.glance = { services.glance = {
enable = true; enable = true;
settings.server.port = 5678; settings = {
server.port = 5678;
pages = [
{
name = "Home";
columns = [
{
size = "full";
widgets = [
{ type = "calendar"; }
{
type = "weather";
location = {
_secret = secrets.glance-location.path;
};
}
];
}
];
}
];
};
}; };
}; };
}; };
@ -25,23 +53,31 @@
extraPythonPackages = extraPythonPackages =
p: with p; [ p: with p; [
beautifulsoup4 beautifulsoup4
pyyaml
types-pyyaml
types-beautifulsoup4 types-beautifulsoup4
]; ];
testScript = '' testScript = ''
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import yaml
machine_default.start() machine_default.start()
machine_default.wait_for_unit("glance.service") machine_default.wait_for_unit("glance.service")
machine_default.wait_for_open_port(8080) machine_default.wait_for_open_port(8080)
machine_custom_port.start() machine_configured.start()
machine_custom_port.wait_for_unit("glance.service") machine_configured.wait_for_unit("glance.service")
machine_custom_port.wait_for_open_port(5678) machine_configured.wait_for_open_port(5678)
soup = BeautifulSoup(machine_default.succeed("curl http://localhost:8080")) soup = BeautifulSoup(machine_default.succeed("curl http://localhost:8080"))
expected_version = "v${config.nodes.machine_default.services.glance.package.version}" expected_version = "v${config.nodes.machine_default.services.glance.package.version}"
assert any(a.text == expected_version for a in soup.select(".footer a")) assert any(a.text == expected_version for a in soup.select(".footer a"))
yaml_contents = machine_configured.succeed("cat /run/glance/glance.yaml")
yaml_parsed = yaml.load(yaml_contents, Loader=yaml.FullLoader)
location = yaml_parsed["pages"][0]["columns"][0]["widgets"][1]["location"]
assert location == "Nivelles, Belgium"
''; '';
meta.maintainers = [ lib.maintainers.drupol ]; meta.maintainers = [ lib.maintainers.drupol ];