This commit is contained in:
aloslider
2026-05-10 11:46:58 +03:00
parent 64b87222bd
commit f212c874b5
29 changed files with 689 additions and 80 deletions
Generated
+17
View File
@@ -18,6 +18,22 @@
"type": "github"
}
},
"dotfiles": {
"flake": false,
"locked": {
"lastModified": 1778400209,
"narHash": "sha256-mEGIHZaTmQulmWLg3C8r0wdpEllFcKWeAt+3FLa4grM=",
"owner": "aloslider",
"repo": "dotfiles",
"rev": "e01f5de3b98f3c85997db571eeaef855bbda80a3",
"type": "github"
},
"original": {
"owner": "aloslider",
"repo": "dotfiles",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -89,6 +105,7 @@
"root": {
"inputs": {
"disko": "disko",
"dotfiles": "dotfiles",
"home-manager": "home-manager",
"nixpkgs": "nixpkgs_2",
"sops-nix": "sops-nix"
+32 -26
View File
@@ -1,40 +1,46 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
disko.url = "github:nix-community/disko";
dotfiles = {
url = "github:aloslider/dotfiles";
flake = false;
};
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
disko.url = "github:nix-community/disko";
sops-nix.url = "github:mic92/sops-nix";
};
outputs = { self, nixpkgs, ... }@inputs:
let
system = "x86_64-linux";
pkgs = nixpkgs;
customLib = pkgs.lib.extend (
self: super: {
custom = (
import ./lib {
inherit (pkgs) lib;
}
);
}
);
in
{
nixosConfigurations = {
homelab = pkgs.lib.nixosSystem {
inherit system;
specialArgs = {
inherit inputs;
lib = customLib;
outputs =
{ self, nixpkgs, ... }@inputs:
let
system = "x86_64-linux";
pkgs = nixpkgs;
customLib = pkgs.lib.extend (
self: super: {
custom = (
import ./lib {
inherit (pkgs) lib;
}
);
}
);
in
{
nixosConfigurations = {
homelab = pkgs.lib.nixosSystem {
inherit system;
specialArgs = {
inherit inputs;
inherit (inputs) disko;
lib = customLib;
};
modules = [
./hosts/homelab/configuration.nix
];
};
modules = [
./hosts/homelab/configuration.nix
];
};
};
};
}
+4 -1
View File
@@ -2,6 +2,9 @@
{
imports = [
./git.nix
./zsh.nix
./packages.nix
./podman.nix
./selfhosted
./sops.nix
];
}
+10
View File
@@ -0,0 +1,10 @@
{ config, pkgs, ... }:
{
home.packages = with pkgs; [
chezmoi
fzf
lazygit
lazydocker
sops
];
}
@@ -1,7 +1,6 @@
{ config, ... }:
{
programs.zsh = {
services.podman = {
enable = true;
envExtra = "";
};
}
@@ -0,0 +1,18 @@
{ config, lib, ... }:
with lib;
{
options.selfhosted.common = {
networkName = mkOption {
type = types.str;
};
dataDir = mkOption {
type = types.str;
};
};
config = {
sops.secrets = {
"domains/serv" = { };
};
};
}
@@ -0,0 +1,53 @@
{ 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";
};
};
}
@@ -0,0 +1,40 @@
{ 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;
};
};
};
};
}
@@ -0,0 +1,195 @@
{ 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
'';
}
@@ -0,0 +1,110 @@
{
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;
};
};
};
};
}
@@ -0,0 +1,20 @@
{ config, ... }:
let
cfg = config.selfhosted.pocket-id;
in
{
sops.secrets = {
"pocket_id/enc_key" = { };
};
sops.templates."pocket-id.env" = {
content = ''
APP_URL=https://${cfg.subdomain}.${config.sops.placeholder."domains/serv"}
ENCRYPTION_KEY=${config.sops.placeholder."pocket_id/enc_key"}
TRUST_PROXY=true
ANALYTICS_DISABLED=true
PUID=1000
PGID=1000
'';
};
}
@@ -0,0 +1,45 @@
{ 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";
};
};
};
};
}
@@ -0,0 +1,26 @@
{ config, ... }:
let
cfg = config.selfhosted.vaultwarden;
poCfg = config.selfhosted.pocket-id;
domain = config.sops.placeholder."domains/serv";
in
{
sops.secrets = {
"vaultwarden/client_id" = { };
"vaultwarden/client_secret" = { };
};
sops.templates."vaultwarden.env".content = ''
DOMAIN=https://${cfg.subdomain}.${domain}
LOG_LEVEL=info,vaultwarden::sso=debug
SIGNUPS_ALLOWED=false
SSO_ENABLED=false
SSO_DEBUG_TOKENS=true
SSO_ONLY=false
SSO_AUTHORITY=https://${poCfg.subdomain}.${domain}
SSO_CLIENT_ID=${config.sops.placeholder."vaultwarden/client_id"}
SSO_CLIENT_SECRET=${config.sops.placeholder."vaultwarden/client_secret"}
SSO_AUTH_ONLY_NOT_SESSION=true
SSO_PKCE=true
'';
}
@@ -0,0 +1,39 @@
{ config, lib, ... }:
with lib;
let
common = config.selfhosted.common;
cfg = config.selfhosted.vaultwarden;
hostDataPath = "${common.dataDir}/vaultwarden/data";
in
{
imports = [
./config.nix
];
options.selfhosted.vaultwarden = {
enable = mkEnableOption "Enable container";
name = mkOption {
type = types.str;
};
subdomain = mkOption {
type = types.str;
};
};
config = {
services.podman.containers.${cfg.name} = {
image = "docker.io/vaultwarden/server:1.36.0";
network = config.selfhosted.common.networkName;
environmentFile = [ "${config.sops.templates."vaultwarden.env".path}" ];
volumes = [
"${hostDataPath}:/data"
];
autoStart = true;
extraConfig = {
Quadlet = {
DefaultDependencies = false;
};
};
};
};
}
+7
View File
@@ -0,0 +1,7 @@
{ config, ... }:
{
imports = [
./network.nix
./containers
];
}
+11
View File
@@ -0,0 +1,11 @@
{ config, ... }:
let
common = config.selfhosted.common;
in
{
services.podman = {
networks.${common.networkName} = {
driver = "bridge";
};
};
}
+18
View File
@@ -0,0 +1,18 @@
{ config, dotfiles, sops-nix, ... }:
{
imports = [
sops-nix.homeManagerModules.sops
];
sops = {
defaultSopsFile = "${dotfiles}/dot_config/sops/secrets.yaml";
age.keyFile = "${config.xdg.configHome}/age/key.txt";
};
home.file.".sops.yaml".source = "${dotfiles}/dot_sops.yaml";
xdg.configFile."sops" = {
source = "${dotfiles}/dot_config/sops";
recursive = true;
};
}
+6 -7
View File
@@ -1,8 +1,8 @@
{
config,
pkgs,
lib,
inputs,
lib,
pkgs,
...
}:
{
@@ -11,6 +11,9 @@
];
home-manager = {
extraSpecialArgs = {
inherit (inputs) disko dotfiles sops-nix;
};
useGlobalPkgs = true;
useUserPackages = true;
users.benq =
@@ -22,15 +25,11 @@
"home/benq/core"
]
++ (map (f: "home/benq/optional/${f}") [
])
))
];
home.packages = with pkgs; [
chezmoi
lazygit
lazydocker
];
home.stateVersion = "25.05";
};
};
+2
View File
@@ -3,8 +3,10 @@
imports = [
./disko.nix
./locale.nix
./netbird.nix
./nix.nix
./packages.nix
./sops.nix
./zsh.nix
];
}
+1 -2
View File
@@ -2,7 +2,6 @@
config,
disko,
lib,
inputs,
...
}:
with lib;
@@ -11,7 +10,7 @@ let
in
{
imports = [
inputs.disko.nixosModules.disko
disko.nixosModules.disko
];
options.disko.cfg = {
+4
View File
@@ -0,0 +1,4 @@
{ config, ... }:
{
services.netbird.enable = true;
}
+6 -1
View File
@@ -1,8 +1,13 @@
{ congif, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
age
bind
btop
git
neovim
jq
openssl
tmux
tree
];
}
+1 -1
View File
@@ -10,7 +10,7 @@
validateSopsFiles = false;
secrets = {
benq-password = {
neededForUsers = true;
neededForUsers = true;
};
};
};
+5
View File
@@ -0,0 +1,5 @@
{ config, ... }:
{
# programs.zsh.enable = true;
programs.nix-ld.enable = true;
}
-18
View File
@@ -1,18 +0,0 @@
{ config, pkgs, ... }:
{
virtualisation.docker = {
enable = true;
rootless = {
enable = true;
setSocketVariable = true;
};
};
security.wrappers = {
docker-rootlesskit = {
owner = "root";
group = "root";
capabilities = "cap_net_bind_service+ep";
source = "${pkgs.rootlesskit}/bin/rootlesskit";
};
};
}
+6 -2
View File
@@ -1,7 +1,7 @@
{
config,
lib,
inputs,
pkgs,
...
}:
with lib;
@@ -9,11 +9,16 @@ with lib;
users = {
mutableUsers = false;
users.benq = {
shell = pkgs.zsh;
isNormalUser = true;
ignoreShellProgramCheck = true;
hashedPasswordFile = config.sops.secrets.benq-password.path;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeIuJzR68xA4ugJjtWbwvaWEU852Hg9FAAhXNw8ou43 benq"
];
linger = true;
subUidRanges = [ { startUid = 100000; count = 65536; } ];
subGidRanges = [ { startGid = 100000; count = 65536; } ];
extraGroups =
let
ifTheyExist = groups: filter (group: hasAttr group config.users.groups) groups;
@@ -21,7 +26,6 @@ with lib;
flatten [
"wheel"
(ifTheyExist [
"docker"
"git"
"networkmanager"
"video"
+4 -1
View File
@@ -1,7 +1,10 @@
{ config, ... }:
{
boot.loader = {
systemd-boot.enable = true;
systemd-boot = {
enable = true;
configurationLimit = 5;
};
efi.canTouchEfiVariables = true;
};
}
+2 -1
View File
@@ -1,6 +1,7 @@
{
config,
lib,
pkgs,
...
}:
{
@@ -16,7 +17,6 @@
]
++ (map (f: "hosts/common/optional/${f}") [
"bbr.nix"
"docker.nix"
"endlessh.nix"
"fail2ban.nix"
"openssh.nix"
@@ -25,5 +25,6 @@
];
disko.cfg.mainDevice = "/dev/disk/by-id/ata-AMD_R5M120G8_07092225C0040";
environment.shells = [ pkgs.zsh ];
system.stateVersion = "26.05";
}
+6 -18
View File
@@ -2,25 +2,13 @@
{
networking = {
hostName = "homelab";
useDHCP = false;
dhcpcd.enable = false;
defaultGateway = "192.168.88.1";
nameservers = [ "192.168.88.1" ];
interfaces.enp7s0.ipv4.addresses = [
{
address = "192.168.88.5";
prefixLength = 24;
}
];
firewall = {
allowedTCPPorts = [
22
80
443
];
allowedUDPPorts = [ ];
allowedTCPPorts = [ 22 80 443 6969 ];
allowedUDPPorts = [ 3478 ];
};
};
boot.kernel.sysctl = {
"net.ipv4.ip_unprivileged_port_start" = 80;
};
}