at 25.11-pre 5.6 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 containing the attribute 104 <literal>_secret</literal> - a string pointing to a file 105 containing the value the option should be set to. See the 106 example in `services.glance.settings.pages` at the weather widget 107 with a location secret to get a better picture of this. 108 ''; 109 }; 110 111 openFirewall = mkOption { 112 type = types.bool; 113 default = false; 114 description = '' 115 Whether to open the firewall for Glance. 116 This adds `services.glance.settings.server.port` to `networking.firewall.allowedTCPPorts`. 117 ''; 118 }; 119 }; 120 121 config = mkIf cfg.enable { 122 systemd.services.glance = { 123 description = "Glance feed dashboard server"; 124 wantedBy = [ "multi-user.target" ]; 125 after = [ "network.target" ]; 126 path = [ pkgs.replace-secret ]; 127 128 serviceConfig = { 129 ExecStartPre = 130 let 131 findSecrets = 132 data: 133 if isAttrs data then 134 if data ? _secret then 135 [ data ] 136 else 137 concatLists (map (attr: findSecrets (getAttr attr data)) (attrNames data)) 138 else if isList data then 139 concatLists (map findSecrets data) 140 else 141 [ ]; 142 secretPaths = catAttrs "_secret" (findSecrets cfg.settings); 143 mkSecretReplacement = secretPath: '' 144 replace-secret ${ 145 lib.escapeShellArgs [ 146 "_secret: ${secretPath}" 147 secretPath 148 mergedSettingsFile 149 ] 150 } 151 ''; 152 secretReplacements = concatMapStrings mkSecretReplacement secretPaths; 153 in 154 # Use "+" to run as root because the secrets may not be accessible to glance 155 "+" 156 + pkgs.writeShellScript "glance-start-pre" '' 157 install -m 600 -o $USER ${settingsFile} ${mergedSettingsFile} 158 ${secretReplacements} 159 ''; 160 ExecStart = "${getExe cfg.package} --config ${mergedSettingsFile}"; 161 WorkingDirectory = "/var/lib/glance"; 162 StateDirectory = "glance"; 163 RuntimeDirectory = "glance"; 164 RuntimeDirectoryMode = "0755"; 165 PrivateTmp = true; 166 DynamicUser = true; 167 DevicePolicy = "closed"; 168 LockPersonality = true; 169 MemoryDenyWriteExecute = true; 170 PrivateUsers = true; 171 ProtectHome = true; 172 ProtectHostname = true; 173 ProtectKernelLogs = true; 174 ProtectKernelModules = true; 175 ProtectKernelTunables = true; 176 ProtectControlGroups = true; 177 ProcSubset = "all"; 178 RestrictNamespaces = true; 179 RestrictRealtime = true; 180 SystemCallArchitectures = "native"; 181 UMask = "0077"; 182 }; 183 }; 184 185 networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.settings.server.port ]; }; 186 }; 187 188 meta.doc = ./glance.md; 189 meta.maintainers = [ lib.maintainers.drupol ]; 190}