style: format tree
This commit is contained in:
@@ -5,13 +5,8 @@ in
|
||||
# FIXME do we really want to delete passwords on module deactivation!?
|
||||
{
|
||||
config = lib.mkIf (!sp.modules.bitwarden.enable) {
|
||||
system.activationScripts.bitwarden =
|
||||
lib.trivial.warn
|
||||
(
|
||||
"bitwarden service is disabled, ${bitwarden-env} will be removed!"
|
||||
)
|
||||
''
|
||||
rm -f -v ${bitwarden-env}
|
||||
'';
|
||||
system.activationScripts.bitwarden = lib.trivial.warn ("bitwarden service is disabled, ${bitwarden-env} will be removed!") ''
|
||||
rm -f -v ${bitwarden-env}
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
config:
|
||||
{
|
||||
config: {
|
||||
sp = config.selfprivacy;
|
||||
bitwarden-env = "/var/lib/bitwarden/.env";
|
||||
}
|
||||
|
@@ -1,34 +1,41 @@
|
||||
{
|
||||
description = "PoC SP module for Bitwarden password management solution";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = _:
|
||||
{ imports = [ ./module.nix ./cleanup-module.nix ]; };
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "bitwarden";
|
||||
name = "Bitwarden";
|
||||
description = "Bitwarden is a password manager.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
backupDescription = "Password database, encryption certificate and attachments.";
|
||||
systemdServices = [
|
||||
"vaultwarden.service"
|
||||
];
|
||||
user = "vaultwarden";
|
||||
folders = [
|
||||
"/var/lib/bitwarden"
|
||||
"/var/lib/bitwarden_rs"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Only
|
||||
];
|
||||
homepage = "https://github.com/dani-garcia/vaultwarden";
|
||||
sourcePage = "https://github.com/dani-garcia/vaultwarden";
|
||||
supportLevel = "normal";
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = _: {
|
||||
imports = [
|
||||
./module.nix
|
||||
./cleanup-module.nix
|
||||
];
|
||||
};
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "bitwarden";
|
||||
name = "Bitwarden";
|
||||
description = "Bitwarden is a password manager.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
backupDescription = "Password database, encryption certificate and attachments.";
|
||||
systemdServices = [
|
||||
"vaultwarden.service"
|
||||
];
|
||||
user = "vaultwarden";
|
||||
folders = [
|
||||
"/var/lib/bitwarden"
|
||||
"/var/lib/bitwarden_rs"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Only
|
||||
];
|
||||
homepage = "https://github.com/dani-garcia/vaultwarden";
|
||||
sourcePage = "https://github.com/dani-garcia/vaultwarden";
|
||||
supportLevel = "normal";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
secrets-filepath = "/etc/selfprivacy/secrets.json";
|
||||
backup-dir = "/var/lib/bitwarden/backup";
|
||||
@@ -7,65 +12,77 @@ let
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.bitwarden = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Vaultwarden";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Vaultwarden";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
location = (lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Vaultwarden location";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "location";
|
||||
location =
|
||||
(lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Vaultwarden location";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "location";
|
||||
};
|
||||
};
|
||||
};
|
||||
subdomain = (lib.mkOption {
|
||||
default = "password";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
}) // {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
subdomain =
|
||||
(lib.mkOption {
|
||||
default = "password";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
signupsAllowed = (lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Allow new user signups";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 1;
|
||||
signupsAllowed =
|
||||
(lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Allow new user signups";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
sendsAllowed = (lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Allow users to use Bitwarden Send";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 2;
|
||||
sendsAllowed =
|
||||
(lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Allow users to use Bitwarden Send";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
emergencyAccessAllowed = (lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Allow users to enable Emergency Access";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 3;
|
||||
emergencyAccessAllowed =
|
||||
(lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Allow users to enable Emergency Access";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.selfprivacy.modules.bitwarden.enable {
|
||||
@@ -118,7 +135,10 @@ in
|
||||
before = [ "vaultwarden.service" ];
|
||||
requiredBy = [ "vaultwarden.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
path = with pkgs; [ coreutils jq ];
|
||||
path = with pkgs; [
|
||||
coreutils
|
||||
jq
|
||||
];
|
||||
script = ''
|
||||
set -o nounset
|
||||
|
||||
|
@@ -1,35 +1,38 @@
|
||||
{
|
||||
description = "PoC SP module for Gitea forge service";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "gitea";
|
||||
name = "Forgejo";
|
||||
description = "Forgejo is a Git forge.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
backupDescription = "Git repositories, database and user data.";
|
||||
systemdServices = [
|
||||
"forgejo.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/gitea"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.gpl3Plus
|
||||
];
|
||||
homepage = "https://forgejo.org";
|
||||
sourcePage = "https://codeberg.org/forgejo/forgejo";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.gitea.users";
|
||||
adminGroup = "sp.gitea.admins";
|
||||
};
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "gitea";
|
||||
name = "Forgejo";
|
||||
description = "Forgejo is a Git forge.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
backupDescription = "Git repositories, database and user data.";
|
||||
systemdServices = [
|
||||
"forgejo.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/gitea"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.gpl3Plus
|
||||
];
|
||||
homepage = "https://forgejo.org";
|
||||
sourcePage = "https://codeberg.org/forgejo/forgejo";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.gitea.users";
|
||||
adminGroup = "sp.gitea.admins";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
sp = config.selfprivacy;
|
||||
stateDir =
|
||||
if sp.useBinds
|
||||
then "/volumes/${cfg.location}/gitea"
|
||||
else "/var/lib/gitea";
|
||||
stateDir = if sp.useBinds then "/volumes/${cfg.location}/gitea" else "/var/lib/gitea";
|
||||
cfg = sp.modules.gitea;
|
||||
themes = [
|
||||
"forgejo-auto"
|
||||
@@ -18,8 +20,7 @@ let
|
||||
oauthClientID = "forgejo";
|
||||
auth-passthru = config.selfprivacy.passthru.auth;
|
||||
oauth2-provider-name = auth-passthru.oauth2-provider-name;
|
||||
redirect-uri =
|
||||
"https://${cfg.subdomain}.${sp.domain}/user/oauth2/${oauth2-provider-name}/callback";
|
||||
redirect-uri = "https://${cfg.subdomain}.${sp.domain}/user/oauth2/${oauth2-provider-name}/callback";
|
||||
oauthDiscoveryURL = auth-passthru.oauth2-discovery-url oauthClientID;
|
||||
|
||||
# SelfPrivacy uses SP Module ID to identify the group!
|
||||
@@ -30,381 +31,401 @@ let
|
||||
linuxGroupOfService = "gitea";
|
||||
forgejoPackage = pkgs.forgejo;
|
||||
|
||||
serviceAccountTokenFP =
|
||||
auth-passthru.mkServiceAccountTokenFP linuxGroupOfService;
|
||||
oauthClientSecretFP =
|
||||
auth-passthru.mkOAuth2ClientSecretFP linuxGroupOfService;
|
||||
serviceAccountTokenFP = auth-passthru.mkServiceAccountTokenFP linuxGroupOfService;
|
||||
oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP linuxGroupOfService;
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.gitea = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Forgejo";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Forgejo";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
location = (lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Forgejo location";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "location";
|
||||
location =
|
||||
(lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Forgejo location";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "location";
|
||||
};
|
||||
};
|
||||
};
|
||||
subdomain = (lib.mkOption {
|
||||
default = "git";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
}) // {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
subdomain =
|
||||
(lib.mkOption {
|
||||
default = "git";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
appName = (lib.mkOption {
|
||||
default = "SelfPrivacy git Service";
|
||||
type = lib.types.str;
|
||||
description = "The name displayed in the web interface";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 1;
|
||||
appName =
|
||||
(lib.mkOption {
|
||||
default = "SelfPrivacy git Service";
|
||||
type = lib.types.str;
|
||||
description = "The name displayed in the web interface";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
enableLfs = (lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Git LFS";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 2;
|
||||
enableLfs =
|
||||
(lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Git LFS";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
forcePrivate = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Force all new repositories to be private";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 3;
|
||||
forcePrivate =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Force all new repositories to be private";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
disableRegistration = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Disable registration of new users";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 4;
|
||||
disableRegistration =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Disable registration of new users";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 4;
|
||||
};
|
||||
};
|
||||
};
|
||||
requireSigninView = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Force users to log in to view any page";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 5;
|
||||
requireSigninView =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Force users to log in to view any page";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
defaultTheme = (lib.mkOption {
|
||||
default = "forgejo-auto";
|
||||
type = lib.types.enum themes;
|
||||
description = "Default theme";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enum";
|
||||
options = themes;
|
||||
weight = 6;
|
||||
defaultTheme =
|
||||
(lib.mkOption {
|
||||
default = "forgejo-auto";
|
||||
type = lib.types.enum themes;
|
||||
description = "Default theme";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enum";
|
||||
options = themes;
|
||||
weight = 6;
|
||||
};
|
||||
};
|
||||
};
|
||||
enableSso = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Single Sign-On";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 7;
|
||||
enableSso =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Single Sign-On";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 7;
|
||||
};
|
||||
};
|
||||
};
|
||||
debug = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable debug logging";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 8;
|
||||
debug =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable debug logging";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 8;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.enableSso -> sp.sso.enable;
|
||||
message =
|
||||
"SSO cannot be enabled for Forgejo when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
"/var/lib/gitea" = {
|
||||
device = "/volumes/${cfg.location}/gitea";
|
||||
options = [ "bind" ];
|
||||
config = lib.mkIf cfg.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.enableSso -> sp.sso.enable;
|
||||
message = "SSO cannot be enabled for Forgejo when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
"/var/lib/gitea" = {
|
||||
device = "/volumes/${cfg.location}/gitea";
|
||||
options = [ "bind" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
services.gitea.enable = false;
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
package = forgejoPackage;
|
||||
inherit stateDir;
|
||||
user = linuxUserOfService;
|
||||
group = linuxGroupOfService;
|
||||
database = {
|
||||
type = "sqlite3";
|
||||
host = "127.0.0.1";
|
||||
name = "gitea";
|
||||
services.gitea.enable = false;
|
||||
services.forgejo = {
|
||||
enable = true;
|
||||
package = forgejoPackage;
|
||||
inherit stateDir;
|
||||
user = linuxUserOfService;
|
||||
path = "${stateDir}/data/gitea.db";
|
||||
createDatabase = true;
|
||||
group = linuxGroupOfService;
|
||||
database = {
|
||||
type = "sqlite3";
|
||||
host = "127.0.0.1";
|
||||
name = "gitea";
|
||||
user = linuxUserOfService;
|
||||
path = "${stateDir}/data/gitea.db";
|
||||
createDatabase = true;
|
||||
};
|
||||
# ssh = {
|
||||
# enable = true;
|
||||
# clonePort = 22;
|
||||
# };
|
||||
lfs = {
|
||||
enable = cfg.enableLfs;
|
||||
contentDir = "${stateDir}/lfs";
|
||||
};
|
||||
repositoryRoot = "${stateDir}/repositories";
|
||||
# cookieSecure = true;
|
||||
settings = {
|
||||
DEFAULT = {
|
||||
APP_NAME = "${cfg.appName}";
|
||||
};
|
||||
server = {
|
||||
DOMAIN = "${cfg.subdomain}.${sp.domain}";
|
||||
ROOT_URL = "https://${cfg.subdomain}.${sp.domain}/";
|
||||
HTTP_ADDR = "0.0.0.0";
|
||||
HTTP_PORT = 3000;
|
||||
};
|
||||
mailer = {
|
||||
ENABLED = false;
|
||||
};
|
||||
ui = {
|
||||
DEFAULT_THEME = cfg.defaultTheme;
|
||||
SHOW_USER_EMAIL = false;
|
||||
};
|
||||
picture = {
|
||||
DISABLE_GRAVATAR = true;
|
||||
};
|
||||
admin = {
|
||||
ENABLE_KANBAN_BOARD = true;
|
||||
};
|
||||
repository = {
|
||||
FORCE_PRIVATE = cfg.forcePrivate;
|
||||
};
|
||||
session = {
|
||||
COOKIE_SECURE = true;
|
||||
};
|
||||
log = {
|
||||
ROOT_PATH = "${stateDir}/log";
|
||||
LEVEL = if cfg.debug then "Warn" else "Trace";
|
||||
};
|
||||
service = {
|
||||
DISABLE_REGISTRATION = cfg.disableRegistration;
|
||||
REQUIRE_SIGNIN_VIEW = cfg.requireSigninView;
|
||||
};
|
||||
};
|
||||
};
|
||||
# ssh = {
|
||||
# enable = true;
|
||||
# clonePort = 22;
|
||||
# };
|
||||
lfs = {
|
||||
enable = cfg.enableLfs;
|
||||
contentDir = "${stateDir}/lfs";
|
||||
|
||||
users.users.gitea = {
|
||||
home = "${stateDir}";
|
||||
useDefaultShell = true;
|
||||
group = linuxGroupOfService;
|
||||
isSystemUser = true;
|
||||
};
|
||||
repositoryRoot = "${stateDir}/repositories";
|
||||
# cookieSecure = true;
|
||||
settings = {
|
||||
DEFAULT = {
|
||||
APP_NAME = "${cfg.appName}";
|
||||
users.groups.${linuxGroupOfService} = { };
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
|
||||
useACMEHost = sp.domain;
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
add_header Strict-Transport-Security $hsts_header;
|
||||
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
|
||||
add_header 'Referrer-Policy' 'origin-when-cross-origin';
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||
'';
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
};
|
||||
};
|
||||
server = {
|
||||
DOMAIN = "${cfg.subdomain}.${sp.domain}";
|
||||
ROOT_URL = "https://${cfg.subdomain}.${sp.domain}/";
|
||||
HTTP_ADDR = "0.0.0.0";
|
||||
HTTP_PORT = 3000;
|
||||
};
|
||||
systemd = {
|
||||
services.forgejo = {
|
||||
unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/gitea";
|
||||
serviceConfig = {
|
||||
Slice = "gitea.slice";
|
||||
};
|
||||
};
|
||||
mailer = {
|
||||
ENABLED = false;
|
||||
};
|
||||
ui = {
|
||||
DEFAULT_THEME = cfg.defaultTheme;
|
||||
SHOW_USER_EMAIL = false;
|
||||
};
|
||||
picture = {
|
||||
DISABLE_GRAVATAR = true;
|
||||
};
|
||||
admin = {
|
||||
ENABLE_KANBAN_BOARD = true;
|
||||
};
|
||||
repository = {
|
||||
FORCE_PRIVATE = cfg.forcePrivate;
|
||||
};
|
||||
session = {
|
||||
COOKIE_SECURE = true;
|
||||
};
|
||||
log = {
|
||||
ROOT_PATH = "${stateDir}/log";
|
||||
LEVEL = if cfg.debug then "Warn" else "Trace";
|
||||
slices.gitea = {
|
||||
description = "Forgejo service slice";
|
||||
};
|
||||
};
|
||||
}
|
||||
# the following part is active only when enableSso = true
|
||||
(lib.mkIf is-auth-enabled {
|
||||
services.forgejo.settings = {
|
||||
auth.DISABLE_LOGIN_FORM = true;
|
||||
service = {
|
||||
DISABLE_REGISTRATION = cfg.disableRegistration;
|
||||
REQUIRE_SIGNIN_VIEW = cfg.requireSigninView;
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
ENABLE_BASIC_AUTHENTICATION = false;
|
||||
};
|
||||
|
||||
# disallow explore page and access to private repositories, but allow public
|
||||
"service.explore".REQUIRE_SIGNIN_VIEW = true;
|
||||
|
||||
# TODO control via selfprivacy parameter
|
||||
# "service.explore".DISABLE_USERS_PAGE = true;
|
||||
|
||||
oauth2_client = {
|
||||
REDIRECT_URI = redirect-uri;
|
||||
ACCOUNT_LINKING = "auto";
|
||||
ENABLE_AUTO_REGISTRATION = true;
|
||||
OPENID_CONNECT_SCOPES = "email openid profile";
|
||||
};
|
||||
# doesn't work if LDAP auth source is not active!
|
||||
"cron.sync_external_users" = {
|
||||
ENABLED = true;
|
||||
RUN_AT_START = true;
|
||||
NOTICE_ON_SUCCESS = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
systemd.services.forgejo = {
|
||||
preStart =
|
||||
let
|
||||
waitForURL = url: maxRetries: delaySec: ''
|
||||
for ((i=1; i<=${toString maxRetries}; i++))
|
||||
do
|
||||
if ${lib.getExe pkgs.curl} -X GET --silent --fail "${url}" > /dev/null
|
||||
then
|
||||
echo "${url} responds to GET HTTP request (attempt #$i)"
|
||||
break
|
||||
else
|
||||
echo "${url} does not respond to GET HTTP request (attempt #$i)"
|
||||
echo sleeping for ${toString delaySec} seconds
|
||||
fi
|
||||
sleep ${toString delaySec}
|
||||
done
|
||||
if [[ "$i" -gt "${toString maxRetries}" ]]
|
||||
then
|
||||
echo "error, max attempts to access "${url}" have been used unsuccessfully!"
|
||||
exit 124
|
||||
fi
|
||||
'';
|
||||
|
||||
users.users.gitea = {
|
||||
home = "${stateDir}";
|
||||
useDefaultShell = true;
|
||||
group = linuxGroupOfService;
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.${linuxGroupOfService} = { };
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
|
||||
useACMEHost = sp.domain;
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
add_header Strict-Transport-Security $hsts_header;
|
||||
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
|
||||
add_header 'Referrer-Policy' 'origin-when-cross-origin';
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||
'';
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
exe = lib.getExe config.services.forgejo.package;
|
||||
# FIXME skip-tls-verify, bind-password
|
||||
ldapConfigArgs = ''
|
||||
--name LDAP \
|
||||
--active \
|
||||
--security-protocol LDAPS \
|
||||
--skip-tls-verify \
|
||||
--host '${auth-passthru.ldap-host}' \
|
||||
--port '${toString auth-passthru.ldap-port}' \
|
||||
--user-search-base '${auth-passthru.ldap-base-dn}' \
|
||||
--user-filter '(&(class=person)(memberof=${usersGroup})(name=%s))' \
|
||||
--admin-filter '(&(class=person)(memberof=${adminsGroup})' \
|
||||
--username-attribute name \
|
||||
--firstname-attribute name \
|
||||
--surname-attribute displayname \
|
||||
--email-attribute mail \
|
||||
--public-ssh-key-attribute sshPublicKey \
|
||||
--bind-dn 'dn=token' \
|
||||
--bind-password "$(< ${serviceAccountTokenFP})" \
|
||||
--synchronize-users
|
||||
'';
|
||||
oauthConfigArgs = ''
|
||||
--name "${oauth2-provider-name}" \
|
||||
--provider openidConnect \
|
||||
--key forgejo \
|
||||
--secret "$(< ${oauthClientSecretFP})" \
|
||||
--group-claim-name groups \
|
||||
--admin-group admins \
|
||||
--auto-discover-url '${oauthDiscoveryURL}'
|
||||
'';
|
||||
in
|
||||
lib.mkMerge [
|
||||
(waitForURL oauthDiscoveryURL 10 10)
|
||||
(lib.mkAfter ''
|
||||
set -o xtrace
|
||||
|
||||
# Check if LDAP is already configured
|
||||
ldap_line="$(${exe} admin auth list | grep LDAP | head -n 1)"
|
||||
|
||||
if [[ -n "$ldap_line" ]]; then
|
||||
# update ldap config
|
||||
id="$(echo "$ldap_line" | ${pkgs.gawk}/bin/awk '{print $1}')"
|
||||
${exe} admin auth update-ldap --id "$id" ${ldapConfigArgs}
|
||||
else
|
||||
# initially configure ldap
|
||||
${exe} admin auth add-ldap ${ldapConfigArgs}
|
||||
fi
|
||||
|
||||
oauth_line="$(${exe} admin auth list | grep "${oauth2-provider-name}" | head -n 1)"
|
||||
if [[ -n "$oauth_line" ]]; then
|
||||
id="$(echo "$oauth_line" | ${pkgs.gawk}/bin/awk '{print $1}')"
|
||||
${exe} admin auth update-oauth --id "$id" ${oauthConfigArgs}
|
||||
else
|
||||
${exe} admin auth add-oauth ${oauthConfigArgs}
|
||||
fi
|
||||
'')
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
|
||||
extraConfig = lib.mkAfter ''
|
||||
rewrite ^/user/login$ /user/oauth2/${oauth2-provider-name} last;
|
||||
# FIXME is it needed?
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
'';
|
||||
};
|
||||
|
||||
selfprivacy.auth.clients."${oauthClientID}" = {
|
||||
inherit adminsGroup usersGroup;
|
||||
imageFile = ./icon.svg;
|
||||
displayName = "Forgejo";
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = true;
|
||||
originLanding = "https://${cfg.subdomain}.${sp.domain}/user/login?redirect_to=%2f";
|
||||
originUrl = redirect-uri;
|
||||
clientSystemdUnits = [ "forgejo.service" ];
|
||||
enablePkce = lib.versionAtLeast forgejoPackage.version "8.0";
|
||||
linuxUserOfClient = linuxUserOfService;
|
||||
linuxGroupOfClient = linuxGroupOfService;
|
||||
claimMaps.groups = {
|
||||
joinType = "array";
|
||||
valuesByGroup.${adminsGroup} = [ "admins" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
systemd = {
|
||||
services.forgejo = {
|
||||
unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/gitea";
|
||||
serviceConfig = {
|
||||
Slice = "gitea.slice";
|
||||
};
|
||||
};
|
||||
slices.gitea = {
|
||||
description = "Forgejo service slice";
|
||||
};
|
||||
};
|
||||
}
|
||||
# the following part is active only when enableSso = true
|
||||
(lib.mkIf is-auth-enabled {
|
||||
services.forgejo.settings = {
|
||||
auth.DISABLE_LOGIN_FORM = true;
|
||||
service = {
|
||||
DISABLE_REGISTRATION = cfg.disableRegistration;
|
||||
REQUIRE_SIGNIN_VIEW = cfg.requireSigninView;
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
ENABLE_BASIC_AUTHENTICATION = false;
|
||||
};
|
||||
|
||||
# disallow explore page and access to private repositories, but allow public
|
||||
"service.explore".REQUIRE_SIGNIN_VIEW = true;
|
||||
|
||||
# TODO control via selfprivacy parameter
|
||||
# "service.explore".DISABLE_USERS_PAGE = true;
|
||||
|
||||
oauth2_client = {
|
||||
REDIRECT_URI = redirect-uri;
|
||||
ACCOUNT_LINKING = "auto";
|
||||
ENABLE_AUTO_REGISTRATION = true;
|
||||
OPENID_CONNECT_SCOPES = "email openid profile";
|
||||
};
|
||||
# doesn't work if LDAP auth source is not active!
|
||||
"cron.sync_external_users" = {
|
||||
ENABLED = true;
|
||||
RUN_AT_START = true;
|
||||
NOTICE_ON_SUCCESS = true;
|
||||
};
|
||||
};
|
||||
systemd.services.forgejo = {
|
||||
preStart =
|
||||
let
|
||||
waitForURL = url: maxRetries: delaySec: ''
|
||||
for ((i=1; i<=${toString maxRetries}; i++))
|
||||
do
|
||||
if ${lib.getExe pkgs.curl} -X GET --silent --fail "${url}" > /dev/null
|
||||
then
|
||||
echo "${url} responds to GET HTTP request (attempt #$i)"
|
||||
break
|
||||
else
|
||||
echo "${url} does not respond to GET HTTP request (attempt #$i)"
|
||||
echo sleeping for ${toString delaySec} seconds
|
||||
fi
|
||||
sleep ${toString delaySec}
|
||||
done
|
||||
if [[ "$i" -gt "${toString maxRetries}" ]]
|
||||
then
|
||||
echo "error, max attempts to access "${url}" have been used unsuccessfully!"
|
||||
exit 124
|
||||
fi
|
||||
'';
|
||||
|
||||
exe = lib.getExe config.services.forgejo.package;
|
||||
# FIXME skip-tls-verify, bind-password
|
||||
ldapConfigArgs = ''
|
||||
--name LDAP \
|
||||
--active \
|
||||
--security-protocol LDAPS \
|
||||
--skip-tls-verify \
|
||||
--host '${auth-passthru.ldap-host}' \
|
||||
--port '${toString auth-passthru.ldap-port}' \
|
||||
--user-search-base '${auth-passthru.ldap-base-dn}' \
|
||||
--user-filter '(&(class=person)(memberof=${usersGroup})(name=%s))' \
|
||||
--admin-filter '(&(class=person)(memberof=${adminsGroup})' \
|
||||
--username-attribute name \
|
||||
--firstname-attribute name \
|
||||
--surname-attribute displayname \
|
||||
--email-attribute mail \
|
||||
--public-ssh-key-attribute sshPublicKey \
|
||||
--bind-dn 'dn=token' \
|
||||
--bind-password "$(< ${serviceAccountTokenFP})" \
|
||||
--synchronize-users
|
||||
'';
|
||||
oauthConfigArgs = ''
|
||||
--name "${oauth2-provider-name}" \
|
||||
--provider openidConnect \
|
||||
--key forgejo \
|
||||
--secret "$(< ${oauthClientSecretFP})" \
|
||||
--group-claim-name groups \
|
||||
--admin-group admins \
|
||||
--auto-discover-url '${oauthDiscoveryURL}'
|
||||
'';
|
||||
in
|
||||
lib.mkMerge [
|
||||
(waitForURL oauthDiscoveryURL 10 10)
|
||||
(lib.mkAfter ''
|
||||
set -o xtrace
|
||||
|
||||
# Check if LDAP is already configured
|
||||
ldap_line="$(${exe} admin auth list | grep LDAP | head -n 1)"
|
||||
|
||||
if [[ -n "$ldap_line" ]]; then
|
||||
# update ldap config
|
||||
id="$(echo "$ldap_line" | ${pkgs.gawk}/bin/awk '{print $1}')"
|
||||
${exe} admin auth update-ldap --id "$id" ${ldapConfigArgs}
|
||||
else
|
||||
# initially configure ldap
|
||||
${exe} admin auth add-ldap ${ldapConfigArgs}
|
||||
fi
|
||||
|
||||
oauth_line="$(${exe} admin auth list | grep "${oauth2-provider-name}" | head -n 1)"
|
||||
if [[ -n "$oauth_line" ]]; then
|
||||
id="$(echo "$oauth_line" | ${pkgs.gawk}/bin/awk '{print $1}')"
|
||||
${exe} admin auth update-oauth --id "$id" ${oauthConfigArgs}
|
||||
else
|
||||
${exe} admin auth add-oauth ${oauthConfigArgs}
|
||||
fi
|
||||
'')
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
|
||||
extraConfig = lib.mkAfter ''
|
||||
rewrite ^/user/login$ /user/oauth2/${oauth2-provider-name} last;
|
||||
# FIXME is it needed?
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
'';
|
||||
};
|
||||
|
||||
selfprivacy.auth.clients."${oauthClientID}" = {
|
||||
inherit adminsGroup usersGroup;
|
||||
imageFile = ./icon.svg;
|
||||
displayName = "Forgejo";
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = true;
|
||||
originLanding =
|
||||
"https://${cfg.subdomain}.${sp.domain}/user/login?redirect_to=%2f";
|
||||
originUrl = redirect-uri;
|
||||
clientSystemdUnits = [ "forgejo.service" ];
|
||||
enablePkce = lib.versionAtLeast forgejoPackage.version "8.0";
|
||||
linuxUserOfClient = linuxUserOfService;
|
||||
linuxGroupOfClient = linuxGroupOfService;
|
||||
claimMaps.groups = {
|
||||
joinType = "array";
|
||||
valuesByGroup.${adminsGroup} = [ "admins" ];
|
||||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
})
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@@ -1,33 +1,36 @@
|
||||
{
|
||||
description = "PoC SP module for Jitsi Meet video conferences server";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "jitsi-meet";
|
||||
name = "JitsiMeet";
|
||||
description = "Jitsi Meet is a free and open-source video conferencing solution.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = false;
|
||||
backupDescription = "Secrets that are used to encrypt the communication.";
|
||||
systemdServices = [
|
||||
"prosody.service"
|
||||
"jitsi-videobridge2.service"
|
||||
"jicofo.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/jitsi-meet"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.asl20
|
||||
];
|
||||
homepage = "https://jitsi.org/meet";
|
||||
sourcePage = "https://github.com/jitsi/jitsi-meet";
|
||||
supportLevel = "normal";
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "jitsi-meet";
|
||||
name = "JitsiMeet";
|
||||
description = "Jitsi Meet is a free and open-source video conferencing solution.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = false;
|
||||
backupDescription = "Secrets that are used to encrypt the communication.";
|
||||
systemdServices = [
|
||||
"prosody.service"
|
||||
"jitsi-videobridge2.service"
|
||||
"jicofo.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/jitsi-meet"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.asl20
|
||||
];
|
||||
homepage = "https://jitsi.org/meet";
|
||||
sourcePage = "https://github.com/jitsi/jitsi-meet";
|
||||
supportLevel = "normal";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -5,37 +5,43 @@ let
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.jitsi-meet = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable JitsiMeet";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable JitsiMeet";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
subdomain = (lib.mkOption {
|
||||
default = "meet";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
}) // {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
subdomain =
|
||||
(lib.mkOption {
|
||||
default = "meet";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
appName = (lib.mkOption {
|
||||
default = "Jitsi Meet";
|
||||
type = lib.types.str;
|
||||
description = "The name displayed in the web interface";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 1;
|
||||
appName =
|
||||
(lib.mkOption {
|
||||
default = "Jitsi Meet";
|
||||
type = lib.types.str;
|
||||
description = "The name displayed in the web interface";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
@@ -43,7 +49,9 @@ in
|
||||
(_: prev: {
|
||||
# We disable E2E for clients below
|
||||
jitsi-meet = prev.jitsi-meet.overrideAttrs (old: {
|
||||
meta = old.meta // { knownVulnerabilities = [ ]; };
|
||||
meta = old.meta // {
|
||||
knownVulnerabilities = [ ];
|
||||
};
|
||||
});
|
||||
})
|
||||
];
|
||||
|
@@ -1,36 +1,39 @@
|
||||
{
|
||||
description = "PoC SP module for Prometheus-based monitoring";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "monitoring";
|
||||
name = "Prometheus";
|
||||
description = "Prometheus is used for resource monitoring and alerts.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = true;
|
||||
canBeBackedUp = false;
|
||||
backupDescription = "Backups are not available for Prometheus.";
|
||||
systemdServices = [
|
||||
"prometheus.service"
|
||||
];
|
||||
ownedFolders = [
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
path = "/var/lib/prometheus";
|
||||
owner = "prometheus";
|
||||
group = "prometheus";
|
||||
}
|
||||
];
|
||||
license = [
|
||||
lib.licenses.asl20
|
||||
];
|
||||
homepage = "https://prometheus.io/";
|
||||
sourcePage = "https://prometheus.io/";
|
||||
supportLevel = "normal";
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "monitoring";
|
||||
name = "Prometheus";
|
||||
description = "Prometheus is used for resource monitoring and alerts.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = true;
|
||||
canBeBackedUp = false;
|
||||
backupDescription = "Backups are not available for Prometheus.";
|
||||
systemdServices = [
|
||||
"prometheus.service"
|
||||
];
|
||||
ownedFolders = [
|
||||
{
|
||||
path = "/var/lib/prometheus";
|
||||
owner = "prometheus";
|
||||
group = "prometheus";
|
||||
}
|
||||
];
|
||||
license = [
|
||||
lib.licenses.asl20
|
||||
];
|
||||
homepage = "https://prometheus.io/";
|
||||
sourcePage = "https://prometheus.io/";
|
||||
supportLevel = "normal";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -4,23 +4,27 @@ let
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.monitoring = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable monitoring service";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable monitoring service";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
location = (lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Monitoring data location";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "location";
|
||||
location =
|
||||
(lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Monitoring data location";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "location";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
fileSystems = lib.mkIf config.selfprivacy.useBinds {
|
||||
@@ -54,15 +58,19 @@ in
|
||||
scrapeConfigs = [
|
||||
{
|
||||
job_name = "node-exporter";
|
||||
static_configs = [{
|
||||
targets = [ "127.0.0.1:9002" ];
|
||||
}];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:9002" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
job_name = "cadvisor";
|
||||
static_configs = [{
|
||||
targets = [ "127.0.0.1:9003" ];
|
||||
}];
|
||||
static_configs = [
|
||||
{
|
||||
targets = [ "127.0.0.1:9003" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
@@ -1,35 +1,38 @@
|
||||
{
|
||||
description = "PoC SP module for Mumble conferences server";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "mumble";
|
||||
name = "Mumble";
|
||||
description = "Open Source, Low Latency, High Quality Voice Chat.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
showUrl = false;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Mumble server data.";
|
||||
systemdServices = [
|
||||
"murmur.service"
|
||||
];
|
||||
user = "murmur";
|
||||
group = "murmur";
|
||||
folders = [
|
||||
"/var/lib/murmur"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.bsd3
|
||||
];
|
||||
homepage = "https://www.mumble.info";
|
||||
sourcePage = "https://github.com/mumble-voip/mumble";
|
||||
supportLevel = "normal";
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "mumble";
|
||||
name = "Mumble";
|
||||
description = "Open Source, Low Latency, High Quality Voice Chat.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
showUrl = false;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Mumble server data.";
|
||||
systemdServices = [
|
||||
"murmur.service"
|
||||
];
|
||||
user = "murmur";
|
||||
group = "murmur";
|
||||
folders = [
|
||||
"/var/lib/murmur"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.bsd3
|
||||
];
|
||||
homepage = "https://www.mumble.info";
|
||||
sourcePage = "https://github.com/mumble-voip/mumble";
|
||||
supportLevel = "normal";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
domain = config.selfprivacy.domain;
|
||||
sp = config.selfprivacy;
|
||||
@@ -6,55 +11,65 @@ let
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.mumble = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Mumble";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Mumble";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
subdomain = (lib.mkOption {
|
||||
default = "mumble";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
}) // {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
subdomain =
|
||||
(lib.mkOption {
|
||||
default = "mumble";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
location = (lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Location";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "location";
|
||||
location =
|
||||
(lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Location";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "location";
|
||||
};
|
||||
};
|
||||
};
|
||||
appName = (lib.mkOption {
|
||||
default = "SelfPrivacy Mumble Service";
|
||||
type = lib.types.str;
|
||||
description = "The name of your Mumble server";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 1;
|
||||
appName =
|
||||
(lib.mkOption {
|
||||
default = "SelfPrivacy Mumble Service";
|
||||
type = lib.types.str;
|
||||
description = "The name of your Mumble server";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
welcomeText = (lib.mkOption {
|
||||
default = "Welcome to my Mumble server!";
|
||||
type = lib.types.str;
|
||||
description = "Welcome message";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 2;
|
||||
welcomeText =
|
||||
(lib.mkOption {
|
||||
default = "Welcome to my Mumble server!";
|
||||
type = lib.types.str;
|
||||
description = "Welcome message";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "string";
|
||||
weight = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
@@ -79,7 +94,9 @@ in
|
||||
};
|
||||
systemd = {
|
||||
services = {
|
||||
murmur = { serviceConfig.Slice = "mumble.slice"; };
|
||||
murmur = {
|
||||
serviceConfig.Slice = "mumble.slice";
|
||||
};
|
||||
murmur-ensure-folder-ownership = {
|
||||
description = "Ensure murmur folder ownership";
|
||||
before = [ "murmur.service" ];
|
||||
|
@@ -13,8 +13,8 @@ in
|
||||
system.activationScripts.nextcloudSecrets =
|
||||
lib.trivial.warn
|
||||
(
|
||||
"nextcloud service is disabled, " +
|
||||
"${override-config-fp}, ${db-pass-filepath} and ${admin-pass-filepath} will be removed!"
|
||||
"nextcloud service is disabled, "
|
||||
+ "${override-config-fp}, ${db-pass-filepath} and ${admin-pass-filepath} will be removed!"
|
||||
)
|
||||
''
|
||||
rm -f -v ${db-pass-filepath}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
config: rec {
|
||||
sp = config.selfprivacy;
|
||||
domain= sp.domain;
|
||||
domain = sp.domain;
|
||||
secrets-filepath = "/etc/selfprivacy/secrets.json";
|
||||
db-pass-filepath = "/var/lib/nextcloud/db-pass";
|
||||
admin-pass-filepath = "/var/lib/nextcloud/admin-pass";
|
||||
|
@@ -1,38 +1,45 @@
|
||||
{
|
||||
description = "PoC SP module for nextcloud";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = _:
|
||||
{ imports = [ ./module.nix ./cleanup-module.nix ]; };
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "nextcloud";
|
||||
name = "Nextcloud";
|
||||
description = "Nextcloud is a cloud storage service that offers a web interface and a desktop client.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "All the files and other data stored in Nextcloud.";
|
||||
systemdServices = [
|
||||
"phpfpm-nextcloud.service"
|
||||
"redis-nextcloud.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/nextcloud"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Plus
|
||||
];
|
||||
homepage = "https://nextcloud.com/";
|
||||
sourcePage = "https://github.com/nextcloud";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.nextcloud.users";
|
||||
adminGroup = "sp.nextcloud.admins";
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = _: {
|
||||
imports = [
|
||||
./module.nix
|
||||
./cleanup-module.nix
|
||||
];
|
||||
};
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "nextcloud";
|
||||
name = "Nextcloud";
|
||||
description = "Nextcloud is a cloud storage service that offers a web interface and a desktop client.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "All the files and other data stored in Nextcloud.";
|
||||
systemdServices = [
|
||||
"phpfpm-nextcloud.service"
|
||||
"redis-nextcloud.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/nextcloud"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Plus
|
||||
];
|
||||
homepage = "https://nextcloud.com/";
|
||||
sourcePage = "https://github.com/nextcloud";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.nextcloud.users";
|
||||
adminGroup = "sp.nextcloud.admins";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (import ./common.nix config)
|
||||
admin-pass-filepath
|
||||
@@ -27,391 +32,417 @@ let
|
||||
usersGroup = "sp.${oauthClientID}.users";
|
||||
wildcardGroup = "sp.${oauthClientID}.*";
|
||||
|
||||
serviceAccountTokenFP =
|
||||
auth-passthru.mkServiceAccountTokenFP linuxUserOfService;
|
||||
oauthClientSecretFP =
|
||||
auth-passthru.mkOAuth2ClientSecretFP linuxUserOfService;
|
||||
serviceAccountTokenFP = auth-passthru.mkServiceAccountTokenFP linuxUserOfService;
|
||||
oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP linuxUserOfService;
|
||||
|
||||
updater-page-substitute =
|
||||
pkgs.runCommandNoCC "nextcloud-updater-page-substitute" { } ''
|
||||
install -m644 ${./updater.html} -DT $out/index.html
|
||||
'';
|
||||
updater-page-substitute = pkgs.runCommandNoCC "nextcloud-updater-page-substitute" { } ''
|
||||
install -m644 ${./updater.html} -DT $out/index.html
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.nextcloud = with lib; {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Nextcloud";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Nextcloud";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
location = (lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Nextcloud location";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "location";
|
||||
location =
|
||||
(lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Nextcloud location";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "location";
|
||||
};
|
||||
};
|
||||
};
|
||||
subdomain = (lib.mkOption {
|
||||
default = "cloud";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
}) // {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
subdomain =
|
||||
(lib.mkOption {
|
||||
default = "cloud";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
enableImagemagick = (lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable ImageMagick";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 1;
|
||||
enableImagemagick =
|
||||
(lib.mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable ImageMagick";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
enableSso = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Single Sign-On";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 2;
|
||||
enableSso =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Single Sign-On";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 2;
|
||||
};
|
||||
};
|
||||
};
|
||||
enableSambaFeatures = (lib.mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable support for Samba/CIFS features";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 3;
|
||||
enableSambaFeatures =
|
||||
(lib.mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable support for Samba/CIFS features";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
debug = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable debug logging";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 4;
|
||||
debug =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable debug logging";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 4;
|
||||
};
|
||||
};
|
||||
};
|
||||
disableMaintenanceModeAtStart = (lib.mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable maintenance mode at Nextcloud service startup";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 5;
|
||||
disableMaintenanceModeAtStart =
|
||||
(lib.mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Disable maintenance mode at Nextcloud service startup";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 5;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# config = lib.mkIf sp.modules.nextcloud.enable
|
||||
config = lib.mkIf sp.modules.nextcloud.enable (lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.enableSso -> sp.sso.enable;
|
||||
message =
|
||||
"SSO cannot be enabled for Nextcloud when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
"/var/lib/nextcloud" = {
|
||||
device = "/volumes/${cfg.location}/nextcloud";
|
||||
options = [
|
||||
"bind"
|
||||
"x-systemd.required-by=nextcloud-setup.service"
|
||||
"x-systemd.required-by=nextcloud-secrets.service"
|
||||
"x-systemd.before=nextcloud-setup.service"
|
||||
"x-systemd.before=nextcloud-secrets.service"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# for ExecStartPost script to have access to /run/keys/*
|
||||
users.groups.keys.members =
|
||||
lib.mkIf is-auth-enabled [ linuxUserOfService ];
|
||||
|
||||
# not needed, due to turnOffCertCheck=1 in used_ldap
|
||||
# users.groups.${config.security.acme.certs.${domain}.group}.members =
|
||||
# [ config.services.phpfpm.pools.nextcloud.user ];
|
||||
|
||||
systemd = {
|
||||
services = {
|
||||
phpfpm-nextcloud.serviceConfig.Slice = lib.mkForce "nextcloud.slice";
|
||||
nextcloud-setup = {
|
||||
serviceConfig.Slice = "nextcloud.slice";
|
||||
serviceConfig.Group = config.services.phpfpm.pools.nextcloud.group;
|
||||
config = lib.mkIf sp.modules.nextcloud.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.enableSso -> sp.sso.enable;
|
||||
message = "SSO cannot be enabled for Nextcloud when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
"/var/lib/nextcloud" = {
|
||||
device = "/volumes/${cfg.location}/nextcloud";
|
||||
options = [
|
||||
"bind"
|
||||
"x-systemd.required-by=nextcloud-setup.service"
|
||||
"x-systemd.required-by=nextcloud-secrets.service"
|
||||
"x-systemd.before=nextcloud-setup.service"
|
||||
"x-systemd.before=nextcloud-secrets.service"
|
||||
];
|
||||
};
|
||||
nextcloud-cron.serviceConfig.Slice = "nextcloud.slice";
|
||||
nextcloud-update-db.serviceConfig.Slice = "nextcloud.slice";
|
||||
nextcloud-update-plugins.serviceConfig.Slice = "nextcloud.slice";
|
||||
nextcloud-secrets = {
|
||||
before = [ "nextcloud-setup.service" ];
|
||||
requiredBy = [ "nextcloud-setup.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
path = with pkgs; [ coreutils jq ];
|
||||
script = ''
|
||||
databasePassword=$(jq -re '.modules.nextcloud.databasePassword' ${secrets-filepath})
|
||||
adminPassword=$(jq -re '.modules.nextcloud.adminPassword' ${secrets-filepath})
|
||||
};
|
||||
|
||||
install -C -m 0440 -o nextcloud -g nextcloud -DT \
|
||||
<(printf "%s\n" "$databasePassword") \
|
||||
${db-pass-filepath}
|
||||
# for ExecStartPost script to have access to /run/keys/*
|
||||
users.groups.keys.members = lib.mkIf is-auth-enabled [ linuxUserOfService ];
|
||||
|
||||
install -C -m 0440 -o nextcloud -g nextcloud -DT \
|
||||
<(printf "%s\n" "$adminPassword") \
|
||||
${admin-pass-filepath}
|
||||
# not needed, due to turnOffCertCheck=1 in used_ldap
|
||||
# users.groups.${config.security.acme.certs.${domain}.group}.members =
|
||||
# [ config.services.phpfpm.pools.nextcloud.user ];
|
||||
|
||||
systemd = {
|
||||
services = {
|
||||
phpfpm-nextcloud.serviceConfig.Slice = lib.mkForce "nextcloud.slice";
|
||||
nextcloud-setup = {
|
||||
serviceConfig.Slice = "nextcloud.slice";
|
||||
serviceConfig.Group = config.services.phpfpm.pools.nextcloud.group;
|
||||
};
|
||||
nextcloud-cron.serviceConfig.Slice = "nextcloud.slice";
|
||||
nextcloud-update-db.serviceConfig.Slice = "nextcloud.slice";
|
||||
nextcloud-update-plugins.serviceConfig.Slice = "nextcloud.slice";
|
||||
nextcloud-secrets = {
|
||||
before = [ "nextcloud-setup.service" ];
|
||||
requiredBy = [ "nextcloud-setup.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
path = with pkgs; [
|
||||
coreutils
|
||||
jq
|
||||
];
|
||||
script = ''
|
||||
databasePassword=$(jq -re '.modules.nextcloud.databasePassword' ${secrets-filepath})
|
||||
adminPassword=$(jq -re '.modules.nextcloud.adminPassword' ${secrets-filepath})
|
||||
|
||||
install -C -m 0440 -o nextcloud -g nextcloud -DT \
|
||||
<(printf "%s\n" "$databasePassword") \
|
||||
${db-pass-filepath}
|
||||
|
||||
install -C -m 0440 -o nextcloud -g nextcloud -DT \
|
||||
<(printf "%s\n" "$adminPassword") \
|
||||
${admin-pass-filepath}
|
||||
'';
|
||||
};
|
||||
};
|
||||
slices.nextcloud = {
|
||||
description = "Nextcloud service slice";
|
||||
};
|
||||
};
|
||||
services.nextcloud = {
|
||||
enable = true;
|
||||
package = pkgs.nextcloud30;
|
||||
inherit hostName;
|
||||
|
||||
# Use HTTPS for links
|
||||
https = true;
|
||||
|
||||
# auto-update Nextcloud Apps
|
||||
autoUpdateApps.enable = true;
|
||||
# set what time makes sense for you
|
||||
autoUpdateApps.startAt = "05:00:00";
|
||||
|
||||
phpOptions.display_errors = "Off";
|
||||
phpOptions."opcache.interned_strings_buffer" = "32";
|
||||
|
||||
configureRedis = true;
|
||||
|
||||
settings =
|
||||
{
|
||||
# further forces Nextcloud to use HTTPS
|
||||
overwriteprotocol = "https";
|
||||
}
|
||||
// lib.attrsets.optionalAttrs is-auth-enabled {
|
||||
loglevel = 0;
|
||||
# log_type = "file";
|
||||
social_login_auto_redirect = false;
|
||||
|
||||
allow_local_remote_servers = true;
|
||||
allow_user_to_change_display_name = false;
|
||||
lost_password_link = "disabled";
|
||||
allow_multiple_user_backends = false;
|
||||
|
||||
updatechecker = false; # nixpkgs handles updates for us, update via web ui will fail on nixos.
|
||||
|
||||
user_oidc = {
|
||||
single_logout = true;
|
||||
use_pkce = true;
|
||||
auto_provision = true;
|
||||
soft_auto_provision = true;
|
||||
disable_account_creation = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
dbtype = "sqlite";
|
||||
dbuser = "nextcloud";
|
||||
dbname = "nextcloud";
|
||||
dbpassFile = db-pass-filepath;
|
||||
# TODO review whether admin user is needed at all - admin group works
|
||||
adminpassFile = admin-pass-filepath;
|
||||
adminuser = "admin";
|
||||
};
|
||||
};
|
||||
services.nginx.virtualHosts.${hostName} = {
|
||||
useACMEHost = sp.domain;
|
||||
forceSSL = true;
|
||||
#locations."/".extraConfig = lib.mkIf is-auth-enabled ''
|
||||
# # FIXME does not work
|
||||
# rewrite ^/login$ /apps/user_oidc/login/1 last;
|
||||
#'';
|
||||
# show an error instead of a blank page on Nextcloud PHP/FastCGI error
|
||||
locations."~ \\.php(?:$|/)".extraConfig = ''
|
||||
error_page 500 502 503 504 ${pkgs.nginx}/html/50x.html;
|
||||
'';
|
||||
locations."^~ /updater/" = {
|
||||
alias = updater-page-substitute + "/";
|
||||
extraConfig = ''
|
||||
error_page 410 /index.html;
|
||||
# otherwise, nginx returns 405 for POST requests to static content
|
||||
error_page 405 =200 $uri;
|
||||
'';
|
||||
};
|
||||
};
|
||||
slices.nextcloud = {
|
||||
description = "Nextcloud service slice";
|
||||
}
|
||||
# enables samba features when requested
|
||||
(lib.mkIf cfg.enableSambaFeatures {
|
||||
# only apply cifs-utils package to this module
|
||||
services.phpfpm.pools.nextcloud.phpEnv.PATH =
|
||||
lib.mkForce "${pkgs.samba}/bin:${pkgs.cifs-utils}/bin:/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
|
||||
systemd.services.nextcloud-cron.path = [
|
||||
pkgs.samba
|
||||
pkgs.cifs-utils
|
||||
];
|
||||
})
|
||||
# the following part is active only when "auth" module is enabled
|
||||
(lib.mkIf is-auth-enabled {
|
||||
systemd.services.nextcloud-setup = {
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "60";
|
||||
};
|
||||
path = [ pkgs.jq ];
|
||||
script = lib.mkMerge [
|
||||
(lib.strings.optionalString cfg.disableMaintenanceModeAtStart (
|
||||
lib.mkBefore "${occ} maintenance:mode --no-interaction --off"
|
||||
))
|
||||
''
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
${lib.strings.optionalString cfg.debug "set -o xtrace"}
|
||||
|
||||
${occ} app:disable logreader
|
||||
|
||||
${occ} app:install user_ldap || :
|
||||
${occ} app:enable user_ldap
|
||||
|
||||
# The following code tries to match an existing config or creates a new one.
|
||||
# The criteria for matching is the ldapHost value.
|
||||
|
||||
# remove broken link after previous nextcloud (un)installation
|
||||
[[ ! -f "${override-config-fp}" && -L "${override-config-fp}" ]] && \
|
||||
rm -v "${override-config-fp}"
|
||||
|
||||
ALL_CONFIG="$(${occ} ldap:show-config --output=json)"
|
||||
|
||||
MATCHING_CONFIG_IDs="$(jq '[to_entries[] | select(.value.ldapHost=="${ldap_scheme_and_host}") | .key]' <<<"$ALL_CONFIG")"
|
||||
if [[ $(jq 'length' <<<"$MATCHING_CONFIG_IDs") > 0 ]]; then
|
||||
CONFIG_ID="$(jq --raw-output '.[0]' <<<"$MATCHING_CONFIG_IDs")"
|
||||
else
|
||||
CONFIG_ID="$(${occ} ldap:create-empty-config --only-print-prefix)"
|
||||
fi
|
||||
|
||||
echo "Using configId $CONFIG_ID"
|
||||
|
||||
# The following CLI commands follow
|
||||
# https://github.com/lldap/lldap/blob/main/example_configs/nextcloud.md#nextcloud-config--the-cli-way
|
||||
|
||||
# StartTLS is not supported in Kanidm due to security risks, whereas
|
||||
# user_ldap doesn't support SASL. Importing certificate doesn't
|
||||
# help:
|
||||
# ${occ} security:certificates:import "${config.security.acme.certs.${domain}.directory}/cert.pem"
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'turnOffCertCheck' '1'
|
||||
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapHost' '${ldap_scheme_and_host}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapPort' '${toString auth-passthru.ldap-port}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapAgentName' 'dn=token'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapAgentPassword' "$(<${serviceAccountTokenFP})"
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapBase' '${auth-passthru.ldap-base-dn}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapBaseGroups' '${auth-passthru.ldap-base-dn}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapBaseUsers' '${auth-passthru.ldap-base-dn}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapEmailAttribute' 'mail'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupFilter' \
|
||||
'(&(class=group)(${wildcardGroup})'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupFilterGroups' \
|
||||
'(&(class=group)(${wildcardGroup}))'
|
||||
# ${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupFilterObjectclass' \
|
||||
# 'groupOfUniqueNames'
|
||||
# ${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupMemberAssocAttr' \
|
||||
# 'uniqueMember'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapLoginFilter' \
|
||||
'(&(class=person)(memberof=${usersGroup})(uid=%uid))'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapLoginFilterAttributes' \
|
||||
'uid'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserDisplayName' \
|
||||
'displayname'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserFilter' \
|
||||
'(&(class=person)(memberof=${usersGroup})(name=%s))'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserFilterMode' \
|
||||
'1'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserFilterObjectclass' \
|
||||
'person'
|
||||
|
||||
${occ} ldap:test-config -- "$CONFIG_ID"
|
||||
|
||||
# delete all configs except "$CONFIG_ID"
|
||||
for configid in $(jq --raw-output "keys[] | select(. != \"$CONFIG_ID\")" <<<"$ALL_CONFIG"); do
|
||||
echo "Deactivating $configid"
|
||||
${occ} ldap:set-config "$configid" 'ldapConfigurationActive' '0'
|
||||
echo "Deactivated $configid"
|
||||
echo "Deleting $configid"
|
||||
${occ} ldap:delete-config "$configid"
|
||||
echo "Deleted $configid"
|
||||
done
|
||||
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapConfigurationActive' '1'
|
||||
|
||||
############################################################################
|
||||
# OIDC app
|
||||
############################################################################
|
||||
${occ} app:install user_oidc || :
|
||||
${occ} app:enable user_oidc
|
||||
|
||||
${occ} user_oidc:provider ${auth-passthru.oauth2-provider-name} \
|
||||
--clientid="${oauthClientID}" \
|
||||
--clientsecret="$(<${oauthClientSecretFP})" \
|
||||
--discoveryuri="${auth-passthru.oauth2-discovery-url "nextcloud"}" \
|
||||
--unique-uid=0 \
|
||||
--scope="email openid profile" \
|
||||
--mapping-uid=preferred_username \
|
||||
--no-interaction \
|
||||
--mapping-groups=groups \
|
||||
--group-provisioning=1 \
|
||||
-vvv
|
||||
|
||||
''
|
||||
(lib.optionalString deleteNextcloudAdmin ''
|
||||
if [[ ! -f /var/lib/nextcloud/.admin-user-deleted ]]; then
|
||||
${occ} user:delete admin
|
||||
touch /var/lib/nextcloud/.admin-user-deleted
|
||||
fi
|
||||
'')
|
||||
];
|
||||
};
|
||||
};
|
||||
services.nextcloud = {
|
||||
enable = true;
|
||||
package = pkgs.nextcloud30;
|
||||
inherit hostName;
|
||||
|
||||
# Use HTTPS for links
|
||||
https = true;
|
||||
|
||||
# auto-update Nextcloud Apps
|
||||
autoUpdateApps.enable = true;
|
||||
# set what time makes sense for you
|
||||
autoUpdateApps.startAt = "05:00:00";
|
||||
|
||||
phpOptions.display_errors = "Off";
|
||||
phpOptions."opcache.interned_strings_buffer" = "32";
|
||||
|
||||
configureRedis = true;
|
||||
|
||||
settings = {
|
||||
# further forces Nextcloud to use HTTPS
|
||||
overwriteprotocol = "https";
|
||||
} // lib.attrsets.optionalAttrs is-auth-enabled {
|
||||
loglevel = 0;
|
||||
# log_type = "file";
|
||||
social_login_auto_redirect = false;
|
||||
|
||||
allow_local_remote_servers = true;
|
||||
allow_user_to_change_display_name = false;
|
||||
lost_password_link = "disabled";
|
||||
allow_multiple_user_backends = false;
|
||||
|
||||
updatechecker = false; # nixpkgs handles updates for us, update via web ui will fail on nixos.
|
||||
|
||||
user_oidc = {
|
||||
single_logout = true;
|
||||
use_pkce = true;
|
||||
auto_provision = true;
|
||||
soft_auto_provision = true;
|
||||
disable_account_creation = false;
|
||||
selfprivacy.auth.clients."${oauthClientID}" = {
|
||||
inherit adminsGroup usersGroup;
|
||||
imageFile = ./icon.svg;
|
||||
displayName = "Nextcloud";
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = true;
|
||||
originUrl = "https://${cfg.subdomain}.${domain}/apps/user_oidc/code";
|
||||
originLanding = "https://${cfg.subdomain}.${domain}/apps/user_oidc/login/1";
|
||||
useShortPreferredUsername = true;
|
||||
clientSystemdUnits = [
|
||||
"nextcloud-setup.service"
|
||||
"phpfpm-nextcloud.service"
|
||||
];
|
||||
enablePkce = true;
|
||||
linuxUserOfClient = linuxUserOfService;
|
||||
linuxGroupOfClient = linuxGroupOfService;
|
||||
scopeMaps.${usersGroup} = [
|
||||
"email"
|
||||
"openid"
|
||||
"profile"
|
||||
];
|
||||
claimMaps.groups = {
|
||||
joinType = "array";
|
||||
valuesByGroup.${adminsGroup} = [ "admin" ];
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
dbtype = "sqlite";
|
||||
dbuser = "nextcloud";
|
||||
dbname = "nextcloud";
|
||||
dbpassFile = db-pass-filepath;
|
||||
# TODO review whether admin user is needed at all - admin group works
|
||||
adminpassFile = admin-pass-filepath;
|
||||
adminuser = "admin";
|
||||
};
|
||||
};
|
||||
services.nginx.virtualHosts.${hostName} = {
|
||||
useACMEHost = sp.domain;
|
||||
forceSSL = true;
|
||||
#locations."/".extraConfig = lib.mkIf is-auth-enabled ''
|
||||
# # FIXME does not work
|
||||
# rewrite ^/login$ /apps/user_oidc/login/1 last;
|
||||
#'';
|
||||
# show an error instead of a blank page on Nextcloud PHP/FastCGI error
|
||||
locations."~ \\.php(?:$|/)".extraConfig = ''
|
||||
error_page 500 502 503 504 ${pkgs.nginx}/html/50x.html;
|
||||
'';
|
||||
locations."^~ /updater/" = {
|
||||
alias = updater-page-substitute + "/";
|
||||
extraConfig = ''
|
||||
error_page 410 /index.html;
|
||||
# otherwise, nginx returns 405 for POST requests to static content
|
||||
error_page 405 =200 $uri;
|
||||
})
|
||||
(lib.mkIf (!is-auth-enabled) {
|
||||
systemd.services.nextcloud-setup = {
|
||||
script = ''
|
||||
${occ} app:disable logreader
|
||||
${occ} app:disable user_oidc
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
# enables samba features when requested
|
||||
(lib.mkIf cfg.enableSambaFeatures {
|
||||
# only apply cifs-utils package to this module
|
||||
services.phpfpm.pools.nextcloud.phpEnv.PATH =
|
||||
lib.mkForce "${pkgs.samba}/bin:${pkgs.cifs-utils}/bin:/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
|
||||
systemd.services.nextcloud-cron.path = [ pkgs.samba pkgs.cifs-utils ];
|
||||
})
|
||||
# the following part is active only when "auth" module is enabled
|
||||
(lib.mkIf is-auth-enabled {
|
||||
systemd.services.nextcloud-setup = {
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "60";
|
||||
};
|
||||
path = [ pkgs.jq ];
|
||||
script = lib.mkMerge [
|
||||
(lib.strings.optionalString cfg.disableMaintenanceModeAtStart (
|
||||
lib.mkBefore "${occ} maintenance:mode --no-interaction --off"
|
||||
))
|
||||
''
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
${lib.strings.optionalString cfg.debug "set -o xtrace"}
|
||||
|
||||
${occ} app:disable logreader
|
||||
|
||||
${occ} app:install user_ldap || :
|
||||
${occ} app:enable user_ldap
|
||||
|
||||
# The following code tries to match an existing config or creates a new one.
|
||||
# The criteria for matching is the ldapHost value.
|
||||
|
||||
# remove broken link after previous nextcloud (un)installation
|
||||
[[ ! -f "${override-config-fp}" && -L "${override-config-fp}" ]] && \
|
||||
rm -v "${override-config-fp}"
|
||||
|
||||
ALL_CONFIG="$(${occ} ldap:show-config --output=json)"
|
||||
|
||||
MATCHING_CONFIG_IDs="$(jq '[to_entries[] | select(.value.ldapHost=="${ldap_scheme_and_host}") | .key]' <<<"$ALL_CONFIG")"
|
||||
if [[ $(jq 'length' <<<"$MATCHING_CONFIG_IDs") > 0 ]]; then
|
||||
CONFIG_ID="$(jq --raw-output '.[0]' <<<"$MATCHING_CONFIG_IDs")"
|
||||
else
|
||||
CONFIG_ID="$(${occ} ldap:create-empty-config --only-print-prefix)"
|
||||
fi
|
||||
|
||||
echo "Using configId $CONFIG_ID"
|
||||
|
||||
# The following CLI commands follow
|
||||
# https://github.com/lldap/lldap/blob/main/example_configs/nextcloud.md#nextcloud-config--the-cli-way
|
||||
|
||||
# StartTLS is not supported in Kanidm due to security risks, whereas
|
||||
# user_ldap doesn't support SASL. Importing certificate doesn't
|
||||
# help:
|
||||
# ${occ} security:certificates:import "${config.security.acme.certs.${domain}.directory}/cert.pem"
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'turnOffCertCheck' '1'
|
||||
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapHost' '${ldap_scheme_and_host}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapPort' '${toString auth-passthru.ldap-port}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapAgentName' 'dn=token'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapAgentPassword' "$(<${serviceAccountTokenFP})"
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapBase' '${auth-passthru.ldap-base-dn}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapBaseGroups' '${auth-passthru.ldap-base-dn}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapBaseUsers' '${auth-passthru.ldap-base-dn}'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapEmailAttribute' 'mail'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupFilter' \
|
||||
'(&(class=group)(${wildcardGroup})'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupFilterGroups' \
|
||||
'(&(class=group)(${wildcardGroup}))'
|
||||
# ${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupFilterObjectclass' \
|
||||
# 'groupOfUniqueNames'
|
||||
# ${occ} ldap:set-config "$CONFIG_ID" 'ldapGroupMemberAssocAttr' \
|
||||
# 'uniqueMember'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapLoginFilter' \
|
||||
'(&(class=person)(memberof=${usersGroup})(uid=%uid))'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapLoginFilterAttributes' \
|
||||
'uid'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserDisplayName' \
|
||||
'displayname'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserFilter' \
|
||||
'(&(class=person)(memberof=${usersGroup})(name=%s))'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserFilterMode' \
|
||||
'1'
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapUserFilterObjectclass' \
|
||||
'person'
|
||||
|
||||
${occ} ldap:test-config -- "$CONFIG_ID"
|
||||
|
||||
# delete all configs except "$CONFIG_ID"
|
||||
for configid in $(jq --raw-output "keys[] | select(. != \"$CONFIG_ID\")" <<<"$ALL_CONFIG"); do
|
||||
echo "Deactivating $configid"
|
||||
${occ} ldap:set-config "$configid" 'ldapConfigurationActive' '0'
|
||||
echo "Deactivated $configid"
|
||||
echo "Deleting $configid"
|
||||
${occ} ldap:delete-config "$configid"
|
||||
echo "Deleted $configid"
|
||||
done
|
||||
|
||||
${occ} ldap:set-config "$CONFIG_ID" 'ldapConfigurationActive' '1'
|
||||
|
||||
############################################################################
|
||||
# OIDC app
|
||||
############################################################################
|
||||
${occ} app:install user_oidc || :
|
||||
${occ} app:enable user_oidc
|
||||
|
||||
${occ} user_oidc:provider ${auth-passthru.oauth2-provider-name} \
|
||||
--clientid="${oauthClientID}" \
|
||||
--clientsecret="$(<${oauthClientSecretFP})" \
|
||||
--discoveryuri="${auth-passthru.oauth2-discovery-url "nextcloud"}" \
|
||||
--unique-uid=0 \
|
||||
--scope="email openid profile" \
|
||||
--mapping-uid=preferred_username \
|
||||
--no-interaction \
|
||||
--mapping-groups=groups \
|
||||
--group-provisioning=1 \
|
||||
-vvv
|
||||
|
||||
''
|
||||
(lib.optionalString deleteNextcloudAdmin ''
|
||||
if [[ ! -f /var/lib/nextcloud/.admin-user-deleted ]]; then
|
||||
${occ} user:delete admin
|
||||
touch /var/lib/nextcloud/.admin-user-deleted
|
||||
fi
|
||||
'')
|
||||
];
|
||||
};
|
||||
selfprivacy.auth.clients."${oauthClientID}" = {
|
||||
inherit adminsGroup usersGroup;
|
||||
imageFile = ./icon.svg;
|
||||
displayName = "Nextcloud";
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = true;
|
||||
originUrl = "https://${cfg.subdomain}.${domain}/apps/user_oidc/code";
|
||||
originLanding =
|
||||
"https://${cfg.subdomain}.${domain}/apps/user_oidc/login/1";
|
||||
useShortPreferredUsername = true;
|
||||
clientSystemdUnits =
|
||||
[ "nextcloud-setup.service" "phpfpm-nextcloud.service" ];
|
||||
enablePkce = true;
|
||||
linuxUserOfClient = linuxUserOfService;
|
||||
linuxGroupOfClient = linuxGroupOfService;
|
||||
scopeMaps.${usersGroup} = [ "email" "openid" "profile" ];
|
||||
claimMaps.groups = {
|
||||
joinType = "array";
|
||||
valuesByGroup.${adminsGroup} = [ "admin" ];
|
||||
};
|
||||
};
|
||||
})
|
||||
(lib.mkIf (! is-auth-enabled) {
|
||||
systemd.services.nextcloud-setup = {
|
||||
script = ''
|
||||
${occ} app:disable logreader
|
||||
${occ} app:disable user_oidc
|
||||
'';
|
||||
};
|
||||
})
|
||||
]);
|
||||
})
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@@ -1,29 +1,32 @@
|
||||
{
|
||||
description = "PoC SP module for OpenConnect VPN server (ocserv)";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "ocserv";
|
||||
name = "OpenConnect VPN";
|
||||
description = "OpenConnect VPN to connect your devices and access the internet.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = false;
|
||||
canBeBackedUp = false;
|
||||
backupDescription = "Backups are not available for OpenConnect VPN.";
|
||||
systemdServices = [
|
||||
"ocserv.service"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.gpl2Plus
|
||||
];
|
||||
homepage = "https://gitlab.com/openconnect/ocserv";
|
||||
sourcePage = "https://gitlab.com/openconnect/ocserv";
|
||||
supportLevel = "deprecated";
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "ocserv";
|
||||
name = "OpenConnect VPN";
|
||||
description = "OpenConnect VPN to connect your devices and access the internet.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = false;
|
||||
canBeBackedUp = false;
|
||||
backupDescription = "Backups are not available for OpenConnect VPN.";
|
||||
systemdServices = [
|
||||
"ocserv.service"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.gpl2Plus
|
||||
];
|
||||
homepage = "https://gitlab.com/openconnect/ocserv";
|
||||
sourcePage = "https://gitlab.com/openconnect/ocserv";
|
||||
supportLevel = "deprecated";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -7,15 +7,17 @@ let
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.ocserv = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
@@ -66,7 +68,10 @@ in
|
||||
systemd = {
|
||||
services = {
|
||||
ocserv = {
|
||||
unitConfig.ConditionPathExists = [ cert key ];
|
||||
unitConfig.ConditionPathExists = [
|
||||
cert
|
||||
key
|
||||
];
|
||||
serviceConfig.Slice = "ocserv.slice";
|
||||
};
|
||||
};
|
||||
|
@@ -5,13 +5,8 @@ in
|
||||
# FIXME do we really want to delete passwords on module deactivation!?
|
||||
{
|
||||
config = lib.mkIf (!sp.modules.pleroma.enable) {
|
||||
system.activationScripts.pleroma =
|
||||
lib.trivial.warn
|
||||
(
|
||||
"pleroma service is disabled, ${secrets-exs} will be removed!"
|
||||
)
|
||||
''
|
||||
rm -f -v ${secrets-exs}
|
||||
'';
|
||||
system.activationScripts.pleroma = lib.trivial.warn ("pleroma service is disabled, ${secrets-exs} will be removed!") ''
|
||||
rm -f -v ${secrets-exs}
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
config:
|
||||
{
|
||||
config: {
|
||||
sp = config.selfprivacy;
|
||||
secrets-exs = "/var/lib/pleroma/secrets.exs";
|
||||
}
|
||||
|
@@ -1,35 +1,38 @@
|
||||
{
|
||||
description = "PoC SP module for Pleroma lightweight fediverse server";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "pleroma";
|
||||
name = "Pleroma";
|
||||
description = "Pleroma is a microblogging service that offers a web interface and a desktop client.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Your Pleroma accounts, posts and media.";
|
||||
systemdServices = [
|
||||
"pleroma.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/pleroma"
|
||||
];
|
||||
postgreDatabases = [
|
||||
"pleroma"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Only
|
||||
];
|
||||
homepage = "https://pleroma.social/";
|
||||
sourcePage = "https://git.pleroma.social/pleroma/pleroma";
|
||||
supportLevel = "deprecated";
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "pleroma";
|
||||
name = "Pleroma";
|
||||
description = "Pleroma is a microblogging service that offers a web interface and a desktop client.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Your Pleroma accounts, posts and media.";
|
||||
systemdServices = [
|
||||
"pleroma.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/pleroma"
|
||||
];
|
||||
postgreDatabases = [
|
||||
"pleroma"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Only
|
||||
];
|
||||
homepage = "https://pleroma.social/";
|
||||
sourcePage = "https://git.pleroma.social/pleroma/pleroma";
|
||||
supportLevel = "deprecated";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
secrets-filepath = "/etc/selfprivacy/secrets.json";
|
||||
cfg = config.selfprivacy.modules.pleroma;
|
||||
@@ -6,35 +11,41 @@ let
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.pleroma = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
location = (lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Location";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "location";
|
||||
location =
|
||||
(lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Location";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "location";
|
||||
};
|
||||
};
|
||||
};
|
||||
subdomain = (lib.mkOption {
|
||||
default = "social";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
}) // {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
subdomain =
|
||||
(lib.mkOption {
|
||||
default = "social";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
@@ -55,10 +66,9 @@ in
|
||||
user = "pleroma";
|
||||
group = "pleroma";
|
||||
configs = [
|
||||
(builtins.replaceStrings
|
||||
[ "$DOMAIN" "$LUSER" ]
|
||||
[ sp.domain sp.username ]
|
||||
(builtins.readFile ./config.exs.in))
|
||||
(builtins.replaceStrings [ "$DOMAIN" "$LUSER" ] [ sp.domain sp.username ] (
|
||||
builtins.readFile ./config.exs.in
|
||||
))
|
||||
];
|
||||
};
|
||||
postgresql = {
|
||||
@@ -94,7 +104,10 @@ in
|
||||
before = [ "pleroma.service" ];
|
||||
requiredBy = [ "pleroma.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
path = with pkgs; [ coreutils jq ];
|
||||
path = with pkgs; [
|
||||
coreutils
|
||||
jq
|
||||
];
|
||||
script = ''
|
||||
set -o nounset
|
||||
|
||||
|
@@ -1,36 +1,39 @@
|
||||
{
|
||||
description = "Roundcube is a web-based email client.";
|
||||
|
||||
outputs = { self }: {
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "roundcube";
|
||||
name = "Roundcube";
|
||||
description = "Roundcube is an open source webmail software.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Users' settings.";
|
||||
postgreDatabases = [
|
||||
"roundcube"
|
||||
];
|
||||
systemdServices = [
|
||||
"phpfpm-roundcube.service"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.gpl3
|
||||
];
|
||||
homepage = "https://roundcube.net/";
|
||||
sourcePage = "https://github.com/roundcube/roundcubemail";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.roundcube.users";
|
||||
adminGroup = "sp.roundcube.admins";
|
||||
};
|
||||
outputs =
|
||||
{ self }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "roundcube";
|
||||
name = "Roundcube";
|
||||
description = "Roundcube is an open source webmail software.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = false;
|
||||
isRequired = false;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Users' settings.";
|
||||
postgreDatabases = [
|
||||
"roundcube"
|
||||
];
|
||||
systemdServices = [
|
||||
"phpfpm-roundcube.service"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.gpl3
|
||||
];
|
||||
homepage = "https://roundcube.net/";
|
||||
sourcePage = "https://github.com/roundcube/roundcubemail";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.roundcube.users";
|
||||
adminGroup = "sp.roundcube.admins";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
domain = config.selfprivacy.domain;
|
||||
cfg = config.selfprivacy.modules.roundcube;
|
||||
@@ -16,129 +21,136 @@ let
|
||||
usersGroup = "sp.${sp-module-name}.users";
|
||||
|
||||
oauth-donor = config.selfprivacy.passthru.mailserver;
|
||||
oauthClientSecretFP =
|
||||
auth-passthru.mkOAuth2ClientSecretFP linuxGroupOfService;
|
||||
oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP linuxGroupOfService;
|
||||
# copy client secret from mailserver
|
||||
kanidmExecStartPreScriptRoot = pkgs.writeShellScript
|
||||
"${sp-module-name}-kanidm-ExecStartPre-root-script.sh"
|
||||
''
|
||||
install -v -m640 -o kanidm -g ${linuxGroupOfService} ${oauth-donor.oauth-client-secret-fp} ${oauthClientSecretFP}
|
||||
'';
|
||||
kanidmExecStartPreScriptRoot = pkgs.writeShellScript "${sp-module-name}-kanidm-ExecStartPre-root-script.sh" ''
|
||||
install -v -m640 -o kanidm -g ${linuxGroupOfService} ${oauth-donor.oauth-client-secret-fp} ${oauthClientSecretFP}
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.roundcube = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
subdomain = (lib.mkOption {
|
||||
default = "roundcube";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
}) // {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
enableSso = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Single Sign-On";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.enableSso -> config.selfprivacy.sso.enable;
|
||||
message =
|
||||
"SSO cannot be enabled for Roundcube when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
services.roundcube = {
|
||||
enable = true;
|
||||
# this is the url of the vhost, not necessarily the same as the fqdn of
|
||||
# the mailserver
|
||||
hostName = "${cfg.subdomain}.${config.selfprivacy.domain}";
|
||||
extraConfig = ''
|
||||
# starttls needed for authentication, so the fqdn required to match
|
||||
# the certificate
|
||||
$config['smtp_host'] = "tls://${config.mailserver.fqdn}";
|
||||
$config['smtp_user'] = "%u";
|
||||
$config['smtp_pass'] = "%p";
|
||||
'';
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${domain}" = {
|
||||
forceSSL = true;
|
||||
useACMEHost = domain;
|
||||
enableACME = false;
|
||||
};
|
||||
|
||||
systemd.slices.roundcube.description = "Roundcube service slice";
|
||||
# Roundcube depends on Dovecot and its OAuth2 client secret.
|
||||
systemd.services.phpfpm-roundcube.after = [ "dovecot2.service" ];
|
||||
}
|
||||
# the following part is active only when "auth" module is enabled
|
||||
(lib.mkIf is-auth-enabled {
|
||||
services.roundcube.extraConfig = lib.mkAfter ''
|
||||
$config['oauth_provider'] = 'generic';
|
||||
$config['oauth_provider_name'] = '${auth-passthru.oauth2-provider-name}';
|
||||
$config['oauth_client_id'] = '${oauth-donor.oauth-client-id}';
|
||||
$config['oauth_client_secret'] = file_get_contents('${oauthClientSecretFP}');
|
||||
$config['oauth_auth_uri'] = 'https://${auth-fqdn}/ui/oauth2';
|
||||
$config['oauth_token_uri'] = 'https://${auth-fqdn}/oauth2/token';
|
||||
$config['oauth_identity_uri'] = 'https://${auth-fqdn}/oauth2/openid/${oauth-donor.oauth-client-id}/userinfo';
|
||||
$config['oauth_scope'] = 'email profile openid';
|
||||
$config['oauth_auth_parameters'] = [];
|
||||
$config['oauth_identity_fields'] = ['email'];
|
||||
$config['oauth_login_redirect'] = true;
|
||||
$config['auto_create_user'] = true;
|
||||
'';
|
||||
systemd.services.roundcube = {
|
||||
after = [ "dovecot2.service" ];
|
||||
requires = [ "dovecot2.service" ];
|
||||
};
|
||||
systemd.services.kanidm.serviceConfig.ExecStartPre = lib.mkAfter [
|
||||
("-+" + kanidmExecStartPreScriptRoot)
|
||||
];
|
||||
|
||||
selfprivacy.auth.clients."${oauth-donor.oauth-client-id}" = {
|
||||
inherit adminsGroup usersGroup;
|
||||
imageFile = ./icon.svg;
|
||||
displayName = "Roundcube";
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = false;
|
||||
isMailserver = true;
|
||||
originUrl = "https://${cfg.subdomain}.${domain}/index.php/login/oauth";
|
||||
originLanding = "https://${cfg.subdomain}.${domain}/";
|
||||
useShortPreferredUsername = false;
|
||||
clientSystemdUnits = [ "dovecot2.service" "phpfpm-roundcube.service" ];
|
||||
enablePkce = false;
|
||||
linuxUserOfClient = linuxUserOfService;
|
||||
linuxGroupOfClient = linuxGroupOfService;
|
||||
scopeMaps = {
|
||||
"${usersGroup}" = [
|
||||
"email"
|
||||
"openid"
|
||||
"profile"
|
||||
];
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
})
|
||||
]);
|
||||
subdomain =
|
||||
(lib.mkOption {
|
||||
default = "roundcube";
|
||||
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
description = "Subdomain";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
widget = "subdomain";
|
||||
type = "string";
|
||||
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
|
||||
weight = 0;
|
||||
};
|
||||
};
|
||||
enableSso =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable Single Sign-On";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "bool";
|
||||
weight = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.enableSso -> config.selfprivacy.sso.enable;
|
||||
message = "SSO cannot be enabled for Roundcube when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
services.roundcube = {
|
||||
enable = true;
|
||||
# this is the url of the vhost, not necessarily the same as the fqdn of
|
||||
# the mailserver
|
||||
hostName = "${cfg.subdomain}.${config.selfprivacy.domain}";
|
||||
extraConfig = ''
|
||||
# starttls needed for authentication, so the fqdn required to match
|
||||
# the certificate
|
||||
$config['smtp_host'] = "tls://${config.mailserver.fqdn}";
|
||||
$config['smtp_user'] = "%u";
|
||||
$config['smtp_pass'] = "%p";
|
||||
'';
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${domain}" = {
|
||||
forceSSL = true;
|
||||
useACMEHost = domain;
|
||||
enableACME = false;
|
||||
};
|
||||
|
||||
systemd.slices.roundcube.description = "Roundcube service slice";
|
||||
# Roundcube depends on Dovecot and its OAuth2 client secret.
|
||||
systemd.services.phpfpm-roundcube.after = [ "dovecot2.service" ];
|
||||
}
|
||||
# the following part is active only when "auth" module is enabled
|
||||
(lib.mkIf is-auth-enabled {
|
||||
services.roundcube.extraConfig = lib.mkAfter ''
|
||||
$config['oauth_provider'] = 'generic';
|
||||
$config['oauth_provider_name'] = '${auth-passthru.oauth2-provider-name}';
|
||||
$config['oauth_client_id'] = '${oauth-donor.oauth-client-id}';
|
||||
$config['oauth_client_secret'] = file_get_contents('${oauthClientSecretFP}');
|
||||
$config['oauth_auth_uri'] = 'https://${auth-fqdn}/ui/oauth2';
|
||||
$config['oauth_token_uri'] = 'https://${auth-fqdn}/oauth2/token';
|
||||
$config['oauth_identity_uri'] = 'https://${auth-fqdn}/oauth2/openid/${oauth-donor.oauth-client-id}/userinfo';
|
||||
$config['oauth_scope'] = 'email profile openid';
|
||||
$config['oauth_auth_parameters'] = [];
|
||||
$config['oauth_identity_fields'] = ['email'];
|
||||
$config['oauth_login_redirect'] = true;
|
||||
$config['auto_create_user'] = true;
|
||||
'';
|
||||
systemd.services.roundcube = {
|
||||
after = [ "dovecot2.service" ];
|
||||
requires = [ "dovecot2.service" ];
|
||||
};
|
||||
systemd.services.kanidm.serviceConfig.ExecStartPre = lib.mkAfter [
|
||||
("-+" + kanidmExecStartPreScriptRoot)
|
||||
];
|
||||
|
||||
selfprivacy.auth.clients."${oauth-donor.oauth-client-id}" = {
|
||||
inherit adminsGroup usersGroup;
|
||||
imageFile = ./icon.svg;
|
||||
displayName = "Roundcube";
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = false;
|
||||
isMailserver = true;
|
||||
originUrl = "https://${cfg.subdomain}.${domain}/index.php/login/oauth";
|
||||
originLanding = "https://${cfg.subdomain}.${domain}/";
|
||||
useShortPreferredUsername = false;
|
||||
clientSystemdUnits = [
|
||||
"dovecot2.service"
|
||||
"phpfpm-roundcube.service"
|
||||
];
|
||||
enablePkce = false;
|
||||
linuxUserOfClient = linuxUserOfService;
|
||||
linuxGroupOfClient = linuxGroupOfService;
|
||||
scopeMaps = {
|
||||
"${usersGroup}" = [
|
||||
"email"
|
||||
"openid"
|
||||
"profile"
|
||||
];
|
||||
};
|
||||
};
|
||||
})
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@@ -1,8 +1,14 @@
|
||||
{ mailserver-service-account-name
|
||||
, mailserver-service-account-token-name
|
||||
, mailserver-service-account-token-fp
|
||||
{
|
||||
mailserver-service-account-name,
|
||||
mailserver-service-account-token-name,
|
||||
mailserver-service-account-token-fp,
|
||||
}:
|
||||
{ config, lib, pkgs, ... }@nixos-args:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}@nixos-args:
|
||||
let
|
||||
inherit (import ./common.nix nixos-args)
|
||||
appendSetting
|
||||
@@ -17,67 +23,70 @@ let
|
||||
keysPath = auth-passthru.keys-path;
|
||||
|
||||
# create service account token, needed for LDAP
|
||||
kanidmExecStartPostScript = pkgs.writeShellScript
|
||||
"mailserver-kanidm-ExecStartPost-script.sh"
|
||||
''
|
||||
export HOME=$RUNTIME_DIRECTORY/client_home
|
||||
readonly KANIDM="${pkgs.kanidm}/bin/kanidm"
|
||||
kanidmExecStartPostScript = pkgs.writeShellScript "mailserver-kanidm-ExecStartPost-script.sh" ''
|
||||
export HOME=$RUNTIME_DIRECTORY/client_home
|
||||
readonly KANIDM="${pkgs.kanidm}/bin/kanidm"
|
||||
|
||||
# get Kanidm service account for mailserver
|
||||
KANIDM_SERVICE_ACCOUNT="$($KANIDM service-account list --name idm_admin | grep -E "^name: ${mailserver-service-account-name}$")"
|
||||
echo KANIDM_SERVICE_ACCOUNT: "$KANIDM_SERVICE_ACCOUNT"
|
||||
if [ -n "$KANIDM_SERVICE_ACCOUNT" ]
|
||||
then
|
||||
echo "kanidm service account \"${mailserver-service-account-name}\" is found"
|
||||
else
|
||||
echo "kanidm service account \"${mailserver-service-account-name}\" is not found"
|
||||
echo "creating new kanidm service account \"${mailserver-service-account-name}\""
|
||||
if $KANIDM service-account create --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-name} idm_admin
|
||||
then
|
||||
"kanidm service account \"${mailserver-service-account-name}\" created"
|
||||
else
|
||||
echo "error: cannot create kanidm service account \"${mailserver-service-account-name}\""
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# get Kanidm service account for mailserver
|
||||
KANIDM_SERVICE_ACCOUNT="$($KANIDM service-account list --name idm_admin | grep -E "^name: ${mailserver-service-account-name}$")"
|
||||
echo KANIDM_SERVICE_ACCOUNT: "$KANIDM_SERVICE_ACCOUNT"
|
||||
if [ -n "$KANIDM_SERVICE_ACCOUNT" ]
|
||||
then
|
||||
echo "kanidm service account \"${mailserver-service-account-name}\" is found"
|
||||
else
|
||||
echo "kanidm service account \"${mailserver-service-account-name}\" is not found"
|
||||
echo "creating new kanidm service account \"${mailserver-service-account-name}\""
|
||||
if $KANIDM service-account create --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-name} idm_admin
|
||||
then
|
||||
"kanidm service account \"${mailserver-service-account-name}\" created"
|
||||
else
|
||||
echo "error: cannot create kanidm service account \"${mailserver-service-account-name}\""
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# add Kanidm service account to `idm_mail_servers` group
|
||||
$KANIDM group add-members idm_mail_servers ${mailserver-service-account-name}
|
||||
# add Kanidm service account to `idm_mail_servers` group
|
||||
$KANIDM group add-members idm_mail_servers ${mailserver-service-account-name}
|
||||
|
||||
# create a new read-only token for mailserver
|
||||
if ! KANIDM_SERVICE_ACCOUNT_TOKEN_JSON="$($KANIDM service-account api-token generate --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-token-name} --output json)"
|
||||
then
|
||||
echo "error: kanidm CLI returns an error when trying to generate service-account api-token"
|
||||
exit 1
|
||||
fi
|
||||
if ! KANIDM_SERVICE_ACCOUNT_TOKEN="$(echo "$KANIDM_SERVICE_ACCOUNT_TOKEN_JSON" | ${lib.getExe pkgs.jq} -r .result)"
|
||||
then
|
||||
echo "error: cannot get service-account API token from JSON"
|
||||
exit 1
|
||||
fi
|
||||
# create a new read-only token for mailserver
|
||||
if ! KANIDM_SERVICE_ACCOUNT_TOKEN_JSON="$($KANIDM service-account api-token generate --name idm_admin ${mailserver-service-account-name} ${mailserver-service-account-token-name} --output json)"
|
||||
then
|
||||
echo "error: kanidm CLI returns an error when trying to generate service-account api-token"
|
||||
exit 1
|
||||
fi
|
||||
if ! KANIDM_SERVICE_ACCOUNT_TOKEN="$(echo "$KANIDM_SERVICE_ACCOUNT_TOKEN_JSON" | ${lib.getExe pkgs.jq} -r .result)"
|
||||
then
|
||||
echo "error: cannot get service-account API token from JSON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! install --mode=640 \
|
||||
<(printf "%s" "$KANIDM_SERVICE_ACCOUNT_TOKEN") \
|
||||
${mailserver-service-account-token-fp}
|
||||
then
|
||||
echo "error: cannot write token to \"${mailserver-service-account-token-fp}\""
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
if ! install --mode=640 \
|
||||
<(printf "%s" "$KANIDM_SERVICE_ACCOUNT_TOKEN") \
|
||||
${mailserver-service-account-token-fp}
|
||||
then
|
||||
echo "error: cannot write token to \"${mailserver-service-account-token-fp}\""
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
|
||||
ldapConfFile = "/run/${runtime-folder}/dovecot-ldap.conf.ext";
|
||||
mkLdapSearchScope = scope: (
|
||||
if scope == "sub" then "subtree"
|
||||
else if scope == "one" then "onelevel"
|
||||
else scope
|
||||
);
|
||||
mkLdapSearchScope =
|
||||
scope:
|
||||
(
|
||||
if scope == "sub" then
|
||||
"subtree"
|
||||
else if scope == "one" then
|
||||
"onelevel"
|
||||
else
|
||||
scope
|
||||
);
|
||||
dovecot-ldap-config = pkgs.writeTextFile {
|
||||
name = "dovecot-ldap.conf.ext.template";
|
||||
text = ''
|
||||
ldap_version = 3
|
||||
uris = ${lib.concatStringsSep " " config.mailserver.ldap.uris}
|
||||
${lib.optionalString config.mailserver.ldap.startTls ''
|
||||
tls = yes
|
||||
tls = yes
|
||||
''}
|
||||
tls_require_cert = hard
|
||||
tls_ca_cert_file = ${config.mailserver.ldap.tlsCAFile}
|
||||
@@ -87,7 +96,7 @@ let
|
||||
base = ${config.mailserver.ldap.searchBase}
|
||||
scope = ${mkLdapSearchScope config.mailserver.ldap.searchScope}
|
||||
${lib.optionalString (config.mailserver.ldap.dovecot.userAttrs != null) ''
|
||||
user_attrs = ${config.mailserver.ldap.dovecot.userAttrs}
|
||||
user_attrs = ${config.mailserver.ldap.dovecot.userAttrs}
|
||||
''}
|
||||
user_filter = ${config.mailserver.ldap.dovecot.userFilter}
|
||||
'';
|
||||
@@ -101,10 +110,8 @@ let
|
||||
destination = ldapConfFile;
|
||||
};
|
||||
oauth-client-id = "mailserver";
|
||||
oauth-client-secret-fp =
|
||||
"${keysPath}/${group}/kanidm-oauth-client-secret";
|
||||
oauth-secret-ExecStartPreScript = pkgs.writeShellScript
|
||||
"${oauth-client-id}-kanidm-ExecStartPre-script.sh" ''
|
||||
oauth-client-secret-fp = "${keysPath}/${group}/kanidm-oauth-client-secret";
|
||||
oauth-secret-ExecStartPreScript = pkgs.writeShellScript "${oauth-client-id}-kanidm-ExecStartPre-script.sh" ''
|
||||
set -o xtrace
|
||||
[ -f "${oauth-client-secret-fp}" ] || \
|
||||
"${lib.getExe pkgs.openssl}" rand -base64 32 | tr "\n:@/+=" "012345" > "${oauth-client-secret-fp}"
|
||||
@@ -122,8 +129,8 @@ let
|
||||
openid_configuration_url = ${auth-passthru.oauth2-discovery-url oauth-client-id}
|
||||
debug = "no"
|
||||
'';
|
||||
prefix = ''introspection_url = "'' +
|
||||
(auth-passthru.oauth2-introspection-url-prefix oauth-client-id);
|
||||
prefix =
|
||||
''introspection_url = "'' + (auth-passthru.oauth2-introspection-url-prefix oauth-client-id);
|
||||
suffix = auth-passthru.oauth2-introspection-url-postfix + ''"'';
|
||||
passwordFile = oauth-client-secret-fp;
|
||||
destination = dovecot-oauth2-conf-fp;
|
||||
|
@@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }@nixos-args:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}@nixos-args:
|
||||
let
|
||||
inherit (import ./common.nix nixos-args)
|
||||
appendSetting
|
||||
@@ -9,8 +14,7 @@ let
|
||||
cfg = config.mailserver;
|
||||
|
||||
ldapSenderLoginMapFile = "/run/postfix/ldap-sender-login-map.cf";
|
||||
submissionOptions.smtpd_sender_login_maps =
|
||||
lib.mkForce "hash:/etc/postfix/vaccounts,ldap:${ldapSenderLoginMapFile}";
|
||||
submissionOptions.smtpd_sender_login_maps = lib.mkForce "hash:/etc/postfix/vaccounts,ldap:${ldapSenderLoginMapFile}";
|
||||
commonLdapConfig = ''
|
||||
server_host = ${lib.concatStringsSep " " cfg.ldap.uris}
|
||||
start_tls = ${if cfg.ldap.startTls then "yes" else "no"}
|
||||
@@ -61,8 +65,10 @@ in
|
||||
${appendPwdInVirtualMailboxMap}
|
||||
${appendPwdInSenderLoginMap}
|
||||
'';
|
||||
restartTriggers =
|
||||
[ appendPwdInVirtualMailboxMap appendPwdInSenderLoginMap ];
|
||||
restartTriggers = [
|
||||
appendPwdInVirtualMailboxMap
|
||||
appendPwdInSenderLoginMap
|
||||
];
|
||||
wants = [ auth-passthru.oauth2-systemd-service ];
|
||||
after = [ auth-passthru.oauth2-systemd-service ];
|
||||
};
|
||||
|
@@ -4,11 +4,17 @@ rec {
|
||||
domain = config.selfprivacy.domain;
|
||||
group = "dovecot2";
|
||||
is-auth-enabled =
|
||||
config.selfprivacy.modules.simple-nixos-mailserver.enableSso
|
||||
&& config.selfprivacy.sso.enable;
|
||||
config.selfprivacy.modules.simple-nixos-mailserver.enableSso && config.selfprivacy.sso.enable;
|
||||
|
||||
appendSetting =
|
||||
{ name, file, prefix, suffix ? "", passwordFile, destination }:
|
||||
{
|
||||
name,
|
||||
file,
|
||||
prefix,
|
||||
suffix ? "",
|
||||
passwordFile,
|
||||
destination,
|
||||
}:
|
||||
pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
|
||||
#!${pkgs.stdenv.shell}
|
||||
set -euo pipefail
|
||||
|
@@ -1,4 +1,9 @@
|
||||
{ config, lib, pkgs, ... }@nixos-args:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}@nixos-args:
|
||||
let
|
||||
sp = config.selfprivacy;
|
||||
|
||||
@@ -11,26 +16,22 @@ let
|
||||
mailserver-service-account = {
|
||||
mailserver-service-account-name = "sp.mailserver.service-account";
|
||||
mailserver-service-account-token-name = "mailserver-service-account-token";
|
||||
mailserver-service-account-token-fp =
|
||||
"/run/keys/${group}/kanidm-service-account-token"; # FIXME sync with auth module
|
||||
mailserver-service-account-token-fp = "/run/keys/${group}/kanidm-service-account-token"; # FIXME sync with auth module
|
||||
};
|
||||
in
|
||||
lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
config.selfprivacy.modules.simple-nixos-mailserver.enableSso
|
||||
-> config.selfprivacy.sso.enable;
|
||||
message =
|
||||
"SSO cannot be enabled for Mailserver when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
fileSystems = lib.mkIf sp.useBinds
|
||||
{
|
||||
lib.mkIf sp.modules.simple-nixos-mailserver.enable (
|
||||
lib.mkMerge [
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
config.selfprivacy.modules.simple-nixos-mailserver.enableSso -> config.selfprivacy.sso.enable;
|
||||
message = "SSO cannot be enabled for Mailserver when SSO is disabled globally.";
|
||||
}
|
||||
];
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
"/var/vmail" = {
|
||||
device =
|
||||
"/volumes/${sp.modules.simple-nixos-mailserver.location}/vmail";
|
||||
device = "/volumes/${sp.modules.simple-nixos-mailserver.location}/vmail";
|
||||
options = [
|
||||
"bind"
|
||||
"x-systemd.required-by=postfix.service"
|
||||
@@ -38,8 +39,7 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [
|
||||
];
|
||||
};
|
||||
"/var/sieve" = {
|
||||
device =
|
||||
"/volumes/${sp.modules.simple-nixos-mailserver.location}/sieve";
|
||||
device = "/volumes/${sp.modules.simple-nixos-mailserver.location}/sieve";
|
||||
options = [
|
||||
"bind"
|
||||
"x-systemd.required-by=dovecot2.service"
|
||||
@@ -48,114 +48,120 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [
|
||||
};
|
||||
};
|
||||
|
||||
users.users = {
|
||||
virtualMail = {
|
||||
isNormalUser = false;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups.acmereceivers.members = [ "dovecot2" "postfix" "virtualMail" ];
|
||||
|
||||
mailserver = {
|
||||
enable = true;
|
||||
fqdn = sp.domain;
|
||||
domains = [ sp.domain ];
|
||||
localDnsResolver = false;
|
||||
|
||||
# A list of all login accounts. To create the password hashes, use
|
||||
# mkpasswd -m sha-512 "super secret password"
|
||||
loginAccounts = ({
|
||||
"${sp.username}@${sp.domain}" = {
|
||||
hashedPassword = sp.hashedMasterPassword;
|
||||
sieveScript = ''
|
||||
require ["fileinto", "mailbox"];
|
||||
if header :contains "Chat-Version" "1.0"
|
||||
{
|
||||
fileinto :create "DeltaChat";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
users.users = {
|
||||
virtualMail = {
|
||||
isNormalUser = false;
|
||||
};
|
||||
} // builtins.listToAttrs (builtins.map
|
||||
(user: {
|
||||
name = "${user.username}@${sp.domain}";
|
||||
value = {
|
||||
hashedPassword = user.hashedPassword;
|
||||
sieveScript = ''
|
||||
require ["fileinto", "mailbox"];
|
||||
if header :contains "Chat-Version" "1.0"
|
||||
{
|
||||
fileinto :create "DeltaChat";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
})
|
||||
sp.users));
|
||||
|
||||
extraVirtualAliases = {
|
||||
"admin@${sp.domain}" = "${sp.username}@${sp.domain}";
|
||||
};
|
||||
|
||||
certificateScheme = "manual";
|
||||
certificateFile = "/var/lib/acme/root-${sp.domain}/fullchain.pem";
|
||||
keyFile = "/var/lib/acme/root-${sp.domain}/key.pem";
|
||||
users.groups.acmereceivers.members = [
|
||||
"dovecot2"
|
||||
"postfix"
|
||||
"virtualMail"
|
||||
];
|
||||
|
||||
# Enable IMAP and POP3
|
||||
enableImap = true;
|
||||
enableImapSsl = true;
|
||||
enablePop3 = false;
|
||||
enablePop3Ssl = false;
|
||||
dkimSelector = "selector";
|
||||
mailserver = {
|
||||
enable = true;
|
||||
fqdn = sp.domain;
|
||||
domains = [ sp.domain ];
|
||||
localDnsResolver = false;
|
||||
|
||||
# Enable the ManageSieve protocol
|
||||
enableManageSieve = true;
|
||||
# A list of all login accounts. To create the password hashes, use
|
||||
# mkpasswd -m sha-512 "super secret password"
|
||||
loginAccounts = (
|
||||
{
|
||||
"${sp.username}@${sp.domain}" = {
|
||||
hashedPassword = sp.hashedMasterPassword;
|
||||
sieveScript = ''
|
||||
require ["fileinto", "mailbox"];
|
||||
if header :contains "Chat-Version" "1.0"
|
||||
{
|
||||
fileinto :create "DeltaChat";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
}
|
||||
// builtins.listToAttrs (
|
||||
builtins.map (user: {
|
||||
name = "${user.username}@${sp.domain}";
|
||||
value = {
|
||||
hashedPassword = user.hashedPassword;
|
||||
sieveScript = ''
|
||||
require ["fileinto", "mailbox"];
|
||||
if header :contains "Chat-Version" "1.0"
|
||||
{
|
||||
fileinto :create "DeltaChat";
|
||||
stop;
|
||||
}
|
||||
'';
|
||||
};
|
||||
}) sp.users
|
||||
)
|
||||
);
|
||||
|
||||
virusScanning = false;
|
||||
extraVirtualAliases = {
|
||||
"admin@${sp.domain}" = "${sp.username}@${sp.domain}";
|
||||
};
|
||||
|
||||
mailDirectory = "/var/vmail";
|
||||
};
|
||||
certificateScheme = "manual";
|
||||
certificateFile = "/var/lib/acme/root-${sp.domain}/fullchain.pem";
|
||||
keyFile = "/var/lib/acme/root-${sp.domain}/key.pem";
|
||||
|
||||
systemd = {
|
||||
services = {
|
||||
dovecot2.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
postfix.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
redis-rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
opendkim.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
# Enable IMAP and POP3
|
||||
enableImap = true;
|
||||
enableImapSsl = true;
|
||||
enablePop3 = false;
|
||||
enablePop3Ssl = false;
|
||||
dkimSelector = "selector";
|
||||
|
||||
# Enable the ManageSieve protocol
|
||||
enableManageSieve = true;
|
||||
|
||||
virusScanning = false;
|
||||
|
||||
mailDirectory = "/var/vmail";
|
||||
};
|
||||
slices."simple_nixos_mailserver" = {
|
||||
name = "simple_nixos_mailserver.slice";
|
||||
description = "Simple NixOS Mailserver service slice";
|
||||
|
||||
systemd = {
|
||||
services = {
|
||||
dovecot2.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
postfix.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
redis-rspamd.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
opendkim.serviceConfig.Slice = "simple_nixos_mailserver.slice";
|
||||
};
|
||||
slices."simple_nixos_mailserver" = {
|
||||
name = "simple_nixos_mailserver.slice";
|
||||
description = "Simple NixOS Mailserver service slice";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
# the following parts are active only when "auth" module is enabled
|
||||
(lib.mkIf is-auth-enabled {
|
||||
mailserver = {
|
||||
extraVirtualAliases = lib.mkForce { };
|
||||
loginAccounts = lib.mkForce { };
|
||||
# LDAP is needed for Postfix to query Kanidm about email address ownership.
|
||||
# LDAP is needed for Dovecot also.
|
||||
ldap = {
|
||||
# false; otherwise, simple-nixos-mailserver enables auth via LDAP
|
||||
enable = false;
|
||||
}
|
||||
# the following parts are active only when "auth" module is enabled
|
||||
(lib.mkIf is-auth-enabled {
|
||||
mailserver = {
|
||||
extraVirtualAliases = lib.mkForce { };
|
||||
loginAccounts = lib.mkForce { };
|
||||
# LDAP is needed for Postfix to query Kanidm about email address ownership.
|
||||
# LDAP is needed for Dovecot also.
|
||||
ldap = {
|
||||
# false; otherwise, simple-nixos-mailserver enables auth via LDAP
|
||||
enable = false;
|
||||
|
||||
# bind.dn = "uid=mail,ou=persons," + ldap_base_dn;
|
||||
bind.dn = "dn=token";
|
||||
# TODO change in this file should trigger system restart dovecot
|
||||
bind.passwordFile =
|
||||
mailserver-service-account.mailserver-service-account-token-fp;
|
||||
# bind.dn = "uid=mail,ou=persons," + ldap_base_dn;
|
||||
bind.dn = "dn=token";
|
||||
# TODO change in this file should trigger system restart dovecot
|
||||
bind.passwordFile = mailserver-service-account.mailserver-service-account-token-fp;
|
||||
|
||||
# searchBase = "ou=persons," + ldap_base_dn;
|
||||
searchBase = auth-passthru.ldap-base-dn; # TODO refine this
|
||||
# searchBase = "ou=persons," + ldap_base_dn;
|
||||
searchBase = auth-passthru.ldap-base-dn; # TODO refine this
|
||||
|
||||
# NOTE: 127.0.0.1 instead of localhost doesn't work (maybe because of TLS)
|
||||
uris = [ "ldaps://localhost:${toString auth-passthru.ldap-port}" ];
|
||||
# NOTE: 127.0.0.1 instead of localhost doesn't work (maybe because of TLS)
|
||||
uris = [ "ldaps://localhost:${toString auth-passthru.ldap-port}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
(lib.mkIf is-auth-enabled
|
||||
(import ./auth-dovecot.nix mailserver-service-account nixos-args))
|
||||
(lib.mkIf is-auth-enabled (import ./auth-postfix.nix nixos-args))
|
||||
])
|
||||
})
|
||||
(lib.mkIf is-auth-enabled (import ./auth-dovecot.nix mailserver-service-account nixos-args))
|
||||
(lib.mkIf is-auth-enabled (import ./auth-postfix.nix nixos-args))
|
||||
]
|
||||
)
|
||||
|
@@ -1,43 +1,45 @@
|
||||
{
|
||||
description = "PoC SP module for the simple-nixos-mailserver";
|
||||
|
||||
inputs.mailserver.url =
|
||||
gitlab:simple-nixos-mailserver/nixos-mailserver;
|
||||
inputs.mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver";
|
||||
|
||||
outputs = { self, mailserver }: {
|
||||
nixosModules.default = _: {
|
||||
imports = [
|
||||
mailserver.nixosModules.default
|
||||
./options.nix
|
||||
./config.nix
|
||||
];
|
||||
};
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = { lib, ... }: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "simple-nixos-mailserver";
|
||||
name = "Mail Server";
|
||||
description = "E-Mail for company and family.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = true;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Mail boxes and filters.";
|
||||
systemdServices = [
|
||||
"dovecot2.service"
|
||||
"postfix.service"
|
||||
];
|
||||
user = "virtualMail";
|
||||
folders = [
|
||||
"/var/vmail"
|
||||
"/var/sieve"
|
||||
];
|
||||
supportLevel = "normal";
|
||||
};
|
||||
outputs =
|
||||
{ self, mailserver }:
|
||||
{
|
||||
nixosModules.default = _: {
|
||||
imports = [
|
||||
mailserver.nixosModules.default
|
||||
./options.nix
|
||||
./config.nix
|
||||
];
|
||||
};
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "simple-nixos-mailserver";
|
||||
name = "Mail Server";
|
||||
description = "E-Mail for company and family.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = true;
|
||||
canBeBackedUp = true;
|
||||
backupDescription = "Mail boxes and filters.";
|
||||
systemdServices = [
|
||||
"dovecot2.service"
|
||||
"postfix.service"
|
||||
];
|
||||
user = "virtualMail";
|
||||
folders = [
|
||||
"/var/vmail"
|
||||
"/var/sieve"
|
||||
];
|
||||
supportLevel = "normal";
|
||||
};
|
||||
|
||||
# TODO generate json docs from module? something like:
|
||||
# nix eval --impure --expr 'let flake = builtins.getFlake (builtins.toPath ./.); pkgs = flake.inputs.mailserver.inputs.nixpkgs.legacyPackages.x86_64-linux; in (pkgs.nixosOptionsDoc { inherit (pkgs.lib.evalModules { modules = [ flake.nixosModules.default ]; }) options; }).optionsJSON'
|
||||
# (doesn't work because of `assertions`)
|
||||
};
|
||||
# TODO generate json docs from module? something like:
|
||||
# nix eval --impure --expr 'let flake = builtins.getFlake (builtins.toPath ./.); pkgs = flake.inputs.mailserver.inputs.nixpkgs.legacyPackages.x86_64-linux; in (pkgs.nixosOptionsDoc { inherit (pkgs.lib.evalModules { modules = [ flake.nixosModules.default ]; }) options; }).optionsJSON'
|
||||
# (doesn't work because of `assertions`)
|
||||
};
|
||||
}
|
||||
|
@@ -1,31 +1,37 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
options.selfprivacy.modules.simple-nixos-mailserver = {
|
||||
enable = (lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable mail server";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
description = "Enable mail server";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
location = (lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Location";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "location";
|
||||
location =
|
||||
(lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Location";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "location";
|
||||
};
|
||||
};
|
||||
};
|
||||
enableSso = (lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Enable SSO for mail server";
|
||||
}) // {
|
||||
meta = {
|
||||
type = "enable";
|
||||
enableSso =
|
||||
(lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
description = "Enable SSO for mail server";
|
||||
})
|
||||
// {
|
||||
meta = {
|
||||
type = "enable";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -5,37 +5,40 @@
|
||||
nixpkgs-24-11.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||
};
|
||||
|
||||
outputs = {nixpkgs-24-11, ...}: {
|
||||
nixosModules.default = import ./module.nix nixpkgs-24-11.legacyPackages.x86_64-linux;
|
||||
configPathsNeeded =
|
||||
builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta = {lib, ...}: {
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "vikunja";
|
||||
name = "Vikunja";
|
||||
description = "Vikunja, the fluffy, open-source, self-hostable to-do app.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
backupDescription = "Tasks and attachments.";
|
||||
systemdServices = [
|
||||
"vikunja.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/vikunja"
|
||||
];
|
||||
postgreDatabases = [
|
||||
"vikunja"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Plus
|
||||
];
|
||||
homepage = "https://vikunja.io";
|
||||
sourcePage = "https://github.com/go-vikunja/vikunja";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.vikunja.users";
|
||||
};
|
||||
outputs =
|
||||
{ nixpkgs-24-11, ... }:
|
||||
{
|
||||
nixosModules.default = import ./module.nix nixpkgs-24-11.legacyPackages.x86_64-linux;
|
||||
configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json);
|
||||
meta =
|
||||
{ lib, ... }:
|
||||
{
|
||||
spModuleSchemaVersion = 1;
|
||||
id = "vikunja";
|
||||
name = "Vikunja";
|
||||
description = "Vikunja, the fluffy, open-source, self-hostable to-do app.";
|
||||
svgIcon = builtins.readFile ./icon.svg;
|
||||
isMovable = true;
|
||||
isRequired = false;
|
||||
backupDescription = "Tasks and attachments.";
|
||||
systemdServices = [
|
||||
"vikunja.service"
|
||||
];
|
||||
folders = [
|
||||
"/var/lib/vikunja"
|
||||
];
|
||||
postgreDatabases = [
|
||||
"vikunja"
|
||||
];
|
||||
license = [
|
||||
lib.licenses.agpl3Plus
|
||||
];
|
||||
homepage = "https://vikunja.io";
|
||||
sourcePage = "https://github.com/go-vikunja/vikunja";
|
||||
supportLevel = "normal";
|
||||
sso = {
|
||||
userGroup = "sp.vikunja.users";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -1,8 +1,10 @@
|
||||
latestPkgs: {
|
||||
latestPkgs:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
}:
|
||||
let
|
||||
sp = config.selfprivacy;
|
||||
cfg = sp.modules.vikunja;
|
||||
oauthClientID = "vikunja";
|
||||
@@ -13,18 +15,16 @@ latestPkgs: {
|
||||
# SelfPrivacy uses SP Module ID to identify the group!
|
||||
usersGroup = "sp.vikunja.users";
|
||||
|
||||
oauthClientSecretFP =
|
||||
auth-passthru.mkOAuth2ClientSecretFP oauthClientID;
|
||||
oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP oauthClientID;
|
||||
|
||||
vikunjaPackage = latestPkgs.vikunja.overrideAttrs (old: {
|
||||
doCheck = false; # Tests are slow.
|
||||
patches =
|
||||
(old.patches or [])
|
||||
++ [
|
||||
./load-client-secret-from-env.patch
|
||||
];
|
||||
patches = (old.patches or [ ]) ++ [
|
||||
./load-client-secret-from-env.patch
|
||||
];
|
||||
});
|
||||
in {
|
||||
in
|
||||
{
|
||||
options.selfprivacy.modules.vikunja = {
|
||||
enable =
|
||||
(lib.mkOption {
|
||||
@@ -63,157 +63,168 @@ in {
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
lib.mkIf cfg.enable
|
||||
{
|
||||
assertions = [
|
||||
{
|
||||
assertion = sp.sso.enable;
|
||||
message = "Vikunja cannot be enabled when SSO is disabled.";
|
||||
}
|
||||
];
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = sp.sso.enable;
|
||||
message = "Vikunja cannot be enabled when SSO is disabled.";
|
||||
}
|
||||
];
|
||||
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
"/var/lib/vikunja" = {
|
||||
device = "/volumes/${cfg.location}/vikunja";
|
||||
options = ["bind"];
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users.vikunja = {
|
||||
isSystemUser = true;
|
||||
group = "vikunja";
|
||||
};
|
||||
groups.vikunja = {};
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
ensureDatabases = ["vikunja"];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "vikunja";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.vikunja = {
|
||||
enable = true;
|
||||
package = vikunjaPackage;
|
||||
frontendScheme = "https";
|
||||
frontendHostname = "${cfg.subdomain}.${sp.domain}";
|
||||
port = 4835;
|
||||
|
||||
database = {
|
||||
type = "postgres";
|
||||
host = "/run/postgresql";
|
||||
};
|
||||
|
||||
settings = {
|
||||
service = {
|
||||
enableregistration = false;
|
||||
enabletotp = false;
|
||||
enableuserdeletion = true;
|
||||
};
|
||||
|
||||
auth = {
|
||||
local.enabled = false;
|
||||
openid = {
|
||||
enabled = true;
|
||||
providers = [
|
||||
{
|
||||
name = oauth2-provider-name;
|
||||
authurl = lib.strings.removeSuffix "/.well-known/openid-configuration" oauthDiscoveryURL;
|
||||
clientid = oauthClientID;
|
||||
clientsecret = ""; # There's patch for our Vikunja to make it load client secret from environment variable.
|
||||
scope = "openid profile email";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
|
||||
useACMEHost = sp.domain;
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
add_header Strict-Transport-Security $hsts_header;
|
||||
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
|
||||
add_header 'Referrer-Policy' 'origin-when-cross-origin';
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||
'';
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:4835";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd = {
|
||||
services.vikunja = {
|
||||
unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/vikunja";
|
||||
serviceConfig = {
|
||||
Slice = "vikunja.slice";
|
||||
LoadCredential = "oauth2-secret:${oauthClientSecretFP}";
|
||||
DynamicUser = lib.mkForce false;
|
||||
User = "vikunja";
|
||||
Group = "vikunja";
|
||||
|
||||
AmbientCapabilities = [""];
|
||||
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
|
||||
ProtectProc = "invisible";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
|
||||
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
|
||||
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
|
||||
RemoveIPC = true;
|
||||
|
||||
SystemCallFilter = ["@system-service" "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"];
|
||||
};
|
||||
environment.SP_VIKUNJA_CLIENT_SECRET_PATH = "%d/oauth2-secret";
|
||||
};
|
||||
slices.vikunja = {
|
||||
description = "Vikunja service slice";
|
||||
};
|
||||
};
|
||||
|
||||
selfprivacy.auth.clients.${oauthClientID} = {
|
||||
inherit usersGroup;
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = false;
|
||||
originLanding = "https://${cfg.subdomain}.${sp.domain}/";
|
||||
originUrl = "https://${cfg.subdomain}.${sp.domain}/auth/openid/${lib.strings.toLower oauth2-provider-name}";
|
||||
clientSystemdUnits = ["vikunja.service"];
|
||||
enablePkce = false;
|
||||
linuxUserOfClient = "vikunja";
|
||||
linuxGroupOfClient = "vikunja";
|
||||
fileSystems = lib.mkIf sp.useBinds {
|
||||
"/var/lib/vikunja" = {
|
||||
device = "/volumes/${cfg.location}/vikunja";
|
||||
options = [ "bind" ];
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
users.vikunja = {
|
||||
isSystemUser = true;
|
||||
group = "vikunja";
|
||||
};
|
||||
groups.vikunja = { };
|
||||
};
|
||||
|
||||
services.postgresql = {
|
||||
ensureDatabases = [ "vikunja" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "vikunja";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.vikunja = {
|
||||
enable = true;
|
||||
package = vikunjaPackage;
|
||||
frontendScheme = "https";
|
||||
frontendHostname = "${cfg.subdomain}.${sp.domain}";
|
||||
port = 4835;
|
||||
|
||||
database = {
|
||||
type = "postgres";
|
||||
host = "/run/postgresql";
|
||||
};
|
||||
|
||||
settings = {
|
||||
service = {
|
||||
enableregistration = false;
|
||||
enabletotp = false;
|
||||
enableuserdeletion = true;
|
||||
};
|
||||
|
||||
auth = {
|
||||
local.enabled = false;
|
||||
openid = {
|
||||
enabled = true;
|
||||
providers = [
|
||||
{
|
||||
name = oauth2-provider-name;
|
||||
authurl = lib.strings.removeSuffix "/.well-known/openid-configuration" oauthDiscoveryURL;
|
||||
clientid = oauthClientID;
|
||||
clientsecret = ""; # There's patch for our Vikunja to make it load client secret from environment variable.
|
||||
scope = "openid profile email";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = {
|
||||
useACMEHost = sp.domain;
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
add_header Strict-Transport-Security $hsts_header;
|
||||
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
|
||||
add_header 'Referrer-Policy' 'origin-when-cross-origin';
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
||||
'';
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:4835";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd = {
|
||||
services.vikunja = {
|
||||
unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/vikunja";
|
||||
serviceConfig = {
|
||||
Slice = "vikunja.slice";
|
||||
LoadCredential = "oauth2-secret:${oauthClientSecretFP}";
|
||||
DynamicUser = lib.mkForce false;
|
||||
User = "vikunja";
|
||||
Group = "vikunja";
|
||||
|
||||
AmbientCapabilities = [ "" ];
|
||||
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
|
||||
ProtectProc = "invisible";
|
||||
|
||||
ProtectSystem = "strict";
|
||||
|
||||
RestrictAddressFamilies = [
|
||||
"AF_UNIX"
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
];
|
||||
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
|
||||
RemoveIPC = true;
|
||||
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@cpu-emulation"
|
||||
"~@debug"
|
||||
"~@keyring"
|
||||
"~@memlock"
|
||||
"~@obsolete"
|
||||
"~@privileged"
|
||||
"~@setuid"
|
||||
];
|
||||
};
|
||||
environment.SP_VIKUNJA_CLIENT_SECRET_PATH = "%d/oauth2-secret";
|
||||
};
|
||||
slices.vikunja = {
|
||||
description = "Vikunja service slice";
|
||||
};
|
||||
};
|
||||
|
||||
selfprivacy.auth.clients.${oauthClientID} = {
|
||||
inherit usersGroup;
|
||||
subdomain = cfg.subdomain;
|
||||
isTokenNeeded = false;
|
||||
originLanding = "https://${cfg.subdomain}.${sp.domain}/";
|
||||
originUrl = "https://${cfg.subdomain}.${sp.domain}/auth/openid/${lib.strings.toLower oauth2-provider-name}";
|
||||
clientSystemdUnits = [ "vikunja.service" ];
|
||||
enablePkce = false;
|
||||
linuxUserOfClient = "vikunja";
|
||||
linuxGroupOfClient = "vikunja";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user