|
87 | 87 | config = |
88 | 88 | let |
89 | 89 | acme-dir = "/var/lib/xnode-dns/acme"; |
| 90 | + leases-dir = "/var/lib/systemd/network/dhcp-server-lease"; |
| 91 | + dns-dir = "/var/lib/xnode-dns/container"; |
90 | 92 | in |
91 | 93 | lib.mkIf cfg.enable { |
92 | 94 | users.groups.xnode-dns = { }; |
|
119 | 121 | allow net 127.0.0.1 ::1 |
120 | 122 | block |
121 | 123 | } |
122 | | - forward . 127.0.0.1:5353 |
| 124 | + auto { |
| 125 | + directory ${dns-dir} |
| 126 | + reload 10s |
| 127 | + } |
123 | 128 | } |
124 | 129 | ''; |
125 | 130 | }; |
|
172 | 177 | }; |
173 | 178 | dhcpServerConfig = { |
174 | 179 | PersistLeases = "runtime"; |
175 | | - LocalLeaseDomain = cfg.container.domain; |
176 | 180 | }; |
177 | 181 | }; |
178 | 182 | }; |
| 183 | + |
| 184 | + systemd.paths.dns-sync-container-leases = { |
| 185 | + wantedBy = [ "multi-user.target" ]; |
| 186 | + description = "Trigger sync script on lease change."; |
| 187 | + pathConfig = { |
| 188 | + PathChanged = leases-dir; |
| 189 | + }; |
| 190 | + }; |
| 191 | + systemd.services.dns-sync-container-leases = { |
| 192 | + description = "Sync container DHCP leases with the DNS server."; |
| 193 | + serviceConfig = { |
| 194 | + Restart = "on-failure"; |
| 195 | + }; |
| 196 | + path = [ |
| 197 | + pkgs.jq |
| 198 | + ]; |
| 199 | + script = '' |
| 200 | + mkdir -p ${dns-dir} |
| 201 | + setfacl -R -m g:xnode-dns:r ${dns-dir} |
| 202 | +
|
| 203 | + # Function to generate zone file content |
| 204 | + generate_zone() { |
| 205 | + local hostname="$1" |
| 206 | + local ip="$2" |
| 207 | + cat <<EOF |
| 208 | + $ORIGIN ''${hostname}.${cfg.container.domain}. |
| 209 | + @ 3600 IN SOA ${cfg.soa.nameserver}. ${ |
| 210 | + builtins.replaceStrings [ "@" ] [ "." ] cfg.soa.mailbox |
| 211 | + }. $(date +"%y%d%m%H%M") ${cfg.soa.refresh} ${cfg.soa.retry} ${cfg.soa.expire} ${cfg.soa.minimumTTL} |
| 212 | + @ 60 IN A ''${ip} |
| 213 | + EOF |
| 214 | + } |
| 215 | +
|
| 216 | + # Process each lease file |
| 217 | + for filepath in ${leases-dir}/*; do |
| 218 | + # Skip if not a file |
| 219 | + [ -f "$filepath" ] || continue |
| 220 | +
|
| 221 | + # Try to parse JSON |
| 222 | + leases=$(jq -c '.Leases[]?' "$filepath" 2>/dev/null) |
| 223 | + if [ -z "$leases" ]; then |
| 224 | + echo "Skipping $filepath: invalid JSON or no leases" |
| 225 | + continue |
| 226 | + fi |
| 227 | +
|
| 228 | + # Iterate over leases |
| 229 | + echo "$leases" | while read -r lease; do |
| 230 | + hostname=$(echo "$lease" | jq -r '.Hostname // empty') |
| 231 | + ip=$(echo "$lease" | jq -r '.Address | join(".") // empty') |
| 232 | +
|
| 233 | + if [ -n "$hostname" ] && [ -n "$ip" ]; then |
| 234 | + out_path="${dns-dir}/db.''${hostname}.${cfg.container.domain}" |
| 235 | + generate_zone "$hostname" "$ip" > "$out_path" |
| 236 | + echo "Generated zone file: $out_path" |
| 237 | + fi |
| 238 | + done |
| 239 | + done |
| 240 | + ''; |
| 241 | + }; |
179 | 242 | }; |
180 | 243 | } |
0 commit comments