add forgejo service
This commit is contained in:
parent
eb11a94d44
commit
253ec980d9
8 changed files with 951 additions and 23 deletions
|
@ -24,7 +24,7 @@ let
|
|||
|
||||
ghRoot = (gitHubDeclaration "ibizaman" "selfhostblocks" "").url;
|
||||
|
||||
buildOptionsDocs = args@{ modules, includeModuleSystemOptions ? true, ... }:
|
||||
buildOptionsDocs = args@{ modules, ... }:
|
||||
let
|
||||
config = {
|
||||
_module.check = false;
|
||||
|
@ -45,9 +45,7 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
options = if includeModuleSystemOptions
|
||||
then eval.options
|
||||
else builtins.removeAttrs eval.options [ "_module" ];
|
||||
options = lib.filterAttrs (name: v: name == "shb") eval.options;
|
||||
in buildPackages.nixosOptionsDoc ({
|
||||
inherit options;
|
||||
|
||||
|
@ -66,18 +64,14 @@ let
|
|||
_module.check = false;
|
||||
};
|
||||
|
||||
optionsDocs = buildOptionsDocs {
|
||||
modules = allModules ++ [
|
||||
scrubbedModule
|
||||
];
|
||||
allOptionsDocs = paths: (buildOptionsDocs {
|
||||
modules = paths ++ allModules ++ [ scrubbedModule ];
|
||||
variablelistId = "selfhostblocks-options";
|
||||
includeModuleSystemOptions = false;
|
||||
};
|
||||
}).optionsJSON;
|
||||
|
||||
individualModuleOptionsDocs = path: (buildOptionsDocs {
|
||||
modules = [ path scrubbedModule ];
|
||||
individualModuleOptionsDocs = paths: (buildOptionsDocs {
|
||||
modules = paths ++ [ scrubbedModule ];
|
||||
variablelistId = "selfhostblocks-options";
|
||||
includeModuleSystemOptions = false;
|
||||
}).optionsJSON;
|
||||
|
||||
nmd = import nmdsrc {
|
||||
|
@ -129,37 +123,47 @@ in stdenv.mkDerivation {
|
|||
substituteInPlace ./options.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${optionsDocs.optionsJSON}/share/doc/nixos/options.json
|
||||
${allOptionsDocs [
|
||||
(pkgs.path + "/nixos/modules/services/misc/forgejo.nix")
|
||||
]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/blocks/ssl/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/blocks/ssl.nix}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs [ ../modules/blocks/ssl.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/blocks/restic/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/blocks/restic.nix}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs [ ../modules/blocks/restic.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/services/nextcloud-server/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/services/nextcloud-server.nix}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs [ ../modules/services/nextcloud-server.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/services/vaultwarden/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/services/vaultwarden.nix}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs [ ../modules/services/vaultwarden.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/services/forgejo/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs [
|
||||
../modules/services/forgejo.nix
|
||||
(pkgs.path + "/nixos/modules/services/misc/forgejo.nix")
|
||||
]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/contracts/backup/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/contracts/backup/dummyModule.nix}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs [ ../modules/contracts/backup/dummyModule.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/contracts/ssl/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/contracts/ssl/dummyModule.nix}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs [ ../modules/contracts/ssl/dummyModule.nix ]}/share/doc/nixos/options.json
|
||||
|
||||
find . -name "*.md" -print0 | \
|
||||
while IFS= read -r -d ''' f; do
|
||||
|
|
|
@ -13,7 +13,8 @@ information is provided in the respective manual sections.
|
|||
| Service | Backup | Reverse Proxy | SSO | LDAP | Monitoring | Profiling |
|
||||
|-----------------------|--------|---------------|-----|-------|------------|-----------|
|
||||
| [Nextcloud Server][1] | P (1) | Y | Y | Y | Y | P (2) |
|
||||
| [Vaultwarden][2] | N | Y | Y | Y | N | N |
|
||||
| [Vaultwarden][2] | P (1) | Y | Y | Y | N | N |
|
||||
| [Forgejo][3] | Y | Y | Y | Y | N | N |
|
||||
|
||||
Legend: **N**: no but WIP; **P**: partial; **Y**: yes
|
||||
|
||||
|
@ -22,6 +23,7 @@ Legend: **N**: no but WIP; **P**: partial; **Y**: yes
|
|||
|
||||
[1]: services-nextcloud.html
|
||||
[2]: services-vaultwarden.html
|
||||
[3]: services-foregjo.html
|
||||
|
||||
```{=include=} chapters html:into-file=//services-vaultwarden.html
|
||||
modules/services/vaultwarden/docs/default.md
|
||||
|
@ -30,3 +32,7 @@ modules/services/vaultwarden/docs/default.md
|
|||
```{=include=} chapters html:into-file=//services-nextcloud.html
|
||||
modules/services/nextcloud-server/docs/default.md
|
||||
```
|
||||
|
||||
```{=include=} chapters html:into-file=//services-forgejo.html
|
||||
modules/services/forgejo/docs/default.md
|
||||
```
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
modules/services/arr.nix
|
||||
modules/services/audiobookshelf.nix
|
||||
modules/services/deluge.nix
|
||||
modules/services/forgejo.nix
|
||||
modules/services/grocy.nix
|
||||
modules/services/hledger.nix
|
||||
modules/services/home-assistant.nix
|
||||
|
@ -55,7 +56,7 @@
|
|||
modules/services/vaultwarden.nix
|
||||
];
|
||||
|
||||
# Only used for documentation.
|
||||
# The contract dummies are used to show options for contracts.
|
||||
contractDummyModules = [
|
||||
modules/contracts/backup/dummyModule.nix
|
||||
modules/contracts/ssl/dummyModule.nix
|
||||
|
@ -116,6 +117,7 @@
|
|||
// (vm_test "arr" ./test/services/arr.nix)
|
||||
// (vm_test "audiobookshelf" ./test/services/audiobookshelf.nix)
|
||||
// (vm_test "deluge" ./test/services/deluge.nix)
|
||||
// (vm_test "forgejo" ./test/services/forgejo.nix)
|
||||
// (vm_test "grocy" ./test/services/grocy.nix)
|
||||
// (vm_test "home-assistant" ./test/services/home-assistant.nix)
|
||||
// (vm_test "jellyfin" ./test/services/jellyfin.nix)
|
||||
|
|
|
@ -42,6 +42,7 @@ let
|
|||
|
||||
autheliaRules = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.attrsOf lib.types.anything);
|
||||
default = [];
|
||||
description = "Authelia rule configuration";
|
||||
example = lib.literalExpression ''[{
|
||||
policy = "two_factor";
|
||||
|
|
519
modules/services/forgejo.nix
Normal file
519
modules/services/forgejo.nix
Normal file
|
@ -0,0 +1,519 @@
|
|||
{ config, options, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.shb.forgejo;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
in
|
||||
{
|
||||
options.shb.forgejo = {
|
||||
enable = lib.mkEnableOption "selfhostblocks.forgejo";
|
||||
|
||||
subdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Subdomain under which Forgejo will be served.
|
||||
|
||||
```
|
||||
<subdomain>.<domain>[:<port>]
|
||||
```
|
||||
'';
|
||||
example = "forgejo";
|
||||
};
|
||||
|
||||
domain = lib.mkOption {
|
||||
description = ''
|
||||
Domain under which Forgejo is served.
|
||||
|
||||
```
|
||||
<subdomain>.<domain>[:<port>]
|
||||
```
|
||||
'';
|
||||
type = lib.types.str;
|
||||
example = "domain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
ldap = lib.mkOption {
|
||||
description = ''
|
||||
LDAP Integration.
|
||||
'';
|
||||
default = {};
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "LDAP integration.";
|
||||
|
||||
provider = lib.mkOption {
|
||||
type = lib.types.enum [ "LLDAP" ];
|
||||
description = "LDAP provider name, used for display.";
|
||||
default = "LLDAP";
|
||||
};
|
||||
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Host serving the LDAP server.
|
||||
'';
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = ''
|
||||
Port of the service serving the LDAP server.
|
||||
'';
|
||||
default = 389;
|
||||
};
|
||||
|
||||
dcdomain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "dc domain for ldap.";
|
||||
example = "dc=mydomain,dc=com";
|
||||
};
|
||||
|
||||
adminName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Admin user of the LDAP server.";
|
||||
default = "admin";
|
||||
};
|
||||
|
||||
adminPasswordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
File containing the admin password of the LDAP server.
|
||||
|
||||
Must be readable by the forgejo system user.
|
||||
'';
|
||||
default = "";
|
||||
};
|
||||
|
||||
userGroup = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Group users must belong to be able to login.";
|
||||
default = "forgejo_user";
|
||||
};
|
||||
|
||||
adminGroup = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Group users must belong to be admins.";
|
||||
default = "forgejo_admin";
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
sso = lib.mkOption {
|
||||
description = ''
|
||||
Setup SSO integration.
|
||||
'';
|
||||
default = {};
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.mkEnableOption "SSO integration.";
|
||||
|
||||
provider = lib.mkOption {
|
||||
type = lib.types.enum [ "Authelia" ];
|
||||
description = "OIDC provider name, used for display.";
|
||||
default = "Authelia";
|
||||
};
|
||||
|
||||
endpoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "OIDC endpoint for SSO.";
|
||||
example = "https://authelia.example.com";
|
||||
};
|
||||
|
||||
clientID = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Client ID for the OIDC endpoint.";
|
||||
default = "forgejo";
|
||||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum [ "one_factor" "two_factor" ];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
||||
secretFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
File containing the secret for the OIDC endpoint.
|
||||
|
||||
Must be readable by the forgejo system user.
|
||||
'';
|
||||
};
|
||||
|
||||
secretFileForAuthelia = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
File containing the secret for the OIDC endpoint, must be readable by the Authelia user.
|
||||
|
||||
Must be readable by the authelia system user.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
adminPasswordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "File containing the Forgejo admin user password.";
|
||||
example = "/run/secrets/forgejo/adminPassword";
|
||||
};
|
||||
|
||||
databasePasswordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "File containing the Forgejo database password.";
|
||||
example = "/run/secrets/forgejo/databasePassword";
|
||||
};
|
||||
|
||||
repositoryRoot = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = "Path where to store the repositories. If null, uses the default under the Forgejo StateDir.";
|
||||
default = null;
|
||||
example = "/srv/forgejo";
|
||||
};
|
||||
|
||||
localActionRunner = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable local action runner that runs for all labels.
|
||||
'';
|
||||
};
|
||||
|
||||
backup = lib.mkOption {
|
||||
type = contracts.backup;
|
||||
description = ''
|
||||
Backup configuration. This is an output option.
|
||||
|
||||
Use it to initialize a block implementing the "backup" contract.
|
||||
For example, with the restic block:
|
||||
|
||||
```
|
||||
shb.restic.instances."forgejo" = {
|
||||
enable = true;
|
||||
|
||||
# Options specific to Restic.
|
||||
} // config.shb.forgejo.backup;
|
||||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = {
|
||||
user = options.services.forgejo.user.value;
|
||||
sourceDirectories = [
|
||||
options.services.forgejo.dump.backupDir.value
|
||||
] ++ lib.optionals (cfg.repositoryRoot != null) [
|
||||
cfg.repositoryRoot
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
mount = lib.mkOption {
|
||||
type = contracts.mount;
|
||||
description = ''
|
||||
Mount configuration. This is an output option.
|
||||
|
||||
Use it to initialize a block implementing the "mount" contract.
|
||||
For example, with a zfs dataset:
|
||||
|
||||
```
|
||||
shb.zfs.datasets."forgejo" = {
|
||||
poolName = "root";
|
||||
} // config.shb.forgejo.mount;
|
||||
```
|
||||
'';
|
||||
readOnly = true;
|
||||
default = { path = config.services.forgejo.stateDir; };
|
||||
};
|
||||
|
||||
smtp = lib.mkOption {
|
||||
description = ''
|
||||
Send notifications by smtp.
|
||||
'';
|
||||
default = null;
|
||||
type = lib.types.nullOr (lib.types.submodule {
|
||||
options = {
|
||||
from_address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "SMTP address from which the emails originate.";
|
||||
example = "authelia@mydomain.com";
|
||||
};
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "SMTP host to send the emails to.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "SMTP port to send the emails to.";
|
||||
default = 25;
|
||||
};
|
||||
username = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Username to connect to the SMTP host.";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "File containing the password to connect to the SMTP host.";
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
debug = lib.mkOption {
|
||||
description = "Enable debug logging.";
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf cfg.enable {
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
repositoryRoot = lib.mkIf (cfg.repositoryRoot != null) cfg.repositoryRoot;
|
||||
settings = {
|
||||
server = {
|
||||
DOMAIN = cfg.domain;
|
||||
PROTOCOL = "http+unix";
|
||||
ROOT_URL = "https://${cfg.subdomain}.${cfg.domain}/";
|
||||
};
|
||||
|
||||
service.DISABLE_REGISTRATION = true;
|
||||
|
||||
log.LEVEL = if cfg.debug then "Debug" else "Info";
|
||||
|
||||
cron = {
|
||||
ENABLE = true;
|
||||
RUN_AT_START = true;
|
||||
SCHEDULE = "@every 1h";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# 1 lower than default, to solve conflict between shb.postgresql and nixpkgs' forgejo module.
|
||||
services.postgresql.enable = lib.mkOverride 999 true;
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/issues/258371#issuecomment-2271967113
|
||||
systemd.services.forgejo.serviceConfig.Type = lib.mkForce "exec";
|
||||
|
||||
shb.nginx.vhosts = [{
|
||||
inherit (cfg) domain subdomain ssl;
|
||||
upstream = "http://unix:${config.services.forgejo.settings.server.HTTP_ADDR}";
|
||||
}];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.enable {
|
||||
services.forgejo.database = {
|
||||
type = "postgres";
|
||||
|
||||
passwordFile = cfg.databasePasswordFile;
|
||||
};
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.enable {
|
||||
services.forgejo.dump = {
|
||||
enable = true;
|
||||
type = "tar.gz";
|
||||
interval = "hourly";
|
||||
};
|
||||
})
|
||||
|
||||
# For Forgejo setup: https://github.com/lldap/lldap/blob/main/example_configs/gitea.md
|
||||
# For cli info: https://docs.gitea.com/usage/command-line
|
||||
# Security protocols in: https://codeberg.org/forgejo/forgejo/src/branch/forgejo/services/auth/source/ldap/security_protocol.go#L27-L31
|
||||
(lib.mkIf (cfg.enable && cfg.ldap.enable != false) {
|
||||
# The delimiter in the `cut` command is a TAB!
|
||||
systemd.services.forgejo.preStart = let
|
||||
provider = "SHB-${cfg.ldap.provider}";
|
||||
in ''
|
||||
auth="${lib.getExe config.services.forgejo.package} admin auth"
|
||||
|
||||
echo "Trying to find existing ldap configuration for ${provider}"...
|
||||
set +e -o pipefail
|
||||
id="$($auth list | grep "${provider}.*LDAP" | cut -d' ' -f1)"
|
||||
found=$?
|
||||
set -e +o pipefail
|
||||
|
||||
if [[ $found = 0 ]]; then
|
||||
echo Found ldap configuration at id=$id, updating it if needed.
|
||||
$auth update-ldap \
|
||||
--id $id \
|
||||
--name ${provider} \
|
||||
--host ${cfg.ldap.host} \
|
||||
--port ${toString cfg.ldap.port} \
|
||||
--bind-dn uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain} \
|
||||
--bind-password $(tr -d '\n' < ${cfg.ldap.adminPasswordFile}) \
|
||||
--security-protocol Unencrypted \
|
||||
--user-search-base ou=people,${cfg.ldap.dcdomain} \
|
||||
--user-filter '(&(memberof=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain})(|(uid=%[1]s)(mail=%[1]s)))' \
|
||||
--admin-filter '(memberof=cn=${cfg.ldap.adminGroup},ou=groups,${cfg.ldap.dcdomain})' \
|
||||
--username-attribute uid \
|
||||
--firstname-attribute givenName \
|
||||
--surname-attribute sn \
|
||||
--email-attribute mail \
|
||||
--avatar-attribute jpegPhoto \
|
||||
--synchronize-users
|
||||
echo "Done updating LDAP configuration."
|
||||
else
|
||||
echo Did not find any ldap configuration, creating one with name ${provider}.
|
||||
$auth add-ldap \
|
||||
--name ${provider} \
|
||||
--host ${cfg.ldap.host} \
|
||||
--port ${toString cfg.ldap.port} \
|
||||
--bind-dn uid=${cfg.ldap.adminName},ou=people,${cfg.ldap.dcdomain} \
|
||||
--bind-password $(tr -d '\n' < ${cfg.ldap.adminPasswordFile}) \
|
||||
--security-protocol Unencrypted \
|
||||
--user-search-base ou=people,${cfg.ldap.dcdomain} \
|
||||
--user-filter '(&(memberof=cn=${cfg.ldap.userGroup},ou=groups,${cfg.ldap.dcdomain})(|(uid=%[1]s)(mail=%[1]s)))' \
|
||||
--admin-filter '(memberof=cn=${cfg.ldap.adminGroup},ou=groups,${cfg.ldap.dcdomain})' \
|
||||
--username-attribute uid \
|
||||
--firstname-attribute givenName \
|
||||
--surname-attribute sn \
|
||||
--email-attribute mail \
|
||||
--avatar-attribute jpegPhoto \
|
||||
--synchronize-users
|
||||
echo "Done adding LDAP configuration."
|
||||
fi
|
||||
'';
|
||||
})
|
||||
|
||||
# For Authelia to Forgejo integration: https://www.authelia.com/integration/openid-connect/gitea/
|
||||
# For Forgejo config: https://forgejo.org/docs/latest/admin/config-cheat-sheet
|
||||
# For cli info: https://docs.gitea.com/usage/command-line
|
||||
(lib.mkIf (cfg.enable && cfg.sso.enable != false) {
|
||||
services.forgejo.settings = {
|
||||
oauth2 = {
|
||||
ENABLED = true;
|
||||
};
|
||||
|
||||
openid = {
|
||||
ENABLE_OPENID_SIGNIN = false;
|
||||
ENABLE_OPENID_SIGNUP = true;
|
||||
WHITELISTED_URIS = cfg.sso.endpoint;
|
||||
};
|
||||
|
||||
service = {
|
||||
# DISABLE_REGISTRATION = lib.mkForce false;
|
||||
# ALLOW_ONLY_EXTERNAL_REGISTRATION = false;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
};
|
||||
};
|
||||
|
||||
# The delimiter in the `cut` command is a TAB!
|
||||
systemd.services.forgejo.preStart = let
|
||||
provider = "SHB-${cfg.sso.provider}";
|
||||
in ''
|
||||
auth="${lib.getExe config.services.forgejo.package} admin auth"
|
||||
|
||||
echo "Trying to find existing sso configuration for ${provider}"...
|
||||
set +e -o pipefail
|
||||
id="$($auth list | grep "${provider}.*OAuth2" | cut -d' ' -f1)"
|
||||
found=$?
|
||||
set -e +o pipefail
|
||||
|
||||
if [[ $found = 0 ]]; then
|
||||
echo Found sso configuration at id=$id, updating it if needed.
|
||||
$auth update-oauth \
|
||||
--id $id \
|
||||
--name ${provider} \
|
||||
--provider openidConnect \
|
||||
--key forgejo \
|
||||
--secret $(tr -d '\n' < ${cfg.sso.secretFile}) \
|
||||
--auto-discover-url ${cfg.sso.endpoint}/.well-known/openid-configuration
|
||||
else
|
||||
echo Did not find any sso configuration, creating one with name ${provider}.
|
||||
$auth add-oauth \
|
||||
--name ${provider} \
|
||||
--provider openidConnect \
|
||||
--key forgejo \
|
||||
--secret $(tr -d '\n' < ${cfg.sso.secretFile}) \
|
||||
--auto-discover-url ${cfg.sso.endpoint}/.well-known/openid-configuration
|
||||
fi
|
||||
'';
|
||||
|
||||
shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [
|
||||
(let
|
||||
provider = "SHB-${cfg.sso.provider}";
|
||||
in {
|
||||
client_id = cfg.sso.clientID;
|
||||
client_name = "Forgejo";
|
||||
client_secret.source = cfg.sso.secretFileForAuthelia;
|
||||
public = false;
|
||||
authorization_policy = cfg.sso.authorization_policy;
|
||||
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/user/oauth2/${provider}/callback" ];
|
||||
})
|
||||
];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.enable {
|
||||
systemd.services.forgejo.preStart = ''
|
||||
admin="${lib.getExe config.services.forgejo.package} admin user"
|
||||
$admin create --admin --email "root@localhost" --username meadmin --password "$(tr -d '\n' < ${cfg.adminPasswordFile})" || true
|
||||
$admin change-password --username meadmin --password "$(tr -d '\n' < ${cfg.adminPasswordFile})" || true
|
||||
'';
|
||||
})
|
||||
|
||||
(lib.mkIf (cfg.enable && cfg.smtp != null) {
|
||||
services.forgejo.settings.mailer = {
|
||||
ENABLED = true;
|
||||
SMTP_ADDR = "${cfg.smtp.host}:${toString cfg.smtp.port}";
|
||||
FROM = cfg.smtp.from_address;
|
||||
USER = cfg.smtp.username;
|
||||
};
|
||||
|
||||
services.forgejo.mailerPasswordFile = cfg.smtp.passwordFile;
|
||||
})
|
||||
|
||||
# https://wiki.nixos.org/wiki/Forgejo#Runner
|
||||
(lib.mkIf cfg.enable {
|
||||
services.forgejo.settings.actions = {
|
||||
ENABLED = true;
|
||||
DEFAULT_ACTIONS_URL = "github";
|
||||
};
|
||||
|
||||
services.gitea-actions-runner = lib.mkIf cfg.localActionRunner {
|
||||
package = pkgs.forgejo-actions-runner;
|
||||
instances.local = {
|
||||
enable = true;
|
||||
name = "local";
|
||||
url = let
|
||||
protocol = if cfg.ssl != null then "https" else "http";
|
||||
in "${protocol}://${cfg.subdomain}.${cfg.domain}";
|
||||
tokenFile = ""; # Empty variable to satisfy an assertion.
|
||||
labels = [
|
||||
# "ubuntu-latest:docker://node:16-bullseye"
|
||||
# "ubuntu-22.04:docker://node:16-bullseye"
|
||||
# "ubuntu-20.04:docker://node:16-bullseye"
|
||||
# "ubuntu-18.04:docker://node:16-buster"
|
||||
"native:host"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# This combined with the next statement takes care of
|
||||
# automatically registering a forgejo runner.
|
||||
systemd.services.forgejo.postStart = lib.mkIf cfg.localActionRunner (lib.mkBefore ''
|
||||
${pkgs.bash}/bin/bash -c '(while ! ${pkgs.netcat-openbsd}/bin/nc -z -U ${config.services.forgejo.settings.server.HTTP_ADDR}; do echo "Waiting for unix ${config.services.forgejo.settings.server.HTTP_ADDR} to open..."; sleep 2; done); sleep 2'
|
||||
actions="${lib.getExe config.services.forgejo.package} actions"
|
||||
echo -n TOKEN= > /run/forgejo/forgejo-runner-token
|
||||
$actions generate-runner-token >> /run/forgejo/forgejo-runner-token
|
||||
'');
|
||||
|
||||
systemd.services.gitea-runner-local.serviceConfig = {
|
||||
# LoadCredential = "TOKEN_FILE:/run/forgejo/forgejo-runner-token";
|
||||
# EnvironmentFile = [ "$CREDENTIALS_DIRECTORY/TOKEN_FILE" ];
|
||||
EnvironmentFile = [ "/run/forgejo/forgejo-runner-token" ];
|
||||
};
|
||||
|
||||
systemd.services.gitea-runner-local.wants = [ "forgejo.service" ];
|
||||
systemd.services.gitea-runner-local.after = [ "forgejo.service" ];
|
||||
})
|
||||
];
|
||||
}
|
245
modules/services/forgejo/docs/default.md
Normal file
245
modules/services/forgejo/docs/default.md
Normal file
|
@ -0,0 +1,245 @@
|
|||
# Forgejo Service {#services-forgejo}
|
||||
|
||||
Defined in [`/modules/services/forgejo.nix`](@REPO@/modules/services/forgejo.nix).
|
||||
|
||||
This NixOS module is a service that sets up a [Forgejo](https://forgejo.org/) instance.
|
||||
|
||||
Compared to the stock module from nixpkgs,
|
||||
this one sets up, in a fully declarative manner,
|
||||
LDAP and SSO integration as well as one local runner.
|
||||
|
||||
## Features {#services-forgejo-features}
|
||||
|
||||
- Declarative [LDAP](#services-forgejo-options-shb.forgejo.ldap) Configuration. [Manual](#services-forgejo-usage-ldap).
|
||||
- Declarative [SSO](#services-forgejo-options-shb.forgejo.sso) Configuration. [Manual](#services-forgejo-usage-sso).
|
||||
- Declarative [local runner](#services-forgejo-options-shb.forgejo.localActionRunner) Configuration.
|
||||
- Access through [subdomain](#services-forgejo-options-shb.forgejo.subdomain) using reverse proxy. [Manual](#services-forgejo-usage-basic).
|
||||
- Access through [HTTPS](#services-forgejo-options-shb.forgejo.ssl) using reverse proxy. [Manual](#services-forgejo-usage-basic).
|
||||
- [Backup](#services-forgejo-options-shb.forgejo.sso) through the [backup block](./blocks-backup.html) with the . [Manual](#services-forgejo-usage-backup).
|
||||
|
||||
## Usage {#services-forgejo-usage}
|
||||
|
||||
### Secrets {#services-forgejo-secrets}
|
||||
|
||||
All the secrets should be readable by the forgejo user.
|
||||
|
||||
Secrets should not be stored in the nix store.
|
||||
If you're using [sops-nix](https://github.com/Mic92/sops-nix)
|
||||
and assuming your secrets file is located at `./secrets.yaml`,
|
||||
you can define a secret with:
|
||||
|
||||
```nix
|
||||
sops.secrets."forgejo/adminPasswordFile" = {
|
||||
sopsFile = ./secrets.yaml;
|
||||
mode = "0400";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
restartUnits = [ "forgejo.service" ];
|
||||
};
|
||||
```
|
||||
|
||||
Then you can use that secret:
|
||||
|
||||
```nix
|
||||
shb.forgejo.adminPasswordFile = config.sops.secrets."forgejo/adminPasswordFile".path;
|
||||
```
|
||||
|
||||
### Forgejo through HTTP(S) {#services-forgejo-usage-basic}
|
||||
|
||||
This will set up a Forgejo service that runs on the NixOS target machine,
|
||||
reachable at `http://forgejo.example.com`.
|
||||
|
||||
```nix
|
||||
shb.forgejo = {
|
||||
enable = true;
|
||||
domain = "example.com";
|
||||
subdomain = "forgejo";
|
||||
};
|
||||
```
|
||||
|
||||
If the `shb.ssl` block is used (see [manual](blocks-ssl.html#usage) on how to set it up),
|
||||
the instance will be reachable at `https://fogejo.example.com`.
|
||||
|
||||
Here is an example with self-signed certificates:
|
||||
|
||||
```nix
|
||||
shb.certs = {
|
||||
cas.selfsigned.myca = {
|
||||
name = "My CA";
|
||||
};
|
||||
certs.selfsigned = {
|
||||
foregejo = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
domain = "forgejo.example.com";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Then you can tell Forgejo to use those certificates.
|
||||
|
||||
```nix
|
||||
shb.forgejo = {
|
||||
ssl = config.shb.certs.certs.selfsigned.forgejo;
|
||||
};
|
||||
```
|
||||
|
||||
### With LDAP Support {#services-forgejo-usage-ldap}
|
||||
|
||||
:::: {.note}
|
||||
We will build upon the [Forgejo through HTTP(S)](#services-forgejo-usage-basic) section,
|
||||
so please follow that first.
|
||||
::::
|
||||
|
||||
We will use the LDAP block provided by Self Host Blocks
|
||||
to setup a [LLDAP](https://github.com/lldap/lldap) service.
|
||||
|
||||
```nix
|
||||
shb.ldap = {
|
||||
enable = true;
|
||||
domain = "example.com";
|
||||
subdomain = "ldap";
|
||||
ldapPort = 3890;
|
||||
webUIListenPort = 17170;
|
||||
dcdomain = "dc=example,dc=com";
|
||||
ldapUserPasswordFile = <path/to/ldapUserPasswordSecret>;
|
||||
jwtSecretFile = <path/to/ldapJwtSecret>;
|
||||
};
|
||||
```
|
||||
|
||||
We also need to configure the `forgejo` service
|
||||
to talk to the LDAP server we just defined:
|
||||
|
||||
```nix
|
||||
shb.forgejo.ldap
|
||||
enable = true;
|
||||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPasswordFile = <path/to/ldapUserPasswordSecret>;
|
||||
};
|
||||
```
|
||||
|
||||
The `shb.forgejo.ldap.adminPasswordFile` must be the same
|
||||
as the `shb.ldap.ldapUserPasswordFile`.
|
||||
The other secrets can be randomly generated with
|
||||
`nix run nixpkgs#openssl -- rand -hex 64`.
|
||||
|
||||
And that's it.
|
||||
Now, go to the LDAP server at `http://ldap.example.com`,
|
||||
create the `forgejo_user` and `forgejo_admin` groups,
|
||||
create a user and add it to one or both groups.
|
||||
When that's done, go back to the Forgejo server at
|
||||
`http://forgejo.example.com` and login with that user.
|
||||
|
||||
### With SSO Support {#services-forgejo-usage-sso}
|
||||
|
||||
:::: {.note}
|
||||
We will build upon the [With LDAP Support](#services-forgejo-usage-ldap) section,
|
||||
so please follow that first.
|
||||
::::
|
||||
|
||||
Here though, we must setup SSL certificates
|
||||
because the SSO provider only works with the https protocol.
|
||||
Let's add self-signed certificates for Authelia and LLDAP:
|
||||
|
||||
```nix
|
||||
shb.certs = {
|
||||
certs.selfsigned = {
|
||||
auth = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
domain = "auth.example.com";
|
||||
};
|
||||
ldap = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
domain = "ldap.example.com";
|
||||
};
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
We then need to setup the SSO provider,
|
||||
here Authelia thanks to the corresponding SHB block:
|
||||
|
||||
```nix
|
||||
shb.authelia = {
|
||||
enable = true;
|
||||
domain = "example.com";
|
||||
subdomain = "auth";
|
||||
ssl = config.shb.certs.certs.selfsigned.auth;
|
||||
|
||||
ldapHostname = "127.0.0.1";
|
||||
ldapPort = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
|
||||
secrets = {
|
||||
jwtSecretFile = <path/to/autheliaJwtSecret>;
|
||||
ldapAdminPasswordFile = <path/to/ldapUserPasswordSecret>;
|
||||
sessionSecretFile = <path/to/autheliaSessionSecret>;
|
||||
storageEncryptionKeyFile = <path/to/autheliaStorageEncryptionKeySecret>;
|
||||
identityProvidersOIDCHMACSecretFile = <path/to/providersOIDCHMACSecret>;
|
||||
identityProvidersOIDCIssuerPrivateKeyFile = <path/to/providersOIDCIssuerSecret>;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The `shb.authelia.secrets.ldapAdminPasswordFile` must be the same
|
||||
as the `shb.ldap.ldapUserPasswordFile` defined in the previous section.
|
||||
The other secrets can be randomly generated
|
||||
with `nix run nixpkgs#openssl -- rand -hex 64`.
|
||||
|
||||
Now, on the forgejo side, you need to add the following options:
|
||||
|
||||
```nix
|
||||
shb.forgejo.sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
|
||||
secretFile = <path/to/oidcForgejoSharedSecret>;
|
||||
secretFileForAuthelia = <path/to/oidcForgejoSharedSecret>;
|
||||
};
|
||||
```
|
||||
|
||||
Passing the `ssl` option will auto-configure nginx to force SSL connections with the given
|
||||
certificate.
|
||||
|
||||
The `shb.foregejo.sso.secretFile` and `shb.forgejo.sso.secretFileForAuthelia` options
|
||||
must have the same content. The former is a file that must be owned by the `forgejo` user while
|
||||
the latter must be owned by the `authelia` user. I want to avoid needing to define the same secret
|
||||
twice with a future secrets SHB block.
|
||||
|
||||
### Backup {#services-forgejo-usage-backup}
|
||||
|
||||
Backing up Forgejo using the [Restic block](blocks-restic.html) is done like so:
|
||||
|
||||
```nix
|
||||
shb.restic.instances."forgejo" = config.shb.forgejo.backup // {
|
||||
enable = true;
|
||||
};
|
||||
```
|
||||
|
||||
The name `"foregjo"` in the `instances` can be anything.
|
||||
The `config.shb.forgejo.backup` option provides what directories to backup.
|
||||
You can define any number of Restic instances to backup Foregejo multiple times.
|
||||
|
||||
### Extra Settings {#services-forgejo-usage-extra-settings}
|
||||
|
||||
Other Forgejo settings can be accessed through the nixpkgs [stock service][].
|
||||
|
||||
[stock service]: https://search.nixos.org/options?channel=24.05&from=0&size=50&sort=alpha_asc&type=packages&query=services.forgejo
|
||||
|
||||
## Debug {#services-forgejo-debug}
|
||||
|
||||
In case of an issue, check the logs for systemd service `forgejo.service`.
|
||||
|
||||
Enable verbose logging by setting the `shb.forgejo.debug` boolean to `true`.
|
||||
|
||||
Access the database with `sudo -u forgejo psql`.
|
||||
|
||||
## Options Reference {#services-forgejo-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: services-forgejo-options-
|
||||
list-id: selfhostblocks-service-forgejo-options
|
||||
source: @OPTIONS_JSON@
|
||||
```
|
|
@ -112,6 +112,12 @@ in
|
|||
default = "jellyfin_user";
|
||||
};
|
||||
|
||||
authorization_policy = lib.mkOption {
|
||||
type = lib.types.enum [ "one_factor" "two_factor" ];
|
||||
description = "Require one factor (password) or two factor (device) authentication.";
|
||||
default = "one_factor";
|
||||
};
|
||||
|
||||
secretFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "File containing the OIDC shared secret.";
|
||||
|
@ -419,7 +425,7 @@ in
|
|||
client_name = "Jellyfin";
|
||||
client_secret.source = cfg.sso.secretFile;
|
||||
public = false;
|
||||
authorization_policy = "one_factor";
|
||||
authorization_policy = cfg.sso.authorization_policy;
|
||||
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.sso.provider}" ];
|
||||
}
|
||||
];
|
||||
|
|
145
test/services/forgejo.nix
Normal file
145
test/services/forgejo.nix
Normal file
|
@ -0,0 +1,145 @@
|
|||
{ pkgs, ... }:
|
||||
let
|
||||
pkgs' = pkgs;
|
||||
|
||||
testLib = pkgs.callPackage ../common.nix {};
|
||||
|
||||
subdomain = "f";
|
||||
domain = "example.com";
|
||||
|
||||
adminPassword = "AdminPassword";
|
||||
|
||||
commonTestScript = testLib.accessScript {
|
||||
inherit subdomain domain;
|
||||
hasSSL = { node, ... }: !(isNull node.config.shb.forgejo.ssl);
|
||||
waitForServices = { ... }: [
|
||||
"forgejo.service"
|
||||
"nginx.service"
|
||||
];
|
||||
waitForUnixSocket = { node, ... }: [
|
||||
node.config.services.forgejo.settings.server.HTTP_ADDR
|
||||
];
|
||||
extraScript = { node, ... }: ''
|
||||
server.wait_for_unit("gitea-runner-local.service", timeout=10)
|
||||
server.succeed("journalctl -o cat -u gitea-runner-local.service | grep -q 'Runner registered successfully'")
|
||||
'';
|
||||
};
|
||||
|
||||
base = testLib.base pkgs' [
|
||||
../../modules/services/forgejo.nix
|
||||
];
|
||||
|
||||
basic = {
|
||||
shb.forgejo = {
|
||||
enable = true;
|
||||
inherit domain subdomain;
|
||||
|
||||
adminPasswordFile = pkgs.writeText "adminPasswordFile" adminPassword;
|
||||
databasePasswordFile = pkgs.writeText "databasePassword" "databasePassword";
|
||||
};
|
||||
|
||||
# Needed for gitea-runner-local to be able to ping forgejo.
|
||||
networking.hosts = {
|
||||
"127.0.0.1" = [ "${subdomain}.${domain}" ];
|
||||
};
|
||||
};
|
||||
|
||||
https = { config, ... }: {
|
||||
shb.forgejo = {
|
||||
ssl = config.shb.certs.certs.selfsigned.n;
|
||||
};
|
||||
};
|
||||
|
||||
ldap = { config, ... }: {
|
||||
shb.forgejo = {
|
||||
ldap = {
|
||||
enable = true;
|
||||
host = "127.0.0.1";
|
||||
port = config.shb.ldap.ldapPort;
|
||||
dcdomain = config.shb.ldap.dcdomain;
|
||||
adminPasswordFile = config.shb.ldap.ldapUserPasswordFile;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sso = { config, ... }: {
|
||||
shb.forgejo = {
|
||||
sso = {
|
||||
enable = true;
|
||||
endpoint = "https://${config.shb.authelia.subdomain}.${config.shb.authelia.domain}";
|
||||
secretFile = pkgs.writeText "ssoSecretFile" "ssoSecretFile";
|
||||
secretFileForAuthelia = pkgs.writeText "ssoSecretFile" "ssoSecretFile";
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
basic = pkgs.testers.runNixOSTest {
|
||||
name = "forgejo_basic";
|
||||
|
||||
nodes.server = {
|
||||
imports = [
|
||||
base
|
||||
basic
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
||||
https = pkgs.testers.runNixOSTest {
|
||||
name = "forgejo_https";
|
||||
|
||||
nodes.server = {
|
||||
imports = [
|
||||
base
|
||||
(testLib.certs domain)
|
||||
basic
|
||||
https
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
||||
ldap = pkgs.testers.runNixOSTest {
|
||||
name = "forgejo_ldap";
|
||||
|
||||
nodes.server = {
|
||||
imports = [
|
||||
base
|
||||
basic
|
||||
(testLib.ldap domain pkgs')
|
||||
ldap
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
|
||||
sso = pkgs.testers.runNixOSTest {
|
||||
name = "forgejo_sso";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
imports = [
|
||||
base
|
||||
(testLib.certs domain)
|
||||
basic
|
||||
https
|
||||
(testLib.ldap domain pkgs')
|
||||
(testLib.sso domain pkgs' config.shb.certs.certs.selfsigned.n)
|
||||
sso
|
||||
];
|
||||
};
|
||||
|
||||
nodes.client = {};
|
||||
|
||||
testScript = commonTestScript;
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue