Add selfhosted services

This commit is contained in:
aloslider
2026-06-30 04:26:40 +03:00
parent f212c874b5
commit 13e407847f
78 changed files with 2346 additions and 690 deletions
-1
View File
@@ -1 +0,0 @@
Home configs per host.
-1
View File
@@ -1 +0,0 @@
Home config for benq user on homelab.
-6
View File
@@ -1,6 +0,0 @@
{ config, ... }:
{
services.podman = {
enable = true;
};
}
@@ -1,18 +0,0 @@
{ config, lib, ... }:
with lib;
{
options.selfhosted.common = {
networkName = mkOption {
type = types.str;
};
dataDir = mkOption {
type = types.str;
};
};
config = {
sops.secrets = {
"domains/serv" = { };
};
};
}
@@ -1,53 +0,0 @@
{ config, ... }:
let
prefix = "sh-";
cfg = config.selfhosted;
in
{
imports = [
./common.nix
./homarr
./netbird
./pocket-id
./vaultwarden
];
config.selfhosted = {
common = {
networkName = "selfhosted";
dataDir = "/mnt/data";
};
homarr = {
name = "${prefix}homarr";
subdomain = "dash";
};
netbird =
let
nbPrefix = "${prefix}nb-";
in
{
subdomain = "nb";
traefik = {
name = "${nbPrefix}traefik";
};
dashboard = {
name = "${nbPrefix}dashboard";
};
server = {
name = "${nbPrefix}server";
};
proxy = {
name = "${nbPrefix}proxy";
};
};
pocket-id = {
name = "${prefix}pocket-id";
subdomain = "id";
};
vaultwarden = {
name = "${prefix}vaultwarden";
subdomain = "vault";
};
};
}
@@ -1,40 +0,0 @@
{ config, lib, ... }:
with lib;
let
common = config.selfhosted.common;
cfg = config.selfhosted.homarr;
hostDataPath = "${common.dataDir}/homarr/data";
in
{
options.selfhosted.homarr = {
name = mkOption {
type = types.str;
};
subdomain = mkOption {
type = types.str;
};
};
config = {
sops.secrets = {
"homarr/enc_key" = { };
};
services.podman.containers."${cfg.name}" = {
image = "ghcr.io/homarr-labs/homarr:v1.60.0";
network = common.networkName;
environment = {
SECRET_ENCRYPTION_KEY = "${config.sops.secrets."homarr/enc_key".path}";
};
volumes = [
"${hostDataPath}:/appdata"
];
autoStart = true;
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
};
};
};
}
@@ -1,195 +0,0 @@
{ config, ... }:
let
cfg = config.selfhosted.netbird;
poCfg = config.selfhosted.pocket-id;
domain = config.sops.placeholder."domains/serv";
fullDomain = "${cfg.subdomain}.${domain}";
in
{
sops.secrets = {
"netbird/traefik/letsEncryptEmail" = { };
"netbird/auth/client_id" = { };
"netbird/auth/client_secret" = { };
"netbird/server/auth_secret" = { };
"netbird/server/enc_key" = { };
"netbird/proxy/token" = { };
};
sops.templates."nb-traefik.yaml".content = ''
log:
level: INFO
providers:
file:
filename: /etc/traefik/dynamic.yaml
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
certificatesResolvers:
letsencrypt:
acme:
email: ${config.sops.placeholder."netbird/traefik/letsEncryptEmail"}
storage: /letsencrypt/acme.json
tlsChallenge: true
'';
sops.templates."nb-traefik-dynamic.yaml".content =
let
nbServerName = "nb-server";
nbServerH2cName = "nb-server-h2c";
nbDashboardName = "nb-dashboard";
in
''
http:
routers:
nb-relay:
rule: Host(`${fullDomain}`) && PathPrefix(`/relay`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ${nbServerName}
priority: 110
nb-ws-signal:
rule: Host(`${fullDomain}`) && PathPrefix(`/ws-proxy/signal`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ${nbServerName}
priority: 110
nb-ws-management:
rule: Host(`${fullDomain}`) && PathPrefix(`/ws-proxy/management`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ${nbServerName}
priority: 110
nb-grpc-signal:
rule: Host(`${fullDomain}`) && PathPrefix(`/signalexchange.SignalExchange/`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ${nbServerH2cName}
priority: 100
nb-grpc-management:
rule: Host(`${fullDomain}`) && PathPrefix(`/management.ManagementService/`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ${nbServerH2cName}
priority: 100
nb-api:
rule: Host(`${fullDomain}`) && PathPrefix(`/api`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ${nbServerName}
priority: 90
${nbDashboardName}:
rule: Host(`${fullDomain}`)
entryPoints:
- websecure
tls:
certResolver: letsencrypt
service: ${nbDashboardName}
priority: 10
services:
${nbServerName}:
loadBalancer:
servers:
- url: http://${cfg.server.name}:80
${nbServerH2cName}:
loadBalancer:
servers:
- url: http://${cfg.server.name}:80
scheme: h2c
${nbDashboardName}:
loadBalancer:
servers:
- url: http://${cfg.dashboard.name}:80
tcp:
serversTransports:
pp-v2:
proxyProtocol:
version: 2
'';
sops.templates."nb-dashboard.env".content = ''
NETBIRD_MGMT_API_ENDPOINT=https://${fullDomain}
NETBIRD_MGMT_GRPC_API_ENDPOINT=https://${fullDomain}
AUTH_AUDIENCE=${cfg.dashboard.name}
AUTH_CLIENT_ID=${config.sops.placeholder."netbird/auth/client_id"}
AUTH_CLIENT_SECRET=${config.sops.placeholder."netbird/auth/client_secret"}
AUTH_AUTHORITY=https://${poCfg.subdomain}.${domain}
USE_AUTH0=false
AUTH_SUPPORTED_SCOPES=openid profile email groups
AUTH_REDIRECT_URI=/nb-auth
AUTH_SILENT_REDIRECT_URI=/nb-silent-auth
LETSENCRYPT_DOMAIN=none
LETSENCRYPT_EMAIL=${config.sops.placeholder."netbird/traefik/letsEncryptEmail"}
'';
sops.templates."nb-config.yaml".content = ''
server:
listenAddress: ":80"
exposedAddress: "https://${fullDomain}"
stunPorts:
- 3478
metricsPort: 9090
healthcheckAddress: ":9000"
logLevel: "info"
logFile: "console"
authSecret: "${config.sops.placeholder."netbird/server/auth_secret"}"
auth:
issuer: "https://${poCfg.subdomain}.${domain}"
clientID: "${config.sops.placeholder."netbird/auth/client_id"}"
clientSecret: "${config.sops.placeholder."netbird/auth/client_secret"}"
reverseProxy:
trustedHTTPProxies:
- "127.0.0.1/32"
store:
engine: "sqlite"
encryptionKey: "${config.sops.placeholder."netbird/server/enc_key"}"
'';
sops.templates."nb-proxy.env".content = ''
NB_PROXY_DOMAIN=${fullDomain}
NB_PROXY_TOKEN=${config.sops.placeholder."netbird/proxy/token"}
NB_PROXY_MANAGEMENT_ADDRESS=http://${cfg.server.name}:80
NB_PROXY_ALLOW_INSECURE=true
NB_PROXY_ADDRESS=:8443
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
'';
}
@@ -1,110 +0,0 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
common = config.selfhosted.common;
cfg = config.selfhosted.netbird;
hostDataPath = "${common.dataDir}/netbird";
in
{
imports = [
./config.nix
];
options.selfhosted.netbird = {
subdomain = mkOption {
type = types.str;
};
traefik = {
name = mkOption {
type = types.str;
};
};
dashboard = {
name = mkOption {
type = types.str;
};
};
server = {
name = mkOption {
type = types.str;
};
};
proxy = {
name = mkOption {
type = types.str;
};
};
};
config = {
services.podman.containers.${cfg.traefik.name} = {
image = "traefik:v3.6";
network = common.networkName;
ports = [
"0.0.0.0:80:80"
"0.0.0.0:443:443"
];
autoStart = true;
volumes = [
"${hostDataPath}/traefik/letsencrypt:/letsencrypt"
"${config.sops.templates."nb-traefik.yaml".path}:/etc/traefik/config.yaml"
"${config.sops.templates."nb-traefik-dynamic.yaml".path}:/etc/traefik/dynamic.yaml"
];
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
};
};
services.podman.containers.${cfg.dashboard.name} = {
image = "netbirdio/dashboard:v2.37.1";
network = common.networkName;
environmentFile = [ "${config.sops.templates."nb-dashboard.env".path}" ];
autoStart = true;
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
};
};
services.podman.containers.${cfg.server.name} = {
image = "netbirdio/netbird-server:0.70.5";
network = common.networkName;
ports = [
"0.0.0.0:3478:3478/udp"
];
autoStart = true;
volumes = [
"${hostDataPath}/server/data:/var/lib/netbird"
"${config.sops.templates."nb-config.yaml".path}:/etc/netbird/config.yaml"
];
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
};
};
services.podman.containers.${cfg.proxy.name} = {
image = "netbirdio/reverse-proxy:0.70.5";
network = common.networkName;
environmentFile = [ "${config.sops.templates."nb-proxy.env".path}" ];
autoStart = true;
volumes = [
"${hostDataPath}/proxy/certs:/certs"
];
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
};
};
};
}
@@ -1,20 +0,0 @@
{ config, ... }:
let
cfg = config.selfhosted.pocket-id;
in
{
sops.secrets = {
"pocket_id/enc_key" = { };
};
sops.templates."pocket-id.env" = {
content = ''
APP_URL=https://${cfg.subdomain}.${config.sops.placeholder."domains/serv"}
ENCRYPTION_KEY=${config.sops.placeholder."pocket_id/enc_key"}
TRUST_PROXY=true
ANALYTICS_DISABLED=true
PUID=1000
PGID=1000
'';
};
}
@@ -1,45 +0,0 @@
{ config, lib, ... }:
with lib;
let
common = config.selfhosted.common;
cfg = config.selfhosted.pocket-id;
hostDataPath = "${common.dataDir}/pocket-id/data";
in
{
imports = [
./config.nix
];
options.selfhosted.pocket-id = {
name = mkOption {
type = types.str;
};
subdomain = mkOption {
type = types.str;
};
};
config = {
services.podman.containers.${cfg.name} = {
image = "ghcr.io/pocket-id/pocket-id:v2";
network = common.networkName;
environmentFile = [ "${config.sops.templates."pocket-id.env".path}" ];
volumes = [
"${hostDataPath}:/app/data"
];
autoStart = true;
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
Container = {
HealthCmd = ''"/app/pocket-id" "healthcheck"'';
HealthInterval = "1m30s";
HealthTimeout = "5s";
HealthRetries = "2";
HealthStartPeriod = "10s";
};
};
};
};
}
@@ -1,26 +0,0 @@
{ config, ... }:
let
cfg = config.selfhosted.vaultwarden;
poCfg = config.selfhosted.pocket-id;
domain = config.sops.placeholder."domains/serv";
in
{
sops.secrets = {
"vaultwarden/client_id" = { };
"vaultwarden/client_secret" = { };
};
sops.templates."vaultwarden.env".content = ''
DOMAIN=https://${cfg.subdomain}.${domain}
LOG_LEVEL=info,vaultwarden::sso=debug
SIGNUPS_ALLOWED=false
SSO_ENABLED=false
SSO_DEBUG_TOKENS=true
SSO_ONLY=false
SSO_AUTHORITY=https://${poCfg.subdomain}.${domain}
SSO_CLIENT_ID=${config.sops.placeholder."vaultwarden/client_id"}
SSO_CLIENT_SECRET=${config.sops.placeholder."vaultwarden/client_secret"}
SSO_AUTH_ONLY_NOT_SESSION=true
SSO_PKCE=true
'';
}
@@ -1,39 +0,0 @@
{ config, lib, ... }:
with lib;
let
common = config.selfhosted.common;
cfg = config.selfhosted.vaultwarden;
hostDataPath = "${common.dataDir}/vaultwarden/data";
in
{
imports = [
./config.nix
];
options.selfhosted.vaultwarden = {
enable = mkEnableOption "Enable container";
name = mkOption {
type = types.str;
};
subdomain = mkOption {
type = types.str;
};
};
config = {
services.podman.containers.${cfg.name} = {
image = "docker.io/vaultwarden/server:1.36.0";
network = config.selfhosted.common.networkName;
environmentFile = [ "${config.sops.templates."vaultwarden.env".path}" ];
volumes = [
"${hostDataPath}:/data"
];
autoStart = true;
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
};
};
};
}
-7
View File
@@ -1,7 +0,0 @@
{ config, ... }:
{
imports = [
./network.nix
./containers
];
}
-11
View File
@@ -1,11 +0,0 @@
{ config, ... }:
let
common = config.selfhosted.common;
in
{
services.podman = {
networks.${common.networkName} = {
driver = "bridge";
};
};
}
+2 -2
View File
@@ -22,9 +22,9 @@
imports = lib.flatten [
(map lib.custom.relativeToRoot (
[
"home/benq/core"
"home/common/core"
]
++ (map (f: "home/benq/optional/${f}") [
++ (map (f: "home/common/optional/${f}") [
])
))
@@ -3,8 +3,6 @@
imports = [
./git.nix
./packages.nix
./podman.nix
./selfhosted
./sops.nix
# ./sops.nix
];
}
@@ -6,7 +6,7 @@
sops = {
defaultSopsFile = "${dotfiles}/dot_config/sops/secrets.yaml";
age.keyFile = "${config.xdg.configHome}/age/key.txt";
age.keyFile = "${config.xdg.configHome}/sops/age/keys.txt";
};
home.file.".sops.yaml".source = "${dotfiles}/dot_sops.yaml";