This commit is contained in:
Aner Zakobar
2026-05-20 23:21:36 +03:00
parent 171ff2f3bc
commit 08e8b5edbe
17 changed files with 419 additions and 322 deletions
+17 -22
View File
@@ -42,6 +42,18 @@ in
options.homey.backup = {
enable = lib.mkEnableOption "Restic backup jobs";
extraPaths = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Paths to include in the restic backup. Each service module contributes its own entries.";
};
extraExcludePaths = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
description = "Paths to exclude from the restic backup. Each service module contributes its own entries.";
};
repository = lib.mkOption {
type = lib.types.str;
example = "sftp:user@nas.local:/backups/homey";
@@ -127,32 +139,15 @@ in
# Runtime env file written by homey-backup-pre.service (which runs first)
environmentFile = "/run/restic-homey-secrets.env";
paths = [
"${dataDir}/openldap"
"${dataDir}/authelia"
"${dataDir}/gitea"
"${dataDir}/nextcloud"
# media and transmission config included when those services are enabled:
"${dataDir}/jellyfin"
"${dataDir}/transmission"
# Deliberately excluded: media/* (large, can be re-downloaded)
# Monitoring — uptime-kuma has monitors/history, ntfy has user accounts
"${dataDir}/uptime-kuma"
"${dataDir}/ntfy"
# Eurovision Vote — SQLite DB with votes and rankings
"/var/lib/eurovote"
"${dataDir}/paperless"
"${dataDir}/mealie"
];
# Paths are contributed by individual service modules via homey.backup.extraPaths.
paths = config.homey.backup.extraPaths;
# Exclude Nextcloud's raw DB directory in favour of the pg_dump file
exclude = [
"${dataDir}/nextcloud/db"
# restic's own local cache is never worth backing up
"${dataDir}/restic-cache"
# media is large and can be re-downloaded; services exclude their own consume dirs
"${dataDir}/media"
# consume dir holds unprocessed drop files; usually empty after ingestion
"${dataDir}/paperless/consume"
];
] ++ config.homey.backup.extraExcludePaths;
timerConfig = {
OnCalendar = cfg.schedule;
+52 -222
View File
@@ -65,6 +65,38 @@ in
default = "admin@zakobar.com";
description = "Email for Let's Encrypt ACME registration.";
};
virtualHosts = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
subdomain = lib.mkOption {
type = lib.types.str;
description = "Subdomain under homeyConfig.domain (e.g. \"mealie\" mealie.zakobar.com).";
};
port = lib.mkOption {
type = lib.types.port;
description = "Host port to reverse-proxy to.";
};
auth = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Prepend Authelia forward_auth to this vhost.";
};
extraConfig = lib.mkOption {
type = lib.types.str;
default = "";
description = "Replaces the auto-generated 'reverse_proxy localhost:<port>' for HTTPS. Empty = use default.";
};
extraHttpConfig = lib.mkOption {
type = lib.types.str;
default = "";
description = "Replaces the auto-generated cfProxy for the HTTP loopback vhost. Empty = use default.";
};
};
});
default = [];
description = "Virtual hosts to generate. Each service module contributes its own entries.";
};
};
config = lib.mkIf cfg.enable {
@@ -89,229 +121,27 @@ in
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
'';
# Each virtual host.
# Each virtual host is generated from homey.caddy.virtualHosts entries.
# Each service module contributes its own entries to that list.
#
# Each service gets two vhost entries:
# - "host" (no scheme) → Caddy handles HTTPS + auto cert (for LAN access)
# - "http://host" → plain HTTP for cloudflared on loopback (no redirect)
#
# Caddy auto-redirects HTTP→HTTPS only when no explicit http:// vhost exists.
# By defining http:// explicitly we suppress that redirect so cloudflared
# (which talks plain HTTP on port 80) gets a direct response.
virtualHosts = {
# ------------------------------------------------------------------
# Authelia — public, no auth gate (it IS the auth gate)
# ------------------------------------------------------------------
"auth.${domain}" = {
extraConfig = ''
reverse_proxy localhost:9091
'';
};
"http://auth.${domain}" = {
extraConfig = cfProxy 9091;
};
# ------------------------------------------------------------------
# Gitea — no forward_auth; git HTTP clients can't handle SSO redirects.
# Access control is handled by Gitea itself (LDAP auth + private repos).
# ------------------------------------------------------------------
"git.${domain}" = {
extraConfig = ''
reverse_proxy localhost:3000
'';
};
"http://git.${domain}" = {
extraConfig = cfProxy 3000;
};
# ------------------------------------------------------------------
# Nextcloud — public auth (Nextcloud manages its own users + LDAP)
# ------------------------------------------------------------------
"nextcloud.${domain}" = {
extraConfig = ''
# Redirect CardDAV/CalDAV discovery
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
# Large uploads (5 GB)
request_body {
max_size 5GB
}
reverse_proxy localhost:8080 {
header_up X-Forwarded-For {remote_host}
}
'';
};
"http://nextcloud.${domain}" = {
extraConfig = ''
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
request_body {
max_size 5GB
}
reverse_proxy localhost:8080 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-For {remote_host}
}
'';
};
# ------------------------------------------------------------------
# phpLDAPadmin — two_factor, admins only (enforced by authelia policy)
# ------------------------------------------------------------------
"ldapadmin.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:8081
'';
};
"http://ldapadmin.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
${cfProxy 8081}
'';
};
# ------------------------------------------------------------------
# Jellyfin — no forward_auth; Jellyfin has its own login UI and
# native app clients can't handle SSO redirects.
# ------------------------------------------------------------------
"jellyfin.${domain}" = {
extraConfig = ''
reverse_proxy localhost:8096
'';
};
"http://jellyfin.${domain}" = {
extraConfig = cfProxy 8096;
};
# ------------------------------------------------------------------
# Transmission — two_factor, admins only (enforced by authelia policy)
# ------------------------------------------------------------------
"torrent.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:9092
'';
# NOTE: transmission is bound to 9092 to avoid clash with authelia on 9091.
};
"http://torrent.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
${cfProxy 9092}
'';
};
# ------------------------------------------------------------------
# Uptime Kuma — two_factor, admins only (enforced by authelia policy)
# ------------------------------------------------------------------
"uptime.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:3001
'';
};
"http://uptime.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
${cfProxy 3001}
'';
};
# ------------------------------------------------------------------
# Ntfy — no forward_auth; ntfy has its own token/password auth so the
# mobile app can connect without Authelia SSO complications.
# ------------------------------------------------------------------
"ntfy.${domain}" = {
extraConfig = ''
reverse_proxy localhost:2586
'';
};
"http://ntfy.${domain}" = {
extraConfig = cfProxy 2586;
};
# ------------------------------------------------------------------
# Eurovision Vote — one_factor for all authenticated users.
# /admin/* is restricted to group:admins by Authelia access_control.
# Caddy passes Remote-User → X-Remote-User so Django auto-logs in
# the SSO-authenticated user via RemoteUserMiddleware.
# ------------------------------------------------------------------
"eurovision-vote.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:8007 {
header_up X-Remote-User {http.request.header.Remote-User}
}
'';
};
"http://eurovision-vote.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:8007 {
header_up X-Forwarded-Proto https
header_up X-Remote-User {http.request.header.Remote-User}
}
'';
};
# ------------------------------------------------------------------
# Paperless — one_factor for all authenticated users (authelia policy).
# Authelia sets Remote-User; Caddy copies it to the upstream request;
# Paperless trusts HTTP_REMOTE_USER for automatic login (no separate
# Paperless login page shown).
# ------------------------------------------------------------------
"paperless.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:8083
'';
};
"http://paperless.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
${cfProxy 8083}
'';
};
# ------------------------------------------------------------------
# Mealie — no forward_auth; LDAP handles auth via Mealie's login page.
# ------------------------------------------------------------------
"mealie.${domain}" = {
extraConfig = ''
reverse_proxy localhost:9093
'';
};
"http://mealie.${domain}" = {
extraConfig = cfProxy 9093;
};
# ------------------------------------------------------------------
# Grafana — two_factor, admins only (enforced by authelia policy).
# After Authelia verifies the user, Caddy maps the Remote-User header
# to X-WEBAUTH-USER so Grafana's proxy auth auto-signs the user in.
# ------------------------------------------------------------------
"grafana.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:3002 {
header_up X-WEBAUTH-USER {http.request.header.Remote-User}
}
'';
};
"http://grafana.${domain}" = {
extraConfig = ''
${autheliaForwardAuth}
reverse_proxy localhost:3002 {
header_up X-Forwarded-Proto https
header_up X-WEBAUTH-USER {http.request.header.Remote-User}
}
'';
};
};
# Each entry produces two Caddy vhosts:
# - "subdomain.domain" → HTTPS (LAN access + Let's Encrypt cert)
# - "http://subdomain.domain" → plain HTTP for cloudflared loopback
virtualHosts = lib.listToAttrs (
lib.concatMap (vh:
let
d = "${vh.subdomain}.${domain}";
authSnip = lib.optionalString vh.auth autheliaForwardAuth;
httpsBody = if vh.extraConfig != "" then vh.extraConfig
else "reverse_proxy localhost:${toString vh.port}\n";
httpBody = if vh.extraHttpConfig != "" then vh.extraHttpConfig
else cfProxy vh.port;
in [
{ name = d; value.extraConfig = "${authSnip}${httpsBody}"; }
{ name = "http://${d}"; value.extraConfig = "${authSnip}${httpBody}"; }
]
) cfg.virtualHosts
);
};
# -----------------------------------------------------------------------
+25 -1
View File
@@ -38,7 +38,7 @@ let
in
{
options.homey.monitoring = {
enable = lib.mkEnableOption "Prometheus + Grafana monitoring stack";
enable = lib.mkEnableOption "Prometheus + Grafana monitoring stack" // { default = true; };
prometheusPort = lib.mkOption {
type = lib.types.port;
@@ -205,6 +205,30 @@ in
mode = "0444";
};
# -----------------------------------------------------------------------
# Caddy virtual host — forward_auth; Caddy maps Remote-User → X-WEBAUTH-USER
# so Grafana's proxy auth auto-signs the user in
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "grafana";
port = cfg.grafanaPort;
auth = true;
extraConfig = ''
reverse_proxy localhost:${toString cfg.grafanaPort} {
header_up X-WEBAUTH-USER {http.request.header.Remote-User}
}
'';
extraHttpConfig = ''
reverse_proxy localhost:${toString cfg.grafanaPort} {
header_up X-Forwarded-Proto https
header_up X-WEBAUTH-USER {http.request.header.Remote-User}
}
'';
}];
# Grafana and Prometheus use system state dirs (/var/lib/grafana,
# /var/lib/prometheus2) — no extraDirs or backup entries needed.
# -----------------------------------------------------------------------
# Uptime Kuma monitor for Grafana
# -----------------------------------------------------------------------
+23 -1
View File
@@ -163,7 +163,7 @@ let
in
{
options.homey.authelia = {
enable = lib.mkEnableOption "Authelia SSO gateway";
enable = lib.mkEnableOption "Authelia SSO gateway" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -241,6 +241,28 @@ in
requires = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Caddy virtual host — no forward_auth (Authelia IS the auth gateway)
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "auth";
port = cfg.port;
auth = false;
}];
# -----------------------------------------------------------------------
# Storage directories
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "authelia"; }
{ path = "authelia/config"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/authelia" ];
# -----------------------------------------------------------------------
# Uptime Kuma monitor for this service
# -----------------------------------------------------------------------
+29 -1
View File
@@ -23,7 +23,7 @@ let
in
{
options.homey.eurovote = {
enable = lib.mkEnableOption "Eurovision Vote app";
enable = lib.mkEnableOption "Eurovision Vote app" // { default = true; };
};
config = lib.mkIf cfg.enable {
@@ -48,6 +48,34 @@ in
logoutRedirectUrl = "https://auth.${domain}/logout";
};
# -----------------------------------------------------------------------
# Caddy virtual host — forward_auth; X-Remote-User passed to Django's
# RemoteUserMiddleware for automatic SSO login
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "eurovision-vote";
port = 8007;
auth = true;
extraConfig = ''
reverse_proxy localhost:8007 {
header_up X-Remote-User {http.request.header.Remote-User}
}
'';
extraHttpConfig = ''
reverse_proxy localhost:8007 {
header_up X-Forwarded-Proto https
header_up X-Remote-User {http.request.header.Remote-User}
}
'';
}];
# Eurovision Vote uses DynamicUser + /var/lib/eurovote — no extraDirs needed.
# -----------------------------------------------------------------------
# Backup — /var/lib/eurovote holds the SQLite DB with votes
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "/var/lib/eurovote" ];
# -----------------------------------------------------------------------
# Uptime Kuma monitor
# -----------------------------------------------------------------------
+1 -1
View File
@@ -30,7 +30,7 @@ let
in
{
options.homey.giteaRunner = {
enable = lib.mkEnableOption "Gitea Actions runner";
enable = lib.mkEnableOption "Gitea Actions runner" // { default = true; };
name = lib.mkOption {
type = lib.types.str;
+23 -1
View File
@@ -32,7 +32,7 @@ let
in
{
options.homey.gitea = {
enable = lib.mkEnableOption "Gitea Git server";
enable = lib.mkEnableOption "Gitea Git server" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -188,6 +188,28 @@ in
requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Caddy virtual host — no forward_auth; git clients can't handle SSO redirects
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "git";
port = cfg.port;
auth = false;
}];
# -----------------------------------------------------------------------
# Storage directories (UID 1000 = Gitea's internal user)
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "gitea"; user = "1000"; group = "1000"; }
{ path = "gitea/data"; user = "1000"; group = "1000"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/gitea" ];
# -----------------------------------------------------------------------
# Uptime Kuma monitor for this service
# -----------------------------------------------------------------------
+23 -1
View File
@@ -14,7 +14,7 @@ let
in
{
options.homey.jellyfin = {
enable = lib.mkEnableOption "Jellyfin media server";
enable = lib.mkEnableOption "Jellyfin media server" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -51,5 +51,27 @@ in
after = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Caddy virtual host — no forward_auth; Jellyfin has its own login UI
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "jellyfin";
port = cfg.port;
auth = false;
}];
# -----------------------------------------------------------------------
# Storage directories
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "jellyfin"; }
{ path = "jellyfin/config"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/jellyfin" ];
};
}
+23 -1
View File
@@ -23,7 +23,7 @@ let
in
{
options.homey.mealie = {
enable = lib.mkEnableOption "Mealie recipe manager";
enable = lib.mkEnableOption "Mealie recipe manager" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -96,6 +96,28 @@ in
requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Caddy virtual host — no forward_auth; Mealie uses LDAP login page
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "mealie";
port = cfg.port;
auth = false;
}];
# -----------------------------------------------------------------------
# Storage directories
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "mealie"; }
{ path = "mealie/data"; mode = "0755"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/mealie" ];
# -----------------------------------------------------------------------
# Uptime Kuma monitor
# -----------------------------------------------------------------------
+49 -1
View File
@@ -52,7 +52,7 @@ let
in
{
options.homey.nextcloud = {
enable = lib.mkEnableOption "Nextcloud file server";
enable = lib.mkEnableOption "Nextcloud file server" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -166,6 +166,54 @@ in
];
};
# -----------------------------------------------------------------------
# Caddy virtual host — no forward_auth; Nextcloud manages its own auth
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "nextcloud";
port = cfg.port;
auth = false;
extraConfig = ''
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
request_body {
max_size 5GB
}
reverse_proxy localhost:${toString cfg.port} {
header_up X-Forwarded-For {remote_host}
}
'';
extraHttpConfig = ''
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
request_body {
max_size 5GB
}
reverse_proxy localhost:${toString cfg.port} {
header_up X-Forwarded-Proto https
header_up X-Forwarded-For {remote_host}
}
'';
}];
# -----------------------------------------------------------------------
# Storage directories
# UID 33 = www-data in the Nextcloud container
# UID 999 = postgres — must own the db dir (creates files directly in it)
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "nextcloud"; }
{ path = "nextcloud/html"; user = "33"; group = "33"; }
{ path = "nextcloud/db"; mode = "0700"; user = "999"; group = "999"; }
{ path = "nextcloud/db-dump"; mode = "0700"; }
];
# -----------------------------------------------------------------------
# Backup — exclude raw DB dir (pg_dump file in db-dump/ is used instead)
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/nextcloud" ];
homey.backup.extraExcludePaths = [ "${dataDir}/nextcloud/db" ];
# -----------------------------------------------------------------------
# Uptime Kuma monitor for this service
# -----------------------------------------------------------------------
+25 -25
View File
@@ -62,7 +62,7 @@ let
in
{
options.homey.ntfy = {
enable = lib.mkEnableOption "Ntfy push notification server";
enable = lib.mkEnableOption "Ntfy push notification server" // { default = true; };
port = lib.mkOption {
type = lib.types.port;
@@ -105,36 +105,14 @@ in
mode = "0444";
};
# Create ntfy data directories on the external HD before ntfy starts.
# Runs as a separate root service (outside ntfy-sh's restricted namespace)
# so it can access /mnt/data without hitting ReadWritePaths restrictions.
systemd.services.ntfy-sh-mkdir = {
description = "Create Ntfy data directories on external HD";
wantedBy = [ "ntfy-sh.service" ];
before = [ "ntfy-sh.service" ];
after = [ "mnt-data.mount" ];
requires = [ "mnt-data.mount" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "ntfy-mkdir" ''
set -euo pipefail
mkdir -p ${dataDir}/ntfy/attachments
chown -R ntfy-sh:ntfy-sh ${dataDir}/ntfy
chmod 0750 ${dataDir}/ntfy ${dataDir}/ntfy/attachments
'';
};
};
# Ensure ntfy-sh starts after the HD is mounted and dirs are ready.
# Widen ReadWritePaths so ntfy-sh can write to the external HD.
# Inject the VAPID private key at runtime: ExecStartPre copies the
# build-time base config to /run/ntfy-sh/server.yml and appends the key,
# then we override ExecStart to use that runtime config file.
systemd.services.ntfy-sh = {
after = lib.mkAfter [ "mnt-data.mount" "ntfy-sh-mkdir.service" ];
requires = lib.mkAfter [ "mnt-data.mount" "ntfy-sh-mkdir.service" ];
after = lib.mkAfter [ "mnt-data.mount" "systemd-tmpfiles-setup.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ];
serviceConfig = {
ReadWritePaths = lib.mkAfter [ "${dataDir}/ntfy" ];
RuntimeDirectory = "ntfy-sh"; # creates /run/ntfy-sh, owned by ntfy-sh user
@@ -198,6 +176,28 @@ in
};
};
# -----------------------------------------------------------------------
# Caddy virtual host — no forward_auth; ntfy uses its own token auth
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "ntfy";
port = cfg.port;
auth = false;
}];
# -----------------------------------------------------------------------
# Storage directories (owned by the ntfy-sh system user)
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "ntfy"; user = "ntfy-sh"; group = "ntfy-sh"; }
{ path = "ntfy/attachments"; user = "ntfy-sh"; group = "ntfy-sh"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/ntfy" ];
# -----------------------------------------------------------------------
# Uptime Kuma monitor for this service
# -----------------------------------------------------------------------
+15 -1
View File
@@ -21,7 +21,7 @@ let
in
{
options.homey.openldap = {
enable = lib.mkEnableOption "OpenLDAP identity provider";
enable = lib.mkEnableOption "OpenLDAP identity provider" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -114,6 +114,20 @@ in
requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Storage directories
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "openldap"; }
{ path = "openldap/etc-ldap-slapd.d"; }
{ path = "openldap/var-lib-ldap"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/openldap" ];
# -----------------------------------------------------------------------
# Firewall — openldap port is NOT opened externally
# -----------------------------------------------------------------------
+27 -1
View File
@@ -28,7 +28,7 @@ let
in
{
options.homey.paperless = {
enable = lib.mkEnableOption "Paperless-ngx document management";
enable = lib.mkEnableOption "Paperless-ngx document management" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -124,6 +124,32 @@ in
requires = lib.mkAfter [ "mnt-data.mount" "podman-paperless-redis.service" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Caddy virtual host — forward_auth; Remote-User passed to Paperless for SSO
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "paperless";
port = cfg.port;
auth = true;
}];
# -----------------------------------------------------------------------
# Storage directories (UID 1000 = USERMAP_UID in container)
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "paperless"; }
{ path = "paperless/data"; user = "1000"; group = "1000"; }
{ path = "paperless/media"; user = "1000"; group = "1000"; }
{ path = "paperless/consume"; user = "1000"; group = "1000"; }
{ path = "paperless/export"; user = "1000"; group = "1000"; }
];
# -----------------------------------------------------------------------
# Backup — exclude consume dir (unprocessed drops, usually empty)
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/paperless" ];
homey.backup.extraExcludePaths = [ "${dataDir}/paperless/consume" ];
# -----------------------------------------------------------------------
# Uptime Kuma monitor
# -----------------------------------------------------------------------
+12 -1
View File
@@ -16,7 +16,7 @@ let
in
{
options.homey.phpldapadmin = {
enable = lib.mkEnableOption "phpLDAPadmin web interface";
enable = lib.mkEnableOption "phpLDAPadmin web interface" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -50,6 +50,17 @@ in
wants = lib.mkAfter [ "podman-openldap.service" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Caddy virtual host — forward_auth + reverse_proxy
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "ldapadmin";
port = cfg.port;
auth = true;
}];
# phpLDAPadmin is stateless (no persistent volumes) — no storage or backup entries needed.
# -----------------------------------------------------------------------
# Uptime Kuma monitor for this service
# -----------------------------------------------------------------------
+23 -1
View File
@@ -18,7 +18,7 @@ let
in
{
options.homey.transmission = {
enable = lib.mkEnableOption "Transmission torrent client";
enable = lib.mkEnableOption "Transmission torrent client" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -60,5 +60,27 @@ in
after = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
};
# -----------------------------------------------------------------------
# Caddy virtual host — forward_auth, admins only
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "torrent";
port = cfg.port;
auth = true;
}];
# -----------------------------------------------------------------------
# Storage directories
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "transmission"; }
{ path = "transmission/config"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/transmission" ];
};
}
+22 -1
View File
@@ -215,7 +215,7 @@ in
};
options.homey.uptimeKuma = {
enable = lib.mkEnableOption "Uptime Kuma uptime monitoring";
enable = lib.mkEnableOption "Uptime Kuma uptime monitoring" // { default = true; };
image = lib.mkOption {
type = lib.types.str;
@@ -285,6 +285,27 @@ in
};
};
# -----------------------------------------------------------------------
# Caddy virtual host — forward_auth, admins only
# -----------------------------------------------------------------------
homey.caddy.virtualHosts = [{
subdomain = "uptime";
port = cfg.port;
auth = true;
}];
# -----------------------------------------------------------------------
# Storage directories
# -----------------------------------------------------------------------
homey.storage.extraDirs = [
{ path = "uptime-kuma"; }
];
# -----------------------------------------------------------------------
# Backup
# -----------------------------------------------------------------------
homey.backup.extraPaths = [ "${dataDir}/uptime-kuma" ];
# -----------------------------------------------------------------------
# Uptime Kuma self-monitor
# -----------------------------------------------------------------------
+23 -33
View File
@@ -63,6 +63,22 @@ in
default = "ext4";
description = "Filesystem type of the external drive.";
};
extraDirs = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
path = lib.mkOption {
type = lib.types.str;
description = "Path relative to mountPoint (e.g. \"gitea/data\").";
};
mode = lib.mkOption { type = lib.types.str; default = "0750"; };
user = lib.mkOption { type = lib.types.str; default = "root"; };
group = lib.mkOption { type = lib.types.str; default = "root"; };
};
});
default = [];
description = "Per-service directories to create under mountPoint. Each service module contributes its own entries.";
};
};
config = lib.mkIf (cfg.device != "") {
@@ -79,46 +95,20 @@ in
];
};
# Ensure the mount point directory exists
# Mount point root + shared infrastructure dirs (restic cache, shared media).
# Per-service dirs are contributed via homey.storage.extraDirs by each
# service module, so adding a new service only requires editing that module.
systemd.tmpfiles.rules = [
"d ${cfg.mountPoint} 0755 root root -"
# Service subdirectories — created on boot so containers can start
# even before any data is restored into them.
"d ${cfg.mountPoint}/openldap 0750 root root -"
"d ${cfg.mountPoint}/openldap/etc-ldap-slapd.d 0750 root root -"
"d ${cfg.mountPoint}/openldap/var-lib-ldap 0750 root root -"
"d ${cfg.mountPoint}/authelia 0750 root root -"
"d ${cfg.mountPoint}/authelia/config 0750 root root -"
"d ${cfg.mountPoint}/gitea 0750 1000 1000 -"
"d ${cfg.mountPoint}/gitea/data 0750 1000 1000 -"
"d ${cfg.mountPoint}/nextcloud 0750 root root -"
# www-data in the Nextcloud container is UID 33; it needs rx on the
# directory and rw on all files it creates inside.
"d ${cfg.mountPoint}/nextcloud/html 0750 33 33 -"
# Postgres (uid 999) must own this directory — it creates files directly in it
"d ${cfg.mountPoint}/nextcloud/db 0700 999 999 -"
"d ${cfg.mountPoint}/jellyfin 0750 root root -"
"d ${cfg.mountPoint}/jellyfin/config 0750 root root -"
# Shared media directories used by both Jellyfin and Transmission.
"d ${cfg.mountPoint}/media 0755 root root -"
"d ${cfg.mountPoint}/media/movies 0755 root root -"
"d ${cfg.mountPoint}/media/tvshows 0755 root root -"
"d ${cfg.mountPoint}/media/general 0755 root root -"
"d ${cfg.mountPoint}/media/complete 0755 root root -"
"d ${cfg.mountPoint}/transmission 0750 root root -"
"d ${cfg.mountPoint}/transmission/config 0750 root root -"
"d ${cfg.mountPoint}/uptime-kuma 0750 root root -"
"d ${cfg.mountPoint}/ntfy 0750 ntfy-sh ntfy-sh -"
"d ${cfg.mountPoint}/ntfy/attachments 0750 ntfy-sh ntfy-sh -"
"d ${cfg.mountPoint}/paperless 0750 root root -"
# Paperless runs as UID 1000 (configured via USERMAP_UID)
"d ${cfg.mountPoint}/paperless/data 0750 1000 1000 -"
"d ${cfg.mountPoint}/paperless/media 0750 1000 1000 -"
"d ${cfg.mountPoint}/paperless/consume 0750 1000 1000 -"
"d ${cfg.mountPoint}/paperless/export 0750 1000 1000 -"
"d ${cfg.mountPoint}/mealie 0750 root root -"
"d ${cfg.mountPoint}/mealie/data 0755 root root -"
"d ${cfg.mountPoint}/restic-cache 0700 root root -"
];
] ++ (map
(d: "d ${cfg.mountPoint}/${d.path} ${d.mode} ${d.user} ${d.group} -")
config.homey.storage.extraDirs);
};
}