chore: split nixos and opentofu

This commit is contained in:
Tibo De Peuter 2026-07-24 22:17:43 +02:00
parent d125848b82
commit 06500a8f01
Signed by: tdpeuter
SSH key fingerprint: SHA256:u/h/LVoqKF1Iz02uOyxe6hcjmoZASCGV2HM0TG9ZMoU
68 changed files with 44 additions and 61 deletions

View file

@ -0,0 +1,278 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.arr;
networkName = "arrStack";
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
appNames = [ "bazarr" "prowlarr" "qbittorrent" "radarr" "sonarr" ];
inUse = builtins.any (app: cfg.${app}.enable) appNames;
PGID = toString config.users.groups.media.gid;
UMASK = "002";
in {
options.homelab.apps.arr = let
mkAppOption = appName: {
enable = lib.mkEnableOption "${appName} using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose ${appName} port";
default = cfg.exposePorts;
};
};
in {
enable = lib.mkEnableOption "Arr Stack using Docker";
exposePorts = lib.mkOption {
type = lib.types.bool;
description = "Expose all app ports";
# Only expose ports by default if Traefik is not in use.
default = ! config.homelab.apps.traefik.enable;
};
bazarr = mkAppOption "Bazarr";
prowlarr = mkAppOption "Prowlarr";
qbittorrent = mkAppOption "qBittorrent";
radarr = mkAppOption "Radarr";
sonarr = mkAppOption "Sonarr";
};
config = {
homelab = {
users = lib.mkIf inUse {
apps.enable = true;
media.enable = true;
};
# "Master switch": Enable all apps.
apps.arr = lib.mkIf cfg.enable {
bazarr.enable = true;
prowlarr.enable = true;
qbittorrent.enable = true;
radarr.enable = true;
sonarr.enable = true;
};
fileSystems.media.video = {
enable = true;
permissions = [ "read" "write" ];
};
virtualisation.containers.enable = lib.mkIf inUse true;
};
homelab.nfsMounts = let
hugoBackup = "192.168.0.11:/mnt/BIG/BACKUP";
arrOptions = [
"auto"
"rsize=1048576" "wsize=1048576"
"hard"
"timeo=600" "retrans=2"
"_netdev"
];
mkMount = device: {
inherit device;
extraOptions = arrOptions;
};
in lib.mkIf inUse {
"/srv/bazarr-backup" = lib.mkIf cfg.bazarr.enable (mkMount "${hugoBackup}/BAZARR");
"/srv/prowlarr-backup" = lib.mkIf cfg.bazarr.enable (mkMount "${hugoBackup}/PROWLARR");
"/srv/qbittorrent" = lib.mkIf cfg.qbittorrent.enable (mkMount "192.168.0.11:/mnt/SMALL/CONFIG/QBITTORRENT");
"/srv/radarr-backup" = lib.mkIf cfg.radarr.enable (mkMount "${hugoBackup}/RADARR");
"/srv/sonarr-backup" = lib.mkIf cfg.sonarr.enable (mkMount "${hugoBackup}/SONARR");
"/srv/torrent" = mkMount "192.168.0.11:/mnt/SMALL/MEDIA/TORRENT";
};
# Make sure the Docker network exists.
homelab.dockerNetworks."${networkName}" = lib.mkIf inUse {
requiredBy = [
"docker-bazarr.service"
"docker-prowlarr.service"
"docker-qbittorrent.service"
"docker-radarr.service"
"docker-sonarr.service"
];
};
# Create a user for each app.
homelab.appUsers = let
mkUser = uid: {
inherit uid;
group = config.users.groups.media.name;
};
in {
bazarr = lib.mkIf cfg.bazarr.enable (mkUser 3003);
prowlarr = lib.mkIf cfg.prowlarr.enable (mkUser 3004);
qbittorrent = lib.mkIf cfg.qbittorrent.enable ((mkUser 3005) // {
extraGroups = [ config.users.groups.apps.name ];
});
radarr = lib.mkIf cfg.radarr.enable (mkUser 3006);
sonarr = lib.mkIf cfg.sonarr.enable (mkUser 3007);
};
homelab.traefikRouters = {
bazarr = lib.mkIf cfg.bazarr.enable {
rule = "Host(`bazarr.depeuter.dev`)";
port = 6767;
};
prowlarr = lib.mkIf cfg.prowlarr.enable {
rule = "Host(`prowlarr.depeuter.dev`)";
port = 9696;
};
qbittorrent = lib.mkIf cfg.qbittorrent.enable {
rule = "Host(`qb.depeuter.dev`)";
port = 10095;
};
radarr = lib.mkIf cfg.radarr.enable {
rule = "Host(`radarr.depeuter.dev`)";
port = 7878;
};
sonarr = lib.mkIf cfg.sonarr.enable {
rule = "Host(`sonarr.depeuter.dev`)";
port = 8989;
};
};
virtualisation.oci-containers.containers = let
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
in {
bazarr = let
port = 6767;
in lib.mkIf cfg.bazarr.enable {
hostname = "bazarr";
image = "ghcr.io/hotio/bazarr:release-1.5.2";
autoStart = true;
ports = lib.mkIf cfg.bazarr.exposePorts [
"${toString port}:${toString port}/tcp"
"${toString port}:${toString port}/udp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.bazarr.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp";
};
volumes = [
"bazarr-config:/config"
"/srv/bazarr-backup:/config/backup"
"${videoHostPath}/Films:/media/movies"
"${videoHostPath}/Series:/media/series"
];
};
prowlarr = let
port = 9696;
in lib.mkIf cfg.prowlarr.enable {
hostname = "prowlarr";
image = "ghcr.io/hotio/prowlarr:release-2.0.5.5160";
autoStart = true;
ports = lib.mkIf cfg.prowlarr.exposePorts [
"${toString port}:${toString port}/tcp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.prowlarr.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
};
volumes = [
"prowlarr-config:/config"
"/srv/prowlarr-backup:/config/Backups"
];
};
qbittorrent = let
port = 10095;
in lib.mkIf cfg.qbittorrent.enable {
hostname = "qbittorrent";
image = "ghcr.io/hotio/qbittorrent:release-5.1.2";
autoStart = true;
ports = lib.mkIf cfg.qbittorrent.exposePorts [
"${toString port}:${toString port}/tcp"
"${toString port}:${toString port}/udp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.qbittorrent.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
WEBUI_PORTS = "${toString port}/tcp,${toString port}/udp";
};
volumes = [
"/srv/qbittorrent:/config"
"/srv/torrent:/media/cache"
];
};
radarr = let
port = 7878;
in lib.mkIf cfg.radarr.enable {
hostname = "radarr";
image = "ghcr.io/hotio/radarr:testing-5.28.0.10205";
autoStart = true;
ports = lib.mkIf cfg.radarr.exposePorts [
"${toString port}:${toString port}/tcp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.radarr.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
};
volumes = [
"radarr-config:/config"
"/srv/radarr-backup:/config/Backups"
"/srv/torrent:/media/cache"
"${videoHostPath}/Films:/media/movies"
];
};
sonarr = let
port = 8989;
in lib.mkIf cfg.sonarr.enable {
hostname = "sonarr";
image = "ghcr.io/hotio/sonarr:release-4.0.15.2941";
autoStart = true;
ports = lib.mkIf cfg.sonarr.exposePorts [
"${toString port}:${toString port}/tcp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
PUID = toString config.users.users.sonarr.uid;
inherit PGID UMASK;
TZ = config.time.timeZone;
};
volumes = [
"sonarr-config:/config"
"/srv/sonarr-backup:/config/Backups"
"/srv/torrent:/media/cache"
"${videoHostPath}/Series:/media/series"
];
};
};
};
}

View file

@ -0,0 +1,45 @@
$TTL 604800
@ IN SOA ns1 admin (
15 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
; Name servers - NS records
IN NS ns1
; IN NS ns2
ns1 IN A 192.168.0.91
;ns2 IN A 192.158.0.X
; Hostnames
hugo.kmtl IN A 192.168.0.11
ingress.kmtl IN A 192.168.0.10
ingress.kmtl IN AAAA fe80::be24:11ff:fed6:842a
; Core services
cloud IN A 192.168.0.10
git IN A 78.23.37.117
home IN A 192.168.0.10
jelly IN CNAME ingress.kmtl
vault IN A 192.168.0.10
; Production VM
books IN A 192.168.0.31
calibre IN A 192.168.0.31
; Production VM - Arr
bazarr IN A 192.168.0.33
prowlarr IN A 192.168.0.33
qb IN A 192.168.0.33
radarr IN A 192.168.0.33
sonarr IN A 192.168.0.33
; Development VM
plex IN A 192.168.0.91
; Catchalls
*.production IN A 192.168.0.31
*.development IN A 192.168.0.91

View file

@ -0,0 +1,54 @@
{ config, lib, ... }:
let
cfg = config.homelab.apps.bind9;
in {
options.homelab.apps.bind9.enable = lib.mkEnableOption "ISC BIND 9 (Docker)";
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
environment.etc = {
"bind/named.conf" = {
source = ./named.conf;
mode = "0555";
};
"bind/named.conf.options" = {
source = ./named.conf.options;
mode = "0555";
};
"bind/named.conf.local" = {
source = ./named.conf.local;
mode = "0555";
};
"bind/zones/db.depeuter.dev" = {
source = ./db.depeuter.dev;
mode = "0555";
};
};
virtualisation.oci-containers.containers.bind9 = {
hostname = "bind9";
#image = "internetsystemsconsortium/bind9:9.20"; # Current stable
image = "ubuntu/bind9"; # Current stable
autoStart = true;
ports = [
"53:53/udp"
"53:53/tcp"
"953:953/tcp"
];
extraOptions = [
];
environment = {
};
volumes = [
"/etc/bind:/etc/bind" # For configuration, your `named.conf` lives here
"bind9-cache:/var/cache/bind"
#"...:/var/lib/bind" # Secondary zones
"bind9-logs:/var/log" # Logfiles
];
labels = {
};
};
};
}

View file

@ -0,0 +1,2 @@
include "/etc/bind/named.conf.options";
include "/etc/bind/named.conf.local";

View file

@ -0,0 +1,4 @@
zone "depeuter.dev" {
type primary;
file "/etc/bind/zones/db.depeuter.dev";
};

View file

@ -0,0 +1,35 @@
http local {
endpoints { "/dns-query"; };
};
acl bogusnets {
};
acl trusted {
192.168.0.0/16;
};
options {
directory "/var/cache/bind";
version "not currently available";
listen-on { any; };
listen-on-v6 { any; };
listen-on tls ephemeral { any; };
listen-on-v6 tls ephemeral { any; };
listen-on tls ephemeral http local { any; };
listen-on-v6 tls ephemeral http local { any; };
recursion yes;
forwarders {
9.9.9.9;
149.112.112.112;
};
forward only;
allow-query { any; };
allow-recursion { any; };
allow-transfer { none; };
blackhole { bogusnets; };
};

View file

