firefly-iii: init

This commit is contained in:
ibizaman 2025-12-29 22:52:15 +01:00
parent 0d47a24342
commit c2797544fe
7 changed files with 1140 additions and 0 deletions

View file

@ -174,6 +174,7 @@ Also, the stack fits together nicely thanks to [contracts](#contracts).
- Nextcloud
- Audiobookshelf
- Deluge + *arr stack
- Firefly-iii
- Forgejo
- Grocy
- Hledger

View file

@ -2711,12 +2711,306 @@
"services-category-documents": [
"services.html#services-category-documents"
],
"services-category-finance": [
"services.html#services-category-finance"
],
"services-category-media": [
"services.html#services-category-media"
],
"services-category-passwords": [
"services.html#services-category-passwords"
],
"services-firefly-iii": [
"services-firefly-iii.html#services-firefly-iii"
],
"services-firefly-iii-certs": [
"services-firefly-iii.html#services-firefly-iii-certs"
],
"services-firefly-iii-database-inspection": [
"services-firefly-iii.html#services-firefly-iii-database-inspection"
],
"services-firefly-iii-declarative-ldap": [
"services-firefly-iii.html#services-firefly-iii-declarative-ldap"
],
"services-firefly-iii-impermanence": [
"services-firefly-iii.html#services-firefly-iii-impermanence"
],
"services-firefly-iii-mobile": [
"services-firefly-iii.html#services-firefly-iii-mobile"
],
"services-firefly-iii-options": [
"services-firefly-iii.html#services-firefly-iii-options"
],
"services-firefly-iii-options-shb.firefly-iii.appKey": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey"
],
"services-firefly-iii-options-shb.firefly-iii.appKey.request": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey.request"
],
"services-firefly-iii-options-shb.firefly-iii.appKey.request.group": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey.request.group"
],
"services-firefly-iii-options-shb.firefly-iii.appKey.request.mode": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey.request.mode"
],
"services-firefly-iii-options-shb.firefly-iii.appKey.request.owner": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey.request.owner"
],
"services-firefly-iii-options-shb.firefly-iii.appKey.request.restartUnits": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey.request.restartUnits"
],
"services-firefly-iii-options-shb.firefly-iii.appKey.result": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey.result"
],
"services-firefly-iii-options-shb.firefly-iii.appKey.result.path": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.appKey.result.path"
],
"services-firefly-iii-options-shb.firefly-iii.backup": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup"
],
"services-firefly-iii-options-shb.firefly-iii.backup.request": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.request"
],
"services-firefly-iii-options-shb.firefly-iii.backup.request.excludePatterns": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.request.excludePatterns"
],
"services-firefly-iii-options-shb.firefly-iii.backup.request.hooks": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.request.hooks"
],
"services-firefly-iii-options-shb.firefly-iii.backup.request.hooks.afterBackup": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.request.hooks.afterBackup"
],
"services-firefly-iii-options-shb.firefly-iii.backup.request.hooks.beforeBackup": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.request.hooks.beforeBackup"
],
"services-firefly-iii-options-shb.firefly-iii.backup.request.sourceDirectories": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.request.sourceDirectories"
],
"services-firefly-iii-options-shb.firefly-iii.backup.request.user": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.request.user"
],
"services-firefly-iii-options-shb.firefly-iii.backup.result": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.result"
],
"services-firefly-iii-options-shb.firefly-iii.backup.result.backupService": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.result.backupService"
],
"services-firefly-iii-options-shb.firefly-iii.backup.result.restoreScript": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.backup.result.restoreScript"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword.request": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword.request"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword.request.group": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword.request.group"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword.request.mode": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword.request.mode"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword.request.owner": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword.request.owner"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword.request.restartUnits": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword.request.restartUnits"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword.result": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword.result"
],
"services-firefly-iii-options-shb.firefly-iii.dbPassword.result.path": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword.result.path"
],
"services-firefly-iii-options-shb.firefly-iii.debug": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.debug"
],
"services-firefly-iii-options-shb.firefly-iii.domain": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.domain"
],
"services-firefly-iii-options-shb.firefly-iii.enable": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.enable"
],
"services-firefly-iii-options-shb.firefly-iii.impermanence": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.impermanence"
],
"services-firefly-iii-options-shb.firefly-iii.importer": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.group": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.group"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.mode": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.mode"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.owner": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.owner"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.restartUnits": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.request.restartUnits"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.result": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.result"
],
"services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.result.path": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.firefly-iii-accessToken.result.path"
],
"services-firefly-iii-options-shb.firefly-iii.importer.subdomain": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.importer.subdomain"
],
"services-firefly-iii-options-shb.firefly-iii.ldap": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ldap"
],
"services-firefly-iii-options-shb.firefly-iii.ldap.adminGroup": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ldap.adminGroup"
],
"services-firefly-iii-options-shb.firefly-iii.ldap.userGroup": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ldap.userGroup"
],
"services-firefly-iii-options-shb.firefly-iii.siteOwnerEmail": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.siteOwnerEmail"
],
"services-firefly-iii-options-shb.firefly-iii.smtp": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.from_address": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.from_address"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.host": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.host"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password.request": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password.request"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password.request.group": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password.request.group"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password.request.mode": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password.request.mode"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password.request.owner": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password.request.owner"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password.request.restartUnits": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password.request.restartUnits"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password.result": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password.result"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.password.result.path": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.password.result.path"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.port": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.port"
],
"services-firefly-iii-options-shb.firefly-iii.smtp.username": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.smtp.username"
],
"services-firefly-iii-options-shb.firefly-iii.ssl": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ssl"
],
"services-firefly-iii-options-shb.firefly-iii.ssl.paths": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ssl.paths"
],
"services-firefly-iii-options-shb.firefly-iii.ssl.paths.cert": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ssl.paths.cert"
],
"services-firefly-iii-options-shb.firefly-iii.ssl.paths.key": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ssl.paths.key"
],
"services-firefly-iii-options-shb.firefly-iii.ssl.systemdService": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.ssl.systemdService"
],
"services-firefly-iii-options-shb.firefly-iii.sso": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso"
],
"services-firefly-iii-options-shb.firefly-iii.sso.adminGroup": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.adminGroup"
],
"services-firefly-iii-options-shb.firefly-iii.sso.authEndpoint": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.authEndpoint"
],
"services-firefly-iii-options-shb.firefly-iii.sso.authorization_policy": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.authorization_policy"
],
"services-firefly-iii-options-shb.firefly-iii.sso.clientID": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.clientID"
],
"services-firefly-iii-options-shb.firefly-iii.sso.enable": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.enable"
],
"services-firefly-iii-options-shb.firefly-iii.sso.port": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.port"
],
"services-firefly-iii-options-shb.firefly-iii.sso.provider": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.provider"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret.request": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret.request"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret.request.group": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret.request.group"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret.request.mode": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret.request.mode"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret.request.owner": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret.request.owner"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret.request.restartUnits": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret.request.restartUnits"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret.result": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret.result"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secret.result.path": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secret.result.path"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.group": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.group"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.mode": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.mode"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.owner": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.owner"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.restartUnits": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.request.restartUnits"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.result": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.result"
],
"services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.result.path": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.sso.secretForAuthelia.result.path"
],
"services-firefly-iii-options-shb.firefly-iii.subdomain": [
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.subdomain"
],
"services-firefly-iii-usage": [
"services-firefly-iii.html#services-firefly-iii-usage"
],
"services-firefly-iii-usage-backup": [
"services-firefly-iii.html#services-firefly-iii-usage-backup"
],
"services-forgejo": [
"services-forgejo.html#services-forgejo"
],

