5171a10079
Rebase the fork branches and refresh nixpkgs, home-manager, and the approved ancillary inputs. Update kernel and release metadata, adapt removed package and Home Assistant options, and keep Determinate Nix on its tested nixpkgs revision to avoid duplicate Boost patches. Retire Sharry and its public endpoint because copyparty replaces it. Document the GitHub mirror gate and require real devshell and system builds in the upgrade validation workflow.
291 lines
9.8 KiB
Nix
291 lines
9.8 KiB
Nix
{ inputs, lib }:
|
|
let
|
|
inherit (builtins) length match elemAt filter replaceStrings substring;
|
|
inherit (lib)
|
|
genAttrs mapAttrsToList filterAttrsRecursive nameValuePair types
|
|
mkOption mkOverride mkForce mkIf mergeEqualOption optional
|
|
showWarnings concatStringsSep flatten unique optionalAttrs
|
|
mkBefore toLower splitString last;
|
|
inherit (lib.flake) defaultSystems;
|
|
in
|
|
rec {
|
|
pow =
|
|
let
|
|
pow' = base: exponent: value:
|
|
# FIXME: It will silently overflow on values > 2**62 :(
|
|
# The value will become negative or zero in this case
|
|
if exponent == 0
|
|
then 1
|
|
else if exponent <= 1
|
|
then value
|
|
else (pow' base (exponent - 1) (value * base));
|
|
in base: exponent: pow' base exponent base;
|
|
|
|
attrsToNVList = mapAttrsToList nameValuePair;
|
|
|
|
inherit ((import "${inputs.libnetRepo}/lib/netu.nix" { inherit lib; }).lib) net;
|
|
dns = import ./dns.nix { inherit lib; };
|
|
c = import ./constants.nix { inherit lib; };
|
|
|
|
# Yoinked from nixpkgs/nixos/modules/services/networking/nat.nix
|
|
isIPv6 = ip: length (lib.splitString ":" ip) > 2;
|
|
parseIPPort = ipp:
|
|
let
|
|
v6 = isIPv6 ipp;
|
|
matchIP = if v6 then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
|
|
m = match "${matchIP}:(.+)" ipp;
|
|
checked = v: if m == null then throw "bad ip:ports `${ipp}'" else v;
|
|
in
|
|
{
|
|
inherit v6;
|
|
ip = checked (elemAt m 0);
|
|
ports = checked (elemAt m 1);
|
|
};
|
|
|
|
netBroadcast = net': net.cidr.host ((pow 2 (net.cidr.size net')) - 1) net';
|
|
|
|
mkDefaultSystemsPkgs = path: args': genAttrs defaultSystems (system: import path ((args' system) // { inherit system; }));
|
|
mkApp = program: { type = "app"; inherit program; };
|
|
mkShellApp = pkgs: name: text: mkApp (pkgs.writeShellScript name text).outPath;
|
|
mkShellApp' = pkgs: args:
|
|
let
|
|
app = pkgs.writeShellApplication args;
|
|
in mkApp "${app}/bin/${app.meta.mainProgram}";
|
|
flakePackageOverlay' = flake: pkg: system: (final: prev:
|
|
let
|
|
pkg' = if pkg != null then flake.packages.${system}.${pkg} else flake.packages.${system}.default;
|
|
name = if pkg != null then pkg else pkg'.name;
|
|
in
|
|
{
|
|
${name} = pkg';
|
|
});
|
|
flakePackageOverlay = flake: flakePackageOverlay' flake null;
|
|
|
|
inlineModule' = path: module: {
|
|
_file = path;
|
|
imports = [ module ];
|
|
};
|
|
inlineModule = path: inlineModule' path (import path);
|
|
|
|
# Merge together modules which are defined as functions with others that aren't
|
|
naiveModule = with types; (coercedTo (attrsOf anything) (conf: { ... }: conf) (functionTo (attrsOf anything)));
|
|
|
|
mkOpt = type: default: mkOption { inherit type default; };
|
|
mkOpt' = type: default: description: mkOption { inherit type default description; };
|
|
mkBoolOpt = default: mkOption {
|
|
inherit default;
|
|
type = types.bool;
|
|
example = true;
|
|
};
|
|
mkBoolOpt' = default: description: mkOption {
|
|
inherit default description;
|
|
type = types.bool;
|
|
example = true;
|
|
};
|
|
nullOrOpt' = type: description: mkOpt' (types.nullOr type) null description;
|
|
dummyOption = mkOption { };
|
|
|
|
# Slightly higher precedence than mkDefault
|
|
mkDefault' = mkOverride 900;
|
|
mkVMOverride' = mkOverride 9;
|
|
|
|
# This is shocking...
|
|
duplicates = l:
|
|
flatten
|
|
(map
|
|
(e:
|
|
optional
|
|
((length (filter (e2: e2 == e) l)) > 1)
|
|
e)
|
|
(unique l));
|
|
|
|
applyAssertions = config: res:
|
|
let
|
|
failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
|
|
in
|
|
if failedAssertions != []
|
|
then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
|
|
else showWarnings config.warnings res;
|
|
|
|
homeStateVersion' = hmBranch: "23.05";
|
|
homeStateVersion = hmBranch: {
|
|
# The flake passes a default setting, but we don't care about that
|
|
home.stateVersion = mkForce (homeStateVersion' hmBranch);
|
|
};
|
|
|
|
commonOpts = with types; {
|
|
moduleType = mkOptionType {
|
|
name = "Inline flake-exportable module";
|
|
merge = loc: defs: inlineModule (mergeEqualOption loc defs);
|
|
};
|
|
|
|
system = mkOpt' (enum defaultSystems) null "Nix-style system string.";
|
|
nixpkgs = mkOpt' (enum [ "unstable" "stable" "mine" "mine-stable" ]) "unstable" "Branch of nixpkgs to use.";
|
|
home-manager = mkOpt' (enum [ "unstable" "stable" "mine" "mine-stable" ]) "unstable" "Branch of home-manager to use.";
|
|
};
|
|
|
|
nft = rec {
|
|
ipEscape = replaceStrings ["." ":"] ["-" "-"];
|
|
natFilterChain = ip: "filter-fwd-${ipEscape ip}";
|
|
dnatChain = ip: "fwd-${ipEscape ip}";
|
|
};
|
|
|
|
mkVLAN = name: vid: {
|
|
"25-${name}" = {
|
|
netdevConfig = {
|
|
Name = name;
|
|
Kind = "vlan";
|
|
};
|
|
vlanConfig.Id = vid;
|
|
};
|
|
};
|
|
networkdAssignment = iface: a: {
|
|
matchConfig.Name = iface;
|
|
address =
|
|
[ "${a.ipv4.address}/${toString a.ipv4.mask}" ] ++
|
|
(optional (a.ipv6.address != null && a.ipv6.iid == null) "${a.ipv6.address}/${toString a.ipv6.mask}");
|
|
gateway =
|
|
(optional (a.ipv4.gateway != null) a.ipv4.gateway) ++
|
|
(optional (a.ipv6.gateway != null) a.ipv6.gateway);
|
|
networkConfig = {
|
|
IPv6AcceptRA = a.ipv6.gateway == null || a.ipv6.iid != null;
|
|
# These are servers: temporary (privacy) addresses only rotate our stable source
|
|
# address out from under long-lived connected sockets (e.g. nginx's resolver, which
|
|
# wedges permanently when the address it bound to expires).
|
|
IPv6PrivacyExtensions = "no";
|
|
# NOTE: LLDP emission / reception is ignored on bridge interfaces
|
|
LLDP = true;
|
|
EmitLLDP = "customer-bridge";
|
|
};
|
|
linkConfig = optionalAttrs (a.mtu != null) {
|
|
MTUBytes = toString a.mtu;
|
|
};
|
|
ipv6AcceptRAConfig = {
|
|
Token = mkIf (a.ipv6.iid != null) "static:${a.ipv6.iid}";
|
|
UseDNS = true;
|
|
UseDomains = true;
|
|
};
|
|
};
|
|
dockerNetAssignment =
|
|
assignments: name: with assignments."${name}".internal; "ip=${ipv4.address},ip=${ipv6.address}";
|
|
|
|
systemdAwaitPostgres = pkg: host: {
|
|
after = [ "systemd-networkd-wait-online.service" ];
|
|
preStart = mkBefore ''
|
|
until ${pkg}/bin/pg_isready -h ${host}; do
|
|
sleep 0.5
|
|
done
|
|
'';
|
|
};
|
|
|
|
vm = rec {
|
|
lvmDisk'' = name: vg: lv: {
|
|
inherit name;
|
|
backend = {
|
|
driver = "host_device";
|
|
filename = "/dev/${vg}/${lv}";
|
|
# It appears this needs to be set on the backend _and_ the format
|
|
discard = "unmap";
|
|
};
|
|
format = {
|
|
driver = "raw";
|
|
discard = "unmap";
|
|
};
|
|
frontend = "virtio-blk";
|
|
};
|
|
lvmDisk' = vg: lv: lvmDisk'' lv vg lv;
|
|
lvmDisk = lvmDisk' "main";
|
|
disk = vm: lv: lvmDisk'' lv "main" "vm-${vm}-${lv}";
|
|
};
|
|
|
|
deploy-rs =
|
|
with types;
|
|
let
|
|
globalOpts = {
|
|
sshUser = nullOrOpt' str "Username deploy-rs will deploy with.";
|
|
user = nullOrOpt' str "Username deploy-rs will deploy with.";
|
|
sudo = nullOrOpt' str "Command to elevate privileges with (used if the deployment user != profile user).";
|
|
sshOpts = mkOpt' (listOf str) [ ]
|
|
"Options deploy-rs will pass to ssh. Note: overriding at a lower level _merges_ options.";
|
|
fastConnection = nullOrOpt' bool "Whether to copy the whole closure instead of using substitution.";
|
|
autoRollback = nullOrOpt' bool "Whether to roll back the profile if activation fails.";
|
|
magicRollback = nullOrOpt' bool "Whether to roll back the profile if connectivity to the deployer is lost.";
|
|
confirmTimeout = nullOrOpt' ints.u16 "Timeout for confirming activation succeeded.";
|
|
tempPath = nullOrOpt' str "Path that deploy-rs will use for temporary files.";
|
|
};
|
|
|
|
profileOpts = {
|
|
path = mkOpt' package "" "Derivation to build (should include activation script).";
|
|
profilePath = nullOrOpt' str "Path to profile location";
|
|
} // globalOpts;
|
|
profileType = submodule { options = profileOpts; };
|
|
|
|
nodeOpts = {
|
|
hostname = mkOpt' str "" "Hostname deploy-rs will connect to.";
|
|
profilesOrder = nullOrOpt' (listOf str)
|
|
"Order to deploy profiles in (remainder will be deployed in arbitrary order).";
|
|
profiles = mkOpt' (attrsOf profileType) { } "Profiles to deploy.";
|
|
} // globalOpts;
|
|
nodeType = submodule { options = nodeOpts; };
|
|
|
|
deployOpts = {
|
|
nodes = mkOption {
|
|
type = attrsOf nodeType;
|
|
default = { };
|
|
internal = true;
|
|
description = "deploy-rs node configurations.";
|
|
};
|
|
} // globalOpts;
|
|
deployType = submodule { options = deployOpts; };
|
|
in
|
|
{
|
|
inherit globalOpts;
|
|
node = mkOpt' nodeType { } "deploy-rs node configuration.";
|
|
deploy = mkOpt' deployType { } "deploy-rs configuration.";
|
|
|
|
filterOpts = filterAttrsRecursive (_: v: v != null);
|
|
};
|
|
|
|
versionOverlay = { self, pkgsFlake }: final: prev:
|
|
let
|
|
date = substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101");
|
|
revCode = flake: flake.shortRev or "dirty";
|
|
in
|
|
{
|
|
trivial = prev.trivial // {
|
|
release = "26.08:u-${prev.trivial.release}";
|
|
codeName = "Irritating";
|
|
revisionWithDefault = default: self.rev or default;
|
|
versionSuffix = ".${date}.${revCode self}:u-${revCode pkgsFlake}";
|
|
};
|
|
};
|
|
upstreamRelease = last (splitString "-" lib.trivial.release);
|
|
|
|
netbootKeaClientClasses = { tftpIP, hostname, systems }:
|
|
let
|
|
testIPXE = "substring(option[user-class].hex, 0, 4) == 'iPXE'";
|
|
in
|
|
(mapAttrsToList (name: mac: {
|
|
name = "nixos-${name}";
|
|
test = "(${testIPXE}) and (hexstring(pkt4.mac, ':') == '${toLower mac}')";
|
|
next-server = tftpIP;
|
|
server-hostname = hostname;
|
|
boot-file-name = "http://${hostname}/systems/${name}/menu.ipxe";
|
|
}) systems) ++ [
|
|
{
|
|
name = "ipxe";
|
|
test = testIPXE;
|
|
next-server = tftpIP;
|
|
server-hostname = hostname;
|
|
boot-file-name = "http://${hostname}/boot.ipxe";
|
|
}
|
|
{
|
|
name = "efi-x86_64";
|
|
test = "option[client-system].hex == 0x0007";
|
|
next-server = tftpIP;
|
|
server-hostname = hostname;
|
|
boot-file-name = "ipxe-x86_64.efi";
|
|
}
|
|
];
|
|
}
|