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
|
@ -25,8 +25,8 @@ in
|
|||
acceptTerms = true;
|
||||
# Use DNS wildcard certificate
|
||||
certs = {
|
||||
"${config.networking.domain}" = {
|
||||
extraDomainNames = [ "*.${config.networking.domain}" ];
|
||||
"${domain}" = {
|
||||
extraDomainNames = [ "*.${domain}" ];
|
||||
dnsProvider = "inwx";
|
||||
inherit (cfg) credentialsFile;
|
||||
};
|
||||
|
|
|
@ -132,7 +132,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "alerts";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -28,7 +28,7 @@ in
|
|||
inherit (cfg) downloadDir;
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "download";
|
||||
root = "${pkgs.ariang}/share/ariang";
|
||||
|
|
|
@ -43,7 +43,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "subtitles";
|
||||
inherit port;
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
./mumble-server
|
||||
./navidrome
|
||||
./nextcloud
|
||||
./nginx
|
||||
./node-exporter
|
||||
./octoprint
|
||||
./paperless
|
||||
|
@ -43,5 +42,6 @@
|
|||
./ssh-server
|
||||
./tandoor-recipes
|
||||
./vpn
|
||||
./webserver
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# finance overview
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.my.services.finance;
|
||||
inherit (config.networking) domain;
|
||||
|
@ -20,18 +25,26 @@ in
|
|||
services.firefly-iii = {
|
||||
enable = true;
|
||||
virtualHost = "finance";
|
||||
enableNginx = true;
|
||||
user = "caddy";
|
||||
group = "caddy";
|
||||
settings = {
|
||||
APP_KEY_FILE = cfg.appKeyFile;
|
||||
SITE_OWNER = "server@buehler.rocks";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."finance" = {
|
||||
serverName = "finance.${domain}";
|
||||
forceSSL = true;
|
||||
useACMEHost = domain;
|
||||
};
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "finance";
|
||||
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 = {
|
||||
dashboard = {
|
||||
|
|
|
@ -45,17 +45,27 @@ in
|
|||
enable = true;
|
||||
baseUrl = "https://news.${domain}";
|
||||
inherit (cfg) language passwordFile defaultUser;
|
||||
virtualHost = null;
|
||||
};
|
||||
|
||||
# Set up a Nginx virtual host.
|
||||
services.nginx = {
|
||||
virtualHosts."freshrss" = {
|
||||
serverName = "news.${domain}";
|
||||
forceSSL = true;
|
||||
useACMEHost = domain;
|
||||
};
|
||||
services.phpfpm.pools.freshrss.settings = {
|
||||
"listen.owner" = lib.mkForce config.services.caddy.user;
|
||||
"listen.group" = lib.mkForce config.services.caddy.group;
|
||||
};
|
||||
|
||||
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 = {
|
||||
dashboard = {
|
||||
name = "News";
|
||||
|
|
|
@ -66,7 +66,7 @@ in
|
|||
|
||||
# Proxy to Gitea
|
||||
my.services = {
|
||||
nginx.virtualHosts = [
|
||||
webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "code";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -79,7 +79,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "visualization";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -76,7 +76,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "notes";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -145,7 +145,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "automation";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -16,7 +16,7 @@ in
|
|||
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "blog";
|
||||
root = inputs.stunkymonkey.packages.${config.nixpkgs.system}.default;
|
||||
|
|
|
@ -26,24 +26,18 @@ in
|
|||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.nginx.virtualHosts = {
|
||||
# This is not a subdomain, cannot use my nginx wrapper module
|
||||
${domain} = {
|
||||
forceSSL = true;
|
||||
useACMEHost = domain;
|
||||
# TODO: 25.05 use stable
|
||||
root = pkgs.unstable.homer;
|
||||
locations."=/assets/config.yml" = {
|
||||
alias = pkgs.writeText "homerConfig.yml" (builtins.toJSON homeConfig);
|
||||
};
|
||||
};
|
||||
# redirect any other attempt to the main site
|
||||
"${domain}-redirect" = {
|
||||
forceSSL = true;
|
||||
useACMEHost = domain;
|
||||
default = true;
|
||||
globalRedirect = "${domain}";
|
||||
};
|
||||
# TODO: 25.05 use stable
|
||||
services.caddy.virtualHosts.${domain} = {
|
||||
extraConfig = ''
|
||||
import common
|
||||
root * ${pkgs.unstable.homer}
|
||||
file_server
|
||||
handle_path /assets/config.yml {
|
||||
root * ${pkgs.writeText "homerConfig.yml" (builtins.toJSON homeConfig)}
|
||||
file_server
|
||||
}
|
||||
'';
|
||||
useACMEHost = domain;
|
||||
};
|
||||
|
||||
webapps = {
|
||||
|
|
|
@ -47,7 +47,7 @@ in
|
|||
};
|
||||
# sadly the metrics do not contain application specific metrics, only c# -> no dashboard
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "media";
|
||||
inherit port;
|
||||
|
|
|
@ -14,7 +14,7 @@ in
|
|||
enable = true;
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "view";
|
||||
inherit (config.services.jellyseerr) port;
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
}:
|
||||
let
|
||||
cfg = config.my.services.mumble-server;
|
||||
domain = "voice.${config.networking.domain}";
|
||||
inherit (config.networking) domain;
|
||||
in
|
||||
{
|
||||
options.my.services.mumble-server = {
|
||||
enable = lib.mkEnableOption "RSS-Bridge service";
|
||||
enable = lib.mkEnableOption "mumble server service";
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
@ -19,29 +19,29 @@ in
|
|||
enable = true;
|
||||
openFirewall = true;
|
||||
welcometext = "Welcome to the Mumble-Server!";
|
||||
sslCert = "/var/lib/acme/${domain}/fullchain.pem";
|
||||
sslKey = "/var/lib/acme/${domain}/key.pem";
|
||||
sslCert = "${config.security.acme.certs.${domain}.directory}/fullchain.pem";
|
||||
sslKey = "${config.security.acme.certs.${domain}.directory}/key.pem";
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${domain}.enableACME = true;
|
||||
security.acme.certs."${domain}" = {
|
||||
group = "voice-buehler-rocks";
|
||||
postRun = ''
|
||||
if ${pkgs.systemd}/bin/systemctl is-active murmur.service; then
|
||||
${pkgs.systemd}/bin/systemctl kill -s SIGUSR1 murmur.service
|
||||
fi
|
||||
'';
|
||||
# create a separate certificate for the mumble server
|
||||
security.acme = {
|
||||
certs.${domain} = {
|
||||
reloadServices = [ "murmur" ];
|
||||
group = "caddyandmurmur";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups."voice-buehler-rocks".members = [
|
||||
users.groups.caddyandmurmur.members = [
|
||||
"caddy"
|
||||
"murmur"
|
||||
"nginx"
|
||||
];
|
||||
|
||||
my.services.prometheus.rules = {
|
||||
mumble_not_running = {
|
||||
condition = ''systemd_unit_state{name="murmur.service", state!="active"} > 0'';
|
||||
description = "{{$labels.host}} should have a running {{$labels.name}}";
|
||||
my.services = {
|
||||
acme.enable = true;
|
||||
prometheus.rules = {
|
||||
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";
|
||||
inherit (cfg) port;
|
||||
|
@ -101,7 +101,8 @@ in
|
|||
name = "Music";
|
||||
category = "media";
|
||||
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 = {
|
||||
enable = true;
|
||||
url = "https://cloud.${domain}";
|
||||
|
@ -144,6 +133,55 @@ in
|
|||
# requires = [ "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 = {
|
||||
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
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "docs";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -21,7 +21,7 @@ in
|
|||
inherit (cfg) port;
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "passworts";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -85,24 +85,10 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "photos";
|
||||
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";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -83,7 +83,7 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "log";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -43,7 +43,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "indexer";
|
||||
inherit port;
|
||||
|
|
|
@ -43,7 +43,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "movies";
|
||||
inherit port;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# Get RSS feeds from websites that don't natively have one
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.my.services.rss-bridge;
|
||||
domain = "rss-bridge.${config.networking.domain}";
|
||||
|
@ -13,13 +18,23 @@ in
|
|||
services.rss-bridge = {
|
||||
enable = true;
|
||||
config.system.enabled_bridges = [ "*" ]; # Whitelist all
|
||||
virtualHost = domain;
|
||||
virtualHost = null;
|
||||
user = "caddy";
|
||||
group = "caddy";
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts.${domain} = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
};
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
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 = {
|
||||
dashboard = {
|
||||
|
|
|
@ -49,7 +49,7 @@ in
|
|||
];
|
||||
};
|
||||
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "series";
|
||||
inherit port;
|
||||
|
|
|
@ -22,7 +22,7 @@ in
|
|||
};
|
||||
|
||||
# Proxy to Tandoor-Recipes
|
||||
my.services.nginx.virtualHosts = [
|
||||
my.services.webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "recipes";
|
||||
inherit (cfg) port;
|
||||
|
|
|
@ -53,17 +53,10 @@ in
|
|||
|
||||
# Proxy to Headscale
|
||||
my.services = {
|
||||
nginx.virtualHosts = [
|
||||
webserver.virtualHosts = [
|
||||
{
|
||||
subdomain = "vpn";
|
||||
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