Personal Homelab
1variant: fcos
2version: 1.5.0
3
4passwd:
5 users:
6 - name: core
7 ssh_authorized_keys:
8%{ for _, ssh_key in ssh_keys ~}
9 - ${ssh_key}
10%{ endfor ~}
11
12storage:
13 directories:
14 # Config dir
15 - path: /var/home/core/.config
16 user:
17 name: core
18 group:
19 name: core
20%{ for _, path in config_dirs ~}
21 - path: /var/home/core/.config/${path}
22 user:
23 name: core
24 group:
25 name: core
26%{ endfor ~}
27
28 # Systemd user dir
29 - path: /var/home/core/.config/systemd
30 user:
31 name: core
32 group:
33 name: core
34 - path: /var/home/core/.config/systemd/user
35 user:
36 name: core
37 group:
38 name: core
39
40 # For local binaries
41 - path: /var/home/core/.local/bin
42 user:
43 name: core
44 group:
45 name: core
46
47 # Backup snapshot mounting points
48 - path: /var/mnt/snapshots/backblaze
49 - path: /var/mnt/snapshots/storj
50
51 links:
52 # Enable Podman socket for Traefik
53 - path: /var/home/core/.config/systemd/user/timers.target.wants/podman.socket
54 target: /usr/lib/systemd/user/podman.socket
55 user:
56 name: core
57 group:
58 name: core
59
60 # Enable http and https sockets for traefik
61 - path: /var/home/core/.config/systemd/user/timers.target.wants/http.socket
62 target: /var/home/core/.config/systemd/user/http.socket
63 user:
64 name: core
65 group:
66 name: core
67 - path: /var/home/core/.config/systemd/user/timers.target.wants/https.socket
68 target: /var/home/core/.config/systemd/user/https.socket
69 user:
70 name: core
71 group:
72 name: core
73 - path: /var/home/core/.config/systemd/user/timers.target.wants/imaps.socket
74 target: /var/home/core/.config/systemd/user/imaps.socket
75 user:
76 name: core
77 group:
78 name: core
79 - path: /var/home/core/.config/systemd/user/timers.target.wants/smtps.socket
80 target: /var/home/core/.config/systemd/user/smtps.socket
81 user:
82 name: core
83 group:
84 name: core
85 - path: /var/home/core/.config/systemd/user/timers.target.wants/ldaps.socket
86 target: /var/home/core/.config/systemd/user/ldaps.socket
87 user:
88 name: core
89 group:
90 name: core
91
92 # Enable Podman auto updates
93 - path: /var/home/core/.config/systemd/user/timers.target.wants/podman-auto-update.timer
94 target: /usr/lib/systemd/user/podman-auto-update.timer
95 user:
96 name: core
97 group:
98 name: core
99
100 # Hometown timezone
101 - path: /etc/localtime
102 target: ../usr/share/zoneinfo/Europe/Belgrade
103
104 # Enable systemd_exporter
105 - path: /var/home/core/.config/systemd/user/default.target.wants/systemd-exporter.service
106 target: /var/home/core/.config/systemd/user/systemd-exporter.service
107 user:
108 name: core
109 group:
110 name: core
111
112 # Enable node_exporter
113 - path: /var/home/core/.config/systemd/user/default.target.wants/node-exporter.service
114 target: /var/home/core/.config/systemd/user/node-exporter.service
115 user:
116 name: core
117 group:
118 name: core
119
120 files:
121 # http and https sockets for traefik
122 - path: /var/home/core/.config/systemd/user/http.socket
123 contents:
124 inline: |
125 [Socket]
126 ListenStream=${ip}:8080
127 FileDescriptorName=web
128 Service=traefik.service
129
130 [Install]
131 WantedBy=sockets.target
132 user:
133 name: core
134 group:
135 name: core
136 - path: /var/home/core/.config/systemd/user/https.socket
137 contents:
138 inline: |
139 [Socket]
140 ListenStream=${ip}:8443
141 ListenDatagram=${ip}:8443
142 FileDescriptorName=websecure
143 Service=traefik.service
144
145 [Install]
146 WantedBy=sockets.target
147 user:
148 name: core
149 group:
150 name: core
151 - path: /var/home/core/.config/systemd/user/imaps.socket
152 contents:
153 inline: |
154 [Socket]
155 ListenStream=${ip}:8993
156 FileDescriptorName=imaps
157 Service=traefik.service
158
159 [Install]
160 WantedBy=sockets.target
161 user:
162 name: core
163 group:
164 name: core
165 - path: /var/home/core/.config/systemd/user/smtps.socket
166 contents:
167 inline: |
168 [Socket]
169 ListenStream=${ip}:8465
170 FileDescriptorName=smtps
171 Service=traefik.service
172
173 [Install]
174 WantedBy=sockets.target
175 user:
176 name: core
177 group:
178 name: core
179 - path: /var/home/core/.config/systemd/user/ldaps.socket
180 contents:
181 inline: |
182 [Socket]
183 ListenStream=${ip}:8636
184 FileDescriptorName=ldaps
185 Service=traefik.service
186
187 [Install]
188 WantedBy=sockets.target
189 user:
190 name: core
191 group:
192 name: core
193
194 - path: /etc/containers/storage.conf
195 contents:
196 inline: |
197 [storage]
198 driver = "overlay"
199 rootless_storage_path = "/var/mnt/docker/$USER"
200
201 # Configs block
202%{ for name, content in config_files ~}
203 - path: /var/home/core/.config/${name}
204 contents:
205 inline: |
206 ${indent(10, content)}
207 user:
208 name: core
209 group:
210 name: core
211%{ endfor ~}
212
213 # Enable linger so containers can continue to run even after core user logouts
214 - path: /var/lib/systemd/linger/core
215
216 # Set machine hostname
217 - path: /etc/hostname
218 contents:
219 inline: ${hostname}
220
221 # Configure iSCSI target
222 - path: /etc/iscsi/iscsid.conf
223 overwrite: true
224 contents:
225 inline: |
226 node.startup = automatic
227 isns.address = ${truenas_ip}
228 isns.port = 3260
229
230 # Import Step CA root certificate
231 - path: /etc/pki/ca-trust/source/anchors/step-online-ca.pem
232 contents:
233 inline: |
234 ${indent(10, root_ca)}
235
236 # Enable zram swap
237 - path: /etc/systemd/zram-generator.conf
238 contents:
239 inline: |
240 [zram0]
241
242 - path: /etc/sysctl.d/90-bbr.conf
243 contents:
244 inline: |
245 net.core.default_qdisc = cake
246 net.ipv4.tcp_congestion_control = bbr
247
248 - path: /etc/sysctl.d/90-inotify.conf
249 contents:
250 inline: |
251 fs.inotify.max_user_instances = 1024
252
253 - path: /etc/ssh/sshd_config.d/20-increase-max-auth-tries.conf
254 mode: 0644
255 contents:
256 inline: |
257 # I have a lot of SSH keys, unfortunately Terraform hasn't "IdentitiesOnly yes" alternative, so it just tries
258 # all keys on machine, reaching the limit.
259 MaxAuthTries 10
260
261 # Firewall configuration
262 - path: /etc/sysconfig/nftables.conf
263 overwrite: true
264 mode: 0644
265 contents:
266 inline: |
267 include "/etc/nftables/main.nft"
268
269 - path: /etc/nftables/main.nft
270 overwrite: true
271 mode: 0644
272 contents:
273 inline: |
274 #!/usr/sbin/nft -f
275
276 flush ruleset
277
278 table inet filter {
279 chain input {
280 type filter hook input priority filter; policy drop;
281
282 # allow established/related connections
283 ct state {established, related} accept
284
285 # early drop of invalid connections
286 ct state invalid drop
287
288 # allow from loopback
289 iifname lo accept
290
291 # allow icmp
292 ip protocol icmp accept
293 ip6 nexthdr icmpv6 counter accept
294
295 # allow ssh
296 tcp dport ssh accept
297 tcp dport 2222 accept
298
299 # allow http and https
300 tcp dport 8080 accept
301 tcp dport 8443 accept
302 udp dport 8443 accept
303 tcp dport 9443 accept
304 udp dport 9443 accept
305
306 # allow imaps, smtps and ldaps
307 tcp dport 8993 accept
308 tcp dport 8465 accept
309 tcp dport 8636 accept
310
311 # allow livekit
312 udp dport 59000-60000 accept
313 udp dport 3478 accept
314 tcp dport 7881 accept
315
316 # allow plex
317 udp dport { 32410, 32412, 32413, 32414 } accept
318
319 # allow minecraft bds
320 udp dport 19132 accept
321
322 # allow truenas to report metrics using graphite
323 ip saddr 192.168.100.3 tcp dport 2003 accept
324 ip saddr 192.168.100.3 udp dport 2003 accept
325
326 # allow plugs to access mqtt broker
327 ip saddr 192.168.100.1 tcp dport 1883 accept
328 }
329
330 chain forward {
331 type filter hook forward priority filter; policy drop;
332
333 ct state {established, related} accept
334 ct state invalid drop
335
336 ct status dnat accept
337 }
338
339 chain output {
340 type filter hook output priority 0; policy accept;
341 }
342 }
343
344 # download systemd_exporter and create systemd user service
345 - path: /var/home/core/.local/bin/systemd_exporter.tar.gz
346 contents:
347 source: https://github.com/prometheus-community/systemd_exporter/releases/download/v0.7.0/systemd_exporter-0.7.0.linux-amd64.tar.gz
348 verification:
349 hash: sha256-2d995ca20249aeeac8f507173176ce5d162f17470a98ca66e289c85b388480c3
350 user:
351 name: core
352 group:
353 name: core
354 - path: /var/home/core/.config/systemd/user/systemd-exporter.service
355 contents:
356 inline: |
357 [Unit]
358 Description=Systemd Exporter
359
360 [Service]
361 Type=simple
362 WorkingDirectory=/var/home/core/.local/bin
363 ExecStartPre=/usr/bin/tar -xvf systemd_exporter.tar.gz systemd_exporter-0.7.0.linux-amd64/systemd_exporter --strip-components=1
364 ExecStart=/var/home/core/.local/bin/systemd_exporter
365
366 [Install]
367 WantedBy=default.target
368 user:
369 name: core
370 group:
371 name: core
372
373 # download node_exporter and create systemd user service
374 - path: /var/home/core/.local/bin/node_exporter.tar.gz
375 contents:
376 source: https://github.com/prometheus/node_exporter/releases/download/v1.10.2/node_exporter-1.10.2.linux-amd64.tar.gz
377 verification:
378 hash: sha256-c46e5b6f53948477ff3a19d97c58307394a29fe64a01905646f026ddc32cb65b
379 user:
380 name: core
381 group:
382 name: core
383 - path: /var/home/core/.config/systemd/user/node-exporter.service
384 contents:
385 inline: |
386 [Unit]
387 Description=Node Exporter
388
389 [Service]
390 Type=simple
391 WorkingDirectory=/var/home/core/.local/bin
392 ExecStartPre=/usr/bin/tar -xvf node_exporter.tar.gz node_exporter-1.10.2.linux-amd64/node_exporter --strip-components=1
393 ExecStart=/var/home/core/.local/bin/node_exporter --collector.systemd --collector.processes
394
395 [Install]
396 WantedBy=default.target
397
398systemd:
399 units:
400 - name: install-additional-software.service
401 enabled: true
402 contents: |
403 [Unit]
404 Description=Additional software installer
405 Wants=network-online.target
406 After=network-online.target
407 Before=zincati.service
408 ConditionPathExists=!/var/lib/%N.stamp
409
410 [Service]
411 Type=oneshot
412 RemainAfterExit=yes
413 ExecStart=/usr/bin/rpm-ostree install --allow-inactive --assumeyes --reboot qemu-guest-agent restic btop nvtop
414 ExecStart=/bin/touch /var/lib/%N.stamp
415
416 [Install]
417 WantedBy=multi-user.target
418
419 - name: iscsi.service
420 enabled: true
421
422 - name: attach-iscsi-disk.service
423 enabled: true
424 contents: |
425 [Unit]
426 Description=Attach iSCSI disk
427 ConditionFirstBoot=yes
428 Wants=network-online.target
429 After=network-online.target iscsi.service
430
431 [Service]
432 Type=oneshot
433 RemainAfterExit=yes
434 ExecStart=/usr/sbin/iscsiadm -m discovery -t sendtargets -p ${truenas_ip}
435 ExecStart=/usr/sbin/iscsiadm -m node -T ${truenas_iqn} -p ${truenas_ip} --login
436 ExecStart=/usr/sbin/lvmdevices --adddev /dev/sda
437 ExecStart=/usr/sbin/vgchange -ay
438
439 [Install]
440 WantedBy=multi-user.target
441
442 - name: var-mnt-docker.mount
443 enabled: true
444 contents: |
445 [Unit]
446 Description=Mount docker directory
447 Before=remote-fs.target
448 Wants=network-online.target iscsi.service
449
450 [Mount]
451 What=/dev/vg0/lv0
452 Where=/var/mnt/docker
453 Type=xfs
454 Options=_netdev
455
456 [Install]
457 WantedBy=remote-fs.target
458
459 - name: podman-fix-selinux-context.service
460 enabled: true
461 contents: |
462 [Unit]
463 Description=Fix SELinux context for Podman storage
464 Requires=var-mnt-docker.mount
465 After=var-mnt-docker.mount
466 ConditionPathExists=!/var/lib/%N.stamp
467
468 [Service]
469 Type=oneshot
470 RemainAfterExit=yes
471 ExecStart=/usr/bin/chcon -R -t container_file_t /var/mnt/docker/core
472 ExecStart=/bin/touch /var/lib/%N.stamp
473
474 [Install]
475 WantedBy=multi-user.target
476
477 - name: var-mnt-observability.mount
478 enabled: true
479 contents: |
480 [Unit]
481 Description=Mount observability directory
482 Before=remote-fs.target
483
484 [Mount]
485 What=${truenas_ip}:/mnt/spool/observability
486 Where=/var/mnt/observability
487 Type=nfs
488
489 [Install]
490 WantedBy=remote-fs.target
491
492 - name: var-mnt-media.mount
493 enabled: true
494 contents: |
495 [Unit]
496 Description=Mount media directory
497 Before=remote-fs.target
498
499 [Mount]
500 What=${truenas_ip}:/mnt/spool/media
501 Where=/var/mnt/media
502 Type=nfs
503
504 [Install]
505 WantedBy=remote-fs.target
506
507 - name: var-mnt-personal.mount
508 enabled: true
509 contents: |
510 [Unit]
511 Description=Mount personal directory
512 Before=remote-fs.target
513
514 [Mount]
515 What=${truenas_ip}:/mnt/spool/personal
516 Where=/var/mnt/personal
517 Type=nfs
518
519 [Install]
520 WantedBy=remote-fs.target
521
522 - name: nftables.service
523 enabled: true
524
525 - name: restic-backblaze.service
526 contents: |
527 [Unit]
528 Description=Backup Docker data to Backblaze
529 Wants=network-online.target
530 After=network-online.target iscsi.service
531
532 [Service]
533 Type=oneshot
534 LoadCredential=restic-b2-account-id
535 LoadCredential=restic-b2-account-key
536 LoadCredential=restic-password
537 Environment=RESTIC_PASSWORD_FILE=%d/restic-password
538 # Create LVM snapshot and do backup
539 ExecStart=/bin/bash -c "lvremove -y vg0/restic-backblaze || true"
540 ExecStart=/usr/sbin/lvcreate --size 1G --snapshot --name restic-backblaze vg0/lv0
541 ExecStart=/usr/bin/mount -o nouuid /dev/vg0/restic-backblaze /mnt/snapshots/backblaze
542 ExecStart=/bin/bash -c "export B2_ACCOUNT_ID=$(cat $CREDENTIALS_DIRECTORY/restic-b2-account-id); export B2_ACCOUNT_KEY=$(cat $CREDENTIALS_DIRECTORY/restic-b2-account-key); restic -r b2:krasovsky-homelab:app-data --verbose backup /var/mnt/docker/app_data"
543 ExecStart=/usr/bin/umount /mnt/snapshots/backblaze
544 ExecStart=/usr/sbin/lvremove -y vg0/restic-backblaze
545
546 [Install]
547 WantedBy=multi-user.target
548
549 - name: restic-backblaze.timer
550 enabled: true
551 contents: |
552 [Unit]
553 Description=Daily backup to Backblaze
554
555 [Timer]
556 OnCalendar=daily
557 RandomizedDelaySec=3600
558 Unit=restic-backblaze.service
559
560 [Install]
561 WantedBy=timers.target
562
563 - name: restic-storj.service
564 contents: |
565 [Unit]
566 Description=Backup Docker data to Storj
567 Wants=network-online.target
568 After=network-online.target iscsi.service
569
570 [Service]
571 Type=oneshot
572 LoadCredential=restic-aws-access-key-id
573 LoadCredential=restic-aws-secret-access-key
574 LoadCredential=restic-password
575 Environment=RESTIC_PASSWORD_FILE=%d/restic-password
576 # Create LVM snapshot and do backup
577 ExecStart=/bin/bash -c "lvremove -y vg0/restic-storj || true"
578 ExecStart=/usr/sbin/lvcreate --size 1G --snapshot --name restic-storj vg0/lv0
579 ExecStart=/usr/bin/mount -o nouuid /dev/vg0/restic-storj /mnt/snapshots/storj
580 ExecStart=/bin/bash -c "export AWS_ACCESS_KEY_ID=$(cat $CREDENTIALS_DIRECTORY/restic-aws-access-key-id); export AWS_SECRET_ACCESS_KEY=$(cat $CREDENTIALS_DIRECTORY/restic-aws-secret-access-key); restic -r s3:https://gateway.eu1.storjshare.io/homelab-backup/app-data --verbose backup /var/mnt/docker/app_data"
581 ExecStart=/usr/bin/umount /mnt/snapshots/storj
582 ExecStart=/usr/sbin/lvremove -y vg0/restic-storj
583
584 [Install]
585 WantedBy=multi-user.target
586
587 - name: restic-storj.timer
588 enabled: true
589 contents: |
590 [Unit]
591 Description=Daily backup to Storj
592
593 [Timer]
594 OnCalendar=daily
595 RandomizedDelaySec=3600
596 Unit=restic-storj.service
597
598 [Install]
599 WantedBy=timers.target
600
601kernel_arguments:
602 should_exist:
603 - pcie_aspm.policy=powersupersave ifname=infra:${mac_address} ip=${ip}::${gateway}:${mask}:${hostname}:infra:none:${nameserver}