nix-config/utils.nix
William af444584d0 Add shared services infrastructure for cross-host data
Created centralized service definitions in shared/services.nix to store
service metadata (domains, IPs, ports) that need to be accessible across
multiple hosts. This replaces the per-service split DNS module approach
with a single source of truth.

Services are now exported through utils.nix for easy access in host configs.
2025-11-08 21:35:13 -03:00

224 lines
6.2 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 =
{
acmeHost,
domains,
}:
let
commonVHostConfig = {
useACMEHost = acmeHost;
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;
}
];
}