@ -0,0 +1,163 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.calibre;
PUID = toString config.users.users.calibre.uid;
PGID = toString config.users.groups.media.gid;
books = "/srv/books";
calibre-config = "/srv/calibre-config";
calibre-web-config = "/srv/calibre-web-config";
networkName = "calibre";
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
in {
options.homelab.apps.calibre = {
enable = lib.mkEnableOption "Calibre (Desktop + Web)";
desktop.enable = lib.mkEnableOption "Calibre Desktop (KasmVNC)";
web.enable = lib.mkEnableOption "Calibre Web";
};
config = lib.mkMerge [
{
homelab.apps.calibre = lib.mkIf cfg.enable {
desktop.enable = true;
web.enable = true;
};
}
# Common
(lib.mkIf (cfg.desktop.enable || cfg.web.enable) {
homelab = {
users.media.enable = true;
virtualisation.containers.enable = true;
};
homelab.appUsers.calibre = {
uid = 3010;
group = config.users.groups.media.name;
};
homelab.nfsMounts."${books}" = {
device = "192.168.0.11:/mnt/SMALL/MEDIA/BOOKS";
extraOptions = [
"auto"
"rsize=1048576" "wsize=1048576"
"timeo=600" "retrans=2"
"_netdev"
];
};
# Make sure the Docker network exists.
homelab.dockerNetworks."${networkName}" = {
requiredBy = [
"docker-calibre.service"
];
};
homelab.traefikRouters = {
calibre = lib.mkIf cfg.desktop.enable {
rule = "Host(`calibre.depeuter.dev`)";
port = 8080;
};
calibre-web = lib.mkIf cfg.web.enable {
rule = "Host(`books.depeuter.dev`)";
port = 8083;
};
};
})
# Calibre desktop
(lib.mkIf cfg.desktop.enable {
homelab.nfsMounts."${calibre-config}" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE";
extraOptions = [
"auto"
"rsize=1048576" "wsize=1048576"
"timeo=600" "retrans=2"
"_netdev"
];
};
virtualisation.oci-containers.containers.calibre = let
innerPort = 8080;
in {
hostname = "calibre";
image = "lscr.io/linuxserver/calibre:v8.10.0-ls354";
autoStart = true;
ports = [
# Open ports if you don't use Traefik
"9480:${toString innerPort}" # Calibre desktop GUI
#"9481:8181" # Calibre desktop GUI HTTPS
#"9581:8081" # Calibre webserver gui
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
# syscalls are unkown to Docker
#"--security-opt" "seccomp=unconfined"
];
environment = {
inherit PUID PGID;
#UMASK = "022";
TZ = config.time.timeZone;
#PASSWORD = "";
#CLI_ARGS = "";
};
volumes = [
"${calibre-config}:/config"
"${books}:/media/books"
];
};
})
# Calibre Web
(lib.mkIf cfg.web.enable {
homelab.nfsMounts."${calibre-web-config}" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/CALIBRE-WEB";
extraOptions = [
"auto"
"rsize=1048576" "wsize=1048576"
"timeo=600" "retrans=2"
"_netdev"
];
};
virtualisation.oci-containers.containers.calibre-web = let
innerPort = 8083;
in {
hostname = "calibre-web";
image = "lscr.io/linuxserver/calibre-web:0.6.25-ls346";
autoStart = true;
ports = [
# Open ports if you don't use Traefik
"8083:${toString innerPort}" # Web UI
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
inherit PUID PGID;
#UMASK = "022";
TZ = config.time.timeZone;
# (x86-64 only) Adds the ability to perform ebook conversion
DOCKER_MODS = "linuxserver/mods:universal-calibre";
# Allow Google Oauth
#OAUTHLIB_RELAX_TOKEN_SCOPE = "1";
};
volumes = [
"${calibre-web-config}:/config"
"${books}:/media/books"
];
};
})
];
}

View file

@ -0,0 +1,28 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.changedetection;
in {
options.homelab.apps.changedetection.enable = lib.mkEnableOption "Changedetection.io";
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
virtualisation.oci-containers.containers.changedetection = {
hostname = "changedetection";
image = "ghcr.io/dgtlmoon/changedetection.io";
autoStart = true;
ports = [
"5000:5000/tcp"
];
extraOptions = [
];
volumes = [
"changedetection:/datastore"
];
environment = {
LOGGER_LEVEL = "WARNING";
};
};
};
}

View file

@ -0,0 +1,19 @@
{
imports = [
./arr
./bind9
./calibre
./changedetection
./freshrss
./gitea
./homepage
./jellyfin
./monitoring
./plex
./solidtime
./speedtest
./technitium-dns
./traefik
./vaultwarden
];
}

View file

@ -0,0 +1,80 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.freshrss;
networkName = "freshrss";
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
in {
options.homelab.apps.freshrss = {
enable = lib.mkEnableOption "FreshRSS";
port = lib.mkOption {
type = lib.types.int;
default = 9080;
description = "FreshRSS WebUI port";
};
};
config = let
inherit (config.homelab.apps.freshrss) port;
in
lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
homelab.nfsMounts."/srv/freshrss" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/FRESHRSS";
extraOptions = [
"auto"
"timeo=600"
"retrans=2"
"_netdev"
];
};
homelab.dockerNetworks."${networkName}" = {
requiredBy = [
"docker-freshrss.service"
];
};
homelab.traefikRouters.freshrss = {
rule = "Host(`rss.depeuter.dev`)";
port = 80;
tls = true;
entryPoints = [ "websecure" ];
middlewares = [ "freshrssM1" "freshrssM2" ];
extraLabels = {
"traefik.http.middlewares.freshrssM1.compress" = "true";
"traefik.http.middlewares.freshrssM2.headers.browserXssFilter" = "true";
"traefik.http.middlewares.freshrssM2.headers.forceSTSHeader" = "true";
"traefik.http.middlewares.freshrssM2.headers.frameDeny" = "true";
"traefik.http.middlewares.freshrssM2.headers.referrerPolicy" = "no-referrer-when-downgrade";
"traefik.http.middlewares.freshrssM2.headers.stsSeconds" = "31536000";
};
};
virtualisation.oci-containers.containers.freshrss = {
hostname = "freshrss";
image = "freshrss/freshrss:1.25.0";
autoStart = true;
user = "0:33";
ports = [
"${toString port}:80/tcp"
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
TZ = config.time.timeZone;
CRON_MIN = "3,18,33,48"; # Alternatively, configure cron inside container.
SERVER_DNS = "rss.depeuter.dev";
TRUSTED_PROXY = "172.16.0.1/12 192.168.0.1/16";
};
volumes = [
"/srv/freshrss/www/freshrss/data:/var/www/FreshRSS/data"
"/srv/freshrss/www/freshrss/extensions:/var/www/FreshRSS/extensions"
];
};
};
}

View file

