Add selfhosted services
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
keys:
|
||||
- &users:
|
||||
- &benq age1tl5hp6v0t38fl8gxuqds40z503mwz885ev22au4jdy5r24aak3zsza23sx
|
||||
creation_rules:
|
||||
- path_regex: secrets.yaml$
|
||||
key_group:
|
||||
- age:
|
||||
- *benq
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
STATUS=$(bw status | jq -r '.status')
|
||||
case "$STATUS" in
|
||||
"unauthenticated")
|
||||
echo "Unauthenticated. Setting server config..."
|
||||
|
||||
read -rp "Enter vault domain: " VAULT_URL
|
||||
if [[ -z "$VAULT_URL" ]]; then
|
||||
echo "Error: url cannot be empty"
|
||||
exit 1
|
||||
fi
|
||||
bw config server "https://$VAULT_URL"
|
||||
|
||||
export BW_SESSION=$(bw login --raw)
|
||||
;;
|
||||
"locked")
|
||||
export BW_SESSION=$(bw unlock --raw)
|
||||
;;
|
||||
"unlocked")
|
||||
echo "Vault is already unlocked"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown status: $STATUS"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Syncing vault..."
|
||||
bw sync
|
||||
|
||||
FILES_DIR="$HOME/nixos-deploy"
|
||||
rm -rf "$FILES_DIR"
|
||||
mkdir -p "$FILES_DIR"
|
||||
|
||||
AGE_DIR="$FILES_DIR/etc/age"
|
||||
mkdir -p "$AGE_DIR"
|
||||
|
||||
KEY_FILE=$AGE_DIR/key.txt
|
||||
|
||||
echo "Writing age key..."
|
||||
bw get item "age-keys" \
|
||||
| jq -r '.fields[] | select(.name == "private_key") | .value' \
|
||||
> "$KEY_FILE"
|
||||
|
||||
chmod 700 "$AGE_DIR"
|
||||
chmod 600 "$KEY_FILE"
|
||||
|
||||
if [[ ! -s "$KEY_FILE" ]]; then
|
||||
echo "Failed to create age key"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copying sops files..."
|
||||
SOPS_DIR="$FILES_DIR/etc/sops"
|
||||
mkdir -p "$SOPS_DIR"
|
||||
cp .sops.yaml secrets.yaml "$SOPS_DIR"
|
||||
|
||||
echo "Copying nixos config..."
|
||||
NIXOS_CFG_DIR="$FILES_DIR/etc/nixos-config"
|
||||
rsync -r --exclude ".sops.yaml" --exclude "secrets.yaml" ./* "$NIXOS_CFG_DIR"
|
||||
|
||||
read -p "Enter IP of the target (user@ip): " TARGET
|
||||
if [[ -z "$TARGET" ]]; then
|
||||
echo "Error: TARGET cannot be empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nix run --extra-experimental-features "nix-command flakes" \
|
||||
github:nix-community/nixos-anywhere -- \
|
||||
--flake .#homelab \
|
||||
--extra-files "$FILES_DIR" \
|
||||
--target-host "$TARGET"
|
||||
Generated
+18
-18
@@ -5,11 +5,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1773889306,
|
||||
"narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
|
||||
"lastModified": 1777713215,
|
||||
"narHash": "sha256-8GzXDOXckDWwST8TY5DbwYFjdvQLlP7K9CLSVx6iTTo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
|
||||
"rev": "63b4e7e6cf75307c1d26ac3762b886b5b0247267",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -21,11 +21,11 @@
|
||||
"dotfiles": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1778400209,
|
||||
"narHash": "sha256-mEGIHZaTmQulmWLg3C8r0wdpEllFcKWeAt+3FLa4grM=",
|
||||
"lastModified": 1778445660,
|
||||
"narHash": "sha256-eiGc//3MdHJJSlIXcdtjT+Fdgg+xrK8eqYpRBALzFyw=",
|
||||
"owner": "aloslider",
|
||||
"repo": "dotfiles",
|
||||
"rev": "e01f5de3b98f3c85997db571eeaef855bbda80a3",
|
||||
"rev": "5cf0727f8aede69b350e46284cf7147d6185ef56",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -41,11 +41,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1772807318,
|
||||
"narHash": "sha256-Qjw6ILt8cb2HQQpCmWNLMZZ63wEo1KjTQt+1BcQBr7k=",
|
||||
"lastModified": 1778365864,
|
||||
"narHash": "sha256-ImoT/wqmgMImf2dAC+E0MverAdA4QXsedOeES9B7Ezw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "daa2c221320809f5514edde74d0ad0193ad54ed8",
|
||||
"rev": "2f419037039a152448c5f4ae9494154753d1b399",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -72,11 +72,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1772542754,
|
||||
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
||||
"lastModified": 1777954456,
|
||||
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
|
||||
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -88,11 +88,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1775126147,
|
||||
"narHash": "sha256-J0dZU4atgcfo4QvM9D92uQ0Oe1eLTxBVXjJzdEMQpD0=",
|
||||
"lastModified": 1775888245,
|
||||
"narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8d8c1fa5b412c223ffa47410867813290cdedfef",
|
||||
"rev": "13043924aaa7375ce482ebe2494338e058282925",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -116,11 +116,11 @@
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775682595,
|
||||
"narHash": "sha256-0E9PohY/VuESLq0LR4doaH7hTag513sDDW5n5qmHd1Q=",
|
||||
"lastModified": 1777944972,
|
||||
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
|
||||
"owner": "mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "d2e8438d5886e92bc5e7c40c035ab6cae0c41f76",
|
||||
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Home configs per host.
|
||||
@@ -1 +0,0 @@
|
||||
Home config for benq user on homelab.
|
||||
@@ -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,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,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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./network.nix
|
||||
./containers
|
||||
];
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
in
|
||||
{
|
||||
services.podman = {
|
||||
networks.${common.networkName} = {
|
||||
driver = "bridge";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
@@ -1 +0,0 @@
|
||||
System config that is required for any host.
|
||||
@@ -3,7 +3,6 @@
|
||||
imports = [
|
||||
./disko.nix
|
||||
./locale.nix
|
||||
./netbird.nix
|
||||
./nix.nix
|
||||
./packages.nix
|
||||
./sops.nix
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
services.netbird.enable = true;
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
nix.settings.experimental-features = [
|
||||
nix = {
|
||||
gc = {
|
||||
automatic = true;
|
||||
dates = "daily";
|
||||
options = "--delete-older-than 7d";
|
||||
};
|
||||
settings = {
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
dates = "daily";
|
||||
options = "--delete-older-than +5";
|
||||
auto-optimise-store = true;
|
||||
};
|
||||
};
|
||||
nix.settings.auto-optimise-store = true;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
age
|
||||
bat
|
||||
bind
|
||||
btop
|
||||
git
|
||||
jq
|
||||
openssl
|
||||
ripgrep
|
||||
tmux
|
||||
tree
|
||||
vim
|
||||
wget
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,10 +8,5 @@
|
||||
defaultSopsFile = "/etc/sops/secrets.yaml";
|
||||
age.keyFile = "/etc/age/key.txt";
|
||||
validateSopsFiles = false;
|
||||
secrets = {
|
||||
benq-password = {
|
||||
neededForUsers = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Optional config that may or not exist on specific host.
|
||||
@@ -0,0 +1,4 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
fonts.fontDir.enable = true;
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./gitea
|
||||
./homarr
|
||||
./immich
|
||||
./jellyfin
|
||||
./netbird
|
||||
./pocket-id
|
||||
./seafile
|
||||
./traefik
|
||||
./vaultwarden
|
||||
./zapret2
|
||||
./zerobyte
|
||||
];
|
||||
|
||||
options.selfhosted.common = {
|
||||
backend = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
rootTarget = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
network = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
mask = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
gateway = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
user = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
uid = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
group = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
gid = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
selfhosted = {
|
||||
common = {
|
||||
backend = "docker";
|
||||
rootTarget.name = "sh-root";
|
||||
network = {
|
||||
name = "selfhosted";
|
||||
address = "172.30.0.0";
|
||||
mask = "24";
|
||||
gateway = "172.30.0.1";
|
||||
};
|
||||
dataDir = "/mnt/data";
|
||||
user = {
|
||||
name = config.users.users.benq.name;
|
||||
uid = "1000";
|
||||
};
|
||||
group = {
|
||||
name = "users";
|
||||
gid = "1000";
|
||||
};
|
||||
};
|
||||
jellyfin = {
|
||||
name = "jellyfin";
|
||||
subdomain = "media";
|
||||
hostHttpPort = 5040;
|
||||
hostUdpPort = 5041;
|
||||
};
|
||||
gitea = {
|
||||
targetName = "gitea";
|
||||
gitea = {
|
||||
name = "gitea";
|
||||
subdomain = "gitea";
|
||||
hostWebPort = 5060;
|
||||
hostSshPort = 5061;
|
||||
};
|
||||
postgresql = {
|
||||
name = "gitea-db";
|
||||
};
|
||||
};
|
||||
immich = {
|
||||
targetName = "immich";
|
||||
version = "v2";
|
||||
immich = {
|
||||
name = "immich";
|
||||
subdomain = "photos";
|
||||
hostPort = 5090;
|
||||
};
|
||||
ml = {
|
||||
name = "immich-ml";
|
||||
};
|
||||
redis = {
|
||||
name = "immich-cache";
|
||||
};
|
||||
postgresql = {
|
||||
name = "immich-db";
|
||||
db = "immich";
|
||||
user = "immich";
|
||||
};
|
||||
};
|
||||
netbird = {
|
||||
targetName = "netbird";
|
||||
subdomain = "nb";
|
||||
dashboard = {
|
||||
name = "nb-dashboard";
|
||||
};
|
||||
proxy = {
|
||||
name = "nb-proxy";
|
||||
};
|
||||
server = {
|
||||
name = "nb-server";
|
||||
};
|
||||
};
|
||||
homarr = {
|
||||
name = "homarr";
|
||||
subdomain = "dash";
|
||||
hostPort = 5030;
|
||||
};
|
||||
pocket-id = {
|
||||
name = "pocket-id";
|
||||
subdomain = "id";
|
||||
hostPort = 5020;
|
||||
};
|
||||
seafile = {
|
||||
targetName = "seafile";
|
||||
server = {
|
||||
name = "sf-server";
|
||||
subdomain = "cloud";
|
||||
hostHttpPort = 5050;
|
||||
};
|
||||
mariadb = {
|
||||
name = "sf-db";
|
||||
};
|
||||
redis = {
|
||||
name = "sf-cache";
|
||||
};
|
||||
seadoc = {
|
||||
name = "sf-seadoc";
|
||||
hostHttpPort = 5051;
|
||||
};
|
||||
notification = {
|
||||
name = "sf-notification";
|
||||
hostHttpPort = 5052;
|
||||
};
|
||||
};
|
||||
traefik = {
|
||||
name = "traefik";
|
||||
ip = "172.30.0.10";
|
||||
};
|
||||
vaultwarden = {
|
||||
name = "vaultwarden";
|
||||
subdomain = "vault";
|
||||
hostPort = 5010;
|
||||
};
|
||||
zapret2 = {
|
||||
name = "zapret2";
|
||||
socksPort = 6000;
|
||||
ssPort = 6001;
|
||||
};
|
||||
zerobyte = {
|
||||
name = "zerobyte";
|
||||
subdomain = "backup";
|
||||
hostPort = 5070;
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation = lib.mkMerge [
|
||||
{ oci-containers.backend = config.selfhosted.common.backend; }
|
||||
(
|
||||
if config.selfhosted.common.backend == "docker" then
|
||||
{
|
||||
docker = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
else if config.selfhosted.common.backend == "podman" then
|
||||
{
|
||||
podman = {
|
||||
enable = true;
|
||||
dockerCompat = true;
|
||||
};
|
||||
}
|
||||
else
|
||||
abort "virtualisation.backend is not set"
|
||||
)
|
||||
];
|
||||
|
||||
services.netbird.clients = {
|
||||
homelab = {
|
||||
port = 51820;
|
||||
openFirewall = true;
|
||||
openInternalFirewall = true;
|
||||
login = {
|
||||
enable = true;
|
||||
setupKeyFile = config.sops.secrets."netbird/setup_keys/homelab".path;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
"netbird/setup_keys/homelab" = { };
|
||||
"netbird/setup_keys/site" = { };
|
||||
};
|
||||
|
||||
sops.templates."netbird-homelab.env".content = ''
|
||||
NB_MANAGEMENT_URL=https://${cfg.netbird.subdomain}.${config.sops.placeholder."domains/serv"}
|
||||
'';
|
||||
|
||||
# Override to pass env file
|
||||
systemd.services = {
|
||||
"netbird-homelab" = {
|
||||
serviceConfig = {
|
||||
EnvironmentFile = [ config.sops.templates."netbird-homelab.env".path ];
|
||||
};
|
||||
after = [ "sops-nix.service" ];
|
||||
wants = [ "sops-nix.service" ];
|
||||
};
|
||||
"netbird-homelab-login" = {
|
||||
after = [ "sops-nix.service" ];
|
||||
wants = [ "sops-nix.service" ];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.targets."${common.backend}-${common.rootTarget.name}" = {
|
||||
unitConfig = {
|
||||
Description = "Root target for homelab stack";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-network-${common.network.name}" = {
|
||||
path = [ pkgs.${common.backend} ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStop = "${common.backend} network rm -f ${common.network.name}";
|
||||
};
|
||||
script = ''
|
||||
${common.backend} network inspect ${common.network.name} \
|
||||
|| ${common.backend} network create ${common.network.name} --driver=bridge --subnet=${common.network.address}/${common.network.mask} --gateway=${common.network.gateway}
|
||||
'';
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
};
|
||||
|
||||
networking.firewall.interfaces = lib.mkMerge [
|
||||
(lib.mkIf (config.selfhosted.common.backend == "podman") (
|
||||
let
|
||||
matchAll = if !config.networking.nftables.enable then "podman+" else "podman*";
|
||||
in
|
||||
{
|
||||
"${matchAll}" = {
|
||||
allowedUDPPorts = [ 53 ];
|
||||
};
|
||||
}
|
||||
))
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.gitea;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./gitea
|
||||
./postgresql
|
||||
];
|
||||
|
||||
options.selfhosted.gitea = {
|
||||
targetName = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
sops.secrets = {
|
||||
"gitea/db_password" = { };
|
||||
"gitea/client_id" = { };
|
||||
"gitea/client_secret" = { };
|
||||
};
|
||||
|
||||
systemd.targets."${common.backend}-${cfg.targetName}" = {
|
||||
unitConfig = {
|
||||
Description = "Root target for ${cfg.targetName}";
|
||||
};
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.gitea;
|
||||
giteaDomain = "${cfg.gitea.subdomain}.${config.sops.placeholder."domains/serv"}";
|
||||
in
|
||||
{
|
||||
sops.templates."gitea.env".content = ''
|
||||
USER_UID=${common.user.uid}
|
||||
USER_GID=${common.group.gid}
|
||||
|
||||
GITEA__database__DB_TYPE=postgres
|
||||
GITEA__database__HOST=${cfg.postgresql.name}:5432
|
||||
GITEA__database__NAME=gitea
|
||||
GITEA__database__USER=gitea
|
||||
GITEA__database__PASSWD=${config.sops.placeholder."gitea/db_password"}
|
||||
|
||||
TZ=Europe/Moscow
|
||||
GITEA__time__DEFAULT_UI_LOCATION=Europe/Moscow
|
||||
|
||||
GITEA__server__DOMAIN=${giteaDomain}
|
||||
GITEA__server__ROOT_URL=https://${giteaDomain}/
|
||||
GITEA__server__HTTP_PORT=3000
|
||||
GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION=false
|
||||
GITEA__service__DISABLE_REGISTRATION=true
|
||||
GITEA__service__SHOW_REGISTRATION_BUTTON=false
|
||||
GITEA__service__ENABLE_BASIC_AUTHENTICATION=false
|
||||
|
||||
GITEA__service__ENABLE_PASSKEY_AUTHENTICATION=false
|
||||
GITEA__server__START_SSH_SERVER=true
|
||||
GITEA__server__SSH_DOMAIN=git.${config.sops.placeholder."domains/serv"}
|
||||
GITEA__server__SSH_LISTEN_HOST=0.0.0.0
|
||||
GITEA__server__SSH_LISTEN_PORT=2222
|
||||
GITEA__server__SSH_PORT=2222
|
||||
|
||||
GITEA__security__INSTALL_LOCK=true
|
||||
|
||||
GITEA__repository__MAX_CREATION_LIMIT=50
|
||||
|
||||
GITEA__server__ENABLE_PASSWORD_SIGNIN_FORM=false
|
||||
GITEA__server__ENABLE_BASIC_AUTHENTICATION=false
|
||||
GITEA__openid__ENABLE_OPENID_SIGNIN=false
|
||||
GITEA__openid__ENABLE_OPENID_SIGNUP=false
|
||||
|
||||
GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION=true
|
||||
GITEA__oauth2_client__ACCOUNT_LINKING=auto
|
||||
GITEA__oauth2_client__USERNAME=preferred_username
|
||||
|
||||
GITEA__session__COOKIE_SECURE=true
|
||||
GITEA__log__LEVEL=Info
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.gitea;
|
||||
hostDataPath = "${common.dataDir}/gitea";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.gitea.gitea = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostWebPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
hostSshPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.gitea.name}" = {
|
||||
image = "gitea/gitea:latest";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.gitea.hostWebPort}:3000"
|
||||
"0.0.0.0:${toString cfg.gitea.hostSshPort}:2222"
|
||||
];
|
||||
environmentFiles = [ config.sops.templates."gitea.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/data:/data"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.gitea.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-${cfg.postgresql.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-${cfg.postgresql.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/data
|
||||
chown -R ${common.user.uid}:${common.group.gid} ${hostDataPath}/data
|
||||
chmod -R 0775 ${hostDataPath}/data
|
||||
'';
|
||||
postStart =
|
||||
let
|
||||
engine =
|
||||
if common.backend == "podman" then
|
||||
pkgs.podman
|
||||
else if common.backend == "docker" then
|
||||
pkgs.docker
|
||||
else
|
||||
abort "Unknown backend ${common.backend}";
|
||||
in
|
||||
''
|
||||
set -euo pipefail
|
||||
DOMAIN=$(cat ${config.sops.secrets."domains/serv".path})
|
||||
CLIENT_ID=$(cat ${config.sops.secrets."gitea/client_id".path})
|
||||
CLIENT_SECRET=$(cat ${config.sops.secrets."gitea/client_secret".path})
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
if ${pkgs.curl}/bin/curl -sf http://127.0.0.1:${toString cfg.gitea.hostWebPort}/api/v1/version >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
echo "Waiting for Gitea to be ready... ($i/20)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if ${engine}/bin/${common.backend} exec --user ${common.user.uid} ${cfg.gitea.name} \
|
||||
gitea admin auth list 2>/dev/null | grep -q 'PocketID'; then
|
||||
echo "PocketID auth source already registered"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
${engine}/bin/${common.backend} exec --user ${common.user.uid} ${cfg.gitea.name} \
|
||||
gitea admin auth add-oauth \
|
||||
--name "PocketID" \
|
||||
--provider "openidConnect" \
|
||||
--key "$CLIENT_ID" \
|
||||
--secret "$CLIENT_SECRET" \
|
||||
--auto-discover-url "https://${config.selfhosted.pocket-id.subdomain}.$DOMAIN/.well-known/openid-configuration" \
|
||||
--scopes "openid email profile" \
|
||||
--skip-local-2fa
|
||||
|
||||
echo "PocketID auth source registered successfully"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.gitea;
|
||||
in
|
||||
{
|
||||
sops.templates."gitea-db.env".content = ''
|
||||
POSTGRES_DB=gitea
|
||||
POSTGRES_USER=gitea
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder."gitea/db_password"}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.gitea;
|
||||
hostDataPath = "${common.dataDir}/gitea";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.gitea.postgresql = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.postgresql.name}" = {
|
||||
image = "postgres:16";
|
||||
environmentFiles = [ config.sops.templates."gitea-db.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/dbData:/var/lib/postgresql/data"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
"--network-alias=${cfg.postgresql.name}"
|
||||
"--health-cmd=pg_isready -U gitea"
|
||||
"--health-interval=10s"
|
||||
"--health-timeout=5s"
|
||||
"--health-retries=5"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.postgresql.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/dbData
|
||||
chown 70:70 ${hostDataPath}/dbData
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.homarr;
|
||||
poCfg = config.selfhosted.pocket-id;
|
||||
oidpDomain = "https://${poCfg.subdomain}.${config.sops.placeholder."domains/serv"}";
|
||||
in
|
||||
{
|
||||
sops.secrets = {
|
||||
"homarr/enc_key" = { };
|
||||
"homarr/client_id" = { };
|
||||
"homarr/client_secret" = { };
|
||||
"homarr/nextauth_secret" = { };
|
||||
};
|
||||
|
||||
sops.templates."homarr.env" = {
|
||||
content = ''
|
||||
SECRET_ENCRYPTION_KEY=${config.sops.placeholder."homarr/enc_key"}
|
||||
NEXTAUTH_SECRET=${config.sops.placeholder."homarr/nextauth_secret"}
|
||||
AUTH_PROVIDERS=oidc
|
||||
AUTH_OIDC_CLIENT_ID=${config.sops.placeholder."homarr/client_id"}
|
||||
AUTH_OIDC_CLIENT_SECRET=${config.sops.placeholder."homarr/client_secret"}
|
||||
AUTH_OIDC_ISSUER=${oidpDomain}
|
||||
AUTH_OIDC_CLIENT_NAME="Pocket ID"
|
||||
AUTH_OIDC_SCOPE_OVERWRITE=openid email profile groups
|
||||
AUTH_OIDC_GROUPS_ATTRIBUTE=groups
|
||||
AUTH_LOGOUT_REDIRECT_URL=${oidpDomain}
|
||||
AUTH_OIDC_AUTO_LOGIN=true
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.homarr;
|
||||
hostDataPath = "${common.dataDir}/homarr";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.homarr = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.name}" = {
|
||||
image = "ghcr.io/homarr-labs/homarr:latest";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.hostPort}:7575"
|
||||
];
|
||||
environmentFiles = [ config.sops.templates."homarr.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/data:/appdata"
|
||||
"/var/run/docker.sock:/var/run/docker.sock"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/data
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.immich;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./immich
|
||||
./ml
|
||||
./postgresql
|
||||
./redis
|
||||
];
|
||||
|
||||
options.selfhosted.immich = {
|
||||
targetName = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
version = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
sops.secrets = {
|
||||
"immich/db_password" = { };
|
||||
"immich/client_id" = { };
|
||||
"immich/client_secret" = { };
|
||||
};
|
||||
|
||||
systemd.targets."${common.backend}-${cfg.targetName}" = {
|
||||
unitConfig = {
|
||||
Description = "Root target for ${cfg.targetName}";
|
||||
};
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
sops.templates."immich.env".content = ''
|
||||
DB_PASSWORD=${config.sops.placeholder."immich/db_password"}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.immich;
|
||||
cfgPath = "/config/immich.json";
|
||||
hostDataPath = "${common.dataDir}/immich";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.immich.immich = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.immich.name}" = {
|
||||
image = "ghcr.io/immich-app/immich-server:${cfg.version}";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.immich.hostPort}:2283"
|
||||
];
|
||||
environmentFiles = [ config.sops.templates."immich.env".path ];
|
||||
environment = {
|
||||
IMMICH_VERSION = cfg.version;
|
||||
DB_HOSTNAME = cfg.postgresql.name;
|
||||
DB_DATABASE_NAME = cfg.postgresql.db;
|
||||
DB_USERNAME = cfg.postgresql.user;
|
||||
REDIS_HOSTNAME = cfg.redis.name;
|
||||
};
|
||||
volumes = [
|
||||
"${hostDataPath}/data:/data"
|
||||
"/etc/localtime:/etc/localtime:ro"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.immich.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${cfg.postgresql.name}.service"
|
||||
"${common.backend}-${cfg.redis.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${cfg.postgresql.name}.service"
|
||||
"${common.backend}-${cfg.redis.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/data
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
sops.templates."immich-db.env".content = ''
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder."immich/db_password"}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.immich;
|
||||
hostDataPath = "${common.dataDir}/immich";
|
||||
in
|
||||
{
|
||||
options.selfhosted.immich.ml = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.ml.name}" = {
|
||||
image = "ghcr.io/immich-app/immich-machine-learning:${cfg.version}";
|
||||
environment = {
|
||||
IMMICH_VERSION = cfg.version;
|
||||
};
|
||||
volumes = [
|
||||
"${hostDataPath}/cache:/cache"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.ml.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/cache
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
sops.templates."immich-db.env".content = ''
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder."immich/db_password"}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.immich;
|
||||
hostDataPath = "${common.dataDir}/immich";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.immich.postgresql = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
db = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.postgresql.name}" = {
|
||||
image = "ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23";
|
||||
environmentFiles = [ config.sops.templates."immich-db.env".path ];
|
||||
environment = {
|
||||
POSTGRES_DB = cfg.postgresql.db;
|
||||
POSTGRES_USER = cfg.postgresql.user;
|
||||
POSTGRES_INITDB_ARGS = "--data-checksums";
|
||||
};
|
||||
volumes = [
|
||||
"${hostDataPath}/db:/var/lib/postgresql/data"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
"--shm-size=128m"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.postgresql.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/db
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.immich;
|
||||
in
|
||||
{
|
||||
options.selfhosted.immich.redis = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.redis.name}" = {
|
||||
image = "docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9";
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
"--health-cmd=redis-cli ping || exit 1"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.redis.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.jellyfin;
|
||||
in
|
||||
{
|
||||
sops.secrets = {
|
||||
};
|
||||
|
||||
sops.templates."jellyfin.env".content = ''
|
||||
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.jellyfin;
|
||||
hostDataPath = "${common.dataDir}/jellyfin";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.jellyfin = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostHttpPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
hostUdpPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.name} = {
|
||||
image = "docker.io/jellyfin/jellyfin:10.11.11";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.hostHttpPort}:8096"
|
||||
"0.0.0.0:${toString cfg.hostUdpPort}:7359"
|
||||
];
|
||||
user = "${common.user.uid}:${common.group.gid}";
|
||||
environmentFiles = [ config.sops.templates."jellyfin.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/config:/config"
|
||||
"${hostDataPath}/cache:/cache"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/{config,cache}
|
||||
chown -R ${common.user.uid}:${common.group.gid} ${hostDataPath}
|
||||
chmod -R 0775 ${hostDataPath}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
fullDomain = "${config.selfhosted.netbird.subdomain}.${config.sops.placeholder."domains/serv"}";
|
||||
in
|
||||
{
|
||||
sops.templates."nb-dashboard.env".content = ''
|
||||
NETBIRD_MGMT_API_ENDPOINT=https://${fullDomain}
|
||||
NETBIRD_MGMT_GRPC_API_ENDPOINT=https://${fullDomain}
|
||||
|
||||
# Initial NB setup: custom OIDCP is added manually in panel later
|
||||
AUTH_AUTHORITY=https://${fullDomain}/oauth2
|
||||
AUTH_AUDIENCE=netbird-dashboard
|
||||
AUTH_CLIENT_ID=netbird-dashboard
|
||||
AUTH_CLIENT_SECRET=
|
||||
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."letsEncrypt/email"}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.netbird;
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.netbird.dashboard = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.dashboard.name} = {
|
||||
image = "netbirdio/dashboard:latest";
|
||||
environmentFiles = [ config.sops.templates."nb-dashboard.env".path ];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network-alias=${cfg.dashboard.name}"
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.dashboard.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-${cfg.server.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-${cfg.server.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.netbird;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./dashboard
|
||||
./proxy
|
||||
./server
|
||||
];
|
||||
|
||||
options.selfhosted.netbird = {
|
||||
targetName = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
sops.secrets = {
|
||||
"netbird/proxy/token" = { };
|
||||
};
|
||||
|
||||
systemd.targets."${common.backend}-${cfg.targetName}" = {
|
||||
unitConfig = {
|
||||
Description = "Root target for ${cfg.targetName}";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.netbird;
|
||||
in
|
||||
{
|
||||
sops.templates."nb-proxy.env".content = ''
|
||||
NB_PROXY_DEBUG_LOGS=false
|
||||
NB_PROXY_PROXY_PROTOCOL=true
|
||||
NB_PROXY_MANAGEMENT_ADDRESS=http://${cfg.server.name}:80
|
||||
NB_PROXY_ALLOW_INSECURE=true
|
||||
NB_PROXY_DOMAIN=${cfg.subdomain}.${config.sops.placeholder."domains/serv"}
|
||||
NB_PROXY_ADDRESS=:8443
|
||||
NB_PROXY_TOKEN=${config.sops.placeholder."netbird/proxy/token"}
|
||||
NB_PROXY_FORWARDED_PROTO=https
|
||||
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
|
||||
NB_PROXY_ACME_CERTIFICATES=true
|
||||
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
|
||||
NB_PROXY_TRUSTED_PROXIES=${config.selfhosted.traefik.ip}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.netbird;
|
||||
hostDataPath = "${common.dataDir}/netbird/proxy";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.netbird.proxy = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.proxy.name} = {
|
||||
image = "netbirdio/reverse-proxy:latest";
|
||||
environmentFiles = [ config.sops.templates."nb-proxy.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/certs:/certs:rw"
|
||||
];
|
||||
ports = [
|
||||
"0.0.0.0:2222:2222/tcp" # gitea
|
||||
"0.0.0.0:7359:7359/udp" # jelly
|
||||
"0.0.0.0:51820:51820/udp"
|
||||
];
|
||||
dependsOn = [ cfg.server.name ];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network-alias=${cfg.proxy.name}"
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.proxy.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${cfg.server.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${cfg.server.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/certs
|
||||
chown -R ${common.user.uid}:${common.group.gid} ${hostDataPath}
|
||||
chmod -R 0755 ${hostDataPath}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.netbird;
|
||||
fullDomain = "${cfg.subdomain}.${config.sops.placeholder."domains/serv"}";
|
||||
in
|
||||
{
|
||||
sops.secrets = {
|
||||
"netbird/server/auth_secret" = { };
|
||||
"netbird/server/enc_key" = { };
|
||||
};
|
||||
|
||||
sops.templates."nb-server.yaml".content = ''
|
||||
server:
|
||||
listenAddress: ":80"
|
||||
exposedAddress: "https://${fullDomain}:443"
|
||||
stunPorts:
|
||||
- 3478
|
||||
metricsPort: 9090
|
||||
healthcheckAddress: ":9000"
|
||||
logLevel: "info"
|
||||
logFile: "console"
|
||||
|
||||
authSecret: "${config.sops.placeholder."netbird/server/auth_secret"}"
|
||||
|
||||
auth:
|
||||
issuer: "https://${fullDomain}/oauth2"
|
||||
dashboardRedirectURIs:
|
||||
- "https://${fullDomain}/nb-auth"
|
||||
- "https://${fullDomain}/nb-silent-auth"
|
||||
cliRedirectURIs:
|
||||
- "http://localhost:53000/"
|
||||
localAuthDisabled: false
|
||||
|
||||
store:
|
||||
engine: "sqlite"
|
||||
encryptionKey: "${config.sops.placeholder."netbird/server/enc_key"}"
|
||||
|
||||
reverseProxy:
|
||||
trustedHTTPProxies:
|
||||
- "${config.selfhosted.traefik.ip}/32"
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.netbird;
|
||||
hostDataPath = "${common.dataDir}/netbird/server";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.netbird.server = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.server.name} = {
|
||||
image = "netbirdio/netbird-server:latest";
|
||||
volumes = [
|
||||
"${config.sops.templates."nb-server.yaml".path}:/etc/netbird/config.yaml:rw"
|
||||
"${hostDataPath}/data:/var/lib/netbird:rw"
|
||||
];
|
||||
ports = [
|
||||
"0.0.0.0:3478:3478/udp"
|
||||
];
|
||||
cmd = [
|
||||
"--config"
|
||||
"/etc/netbird/config.yaml"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network-alias=${cfg.server.name}"
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.server.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/data
|
||||
chown -R ${common.user.uid}:${common.group.gid} ${hostDataPath}
|
||||
chmod -R 0755 ${hostDataPath}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.pocket-id;
|
||||
hostDataPath = "${common.dataDir}/pocket-id";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.pocket-id = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.name} = {
|
||||
image = "ghcr.io/pocket-id/pocket-id:latest";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.hostPort}:1411"
|
||||
];
|
||||
user = "${common.user.uid}:${common.group.gid}";
|
||||
environmentFiles = [ config.sops.templates."pocket-id.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/data:/app/data"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/letsencrypt
|
||||
chown -R ${common.user.uid}:${common.group.gid} ${hostDataPath}
|
||||
chmod -R 0775 ${hostDataPath}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.seafile;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./mariadb
|
||||
./notification
|
||||
./redis
|
||||
./seadoc
|
||||
./server
|
||||
];
|
||||
|
||||
options.selfhosted.seafile = {
|
||||
targetName = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
systemd.targets."${common.backend}-${cfg.targetName}" = {
|
||||
unitConfig = {
|
||||
Description = "Root target for ${cfg.targetName}";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
sops.secrets = {
|
||||
"seafile/mysql_password" = { };
|
||||
};
|
||||
|
||||
sops.templates."seafile-db.env".content = ''
|
||||
MYSQL_ROOT_PASSWORD=${config.sops.placeholder."seafile/mysql_password"}
|
||||
MYSQL_LOG_CONSOLE=true
|
||||
MARIADB_AUTO_UPGRADE=1
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.seafile;
|
||||
hostDataPath = "${common.dataDir}/seafile";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.seafile.mariadb = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.mariadb.name} = {
|
||||
image = "docker.io/mariadb:10.11";
|
||||
environmentFiles = [ config.sops.templates."seafile-db.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/mysql:/var/lib/mysql"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
"--health-cmd=/usr/local/bin/healthcheck.sh --connect --mariadbupgrade --innodb_initialized"
|
||||
"--health-interval=20s"
|
||||
"--health-start-period=30s"
|
||||
"--health-timeout=5s"
|
||||
"--health-retries=10"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.mariadb.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/mysql
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.seafile;
|
||||
in
|
||||
{
|
||||
sops.templates."seafile-notification.env".content = ''
|
||||
SEAFILE_MYSQL_DB_HOST=${cfg.mariadb.name}
|
||||
SEAFILE_MYSQL_DB_PORT=3306
|
||||
SEAFILE_MYSQL_DB_USER=seafile
|
||||
SEAFILE_MYSQL_DB_PASSWORD=${config.sops.placeholder."seafile/mysql_password"}
|
||||
SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
|
||||
SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
|
||||
JWT_PRIVATE_KEY=${config.sops.placeholder."seafile/jwt_private_key"}
|
||||
SEAFILE_LOG_TO_STDOUT=false
|
||||
NOTIFICATION_SERVER_LOG_LEVEL=info
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.seafile;
|
||||
hostDataPath = "${common.dataDir}/seafile";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.seafile.notification = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostHttpPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.notification.name} = {
|
||||
image = "docker.io/seafileltd/notification-server:13.0-latest";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.notification.hostHttpPort}:8083"
|
||||
];
|
||||
environmentFiles = [ config.sops.templates."seafile-notification.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/notification/logs:/shared/seafile/logs"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.notification.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
"${common.backend}-${cfg.mariadb.name}.service"
|
||||
"${common.backend}-${cfg.server.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
"${common.backend}-${cfg.mariadb.name}.service"
|
||||
"${common.backend}-${cfg.server.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
path = with pkgs; [ curl gawk ];
|
||||
preStart = ''
|
||||
echo "Waiting for Seafile server to be ready..."
|
||||
for i in {1..60}; do
|
||||
status=$(curl -sI --max-time 5 "http://localhost:${toString cfg.server.hostHttpPort}" | head -1 | awk '{print $2}')
|
||||
|
||||
if [[ "$status" =~ ^(200|301|302)$ ]]; then
|
||||
echo "Seafile server is up (HTTP $status, attempt $i)"
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Seafile not ready yet (status: $status) ... ($i/60)"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
mkdir -p ${hostDataPath}/notification
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
sops.secrets = {
|
||||
"seafile/redis_password" = { };
|
||||
};
|
||||
|
||||
sops.templates."seafile-redis.env".content = ''
|
||||
REDIS_PASSWORD=${config.sops.placeholder."seafile/redis_password"}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.seafile;
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.seafile.redis = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.redis.name} = {
|
||||
image = "docker.io/redis:7";
|
||||
environmentFiles = [ config.sops.templates."seafile-redis.env".path ];
|
||||
cmd = [ "/bin/sh" "-c" "exec redis-server --requirepass \"$REDIS_PASSWORD\"" ];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.redis.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.seafile;
|
||||
in
|
||||
{
|
||||
sops.templates."seadoc.env".content = ''
|
||||
DB_HOST=${cfg.mariadb.name}
|
||||
DB_PORT=3306
|
||||
DB_USER=seafile
|
||||
DB_PASSWORD=${config.sops.placeholder."seafile/mysql_password"}
|
||||
DB_NAME=seahub_db
|
||||
TIME_ZONE=${config.time.timeZone}
|
||||
JWT_PRIVATE_KEY=${config.sops.placeholder."seafile/jwt_private_key"}
|
||||
NON_ROOT=false
|
||||
SEAHUB_SERVICE_URL=http://${cfg.server.name}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.seafile;
|
||||
hostDataPath = "${common.dataDir}/seafile";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.seafile.seadoc = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostHttpPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.seadoc.name} = {
|
||||
image = "docker.io/seafileltd/sdoc-server:2.0-latest";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.seadoc.hostHttpPort}:80"
|
||||
];
|
||||
volumes = [
|
||||
"${hostDataPath}/seadoc/shared:/shared"
|
||||
];
|
||||
environmentFiles = [ config.sops.templates."seadoc.env".path ];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.seadoc.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
"${common.backend}-${cfg.mariadb.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
"${common.backend}-${cfg.mariadb.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/seadoc/shared
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.seafile;
|
||||
poCfg = config.selfhosted.pocket-id;
|
||||
domain = config.sops.placeholder."domains/serv";
|
||||
in
|
||||
{
|
||||
sops.secrets = {
|
||||
"seafile/admin_email" = { };
|
||||
"seafile/admin_password" = { };
|
||||
"seafile/client_id" = { };
|
||||
"seafile/client_secret" = { };
|
||||
"seafile/jwt_private_key" = { };
|
||||
};
|
||||
|
||||
sops.templates."seafile.env".content = ''
|
||||
SEAFILE_MYSQL_DB_HOST=${cfg.mariadb.name}
|
||||
SEAFILE_MYSQL_DB_PORT=3306
|
||||
SEAFILE_MYSQL_DB_USER=seafile
|
||||
SEAFILE_MYSQL_DB_PASSWORD=${config.sops.placeholder."seafile/mysql_password"}
|
||||
SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
|
||||
SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
|
||||
SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db
|
||||
TIME_ZONE=${config.time.timeZone}
|
||||
INIT_SEAFILE_MYSQL_ROOT_PASSWORD=${config.sops.placeholder."seafile/mysql_password"}
|
||||
INIT_SEAFILE_ADMIN_EMAIL=${config.sops.placeholder."seafile/admin_email"}
|
||||
INIT_SEAFILE_ADMIN_PASSWORD=${config.sops.placeholder."seafile/admin_password"}
|
||||
SEAFILE_SERVER_HOSTNAME=${cfg.server.subdomain}.${domain}
|
||||
SEAFILE_SERVER_PROTOCOL=https
|
||||
SEAFILE_SERVER_LETSENCRYPT=false
|
||||
SITE_ROOT=/
|
||||
NON_ROOT=false
|
||||
JWT_PRIVATE_KEY=${config.sops.placeholder."seafile/jwt_private_key"}
|
||||
SEAFILE_LOG_TO_STDOUT=false
|
||||
ENABLE_GO_FILESERVER=true
|
||||
ENABLE_SEADOC=true
|
||||
SEADOC_SERVER_URL=https://${cfg.server.subdomain}.${domain}/sdoc-server
|
||||
CACHE_PROVIDER=redis
|
||||
REDIS_HOST=${cfg.redis.name}
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=${config.sops.placeholder."seafile/redis_password"}
|
||||
ENABLE_NOTIFICATION_SERVER=true
|
||||
INNER_NOTIFICATION_SERVER_URL=http://${cfg.notification.name}:8083
|
||||
NOTIFICATION_SERVER_URL=https://${cfg.server.subdomain}.${domain}/notification
|
||||
ENABLE_SEAFILE_AI=false
|
||||
MD_FILE_COUNT_LIMIT=100000
|
||||
'';
|
||||
|
||||
sops.templates."seahub_settings.py".content = ''
|
||||
ENABLE_VIDEO_THUMBNAIL = True
|
||||
|
||||
ENABLE_OAUTH = True
|
||||
OAUTH_CREATE_UNKNOWN_USER = True
|
||||
OAUTH_ACTIVATE_USER_AFTER_CREATION = True
|
||||
OAUTH_ENABLE_INSECURE_TRANSPORT = False
|
||||
OAUTH_CLIENT_ID = "${config.sops.placeholder."seafile/client_id"}"
|
||||
OAUTH_CLIENT_SECRET = "${config.sops.placeholder."seafile/client_secret"}"
|
||||
OAUTH_REDIRECT_URL = "https://${cfg.server.subdomain}.${domain}/oauth/callback"
|
||||
OAUTH_PROVIDER = "pocket-id"
|
||||
OAUTH_PROVIDER_DOMAIN = "pocket-id"
|
||||
OAUTH_AUTHORIZATION_URL = "https://${poCfg.subdomain}.${domain}/authorize"
|
||||
OAUTH_TOKEN_URL = "https://${poCfg.subdomain}.${domain}/api/oidc/token"
|
||||
OAUTH_USER_INFO_URL = "https://${poCfg.subdomain}.${domain}/api/oidc/userinfo"
|
||||
OAUTH_SCOPE = [
|
||||
"openid",
|
||||
"profile",
|
||||
"email"
|
||||
]
|
||||
OAUTH_ATTRIBUTE_MAP = {
|
||||
"sub": (True, "uid"),
|
||||
"name": (False, "name"),
|
||||
"email": (False, "contact_email"),
|
||||
}
|
||||
CLIENT_SSO_VIA_LOCAL_BROWSER = True
|
||||
|
||||
ENABLE_WEBDAV_SECRET = True
|
||||
WEBDAV_SECRET_MIN_LENGTH = 8
|
||||
SERVICE_URL = "https://${cfg.server.subdomain}.${domain}"
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
ALLOWED_HOSTS = [".${domain}"]
|
||||
CSRF_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SAMESITE = "Lax"
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"https://${cfg.server.subdomain}.${domain}"
|
||||
]
|
||||
'';
|
||||
|
||||
sops.templates."seafdav.conf".content = ''
|
||||
[WEBDAV]
|
||||
enabled = true
|
||||
port = 8080
|
||||
debug = true
|
||||
share_name = /seafdav
|
||||
workers = 5
|
||||
timeout = 1200
|
||||
'';
|
||||
|
||||
sops.templates."seafile.conf".content = ''
|
||||
[quota]
|
||||
default = 100
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.seafile;
|
||||
hostDataPath = "${common.dataDir}/seafile";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.seafile.server = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostHttpPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.server.name} = {
|
||||
image = "docker.io/seafileltd/seafile-mc:13.0-latest";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.server.hostHttpPort}:80"
|
||||
];
|
||||
environmentFiles = [ config.sops.templates."seafile.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/server/shared:/shared"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.server.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
"${common.backend}-${config.selfhosted.seafile.mariadb.name}.service"
|
||||
"${common.backend}-${config.selfhosted.seafile.redis.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
"${common.backend}-${cfg.mariadb.name}.service"
|
||||
"${common.backend}-${cfg.redis.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
wantedBy = [ "${common.backend}-${cfg.targetName}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/server/shared/seafile/conf
|
||||
|
||||
cp -f ${config.sops.templates."seahub_settings.py".path} \
|
||||
${hostDataPath}/server/shared/seafile/conf/seahub_settings.py
|
||||
cp -f ${config.sops.templates."seafdav.conf".path} \
|
||||
${hostDataPath}/server/shared/seafile/conf/seafdav.conf
|
||||
cp -f ${config.sops.templates."seafile.conf".path} \
|
||||
${hostDataPath}/server/shared/seafile/conf/seafile.conf
|
||||
chmod 644 ${hostDataPath}/server/shared/seafile/conf/{seafile.conf,seafdav.conf,seahub_settings.py}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.netbird;
|
||||
in
|
||||
{
|
||||
sops.templates."traefik.env".content = ''
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=${config.sops.placeholder."letsEncrypt/email"}
|
||||
'';
|
||||
|
||||
sops.templates."traefik-static-config.yaml".content = ''
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entrypoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: ":443"
|
||||
allowACMEByPass: true
|
||||
transport:
|
||||
respondingTimeouts:
|
||||
readTimeout: 0
|
||||
writeTimeout: 0
|
||||
idleTimeout: 0
|
||||
proxyProtocol:
|
||||
trustedIPs:
|
||||
- "${common.network.address}/${common.network.mask}"
|
||||
|
||||
serversTransport:
|
||||
forwardingTimeouts:
|
||||
responseHeaderTimeout: 0
|
||||
idleConnTimeout: 0
|
||||
|
||||
providers:
|
||||
docker:
|
||||
exposedByDefault: false
|
||||
network: "${common.network.name}"
|
||||
file:
|
||||
filename: /etc/traefik/dynamic.yaml
|
||||
watch: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
storage: /letsencrypt/acme.json
|
||||
tlsChallenge: true
|
||||
'';
|
||||
|
||||
sops.templates."traefik-dynamic-config.yaml".content = ''
|
||||
http:
|
||||
routers:
|
||||
netbird-dashboard:
|
||||
entryPoints:
|
||||
- websecure
|
||||
rule: "Host(`${cfg.subdomain}.${config.sops.placeholder."domains/serv"}`)"
|
||||
service: dashboard
|
||||
priority: 1
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
netbird-backend:
|
||||
entryPoints:
|
||||
- websecure
|
||||
rule: "Host(`${cfg.subdomain}.${config.sops.placeholder."domains/serv"}`) && (PathPrefix(`/relay`) || PathPrefix(`/ws-proxy/`) || PathPrefix(`/api`) || PathPrefix(`/oauth2`))"
|
||||
service: netbird-server
|
||||
priority: 100
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
netbird-grpc:
|
||||
entryPoints:
|
||||
- websecure
|
||||
rule: "Host(`${cfg.subdomain}.${config.sops.placeholder."domains/serv"}`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`))"
|
||||
service: netbird-server-h2c
|
||||
priority: 100
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
services:
|
||||
dashboard:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://${cfg.dashboard.name}:80"
|
||||
netbird-server:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://${cfg.server.name}:80"
|
||||
netbird-server-h2c:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "h2c://${cfg.server.name}:80"
|
||||
tcp:
|
||||
serversTransports:
|
||||
pp-v2:
|
||||
proxyProtocol:
|
||||
version: 2
|
||||
|
||||
routers:
|
||||
proxy-passthrough:
|
||||
entrypoints:
|
||||
- websecure
|
||||
rule: HostSNI(`*`)
|
||||
service: proxy-tls
|
||||
priority: 1
|
||||
tls:
|
||||
passthrough: true
|
||||
|
||||
services:
|
||||
proxy-tls:
|
||||
loadBalancer:
|
||||
serversTransport: pp-v2@file
|
||||
servers:
|
||||
- address: "${cfg.proxy.name}:8443"
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.traefik;
|
||||
hostDataPath = "${common.dataDir}/traefik";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.traefik = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
sops.secrets = {
|
||||
"domains/serv" = { };
|
||||
"letsEncrypt/email" = { };
|
||||
};
|
||||
|
||||
virtualisation.oci-containers.containers.${cfg.name} = {
|
||||
image = "traefik:latest";
|
||||
environmentFiles = [ config.sops.templates."traefik.env".path ];
|
||||
volumes = [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
"${config.sops.templates."traefik-static-config.yaml".path}:/etc/traefik/traefik.yaml:ro"
|
||||
"${config.sops.templates."traefik-dynamic-config.yaml".path}:/etc/traefik/dynamic.yaml:ro"
|
||||
"${hostDataPath}/letsencrypt:/letsencrypt:rw"
|
||||
];
|
||||
ports = [
|
||||
"0.0.0.0:80:80"
|
||||
"0.0.0.0:443:443"
|
||||
];
|
||||
cmd = [
|
||||
"--log.level=INFO"
|
||||
"--accesslog=true"
|
||||
"--configfile=/etc/traefik/traefik.yaml"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--ip=172.30.0.10"
|
||||
"--network-alias=${cfg.name}"
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/letsencrypt
|
||||
chown -R ${common.user.uid}:${common.group.gid} ${hostDataPath}
|
||||
chmod -R 0600 ${hostDataPath}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
+10
-9
@@ -1,26 +1,27 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted.vaultwarden;
|
||||
poCfg = config.selfhosted.pocket-id;
|
||||
domain = config.sops.placeholder."domains/serv";
|
||||
cfg = config.selfhosted;
|
||||
domain = "${config.sops.placeholder."domains/serv"}";
|
||||
in
|
||||
{
|
||||
sops.secrets = {
|
||||
"vaultwarden/admin_token" = { };
|
||||
"vaultwarden/client_id" = { };
|
||||
"vaultwarden/client_secret" = { };
|
||||
};
|
||||
|
||||
sops.templates."vaultwarden.env".content = ''
|
||||
DOMAIN=https://${cfg.subdomain}.${domain}
|
||||
LOG_LEVEL=info,vaultwarden::sso=debug
|
||||
DOMAIN=https://${cfg.vaultwarden.subdomain}.${domain}
|
||||
SIGNUPS_ALLOWED=false
|
||||
SSO_ENABLED=false
|
||||
SSO_DEBUG_TOKENS=true
|
||||
ADMIN_TOKEN=${config.sops.placeholder."vaultwarden/admin_token"}
|
||||
|
||||
# SSO
|
||||
SSO_ENABLED=true
|
||||
SSO_ONLY=false
|
||||
SSO_AUTHORITY=https://${poCfg.subdomain}.${domain}
|
||||
SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION=true
|
||||
SSO_AUTHORITY=https://${cfg.pocket-id.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
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.vaultwarden;
|
||||
hostDataPath = "${common.dataDir}/vaultwarden";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.vaultwarden = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers."${cfg.name}" = {
|
||||
image = "vaultwarden/server:latest";
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.hostPort}:80"
|
||||
];
|
||||
user = "${common.user.uid}:${common.group.gid}";
|
||||
environmentFiles = [ config.sops.templates."vaultwarden.env".path ];
|
||||
volumes = [
|
||||
"${hostDataPath}/data:/data"
|
||||
];
|
||||
log-driver = "journald";
|
||||
extraOptions = [
|
||||
"--network=${common.network.name}"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/data
|
||||
chown -R ${common.user.uid}:${common.group.gid} ${hostDataPath}
|
||||
chmod -R 0775 ${hostDataPath}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{ pkgs }:
|
||||
let
|
||||
configFiles = {
|
||||
config = ''
|
||||
FWTYPE=iptables
|
||||
|
||||
SET_MAXELEM=522288
|
||||
|
||||
IPSET_OPT="hashsize 262144 maxelem $SET_MAXELEM"
|
||||
|
||||
IP2NET_OPT4="--prefix-length=22-30 --v4-threshold=3/4"
|
||||
IP2NET_OPT6="--prefix-length=56-64 --v6-threshold=5"
|
||||
|
||||
AUTOHOSTLIST_INCOMING_MAXSEQ=4096
|
||||
AUTOHOSTLIST_RETRANS_MAXSEQ=32768
|
||||
AUTOHOSTLIST_RETRANS_RESET=1
|
||||
AUTOHOSTLIST_RETRANS_THRESHOLD=3
|
||||
AUTOHOSTLIST_FAIL_THRESHOLD=3
|
||||
AUTOHOSTLIST_FAIL_TIME=60
|
||||
AUTOHOSTLIST_UDP_IN=1
|
||||
AUTOHOSTLIST_UDP_OUT=4
|
||||
AUTOHOSTLIST_DEBUGLOG=0
|
||||
|
||||
MDIG_THREADS=30
|
||||
|
||||
GZIP_LISTS=1
|
||||
|
||||
DESYNC_MARK=0x40000000
|
||||
DESYNC_MARK_POSTNAT=0x20000000
|
||||
|
||||
NFQWS2_ENABLE=1
|
||||
NFQWS2_PORTS_TCP=80,443
|
||||
NFQWS2_PORTS_UDP=443
|
||||
NFQWS2_TCP_PKT_OUT=20
|
||||
NFQWS2_TCP_PKT_IN=10
|
||||
NFQWS2_UDP_PKT_OUT=5
|
||||
NFQWS2_UDP_PKT_IN=3
|
||||
NFQWS2_OPT="
|
||||
--filter-tcp=80 --filter-l7=http --payload http_req --lua-desync=tcpseg:pos=0,midsld:ip_id=rnd:repeats=1 --new
|
||||
--filter-tcp=443 --filter-l7=tls --payload=tls_client_hello --hostlist-domains=x.com --lua-desync=hostfakesplit:nofake1:midhost=midsld:ip_ttl=6:repeats=1 --new
|
||||
--filter-tcp=443 --filter-l7=tls --payload=tls_client_hello --lua-desync=tcpseg:pos=0,midsld:ip_id=rnd:repeats=1 --new
|
||||
--filter-udp=443 --filter-l7=quic --payload=quic_initial --lua-desync=tcpseg:pos=0,midsld:ip_id=rnd:repeats=1
|
||||
"
|
||||
|
||||
MODE_FILTER=none
|
||||
|
||||
FLOWOFFLOAD=donttouch
|
||||
|
||||
INIT_APPLY_FW=1
|
||||
|
||||
DISABLE_IPV6=1
|
||||
|
||||
FILTER_TTL_EXPIRED_ICMP=1
|
||||
'';
|
||||
};
|
||||
|
||||
in
|
||||
pkgs.lib.mapAttrs (name: content: pkgs.writeText name content) configFiles
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.zapret2;
|
||||
in
|
||||
{
|
||||
options.selfhosted.zapret2 = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
socksPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
ssPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
socksPort = "${toString cfg.socksPort}";
|
||||
ssPort = "${toString cfg.ssPort}";
|
||||
configFiles = import ./config.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
virtualisation.oci-containers.containers.${cfg.name} = {
|
||||
image = "vernette/ss-zapret2:latest";
|
||||
volumes = [
|
||||
"${configFiles.config}:/opt/zapret2/config"
|
||||
];
|
||||
environment = {
|
||||
SOCKS_PORT = socksPort;
|
||||
SS_PORT = ssPort;
|
||||
SS_PASSWORD = "test";
|
||||
SS_ENCRYPT_METHOD = "chacha20-ietf-poly1305";
|
||||
SS_TIMEOUT = "300";
|
||||
};
|
||||
ports = [
|
||||
"127.0.0.1:${socksPort}:${socksPort}"
|
||||
"127.0.0.1:${ssPort}:${ssPort}"
|
||||
];
|
||||
extraOptions = [
|
||||
"--cap-add=NET_ADMIN"
|
||||
"--network=host"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
postStart = ''
|
||||
mv /opt/zapret2/lua.dist /opt/zapret2/lua || true
|
||||
'';
|
||||
after = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
requires = [ "${common.backend}-network-${common.network.name}.service" ];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.selfhosted;
|
||||
domain = "${config.sops.placeholder."domains/serv"}";
|
||||
zerobyteDomain = "${cfg.zerobyte.subdomain}.${domain}";
|
||||
trustedOrigins = [
|
||||
"https://${zerobyteDomain}"
|
||||
"https://${cfg.pocket-id.subdomain}.${domain}"
|
||||
];
|
||||
in
|
||||
{
|
||||
sops.secrets = {
|
||||
"zerobyte/app_secret" = { };
|
||||
};
|
||||
|
||||
sops.templates."zerobyte.env".content = ''
|
||||
TZ=${config.time.timeZone}
|
||||
BASE_URL=https://${zerobyteDomain}
|
||||
APP_SECRET=${config.sops.placeholder."zerobyte/app_secret"}
|
||||
TRUST_PROXY=true
|
||||
TRUSTED_ORIGINS=${(concatStringsSep "," trustedOrigins)}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
common = config.selfhosted.common;
|
||||
cfg = config.selfhosted.zerobyte;
|
||||
hostDataPath = "${common.dataDir}/zerobyte";
|
||||
in
|
||||
{
|
||||
imports = [ ./config.nix ];
|
||||
|
||||
options.selfhosted.zerobyte = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
subdomain = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
hostPort = mkOption {
|
||||
type = types.port;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
virtualisation.oci-containers.containers.${cfg.name} = {
|
||||
image = "ghcr.io/nicotsx/zerobyte:latest";
|
||||
environmentFiles = [ config.sops.templates."zerobyte.env".path ];
|
||||
ports = [
|
||||
"0.0.0.0:${toString cfg.hostPort}:4096"
|
||||
];
|
||||
volumes = [
|
||||
"/etc/localtime:/etc/localtime:ro"
|
||||
"${hostDataPath}/data:/var/lib/zerobyte"
|
||||
"${hostDataPath}/backups:/mydata"
|
||||
];
|
||||
};
|
||||
|
||||
systemd.services."${common.backend}-${cfg.name}" = {
|
||||
serviceConfig = {
|
||||
Restart = lib.mkOverride 90 "always";
|
||||
};
|
||||
after = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
requires = [
|
||||
"${common.backend}-network-${common.network.name}.service"
|
||||
"${common.backend}-${config.selfhosted.traefik.name}.service"
|
||||
];
|
||||
partOf = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
wantedBy = [ "${common.backend}-${common.rootTarget.name}.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${hostDataPath}/{data,backups}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,12 @@
|
||||
}:
|
||||
with lib;
|
||||
{
|
||||
sops.secrets = {
|
||||
benq-password = {
|
||||
neededForUsers = true;
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
mutableUsers = false;
|
||||
users.benq = {
|
||||
@@ -17,8 +23,18 @@ with lib;
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeIuJzR68xA4ugJjtWbwvaWEU852Hg9FAAhXNw8ou43 benq"
|
||||
];
|
||||
linger = true;
|
||||
subUidRanges = [ { startUid = 100000; count = 65536; } ];
|
||||
subGidRanges = [ { startGid = 100000; count = 65536; } ];
|
||||
subUidRanges = [
|
||||
{
|
||||
startUid = 100000;
|
||||
count = 65536;
|
||||
}
|
||||
];
|
||||
subGidRanges = [
|
||||
{
|
||||
startGid = 100000;
|
||||
count = 65536;
|
||||
}
|
||||
];
|
||||
extraGroups =
|
||||
let
|
||||
ifTheyExist = groups: filter (group: hasAttr group config.users.groups) groups;
|
||||
@@ -26,6 +42,7 @@ with lib;
|
||||
flatten [
|
||||
"wheel"
|
||||
(ifTheyExist [
|
||||
"docker"
|
||||
"git"
|
||||
"networkmanager"
|
||||
"video"
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
"bbr.nix"
|
||||
"endlessh.nix"
|
||||
"fail2ban.nix"
|
||||
"fonts.nix"
|
||||
"openssh.nix"
|
||||
"selfhosted"
|
||||
])
|
||||
))
|
||||
];
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
networking = {
|
||||
hostName = "homelab";
|
||||
firewall = {
|
||||
allowedTCPPorts = [ 22 80 443 6969 ];
|
||||
allowedUDPPorts = [ 3478 ];
|
||||
allowedTCPPorts = [ 22 80 443 2222 6969 ];
|
||||
allowedUDPPorts = [ 3478 7359 ];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
benq-password: ENC[AES256_GCM,data:jWOUkdqTtJDvPp3G0uSJDAlzdswT+efZf8keG8rhw5MgiNjWenegiC4eGdQ0ykeS+wQJJaHsrH5wIgpiMI32s18py70JDEqHnrcB0DPQnbE7DzfO7/4BhwdBWHSeEcQaiHjtFhuKr5c8hw==,iv:aA3qcFE5zyBoVMJzuGYkk5J0UyQPu4Xx3fTAl8G4Slw=,tag:IDjwsJ6c1Sm+lbo8qEfJAw==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age1tl5hp6v0t38fl8gxuqds40z503mwz885ev22au4jdy5r24aak3zsza23sx
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDbUpHc0l1Snd4eWVUUWRR
|
||||
ZWdvVDNlNUp4WWxBVGxxOWdDcjQ3WDM2b2hVCjVqNCs2cThsL2gyckE1K2NsSFdJ
|
||||
d0ZCamFlcVlnRmFDS05NcldmLzZZN2sKLS0tIDF1U2NhQkhjd1hEazRXbVpocnln
|
||||
cXhlUUY3a0tzSURRVEdYMDZGNGRmaGcKYknpSmwVYxSFVIde43kjvJFD5zS3hmvq
|
||||
JMXFAHfa7Nf3deu83gSGsvgQADb61ZI9v78x66lWwvdPKzcZFUriUg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-04-10T20:15:48Z"
|
||||
mac: ENC[AES256_GCM,data:MinxRU59BGQm3CH/EtvX0LaPLHkU2CsEqbbURqTtcY9H3tkLPTlf6NVJYaA4WtU2W3DZbxcRP4VzCj6o3LNdZ0qnb/CLG4INhIxRNL71SQZ8r5sIAkYHGLYOvI5kXIfFEdeXbK0Y12tb5CFIldERqbO+kcSARmlOqSoNCpFGlgw=,iv:HjwQ8n4yd8Q8Q7S/5tQFgrve960kcelwLpLIfYf6mcU=,tag:jarkIS3EkTTq2H6ZAdp+xg==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.12.2
|
||||
Reference in New Issue
Block a user