From 985ab2b433a7d46b22737fbf1effbe90a0768bd6 Mon Sep 17 00:00:00 2001 From: Noah Masur <7386960+nmasur@users.noreply.github.com> Date: Sat, 12 Sep 2026 16:22:53 +0000 Subject: [PATCH] feat(services): integrate api service for flame --- docs/CHANGELOG.md | 19 ++++ flake.lock | 73 ++++++++++-- flake.nix | 7 ++ lib/default.nix | 3 + .../api/api-actual-budget1-sync-id.age | 17 +++ .../api/api-actual-budget2-sync-id.age | 17 +++ .../presets/services/api/api-actual-keys.age | 18 +++ .../nmasur/presets/services/api/api.nix | 104 ++++++++++++++++++ .../nmasur/presets/services/postgresql.nix | 12 ++ .../nmasur/profiles/communications.nix | 3 +- 10 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget1-sync-id.age create mode 100644 platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget2-sync-id.age create mode 100644 platforms/nixos/modules/nmasur/presets/services/api/api-actual-keys.age create mode 100644 platforms/nixos/modules/nmasur/presets/services/api/api.nix diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 122d4349..e596e85a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 2026-09-12 + +- **Integrated `api` service for `flame`**: + - Added `github:nmasur/api` as a flake input with `inputs.nixpkgs.follows = "nixpkgs"`. + - Added hostname `api = "api.masu.rs"`. + - Added `inputs.api.overlays.default` to system overlays and `inputs.api.nixosModules.default` to `buildNixos` and `generateImage` modules in `lib/default.nix`. + - Created preset `platforms/nixos/modules/nmasur/presets/services/api/api.nix` with options for `nmasur.presets.services.api.enable`: + - Configures `services.api.enable = true` and `services.api.hostname = hostnames.api`. + - Configures `services.api.backends.actual` with `actualServerUrl`, `apiKeysFile`, `serverPasswordFile`, and budget sync ID files for `budget1` and `budget2`. + - Added secret definitions for `api-actual-keys`, `api-actual-budget1-sync-id`, and `api-actual-budget2-sync-id` owned by `api_actual` with prefixes `API_KEYS=`, `ACTUAL_SYNC_ID_BUDGET1=`, and `ACTUAL_SYNC_ID_BUDGET2=`. + - Reused `config.secrets.actualbudget-password.dest` for upstream Actual server password. + - Ordered `systemd.services.api-actual` after and required by `postgresql-setup.service`, secret services, and `actual.service`. + - Added PostgreSQL peer authentication for `api_actual` (`local api_actual api_actual peer`) and database backup via `services.postgresqlBackup.databases = [ "api_actual" ]`. + - Added `systemd.services.postgresql-setup.preStart` hook in `postgresql.nix` to refresh collation version on `template1` and `postgres` (`ALTER DATABASE ... REFRESH COLLATION VERSION`), preventing database creation failure after glibc upgrades. + - Mounted generated `caddyRoutes` onto `nmasur.presets.services.caddy.routes`. + - Added `hostnames.api` to `services.cloudflare-dyndns.domains`. + - Added `/var/lib/api-actual` to restic backups. + - Enabled `nmasur.presets.services.api` and disabled `actualtap` in `platforms/nixos/modules/nmasur/profiles/communications.nix`. + ## 2026-09-07 - **Configured OpenID Connect (OIDC) authentication for Paperless-ngx**: diff --git a/flake.lock b/flake.lock index cc62e46d..5ee07504 100644 --- a/flake.lock +++ b/flake.lock @@ -1,10 +1,31 @@ { "nodes": { + "api": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1789229011, + "narHash": "sha256-sbC3E9CAQwWto+ynyA0zXvEMMK5jboVnYPYoqTcxm0Y=", + "owner": "nmasur", + "repo": "api", + "rev": "28a522a274fe86da6d3390adcc026a6618911cb0", + "type": "github" + }, + "original": { + "owner": "nmasur", + "repo": "api", + "type": "github" + } + }, "cl-nix-lite": { "inputs": { "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", - "systems": "systems", + "systems": "systems_2", "treefmt-nix": "treefmt-nix" }, "locked": { @@ -151,6 +172,24 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { "inputs": { "systems": [ "mac-app-util", @@ -170,9 +209,9 @@ "type": "indirect" } }, - "flake-utils_2": { + "flake-utils_3": { "inputs": { - "systems": "systems_3" + "systems": "systems_4" }, "locked": { "lastModified": 1731533236, @@ -213,9 +252,9 @@ "inputs": { "cl-nix-lite": "cl-nix-lite", "flake-compat": "flake-compat", - "flake-utils": "flake-utils", + "flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs_3", - "systems": "systems_2", + "systems": "systems_3", "treefmt-nix": "treefmt-nix_2" }, "locked": { @@ -274,7 +313,7 @@ }, "nix2vim": { "inputs": { - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "nixpkgs": [ "nixpkgs" ] @@ -495,6 +534,7 @@ }, "root": { "inputs": { + "api": "api", "darwin": "darwin", "disko": "disko", "home-manager": "home-manager", @@ -546,6 +586,21 @@ } }, "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { "locked": { "lastModified": 1689347925, "narHash": "sha256-ozenz5bFe1UUqOn7f60HRmgc01BgTGIKZ4Xl+HbocGQ=", @@ -560,7 +615,7 @@ "type": "github" } }, - "systems_3": { + "systems_4": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -575,7 +630,7 @@ "type": "github" } }, - "systems_4": { + "systems_5": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -655,7 +710,7 @@ "rust-overlay": [ "rust-overlay" ], - "systems": "systems_4" + "systems": "systems_5" }, "locked": { "lastModified": 1781873766, diff --git a/flake.nix b/flake.nix index 5efd8e3b..c66fe965 100644 --- a/flake.nix +++ b/flake.nix @@ -86,6 +86,12 @@ inputs.rust-overlay.follows = "rust-overlay"; }; + # Generic self-hosted REST API service + api = { + url = "github:nmasur/api"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # # Text editor # helix = { # url = "github:helix-editor/helix"; @@ -127,6 +133,7 @@ baseName = "masu.rs"; in { + api = "api.${baseName}"; audiobooks = "read.${baseName}"; auth = "auth.${baseName}"; bookmarks = "keep.${baseName}"; diff --git a/lib/default.nix b/lib/default.nix index bb82f1aa..718b0bf9 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -66,6 +66,7 @@ lib overlays = [ inputs.nur.overlays.default inputs.nix2vim.overlay + inputs.api.overlays.default (final: prev: { zellij-switch = inputs.zellij-switch.packages.${prev.stdenv.hostPlatform.system}.default; }) @@ -156,6 +157,7 @@ lib inputs.home-manager.nixosModules.home-manager inputs.nix-index-database.nixosModules.default inputs.disko.nixosModules.disko + inputs.api.nixosModules.default inputs.wsl.nixosModules.wsl { imports = (nixFiles ../platforms/nixos); } module @@ -229,6 +231,7 @@ lib inputs.home-manager.nixosModules.home-manager inputs.nix-index-database.nixosModules.default inputs.disko.nixosModules.disko + inputs.api.nixosModules.default inputs.wsl.nixosModules.wsl { imports = (nixFiles ../platforms/nixos) ++ (nixFiles ../platforms/generators); diff --git a/platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget1-sync-id.age b/platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget1-sync-id.age new file mode 100644 index 00000000..525b663e --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget1-sync-id.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBaUWNy +UGZPWkFhOS9nZ0N4bWY1S1FUc2pIVS95R2NLK2xLMWFmamNFNDBNCm5yQXhCNXM4 +cUtqbHdjcVRmTWUzd2JoeUtMWDQ3ZEtwcFdsSU9MSE1pcDQKLT4gc3NoLWVkMjU1 +MTkgWXlTVU1RIEtDMUo5cERoSHd0dm9jNCtMNDNWTkNXbTc2bjlPbUR4K0phSWdT +QVR2bUEKY3Y0b29MbzVCSVN0UWhkcEtzN2pBT3A4OWhpeitGQjAvLzg1OUVRdENQ +QQotPiBzc2gtZWQyNTUxOSBuanZYNUEgOWc0aXJtMWNKcXpUMEw4blJCSTZnVS9H +YmVyVE13TG1qaTcrdThMRk94WQovNHl5ZHJrYlhRNDVQUkc5TXRKd1Y2TXRUb0p0 +NElrT1BzbWwwd2pFb1VvCi0+IHNzaC1lZDI1NTE5IENxSU9VQSB0RlpZVnAybFZk +R0E1TTVSR3lxVmpqVnNPOHo2VG5xckdONllVcUZWRzFJCk1RUnhoUzBPc1h5cmdn +MFdtSHRvU0xBRWllKy90dW9aV2NhVUVqaVpoVDgKLT4gc3NoLWVkMjU1MTkgejFP +Y1p3IFY2RmJMOHo1V0U1K3FNeVBhdDVYUE5lYnFHV2ZqZm5Uek1waDhNeGIwaDQK +UFJBcFdBb2hlOWxoTHIwdUtibjk2Q1E1emFJbVlxNDdLSzlqbWI1dm5XbwotLS0g +RDV3bWVQUG95c2h4S0lJQlJ4dlNBYkVNb2sxTlUxc0tlQ2pzb04yVmhPcwpv4CRQ +kRFkaF8/q1wY4JC7oD6VbJzZWpMsAUbQQWEs8QI9nI2HyqHB4yZ/dT8wSfoh7JPY +fHIVqN25xXGTu7uR2S/8Iic= +-----END AGE ENCRYPTED FILE----- diff --git a/platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget2-sync-id.age b/platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget2-sync-id.age new file mode 100644 index 00000000..204c0adc --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/api/api-actual-budget2-sync-id.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyA5Y0JT +SjBqU1dEUVIwSXFBY3h3M3FDMFFpUTdSVmp6S3Y5QXYxK2Rjc2hrCll4UzN0RXVl +a2s0ZkxOdDIxd05kVkdJZWJHQmQ3ZDNjeGRpWnVFQ1Jzd3cKLT4gc3NoLWVkMjU1 +MTkgWXlTVU1RIDJHS2JTM2R1NzNLSG12bC9yRURBYWx5K1N4d2VWUS8vU2NHamta +amsvUlUKRy8wOWc0cStGN3M1RnpGc2Z0aCt3Y3JiU3dZd3I0V0sxOWdSSE5OUEVy +QQotPiBzc2gtZWQyNTUxOSBuanZYNUEgSmJBbngwMVN3UHpvcHkxUjkvQ1FJaVdX +MzZrVnZtS1VRQVRGWVJocnB6WQpvb01VZ2tleGh6VW9FRTJjN0EwMncyVWZ4WHpi +WXRGZklZcHRkSDYyYmNZCi0+IHNzaC1lZDI1NTE5IENxSU9VQSBKTE0xWlMzRTh0 +cEJjUU9NY0NiRWNGU1hvNzQ4clRQRTRJaU1VVzgrZDJZClluVXZiTHp6YUZxOWF3 +U0UzWkRIM0xicmw3Qzc5QnhjeGNKOWFGMnptc28KLT4gc3NoLWVkMjU1MTkgejFP +Y1p3IFNkS01KbzhFcW9iS05OcVI2Szl3dXI5QTAvejdhRU1EbEhnQTcwRGo2MWcK +V01MTmhoYlE5YnBvL21qT0tubXRTOGtDYno2Wjd6M2hlY3Z4UHBjQU9WbwotLS0g +VWpndk84OHJSRVMwUFg4aUYwQmNVamIwN3BVL1dzcEV2LzZBODE5bmdoYwpg3LTS +kiMJpjLJ12CGFqmMCjwWwiJHjsC8FM47Ptf2uRxjD1XsOq6MWKHYSI9KxzrgJdyY +uxhU1m3Jfi5+7+oWX6BhfQA= +-----END AGE ENCRYPTED FILE----- diff --git a/platforms/nixos/modules/nmasur/presets/services/api/api-actual-keys.age b/platforms/nixos/modules/nmasur/presets/services/api/api-actual-keys.age new file mode 100644 index 00000000..9ffef47c --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/api/api-actual-keys.age @@ -0,0 +1,18 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBlZEhP +b1ZUdTlROVAydktUNG9lRDVEejMxVmRtNjlhQ2V1NWMyblZFZHlJClczMHM4RXda +TzYzOUFhWmxxUTF0bjJXWVJoSS9HaWZ6TFJ1dndWQXF6WWcKLT4gc3NoLWVkMjU1 +MTkgWXlTVU1RIGZuR28vNVFqLzUxUHJ4Nlc1c2h5ODNQQnh2bFZRU3hDS3VidERF +T3htUmsKRjd6ZVZuTE1lN2hrOTJaaWQxdE01djl0QlJBVUNnRHlEQ3JMWmMxNkEw +cwotPiBzc2gtZWQyNTUxOSBuanZYNUEgM3ZnOFd6REZNNkxXdW9NT2gzYkJ3ZUhm +UlRjb3kxRnppN2M2UERLNUFrSQp0YWRBL3JXZWs5Zyt0RjRBS2JjUkJockE3Ti9m +TzlNR1c4dFJYUzdRSzBNCi0+IHNzaC1lZDI1NTE5IENxSU9VQSA0SFRseGsxMnlI +TzhueGZGS2VzMzVxcmNDZVdPZDYwQ0c4SjNhbWp5OUVJCkpnSXJiVVZiTHY5SDg2 +WnpmRDZQcHlOWnpySTZmUG9hTFdremp2MXEvRFUKLT4gc3NoLWVkMjU1MTkgejFP +Y1p3IHhHWFk4ckt5UCtGM3VGaVljOGl3aFV1UDhCVm5PV0lqRGJRcGg0RHJGejgK +NVR2cmFWTW9IYTRsT0V0VGs4VHBYY1BPM3A3bUY1enA2cVZhU0NTWXI2awotLS0g +a1Q4SnBSWXJxREpuaEJtS2VJaTZiNmVWdFp1QXhUOFJRSVVUUEUrVFA2SQqmH3al +3mHIyywuvc6U0lf0FheRGCp//BFORrAqo9bgJmUj/SsduvT1DNyJBGfi62k/8llH +/oQmcYx8rTIbZzCkDHu18DCxJxiZahaIVV/pilQAtSZVfdq/pPnzCBaB1QMMOnWY +cB/H5eQh +-----END AGE ENCRYPTED FILE----- diff --git a/platforms/nixos/modules/nmasur/presets/services/api/api.nix b/platforms/nixos/modules/nmasur/presets/services/api/api.nix new file mode 100644 index 00000000..315e7778 --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/api/api.nix @@ -0,0 +1,104 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + inherit (config.nmasur.settings) hostnames; + cfg = config.nmasur.presets.services.api; +in + +{ + + options.nmasur.presets.services.api = { + enable = lib.mkEnableOption "API gateway and backend services"; + }; + + config = lib.mkIf cfg.enable { + + services.api = { + enable = true; + hostname = hostnames.api; + backends.actual = { + enable = true; + actualServerUrl = "http://127.0.0.1:${builtins.toString config.nmasur.presets.services.actualbudget.port}"; + apiKeysFile = config.secrets.api-actual-keys.dest; + serverPasswordFile = config.secrets.actualbudget-password.dest; + budgets = { + budget1 = { + syncIdFile = config.secrets.api-actual-budget1-sync-id.dest; + }; + budget2 = { + syncIdFile = config.secrets.api-actual-budget2-sync-id.dest; + }; + }; + }; + }; + + secrets = { + api-actual-keys = { + source = ./api-actual-keys.age; + dest = "${config.secretsDirectory}/api-actual-keys"; + owner = "api_actual"; + group = "api_actual"; + prefix = "API_KEYS="; + }; + api-actual-budget1-sync-id = { + source = ./api-actual-budget1-sync-id.age; + dest = "${config.secretsDirectory}/api-actual-budget1-sync-id"; + owner = "api_actual"; + group = "api_actual"; + prefix = "ACTUAL_SYNC_ID_BUDGET1="; + }; + api-actual-budget2-sync-id = { + source = ./api-actual-budget2-sync-id.age; + dest = "${config.secretsDirectory}/api-actual-budget2-sync-id"; + owner = "api_actual"; + group = "api_actual"; + prefix = "ACTUAL_SYNC_ID_BUDGET2="; + }; + }; + + systemd.services.api-actual = { + after = [ + "postgresql-setup.service" + "actual.service" + "api-actual-keys-secret.service" + "api-actual-budget1-sync-id-secret.service" + "api-actual-budget2-sync-id-secret.service" + "actualbudget-password-secret.service" + ]; + requires = [ + "postgresql-setup.service" + "api-actual-keys-secret.service" + "api-actual-budget1-sync-id-secret.service" + "api-actual-budget2-sync-id-secret.service" + "actualbudget-password-secret.service" + ]; + }; + + # Postgres peer authentication for api_actual + services.postgresql.authentication = lib.mkAfter '' + local api_actual api_actual peer + ''; + + # Backup PostgreSQL database for api_actual + services.postgresqlBackup = { + enable = true; + databases = [ "api_actual" ]; + }; + + # Allow web traffic to Caddy + nmasur.presets.services.caddy.routes = config.services.api.caddyRoutes; + + # Configure Cloudflare DNS to point to this machine + services.cloudflare-dyndns.domains = [ hostnames.api ]; + + # Backups + services.restic.backups.default.paths = [ "/var/lib/api-actual" ]; + + }; + +} diff --git a/platforms/nixos/modules/nmasur/presets/services/postgresql.nix b/platforms/nixos/modules/nmasur/presets/services/postgresql.nix index 58a8b1e9..b2a00bd1 100644 --- a/platforms/nixos/modules/nmasur/presets/services/postgresql.nix +++ b/platforms/nixos/modules/nmasur/presets/services/postgresql.nix @@ -37,6 +37,18 @@ in } ]; }; + + # Refresh collation version if glibc was upgraded across system releases + systemd.services.postgresql-setup = { + preStart = '' + while ! ${config.services.postgresql.package}/bin/psql -d postgres -tAc 'SELECT 1' >/dev/null 2>&1; do + if ! systemctl is-active --quiet postgresql.service; then break; fi + sleep 0.1 + done + ${config.services.postgresql.package}/bin/psql -d postgres -tAc 'ALTER DATABASE template1 REFRESH COLLATION VERSION;' || true + ${config.services.postgresql.package}/bin/psql -d postgres -tAc 'ALTER DATABASE postgres REFRESH COLLATION VERSION;' || true + ''; + }; }; } diff --git a/platforms/nixos/modules/nmasur/profiles/communications.nix b/platforms/nixos/modules/nmasur/profiles/communications.nix index 8f109972..63a812c2 100644 --- a/platforms/nixos/modules/nmasur/profiles/communications.nix +++ b/platforms/nixos/modules/nmasur/profiles/communications.nix @@ -22,7 +22,7 @@ in services = { actualbudget.enable = lib.mkDefault true; actualtap = { - enable = lib.mkDefault true; + enable = lib.mkDefault false; instances = { budget1 = { port = 3031; @@ -38,6 +38,7 @@ in }; }; }; + api.enable = lib.mkDefault true; caddy.enable = lib.mkDefault true; cloudflare.enable = lib.mkDefault true; cloudflared.enable = lib.mkDefault true;