@ -0,0 +1,640 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.gitea;
networkName = "gitea";
UID = 3015;
GID = config.users.groups.apps.gid;
postgresPassword = "ChangeMe";
repoDir = "/srv/git";
webPort = 3000;
sshPort = 2222;
dbPort = 5432;
redisPort = 6379;
title = "Hugo's Forge";
slogan = "Forging ideas into reality.";
description = "Personal git server for projects that don't need collaboration.";
in {
options.homelab.apps.gitea.enable = lib.mkEnableOption "Gitea";
config = lib.mkIf cfg.enable {
homelab = {
users = {
apps.enable = true;
backup.enable = true;
};
virtualisation.containers.enable = true;
};
homelab.appUsers.gitea = {
uid = UID;
};
# Use filesystem mounts because rootless containers otherwise don't have access to the mount path (nested in docker directories).
# You could probably fix this by modifying the access rights on the path, but what would the point of that be?
homelab.nfsMounts = {
"/srv/gitea-config" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/GITEA";
};
"/srv/gitea-git" = {
device = "192.168.0.11:/mnt/SMALL/DATA/GIT";
};
};
# Make sure the Docker network exists.
homelab.dockerNetworks."${networkName}" = {
requiredBy = [
"docker-gitea-db.service"
"docker-gitea.service"
];
};
virtualisation.oci-containers.containers = {
gitea-db = {
hostname = "gitea-db";
image = "postgres:15.8-alpine";
autoStart = true;
ports = [
"5432:${toString dbPort}/tcp"
];
extraOptions = [
"--network=${networkName}"
];
environment = {
POSTGRES_PASSWORD = "ChangeMe";
PGDATA = "/var/lib/postgresql/data/pgdata";
};
volumes = [
"gitea-db:/var/lib/postgresql/data/pgdata"
];
};
gitea-redis = {
hostname = "gitea-redis";
image = "redis:7.4.0-alpine3.20";
autoStart = true;
ports = [
"6379:${toString redisPort}/tcp"
];
extraOptions = [
"--network=${networkName}"
];
volumes = [
"gitea-redis:/data"
];
};
gitea = {
hostname = "gitea";
image = "codeberg.org/forgejo/forgejo:11.0.1-rootless";
autoStart = true;
user = "${toString UID}:${toString GID}";
ports = [
"3000:${toString webPort}/tcp"
"2222:${toString sshPort}/tcp"
];
extraOptions = [
"--network=${networkName}"
];
dependsOn = [
"gitea-db"
"gitea-redis"
];
volumes = [
"/srv/gitea-config:/var/lib/gitea"
"/srv/gitea-git:/srv/git"
"/etc/timezone:/etc/timezone:ro"
"/etc/localtime:/etc/localtime:ro"
];
environmentFiles = [
# NOTE Don't forget to create this file.
# TODO Put in place using age(nix)?
"/var/lib/gitea.env"
];
environment = {
# App name that shows in every page title.
FORGEJO__APP_NAME = title;
# Shows a slogan near the App name in every page title.
FORGEJO__APP_SLOGAN = slogan;
# Defines how the AppDisplayName should be presented.
#FORGEJO__APP_DISPLAY_NAME_FORMAT = "";
# Will automaticaly detect the current user - but you can set it here.
FORGEJO__RUN_USER = "gitea";
# Application run mode, affects performance and debugging: "dev" or "prod", default is
# "prod". Mode "dev" makes Gitea easier to develop and debug, values other than "dev" are
# treated as "prod" which is for production use.
FORGEJO__RUN_MODE = "prod";
# The working directory.
#WORK_PATH = "";
# Disable SSH feature when not available.
FORGEJO__server__DISABLE_SSH = "false";
# Whether to use the builltin SSH server or not.
FORGEJO__server__START_SSH_SERVER = "true";
# Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
#FORGEJO__server__BUILTIN_SSH_SERVER_USER = "git";
# Domain to be exposed in clone URL.
#FORGEJO__server__SSH_DOMAIN = "";
# SSH username displayed in clone URLs.
#FORGEJO__server__SSH_USER = "git";
# The network interface the builtin SSH server should listen on.
#FORGEJO__server__SSH_LISTEN_HOST = "ens18";
# Port number to be exposed in clone URL.
FORGEJO__server__SSH_PORT = "22";
# Port number the builtin SSH server should listen on.
FORGEJO__server__SSH_LISTEN_PORT = toString sshPort;
# Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
FORGEJO__server__SSH_ROOT_PATH = "/var/lib/gitea/ssh";
# Gitea will create a authorized_keys file by default when it is not using the internal ssh server
# If you intend to use the AuthorizedKeysCommand functionality then you should turn this off.
#FORGEJO__server__SSH_CREATE_AUTHORIZED_KEYS_FILE = "true";
# Gitea will create a authorized_principals file by default when it is not using the internal ssh server
# If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off.
#FORGEJO__server__SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = "true";
# For the built-in SSH server, choose the ciphers to support for SSH connections,
# for system SSH this setting has no effect
#FORGEJO__server__SSH_SERVER_CIPHERS = "chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com";
# For the built-in SSH server, choose the key exchange algorithms to support for SSH connections,
# for system SSH this setting has no effect
#FORGEJO__server__SSH_SERVER_KEY_EXCHANGES = "curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1";
# For the built-in SSH server, choose the MACs to support for SSH connections,
# for system SSH this setting has no effect
#FORGEJO__server__SSH_SERVER_MACS = "hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1";
# For the built-in SSH server, choose the keypair to offer as the host key
# The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub
# relative paths are made absolute relative to the %(APP_DATA_PATH)s
FORGEJO__server__SSH_SERVER_HOST_KEYS = "/var/lib/gitea/ssh/forgejo.ed25519";
# Directory to create temporary files in when testing public keys using ssh-keygen,
# default is the system temporary directory.
#FORGEJO__server__SSH_KEY_TEST_PATH = "";
# Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself.
#FORGEJO__server__SSH_KEYGEN_PATH = "";
# Enable SSH Authorized Key Backup when rewriting all keys, default is false
FORGEJO__server__SSH_AUTHORIZED_KEYS_BACKUP = "false";
# ...
# Enable exposure of SSH clone URL to anonymous visitors, default is false.
FORGEJO__server__EXPOSE_ANONYMOUS = "false";
# ...
# Enables git-lfs support. true or false, default is false.
FORGEJO__server__LFS_START_SERVER = "false";
# ...
# Database to use. Either "mysql", "postgres" or "sqlite3".
FORGEJO__database__DB_TYPE = "postgres";
FORGEJO__database__HOST = "gitea-db:${toString dbPort}";
FORGEJO__database__NAME = "gitea";
FORGEJO__database__USER = "gitea";
FORGEJO__database__PASSWD = postgresPassword;
#FORGEJO__database__SCHEMA = "";
#FORGEJO__database__SSL_MODE = "disable";
# Whether the installer is disabled (set to true to disable the installer).
#FORGEJO__security__INSTALL_LOCK = "false";
# Global security key that will be used.
# This key is VERY IMPORTANT. If you lose it, the data encrypted by it can't be decrypted anymore.
#FORGEJO__security__SECRET_KEY = "";
# Alternatively, specify the location of the secret key.
#FORGEJO__security__SECRET_KEY_URI = "file:/etc/gitea/secret_key";
# ...
# IF the camo is enabled.
#FORGEJO__camo__ENABLED = "false";
# ....
# Enables OAuth2 provider
FORGEJO__oauth2__ENABLED = "false";
# ...
# Root path for the log files - defaults to %(GITEA_WORK_DIR)/log
#FORGEJO__log__ROOT_PATH = "";
# Either "console", "file" or "conn", default is "console"
FORGEJO__log__MODE = "file";
# Either "Trace", "Debug", "Info", "Warn", "Error" or "None", default is "Info".
FORGEJO__log__LEVEL = "Warn";
# ...
# Collect SSH logs (Creates logs from ssh git requests)
FORGEJO__log__ENABLE_SSH_LOG = "true";
# ...
# The path of git executable. If empty, Gitea searches through the PATH environment.
#FORGEJO__git__PATH = "";
# ...
FORGEJO__git_0x2E_timeout__MIGRATE = "600";
FORGEJO__git_0x2E_timeout__MIRROR = "600";
# Time limit to confirm account/email registration.
#FORGEJO__service__ACTIVE_CODE_LIVE_MINUTES = "180";
# Time limit to perform the reset of a forgotten password.
#FORGEJO__service__RESET_PASSWD_CODE_LIVE_MINUTES = "180";
# Whether a new user needs to confirm their email when registering.
FORGEJO__service__REGISTER_EMAIL_CONFIRM = "true";
# Whether a new user needs to be confirmed manually after registration.
FORGEJO__service__REGISTER_MANUAL_CONFIRM = "true";
# List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported.
#FORGEJO__service__EMAIL_DOMAIN_ALLOWLIST = "";
# Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported.
#FORGEJO__service__EMAIL_DOMAIN_BLOCKLIST = "";
# Disallow registration, only allow admins to create accounts.
FORGEJO__service__DISABLE_REGISTRATION = "true";
# Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false.
FORGEJO__service__ALLOW_ONLY_INTERNAL_REGISTRATION = "true";
# Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false.
FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION = "false";
# User must sign in to view anything.
FORGEJO__service__REQUIRE_SIGNIN_VIEW = "false";
# Mail notification
FORGEJO__service__ENABLE_NOTIFY_MAIL = "true";
# This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password.
# If you set this to false you will not be able to access the tokens endpoints on the API with your password.
# Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token.
FORGEJO__service__ENABLE_BASIC_AUTHENTICATION = "false";
# ...
# Enable captcha validation for registration.
FORGEJO__service__ENABLE_CAPTCHA = "true";
# Enable this to require captcha validation for login.
FORGEJO__service__REQUIRE_CAPTCHA_FOR_LOGIN = "true";
# Requires captcha for external registrations
#FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA = "false";
# Requires a password for external registrations.
#FORGEJO__service__REQUIRE_EXTERNAL_REGISTRATION_PASSWORD = "false";
# Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha, cfturnstile.
FORGEJO__service__CAPTCHA_TYPE = "image";
# ...
# Default value for KeepEmailPrivate
# Each new user will get the value of this setting copied into their profile
FORGEJO__service__DEFAULT_KEEP_EMAIL_PRIVATE = "true";
# Default value for AllowCreateOrganization
# Every new user will have rights set to create organizations depending on this setting.
FORGEJO__service__DEFAULT_ALLOW_CREATE_ORGANIZATION = "true";
# Default value for IsRestricted
# Every new user will have restricted permissions depending on this setting.
FORGEJO__service__DEFAULT_USER_IS_RESTRICTED = "false";
# Users will be able to use dots when choosing their username. Disabling this is
# helpful if your usersare having issues with e.g. RSS feeds or advanced third-party
# extensions that use strange regex patterns.
FORGEJO__service__ALLOW_DOTS_IN_USERNAMES = "false";
# Either "public", "limited" or "private", default is "public".
# Limited is for users visible only to signed users.
# Private is for users visible only to members of their organizations
# Public is for users visible for everyone
FORGEJO__service__DEFAULT_USER_VISIBILITY = "limited";
# Set which visibility modes a user can have
FORGEJO__service__ALLOWED_USER_VISIBILITY_MODES = "public,limited,private";
# Either "public", "limited" or "private", default is "public".
# Limited is for organizations visible only to signed users
# Private is for organizations visible only to members of the organization
# Public is for organizations visible to everyone
FORGEJO__service__DEFAULT_ORG_VISIBILITY = "limited";
# Default value for DefaultOrgMemberVisible
# True will make the membership of the users visible when added to the organisation
FORGEJO__service__DEFAULT_ORG_MEMBER_VISIBLE = "false";
# Default value for EnableDependencies
# Repositories will use dependencies by default depending on this setting
#FORGEJO__service__DEFAULT_ENABLE_DEPENDENCIES = "true";
# Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting.
#FORGEJO__service__ALLOW_CROSS_REPOSITORY_DEPENDENCIES = "true";
# Default map service. No external API support has been included. A service has to allow
# searching using URL parameters, the location will be appended to the URL as escaped query parameter.
# Some example values are:
# - OpenStreetMap: https://www.openstreetmap.org/search?query=
# - Google Maps: https://www.google.com/maps/place/
# - MapQuest: https://www.mapquest.com/search/
# - Bing Maps: https://www.bing.com/maps?where1=
#FORGEJO__service__USER_LOCATION_MAP_URL = "https://www.openstreetmap.org/search?query=";
# Enable heatmap on users profiles.
FORGEJO__service__ENABLE_USER_HEATMAP = "true";
# Enable Timetracking
FORGEJO__service__ENABLE_TIMETRACKING = "true";
# Default value for EnableTimetracking
# Repositories will use timetracking by default depending on this setting
FORGEJO__service__DEFAULT_ENABLE_TIMETRACKING = "false";
# Default value for AllowOnlyContributorsToTrackTime
# Only users with write permissions can track time if this is true
#FORGEJO__service__DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = "true";
# Value for the domain part of the user's email address in the git log if user
# has set KeepEmailPrivate to true. The user's email will be replaced with a
# concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default
# value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN
# Note: do not use the <DOMAIN> notation below
FORGEJO__service__NO_REPLY_ADDRESS = "noreply.depeuter.dev";
# Show Registration button.
FOGEJO__service__SHOW_REGISTRATION_BUTTON = "false";
# Show milestones dashboard page - a view of all the user's milestones.
#FORGEJO__service__SHOW_MILESTONES_DASHBOARD_PAGE = "true";
# Default value for AutoWatchNewRepos
# When adding a repo to a team or creating a new repo all team members will watch the
# repo automatically if enabled
#FORGEJO__service__AUTO_WATCH_NEW_REPOS = "true";
# Default value for AutoWatchOnChanges
# Make the user watch a repository When they commit for the first time
#FORGEJO__service__AUTO_WATCH_ON_CHANGES = "false";
# Minimum amount of time a user must exist before comments are kept when the user is deleted.
#FORGEJO__service__USER_DELETE_WITH_COMMENTS_MAX_TIME = "0";
# Valid site url schemes for user profiles
#FORGEJO__service__VALID_SITE_URL_SCHEMES = "http,https";
# Enable repository badges (via shields.io or a similar generator)
#FORGEJO__badges__ENABLED = "true";
# ...
# Root path for storing all repository data. By default, it is set to %(APP_DATA_PATH)s/gitea-repositories.
# A relative path is interpreted as _`AppWorkPath`_/%(ROOT)s
FORGEJO__repository__ROOT = repoDir;
# ...
# Force every new repository to be private.
FORGEJO__repository__FORCE_PRIVATE = "false";
# Default private when creating a new repository with push-to-create.
FORGEJO__repository__DEFAULT_PUSH_TO_CREATE = "true";
# ...
# Allow users to push local repositories to Forgejo and have them automatically created for a user.
FORGEJO__repository__ENABLE_PUSH_CREATE_USER = "true";
# Allow users to push local repositories to Forgejo and have them automatically created for an org.
FORGEJO__repository__ENABLE_PUSH_CREATE_ORG = "false";
# Comma separated list of globally disabled repo units.
FORGEJO__repository__DISABLED_REPO_UNITS = "";
# Comma separated list of default new repo units.
FORGEJO__repository__DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls,repo.releases,repo.actions";
# Comma separated list of default forked repo units.
FORGEJO__repository__DEFAULT_FORK_REPO_UNITS = "repo.code,repo.pulls";
# Prefix archive files by placing them in a directory named after the repository.
FORGEJO__repository__PREFIX_ARCHIVE_FILES = "true";
# Disable migrating feature.
FORGEJO__repository__DISABLE_MIGRATIONS = "false";
# Disable stars feature.
FORGEJO__repository__DISABLE_STARS = "true";
# Disable repository forking.
#FORGEJO__repository__DISABLE_FORKS = "false";
# The default branch name of new repositories
FORGEJO__repository__DEFAULT_BRANCH = "main";
# ...
# List of prefixes used in Pull Request title to mark them as Work In Progress (matched in a case-insensitive manner)
FORGEJO__repository_0x2E_pull_0X2D_request__WORK_IN_PROGRESS_PREFIXES = "WIP:,[WIP],WIP";
# ...
# In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list.
FORGEJO__repository_0x2E_pull_0X2D_request__DEFAULT_MERGE_MESSAGE_ALL_AUTHORS = "true";
# ...
# Enable cors headers (disabled by default)
FORGEJO__cors__ENABLED = "true";
# list of requesting origins that are allowed, eg: "https://*.example.com".
FORGEJO__cors__ALLOW_DOMAINS = "https://git.depeuter.dev,http://192.168.0.24:${toString webPort}";
# Set the default theme for the Gitea install.
FORGEJO__ui__DEFAULT_THEME = "gitea-auto";
# All available themes. Allow users to select personalized themes regardless of `DEFAULT_THEME`.
FORGEJO__ui__THEMES = "gitea-auto,gitea-light,gitea-dark,forgejo-auto,forgejo-light,forgejo-dark,forgejo-auto-deuteranopia-protanopia,forgejo-light-deuteranopia-protanopia,forgejo-dark-deuteranopia-protanopia,forgejo-auto-tritanopia,forgejo-light-tritanopia-forgejo-dark-tritanopia,github-auto,github,github-dark,edge-auto,edge-light,edge-dark,everforest-auto,everforest-light,everforest-dark,gruvbox-auto,gruvbox-light,gruvbox-dark,gruvbox-material-auto,grubox-material-dark,gruvbox-material-light,sonokai-andromeda,sonokai-atlantis,sonokai-espresso,sonokai-maia,sonokai-shusia,sonokai,catppuccin-frappe-green,catppuccin-frappe-teal,catppuccin-frappe-sky,catppuccin-frappe-sapphire,catppuccin-frappe-blue,catppuccin-frappe-lavender,catppuccin-macchiato-green,catppuccin-macchiato-teal,catppuccin-macchiato-sky,catppuccin-macchiato-sapphire,catppuccin-macchiato-blue,catppuccin-macchiato-lavender,catppuccin-mocha-green,catppuccin-mocha-teal,catppuccin-mocha-sky,catppuccin-mocha-sapphire,catppuccin-mocha-blue,catppuccin-mocha-lavender,nord,pitchblack,matrix,dark-arc";
FORGEJO__ui_0x2E_meta__AUTHOR = "${title} - ${slogan}";
FORGEJO__ui_0x2E_meta__DESCRIPTION = description;
FORGEJO__ui_0x2E_meta__KEYWORDS = "git,self-hosted,projects,code";
# Whether to render SVG files as images. If SVG rendering is disabled, SVG files are displayed as text and cannot be embedded in markdown files as images.
FORGEJO__ui_0x2E_svg__ENABLE_RENDER = "true";
# ...
# Enables math inline and block detection
FORGEJO__markdown__ENABLE_MATH = "true";
# Define allowed algorithms and their minimum key length (use -1 to disable a type)
#FORGEJO__ssh__0x2E__minimum_key_sizes__ED25519 = "256";
#FORGEJO__ssh__0x2E__minimum_key_sizes__ECDSA = "256";
FORGEJO__ssh_0x2E_minimum_key_sizes__RSA = "-1";
FORGEJO__ssh_0x2E_minimum_key_sizes__DSA = "-1";
# ... indexer
# ... queue
# Disallow regular (non-admin) users from creating organizations.
#FORGEJO__admin__DISABLE_REGULAR_ORG_CREATION = "false";
# Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
FORGEJO__admin__DEFAULT_EMAIL_NOTIFICATIONS = "enabled";
# Send an email to all admins when a new user signs up to inform the admins about this act. Options: true, false
FORGEJO__admin__SEND_NOTIFICATION_EMAIL_ON_NEW_USER = "true";
# Disabled features for users, could be "deletion", "manage_ssh_keys","manage_gpg_keys" more features can be disabled in future
# - deletion: a user cannot delete their own account
# - manage_ssh_keys: a user cannot configure ssh keys
# - manage_gpg_keys: a user cannot configure gpg keys
#FORGEJO__admin__USER_DISABLED_FEATURES = "";
# Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be `deletion`, `manage_ssh_keys`, `manage_gpg_keys`. This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
# - deletion: a user cannot delete their own account
# - manage_ssh_keys: a user cannot configure ssh keys
# - manage_gpg_keys: a user cannot configure gpg keys
#FORGEJO__admin__EXTERNAL_USER_DISABLE_FEATURES = "";
# Whether to allow signin in via OpenID
FORGEJO__openid__ENABLE_OPENID_SIGNIN = "false";
# Whether to allow registering via OpenID
# Do not include to rely on rhw DISABLE_REGISTRATION setting
FORGEJO__openid__ENABLE_OPENID_SIGNUP = "false";
# ...
# ... oath2_client
# ... webhook
FORGEJO__mailer__ENABLED = "true";
# Buffer length of channel, keep it as it is if you don't know what it is.
#FORGEJO__mailer__SEND_BUFFER_LEN = "100";
# Prefix displayed before subject in mail.
#FORGEJO__mailer__SUBJECT_PREFIX = "";
# Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"
FORGEJO__mailer__PROTOCOL = "smtps";
# Mail server address
FORGEJO__mailer__SMTP_ADDR = "smtp.gmail.com";
# Mail server port. If no protocol is specified, it will be inferred by this setting.
FORGEJO__mailer__SMTP_PORT = "465";
# Enable HELO operation. Defaults to true.
#FORGEJO__mailer__ENABLE_HELO = "true";
# Custom hostname fo the HELO operation. If no value is provided, one is retrieved from
# the system.
#FORGEJO__mailer__HELO_HOSTNAME = "";
# If set to 'true', completely ignores server certificate validation errors. UNSAFE!
#FORGEJO__mailer__FORCE_TRUST_SERVER_CERT = "false";
# Use client certificate in connection.
#FORGEJO__mailer__USE_CLIENT_CERT = "false";
#FORGEJO__mailer__CLIENT_CERT_FILE = "custom/mailer/cert.pem";
#FORGEJO__mailer__CLIENT_KEY_FILE = "custom/mailer/key.pem";
# Mail from address, RFC 5322. This can be just an email address, or the
# `"Name" <email@example.com>` format.
FORGEJO__mailer__FROM = ''"${title}" <git@depeuter.dev>'';
# Sometimes it is helpful to use a different address on the envelope. Set this to use
# ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address.
#FORGEJO__mailer__ENVELOPE_FROM = "";
# If gitea sends mails on behave of users, it will just use the name also displayed in the
# WebUI. If you want e.g. `Mister X (by CodeIt) <gitea@codeit.net>`, set it to
# `{{ .DisplayName }} (by {{ .AppName }})`.
# Available Variables: `.DisplayName`, `.AppName` and `.Domain`.
#FORGEJO__mailer__FROM_DISPLAY_NAME_FORMAT = "{{ .DisplayName }}";
# Mailer user name and password, if required by provider.
#FORGEJO__mailer__USER = "";
# Use PASSWD = `your password` for quoting if you use special characters in the password.
#FORGEJO__mailer__PASSWD = "";
# Send mails only in plain text, without HTML alternative
#FORGEJO__mailer__SEND_AS_PLAIN_TEXT = "false";
# Specify an alternative sendmail binary
#FORGEJO__mailer__SENDMAIL_PATH = "sendmail";
# Specify any extra sendmail arguments
# WARNING: if your sendmail program interprets options you should set this to "--" or terminate these args with "--"
#FORGEJO__mailer__SENDMAIL_ARGS = "";
# Timeout for Sendmail
#FORGEJO__mailer__SENDMAIL_TIMEOUT = "5m";
# convert \r\n to \n for Sendmail
#FORGEJO__mailer__SENDMAIL_CONVERT_CRLF = "true";
# ... email.incoming
# Either "memory", "redis", "memcache", or "twoqueue". default is "memory"
FORGEJO__cache__ADAPTER = "redis";
# For "memory" only, GC interval in seconds, default is 60.
#FORGEJO__cache__INTERVAL = "60";
# For "redis" and "memcache", connection host address
# redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
# memcache: `127.0.0.1:11211`
# twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000`
FORGEJO__cache__HOST = "redis://gitea-redis:${toString redisPort}/0?pool_size=100&idle_timeout=180s";
# Time to keep items in cache if not used, default is 16 hours.
# Setting it to -1 disables caching
FORGEJO__cache__ITEM_TTL = "16h";
# Time to keep items in cache if not used, default is 8760 hours.
# Setting it to -1 disables caching
FORGEJO__cache_0X2E_last_0X2D_commit__ITEM_TTL = "8760h";
# Only enable the cache when repository's commits count great than
FORGEJO__cache_0X2E_last_0X2D_commit__COMMITS_COUNT = "100";
# Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
# Default is "memory". "db" will reuse the configuration in [database]
#FORGEJO__session__PROVIDER = "memory";
# Provider config options
# memory: doesn't have any config yet
# file: session file path, e.g. `data/sessions`
# redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
# mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
#FORGEJO__session__PROVIDER_CONFIG = "data/sessions"; # Relative paths will be made absolute against _`AppWorkPath`_.
# Session cookie name
FORGEJO__session__COOKIE_NAME = "i_like_tibo";
# If you use session in https only: true or false. If not set, it defaults to `true` if the ROOT_URL is an HTTPS URL.
FORGEJO__session__COOKIE_SECURE = "true";
# Session GC time interval in seconds, default is 86400 (1 day)
#FORGEJO__session__GC_0X2E_INTERVAL_0X2E_TIME = "86400";
# Session life time in seconds, default is 86400 (1 day)
#FORGEJO__session__SESSION_0X2E_LIFE_0X2E_TIME = "86400";
# Cookie domain name. Default is empty
FORGEJO__session__DOMAIN = "git.depeuter.dev";
# SameSite settings. Either "none", "lax", or "strict"
FORGEJO__session__SAME_SITE = "strict";
# How Gitea deals with missing repository avatars
# none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
#FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK = "none";
#FORGEJO__picture__REPOSITORY_AVATAR_FALLBACK_IMAGE = "/img/repo_default.png";
# Max Width and Height of uploaded avatars.
# This is to limit the amount of RAM used when resizing the image.
FORGEJO__picture__AVATAR_MAX_WIDTH = "10000";
FORGEJO__picture__AVATAR_MAX_HEIGTH = "10000";
# The multiplication factor for rendered avatar images.
# Larger values result in finer rendering on HiDPI devices.
#FORGEJO__picture__AVATAR_RENDERED_SIZE_FACTOR = "2";
# Maximum allowed file size for uploaded avatars.
# This is to limit the amount of RAM used when resizing the image.
FORGEJO__picture__AVATAR_MAX_FILE_SIZE = "1048576";
# If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting.
#FORGEJO__picture__AVATAR_MAX_ORIGIN_SIZE = "262144";
# Chinese users can choose "duoshuo"
# or a custom avatar source, like: http://cn.gravatar.com/avatar/
#FORGEJO__picture__GRAVATAR_SOURCE = "gravatar";
# This value will always be true in offline mode.
#FORGEJO__picture__DISABLE_GRAVATAR = "false";
# Federated avatar lookup uses DNS to discover avatar associated.
# with emails, see https://www.libravatar.org
# This value will always be false in offline mode or when Gravatar is disabled.
#FORGEJO__picture__ENABLE_FEDERATED_AVATAR = "false";
# ... attachment
# ... time
# ... cron
# Enables the mirror functionality. Set to **false** to disable all mirrors. Pre-existing mirrors remain valid but won't be updated; may be converted to regular repo.
FORGEJO__mirror__ENABLED = "true";
# Disable the creation of **new** pull mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`.
FORGEJO__mirror__DISABLE_NEW_PULL = "false";
# Disable the creation of **new** push mirrors. Pre-existing mirrors remain valid. Will be ignored if `mirror.ENABLED` is `false`.
FORGEJO__mirror__DISABLE_NEW_PUSH = "false";
# Default interval as a duration between each check
FORGEJO__mirror__DEFAULT_INTERVAL = "1h";
# Min interval as a duration must be > 1m
FORGEJO__mirror__MIN_INTERVAL = "5m";
# ... api
# ... i18n
# .. highlight.mapping
# Show version information about Gitea and Go in the footer
FORGEJO__other__SHOW_FOOTER_VERSION = "false";
# Show template execution time in the footer
FORGEJO__other__SHOW_FOOTER_TEMPLATE_LOAD_TIME = "false";
# Show the "powered by" text in the footer
FORGEJO__other__SHOW_FOOTER_POWERED_BY = "false";
# Generate sitemap. Defaults to `true`.
FORGEJO__other__ENABLE_SITEMAP = "true";
# Enable/Disable RSS/Atom feed
FORGEJO__other__ENABLE_FEED = "true";
# ... markup
# ... metrics
# ... migrations
# ... f3
# Enable/Disable federation capabilities
FORGEJO__federation_ENABLED = "false";
# ...
# Enable/Disable package registry capabilities
FORGEJO__packages__ENABLED = "true";
# ... storage
# Repo-archive storage will override storage.
#FORGEJO__repo_0X2D_archive__STORAGE_TYPE = "local";
# Where your lfs files reside, default is data/lfs
FORGEJO__repo_0X2D_archive__PATH = "";
# Override the minio base path if storage type is minio.
#FORGEJO__repo_0X2D_archive__MINIO_BASE_PATH = "";
# lfs storage will override storage.
#FORGEJO__lfs__STORAGE_TYPE = "local";
# Where your lfs files reside, default is data/lfs
FORGEJO__lfs__PATH = "";
# Override the minio base path if storage is set to minio.
#FORGEJO__lfs__MINIO_BASE_PATH = "lfs/";
# Enable the proxy, all requests to external via HTTP will be affected
FORGEJO__proxy__PROXY_ENABLED = "false";
# Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy/no_proxy
#FORGEJO__proxy__PROXY_URL = "";
# Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts.
#FORGEJO__proxy__PROXY_HOSTS = "";
# Enable/Disable actions capabilities
FORGEJO__actions__ENABLED = "true";
# Default address to get action plugins, e.g. the default value means downloading from "https://code.forgejo.org/actions/checkout" for "uses: actions/checkout@v3"
#FORGEJO__actions__DEFAULT_ACTIONS_URL = "https://code.forgejo.org";
# ...
};
};
};
};
}

