dump
All checks were successful
Hello World / test (push) Successful in 12s

This commit is contained in:
plasmagoat 2025-07-05 11:12:20 +02:00
parent 4ed9ba0d24
commit a90630ecb6
98 changed files with 2063 additions and 729 deletions

View file

@ -33,6 +33,8 @@
# "/proxmox-01.lab/192.168.1.205"
# "/nas-01.lab/192.168.1.226"
"/mail.procopius.dk/213.32.245.247"
# Split Horizon DNS
"/procopius.dk/192.168.1.80"
"/.procopius.dk/192.168.1.80"

View file

@ -1,46 +0,0 @@
{ config, pkgs, modulesPath, lib, ... }:
{
# Pull in all the shared settings from configuration.nix
imports = [ ../configuration.nix ];
config = lib.recursiveUpdate config ({
# (Here, add anything liveVMspecific—e.g. NFS mounts, Docker, Compose service,
# static IP, or “import users/plasmagoat.nix” if you prefer.)
networking.interfaces.enp0s25 = {
useDHCP = false;
ipv4.addresses = [ { address = "192.168.1.50"; prefixLength = 24; } ];
ipv4.gateway = "192.168.1.1";
};
# Docker + Compose bits, for example:
fileSystems."/mnt/nas" = {
device = "192.168.1.100:/export/docker-volumes";
fsType = "nfs";
options = [ "defaults" "nofail" "x-systemd.requires=network-online.target" ];
};
environment.systemPackages = with pkgs; [
pkgs.docker
pkgs.docker-compose
# …plus anything else you want only on live VM…
];
services.docker.enable = true;
systemd.services.dockerComposeApp = {
description = "Auto-start DockerCompose stack";
after = [ "network-online.target" "docker.service" ];
wants = [ "network-online.target" "docker.service" ];
serviceConfig = {
WorkingDirectory = "/etc/docker-compose-app";
ExecStart = "${pkgs.docker-compose}/bin/docker-compose -f /etc/docker-compose-app/docker-compose.yml up";
ExecStop = "${pkgs.docker-compose}/bin/docker-compose -f /etc/docker-compose-app/docker-compose.yml down";
Restart = "always";
RestartSec = 10;
};
wantedBy = [ "multi-user.target" ];
};
});
}

View file

@ -1,11 +1,8 @@
{ config, pkgs,... }:
{
# users.users.forgejo-runner = {
# isSystemUser = true;
# extraGroups = [ "docker" ]; # Optional: if using docker jobs
# };
config,
pkgs,
...
}: {
services.gitea-actions-runner = {
package = pkgs.forgejo-actions-runner;
instances.default = {
@ -25,19 +22,18 @@
## optionally provide native execution on the host:
"native:host"
];
settings = {
log = {
level = "debug";
};
};
};
};
environment.systemPackages = with pkgs; [
wget
nodejs
];
# systemd.services."forgejo-actions-runner-default".serviceConfig = {
# User = "forgejo-runner";
# Group = "forgejo-runner";
# };
virtualisation.docker.enable = true; # Optional: if using docker
virtualisation.docker.enable = true; # Optional: if using docker
}

View file

