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"
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773889306,
|
"lastModified": 1777713215,
|
||||||
"narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
|
"narHash": "sha256-8GzXDOXckDWwST8TY5DbwYFjdvQLlP7K9CLSVx6iTTo=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
|
"rev": "63b4e7e6cf75307c1d26ac3762b886b5b0247267",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -21,11 +21,11 @@
|
|||||||
"dotfiles": {
|
"dotfiles": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1778400209,
|
"lastModified": 1778445660,
|
||||||
"narHash": "sha256-mEGIHZaTmQulmWLg3C8r0wdpEllFcKWeAt+3FLa4grM=",
|
"narHash": "sha256-eiGc//3MdHJJSlIXcdtjT+Fdgg+xrK8eqYpRBALzFyw=",
|
||||||
"owner": "aloslider",
|
"owner": "aloslider",
|
||||||
"repo": "dotfiles",
|
"repo": "dotfiles",
|
||||||
"rev": "e01f5de3b98f3c85997db571eeaef855bbda80a3",
|
"rev": "5cf0727f8aede69b350e46284cf7147d6185ef56",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772807318,
|
"lastModified": 1778365864,
|
||||||
"narHash": "sha256-Qjw6ILt8cb2HQQpCmWNLMZZ63wEo1KjTQt+1BcQBr7k=",
|
"narHash": "sha256-ImoT/wqmgMImf2dAC+E0MverAdA4QXsedOeES9B7Ezw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "daa2c221320809f5514edde74d0ad0193ad54ed8",
|
"rev": "2f419037039a152448c5f4ae9494154753d1b399",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -72,11 +72,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772542754,
|
"lastModified": 1777954456,
|
||||||
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
|
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -88,11 +88,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1775126147,
|
"lastModified": 1775888245,
|
||||||
"narHash": "sha256-J0dZU4atgcfo4QvM9D92uQ0Oe1eLTxBVXjJzdEMQpD0=",
|
"narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8d8c1fa5b412c223ffa47410867813290cdedfef",
|
"rev": "13043924aaa7375ce482ebe2494338e058282925",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -116,11 +116,11 @@
|
|||||||
"nixpkgs": "nixpkgs_3"
|
"nixpkgs": "nixpkgs_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1775682595,
|
"lastModified": 1777944972,
|
||||||
"narHash": "sha256-0E9PohY/VuESLq0LR4doaH7hTag513sDDW5n5qmHd1Q=",
|
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
|
||||||
"owner": "mic92",
|
"owner": "mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "d2e8438d5886e92bc5e7c40c035ab6cae0c41f76",
|
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"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 [
|
imports = lib.flatten [
|
||||||
(map lib.custom.relativeToRoot (
|
(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 = [
|
imports = [
|
||||||
./git.nix
|
./git.nix
|
||||||
./packages.nix
|
./packages.nix
|
||||||
./podman.nix
|
# ./sops.nix
|
||||||
./selfhosted
|
|
||||||
./sops.nix
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
sops = {
|
sops = {
|
||||||
defaultSopsFile = "${dotfiles}/dot_config/sops/secrets.yaml";
|
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";
|
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 = [
|
imports = [
|
||||||
./disko.nix
|
./disko.nix
|
||||||
./locale.nix
|
./locale.nix
|
||||||
./netbird.nix
|
|
||||||
./nix.nix
|
./nix.nix
|
||||||
./packages.nix
|
./packages.nix
|
||||||
./sops.nix
|
./sops.nix
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
services.netbird.enable = true;
|
|
||||||
}
|
|
||||||
+13
-10
@@ -1,14 +1,17 @@
|
|||||||
{ config, ... }:
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
nix.settings.experimental-features = [
|
nix = {
|
||||||
"nix-command"
|
gc = {
|
||||||
"flakes"
|
automatic = true;
|
||||||
];
|
dates = "daily";
|
||||||
|
options = "--delete-older-than 7d";
|
||||||
nix.gc = {
|
};
|
||||||
automatic = true;
|
settings = {
|
||||||
dates = "daily";
|
experimental-features = [
|
||||||
options = "--delete-older-than +5";
|
"nix-command"
|
||||||
|
"flakes"
|
||||||
|
];
|
||||||
|
auto-optimise-store = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
nix.settings.auto-optimise-store = true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
{
|
{
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
age
|
age
|
||||||
|
bat
|
||||||
bind
|
bind
|
||||||
btop
|
btop
|
||||||
git
|
git
|
||||||
jq
|
jq
|
||||||
openssl
|
openssl
|
||||||
|
ripgrep
|
||||||
tmux
|
tmux
|
||||||
tree
|
tree
|
||||||
|
vim
|
||||||
|
wget
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,5 @@
|
|||||||
defaultSopsFile = "/etc/sops/secrets.yaml";
|
defaultSopsFile = "/etc/sops/secrets.yaml";
|
||||||
age.keyFile = "/etc/age/key.txt";
|
age.keyFile = "/etc/age/key.txt";
|
||||||
validateSopsFiles = false;
|
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, ... }:
|
{ config, ... }:
|
||||||
let
|
let
|
||||||
cfg = config.selfhosted.vaultwarden;
|
cfg = config.selfhosted;
|
||||||
poCfg = config.selfhosted.pocket-id;
|
domain = "${config.sops.placeholder."domains/serv"}";
|
||||||
domain = config.sops.placeholder."domains/serv";
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
sops.secrets = {
|
sops.secrets = {
|
||||||
|
"vaultwarden/admin_token" = { };
|
||||||
"vaultwarden/client_id" = { };
|
"vaultwarden/client_id" = { };
|
||||||
"vaultwarden/client_secret" = { };
|
"vaultwarden/client_secret" = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
sops.templates."vaultwarden.env".content = ''
|
sops.templates."vaultwarden.env".content = ''
|
||||||
DOMAIN=https://${cfg.subdomain}.${domain}
|
DOMAIN=https://${cfg.vaultwarden.subdomain}.${domain}
|
||||||
LOG_LEVEL=info,vaultwarden::sso=debug
|
|
||||||
SIGNUPS_ALLOWED=false
|
SIGNUPS_ALLOWED=false
|
||||||
SSO_ENABLED=false
|
ADMIN_TOKEN=${config.sops.placeholder."vaultwarden/admin_token"}
|
||||||
SSO_DEBUG_TOKENS=true
|
|
||||||
|
# SSO
|
||||||
|
SSO_ENABLED=true
|
||||||
SSO_ONLY=false
|
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_ID=${config.sops.placeholder."vaultwarden/client_id"}
|
||||||
SSO_CLIENT_SECRET=${config.sops.placeholder."vaultwarden/client_secret"}
|
SSO_CLIENT_SECRET=${config.sops.placeholder."vaultwarden/client_secret"}
|
||||||
SSO_AUTH_ONLY_NOT_SESSION=true
|
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;
|
with lib;
|
||||||
{
|
{
|
||||||
|
sops.secrets = {
|
||||||
|
benq-password = {
|
||||||
|
neededForUsers = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
mutableUsers = false;
|
mutableUsers = false;
|
||||||
users.benq = {
|
users.benq = {
|
||||||
@@ -17,18 +23,29 @@ with lib;
|
|||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeIuJzR68xA4ugJjtWbwvaWEU852Hg9FAAhXNw8ou43 benq"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeIuJzR68xA4ugJjtWbwvaWEU852Hg9FAAhXNw8ou43 benq"
|
||||||
];
|
];
|
||||||
linger = true;
|
linger = true;
|
||||||
subUidRanges = [ { startUid = 100000; count = 65536; } ];
|
subUidRanges = [
|
||||||
subGidRanges = [ { startGid = 100000; count = 65536; } ];
|
{
|
||||||
|
startUid = 100000;
|
||||||
|
count = 65536;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
subGidRanges = [
|
||||||
|
{
|
||||||
|
startGid = 100000;
|
||||||
|
count = 65536;
|
||||||
|
}
|
||||||
|
];
|
||||||
extraGroups =
|
extraGroups =
|
||||||
let
|
let
|
||||||
ifTheyExist = groups: filter (group: hasAttr group config.users.groups) groups;
|
ifTheyExist = groups: filter (group: hasAttr group config.users.groups) groups;
|
||||||
in
|
in
|
||||||
flatten [
|
flatten [
|
||||||
"wheel"
|
"wheel"
|
||||||
(ifTheyExist [
|
(ifTheyExist [
|
||||||
"git"
|
"docker"
|
||||||
"networkmanager"
|
"git"
|
||||||
"video"
|
"networkmanager"
|
||||||
|
"video"
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
"bbr.nix"
|
"bbr.nix"
|
||||||
"endlessh.nix"
|
"endlessh.nix"
|
||||||
"fail2ban.nix"
|
"fail2ban.nix"
|
||||||
|
"fonts.nix"
|
||||||
"openssh.nix"
|
"openssh.nix"
|
||||||
|
"selfhosted"
|
||||||
])
|
])
|
||||||
))
|
))
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
networking = {
|
networking = {
|
||||||
hostName = "homelab";
|
hostName = "homelab";
|
||||||
firewall = {
|
firewall = {
|
||||||
allowedTCPPorts = [ 22 80 443 6969 ];
|
allowedTCPPorts = [ 22 80 443 2222 6969 ];
|
||||||
allowedUDPPorts = [ 3478 ];
|
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