6 Commits
Author SHA1 Message Date
Noah Masur 2ac07e2874 remove home-manager module from nixos 2026-09-12 22:29:53 +00:00
Noah Masur 985ab2b433 feat(services): integrate api service for flame 2026-09-12 21:56:19 +00:00
github-actions[bot] d6209c039b flake.lock: Update
Flake lock file updates:

• Updated input 'home-manager':
    'github:nix-community/home-manager/693e8ce0fb240a73c116a03cfd7b19269c87af88' (2026-09-04)
  → 'github:nix-community/home-manager/cd1c9e552f41894aeb5cc5cb353d5a1d61550357' (2026-09-12)
• Updated input 'nix-index-database':
    'github:nix-community/nix-index-database/dbb978fa87faf13398e90ccbc52c343d21c7dd6d' (2026-08-30)
  → 'github:nix-community/nix-index-database/116ad1c2adb642405ef8916f6a94c8626f971344' (2026-09-06)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/801bef6abd86b91e51083066b83fb354a11fc640' (2026-09-04)
  → 'github:nixos/nixpkgs/8ce4ef6cb6f871616146b9fe26d2a5ae594e94fe' (2026-09-10)
• Updated input 'nixpkgs-stable':
    'github:nixos/nixpkgs/a5cc6f2c37bf518436dc8d1c288ccd0c43c2f4c4' (2026-09-03)
  → 'github:nixos/nixpkgs/21a67dc470149f337cecafbe965d8d252a390518' (2026-09-11)
• Updated input 'nur':
    'github:nix-community/nur/82ca52a282cd5f90afabcf7c9acd27571b253523' (2026-09-05)
  → 'github:nix-community/nur/2c789298fc525713063e3b88359d02101651144f' (2026-09-12)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/c361047d3a538f547f1617bb6b410411929ac9cc' (2026-09-05)
  → 'github:oxalica/rust-overlay/228ecefb6329d5a531b77b46b581a2f0c26ee056' (2026-09-12)
• Updated input 'wsl':
    'github:nix-community/NixOS-WSL/eaeb18da90024448a60eb1ec7132eafa4003404e' (2026-07-21)
  → 'github:nix-community/NixOS-WSL/72c92b11bb8289e6651c7fef29cc0a885fd6a255' (2026-09-11)
