feat(services): integrate api service for flame

This commit is contained in:
Noah Masur
2026-09-12 21:56:19 +00:00
parent d6209c039b
commit 985ab2b433
10 changed files with 263 additions and 10 deletions
+19
View File
@@ -1,5 +1,24 @@
# Changelog # 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 ## 2026-09-07
- **Configured OpenID Connect (OIDC) authentication for Paperless-ngx**: - **Configured OpenID Connect (OIDC) authentication for Paperless-ngx**:
Generated
+64 -9
View File
@@ -1,10 +1,31 @@
{ {
"nodes": { "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": { "cl-nix-lite": {
"inputs": { "inputs": {
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"systems": "systems", "systems": "systems_2",
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix"
}, },
"locked": { "locked": {
@@ -151,6 +172,24 @@
} }
}, },
"flake-utils": { "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": { "inputs": {
"systems": [ "systems": [
"mac-app-util", "mac-app-util",
@@ -170,9 +209,9 @@
"type": "indirect" "type": "indirect"
} }
}, },
"flake-utils_2": { "flake-utils_3": {
"inputs": { "inputs": {
"systems": "systems_3" "systems": "systems_4"
}, },
"locked": { "locked": {
"lastModified": 1731533236, "lastModified": 1731533236,
@@ -213,9 +252,9 @@
"inputs": { "inputs": {
"cl-nix-lite": "cl-nix-lite", "cl-nix-lite": "cl-nix-lite",
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-utils": "flake-utils", "flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_3", "nixpkgs": "nixpkgs_3",
"systems": "systems_2", "systems": "systems_3",
"treefmt-nix": "treefmt-nix_2" "treefmt-nix": "treefmt-nix_2"
}, },
"locked": { "locked": {
@@ -274,7 +313,7 @@
}, },
"nix2vim": { "nix2vim": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_3",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
@@ -495,6 +534,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"api": "api",
"darwin": "darwin", "darwin": "darwin",
"disko": "disko", "disko": "disko",
"home-manager": "home-manager", "home-manager": "home-manager",
@@ -546,6 +586,21 @@
} }
}, },
"systems_2": { "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": { "locked": {
"lastModified": 1689347925, "lastModified": 1689347925,
"narHash": "sha256-ozenz5bFe1UUqOn7f60HRmgc01BgTGIKZ4Xl+HbocGQ=", "narHash": "sha256-ozenz5bFe1UUqOn7f60HRmgc01BgTGIKZ4Xl+HbocGQ=",
@@ -560,7 +615,7 @@
"type": "github" "type": "github"
} }
}, },
"systems_3": { "systems_4": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@@ -575,7 +630,7 @@
"type": "github" "type": "github"
} }
}, },
"systems_4": { "systems_5": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
@@ -655,7 +710,7 @@
"rust-overlay": [ "rust-overlay": [
"rust-overlay" "rust-overlay"
], ],
"systems": "systems_4" "systems": "systems_5"
}, },
"locked": { "locked": {
"lastModified": 1781873766, "lastModified": 1781873766,
+7
View File
@@ -86,6 +86,12 @@
inputs.rust-overlay.follows = "rust-overlay"; inputs.rust-overlay.follows = "rust-overlay";
}; };
# Generic self-hosted REST API service
api = {
url = "github:nmasur/api";
inputs.nixpkgs.follows = "nixpkgs";
};
# # Text editor # # Text editor
# helix = { # helix = {
# url = "github:helix-editor/helix"; # url = "github:helix-editor/helix";
@@ -127,6 +133,7 @@
baseName = "masu.rs"; baseName = "masu.rs";
in in
{ {
api = "api.${baseName}";
audiobooks = "read.${baseName}"; audiobooks = "read.${baseName}";
auth = "auth.${baseName}"; auth = "auth.${baseName}";
bookmarks = "keep.${baseName}"; bookmarks = "keep.${baseName}";
+3
View File
@@ -66,6 +66,7 @@ lib
overlays = [ overlays = [
inputs.nur.overlays.default inputs.nur.overlays.default
inputs.nix2vim.overlay inputs.nix2vim.overlay
inputs.api.overlays.default
(final: prev: { (final: prev: {
zellij-switch = inputs.zellij-switch.packages.${prev.stdenv.hostPlatform.system}.default; zellij-switch = inputs.zellij-switch.packages.${prev.stdenv.hostPlatform.system}.default;
}) })
@@ -156,6 +157,7 @@ lib
inputs.home-manager.nixosModules.home-manager inputs.home-manager.nixosModules.home-manager
inputs.nix-index-database.nixosModules.default inputs.nix-index-database.nixosModules.default
inputs.disko.nixosModules.disko inputs.disko.nixosModules.disko
inputs.api.nixosModules.default
inputs.wsl.nixosModules.wsl inputs.wsl.nixosModules.wsl
{ imports = (nixFiles ../platforms/nixos); } { imports = (nixFiles ../platforms/nixos); }
module module
@@ -229,6 +231,7 @@ lib
inputs.home-manager.nixosModules.home-manager inputs.home-manager.nixosModules.home-manager
inputs.nix-index-database.nixosModules.default inputs.nix-index-database.nixosModules.default
inputs.disko.nixosModules.disko inputs.disko.nixosModules.disko
inputs.api.nixosModules.default
inputs.wsl.nixosModules.wsl inputs.wsl.nixosModules.wsl
{ {
imports = (nixFiles ../platforms/nixos) ++ (nixFiles ../platforms/generators); imports = (nixFiles ../platforms/nixos) ++ (nixFiles ../platforms/generators);
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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" ];
};
}
@@ -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
'';
};
}; };
} }
@@ -22,7 +22,7 @@ in
services = { services = {
actualbudget.enable = lib.mkDefault true; actualbudget.enable = lib.mkDefault true;
actualtap = { actualtap = {
enable = lib.mkDefault true; enable = lib.mkDefault false;
instances = { instances = {
budget1 = { budget1 = {
port = 3031; port = 3031;
@@ -38,6 +38,7 @@ in
}; };
}; };
}; };
api.enable = lib.mkDefault true;
caddy.enable = lib.mkDefault true; caddy.enable = lib.mkDefault true;
cloudflare.enable = lib.mkDefault true; cloudflare.enable = lib.mkDefault true;
cloudflared.enable = lib.mkDefault true; cloudflared.enable = lib.mkDefault true;