Adding mkip.

This commit is contained in:
Yaro Kasear 2025-11-28 16:12:13 -06:00
parent 633a9c1856
commit 551b401efa
2 changed files with 571 additions and 0 deletions

139
flake.nix Normal file
View file

@ -0,0 +1,139 @@
{
description = "Metanix static flake: meta.nix addressing, deploy-rs, disko, nixos-anywhere";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
deploy-rs.url = "github:serokell/deploy-rs";
disko.url = "github:nix-community/disko";
nixos-anywhere.url = "github:numtide/nixos-anywhere";
};
outputs = { self, nixpkgs, deploy-rs, disko, nixos-anywhere, ... }:
let
# Default architecture if meta.nix doesn't say otherwise
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
};
lib = pkgs.lib;
# Your mkIp / mkHostsFromSpec / mkSubnetsFromSpec, etc.
# This expects: lib/addressing/default.nix
addressing = import ./lib/addressing {
inherit lib;
};
# User-provided world model.
meta = import ./meta.nix;
# Uses your rewritten mkNetworkFromSpec that understands meta.nix shape.
network = addressing.mkNetworkFromSpec meta;
# Optional: meta.systems = { hostName = { system = "..."; modules = [ ... ]; ... }; ...; }
systemsFromMeta = meta.systems or { };
in
{
#######################
# Library-style exports
#######################
lib = {
metanix = {
inherit meta network addressing;
};
};
#############################
# Per-host NixOS configs
#
# Driven by meta.systems if present. Shape example:
# meta.systems = {
# deimos = {
# system = "x86_64-linux";
# modules = [ ./hosts/deimos.nix ];
# diskoConfig = ./disko/deimos.nix; # optional
# deployHost = "deimos.kasear.net"; # optional
# deployUser = "root"; # optional
# };
# };
#############################
nixosConfigurations =
lib.mapAttrs
(name: sysCfg:
let
systemForHost = sysCfg.system or system;
pkgsForHost = import nixpkgs {
inherit systemForHost;
};
hostLib = pkgsForHost.lib;
# Optional disko module wiring if sysCfg.diskoConfig exists.
diskoModule =
if sysCfg ? diskoConfig then
{ imports = [ disko.nixosModules.disko sysCfg.diskoConfig ]; }
else
{ };
in
hostLib.nixosSystem {
system = systemForHost;
modules =
(sysCfg.modules or [ ]) ++ [
diskoModule
({ ... }: {
_module.args = {
inherit meta network addressing;
hostName = name;
};
})
];
})
systemsFromMeta;
########################################
# deploy-rs integration
#
# Builds deploy.nodes using meta.systems + addressing.
########################################
deploy = {
nodes =
lib.mapAttrs
(name: sysCfg:
let
hasNetworkHost = builtins.hasAttr name network.hosts;
hostInfo = if hasNetworkHost then network.hosts.${name} else null;
defaultHostname =
if hasNetworkHost then hostInfo.fqdn else "${name}.${meta.domain}";
in
{
hostname = sysCfg.deployHost or defaultHostname;
profiles.system = {
user = sysCfg.deployUser or "root";
path =
deploy-rs.lib.${system}.activate.nixos
self.nixosConfigurations.${name};
};
})
systemsFromMeta;
};
########################################
# deploy-rs sanity checks
########################################
checks.${system}.deploy =
deploy-rs.lib.${system}.deployChecks self.deploy;
########################################
# nixos-anywhere convenience app
########################################
apps.${system}.nixos-anywhere = {
type = "app";
program =
"${nixos-anywhere.packages.${system}.nixos-anywhere}/bin/nixos-anywhere";
};
};
}

432
lib/addressing/default.nix Normal file
View file

