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;
inherit (lib)
mkEnableOption
mkPackageOption
mkOption
mkIf
catAttrs
concatMapStrings
getExe
mkEnableOption
mkIf
mkOption
mkPackageOption
types
;
inherit (builtins)
concatLists
isAttrs
isList
attrNames
getAttr
;
settingsFormat = pkgs.formats.yaml { };
settingsFile = settingsFormat.generate "glance.yaml" cfg.settings;
mergedSettingsFile = "/run/glance/glance.yaml";
in
{
options.services.glance = {
@ -69,7 +81,9 @@ in
{ type = "calendar"; }
{
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
<https://github.com/glanceapp/glance/blob/main/docs/configuration.md>
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";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = [ pkgs.replace-secret ];
serviceConfig = {
ExecStart =
ExecStartPre =
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
"${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";
StateDirectory = "glance";
RuntimeDirectory = "glance";

View file

@ -5,19 +5,47 @@
nodes = {
machine_default =
{ pkgs, ... }:
{ ... }:
{
services.glance = {
enable = true;
};
};
machine_custom_port =
machine_configured =
{ 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 = {
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 =
p: with p; [
beautifulsoup4
pyyaml
types-pyyaml
types-beautifulsoup4
];
testScript = ''
from bs4 import BeautifulSoup
import yaml
machine_default.start()
machine_default.wait_for_unit("glance.service")
machine_default.wait_for_open_port(8080)
machine_custom_port.start()
machine_custom_port.wait_for_unit("glance.service")
machine_custom_port.wait_for_open_port(5678)
machine_configured.start()
machine_configured.wait_for_unit("glance.service")
machine_configured.wait_for_open_port(5678)
soup = BeautifulSoup(machine_default.succeed("curl http://localhost:8080"))
expected_version = "v${config.nodes.machine_default.services.glance.package.version}"
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 ];