Refactor in preparation for service tests #163

Merged
tlater merged 3 commits from tlater/refactor-for-testing into master 2025-11-12 21:34:32 +00:00
13 changed files with 399 additions and 991 deletions

View file

@ -1,61 +1,45 @@
{
self,
nixpkgs,
deploy-rs,
system,
...
}:
{ flake-inputs }:
let
pkgs = nixpkgs.legacyPackages.${system};
statix' = pkgs.statix.overrideAttrs (old: {
patches = old.patches ++ [
(pkgs.fetchpatch {
url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
})
];
});
runNuCheck =
inherit (flake-inputs.nixpkgs) lib;
pkgs = flake-inputs.nixpkgs.legacyPackages.x86_64-linux;
checkLib = pkgs.callPackage ./lib.nix { };
in
{
x86_64-linux = lib.mergeAttrsList [
{
name,
packages,
check,
}:
pkgs.stdenvNoCC.mkDerivation {
inherit name;
nix = checkLib.mkLint {
name = "nix-lints";
fileset = lib.fileset.fileFilter (file: file.hasExt "nix") ../.;
src = nixpkgs.lib.cleanSourceWith {
src = self;
filter = nixpkgs.lib.cleanSourceFilter;
checkInputs = lib.attrValues {
inherit (pkgs) deadnix nixfmt-rfc-style;
statix = pkgs.statix.overrideAttrs (old: {
patches = old.patches ++ [
(pkgs.fetchpatch {
url = "https://github.com/oppiliappan/statix/commit/925dec39bb705acbbe77178b4d658fe1b752abbb.patch";
hash = "sha256-0wacO6wuYJ4ufN9PGucRVJucFdFFNF+NoHYIrLXsCWs=";
})
];
});
};
script = ''
statix check **/*.nix
deadnix --fail **/*.nix
nixfmt --check --strict **/*.nix
'';
};
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontInstall = true;
dontFixup = true;
doCheck = true;
lockfile = checkLib.mkLint {
name = "nix-lockfile";
fileset = ../flake.lock;
checkInputs = lib.attrValues { inherit (flake-inputs.flint.packages.x86_64-linux) flint; };
checkInputs = nixpkgs.lib.singleton pkgs.nushell ++ packages;
checkPhase = ''
nu ${check}
'';
};
in
nixpkgs.lib.recursiveUpdate {
lints = runNuCheck {
name = "lints";
packages = [
pkgs.deadnix
pkgs.nixfmt-rfc-style
pkgs.shellcheck
statix'
];
check = ./lints.nu;
};
} (deploy-rs.lib.${system}.deployChecks self.deploy)
script = ''
flint --fail-if-multiple-versions
'';
};
}
];
}

35
checks/lib.nix Normal file
View file

@ -0,0 +1,35 @@
{ pkgs, lib, ... }:
{
mkLint =
{
name,
fileset,
checkInputs ? [ ],
script,
}:
pkgs.stdenvNoCC.mkDerivation {
inherit name;
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.difference fileset (
lib.fileset.fileFilter (
file: file.type != "regular" || file.name == "hardware-configuration.nix"
) ../.
);
};
checkInputs = [ pkgs.nushell ] ++ checkInputs;
checkPhase = ''
nu -c '${script}' | tee $out
'';
dontPatch = true;
dontConfigure = true;
dontBuild = true;
dontInstall = true;
dontFixup = true;
doCheck = true;
};
}

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

@ -48,6 +48,7 @@
memorySize = 3941;
cores = 2;
graphics = false;
diskSize = 1024 * 20;
};
virtualisation.qemu = {

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):

864
flake.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,19 +3,44 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05-small";
## Nix/OS utilities
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
deploy-rs.url = "github:serokell/deploy-rs";
deploy-rs = {
url = "github:serokell/deploy-rs";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
tlaternet-webserver = {
url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
## Programs
flint = {
url = "github:NotAShelf/flint";
inputs.nixpkgs.follows = "nixpkgs";
};
## Services
tlaternet-webserver = {
url = "git+https://gitea.tlater.net/tlaternet/tlaternet.git";
inputs = {
nixpkgs.follows = "nixpkgs";
dream2nix.inputs = {
nixpkgs.follows = "nixpkgs";
purescript-overlay.inputs.flake-compat.follows = "deploy-rs/flake-compat";
};
};
};
foundryvtt = {
url = "github:reckenrode/nix-foundryvtt";
inputs.nixpkgs.follows = "nixpkgs";
@ -23,7 +48,20 @@
sonnenshift = {
url = "git+ssh://git@github.com/sonnenshift/battery-manager";
inputs.nixpkgs.follows = "nixpkgs";
inputs = {
nixpkgs.follows = "nixpkgs";
crate2nix.inputs = {
nixpkgs.follows = "nixpkgs";
flake-compat.follows = "deploy-rs/flake-compat";
cachix.inputs.nixpkgs.follows = "nixpkgs";
pre-commit-hooks.inputs.gitignore.follows = "sonnenshift/crate2nix/cachix/git-hooks/gitignore";
# Yes, they do this insanity:
# https://github.com/nix-community/crate2nix/issues/371
crate2nix_stable.follows = "sonnenshift/crate2nix";
};
};
};
};
@ -90,7 +128,7 @@
#########
# Tests #
#########
checks.${system} = import ./checks (inputs // { inherit system; });
checks = import ./checks { flake-inputs = inputs; };
###########################
# Garbage collection root #

View file

@ -1,6 +1 @@
{
imports = [
./crowdsec
./nginxExtensions.nix
];
}
{ imports = [ ./crowdsec ]; }

View file

@ -1,59 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
services.nginx.domain = lib.mkOption {
type = lib.types.str;
description = "The base domain name to append to virtual domain names";
};
services.nginx.virtualHosts =
let
extraVirtualHostOptions =
{ name, config, ... }:
{
options = {
enableHSTS = lib.mkEnableOption "Enable HSTS";
addAccessLog = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Add special logging to `/var/log/nginx/''${serverName}`
'';
};
};
config = {
extraConfig = lib.concatStringsSep "\n" [
(lib.optionalString config.enableHSTS ''
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
'')
(lib.optionalString config.addAccessLog ''
access_log /var/log/nginx/${name}/access.log upstream_time;
'')
];
};
};
in
lib.mkOption { type = lib.types.attrsOf (lib.types.submodule extraVirtualHostOptions); };
};
config = {
# Don't attempt to run acme if the domain name is not tlater.net
systemd.services =
let
confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]'';
in
lib.mapAttrs' (
cert: _:
lib.nameValuePair "acme-${cert}" {
serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' '';
}
) config.security.acme.certs;
};
}