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.int;
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 settings = lib.mkOption {
195 inherit (settingsFormat) type;
196 description = ''
197 Homepage settings.
198
199 See <https://gethomepage.dev/configs/settings/>.
200 '';
201 # Defaults: https://github.com/gethomepage/homepage/blob/main/src/skeleton/settings.yaml
202 default = { };
203 };
204 };
205 };
206
207 config = lib.mkIf cfg.enable {
208 environment.etc = {
209 "homepage-dashboard/custom.css".text = cfg.customCSS;
210 "homepage-dashboard/custom.js".text = cfg.customJS;
211 "homepage-dashboard/bookmarks.yaml".source = settingsFormat.generate "bookmarks.yaml" cfg.bookmarks;
212 "homepage-dashboard/docker.yaml".source = settingsFormat.generate "docker.yaml" cfg.docker;
213 "homepage-dashboard/kubernetes.yaml".source =
214 settingsFormat.generate "kubernetes.yaml" cfg.kubernetes;
215 "homepage-dashboard/services.yaml".source = settingsFormat.generate "services.yaml" cfg.services;
216 "homepage-dashboard/settings.yaml".source = settingsFormat.generate "settings.yaml" cfg.settings;
217 "homepage-dashboard/widgets.yaml".source = settingsFormat.generate "widgets.yaml" cfg.widgets;
218 };
219
220 systemd.services.homepage-dashboard = {
221 description = "Homepage Dashboard";
222 after = [ "network.target" ];
223 wantedBy = [ "multi-user.target" ];
224
225 environment = {
226 HOMEPAGE_CONFIG_DIR = "/etc/homepage-dashboard";
227 NIXPKGS_HOMEPAGE_CACHE_DIR = "/var/cache/homepage-dashboard";
228 PORT = toString cfg.listenPort;
229 LOG_TARGETS = "stdout";
230 HOMEPAGE_ALLOWED_HOSTS = cfg.allowedHosts;
231 };
232
233 serviceConfig = {
234 Type = "simple";
235 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
236 StateDirectory = "homepage-dashboard";
237 CacheDirectory = "homepage-dashboard";
238 ExecStart = lib.getExe cfg.package;
239 Restart = "on-failure";
240
241 # hardening
242 DynamicUser = true;
243 DevicePolicy = "closed";
244 CapabilityBoundingSet = "";
245 RestrictAddressFamilies = [
246 "AF_INET"
247 "AF_INET6"
248 "AF_UNIX"
249 "AF_NETLINK"
250 ];
251 DeviceAllow = "";
252 NoNewPrivileges = true;
253 PrivateDevices = true;
254 PrivateMounts = true;
255 PrivateTmp = true;
256 PrivateUsers = true;
257 ProtectClock = true;
258 ProtectControlGroups = true;
259 ProtectHome = true;
260 ProtectKernelLogs = true;
261 ProtectKernelModules = true;
262 ProtectKernelTunables = true;
263 ProtectSystem = "strict";
264 LockPersonality = true;
265 RemoveIPC = true;
266 RestrictNamespaces = true;
267 RestrictRealtime = true;
268 RestrictSUIDSGID = true;
269 SystemCallArchitectures = "native";
270 SystemCallFilter = [
271 "@system-service"
272 "~@resources"
273 ];
274 ProtectProc = "invisible";
275 ProtectHostname = true;
276 UMask = "0077";
277 # cpu widget requires access to /proc
278 ProcSubset = if lib.any (widget: widget.resources.cpu or false) cfg.widgets then "all" else "pid";
279 };
280
281 enableStrictShellChecks = true;
282
283 # Related:
284 # * https://github.com/NixOS/nixpkgs/issues/346016 ("homepage-dashboard: cache dir is not cleared upon version upgrade")
285 # * https://github.com/gethomepage/homepage/discussions/4560 ("homepage NixOS package does not clear cache on upgrade leaving broken state")
286 # * https://github.com/vercel/next.js/discussions/58864 ("Feature Request: Allow configuration of cache dir")
287 preStart = ''
288 rm -rf "''${NIXPKGS_HOMEPAGE_CACHE_DIR:?}"/*
289 '';
290 };
291
292 networking.firewall = lib.mkIf cfg.openFirewall {
293 allowedTCPPorts = [ cfg.listenPort ];
294 };
295 };
296}