nix-config/utils.nix

256 lines
7.4 KiB
Nix

{ inputs, lib }:
let
inherit (inputs)
self
nixpkgs
nixpkgs-stable
home-manager
agenix
terranix
;
in
{
# 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;
# Terranix configuration system
mkTerranixDerivation =
{
system,
configs,
}:
lib.mapAttrs (
name: cfg:
let
pkgs = nixpkgs.legacyPackages.${system};
modules = cfg.modules;
extraArgs = cfg.extraArgs or { };
terraformConfiguration = terranix.lib.terranixConfiguration {
inherit system modules;
extraArgs = {
inherit lib pkgs;
} // extraArgs;
};
tf-json = pkgs.writeShellScriptBin "default" ''
cat ${terraformConfiguration} | ${pkgs.jq}/bin/jq
'';
apply = pkgs.writeShellScriptBin "apply" ''
if [[ -e config.tf.json ]]; then rm -f config.tf.json; fi
cp ${terraformConfiguration} config.tf.json \
&& ${pkgs.terraform}/bin/terraform init \
&& ${pkgs.terraform}/bin/terraform apply
'';
plan = pkgs.writeShellScriptBin "plan" ''
if [[ -e config.tf.json ]]; then rm -f config.tf.json; fi
cp ${terraformConfiguration} config.tf.json \
&& ${pkgs.terraform}/bin/terraform init \
&& ${pkgs.terraform}/bin/terraform plan
'';
destroy = pkgs.writeShellScriptBin "destroy" ''
if [[ -e config.tf.json ]]; then rm -f config.tf.json; fi
cp ${terraformConfiguration} config.tf.json \
&& ${pkgs.terraform}/bin/terraform init \
&& ${pkgs.terraform}/bin/terraform destroy
'';
create-state-bucket = pkgs.writeShellScriptBin "create-state-bucket" ''
BUCKET_NAME=''${1:-"terraform-state-bucket"}
ACCOUNT_ID=''${2:?"Error: Cloudflare account ID required as second argument"}
R2_ENDPOINT="https://$ACCOUNT_ID.r2.cloudflarestorage.com"
echo "Creating R2 bucket $BUCKET_NAME..."
${pkgs.awscli}/bin/aws s3api create-bucket \
--bucket "$BUCKET_NAME" \
--endpoint-url "$R2_ENDPOINT"
echo "Bucket created successfully at $R2_ENDPOINT"
'';
in
tf-json // {
inherit apply plan destroy create-state-bucket;
}
) configs;
}