refactor(nginx): Clean up nginx configuration

This commit is contained in:
Tristan Daniël Maat 2025-11-13 04:07:10 +08:00
parent d82c353329
commit 767a14ab6e
Signed by: tlater
GPG key ID: 02E935006CF2E8E7
8 changed files with 177 additions and 171 deletions

View file

@ -27,7 +27,7 @@
./services/wireguard.nix
# ./services/starbound.nix -- Not currently used
./services/postgres.nix
./nginx.nix
./nginx
./sops.nix
];

View file

@ -1,77 +0,0 @@
{ config, lib, ... }:
{
services = {
nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
clientMaxBodySize = "10G";
statusPage = true; # For metrics, should be accessible only from localhost
commonHttpConfig = ''
log_format upstream_time '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
'';
};
logrotate.settings = {
# Override the default, just keep fewer logs
nginx.rotate = 6;
}
// lib.mapAttrs' (
virtualHost: _:
lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" {
frequency = "daily";
rotate = 2;
compress = true;
delaycompress = true;
su = "${config.services.nginx.user} ${config.services.nginx.group}";
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
}
) config.services.nginx.virtualHosts;
backups.acme = {
user = "acme";
paths = lib.mapAttrsToList (
virtualHost: _: "/var/lib/acme/${virtualHost}"
) config.services.nginx.virtualHosts;
};
};
systemd.tmpfiles.rules = lib.mapAttrsToList (
virtualHost: _:
#
"d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}"
) config.services.nginx.virtualHosts;
security.acme = {
defaults.email = "tm@tlater.net";
acceptTerms = true;
certs."tlater.net" = {
extraDomainNames = [
"*.tlater.net"
"tlater.com"
"*.tlater.com"
];
dnsProvider = "porkbun";
group = "ssl-cert";
credentialFiles = {
PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
};
};
};
users.groups.ssl-cert = { };
systemd.services.nginx.serviceConfig.SupplementaryGroups = [
config.security.acme.certs."tlater.net".group
];
}

View file

@ -0,0 +1,22 @@
{ lib, ... }:
{
imports = [
./logging.nix
./ssl.nix
];
options.services.nginx.domain = lib.mkOption {
type = lib.types.str;
description = "The base domain name to append to virtual domain names";
};
config.services.nginx = {
enable = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
clientMaxBodySize = "10G";
statusPage = true; # For metrics, should be accessible only from localhost
};
}

View file

@ -0,0 +1,84 @@
{ config, lib, ... }:
let
hostNames = lib.attrNames config.services.nginx.virtualHosts;
logPath = name: "/var/log/nginx/${name}/access.log";
logFormat = lib.concatStringsSep " " [
"$remote_addr - $remote_user [$time_local]"
''"$request" $status $body_bytes_sent''
''"$http_referer" "$http_user_agent"''
''rt=$request_time uct="$upstream_connect_time"''
''uht="$upstream_header_time" urt="$upstream_response_time"''
];
in
{
# Extend the default configuration for nginx virtual hosts; we'd
# like to create log files for each of them, so that the prometheus
# nginxlog exporter can process per-host logs.
options.services.nginx.virtualHosts = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
config.extraConfig = ''
access_log ${logPath name} upstream_time;
'';
}
)
);
};
config = {
# Create directories for host-specific logs with systemd tmpfiles
systemd.tmpfiles.settings."10-nginx-logs" = lib.listToAttrs (
map (
name:
lib.nameValuePair "/var/log/nginx/${name}" {
d = {
inherit (config.services.nginx) user group;
mode = "0750";
};
}
) hostNames
);
services = {
# Set the nginx-wide log format
nginx.commonHttpConfig = ''
log_format upstream_time '${logFormat}';
'';
# Set up nginxlog to read the file and log format defined above
# for each virtual host
prometheus.exporters.nginxlog = {
enable = true;
listenAddress = "127.0.0.1";
group = "nginx";
settings.namespaces = map (name: {
inherit name;
metrics_override.prefix = "nginxlog";
namespace_label = "vhost";
format = logFormat;
source.files = [ (logPath name) ];
}) hostNames;
};
logrotate.settings = {
# Override the nginx module default, just keep fewer logs
nginx.rotate = 6;
# Configure logrotate for host-specific logs
nginxVirtualHosts = {
files = map logPath hostNames;
frequency = "daily";
rotate = 2;
compress = true;
delaycompress = true;
su = "${config.services.nginx.user} ${config.services.nginx.group}";
postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`";
};
};
};
};
}

View file

@ -0,0 +1,68 @@
{
pkgs,
config,
lib,
...
}:
{
options = {
# Add a custom per-host option to enable HSTS
services.nginx.virtualHosts = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ config, ... }:
{
options.enableHSTS = lib.mkEnableOption "HSTS";
config.extraConfig = lib.mkIf config.enableHSTS ''
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
'';
}
)
);
};
};
config = {
# Certificate settings
security.acme = {
defaults.email = "tm@tlater.net";
acceptTerms = true;
certs."tlater.net" = {
extraDomainNames = [
"*.tlater.net"
"tlater.com"
"*.tlater.com"
];
dnsProvider = "porkbun";
group = config.users.groups.ssl-cert.name;
credentialFiles = {
PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path;
PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path;
};
};
};
users.groups.ssl-cert = { };
# Back up the SSL certificate, just in case
services.backups.acme = {
user = "acme";
paths = [ "/var/lib/acme/tlater.net" ];
};
systemd.services = {
nginx.serviceConfig.SupplementaryGroups = [ config.security.acme.certs."tlater.net".group ];
# Don't attempt to retrieve a certificate if the domain name
# doesn't *actually* match the cert name
#
# TODO(tlater): Set up pebble to retrieve certs "properly"
# instead
"acme-tlater.net".serviceConfig.ExecCondition =
let
confirm = ''[[ "tlater.net" = "${config.services.nginx.domain}" ]]'';
in
''${pkgs.runtimeShell} -c '${confirm}' '';
};
};
}

View file

@ -1,9 +1,4 @@
{
config,
pkgs,
lib,
...
}:
{ pkgs, ... }:
let
yaml = pkgs.formats.yaml { };
in
@ -68,28 +63,6 @@ in
enable = true;
listenAddress = "127.0.0.1";
};
nginxlog = {
enable = true;
listenAddress = "127.0.0.1";
group = "nginx";
settings.namespaces = lib.mapAttrsToList (name: _: {
inherit name;
metrics_override.prefix = "nginxlog";
namespace_label = "vhost";
format = lib.concatStringsSep " " [
"$remote_addr - $remote_user [$time_local]"
''"$request" $status $body_bytes_sent''
''"$http_referer" "$http_user_agent"''
''rt=$request_time uct="$upstream_connect_time"''
''uht="$upstream_header_time" urt="$upstream_response_time"''
];
source.files = [ "/var/log/nginx/${name}/access.log" ];
}) config.services.nginx.virtualHosts;
};
};
# TODO(tlater):