homepage: init with dashboard contract
This commit is contained in:
parent
77869300ee
commit
f485cc60fe
50 changed files with 3072 additions and 411 deletions
|
|
@ -37,13 +37,17 @@ Provided contracts are:
|
||||||
- [SSL generator contract](contracts-ssl.html) to generate SSL certificates.
|
- [SSL generator contract](contracts-ssl.html) to generate SSL certificates.
|
||||||
Two providers are implemented: self-signed and Let's Encrypt.
|
Two providers are implemented: self-signed and Let's Encrypt.
|
||||||
- [Backup contract][] to backup directories.
|
- [Backup contract][] to backup directories.
|
||||||
One provider is implemented: [Restic][].
|
Two providers are implemented: [BorgBackup][] and [Restic][].
|
||||||
- [Database Backup contract](contracts-databasebackup.html) to backup database dumps.
|
- [Database Backup contract](contracts-databasebackup.html) to backup database dumps.
|
||||||
One provider is implemented: [Restic][].
|
One provider is implemented: [BorgBackup][] and [Restic][].
|
||||||
- [Secret contract](contracts-secret.html) to provide secrets that are deployed outside of the Nix store.
|
- [Contract for Secrets](contracts-secret.html) to provide secrets that are deployed outside of the Nix store.
|
||||||
One provider is implemented: [SOPS][].
|
One provider is implemented: [SOPS][].
|
||||||
|
- [Dashboard contract](contracts-dashboard.html) to show services in a nice user-facing dashboard.
|
||||||
|
One provider is implemented: [Homepage][].
|
||||||
|
|
||||||
[backup contract]: contracts-backup.html
|
[backup contract]: contracts-backup.html
|
||||||
|
[borgbackup]: blocks-borgbackup.html
|
||||||
|
[homepage]: services-homepage.html
|
||||||
[restic]: blocks-restic.html
|
[restic]: blocks-restic.html
|
||||||
[sops]: blocks-sops.html
|
[sops]: blocks-sops.html
|
||||||
|
|
||||||
|
|
@ -63,6 +67,10 @@ modules/contracts/databasebackup/docs/default.md
|
||||||
modules/contracts/secret/docs/default.md
|
modules/contracts/secret/docs/default.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{=include=} chapters html:into-file=//contracts-dashboard.html
|
||||||
|
modules/contracts/dashboard/docs/default.md
|
||||||
|
```
|
||||||
|
|
||||||
## Problem Statement {#contracts-why}
|
## Problem Statement {#contracts-why}
|
||||||
|
|
||||||
Currently in nixpkgs, every module accessing a shared resource
|
Currently in nixpkgs, every module accessing a shared resource
|
||||||
|
|
|
||||||
|
|
@ -128,3 +128,29 @@ shb.sops.secret."awesome" = {
|
||||||
request = config.shb.restic.instances.awesome.settings.passphrase.request;
|
request = config.shb.restic.instances.awesome.settings.passphrase.request;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Impermanence {#recipes-exposeService-impermanence}
|
||||||
|
|
||||||
|
To save the data folder in an impermanence setup, add:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.zfs.datasets."safe/awesome".path = config.services.awesome.dataDir;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Application Dashboard {#recipes-exposeService-applicationdashboard}
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.MyServices.services.Awesome = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://${fqdn}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString listenPort}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,6 @@
|
||||||
"blocks-authelia-forward-auth": [
|
"blocks-authelia-forward-auth": [
|
||||||
"blocks-authelia.html#blocks-authelia-forward-auth"
|
"blocks-authelia.html#blocks-authelia-forward-auth"
|
||||||
],
|
],
|
||||||
"blocks-authelia-global-setup": [
|
|
||||||
"blocks-authelia.html#blocks-authelia-global-setup"
|
|
||||||
],
|
|
||||||
"blocks-authelia-oidc": [
|
"blocks-authelia-oidc": [
|
||||||
"blocks-authelia.html#blocks-authelia-oidc"
|
"blocks-authelia.html#blocks-authelia-oidc"
|
||||||
],
|
],
|
||||||
|
|
@ -65,6 +62,21 @@
|
||||||
"blocks-authelia-options-shb.authelia.autheliaUser": [
|
"blocks-authelia-options-shb.authelia.autheliaUser": [
|
||||||
"blocks-authelia.html#blocks-authelia-options-shb.authelia.autheliaUser"
|
"blocks-authelia.html#blocks-authelia-options-shb.authelia.autheliaUser"
|
||||||
],
|
],
|
||||||
|
"blocks-authelia-options-shb.authelia.dashboard": [
|
||||||
|
"blocks-authelia.html#blocks-authelia-options-shb.authelia.dashboard"
|
||||||
|
],
|
||||||
|
"blocks-authelia-options-shb.authelia.dashboard.request": [
|
||||||
|
"blocks-authelia.html#blocks-authelia-options-shb.authelia.dashboard.request"
|
||||||
|
],
|
||||||
|
"blocks-authelia-options-shb.authelia.dashboard.request.externalUrl": [
|
||||||
|
"blocks-authelia.html#blocks-authelia-options-shb.authelia.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"blocks-authelia-options-shb.authelia.dashboard.request.internalUrl": [
|
||||||
|
"blocks-authelia.html#blocks-authelia-options-shb.authelia.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"blocks-authelia-options-shb.authelia.dashboard.result": [
|
||||||
|
"blocks-authelia.html#blocks-authelia-options-shb.authelia.dashboard.result"
|
||||||
|
],
|
||||||
"blocks-authelia-options-shb.authelia.dcdomain": [
|
"blocks-authelia-options-shb.authelia.dcdomain": [
|
||||||
"blocks-authelia.html#blocks-authelia-options-shb.authelia.dcdomain"
|
"blocks-authelia.html#blocks-authelia-options-shb.authelia.dcdomain"
|
||||||
],
|
],
|
||||||
|
|
@ -323,6 +335,9 @@
|
||||||
"blocks-authelia-troubleshooting": [
|
"blocks-authelia-troubleshooting": [
|
||||||
"blocks-authelia.html#blocks-authelia-troubleshooting"
|
"blocks-authelia.html#blocks-authelia-troubleshooting"
|
||||||
],
|
],
|
||||||
|
"blocks-authelia-usage-configuration": [
|
||||||
|
"blocks-authelia.html#blocks-authelia-usage-configuration"
|
||||||
|
],
|
||||||
"blocks-borgbackup": [
|
"blocks-borgbackup": [
|
||||||
"blocks-borgbackup.html#blocks-borgbackup"
|
"blocks-borgbackup.html#blocks-borgbackup"
|
||||||
],
|
],
|
||||||
|
|
@ -578,8 +593,8 @@
|
||||||
"blocks-lldap": [
|
"blocks-lldap": [
|
||||||
"blocks-lldap.html#blocks-lldap"
|
"blocks-lldap.html#blocks-lldap"
|
||||||
],
|
],
|
||||||
"blocks-lldap-global-setup": [
|
"blocks-lldap-features": [
|
||||||
"blocks-lldap.html#blocks-lldap-global-setup"
|
"blocks-lldap.html#blocks-lldap-features"
|
||||||
],
|
],
|
||||||
"blocks-lldap-manage-groups": [
|
"blocks-lldap-manage-groups": [
|
||||||
"blocks-lldap.html#blocks-lldap-manage-groups"
|
"blocks-lldap.html#blocks-lldap-manage-groups"
|
||||||
|
|
@ -623,6 +638,21 @@
|
||||||
"blocks-lldap-options-shb.lldap.backup.result.restoreScript": [
|
"blocks-lldap-options-shb.lldap.backup.result.restoreScript": [
|
||||||
"blocks-lldap.html#blocks-lldap-options-shb.lldap.backup.result.restoreScript"
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"blocks-lldap-options-shb.lldap.dashboard": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.dashboard"
|
||||||
|
],
|
||||||
|
"blocks-lldap-options-shb.lldap.dashboard.request": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.dashboard.request"
|
||||||
|
],
|
||||||
|
"blocks-lldap-options-shb.lldap.dashboard.request.externalUrl": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"blocks-lldap-options-shb.lldap.dashboard.request.internalUrl": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"blocks-lldap-options-shb.lldap.dashboard.result": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.dashboard.result"
|
||||||
|
],
|
||||||
"blocks-lldap-options-shb.lldap.dcdomain": [
|
"blocks-lldap-options-shb.lldap.dcdomain": [
|
||||||
"blocks-lldap.html#blocks-lldap-options-shb.lldap.dcdomain"
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.dcdomain"
|
||||||
],
|
],
|
||||||
|
|
@ -824,18 +854,27 @@
|
||||||
"blocks-lldap-options-shb.lldap.webUIListenPort": [
|
"blocks-lldap-options-shb.lldap.webUIListenPort": [
|
||||||
"blocks-lldap.html#blocks-lldap-options-shb.lldap.webUIListenPort"
|
"blocks-lldap.html#blocks-lldap-options-shb.lldap.webUIListenPort"
|
||||||
],
|
],
|
||||||
"blocks-lldap-restrict-access-by-ip": [
|
|
||||||
"blocks-lldap.html#blocks-lldap-restrict-access-by-ip"
|
|
||||||
],
|
|
||||||
"blocks-lldap-ssl": [
|
|
||||||
"blocks-lldap.html#blocks-lldap-ssl"
|
|
||||||
],
|
|
||||||
"blocks-lldap-tests": [
|
"blocks-lldap-tests": [
|
||||||
"blocks-lldap.html#blocks-lldap-tests"
|
"blocks-lldap.html#blocks-lldap-tests"
|
||||||
],
|
],
|
||||||
"blocks-lldap-troubleshooting": [
|
"blocks-lldap-troubleshooting": [
|
||||||
"blocks-lldap.html#blocks-lldap-troubleshooting"
|
"blocks-lldap.html#blocks-lldap-troubleshooting"
|
||||||
],
|
],
|
||||||
|
"blocks-lldap-usage": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-usage"
|
||||||
|
],
|
||||||
|
"blocks-lldap-usage-applicationdashboard": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-usage-applicationdashboard"
|
||||||
|
],
|
||||||
|
"blocks-lldap-usage-configuration": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-usage-configuration"
|
||||||
|
],
|
||||||
|
"blocks-lldap-usage-restrict-access-by-ip": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-usage-restrict-access-by-ip"
|
||||||
|
],
|
||||||
|
"blocks-lldap-usage-ssl": [
|
||||||
|
"blocks-lldap.html#blocks-lldap-usage-ssl"
|
||||||
|
],
|
||||||
"blocks-mitmdump": [
|
"blocks-mitmdump": [
|
||||||
"blocks-mitmdump.html#blocks-mitmdump"
|
"blocks-mitmdump.html#blocks-mitmdump"
|
||||||
],
|
],
|
||||||
|
|
@ -953,6 +992,21 @@
|
||||||
"blocks-monitoring-options-shb.monitoring.contactPoints": [
|
"blocks-monitoring-options-shb.monitoring.contactPoints": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.contactPoints"
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.contactPoints"
|
||||||
],
|
],
|
||||||
|
"blocks-monitoring-options-shb.monitoring.dashboard": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.dashboard"
|
||||||
|
],
|
||||||
|
"blocks-monitoring-options-shb.monitoring.dashboard.request": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.dashboard.request"
|
||||||
|
],
|
||||||
|
"blocks-monitoring-options-shb.monitoring.dashboard.request.externalUrl": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"blocks-monitoring-options-shb.monitoring.dashboard.request.internalUrl": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"blocks-monitoring-options-shb.monitoring.dashboard.result": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.dashboard.result"
|
||||||
|
],
|
||||||
"blocks-monitoring-options-shb.monitoring.dashboards": [
|
"blocks-monitoring-options-shb.monitoring.dashboards": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.dashboards"
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.dashboards"
|
||||||
],
|
],
|
||||||
|
|
@ -1133,6 +1187,12 @@
|
||||||
"blocks-monitoring-usage": [
|
"blocks-monitoring-usage": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-usage"
|
"blocks-monitoring.html#blocks-monitoring-usage"
|
||||||
],
|
],
|
||||||
|
"blocks-monitoring-usage-applicationdashboard": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-usage-applicationdashboard"
|
||||||
|
],
|
||||||
|
"blocks-monitoring-usage-configuration": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-usage-configuration"
|
||||||
|
],
|
||||||
"blocks-monitoring-usage-log-optimization": [
|
"blocks-monitoring-usage-log-optimization": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-usage-log-optimization"
|
"blocks-monitoring.html#blocks-monitoring-usage-log-optimization"
|
||||||
],
|
],
|
||||||
|
|
@ -1712,6 +1772,15 @@
|
||||||
"contract-backup-usage": [
|
"contract-backup-usage": [
|
||||||
"contracts-backup.html#contract-backup-usage"
|
"contracts-backup.html#contract-backup-usage"
|
||||||
],
|
],
|
||||||
|
"contract-dashboard": [
|
||||||
|
"contracts-dashboard.html#contract-dashboard"
|
||||||
|
],
|
||||||
|
"contract-dashboard-options": [
|
||||||
|
"contracts-dashboard.html#contract-dashboard-options"
|
||||||
|
],
|
||||||
|
"contract-dashboard-providers": [
|
||||||
|
"contracts-dashboard.html#contract-dashboard-providers"
|
||||||
|
],
|
||||||
"contract-databasebackup": [
|
"contract-databasebackup": [
|
||||||
"contracts-databasebackup.html#contract-databasebackup"
|
"contracts-databasebackup.html#contract-databasebackup"
|
||||||
],
|
],
|
||||||
|
|
@ -1805,6 +1874,27 @@
|
||||||
"contracts-concept": [
|
"contracts-concept": [
|
||||||
"contracts.html#contracts-concept"
|
"contracts.html#contracts-concept"
|
||||||
],
|
],
|
||||||
|
"contracts-dashboard-options-shb.contracts.dashboard": [
|
||||||
|
"contracts-dashboard.html#contracts-dashboard-options-shb.contracts.dashboard"
|
||||||
|
],
|
||||||
|
"contracts-dashboard-options-shb.contracts.dashboard.request": [
|
||||||
|
"contracts-dashboard.html#contracts-dashboard-options-shb.contracts.dashboard.request"
|
||||||
|
],
|
||||||
|
"contracts-dashboard-options-shb.contracts.dashboard.request.externalUrl": [
|
||||||
|
"contracts-dashboard.html#contracts-dashboard-options-shb.contracts.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"contracts-dashboard-options-shb.contracts.dashboard.request.internalUrl": [
|
||||||
|
"contracts-dashboard.html#contracts-dashboard-options-shb.contracts.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"contracts-dashboard-options-shb.contracts.dashboard.result": [
|
||||||
|
"contracts-dashboard.html#contracts-dashboard-options-shb.contracts.dashboard.result"
|
||||||
|
],
|
||||||
|
"contracts-dashboard-options-shb.contracts.dashboard.settings": [
|
||||||
|
"contracts-dashboard.html#contracts-dashboard-options-shb.contracts.dashboard.settings"
|
||||||
|
],
|
||||||
|
"contracts-dashboard-usage": [
|
||||||
|
"contracts-dashboard.html#contracts-dashboard-usage"
|
||||||
|
],
|
||||||
"contracts-databasebackup-options-shb.contracts.databasebackup": [
|
"contracts-databasebackup-options-shb.contracts.databasebackup": [
|
||||||
"contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup"
|
"contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup"
|
||||||
],
|
],
|
||||||
|
|
@ -2156,12 +2246,18 @@
|
||||||
"recipes-exposeService": [
|
"recipes-exposeService": [
|
||||||
"recipes-exposeService.html#recipes-exposeService"
|
"recipes-exposeService.html#recipes-exposeService"
|
||||||
],
|
],
|
||||||
|
"recipes-exposeService-applicationdashboard": [
|
||||||
|
"recipes-exposeService.html#recipes-exposeService-applicationdashboard"
|
||||||
|
],
|
||||||
"recipes-exposeService-backup": [
|
"recipes-exposeService-backup": [
|
||||||
"recipes-exposeService.html#recipes-exposeService-backup"
|
"recipes-exposeService.html#recipes-exposeService-backup"
|
||||||
],
|
],
|
||||||
"recipes-exposeService-debug": [
|
"recipes-exposeService-debug": [
|
||||||
"recipes-exposeService.html#recipes-exposeService-debug"
|
"recipes-exposeService.html#recipes-exposeService-debug"
|
||||||
],
|
],
|
||||||
|
"recipes-exposeService-impermanence": [
|
||||||
|
"recipes-exposeService.html#recipes-exposeService-impermanence"
|
||||||
|
],
|
||||||
"recipes-exposeService-ldap": [
|
"recipes-exposeService-ldap": [
|
||||||
"recipes-exposeService.html#recipes-exposeService-ldap"
|
"recipes-exposeService.html#recipes-exposeService-ldap"
|
||||||
],
|
],
|
||||||
|
|
@ -2216,6 +2312,9 @@
|
||||||
"services-arr": [
|
"services-arr": [
|
||||||
"services-arr.html#services-arr"
|
"services-arr.html#services-arr"
|
||||||
],
|
],
|
||||||
|
"services-arr-features": [
|
||||||
|
"services-arr.html#services-arr-features"
|
||||||
|
],
|
||||||
"services-arr-options": [
|
"services-arr-options": [
|
||||||
"services-arr.html#services-arr-options"
|
"services-arr.html#services-arr-options"
|
||||||
],
|
],
|
||||||
|
|
@ -2258,6 +2357,21 @@
|
||||||
"services-arr-options-shb.arr.bazarr.backup.result.restoreScript": [
|
"services-arr-options-shb.arr.bazarr.backup.result.restoreScript": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.bazarr.backup.result.restoreScript"
|
"services-arr.html#services-arr-options-shb.arr.bazarr.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.dashboard": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.dashboard"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.dashboard.request": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.dashboard.request.externalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.dashboard.request.internalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.dashboard.result": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.dashboard.result"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.bazarr.dataDir": [
|
"services-arr-options-shb.arr.bazarr.dataDir": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.bazarr.dataDir"
|
"services-arr.html#services-arr-options-shb.arr.bazarr.dataDir"
|
||||||
],
|
],
|
||||||
|
|
@ -2273,6 +2387,15 @@
|
||||||
"services-arr-options-shb.arr.bazarr.settings": [
|
"services-arr-options-shb.arr.bazarr.settings": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.bazarr.settings"
|
"services-arr.html#services-arr-options-shb.arr.bazarr.settings"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.settings.ApiKey": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.settings.ApiKey"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.settings.ApiKey.source": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.settings.ApiKey.source"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.bazarr.settings.ApiKey.transform": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.bazarr.settings.ApiKey.transform"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.bazarr.settings.LogLevel": [
|
"services-arr-options-shb.arr.bazarr.settings.LogLevel": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.bazarr.settings.LogLevel"
|
"services-arr.html#services-arr-options-shb.arr.bazarr.settings.LogLevel"
|
||||||
],
|
],
|
||||||
|
|
@ -2336,6 +2459,21 @@
|
||||||
"services-arr-options-shb.arr.jackett.backup.result.restoreScript": [
|
"services-arr-options-shb.arr.jackett.backup.result.restoreScript": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.jackett.backup.result.restoreScript"
|
"services-arr.html#services-arr-options-shb.arr.jackett.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.jackett.dashboard": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.jackett.dashboard"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.jackett.dashboard.request": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.jackett.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.jackett.dashboard.request.externalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.jackett.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.jackett.dashboard.request.internalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.jackett.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.jackett.dashboard.result": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.jackett.dashboard.result"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.jackett.dataDir": [
|
"services-arr-options-shb.arr.jackett.dataDir": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.jackett.dataDir"
|
"services-arr.html#services-arr-options-shb.arr.jackett.dataDir"
|
||||||
],
|
],
|
||||||
|
|
@ -2441,6 +2579,21 @@
|
||||||
"services-arr-options-shb.arr.lidarr.backup.result.restoreScript": [
|
"services-arr-options-shb.arr.lidarr.backup.result.restoreScript": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.lidarr.backup.result.restoreScript"
|
"services-arr.html#services-arr-options-shb.arr.lidarr.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.dashboard": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.dashboard"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.dashboard.request": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.dashboard.request.externalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.dashboard.request.internalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.dashboard.result": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.dashboard.result"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.lidarr.dataDir": [
|
"services-arr-options-shb.arr.lidarr.dataDir": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.lidarr.dataDir"
|
"services-arr.html#services-arr-options-shb.arr.lidarr.dataDir"
|
||||||
],
|
],
|
||||||
|
|
@ -2456,6 +2609,15 @@
|
||||||
"services-arr-options-shb.arr.lidarr.settings": [
|
"services-arr-options-shb.arr.lidarr.settings": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.lidarr.settings"
|
"services-arr.html#services-arr-options-shb.arr.lidarr.settings"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.settings.ApiKey": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.settings.ApiKey"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.settings.ApiKey.source": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.settings.ApiKey.source"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.lidarr.settings.ApiKey.transform": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.lidarr.settings.ApiKey.transform"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.lidarr.settings.LogLevel": [
|
"services-arr-options-shb.arr.lidarr.settings.LogLevel": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.lidarr.settings.LogLevel"
|
"services-arr.html#services-arr-options-shb.arr.lidarr.settings.LogLevel"
|
||||||
],
|
],
|
||||||
|
|
@ -2519,6 +2681,21 @@
|
||||||
"services-arr-options-shb.arr.radarr.backup.result.restoreScript": [
|
"services-arr-options-shb.arr.radarr.backup.result.restoreScript": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.radarr.backup.result.restoreScript"
|
"services-arr.html#services-arr-options-shb.arr.radarr.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.radarr.dashboard": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.radarr.dashboard"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.radarr.dashboard.request": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.radarr.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.radarr.dashboard.request.externalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.radarr.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.radarr.dashboard.request.internalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.radarr.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.radarr.dashboard.result": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.radarr.dashboard.result"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.radarr.dataDir": [
|
"services-arr-options-shb.arr.radarr.dataDir": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.radarr.dataDir"
|
"services-arr.html#services-arr-options-shb.arr.radarr.dataDir"
|
||||||
],
|
],
|
||||||
|
|
@ -2609,6 +2786,21 @@
|
||||||
"services-arr-options-shb.arr.readarr.backup.result.restoreScript": [
|
"services-arr-options-shb.arr.readarr.backup.result.restoreScript": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.readarr.backup.result.restoreScript"
|
"services-arr.html#services-arr-options-shb.arr.readarr.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.dashboard": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.dashboard"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.dashboard.request": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.dashboard.request.externalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.dashboard.request.internalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.dashboard.result": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.dashboard.result"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.readarr.dataDir": [
|
"services-arr-options-shb.arr.readarr.dataDir": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.readarr.dataDir"
|
"services-arr.html#services-arr-options-shb.arr.readarr.dataDir"
|
||||||
],
|
],
|
||||||
|
|
@ -2624,6 +2816,15 @@
|
||||||
"services-arr-options-shb.arr.readarr.settings": [
|
"services-arr-options-shb.arr.readarr.settings": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.readarr.settings"
|
"services-arr.html#services-arr-options-shb.arr.readarr.settings"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.settings.ApiKey": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.settings.ApiKey"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.settings.ApiKey.source": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.settings.ApiKey.source"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.readarr.settings.ApiKey.transform": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.readarr.settings.ApiKey.transform"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.readarr.settings.LogLevel": [
|
"services-arr-options-shb.arr.readarr.settings.LogLevel": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.readarr.settings.LogLevel"
|
"services-arr.html#services-arr-options-shb.arr.readarr.settings.LogLevel"
|
||||||
],
|
],
|
||||||
|
|
@ -2687,6 +2888,21 @@
|
||||||
"services-arr-options-shb.arr.sonarr.backup.result.restoreScript": [
|
"services-arr-options-shb.arr.sonarr.backup.result.restoreScript": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.sonarr.backup.result.restoreScript"
|
"services-arr.html#services-arr-options-shb.arr.sonarr.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-arr-options-shb.arr.sonarr.dashboard": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.sonarr.dashboard"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.sonarr.dashboard.request": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.sonarr.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.sonarr.dashboard.request.externalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.sonarr.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.sonarr.dashboard.request.internalUrl": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.sonarr.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-arr-options-shb.arr.sonarr.dashboard.result": [
|
||||||
|
"services-arr.html#services-arr-options-shb.arr.sonarr.dashboard.result"
|
||||||
|
],
|
||||||
"services-arr-options-shb.arr.sonarr.dataDir": [
|
"services-arr-options-shb.arr.sonarr.dataDir": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.sonarr.dataDir"
|
"services-arr.html#services-arr-options-shb.arr.sonarr.dataDir"
|
||||||
],
|
],
|
||||||
|
|
@ -2735,6 +2951,30 @@
|
||||||
"services-arr-options-shb.arr.sonarr.subdomain": [
|
"services-arr-options-shb.arr.sonarr.subdomain": [
|
||||||
"services-arr.html#services-arr-options-shb.arr.sonarr.subdomain"
|
"services-arr.html#services-arr-options-shb.arr.sonarr.subdomain"
|
||||||
],
|
],
|
||||||
|
"services-arr-usage": [
|
||||||
|
"services-arr.html#services-arr-usage"
|
||||||
|
],
|
||||||
|
"services-arr-usage-apikeys": [
|
||||||
|
"services-arr.html#services-arr-usage-apikeys"
|
||||||
|
],
|
||||||
|
"services-arr-usage-applicationdashboard": [
|
||||||
|
"services-arr.html#services-arr-usage-applicationdashboard"
|
||||||
|
],
|
||||||
|
"services-arr-usage-configuration": [
|
||||||
|
"services-arr.html#services-arr-usage-configuration"
|
||||||
|
],
|
||||||
|
"services-arr-usage-jackett-proxy": [
|
||||||
|
"services-arr.html#services-arr-usage-jackett-proxy"
|
||||||
|
],
|
||||||
|
"services-authelia-features": [
|
||||||
|
"blocks-authelia.html#services-authelia-features"
|
||||||
|
],
|
||||||
|
"services-authelia-usage": [
|
||||||
|
"blocks-authelia.html#services-authelia-usage"
|
||||||
|
],
|
||||||
|
"services-authelia-usage-applicationdashboard": [
|
||||||
|
"blocks-authelia.html#services-authelia-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-category-ai": [
|
"services-category-ai": [
|
||||||
"services.html#services-category-ai"
|
"services.html#services-category-ai"
|
||||||
],
|
],
|
||||||
|
|
@ -2744,6 +2984,9 @@
|
||||||
"services-category-code": [
|
"services-category-code": [
|
||||||
"services.html#services-category-code"
|
"services.html#services-category-code"
|
||||||
],
|
],
|
||||||
|
"services-category-dashboard": [
|
||||||
|
"services.html#services-category-dashboard"
|
||||||
|
],
|
||||||
"services-category-documents": [
|
"services-category-documents": [
|
||||||
"services.html#services-category-documents"
|
"services.html#services-category-documents"
|
||||||
],
|
],
|
||||||
|
|
@ -2771,6 +3014,9 @@
|
||||||
"services-firefly-iii-declarative-ldap": [
|
"services-firefly-iii-declarative-ldap": [
|
||||||
"services-firefly-iii.html#services-firefly-iii-declarative-ldap"
|
"services-firefly-iii.html#services-firefly-iii-declarative-ldap"
|
||||||
],
|
],
|
||||||
|
"services-firefly-iii-features": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-features"
|
||||||
|
],
|
||||||
"services-firefly-iii-impermanence": [
|
"services-firefly-iii-impermanence": [
|
||||||
"services-firefly-iii.html#services-firefly-iii-impermanence"
|
"services-firefly-iii.html#services-firefly-iii-impermanence"
|
||||||
],
|
],
|
||||||
|
|
@ -2837,6 +3083,21 @@
|
||||||
"services-firefly-iii-options-shb.firefly-iii.backup.result.restoreScript": [
|
"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.html#services-firefly-iii-options-shb.firefly-iii.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-firefly-iii-options-shb.firefly-iii.dashboard": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dashboard"
|
||||||
|
],
|
||||||
|
"services-firefly-iii-options-shb.firefly-iii.dashboard.request": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-firefly-iii-options-shb.firefly-iii.dashboard.request.externalUrl": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-firefly-iii-options-shb.firefly-iii.dashboard.request.internalUrl": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-firefly-iii-options-shb.firefly-iii.dashboard.result": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dashboard.result"
|
||||||
|
],
|
||||||
"services-firefly-iii-options-shb.firefly-iii.dbPassword": [
|
"services-firefly-iii-options-shb.firefly-iii.dbPassword": [
|
||||||
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword"
|
"services-firefly-iii.html#services-firefly-iii-options-shb.firefly-iii.dbPassword"
|
||||||
],
|
],
|
||||||
|
|
@ -3050,9 +3311,15 @@
|
||||||
"services-firefly-iii-usage": [
|
"services-firefly-iii-usage": [
|
||||||
"services-firefly-iii.html#services-firefly-iii-usage"
|
"services-firefly-iii.html#services-firefly-iii-usage"
|
||||||
],
|
],
|
||||||
|
"services-firefly-iii-usage-applicationdashboard": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-firefly-iii-usage-backup": [
|
"services-firefly-iii-usage-backup": [
|
||||||
"services-firefly-iii.html#services-firefly-iii-usage-backup"
|
"services-firefly-iii.html#services-firefly-iii-usage-backup"
|
||||||
],
|
],
|
||||||
|
"services-firefly-iii-usage-configuration": [
|
||||||
|
"services-firefly-iii.html#services-firefly-iii-usage-configuration"
|
||||||
|
],
|
||||||
"services-forgejo": [
|
"services-forgejo": [
|
||||||
"services-forgejo.html#services-forgejo"
|
"services-forgejo.html#services-forgejo"
|
||||||
],
|
],
|
||||||
|
|
@ -3098,6 +3365,21 @@
|
||||||
"services-forgejo-options-shb.forgejo.backup.result.restoreScript": [
|
"services-forgejo-options-shb.forgejo.backup.result.restoreScript": [
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.backup.result.restoreScript"
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.dashboard": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.dashboard"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.dashboard.request": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.dashboard.request.externalUrl": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.dashboard.request.internalUrl": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.dashboard.result": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.dashboard.result"
|
||||||
|
],
|
||||||
"services-forgejo-options-shb.forgejo.databasePassword": [
|
"services-forgejo-options-shb.forgejo.databasePassword": [
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword"
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.databasePassword"
|
||||||
],
|
],
|
||||||
|
|
@ -3359,6 +3641,9 @@
|
||||||
"services-forgejo-usage": [
|
"services-forgejo-usage": [
|
||||||
"services-forgejo.html#services-forgejo-usage"
|
"services-forgejo.html#services-forgejo-usage"
|
||||||
],
|
],
|
||||||
|
"services-forgejo-usage-applicationdashboard": [
|
||||||
|
"services-forgejo.html#services-forgejo-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-forgejo-usage-backup": [
|
"services-forgejo-usage-backup": [
|
||||||
"services-forgejo.html#services-forgejo-usage-backup"
|
"services-forgejo.html#services-forgejo-usage-backup"
|
||||||
],
|
],
|
||||||
|
|
@ -3446,6 +3731,21 @@
|
||||||
"services-home-assistant-options-shb.home-assistant.config.unit_system": [
|
"services-home-assistant-options-shb.home-assistant.config.unit_system": [
|
||||||
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.config.unit_system"
|
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.config.unit_system"
|
||||||
],
|
],
|
||||||
|
"services-home-assistant-options-shb.home-assistant.dashboard": [
|
||||||
|
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.dashboard"
|
||||||
|
],
|
||||||
|
"services-home-assistant-options-shb.home-assistant.dashboard.request": [
|
||||||
|
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-home-assistant-options-shb.home-assistant.dashboard.request.externalUrl": [
|
||||||
|
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-home-assistant-options-shb.home-assistant.dashboard.request.internalUrl": [
|
||||||
|
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-home-assistant-options-shb.home-assistant.dashboard.result": [
|
||||||
|
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.dashboard.result"
|
||||||
|
],
|
||||||
"services-home-assistant-options-shb.home-assistant.domain": [
|
"services-home-assistant-options-shb.home-assistant.domain": [
|
||||||
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.domain"
|
"services-home-assistant.html#services-home-assistant-options-shb.home-assistant.domain"
|
||||||
],
|
],
|
||||||
|
|
@ -3503,6 +3803,9 @@
|
||||||
"services-home-assistant-usage": [
|
"services-home-assistant-usage": [
|
||||||
"services-home-assistant.html#services-home-assistant-usage"
|
"services-home-assistant.html#services-home-assistant-usage"
|
||||||
],
|
],
|
||||||
|
"services-home-assistant-usage-applicationdashboard": [
|
||||||
|
"services-home-assistant.html#services-home-assistant-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-home-assistant-usage-backup": [
|
"services-home-assistant-usage-backup": [
|
||||||
"services-home-assistant.html#services-home-assistant-usage-backup"
|
"services-home-assistant.html#services-home-assistant-usage-backup"
|
||||||
],
|
],
|
||||||
|
|
@ -3539,6 +3842,132 @@
|
||||||
"services-home-assistant-usage-voice": [
|
"services-home-assistant-usage-voice": [
|
||||||
"services-home-assistant.html#services-home-assistant-usage-voice"
|
"services-home-assistant.html#services-home-assistant-usage-voice"
|
||||||
],
|
],
|
||||||
|
"services-homepage": [
|
||||||
|
"services-homepage.html#services-homepage"
|
||||||
|
],
|
||||||
|
"services-homepage-features": [
|
||||||
|
"services-homepage.html#services-homepage-features"
|
||||||
|
],
|
||||||
|
"services-homepage-options": [
|
||||||
|
"services-homepage.html#services-homepage-options"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.domain": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.domain"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.enable": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.enable"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.ldap": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.ldap"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.ldap.userGroup": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.ldap.userGroup"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.name": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.name"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.group": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.group"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.mode": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.mode"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.owner": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.owner"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.restartUnits": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.request.restartUnits"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.result": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.result"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.result.path": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.apiKey.result.path"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.request": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.request.externalUrl": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.request.internalUrl": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.result": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard.result"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.name": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.name"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.settings": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.settings"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.sortOrder": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.sortOrder"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.servicesGroups._name_.sortOrder": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.servicesGroups._name_.sortOrder"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.ssl": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.ssl"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.ssl.paths": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.ssl.paths"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.ssl.paths.cert": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.ssl.paths.cert"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.ssl.paths.key": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.ssl.paths.key"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.ssl.systemdService": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.ssl.systemdService"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.sso": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.sso"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.sso.authEndpoint": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.sso.authEndpoint"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.sso.authorization_policy": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.sso.authorization_policy"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.sso.enable": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.sso.enable"
|
||||||
|
],
|
||||||
|
"services-homepage-options-shb.homepage.subdomain": [
|
||||||
|
"services-homepage.html#services-homepage-options-shb.homepage.subdomain"
|
||||||
|
],
|
||||||
|
"services-homepage-usage": [
|
||||||
|
"services-homepage.html#services-homepage-usage"
|
||||||
|
],
|
||||||
|
"services-homepage-usage-custom": [
|
||||||
|
"services-homepage.html#services-homepage-usage-custom"
|
||||||
|
],
|
||||||
|
"services-homepage-usage-main": [
|
||||||
|
"services-homepage.html#services-homepage-usage-main"
|
||||||
|
],
|
||||||
|
"services-homepage-usage-service": [
|
||||||
|
"services-homepage.html#services-homepage-usage-service"
|
||||||
|
],
|
||||||
|
"services-homepage-usage-widget": [
|
||||||
|
"services-homepage.html#services-homepage-usage-widget"
|
||||||
|
],
|
||||||
"services-jellyfin": [
|
"services-jellyfin": [
|
||||||
"services-jellyfin.html#services-jellyfin"
|
"services-jellyfin.html#services-jellyfin"
|
||||||
],
|
],
|
||||||
|
|
@ -3623,6 +4052,21 @@
|
||||||
"services-jellyfin-options-shb.jellyfin.backup.result.restoreScript": [
|
"services-jellyfin-options-shb.jellyfin.backup.result.restoreScript": [
|
||||||
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.backup.result.restoreScript"
|
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-jellyfin-options-shb.jellyfin.dashboard": [
|
||||||
|
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.dashboard"
|
||||||
|
],
|
||||||
|
"services-jellyfin-options-shb.jellyfin.dashboard.request": [
|
||||||
|
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-jellyfin-options-shb.jellyfin.dashboard.request.externalUrl": [
|
||||||
|
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-jellyfin-options-shb.jellyfin.dashboard.request.internalUrl": [
|
||||||
|
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-jellyfin-options-shb.jellyfin.dashboard.result": [
|
||||||
|
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.dashboard.result"
|
||||||
|
],
|
||||||
"services-jellyfin-options-shb.jellyfin.debug": [
|
"services-jellyfin-options-shb.jellyfin.debug": [
|
||||||
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.debug"
|
"services-jellyfin.html#services-jellyfin-options-shb.jellyfin.debug"
|
||||||
],
|
],
|
||||||
|
|
@ -3776,9 +4220,15 @@
|
||||||
"services-jellyfin-usage": [
|
"services-jellyfin-usage": [
|
||||||
"services-jellyfin.html#services-jellyfin-usage"
|
"services-jellyfin.html#services-jellyfin-usage"
|
||||||
],
|
],
|
||||||
|
"services-jellyfin-usage-applicationdashboard": [
|
||||||
|
"services-jellyfin.html#services-jellyfin-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-jellyfin-usage-backup": [
|
"services-jellyfin-usage-backup": [
|
||||||
"services-jellyfin.html#services-jellyfin-usage-backup"
|
"services-jellyfin.html#services-jellyfin-usage-backup"
|
||||||
],
|
],
|
||||||
|
"services-jellyfin-usage-configuration": [
|
||||||
|
"services-jellyfin.html#services-jellyfin-usage-configuration"
|
||||||
|
],
|
||||||
"services-karakeep": [
|
"services-karakeep": [
|
||||||
"services-karakeep.html#services-karakeep"
|
"services-karakeep.html#services-karakeep"
|
||||||
],
|
],
|
||||||
|
|
@ -3824,6 +4274,21 @@
|
||||||
"services-karakeep-options-shb.karakeep.backup.result.restoreScript": [
|
"services-karakeep-options-shb.karakeep.backup.result.restoreScript": [
|
||||||
"services-karakeep.html#services-karakeep-options-shb.karakeep.backup.result.restoreScript"
|
"services-karakeep.html#services-karakeep-options-shb.karakeep.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-karakeep-options-shb.karakeep.dashboard": [
|
||||||
|
"services-karakeep.html#services-karakeep-options-shb.karakeep.dashboard"
|
||||||
|
],
|
||||||
|
"services-karakeep-options-shb.karakeep.dashboard.request": [
|
||||||
|
"services-karakeep.html#services-karakeep-options-shb.karakeep.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-karakeep-options-shb.karakeep.dashboard.request.externalUrl": [
|
||||||
|
"services-karakeep.html#services-karakeep-options-shb.karakeep.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-karakeep-options-shb.karakeep.dashboard.request.internalUrl": [
|
||||||
|
"services-karakeep.html#services-karakeep-options-shb.karakeep.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-karakeep-options-shb.karakeep.dashboard.result": [
|
||||||
|
"services-karakeep.html#services-karakeep-options-shb.karakeep.dashboard.result"
|
||||||
|
],
|
||||||
"services-karakeep-options-shb.karakeep.domain": [
|
"services-karakeep-options-shb.karakeep.domain": [
|
||||||
"services-karakeep.html#services-karakeep-options-shb.karakeep.domain"
|
"services-karakeep.html#services-karakeep-options-shb.karakeep.domain"
|
||||||
],
|
],
|
||||||
|
|
@ -3974,6 +4439,12 @@
|
||||||
"services-karakeep-usage": [
|
"services-karakeep-usage": [
|
||||||
"services-karakeep.html#services-karakeep-usage"
|
"services-karakeep.html#services-karakeep-usage"
|
||||||
],
|
],
|
||||||
|
"services-karakeep-usage-applicationdashboard": [
|
||||||
|
"services-karakeep.html#services-karakeep-usage-applicationdashboard"
|
||||||
|
],
|
||||||
|
"services-karakeep-usage-configuration": [
|
||||||
|
"services-karakeep.html#services-karakeep-usage-configuration"
|
||||||
|
],
|
||||||
"services-mailserver": [
|
"services-mailserver": [
|
||||||
"services-mailserver.html#services-mailserver"
|
"services-mailserver.html#services-mailserver"
|
||||||
],
|
],
|
||||||
|
|
@ -4583,6 +5054,21 @@
|
||||||
"services-nextcloudserver-options-shb.nextcloud.backup.result.restoreScript": [
|
"services-nextcloudserver-options-shb.nextcloud.backup.result.restoreScript": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.result.restoreScript"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.dashboard": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dashboard"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.dashboard.request": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.dashboard.request.externalUrl": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.dashboard.request.internalUrl": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.dashboard.result": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dashboard.result"
|
||||||
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.dataDir": [
|
"services-nextcloudserver-options-shb.nextcloud.dataDir": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dataDir"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.dataDir"
|
||||||
],
|
],
|
||||||
|
|
@ -4667,6 +5153,9 @@
|
||||||
"services-nextcloudserver-usage": [
|
"services-nextcloudserver-usage": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-usage"
|
"services-nextcloud.html#services-nextcloudserver-usage"
|
||||||
],
|
],
|
||||||
|
"services-nextcloudserver-usage-applicationdashboard": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-nextcloudserver-usage-backup": [
|
"services-nextcloudserver-usage-backup": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-usage-backup"
|
"services-nextcloud.html#services-nextcloudserver-usage-backup"
|
||||||
],
|
],
|
||||||
|
|
@ -4754,6 +5243,21 @@
|
||||||
"services-open-webui-options-shb.open-webui.backup.result.restoreScript": [
|
"services-open-webui-options-shb.open-webui.backup.result.restoreScript": [
|
||||||
"services-open-webui.html#services-open-webui-options-shb.open-webui.backup.result.restoreScript"
|
"services-open-webui.html#services-open-webui-options-shb.open-webui.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-open-webui-options-shb.open-webui.dashboard": [
|
||||||
|
"services-open-webui.html#services-open-webui-options-shb.open-webui.dashboard"
|
||||||
|
],
|
||||||
|
"services-open-webui-options-shb.open-webui.dashboard.request": [
|
||||||
|
"services-open-webui.html#services-open-webui-options-shb.open-webui.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-open-webui-options-shb.open-webui.dashboard.request.externalUrl": [
|
||||||
|
"services-open-webui.html#services-open-webui-options-shb.open-webui.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-open-webui-options-shb.open-webui.dashboard.request.internalUrl": [
|
||||||
|
"services-open-webui.html#services-open-webui-options-shb.open-webui.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-open-webui-options-shb.open-webui.dashboard.result": [
|
||||||
|
"services-open-webui.html#services-open-webui-options-shb.open-webui.dashboard.result"
|
||||||
|
],
|
||||||
"services-open-webui-options-shb.open-webui.domain": [
|
"services-open-webui-options-shb.open-webui.domain": [
|
||||||
"services-open-webui.html#services-open-webui-options-shb.open-webui.domain"
|
"services-open-webui.html#services-open-webui-options-shb.open-webui.domain"
|
||||||
],
|
],
|
||||||
|
|
@ -4859,12 +5363,21 @@
|
||||||
"services-open-webui-usage": [
|
"services-open-webui-usage": [
|
||||||
"services-open-webui.html#services-open-webui-usage"
|
"services-open-webui.html#services-open-webui-usage"
|
||||||
],
|
],
|
||||||
|
"services-open-webui-usage-applicationdashboard": [
|
||||||
|
"services-open-webui.html#services-open-webui-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-open-webui-usage-backup": [
|
"services-open-webui-usage-backup": [
|
||||||
"services-open-webui.html#services-open-webui-usage-backup"
|
"services-open-webui.html#services-open-webui-usage-backup"
|
||||||
],
|
],
|
||||||
|
"services-open-webui-usage-configuration": [
|
||||||
|
"services-open-webui.html#services-open-webui-usage-configuration"
|
||||||
|
],
|
||||||
"services-pinchflat": [
|
"services-pinchflat": [
|
||||||
"services-pinchflat.html#services-pinchflat"
|
"services-pinchflat.html#services-pinchflat"
|
||||||
],
|
],
|
||||||
|
"services-pinchflat-features": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-features"
|
||||||
|
],
|
||||||
"services-pinchflat-options": [
|
"services-pinchflat-options": [
|
||||||
"services-pinchflat.html#services-pinchflat-options"
|
"services-pinchflat.html#services-pinchflat-options"
|
||||||
],
|
],
|
||||||
|
|
@ -4901,6 +5414,21 @@
|
||||||
"services-pinchflat-options-shb.pinchflat.backup.result.restoreScript": [
|
"services-pinchflat-options-shb.pinchflat.backup.result.restoreScript": [
|
||||||
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.backup.result.restoreScript"
|
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-pinchflat-options-shb.pinchflat.dashboard": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.dashboard"
|
||||||
|
],
|
||||||
|
"services-pinchflat-options-shb.pinchflat.dashboard.request": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-pinchflat-options-shb.pinchflat.dashboard.request.externalUrl": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-pinchflat-options-shb.pinchflat.dashboard.request.internalUrl": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-pinchflat-options-shb.pinchflat.dashboard.result": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.dashboard.result"
|
||||||
|
],
|
||||||
"services-pinchflat-options-shb.pinchflat.domain": [
|
"services-pinchflat-options-shb.pinchflat.domain": [
|
||||||
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.domain"
|
"services-pinchflat.html#services-pinchflat-options-shb.pinchflat.domain"
|
||||||
],
|
],
|
||||||
|
|
@ -4982,9 +5510,15 @@
|
||||||
"services-pinchflat-usage": [
|
"services-pinchflat-usage": [
|
||||||
"services-pinchflat.html#services-pinchflat-usage"
|
"services-pinchflat.html#services-pinchflat-usage"
|
||||||
],
|
],
|
||||||
|
"services-pinchflat-usage-applicationdashboard": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-pinchflat-usage-backup": [
|
"services-pinchflat-usage-backup": [
|
||||||
"services-pinchflat.html#services-pinchflat-usage-backup"
|
"services-pinchflat.html#services-pinchflat-usage-backup"
|
||||||
],
|
],
|
||||||
|
"services-pinchflat-usage-configuration": [
|
||||||
|
"services-pinchflat.html#services-pinchflat-usage-configuration"
|
||||||
|
],
|
||||||
"services-vaultwarden": [
|
"services-vaultwarden": [
|
||||||
"services-vaultwarden.html#services-vaultwarden"
|
"services-vaultwarden.html#services-vaultwarden"
|
||||||
],
|
],
|
||||||
|
|
@ -5039,6 +5573,21 @@
|
||||||
"services-vaultwarden-options-shb.vaultwarden.backup.result.restoreScript": [
|
"services-vaultwarden-options-shb.vaultwarden.backup.result.restoreScript": [
|
||||||
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.result.restoreScript"
|
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.backup.result.restoreScript"
|
||||||
],
|
],
|
||||||
|
"services-vaultwarden-options-shb.vaultwarden.dashboard": [
|
||||||
|
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.dashboard"
|
||||||
|
],
|
||||||
|
"services-vaultwarden-options-shb.vaultwarden.dashboard.request": [
|
||||||
|
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.dashboard.request"
|
||||||
|
],
|
||||||
|
"services-vaultwarden-options-shb.vaultwarden.dashboard.request.externalUrl": [
|
||||||
|
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.dashboard.request.externalUrl"
|
||||||
|
],
|
||||||
|
"services-vaultwarden-options-shb.vaultwarden.dashboard.request.internalUrl": [
|
||||||
|
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.dashboard.request.internalUrl"
|
||||||
|
],
|
||||||
|
"services-vaultwarden-options-shb.vaultwarden.dashboard.result": [
|
||||||
|
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.dashboard.result"
|
||||||
|
],
|
||||||
"services-vaultwarden-options-shb.vaultwarden.databasePassword": [
|
"services-vaultwarden-options-shb.vaultwarden.databasePassword": [
|
||||||
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword"
|
"services-vaultwarden.html#services-vaultwarden-options-shb.vaultwarden.databasePassword"
|
||||||
],
|
],
|
||||||
|
|
@ -5150,6 +5699,9 @@
|
||||||
"services-vaultwarden-usage": [
|
"services-vaultwarden-usage": [
|
||||||
"services-vaultwarden.html#services-vaultwarden-usage"
|
"services-vaultwarden.html#services-vaultwarden-usage"
|
||||||
],
|
],
|
||||||
|
"services-vaultwarden-usage-applicationdashboard": [
|
||||||
|
"services-vaultwarden.html#services-vaultwarden-usage-applicationdashboard"
|
||||||
|
],
|
||||||
"services-vaultwarden-usage-configuration": [
|
"services-vaultwarden-usage-configuration": [
|
||||||
"services-vaultwarden.html#services-vaultwarden-usage-configuration"
|
"services-vaultwarden.html#services-vaultwarden-usage-configuration"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<!-- Read these docs at https://shb.skarabox.com -->
|
<!-- Read these docs at https://shb.skarabox.com -->
|
||||||
# Services {#services}
|
# Services {#services}
|
||||||
|
|
||||||
Services are usually web applications that SHB help you self-host.
|
Services are usually web applications that SHB help you self-host some of your data.
|
||||||
Configuration of those is purposely made more opinionated than the upstream nixpkgs modules
|
Configuration of those is purposely made more opinionated than the upstream nixpkgs modules
|
||||||
in exchange for requiring less options to define.
|
in exchange for an uniformized configuration experience.
|
||||||
That is possible thanks to the extensive use of blocks provided by SHB.
|
That is possible thanks to the extensive use of blocks provided by SHB.
|
||||||
|
|
||||||
::: {.note}
|
::: {.note}
|
||||||
|
|
@ -19,6 +19,7 @@ information is provided in the respective manual sections.
|
||||||
| [Firefly-iii][] | Y (1) | Y | Y | Y | Y (2) | N |
|
| [Firefly-iii][] | Y (1) | Y | Y | Y | Y (2) | N |
|
||||||
| [Forgejo][] | 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 |
|
| [Home-Assistant][] | Y (1) | Y | N | Y | Y (2) | N |
|
||||||
|
| [Homepage][] | Y (1) | Y | N | Y | Y (2) | N |
|
||||||
| [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N |
|
| [Jellyfin][] | Y (1) | Y | Y | Y | Y (2) | N |
|
||||||
| [Karakeep][] | Y (1) | Y | Y | Y | Y (2) | N |
|
| [Karakeep][] | Y (1) | Y | Y | Y | Y (2) | N |
|
||||||
| [Nextcloud Server][] | Y (1) | Y | Y | Y | Y (2) | P (3) |
|
| [Nextcloud Server][] | Y (1) | Y | Y | Y | Y (2) | P (3) |
|
||||||
|
|
@ -40,6 +41,7 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
|
||||||
[Firefly-iii]: services-firefly-iii.html
|
[Firefly-iii]: services-firefly-iii.html
|
||||||
[Forgejo]: services-forgejo.html
|
[Forgejo]: services-forgejo.html
|
||||||
[Home-Assistant]: services-home-assistant.html
|
[Home-Assistant]: services-home-assistant.html
|
||||||
|
[Homepage]: services-homepage.html
|
||||||
[Jellyfin]: services-jellyfin.html
|
[Jellyfin]: services-jellyfin.html
|
||||||
[Karakeep]: services-karakeep.html
|
[Karakeep]: services-karakeep.html
|
||||||
[Nextcloud Server]: services-nextcloud.html
|
[Nextcloud Server]: services-nextcloud.html
|
||||||
|
|
@ -48,6 +50,12 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
|
||||||
[Simple NixOS Mailserver]: services-mailserver.html
|
[Simple NixOS Mailserver]: services-mailserver.html
|
||||||
[Vaultwarden]: services-vaultwarden.html
|
[Vaultwarden]: services-vaultwarden.html
|
||||||
|
|
||||||
|
## Dashboard {#services-category-dashboard}
|
||||||
|
|
||||||
|
```{=include=} chapters html:into-file=//services-homepage.html
|
||||||
|
modules/services/homepage/docs/default.md
|
||||||
|
```
|
||||||
|
|
||||||
## Documents {#services-category-documents}
|
## Documents {#services-category-documents}
|
||||||
|
|
||||||
```{=include=} chapters html:into-file=//services-nextcloud.html
|
```{=include=} chapters html:into-file=//services-nextcloud.html
|
||||||
|
|
|
||||||
15
flake.nix
15
flake.nix
|
|
@ -66,6 +66,9 @@
|
||||||
# The contract dummies are used to show options for contracts.
|
# The contract dummies are used to show options for contracts.
|
||||||
contractDummyModules = [
|
contractDummyModules = [
|
||||||
modules/contracts/backup/dummyModule.nix
|
modules/contracts/backup/dummyModule.nix
|
||||||
|
modules/contracts/dashboard/dummyModule.nix
|
||||||
|
modules/contracts/databasebackup/dummyModule.nix
|
||||||
|
modules/contracts/secret/dummyModule.nix
|
||||||
modules/contracts/ssl/dummyModule.nix
|
modules/contracts/ssl/dummyModule.nix
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
|
|
@ -110,6 +113,7 @@
|
||||||
(pkgs.path + "/nixos/modules/services/misc/forgejo.nix")
|
(pkgs.path + "/nixos/modules/services/misc/forgejo.nix")
|
||||||
];
|
];
|
||||||
"services/home-assistant" = ./modules/services/home-assistant.nix;
|
"services/home-assistant" = ./modules/services/home-assistant.nix;
|
||||||
|
"services/homepage" = ./modules/services/homepage.nix;
|
||||||
"services/jellyfin" = ./modules/services/jellyfin.nix;
|
"services/jellyfin" = ./modules/services/jellyfin.nix;
|
||||||
"services/karakeep" = ./modules/services/karakeep.nix;
|
"services/karakeep" = ./modules/services/karakeep.nix;
|
||||||
"services/mailserver" = ./modules/services/mailserver.nix;
|
"services/mailserver" = ./modules/services/mailserver.nix;
|
||||||
|
|
@ -131,6 +135,14 @@
|
||||||
"backup"
|
"backup"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
"contracts/dashboard" = {
|
||||||
|
module = ./modules/contracts/dashboard/dummyModule.nix;
|
||||||
|
optionRoot = [
|
||||||
|
"shb"
|
||||||
|
"contracts"
|
||||||
|
"dashboard"
|
||||||
|
];
|
||||||
|
};
|
||||||
"contracts/databasebackup" = {
|
"contracts/databasebackup" = {
|
||||||
module = ./modules/contracts/databasebackup/dummyModule.nix;
|
module = ./modules/contracts/databasebackup/dummyModule.nix;
|
||||||
optionRoot = [
|
optionRoot = [
|
||||||
|
|
@ -298,6 +310,7 @@
|
||||||
// (vm_test "hledger" ./test/services/hledger.nix)
|
// (vm_test "hledger" ./test/services/hledger.nix)
|
||||||
// (vm_test "immich" ./test/services/immich.nix)
|
// (vm_test "immich" ./test/services/immich.nix)
|
||||||
// (vm_test "homeassistant" ./test/services/home-assistant.nix)
|
// (vm_test "homeassistant" ./test/services/home-assistant.nix)
|
||||||
|
// (vm_test "homepage" ./test/services/homepage.nix)
|
||||||
// (vm_test "jellyfin" ./test/services/jellyfin.nix)
|
// (vm_test "jellyfin" ./test/services/jellyfin.nix)
|
||||||
// (vm_test "karakeep" ./test/services/karakeep.nix)
|
// (vm_test "karakeep" ./test/services/karakeep.nix)
|
||||||
// (vm_test "nextcloud" ./test/services/nextcloud.nix)
|
// (vm_test "nextcloud" ./test/services/nextcloud.nix)
|
||||||
|
|
@ -407,6 +420,7 @@
|
||||||
self.nixosModules.hledger
|
self.nixosModules.hledger
|
||||||
self.nixosModules.immich
|
self.nixosModules.immich
|
||||||
self.nixosModules.home-assistant
|
self.nixosModules.home-assistant
|
||||||
|
self.nixosModules.homepage
|
||||||
self.nixosModules.jellyfin
|
self.nixosModules.jellyfin
|
||||||
self.nixosModules.karakeep
|
self.nixosModules.karakeep
|
||||||
self.nixosModules.mailserver
|
self.nixosModules.mailserver
|
||||||
|
|
@ -445,6 +459,7 @@
|
||||||
nixosModules.hledger = modules/services/hledger.nix;
|
nixosModules.hledger = modules/services/hledger.nix;
|
||||||
nixosModules.immich = modules/services/immich.nix;
|
nixosModules.immich = modules/services/immich.nix;
|
||||||
nixosModules.home-assistant = modules/services/home-assistant.nix;
|
nixosModules.home-assistant = modules/services/home-assistant.nix;
|
||||||
|
nixosModules.homepage = modules/services/homepage.nix;
|
||||||
nixosModules.jellyfin = modules/services/jellyfin.nix;
|
nixosModules.jellyfin = modules/services/jellyfin.nix;
|
||||||
nixosModules.karakeep = modules/services/karakeep.nix;
|
nixosModules.karakeep = modules/services/karakeep.nix;
|
||||||
nixosModules.mailserver = modules/services/mailserver.nix;
|
nixosModules.mailserver = modules/services/mailserver.nix;
|
||||||
|
|
|
||||||
743
lib/default.nix
743
lib/default.nix
|
|
@ -2,411 +2,420 @@
|
||||||
let
|
let
|
||||||
inherit (builtins) isAttrs hasAttr;
|
inherit (builtins) isAttrs hasAttr;
|
||||||
inherit (lib) any concatMapStringsSep concatStringsSep;
|
inherit (lib) any concatMapStringsSep concatStringsSep;
|
||||||
in
|
shb = rec {
|
||||||
rec {
|
# Replace secrets in a file.
|
||||||
# Replace secrets in a file.
|
# - userConfig is an attrset that will produce a config file.
|
||||||
# - userConfig is an attrset that will produce a config file.
|
# - resultPath is the location the config file should have on the filesystem.
|
||||||
# - resultPath is the location the config file should have on the filesystem.
|
# - generator is a function taking two arguments name and value and returning path in the nix
|
||||||
# - generator is a function taking two arguments name and value and returning path in the nix
|
# nix store where the
|
||||||
# nix store where the
|
replaceSecrets =
|
||||||
replaceSecrets =
|
{
|
||||||
{
|
userConfig,
|
||||||
userConfig,
|
resultPath,
|
||||||
resultPath,
|
generator,
|
||||||
generator,
|
user ? null,
|
||||||
user ? null,
|
permissions ? "u=r,g=r,o=",
|
||||||
permissions ? "u=r,g=r,o=",
|
}:
|
||||||
}:
|
let
|
||||||
let
|
configWithTemplates = withReplacements userConfig;
|
||||||
configWithTemplates = withReplacements userConfig;
|
|
||||||
|
|
||||||
nonSecretConfigFile = generator "template" configWithTemplates;
|
nonSecretConfigFile = generator "template" configWithTemplates;
|
||||||
|
|
||||||
replacements = getReplacements userConfig;
|
replacements = getReplacements userConfig;
|
||||||
in
|
in
|
||||||
replaceSecretsScript {
|
replaceSecretsScript {
|
||||||
file = nonSecretConfigFile;
|
file = nonSecretConfigFile;
|
||||||
inherit resultPath replacements;
|
inherit resultPath replacements;
|
||||||
inherit user permissions;
|
inherit user permissions;
|
||||||
};
|
|
||||||
|
|
||||||
replaceSecretsFormatAdapter = format: format.generate;
|
|
||||||
replaceSecretsGeneratorAdapter =
|
|
||||||
generator: name: value:
|
|
||||||
pkgs.writeText "generator " (generator value);
|
|
||||||
toEnvVar = replaceSecretsGeneratorAdapter (
|
|
||||||
v: (lib.generators.toINIWithGlobalSection { } { globalSection = v; })
|
|
||||||
);
|
|
||||||
|
|
||||||
template =
|
|
||||||
file: newPath: replacements:
|
|
||||||
replaceSecretsScript {
|
|
||||||
inherit file replacements;
|
|
||||||
resultPath = newPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
genReplacement =
|
|
||||||
secret:
|
|
||||||
let
|
|
||||||
t =
|
|
||||||
{
|
|
||||||
transform ? null,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
if isNull transform then x: x else transform;
|
|
||||||
in
|
|
||||||
lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})");
|
|
||||||
|
|
||||||
replaceSecretsScript =
|
|
||||||
{
|
|
||||||
file,
|
|
||||||
resultPath,
|
|
||||||
replacements,
|
|
||||||
user ? null,
|
|
||||||
permissions ? "u=r,g=r,o=",
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
templatePath = resultPath + ".template";
|
|
||||||
|
|
||||||
# We check that the files containing the secrets have the
|
|
||||||
# correct permissions for us to read them in this separate
|
|
||||||
# step. Otherwise, the $(cat ...) commands inside the sed
|
|
||||||
# replacements could fail but not fail individually but
|
|
||||||
# not fail the whole script.
|
|
||||||
checkPermissions = concatMapStringsSep "\n" (
|
|
||||||
pattern: "cat ${pattern.source} > /dev/null"
|
|
||||||
) replacements;
|
|
||||||
|
|
||||||
sedPatterns = concatMapStringsSep " " (pattern: "-e \"s|${pattern.name}|${pattern.value}|\"") (
|
|
||||||
map genReplacement replacements
|
|
||||||
);
|
|
||||||
|
|
||||||
sedCmd = if replacements == [ ] then "cat" else "${pkgs.gnused}/bin/sed ${sedPatterns}";
|
|
||||||
in
|
|
||||||
''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
${checkPermissions}
|
|
||||||
|
|
||||||
mkdir -p $(dirname ${templatePath})
|
|
||||||
ln -fs ${file} ${templatePath}
|
|
||||||
rm -f ${resultPath}
|
|
||||||
touch ${resultPath}
|
|
||||||
''
|
|
||||||
+ (lib.optionalString (user != null) ''
|
|
||||||
chown ${user} ${resultPath}
|
|
||||||
'')
|
|
||||||
+ ''
|
|
||||||
${sedCmd} ${templatePath} > ${resultPath}
|
|
||||||
chmod ${permissions} ${resultPath}
|
|
||||||
'';
|
|
||||||
|
|
||||||
secretFileType = lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
source = lib.mkOption {
|
|
||||||
type = lib.types.path;
|
|
||||||
description = "File containing the value.";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
transform = lib.mkOption {
|
replaceSecretsFormatAdapter = format: format.generate;
|
||||||
type = lib.types.raw;
|
replaceSecretsGeneratorAdapter =
|
||||||
description = "An optional function to transform the secret.";
|
generator: name: value:
|
||||||
default = null;
|
pkgs.writeText "generator " (generator value);
|
||||||
example = lib.literalExpression ''
|
toEnvVar = replaceSecretsGeneratorAdapter (
|
||||||
v: "prefix-$${v}-suffix"
|
v: (lib.generators.toINIWithGlobalSection { } { globalSection = v; })
|
||||||
'';
|
);
|
||||||
|
|
||||||
|
template =
|
||||||
|
file: newPath: replacements:
|
||||||
|
replaceSecretsScript {
|
||||||
|
inherit file replacements;
|
||||||
|
resultPath = newPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
genReplacement =
|
||||||
|
secret:
|
||||||
|
let
|
||||||
|
t =
|
||||||
|
{
|
||||||
|
transform ? null,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
if isNull transform then x: x else transform;
|
||||||
|
in
|
||||||
|
lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})");
|
||||||
|
|
||||||
|
replaceSecretsScript =
|
||||||
|
{
|
||||||
|
file,
|
||||||
|
resultPath,
|
||||||
|
replacements,
|
||||||
|
user ? null,
|
||||||
|
permissions ? "u=r,g=r,o=",
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
templatePath = resultPath + ".template";
|
||||||
|
|
||||||
|
# We check that the files containing the secrets have the
|
||||||
|
# correct permissions for us to read them in this separate
|
||||||
|
# step. Otherwise, the $(cat ...) commands inside the sed
|
||||||
|
# replacements could fail but not fail individually but
|
||||||
|
# not fail the whole script.
|
||||||
|
checkPermissions = concatMapStringsSep "\n" (
|
||||||
|
pattern: "cat ${pattern.source} > /dev/null"
|
||||||
|
) replacements;
|
||||||
|
|
||||||
|
sedPatterns = concatMapStringsSep " " (pattern: "-e \"s|${pattern.name}|${pattern.value}|\"") (
|
||||||
|
map genReplacement replacements
|
||||||
|
);
|
||||||
|
|
||||||
|
sedCmd = if replacements == [ ] then "cat" else "${pkgs.gnused}/bin/sed ${sedPatterns}";
|
||||||
|
in
|
||||||
|
''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
${checkPermissions}
|
||||||
|
|
||||||
|
mkdir -p $(dirname ${templatePath})
|
||||||
|
ln -fs ${file} ${templatePath}
|
||||||
|
rm -f ${resultPath}
|
||||||
|
touch ${resultPath}
|
||||||
|
''
|
||||||
|
+ (lib.optionalString (user != null) ''
|
||||||
|
chown ${user} ${resultPath}
|
||||||
|
'')
|
||||||
|
+ ''
|
||||||
|
${sedCmd} ${templatePath} > ${resultPath}
|
||||||
|
chmod ${permissions} ${resultPath}
|
||||||
|
'';
|
||||||
|
|
||||||
|
secretFileType = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
source = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = "File containing the value.";
|
||||||
|
};
|
||||||
|
|
||||||
|
transform = lib.mkOption {
|
||||||
|
type = lib.types.raw;
|
||||||
|
description = "An optional function to transform the secret.";
|
||||||
|
default = null;
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
v: "prefix-$${v}-suffix"
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
secretName =
|
secretName =
|
||||||
names: "%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) names)}%";
|
names: "%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) names)}%";
|
||||||
|
|
||||||
withReplacements =
|
withReplacements =
|
||||||
attrs:
|
attrs:
|
||||||
let
|
let
|
||||||
valueOrReplacement =
|
valueOrReplacement =
|
||||||
name: value: if !(builtins.isAttrs value && value ? "source") then value else secretName name;
|
name: value: if !(builtins.isAttrs value && value ? "source") then value else secretName name;
|
||||||
in
|
in
|
||||||
mapAttrsRecursiveCond (v: !v ? "source") valueOrReplacement attrs;
|
mapAttrsRecursiveCond (v: !v ? "source") valueOrReplacement attrs;
|
||||||
|
|
||||||
getReplacements =
|
getReplacements =
|
||||||
attrs:
|
attrs:
|
||||||
let
|
let
|
||||||
addNameField =
|
addNameField =
|
||||||
name: value:
|
name: value:
|
||||||
if !(builtins.isAttrs value && value ? "source") then value else value // { name = name; };
|
if !(builtins.isAttrs value && value ? "source") then value else value // { name = name; };
|
||||||
|
|
||||||
secretsWithName = mapAttrsRecursiveCond (v: !v ? "source") addNameField attrs;
|
secretsWithName = mapAttrsRecursiveCond (v: !v ? "source") addNameField attrs;
|
||||||
in
|
in
|
||||||
collect (v: builtins.isAttrs v && v ? "source") secretsWithName;
|
collect (v: builtins.isAttrs v && v ? "source") secretsWithName;
|
||||||
|
|
||||||
# Inspired lib.attrsets.mapAttrsRecursiveCond but also recurses on lists.
|
# Inspired lib.attrsets.mapAttrsRecursiveCond but also recurses on lists.
|
||||||
mapAttrsRecursiveCond =
|
mapAttrsRecursiveCond =
|
||||||
# A function, given the attribute set the recursion is currently at, determine if to recurse deeper into that attribute set.
|
# A function, given the attribute set the recursion is currently at, determine if to recurse deeper into that attribute set.
|
||||||
cond:
|
cond:
|
||||||
# A function, given a list of attribute names and a value, returns a new value.
|
# A function, given a list of attribute names and a value, returns a new value.
|
||||||
f:
|
f:
|
||||||
# Attribute set or list to recursively map over.
|
# Attribute set or list to recursively map over.
|
||||||
set:
|
set:
|
||||||
let
|
let
|
||||||
recurse =
|
recurse =
|
||||||
path: val:
|
path: val:
|
||||||
if builtins.isAttrs val && cond val then
|
if builtins.isAttrs val && cond val then
|
||||||
lib.attrsets.mapAttrs (n: v: recurse (path ++ [ n ]) v) val
|
lib.attrsets.mapAttrs (n: v: recurse (path ++ [ n ]) v) val
|
||||||
else if builtins.isList val && cond val then
|
else if builtins.isList val && cond val then
|
||||||
lib.lists.imap0 (i: v: recurse (path ++ [ (builtins.toString i) ]) v) val
|
lib.lists.imap0 (i: v: recurse (path ++ [ (builtins.toString i) ]) v) val
|
||||||
else
|
else
|
||||||
f path val;
|
f path val;
|
||||||
in
|
in
|
||||||
recurse [ ] set;
|
recurse [ ] set;
|
||||||
|
|
||||||
# Like lib.attrsets.collect but also recurses on lists.
|
# Like lib.attrsets.collect but also recurses on lists.
|
||||||
collect =
|
collect =
|
||||||
# Given an attribute's value, determine if recursion should stop.
|
# Given an attribute's value, determine if recursion should stop.
|
||||||
pred:
|
pred:
|
||||||
# The attribute set to recursively collect.
|
# The attribute set to recursively collect.
|
||||||
attrs:
|
attrs:
|
||||||
if pred attrs then
|
if pred attrs then
|
||||||
[ attrs ]
|
[ attrs ]
|
||||||
else if builtins.isAttrs attrs then
|
else if builtins.isAttrs attrs then
|
||||||
lib.lists.concatMap (collect pred) (lib.attrsets.attrValues attrs)
|
lib.lists.concatMap (collect pred) (lib.attrsets.attrValues attrs)
|
||||||
else if builtins.isList attrs then
|
else if builtins.isList attrs then
|
||||||
lib.lists.concatMap (collect pred) attrs
|
lib.lists.concatMap (collect pred) attrs
|
||||||
else
|
else
|
||||||
[ ];
|
[ ];
|
||||||
|
|
||||||
indent =
|
indent =
|
||||||
i: str:
|
i: str:
|
||||||
lib.concatMapStringsSep "\n" (x: (lib.strings.replicate i " ") + x) (lib.splitString "\n" str);
|
lib.concatMapStringsSep "\n" (x: (lib.strings.replicate i " ") + x) (lib.splitString "\n" str);
|
||||||
|
|
||||||
# Generator for XML
|
# Generator for XML
|
||||||
formatXML =
|
formatXML =
|
||||||
{
|
{
|
||||||
enclosingRoot ? null,
|
enclosingRoot ? null,
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
type =
|
type =
|
||||||
with lib.types;
|
with lib.types;
|
||||||
let
|
let
|
||||||
valueType =
|
valueType =
|
||||||
nullOr (oneOf [
|
nullOr (oneOf [
|
||||||
bool
|
bool
|
||||||
int
|
int
|
||||||
float
|
float
|
||||||
str
|
str
|
||||||
path
|
path
|
||||||
(attrsOf valueType)
|
(attrsOf valueType)
|
||||||
(listOf valueType)
|
(listOf valueType)
|
||||||
])
|
])
|
||||||
// {
|
// {
|
||||||
description = "XML value";
|
description = "XML value";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
valueType;
|
valueType;
|
||||||
|
|
||||||
generate =
|
generate =
|
||||||
name: value:
|
name: value:
|
||||||
pkgs.callPackage (
|
pkgs.callPackage (
|
||||||
|
{ runCommand, python3 }:
|
||||||
|
runCommand "config"
|
||||||
|
{
|
||||||
|
value = builtins.toJSON (if enclosingRoot == null then value else { ${enclosingRoot} = value; });
|
||||||
|
passAsFile = [ "value" ];
|
||||||
|
}
|
||||||
|
(
|
||||||
|
pkgs.writers.writePython3 "dict2xml"
|
||||||
|
{
|
||||||
|
libraries = with python3.pkgs; [
|
||||||
|
python
|
||||||
|
dict2xml
|
||||||
|
];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from dict2xml import dict2xml
|
||||||
|
|
||||||
|
with open(os.environ["valuePath"]) as f:
|
||||||
|
content = json.loads(f.read())
|
||||||
|
if content is None:
|
||||||
|
print("Could not parse env var valuePath as json")
|
||||||
|
os.exit(2)
|
||||||
|
with open(os.environ["out"], "w") as out:
|
||||||
|
out.write(dict2xml(content))
|
||||||
|
''
|
||||||
|
)
|
||||||
|
) { };
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
parseXML =
|
||||||
|
xml:
|
||||||
|
let
|
||||||
|
xmlToJsonFile = pkgs.callPackage (
|
||||||
{ runCommand, python3 }:
|
{ runCommand, python3 }:
|
||||||
runCommand "config"
|
runCommand "config"
|
||||||
{
|
{
|
||||||
value = builtins.toJSON (if enclosingRoot == null then value else { ${enclosingRoot} = value; });
|
inherit xml;
|
||||||
passAsFile = [ "value" ];
|
passAsFile = [ "xml" ];
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
pkgs.writers.writePython3 "dict2xml"
|
pkgs.writers.writePython3 "xml2json"
|
||||||
{
|
{
|
||||||
libraries = with python3.pkgs; [
|
libraries = with python3.pkgs; [ python ];
|
||||||
python
|
|
||||||
dict2xml
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from dict2xml import dict2xml
|
from collections import ChainMap
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
|
||||||
|
def xml_to_dict_recursive(root):
|
||||||
|
all_descendants = list(root)
|
||||||
|
if len(all_descendants) == 0:
|
||||||
|
return {root.tag: root.text}
|
||||||
|
else:
|
||||||
|
merged_dict = ChainMap(*map(xml_to_dict_recursive, all_descendants))
|
||||||
|
return {root.tag: dict(merged_dict)}
|
||||||
|
|
||||||
|
|
||||||
|
with open(os.environ["xmlPath"]) as f:
|
||||||
|
root = ElementTree.XML(f.read())
|
||||||
|
xml = xml_to_dict_recursive(root)
|
||||||
|
j = json.dumps(xml)
|
||||||
|
|
||||||
with open(os.environ["valuePath"]) as f:
|
|
||||||
content = json.loads(f.read())
|
|
||||||
if content is None:
|
|
||||||
print("Could not parse env var valuePath as json")
|
|
||||||
os.exit(2)
|
|
||||||
with open(os.environ["out"], "w") as out:
|
with open(os.environ["out"], "w") as out:
|
||||||
out.write(dict2xml(content))
|
out.write(j)
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
) { };
|
) { };
|
||||||
|
in
|
||||||
|
builtins.fromJSON (builtins.readFile xmlToJsonFile);
|
||||||
|
|
||||||
};
|
renameAttrName =
|
||||||
|
attrset: from: to:
|
||||||
|
(lib.attrsets.filterAttrs (name: v: name == from) attrset)
|
||||||
|
// {
|
||||||
|
${to} = attrset.${from};
|
||||||
|
};
|
||||||
|
|
||||||
parseXML =
|
# Taken from https://github.com/antifuchs/nix-flake-tests/blob/main/default.nix
|
||||||
xml:
|
# with a nicer diff display function.
|
||||||
let
|
check =
|
||||||
xmlToJsonFile = pkgs.callPackage (
|
{ pkgs, tests }:
|
||||||
{ runCommand, python3 }:
|
let
|
||||||
runCommand "config"
|
formatValue =
|
||||||
|
val:
|
||||||
|
if (builtins.isList val || builtins.isAttrs val) then
|
||||||
|
builtins.toJSON val
|
||||||
|
else
|
||||||
|
builtins.toString val;
|
||||||
|
|
||||||
|
resultToString =
|
||||||
{
|
{
|
||||||
inherit xml;
|
name,
|
||||||
passAsFile = [ "xml" ];
|
expected,
|
||||||
}
|
result,
|
||||||
(
|
}:
|
||||||
pkgs.writers.writePython3 "xml2json"
|
builtins.readFile (
|
||||||
|
pkgs.runCommand "nix-flake-tests-error"
|
||||||
{
|
{
|
||||||
libraries = with python3.pkgs; [ python ];
|
expected = formatValue expected;
|
||||||
|
result = formatValue result;
|
||||||
|
passAsFile = [
|
||||||
|
"expected"
|
||||||
|
"result"
|
||||||
|
];
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
import os
|
echo "${name} failed (- expected, + result)" > $out
|
||||||
import json
|
cp ''${expectedPath} ''${expectedPath}.json
|
||||||
from collections import ChainMap
|
cp ''${resultPath} ''${resultPath}.json
|
||||||
from xml.etree import ElementTree
|
${pkgs.deepdiff}/bin/deep diff ''${expectedPath}.json ''${resultPath}.json >> $out
|
||||||
|
|
||||||
|
|
||||||
def xml_to_dict_recursive(root):
|
|
||||||
all_descendants = list(root)
|
|
||||||
if len(all_descendants) == 0:
|
|
||||||
return {root.tag: root.text}
|
|
||||||
else:
|
|
||||||
merged_dict = ChainMap(*map(xml_to_dict_recursive, all_descendants))
|
|
||||||
return {root.tag: dict(merged_dict)}
|
|
||||||
|
|
||||||
|
|
||||||
with open(os.environ["xmlPath"]) as f:
|
|
||||||
root = ElementTree.XML(f.read())
|
|
||||||
xml = xml_to_dict_recursive(root)
|
|
||||||
j = json.dumps(xml)
|
|
||||||
|
|
||||||
with open(os.environ["out"], "w") as out:
|
|
||||||
out.write(j)
|
|
||||||
''
|
''
|
||||||
)
|
);
|
||||||
|
|
||||||
|
results = pkgs.lib.runTests tests;
|
||||||
|
in
|
||||||
|
if results != [ ] then
|
||||||
|
builtins.throw (concatStringsSep "\n" (map resultToString (lib.traceValSeq results)))
|
||||||
|
else
|
||||||
|
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";
|
||||||
|
|
||||||
|
genConfigOutOfBandSystemd =
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
configLocation,
|
||||||
|
generator,
|
||||||
|
user ? null,
|
||||||
|
permissions ? "u=r,g=r,o=",
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
loadCredentials = getLoadCredentials "source" config;
|
||||||
|
preStart = lib.mkBefore (replaceSecrets {
|
||||||
|
userConfig = updateToLoadCredentials "source" "$CREDENTIALS_DIRECTORY" config;
|
||||||
|
resultPath = configLocation;
|
||||||
|
inherit generator;
|
||||||
|
inherit user permissions;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateToLoadCredentials =
|
||||||
|
sourceField: rootDir: attrs:
|
||||||
|
let
|
||||||
|
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||||
|
|
||||||
|
valueOrLoadCredential =
|
||||||
|
path: value:
|
||||||
|
if !(hasPlaceholderField value) then
|
||||||
|
value
|
||||||
|
else
|
||||||
|
value // { ${sourceField} = rootDir + "/" + concatStringsSep "_" path; };
|
||||||
|
in
|
||||||
|
mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) valueOrLoadCredential attrs;
|
||||||
|
|
||||||
|
getLoadCredentials =
|
||||||
|
sourceField: attrs:
|
||||||
|
let
|
||||||
|
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
||||||
|
|
||||||
|
addPathField =
|
||||||
|
path: value: if !(hasPlaceholderField value) then value else value // { inherit path; };
|
||||||
|
|
||||||
|
secretsWithPath = mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) addPathField attrs;
|
||||||
|
|
||||||
|
allSecrets = collect (v: hasPlaceholderField v) secretsWithPath;
|
||||||
|
|
||||||
|
genLoadCredentials = secret: "${concatStringsSep "_" secret.path}:${secret.${sourceField}}";
|
||||||
|
in
|
||||||
|
map genLoadCredentials allSecrets;
|
||||||
|
|
||||||
|
anyNotNull = any (x: x != null);
|
||||||
|
|
||||||
|
mkJellyfinPlugin =
|
||||||
|
{
|
||||||
|
pname,
|
||||||
|
version,
|
||||||
|
hash,
|
||||||
|
url,
|
||||||
|
}:
|
||||||
|
pkgs.callPackage (
|
||||||
|
{ stdenv, fetchzip }:
|
||||||
|
stdenv.mkDerivation (finalAttrs: {
|
||||||
|
inherit pname version;
|
||||||
|
|
||||||
|
src = fetchzip {
|
||||||
|
inherit url hash;
|
||||||
|
stripRoot = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
dontBuild = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir $out
|
||||||
|
cp -r . $out
|
||||||
|
'';
|
||||||
|
})
|
||||||
) { };
|
) { };
|
||||||
in
|
|
||||||
builtins.fromJSON (builtins.readFile xmlToJsonFile);
|
|
||||||
|
|
||||||
renameAttrName =
|
update =
|
||||||
attrset: from: to:
|
attr: fn: attrset:
|
||||||
(lib.attrsets.filterAttrs (name: v: name == from) attrset)
|
attrset // { ${attr} = fn attrset.${attr}; };
|
||||||
// {
|
|
||||||
${to} = attrset.${from};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Taken from https://github.com/antifuchs/nix-flake-tests/blob/main/default.nix
|
};
|
||||||
# with a nicer diff display function.
|
in
|
||||||
check =
|
shb
|
||||||
{ pkgs, tests }:
|
// {
|
||||||
let
|
homepage = pkgs.callPackage ./homepage.nix { inherit shb; };
|
||||||
formatValue =
|
|
||||||
val:
|
|
||||||
if (builtins.isList val || builtins.isAttrs val) then
|
|
||||||
builtins.toJSON val
|
|
||||||
else
|
|
||||||
builtins.toString val;
|
|
||||||
|
|
||||||
resultToString =
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
expected,
|
|
||||||
result,
|
|
||||||
}:
|
|
||||||
builtins.readFile (
|
|
||||||
pkgs.runCommand "nix-flake-tests-error"
|
|
||||||
{
|
|
||||||
expected = formatValue expected;
|
|
||||||
result = formatValue result;
|
|
||||||
passAsFile = [
|
|
||||||
"expected"
|
|
||||||
"result"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
''
|
|
||||||
echo "${name} failed (- expected, + result)" > $out
|
|
||||||
cp ''${expectedPath} ''${expectedPath}.json
|
|
||||||
cp ''${resultPath} ''${resultPath}.json
|
|
||||||
${pkgs.deepdiff}/bin/deep diff ''${expectedPath}.json ''${resultPath}.json >> $out
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
results = pkgs.lib.runTests tests;
|
|
||||||
in
|
|
||||||
if results != [ ] then
|
|
||||||
builtins.throw (concatStringsSep "\n" (map resultToString (lib.traceValSeq results)))
|
|
||||||
else
|
|
||||||
pkgs.runCommand "nix-flake-tests-success" { } "echo > $out";
|
|
||||||
|
|
||||||
genConfigOutOfBandSystemd =
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
configLocation,
|
|
||||||
generator,
|
|
||||||
user ? null,
|
|
||||||
permissions ? "u=r,g=r,o=",
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
loadCredentials = getLoadCredentials "source" config;
|
|
||||||
preStart = lib.mkBefore (replaceSecrets {
|
|
||||||
userConfig = updateToLoadCredentials "source" "$CREDENTIALS_DIRECTORY" config;
|
|
||||||
resultPath = configLocation;
|
|
||||||
inherit generator;
|
|
||||||
inherit user permissions;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
updateToLoadCredentials =
|
|
||||||
sourceField: rootDir: attrs:
|
|
||||||
let
|
|
||||||
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
|
||||||
|
|
||||||
valueOrLoadCredential =
|
|
||||||
path: value:
|
|
||||||
if !(hasPlaceholderField value) then
|
|
||||||
value
|
|
||||||
else
|
|
||||||
value // { ${sourceField} = rootDir + "/" + concatStringsSep "_" path; };
|
|
||||||
in
|
|
||||||
mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) valueOrLoadCredential attrs;
|
|
||||||
|
|
||||||
getLoadCredentials =
|
|
||||||
sourceField: attrs:
|
|
||||||
let
|
|
||||||
hasPlaceholderField = v: isAttrs v && hasAttr sourceField v;
|
|
||||||
|
|
||||||
addPathField =
|
|
||||||
path: value: if !(hasPlaceholderField value) then value else value // { inherit path; };
|
|
||||||
|
|
||||||
secretsWithPath = mapAttrsRecursiveCond (v: !(hasPlaceholderField v)) addPathField attrs;
|
|
||||||
|
|
||||||
allSecrets = collect (v: hasPlaceholderField v) secretsWithPath;
|
|
||||||
|
|
||||||
genLoadCredentials = secret: "${concatStringsSep "_" secret.path}:${secret.${sourceField}}";
|
|
||||||
in
|
|
||||||
map genLoadCredentials allSecrets;
|
|
||||||
|
|
||||||
anyNotNull = any (x: x != null);
|
|
||||||
|
|
||||||
mkJellyfinPlugin =
|
|
||||||
{
|
|
||||||
pname,
|
|
||||||
version,
|
|
||||||
hash,
|
|
||||||
url,
|
|
||||||
}:
|
|
||||||
pkgs.callPackage (
|
|
||||||
{ stdenv, fetchzip }:
|
|
||||||
stdenv.mkDerivation (finalAttrs: {
|
|
||||||
inherit pname version;
|
|
||||||
|
|
||||||
src = fetchzip {
|
|
||||||
inherit url hash;
|
|
||||||
stripRoot = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
dontBuild = true;
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir $out
|
|
||||||
cp -r . $out
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
) { };
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
92
lib/homepage.nix
Normal file
92
lib/homepage.nix
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
{ lib, shb }:
|
||||||
|
let
|
||||||
|
sort =
|
||||||
|
attr: vs:
|
||||||
|
map (v: { ${v.name} = v.${attr}; }) (
|
||||||
|
lib.sortOn (v: v.sortOrder) (lib.mapAttrsToList (n: v: v // { name = n; }) vs)
|
||||||
|
);
|
||||||
|
|
||||||
|
slufigy = builtins.replaceStrings [ "-" ] [ "_" ];
|
||||||
|
|
||||||
|
mkService =
|
||||||
|
groupName: serviceName:
|
||||||
|
{
|
||||||
|
request,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
apiKey: settings:
|
||||||
|
lib.recursiveUpdate (
|
||||||
|
{
|
||||||
|
href = request.externalUrl;
|
||||||
|
siteMonitor = if (request.internalUrl == null) then null else request.internalUrl;
|
||||||
|
icon = "sh-${lib.toLower serviceName}";
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (apiKey != null) {
|
||||||
|
widget = {
|
||||||
|
# Duplicating because widgets call the api key various names
|
||||||
|
# and duplicating is a hacky but easy solution.
|
||||||
|
key = "{{HOMEPAGE_FILE_${slufigy groupName}_${slufigy serviceName}}}";
|
||||||
|
password = "{{HOMEPAGE_FILE_${slufigy groupName}_${slufigy serviceName}}}";
|
||||||
|
type = lib.toLower serviceName;
|
||||||
|
url = if (request.internalUrl != null) then request.internalUrl else request.externalUrl;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) settings;
|
||||||
|
|
||||||
|
asServiceGroup =
|
||||||
|
cfg:
|
||||||
|
sort "services" (
|
||||||
|
lib.mapAttrs (
|
||||||
|
groupName: groupCfg:
|
||||||
|
shb.update "services" (
|
||||||
|
services:
|
||||||
|
sort "dashboard" (
|
||||||
|
lib.mapAttrs (
|
||||||
|
serviceName: serviceCfg:
|
||||||
|
shb.update "dashboard" (
|
||||||
|
dashboard:
|
||||||
|
(mkService groupName serviceName) dashboard serviceCfg.apiKey (serviceCfg.settings or { })
|
||||||
|
) serviceCfg
|
||||||
|
) services
|
||||||
|
)
|
||||||
|
) groupCfg
|
||||||
|
) cfg
|
||||||
|
);
|
||||||
|
|
||||||
|
allKeys =
|
||||||
|
cfg:
|
||||||
|
let
|
||||||
|
flat = lib.flatten (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
groupName: groupCfg:
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
serviceName: serviceCfg:
|
||||||
|
lib.optionalAttrs (serviceCfg.apiKey != null) {
|
||||||
|
inherit serviceName groupName;
|
||||||
|
inherit (serviceCfg.apiKey.result) path;
|
||||||
|
}
|
||||||
|
) groupCfg.services
|
||||||
|
) cfg
|
||||||
|
);
|
||||||
|
|
||||||
|
flatWithApiKey = builtins.filter (v: v != { }) flat;
|
||||||
|
in
|
||||||
|
builtins.listToAttrs (
|
||||||
|
map (
|
||||||
|
{
|
||||||
|
groupName,
|
||||||
|
serviceName,
|
||||||
|
path,
|
||||||
|
}:
|
||||||
|
lib.nameValuePair "${slufigy groupName}_${slufigy serviceName}" path
|
||||||
|
) flatWithApiKey
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit
|
||||||
|
allKeys
|
||||||
|
asServiceGroup
|
||||||
|
mkService
|
||||||
|
sort
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
@ -390,6 +390,20 @@ in
|
||||||
to see exactly what Authelia receives and sends back.
|
to see exactly what Authelia receives and sends back.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.authelia.subdomain}.\${config.shb.authelia.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString listenPort}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,13 @@ as well as some extensive [troubleshooting](#blocks-authelia-troubleshooting) fe
|
||||||
|
|
||||||
Note that forward authentication is configured with the [nginx block](blocks-nginx.html#blocks-nginx-usage-shbforwardauth).
|
Note that forward authentication is configured with the [nginx block](blocks-nginx.html#blocks-nginx-usage-shbforwardauth).
|
||||||
|
|
||||||
## Global Setup {#blocks-authelia-global-setup}
|
## Features {#services-authelia-features}
|
||||||
|
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-authelia-usage-applicationdashboard)
|
||||||
|
|
||||||
|
## Usage {#services-authelia-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#blocks-authelia-usage-configuration}
|
||||||
|
|
||||||
Authelia cannot work without SSL and LDAP.
|
Authelia cannot work without SSL and LDAP.
|
||||||
So setting up the Authelia block requires to setup the [SSL block][] first
|
So setting up the Authelia block requires to setup the [SSL block][] first
|
||||||
|
|
@ -80,6 +86,22 @@ Crucially, the `shb.authelia.secrets.ldapAdminPasswordFile` must be the same
|
||||||
as the `shb.lldap.ldapUserPassword` defined for the [LLDAP block][].
|
as the `shb.lldap.ldapUserPassword` defined for the [LLDAP block][].
|
||||||
This is done using Sops' `key` option.
|
This is done using Sops' `key` option.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-authelia-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#blocks-authelia-options-shb.authelia.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Admin.services.Authelia = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = config.shb.authelia.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## SHB OIDC integration {#blocks-authelia-shb-oidc}
|
## SHB OIDC integration {#blocks-authelia-shb-oidc}
|
||||||
|
|
||||||
For services [provided by SelfHostBlocks][services] that handle [OIDC integration][OIDC],
|
For services [provided by SelfHostBlocks][services] that handle [OIDC integration][OIDC],
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,20 @@ in
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.lldap.subdomain}.\${config.shb.lldap.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.webUIListenPort}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,13 @@ across services.
|
||||||
|
|
||||||
[LLDAP]: https://github.com/lldap/lldap
|
[LLDAP]: https://github.com/lldap/lldap
|
||||||
|
|
||||||
## Global Setup {#blocks-lldap-global-setup}
|
## Features {#blocks-lldap-features}
|
||||||
|
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#blocks-lldap-usage-applicationdashboard)
|
||||||
|
|
||||||
|
## Usage {#blocks-lldap-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#blocks-lldap-usage-configuration}
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
shb.lldap = {
|
shb.lldap = {
|
||||||
|
|
@ -29,7 +35,7 @@ shb.sops.secret."lldap/user_password".request = config.shb.lldap.ldapUserPasswor
|
||||||
This assumes secrets are setup with SOPS
|
This assumes secrets are setup with SOPS
|
||||||
as mentioned in [the secrets setup section](usage.html#usage-secrets) of the manual.
|
as mentioned in [the secrets setup section](usage.html#usage-secrets) of the manual.
|
||||||
|
|
||||||
## SSL {#blocks-lldap-ssl}
|
### SSL {#blocks-lldap-usage-ssl}
|
||||||
|
|
||||||
Using SSL is an important security practice, like always.
|
Using SSL is an important security practice, like always.
|
||||||
Using the [SSL block][], the configuration to add to the one above is:
|
Using the [SSL block][], the configuration to add to the one above is:
|
||||||
|
|
@ -44,7 +50,7 @@ shb.certs.certs.letsencrypt.${domain}.extraDomains = [
|
||||||
shb.lldap.ssl = config.shb.certs.certs.letsencrypt.${config.shb.lldap.domain};
|
shb.lldap.ssl = config.shb.certs.certs.letsencrypt.${config.shb.lldap.domain};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Restrict Access By IP {#blocks-lldap-restrict-access-by-ip}
|
### Restrict Access By IP {#blocks-lldap-usage-restrict-access-by-ip}
|
||||||
|
|
||||||
For added security, you can restrict access to the LLDAP UI
|
For added security, you can restrict access to the LLDAP UI
|
||||||
by adding the following line:
|
by adding the following line:
|
||||||
|
|
@ -53,6 +59,22 @@ by adding the following line:
|
||||||
shb.lldap.restrictAccessIPRange = "192.168.50.0/24";
|
shb.lldap.restrictAccessIPRange = "192.168.50.0/24";
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Application Dashboard {#blocks-lldap-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#blocks-lldap-options-shb.lldap.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Admin.services.LLDAP = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = config.shb.lldap.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Manage Groups {#blocks-lldap-manage-groups}
|
## Manage Groups {#blocks-lldap-manage-groups}
|
||||||
|
|
||||||
The following snippet will create group named "family" if it does not exist yet.
|
The following snippet will create group named "family" if it does not exist yet.
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,21 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.monitoring.subdomain}.\${config.shb.monitoring.domain}";
|
||||||
|
internalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
internalUrlText = "https://\${config.shb.monitoring.subdomain}.\${config.shb.monitoring.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,12 @@ This block sets up the monitoring stack for Self Host Blocks. It is composed of:
|
||||||
- Registration is enabled through SSO.
|
- Registration is enabled through SSO.
|
||||||
- Access through [subdomain](#blocks-monitoring-options-shb.monitoring.subdomain) using reverse proxy.
|
- Access through [subdomain](#blocks-monitoring-options-shb.monitoring.subdomain) using reverse proxy.
|
||||||
- Access through [HTTPS](#blocks-monitoring-options-shb.monitoring.ssl) using reverse proxy.
|
- Access through [HTTPS](#blocks-monitoring-options-shb.monitoring.ssl) using reverse proxy.
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#blocks-monitoring-usage-applicationdashboard)
|
||||||
|
|
||||||
## Usage {#blocks-monitoring-usage}
|
## Usage {#blocks-monitoring-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#blocks-monitoring-usage-configuration}
|
||||||
|
|
||||||
The following snippet assumes a few blocks have been setup already:
|
The following snippet assumes a few blocks have been setup already:
|
||||||
|
|
||||||
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
||||||
|
|
@ -110,6 +113,22 @@ You might for example want to update the metrics retention time with:
|
||||||
services.prometheus.retentionTime = "60d";
|
services.prometheus.retentionTime = "60d";
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Application Dashboard {#blocks-monitoring-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#blocks-monitoring-options-shb.monitoring.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Admin.services.Grafana = {
|
||||||
|
sortOrder = 10;
|
||||||
|
dashboard.request = config.shb.monitoring.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Provisioning {#blocks-monitoring-provisioning}
|
## Provisioning {#blocks-monitoring-provisioning}
|
||||||
|
|
||||||
Self Host Blocks will create automatically the following resources:
|
Self Host Blocks will create automatically the following resources:
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ Here is an example module defining such a `backup` option:
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
myservice.backup = mkOption {
|
myservice.backup = mkOption {
|
||||||
type = contracts.backup.request;
|
type = lib.types.submodule {
|
||||||
|
options = contracts.backup.request;
|
||||||
|
};
|
||||||
default = {
|
default = {
|
||||||
user = "myservice";
|
user = "myservice";
|
||||||
sourceDirectories = [
|
sourceDirectories = [
|
||||||
|
|
|
||||||
77
modules/contracts/dashboard.nix
Normal file
77
modules/contracts/dashboard.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption;
|
||||||
|
inherit (lib.types)
|
||||||
|
nullOr
|
||||||
|
submodule
|
||||||
|
str
|
||||||
|
;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
mkRequest =
|
||||||
|
{
|
||||||
|
serviceName ? "",
|
||||||
|
externalUrl ? "",
|
||||||
|
externalUrlText ? null,
|
||||||
|
internalUrl ? null,
|
||||||
|
internalUrlText ? null,
|
||||||
|
apiKey ? null,
|
||||||
|
}:
|
||||||
|
mkOption {
|
||||||
|
description = ''
|
||||||
|
Request part of the dashboard contract.
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = submodule {
|
||||||
|
options = {
|
||||||
|
externalUrl =
|
||||||
|
mkOption {
|
||||||
|
description = ''
|
||||||
|
URL at which the service can be accessed.
|
||||||
|
|
||||||
|
This URL should go through the reverse proxy.
|
||||||
|
'';
|
||||||
|
type = str;
|
||||||
|
default = externalUrl;
|
||||||
|
example = "https://jellyfin.example.com";
|
||||||
|
}
|
||||||
|
// (lib.optionalAttrs (externalUrlText != null) {
|
||||||
|
defaultText = externalUrlText;
|
||||||
|
});
|
||||||
|
|
||||||
|
internalUrl =
|
||||||
|
mkOption {
|
||||||
|
description = ''
|
||||||
|
URL at which the service can be accessed directly.
|
||||||
|
|
||||||
|
This URL should bypass the reverse proxy.
|
||||||
|
It can be used for example to ping the service
|
||||||
|
and making sure it is up and running correctly.
|
||||||
|
'';
|
||||||
|
type = nullOr str;
|
||||||
|
default = internalUrl;
|
||||||
|
example = "http://127.0.0.1:8081";
|
||||||
|
}
|
||||||
|
// (lib.optionalAttrs (internalUrlText != null) {
|
||||||
|
defaultText = internalUrlText;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkResult =
|
||||||
|
{
|
||||||
|
}:
|
||||||
|
mkOption {
|
||||||
|
description = ''
|
||||||
|
Result part of the dashboard contract.
|
||||||
|
|
||||||
|
No option is provided here.
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = submodule {
|
||||||
|
options = {
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
65
modules/contracts/dashboard/docs/default.md
Normal file
65
modules/contracts/dashboard/docs/default.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Dashboard Contract {#contract-dashboard}
|
||||||
|
|
||||||
|
This NixOS contract is used for user-facing services
|
||||||
|
that want to be displayed on a dashboard.
|
||||||
|
|
||||||
|
It is a contract between a service that can be accessed through an URL
|
||||||
|
and a service that wants to show a list of those services.
|
||||||
|
|
||||||
|
## Providers {#contract-dashboard-providers}
|
||||||
|
|
||||||
|
The providers of this contract in SHB are:
|
||||||
|
|
||||||
|
<!-- Somehow generate this list -->
|
||||||
|
|
||||||
|
- The homepage service under its [shb.homepage.servicesGroups.<name>.services.<name>.dashboard](#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.dashboard) option.
|
||||||
|
|
||||||
|
## Usage {#contracts-dashboard-usage}
|
||||||
|
|
||||||
|
A service that can be shown on a dashboard will provide a `dashboard` option.
|
||||||
|
|
||||||
|
Here is an example module defining such a requester option for this dashboard contract:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
myservice.dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${config.myservice.subdomain}.${config.myservice.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${config.myservice.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, plug both consumer and provider together in the `config`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
<provider-module> = {
|
||||||
|
dashboard.request = config.myservice.dashboard.request;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And that's it for the contract part.
|
||||||
|
For more specific details on each provider, go to their respective manual pages.
|
||||||
|
|
||||||
|
## Contract Reference {#contract-dashboard-options}
|
||||||
|
|
||||||
|
These are all the options that are expected to exist for this contract to be respected.
|
||||||
|
|
||||||
|
```{=include=} options
|
||||||
|
id-prefix: contracts-dashboard-options-
|
||||||
|
list-id: selfhostblocks-options
|
||||||
|
source: @OPTIONS_JSON@
|
||||||
|
```
|
||||||
30
modules/contracts/dashboard/dummyModule.nix
Normal file
30
modules/contracts/dashboard/dummyModule.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{ lib, shb, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption;
|
||||||
|
inherit (lib.types) submodule;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../../../lib/module.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
options.shb.contracts.dashboard = mkOption {
|
||||||
|
description = ''
|
||||||
|
Contract for user-facing services that want to
|
||||||
|
be displayed on a dashboard.
|
||||||
|
|
||||||
|
The requester communicates to the provider
|
||||||
|
how to access the service
|
||||||
|
through the `request` options.
|
||||||
|
|
||||||
|
The provider reads from the `request` options
|
||||||
|
and configures what is necessary on its side
|
||||||
|
to show the service and check its availability.
|
||||||
|
It does not communicate back to the requester.
|
||||||
|
'';
|
||||||
|
|
||||||
|
type = submodule {
|
||||||
|
options = shb.contracts.dashboard.contract;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -48,21 +48,28 @@ let
|
||||||
importContract =
|
importContract =
|
||||||
module:
|
module:
|
||||||
let
|
let
|
||||||
importedModule = pkgs.callPackage module { inherit shb; };
|
importedModule = pkgs.callPackage module {
|
||||||
|
shb = shb // {
|
||||||
|
inherit contracts;
|
||||||
|
};
|
||||||
|
};
|
||||||
in
|
in
|
||||||
mkContractFunctions {
|
mkContractFunctions {
|
||||||
inherit (importedModule) mkRequest mkResult;
|
inherit (importedModule) mkRequest mkResult;
|
||||||
};
|
};
|
||||||
in
|
|
||||||
{
|
contracts = {
|
||||||
databasebackup = importContract ./databasebackup.nix;
|
databasebackup = importContract ./databasebackup.nix;
|
||||||
backup = importContract ./backup.nix;
|
dashboard = importContract ./dashboard.nix;
|
||||||
mount = pkgs.callPackage ./mount.nix { };
|
backup = importContract ./backup.nix;
|
||||||
secret = importContract ./secret.nix;
|
mount = pkgs.callPackage ./mount.nix { };
|
||||||
ssl = pkgs.callPackage ./ssl.nix { };
|
secret = importContract ./secret.nix;
|
||||||
test = {
|
ssl = pkgs.callPackage ./ssl.nix { };
|
||||||
secret = pkgs.callPackage ./secret/test.nix { inherit shb; };
|
test = {
|
||||||
databasebackup = pkgs.callPackage ./databasebackup/test.nix { inherit shb; };
|
secret = pkgs.callPackage ./secret/test.nix { inherit shb; };
|
||||||
backup = pkgs.callPackage ./backup/test.nix { inherit shb; };
|
databasebackup = pkgs.callPackage ./databasebackup/test.nix { inherit shb; };
|
||||||
|
backup = pkgs.callPackage ./backup/test.nix { inherit shb; };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
in
|
||||||
|
contracts
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,20 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.${name}.subdomain}.${cfg.${name}.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.arr.${name}.subdomain}.\${config.shb.arr.${name}.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.${name}.settings.Port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// (c.moreOptions or { });
|
// (c.moreOptions or { });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,242 @@
|
||||||
Defined in [`/modules/services/arr.nix`](@REPO@/modules/services/arr.nix).
|
Defined in [`/modules/services/arr.nix`](@REPO@/modules/services/arr.nix).
|
||||||
|
|
||||||
This NixOS module sets up multiple [Servarr](https://wiki.servarr.com/) services.
|
This NixOS module sets up multiple [Servarr](https://wiki.servarr.com/) services.
|
||||||
|
## Features {#services-arr-features}
|
||||||
|
|
||||||
Compared to the stock module from nixpkgs,
|
Compared to the stock module from nixpkgs,
|
||||||
this one sets up, in a fully declarative manner
|
this one sets up, in a fully declarative manner
|
||||||
LDAP and SSO integration as well as the API key.
|
LDAP and SSO integration as well as the API key.
|
||||||
|
|
||||||
This manual page is under construction.
|
## Usage {#services-arr-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#services-arr-usage-configuration}
|
||||||
|
|
||||||
|
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.certs.certs.letsencrypt.${domain}.extraDomains = [
|
||||||
|
"moviesdl.${domain}"
|
||||||
|
"seriesdl.${domain}"
|
||||||
|
"subtitlesdl.${domain}"
|
||||||
|
"booksdl.${domain}"
|
||||||
|
"musicdl.${domain}"
|
||||||
|
"indexer.${domain}"
|
||||||
|
];
|
||||||
|
|
||||||
|
shb.arr = {
|
||||||
|
radarr = {
|
||||||
|
inherit domain;
|
||||||
|
enable = true;
|
||||||
|
ssl = config.shb.certs.certs.letsencrypt.${domain};
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
sonarr = {
|
||||||
|
inherit domain;
|
||||||
|
enable = true;
|
||||||
|
ssl = config.shb.certs.certs.letsencrypt."${domain}";
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
bazarr = {
|
||||||
|
inherit domain;
|
||||||
|
enable = true;
|
||||||
|
ssl = config.shb.certs.certs.letsencrypt."${domain}";
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
readarr = {
|
||||||
|
inherit domain;
|
||||||
|
enable = true;
|
||||||
|
ssl = config.shb.certs.certs.letsencrypt."${domain}";
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
lidarr = {
|
||||||
|
inherit domain;
|
||||||
|
enable = true;
|
||||||
|
ssl = config.shb.certs.certs.letsencrypt."${domain}";
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
jackett = {
|
||||||
|
inherit domain;
|
||||||
|
enable = true;
|
||||||
|
ssl = config.shb.certs.certs.letsencrypt."${domain}";
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The user and admin LDAP groups are created automatically.
|
||||||
|
|
||||||
|
### API Keys {#services-arr-usage-apikeys}
|
||||||
|
|
||||||
|
The API keys for each arr service can be created declaratively.
|
||||||
|
|
||||||
|
First, generate one secret for each service with `nix run nixpkgs#openssl -- rand -hex 64`
|
||||||
|
and store it in your secrets file (for example the SOPS file).
|
||||||
|
|
||||||
|
Then, add the API key to each service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.arr = {
|
||||||
|
radarr = {
|
||||||
|
settings = {
|
||||||
|
ApiKey.source = config.shb.sops.secret."radarr/apikey".result.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
sonarr = {
|
||||||
|
settings = {
|
||||||
|
ApiKey.source = config.shb.sops.secret."sonarr/apikey".result.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
bazarr = {
|
||||||
|
settings = {
|
||||||
|
ApiKey.source = config.shb.sops.secret."bazarr/apikey".result.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
readarr = {
|
||||||
|
settings = {
|
||||||
|
ApiKey.source = config.shb.sops.secret."readarr/apikey".result.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
lidarr = {
|
||||||
|
settings = {
|
||||||
|
ApiKey.source = config.shb.sops.secret."lidarr/apikey".result.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
jackett = {
|
||||||
|
settings = {
|
||||||
|
ApiKey.source = config.shb.sops.secret."jackett/apikey".result.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.sops.secret."radarr/apikey".request = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "radarr";
|
||||||
|
group = "radarr";
|
||||||
|
restartUnits = [ "radarr.service" ];
|
||||||
|
};
|
||||||
|
shb.sops.secret."sonarr/apikey".request = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "sonarr";
|
||||||
|
group = "sonarr";
|
||||||
|
restartUnits = [ "sonarr.service" ];
|
||||||
|
};
|
||||||
|
shb.sops.secret."bazarr/apikey".request = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "bazarr";
|
||||||
|
group = "bazarr";
|
||||||
|
restartUnits = [ "bazarr.service" ];
|
||||||
|
};
|
||||||
|
shb.sops.secret."readarr/apikey".request = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "readarr";
|
||||||
|
group = "readarr";
|
||||||
|
restartUnits = [ "readarr.service" ];
|
||||||
|
};
|
||||||
|
shb.sops.secret."lidarr/apikey".request = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "lidarr";
|
||||||
|
group = "lidarr";
|
||||||
|
restartUnits = [ "lidarr.service" ];
|
||||||
|
};
|
||||||
|
shb.sops.secret."jackett/apikey".request = {
|
||||||
|
mode = "0440";
|
||||||
|
owner = "jackett";
|
||||||
|
group = "jackett";
|
||||||
|
restartUnits = [ "jackett.service" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Application Dashboard {#services-arr-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the various dashboard options.
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Radarr = {
|
||||||
|
sortOrder = 10;
|
||||||
|
dashboard.request = config.shb.arr.radarr.dashboard.request;
|
||||||
|
apiKey.result = config.shb.sops.secret."radarr/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
shb.sops.secret."radarr/homepageApiKey" = {
|
||||||
|
settings.key = "radarr/apikey";
|
||||||
|
request = config.shb.homepage.servicesGroups.Media.services.Radarr.apiKey.request;
|
||||||
|
};
|
||||||
|
shb.homepage.servicesGroups.Media.services.Sonarr = {
|
||||||
|
sortOrder = 11;
|
||||||
|
dashboard.request = config.shb.arr.sonarr.dashboard.request;
|
||||||
|
apiKey.result = config.shb.sops.secret."sonarr/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
shb.sops.secret."sonarr/homepageApiKey" = {
|
||||||
|
settings.key = "sonarr/apikey";
|
||||||
|
request = config.shb.homepage.servicesGroups.Media.services.Sonarr.apiKey.request;
|
||||||
|
};
|
||||||
|
shb.homepage.servicesGroups.Media.services.Bazarr = {
|
||||||
|
sortOrder = 12;
|
||||||
|
dashboard.request = config.shb.arr.bazarr.dashboard.request;
|
||||||
|
apiKey.result = config.shb.sops.secret."bazarr/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
shb.sops.secret."bazarr/homepageApiKey" = {
|
||||||
|
settings.key = "bazarr/apikey";
|
||||||
|
request = config.shb.homepage.servicesGroups.Media.services.Bazarr.apiKey.request;
|
||||||
|
};
|
||||||
|
shb.homepage.servicesGroups.Media.services.Readarr = {
|
||||||
|
sortOrder = 13;
|
||||||
|
dashboard.request = config.shb.arr.readarr.dashboard.request;
|
||||||
|
apiKey.result = config.shb.sops.secret."readarr/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
shb.sops.secret."readarr/homepageApiKey" = {
|
||||||
|
settings.key = "readarr/apikey";
|
||||||
|
request = config.shb.homepage.servicesGroups.Media.services.Readarr.apiKey.request;
|
||||||
|
};
|
||||||
|
shb.homepage.servicesGroups.Media.services.Lidarr = {
|
||||||
|
sortOrder = 14;
|
||||||
|
dashboard.request = config.shb.arr.lidarr.dashboard.request;
|
||||||
|
apiKey.result = config.shb.sops.secret."lidarr/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
shb.sops.secret."lidarr/homepageApiKey" = {
|
||||||
|
settings.key = "lidarr/apikey";
|
||||||
|
request = config.shb.homepage.servicesGroups.Media.services.Lidarr.apiKey.request;
|
||||||
|
};
|
||||||
|
shb.homepage.servicesGroups.Media.services.Jackett = {
|
||||||
|
sortOrder = 15;
|
||||||
|
dashboard.request = config.shb.arr.jackett.dashboard.request;
|
||||||
|
apiKey.result = config.shb.sops.secret."jackett/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
shb.sops.secret."jackett/homepageApiKey" = {
|
||||||
|
settings.key = "jackett/apikey";
|
||||||
|
request = config.shb.homepage.servicesGroups.Media.services.Jackett.apiKey.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This example reuses the API keys generated declaratively from the previous section.
|
||||||
|
|
||||||
|
### Jackett Proxy {#services-arr-usage-jackett-proxy}
|
||||||
|
|
||||||
|
The Jackett service can be made to use a proxy with:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.arr.jackett = {
|
||||||
|
settings = {
|
||||||
|
ProxyType = "0";
|
||||||
|
ProxyUrl = "127.0.0.1:1234";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## Options Reference {#services-arr-options}
|
## Options Reference {#services-arr-options}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,20 @@ in
|
||||||
default = false;
|
default = false;
|
||||||
example = true;
|
example = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.audiobookshelf.subdomain}.\${config.shb.audiobookshelf.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.webPort}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (
|
config = lib.mkIf cfg.enable (
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ in
|
||||||
options.shb.deluge = {
|
options.shb.deluge = {
|
||||||
enable = lib.mkEnableOption "the SHB Deluge service";
|
enable = lib.mkEnableOption "the SHB Deluge service";
|
||||||
|
|
||||||
enableDashboard = lib.mkEnableOption "the Torrents SHB dashboard" // {
|
enableDashboard = lib.mkEnableOption "the Torrents SHB monitoring dashboard" // {
|
||||||
default = true;
|
default = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -301,6 +301,20 @@ in
|
||||||
default = null;
|
default = null;
|
||||||
example = "info";
|
example = "info";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${fqdn}";
|
||||||
|
externalUrlText = "https://\${config.shb.deluge.subdomain}.\${config.shb.deluge.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.webPort}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (
|
config = lib.mkIf cfg.enable (
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ let
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
../blocks/nginx.nix
|
||||||
|
../blocks/lldap.nix
|
||||||
|
|
||||||
../../lib/module.nix
|
../../lib/module.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -293,6 +296,22 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.firefly-iii.subdomain}.\${config.shb.firefly-iii.domain}";
|
||||||
|
# This works thanks to the Personal Access Token.
|
||||||
|
internalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
internalUrlText = "https://\${config.shb.firefly-iii.subdomain}.\${config.shb.firefly-iii.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (
|
config = lib.mkIf cfg.enable (
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,14 @@ It also sets up the Firefly-iii data importer service
|
||||||
and nearly automatically links it to the Firefly-iii instance using a Personal Account Token.
|
and nearly automatically links it to the Firefly-iii instance using a Personal Account Token.
|
||||||
Instructions on how to do so is given in the next section.
|
Instructions on how to do so is given in the next section.
|
||||||
|
|
||||||
|
## Features {#services-firefly-iii-features}
|
||||||
|
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-firefly-iii-usage-applicationdashboard)
|
||||||
|
|
||||||
## Usage {#services-firefly-iii-usage}
|
## Usage {#services-firefly-iii-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#services-firefly-iii-usage-configuration}
|
||||||
|
|
||||||
The following snippet assumes a few blocks have been setup already:
|
The following snippet assumes a few blocks have been setup already:
|
||||||
|
|
||||||
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
||||||
|
|
@ -127,6 +133,38 @@ shb.lldap.ensureUsers.USERNAME.groups = [
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Application Dashboard {#services-firefly-iii-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-firefly-iii-options-shb.firefly-iii.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Finance.services.Firefly-iii = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = config.shb.firefly-iii.dashboard.request;
|
||||||
|
settings.widget.type = "firefly";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The widget type needs to be set manually otherwise it is not displayed correctly.
|
||||||
|
|
||||||
|
An API key can be set to show extra info:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Finance.services.Firefly-iii = {
|
||||||
|
apiKey.result = config.shb.sops.secret."firefly-iii/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.sops.secret."firefly-iii/homepageApiKey".request =
|
||||||
|
config.shb.homepage.servicesGroups.Finance.services.Firefly-iii.apiKey.request;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Database Inspection {#services-firefly-iii-database-inspection}
|
## Database Inspection {#services-firefly-iii-database-inspection}
|
||||||
|
|
||||||
Access the database with:
|
Access the database with:
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,21 @@ in
|
||||||
type = bool;
|
type = bool;
|
||||||
default = false;
|
default = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.forgejo.subdomain}.\${config.shb.forgejo.domain}";
|
||||||
|
internalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
internalUrlText = "https://\${config.shb.forgejo.subdomain}.\${config.shb.forgejo.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkMerge [
|
config = mkMerge [
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ LDAP and SSO integration as well as one local runner.
|
||||||
- Access through [subdomain](#services-forgejo-options-shb.forgejo.subdomain) using reverse proxy. [Manual](#services-forgejo-usage-configuration).
|
- Access through [subdomain](#services-forgejo-options-shb.forgejo.subdomain) using reverse proxy. [Manual](#services-forgejo-usage-configuration).
|
||||||
- Access through [HTTPS](#services-forgejo-options-shb.forgejo.ssl) using reverse proxy. [Manual](#services-forgejo-usage-configuration).
|
- Access through [HTTPS](#services-forgejo-options-shb.forgejo.ssl) using reverse proxy. [Manual](#services-forgejo-usage-configuration).
|
||||||
- [Backup](#services-forgejo-options-shb.forgejo.sso) through the [backup block](./blocks-backup.html). [Manual](#services-forgejo-usage-backup).
|
- [Backup](#services-forgejo-options-shb.forgejo.sso) through the [backup block](./blocks-backup.html). [Manual](#services-forgejo-usage-backup).
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-forgejo-usage-applicationdashboard)
|
||||||
|
|
||||||
## Usage {#services-forgejo-usage}
|
## Usage {#services-forgejo-usage}
|
||||||
|
|
||||||
|
|
@ -206,6 +207,22 @@ The name `"forgejo"` in the `instances` can be anything.
|
||||||
The `config.shb.forgejo.backup` option provides what directories to backup.
|
The `config.shb.forgejo.backup` option provides what directories to backup.
|
||||||
You can define any number of Restic instances to backup Forgejo multiple times.
|
You can define any number of Restic instances to backup Forgejo multiple times.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-forgejo-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-forgejo-options-shb.forgejo.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Admin.services.Forgejo = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = config.shb.forgejo.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Extra Settings {#services-forgejo-usage-extra-settings}
|
### Extra Settings {#services-forgejo-usage-extra-settings}
|
||||||
|
|
||||||
Other Forgejo settings can be accessed through the nixpkgs [stock service][].
|
Other Forgejo settings can be accessed through the nixpkgs [stock service][].
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,21 @@ in
|
||||||
default = false;
|
default = false;
|
||||||
example = true;
|
example = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.grocy.subdomain}.\${config.shb.grocy.domain}";
|
||||||
|
internalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
internalUrlText = "https://\${config.shb.grocy.subdomain}.\${config.shb.grocy.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable (
|
config = lib.mkIf cfg.enable (
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,21 @@ in
|
||||||
default = [ "--forecast" ];
|
default = [ "--forecast" ];
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.hledger.subdomain}.\${config.shb.hledger.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString config.services.hledger-web.port}";
|
||||||
|
internalUrlText = "http://127.0.0.1:\${config.services.hledger-web.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,21 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.home-assistant.subdomain}.\${config.shb.home-assistant.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString config.services.home-assistant.config.http.server_port}";
|
||||||
|
internalUrlText = "http://127.0.0.1:\${config.services.home-assistant.config.http.server_port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ LDAP and SSO integration.
|
||||||
- Access through [subdomain](#services-home-assistant-options-shb.home-assistant.subdomain) using reverse proxy. [Manual](#services-home-assistant-usage-configuration).
|
- Access through [subdomain](#services-home-assistant-options-shb.home-assistant.subdomain) using reverse proxy. [Manual](#services-home-assistant-usage-configuration).
|
||||||
- Access through [HTTPS](#services-home-assistant-options-shb.home-assistant.ssl) using reverse proxy. [Manual](#services-home-assistant-usage-configuration).
|
- Access through [HTTPS](#services-home-assistant-options-shb.home-assistant.ssl) using reverse proxy. [Manual](#services-home-assistant-usage-configuration).
|
||||||
- [Backup](#services-home-assistant-options-shb.home-assistant.backup) through the [backup block](./blocks-backup.html). [Manual](#services-home-assistant-usage-backup).
|
- [Backup](#services-home-assistant-options-shb.home-assistant.backup) through the [backup block](./blocks-backup.html). [Manual](#services-home-assistant-usage-backup).
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-home-assistant-usage-applicationdashboard)
|
||||||
|
|
||||||
- Not yet: declarative SSO.
|
- Not yet: declarative SSO.
|
||||||
|
|
||||||
|
|
@ -163,6 +164,57 @@ You can define any number of Restic instances to backup Home-Assistant multiple
|
||||||
You will then need to configure more options like the `repository`,
|
You will then need to configure more options like the `repository`,
|
||||||
as explained in the [restic](blocks-restic.html) documentation.
|
as explained in the [restic](blocks-restic.html) documentation.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-home-assistant-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-home-assistant-options-shb.home-assistant.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Home.services.HomeAssistant = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = config.shb.home-assistant.dashboard.request;
|
||||||
|
settings.icon = "si-homeassistant";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The icon needs to be set manually otherwise it is not displayed correctly.
|
||||||
|
|
||||||
|
An API key can be set to show extra info:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Home.services.HomeAssistant = {
|
||||||
|
apiKey.result = config.shb.sops.secret."home-assistant/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.sops.secret."home-assistant/homepageApiKey".request =
|
||||||
|
config.shb.homepage.servicesGroups.Home.services.HomeAssistant.apiKey.request;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom widgets can be set using Home Assistant templating:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Home.services.HomeAssistant = {
|
||||||
|
settings.widget.custom = [
|
||||||
|
{
|
||||||
|
template = "{{ states('sensor.power_consumption_power_consumption', with_unit=True, rounded=True) }}";
|
||||||
|
label = "energy now";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
state = "sensor.power_consumption_daily_power_consumption";
|
||||||
|
label = "energy today";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Extra Components {#services-home-assistant-usage-extra-components}
|
### Extra Components {#services-home-assistant-usage-extra-components}
|
||||||
|
|
||||||
Packaged components can be found in the documentation of the corresponding option
|
Packaged components can be found in the documentation of the corresponding option
|
||||||
|
|
|
||||||
286
modules/services/homepage.nix
Normal file
286
modules/services/homepage.nix
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
shb,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.shb.homepage;
|
||||||
|
|
||||||
|
inherit (lib) types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../../lib/module.nix
|
||||||
|
|
||||||
|
../blocks/lldap.nix
|
||||||
|
../blocks/nginx.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
options.shb.homepage = {
|
||||||
|
enable = lib.mkEnableOption "the SHB homepage service";
|
||||||
|
|
||||||
|
subdomain = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
Subdomain under which homepage will be served.
|
||||||
|
|
||||||
|
```
|
||||||
|
<subdomain>.<domain>
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
example = "homepage";
|
||||||
|
};
|
||||||
|
|
||||||
|
domain = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Domain under which homepage is served.
|
||||||
|
|
||||||
|
```
|
||||||
|
<subdomain>.<domain>
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
type = types.str;
|
||||||
|
example = "domain.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
ssl = lib.mkOption {
|
||||||
|
description = "Path to SSL files";
|
||||||
|
type = types.nullOr shb.contracts.ssl.certs;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
servicesGroups = lib.mkOption {
|
||||||
|
description = "Group of services that should be showed on the dashboard.";
|
||||||
|
default = { };
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Display name of the group. Defaults to the attr name.";
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
sortOrder = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Order in which groups will be shown.
|
||||||
|
|
||||||
|
The rules are:
|
||||||
|
|
||||||
|
- Lowest number is shown first.
|
||||||
|
- Two groups having the same number are shown in a consistent (same across multiple deploys) but undefined order.
|
||||||
|
- Default is null which means at the end.
|
||||||
|
'';
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
services = lib.mkOption {
|
||||||
|
description = "Services that should be showed in the group on the dashboard.";
|
||||||
|
default = { };
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Display name of the service. Defaults to the attr name.";
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
sortOrder = lib.mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
description = ''
|
||||||
|
Order in which groups will be shown.
|
||||||
|
|
||||||
|
The rules are:
|
||||||
|
|
||||||
|
- Lowest number is shown first.
|
||||||
|
- Two groups having the same number are shown in a consistent (same across multiple deploys) but undefined order.
|
||||||
|
- Default is null which means at the end.
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Provider of the dashboard contract.
|
||||||
|
|
||||||
|
By default:
|
||||||
|
|
||||||
|
- The `serviceName` option comes from the attr name.
|
||||||
|
- The `icon` option comes from applying `toLower` on the attr name.
|
||||||
|
- The `siteMonitor` option is set only if `internalUrl` is set.
|
||||||
|
'';
|
||||||
|
type = types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkProvider {
|
||||||
|
resultCfg = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
apiKey = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
API key used to access the service.
|
||||||
|
|
||||||
|
This can be used to get data from the service.
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = shb.contracts.secret.mkRequester {
|
||||||
|
owner = "root";
|
||||||
|
restartUnits = [ "homepage-dashboard.service" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
settings = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Extra options to pass to the homepage service.
|
||||||
|
|
||||||
|
Check https://gethomepage.dev/configs/services/#icons
|
||||||
|
if the default icon is not correct.
|
||||||
|
|
||||||
|
And check https://gethomepage.dev/widgets
|
||||||
|
if the default widget type is not correct.
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
icon = "si-homeassistant";
|
||||||
|
widget.type = "firefly";
|
||||||
|
widget.custom = [
|
||||||
|
{
|
||||||
|
template = "{{ states('sensor.total_power', with_unit=True, rounded=True) }}";
|
||||||
|
label = "energy now";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
state = "sensor.total_power_today";
|
||||||
|
label = "energy today";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ldap = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Setup LDAP integration.
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
userGroup = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Group users must belong to be able to login.";
|
||||||
|
default = "homepage_user";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sso = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Setup SSO integration.
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkEnableOption "SSO integration.";
|
||||||
|
|
||||||
|
authEndpoint = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Endpoint to the SSO provider.";
|
||||||
|
example = "https://authelia.example.com";
|
||||||
|
};
|
||||||
|
|
||||||
|
authorization_policy = lib.mkOption {
|
||||||
|
type = types.enum [
|
||||||
|
"one_factor"
|
||||||
|
"two_factor"
|
||||||
|
];
|
||||||
|
description = "Require one factor (password) or two factor (device) authentication.";
|
||||||
|
default = "one_factor";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.homepage-dashboard = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
allowedHosts = "${cfg.subdomain}.${cfg.domain}";
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
baseUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
startUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
disableUpdateCheck = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
bookmarks = [ ];
|
||||||
|
|
||||||
|
services = shb.homepage.asServiceGroup cfg.servicesGroups;
|
||||||
|
|
||||||
|
widgets = [ ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.homepage-dashboard.serviceConfig =
|
||||||
|
let
|
||||||
|
keys = shb.homepage.allKeys cfg.servicesGroups;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# LoadCredential = [
|
||||||
|
# "Media_Jellyfin:/path"
|
||||||
|
# ];
|
||||||
|
LoadCredential = lib.mapAttrsToList (name: path: "${name}:${path}") keys;
|
||||||
|
# Environment = [
|
||||||
|
# "HOMEPAGE_FILE_Media_Jellyfin=%d/Media_Jellyfin"
|
||||||
|
# ];
|
||||||
|
Environment = lib.mapAttrsToList (name: path: "HOMEPAGE_FILE_${name}=%d/${name}") keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
# This should be using a contract instead of setting the option directly.
|
||||||
|
shb.lldap = lib.mkIf config.shb.lldap.enable {
|
||||||
|
ensureGroups = {
|
||||||
|
${cfg.ldap.userGroup} = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.nginx.vhosts = [
|
||||||
|
(
|
||||||
|
{
|
||||||
|
inherit (cfg) subdomain domain ssl;
|
||||||
|
|
||||||
|
upstream = "http://127.0.0.1:${toString config.services.homepage-dashboard.listenPort}/";
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_read_timeout 300s;
|
||||||
|
proxy_send_timeout 300s;
|
||||||
|
'';
|
||||||
|
autheliaRules = lib.optionals (cfg.sso.enable) [
|
||||||
|
{
|
||||||
|
domain = "${cfg.subdomain}.${cfg.domain}";
|
||||||
|
policy = cfg.sso.authorization_policy;
|
||||||
|
subject = [ "group:${cfg.ldap.userGroup}" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs cfg.sso.enable {
|
||||||
|
inherit (cfg.sso) authEndpoint;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
modules/services/homepage/docs/Screenshot.png
Normal file
BIN
modules/services/homepage/docs/Screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
191
modules/services/homepage/docs/default.md
Normal file
191
modules/services/homepage/docs/default.md
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
# Homepage Service {#services-homepage}
|
||||||
|
|
||||||
|
Defined in [`/modules/services/homepage.nix`](@REPO@/modules/services/homepage.nix),
|
||||||
|
found in the `selfhostblocks.nixosModules.homepage` module.
|
||||||
|
See [the manual](usage.html#usage-flake) for how to import the module in your code.
|
||||||
|
|
||||||
|
This service sets up [Homepage Dashboard][] which provides
|
||||||
|
a highly customizable homepage Docker and service API integrations.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[Homepage Dashboard]: https://github.com/gethomepage/homepage
|
||||||
|
|
||||||
|
## Features {#services-homepage-features}
|
||||||
|
|
||||||
|
- Declarative SSO login through forward authentication.
|
||||||
|
Only users of the [Homepage LDAP user group][] can access the web UI.
|
||||||
|
This is enforced using the [Authelia block][] which integrates with the LLDAP block.
|
||||||
|
- Access through [subdomain][] using the reverse proxy.
|
||||||
|
It is implemented with the [Nginx block][].
|
||||||
|
- Access through [HTTPS][] using the reverse proxy.
|
||||||
|
It is implemented with the [SSL block][].
|
||||||
|
- Integration with [secrets contract][] to set the API key for a widget.
|
||||||
|
|
||||||
|
[Homepage LDAP user group]: #services-homepage-options-shb.homepage.ldap.userGroup
|
||||||
|
[Authelia block]: blocks-authelia.html
|
||||||
|
[subdomain]: #services-open-webui-options-shb.open-webui.subdomain
|
||||||
|
[HTTPS]: #services-open-webui-options-shb.open-webui.ssl
|
||||||
|
[Nginx block]: blocks-nginx.html
|
||||||
|
[SSL block]: blocks-ssl.html
|
||||||
|
[secrets contract]: contracts-secret.html
|
||||||
|
|
||||||
|
::: {.note}
|
||||||
|
The service does not use state so no backup or impermanence integration is provided.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Usage {#services-homepage-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).
|
||||||
|
|
||||||
|
::: {.note}
|
||||||
|
Part of the configuration is done through the `shb.homepage` option described here
|
||||||
|
and the rest is done through the upstream [`services.homepage-dashboard`][] option.
|
||||||
|
:::
|
||||||
|
|
||||||
|
[`services.homepage-dashboard`]: https://search.nixos.org/options?query=services.homepage-dashboard
|
||||||
|
|
||||||
|
### Main service configuration {#services-homepage-usage-main}
|
||||||
|
|
||||||
|
This part sets up the web UI and its integration with the other SHB services.
|
||||||
|
It also creates the various service groups which will hold each service.
|
||||||
|
The names are arbitrary and you can order them as you wish through the `sortOrder` option.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.certs.certs.letsencrypt.${domain}.extraDomains = [
|
||||||
|
"${config.shb.homepage.subdomain}.${config.shb.homepage.domain}"
|
||||||
|
];
|
||||||
|
shb.homepage = {
|
||||||
|
enable = true;
|
||||||
|
subdomain = "home";
|
||||||
|
inherit domain;
|
||||||
|
ssl = config.shb.certs.certs.letsencrypt.${domain};
|
||||||
|
|
||||||
|
sso = {
|
||||||
|
enable = true;
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
|
||||||
|
servicesGroups = {
|
||||||
|
Home.sortOrder = 1;
|
||||||
|
Documents.sortOrder = 2;
|
||||||
|
Finance.sortOrder = 3;
|
||||||
|
Media.sortOrder = 4;
|
||||||
|
Admin.sortOrder = 5;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.homepage-dashboard = {
|
||||||
|
settings = {
|
||||||
|
statusStyle = "dot";
|
||||||
|
disableIndexing = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
widgets = [
|
||||||
|
{
|
||||||
|
locale = "fr";
|
||||||
|
format = {
|
||||||
|
dateStyle = "long";
|
||||||
|
timeStyle = "long";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The [Homepage LDAP user group][] is created automatically and users can be added declaratively to the group with:.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.lldap.ensureUsers.${user}.groups = [
|
||||||
|
config.shb.homepage.ldap.userGroup
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Display SHB service {#services-homepage-usage-service}
|
||||||
|
|
||||||
|
A service consumer of the dashboard contract provides a `dashboard` option that can be used like so:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Jellyfin = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = config.shb.jellyfin.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By default:
|
||||||
|
|
||||||
|
- The `serviceName` option comes from the attr name, here `Jellyfin`.
|
||||||
|
- The `icon` option comes from applying `toLower` on the attr name.
|
||||||
|
- The `siteMonitor` option is set only if `internalUrl` is set.
|
||||||
|
|
||||||
|
They can be overridden by setting them in the [settings](#services-homepage-options-shb.homepage.servicesGroups._name_.services._name_.settings option) (see option documentation for examples):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Jellyfin = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = config.shb.<service>.dashboard.request;
|
||||||
|
settings = {
|
||||||
|
// custom options here.
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`.
|
||||||
|
|
||||||
|
### Display custom service {#services-homepage-usage-custom}
|
||||||
|
|
||||||
|
To display a service that does not provide a `dashboard` option like in the previous section, set the values of the request manually:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Jellyfin = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://jellyfin.example.com";
|
||||||
|
internalUrl = "http://127.0.0.1:8081";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add API key for widget {#services-homepage-usage-widget}
|
||||||
|
|
||||||
|
For services [supporting a widget](https://gethomepage.dev/widgets/),
|
||||||
|
create an API key through the service's web UI if available
|
||||||
|
then store it securely (using SOPS for example) and provide it through the
|
||||||
|
`apiKey` option:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Jellyfin = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = config.shb.jellyfin.dashboard.request;
|
||||||
|
apiKey.result = config.shb.sops.secret."jellyfin/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
shb.sops.secret."jellyfin/homepageApiKey".request =
|
||||||
|
config.shb.homepage.servicesGroups.Media.services.Jellyfin.apiKey.request;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unfortunately creating API keys declaratively is rarely supported by upstream services.
|
||||||
|
|
||||||
|
## Options Reference {#services-homepage-options}
|
||||||
|
|
||||||
|
```{=include=} options
|
||||||
|
id-prefix: services-homepage-options-
|
||||||
|
list-id: selfhostblocks-service-homepage-options
|
||||||
|
source: @OPTIONS_JSON@
|
||||||
|
```
|
||||||
|
|
@ -465,6 +465,20 @@ in
|
||||||
default = false;
|
default = false;
|
||||||
example = true;
|
example = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${fqdn}";
|
||||||
|
externalUrlText = "https://\${config.shb.immich.subdomain}.\${config.shb.immich.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,20 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${fqdn}";
|
||||||
|
externalUrlText = "https://\${config.shb.jellyfin.subdomain}.\${config.shb.jellyfin.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,12 @@ this one sets up, in a fully declarative manner:
|
||||||
- Declarative [LDAP](#services-jellyfin-options-shb.jellyfin.ldap) configuration.
|
- Declarative [LDAP](#services-jellyfin-options-shb.jellyfin.ldap) configuration.
|
||||||
- Declarative [SSO](#services-jellyfin-options-shb.jellyfin.sso) configuration.
|
- Declarative [SSO](#services-jellyfin-options-shb.jellyfin.sso) configuration.
|
||||||
- [Backup](#services-jellyfin-options-shb.jellyfin.backup) through the [backup block](./blocks-backup.html). [Manual](#services-jellyfin-usage-backup).
|
- [Backup](#services-jellyfin-options-shb.jellyfin.backup) through the [backup block](./blocks-backup.html). [Manual](#services-jellyfin-usage-backup).
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-jellyfin-usage-applicationdashboard)
|
||||||
|
|
||||||
## Usage {#services-jellyfin-usage}
|
## Usage {#services-jellyfin-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#services-jellyfin-usage-configuration}
|
||||||
|
|
||||||
The following snippet assumes a few blocks have been setup already:
|
The following snippet assumes a few blocks have been setup already:
|
||||||
|
|
||||||
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
||||||
|
|
@ -133,6 +136,35 @@ shb.lldap.ensureUsers.USERNAME.groups = [
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Application Dashboard {#services-jellyfin-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-jellyfin-options-shb.jellyfin.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Jellyfin = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = config.shb.jellyfin.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
An API key can be set to show extra info:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Jellyfin = {
|
||||||
|
apiKey.result = config.shb.sops.secret."jellyfin/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.sops.secret."jellyfin/homepageApiKey".request =
|
||||||
|
config.shb.homepage.servicesGroups.Media.services.Jellyfin.apiKey.request;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Debug {#services-jellyfin-debug}
|
## Debug {#services-jellyfin-debug}
|
||||||
|
|
||||||
In case of an issue, check the logs for systemd service `jellyfin.service`.
|
In case of an issue, check the logs for systemd service `jellyfin.service`.
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,20 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.karakeep.subdomain}.\${config.shb.karakeep.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = (
|
config = (
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,12 @@ It integrates well with [Ollama][].
|
||||||
- Access through [subdomain](#services-karakeep-options-shb.karakeep.subdomain) using reverse proxy.
|
- Access through [subdomain](#services-karakeep-options-shb.karakeep.subdomain) using reverse proxy.
|
||||||
- Access through [HTTPS](#services-karakeep-options-shb.karakeep.ssl) using reverse proxy.
|
- Access through [HTTPS](#services-karakeep-options-shb.karakeep.ssl) using reverse proxy.
|
||||||
- [Backup](#services-karakeep-options-shb.karakeep.sso) through the [backup block](./blocks-backup.html).
|
- [Backup](#services-karakeep-options-shb.karakeep.sso) through the [backup block](./blocks-backup.html).
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-karakeep-usage-applicationdashboard)
|
||||||
|
|
||||||
## Usage {#services-karakeep-usage}
|
## Usage {#services-karakeep-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#services-karakeep-usage-configuration}
|
||||||
|
|
||||||
The following snippet assumes a few blocks have been setup already:
|
The following snippet assumes a few blocks have been setup already:
|
||||||
|
|
||||||
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
||||||
|
|
@ -66,6 +69,35 @@ The [user](#services-open-webui-options-shb.open-webui.ldap.userGroup)
|
||||||
and [admin](#services-open-webui-options-shb.open-webui.ldap.adminGroup)
|
and [admin](#services-open-webui-options-shb.open-webui.ldap.adminGroup)
|
||||||
LDAP groups are created automatically.
|
LDAP groups are created automatically.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-karakeep-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-karakeep-options-shb.karakeep.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Documents.services.Karakeep = {
|
||||||
|
sortOrder = 3;
|
||||||
|
dashboard.request = config.shb.karakeep.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
An API key can be set to show extra info:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Documents.services.Karakeep = {
|
||||||
|
apiKey.result = config.shb.sops.secret."karakeep/homepageApiKey".result;
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.sops.secret."karakeep/homepageApiKey".request =
|
||||||
|
config.shb.homepage.servicesGroups.Documents.services.Karakeep.apiKey.request;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Integration with Ollama {#services-karakeep-ollama}
|
## Integration with Ollama {#services-karakeep-ollama}
|
||||||
|
|
||||||
Assuming ollama is enabled, it will be available on port `config.services.ollama.port`.
|
Assuming ollama is enabled, it will be available on port `config.services.ollama.port`.
|
||||||
|
|
|
||||||
|
|
@ -694,6 +694,21 @@ in
|
||||||
but Self Host Blocks assumes that it will not be the case for most users.
|
but Self Host Blocks assumes that it will not be the case for most users.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${fqdn}";
|
||||||
|
externalUrlText = "https://\${config.shb.nextcloud.subdomain}.\${config.shb.nextcloud.domain}";
|
||||||
|
internalUrl = "https://${fqdn}";
|
||||||
|
internalUrlText = "https://\${config.shb.nextcloud.subdomain}.\${config.shb.nextcloud.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkMerge [
|
config = lib.mkMerge [
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,8 @@ It is based on the nixpkgs Nextcloud server and provides opinionated defaults.
|
||||||
- Forces PostgreSQL as the database.
|
- Forces PostgreSQL as the database.
|
||||||
- Forces Redis as the cache and sets good defaults.
|
- Forces Redis as the cache and sets good defaults.
|
||||||
- Backup of the [`shb.nextcloud.dataDir`][dataDir] through the [backup block](./blocks-backup.html).
|
- Backup of the [`shb.nextcloud.dataDir`][dataDir] through the [backup block](./blocks-backup.html).
|
||||||
- [Dashboard](#services-nextcloudserver-dashboard) for monitoring of reverse proxy, PHP-FPM, and database backups through the [monitoring
|
- [Monitoring Dashboard](#services-nextcloudserver-dashboard) for monitoring of reverse proxy, PHP-FPM, and database backups through the [monitoring block](./blocks-monitoring.html).
|
||||||
block](./blocks-monitoring.html).
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard.
|
||||||
- [Integration Tests](@REPO@/test/services/nextcloud.nix)
|
- [Integration Tests](@REPO@/test/services/nextcloud.nix)
|
||||||
- Tests system cron job is setup correctly.
|
- Tests system cron job is setup correctly.
|
||||||
- Tests initial admin user and password are setup correctly.
|
- Tests initial admin user and password are setup correctly.
|
||||||
|
|
@ -344,6 +344,22 @@ Note that this will backup the whole PostgreSQL instance,
|
||||||
not just the Nextcloud database.
|
not just the Nextcloud database.
|
||||||
This limitation will be lifted in the future.
|
This limitation will be lifted in the future.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-nextcloudserver-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-nextcloudserver-options-shb.nextcloud.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Documents.services.Nextcloud = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = config.shb.nextcloud.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Enable Preview Generator App {#services-nextcloudserver-usage-previewgenerator}
|
### Enable Preview Generator App {#services-nextcloudserver-usage-previewgenerator}
|
||||||
|
|
||||||
The following snippet installs and enables the [Preview
|
The following snippet installs and enables the [Preview
|
||||||
|
|
@ -537,7 +553,7 @@ by issuing the command `nextcloud-occ config:system:delete instanceid`.
|
||||||
Head over to the [Nextcloud demo](demo-nextcloud-server.html) for a demo that installs Nextcloud with or
|
Head over to the [Nextcloud demo](demo-nextcloud-server.html) for a demo that installs Nextcloud with or
|
||||||
without LDAP integration on a VM with minimal manual steps.
|
without LDAP integration on a VM with minimal manual steps.
|
||||||
|
|
||||||
## Dashboard {#services-nextcloudserver-dashboard}
|
## Monitoring Dashboard {#services-nextcloudserver-dashboard}
|
||||||
|
|
||||||
The dashboard is added to Grafana automatically under "Self Host Blocks > Nextcloud"
|
The dashboard is added to Grafana automatically under "Self Host Blocks > Nextcloud"
|
||||||
as long as the Nextcloud service is [enabled][]
|
as long as the Nextcloud service is [enabled][]
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,20 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.open-webui.subdomain}.\${config.shb.open-webui.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = (
|
config = (
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,12 @@ This service sets up [Open WebUI][] which provides a frontend to various LLMs.
|
||||||
- Access through [subdomain](#services-open-webui-options-shb.open-webui.subdomain) using reverse proxy.
|
- Access through [subdomain](#services-open-webui-options-shb.open-webui.subdomain) using reverse proxy.
|
||||||
- Access through [HTTPS](#services-open-webui-options-shb.open-webui.ssl) using reverse proxy.
|
- Access through [HTTPS](#services-open-webui-options-shb.open-webui.ssl) using reverse proxy.
|
||||||
- [Backup](#services-open-webui-options-shb.open-webui.sso) through the [backup block](./blocks-backup.html).
|
- [Backup](#services-open-webui-options-shb.open-webui.sso) through the [backup block](./blocks-backup.html).
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-open-webui-usage-applicationdashboard)
|
||||||
|
|
||||||
## Usage {#services-open-webui-usage}
|
## Usage {#services-open-webui-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#services-open-webui-usage-configuration}
|
||||||
|
|
||||||
The following snippet assumes a few blocks have been setup already:
|
The following snippet assumes a few blocks have been setup already:
|
||||||
|
|
||||||
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
||||||
|
|
@ -63,6 +66,25 @@ The [user](#services-open-webui-options-shb.open-webui.ldap.userGroup)
|
||||||
and [admin](#services-open-webui-options-shb.open-webui.ldap.adminGroup)
|
and [admin](#services-open-webui-options-shb.open-webui.ldap.adminGroup)
|
||||||
LDAP groups are created automatically.
|
LDAP groups are created automatically.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-open-webui-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-open-webui-options-shb.open-webui.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Documents.services.OpenWebUI = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = config.shb.home-assistant.dashboard.request;
|
||||||
|
settings.icon = "sh-open-webui";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The icon needs to be set manually otherwise it is not displayed correctly.
|
||||||
|
|
||||||
## Integration with OLLAMA {#services-open-webui-ollama}
|
## Integration with OLLAMA {#services-open-webui-ollama}
|
||||||
|
|
||||||
Assuming ollama is enabled, it will be available on port `config.services.ollama.port`.
|
Assuming ollama is enabled, it will be available on port `config.services.ollama.port`.
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,8 @@ let
|
||||||
mkIf
|
mkIf
|
||||||
lists
|
lists
|
||||||
mkOption
|
mkOption
|
||||||
optionals
|
|
||||||
;
|
;
|
||||||
inherit (lib.types)
|
inherit (lib.types)
|
||||||
attrs
|
|
||||||
attrsOf
|
attrsOf
|
||||||
bool
|
bool
|
||||||
enum
|
enum
|
||||||
|
|
@ -346,6 +344,20 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.paperless.subdomain}.\${config.shb.paperless.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
|
||||||
shb,
|
shb,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
@ -132,6 +131,20 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.pinchflat.subdomain}.\${config.shb.pinchflat.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,14 @@ this one sets up, in a fully declarative manner,
|
||||||
LDAP and SSO integration
|
LDAP and SSO integration
|
||||||
and has a nicer option for secrets.
|
and has a nicer option for secrets.
|
||||||
|
|
||||||
|
## Features {#services-pinchflat-features}
|
||||||
|
|
||||||
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard. [Manual](#services-pinchflat-usage-applicationdashboard)
|
||||||
|
|
||||||
## Usage {#services-pinchflat-usage}
|
## Usage {#services-pinchflat-usage}
|
||||||
|
|
||||||
|
### Initial Configuration {#services-pinchflat-usage-configuration}
|
||||||
|
|
||||||
The following snippet assumes a few blocks have been setup already:
|
The following snippet assumes a few blocks have been setup already:
|
||||||
|
|
||||||
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
- the [secrets block](usage.html#usage-secrets) with SOPS,
|
||||||
|
|
@ -46,7 +52,7 @@ Secrets can be randomly generated with `nix run nixpkgs#openssl -- rand -hex 64`
|
||||||
The [user](#services-pinchflat-options-shb.pinchflat.ldap.userGroup)
|
The [user](#services-pinchflat-options-shb.pinchflat.ldap.userGroup)
|
||||||
LDAP group is created automatically.
|
LDAP group is created automatically.
|
||||||
|
|
||||||
## Backup {#services-pinchflat-usage-backup}
|
### Backup {#services-pinchflat-usage-backup}
|
||||||
|
|
||||||
Backing up Pinchflat using the [Restic block](blocks-restic.html) is done like so:
|
Backing up Pinchflat using the [Restic block](blocks-restic.html) is done like so:
|
||||||
|
|
||||||
|
|
@ -63,6 +69,22 @@ The name `"pinchflat"` in the `instances` can be anything.
|
||||||
The `config.shb.pinchflat.backup` option provides what directories to backup.
|
The `config.shb.pinchflat.backup` option provides what directories to backup.
|
||||||
You can define any number of Restic instances to backup Pinchflat multiple times.
|
You can define any number of Restic instances to backup Pinchflat multiple times.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-pinchflat-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-pinchflat-options-shb.pinchflat.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Media.services.Pinchflat = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = config.shb.pinchflat.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Options Reference {#services-pinchflat-options}
|
## Options Reference {#services-pinchflat-options}
|
||||||
|
|
||||||
```{=include=} options
|
```{=include=} options
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,20 @@ in
|
||||||
default = false;
|
default = false;
|
||||||
example = true;
|
example = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dashboard = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Dashboard contract consumer
|
||||||
|
'';
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = shb.contracts.dashboard.mkRequester {
|
||||||
|
externalUrl = "https://${cfg.subdomain}.${cfg.domain}";
|
||||||
|
externalUrlText = "https://\${config.shb.vaultwarden.subdomain}.\${config.shb.vaultwarden.domain}";
|
||||||
|
internalUrl = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ This NixOS module is a service that sets up a [Vaultwarden Server](https://githu
|
||||||
- Backup of the data directory through the [backup contract](./contracts-backup.html).
|
- Backup of the data directory through the [backup contract](./contracts-backup.html).
|
||||||
- [Integration Tests](@REPO@/test/services/vaultwarden.nix)
|
- [Integration Tests](@REPO@/test/services/vaultwarden.nix)
|
||||||
- Tests /admin can only be accessed when authenticated with SSO.
|
- Tests /admin can only be accessed when authenticated with SSO.
|
||||||
- Access to advanced options not exposed here thanks to how NixOS modules work.
|
- Integration with the [dashboard contract](contracts-dashboard.html) for displaying user facing application in a dashboard.
|
||||||
|
|
||||||
## Usage {#services-vaultwarden-usage}
|
## Usage {#services-vaultwarden-usage}
|
||||||
|
|
||||||
|
|
@ -120,6 +120,22 @@ The name `"vaultwarden"` in the `instances` can be anything.
|
||||||
The `config.shb.vaultwarden.backup` option provides what directories to backup.
|
The `config.shb.vaultwarden.backup` option provides what directories to backup.
|
||||||
You can define any number of Restic instances to backup Vaultwarden multiple times.
|
You can define any number of Restic instances to backup Vaultwarden multiple times.
|
||||||
|
|
||||||
|
### Application Dashboard {#services-vaultwarden-usage-applicationdashboard}
|
||||||
|
|
||||||
|
Integration with the [dashboard contract](contracts-dashboard.html) is provided
|
||||||
|
by the [dashboard option](#services-vaultwarden-options-shb.vaultwarden.dashboard).
|
||||||
|
|
||||||
|
For example using the [Homepage](services-homepage.html) service:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
shb.homepage.servicesGroups.Documents.services.Vaultwarden = {
|
||||||
|
sortOrder = 10;
|
||||||
|
dashboard.request = config.shb.vaultwarden.dashboard.request;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Maintenance {#services-vaultwarden-maintenance}
|
## Maintenance {#services-vaultwarden-maintenance}
|
||||||
|
|
||||||
No command-line tool is provided to administer Vaultwarden.
|
No command-line tool is provided to administer Vaultwarden.
|
||||||
|
|
|
||||||
269
test/modules/homepage.nix
Normal file
269
test/modules/homepage.nix
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
{ shb }:
|
||||||
|
{
|
||||||
|
testHomepageAsServiceGroup = {
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"Media" = [
|
||||||
|
{
|
||||||
|
"Jellyfin" = {
|
||||||
|
"href" = "https://example.com/jellyfin";
|
||||||
|
"icon" = "sh-jellyfin";
|
||||||
|
"siteMonitor" = "http://127.0.0.1:8096";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
expr = shb.homepage.asServiceGroup {
|
||||||
|
Media = {
|
||||||
|
services = {
|
||||||
|
Jellyfin = {
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/jellyfin";
|
||||||
|
internalUrl = "http://127.0.0.1:8096";
|
||||||
|
};
|
||||||
|
apiKey = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testHomepageAsServiceGroupApiKey = {
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"Media" = [
|
||||||
|
{
|
||||||
|
"Jellyfin" = {
|
||||||
|
"href" = "https://example.com/jellyfin";
|
||||||
|
"icon" = "sh-jellyfin";
|
||||||
|
"siteMonitor" = "http://127.0.0.1:8096";
|
||||||
|
"widget" = {
|
||||||
|
"key" = "{{HOMEPAGE_FILE_Media_Jellyfin}}";
|
||||||
|
"password" = "{{HOMEPAGE_FILE_Media_Jellyfin}}";
|
||||||
|
"type" = "jellyfin";
|
||||||
|
"url" = "http://127.0.0.1:8096";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
expr = shb.homepage.asServiceGroup {
|
||||||
|
Media = {
|
||||||
|
services = {
|
||||||
|
Jellyfin = {
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/jellyfin";
|
||||||
|
internalUrl = "http://127.0.0.1:8096";
|
||||||
|
};
|
||||||
|
apiKey.result.path = "path_D";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testHomepageAsServiceGroupNoServiceMonitor = {
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"Media" = [
|
||||||
|
{
|
||||||
|
"Jellyfin" = {
|
||||||
|
"href" = "https://example.com/jellyfin";
|
||||||
|
"icon" = "sh-jellyfin";
|
||||||
|
"siteMonitor" = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
expr = shb.homepage.asServiceGroup {
|
||||||
|
Media = {
|
||||||
|
services = {
|
||||||
|
Jellyfin = {
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/jellyfin";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testHomepageAsServiceGroupOverride = {
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"Media" = [
|
||||||
|
{
|
||||||
|
"Jellyfin" = {
|
||||||
|
"href" = "https://example.com/jellyfin";
|
||||||
|
"icon" = "sh-icon";
|
||||||
|
"siteMonitor" = "http://127.0.0.1:8096";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
expr = shb.homepage.asServiceGroup {
|
||||||
|
Media = {
|
||||||
|
services = {
|
||||||
|
Jellyfin = {
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/jellyfin";
|
||||||
|
internalUrl = "http://127.0.0.1:8096";
|
||||||
|
};
|
||||||
|
settings = {
|
||||||
|
icon = "sh-icon";
|
||||||
|
};
|
||||||
|
apiKey = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testHomepageAsServiceGroupSortOrder = {
|
||||||
|
expected = [
|
||||||
|
{ "C" = [ ]; }
|
||||||
|
{ "A" = [ ]; }
|
||||||
|
{ "B" = [ ]; }
|
||||||
|
];
|
||||||
|
|
||||||
|
expr = shb.homepage.asServiceGroup {
|
||||||
|
A = {
|
||||||
|
sortOrder = 2;
|
||||||
|
services = { };
|
||||||
|
};
|
||||||
|
B = {
|
||||||
|
sortOrder = 3;
|
||||||
|
services = { };
|
||||||
|
};
|
||||||
|
C = {
|
||||||
|
sortOrder = 1;
|
||||||
|
services = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testHomepageAsServiceServicesSortOrder = {
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
"Media" = [
|
||||||
|
{
|
||||||
|
"A" = {
|
||||||
|
"href" = "https://example.com/a";
|
||||||
|
"icon" = "sh-a";
|
||||||
|
"siteMonitor" = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"C" = {
|
||||||
|
"href" = "https://example.com/c";
|
||||||
|
"icon" = "sh-c";
|
||||||
|
"siteMonitor" = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"B" = {
|
||||||
|
"href" = "https://example.com/b";
|
||||||
|
"icon" = "sh-b";
|
||||||
|
"siteMonitor" = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
expr = shb.homepage.asServiceGroup {
|
||||||
|
Media = {
|
||||||
|
sortOrder = null;
|
||||||
|
services = {
|
||||||
|
A = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/a";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey = null;
|
||||||
|
};
|
||||||
|
B = {
|
||||||
|
sortOrder = 3;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/b";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey = null;
|
||||||
|
};
|
||||||
|
C = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/c";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testHomepageAllKeys = {
|
||||||
|
expected = {
|
||||||
|
"A_A" = "path_A";
|
||||||
|
"A_B" = "path_B";
|
||||||
|
"B_D" = "path_D";
|
||||||
|
};
|
||||||
|
|
||||||
|
expr = shb.homepage.allKeys {
|
||||||
|
A = {
|
||||||
|
sortOrder = 1;
|
||||||
|
services = {
|
||||||
|
A = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/a";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey.result.path = "path_A";
|
||||||
|
};
|
||||||
|
B = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/b";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey.result.path = "path_B";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
B = {
|
||||||
|
sortOrder = 2;
|
||||||
|
services = {
|
||||||
|
C = {
|
||||||
|
sortOrder = 1;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/a";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey = null;
|
||||||
|
};
|
||||||
|
D = {
|
||||||
|
sortOrder = 2;
|
||||||
|
dashboard.request = {
|
||||||
|
externalUrl = "https://example.com/b";
|
||||||
|
internalUrl = null;
|
||||||
|
};
|
||||||
|
apiKey.result.path = "path_D";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -131,3 +131,4 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// import ./homepage.nix { inherit shb; }
|
||||||
|
|
|
||||||
209
test/services/homepage.nix
Normal file
209
test/services/homepage.nix
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
{ shb, ... }:
|
||||||
|
let
|
||||||
|
commonTestScript = shb.test.mkScripts {
|
||||||
|
hasSSL = { node, ... }: !(isNull node.config.shb.homepage.ssl);
|
||||||
|
waitForServices =
|
||||||
|
{ ... }:
|
||||||
|
[
|
||||||
|
"homepage-dashboard.service"
|
||||||
|
"nginx.service"
|
||||||
|
];
|
||||||
|
waitForPorts =
|
||||||
|
{ node, ... }:
|
||||||
|
[
|
||||||
|
node.config.services.homepage-dashboard.listenPort
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
basic =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
shb.test.baseModule
|
||||||
|
../../modules/blocks/hardcodedsecret.nix
|
||||||
|
../../modules/services/homepage.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
subdomain = "h";
|
||||||
|
};
|
||||||
|
|
||||||
|
shb.homepage = {
|
||||||
|
enable = true;
|
||||||
|
inherit (config.test) subdomain domain;
|
||||||
|
|
||||||
|
servicesGroups.MyHomeGroup.services.TestService.dashboard = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
clientLogin =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
shb.test.baseModule
|
||||||
|
shb.test.clientLoginModule
|
||||||
|
];
|
||||||
|
test = {
|
||||||
|
subdomain = "h";
|
||||||
|
};
|
||||||
|
|
||||||
|
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('TestService')).to_be_visible()"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
https =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
shb.homepage = {
|
||||||
|
ssl = config.shb.certs.certs.selfsigned.n;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ldap =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
shb.homepage = {
|
||||||
|
ldap = {
|
||||||
|
userGroup = "user_group";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
clientLoginSso =
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
shb.test.baseModule
|
||||||
|
shb.test.clientLoginModule
|
||||||
|
];
|
||||||
|
test = {
|
||||||
|
subdomain = "h";
|
||||||
|
};
|
||||||
|
|
||||||
|
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('TestService')).to_be_visible(timeout=10000)"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
# Bob, with its admin role only, cannot login into Karakeep because admins do not exist in Karakeep.
|
||||||
|
{
|
||||||
|
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.homepage = {
|
||||||
|
sso = {
|
||||||
|
enable = true;
|
||||||
|
authEndpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
basic = shb.test.runNixOSTest {
|
||||||
|
name = "homepage_basic";
|
||||||
|
|
||||||
|
nodes.client = {
|
||||||
|
imports = [
|
||||||
|
clientLogin
|
||||||
|
];
|
||||||
|
};
|
||||||
|
nodes.server = {
|
||||||
|
imports = [
|
||||||
|
basic
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = commonTestScript.access;
|
||||||
|
};
|
||||||
|
|
||||||
|
https = shb.test.runNixOSTest {
|
||||||
|
name = "homepage_https";
|
||||||
|
|
||||||
|
nodes.client = {
|
||||||
|
imports = [
|
||||||
|
clientLogin
|
||||||
|
];
|
||||||
|
};
|
||||||
|
nodes.server = {
|
||||||
|
imports = [
|
||||||
|
basic
|
||||||
|
shb.test.certs
|
||||||
|
https
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = commonTestScript.access;
|
||||||
|
};
|
||||||
|
|
||||||
|
sso = shb.test.runNixOSTest {
|
||||||
|
name = "homepage_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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue