1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7let
8 cfg = config.services.homepage-dashboard;
9 # Define the settings format used for this program
10 settingsFormat = pkgs.formats.yaml { };
11in
12{
13 options = {
14 services.homepage-dashboard = {
15 enable = lib.mkEnableOption "Homepage Dashboard, a highly customizable application dashboard";
16
17 package = lib.mkPackageOption pkgs "homepage-dashboard" { };
18
19 openFirewall = lib.mkOption {
20 type = lib.types.bool;
21 default = false;
22 description = "Open ports in the firewall for Homepage.";
23 };
24
25 listenPort = lib.mkOption {
26 type = lib.types.port;
27 default = 8082;
28 description = "Port for Homepage to bind to.";
29 };
30
31 allowedHosts = lib.mkOption {
32 type = lib.types.str;
33 default = "localhost:8082,127.0.0.1:8082";
34 example = "example.com";
35 description = ''
36 Hosts that homepage-dashboard will be running under.
37 You will want to change this in order to acess homepage from anything other than localhost.
38 see the upsream documentation:
39
40 <https://gethomepage.dev/installation/#homepage_allowed_hosts>
41 '';
42 };
43
44 environmentFile = lib.mkOption {
45 type = lib.types.str;
46 description = ''
47 The path to an environment file that contains environment variables to pass
48 to the homepage-dashboard service, for the purpose of passing secrets to
49 the service.
50
51 See the upstream documentation:
52
53 <https://gethomepage.dev/installation/docker/#using-environment-secrets>
54 '';
55 default = "";
56 };
57
58 customCSS = lib.mkOption {
59 type = lib.types.lines;
60 description = ''
61 Custom CSS for styling Homepage.
62
63 See <https://gethomepage.dev/configs/custom-css-js/>.
64 '';
65 default = "";
66 };
67
68 customJS = lib.mkOption {
69 type = lib.types.lines;
70 description = ''
71 Custom Javascript for Homepage.
72
73 See <https://gethomepage.dev/configs/custom-css-js/>.
74 '';
75 default = "";
76 };
77
78 bookmarks = lib.mkOption {
79 inherit (settingsFormat) type;
80 description = ''
81 Homepage bookmarks configuration.
82
83 See <https://gethomepage.dev/configs/bookmarks/>.
84 '';
85 # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/bookmarks.yaml
86 example = [
87 {
88 Developer = [
89 {
90 Github = [
91 {
92 abbr = "GH";
93 href = "https://github.com/";
94 }
95 ];
96 }
97 ];
98 }
99 {
100 Entertainment = [
101 {
102 YouTube = [
103 {
104 abbr = "YT";
105 href = "https://youtube.com/";
106 }
107 ];
108 }
109 ];
110 }
111 ];
112 default = [ ];
113 };
114
115 services = lib.mkOption {
116 inherit (settingsFormat) type;
117 description = ''
118 Homepage services configuration.
119
120 See <https://gethomepage.dev/configs/services/>.
121 '';
122 # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/services.yaml
123 example = [
124 {
125 "My First Group" = [
126 {
127 "My First Service" = {
128 href = "http://localhost/";
129 description = "Homepage is awesome";
130 };
131 }
132 ];
133 }
134 {
135 "My Second Group" = [
136 {
137 "My Second Service" = {
138 href = "http://localhost/";
139 description = "Homepage is the best";
140 };
141 }
142 ];
143 }
144 ];
145 default = [ ];
146 };
147
148 widgets = lib.mkOption {
149 inherit (settingsFormat) type;
150 description = ''
151 Homepage widgets configuration.
152
153 See <https://gethomepage.dev/widgets/>.
154 '';
155 # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/widgets.yaml
156 example = [
157 {
158 resources = {
159 cpu = true;
160 memory = true;
161 disk = "/";
162 };
163 }
164 {
165 search = {
166 provider = "duckduckgo";
167 target = "_blank";
168 };
169 }
170 ];
171 default = [ ];
172 };
173
174 kubernetes = lib.mkOption {
175 inherit (settingsFormat) type;
176 description = ''
177 Homepage kubernetes configuration.
178
179 See <https://gethomepage.dev/configs/kubernetes/>.
180 '';
181 default = { };
182 };
183
184 docker = lib.mkOption {
185 inherit (settingsFormat) type;
186 description = ''
187 Homepage docker configuration.
188
189 See <https://gethomepage.dev/configs/docker/>.
190 '';
191 default = { };
192 };
193
194 proxmox = lib.mkOption {
195 inherit (settingsFormat) type;
196 description = ''
197 Homepage proxmox configuration.
198
199 See <https://gethomepage.dev/configs/proxmox/>.
200 '';
201 default = { };
202 };
203
204 settings = lib.mkOption {
205 inherit (settingsFormat) type;
206 description = ''
207 Homepage settings.
208
209 See <https://gethomepage.dev/configs/settings/>.
210 '';
211 # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/settings.yaml
212 default = { };
213 };
214 };
215 };
216
217 config = lib.mkIf cfg.enable {
218 environment.etc = {
219 "homepage-dashboard/custom.css".text = cfg.customCSS;
220 "homepage-dashboard/custom.js".text = cfg.customJS;
221 "homepage-dashboard/bookmarks.yaml".source = settingsFormat.generate "bookmarks.yaml" cfg.bookmarks;
222 "homepage-dashboard/docker.yaml".source = settingsFormat.generate "docker.yaml" cfg.docker;
223 "homepage-dashboard/kubernetes.yaml".source =
224 settingsFormat.generate "kubernetes.yaml" cfg.kubernetes;
225 "homepage-dashboard/services.yaml".source = settingsFormat.generate "services.yaml" cfg.services;
226 "homepage-dashboard/settings.yaml".source = settingsFormat.generate "settings.yaml" cfg.settings;
227 "homepage-dashboard/widgets.yaml".source = settingsFormat.generate "widgets.yaml" cfg.widgets;
228 "homepage-dashboard/proxmox.yaml".source = settingsFormat.generate "proxmox.yaml" cfg.proxmox;
229 };
230
231 systemd.services.homepage-dashboard = {
232 description = "Homepage Dashboard";
233 after = [ "network.target" ];
234 wantedBy = [ "multi-user.target" ];
235
236 environment = {
237 HOMEPAGE_CONFIG_DIR = "/etc/homepage-dashboard";
238 NIXPKGS_HOMEPAGE_CACHE_DIR = "/var/cache/homepage-dashboard";
239 PORT = toString cfg.listenPort;
240 LOG_TARGETS = "stdout";
241 HOMEPAGE_ALLOWED_HOSTS = cfg.allowedHosts;
242 };
243
244 serviceConfig = {
245 Type = "simple";
246 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
247 StateDirectory = "homepage-dashboard";
248 CacheDirectory = "homepage-dashboard";
249 ExecStart = lib.getExe cfg.package;
250 Restart = "on-failure";
251
252 # hardening
253 DynamicUser = true;
254 DevicePolicy = "closed";
255 CapabilityBoundingSet = "";
256 RestrictAddressFamilies = [
257 "AF_INET"
258 "AF_INET6"
259 "AF_UNIX"
260 "AF_NETLINK"
261 ];
262 DeviceAllow = "";
263 NoNewPrivileges = true;
264 PrivateDevices = true;
265 PrivateMounts = true;
266 PrivateTmp = true;
267 PrivateUsers = true;
268 ProtectClock = true;
269 ProtectControlGroups = true;
270 ProtectHome = true;
271 ProtectKernelLogs = true;
272 ProtectKernelModules = true;
273 ProtectKernelTunables = true;
274 ProtectSystem = "strict";
275 LockPersonality = true;
276 RemoveIPC = true;
277 RestrictNamespaces = true;
278 RestrictRealtime = true;
279 RestrictSUIDSGID = true;
280 SystemCallArchitectures = "native";
281 SystemCallFilter = [
282 "@system-service"
283 "~@resources"
284 ];
285 ProtectProc = "invisible";
286 ProtectHostname = true;
287 UMask = "0077";
288 # cpu widget requires access to /proc
289 ProcSubset = if lib.any (widget: widget.resources.cpu or false) cfg.widgets then "all" else "pid";
290 };
291
292 enableStrictShellChecks = true;
293
294 # Related:
295 # * https://github.com/NixOS/nixpkgs/issues/346016 ("homepage-dashboard: cache dir is not cleared upon version upgrade")
296 # * https://github.com/gethomepage/homepage/discussions/4560 ("homepage NixOS package does not clear cache on upgrade leaving broken state")
297 # * https://github.com/vercel/next.js/discussions/58864 ("Feature Request: Allow configuration of cache dir")
298 preStart = ''
299 rm -rf "''${NIXPKGS_HOMEPAGE_CACHE_DIR:?}"/*
300 '';
301 };
302
303 networking.firewall = lib.mkIf cfg.openFirewall {
304 allowedTCPPorts = [ cfg.listenPort ];
305 };
306 };
307}