View file

@ -0,0 +1,71 @@
{ config, lib, ... }:
let
cfg = config.homelab.apps.homepage;
PUID = toString config.users.users.homepage.uid;
PGID = toString config.users.groups.apps.gid;
homepage-config = "/srv/homepage-config";
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
in {
options.homelab.apps.homepage = {
enable = lib.mkEnableOption "homepage";
port = lib.mkOption {
type = lib.types.int;
default = 3000;
description = "homepage WebUI port";
};
exposePort = lib.mkEnableOption "expose homepage port";
};
config = lib.mkIf cfg.enable {
homelab = {
users.apps.enable = true;
virtualisation.containers.enable = true;
};
homelab.appUsers.homepage = {
uid = 3018;
};
homelab.nfsMounts."${homepage-config}" = {
device = "192.168.0.11:/mnt/SMALL/CONFIG/HOMEPAGE";
extraOptions = [
"auto"
];
};
homelab.traefikRouters.homepage = let
host = "homepage.${config.networking.domain}";
in {
rule = "Host(`${host}`)";
port = cfg.port;
};
virtualisation.oci-containers.containers.homepage = let
host = "homepage.${config.networking.domain}";
in {
hostname = "homepage";
image = "ghcr.io/gethomepage/homepage:v1.10.1";
autoStart = true;
user = "${toString PUID}:${toString PGID}";
ports = lib.mkIf cfg.exposePort [
"${toString cfg.port}:3000/tcp"
];
networks = [
proxyNet
];
volumes = [
"${homepage-config}:/app/config"
# "/var/run/docker.sock:/var/run/docker.sock:ro" # For docker integrations
];
environment = {
inherit PUID PGID;
HOMEPAGE_ALLOWED_HOSTS = "${host},192.168.0.91:3000";
};
};
};
}

View file

