256 lines
7.4 KiB
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;
|
|
}
|