mirror of
https://github.com/Stunkymonkey/nixos.git
synced 2025-05-24 09:54:40 +02:00
service/webserver: migrate nginx to caddy
This commit is contained in:
parent
13470f55e4
commit
589714db4b
34 changed files with 347 additions and 688 deletions
|
@ -136,21 +136,8 @@ in
|
||||||
blackbox = {
|
blackbox = {
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
# Webserver
|
webserver = {
|
||||||
nginx = {
|
|
||||||
enable = true;
|
enable = true;
|
||||||
sso = {
|
|
||||||
authKeyFile = secrets."sso/auth-key".path;
|
|
||||||
users = {
|
|
||||||
felix = {
|
|
||||||
passwordHashFile = secrets."sso/felix/password-hash".path;
|
|
||||||
totpSecretFile = secrets."sso/felix/totp-secret".path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
groups = {
|
|
||||||
root = [ "felix" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
acme = {
|
acme = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
@ -86,21 +86,8 @@ in
|
||||||
homer = {
|
homer = {
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
# Webserver
|
webserver = {
|
||||||
nginx = {
|
|
||||||
enable = true;
|
enable = true;
|
||||||
sso = {
|
|
||||||
authKeyFile = secrets."sso/auth-key".path;
|
|
||||||
users = {
|
|
||||||
felix = {
|
|
||||||
passwordHashFile = secrets."sso/felix/password-hash".path;
|
|
||||||
totpSecretFile = secrets."sso/felix/totp-secret".path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
groups = {
|
|
||||||
root = [ "felix" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
acme = {
|
acme = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
|
@ -25,8 +25,8 @@ in
|
||||||
acceptTerms = true;
|
acceptTerms = true;
|
||||||
# Use DNS wildcard certificate
|
# Use DNS wildcard certificate
|
||||||
certs = {
|
certs = {
|
||||||
"${config.networking.domain}" = {
|
"${domain}" = {
|
||||||
extraDomainNames = [ "*.${config.networking.domain}" ];
|
extraDomainNames = [ "*.${domain}" ];
|
||||||
dnsProvider = "inwx";
|
dnsProvider = "inwx";
|
||||||
inherit (cfg) credentialsFile;
|
inherit (cfg) credentialsFile;
|
||||||
};
|
};
|
||||||
|
|
|
@ -132,7 +132,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "alerts";
|
subdomain = "alerts";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -28,7 +28,7 @@ in
|
||||||
inherit (cfg) downloadDir;
|
inherit (cfg) downloadDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "download";
|
subdomain = "download";
|
||||||
root = "${pkgs.ariang}/share/ariang";
|
root = "${pkgs.ariang}/share/ariang";
|
||||||
|
|
|
@ -43,7 +43,7 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "subtitles";
|
subdomain = "subtitles";
|
||||||
inherit port;
|
inherit port;
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
./mumble-server
|
./mumble-server
|
||||||
./navidrome
|
./navidrome
|
||||||
./nextcloud
|
./nextcloud
|
||||||
./nginx
|
|
||||||
./node-exporter
|
./node-exporter
|
||||||
./octoprint
|
./octoprint
|
||||||
./paperless
|
./paperless
|
||||||
|
@ -43,5 +42,6 @@
|
||||||
./ssh-server
|
./ssh-server
|
||||||
./tandoor-recipes
|
./tandoor-recipes
|
||||||
./vpn
|
./vpn
|
||||||
|
./webserver
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# finance overview
|
# finance overview
|
||||||
{ config, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.my.services.finance;
|
cfg = config.my.services.finance;
|
||||||
inherit (config.networking) domain;
|
inherit (config.networking) domain;
|
||||||
|
@ -20,18 +25,26 @@ in
|
||||||
services.firefly-iii = {
|
services.firefly-iii = {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHost = "finance";
|
virtualHost = "finance";
|
||||||
enableNginx = true;
|
user = "caddy";
|
||||||
|
group = "caddy";
|
||||||
settings = {
|
settings = {
|
||||||
APP_KEY_FILE = cfg.appKeyFile;
|
APP_KEY_FILE = cfg.appKeyFile;
|
||||||
SITE_OWNER = "server@buehler.rocks";
|
SITE_OWNER = "server@buehler.rocks";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts."finance" = {
|
my.services.webserver.virtualHosts = [
|
||||||
serverName = "finance.${domain}";
|
{
|
||||||
forceSSL = true;
|
subdomain = "finance";
|
||||||
useACMEHost = domain;
|
extraConfig = ''
|
||||||
};
|
file_server
|
||||||
|
root * "${config.services.firefly-iii.package}/public"
|
||||||
|
php_fastcgi unix/${config.services.phpfpm.pools."firefly-iii".socket} {
|
||||||
|
env modHeadersAvailable true
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
webapps.apps.finance = {
|
webapps.apps.finance = {
|
||||||
dashboard = {
|
dashboard = {
|
||||||
|
|
|
@ -45,17 +45,27 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
baseUrl = "https://news.${domain}";
|
baseUrl = "https://news.${domain}";
|
||||||
inherit (cfg) language passwordFile defaultUser;
|
inherit (cfg) language passwordFile defaultUser;
|
||||||
|
virtualHost = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Set up a Nginx virtual host.
|
services.phpfpm.pools.freshrss.settings = {
|
||||||
services.nginx = {
|
"listen.owner" = lib.mkForce config.services.caddy.user;
|
||||||
virtualHosts."freshrss" = {
|
"listen.group" = lib.mkForce config.services.caddy.group;
|
||||||
serverName = "news.${domain}";
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = domain;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
my.services.webserver.virtualHosts = [
|
||||||
|
{
|
||||||
|
subdomain = "news";
|
||||||
|
extraConfig = ''
|
||||||
|
root * ${config.services.freshrss.package}/p
|
||||||
|
php_fastcgi unix/${config.services.phpfpm.pools.freshrss.socket} {
|
||||||
|
env FRESHRSS_DATA_PATH ${config.services.freshrss.dataDir}
|
||||||
|
}
|
||||||
|
file_server
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
webapps.apps.freshrss = {
|
webapps.apps.freshrss = {
|
||||||
dashboard = {
|
dashboard = {
|
||||||
name = "News";
|
name = "News";
|
||||||
|
|
|
@ -66,7 +66,7 @@ in
|
||||||
|
|
||||||
# Proxy to Gitea
|
# Proxy to Gitea
|
||||||
my.services = {
|
my.services = {
|
||||||
nginx.virtualHosts = [
|
webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "code";
|
subdomain = "code";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -79,7 +79,7 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "visualization";
|
subdomain = "visualization";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -76,7 +76,7 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "notes";
|
subdomain = "notes";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -145,7 +145,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "automation";
|
subdomain = "automation";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -16,7 +16,7 @@ in
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "blog";
|
subdomain = "blog";
|
||||||
root = inputs.stunkymonkey.packages.${config.nixpkgs.system}.default;
|
root = inputs.stunkymonkey.packages.${config.nixpkgs.system}.default;
|
||||||
|
|
|
@ -26,24 +26,18 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
services.nginx.virtualHosts = {
|
# TODO: 25.05 use stable
|
||||||
# This is not a subdomain, cannot use my nginx wrapper module
|
services.caddy.virtualHosts.${domain} = {
|
||||||
${domain} = {
|
extraConfig = ''
|
||||||
forceSSL = true;
|
import common
|
||||||
useACMEHost = domain;
|
root * ${pkgs.unstable.homer}
|
||||||
# TODO: 25.05 use stable
|
file_server
|
||||||
root = pkgs.unstable.homer;
|
handle_path /assets/config.yml {
|
||||||
locations."=/assets/config.yml" = {
|
root * ${pkgs.writeText "homerConfig.yml" (builtins.toJSON homeConfig)}
|
||||||
alias = pkgs.writeText "homerConfig.yml" (builtins.toJSON homeConfig);
|
file_server
|
||||||
};
|
}
|
||||||
};
|
'';
|
||||||
# redirect any other attempt to the main site
|
useACMEHost = domain;
|
||||||
"${domain}-redirect" = {
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = domain;
|
|
||||||
default = true;
|
|
||||||
globalRedirect = "${domain}";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
webapps = {
|
webapps = {
|
||||||
|
|
|
@ -47,7 +47,7 @@ in
|
||||||
};
|
};
|
||||||
# sadly the metrics do not contain application specific metrics, only c# -> no dashboard
|
# sadly the metrics do not contain application specific metrics, only c# -> no dashboard
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "media";
|
subdomain = "media";
|
||||||
inherit port;
|
inherit port;
|
||||||
|
|
|
@ -14,7 +14,7 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "view";
|
subdomain = "view";
|
||||||
inherit (config.services.jellyseerr) port;
|
inherit (config.services.jellyseerr) port;
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.my.services.mumble-server;
|
cfg = config.my.services.mumble-server;
|
||||||
domain = "voice.${config.networking.domain}";
|
inherit (config.networking) domain;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.my.services.mumble-server = {
|
options.my.services.mumble-server = {
|
||||||
enable = lib.mkEnableOption "RSS-Bridge service";
|
enable = lib.mkEnableOption "mumble server service";
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
@ -19,29 +19,29 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
welcometext = "Welcome to the Mumble-Server!";
|
welcometext = "Welcome to the Mumble-Server!";
|
||||||
sslCert = "/var/lib/acme/${domain}/fullchain.pem";
|
sslCert = "${config.security.acme.certs.${domain}.directory}/fullchain.pem";
|
||||||
sslKey = "/var/lib/acme/${domain}/key.pem";
|
sslKey = "${config.security.acme.certs.${domain}.directory}/key.pem";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts.${domain}.enableACME = true;
|
# create a separate certificate for the mumble server
|
||||||
security.acme.certs."${domain}" = {
|
security.acme = {
|
||||||
group = "voice-buehler-rocks";
|
certs.${domain} = {
|
||||||
postRun = ''
|
reloadServices = [ "murmur" ];
|
||||||
if ${pkgs.systemd}/bin/systemctl is-active murmur.service; then
|
group = "caddyandmurmur";
|
||||||
${pkgs.systemd}/bin/systemctl kill -s SIGUSR1 murmur.service
|
};
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
users.groups.caddyandmurmur.members = [
|
||||||
users.groups."voice-buehler-rocks".members = [
|
"caddy"
|
||||||
"murmur"
|
"murmur"
|
||||||
"nginx"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
my.services.prometheus.rules = {
|
my.services = {
|
||||||
mumble_not_running = {
|
acme.enable = true;
|
||||||
condition = ''systemd_unit_state{name="murmur.service", state!="active"} > 0'';
|
prometheus.rules = {
|
||||||
description = "{{$labels.host}} should have a running {{$labels.name}}";
|
mumble_not_running = {
|
||||||
|
condition = ''systemd_unit_state{name="murmur.service", state!="active"} > 0'';
|
||||||
|
description = "{{$labels.host}} should have a running {{$labels.name}}";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -89,7 +89,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "music";
|
subdomain = "music";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
@ -101,7 +101,8 @@ in
|
||||||
name = "Music";
|
name = "Music";
|
||||||
category = "media";
|
category = "media";
|
||||||
icon = "music";
|
icon = "music";
|
||||||
url = "https://music.${domain}/app/#/login";
|
url = "https://music.${domain}";
|
||||||
|
method = "get";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -97,17 +97,6 @@ in
|
||||||
# ];
|
# ];
|
||||||
#};
|
#};
|
||||||
|
|
||||||
# The service above configures the domain, no need for my wrapper
|
|
||||||
nginx.virtualHosts."cloud.${domain}" = {
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = domain;
|
|
||||||
|
|
||||||
# so homer can get the online status
|
|
||||||
extraConfig = lib.optionalString config.my.services.homer.enable ''
|
|
||||||
add_header Access-Control-Allow-Origin https://${domain};
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
prometheus.exporters.nextcloud = {
|
prometheus.exporters.nextcloud = {
|
||||||
enable = true;
|
enable = true;
|
||||||
url = "https://cloud.${domain}";
|
url = "https://cloud.${domain}";
|
||||||
|
@ -144,6 +133,55 @@ in
|
||||||
# requires = [ "postgresql.service" ];
|
# requires = [ "postgresql.service" ];
|
||||||
# after = [ "postgresql.service" ];
|
# after = [ "postgresql.service" ];
|
||||||
#};
|
#};
|
||||||
|
services.phpfpm.pools.nextcloud.settings = {
|
||||||
|
"listen.owner" = config.services.caddy.user;
|
||||||
|
"listen.group" = config.services.caddy.group;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.nextcloud.members = [
|
||||||
|
"nextcloud"
|
||||||
|
config.services.caddy.user
|
||||||
|
];
|
||||||
|
|
||||||
|
my.services.webserver.virtualHosts = [
|
||||||
|
{
|
||||||
|
subdomain = "cloud";
|
||||||
|
extraConfig = ''
|
||||||
|
redir /.well-known/carddav /remote.php/dav/ 301
|
||||||
|
redir /.well-known/caldav /remote.php/dav/ 301
|
||||||
|
|
||||||
|
@forbidden {
|
||||||
|
path /.htaccess
|
||||||
|
path /data/*
|
||||||
|
path /config/*
|
||||||
|
path /db_structure
|
||||||
|
path /.xml
|
||||||
|
path /README
|
||||||
|
path /3rdparty/*
|
||||||
|
path /lib/*
|
||||||
|
path /templates/*
|
||||||
|
path /occ
|
||||||
|
path /console.php
|
||||||
|
}
|
||||||
|
respond @forbidden 403
|
||||||
|
|
||||||
|
header {
|
||||||
|
X-Frame-Options "sameorigin"
|
||||||
|
X-Permitted-Cross-Domain-Policies "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: `config.services.nextcloud.package` does not contain additional apps. in nixpkgs there is "nextcloud-with-apps".
|
||||||
|
# for now we use the path passed to nginx. Can be improved in 25.05 via: https://github.com/NixOS/nixpkgs/pull/376818
|
||||||
|
root * ${config.services.nginx.virtualHosts."cloud.${domain}".root}
|
||||||
|
file_server
|
||||||
|
php_fastcgi unix/${config.services.phpfpm.pools."nextcloud".socket} {
|
||||||
|
root ${config.services.nginx.virtualHosts."cloud.${domain}".root}
|
||||||
|
env front_controller_active true
|
||||||
|
env modHeadersAvailable true
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
my.services.backup = {
|
my.services.backup = {
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|
|
@ -1,452 +0,0 @@
|
||||||
# A simple abstraction layer for almost all of my services' needs
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.my.services.nginx;
|
|
||||||
virtualHostOption =
|
|
||||||
with lib;
|
|
||||||
types.submodule {
|
|
||||||
options = {
|
|
||||||
subdomain = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
example = "dev";
|
|
||||||
description = ''
|
|
||||||
Which subdomain, under config.networking.domain, to use
|
|
||||||
for this virtual host.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
port = mkOption {
|
|
||||||
type = with types; nullOr port;
|
|
||||||
default = null;
|
|
||||||
example = 8080;
|
|
||||||
description = ''
|
|
||||||
Which port to proxy to, through localhost, for this virtual host.
|
|
||||||
This option is incompatible with `root`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
root = mkOption {
|
|
||||||
type = with types; nullOr path;
|
|
||||||
default = null;
|
|
||||||
example = "/var/www/blog";
|
|
||||||
description = ''
|
|
||||||
The root folder for this virtual host. This option is incompatible
|
|
||||||
with `port`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
sso = {
|
|
||||||
enable = mkEnableOption "SSO authentication";
|
|
||||||
};
|
|
||||||
extraConfig = mkOption {
|
|
||||||
type = types.attrs; # FIXME: forward type of virtualHosts
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
locations."/socket" = {
|
|
||||||
proxyPass = "http://localhost:8096/";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
default = { };
|
|
||||||
description = ''
|
|
||||||
Any extra configuration that should be applied to this virtual host.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ./sso ];
|
|
||||||
options.my.services.nginx = with lib; {
|
|
||||||
enable = mkEnableOption "Nginx";
|
|
||||||
acme = {
|
|
||||||
credentialsFile = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
example = "/var/lib/acme/creds.env";
|
|
||||||
description = ''
|
|
||||||
INWX API key file as an 'EnvironmentFile' (see `systemd.exec(5)`)
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
virtualHosts = mkOption {
|
|
||||||
type = types.listOf virtualHostOption;
|
|
||||||
default = [ ];
|
|
||||||
example = literalExpression ''
|
|
||||||
[
|
|
||||||
{
|
|
||||||
subdomain = "gitea";
|
|
||||||
port = 8080;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
subdomain = "dev";
|
|
||||||
root = "/var/www/dev";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
subdomain = "jellyfin";
|
|
||||||
port = 8096;
|
|
||||||
extraConfig = {
|
|
||||||
locations."/socket" = {
|
|
||||||
proxyPass = "http://localhost:8096/";
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
]
|
|
||||||
'';
|
|
||||||
description = ''
|
|
||||||
List of virtual hosts to set-up using default settings.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
sso = {
|
|
||||||
authKeyFile = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
example = "/var/lib/nginx-sso/auth-key.txt";
|
|
||||||
description = ''
|
|
||||||
Path to the auth key.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
subdomain = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "login";
|
|
||||||
example = "auth";
|
|
||||||
description = "Which subdomain, to use for SSO.";
|
|
||||||
};
|
|
||||||
port = mkOption {
|
|
||||||
type = types.port;
|
|
||||||
default = 8082;
|
|
||||||
example = 8080;
|
|
||||||
description = "Port to use for internal webui.";
|
|
||||||
};
|
|
||||||
users = mkOption {
|
|
||||||
type = types.attrsOf (
|
|
||||||
types.submodule {
|
|
||||||
options = {
|
|
||||||
passwordHashFile = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
example = "/var/lib/nginx-sso/alice/password-hash.txt";
|
|
||||||
description = "Path to file containing the user's password hash.";
|
|
||||||
};
|
|
||||||
totpSecretFile = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
example = "/var/lib/nginx-sso/alice/totp-secret.txt";
|
|
||||||
description = "Path to file containing the user's TOTP secret.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
alice = {
|
|
||||||
passwordHashFile = "/var/lib/nginx-sso/alice/password-hash.txt";
|
|
||||||
totpSecretFile = "/var/lib/nginx-sso/alice/totp-secret.txt";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
description = "Definition of users";
|
|
||||||
};
|
|
||||||
groups = mkOption {
|
|
||||||
type = with types; attrsOf (listOf str);
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
root = [ "alice" ];
|
|
||||||
users = [ "alice" "bob" ];
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
description = "Groups of users";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
assertions = lib.flip builtins.map cfg.virtualHosts (
|
|
||||||
{ subdomain, ... }@args:
|
|
||||||
let
|
|
||||||
conflicts = [
|
|
||||||
"port"
|
|
||||||
"root"
|
|
||||||
];
|
|
||||||
optionsNotNull = builtins.map (v: args.${v} != null) conflicts;
|
|
||||||
optionsSet = lib.filter lib.id optionsNotNull;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assertion = builtins.length optionsSet == 1;
|
|
||||||
message = ''
|
|
||||||
Subdomain '${subdomain}' must have exactly one of ${
|
|
||||||
lib.concatStringsSep ", " (builtins.map (v: "'${v}'") conflicts)
|
|
||||||
} configured.
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
)
|
|
||||||
# ++ (
|
|
||||||
# let
|
|
||||||
# ports = lib.my.mapFilter
|
|
||||||
# (v: v != null)
|
|
||||||
# ({ port, ... }: port)
|
|
||||||
# cfg.virtualHosts;
|
|
||||||
# lib.unique ports;
|
|
||||||
# lib.compareLists ports
|
|
||||||
# portCounts = lib.my.countValues ports;
|
|
||||||
# nonUniquesCounts = lib.filterAttrs (_: v: v != 1) portCounts;
|
|
||||||
# nonUniques = builtins.attrNames nonUniquesCounts;
|
|
||||||
# mkAssertion = port: {
|
|
||||||
# assertion = false;
|
|
||||||
# message = "Port ${port} cannot appear in multiple virtual hosts.";
|
|
||||||
# };
|
|
||||||
# in
|
|
||||||
# map mkAssertion nonUniques
|
|
||||||
# ) ++ (
|
|
||||||
# let
|
|
||||||
# subs = map ({ subdomain, ... }: subdomain) cfg.virtualHosts;
|
|
||||||
# subsCounts = lib.my.countValues subs;
|
|
||||||
# nonUniquesCounts = lib.filterAttrs (_: v: v != 1) subsCounts;
|
|
||||||
# nonUniques = builtins.attrNames nonUniquesCounts;
|
|
||||||
# mkAssertion = v: {
|
|
||||||
# assertion = false;
|
|
||||||
# message = ''
|
|
||||||
# Subdomain '${v}' cannot appear in multiple virtual hosts.
|
|
||||||
# '';
|
|
||||||
# };
|
|
||||||
# in
|
|
||||||
# map mkAssertion nonUniques
|
|
||||||
# )
|
|
||||||
;
|
|
||||||
services = {
|
|
||||||
nginx = {
|
|
||||||
enable = true;
|
|
||||||
statusPage = true; # For monitoring scraping.
|
|
||||||
|
|
||||||
recommendedGzipSettings = true;
|
|
||||||
recommendedOptimisation = true;
|
|
||||||
recommendedTlsSettings = true;
|
|
||||||
recommendedProxySettings = true;
|
|
||||||
recommendedBrotliSettings = true;
|
|
||||||
recommendedZstdSettings = true;
|
|
||||||
|
|
||||||
# Only allow PFS-enabled ciphers with AES256
|
|
||||||
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
|
|
||||||
|
|
||||||
commonHttpConfig = ''
|
|
||||||
# Add HSTS header with preloading to HTTPS requests.
|
|
||||||
# Adding this header to HTTP requests is discouraged
|
|
||||||
map $scheme $hsts_header {
|
|
||||||
https "max-age=31536000; includeSubdomains; preload";
|
|
||||||
}
|
|
||||||
add_header Strict-Transport-Security $hsts_header;
|
|
||||||
|
|
||||||
# CORS header
|
|
||||||
# some applications set it to wildcard, therefore this overrides it
|
|
||||||
proxy_hide_header Access-Control-Allow-Origin;
|
|
||||||
add_header Access-Control-Allow-Origin https://${config.networking.domain};
|
|
||||||
|
|
||||||
# Minimize information leaked to other domains
|
|
||||||
add_header 'Referrer-Policy' 'strict-origin-when-cross-origin';
|
|
||||||
|
|
||||||
# Disable embedding as a frame
|
|
||||||
add_header X-Frame-Options DENY;
|
|
||||||
|
|
||||||
# Prevent injection of code in other mime types (XSS Attacks)
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
|
||||||
|
|
||||||
# Enable XSS protection of the browser.
|
|
||||||
# May be unnecessary when CSP is configured properly (see above)
|
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
|
||||||
|
|
||||||
# This might create errors
|
|
||||||
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
|
|
||||||
|
|
||||||
# Enable CSP for your services.
|
|
||||||
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
|
|
||||||
'';
|
|
||||||
|
|
||||||
virtualHosts =
|
|
||||||
let
|
|
||||||
genAttrs' = values: f: lib.listToAttrs (map f values);
|
|
||||||
inherit (config.networking) domain;
|
|
||||||
mkVHost =
|
|
||||||
{ subdomain, ... }@args:
|
|
||||||
lib.nameValuePair "${subdomain}.${domain}" (
|
|
||||||
lib.foldl lib.recursiveUpdate { } [
|
|
||||||
# Base configuration
|
|
||||||
{
|
|
||||||
forceSSL = true;
|
|
||||||
useACMEHost = domain;
|
|
||||||
}
|
|
||||||
# Proxy to port
|
|
||||||
(lib.optionalAttrs (args.port != null) {
|
|
||||||
locations."/".proxyPass = "http://localhost:${toString args.port}";
|
|
||||||
})
|
|
||||||
# Serve filesystem content
|
|
||||||
(lib.optionalAttrs (args.root != null) { inherit (args) root; })
|
|
||||||
# VHost specific configuration
|
|
||||||
args.extraConfig
|
|
||||||
# SSO configuration
|
|
||||||
(lib.optionalAttrs args.sso.enable {
|
|
||||||
extraConfig =
|
|
||||||
(args.extraConfig.extraConfig or "")
|
|
||||||
+ ''
|
|
||||||
error_page 401 = @error401;
|
|
||||||
'';
|
|
||||||
locations = {
|
|
||||||
"@error401".return = ''
|
|
||||||
302 https://${cfg.sso.subdomain}.${config.networking.domain}/login?go=$scheme://$http_host$request_uri
|
|
||||||
'';
|
|
||||||
"/" = {
|
|
||||||
extraConfig =
|
|
||||||
(args.extraConfig.locations."/".extraConfig or "")
|
|
||||||
+ ''
|
|
||||||
# Use SSO
|
|
||||||
auth_request /sso-auth;
|
|
||||||
# Set username through header
|
|
||||||
auth_request_set $username $upstream_http_x_username;
|
|
||||||
proxy_set_header X-User $username;
|
|
||||||
# Renew SSO cookie on request
|
|
||||||
auth_request_set $cookie $upstream_http_set_cookie;
|
|
||||||
add_header Set-Cookie $cookie;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
"/sso-auth" = {
|
|
||||||
proxyPass = "http://localhost:${toString cfg.sso.port}/auth";
|
|
||||||
extraConfig = ''
|
|
||||||
# Do not allow requests from outside
|
|
||||||
internal;
|
|
||||||
# Do not forward the request body
|
|
||||||
proxy_pass_request_body off;
|
|
||||||
proxy_set_header Content-Length "";
|
|
||||||
# Set X-Application according to subdomain for matching
|
|
||||||
proxy_set_header X-Application "${subdomain}";
|
|
||||||
# Set origin URI for matching
|
|
||||||
proxy_set_header X-Origin-URI $request_uri;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
]
|
|
||||||
);
|
|
||||||
in
|
|
||||||
genAttrs' cfg.virtualHosts mkVHost;
|
|
||||||
sso = {
|
|
||||||
enable = true;
|
|
||||||
configuration = {
|
|
||||||
listen = {
|
|
||||||
addr = "localhost";
|
|
||||||
inherit (cfg.sso) port;
|
|
||||||
};
|
|
||||||
audit_log = {
|
|
||||||
target = [ "fd://stdout" ];
|
|
||||||
events = [
|
|
||||||
"access_denied"
|
|
||||||
"login_success"
|
|
||||||
"login_failure"
|
|
||||||
"logout"
|
|
||||||
"validate"
|
|
||||||
];
|
|
||||||
headers = [
|
|
||||||
"x-origin-uri"
|
|
||||||
"x-application"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
cookie = {
|
|
||||||
domain = ".${config.networking.domain}";
|
|
||||||
secure = true;
|
|
||||||
authentication_key = {
|
|
||||||
_secret = cfg.sso.authKeyFile;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
login = {
|
|
||||||
title = "Bühlers's SSO";
|
|
||||||
default_method = "simple";
|
|
||||||
hide_mfa_field = false;
|
|
||||||
names = {
|
|
||||||
simple = "Username / Password";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
providers = {
|
|
||||||
simple =
|
|
||||||
let
|
|
||||||
applyUsers = lib.flip lib.mapAttrs cfg.sso.users;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
users = applyUsers (_: v: { _secret = v.passwordHashFile; });
|
|
||||||
mfa = applyUsers (
|
|
||||||
_: v: [
|
|
||||||
{
|
|
||||||
provider = "totp";
|
|
||||||
attributes = {
|
|
||||||
secret = {
|
|
||||||
_secret = v.totpSecretFile;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
inherit (cfg.sso) groups;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
acl = {
|
|
||||||
rule_sets = [
|
|
||||||
{
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
field = "x-application";
|
|
||||||
present = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
allow = [ "@root" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# services.prometheus = lib.mkIf cfg.monitoring.enable {
|
|
||||||
prometheus = {
|
|
||||||
exporters.nginx.enable = true;
|
|
||||||
scrapeConfigs = [
|
|
||||||
{
|
|
||||||
job_name = "nginx";
|
|
||||||
static_configs = [
|
|
||||||
{
|
|
||||||
targets = [ "localhost:${toString config.services.prometheus.exporters.nginx.port}" ];
|
|
||||||
labels = {
|
|
||||||
instance = config.networking.hostName;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
grafana.provision = {
|
|
||||||
dashboards.settings.providers = [
|
|
||||||
{
|
|
||||||
name = "Nginx";
|
|
||||||
options.path = pkgs.grafana-dashboards.nginx;
|
|
||||||
disableDeletion = true;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
|
||||||
{
|
|
||||||
subdomain = "login";
|
|
||||||
inherit (cfg.sso) port;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
my.services.backup = {
|
|
||||||
exclude = [
|
|
||||||
# fails often because the file changed
|
|
||||||
"/var/log/nginx/access.log"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
# Nginx needs to be able to read the certificates
|
|
||||||
users.users.nginx.extraGroups = [ "acme" ];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
# I must override the module to allow having runtime secrets
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
utils,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.services.nginx.sso;
|
|
||||||
pkg = lib.getBin cfg.package;
|
|
||||||
confPath = "/var/lib/nginx-sso/config.json";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
disabledModules = [ "services/security/nginx-sso.nix" ];
|
|
||||||
|
|
||||||
options.services.nginx.sso = with lib; {
|
|
||||||
enable = mkEnableOption "nginx-sso service";
|
|
||||||
|
|
||||||
package = mkOption {
|
|
||||||
type = types.package;
|
|
||||||
default = pkgs.nginx-sso;
|
|
||||||
defaultText = "pkgs.nginx-sso";
|
|
||||||
description = ''
|
|
||||||
The nginx-sso package that should be used.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
configuration = mkOption {
|
|
||||||
type = types.attrsOf types.unspecified;
|
|
||||||
default = { };
|
|
||||||
example = literalExpression ''
|
|
||||||
{
|
|
||||||
listen = { addr = "localhost"; port = 8080; };
|
|
||||||
|
|
||||||
providers.token.tokens = {
|
|
||||||
myuser = "MyToken";
|
|
||||||
};
|
|
||||||
|
|
||||||
acl = {
|
|
||||||
rule_sets = [
|
|
||||||
{
|
|
||||||
rules = [ { field = "x-application"; equals = "MyApp"; } ];
|
|
||||||
allow = [ "myuser" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
description = ''
|
|
||||||
nginx-sso configuration
|
|
||||||
(<link xlink:href="https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration">documentation</link>)
|
|
||||||
as a Nix attribute set.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
systemd.services.nginx-sso = {
|
|
||||||
description = "Nginx SSO Backend";
|
|
||||||
after = [ "network.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
StateDirectory = "nginx-sso";
|
|
||||||
WorkingDirectory = "/var/lib/nginx-sso";
|
|
||||||
# The files to be merged might not have the correct permissions
|
|
||||||
ExecStartPre = ''+${pkgs.writeScript "merge-nginx-sso-config" ''
|
|
||||||
#!${pkgs.bash}/bin/bash
|
|
||||||
rm -f '${confPath}'
|
|
||||||
${utils.genJqSecretsReplacementSnippet cfg.configuration confPath}
|
|
||||||
|
|
||||||
# Fix permissions
|
|
||||||
chown nginx-sso:nginx-sso ${confPath}
|
|
||||||
chmod 0600 ${confPath}
|
|
||||||
''}'';
|
|
||||||
ExecStart = lib.mkForce ''
|
|
||||||
${pkg}/bin/nginx-sso \
|
|
||||||
--config ${confPath} \
|
|
||||||
--frontend-dir ${pkg}/share/frontend
|
|
||||||
'';
|
|
||||||
Restart = "always";
|
|
||||||
User = "nginx-sso";
|
|
||||||
Group = "nginx-sso";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.nginx-sso = {
|
|
||||||
isSystemUser = true;
|
|
||||||
group = "nginx-sso";
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups.nginx-sso = { };
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -45,7 +45,7 @@ in
|
||||||
|
|
||||||
# monitoring is not really useful, because it only contains the http-worker infos -> skipped for now
|
# monitoring is not really useful, because it only contains the http-worker infos -> skipped for now
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "docs";
|
subdomain = "docs";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -21,7 +21,7 @@ in
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "passworts";
|
subdomain = "passworts";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -85,24 +85,10 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "photos";
|
subdomain = "photos";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
extraConfig = {
|
|
||||||
locations."/" = {
|
|
||||||
proxyWebsockets = true;
|
|
||||||
extraConfig = ''
|
|
||||||
# Allow large file uploads
|
|
||||||
client_max_body_size 1G;
|
|
||||||
|
|
||||||
# Configure timeout
|
|
||||||
proxy_read_timeout 600s;
|
|
||||||
proxy_send_timeout 600s;
|
|
||||||
send_timeout 600s;
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -188,7 +188,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nginx.virtualHosts = [
|
webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "monitor";
|
subdomain = "monitor";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -83,7 +83,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "log";
|
subdomain = "log";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -43,7 +43,7 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "indexer";
|
subdomain = "indexer";
|
||||||
inherit port;
|
inherit port;
|
||||||
|
|
|
@ -43,7 +43,7 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "movies";
|
subdomain = "movies";
|
||||||
inherit port;
|
inherit port;
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# Get RSS feeds from websites that don't natively have one
|
# Get RSS feeds from websites that don't natively have one
|
||||||
{ config, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.my.services.rss-bridge;
|
cfg = config.my.services.rss-bridge;
|
||||||
domain = "rss-bridge.${config.networking.domain}";
|
domain = "rss-bridge.${config.networking.domain}";
|
||||||
|
@ -13,13 +18,23 @@ in
|
||||||
services.rss-bridge = {
|
services.rss-bridge = {
|
||||||
enable = true;
|
enable = true;
|
||||||
config.system.enabled_bridges = [ "*" ]; # Whitelist all
|
config.system.enabled_bridges = [ "*" ]; # Whitelist all
|
||||||
virtualHost = domain;
|
virtualHost = null;
|
||||||
|
user = "caddy";
|
||||||
|
group = "caddy";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx.virtualHosts.${domain} = {
|
my.services.webserver.virtualHosts = [
|
||||||
forceSSL = true;
|
{
|
||||||
enableACME = true;
|
subdomain = "rss-bridge";
|
||||||
};
|
extraConfig = ''
|
||||||
|
root * ${pkgs.rss-bridge}
|
||||||
|
php_fastcgi unix/${config.services.phpfpm.pools."rss-bridge".socket} {
|
||||||
|
env RSSBRIDGE_fileCache_path ${config.services.rss-bridge.dataDir}/cache/
|
||||||
|
}
|
||||||
|
file_server
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
webapps.apps.rss-bridge = {
|
webapps.apps.rss-bridge = {
|
||||||
dashboard = {
|
dashboard = {
|
||||||
|
|
|
@ -49,7 +49,7 @@ in
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "series";
|
subdomain = "series";
|
||||||
inherit port;
|
inherit port;
|
||||||
|
|
|
@ -22,7 +22,7 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
# Proxy to Tandoor-Recipes
|
# Proxy to Tandoor-Recipes
|
||||||
my.services.nginx.virtualHosts = [
|
my.services.webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "recipes";
|
subdomain = "recipes";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
|
|
|
@ -53,17 +53,10 @@ in
|
||||||
|
|
||||||
# Proxy to Headscale
|
# Proxy to Headscale
|
||||||
my.services = {
|
my.services = {
|
||||||
nginx.virtualHosts = [
|
webserver.virtualHosts = [
|
||||||
{
|
{
|
||||||
subdomain = "vpn";
|
subdomain = "vpn";
|
||||||
inherit (cfg) port;
|
inherit (cfg) port;
|
||||||
extraConfig = {
|
|
||||||
locations = {
|
|
||||||
"/" = {
|
|
||||||
proxyWebsockets = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
181
modules/services/webserver/default.nix
Normal file
181
modules/services/webserver/default.nix
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
# public webserver with reverseproxy
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.my.services.webserver;
|
||||||
|
inherit (config.networking) domain;
|
||||||
|
|
||||||
|
virtualHostOption = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
subdomain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "dev";
|
||||||
|
description = ''
|
||||||
|
Which subdomain, under config.networking.domain, to use
|
||||||
|
for this virtual host.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr port;
|
||||||
|
default = null;
|
||||||
|
example = 8080;
|
||||||
|
description = ''
|
||||||
|
Which port to proxy to, through localhost, for this virtual host.
|
||||||
|
This option is incompatible with `root`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
root = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
example = "/var/www/blog";
|
||||||
|
description = ''
|
||||||
|
The root folder for this virtual host. This option is incompatible
|
||||||
|
with `port`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
extraConfig = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr lines;
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
locations."/socket" = {
|
||||||
|
proxyPass = "http://localhost:8096/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Any extra configuration that should be applied to this virtual host.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.my.services.webserver = {
|
||||||
|
enable = lib.mkEnableOption "webserver";
|
||||||
|
virtualHosts = lib.mkOption {
|
||||||
|
type = lib.types.listOf virtualHostOption;
|
||||||
|
default = [ ];
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
[
|
||||||
|
{
|
||||||
|
subdomain = "gitea";
|
||||||
|
port = 8080;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
subdomain = "dev";
|
||||||
|
root = "/var/www/dev";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
subdomain = "jellyfin";
|
||||||
|
port = 8096;
|
||||||
|
extraConfig = {
|
||||||
|
locations."/socket" = {
|
||||||
|
proxyPass = "http://localhost:8096/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
List of virtual hosts to set-up using default settings.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services = {
|
||||||
|
nginx.enable = false;
|
||||||
|
caddy = {
|
||||||
|
enable = true;
|
||||||
|
email = "server@buehler.rocks";
|
||||||
|
|
||||||
|
globalConfig = ''
|
||||||
|
servers{
|
||||||
|
metrics
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
extraConfig = ''
|
||||||
|
(compress) {
|
||||||
|
encode gzip zstd
|
||||||
|
}
|
||||||
|
(headers) {
|
||||||
|
header {
|
||||||
|
# enable CORS
|
||||||
|
Access-Control-Allow-Origin "https://${config.networking.domain}"
|
||||||
|
# disable FLoC tracking
|
||||||
|
Permissions-Policy interest-cohort=()
|
||||||
|
# enable HSTS
|
||||||
|
Strict-Transport-Security max-age=31536000;
|
||||||
|
# disable clients from sniffing the media type
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
# clickjacking protection
|
||||||
|
X-Frame-Options "DENY"
|
||||||
|
# enable XSS protection
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
# referrer policy
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(common) {
|
||||||
|
import headers
|
||||||
|
import compress
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
virtualHosts =
|
||||||
|
let
|
||||||
|
mkVHost =
|
||||||
|
{ subdomain, ... }@args:
|
||||||
|
lib.nameValuePair "${subdomain}.${domain}" (
|
||||||
|
lib.foldl lib.recursiveUpdate { } [
|
||||||
|
{
|
||||||
|
useACMEHost = domain;
|
||||||
|
extraConfig = ''
|
||||||
|
import common
|
||||||
|
${lib.optionalString (args.root != null) ''
|
||||||
|
root * ${args.root}
|
||||||
|
file_server
|
||||||
|
''}
|
||||||
|
${lib.optionalString (args.port != null) ''
|
||||||
|
reverse_proxy localhost:${toString args.port} {
|
||||||
|
# remove CORS headers from proxied server, because duplicate headers are not allowed
|
||||||
|
# remove after new release: https://github.com/navidrome/navidrome/commit/657fe11f5327ff7a3cb6aa9308b0bb7c71eea5c6
|
||||||
|
header_down -Access-Control-Allow-Origin
|
||||||
|
}
|
||||||
|
''}
|
||||||
|
${lib.optionalString (args.extraConfig != null) args.extraConfig}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
in
|
||||||
|
lib.listToAttrs (map mkVHost cfg.virtualHosts);
|
||||||
|
};
|
||||||
|
|
||||||
|
prometheus.scrapeConfigs = [
|
||||||
|
{
|
||||||
|
job_name = "caddy";
|
||||||
|
static_configs = [
|
||||||
|
{
|
||||||
|
targets = [ "localhost:2019" ];
|
||||||
|
labels.instance = config.networking.hostName;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
443
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue