at master 7.1 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.glance; 9 10 inherit (lib) 11 catAttrs 12 concatMapStrings 13 getExe 14 mkEnableOption 15 mkIf 16 mkOption 17 mkPackageOption 18 types 19 ; 20 21 inherit (builtins) 22 concatLists 23 isAttrs 24 isList 25 attrNames 26 getAttr 27 ; 28 29 settingsFormat = pkgs.formats.yaml { }; 30 settingsFile = settingsFormat.generate "glance.yaml" cfg.settings; 31 mergedSettingsFile = "/run/glance/glance.yaml"; 32in 33{ 34 options.services.glance = { 35 enable = mkEnableOption "glance"; 36 package = mkPackageOption pkgs "glance" { }; 37 38 settings = mkOption { 39 type = types.submodule { 40 freeformType = settingsFormat.type; 41 options = { 42 server = { 43 host = mkOption { 44 description = "Glance bind address"; 45 default = "127.0.0.1"; 46 example = "0.0.0.0"; 47 type = types.str; 48 }; 49 port = mkOption { 50 description = "Glance port to listen on"; 51 default = 8080; 52 example = 5678; 53 type = types.port; 54 }; 55 }; 56 pages = mkOption { 57 type = settingsFormat.type; 58 description = '' 59 List of pages to be present on the dashboard. 60 61 See <https://github.com/glanceapp/glance/blob/main/docs/configuration.md#pages--columns> 62 ''; 63 default = [ 64 { 65 name = "Calendar"; 66 columns = [ 67 { 68 size = "full"; 69 widgets = [ { type = "calendar"; } ]; 70 } 71 ]; 72 } 73 ]; 74 example = [ 75 { 76 name = "Home"; 77 columns = [ 78 { 79 size = "full"; 80 widgets = [ 81 { type = "calendar"; } 82 { 83 type = "weather"; 84 location = { 85 _secret = "/var/lib/secrets/glance/location"; 86 }; 87 } 88 ]; 89 } 90 ]; 91 } 92 ]; 93 }; 94 }; 95 }; 96 default = { }; 97 description = '' 98 Configuration written to a yaml file that is read by glance. See 99 <https://github.com/glanceapp/glance/blob/main/docs/configuration.md> 100 for more. 101 102 Settings containing secret data should be set to an 103 attribute set with this format: `{ _secret = "/path/to/secret"; }`. 104 See the example in `services.glance.settings.pages` at the weather widget 105 with a location secret to get a better picture of this. 106 107 Alternatively, you can use a single file with environment variables, 108 see `services.glance.environmentFile`. 109 ''; 110 }; 111 112 environmentFile = mkOption { 113 type = types.nullOr types.path; 114 description = 115 let 116 singleQuotes = "''"; 117 in 118 '' 119 Path to an environment file as defined in {manpage}`systemd.exec(5)`. 120 121 See upstream documentation 122 <https://github.com/glanceapp/glance/blob/main/docs/configuration.md#environment-variables>. 123 124 Example content of the file: 125 ``` 126 TIMEZONE=Europe/Paris 127 ``` 128 129 Example `services.glance.settings.pages` configuration: 130 ```nix 131 [ 132 { 133 name = "Home"; 134 columns = [ 135 { 136 size = "full"; 137 widgets = [ 138 { 139 type = "clock"; 140 timezone = "\''${TIMEZONE}"; 141 label = "Local Time"; 142 } 143 ]; 144 } 145 ]; 146 } 147 ]; 148 ``` 149 150 Note that when using Glance's `''${ENV_VAR}` syntax in Nix, 151 you need to escape it as follows: use `\''${ENV_VAR}` in `"` strings 152 and `${singleQuotes}''${ENV_VAR}` in `${singleQuotes}` strings. 153 154 Alternatively, you can put each secret in it's own file, 155 see `services.glance.settings`. 156 ''; 157 default = "/dev/null"; 158 example = "/var/lib/secrets/glance"; 159 }; 160 161 openFirewall = mkOption { 162 type = types.bool; 163 default = false; 164 description = '' 165 Whether to open the firewall for Glance. 166 This adds `services.glance.settings.server.port` to `networking.firewall.allowedTCPPorts`. 167 ''; 168 }; 169 }; 170 171 config = mkIf cfg.enable { 172 systemd.services.glance = { 173 description = "Glance feed dashboard server"; 174 wantedBy = [ "multi-user.target" ]; 175 after = [ "network.target" ]; 176 path = [ pkgs.replace-secret ]; 177 178 serviceConfig = { 179 ExecStartPre = 180 let 181 findSecrets = 182 data: 183 if isAttrs data then 184 if data ? _secret then 185 [ data ] 186 else 187 concatLists (map (attr: findSecrets (getAttr attr data)) (attrNames data)) 188 else if isList data then 189 concatLists (map findSecrets data) 190 else 191 [ ]; 192 secretPaths = catAttrs "_secret" (findSecrets cfg.settings); 193 mkSecretReplacement = secretPath: '' 194 replace-secret ${ 195 lib.escapeShellArgs [ 196 "_secret: ${secretPath}" 197 secretPath 198 mergedSettingsFile 199 ] 200 } 201 ''; 202 secretReplacements = concatMapStrings mkSecretReplacement secretPaths; 203 in 204 # Use "+" to run as root because the secrets may not be accessible to glance 205 "+" 206 + pkgs.writeShellScript "glance-start-pre" '' 207 install -m 600 -o $USER ${settingsFile} ${mergedSettingsFile} 208 ${secretReplacements} 209 ''; 210 ExecStart = "${getExe cfg.package} --config ${mergedSettingsFile}"; 211 WorkingDirectory = "/var/lib/glance"; 212 EnvironmentFile = cfg.environmentFile; 213 StateDirectory = "glance"; 214 RuntimeDirectory = "glance"; 215 RuntimeDirectoryMode = "0755"; 216 PrivateTmp = true; 217 DynamicUser = true; 218 DevicePolicy = "closed"; 219 LockPersonality = true; 220 MemoryDenyWriteExecute = true; 221 PrivateUsers = true; 222 ProtectHome = true; 223 ProtectHostname = true; 224 ProtectKernelLogs = true; 225 ProtectKernelModules = true; 226 ProtectKernelTunables = true; 227 ProtectControlGroups = true; 228 ProcSubset = "all"; 229 RestrictNamespaces = true; 230 RestrictRealtime = true; 231 SystemCallArchitectures = "native"; 232 UMask = "0077"; 233 }; 234 }; 235 236 networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.settings.server.port ]; }; 237 }; 238 239 meta.doc = ./glance.md; 240 meta.maintainers = [ ]; 241}