View file

@ -16,6 +16,7 @@ information is provided in the respective manual sections.
| Service | Backup | Reverse Proxy | SSO | LDAP | Monitoring | Profiling |
|----------------------|--------|---------------|-----|-------|------------|-----------|
| [*Arr][] | Y (1) | Y | Y | Y (4) | Y (2) | N |
| [Firefly-iii][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Forgejo][] | Y (1) | Y | Y | Y | Y (2) | N |
| [Home-Assistant][] | Y (1) | Y | N | Y | Y (2) | N |
| [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N |
@ -35,6 +36,7 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
4. Uses LDAP indirectly through forward auth.
[*Arr]: services-arr.html
[Firefly-iii]: services-firefly-iii.html
[Forgejo]: services-forgejo.html
[Home-Assistant]: services-home-assistant.html
[Jellyfin]: services-jellyfin.html
@ -91,3 +93,9 @@ modules/services/jellyfin/docs/default.md
```{=include=} chapters html:into-file=//services-pinchflat.html
modules/services/pinchflat/docs/default.md
```
## Finance {#services-category-finance}
```{=include=} chapters html:into-file=//services-firefly-iii.html
modules/services/firefly-iii/docs/default.md
```

View file

@ -104,6 +104,7 @@
"blocks/restic" = ./modules/blocks/restic.nix;
"blocks/sops" = ./modules/blocks/sops.nix;
"services/arr" = ./modules/services/arr.nix;
"services/firefly-iii" = ./modules/services/firefly-iii.nix;
"services/forgejo" = [
./modules/services/forgejo.nix
(pkgs.path + "/nixos/modules/services/misc/forgejo.nix")
@ -280,6 +281,7 @@
// (vm_test "arr" ./test/services/arr.nix)
// (vm_test "audiobookshelf" ./test/services/audiobookshelf.nix)
// (vm_test "deluge" ./test/services/deluge.nix)
// (vm_test "firefly-iii" ./test/services/firefly-iii.nix)
// (vm_test "forgejo" ./test/services/forgejo.nix)
// (vm_test "grocy" ./test/services/grocy.nix)
// (vm_test "hledger" ./test/services/hledger.nix)
@ -388,6 +390,7 @@
self.nixosModules.arr
self.nixosModules.audiobookshelf
self.nixosModules.deluge
self.nixosModules.firefly-iii
self.nixosModules.forgejo
self.nixosModules.grocy
self.nixosModules.hledger
@ -424,6 +427,7 @@
nixosModules.arr = modules/services/arr.nix;
nixosModules.audiobookshelf = modules/services/audiobookshelf.nix;
nixosModules.deluge = modules/services/deluge.nix;
nixosModules.firefly-iii = modules/services/firefly-iii.nix;
nixosModules.forgejo = modules/services/forgejo.nix;
nixosModules.grocy = modules/services/grocy.nix;
nixosModules.hledger = modules/services/hledger.nix;

View file

@ -0,0 +1,419 @@
{
config,
lib,
shb,
...
}:
let
cfg = config.shb.firefly-iii;
in
{
imports = [
../../lib/module.nix
];
options.shb.firefly-iii = {
enable = lib.mkEnableOption "SHB's firefly-iii module";
subdomain = lib.mkOption {
type = lib.types.str;
description = ''
Subdomain under which firefly-iii will be served.
```
<subdomain>.<domain>
```
'';
example = "firefly-iii";
};
domain = lib.mkOption {
description = ''
Domain under which firefly-iii is served.
```
<subdomain>.<domain>[:<port>]
```
'';
type = lib.types.str;
example = "domain.com";
};
ssl = lib.mkOption {
description = "Path to SSL files";
type = lib.types.nullOr shb.contracts.ssl.certs;
default = null;
};
siteOwnerEmail = lib.mkOption {
description = "Email of the site owner.";
type = lib.types.str;
example = "mail@example.com";
};
impermanence = lib.mkOption {
description = ''
Path to save when using impermanence setup.
'';
type = lib.types.str;
default = config.services.firefly-iii.dataDir;
defaultText = "services.firefly-iii.dataDir";
};
backup = lib.mkOption {
description = ''
Backup configuration.
'';
default = { };
type = lib.types.submodule {
options = shb.contracts.backup.mkRequester {
user = config.services.firefly-iii.user;
userText = "services.firefly-iii.user";
sourceDirectories = [
config.services.firefly-iii.dataDir
];
sourceDirectoriesText = ''
[
config.services.firefly-iii.dataDir
]
'';
};
};
};
appKey = lib.mkOption {
description = "Encryption key used for sessions. Must be 32 characters long exactly.";
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = config.services.firefly-iii.user;
ownerText = "services.firefly-iii.user";
restartUnits = [ "firefly-iii-setup.service" ];
};
};
};
dbPassword = lib.mkOption {
description = "DB password.";
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0440";
owner = config.services.firefly-iii.user;
ownerText = "services.firefly-iii.user";
group = "postgres";
restartUnits = [
"postgresql.service"
"firefly-iii-setup.service"
];
};
};
};
ldap = lib.mkOption {
description = ''
LDAP Integration
'';
default = { };
type = lib.types.submodule {
options = {
userGroup = lib.mkOption {
type = lib.types.str;
description = "Group users must belong to to be able to login to Firefly-iii.";
default = "firefly-iii_user";
};
adminGroup = lib.mkOption {
type = lib.types.str;
description = "Group users must belong to to be able to import data user the Firefly-iii data importer.";
default = "firefly-iii_admin";
};
};
};
};
sso = lib.mkOption {
description = ''
SSO Integration
'';
default = { };
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption "SSO integration.";
authEndpoint = lib.mkOption {
type = lib.types.str;
description = "OIDC endpoint for SSO.";
example = "https://authelia.example.com";
};
port = lib.mkOption {
description = "If given, adds a port to the endpoint.";
type = lib.types.nullOr lib.types.port;
default = null;
};
provider = lib.mkOption {
type = lib.types.enum [ "Authelia" ];
description = "OIDC provider name, used for display.";
default = "Authelia";
};
clientID = lib.mkOption {
type = lib.types.str;
description = "Client ID for the OIDC endpoint.";
default = "firefly-iii";
};
authorization_policy = lib.mkOption {
type = lib.types.enum [
"one_factor"
"two_factor"
];
description = "Require one factor (password) or two factor (device) authentication.";
default = "one_factor";
};
adminGroup = lib.mkOption {
type = lib.types.str;
description = "Group admins must belong to to be able to login to Firefly-iii.";
default = "firefly-iii_admin";
};
secret = lib.mkOption {
description = "OIDC shared secret.";
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = "firefly-iii";
restartUnits = [ "firefly-iii-setup.service" ];
};
};
};
secretForAuthelia = lib.mkOption {
description = "OIDC shared secret. Content must be the same as `secretFile` option.";
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = "authelia";
};
};
};
};
};
};
smtp = lib.mkOption {
description = ''
If set, send notifications through smtp.
https://docs.firefly-iii.org/how-to/firefly-iii/advanced/notifications/
'';
default = null;
type = lib.types.nullOr (
lib.types.submodule {
options = {
from_address = lib.mkOption {
type = lib.types.str;
description = "SMTP address from which the emails originate.";
example = "authelia@mydomain.com";
};
host = lib.mkOption {
type = lib.types.str;
description = "SMTP host to send the emails to.";
};
port = lib.mkOption {
type = lib.types.port;
description = "SMTP port to send the emails to.";
default = 25;
};
username = lib.mkOption {
type = lib.types.str;
description = "Username to connect to the SMTP host.";
};
password = lib.mkOption {
description = "File containing the password to connect to the SMTP host.";
type = lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = config.services.firefly-iii.user;
ownerText = "services.firefly-iii.user";
restartUnits = [ "firefly-iii-setup.service" ];
};
};
};
};
}
);
};
debug = lib.mkOption {
type = lib.types.bool;
description = "Enable more verbose logging.";
default = false;
example = true;
};
importer = lib.mkOption {
description = ''
Configuration for Firefly-iii data importer.
'';
default = { };
type = lib.types.submodule {
options = {
subdomain = lib.mkOption {
type = lib.types.str;
description = ''
Subdomain under which the firefly-iii data importer will be served.
'';
default = "${cfg.subdomain}-importer";
defaultText = lib.literalExpression ''''${shb.firefly-iii.subdomain}-importer'';
};
firefly-iii-accessToken = lib.mkOption {
type = lib.types.nullOr (
lib.types.submodule {
options = shb.contracts.secret.mkRequester {
mode = "0400";
owner = config.services.firefly-iii-data-importer.user;
ownerText = "services.firefly-iii-data-importer.user";
restartUnits = [ "firefly-iii-data-importer-setup.service" ];
};
}
);
description = ''
Create a Personal Access Token then set then token in this option.
'';
default = null;
};
};
};
};
};
config = lib.mkIf cfg.enable (
lib.mkMerge [
{
services.firefly-iii = {
enable = true;
group = "nginx";
virtualHost = "${cfg.subdomain}.${cfg.domain}";
# https://github.com/firefly-iii/firefly-iii/blob/main/.env.example
settings = {
APP_ENV = "production";
APP_URL = "https://${cfg.subdomain}.${cfg.domain}";
APP_DEBUG = cfg.debug;
APP_LOG_LEVEL = if cfg.debug then "debug" else "notice";
LOG_CHANNEL = "stdout";
APP_KEY_FILE = cfg.appKey.result.path;
SITE_OWNER = cfg.siteOwnerEmail;
DB_CONNECTION = "pgsql";
DB_HOST = "localhost";
DB_PORT = config.services.postgresql.settings.port;
DB_DATABASE = "firefly-iii";
DB_USERNAME = "firefly-iii";
DB_PASSWORD_FILE = cfg.dbPassword.result.path;
# MAP_DEFAULT_LAT = "51.983333";
# MAP_DEFAULT_LONG = "5.916667";
# MAP_DEFAULT_ZOOM = "6";
};
};
shb.postgresql.enableTCPIP = true;
shb.postgresql.ensures = [
{
username = "firefly-iii";
database = "firefly-iii";
passwordFile = cfg.dbPassword.result.path;
}
];
# This should be using a contract instead of setting the option directly.
shb.lldap = lib.mkIf config.shb.lldap.enable {
ensureGroups = {
${cfg.ldap.userGroup} = { };
${cfg.ldap.adminGroup} = { };
};
};
# We enable the firefly-iii nginx integration and merge it with SHB's nginx configuration.
services.firefly-iii.enableNginx = true;
shb.nginx.vhosts = [
{
inherit (cfg) subdomain domain ssl;
}
];
}
{
services.firefly-iii-data-importer = {
enable = true;
virtualHost = "${cfg.importer.subdomain}.${cfg.domain}";
settings = {
FIREFLY_III_URL = "https://${config.services.firefly-iii.virtualHost}";
}
// lib.optionalAttrs (cfg.importer.firefly-iii-accessToken != null) {
FIREFLY_III_ACCESS_TOKEN_FILE = cfg.importer.firefly-iii-accessToken.result.path;
};
};
# We enable the firefly-iii-data-importer nginx integration and merge it with SHB's nginx configuration.
services.firefly-iii-data-importer.enableNginx = true;
shb.nginx.vhosts = [
{
inherit (cfg) domain ssl;
subdomain = cfg.importer.subdomain;
}
];
}
(lib.mkIf (cfg.smtp != null) {
services.firefly-iii.settings = {
MAIL_MAILER = "smtp";
MAIL_HOST = cfg.smtp.host;
MAIL_PORT = cfg.smtp.port;
MAIL_FROM = cfg.smtp.from_address;
MAIL_USERNAME = cfg.smtp.username;
MAIL_PASSWORD_FILE = cfg.smtp.password.result.path;
MAIL_ENCRYPTION = "tls";
};
})
(lib.mkIf cfg.sso.enable {
services.firefly-iii.settings = {
AUTHENTICATION_GUARD = "remote_user_guard";
AUTHENTICATION_GUARD_HEADER = "HTTP_X_FORWARDED_USER";
};
shb.nginx.vhosts = [
{
inherit (cfg) subdomain domain ssl;
inherit (cfg.sso) authEndpoint;
phpForwardAuth = true;
autheliaRules = [
{
domain = "${cfg.subdomain}.${cfg.domain}";
policy = "bypass";
resources = [ "^/api" ];
}
{
domain = "${cfg.subdomain}.${cfg.domain}";
policy = cfg.sso.authorization_policy;
subject = [ "group:${cfg.ldap.userGroup}" ];
}
{
domain = "${cfg.importer.subdomain}.${cfg.domain}";
policy = cfg.sso.authorization_policy;
subject = [ "group:${cfg.ldap.adminGroup}" ];
}
];
}
];
})
]
);
}

View file

@ -0,0 +1,152 @@
# Firefly-iii Service {#services-firefly-iii}
Defined in [`/modules/services/firefly-iii.nix`](@REPO@/modules/services/firefly-iii.nix).
This NixOS module is a service that sets up a [Firefly-iii](https://www.firefly-iii.org/) instance.
Compared to the stock module from nixpkgs,
this one sets up, in a fully declarative manner,
LDAP and SSO integration
and has a nicer option for secrets.
## Usage {#services-firefly-iii-usage}
The following snippet assumes a few blocks have been setup already:
- the [secrets block](usage.html#usage-secrets) with SOPS,
- the [`shb.ssl` block](blocks-ssl.html#usage),
- the [`shb.lldap` block](blocks-lldap.html#blocks-lldap-global-setup).
- the [`shb.authelia` block](blocks-authelia.html#blocks-sso-global-setup).
```nix
shb.firefly-iii = {
enable = true;
debug = false;
appKey.result = config.shb.sops.secret."firefly-iii/appKey".result;
dbPassword.result = config.shb.sops.secret."firefly-iii/dbPassword".result;
domain = "example.com";
subdomain = "firefly-iii";
siteOwnerEmail = "mail@example.com";
ssl = config.shb.certs.certs.letsencrypt.${domain};
smtp = {
host = "smtp.eu.mailgun.org";
port = 587;
username = "postmaster@mg.example.com";
from_address = "firefly-iii@example.com";
password.result = config.shb.sops.secrets."firefly-iii/smtpPassword".result;
};
sso = {
enable = true;
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
};
importer = {
# See note hereunder.
# firefly-iii-accessToken.result = config.shb.sops.secret."firefly-iii/importerAccessToken".result;
};
};
shb.sops.secret."firefly-iii/appKey".request = config.shb.firefly-iii.appKey.request;
shb.sops.secret."firefly-iii/dbPassword".request = config.shb.firefly-iii.dbPassword.request;
shb.sops.secret."firefly-iii/smtpPassword".request = config.shb.firefly-iii.smtp.password.request;
# See not hereunder.
# shb.sops.secret."firefly-iii/importerAccessToken".request = config.shb.firefly-iii.importer.firefly-iii-accessToken.request;
```
Secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`.
Note that for `appKey`, the secret length must be exactly 32 characters.
The [user](#services-firefly-iii-options-shb.firefly-iii.ldap.userGroup)
and [admin](#services-firefly-iii-options-shb.firefly-iii.ldap.adminGroup)
LDAP groups are created automatically.
Only admin users have access to the Firefly-iii data importer.
On the Firefly-iii web UI, the first user to login will be the admin.
We cannot yet create multiple admins in the Firefly-iii web UI.
On first start, leave the `shb.firefly-iii.importer.firefly-iii-accessToken` option empty.
To fill it out and connect the data importer to the Firefly-iii instance,
you must first create a personal access token then fill that option and redeploy.
## Backup {#services-firefly-iii-usage-backup}
Backing up Firefly-iii using the [Restic block](blocks-restic.html) is done like so:
```nix
shb.restic.instances."firefly-iii" = {
request = config.shb.firefly-iii.backup;
settings = {
enable = true;
};
};
```
The name `"firefly-iii"` in the `instances` can be anything.
The `config.shb.firefly-iii.backup` option provides what directories to backup.
You can define any number of Restic instances to backup Firefly-iii multiple times.
Linking the Firefly-iii data importer to the Firefly-iii instance must still be done manually
by following the instructions appearing on the web UI.
## Certificates {#services-firefly-iii-certs}
For Let's Encrypt certificates, add:
```nix
{
shb.certs.certs.letsencrypt.${domain}.extraDomains = [
"${config.shb.firefly-iii.subdomain}.${config.shb.firefly-iii.domain}"
"${config.shb.firefly-iii.importer.subdomain}.${config.shb.firefly-iii.domain}"
];
}
```
## Impermanence {#services-firefly-iii-impermanence}
To save the data folder in an impermanence setup, add:
```nix
{
shb.zfs.datasets."safe/firefly-iii".path = config.shb.firefly-iii.impermanence;
}
```
## Declarative LDAP {#services-firefly-iii-declarative-ldap}
To add a user `USERNAME` to the user and admin groups for Firefly-iii, add:
```nix
shb.lldap.ensureUsers.USERNAME.groups = [
config.shb.firefly-iii.ldap.userGroup
config.shb.firefly-iii.ldap.adminGroup
];
```
## Database Inspection {#services-firefly-iii-database-inspection}
Access the database with:
```nix
sudo -u firefly-iii psql
```
Dump the database with:
```nix
sudo -u firefly-iii pg_dump --data-only --inserts firefly-iii > dump
```
## Mobile Apps {#services-firefly-iii-mobile}
This module was tested with the [Abacus iOS](https://github.com/victorbalssa/abacus) mobile app
using a Personal Account Token.
## Options Reference {#services-firefly-iii-options}
```{=include=} options
id-prefix: services-firefly-iii-options-
list-id: selfhostblocks-service-firefly-iii-options
source: @OPTIONS_JSON@
```

View file

@ -0,0 +1,262 @@
{ pkgs, shb, ... }:
let
commonTestScript = shb.test.mkScripts {
hasSSL = { node, ... }: !(isNull node.config.shb.firefly-iii.ssl);
waitForServices =
{ ... }:
[
"phpfpm-firefly-iii.service"
"nginx.service"
];
waitForPorts =
{ node, ... }:
[
# node.config.shb.firefly-iii.port
];
};
basic =
{ config, ... }:
{
imports = [
shb.test.baseModule
../../modules/blocks/hardcodedsecret.nix
../../modules/services/firefly-iii.nix
];
test = {
subdomain = "f";
};
shb.firefly-iii = {
enable = true;
debug = true;
inherit (config.test) subdomain domain;
siteOwnerEmail = "mail@example.com";
appKey.result = config.shb.hardcodedsecret.appKey.result;
dbPassword.result = config.shb.hardcodedsecret.dbPassword.result;
};
# systemd.tmpfiles.rules = [
# "d '/src/firefly-iii' 0750 pinchflat pinchflat - -"
# ];
# Needed for gitea-runner-local to be able to ping firefly-iii.
# networking.hosts = {
# "127.0.0.1" = [ "${config.test.subdomain}.${config.test.domain}" ];
# };
shb.hardcodedsecret.appKey = {
request = config.shb.firefly-iii.appKey.request;
# Firefly-iir requires this to be exactly 32 characters.
settings.content = pkgs.lib.strings.replicate 32 "Z";
};
shb.hardcodedsecret.dbPassword = {
request = config.shb.firefly-iii.dbPassword.request;
settings.content = pkgs.lib.strings.replicate 64 "Y";
};
};
clientLogin =
{ config, ... }:
{
imports = [
shb.test.baseModule
shb.test.clientLoginModule
];
test = {
subdomain = "f";
};
test.login = {
startUrl = "http://${config.test.fqdn}";
# There is no login without SSO integration.
testLoginWith = [
{
username = null;
password = null;
nextPageExpect = [
"expect(page.get_by_text('Register a new account')).to_be_visible()"
];
}
];
};
};
https =
{ config, ... }:
{
shb.firefly-iii = {
ssl = config.shb.certs.certs.selfsigned.n;
};
};
ldap =
{ config, ... }:
{
shb.firefly-iii = {
ldap = {
userGroup = "user_group";
};
};
};
clientLoginSso =
{ config, ... }:
{
imports = [
shb.test.baseModule
shb.test.clientLoginModule
];
test = {
subdomain = "f";
};
test.login = {
startUrl = "https://${config.test.fqdn}";
usernameFieldLabelRegex = "Username";
passwordFieldLabelRegex = "Password";
loginButtonNameRegex = "[sS]ign [iI]n";
testLoginWith = [
{
username = "alice";
password = "NotAlicePassword";
nextPageExpect = [
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
];
}
{
username = "alice";
password = "AlicePassword";
nextPageExpect = [
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()"
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
"expect(page.get_by_text('Dashboard')).to_be_visible(timeout=10000)"
];
}
{
username = "bob";
password = "NotBobPassword";
nextPageExpect = [
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
];
}
{
username = "bob";
password = "BobPassword";
nextPageExpect = [
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).not_to_be_visible()"
"expect(page.get_by_role('button', name=re.compile('Sign In'))).not_to_be_visible()"
"expect(page.get_by_text('Dashboard')).to_be_visible(timeout=10000)"
];
}
{
username = "charlie";
password = "NotCharliePassword";
nextPageExpect = [
"expect(page.get_by_text(re.compile('[Ii]ncorrect'))).to_be_visible()"
];
}
{
username = "charlie";
password = "CharliePassword";
nextPageExpect = [
"expect(page).to_have_url(re.compile('.*/authenticated'))"
];
}
];
};
};
sso =
{ config, ... }:
{
shb.firefly-iii = {
sso = {
enable = true;
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
};
};
};
in
{
basic = shb.test.runNixOSTest {
name = "firefly-iii_basic";
nodes.client = {
imports = [
clientLogin
];
};
nodes.server = {
imports = [
basic
];
};
testScript = commonTestScript.access;
};
backup = shb.test.runNixOSTest {
name = "firefly-iii_backup";
nodes.server =
{ config, ... }:
{
imports = [
basic
(shb.test.backup config.shb.firefly-iii.backup)
];
};
nodes.client = { };
testScript = commonTestScript.backup;
};
https = shb.test.runNixOSTest {
name = "firefly-iii_https";
nodes.client = {
imports = [
clientLogin
];
};
nodes.server = {
imports = [
basic
shb.test.certs
https
];
};
testScript = commonTestScript.access;
};
sso = shb.test.runNixOSTest {
name = "firefly-iii_sso";
nodes.client = {
imports = [
clientLoginSso
];
};
nodes.server =
{ config, pkgs, ... }:
{
imports = [
basic
shb.test.certs
https
shb.test.ldap
ldap
(shb.test.sso config.shb.certs.certs.selfsigned.n)
sso
];
};
testScript = commonTestScript.access.override {
redirectSSO = true;
};
};
}