@ -0,0 +1,130 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.jellyfin;
networkName = "jellyfin";
proxyNet = config.homelab.apps.traefik.sharedNetworkName;
inherit (config.homelab.fileSystems) media;
UID = 3008;
GID = config.users.groups.media.gid;
in {
options.homelab.apps.jellyfin.enable = lib.mkEnableOption "Jellyfin using Docker";
config = lib.mkIf cfg.enable {
homelab = {
fileSystems.media.video = {
enable = true;
permissions = [ "read" ];
};
users = {
apps.enable = true;
media.enable = true;
};
virtualisation.containers.enable = true;
};
homelab.nfsMounts = let
mkMount = device: {
inherit device;
readOnly = true;
};
in {
"/srv/audio" = mkMount "192.168.0.11:/mnt/SMALL/MEDIA/AUDIO";
"/srv/homevideo" = mkMount "192.168.0.11:/mnt/BIG/MEDIA/HOMEVIDEO/ARCHIVE";
"/srv/photo" = mkMount "192.168.0.11:/mnt/BIG/MEDIA/PHOTO/ARCHIVE";
};
homelab.appUsers.jellyfin = {
uid = UID;
extraGroups = [ config.users.groups.media.name ];
};
# Make sure the Docker network exists.
homelab.dockerNetworks."${networkName}" = {
requiredBy = [
"docker-jellyfin.service"
"docker-feishin.service"
];
};
homelab.traefikRouters.feishin = {
rule = "Host(`play.jelly.depeuter.dev`)";
port = 9180;
extraLabels = {
"traefik.tls.options.default.minVersion" = "VersionTLS13";
};
};
virtualisation.oci-containers.containers = {
jellyfin = {
hostname = "jellyfin";
image = "jellyfin/jellyfin:10.10.7";
user = "${toString UID}:${toString GID}";
autoStart = true;
ports = [
"8096:8096/tcp"
# "8920:8920/tcp"
];
extraOptions = [
"--network=${networkName}"
"--device=nvidia.com/gpu=all" # Equivalent to --gpus=all
];
volumes = [
"jellyfin-config:/config"
"cache:/cache"
"/srv/audio:/media/audio"
"${media.video.hostPath}:/media/video"
"/srv/homevideo:/media/homevideo"
"/srv/photo:/media/photo"
];
environment = {
# TODO
};
};
jellyfin-vue = {
hostname = "jellyfin-vue";
image = "ghcr.io/jellyfin/jellyfin-vue:unstable";
autoStart = true;
ports = [
"8080:80/tcp"
];
extraOptions = [
"--network=${networkName}"
];
labels = {
};
};
feishin = let
feishinPort = "9180";
in {
hostname = "feishin";
image = "ghcr.io/jeffvli/feishin:0.19.0";
autoStart = true;
ports = [
"${feishinPort}:9180/tcp" # Web player (HTTP)
];
extraOptions = [
"--network=${networkName}"
"--network=${proxyNet}"
];
environment = {
# pre defined server name
SERVER_NAME = "Hugo";
# When true AND name/type/url are set, only username/password can be toggled
SERVER_LOCK = "true";
# Either "jellyfin" or "navidrome"
SERVER_TYPE = "jellyfin";
# http://address:port
SERVER_URL= "https://jelly.depeuter.dev";
TZ = config.time.timeZone;
};
};
};
};
}

View file

@ -0,0 +1,158 @@
{ config, lib, pkgs, ... }:
{
sops.secrets."grafana/admin_password" = {
sopsFile = ../../../secrets/prod/monitoring.yaml;
};
sops.secrets."alertmanager/smtp_password" = {
sopsFile = ../../../secrets/prod/monitoring.yaml;
};
# 1. Loki Log Storage
services.loki = {
enable = true;
configuration = {
auth_enabled = false;
server.http_listen_port = 3100;
common.ring.instance_addr = "127.0.0.1";
common.ring.kvstore.store = "inmemory";
schema_config = {
configs = [{
from = "2020-10-24";
store = "boltdb-shipper";
object_store = "filesystem";
schema = "v11";
index = {
prefix = "index_";
period = "24h";
};
}];
};
storage_config = {
boltdb_shipper = {
active_index_directory = "/var/lib/loki/boltdb-shipper-active";
cache_location = "/var/lib/loki/boltdb-shipper-cache";
};
filesystem.directory = "/var/lib/loki/chunks";
};
};
};
# 2. Prometheus Time-Series DB
services.prometheus = {
enable = true;
port = 9090;
scrapeConfigs = [
{
job_name = "node";
scrape_interval = "15s";
# In a real setup, we would use Prometheus service discovery (e.g., file_sd_configs)
# or list all homelab IPs here. For now, we scrape localhost.
static_configs = [{
targets = [ "127.0.0.1:9100" ];
}];
}
];
# Connect Prometheus to Alertmanager
alertmanagers = [{
static_configs = [{
targets = [ "127.0.0.1:9093" ];
}];
}];
};
# 3. Alertmanager (Routing alerts to NTFY and Email)
services.prometheus.alertmanager = {
enable = true;
port = 9093;
configuration = {
global = {
smtp_smarthost = "smtp.example.com:587";
smtp_from = "alerts@depeuter.dev";
smtp_auth_username = "alerts@depeuter.dev";
smtp_auth_password_file = config.sops.secrets."alertmanager/smtp_password".path;
};
route = {
receiver = "ntfy-and-email";
group_wait = "30s";
group_interval = "5m";
repeat_interval = "4h";
group_by = [ "alertname" "instance" ];
};
receivers = [{
name = "ntfy-and-email";
email_configs = [{
to = "your-email@example.com";
# Use smarthost settings defined in global
}];
webhook_configs = [{
# Alertmanager natively supports webhooks. We send the JSON to NTFY's Prometheus endpoint.
url = "http://127.0.0.1:2586/alerts";
}];
}];
};
};
# 4. NTFY Push Notification Server
services.ntfy-sh = {
enable = true;
settings = {
base-url = "https://ntfy.lab.depeuter.dev";
listen-http = ":2586";
# You can configure auth via the CLI once the service is running, or via auth-file.
# For now, it is open locally.
};
};
# 5. Grafana Visualization
services.grafana = {
enable = true;
settings.server = {
http_port = 3000;
http_addr = "127.0.0.1";
domain = "grafana.lab.depeuter.dev";
};
settings.security.admin_password = "$__file{${config.sops.secrets."grafana/admin_password".path}}";
# Declarative Data Sources
provision = {
enable = true;
datasources.settings.datasources = [
{
name = "Prometheus";
type = "prometheus";
access = "proxy";
url = "http://127.0.0.1:9090";
isDefault = true;
}
{
name = "Loki";
type = "loki";
access = "proxy";
url = "http://127.0.0.1:3100";
}
];
};
};
# 6. Traefik Reverse Proxy for Grafana and NTFY
homelab.apps.traefik.dynamicConfigOptions.http = {
routers = {
grafana = {
rule = "Host(`grafana.lab.depeuter.dev`)";
service = "grafana";
};
ntfy = {
rule = "Host(`ntfy.lab.depeuter.dev`)";
service = "ntfy";
};
};
services = {
grafana.loadBalancer.servers = [{ url = "http://host.docker.internal:3000"; }];
ntfy.loadBalancer.servers = [{ url = "http://host.docker.internal:2586"; }];
};
};
# Open firewall for Loki so agents can push logs
networking.firewall.allowedTCPPorts = [ 3100 ];
}

View file

@ -0,0 +1,69 @@
{ config, lib, ... }:
let
cfg = config.homelab.apps.plex;
in {
options.homelab.apps.plex.enable = lib.mkEnableOption "Plex";
config = lib.mkIf cfg.enable {
homelab = {
users = {
apps.enable = true;
media.enable = true;
};
fileSystems.media.video.enable = true;
virtualisation.containers.enable = true;
};
users.users.plex = {
uid = lib.mkForce 3009;
isSystemUser = true;
group = config.users.groups.apps.name;
extraGroups = [
config.users.groups.media.name
];
home = "/var/empty";
shell = null;
};
virtualisation.oci-containers.containers.plex = let
videoHostPath = config.homelab.fileSystems.media.video.hostPath;
in {
hostname = "plex";
image = "plexinc/pms-docker:1.41.6.9685-d301f511a";
autoStart = true;
ports = [
"32400:32400/tcp" # Plex Media Server
"1900:1900/udp" # Plex DLNA Server
"32469:32469/tcp" # Plex DLNA Server
"32410:32410/udp" # GDM network discovery
"32412:32412/udp" # GDM network discovery
"32413:32413/udp" # GDM network discovery
"32414:32414/udp" # GDM network discovery
# "8324:8324/tcp" # Controlling Plex for Roku via Plex Companion
];
environment = {
#ADVERTISE_AP = "..."; # TODO Configure ip
ALLOWED_NETWORKS = "192.168.0.0/24,172.16.0.0/16";
CHANGE_CONFIG_DIR_OWNERSHIP = "false";
HOSTNAME = "Hugo-Plex";
PLEX_CLAIM = "claim-d5MqsjMeCZrUF6oUvssr";
PLEX_UID = toString config.users.users.plex.uid;
PLEX_GID = toString config.users.groups.media.gid;
TZ = config.time.timeZone;
};
volumes = [
# TODO Backup over NFS
"plex-config:/config"
"plex-transcode:/transcode"
"${videoHostPath}:/data/video:ro"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.plex.rule" = "Host(`plex.depeuter.dev`)";
"traefik.http.services.plex.loadbalancer.server.port" = "32400";
};
};
};
}

View file