@ -0,0 +1,432 @@
{ lib ? import <nixpkgs/lib> { } }:
let
inherit (lib)
assertMsg
attrNames
concatMapAttrs
concatStringsSep
listToAttrs
nameValuePair
optionalAttrs
imap1
mapAttrs'
imap0
length
filter
findFirst
toLower;#
mkInitials =
name:
let
lower = toLower name; # was builtins.toLower
noApos = builtins.replaceStrings [ "'" ] [ " " ] lower;
words = filter (w: w != "") (builtins.split " +" noApos);
letters =
concatStringsSep ""
(builtins.map (w: builtins.substring 0 1 w) words);
in
if letters == "" then "loc" else builtins.substring 0 3 letters;
mkLocationCode =
name:
let
base = mkInitials name;
hash = builtins.substring 0 4 (builtins.hashString "sha256" name);
in
"${base}-${hash}";
# Manual elemIndex so we don't care what nixpkgs version you're on
elemIndex =
name: list:
let
idxs = imap0 (i: v: if v == name then i else null) list;
in
findFirst (i: i != null) null idxs;
subnetTypes = {
dmz = 0;
main = 1;
guest = 2;
iot = 3;
storage = 4;
management = 5;
infra = 6;
lab = 7;
};
roles = {
coreServer = 0;
router = 1;
infraDevice = 2;
server = 3;
workstation = 4;
thinClient = 5;
printer = 6;
nas = 7;
phone = 8;
camera = 9;
mobile = 10;
guestClient = 11;
appliance = 12;
automation = 13;
oobMgmt = 14;
hypervisor = 15;
containerHost = 16;
monitoring = 17;
logging = 18;
identity = 19;
labDevice = 20;
adminWorkstation = 21;
repoMirror = 22;
ci = 23;
media = 24;
homeAutomation = 25;
pool = 31;
};
subnetSlugs = {
dmz = "dmz";
main = "main";
guest = "guest";
iot = "iot";
storage = "storage";
management = "mgmt";
infra = "infra";
lab = "lab";
};
roleSlugs = {
coreServer = "core";
router = "router";
infraDevice = "infra-dev";
server = "server";
workstation = "ws";
thinClient = "thin";
printer = "printer";
nas = "nas";
phone = "phone";
camera = "camera";
mobile = "mobile";
guestClient = "guest";
appliance = "appliance";
automation = "automation";
oobMgmt = "oobm";
hypervisor = "hypervisor";
containerHost = "container";
monitoring = "monitoring";
logging = "logging";
identity = "identity";
labDevice = "lab-dev";
adminWorkstation = "admin-ws";
repoMirror = "mirror";
ci = "ci";
media = "media";
homeAutomation = "home-auto";
pool = "dhcp-pool";
};
thirdOctet =
{ type, role }:
let
_ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
__ = assertMsg (role >= 0 && role <= 31)
"metatron-addressing: role must be in 0..31, got ${toString role}";
in
type * 32 + role;
decodeThirdOctet =
octet:
let
_ = assertMsg (octet >= 0 && octet <= 255)
"metatron-addressing: octet must be in 0..255, got ${toString octet}";
type = builtins.div octet 32;
role = octet - (type * 32);
in
{ inherit type role; };
mkIp =
{ location, type, role, host }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (host >= 0 && host <= 255)
"metatron-addressing: host must be in 0..255, got ${toString host}";
oct3 = thirdOctet { inherit type role; };
in
"10.${toString location}.${toString oct3}.${toString host}";
mkRoleRange =
{ location, type, role }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
___ = assertMsg (role >= 0 && role <= 31)
"metatron-addressing: role must be in 0..31, got ${toString role}";
oct3 = thirdOctet { inherit type role; };
base = "10.${toString location}.${toString oct3}";
in
{
start = "${base}.0";
end = "${base}.255";
};
mkRoleRangeNamed =
{ location, typeName, roleName }:
let
_ = assertMsg (subnetTypes ? typeName)
"metatron-addressing: unknown subnet type '${typeName}'";
__ = assertMsg (roles ? roleName)
"metatron-addressing: unknown role '${roleName}'";
type = subnetTypes.${typeName};
role = roles.${roleName};
in
mkRoleRange { inherit location type role; };
mkIpNamed =
{ location, typeName, roleName, host }:
let
_ = assertMsg (subnetTypes ? typeName)
"metatron-addressing: unknown subnet type '${typeName}'";
__ = assertMsg (roles ? roleName)
"metatron-addressing: unknown role '${roleName}'";
type = subnetTypes.${typeName};
role = roles.${roleName};
in
mkIp { inherit location host type role; };
mkSubnet =
{ location, type }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
oct3 = type * 32;
in
"10.${toString location}.${toString oct3}.0/19";
mkSubnetNamed =
{ location, typeName }:
let
_ = assertMsg (subnetTypes ? typeName)
"metatron-addressing: unknown subnet type '${typeName}'";
type = subnetTypes.${typeName};
in
mkSubnet { inherit location type; };
mkVlan =
{ location, type }:
let
_ = assertMsg (location >= 0 && location <= 255)
"metatron-addressing: location must be in 0..255, got ${toString location}";
__ = assertMsg (type >= 0 && type <= 7)
"metatron-addressing: type must be in 0..7, got ${toString type}";
in
(location * 8) + type + 2;
mkVlanNamed =
{ location, typeName }:
let
_ = assertMsg (subnetTypes ? typeName)
"metatron-addressing: unknown subnet type '${typeName}'";
type = subnetTypes.${typeName};
in
mkVlan { inherit location type; };
# locationName → numeric id (0, 1, 2, …)
locationIdForSpec =
spec:
let
locations =
spec.locations or(throw "metatron-addressing: spec.locations is required");
locationNames = attrNames locations;
in
name:
let
idx = elemIndex name locationNames;
in
if idx == null then
throw "metatron-addressing: unknown location '${name}'"
else
idx;
# Just the host map: { deimos = { ip-address = "..."; fqdn = "..."; ... }; ... }
mkHostsFromSpec =
spec:
let
locations =
spec.locations or (throw "metatron-addressing: spec.locations is required");
locationId = locationIdForSpec spec;
domain =
spec.domain or (throw "metatron-addressing: spec.domain is required for FQDN generation");
in
concatMapAttrs
(locationName: locCfg:
let
loc = locationId locationName;
locCode = mkLocationCode locationName;
# Strip meta-level hints; whats left are subnets (main, dmz, etc.)
subnets =
builtins.removeAttrs locCfg [ "owner" "admins" "users" ];
in
concatMapAttrs
(typeName: subnetCfg:
let
# New shape: subnetCfg.hosts = { hostName = { role = "..."; ... }; ... }
hostsForSubnet = subnetCfg.hosts or { };
# Only treat non-underscore-prefixed keys as hosts
hostNames =
filter (hn: builtins.substring 0 1 hn != "_") (attrNames hostsForSubnet);
in
listToAttrs (imap1
(idx: hostName:
let
hostCfg = hostsForSubnet.${hostName};
roleName = hostCfg.role;
# Count how many hosts of this role exist up to *and including* this one
roleIndex =
let
prefix = lib.lists.sublist 0 idx hostNames;
in
length (filter
(hn: (hostsForSubnet.${hn}.role or null) == roleName)
prefix);
hostId = hostCfg.hostId or roleIndex;
ip = mkIpNamed {
location = loc;
inherit typeName;
roleName = hostCfg.role;
host = hostId;
};
subnetSlug =
if builtins.hasAttr typeName subnetSlugs
then subnetSlugs.${typeName}
else typeName;
roleSlug =
if builtins.hasAttr roleName roleSlugs
then roleSlugs.${roleName}
else roleName;
hostnameBase = "${roleSlug}-${builtins.toString hostId}";
hostname = hostCfg.hostname or hostnameBase;
fqdn = "${hostname}.${subnetSlug}.${locCode}.${domain}";
in
nameValuePair hostName (
{
ip-address = ip;
inherit hostname fqdn;
}
// optionalAttrs (hostCfg ? hw-address) {
inherit (hostCfg) hw-address;
}
// optionalAttrs (hostCfg ? dns) {
inherit (hostCfg) dns;
}
// optionalAttrs (hostCfg ? aliases) {
inherit (hostCfg) aliases;
}
))
hostNames))
subnets)
locations;
# Subnet map: { "home-dmz" = { cidr = "..."; vlan = ...; zone = "..."; ... }; ... }
mkSubnetsFromSpec =
spec:
let
locations =
spec.locations or (throw "metatron-addressing: spec.locations is required");
locationId = locationIdForSpec spec;
domain =
spec.domain or (throw "metatron-addressing: spec.domain is required");
in
concatMapAttrs
(locationName: locCfg:
let
loc = locationId locationName;
locCode = mkLocationCode locationName;
# Strip meta-level hints; remaining attrs are subnets
subnets =
builtins.removeAttrs locCfg [ "owner" "admins" "users" ];
in
mapAttrs'
(typeName: subnetCfg:
let
hostsForSubnet = subnetCfg.hosts or { };
cidr = mkSubnetNamed { location = loc; inherit typeName; };
subnetSlug =
if builtins.hasAttr typeName subnetSlugs
then subnetSlugs.${typeName}
else typeName;
zone = "${subnetSlug}.${locCode}.${domain}";
vlan =
# Priority: explicit vlan on subnet > old-style _vlan > computed
subnetCfg.vlan or
hostsForSubnet._vlan or
(mkVlanNamed { location = loc; inherit typeName; });
in
nameValuePair
"${locationName}-${typeName}"
{
inherit cidr locationName typeName zone vlan;
})
subnets)
locations;
# Combined view, if you want both
mkNetworkFromSpec =
spec:
let
domain =
spec.domain or (throw "metatron-addressing: spec.domain is required");
in
{
inherit domain;
hosts = mkHostsFromSpec spec;
subnets = mkSubnetsFromSpec spec;
};
in
{
inherit
subnetTypes
roles
thirdOctet
decodeThirdOctet
mkIp
mkIpNamed
mkSubnet
mkSubnetNamed
mkHostsFromSpec
mkSubnetsFromSpec
mkNetworkFromSpec
mkRoleRange
mkRoleRangeNamed;
# Shorthand; you were calling mkHosts before
mkHosts = mkNetworkFromSpec;
}