@ -1,14 +1,17 @@
{ lib, pkgs, config, ... }:
{
lib,
pkgs,
config,
...
}: {
systemd.services.forgejo = {
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
after = ["postgresql.service"];
requires = ["postgresql.service"];
};
services.postgresql = {
enable = true;
ensureDatabases = [ "forgejo" ];
ensureDatabases = ["forgejo"];
ensureUsers = [
{
name = "forgejo";

View file

@ -1,12 +1,15 @@
{ lib, pkgs, config, ... }:
let
{
lib,
pkgs,
config,
...
}: let
cfg = config.services.forgejo;
srv = cfg.settings.server;
domain = "git.procopius.dk";
ssh_domain = "gitssh.procopius.dk";
in
{
users.users.plasmagoat.extraGroups = [ "forgejo" ];
in {
users.users.plasmagoat.extraGroups = ["forgejo"];
services.forgejo = {
enable = true;
@ -25,6 +28,15 @@ in
SSH_PORT = 2222;
SSH_DOMAIN = ssh_domain;
};
mailer = {
ENABLED = true;
FROM = "git@procopius.dk";
PROTOCOL = "smtp+starttls";
SMTP_ADDR = "mail.procopius.dk";
USER = "admin@procopius.dk";
PASSWD = "mikael";
};
database = {
DB_TYPE = lib.mkForce "postgres";
HOST = "/run/postgresql";
@ -33,12 +45,23 @@ in
};
service = {
DISABLE_REGISTRATION = true;
# ENABLE_INTERNAL_SIGNIN = false;
ENABLE_NOTIFY_MAIL = true;
};
metrics = {
ENABLED = true;
ENABLED_ISSUE_BY_REPOSITORY = true;
ENABLED_ISSUE_BY_LABEL = true;
};
actions = {
ZOMBIE_TASK_TIMEOUT = "30m";
};
oauth2 = {
};
oauth2_client = {
ENABLE_AUTO_REGISTRATION = true;
UPDATE_AVATAR = true;
};
# log = {
# ROOT_PATH = "/var/log/forgejo";
# MODE = "file";
@ -63,5 +86,5 @@ in
'';
# Optional: firewall
networking.firewall.allowedTCPPorts = [ 3000 2222 ];
networking.firewall.allowedTCPPorts = [3000 2222];
}

View file

@ -0,0 +1,13 @@
{ config, pkgs, modulesPath, lib, ... }:
{
imports = [
../../templates/base.nix
../../secrets/shared-sops.nix
./sops.nix
./networking.nix
./storage.nix
./forgejo.nix
./database.nix
];
}

View file

@ -0,0 +1,15 @@
{stdenv}:
stdenv.mkDerivation rec {
name = "keycloak_custom_theme";
version = "1.0";
src = ./custom_theme;
nativeBuildInputs = [];
buildInputs = [];
installPhase = ''
mkdir -p $out
cp -a login $out
'';
}

View file

@ -0,0 +1,4 @@
body {
background: red;
color: blue;
}

View file

@ -0,0 +1,3 @@
parent=base
import=common/keycloak
styles=css/custom.css

View file

@ -0,0 +1,11 @@
{pkgs, ...}: let
callPackage = pkgs.callPackage;
in {
nixpkgs.overlays = [
(final: prev: {
custom_keycloak_themes = {
custom = callPackage ./custom_theme.nix {};
};
})
];
}

View file

@ -0,0 +1,14 @@
{
config,
pkgs,
modulesPath,
lib,
...
}: {
imports = [
../../templates/base.nix
./networking.nix
./sops.nix
./keycloak.nix
];
}

View file

@ -0,0 +1,31 @@
{
config,
pkgs,
...
}: {
services.postgresql.enable = true;
services.keycloak = {
enable = true;
initialAdminPassword = "password";
database = {
type = "postgresql";
createLocally = true;
username = "keycloak";
passwordFile = config.sops.secrets.keycloak_psql_pass.path;
};
settings = {
hostname = "keycloak.procopius.dk";
# hostname-admin = "http://keycloak.lab:8080";
# hostname-strict = false;
# hostname-backchannel-dynamic = true;
http-enabled = true;
http-port = 8080;
proxy-headers = "xforwarded";
};
};
networking.firewall.allowedTCPPorts = [8080];
}

View file

@ -0,0 +1,8 @@
{
config,
lib,
pkgs,
...
}: {
networking.hostName = "keycloak";
}

View file

@ -0,0 +1,12 @@
{...}: let
keycloakSops = ../../secrets/keycloak/secrets.yml;
in {
sops.secrets.keycloak_psql_pass = {
sopsFile = keycloakSops;
mode = "0440";
};
sops.secrets.keycloak_admin_pass = {
sopsFile = keycloakSops;
mode = "0440";
};
}

14
nixos/hosts/mail/host.nix Normal file
View file

@ -0,0 +1,14 @@
{
config,
pkgs,
modulesPath,
lib,
...
}: {
imports = [
../../templates/base.nix
./networking.nix
./sops.nix
./mailserver.nix
];
}

View file

@ -0,0 +1,39 @@
{
config,
pkgs,
...
}: {
imports = [
(builtins.fetchTarball {
# Pick a release version you are interested in and set its hash, e.g.
url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/nixos-25.05/nixos-mailserver-nixos-25.05.tar.gz";
# To get the sha256 of the nixos-mailserver tarball, we can use the nix-prefetch-url command:
# release="nixos-25.05"; nix-prefetch-url "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz" --unpack
sha256 = "0jpp086m839dz6xh6kw5r8iq0cm4nd691zixzy6z11c4z2vf8v85";
})
];
mailserver = {
enable = true;
fqdn = "mail.procopius.dk";
domains = ["procopius.dk"];
# A list of all login accounts. To create the password hashes, use
# nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
loginAccounts = {
"admin@procopius.dk" = {
hashedPasswordFile = config.sops.secrets.mailserver-admin-pass.path;
aliases = [
"@procopius.dk"
"postmaster@procopius.dk"
];
};
};
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
# down nginx and opens port 80.
certificateScheme = "acme-nginx";
};
security.acme.acceptTerms = true;
security.acme.defaults.email = "david.mikael@proton.me";
}

View file

@ -0,0 +1,8 @@
{
config,
lib,
pkgs,
...
}: {
networking.hostName = "mail";
}

View file

@ -0,0 +1,8 @@
{...}: let
mailserverSops = ../../secrets/mailserver/secrets.yml;
in {
sops.secrets.mailserver-admin-pass = {
sopsFile = mailserverSops;
mode = "0440";
};
}

View file

@ -0,0 +1,44 @@
{config, ...}: {
services.prometheus.exporters.exportarr-sonarr = {
enable = true;
url = "http://media.lab:8989";
port = 9707;
openFirewall = true;
apiKeyFile = config.sops.secrets.sonarr-api-key.path;
};
services.prometheus.exporters.exportarr-readarr = {
enable = true;
url = "http://media.lab:8787";
port = 9708;
openFirewall = true;
apiKeyFile = config.sops.secrets.readarr-api-key.path;
};
services.prometheus.exporters.exportarr-radarr = {
enable = true;
url = "http://media.lab:7878";
port = 9709;
openFirewall = true;
apiKeyFile = config.sops.secrets.radarr-api-key.path;
};
services.prometheus.exporters.exportarr-prowlarr = {
enable = true;
url = "http://media.lab:9696";
port = 9710;
openFirewall = true;
apiKeyFile = config.sops.secrets.prowlarr-api-key.path;
};
services.prometheus.exporters.exportarr-lidarr = {
enable = true;
url = "http://media.lab:8686";
port = 9711;
openFirewall = true;
apiKeyFile = config.sops.secrets.lidarr-api-key.path;
};
services.prometheus.exporters.exportarr-bazarr = {
enable = true;
url = "http://media.lab:6767";
port = 9712;
openFirewall = true;
apiKeyFile = config.sops.secrets.bazarr-api-key.path;
};
}

View file

@ -0,0 +1,12 @@
{
imports = [
../../templates/base.nix
../../secrets/shared-sops.nix
./networking.nix
./storage.nix
./nixarr.nix
./exportarr.nix
./jellyfin-exporter.nix
./sops.nix
];
}

View file

@ -0,0 +1,8 @@
{config, ...}: {
services.prometheus.exporters.json = {
enable = true;
configFile = config.sops.secrets.jellyfin-exporter-config.path;
openFirewall = true;
user = "jellyfin";
};
}

View file

@ -0,0 +1,3 @@
{
networking.hostName = "media";
}

View file

@ -0,0 +1,42 @@
{config, ...}: {
nixarr = {
enable = true;
# These two values are also the default, but you can set them to whatever
# else you want
# WARNING: Do _not_ set them to `/home/user/whatever`, it will not work!
mediaDir = "/data/media";
stateDir = "/data/media/.state/nixarr";
vpn = {
enable = true;
# WARNING: This file must _not_ be in the config git directory
# You can usually get this wireguard file from your VPN provider
wgConf = config.sops.secrets.nixarr-vpn-conf.path;
};
jellyfin = {
enable = true;
};
transmission = {
enable = true;
vpn.enable = true;
peerPort = 51820; # Set this to the port forwarded by your VPN
};
# It is possible for this module to run the *Arrs through a VPN, but it
# is generally not recommended, as it can cause rate-limiting issues.
bazarr.enable = true;
lidarr.enable = true;
prowlarr.enable = true;
radarr.enable = true;
readarr.enable = true;
sonarr.enable = true;
jellyseerr.enable = true;
recyclarr = {
enable = true;
configFile = ./recyclarr.yml;
};
};
}

View file

@ -0,0 +1,183 @@
sonarr:
series:
base_url: http://localhost:8989
api_key: !env_var SONARR_API_KEY
include:
- template: sonarr-quality-definition-series
- template: sonarr-v4-quality-profile-web-2160p-alternative
- template: sonarr-v4-custom-formats-web-2160p
- template: sonarr-v4-quality-profile-anime
- template: sonarr-v4-custom-formats-anime
custom_formats:
# HDR Formats
- trash_ids:
# Comment out the next line if you and all of your users' setups are fully DV compatible
- 9b27ab6498ec0f31a3353992e19434ca # DV (WEBDL)
# HDR10+ Boost - Uncomment the next two lines if any of your devices DO support HDR10+
- 0dad0a507451acddd754fe6dc3a7f5e7 # HDR10+ Boost
- 385e9e8581d33133c3961bdcdeffb7b4 # DV HDR10+ Boost
assign_scores_to:
- name: WEB-2160p
# Unwanted
- trash_ids:
- 32b367365729d530ca1c124a0b180c64 # Bad Dual Groups
- 82d40da2bc6923f41e14394075dd4b03 # No-RlsGroup
- e1a997ddb54e3ecbfe06341ad323c458 # Obfuscated
- 06d66ab109d4d2eddb2794d21526d140 # Retags
- 1b3994c551cbb92a2c781af061f4ab44 # Scene
assign_scores_to:
- name: WEB-2160p
# Optional SDR
# Only ever use ONE of the following custom formats:
# SDR - block ALL SDR releases
# SDR (no WEBDL) - block UHD/4k Remux and Bluray encode SDR releases, but allow SDR WEB
# - trash_ids:
# - 2016d1676f5ee13a5b7257ff86ac9a93 # SDR
# # - 83304f261cf516bb208c18c54c0adf97 # SDR (no WEBDL)
# assign_scores_to:
# - name: WEB-2160p
- trash_ids:
- 026d5aadd1a6b4e550b134cb6c72b3ca # Uncensored
- b2550eb333d27b75833e25b8c2557b38 # 10bit
assign_scores_to:
- name: Remux-1080p - Anime
score: 1075 # Adjust scoring as desired
- trash_ids:
- 418f50b10f1907201b6cfdf881f467b7 # Anime Dual Audio
assign_scores_to:
- name: Remux-1080p - Anime
score: 2000 # Adjust scoring as desired
media_naming:
series: default
season: default
episodes:
rename: true
standard: default
daily: default
anime: default
delete_old_custom_formats: true
radarr:
movies:
base_url: http://localhost:7878
api_key: !env_var RADARR_API_KEY
include:
- template: radarr-quality-definition-movie
- template: radarr-custom-formats-remux-web-2160p
- template: radarr-quality-profile-anime
- template: radarr-custom-formats-anime
quality_profiles:
- name: Remux + WEB 2160p
reset_unmatched_scores:
enabled: true
upgrade:
allowed: true
until_quality: Remux-2160p
until_score: 10000
min_format_score: 0
quality_sort: top
qualities:
- name: Remux-2160p
- name: WEB 2160p
qualities:
- WEBDL-2160p
- WEBRip-2160p
- name: Remux-1080p
- name: WEB 1080p
qualities:
- WEBDL-1080p
- WEBRip-1080p
custom_formats:
- trash_ids:
# Audio
# Uncomment the next section to enable Advanced Audio Formats
- 496f355514737f7d83bf7aa4d24f8169 # TrueHD Atmos
- 2f22d89048b01681dde8afe203bf2e95 # DTS X
- 417804f7f2c4308c1f4c5d380d4c4475 # ATMOS (undefined)
- 1af239278386be2919e1bcee0bde047e # DD+ ATMOS
- 3cafb66171b47f226146a0770576870f # TrueHD
- dcf3ec6938fa32445f590a4da84256cd # DTS-HD MA
- a570d4a0e56a2874b64e5bfa55202a1b # FLAC
- e7c2fcae07cbada050a0af3357491d7b # PCM
- 8e109e50e0a0b83a5098b056e13bf6db # DTS-HD HRA
- 185f1dd7264c4562b9022d963ac37424 # DD+
- f9f847ac70a0af62ea4a08280b859636 # DTS-ES
- 1c1a4c5e823891c75bc50380a6866f73 # DTS
- 240770601cc226190c367ef59aba7463 # AAC
- c2998bd0d90ed5621d8df281e839436e # DD
assign_scores_to:
- name: Remux + WEB 2160p
# Movie Versions
- trash_ids:
# Uncomment any of the following lines to prefer these movie versions
# - 0f12c086e289cf966fa5948eac571f44 # Hybrid
# - 570bc9ebecd92723d2d21500f4be314c # Remaster
# - eca37840c13c6ef2dd0262b141a5482f # 4K Remaster
- e0c07d59beb37348e975a930d5e50319 # Criterion Collection
- 9d27d9d2181838f76dee150882bdc58c # Masters of Cinema
- db9b4c4b53d312a3ca5f1378f6440fc9 # Vinegar Syndrome
# - 957d0f44b592285f26449575e8b1167e # Special Edition
# - eecf3a857724171f968a66cb5719e152 # IMAX
# - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # IMAX Enhanced
assign_scores_to:
- name: Remux + WEB 2160p
# Optional
- trash_ids:
- b6832f586342ef70d9c128d40c07b872 # Bad Dual Groups
- cc444569854e9de0b084ab2b8b1532b2 # Black and White Editions
- ae9b7c9ebde1f3bd336a8cbd1ec4c5e5 # No-RlsGroup
- 7357cf5161efbf8c4d5d0c30b4815ee2 # Obfuscated
- 5c44f52a8714fdd79bb4d98e2673be1f # Retags
- f537cf427b64c38c8e36298f657e4828 # Scene
assign_scores_to:
- name: Remux + WEB 2160p
- trash_ids:
# Comment out the next line if you and all of your users' setups are fully DV compatible
- 923b6abef9b17f937fab56cfcf89e1f1 # DV (WEBDL)
# HDR10+ Boost - Uncomment the next two lines if any of your devices DO support HDR10+
- b17886cb4158d9fea189859409975758 # HDR10Plus Boost
- 55a5b50cb416dea5a50c4955896217ab # DV HDR10+ Boost
assign_scores_to:
- name: Remux + WEB 2160p
# Optional SDR
# Only ever use ONE of the following custom formats:
# SDR - block ALL SDR releases
# SDR (no WEBDL) - block UHD/4k Remux and Bluray encode SDR releases, but allow SDR WEB
- trash_ids:
- 9c38ebb7384dada637be8899efa68e6f # SDR
# - 25c12f78430a3a23413652cbd1d48d77 # SDR (no WEBDL)
assign_scores_to:
- name: Remux + WEB 2160p
- trash_ids:
- 064af5f084a0a24458cc8ecd3220f93f # Uncensored
- a5d148168c4506b55cf53984107c396e # 10bit
assign_scores_to:
- name: Remux-1080p - Anime
score: 1075 # Adjust scoring as desired
- trash_ids:
- 4a3b087eea2ce012fcc1ce319259a3be # Anime Dual Audio
assign_scores_to:
- name: Remux-1080p - Anime
score: 2000 # Adjust scoring as desired
media_naming:
folder: default
movie:
rename: true
standard: default
delete_old_custom_formats: true

View file

@ -0,0 +1,41 @@
{
sops.secrets.nixarr-vpn-conf = {
sopsFile = ../../secrets/nixarr/secrets.yml;
mode = "0440";
};
sops.secrets.sonarr-api-key = {
sopsFile = ../../secrets/nixarr/secrets.yml;
mode = "0440";
};
sops.secrets.radarr-api-key = {
sopsFile = ../../secrets/nixarr/secrets.yml;
mode = "0440";
};
sops.secrets.readarr-api-key = {
sopsFile = ../../secrets/nixarr/secrets.yml;
mode = "0440";
};
sops.secrets.bazarr-api-key = {
sopsFile = ../../secrets/nixarr/secrets.yml;
mode = "0440";
};
sops.secrets.lidarr-api-key = {
sopsFile = ../../secrets/nixarr/secrets.yml;
mode = "0440";
};
sops.secrets.prowlarr-api-key = {
sopsFile = ../../secrets/nixarr/secrets.yml;
mode = "0440";
};
sops.secrets.jellyfin-exporter-config = {
sopsFile = ../../secrets/nixarr/secrets.yml;
owner = "jellyfin";
};
}

View file

@ -0,0 +1,23 @@
{
boot.supportedFilesystems = ["nfs"];
services.rpcbind.enable = true;
fileSystems."/data/media/library/shows" = {
device = "192.168.1.226:/volume1/Media/TV Shows";
fsType = "nfs4";
options = ["x-systemd.automount" "noatime" "_netdev"];
};
fileSystems."/data/media/library/movies" = {
device = "192.168.1.226:/volume1/Media/Movies";
fsType = "nfs4";
options = ["x-systemd.automount" "noatime" "_netdev"];
};
fileSystems."/data/media/torrents" = {
device = "192.168.1.226:/volume1/data/torrents";
fsType = "nfs4";
options = ["x-systemd.automount" "noatime" "_netdev"];
};
}

View file

@ -1,19 +1,25 @@
{ config, pkgs, modulesPath, lib, ... }:
{
services.prometheus.alertmanagers = [ {
scheme = "http";
# path_prefix = "/alertmanager";
static_configs = [ {
targets = [
"localhost:9093"
config,
pkgs,
...
}: {
services.prometheus.alertmanagers = [
{
scheme = "http";
# path_prefix = "/alertmanager";
static_configs = [
{
targets = [
"localhost:9093"
];
}
];
} ];
} ];
}
];
services.prometheus.alertmanager = {
enable = true;
openFirewall = true;
webExternalUrl = "http://monitor.lab:9093"; # optional but helpful
webExternalUrl = "http://monitor.lab:9093"; # optional but helpful
configuration = {
route = {
group_wait = "10s";
@ -39,11 +45,12 @@
telegram_configs = [
{
api_url = "https://api.telegram.org";
bot_token = config.sops.secrets."telegram-alert-bot-token".path;
# FIX ME!
bot_token = "7597031094:AAHjjo3HL1XdY38pSNlR66-4wCP47o4LlSw"; # config.sops.secrets."telegram-alert-bot-token".path;
chat_id = -1002642560007;
message_thread_id = 4;
parse_mode = "HTML";
send_resolved = false;
send_resolved = true;
message = "{{ template \"telegram.message\". }}";
}
];

View file

@ -162,7 +162,7 @@
"pluginVersion": "7.3.6",
"targets": [
{
"expr": "sum(count_over_time({job=\"/var/log/traefik.log\"} |= \"RequestProtocol\" [$__interval]))",
"expr": "sum(count_over_time({job=\"traefik\"} |= \"RequestProtocol\" [$__interval]))",
"legendFormat": "",
"refId": "A"
}
@ -219,7 +219,7 @@
"pluginVersion": "7.3.6",
"targets": [
{
"expr": "sum by (OriginStatus) (count_over_time({job=\"/var/log/traefik.log\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))",
"expr": "sum by (OriginStatus) (count_over_time({job=\"traefik\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))",
"legendFormat": "HTTP Status: {{OriginStatus}}",
"refId": "A"
}
@ -284,7 +284,7 @@
"pluginVersion": "7.3.6",
"targets": [
{
"expr": " sum(rate({job=\"/var/log/traefik.log\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval])) / (sum(rate({job=\"/var/log/traefik.log\"} |~ \"RequestProtocol\" | json | __error__=\"\"[$__interval])) / 100)",
"expr": " sum(rate({job=\"traefik\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval])) / (sum(rate({job=\"traefik\"} |~ \"RequestProtocol\" | json | __error__=\"\"[$__interval])) / 100)",
"legendFormat": "",
"refId": "A"
}
@ -367,7 +367,7 @@
"steppedLine": false,
"targets": [
{
"expr": " sum by (OriginStatus,ServiceName) (count_over_time({job=\"/var/log/traefik.log\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval]))",
"expr": " sum by (OriginStatus,ServiceName) (count_over_time({job=\"traefik\"} |~ \"RequestProtocol\" | json | OriginStatus >= 400 |__error__=\"\"[$__interval]))",
"legendFormat": " {{ServiceName}} / {{OriginStatus}} ",
"refId": "A"
}
@ -474,7 +474,7 @@
"pluginVersion": "7.3.6",
"targets": [
{
"expr": "count(sum by (ClientHost) (count_over_time({job=\"/var/log/traefik.log\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval])))",
"expr": "count(sum by (ClientHost) (count_over_time({job=\"traefik\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval])))",
"legendFormat": "",
"refId": "A"
}
@ -544,7 +544,7 @@
"pluginVersion": "7.3.6",
"targets": [
{
"expr": "sum_over_time({job=\"/var/log/traefik.log\"}|= \"RequestProtocol\" | json | OriginStatus=200 | unwrap DownstreamContentSize | __error__=\"\" [$__interval])",
"expr": "sum_over_time({job=\"traefik\"}|= \"RequestProtocol\" | json | OriginStatus=200 | unwrap DownstreamContentSize | __error__=\"\" [$__interval])",
"legendFormat": "Bytes sent",
"refId": "A"
}
@ -638,7 +638,7 @@
"strokeWidth": 1,
"targets": [
{
"expr": "sum by (RouterName) (count_over_time({job=\"/var/log/traefik.log\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))",
"expr": "sum by (RouterName) (count_over_time({job=\"traefik\"}|= \"RequestProtocol\" | json | __error__=\"\" [$__interval]))",
"legendFormat": "{{RouterName}}",
"refId": "A"
}
@ -675,7 +675,7 @@
},
"targets": [
{
"expr": "{job=\"/var/log/traefik.log\"} |= \"RequestProtocol\"| json | line_format \"Status:{{.OriginStatus}} Client From {{.ClientAddr}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} Route To {{.ServiceAddr}}\"",
"expr": "{job=\"traefik\"} |= \"RequestProtocol\"| json | line_format \"Status:{{.OriginStatus}} Client From {{.ClientAddr}} {{.RequestMethod}} {{.RequestAddr}}{{.RequestPath}} Route To {{.ServiceAddr}}\"",
"legendFormat": "",
"refId": "A"
}
@ -749,7 +749,7 @@
"steppedLine": false,
"targets": [
{
"expr": "quantile_over_time(0.95,{job=\"/var/log/traefik.log\"} |= \"RequestProtocol\"| json | unwrap Duration | __error__=\"\" [$__interval]) by (ServiceName)",
"expr": "quantile_over_time(0.95,{job=\"traefik\"} |= \"RequestProtocol\"| json | unwrap Duration | __error__=\"\" [$__interval]) by (ServiceName)",
"hide": false,
"legendFormat": " {{ ServiceName }}",
"refId": "C"
@ -872,7 +872,7 @@
"steppedLine": false,
"targets": [
{
"expr": "max by (ServiceName) (max_over_time({job=\"/var/log/traefik.log\"} |= \"RequestProtocol\" |json | unwrap Duration | __error__=\"\" [$__interval]))",
"expr": "max by (ServiceName) (max_over_time({job=\"traefik\"} |= \"RequestProtocol\" |json | unwrap Duration | __error__=\"\" [$__interval]))",
"hide": false,
"legendFormat": "{{ ServiceName}}",
"refId": "D"
@ -995,7 +995,7 @@
"steppedLine": false,
"targets": [
{
"expr": "sum by (ServiceName) (sum_over_time({job=\"/var/log/traefik.log\"} |= \"RequestProtocol\" |json | unwrap RequestContentSize | __error__=\"\" [$__interval]))",
"expr": "sum by (ServiceName) (sum_over_time({job=\"traefik\"} |= \"RequestProtocol\" |json | unwrap RequestContentSize | __error__=\"\" [$__interval]))",
"hide": false,
"legendFormat": "{{ ServiceName}}",
"refId": "D"

View file

@ -852,7 +852,7 @@
"uid": "Prometheus"
},
"editorMode": "code",
"expr": "sum by(router) (rate(traefik_router_requests_total[$__rate_interval]))",
"expr": "sum by(service) (rate(traefik_service_requests_total[$__rate_interval]))",
"instant": false,
"legendFormat": "__auto",
"range": true,
@ -949,7 +949,7 @@
"uid": "Prometheus"
},
"editorMode": "code",
"expr": "sum by(service) (rate(traefik_router_request_duration_seconds_count[$__rate_interval]))",
"expr": "sum by(service) (rate(traefik_service_request_duration_seconds_count[$__rate_interval]))",
"instant": false,
"legendFormat": "__auto",
"range": true,

View file

@ -1,17 +1,30 @@
{ config, pkgs, modulesPath, lib, ... }:
{
config,
pkgs,
modulesPath,
lib,
...
}: {
services.grafana.enable = true;
services.grafana.settings.server = {
http_port = 3000;
http_addr = "0.0.0.0";
# Grafana needs to know on which domain and URL it's running
domain = "grafana.lab";
# root_url = "https://monitor.local/grafana/"; # Not needed if it is `https://your.domain/`
# serve_from_sub_path = true;
services.grafana.settings = {
server = {
http_port = 3000;
http_addr = "0.0.0.0";
# Grafana needs to know on which domain and URL it's running
domain = "grafana.procopius.dk";
root_url = "https://grafana.procopius.dk"; # Not needed if it is `https://your.domain/`
# serve_from_sub_path = true;
oauth_auto_login = false;
};
"auth.generic_oauth" = {
enabled = false;
};
"auth" = {
disable_login_form = false;
};
};
networking.firewall.allowedTCPPorts = [ 3000 ];
networking.firewall.allowedTCPPorts = [3000];
services.grafana = {
# declarativePlugins = with pkgs.grafanaPlugins; [ ... ];
@ -33,22 +46,32 @@
type = "loki";
url = "http://127.0.0.1:${toString config.services.loki.configuration.server.http_listen_port}";
}
# Some plugins also can - c.f. https://grafana.com/docs/plugins/yesoreyeram-infinity-datasource/latest/setup/provisioning/
# {
# name = "Infinity";
# type = "yesoreyeram-infinity-datasource";
# }
# But not all - c.f. https://github.com/fr-ser/grafana-sqlite-datasource/issues/141
{
uid = "influxdb";
name = "InfluxDB";
type = "influxdb";
url = "http://127.0.0.1:8086";
access = "proxy";
jsonData = {
dbName = "proxmox";
httpHeaderName1 = "Authorization";
};
secureJsonData = {
httpHeaderValue1 = "Token iY4MTuqUAVJbBkDUiMde";
};
}
];
# Note: removing attributes from the above `datasources.settings.datasources` is not enough for them to be deleted on `grafana`;
# One needs to use the following option:
# datasources.settings.deleteDatasources = [ { name = "prometheus"; orgId = 1; } { name = "loki"; orgId = 1; } ];
dashboards.settings.providers = [{
name = "my dashboards";
options.path = "/etc/grafana-dashboards";
}];
dashboards.settings.providers = [
{
name = "my dashboards";
options.path = "/etc/grafana-dashboards";
}
];
};
};
@ -59,6 +82,13 @@
mode = "0644";
};
environment.etc."grafana-dashboards/traefik-access.json" = {
source = ./dashboards/traefik-access.json;
user = "grafana";
group = "grafana";
mode = "0644";
};
environment.etc."grafana-dashboards/grafana-traefik.json" = {
source = ./dashboards/grafana-traefik.json;
user = "grafana";

View file

@ -1,14 +1,20 @@
{ config, pkgs, modulesPath, lib, ... }:
{
config,
pkgs,
modulesPath,
lib,
...
}: {
imports = [
../../templates/base.nix
../../secrets/shared-sops.nix
./networking.nix
./prometheus.nix
./influxdb.nix
./grafana.nix
./loki.nix
./alertmanager.nix
./sops.nix
./jellyfin-exporter.nix
];
}

View file

@ -0,0 +1,25 @@
{
config,
pkgs,
modulesPath,
lib,
...
}: {
services.influxdb2 = {
enable = true;
settings = {
};
provision = {
enable = true;
initialSetup = {
username = "plasmagoat";
passwordFile = config.sops.secrets.influxdb-password.path;
tokenFile = config.sops.secrets.influxdb-token.path;
organization = "procopius";
bucket = "proxmox";
};
};
};
networking.firewall.allowedTCPPorts = [8086];
}

View file

@ -0,0 +1,14 @@
{
virtualisation.oci-containers.containers = {
jellyfin_exporter = {
image = "rebelcore/jellyfin-exporter:latest";
ports = [
"9594:9594"
];
cmd = [
"--jellyfin.address=http://media.lab:8096"
"--jellyfin.token=f7c89e5aa307434c9b3ecb329e896335"
];
};
};
}

View file

@ -1,51 +1,137 @@
{ config, pkgs, modulesPath, lib, ... }:
{
config,
pkgs,
modulesPath,
lib,
...
}: let
monitor_hostname = "monitor.lab";
traefik_hostname = "traefik.lab";
sandbox_hostname = "sandbox.lab";
forgejo_hostname = "forgejo.lab";
runner01_hostname = "forgejo-runner-01.lab";
dnsmasq_hostname = "dns.lab";
media_hostname = "media.lab";
mail_hostname = "mail.lab";
keycloak_hostname = "keycloak.lab";
let
monitor_ip = "monitor.lab";
traefik_ip = "traefik.lab";
sandbox_ip = "sandbox.lab";
forgejo_ip = "forgejo.lab";
runner01_ip = "forgejo-runner-01.lab";
dnsmasq_ip = "dns.lab";
monitored_hosts = [
monitor_hostname
traefik_hostname
sandbox_hostname
forgejo_hostname
runner01_hostname
dnsmasq_hostname
media_hostname
mail_hostname
keycloak_hostname
];
generateTargets = port:
map (host: "${host}:${toString port}") monitored_hosts;
instance_relabel_config = [
{
source_labels = ["__address__"];
regex = "([^:]+):\\d+"; # Captures everything before the last colon
target_label = "instance";
replacement = "$1";
}
];
node_exporter_port = 9100;
node_exporter_job = {
job_name = "node";
static_configs = [{targets = generateTargets node_exporter_port;}];
relabel_configs = instance_relabel_config;
};
promtail_port = 9080;
promtail_job = {
job_name = "promtail";
static_configs = [{targets = generateTargets promtail_port;}];
relabel_configs = instance_relabel_config;
};
prometheus_exporter_port = 9100;
postgres_exporter_port = 9187;
prometheus_port = 9090;
alertmanager_port = 9093;
grafana_port = 3000;
promtail_port = 9080;
traefik_monitor_port = 8082;
forgejo_monitor_port = 3000;
dnsmasq_exporter_port = 9153;
exporters = {
node = [
"${monitor_ip}:${toString prometheus_exporter_port}"
"${traefik_ip}:${toString prometheus_exporter_port}"
"${sandbox_ip}:${toString prometheus_exporter_port}"
"${forgejo_ip}:${toString prometheus_exporter_port}"
"${runner01_ip}:${toString prometheus_exporter_port}"
monitoring_infra_job = {
job_name = "monitoring_infra";
static_configs = [
{
targets = [
"${monitor_hostname}:${toString prometheus_port}"
"${monitor_hostname}:${toString alertmanager_port}"
"${monitor_hostname}:${toString grafana_port}"
];
}
];
promtail = [
"${monitor_ip}:${toString promtail_port}"
"${traefik_ip}:${toString promtail_port}"
"${sandbox_ip}:${toString promtail_port}"
"${forgejo_ip}:${toString promtail_port}"
"${runner01_ip}:${toString promtail_port}"
];
grafana = [ "${monitor_ip}:${toString grafana_port}" ];
prometheus = [ "${monitor_ip}:${toString prometheus_port}" ];
alertmanager = [ "${monitor_ip}:${toString alertmanager_port}" ];
traefik = [ "${traefik_ip}:${toString traefik_monitor_port}" ];
gitea = [ "${forgejo_ip}:${toString forgejo_monitor_port}" ];
postgres = [ "${forgejo_ip}:${toString postgres_exporter_port}" ];
dnsmasq = [ "${dnsmasq_ip}:${toString dnsmasq_exporter_port}" ];
relabel_configs = instance_relabel_config;
};
traefik_monitor_port = 8082;
traefik_job = {
job_name = "traefik";
static_configs = [{targets = ["${traefik_hostname}:${toString traefik_monitor_port}"];}];
relabel_configs = instance_relabel_config;
};
forgejo_monitor_port = 3000;
forgejo_job = {
job_name = "forgejo";
static_configs = [{targets = ["${forgejo_hostname}:${toString forgejo_monitor_port}"];}];
relabel_configs = instance_relabel_config;
};
postgres_exporter_port = 9187;
postgres_job = {
job_name = "postgres";
static_configs = [{targets = ["${forgejo_hostname}:${toString postgres_exporter_port}"];}];
relabel_configs = instance_relabel_config;
};
dnsmasq_exporter_port = 9153;
dnsmasq_job = {
job_name = "dnsmasq";
static_configs = [{targets = ["${dnsmasq_hostname}:${toString dnsmasq_exporter_port}"];}];
relabel_configs = instance_relabel_config;
};
# --- Media Stack Scrape Job ---
media_stack_job = {
job_name = "media_stack";
static_configs = [
{
targets = [
"${media_hostname}:9707" # sonarr
"${media_hostname}:9708" # readarr
"${media_hostname}:9709" # radarr
"${media_hostname}:9710" # prowlarr
"${media_hostname}:9711" # lidarr
"${media_hostname}:9712" # bazarr
];
}
];
relabel_configs = instance_relabel_config;
};
jellyfin_port = 8096;
jellyfin_exporter_port = 9594;
jellyfin_job = {
job_name = "jellyfin";
static_configs = [
{
targets = [
"${media_hostname}:${toString jellyfin_port}"
"${monitor_hostname}:${toString jellyfin_exporter_port}"
];
}
];
relabel_configs = instance_relabel_config;
};
in {
networking.firewall.allowedTCPPorts = [ 9090 ];
networking.firewall.allowedTCPPorts = [9090];
services.prometheus = {
enable = true;
@ -61,10 +147,17 @@ in {
"--web.enable-admin-api"
];
scrapeConfigs = lib.mapAttrsToList (job_name: targets: {
inherit job_name;
static_configs = [ { inherit targets; } ];
}) exporters;
scrapeConfigs = [
node_exporter_job
promtail_job
monitoring_infra_job
traefik_job
forgejo_job
postgres_job
dnsmasq_job
media_stack_job
jellyfin_job
];
# 🔔 Alerts provisioning
ruleFiles = [

View file

@ -1,11 +0,0 @@
# /etc/grafana/provisioning/notifiers/contact-points.yml
apiVersion: 1
contactPoints:
- orgId: 1
name: telegram
type: telegram
settings:
bottoken: "__YOUR_BOT_TOKEN__"
chatid: "__YOUR_CHAT_ID__"
disableResolveMessage: false

View file

@ -1,7 +1,18 @@
{ config, lib, ... }:
{
config,
lib,
...
}: {
sops.secrets."telegram-alert-bot-token" = {
sopsFile = ../../secrets/telegram/secrets.yml;
owner = "prometheus";
mode = "0440";
};
sops.secrets."influxdb-password" = {
sopsFile = ../../secrets/influxdb/secrets.yml;
owner = "influxdb2";
};
sops.secrets."influxdb-token" = {
sopsFile = ../../secrets/influxdb/secrets.yml;
owner = "influxdb2";
};
}

View file

@ -0,0 +1,3 @@
{
virtualisation.kvmgt.enable = true;
}

View file

@ -0,0 +1,9 @@
{
imports = [
../../templates/base.nix
../../secrets/shared-sops.nix
./networking.nix
./runner-user.nix
./builder.nix
];
}

View file

@ -0,0 +1,9 @@
{
config,
lib,
pkgs,
runnerId,
...
}: {
networking.hostName = "nixos-builder";
}

View file

@ -0,0 +1,19 @@
{
config,
lib,
pkgs,
...
}: {
users.users.runner = {
isNormalUser = true;
description = "forgejo-runner";
extraGroups = [
"wheel"
];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGlzZWik5bbH6/xjiCpwo1SQSJ/J/Cv7y4ZQ45P68GLB forgejo-runner"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICUP7m8jZJiclZGfSje8CeBYFhX10SrdtjYziuChmj1X plasmagoat@macbook-air"
];
};
}

View file

@ -1,10 +1,15 @@
{ config, pkgs, modulesPath, lib, ... }:
{
config,
pkgs,
modulesPath,
lib,
...
}: {
imports = [
../../templates/base.nix
./networking.nix
./storage.nix
./sandbox.nix
./warpgate.nix
];
}

View file

@ -1,11 +1,11 @@
{
boot.supportedFilesystems = [ "nfs" ];
boot.supportedFilesystems = ["nfs"];
services.rpcbind.enable = true;
fileSystems."/mnt/nas" = {
device = "192.168.1.226:/volume1/docker";
fsType = "nfs";
options = [ "noatime" "vers=4" "rsize=8192" "wsize=8192" ];
};
# fileSystems."/mnt/nas" = {
# device = "192.168.1.226:/volume1/docker";
# fsType = "nfs";
# options = [ "noatime" "vers=4" "rsize=8192" "wsize=8192" ];
# };
}

View file

@ -0,0 +1,35 @@
{
virtualisation = {
containers.enable = true;
oci-containers.backend = "podman";
podman = {
enable = true;
# Create a `docker` alias for podman, to use it as a drop-in replacement
dockerCompat = true;
# Required for containers under podman-compose to be able to talk to each other.
defaultNetwork.settings.dns_enabled = true;
};
};
virtualisation.oci-containers.containers = {
warpgate = {
image = "ghcr.io/warp-tech/warpgate";
ports = [
"2222:2222"
"8888:8888"
];
volumes = [
"/srv/warpgate/data:/data"
];
};
};
systemd.tmpfiles.rules = [
"d /srv/warpgate 0755 root root -"
"d /srv/warpgate/data 0755 root root -"
];
networking.firewall.allowedTCPPorts = [8888];
}

View file

@ -0,0 +1,24 @@
{
keycloak = {
rule = "Host(`keycloak.procopius.dk`)";
service = "keycloak";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
oauth2proxy = {
rule = "Host(`radarr.procopius.dk`) && PathPrefix(`/oauth2/`)";
service = "oauth2proxy";
entryPoints = ["websecure"];
middlewares = ["auth-headers"];
tls.certResolver = "letsencrypt";
};
oauth2route = {
rule = "Host(`oauth.procopius.dk`)";
service = "oauth2proxy";
entryPoints = ["websecure"];
middlewares = ["auth-headers"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,5 @@
{
authentik.loadBalancer.servers = [{url = "http://authentik.lab:9000";}];
keycloak.loadBalancer.servers = [{url = "http://keycloak.lab:8080";}];
oauth2proxy.loadBalancer.servers = [{url = "http://localhost:4180";}];
}

View file

@ -0,0 +1,43 @@
{
traefik = {
rule = "Host(`traefik.procopius.dk`)";
service = "traefik";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
mail-acme = {
rule = "Host(`mail.procopius.dk`) && PathPrefix(`/.well-known/acme-challenge/`)";
service = "mail-acme";
entryPoints = ["web"];
priority = 1000;
middlewares = [];
};
forgejo = {
rule = "Host(`git.procopius.dk`)";
service = "forgejo";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
proxmox = {
rule = "Host(`proxmox.procopius.dk`)";
service = "proxmox";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
nas = {
rule = "Host(`nas.procopius.dk`)";
service = "nas";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
catchAll = {
rule = "HostRegexp(`.+`)";
service = "nginx";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,13 @@
{
traefik.loadBalancer.servers = [{url = "http://localhost:8080";}];
mail-acme.loadBalancer.servers = [{url = "http://mail.lab:80";}];
forgejo.loadBalancer.servers = [{url = "http://forgejo.lab:3000";}];
proxmox.loadBalancer.servers = [{url = "https://192.168.1.205:8006";}];
proxmox.loadBalancer.serversTransport = "insecureTransport";
nas.loadBalancer.servers = [{url = "https://192.168.1.226:5001";}];
nas.loadBalancer.serversTransport = "insecureTransport";
nginx.loadBalancer.servers = [{url = "https://192.168.1.226:4433";}];
nginx.loadBalancer.serversTransport = "insecureTransport";
}

View file

@ -0,0 +1,35 @@
{
jellyfin = {
rule = "Host(`jellyfin.procopius.dk`)";
service = "jellyfin";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
radarr = {
rule = "Host(`radarr.procopius.dk`)";
service = "radarr";
entryPoints = ["websecure"];
middlewares = [
"oauth-auth"
"restrict-admin"
];
tls.certResolver = "letsencrypt";
};
sonarr = {
rule = "Host(`sonarr.procopius.dk`)";
service = "sonarr";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
jellyseerr = {
rule = "Host(`jellyseerr.procopius.dk`)";
service = "jellyseerr";
entryPoints = ["websecure"];
# middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,6 @@
{
jellyfin.loadBalancer.servers = [{url = "http://media.lab:8096";}];
radarr.loadBalancer.servers = [{url = "http://media.lab:7878";}];
sonarr.loadBalancer.servers = [{url = "http://media.lab:8989";}];
jellyseerr.loadBalancer.servers = [{url = "http://media.lab:5055";}];
}

View file

@ -1,10 +1,43 @@
{ lib, config, ... }:
let
internalNetwork = "192.168.1.0/24";
in
{
in {
internal-whitelist = {
ipWhiteList.sourceRange = [ internalNetwork ];
ipWhiteList.sourceRange = [internalNetwork];
};
auth-headers = {
headers = {
sslRedirect = true;
stsSeconds = 315360000;
browserXssFilter = true;
contentTypeNosniff = true;
forceSTSHeader = true;
sslHost = "procopius.dk";
stsIncludeSubdomains = true;
stsPreload = true;
frameDeny = true;
};
};
oauth-auth = {
forwardAuth = {
address = "http://localhost:4180/";
trustForwardHeader = true;
authResponseHeaders = [
"Authorization"
"X-Auth-Request-Access-Token"
"X-Auth-Request-User"
"X-Auth-Request-Email"
"X-Auth-Request-Preferred-Username" # Recommended
"X-Auth-Request-Access-Token" # If you want to pass the token
"X-Auth-Request-Groups" # If you configured a mapper in Keycloak to emit groups
];
};
};
restrict-admin = {
forwardAuth = {
address = "http://localhost:4180/oauth2/auth?allowed_groups=role:admin";
};
};
}

View file

@ -0,0 +1,8 @@
{
mesterjakob = {
rule = "Host(`mester.jakobblum.dk`)";
service = "mesterjakob";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,3 @@
{
mesterjakob.loadBalancer.servers = [{url = "http://192.168.1.226:4200";}];
}

View file

@ -0,0 +1,28 @@
{
prometheus = {
rule = "Host(`prometheus.procopius.dk`)";
service = "prometheus";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
grafana = {
rule = "Host(`grafana.procopius.dk`)";
service = "grafana";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
alertmanager = {
rule = "Host(`alertmanager.procopius.dk`)";
service = "alertmanager";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
umami = {
rule = "Host(`umami.procopius.dk`)";
service = "umami";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,6 @@
{
prometheus.loadBalancer.servers = [{url = "http://monitor.lab:9090";}];
grafana.loadBalancer.servers = [{url = "http://monitor.lab:3000";}];
alertmanager.loadBalancer.servers = [{url = "http://monitor.lab:9093";}];
umami.loadBalancer.servers = [{url = "http://192.168.1.226:3333";}];
}

View file

@ -0,0 +1,35 @@
{
ente = {
rule = "Host(`ente.procopius.dk`)";
service = "ente";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
photos = {
rule = "Host(`photos.procopius.dk`)";
service = "photos";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
account = {
rule = "Host(`account.procopius.dk`)";
service = "account";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
minio = {
rule = "Host(`minio.procopius.dk`)";
service = "minio";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
minio-api = {
rule = "Host(`minio-api.procopius.dk`)";
service = "minio-api";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,7 @@
{
ente.loadBalancer.servers = [{url = "http://192.168.1.226:8087";}];
photos.loadBalancer.servers = [{url = "http://192.168.1.226:3000";}];
account.loadBalancer.servers = [{url = "http://192.168.1.226:3001";}];
minio.loadBalancer.servers = [{url = "http://192.168.1.226:3201";}];
minio-api.loadBalancer.servers = [{url = "http://192.168.1.226:3200";}];
}

View file

@ -1,140 +0,0 @@
{ lib, config, ... }:
{
traefik = {
rule = "Host(`traefik.procopius.dk`)";
service = "traefik";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
proxmox = {
rule = "Host(`proxmox.procopius.dk`)";
service = "proxmox";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
forgejo = {
rule = "Host(`git.procopius.dk`)";
service = "forgejo";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
prometheus = {
rule = "Host(`prometheus.procopius.dk`)";
service = "prometheus";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
grafana = {
rule = "Host(`grafana.procopius.dk`)";
service = "grafana";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
alertmanager = {
rule = "Host(`alertmanager.procopius.dk`)";
service = "alertmanager";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
jellyfin = {
rule = "Host(`jellyfin.procopius.dk`)";
service = "jellyfin";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
sonarr = {
rule = "Host(`sonarr.procopius.dk`)";
service = "sonarr";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
radarr = {
rule = "Host(`radarr.procopius.dk`)";
service = "radarr";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
ente = {
rule = "Host(`ente.procopius.dk`)";
service = "ente";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
photos = {
rule = "Host(`photos.procopius.dk`)";
service = "photos";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
minio = {
rule = "Host(`minio.procopius.dk`)";
service = "minio";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
minio-api = {
rule = "Host(`minio-api.procopius.dk`)";
service = "minio-api";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
account = {
rule = "Host(`account.procopius.dk`)";
service = "account";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
auth = {
rule = "Host(`auth.procopius.dk`)";
service = "auth";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
nas = {
rule = "Host(`nas.procopius.dk`)";
service = "nas";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
umami = {
rule = "Host(`umami.procopius.dk`)";
service = "umami";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
mesterjakob = {
rule = "Host(`mester.jakobblum.dk`)";
service = "mesterjakob";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
catchAll = {
rule = "HostRegexp(`.+`)";
service = "nginx";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
}

View file

@ -1,38 +0,0 @@
{ lib, config, ... }:
{
proxmox.loadBalancer.servers = [ { url = "https://192.168.1.205:8006"; } ];
proxmox.loadBalancer.serversTransport = "insecureTransport";
traefik.loadBalancer.servers = [ { url = "http://localhost:8080"; } ];
forgejo.loadBalancer.servers = [ { url = "http://forgejo.lab:3000"; } ];
nginx.loadBalancer.servers = [ { url = "https://192.168.1.226:4433"; } ];
nginx.loadBalancer.serversTransport = "insecureTransport";
prometheus.loadBalancer.servers = [ { url = "http://monitor.lab:9090"; } ];
grafana.loadBalancer.servers = [ { url = "http://monitor.lab:3000"; } ];
alertmanager.loadBalancer.servers = [ { url = "http://monitor.lab:9093"; } ];
# from nginx
account.loadBalancer.servers = [ { url = "http://192.168.1.226:3001"; } ];
auth.loadBalancer.servers = [ { url = "http://192.168.1.226:3005"; } ];
ente.loadBalancer.servers = [ { url = "http://192.168.1.226:8087"; } ];
photos.loadBalancer.servers = [ { url = "http://192.168.1.226:3000"; } ];
minio.loadBalancer.servers = [ { url = "http://192.168.1.226:3201"; } ];
minio-api.loadBalancer.servers = [ { url = "http://192.168.1.226:3200"; } ];
nas.loadBalancer.servers = [ { url = "https://192.168.1.226:5001"; } ];
nas.loadBalancer.serversTransport = "insecureTransport";
jellyfin.loadBalancer.servers = [ { url = "http://192.168.1.226:8096"; } ];
radarr.loadBalancer.servers = [ { url = "http://192.168.1.226:7878"; } ];
sonarr.loadBalancer.servers = [ { url = "http://192.168.1.226:8989"; } ];
umami.loadBalancer.servers = [ { url = "http://192.168.1.226:3333"; } ];
mesterjakob.loadBalancer.servers = [ { url = "http://192.168.1.226:4200"; } ];
}

View file

@ -1,11 +1,11 @@
{ lib, config, ... }:
{
entryPoints = {
web = {
address = ":80";
asDefault = true;
allowACMEByPass = true;
http.redirections.entrypoint = {
priority = 10;
to = "websecure";
scheme = "https";
};
@ -21,6 +21,8 @@
};
};
providers.file.watch = true;
api = {
dashboard = true;
insecure = true;
@ -37,7 +39,7 @@
dnsChallenge = {
provider = "cloudflare";
delayBeforeCheck = 10;
resolvers = [ "1.1.1.1:53" "8.8.8.8:53" ];
resolvers = ["1.1.1.1:53" "8.8.8.8:53"];
};
};
};

View file

@ -1,10 +1,16 @@
{ config, pkgs, modulesPath, lib, ... }:
{
config,
pkgs,
modulesPath,
lib,
...
}: {
imports = [
../../templates/base.nix
./networking.nix
./traefik.nix
./promtail.nix
./sops.nix
./oauth2proxy.nix
];
}

View file

@ -0,0 +1,76 @@
# /etc/nixos/configuration.nix
{
config,
lib,
pkgs,
...
}: let
oauth2ProxyKeyFile = config.sops.secrets."oauth2-proxy-env".path;
in {
services.oauth2-proxy = {
enable = true;
package = pkgs.oauth2-proxy;
keyFile = oauth2ProxyKeyFile;
provider = "keycloak-oidc"; # Use "oidc" for standard OIDC providers like Keycloak
oidcIssuerUrl = "https://keycloak.procopius.dk/realms/homelab";
clientID = "oauth2-proxy"; # Matches the client ID in Keycloak
# Public URL for oauth2-proxy itself, where Keycloak redirects back to
redirectURL = "https://oauth.procopius.dk/oauth2/callback";
upstream = ["static://202"];
extraConfig = {
code-challenge-method = "S256";
# email-domain = "*";
auth-logging = true;
request-logging = true;
whitelist-domain = ".procopius.dk";
pass-host-header = true;
skip-provider-button = true;
};
# Cookie configuration
cookie = {
name = "_oauth2_proxy_homelab";
domain = ".procopius.dk";
secure = true;
httpOnly = true;
expire = "24h";
refresh = "1h";
};
# Listen address for oauth2-proxy internally. Traefik will forward to this.
httpAddress = "http://127.0.0.1:4180"; # Ensure this port is not blocked by your firewall internally
# Reverse proxy settings for headers
reverseProxy = true; # Set to true because it's behind Traefik
# Headers to set for the upstream applications after successful authentication
setXauthrequest = true; # Set X-Auth-Request-User, X-Auth-Request-Email etc.
passBasicAuth = true; # Pass HTTP Basic Auth headers
passHostHeader = true; # Pass the original Host header to the upstream
# Authorization rules for who can access
# You can restrict by email domain (allows everyone from that domain)
email.domains = ["*"]; # Allows any authenticated user from Keycloak
# Or restrict by specific email addresses (if you want tighter control):
# email.addresses = allowedOauth2ProxyEmails;
# Logging
requestLogging = true;
# Optional: If you use specific scopes for Keycloak (e.g., if you want groups claim)
# scope = "openid profile email";
# If you specifically added a 'groups' claim in Keycloak:
scope = "openid profile email";
# You can add extra command-line flags here if needed, e.g., for debug logging
# extraConfig = {
#
# };
};
# Expose the internal port for oauth2-proxy if needed for debugging or direct access (less common)
networking.firewall.allowedTCPPorts = [4180];
}

View file

@ -1,7 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}: {
# This ensures the directory exists at boot, owned by traefik (writer) and readable by promtail.
systemd.tmpfiles.rules = [
"d /var/log/traefik 0755 traefik promtail -"
@ -12,9 +14,9 @@
job_name = "traefik";
static_configs = [
{
targets = [ "localhost" ];
targets = ["localhost"];
labels = {
job = "/var/log/traefik/*.log";
job = "traefik";
host = config.networking.hostName;
env = "proxmox";
instance = "${config.networking.hostName}.lab"; # prometheus scrape target

View file

@ -0,0 +1,10 @@
{
sops.secrets."traefik-env" = {
sopsFile = ../../secrets/traefik/secrets.yml;
mode = "0440";
};
sops.secrets."oauth2-proxy-env" = {
sopsFile = ../../secrets/traefik/secrets.yml;
mode = "0440";
};
}

View file

@ -1,23 +1,59 @@
{ config, lib, pkgs, ... }:
let
staticConfig = import ./configuration/static.nix { inherit lib config; };
middlewaresConfig = import ./configuration/middlewares.nix { inherit lib config; };
routersConfig = import ./configuration/routers.nix { inherit lib config; };
servicesConfig = import ./configuration/services.nix { inherit lib config; };
in
{
config,
lib,
...
}: let
# Import router and service declarations grouped in files
infraRouters = import ./configuration/infra/routers.nix;
infraServices = import ./configuration/infra/services.nix;
monitoringRouters = import ./configuration/monitoring/routers.nix;
monitoringServices = import ./configuration/monitoring/services.nix;
mediaRouters = import ./configuration/media-center/routers.nix;
mediaServices = import ./configuration/media-center/services.nix;
photosRouters = import ./configuration/photos/routers.nix;
photosServices = import ./configuration/photos/services.nix;
authRouters = import ./configuration/auth/routers.nix;
authServices = import ./configuration/auth/services.nix;
miscRouters = import ./configuration/misc/routers.nix;
miscServices = import ./configuration/misc/services.nix;
middlewares = import ./configuration/middlewares.nix;
staticConfig = import ./configuration/static.nix;
# Combine all routers and services from groups
allRouters = lib.foldl' (acc: routers: acc // routers) {} [
infraRouters
monitoringRouters
mediaRouters
photosRouters
authRouters
miscRouters
];
allServices = lib.foldl' (acc: services: acc // services) {} [
infraServices
monitoringServices
mediaServices
photosServices
authServices
miscServices
];
in {
services.traefik = {
enable = true;
environmentFiles = [config.sops.secrets."traefik-env".path];
# ==== Static Configuration ====
staticConfigOptions = staticConfig;
# ==== Dynamic Configuration ====
dynamicConfigOptions.http = {
routers = routersConfig;
services = servicesConfig;
middlewares = middlewaresConfig;
routers = allRouters;
services = allServices;
middlewares = middlewares;
serversTransports = {
insecureTransport = {
@ -26,11 +62,4 @@ in
};
};
};
systemd.services.traefik.serviceConfig.Environment = [
"CLOUDFLARE_DNS_API_TOKEN=gQYyG6cRw-emp_qpsUj9TrkYgoVC1v9UUtv94ozA"
"CLOUDFLARE_ZONE_API_TOKEN=gQYyG6cRw-emp_qpsUj9TrkYgoVC1v9UUtv94ozA"
];
virtualisation.docker.enable = true;
}

View file

View file

@ -0,0 +1,14 @@
{
virtualisation.oci-containers.containers = {
warpgate = {
image = "ghcr.io/warp-tech/warpgate";
ports = [
"2222:2222"
"8888:8888"
];
volumes = [
"/srv/warpgate/data:/data"
];
};
};
}