nix-config/utils.nix
William 73db534269 Switch from wildcard to per-domain SSL certificates
Updated mkNginxVHosts to use per-domain certificates (enableACME) instead
of shared wildcard certificates (useACMEHost). Each service now requests
its own certificate, avoiding conflicts between hosts and following the
principle of least privilege.

Removed wildcard certificate configuration from both alexandria and trantor.
Each host now only obtains certificates for domains it actually serves:
- Alexandria: pass.baduhai.dev, cloud.baduhai.dev, jellyfin.baduhai.dev
- Trantor: git.baduhai.dev
2025-11-08 21:47:41 -03:00

221 lines
6.1 KiB
Nix

{ inputs, lib }:
let
inherit (inputs)
self
nixpkgs
nixpkgs-stable
home-manager
agenix
;
# Import shared service definitions
sharedServices = import ./shared/services.nix;
in
{
# Re-export shared services for use in host configs
inherit (sharedServices) services;
# Tag-based host configuration system
mkHost =
{
hostname,
tags ? [ ],
system ? "x86_64-linux",
extraModules ? [ ],
}:
let
# Validate that server and desktop tags are mutually exclusive
hasServer = builtins.elem "server" tags;
hasDesktop = builtins.elem "desktop" tags;
# Always include "common" tag implicitly
allTags =
if hasServer && hasDesktop then
throw "Error: 'server' and 'desktop' tags are mutually exclusive for host '${hostname}'"
else
[ "common" ] ++ tags;
# Choose nixpkgs based on server tag
pkgs = if builtins.elem "server" allTags then nixpkgs-stable else nixpkgs;
# Tag-specific modules: each tag can be either:
# 1. A file: hosts/modules/${tag}.nix
# 2. A directory: hosts/modules/${tag}/*.nix (all .nix files imported)
tagModuleFiles = builtins.concatMap (
tag:
let
filePath = ./hosts/modules/${tag}.nix;
dirPath = ./hosts/modules/${tag};
in
# Check if it's a file first
if builtins.pathExists filePath then
[ filePath ]
# Then check if it's a directory
else if builtins.pathExists dirPath then
let
entries = builtins.readDir dirPath;
nixFiles = pkgs.lib.filterAttrs (
name: type: type == "regular" && pkgs.lib.hasSuffix ".nix" name
) entries;
in
map (name: dirPath + "/${name}") (builtins.attrNames nixFiles)
else
[ ]
) allTags;
# Automatically import all .nix files from hosts/${hostname}/
hostModulePath = ./hosts/${hostname};
hostModuleFiles =
if builtins.pathExists hostModulePath then
let
entries = builtins.readDir hostModulePath;
nixFiles = pkgs.lib.filterAttrs (
name: type: type == "regular" && pkgs.lib.hasSuffix ".nix" name && name != "${hostname}.nix"
) entries;
in
map (name: hostModulePath + "/${name}") (builtins.attrNames nixFiles)
else
[ ];
# Combine all modules
allModules = [
agenix.nixosModules.default
{
networking.hostName = hostname;
nix.nixPath = [ "nixos-config=${self.outPath}/nixosConfigurations/${hostname}" ];
nixpkgs.overlays = [
agenix.overlays.default
self.overlays.default
];
}
]
++ tagModuleFiles
++ hostModuleFiles
++ extraModules;
in
pkgs.lib.nixosSystem {
inherit system;
specialArgs = {
inherit inputs;
hostTags = allTags;
};
modules = allModules;
};
# Tag-based user configuration system
mkHome =
{
username,
hostname ? null,
homeDirectory ? "/home/${username}",
tags ? [ ],
extraModules ? [ ],
}:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
# Always include "common" tag implicitly
allTags = [ "common" ] ++ tags;
# Tag-specific modules: each tag maps to users/modules/${tag}.nix if it exists
tagModuleFiles = builtins.concatMap (
tag:
let
filePath = ./users/modules/${tag}.nix;
dirPath = ./users/modules/${tag};
in
# Check if it's a file first
if builtins.pathExists filePath then
[ filePath ]
# Then check if it's a directory
else if builtins.pathExists dirPath then
let
entries = builtins.readDir dirPath;
nixFiles = pkgs.lib.filterAttrs (
name: type: type == "regular" && pkgs.lib.hasSuffix ".nix" name
) entries;
in
map (name: dirPath + "/${name}") (builtins.attrNames nixFiles)
else
[ ]
) allTags;
# Automatically import all .nix files from users/${username}/
userModulePath = ./users/${username};
userModuleFiles =
if builtins.pathExists userModulePath then
let
entries = builtins.readDir userModulePath;
nixFiles = pkgs.lib.filterAttrs (
name: type: type == "regular" && pkgs.lib.hasSuffix ".nix" name
) entries;
in
map (name: userModulePath + "/${name}") (builtins.attrNames nixFiles)
else
[ ];
# Combine all modules
allModules = [
{
home = {
inherit username homeDirectory;
stateVersion = "22.05";
};
}
]
++ tagModuleFiles
++ userModuleFiles
++ extraModules;
in
home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = {
inherit inputs hostname;
userTags = allTags;
};
modules = allModules ++ [
{
nixpkgs.overlays = [ self.overlays.default ];
}
];
};
# Nginx virtual host utilities
mkNginxVHosts =
{ domains }:
let
commonVHostConfig = {
enableACME = true;
forceSSL = true;
kTLS = true;
};
in
lib.mapAttrs (_: lib.recursiveUpdate commonVHostConfig) domains;
# Split DNS utilities for unbound
# Generates unbound view config from a list of DNS entries
mkSplitDNS =
entries:
let
# Generate local-data entries for all domains
tailscaleData = map (e: ''"${e.domain}. IN A ${e.tailscaleIP}"'') entries;
lanData = map (e: ''"${e.domain}. IN A ${e.lanIP}"'') entries;
in
[
# Single Tailscale view with all domains
{
name = "tailscale";
view-first = true;
local-zone = ''"baduhai.dev." transparent'';
local-data = tailscaleData;
}
# Single LAN view with all domains
{
name = "lan";
view-first = true;
local-zone = ''"baduhai.dev." transparent'';
local-data = lanData;
}
];
}