@ -0,0 +1,258 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.solidtime;
networkName = "solidtime";
internalNetworkName = "solidtime-internal";
proxyNet = config.homelab.apps.traefiik.sharedNetworkName;
user = "1000:1000";
# dbExternalPort = ...;
dbInternalPort = 5432;
gotenbergPort = 3000;
inherit (config.virtualisation.oci-containers) containers;
solidtimeImageName = "solidtime/solidtime";
version = "0.10.0";
solidtimeImage = "${solidtimeImageName}:${version}";
solidtimeImageFile = pkgs.dockerTools.pullImage {
imageName = solidtimeImageName;
finalImageTag = version;
imageDigest = "sha256:817d3a366ecc39f0473d7154372afa82dd4e6e50c66d70be45804892c8421cbb";
sha256 = "sha256-h5aCKaquUF/EVsOHaLOHrn1HAoXZYPhAbJ+e4cmjSA8=";
};
volumes = [
"solidtime-storage:/var/www/html/storage"
"solidtime-logs:/var/www/html/storage/logs"
"solidtime-app:/var/www/html/storage/app"
];
# laravel.env
laravelEnv = {
APP_NAME = "Solidtime";
VITE_APP_NAME = laravelEnv.APP_NAME;
APP_ENV = "production";
APP_DEBUG = "false";
APP_URL = "http://localhost:${toString cfg.port}";
APP_FORCE_HTTPS = "false";
APP_ENABLE_REGISTRATION = "false";
TRUSTED_PROXIES = "0.0.0.0/0,2000:0:0:0:0:0:0:0/3";
# Logging
LOG_CHANNEL = "stderr_daily";
LOG_LEVEL = "debug";
# Database
DB_CONNECTION = "pgsql";
DB_HOST = containers.solidtimeDb.hostname;
DB_PORT = toString dbInternalPort;
DB_SSL_MODE = "require";
DB_DATABASE = "solidtime";
DB_USERNAME = "solidtime";
DB_PASSWORD = "ChangeMe";
# Mail
#MAIL_MAILER = "smtp";
#MAIL_HOST = "smtp.gmail.com";
#MAIL_PORT = "465";
#MAIL_ENCRYPTION = "tls";
#MAIL_FROM_ADDRESS = "no-reply@time.depeuter.dev";
MAIL_FROM_NAME = laravelEnv.APP_NAME;
#MAIL_USERNAME = "kmtl.hugo@gmail.com";
#MAIL_PASSWORD = "fhfxoequhhqidrhd";
# Queue
QUEUE_CONNECTION = "database";
# File storage
FILESYSTEM_DISK = "local";
PUBLIC_FILESYSTEM_DISK = "public";
# Services
GOTENBERG_URL = "http://${containers.solidtimeGotenberg.hostname}:${toString gotenbergPort}";
};
in {
options.homelab.apps.solidtime = {
enable = lib.mkEnableOption "Solidtime time tracker using Docker";
port = lib.mkOption {
type = lib.types.int;
default = 8000;
description = "Solidtime WebUI port";
};
exposePort = lib.mkEnableOption "Expose Soldtime port";
};
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
# Make sure the Docker network exists.
homelab.dockerNetworks = {
"${networkName}" = {
requiredBy = [
"${containers.solidtime.serviceName}.service"
];
};
"${internalNetworkName}" = {
requiredBy = [
"${containers.solidtime.serviceName}.service"
"${containers.solidtimeScheduler.serviceName}.service"
"${containers.solidtimeQueue.serviceName}.service"
"${containers.solidtimeDb.serviceName}.service"
"${containers.solidtimeGotenberg.serviceName}.service"
];
};
};
virtualisation.oci-containers.containers = {
solidtime = {
hostname = "solidtime";
image = solidtimeImage;
imageFile = solidtimeImageFile;
inherit user;
autoStart = true;
dependsOn = [
"solidtimeDb"
];
ports = [
# Open ports if you don't use Traefik
"${toString cfg.port}:8000"
];
networks = [
networkName
internalNetworkName
];
extraOptions = [
# Healthecks
# test: [ "CMD", "curl", "--fail", "http://localhost:8000/health-check/up" ]
''--health-cmd=curl --fail http://localhost:8000/health-check/up''
];
inherit volumes;
labels = {
"traefik.enable" = "true";
"traefik.http.routers.solidtime.rule" = "Host(`time.${config.networking.hostName}.depeuter.dev`)";
"traefik.http.services.solidtime.loadbalancer.server.port" = toString cfg.port;
};
environmentFiles = [
"/home/admin/.solidtime.env"
];
environment = laravelEnv // {
CONTAINER_MODE = "http";
};
};
solidtimeScheduler = {
hostname = "scheduler";
image = solidtimeImage;
imageFile = solidtimeImageFile;
inherit user;
autoStart = true;
dependsOn = [
"solidtimeDb"
];
networks = [
internalNetworkName
];
extraOptions = [
# Healthchecks
# test: [ "CMD", "healthcheck" ]
''--health-cmd="healthcheck"''
];
inherit volumes;
environmentFiles = [
"/home/admin/.solidtime.env"
];
environment = laravelEnv // {
CONTAINER_MODE = "scheduler";
};
};
solidtimeQueue = {
hostname = "queue";
image = solidtimeImage;
imageFile = solidtimeImageFile;
inherit user;
autoStart = true;
networks = [
internalNetworkName
];
extraOptions = [
# Healthchecks
# test: [ "CMD", "healthcheck" ]
''--health-cmd="healthcheck"''
];
inherit volumes;
dependsOn = [
"solidtimeDb"
];
environmentFiles = [
"/home/admin/.solidtime.env"
];
environment = laravelEnv // {
CONTAINER_MODE = "worker";
WORKER_COMMAND = "php /var/www/html/artisan queue:work";
};
};
solidtimeDb = let
imageName = "postgres";
finalImageTag = "15";
in {
hostname = "database";
image = "${imageName}:${finalImageTag}";
imageFile = pkgs.dockerTools.pullImage {
inherit imageName finalImageTag;
imageDigest = "sha256:98fe06b500b5eb29e45bf8c073eb0ca399790ce17b1d586448edc4203627d342";
sha256 = "sha256-AZ4VkOlROX+nR/MjDjsA4xdHzmtKjiBAtsp2Q6IdOvg=";
};
autoStart = true;
ports = [
# "${toString dbExternalPort}:${toString dbInternalPort}"
];
networks = [
internalNetworkName
];
extraOptions = [
# Healthchecks
# test: - CMD - pg_isready - '-q' - '-d' - '${DB_DATABASE}' - '-U' - '${DB_USERNAME}' retries: 3 timeout: 5s
''--health-cmd="pg_isready -q -d ${laravelEnv.DB_DATABASE} -U ${laravelEnv.DB_USERNAME}"''
"--health-retries=3"
"--health-timeout=5s"
];
volumes = [
"solidtime-db:/var/lib/postgresql/data"
];
environment = {
PGPASSWORD = laravelEnv.DB_PASSWORD;
POSTGRES_DB = laravelEnv.DB_DATABASE;
POSTGRES_USER = laravelEnv.DB_USERNAME;
POSTGRES_PASSWORD = laravelEnv.DB_PASSWORD;
};
};
solidtimeGotenberg = let
imageName = "gotenberg/gotenberg";
finalImageTag = "8.26.0";
in {
hostname = "gotenberg";
image = "${imageName}:${finalImageTag}";
imageFile = pkgs.dockerTools.pullImage {
inherit imageName finalImageTag;
imageDigest = "sha256:328551506b3dec3ff6381dd47e5cd72a44def97506908269e201a8fbfa1c12c0";
sha256 = "sha256-1zz4xDAgXxHUnkCVIfjHTgXb82EFEx+5am6Cu9+eZj4=";
};
autoStart = true;
networks = [
internalNetworkName
];
extraOptions = [
# Healthchecks
# test: [ "CMD", "curl", "--silent", "--fail", "http://localhost:3000/health" ]
''--health-cmd="curl --silent --fail http://localhost:${toString gotenbergPort}/health"''
];
};
};
};
}

View file

@ -0,0 +1,27 @@
{ config, lib, ... }:
let
cfg = config.homelab.apps.speedtest;
in {
options.homelab.apps.speedtest.enable = lib.mkEnableOption "Speedtest";
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
virtualisation.oci-containers.containers.speedtest = {
hostname = "speedtest";
image = "openspeedtest/latest:v2.0.5";
ports = [
"3000:3000"
"3001:3001"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.speedtest.rule" = "Host(`speedtest.${config.networking.hostName}.${config.networking.domain}`)";
"traefik.http.services.speedtest.loadbalancer.server.port" = "9090";
"traefik.tls.options.default.minVersion" = "VersionTLS13";
};
autoStart = true;
};
};
}

View file

@ -0,0 +1,73 @@
{ config, lib, ... }:
let
cfg = config.homelab.apps.technitiumDNS;
in {
options.homelab.apps.technitiumDNS.enable = lib.mkEnableOption "Technitium DNS";
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
virtualisation.oci-containers.containers.technitium-dns = {
hostname = "technitium-dns";
image = "technitium/dns-server:12.1";
ports = [
# "5380:5380/tcp" #DNS web console (HTTP)
# "53443:53443/tcp" #DNS web console (HTTPS)
"53:53/udp" #DNS service
"53:53/tcp" #DNS service
# "853:853/udp" #DNS-over-QUIC service
# "853:853/tcp" #DNS-over-TLS service
# "443:443/udp" #DNS-over-HTTPS service (HTTP/3)
# "443:443/tcp" #DNS-over-HTTPS service (HTTP/1.1, HTTP/2)
# "80:80/tcp" #DNS-over-HTTP service (use with reverse proxy or certbot certificate renewal)
# "8053:8053/tcp" #DNS-over-HTTP service (use with reverse proxy)
# "67:67/udp" #DHCP service
];
environment = {
# The primary domain name used by this DNS Server to identify itself.
DNS_SERVER_DOMAIN = config.networking.hostName;
# DNS Server will use IPv6 for querying whenever possible with this option enabled.
DNS_SERVER_PREFER_IPV6 = "true";
# The TCP port number for the DNS web console over HTTP protocol.
# DNS_SERVER_WEB_SERVICE_HTTP_PORT=5380
# The TCP port number for the DNS web console over HTTPS protocol.
# DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443
# Enables HTTPS for the DNS web console.
# DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=false
# Enables self signed TLS certificate for the DNS web console.
# DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=false
# Enables DNS server optional protocol DNS-over-HTTP on TCP port 8053 to be used with a TLS terminating reverse proxy like nginx.
# DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP=false
# Recursion options: Allow, Deny, AllowOnlyForPrivateNetworks, UseSpecifiedNetworks.
#nDNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks
# Comma separated list of IP addresses or network addresses to deny recursion. Valid only for `UseSpecifiedNetworks` recursion option.
# DNS_SERVER_RECURSION_DENIED_NETWORKS=1.1.1.0/24
# Comma separated list of IP addresses or network addresses to allow recursion. Valid only for `UseSpecifiedNetworks` recursion option.
# DNS_SERVER_RECURSION_ALLOWED_NETWORKS=127.0.0.1, 192.168.1.0/24
# Sets the DNS server to block domain names using Blocked Zone and Block List Zone.
DNS_SERVER_ENABLE_BLOCKING = "false";
# Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests.
# DNS_SERVER_ALLOW_TXT_BLOCKING_REPORT=false
# A comma separated list of block list URLs.
# DNS_SERVER_BLOCK_LIST_URLS=
#Comma separated list of forwarder addresses.
DNS_SERVER_FORWARDERS="195.130.130.2,195.130.131.2";
# Forwarder protocol options: Udp, Tcp, Tls, Https, HttpsJson.
# DNS_SERVER_FORWARDER_PROTOCOL=Tcp
# Enable this option to use local time instead of UTC for logging.
# DNS_SERVER_LOG_USING_LOCAL_TIME=true
};
volumes = [
"technitium_dns:/etc/dns"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.technitium-dns.rule" = "Host(`dns.${config.networking.hostName}.${config.networking.domain}`)";
"traefik.http.services.technitium-dns.loadbalancer.server.port" = "5380";
"traefik.tls.options.default.minVersion" = "VersionTLS13";
};
autoStart = true;
};
};
}

View file

@ -0,0 +1,94 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.traefik;
port = 8080;
in {
options.homelab.apps.traefik = {
enable = lib.mkEnableOption "Traefik Reverse Proxy";
sharedNetworkName = lib.mkOption {
type = lib.types.str;
default = "traefik";
description = "The name of the shared network to connect the container to.";
};
dynamicConfigOptions = lib.mkOption {
type = lib.types.attrs;
default = {};
description = "Dynamic configuration options to write to file and mount into Traefik.";
};
};
config = lib.mkIf cfg.enable {
homelab.virtualisation.containers.enable = true;
# Make sure the Docker network exists.
homelab.dockerNetworks."${cfg.sharedNetworkName}" = {
requiredBy = [
"docker-traefik.service"
];
};
virtualisation.oci-containers.containers.traefik = {
hostname = "traefik";
image = "traefik:v3.4.3";
autoStart = true;
ports = [
"80:80/tcp"
"443:443/tcp"
"${toString port}:${toString port}/tcp" # Web UI (enabled by --api.insecure=true)
];
extraOptions = [
"--network=${cfg.sharedNetworkName}"
"--add-host=host.docker.internal:host-gateway"
];
environmentFiles = [
/home/admin/.cloudflare.secret
];
cmd = [
"--api.insecure=true"
# Add Docker provider
"--providers.docker=true"
"--providers.docker.exposedByDefault=false"
# Add File provider
"--providers.file.filename=/etc/traefik/dynamic_conf.yml"
"--providers.file.watch=true"
# Add web entrypoint
"--entrypoints.web.address=:80/tcp"
"--entrypoints.web.http.redirections.entrypoint.to=websecure"
"--entrypoints.web.http.redirections.entrypoint.scheme=https"
# Add websecure entrypoint
"--entrypoints.websecure.address=:443/tcp"
"--entrypoints.websecure.http.tls=true"
"--entrypoints.websecure.http.tls.certResolver=letsencrypt"
"--entrypoints.websecure.http.tls.domains[0].main=depeuter.dev"
"--entrypoints.websecure.http.tls.domains[0].sans=*.depeuter.dev"
"--entrypoints.websecure.http.tls.domains[1].sans=*.${config.networking.hostName}.depeuter.dev"
# Certificates
"--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
"--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
"--certificatesresolvers.letsencrypt.acme.email=tibo.depeuter@telenet.be"
"--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
];
volumes = let
dynamicConfFormat = pkgs.formats.yaml { };
dynamicConfFile = dynamicConfFormat.generate "traefik-dynamic-conf.yml" cfg.dynamicConfigOptions;
in [
"letsencryp:/letsencrypt"
"/var/run/docker.sock:/var/run/docker.sock:ro"
"${dynamicConfFile}:/etc/traefik/dynamic_conf.yml:ro"
];
labels = {
"traefik.enable" = "true";
"traefik.http.routers.traefik.rule" = "Host(`traefik.${config.networking.hostName}.depeuter.dev`)";
"traefik.http.services.traefik.loadbalancer.server.port" = toString port;
};
};
};
}

View file

