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.
This commit is contained in:
Alexander Heldt
2026-08-13 12:36:18 +00:00
parent d0ef7f5b7e
commit cffaec5fe8
8 changed files with 117 additions and 5 deletions
Generated
+26 -4
View File
@@ -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": [
+9
View File
@@ -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 =
+13
View File
@@ -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"
];
};
};
};
+1
View File
@@ -27,6 +27,7 @@ in
disk-smart.enable = true;
puppy-tracker.enable = true;
solo-referee.enable = true;
todo.enable = true;
};
};
}
@@ -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)
+58
View File
@@ -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;
};
}
+8
View File
@@ -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š‘ô[6HIÂe>™“‚¾Û¡1Ak¬’ä4ZHG
T}o8×f<Û¦r<Â
+1
View File
@@ -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 ];