Compare commits
10 Commits
c3a11470b9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 13e407847f | |||
| f212c874b5 | |||
| 64b87222bd | |||
| 91a36ec0c6 | |||
| 4a21cb9a9d | |||
| 6ea769417b | |||
| 6a3ff12527 | |||
| 03548606e4 | |||
| 7c82eb5dc5 | |||
| e4abb866e9 |
@@ -0,0 +1 @@
|
||||
result
|
||||
@@ -1,51 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./home-manager.nix
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
boot.loader = {
|
||||
limine = {
|
||||
enable = true;
|
||||
};
|
||||
efi.canTouchEfiVariables = true;
|
||||
};
|
||||
|
||||
networking.hostName = "benq-serv";
|
||||
networking.networkmanager.enable = true;
|
||||
|
||||
time.timeZone = "Europe/Moscow";
|
||||
|
||||
i18n = {
|
||||
defaultLocale = "en_US.UTF-8";
|
||||
extraLocaleSettings = {
|
||||
LC_TIME = "en_GB.UTF-8";
|
||||
};
|
||||
};
|
||||
|
||||
services.xserver.xkb = {
|
||||
layout = "us,ru";
|
||||
options = "eurosign:e,caps:escape";
|
||||
};
|
||||
|
||||
services.pipewire = {
|
||||
enable = true;
|
||||
pulse.enable = true;
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
git
|
||||
vim
|
||||
];
|
||||
|
||||
services.openssh.enable = true;
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
|
||||
networking.firewall.allowedUDPPorts = [];
|
||||
|
||||
system.stateVersion = "25.05";
|
||||
}
|
||||
|
||||
Generated
+112
-4
@@ -1,12 +1,82 @@
|
||||
{
|
||||
"nodes": {
|
||||
"disko": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777713215,
|
||||
"narHash": "sha256-8GzXDOXckDWwST8TY5DbwYFjdvQLlP7K9CLSVx6iTTo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "63b4e7e6cf75307c1d26ac3762b886b5b0247267",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"dotfiles": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1778445660,
|
||||
"narHash": "sha256-eiGc//3MdHJJSlIXcdtjT+Fdgg+xrK8eqYpRBALzFyw=",
|
||||
"owner": "aloslider",
|
||||
"repo": "dotfiles",
|
||||
"rev": "5cf0727f8aede69b350e46284cf7147d6185ef56",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "aloslider",
|
||||
"repo": "dotfiles",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778365864,
|
||||
"narHash": "sha256-ImoT/wqmgMImf2dAC+E0MverAdA4QXsedOeES9B7Ezw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "2f419037039a152448c5f4ae9494154753d1b399",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1772542754,
|
||||
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
||||
"lastModified": 1773628058,
|
||||
"narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f8573b9c935cfaa162dd62cc9e75ae2db86f85df",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1777954456,
|
||||
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
|
||||
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -16,9 +86,47 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1775888245,
|
||||
"narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "13043924aaa7375ce482ebe2494338e058282925",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
"disko": "disko",
|
||||
"dotfiles": "dotfiles",
|
||||
"home-manager": "home-manager",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"sops-nix": "sops-nix"
|
||||
}
|
||||
},
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777944972,
|
||||
"narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
|
||||
"owner": "mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "mic92",
|
||||
"repo": "sops-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,16 +1,46 @@
|
||||
{
|
||||
description = "Homelab server flake";
|
||||
|
||||
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";
|
||||
};
|
||||
sops-nix.url = "github:mic92/sops-nix";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs }@inputs: {
|
||||
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
|
||||
specialArgs = { inherit inputs; };
|
||||
modules = [
|
||||
./configuration.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;
|
||||
inherit (inputs) disko;
|
||||
lib = customLib;
|
||||
};
|
||||
modules = [
|
||||
./hosts/homelab/configuration.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-amd" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
fileSystems."/" =
|
||||
{ device = "/dev/disk/by-uuid/5454b429-e268-4399-9eaf-48e16951d1b4";
|
||||
fsType = "btrfs";
|
||||
};
|
||||
|
||||
fileSystems."/boot" =
|
||||
{ device = "/dev/disk/by-uuid/F6C1-695C";
|
||||
fsType = "vfat";
|
||||
options = [ "fmask=0077" "dmask=0077" ];
|
||||
};
|
||||
|
||||
swapDevices = [ ];
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
home-manager = builtins.fetchTarball {
|
||||
url = "https://github.com/nix-community/home-manager/archive/release-25.05.tar.gz";
|
||||
sha256 = "07pk5m6mxi666dclaxdwf7xrinifv01vvgxn49bjr8rsbh31syaq";
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(import "${home-manager}/nixos")
|
||||
];
|
||||
|
||||
users.users.benq.isNormalUser = true;
|
||||
|
||||
home-manager = {
|
||||
useGlobalPkgs = true;
|
||||
users.benq = { pkgs, ... }: {
|
||||
home.packages = with pkgs; [
|
||||
lazygit
|
||||
tmux
|
||||
];
|
||||
home.stateVersion = "25.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
];
|
||||
|
||||
home-manager = {
|
||||
extraSpecialArgs = {
|
||||
inherit (inputs) disko dotfiles sops-nix;
|
||||
};
|
||||
useGlobalPkgs = true;
|
||||
useUserPackages = true;
|
||||
users.benq =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = lib.flatten [
|
||||
(map lib.custom.relativeToRoot (
|
||||
[
|
||||
"home/common/core"
|
||||
]
|
||||
++ (map (f: "home/common/optional/${f}") [
|
||||
|
||||
])
|
||||
))
|
||||
];
|
||||
|
||||
home.stateVersion = "25.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./git.nix
|
||||
./packages.nix
|
||||
# ./sops.nix
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.programs.git.options;
|
||||
in
|
||||
{
|
||||
options.programs.git.options = {
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
default = "aloslider";
|
||||
};
|
||||
email = mkOption {
|
||||
type = types.str;
|
||||
default = "53711835+aloslider@users.noreply.github.com";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
programs.git = {
|
||||
settings = {
|
||||
user = {
|
||||
name = cfg.username;
|
||||
email = cfg.email;
|
||||
};
|
||||
init.defaultBranch = "master";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
chezmoi
|
||||
fzf
|
||||
lazygit
|
||||
lazydocker
|
||||
sops
|
||||
];
|
||||
}
|
||||
@@ -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}/sops/age/keys.txt";
|
||||
};
|
||||
|
||||
home.file.".sops.yaml".source = "${dotfiles}/dot_sops.yaml";
|
||||
|
||||
xdg.configFile."sops" = {
|
||||
source = "${dotfiles}/dot_config/sops";
|
||||
recursive = true;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
imports = [
|
||||
./disko.nix
|
||||
./locale.nix
|
||||
./nix.nix
|
||||
./packages.nix
|
||||
./sops.nix
|
||||
./zsh.nix
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
config,
|
||||
disko,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.disko.cfg;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
disko.nixosModules.disko
|
||||
];
|
||||
|
||||
options.disko.cfg = {
|
||||
mainDevice = mkOption {
|
||||
type = types.str;
|
||||
description = "Main disk name";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
disko.devices.disk.ssd = {
|
||||
device = cfg.mainDevice;
|
||||
type = "disk";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
ESP = {
|
||||
name = "ESP";
|
||||
size = "1G";
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
};
|
||||
};
|
||||
root = {
|
||||
name = "root";
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "btrfs";
|
||||
extraArgs = [
|
||||
"-f"
|
||||
"-L"
|
||||
"root"
|
||||
];
|
||||
mountpoint = "/";
|
||||
mountOptions = [
|
||||
"compress=zstd"
|
||||
"noatime"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
time.timeZone = "Europe/Moscow";
|
||||
|
||||
i18n = {
|
||||
defaultLocale = "en_US.UTF-8";
|
||||
extraLocaleSettings = {
|
||||
LC_TIME = "en_GB.UTF-8";
|
||||
};
|
||||
};
|
||||
|
||||
services.xserver.xkb = {
|
||||
layout = "us,ru";
|
||||
options = "eurosign:e,caps:escape";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
nix = {
|
||||
gc = {
|
||||
automatic = true;
|
||||
dates = "daily";
|
||||
options = "--delete-older-than 7d";
|
||||
};
|
||||
settings = {
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
auto-optimise-store = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{ congif, pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
age
|
||||
bat
|
||||
bind
|
||||
btop
|
||||
git
|
||||
jq
|
||||
openssl
|
||||
ripgrep
|
||||
tmux
|
||||
tree
|
||||
vim
|
||||
wget
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{ config, inputs, ... }:
|
||||
{
|
||||
imports = [
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
];
|
||||
|
||||
sops = {
|
||||
defaultSopsFile = "/etc/sops/secrets.yaml";
|
||||
age.keyFile = "/etc/age/key.txt";
|
||||
validateSopsFiles = false;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
# programs.zsh.enable = true;
|
||||
programs.nix-ld.enable = true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
services.pipewire = {
|
||||
enable = true;
|
||||
pulse.enable = true;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
boot.kernelModules = [ "tcp_bbr" ];
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv4.tcp_congestion_control" = "bbr";
|
||||
"net.core.deafult_qdisc" = "fq";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
services.endlessh = {
|
||||
enable = true;
|
||||
port = 22;
|
||||
openFirewall = true;
|
||||
};
|
||||
|
||||
systemd.services.endlessh = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
services.fail2ban = {
|
||||
enable = true;
|
||||
maxretry = 3;
|
||||
bantime = "48h";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
fonts.fontDir.enable = true;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
ports = [ 6969 ];
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
PermitEmptyPasswords = false;
|
||||
KbdInteractiveAuthentication = false;
|
||||
PermitRootLogin = "no";
|
||||
PubkeyAuthentication = 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,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,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}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
cfg = config.selfhosted;
|
||||
domain = "${config.sops.placeholder."domains/serv"}";
|
||||
in
|
||||
{
|
||||
sops.secrets = {
|
||||
"vaultwarden/admin_token" = { };
|
||||
"vaultwarden/client_id" = { };
|
||||
"vaultwarden/client_secret" = { };
|
||||
};
|
||||
|
||||
sops.templates."vaultwarden.env".content = ''
|
||||
DOMAIN=https://${cfg.vaultwarden.subdomain}.${domain}
|
||||
SIGNUPS_ALLOWED=false
|
||||
ADMIN_TOKEN=${config.sops.placeholder."vaultwarden/admin_token"}
|
||||
|
||||
# SSO
|
||||
SSO_ENABLED=true
|
||||
SSO_ONLY=false
|
||||
SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION=true
|
||||
SSO_AUTHORITY=https://${cfg.pocket-id.subdomain}.${domain}
|
||||
SSO_CLIENT_ID=${config.sops.placeholder."vaultwarden/client_id"}
|
||||
SSO_CLIENT_SECRET=${config.sops.placeholder."vaultwarden/client_secret"}
|
||||
SSO_AUTH_ONLY_NOT_SESSION=true
|
||||
'';
|
||||
}
|
||||
@@ -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}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib;
|
||||
{
|
||||
sops.secrets = {
|
||||
benq-password = {
|
||||
neededForUsers = true;
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
in
|
||||
flatten [
|
||||
"wheel"
|
||||
(ifTheyExist [
|
||||
"docker"
|
||||
"git"
|
||||
"networkmanager"
|
||||
"video"
|
||||
])
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
boot.loader = {
|
||||
systemd-boot = {
|
||||
enable = true;
|
||||
configurationLimit = 5;
|
||||
};
|
||||
efi.canTouchEfiVariables = true;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = lib.flatten [
|
||||
./boot.nix
|
||||
./network.nix
|
||||
./hardware-configuration.nix
|
||||
(map lib.custom.relativeToRoot (
|
||||
[
|
||||
"hosts/common/core"
|
||||
"hosts/common/users/benq.nix"
|
||||
"home/benq/homelab.nix"
|
||||
]
|
||||
++ (map (f: "hosts/common/optional/${f}") [
|
||||
"bbr.nix"
|
||||
"endlessh.nix"
|
||||
"fail2ban.nix"
|
||||
"fonts.nix"
|
||||
"openssh.nix"
|
||||
"selfhosted"
|
||||
])
|
||||
))
|
||||
];
|
||||
|
||||
disko.cfg.mainDevice = "/dev/disk/by-id/ata-AMD_R5M120G8_07092225C0040";
|
||||
environment.shells = [ pkgs.zsh ];
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [
|
||||
"ahci"
|
||||
"xhci_pci"
|
||||
"nvme"
|
||||
"usb_storage"
|
||||
"usbhid"
|
||||
"sd_mod"
|
||||
];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-amd" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
# hdd
|
||||
fileSystems."/mnt/data" = {
|
||||
device = "/dev/disk/by-id/ata-WDC_WD20EFPX-68C4TN0_WD-WX32DA423Z41";
|
||||
fsType = "ext4";
|
||||
options = [
|
||||
"noatime"
|
||||
"nofail"
|
||||
"x-systemd.device-timeout=5s"
|
||||
];
|
||||
};
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
networking = {
|
||||
hostName = "homelab";
|
||||
firewall = {
|
||||
allowedTCPPorts = [ 22 80 443 2222 6969 ];
|
||||
allowedUDPPorts = [ 3478 7359 ];
|
||||
};
|
||||
};
|
||||
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv4.ip_unprivileged_port_start" = 80;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
relativeToRoot = lib.path.append ../.;
|
||||
}
|
||||
Reference in New Issue
Block a user