mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
feat: wip: add container backend to guests
This commit is contained in:
parent
83f1908e21
commit
abb8330d86
23 changed files with 256 additions and 208 deletions
84
hosts/ward/guests/adguardhome.nix
Normal file
84
hosts/ward/guests/adguardhome.nix
Normal file
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
nodes,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
adguardhomeDomain = "adguardhome.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.adguardhome.settings.bind_port];
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.adguard = adguardhomeDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.adguardhome = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.adguardhome.settings.bind_port}" = {};
|
||||
extraConfig = ''
|
||||
zone adguardhome 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${adguardhomeDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
oauth2.enable = true;
|
||||
oauth2.allowedGroups = ["access_adguardhome"];
|
||||
locations."/" = {
|
||||
proxyPass = "http://adguardhome";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [53];
|
||||
allowedUDPPorts = [53];
|
||||
};
|
||||
|
||||
services.adguardhome = {
|
||||
enable = true;
|
||||
# TODO allow mutable settings, replace 123.123.123.123 with
|
||||
# simpler sed dns.host_addr logic.
|
||||
mutableSettings = false;
|
||||
settings = {
|
||||
bind_host = "0.0.0.0";
|
||||
bind_port = 3000;
|
||||
dns = {
|
||||
bind_hosts = [
|
||||
# This dummy address passes the configuration check and will
|
||||
# later be replaced by the actual interface address.
|
||||
"123.123.123.123"
|
||||
];
|
||||
# allowed_clients = [
|
||||
# ];
|
||||
#trusted_proxied = [];
|
||||
ratelimit = 60;
|
||||
upstream_dns = [
|
||||
"1.1.1.1"
|
||||
"2606:4700:4700::1111"
|
||||
"8.8.8.8"
|
||||
"2001:4860:4860::8844"
|
||||
];
|
||||
bootstrap_dns = [
|
||||
"1.1.1.1"
|
||||
"2606:4700:4700::1111"
|
||||
"8.8.8.8"
|
||||
"2001:4860:4860::8844"
|
||||
];
|
||||
dhcp.enabled = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.adguardhome = {
|
||||
preStart = lib.mkAfter ''
|
||||
INTERFACE_ADDR=$(${pkgs.iproute2}/bin/ip -family inet -brief addr show wan | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+")
|
||||
sed -i -e "s/123.123.123.123/$INTERFACE_ADDR/" "$STATE_DIRECTORY/AdGuardHome.yaml"
|
||||
'';
|
||||
serviceConfig.RestartSec = lib.mkForce "600"; # Retry every 10 minutes
|
||||
};
|
||||
}
|
22
hosts/ward/guests/common.nix
Normal file
22
hosts/ward/guests/common.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{nodes, ...}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel = {};
|
||||
meta.promtail = {
|
||||
enable = true;
|
||||
proxy = "sentinel";
|
||||
};
|
||||
|
||||
# Connect safely via wireguard to skip http authentication
|
||||
networking.hosts.${sentinelCfg.meta.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb];
|
||||
meta.telegraf = {
|
||||
enable = true;
|
||||
scrapeSensors = false;
|
||||
influxdb2 = {
|
||||
domain = sentinelCfg.networking.providedDomains.influxdb;
|
||||
organization = "servers";
|
||||
bucket = "telegraf";
|
||||
node = "ward-influxdb";
|
||||
};
|
||||
};
|
||||
}
|
168
hosts/ward/guests/forgejo.nix
Normal file
168
hosts/ward/guests/forgejo.nix
Normal file
|
@ -0,0 +1,168 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
nodes,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
# XXX: other domain on other proxy?
|
||||
forgejoDomain = "git.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
# TODO forward ssh port
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [
|
||||
config.services.gitea.settings.server.HTTP_PORT
|
||||
];
|
||||
|
||||
age.secrets.forgejo-mailer-password = {
|
||||
rekeyFile = config.node.secretsDir + "/forgejo-mailer-password.age";
|
||||
mode = "440";
|
||||
inherit (config.services.gitea) group;
|
||||
};
|
||||
|
||||
# Mirror the original oauth2 secret
|
||||
age.secrets.forgejo-oauth2-client-secret = {
|
||||
inherit (nodes.ward-kanidm.config.age.secrets.kanidm-oauth2-forgejo) rekeyFile;
|
||||
mode = "440";
|
||||
inherit (config.services.gitea) group;
|
||||
};
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.forgejo = forgejoDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.forgejo = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.gitea.settings.server.HTTP_PORT}" = {};
|
||||
extraConfig = ''
|
||||
zone forgejo 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${forgejoDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
extraConfig = ''
|
||||
client_max_body_size 512M;
|
||||
'';
|
||||
locations."/".proxyPass = "http://forgejo";
|
||||
locations."/metrics" = {
|
||||
proxyPass = "http://forgejo/metrics";
|
||||
extraConfig = ''
|
||||
allow 127.0.0.0/8;
|
||||
allow ::1;
|
||||
deny all;
|
||||
access_log off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Recommended by forgejo: https://forgejo.org/docs/latest/admin/recommendations/#git-over-ssh
|
||||
services.openssh.settings.AcceptEnv = "GIT_PROTOCOL";
|
||||
|
||||
services.gitea = {
|
||||
enable = true;
|
||||
package = pkgs.forgejo;
|
||||
appName = "Redlew Git"; # tungsten inert gas?
|
||||
stateDir = "/var/lib/forgejo";
|
||||
# TODO db backups
|
||||
# dump.enable = true;
|
||||
lfs.enable = true;
|
||||
mailerPasswordFile = config.age.secrets.forgejo-mailer-password.path;
|
||||
settings = {
|
||||
actions = {
|
||||
ENABLED = true;
|
||||
DEFAULT_ACTIONS_URL = "https://gitea.com";
|
||||
};
|
||||
database = {
|
||||
SQLITE_JOURNAL_MODE = "WAL";
|
||||
LOG_SQL = false; # Leaks secrets
|
||||
};
|
||||
# federation.ENABLED = true;
|
||||
mailer = {
|
||||
ENABLED = true;
|
||||
HOST = config.repo.secrets.local.forgejo.mail.host;
|
||||
FROM = config.repo.secrets.local.forgejo.mail.from;
|
||||
USER = config.repo.secrets.local.forgejo.mail.user;
|
||||
SEND_AS_PLAIN_TEXT = true;
|
||||
};
|
||||
metrics = {
|
||||
# XXX: query with local telegraf
|
||||
ENABLED = true;
|
||||
ENABLED_ISSUE_BY_REPOSITORY = true;
|
||||
ENABLED_ISSUE_BY_LABEL = true;
|
||||
};
|
||||
oauth2_client = {
|
||||
ACCOUNT_LINKING = "auto";
|
||||
ENABLE_AUTO_REGISTRATION = true;
|
||||
OPENID_CONNECT_SCOPES = "email profile";
|
||||
REGISTER_EMAIL_CONFIRM = false;
|
||||
UPDATE_AVATAR = true;
|
||||
};
|
||||
# packages.ENABLED = true;
|
||||
repository = {
|
||||
DEFAULT_PRIVATE = false;
|
||||
ENABLE_PUSH_CREATE_USER = true;
|
||||
ENABLE_PUSH_CREATE_ORG = true;
|
||||
};
|
||||
server = {
|
||||
HTTP_ADDR = "0.0.0.0";
|
||||
HTTP_PORT = 3000;
|
||||
DOMAIN = forgejoDomain;
|
||||
ROOT_URL = "https://${forgejoDomain}/";
|
||||
LANDING_PAGE = "login";
|
||||
SSH_PORT = 9922;
|
||||
};
|
||||
service = {
|
||||
DISABLE_REGISTRATION = false;
|
||||
ALLOW_ONLY_INTERNAL_REGISTRATION = false;
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
REGISTER_EMAIL_CONFIRM = false;
|
||||
ENABLE_NOTIFY_MAIL = true;
|
||||
};
|
||||
session.COOKIE_SECURE = true;
|
||||
ui.DEFAULT_THEME = "forgejo-auto";
|
||||
"ui.meta" = {
|
||||
AUTHOR = "Redlew Git";
|
||||
DESCRIPTION = "Tungsten Inert Gas?";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# XXX: PKCE is currently not supported by gitea/forgejo,
|
||||
# see https://github.com/go-gitea/gitea/issues/21376.
|
||||
# Disable PKCE manually in kanidm for now.
|
||||
# `kanidm system oauth2 warning-insecure-client-disable-pkce forgejo`
|
||||
systemd.services.gitea = {
|
||||
serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
preStart = let
|
||||
exe = lib.getExe config.services.gitea.package;
|
||||
providerName = "kanidm";
|
||||
clientId = "forgejo";
|
||||
args = lib.escapeShellArgs [
|
||||
"--name"
|
||||
providerName
|
||||
"--provider"
|
||||
"openidConnect"
|
||||
"--key"
|
||||
clientId
|
||||
"--auto-discover-url"
|
||||
"https://${sentinelCfg.networking.providedDomains.kanidm}/oauth2/openid/${clientId}/.well-known/openid-configuration"
|
||||
#"--required-claim-name" "groups"
|
||||
#"--group-claim-name" "groups"
|
||||
#"--admin-group" "/forge_admins@${domain}"
|
||||
"--skip-local-2fa"
|
||||
];
|
||||
in
|
||||
lib.mkAfter ''
|
||||
provider_id=$(${exe} admin auth list | ${pkgs.gnugrep}/bin/grep -w '${providerName}' | cut -f1)
|
||||
if [[ -z "$provider_id" ]]; then
|
||||
FORGEJO_ADMIN_OAUTH2_SECRET="$(< ${config.age.secrets.forgejo-oauth2-client-secret.path})" ${exe} admin auth add-oauth ${args}
|
||||
else
|
||||
FORGEJO_ADMIN_OAUTH2_SECRET="$(< ${config.age.secrets.forgejo-oauth2-client-secret.path})" ${exe} admin auth update-oauth --id "$provider_id" ${args}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
152
hosts/ward/guests/grafana.nix
Normal file
152
hosts/ward/guests/grafana.nix
Normal file
|
@ -0,0 +1,152 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
grafanaDomain = "grafana.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.grafana.settings.server.http_port];
|
||||
|
||||
age.secrets.grafana-secret-key = {
|
||||
rekeyFile = config.node.secretsDir + "/grafana-secret-key.age";
|
||||
mode = "440";
|
||||
group = "grafana";
|
||||
};
|
||||
|
||||
age.secrets.grafana-loki-basic-auth-password = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "grafana";
|
||||
};
|
||||
|
||||
age.secrets.grafana-influxdb-token = {
|
||||
generator.script = "alnum";
|
||||
generator.tags = ["influxdb"];
|
||||
mode = "440";
|
||||
group = "grafana";
|
||||
};
|
||||
|
||||
# Mirror the original oauth2 secret
|
||||
age.secrets.grafana-oauth2-client-secret = {
|
||||
inherit (nodes.ward-kanidm.config.age.secrets.kanidm-oauth2-grafana) rekeyFile;
|
||||
mode = "440";
|
||||
group = "grafana";
|
||||
};
|
||||
|
||||
nodes.ward-influxdb = {
|
||||
# Mirror the original secret on the influx host
|
||||
age.secrets."grafana-influxdb-token-${config.node.name}" = {
|
||||
inherit (config.age.secrets.grafana-influxdb-token) rekeyFile;
|
||||
mode = "440";
|
||||
group = "influxdb2";
|
||||
};
|
||||
|
||||
services.influxdb2.provision.organization.servers.auths."grafana servers:telegraf (${config.node.name})" = {
|
||||
readBuckets = ["telegraf"];
|
||||
writeBuckets = ["telegraf"];
|
||||
tokenFile = nodes.ward-influxdb.config.age.secrets."grafana-influxdb-token-${config.node.name}".path;
|
||||
};
|
||||
};
|
||||
|
||||
nodes.sentinel = {
|
||||
age.secrets.loki-basic-auth-hashes.generator.dependencies = [
|
||||
config.age.secrets.grafana-loki-basic-auth-password
|
||||
];
|
||||
|
||||
networking.providedDomains.grafana = grafanaDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.grafana = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.grafana.settings.server.http_port}" = {};
|
||||
extraConfig = ''
|
||||
zone grafana 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${grafanaDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://grafana";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.grafana = {
|
||||
enable = true;
|
||||
settings = {
|
||||
analytics.reporting_enabled = false;
|
||||
users.allow_sign_up = false;
|
||||
|
||||
server = {
|
||||
domain = grafanaDomain;
|
||||
root_url = "https://${grafanaDomain}";
|
||||
enforce_domain = true;
|
||||
enable_gzip = true;
|
||||
http_addr = "0.0.0.0";
|
||||
http_port = 3001;
|
||||
};
|
||||
|
||||
security = {
|
||||
disable_initial_admin_creation = true;
|
||||
secret_key = "$__file{${config.age.secrets.grafana-secret-key.path}}";
|
||||
cookie_secure = true;
|
||||
disable_gravatar = true;
|
||||
hide_version = true;
|
||||
};
|
||||
|
||||
auth.disable_login_form = true;
|
||||
"auth.generic_oauth" = {
|
||||
enabled = true;
|
||||
name = "Kanidm";
|
||||
icon = "signin";
|
||||
allow_sign_up = true;
|
||||
#auto_login = true;
|
||||
client_id = "grafana";
|
||||
client_secret = "$__file{${config.age.secrets.grafana-oauth2-client-secret.path}}";
|
||||
scopes = "openid email profile";
|
||||
login_attribute_path = "prefered_username";
|
||||
auth_url = "https://${sentinelCfg.networking.providedDomains.kanidm}/ui/oauth2";
|
||||
token_url = "https://${sentinelCfg.networking.providedDomains.kanidm}/oauth2/token";
|
||||
api_url = "https://${sentinelCfg.networking.providedDomains.kanidm}/oauth2/openid/grafana/userinfo";
|
||||
use_pkce = true;
|
||||
# Allow mapping oauth2 roles to server admin
|
||||
allow_assign_grafana_admin = true;
|
||||
role_attribute_path = "contains(scopes[*], 'server_admin') && 'GrafanaAdmin' || contains(scopes[*], 'admin') && 'Admin' || contains(scopes[*], 'editor') && 'Editor' || 'Viewer'";
|
||||
};
|
||||
};
|
||||
|
||||
provision = {
|
||||
enable = true;
|
||||
datasources.settings.datasources = [
|
||||
{
|
||||
name = "InfluxDB (servers)";
|
||||
type = "influxdb";
|
||||
access = "proxy";
|
||||
url = "https://${sentinelCfg.networking.providedDomains.influxdb}";
|
||||
orgId = 1;
|
||||
secureJsonData.token = "$__file{${config.age.secrets.grafana-influxdb-token.path}}";
|
||||
jsonData.version = "Flux";
|
||||
jsonData.organization = "servers";
|
||||
jsonData.defaultBucket = "telegraf";
|
||||
}
|
||||
# TODO duplicate above influxdb source (with scoped read tokens??) for each organization
|
||||
{
|
||||
name = "Loki";
|
||||
type = "loki";
|
||||
access = "proxy";
|
||||
url = "https://${sentinelCfg.networking.providedDomains.loki}";
|
||||
orgId = 1;
|
||||
basicAuth = true;
|
||||
basicAuthUser = "${config.node.name}+grafana-loki-basic-auth-password";
|
||||
secureJsonData.basicAuthPassword = "$__file{${config.age.secrets.grafana-loki-basic-auth-password.path}}";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.grafana.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
}
|
40
hosts/ward/guests/immich.nix
Normal file
40
hosts/ward/guests/immich.nix
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
immichDomain = "immich.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.immich.web_port];
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.immich = immichDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.immich = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.immich.settings.bind_port}" = {};
|
||||
extraConfig = ''
|
||||
zone immich 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${immichDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
oauth2.enable = true;
|
||||
oauth2.allowedGroups = ["access_immich"];
|
||||
locations."/" = {
|
||||
proxyPass = "http://immich";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.immich = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
systemd.services.grafana.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
}
|
92
hosts/ward/guests/influxdb.nix
Normal file
92
hosts/ward/guests/influxdb.nix
Normal file
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
nodes,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
influxdbDomain = "influxdb.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
influxdbPort = 8086;
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [influxdbPort];
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.influxdb = influxdbDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.influxdb = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString influxdbPort}" = {};
|
||||
extraConfig = ''
|
||||
zone influxdb 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${influxdbDomain} = let
|
||||
accessRules = ''
|
||||
satisfy any;
|
||||
${lib.concatMapStrings (ip: "allow ${ip};\n") sentinelCfg.meta.wireguard.proxy-sentinel.server.reservedAddresses}
|
||||
deny all;
|
||||
'';
|
||||
in {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
oauth2.enable = true;
|
||||
oauth2.allowedGroups = ["access_influxdb"];
|
||||
locations."/" = {
|
||||
proxyPass = "http://influxdb";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = accessRules;
|
||||
};
|
||||
locations."/api/v2/write" = {
|
||||
proxyPass = "http://influxdb/api/v2/write";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
${accessRules}
|
||||
access_log off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
age.secrets.influxdb-admin-password = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "influxdb2";
|
||||
};
|
||||
|
||||
age.secrets.influxdb-admin-token = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "influxdb2";
|
||||
};
|
||||
|
||||
age.secrets.influxdb-user-telegraf-token = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "influxdb2";
|
||||
};
|
||||
|
||||
services.influxdb2 = {
|
||||
enable = true;
|
||||
settings = {
|
||||
reporting-disabled = true;
|
||||
http-bind-address = "0.0.0.0:${toString influxdbPort}";
|
||||
};
|
||||
provision = {
|
||||
enable = true;
|
||||
initialSetup = {
|
||||
organization = "default";
|
||||
bucket = "default";
|
||||
passwordFile = config.age.secrets.influxdb-admin-password.path;
|
||||
tokenFile = config.age.secrets.influxdb-admin-token.path;
|
||||
};
|
||||
organizations.servers.buckets.telegraf = {};
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [pkgs.influxdb2-cli];
|
||||
|
||||
systemd.services.grafana.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
}
|
157
hosts/ward/guests/kanidm.nix
Normal file
157
hosts/ward/guests/kanidm.nix
Normal file
|
@ -0,0 +1,157 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
inherit (sentinelCfg.repo.secrets.local) personalDomain;
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
kanidmDomain = "auth.${personalDomain}";
|
||||
kanidmPort = 8300;
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [kanidmPort];
|
||||
|
||||
age.secrets."kanidm-self-signed.crt" = {
|
||||
rekeyFile = config.node.secretsDir + "/kanidm-self-signed.crt.age";
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
|
||||
age.secrets."kanidm-self-signed.key" = {
|
||||
rekeyFile = config.node.secretsDir + "/kanidm-self-signed.key.age";
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
|
||||
age.secrets.kanidm-admin-password = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
|
||||
age.secrets.kanidm-idm-admin-password = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
|
||||
age.secrets.kanidm-oauth2-grafana = {
|
||||
generator.script = "alnum";
|
||||
generator.tags = ["oauth2"];
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
|
||||
age.secrets.kanidm-oauth2-forgejo = {
|
||||
generator.script = "alnum";
|
||||
generator.tags = ["oauth2"];
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
|
||||
age.secrets.kanidm-oauth2-web-sentinel = {
|
||||
generator.script = "alnum";
|
||||
generator.tags = ["oauth2"];
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.kanidm = kanidmDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.kanidm = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString kanidmPort}" = {};
|
||||
extraConfig = ''
|
||||
zone kanidm 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${kanidmDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
locations."/".proxyPass = "https://kanidm";
|
||||
# Allow using self-signed certs to satisfy kanidm's requirement
|
||||
# for TLS connections. (Although this is over wireguard anyway)
|
||||
extraConfig = ''
|
||||
proxy_ssl_verify off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.kanidm = {
|
||||
enableServer = true;
|
||||
serverSettings = {
|
||||
domain = kanidmDomain;
|
||||
origin = "https://${kanidmDomain}";
|
||||
tls_chain = config.age.secrets."kanidm-self-signed.crt".path;
|
||||
tls_key = config.age.secrets."kanidm-self-signed.key".path;
|
||||
bindaddress = "0.0.0.0:${toString kanidmPort}";
|
||||
trust_x_forward_for = true;
|
||||
};
|
||||
|
||||
enableClient = true;
|
||||
clientSettings = {
|
||||
uri = config.services.kanidm.serverSettings.origin;
|
||||
verify_ca = true;
|
||||
verify_hostnames = true;
|
||||
};
|
||||
|
||||
provision = {
|
||||
enable = true;
|
||||
adminPasswordFile = config.age.secrets.kanidm-admin-password.path;
|
||||
idmAdminPasswordFile = config.age.secrets.kanidm-idm-admin-password.path;
|
||||
|
||||
inherit (config.repo.secrets.global.kanidm) persons;
|
||||
|
||||
# Grafana
|
||||
groups.grafana = {};
|
||||
groups."grafana.admins" = {};
|
||||
groups."grafana.editors" = {};
|
||||
groups."grafana.server-admins" = {};
|
||||
systems.oauth2.grafana = {
|
||||
displayName = "Grafana";
|
||||
originUrl = "https://${sentinelCfg.networking.providedDomains.grafana}";
|
||||
basicSecretFile = config.age.secrets.kanidm-oauth2-grafana.path;
|
||||
scopeMaps.grafana = ["openid" "email" "profile"];
|
||||
supplementaryScopeMaps = {
|
||||
"grafana.admins" = ["admin"];
|
||||
"grafana.editors" = ["editor"];
|
||||
"grafana.server-admins" = ["server_admin"];
|
||||
};
|
||||
};
|
||||
|
||||
# Forgejo
|
||||
groups.forgejo = {};
|
||||
groups."forgejo.admins" = {};
|
||||
systems.oauth2.forgejo = {
|
||||
displayName = "Forgejo";
|
||||
originUrl = "https://${sentinelCfg.networking.providedDomains.forgejo}";
|
||||
basicSecretFile = config.age.secrets.kanidm-oauth2-forgejo.path;
|
||||
scopeMaps.forgejo = ["openid" "email" "profile"];
|
||||
supplementaryScopeMaps = {
|
||||
"forgejo.admins" = ["admin"];
|
||||
};
|
||||
};
|
||||
|
||||
# Web Sentinel
|
||||
groups.web-sentinel = {};
|
||||
groups."web-sentinel.adguardhome" = {};
|
||||
groups."web-sentinel.influxdb" = {};
|
||||
systems.oauth2.web-sentinel = {
|
||||
displayName = "Web Sentinel";
|
||||
originUrl = "https://oauth2.${personalDomain}";
|
||||
basicSecretFile = config.age.secrets.kanidm-oauth2-web-sentinel.path;
|
||||
scopeMaps.web-sentinel = ["openid" "email"];
|
||||
supplementaryScopeMaps = {
|
||||
"web-sentinel.adguardhome" = ["access_adguardhome"];
|
||||
"web-sentinel.influxdb" = ["access_influxdb"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [pkgs.kanidm];
|
||||
systemd.services.kanidm.serviceConfig.RestartSec = "60"; # Retry every minute
|
||||
}
|
126
hosts/ward/guests/loki.nix
Normal file
126
hosts/ward/guests/loki.nix
Normal file
|
@ -0,0 +1,126 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
lokiDomain = "loki.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.loki.configuration.server.http_listen_port];
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.loki = lokiDomain;
|
||||
|
||||
age.secrets.loki-basic-auth-hashes = {
|
||||
generator.script = "basic-auth";
|
||||
mode = "440";
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
upstreams.loki = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.loki.configuration.server.http_listen_port}" = {};
|
||||
extraConfig = ''
|
||||
zone loki 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${lokiDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://loki";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
auth_basic "Authentication required";
|
||||
auth_basic_user_file ${sentinelCfg.age.secrets.loki-basic-auth-hashes.path};
|
||||
|
||||
proxy_read_timeout 1800s;
|
||||
proxy_connect_timeout 1600s;
|
||||
|
||||
access_log off;
|
||||
'';
|
||||
};
|
||||
locations."= /ready" = {
|
||||
proxyPass = "http://loki";
|
||||
extraConfig = ''
|
||||
auth_basic off;
|
||||
access_log off;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.loki = let
|
||||
lokiDir = "/var/lib/loki";
|
||||
in {
|
||||
enable = true;
|
||||
configuration = {
|
||||
analytics.reporting_enabled = false;
|
||||
auth_enabled = false;
|
||||
|
||||
server = {
|
||||
http_listen_address = "0.0.0.0";
|
||||
http_listen_port = 3100;
|
||||
log_level = "warn";
|
||||
};
|
||||
|
||||
ingester = {
|
||||
lifecycler = {
|
||||
address = "127.0.0.1";
|
||||
ring = {
|
||||
kvstore.store = "inmemory";
|
||||
replication_factor = 1;
|
||||
};
|
||||
final_sleep = "0s";
|
||||
};
|
||||
chunk_idle_period = "5m";
|
||||
chunk_retain_period = "30s";
|
||||
};
|
||||
|
||||
schema_config.configs = [
|
||||
{
|
||||
from = "2023-06-01";
|
||||
store = "tsdb";
|
||||
object_store = "filesystem";
|
||||
schema = "v12";
|
||||
index = {
|
||||
prefix = "index_";
|
||||
period = "24h";
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
storage_config = {
|
||||
tsdb_shipper = {
|
||||
active_index_directory = "${lokiDir}/tsdb-index";
|
||||
cache_location = "${lokiDir}/tsdb-cache";
|
||||
cache_ttl = "24h";
|
||||
shared_store = "filesystem";
|
||||
};
|
||||
filesystem.directory = "${lokiDir}/chunks";
|
||||
};
|
||||
|
||||
# Do not accept new logs that are ingressed when they are actually already old.
|
||||
limits_config = {
|
||||
reject_old_samples = true;
|
||||
reject_old_samples_max_age = "168h";
|
||||
};
|
||||
|
||||
# Do not delete old logs automatically
|
||||
table_manager = {
|
||||
retention_deletes_enabled = false;
|
||||
retention_period = "0s";
|
||||
};
|
||||
|
||||
compactor = {
|
||||
working_directory = lokiDir;
|
||||
shared_store = "filesystem";
|
||||
compactor_ring.kvstore.store = "inmemory";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.loki.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
}
|
68
hosts/ward/guests/paperless.nix
Normal file
68
hosts/ward/guests/paperless.nix
Normal file
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
paperlessDomain = "paperless.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
# XXX: remove microvm.mem = 1024 * 12;
|
||||
# XXX: remove microvm.vcpu = 4;
|
||||
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [
|
||||
config.services.paperless.port
|
||||
];
|
||||
|
||||
age.secrets.paperless-admin-password = {
|
||||
rekeyFile = config.node.secretsDir + "/paperless-admin-password.age";
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "paperless";
|
||||
};
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.paperless = paperlessDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.paperless = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.paperless.port}" = {};
|
||||
extraConfig = ''
|
||||
zone paperless 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${paperlessDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
extraConfig = ''
|
||||
client_max_body_size 512M;
|
||||
'';
|
||||
locations."/" = {
|
||||
proxyPass = "http://paperless";
|
||||
proxyWebsockets = true;
|
||||
X-Frame-Options = "SAMEORIGIN";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.paperless = {
|
||||
enable = true;
|
||||
address = "0.0.0.0";
|
||||
passwordFile = config.age.secrets.paperless-admin-password.path;
|
||||
extraConfig = {
|
||||
PAPERLESS_URL = "https://${paperlessDomain}";
|
||||
PAPERLESS_CONSUMER_ENABLE_BARCODES = true;
|
||||
PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE = true;
|
||||
PAPERLESS_CONSUMER_BARCODE_SCANNER = "ZXING";
|
||||
PAPERLESS_FILENAME_FORMAT = "{created_year}-{created_month}-{created_day}_{asn}_{title}";
|
||||
#PAPERLESS_IGNORE_DATES = concatStringsSep "," ignoreDates;
|
||||
PAPERLESS_NUMBER_OF_SUGGESTED_DATES = 4;
|
||||
PAPERLESS_OCR_LANGUAGE = "deu+eng";
|
||||
PAPERLESS_TASK_WORKERS = 4;
|
||||
PAPERLESS_WEBSERVER_WORKERS = 4;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.paperless.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
}
|
93
hosts/ward/guests/vaultwarden.nix
Normal file
93
hosts/ward/guests/vaultwarden.nix
Normal file
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
nodes,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
vaultwardenDomain = "pw.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [
|
||||
config.services.vaultwarden.config.rocketPort
|
||||
config.services.vaultwarden.config.websocketPort
|
||||
];
|
||||
|
||||
age.secrets.vaultwarden-env = {
|
||||
rekeyFile = config.node.secretsDir + "/vaultwarden-env.age";
|
||||
mode = "440";
|
||||
group = "vaultwarden";
|
||||
};
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.vaultwarden = vaultwardenDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.vaultwarden = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.vaultwarden.config.rocketPort}" = {};
|
||||
extraConfig = ''
|
||||
zone vaultwarden 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
upstreams.vaultwarden-websocket = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.vaultwarden.config.websocketPort}" = {};
|
||||
extraConfig = ''
|
||||
zone vaultwarden-websocket 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${vaultwardenDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
extraConfig = ''
|
||||
client_max_body_size 256M;
|
||||
'';
|
||||
locations."/".proxyPass = "http://vaultwarden";
|
||||
locations."/notifications/hub" = {
|
||||
proxyPass = "http://vaultwarden-websocket";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
locations."/notifications/hub/negotiate" = {
|
||||
proxyPass = "http://vaultwarden";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.vaultwarden = {
|
||||
enable = true;
|
||||
dbBackend = "sqlite";
|
||||
config = {
|
||||
dataFolder = lib.mkForce "/var/lib/vaultwarden";
|
||||
extendedLogging = true;
|
||||
useSyslog = true;
|
||||
webVaultEnabled = true;
|
||||
|
||||
websocketEnabled = true;
|
||||
websocketAddress = "0.0.0.0";
|
||||
websocketPort = 3012;
|
||||
rocketAddress = "0.0.0.0";
|
||||
rocketPort = 8012;
|
||||
|
||||
signupsAllowed = false;
|
||||
passwordIterations = 1000000;
|
||||
invitationsAllowed = true;
|
||||
invitationOrgName = "Vaultwarden";
|
||||
domain = "https://${vaultwardenDomain}";
|
||||
|
||||
smtpEmbedImages = true;
|
||||
smtpSecurity = "force_tls";
|
||||
smtpPort = 465;
|
||||
};
|
||||
#backupDir = "/data/backup";
|
||||
environmentFile = config.age.secrets.vaultwarden-env.path;
|
||||
};
|
||||
|
||||
# Replace uses of old name
|
||||
systemd.services.backup-vaultwarden.environment.DATA_FOLDER = lib.mkForce "/var/lib/vaultwarden";
|
||||
systemd.services.vaultwarden.serviceConfig = {
|
||||
StateDirectory = lib.mkForce "vaultwarden";
|
||||
RestartSec = "600"; # Retry every 10 minutes
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue