From cffaec5fe8c054e52ae74769b778b031a973f551 Mon Sep 17 00:00:00 2001 From: Alexander Heldt Date: Thu, 13 Aug 2026 12:36:18 +0000 Subject: [PATCH] manatee: add self-hosted todo app Wire the offline-first todo app (github: local /home/alex/code/todo) into manatee, following the app-module convention: - flake input `todo` (local git checkout for now; gitea URL commented) with nixpkgs following. - hosts/manatee/modules/todo: mod.todo option importing the app's NixOS module; runs on 127.0.0.1:8091, secureCookies, token from agenix; the host owns the nginx vhost (todo.ppp.pm, forceSSL + useACMEHost) and homepage card. - mod.todo.enable = true. - certs: todo.ppp.pm DNS-01 cert via hetzner. - home-assistant DNS updater: add `todo` to the Hetzner subdomain list. - secrets: todo-token.age (encrypted access token) + recipients in secrets.nix. --- flake.lock | 30 ++++++++-- flake.nix | 9 +++ hosts/manatee/modules/certs/default.nix | 13 +++++ hosts/manatee/modules/default.nix | 1 + .../modules/home-assistant/default.nix | 2 +- hosts/manatee/modules/todo/default.nix | 58 +++++++++++++++++++ secrets/manatee/todo-token.age | 8 +++ secrets/secrets.nix | 1 + 8 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 hosts/manatee/modules/todo/default.nix create mode 100644 secrets/manatee/todo-token.age diff --git a/flake.lock b/flake.lock index 14761ee..88b6e6d 100644 --- a/flake.lock +++ b/flake.lock @@ -877,11 +877,11 @@ ] }, "locked": { - "lastModified": 1784906229, - "narHash": "sha256-9ZzpwieFwushXgbZZj1W4i8uhI0dmLZCcQSb6qVhn18=", + "lastModified": 1785677859, + "narHash": "sha256-ZXWSL0F1ZDzeLSXFGvRom89nOEq80gXghc+ky/qA/l8=", "ref": "main", - "rev": "374e630d8fd0fa063753ffaa3537ada4fb4bc11e", - "revCount": 55, + "rev": "f9894abfc9cd9c2a5dacb9fe3609b58f5d407392", + "revCount": 69, "type": "git", "url": "ssh://gitea@git.ppp.pm:1122/alex/puppy-tracker.git" }, @@ -911,6 +911,7 @@ "pppdotpm-site": "pppdotpm-site", "puppy-tracker": "puppy-tracker", "solo-referee": "solo-referee", + "todo": "todo", "whib-backend": "whib-backend", "whib-frontend": "whib-frontend" } @@ -1042,6 +1043,27 @@ "type": "github" } }, + "todo": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1786622639, + "narHash": "sha256-q28DCpRkhuFcysdWNio4mIsR5VlonXbM8G+DyxitRPo=", + "ref": "main", + "rev": "a43e443720c3cc3fc38e9367bdac32e6f046c238", + "revCount": 17, + "type": "git", + "url": "file:///home/alex/code/todo" + }, + "original": { + "ref": "main", + "type": "git", + "url": "file:///home/alex/code/todo" + } + }, "whib-backend": { "inputs": { "nixpkgs": [ diff --git a/flake.nix b/flake.nix index 2eff426..61e1749 100644 --- a/flake.nix +++ b/flake.nix @@ -98,6 +98,15 @@ # url = "path:/home/alex/code/solo-referee"; inputs.nixpkgs.follows = "nixpkgs"; }; + + todo = { + # Not yet pushed to gitea; using the local git checkout for now (git+file + # respects .gitignore, so web/node_modules isn't copied into the store). + # Flip these once the repo is published, matching the other apps above. + # url = "git+ssh://gitea@git.ppp.pm:1122/alex/todo.git?ref=main"; + url = "git+file:///home/alex/code/todo?ref=main"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = diff --git a/hosts/manatee/modules/certs/default.nix b/hosts/manatee/modules/certs/default.nix index 0d16237..788ed83 100644 --- a/hosts/manatee/modules/certs/default.nix +++ b/hosts/manatee/modules/certs/default.nix @@ -59,6 +59,19 @@ "--http-timeout=60" ]; }; + + "todo.ppp.pm" = { + dnsProvider = "hetzner"; + environmentFile = config.age.secrets.hetzner-dns.path; + group = "nginx"; + + extraLegoFlags = [ + "--dns.resolvers=1.1.1.1:53,8.8.8.8:53" + "--dns.propagation-wait=60s" + "--dns-timeout=60" + "--http-timeout=60" + ]; + }; }; }; diff --git a/hosts/manatee/modules/default.nix b/hosts/manatee/modules/default.nix index f9553ec..e4e5c1a 100644 --- a/hosts/manatee/modules/default.nix +++ b/hosts/manatee/modules/default.nix @@ -27,6 +27,7 @@ in disk-smart.enable = true; puppy-tracker.enable = true; solo-referee.enable = true; + todo.enable = true; }; }; } diff --git a/hosts/manatee/modules/home-assistant/default.nix b/hosts/manatee/modules/home-assistant/default.nix index bb870c0..c841f35 100644 --- a/hosts/manatee/modules/home-assistant/default.nix +++ b/hosts/manatee/modules/home-assistant/default.nix @@ -251,7 +251,7 @@ in ]; script = '' - SUBDOMAINS="ha komga romm puppy" + SUBDOMAINS="ha komga romm puppy todo" INTERFACE="enp3s0" CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me) diff --git a/hosts/manatee/modules/todo/default.nix b/hosts/manatee/modules/todo/default.nix new file mode 100644 index 0000000..d0994e2 --- /dev/null +++ b/hosts/manatee/modules/todo/default.nix @@ -0,0 +1,58 @@ +{ + inputs, + lib, + config, + ... +}: +let + enabled = config.mod.todo.enable; + nginxEnabled = config.mod.nginx.enable; + port = 8091; +in +{ + options = { + mod.todo = { + enable = lib.mkEnableOption "Enable todo module"; + }; + }; + + imports = [ + inputs.todo.nixosModules.default + ]; + + config = lib.mkIf enabled { + mod.homepage.services = [ + { + name = "Todo"; + port = port; + description = "Offline-first todo"; + # Login needs HTTPS (Secure cookies), so link to the public vhost. + url = "https://todo.ppp.pm"; + } + ]; + + services.todo = { + enable = true; + address = "127.0.0.1"; + inherit port; + # Served publicly over HTTPS via the nginx vhost below. + secureCookies = true; + tokenFile = config.age.secrets."todo-token".path; + # sessionSecretFile is left unset: the app generates one in its state dir + # (/var/lib/todo) on first boot, mode 0600. + }; + + services.nginx = lib.mkIf nginxEnabled { + virtualHosts."todo.ppp.pm" = { + forceSSL = true; + useACMEHost = "todo.ppp.pm"; + + locations."/" = { + proxyPass = "http://127.0.0.1:${toString port}"; + }; + }; + }; + + age.secrets."todo-token".file = ../../../../secrets/manatee/todo-token.age; + }; +} diff --git a/secrets/manatee/todo-token.age b/secrets/manatee/todo-token.age new file mode 100644 index 0000000..b429880 --- /dev/null +++ b/secrets/manatee/todo-token.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 wkRvNA 11epAkGKr0IpXqtWGQ73rZUsiWQRlL++fc2B2TvQ90M +lSKGeNiL82UIyhg1wrY9ylltFcITDffJLgA6j02eS2M +-> ssh-ed25519 +oNaHQ pg1rC56bQRxO2Eb5onV8TkeB/Wsc7HhvufBfslkK+gA +znYqNOBhQX9bx5k07vVTHye/RLxOAkQ2dCagZWyWBkQ +--- Dxthx4NFJfe26jFnXH/3NYILME+tlO2FUsSVGCP4ucY +]&́+/D[6HIe>I N1Ak4ZHG +T}o8f<ۦr< \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 89606b7..46c6328 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -44,6 +44,7 @@ in { "manatee/romm-metadata-api-keys.age".publicKeys = [ manatee alex ]; "manatee/puppy-tracker-invite-code.age".publicKeys = [ manatee alex ]; "manatee/solo-referee-api-key.age".publicKeys = [ manatee alex ]; + "manatee/todo-token.age".publicKeys = [ manatee alex ]; "backwards/root.backwards.age".publicKeys = [ backwards alex ]; "backwards/root.backwards.pub.age".publicKeys = [ backwards alex ];