2026-09-12 08:10:45 +00:00
Noah Masur 3e246645a2 paperless: configure OIDC authentication 2026-09-07 18:16:45 +00:00
Noah Masur ed8be9dee6 jellyfin: forward client IP headers from caddy and cloudflare 2026-09-07 17:00:31 +00:00
Noah Masur ab98a202da nextcloud: implement OIDC authentication 2026-09-07 16:34:45 +00:00
17 changed files with 517 additions and 73 deletions
+41
View File
@@ -1,5 +1,46 @@
# 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**:
- Added secret management for `paperless-oidc-secret.age` via `secrets.paperless-oidc-secret` owned by `paperless:paperless` (0440).
- Configured `systemd.services.paperless-oidc-secret-secret` to run before and be required by all Paperless units, dynamically generating `/var/private/paperless-env` with `PAPERLESS_SOCIALACCOUNT_PROVIDERS` configured for Pocket ID (`auth.masu.rs`) using `client_id = "e6ff7fce-8e67-4c66-8f32-5ec3db4740a9"`, `oauth_pkce_enabled = true`, and email authentication linking (`email_authentication = true`, `verified_email = true`) in the native `APPS` schema.
- Added `services.paperless.environmentFile = "${config.secretsDirectory}/paperless-env"` to securely pass the OIDC configuration to Paperless services without exposing secrets in the Nix store.
- Configured Paperless settings to enable `allauth.socialaccount.providers.openid_connect` in `PAPERLESS_APPS` and enabled `PAPERLESS_REDIRECT_LOGIN_TO_SSO`.
- Configured reverse proxy trust settings (`PAPERLESS_USE_X_FORWARD_HOST`, `PAPERLESS_PROXY_SSL_HEADER`, and `PAPERLESS_TRUSTED_PROXIES`) to ensure callback URIs preserve the `https://` scheme behind Caddy.
- Fixed `systemd.services.paperless-secret` to order before and be required by `paperless-scheduler.service` instead of non-existent `paperless.service`.
- **Configured real client IP forwarding for Jellyfin reverse proxy**:
- Added a `map` handler to Jellyfin's Caddy route in `platforms/nixos/modules/nmasur/presets/services/jellyfin.nix` to resolve `{client_ip}` using Cloudflare's `CF-Connecting-IP` header when available, falling back to `{http.request.remote.host}` for direct local LAN connections.
- Configured `reverse_proxy.headers.request.set` to forward `X-Real-IP`, `X-Forwarded-For`, and `X-Forwarded-Proto` with the resolved client IP and request scheme to Jellyfin.
- **Configured OpenID Connect (OIDC) authentication for Nextcloud**:
- Added `user_oidc` to `services.nextcloud.extraApps`.
- Added secret management for `nextcloud-oidc-secret.age` via `secrets.nextcloud-oidc-secret` with owner `nextcloud` and group `nextcloud` (0440).
- Configured `systemd.services.nextcloud-oidc-secret-secret` to be required by and order before `nextcloud-setup.service`.
- Set `services.nextcloud.settings.overwriteprotocol = "https"` to ensure correct scheme handling for redirect URIs behind reverse proxy.
- Configured automated idempotent upsert of the Pocket ID provider via `systemd.services.nextcloud-setup.postStart` running `nextcloud-occ user_oidc:provider pocket-id` with client ID `c8a32c58-a781-4f14-9070-f498fdfda438`, `--clientsecret-file`, discovery URI `https://${hostnames.auth}/.well-known/openid-configuration`, `--mapping-uid="preferred_username"`, and `--unique-uid=0` to connect OIDC logins directly to existing local Nextcloud accounts.
- Updated `docs/oidc-services.md` with the verified configuration and callback URI (`https://cloud.masu.rs/apps/user_oidc/code`).
## 2026-09-06
- **Configured OpenID Connect (OIDC) authentication for Immich**:
+54 -26
View File
@@ -121,15 +121,39 @@ All client secrets should be encrypted with `agenix` under the respective servic
### 3. Nextcloud (`cloud.masu.rs`)
- **Pocket ID Redirect URI:** `https://cloud.masu.rs/apps/user_oidc/code`
- **Setup in `nextcloud/nextcloud.nix`:**
1. Add `user_oidc` to `services.nextcloud.extraApps`.
2. Configure the provider via `nextcloud-occ`:
```bash
nextcloud-occ user_oidc:provider pocket-id \
--clientid="<client_id>" \
--clientsecret="<client_secret>" \
--discoveryuri="https://auth.masu.rs/.well-known/openid-configuration" \
--scope="openid profile email"
```
```nix
secrets.nextcloud-oidc-secret = {
source = ./nextcloud-oidc-secret.age;
dest = "${config.secretsDirectory}/nextcloud-oidc-secret";
owner = "nextcloud";
group = "nextcloud";
permissions = "0440";
};
systemd.services.nextcloud-oidc-secret-secret = {
requiredBy = [ "nextcloud-setup.service" ];
before = [ "nextcloud-setup.service" ];
};
services.nextcloud = {
settings.overwriteprotocol = "https";
extraApps = {
user_oidc = config.services.nextcloud.package.packages.apps.user_oidc;
};
};
systemd.services.nextcloud-setup = {
after = [ "nextcloud-oidc-secret-secret.service" ];
postStart = ''
${config.services.nextcloud.occ}/bin/nextcloud-occ user_oidc:provider pocket-id \
--clientid="c8a32c58-a781-4f14-9070-f498fdfda438" \
--clientsecret-file="${config.secrets.nextcloud-oidc-secret.dest}" \
--discoveryuri="https://${hostnames.auth}/.well-known/openid-configuration" \
--scope="openid profile email" \
--mapping-uid="preferred_username" \
--unique-uid=0
'';
};
```
### 4. Grafana (`metrics.masu.rs`)
- **Pocket ID Redirect URI:** `https://metrics.masu.rs/login/generic_oauth`
@@ -159,24 +183,28 @@ All client secrets should be encrypted with `agenix` under the respective servic
```nix
services.paperless.settings = {
PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect";
PAPERLESS_SOCIALACCOUNT_PROVIDERS = builtins.toJSON {
openid_connect = {
SERVERS = [
{
id = "pocket-id";
name = "Pocket ID";
server_url = "https://auth.masu.rs";
token_auth_method = "client_secret_basic";
APP = {
client_id = "paperless";
secret = "..."; # or via environmentFile
};
}
];
};
};
PAPERLESS_REDIRECT_LOGIN_TO_SSO = "true"; # Optional: bypass local login
PAPERLESS_REDIRECT_LOGIN_TO_SSO = true;
};
# Configured via environmentFile to pass the client secret, enable PKCE, and link by email:
# PAPERLESS_SOCIALACCOUNT_PROVIDERS = builtins.toJSON {
# openid_connect = {
# APPS = [
# {
# provider_id = "pocket-id";
# name = "Pocket ID";
# client_id = "e6ff7fce-8e67-4c66-8f32-5ec3db4740a9";
# secret = "...";
# settings = {
# server_url = "https://auth.masu.rs";
# token_auth_method = "client_secret_basic";
# oauth_pkce_enabled = true;
# email_authentication = true;
# verified_email = true;
# };
# }
# ];
# };
# };
```
### 6. Mealie (`cooking.masu.rs`)
Generated
+85 -30
View File
@@ -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,
@@ -195,11 +234,11 @@
]
},
"locked": {
"lastModified": 1788487777,
"narHash": "sha256-Ro/e1N4ZR8/XaFF+sF9SgfPK79HM5YNEppzFj8p+0Ak=",
"lastModified": 1789183968,
"narHash": "sha256-pEWnYdIF1pBMZwarp/rEPzl+Lknhm5hhjSfeLxH8nAA=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "693e8ce0fb240a73c116a03cfd7b19269c87af88",
"rev": "cd1c9e552f41894aeb5cc5cb353d5a1d61550357",
"type": "github"
},
"original": {
@@ -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": {
@@ -259,11 +298,11 @@
]
},
"locked": {
"lastModified": 1788080100,
"narHash": "sha256-n14luQo3F/ZLfCEHk1QieiI1nGnW92ZQZeZ0UIb0UMQ=",
"lastModified": 1788680125,
"narHash": "sha256-amGSoDobwmp4CFvCn841ws2iuitus+HUJdD/gKgsrJA=",
"owner": "nix-community",
"repo": "nix-index-database",
"rev": "dbb978fa87faf13398e90ccbc52c343d21c7dd6d",
"rev": "116ad1c2adb642405ef8916f6a94c8626f971344",
"type": "github"
},
"original": {
@@ -274,7 +313,7 @@
},
"nix2vim": {
"inputs": {
"flake-utils": "flake-utils_2",
"flake-utils": "flake-utils_3",
"nixpkgs": [
"nixpkgs"
]
@@ -362,11 +401,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1788405554,
"narHash": "sha256-r2f1oUwixlgq9zOdYLqJLfS/lWBT60/IITjhTKI59JU=",
"lastModified": 1789114715,
"narHash": "sha256-ugpsyk3NM2s87vXfUiIIiibbJ4Pp0JPS5p/3mfs+q+c=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a5cc6f2c37bf518436dc8d1c288ccd0c43c2f4c4",
"rev": "21a67dc470149f337cecafbe965d8d252a390518",
"type": "github"
},
"original": {
@@ -442,11 +481,11 @@
},
"nixpkgs_6": {
"locked": {
"lastModified": 1788531059,
"narHash": "sha256-hLD4l3QOGBQhkVp3mQ2lJ/YbEi99qUgKapb40KovZ88=",
"lastModified": 1789006805,
"narHash": "sha256-xB8mKMOx1IA9vTDNLmJZ6n4wCMq/cuWBBOzGCRnqxrU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "801bef6abd86b91e51083066b83fb354a11fc640",
"rev": "8ce4ef6cb6f871616146b9fe26d2a5ae594e94fe",
"type": "github"
},
"original": {
@@ -480,11 +519,11 @@
]
},
"locked": {
"lastModified": 1788594396,
"narHash": "sha256-HLvxvUIMxBHl+vv/1OVTLPKrSLTinId97WBIxWWByJ0=",
"lastModified": 1789200175,
"narHash": "sha256-JriLm/izGMXB4uAHWXKj13kXa3nKZj2r/sR5xHdisgs=",
"owner": "nix-community",
"repo": "nur",
"rev": "82ca52a282cd5f90afabcf7c9acd27571b253523",
"rev": "2c789298fc525713063e3b88359d02101651144f",
"type": "github"
},
"original": {
@@ -495,6 +534,7 @@
},
"root": {
"inputs": {
"api": "api",
"darwin": "darwin",
"disko": "disko",
"home-manager": "home-manager",
@@ -517,11 +557,11 @@
"nixpkgs": "nixpkgs_7"
},
"locked": {
"lastModified": 1788591095,
"narHash": "sha256-Vh+BeLWfbTT9AecazIsQ/Tkg/RzJeX3lEduANf256WA=",
"lastModified": 1789196581,
"narHash": "sha256-yJr1Bt4fKkKIpPYbsKGqJ0VFdoDnURgRwuffmBQ2WzY=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "c361047d3a538f547f1617bb6b410411929ac9cc",
"rev": "228ecefb6329d5a531b77b46b581a2f0c26ee056",
"type": "github"
},
"original": {
@@ -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=",
@@ -634,11 +689,11 @@
]
},
"locked": {
"lastModified": 1784642409,
"narHash": "sha256-hcbDqFuySAJawljt5r0sKBCJKYnbtGD0T/ZIozH1Dq0=",
"lastModified": 1789164534,
"narHash": "sha256-DoYGPM6QpnYBLWj9gGw6ZwAzIX+HrAVov1BoT+8Jixo=",
"owner": "nix-community",
"repo": "NixOS-WSL",
"rev": "eaeb18da90024448a60eb1ec7132eafa4003404e",
"rev": "72c92b11bb8289e6651c7fef29cc0a885fd6a255",
"type": "github"
},
"original": {
@@ -655,7 +710,7 @@
"rust-overlay": [
"rust-overlay"
],
"systems": "systems_4"
"systems": "systems_5"
},
"locked": {
"lastModified": 1781873766,
+7
View File
@@ -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}";
+4 -11
View File
@@ -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,19 +157,10 @@ 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
# (builtins.removeAttrs module [ "home-manager" ])
{
home-manager = {
extraSpecialArgs = {
inherit colorscheme;
}
// specialArgs;
}
// homeModule.home-manager;
}
(builtins.removeAttrs module [ "home-manager" ])
];
};
@@ -229,6 +221,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);
@@ -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" ];
};
}
@@ -44,9 +44,27 @@ in
{
match = [ { host = [ hostnames.stream ]; } ];
handle = [
# Resolve client IP: use Cloudflare's CF-Connecting-IP if present, otherwise remote host
{
handler = "map";
source = "{http.request.header.CF-Connecting-IP}";
destinations = [ "{client_ip}" ];
defaults = [ "{http.request.remote.host}" ];
mappings = [
{
input_regexp = "^(.+)$";
outputs = [ "\${1}" ];
}
];
}
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:8096"; } ];
headers.request.set = {
"X-Real-IP" = [ "{client_ip}" ];
"X-Forwarded-For" = [ "{client_ip}" ];
"X-Forwarded-Proto" = [ "{http.request.scheme}" ];
};
}
];
}
@@ -0,0 +1,17 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyAzMEJs
WW9mb1E5MW5vT0tYeDhKSnFSRWZFVFdrL0d6Vk45UUVBd3JubFNvClpIRDYzM1Zh
dEtMaFNSeEF4aHY2cWpZbUNNMUw0UW93SFV0NVBMZ2wvKzgKLT4gc3NoLWVkMjU1
MTkgWXlTVU1RIDlGUUQwdlFwd1JiOE9Sb0svWTM4cDVHbGNQeFVDcUJsa0xqZGp6
SGxkUlUKMHpPSFFSQXZSYjdwNUhnRnUxZzJVQWxLblB5MjBTZjRpM0d4NDJFSk1Z
RQotPiBzc2gtZWQyNTUxOSBuanZYNUEgK055eTE2NG9yN3FEZ1BSaTZKTGpWUmt0
YlFWU0hjSnZ3MVZSdE12Y0VVTQovNWpGM2Y0TUlNSHRVQmRJK3VCNTBHTFAzdlYx
MXNJOVNOdTJiNlZZdWkwCi0+IHNzaC1lZDI1NTE5IENxSU9VQSArblhobTFoN1BQ
ZUhuL04yVUtTRnF1V0tPR1BVK3lzWUVrMlhJQWxmZXhZClJkUnU0QUxuRnZ3Vjg2
TVFzdGs5dDJSbFZMQnFRamxObnFpZVBaTExFajQKLT4gc3NoLWVkMjU1MTkgejFP
Y1p3IFE1clkxRFFZbmVEV01LVWNXaVR6ZkdrcHJYUFNaSXI1STUrSmN5ejJnU2MK
STRhOWlXTFU2emVvVUkxMmxKc1FmbmJrQ1RFU3RLa2RBSUJlak5CRG1obwotLS0g
WFhjSmJxSmVMQlR0bjRKWVR1cllmQ1JjUkZZYWpTbEFzTjNQVzRvbHNJbwrrqVI1
yl6w97t0CDhjOml46f3wlob0kztDAdOFmUSiEVfiQU0eCrfNinQg7aohu5Y8MJpD
k2Du2m/AV6aIPR47LA==
-----END AGE ENCRYPTED FILE-----
@@ -33,6 +33,7 @@ in
# Allow access when hitting either of these hosts or IPs
trusted_domains = [ hostnames.content ];
trusted_proxies = [ "127.0.0.1" ];
overwriteprotocol = "https";
maintenance_window_start = 4; # Run jobs at 4am UTC
log_type = "file";
loglevel = 1; # Include all actions in the log
@@ -41,6 +42,7 @@ in
extraApps = {
calendar = config.services.nextcloud.package.packages.apps.calendar;
contacts = config.services.nextcloud.package.packages.apps.contacts;
user_oidc = config.services.nextcloud.package.packages.apps.user_oidc;
# These apps are defined and pinned by overlay in flake.
# news = pkgs.nextcloudApps.news;
# external = pkgs.nextcloudApps.external;
@@ -211,6 +213,32 @@ in
before = [ "nextcloud-setup.service" ];
};
secrets.nextcloud-oidc-secret = {
source = ./nextcloud-oidc-secret.age;
dest = "${config.secretsDirectory}/nextcloud-oidc-secret";
owner = "nextcloud";
group = "nextcloud";
permissions = "0440";
};
systemd.services.nextcloud-oidc-secret-secret = {
requiredBy = [ "nextcloud-setup.service" ];
before = [ "nextcloud-setup.service" ];
};
# Configure Pocket ID OIDC provider after Nextcloud setup enables apps
systemd.services.nextcloud-setup = {
after = [ "nextcloud-oidc-secret-secret.service" ];
postStart = ''
${config.services.nextcloud.occ}/bin/nextcloud-occ user_oidc:provider pocket-id \
--clientid="c8a32c58-a781-4f14-9070-f498fdfda438" \
--clientsecret-file="${config.secrets.nextcloud-oidc-secret.dest}" \
--discoveryuri="https://${hostnames.auth}/.well-known/openid-configuration" \
--scope="openid profile email" \
--mapping-uid="preferred_username" \
--unique-uid=0
'';
};
# Grant user access to Nextcloud directories
users.users.${username}.extraGroups = [ "nextcloud" ];
@@ -0,0 +1,17 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBCa2J1
QjFGRjdZa0RHcjd0V0JXL3lUNzRGczNHanIzb3BtaE93NlNvcGhnCnNOM3JBc0t3
VENnWVp1b09Sd1JwcDk1Q0pQM3BBMlJmWHh5TTJHakIvcGsKLT4gc3NoLWVkMjU1
MTkgWXlTVU1RIDljQUIrenlGZHMxSHlReG80VVhaSXhsbFZ0L2dtcCtWbTRvNE1i
WEdOeVkKUk9HK25Fci9TYWRSS1htSU9BNHlqbHpGT3c5bkI4b3RUL09QK1BYTE1Y
VQotPiBzc2gtZWQyNTUxOSBuanZYNUEgNXk5bzRhR1BoTjlMeHVRR2UyUEZJN0Y1
ZXAyU3BjeFJvRHhER0ptaVlsVQo0K3p6bXBwcTVIdGlTajBucWV5dzZKM2JyWWNB
R2s2UnhTMFVPRGwrVWhRCi0+IHNzaC1lZDI1NTE5IENxSU9VQSBaL3NTdHBjVFRX
ODNrcDVmZGFxNVRwbGkrcC9heFN5bmVCZ0xId1NESUhFCm5aQS9xZmhkSWRwMFl1
b3FPdUhPZU5YdVQvTEQ3eVdRODBhVzJSWjdyek0KLT4gc3NoLWVkMjU1MTkgejFP
Y1p3IHdDR3YwYTFyWlZQZWFDZ1cvWk00bjdRdW16c1M4eDlDaHl4Y3lkbVFGVFEK
dndhaG5pUTM3aU04WHZUSHJDdUdkZ0VWRXRaL2t6cUJoZTR1bTh6NlBFawotLS0g
NXRDeDFZL0RvaURVK2k3R0NVZGdKR0diZ2RwZjQxTFhuNk82SW9GMGVaRQqYDG6J
+/POhVn84NGh8ganDIzNUqQF7uBIxdUMiRoFa0kU4eJd9V4vmjsjtr/lbQ9O+Xfq
WJJ8Hjwyzwopx1Phrw==
-----END AGE ENCRYPTED FILE-----
@@ -1,6 +1,11 @@
# Paperless-ngx is a document scanning and management solution.
{ config, lib, ... }:
{
config,
pkgs,
lib,
...
}:
let
inherit (config.nmasur.settings) hostnames username;
@@ -17,12 +22,26 @@ in
enable = true;
mediaDir = "/data/generic/paperless";
passwordFile = config.secrets.paperless.dest;
environmentFile = "${config.secretsDirectory}/paperless-env";
configureTika = true; # Enable processing of emails
settings = {
PAPERLESS_OCR_USER_ARGS = builtins.toJSON { invalidate_digital_signatures = true; };
PAPERLESS_URL = "https://${hostnames.paperless}";
PAPERLESS_DATE_ORDER = "MDY"; # Check document for US-formatted dates
# OIDC Authentication with Pocket ID
PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect";
PAPERLESS_REDIRECT_LOGIN_TO_SSO = true;
PAPERLESS_USE_X_FORWARD_HOST = true;
PAPERLESS_PROXY_SSL_HEADER = [
"HTTP_X_FORWARDED_PROTO"
"https"
];
PAPERLESS_TRUSTED_PROXIES = [
"127.0.0.1"
"::1"
];
# Enable if changing the path name in Caddy
# PAPERLESS_FORCE_SCRIPT_NAME = "/paperless";
# PAPERLESS_STATIC_URL = "/paperless/static/";
@@ -61,8 +80,60 @@ in
permissions = "0440";
};
systemd.services.paperless-secret = {
requiredBy = [ "paperless.service" ];
before = [ "paperless.service" ];
requiredBy = [ "paperless-scheduler.service" ];
before = [ "paperless-scheduler.service" ];
};
secrets.paperless-oidc-secret = {
source = ./paperless-oidc-secret.age;
dest = "${config.secretsDirectory}/paperless-oidc-secret";
owner = "paperless";
group = "paperless";
permissions = "0440";
};
systemd.services.paperless-oidc-secret-secret = {
requiredBy = [
"paperless-secret-key.service"
"paperless-scheduler.service"
"paperless-task-queue.service"
"paperless-consumer.service"
"paperless-web.service"
];
before = [
"paperless-secret-key.service"
"paperless-scheduler.service"
"paperless-task-queue.service"
"paperless-consumer.service"
"paperless-web.service"
];
postStart = ''
SECRET="$(tr -d '\r\n' < '${config.secrets.paperless-oidc-secret.dest}')"
JSON=$(${pkgs.jq}/bin/jq -nc \
--arg secret "$SECRET" \
--arg auth "https://${hostnames.auth}" \
'{
openid_connect: {
APPS: [
{
provider_id: "pocket-id",
name: "Pocket ID",
client_id: "e6ff7fce-8e67-4c66-8f32-5ec3db4740a9",
secret: $secret,
settings: {
server_url: $auth,
token_auth_method: "client_secret_basic",
oauth_pkce_enabled: true,
email_authentication: true,
verified_email: true
}
}
]
}
}')
echo "PAPERLESS_SOCIALACCOUNT_PROVIDERS='$JSON'" > '${config.secretsDirectory}/paperless-env'
chown paperless:paperless '${config.secretsDirectory}/paperless-env'
chmod 0440 '${config.secretsDirectory}/paperless-env'
'';
};
# Fix paperless shared permissions
@@ -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
'';
};
};
}
@@ -29,7 +29,7 @@ in
};
};
programs.fish.enable = lib.mkDefault config.home-manager.users.${username}.programs.fish.enable;
programs.fish.enable = lib.mkDefault (config.home-manager.users.${username}.programs.fish.enable or true);
# Allows us to declaritively set password
users.mutableUsers = lib.mkDefault false;
@@ -62,7 +62,7 @@ in
# serviceConfig.TimeoutStartSec = lib.mkForce "45m";
# };
allowUnfreePackages = config.home-manager.users.${username}.allowUnfreePackages;
allowUnfreePackages = config.home-manager.users.${username}.allowUnfreePackages or [];
wsl.enable = lib.mkDefault false;
@@ -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;