Compare commits

...

2 Commits

Author SHA1 Message Date
Berwn de7d950596 Format tree with treefmt 2026-06-16 16:53:00 +07:00
Berwn cf0d796bee Add treefmt formatter (nix fmt + flake check gate) 2026-06-16 16:53:00 +07:00
11 changed files with 185 additions and 54 deletions
+5 -4
View File
@@ -4,9 +4,9 @@
meta.domain = "cnx-network.internal"; meta.domain = "cnx-network.internal";
inventory.machines = { inventory.machines = {
control = {}; control = { };
ns1 = {}; ns1 = { };
ns2 = {}; ns2 = { };
}; };
inventory.instances = { inventory.instances = {
@@ -15,7 +15,8 @@
roles.default.tags.all = { }; roles.default.tags.all = { };
roles.default.settings.allowedKeys = { roles.default.settings.allowedKeys = {
"berwn" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIENAjhGQGraQoAjJzsomKP8GAmQPeGL1rNRNHgRcLqtT"; "berwn" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIENAjhGQGraQoAjJzsomKP8GAmQPeGL1rNRNHgRcLqtT";
"kurogeek" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEcZ/p1Ofa9liwIzPWzNtONhJ7+FUWd2lCz33r81t8+w kurogeek@kurogeek"; "kurogeek" =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEcZ/p1Ofa9liwIzPWzNtONhJ7+FUWd2lCz33r81t8+w kurogeek@kurogeek";
}; };
}; };
Generated
+22 -1
View File
@@ -166,7 +166,8 @@
"nixpkgs": [ "nixpkgs": [
"clan-core", "clan-core",
"nixpkgs" "nixpkgs"
] ],
"treefmt-nix": "treefmt-nix_2"
} }
}, },
"sops-nix": { "sops-nix": {
@@ -225,6 +226,26 @@
"repo": "treefmt-nix", "repo": "treefmt-nix",
"type": "github" "type": "github"
} }
},
"treefmt-nix_2": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1780220602,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "db947814a175b7ca6ded66e21383d938df01c227",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",
+31 -17
View File
@@ -1,6 +1,8 @@
{ {
inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/25.11.tar.gz"; inputs.clan-core.url = "https://git.clan.lol/clan/clan-core/archive/25.11.tar.gz";
inputs.nixpkgs.follows = "clan-core/nixpkgs"; inputs.nixpkgs.follows = "clan-core/nixpkgs";
inputs.treefmt-nix.url = "github:numtide/treefmt-nix";
inputs.treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
outputs = outputs =
{ {
@@ -26,29 +28,41 @@
# }; # };
# overlays = []; # overlays = [];
# }; # };
secrets.age.plugins = [ secrets.age.plugins = [
"age-plugin-yubikey" "age-plugin-yubikey"
"age-plugin-fido2-hmac" "age-plugin-fido2-hmac"
]; ];
}; };
systems = [
"x86_64-linux"
"aarch64-linux"
"aarch64-darwin"
"x86_64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs systems;
pkgsFor = system: clan-core.inputs.nixpkgs.legacyPackages.${system};
treefmtFor = system: inputs.treefmt-nix.lib.evalModule (pkgsFor system) ./fmt.nix;
in in
{ {
inherit (clan.config) nixosConfigurations nixosModules clanInternals; inherit (clan.config) nixosConfigurations nixosModules clanInternals;
clan = clan.config; clan = clan.config;
# `nix fmt` and the `nix flake check` formatting gate.
formatter = forAllSystems (system: (treefmtFor system).config.build.wrapper);
checks = forAllSystems (system: {
formatting = (treefmtFor system).config.build.check self;
});
# Add the Clan cli tool to the dev shell. # Add the Clan cli tool to the dev shell.
# Use "nix develop" to enter the dev shell. # Use "nix develop" to enter the dev shell.
devShells = devShells = forAllSystems (system: {
nixpkgs.lib.genAttrs default = (pkgsFor system).mkShell {
[ packages = [
"x86_64-linux" clan-core.packages.${system}.clan-cli
"aarch64-linux" (treefmtFor system).config.build.wrapper
"aarch64-darwin" ];
"x86_64-darwin" };
] });
(system: {
default = clan-core.inputs.nixpkgs.legacyPackages.${system}.mkShell {
packages = [ clan-core.packages.${system}.clan-cli ];
};
});
}; };
} }
+32
View File
@@ -0,0 +1,32 @@
# treefmt config, evaluated per-system in flake.nix and exposed as
# `nix fmt` (the formatter) plus a `nix flake check` formatting gate.
{ ... }:
{
projectRootFile = "flake.nix";
programs = {
nixfmt.enable = true;
prettier.enable = true;
yamlfmt.enable = true;
shfmt.enable = true;
};
settings = {
on-unmatched = "fatal";
global.excludes = [
# Secrets and clan-managed state — never reformat.
"sops/*"
"vars/*"
"inventory.json"
# Generated — don't reformat (regeneration would churn the diff).
"*facter.json"
# No formatter, or reformatting would corrupt them.
"*.zone" # Knot zone files
"flake.lock"
".envrc"
".gitignore"
];
};
}
+4 -1
View File
@@ -70,6 +70,9 @@ in
"dnssec-signing" = true; "dnssec-signing" = true;
"dnssec-policy" = "cnx"; "dnssec-policy" = "cnx";
notify = [ "ns2" ]; notify = [ "ns2" ];
acl = [ "acl_ns2" "acl_acme" ]; # ns2 transfers; acme_ddns key does DNS-01 updates acl = [
"acl_ns2"
"acl_acme"
]; # ns2 transfers; acme_ddns key does DNS-01 updates
}) domains; }) domains;
} }
+38 -6
View File
@@ -33,17 +33,49 @@ in
# Including the key via keyFiles keeps the secret out of the Nix store. # Including the key via keyFiles keeps the secret out of the Nix store.
keyFiles = [ config.clan.core.vars.generators.dns-tsig.files."tsig.conf".path ]; keyFiles = [ config.clan.core.vars.generators.dns-tsig.files."tsig.conf".path ];
settings = { settings = {
server.listen = [ "0.0.0.0@53" "::@53" ]; server.listen = [
log = [ { target = "syslog"; any = "info"; } ]; "0.0.0.0@53"
"::@53"
];
log = [
{
target = "syslog";
any = "info";
}
];
remote = [ remote = [
{ id = "ns1"; address = [ ns1zt ]; key = "cnx_xfr"; } {
{ id = "ns2"; address = [ ns2zt ]; key = "cnx_xfr"; } id = "ns1";
address = [ ns1zt ];
key = "cnx_xfr";
}
{
id = "ns2";
address = [ ns2zt ];
key = "cnx_xfr";
}
]; ];
acl = [ acl = [
{ id = "acl_ns1"; address = [ ns1zt ]; key = "cnx_xfr"; action = [ "transfer" "notify" ]; } {
{ id = "acl_ns2"; address = [ ns2zt ]; key = "cnx_xfr"; action = [ "transfer" "notify" ]; } id = "acl_ns1";
address = [ ns1zt ];
key = "cnx_xfr";
action = [
"transfer"
"notify"
];
}
{
id = "acl_ns2";
address = [ ns2zt ];
key = "cnx_xfr";
action = [
"transfer"
"notify"
];
}
]; ];
}; };
}; };
+22 -4
View File
@@ -4,7 +4,10 @@
# Public SSH (22) is intentionally absent: admin access rides the ZeroTier mesh # Public SSH (22) is intentionally absent: admin access rides the ZeroTier mesh
# (inside UDP 9993), with emergency-access as the console fallback. # (inside UDP 9993), with emergency-access as the console fallback.
let let
world = [ "0.0.0.0/0" "::/0" ]; world = [
"0.0.0.0/0"
"::/0"
];
zerotier = { zerotier = {
direction = "in"; direction = "in";
@@ -22,14 +25,29 @@ let
}; };
dnsRules = [ dnsRules = [
{ direction = "in"; protocol = "udp"; port = "53"; source_ips = world; description = "DNS (UDP)"; } {
{ direction = "in"; protocol = "tcp"; port = "53"; source_ips = world; description = "DNS (TCP)"; } direction = "in";
protocol = "udp";
port = "53";
source_ips = world;
description = "DNS (UDP)";
}
{
direction = "in";
protocol = "tcp";
port = "53";
source_ips = world;
description = "DNS (TCP)";
}
zerotier zerotier
ping ping
]; ];
in in
{ {
"clan-control" = [ zerotier ping ]; "clan-control" = [
zerotier
ping
];
"clan-ns1" = dnsRules; "clan-ns1" = dnsRules;
"clan-ns2" = dnsRules; "clan-ns2" = dnsRules;
} }
+28 -18
View File
@@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
let let
cfg = config.cnx.hetznerFirewall; cfg = config.cnx.hetznerFirewall;
in in
@@ -29,8 +34,7 @@ in
tokenFile = lib.mkOption { tokenFile = lib.mkOption {
type = lib.types.path; type = lib.types.path;
default = config.clan.core.vars.generators.hetzner-firewall.files.token.path; default = config.clan.core.vars.generators.hetzner-firewall.files.token.path;
defaultText = lib.literalExpression defaultText = lib.literalExpression "config.clan.core.vars.generators.hetzner-firewall.files.token.path";
"config.clan.core.vars.generators.hetzner-firewall.files.token.path";
description = "File holding the Hetzner Cloud API token (Read & Write)."; description = "File holding the Hetzner Cloud API token (Read & Write).";
}; };
}; };
@@ -48,7 +52,11 @@ in
description = "Sync Hetzner Cloud firewall rules from Nix config"; description = "Sync Hetzner Cloud firewall rules from Nix config";
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wants = [ "network-online.target" ]; wants = [ "network-online.target" ];
path = [ pkgs.curl pkgs.jq pkgs.coreutils ]; path = [
pkgs.curl
pkgs.jq
pkgs.coreutils
];
environment.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; environment.SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt";
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
@@ -71,20 +79,22 @@ in
curl -fsS -H @"$hdr" -H "Content-Type: application/json" "$@" curl -fsS -H @"$hdr" -H "Content-Type: application/json" "$@"
} }
${lib.concatStringsSep "\n" (lib.mapAttrsToList (fwName: rules: '' ${lib.concatStringsSep "\n" (
name=${lib.escapeShellArg fwName} lib.mapAttrsToList (fwName: rules: ''
rules=${lib.escapeShellArg (builtins.toJSON rules)} name=${lib.escapeShellArg fwName}
id="$(hapi "$api/firewalls?name=$name" | jq -r '.firewalls[0].id // empty')" rules=${lib.escapeShellArg (builtins.toJSON rules)}
if [ -z "$id" ]; then id="$(hapi "$api/firewalls?name=$name" | jq -r '.firewalls[0].id // empty')"
echo "hetzner-firewall: creating $name" if [ -z "$id" ]; then
jq -n --arg name "$name" --argjson rules "$rules" '{name: $name, rules: $rules}' \ echo "hetzner-firewall: creating $name"
| hapi -X POST --data-binary @- "$api/firewalls" > /dev/null jq -n --arg name "$name" --argjson rules "$rules" '{name: $name, rules: $rules}' \
else | hapi -X POST --data-binary @- "$api/firewalls" > /dev/null
echo "hetzner-firewall: setting rules on $name (id $id)" else
jq -n --argjson rules "$rules" '{rules: $rules}' \ echo "hetzner-firewall: setting rules on $name (id $id)"
| hapi -X POST --data-binary @- "$api/firewalls/$id/actions/set_rules" > /dev/null jq -n --argjson rules "$rules" '{rules: $rules}' \
fi | hapi -X POST --data-binary @- "$api/firewalls/$id/actions/set_rules" > /dev/null
'') cfg.firewalls)} fi
'') cfg.firewalls
)}
''; '';
}; };