@ -0,0 +1,646 @@
{ config, lib, pkgs, ... }:
let
cfg = config.homelab.apps.vaultwarden;
networkName = "vaultwarden";
in {
options.homelab.apps.vaultwarden = {
enable = lib.mkEnableOption "Vaultwarden";
port = lib.mkOption {
type = lib.types.int;
default = 10102;
description = "Vaultwarden WebUI port";
};
domain = lib.mkOption {
type = lib.types.string;
example = "https://vault.depeuter.dev";
description = "Domain to configure Vaultwarden on";
};
name = lib.mkOption {
type = lib.types.string;
example = "Hugo's Vault";
description = "Service name to use for invitations and mail";
};
};
config = lib.mkIf cfg.enable {
homelab = {
# Allow remote backups.
users.backup.enable = true;
virtualisation.containers.enable = true;
};
# Make sure the Docker network exists.
homelab.dockerNetworks."${networkName}" = {
requiredBy = [
"docker-vaultwarden-db.service"
"docker-vaultwarden.service"
];
};
virtualisation.oci-containers.containers = let
dbHostname = "vaultwarden-db";
dbPort = 5432;
in {
vaultwardenDb = {
hostname = dbHostname;
image = "postgres:15.8-alpine";
autoStart = true;
ports = [
"${toString dbPort}:5432/tcp"
];
extraOptions = [
"--network=${networkName}"
];
environment = {
POSTGRES_PASSWORD = "ChangeMe";
PGDATA = "/var/lib/postgresql/data/pgdata";
};
volumes = [
"vaultwarden-db:/var/lib/postgresql/data"
];
};
vaultwarden = let
dataDir = "/data";
in {
hostname = "vaultwarden";
image = "vaultwarden/server:1.34.3-alpine";
autoStart = true;
ports = [
"${toString cfg.port}:80/tcp"
];
extraOptions = [
"--network=${networkName}"
];
dependsOn = [
"vaultwardenDb"
];
volumes = [
"vaultwarden:${dataDir}"
];
environmentFiles = [
# NOTE Don't forget to create this file
# TODO Put in place using age(nix)?
"/var/lib/vaultwarden.env"
];
environment = {
####################
### Data folders ###
####################
## Main data folder
DATA_FOLDER = dataDir;
## Individual folders, these override %DATA_FOLDER%
# ICON_CACHE_FOLDER=data/icon_cache
# ATTACHMENTS_FOLDER=data/attachments
# SENDS_FOLDER=data/sends
# TMP_FOLDER=data/tmp
## Templates data folder, by default uses embedded templates
## Check source code to see the format
# TEMPLATES_FOLDER=data/templates
## Automatically reload the templates for every request, slow, use only for development
# RELOAD_TEMPLATES=false
## Web vault settings
# WEB_VAULT_FOLDER=web-vault/
# WEB_VAULT_ENABLED=true
#########################
### Database settings ###
#########################
## Database URL
## When using SQLite, this is the path to the DB file, default to %DATA_FOLDER%/db.sqlite3
# DATABASE_URL=data/db.sqlite3
## When using MySQL, specify an appropriate connection URI.
## Details: https://docs.diesel.rs/2.1.x/diesel/mysql/struct.MysqlConnection.html
# DATABASE_URL=mysql://user:password@host[:port]/database_name
## When using PostgreSQL, specify an appropriate connection URI (recommended)
## or keyword/value connection string.
## Details:
## - https://docs.diesel.rs/2.1.x/diesel/pg/struct.PgConnection.html
## - https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
DATABASE_URL = "postgresql://vaultwarden:ChangeMe@${dbHostname}:${toString dbPort}/vaultwarden";
## Enable WAL for the DB
## Set to false to avoid enabling WAL during startup.
## Note that if the DB already has WAL enabled, you will also need to disable WAL in the DB,
## this setting only prevents Vaultwarden from automatically enabling it on start.
## Please read project wiki page about this setting first before changing the value as it can
## cause performance degradation or might render the service unable to start.
# ENABLE_DB_WAL=true
## Database connection retries
## Number of times to retry the database connection during startup, with 1 second delay between each retry, set to 0 to retry indefinitely
# DB_CONNECTION_RETRIES=15
## Database timeout
## Timeout when acquiring database connection
# DATABASE_TIMEOUT=30
## Database max connections
## Define the size of the connection pool used for connecting to the database.
# DATABASE_MAX_CONNS=10
## Database connection initialization
## Allows SQL statements to be run whenever a new database connection is created.
## This is mainly useful for connection-scoped pragmas.
## If empty, a database-specific default is used:
## - SQLite: "PRAGMA busy_timeout = 5000; PRAGMA synchronous = NORMAL;"
## - MySQL: ""
## - PostgreSQL: ""
# DATABASE_CONN_INIT=""
#################
### WebSocket ###
#################
## Enable websocket notifications
# ENABLE_WEBSOCKET=true
##########################
### Push notifications ###
##########################
## Enables push notifications (requires key and id from https://bitwarden.com/host)
## Details about mobile client push notification:
## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-Mobile-Client-push-notification
# PUSH_ENABLED=false
# PUSH_INSTALLATION_ID=CHANGEME
# PUSH_INSTALLATION_KEY=CHANGEME
# WARNING: Do not modify the following settings unless you fully understand their implications!
# Default Push Relay and Identity URIs
# PUSH_RELAY_URI=https://push.bitwarden.com
# PUSH_IDENTITY_URI=https://identity.bitwarden.com
# European Union Data Region Settings
# If you have selected "European Union" as your data region, use the following URIs instead.
# PUSH_RELAY_URI=https://api.bitwarden.eu
# PUSH_IDENTITY_URI=https://identity.bitwarden.eu
#####################
### Schedule jobs ###
#####################
## Job scheduler settings
##
## Job schedules use a cron-like syntax (as parsed by https://crates.io/crates/cron),
## and are always in terms of UTC time (regardless of your local time zone settings).
##
## The schedule format is a bit different from crontab as crontab does not contains seconds.
## You can test the the format here: https://crontab.guru, but remove the first digit!
## SEC MIN HOUR DAY OF MONTH MONTH DAY OF WEEK
## "0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri"
## "0 30 * * * * "
## "0 30 1 * * * "
##
## How often (in ms) the job scheduler thread checks for jobs that need running.
## Set to 0 to globally disable scheduled jobs.
# JOB_POLL_INTERVAL_MS=30000
##
## Cron schedule of the job that checks for Sends past their deletion date.
## Defaults to hourly (5 minutes after the hour). Set blank to disable this job.
# SEND_PURGE_SCHEDULE="0 5 * * * *"
##
## Cron schedule of the job that checks for trashed items to delete permanently.
## Defaults to daily (5 minutes after midnight). Set blank to disable this job.
# TRASH_PURGE_SCHEDULE="0 5 0 * * *"
##
## Cron schedule of the job that checks for incomplete 2FA logins.
## Defaults to once every minute. Set blank to disable this job.
# INCOMPLETE_2FA_SCHEDULE="30 * * * * *"
##
## Cron schedule of the job that sends expiration reminders to emergency access grantors.
## Defaults to hourly (3 minutes after the hour). Set blank to disable this job.
# EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE="0 3 * * * *"
##
## Cron schedule of the job that grants emergency access requests that have met the required wait time.
## Defaults to hourly (7 minutes after the hour). Set blank to disable this job.
# EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 7 * * * *"
##
## Cron schedule of the job that cleans old events from the event table.
## Defaults to daily. Set blank to disable this job. Also without EVENTS_DAYS_RETAIN set, this job will not start.
# EVENT_CLEANUP_SCHEDULE="0 10 0 * * *"
## Number of days to retain events stored in the database.
## If unset (the default), events are kept indefinitely and the scheduled job is disabled!
# EVENTS_DAYS_RETAIN=
##
## Cron schedule of the job that cleans old auth requests from the auth request.
## Defaults to every minute. Set blank to disable this job.
# AUTH_REQUEST_PURGE_SCHEDULE="30 * * * * *"
##
## Cron schedule of the job that cleans expired Duo contexts from the database. Does nothing if Duo MFA is disabled or set to use the legacy iframe prompt.
## Defaults to every minute. Set blank to disable this job.
# DUO_CONTEXT_PURGE_SCHEDULE="30 * * * * *"
########################
### General settings ###
########################
## Domain settings
## The domain must match the address from where you access the server
## It's recommended to configure this value, otherwise certain functionality might not work,
## like attachment downloads, email links and U2F.
## For U2F to work, the server must use HTTPS, you can use Let's Encrypt for free certs
## To use HTTPS, the recommended way is to put Vaultwarden behind a reverse proxy
## Details:
## - https://github.com/dani-garcia/vaultwarden/wiki/Enabling-HTTPS
## - https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples
## For development
# DOMAIN=http://localhost
## For public server
DOMAIN = cfg.domain;
## For public server (URL with port number)
# DOMAIN=https://vw.domain.tld:8443
## For public server (URL with path)
# DOMAIN=https://domain.tld/vw
## Controls whether users are allowed to create Bitwarden Sends.
## This setting applies globally to all users.
## To control this on a per-org basis instead, use the "Disable Send" org policy.
# SENDS_ALLOWED=true
## HIBP Api Key
## HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
# HIBP_API_KEY=
## Per-organization attachment storage limit (KB)
## Max kilobytes of attachment storage allowed per organization.
## When this limit is reached, organization members will not be allowed to upload further attachments for ciphers owned by that organization.
# ORG_ATTACHMENT_LIMIT=
## Per-user attachment storage limit (KB)
## Max kilobytes of attachment storage allowed per user.
## When this limit is reached, the user will not be allowed to upload further attachments.
# USER_ATTACHMENT_LIMIT=
## Per-user send storage limit (KB)
## Max kilobytes of send storage allowed per user.
## When this limit is reached, the user will not be allowed to upload further sends.
# USER_SEND_LIMIT=
## Number of days to wait before auto-deleting a trashed item.
## If unset (the default), trashed items are not auto-deleted.
## This setting applies globally, so make sure to inform all users of any changes to this setting.
# TRASH_AUTO_DELETE_DAYS=
## Number of minutes to wait before a 2FA-enabled login is considered incomplete,
## resulting in an email notification. An incomplete 2FA login is one where the correct
## master password was provided but the required 2FA step was not completed, which
## potentially indicates a master password compromise. Set to 0 to disable this check.
## This setting applies globally to all users.
# INCOMPLETE_2FA_TIME_LIMIT=3
## Disable icon downloading
## Set to true to disable icon downloading in the internal icon service.
## This still serves existing icons from $ICON_CACHE_FOLDER, without generating any external
## network requests. $ICON_CACHE_TTL must also be set to 0; otherwise, the existing icons
## will be deleted eventually, but won't be downloaded again.
# DISABLE_ICON_DOWNLOAD=false
## Controls if new users can register
SIGNUPS_ALLOWED = "false";
## Controls if new users need to verify their email address upon registration
## Note that setting this option to true prevents logins until the email address has been verified!
## The welcome email will include a verification link, and login attempts will periodically
## trigger another verification email to be sent.
SIGNUPS_VERIFY = "false";
## If SIGNUPS_VERIFY is set to true, this limits how many seconds after the last time
## an email verification link has been sent another verification email will be sent
# SIGNUPS_VERIFY_RESEND_TIME=3600
## If SIGNUPS_VERIFY is set to true, this limits how many times an email verification
## email will be re-sent upon an attempted login.
# SIGNUPS_VERIFY_RESEND_LIMIT=6
## Controls if new users from a list of comma-separated domains can register
## even if SIGNUPS_ALLOWED is set to false
# SIGNUPS_DOMAINS_WHITELIST=example.com,example.net,example.org
## Controls whether event logging is enabled for organizations
## This setting applies to organizations.
## Disabled by default. Also check the EVENT_CLEANUP_SCHEDULE and EVENTS_DAYS_RETAIN settings.
# ORG_EVENTS_ENABLED=false
## Controls which users can create new orgs.
## Blank or 'all' means all users can create orgs (this is the default):
# ORG_CREATION_USERS=
## 'none' means no users can create orgs:
# ORG_CREATION_USERS=none
## A comma-separated list means only those users can create orgs:
# ORG_CREATION_USERS=admin1@example.com,admin2@example.com
## Invitations org admins to invite users, even when signups are disabled
# INVITATIONS_ALLOWED=true
## Name shown in the invitation emails that don't come from a specific organization
INVITATION_ORG_NAME = cfg.name;
## The number of hours after which an organization invite token, emergency access invite token,
## email verification token and deletion request token will expire (must be at least 1)
# INVITATION_EXPIRATION_HOURS=120
## Controls whether users can enable emergency access to their accounts.
## This setting applies globally to all users.
# EMERGENCY_ACCESS_ALLOWED=true
## Controls whether users can change their email.
## This setting applies globally to all users
# EMAIL_CHANGE_ALLOWED=true
## Number of server-side passwords hashing iterations for the password hash.
## The default for new users. If changed, it will be updated during login for existing users.
# PASSWORD_ITERATIONS=600000
## Controls whether users can set password hints. This setting applies globally to all users.
# PASSWORD_HINTS_ALLOWED=true
## Controls whether a password hint should be shown directly in the web page if
## SMTP service is not configured. Not recommended for publicly-accessible instances
## as this provides unauthenticated access to potentially sensitive data.
SHOW_PASSWORD_HINT = "false";
#########################
### Advanced settings ###
#########################
## Client IP Header, used to identify the IP of the client, defaults to "X-Real-IP"
## Set to the string "none" (without quotes), to disable any headers and just use the remote IP
# IP_HEADER=X-Real-IP
## Icon service
## The predefined icon services are: internal, bitwarden, duckduckgo, google.
## To specify a custom icon service, set a URL template with exactly one instance of `{}`,
## which is replaced with the domain. For example: `https://icon.example.com/domain/{}`.
##
## `internal` refers to Vaultwarden's built-in icon fetching implementation.
## If an external service is set, an icon request to Vaultwarden will return an HTTP
## redirect to the corresponding icon at the external service. An external service may
## be useful if your Vaultwarden instance has no external network connectivity, or if
## you are concerned that someone may probe your instance to try to detect whether icons
## for certain sites have been cached.
# ICON_SERVICE=internal
## Icon redirect code
## The HTTP status code to use for redirects to an external icon service.
## The supported codes are 301 (legacy permanent), 302 (legacy temporary), 307 (temporary), and 308 (permanent).
## Temporary redirects are useful while testing different icon services, but once a service
## has been decided on, consider using permanent redirects for cacheability. The legacy codes
## are currently better supported by the Bitwarden clients.
# ICON_REDIRECT_CODE=302
## Cache time-to-live for successfully obtained icons, in seconds (0 is "forever")
## Default: 2592000 (30 days)
# ICON_CACHE_TTL=2592000
## Cache time-to-live for icons which weren't available, in seconds (0 is "forever")
## Default: 2592000 (3 days)
# ICON_CACHE_NEGTTL=259200
## Icon download timeout
## Configure the timeout value when downloading the favicons.
## The default is 10 seconds, but this could be to low on slower network connections
# ICON_DOWNLOAD_TIMEOUT=10
## Block HTTP domains/IPs by Regex
## Any domains or IPs that match this regex won't be fetched by the internal HTTP client.
## Useful to hide other servers in the local network. Check the WIKI for more details
## NOTE: Always enclose this regex withing single quotes!
# HTTP_REQUEST_BLOCK_REGEX='^(192\.168\.0\.[0-9]+|192\.168\.1\.[0-9]+)$'
## Enabling this will cause the internal HTTP client to refuse to connect to any non global IP address.
## Useful to secure your internal environment: See https://en.wikipedia.org/wiki/Reserved_IP_addresses for a list of IPs which it will block
# HTTP_REQUEST_BLOCK_NON_GLOBAL_IPS=true
## Client Settings
## Enable experimental feature flags for clients.
## This is a comma-separated list of flags, e.g. "flag1,flag2,flag3".
##
## The following flags are available:
## - "autofill-overlay": Add an overlay menu to form fields for quick access to credentials.
## - "autofill-v2": Use the new autofill implementation.
## - "browser-fileless-import": Directly import credentials from other providers without a file.
## - "fido2-vault-credentials": Enable the use of FIDO2 security keys as second factor.
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials
## Require new device emails. When a user logs in an email is required to be sent.
## If sending the email fails the login attempt will fail!!
# REQUIRE_DEVICE_EMAIL=false
## Enable extended logging, which shows timestamps and targets in the logs
# EXTENDED_LOGGING=true
## Timestamp format used in extended logging.
## Format specifiers: https://docs.rs/chrono/latest/chrono/format/strftime
# LOG_TIMESTAMP_FORMAT="%Y-%m-%d %H:%M:%S.%3f"
## Logging to Syslog
## This requires extended logging
# USE_SYSLOG=false
## Logging to file
# LOG_FILE=/path/to/log
## Log level
## Change the verbosity of the log output
## Valid values are "trace", "debug", "info", "warn", "error" and "off"
## Setting it to "trace" or "debug" would also show logs for mounted routes and static file, websocket and alive requests
## For a specific module append a comma separated `path::to::module=log_level`
## For example, to only see debug logs for icons use: LOG_LEVEL="info,vaultwarden::api::icons=debug"
LOG_LEVEL = "warn";
## Token for the admin interface, preferably an Argon2 PCH string
## Vaultwarden has a built-in generator by calling `vaultwarden hash`
## For details see: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token
## If not set, the admin panel is disabled
## New Argon2 PHC string
## Note that for some environments, like docker-compose you need to escape all the dollar signs `$` with an extra dollar sign like `$$`
## Also, use single quotes (') instead of double quotes (") to enclose the string when needed
# ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'
## Old plain text string (Will generate warnings in favor of Argon2)
# ADMIN_TOKEN=Vy2VyYTTsKPv8W5aEOWUbB/Bt3DEKePbHmI4m9VcemUMS2rEviDowNAFqYi1xjmp
## Enable this to bypass the admin panel security. This option is only
## meant to be used with the use of a separate auth layer in front
# DISABLE_ADMIN_TOKEN=false
## Number of seconds, on average, between admin login requests from the same IP address before rate limiting kicks in.
# ADMIN_RATELIMIT_SECONDS=300
## Allow a burst of requests of up to this size, while maintaining the average indicated by `ADMIN_RATELIMIT_SECONDS`.
# ADMIN_RATELIMIT_MAX_BURST=3
## Set the lifetime of admin sessions to this value (in minutes).
# ADMIN_SESSION_LIFETIME=20
## Allowed iframe ancestors (Know the risks!)
## https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
## Allows other domains to embed the web vault into an iframe, useful for embedding into secure intranets
## This adds the configured value to the 'Content-Security-Policy' headers 'frame-ancestors' value.
## Multiple values must be separated with a whitespace.
# ALLOWED_IFRAME_ANCESTORS=
## Number of seconds, on average, between login requests from the same IP address before rate limiting kicks in.
# LOGIN_RATELIMIT_SECONDS=60
## Allow a burst of requests of up to this size, while maintaining the average indicated by `LOGIN_RATELIMIT_SECONDS`.
## Note that this applies to both the login and the 2FA, so it's recommended to allow a burst size of at least 2.
# LOGIN_RATELIMIT_MAX_BURST=10
## BETA FEATURE: Groups
## Controls whether group support is enabled for organizations
## This setting applies to organizations.
## Disabled by default because this is a beta feature, it contains known issues!
## KNOW WHAT YOU ARE DOING!
# ORG_GROUPS_ENABLED=false
## Increase secure note size limit (Know the risks!)
## Sets the secure note size limit to 100_000 instead of the default 10_000.
## WARNING: This could cause issues with clients. Also exports will not work on Bitwarden servers!
## KNOW WHAT YOU ARE DOING!
# INCREASE_NOTE_SIZE_LIMIT=false
## Enforce Single Org with Reset Password Policy
## Enforce that the Single Org policy is enabled before setting the Reset Password policy
## Bitwarden enforces this by default. In Vaultwarden we encouraged to use multiple organizations because groups were not available.
## Setting this to true will enforce the Single Org Policy to be enabled before you can enable the Reset Password policy.
# ENFORCE_SINGLE_ORG_WITH_RESET_PW_POLICY=false
########################
### MFA/2FA settings ###
########################
## Yubico (Yubikey) Settings
## Set your Client ID and Secret Key for Yubikey OTP
## You can generate it here: https://upgrade.yubico.com/getapikey/
## You can optionally specify a custom OTP server
# YUBICO_CLIENT_ID=11111
# YUBICO_SECRET_KEY=AAAAAAAAAAAAAAAAAAAAAAAA
# YUBICO_SERVER=http://yourdomain.com/wsapi/2.0/verify
## Duo Settings
## You need to configure the DUO_IKEY, DUO_SKEY, and DUO_HOST options to enable global Duo support.
## Otherwise users will need to configure it themselves.
## Create an account and protect an application as mentioned in this link (only the first step, not the rest):
## https://help.bitwarden.com/article/setup-two-step-login-duo/#create-a-duo-security-account
## Then set the following options, based on the values obtained from the last step:
# DUO_IKEY=<Client ID>
# DUO_SKEY=<Client Secret>
# DUO_HOST=<API Hostname>
## After that, you should be able to follow the rest of the guide linked above,
## ignoring the fields that ask for the values that you already configured beforehand.
##
## If you want to attempt to use Duo's 'Traditional Prompt' (deprecated, iframe based) set DUO_USE_IFRAME to 'true'.
## Duo no longer supports this, but it still works for some integrations.
## If you aren't sure, leave this alone.
# DUO_USE_IFRAME=false
## Email 2FA settings
## Email token size
## Number of digits in an email 2FA token (min: 6, max: 255).
## Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting!
# EMAIL_TOKEN_SIZE=6
##
## Token expiration time
## Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
# EMAIL_EXPIRATION_TIME=600
##
## Maximum attempts before an email token is reset and a new email will need to be sent.
# EMAIL_ATTEMPTS_LIMIT=3
##
## Setup email 2FA regardless of any organization policy
# EMAIL_2FA_ENFORCE_ON_VERIFIED_INVITE=false
## Automatically setup email 2FA as fallback provider when needed
# EMAIL_2FA_AUTO_FALLBACK=false
## Other MFA/2FA settings
## Disable 2FA remember
## Enabling this would force the users to use a second factor to login every time.
## Note that the checkbox would still be present, but ignored.
# DISABLE_2FA_REMEMBER=false
##
## Authenticator Settings
## Disable authenticator time drifted codes to be valid.
## TOTP codes of the previous and next 30 seconds will be invalid
##
## According to the RFC6238 (https://tools.ietf.org/html/rfc6238),
## we allow by default the TOTP code which was valid one step back and one in the future.
## This can however allow attackers to be a bit more lucky with there attempts because there are 3 valid codes.
## You can disable this, so that only the current TOTP Code is allowed.
## Keep in mind that when a sever drifts out of time, valid codes could be marked as invalid.
## In any case, if a code has been used it can not be used again, also codes which predates it will be invalid.
# AUTHENTICATOR_DISABLE_TIME_DRIFT=false
###########################
### SMTP Email settings ###
###########################
## Mail specific settings, set SMTP_FROM and either SMTP_HOST or USE_SENDMAIL to enable the mail service.
## To make sure the email links are pointing to the correct host, set the DOMAIN variable.
## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory
SMTP_HOST = "smtp.gmail.com";
SMTP_FROM = "vault@depeuter.dev";
SMTP_FROM_NAME = cfg.name;
# SMTP_USERNAME=username
# SMTP_PASSWORD=password
# SMTP_TIMEOUT=15
## Choose the type of secure connection for SMTP. The default is "starttls".
## The available options are:
## - "starttls": The default port is 587.
## - "force_tls": The default port is 465.
## - "off": The default port is 25.
## Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS).
SMTP_SECURITY = "starttls";
SMTP_PORT = "587";
# Whether to send mail via the `sendmail` command
# USE_SENDMAIL=false
# Which sendmail command to use. The one found in the $PATH is used if not specified.
# SENDMAIL_COMMAND="/path/to/sendmail"
## Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections.
## Possible values: ["Plain", "Login", "Xoauth2"].
## Multiple options need to be separated by a comma ','.
SMTP_AUTH_MECHANISM = "Login";
## Server name sent during the SMTP HELO
## By default this value should be is on the machine's hostname,
## but might need to be changed in case it trips some anti-spam filters
# HELO_NAME=
## Embed images as email attachments
# SMTP_EMBED_IMAGES=true
## SMTP debugging
## When set to true this will output very detailed SMTP messages.
## WARNING: This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting!
# SMTP_DEBUG=false
## Accept Invalid Certificates
## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks!
## Only use this as a last resort if you are not able to use a valid certificate.
## If the Certificate is valid but the hostname doesn't match, please use SMTP_ACCEPT_INVALID_HOSTNAMES instead.
# SMTP_ACCEPT_INVALID_CERTS=false
## Accept Invalid Hostnames
## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks!
## Only use this as a last resort if you are not able to use a valid certificate.
# SMTP_ACCEPT_INVALID_HOSTNAMES=false
#######################
### Rocket settings ###
#######################
## Rocket specific settings
## See https://rocket.rs/v0.5/guide/configuration/ for more details.
# ROCKET_ADDRESS=0.0.0.0
## The default port is 8000, unless running in a Docker container, in which case it is 80.
# ROCKET_PORT=8000
# ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"}
};
};
};
};
}