diff --git a/.gitea/workflows/build-images.yml b/.gitea/workflows/build-images.yml index 0d5f042..c5eae6f 100644 --- a/.gitea/workflows/build-images.yml +++ b/.gitea/workflows/build-images.yml @@ -17,9 +17,6 @@ jobs: - name: Install Skopeo run: nix shell nixpkgs#skopeo -c echo "skopeo installed" - - name: Build x86_64 Image - run: nix build .#dockerImages.x86_64-linux.node --out-link ./image-x86_64.tar.gz - - name: Build aarch64 Image run: nix build .#dockerImages.aarch64-linux.node --out-link ./image-aarch64.tar.gz @@ -28,24 +25,16 @@ jobs: env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # Push x86_64 image - skopeo copy \ - --insecure-policy \ - --dest-creds "${{ github.actor }}:${GITEA_TOKEN}" \ - "docker-archive:./image-x86_64.tar.gz" \ - "docker://${{ github.server_url }}/${{ github.repository }}/node:latest-amd64" - - # Push aarch64 image + # Push aarch64 image skopeo copy \ --insecure-policy \ --dest-creds "${{ github.actor }}:${GITEA_TOKEN}" \ "docker-archive:./image-aarch64.tar.gz" \ "docker://${{ github.server_url }}/${{ github.repository }}/node:latest-arm64" - - # Create and push manifest for multi-arch + + # Create and push manifest for arm64 skopeo manifest create \ --insecure-policy \ --dest-creds "${{ github.actor }}:${GITEA_TOKEN}" \ "docker://${{ github.server_url }}/${{ github.repository }}/node:latest" \ - "docker://${{ github.server_url }}/${{ github.repository }}/node:latest-amd64" \ "docker://${{ github.server_url }}/${{ github.repository }}/node:latest-arm64" diff --git a/modules/services/dhcp-dns-sync/default.nix b/modules/services/dhcp-dns-sync/default.nix index 4e6bd5a..5443edf 100644 --- a/modules/services/dhcp-dns-sync/default.nix +++ b/modules/services/dhcp-dns-sync/default.nix @@ -41,12 +41,6 @@ in description = "Path to write Unbound include file"; }; - leasesJsonPath = lib.mkOption { - type = lib.types.str; - default = "/var/lib/router/leases.json"; - description = "Path to write leases JSON file"; - }; - interval = lib.mkOption { type = lib.types.str; default = "30s"; @@ -67,7 +61,6 @@ in # Ensure directories and files exist with proper permissions systemd.tmpfiles.rules = [ "d /var/lib/unbound 0755 unbound unbound -" - "d /var/lib/router 0755 dhcp-dns-sync dhcp-dns-sync -" "f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync dhcp-dns-sync -" ]; @@ -99,22 +92,16 @@ in # Allow access to networkctl via D-Bus SupplementaryGroups = [ "systemd-network" ]; # Read/write paths - ReadWritePaths = [ - "/var/lib/unbound" - "/var/lib/router" + ReadWritePaths = [ "/var/lib/unbound" ]; + ExecStart = lib.concatStringsSep " " [ + (lib.getExe dhcp-leases-to-unbound) + "-i ${cfg.interface}" + "-d ${cfg.domain}" + "-o ${cfg.unboundConfigPath}" + "--networkctl ${lib.getExe' pkgs.systemd "networkctl"}" + "--unbound-control ${lib.getExe' pkgs.unbound "unbound-control"}" ]; - # Execute paths - ExecPaths = [ "/run/current-system/sw/bin" ]; }; - - script = '' - ${lib.getExe dhcp-leases-to-unbound} \ - -i ${cfg.interface} \ - -d ${cfg.domain} \ - -o ${cfg.unboundConfigPath} \ - --leases-json ${cfg.leasesJsonPath} \ - --networkctl ${lib.getExe' pkgs.systemd "networkctl"} - ''; }; # Systemd timer diff --git a/modules/services/dhcp-dns-sync/dhcp-leases-to-unbound.cr b/modules/services/dhcp-dns-sync/dhcp-leases-to-unbound.cr index 6101e7a..c5f46bc 100644 --- a/modules/services/dhcp-dns-sync/dhcp-leases-to-unbound.cr +++ b/modules/services/dhcp-dns-sync/dhcp-leases-to-unbound.cr @@ -8,23 +8,34 @@ struct Lease include JSON::Serializable @[JSON::Field(key: "Address")] - property address : String + property address_bytes : Array(Int32) - @[JSON::Field(key: "MACAddress")] - property mac_address : String? + @[JSON::Field(key: "HardwareAddress")] + property hardware_address : Array(Int32)? @[JSON::Field(key: "Hostname")] property hostname : String? - @[JSON::Field(key: "Lifetime")] - property lifetime : Int64? + @[JSON::Field(key: "ExpirationUSec")] + property expiration_usec : Int64? + + def address : String + address_bytes.join('.') + end +end + +struct DHCPServer + include JSON::Serializable + + @[JSON::Field(key: "Leases")] + property leases : Array(Lease)? end struct NetworkStatus include JSON::Serializable - @[JSON::Field(key: "OfferedDHCPLeases")] - property offered_dhcp_leases : Array(Lease)? + @[JSON::Field(key: "DHCPServer")] + property dhcp_server : DHCPServer? end def sanitize_hostname(hostname : String) : String? @@ -86,7 +97,7 @@ def get_leases(interface : String, networkctl_path : String? = nil) : Array(Leas raise "networkctl failed (exit code #{$?.exit_status}): #{output}" unless $?.success? status = NetworkStatus.from_json(output) - status.offered_dhcp_leases || [] of Lease + status.dhcp_server.try(&.leases) || [] of Lease end def write_if_changed(content : String, path : String) : Bool @@ -107,8 +118,8 @@ end interface = "koti" domain = "home.arpa" output_path = "/var/lib/unbound/dhcp-hosts.conf" -leases_json_path = "/var/lib/router/leases.json" networkctl_path : String? = nil +unbound_control_path : String? = nil OptionParser.parse do |parser| parser.banner = "Usage: dhcp-leases-to-unbound [options]" @@ -125,20 +136,30 @@ OptionParser.parse do |parser| output_path = o end - parser.on("--leases-json PATH", "Output path for leases JSON (default: /var/lib/router/leases.json)") do |path| - leases_json_path = path - end - parser.on("--networkctl PATH", "Path to networkctl binary (default: networkctl from PATH)") do |path| networkctl_path = path end + parser.on("--unbound-control PATH", "Path to unbound-control binary (default: unbound-control from PATH)") do |path| + unbound_control_path = path + end + parser.on("-h", "--help", "Show this help") do puts parser exit end end +def reload_unbound(unbound_control_path : String?) + cmd = unbound_control_path ? "#{unbound_control_path} reload" : "unbound-control reload" + puts "Reloading Unbound..." + result = system(cmd) + unless result + # Fallback to systemctl + system("systemctl reload unbound") + end +end + begin # Get leases from networkd leases = get_leases(interface, networkctl_path) @@ -149,18 +170,9 @@ begin # Write Unbound config if changed changed = write_if_changed(config, output_path) - # Also write leases JSON for dashboard - FileUtils.mkdir_p(File.dirname(leases_json_path)) - File.write(leases_json_path, leases.to_json) - # Reload Unbound if config changed if changed - puts "DHCP leases updated, reloading Unbound..." - result = system("unbound-control reload") - unless result - # Fallback to systemctl if unbound-control fails - system("systemctl reload unbound") - end + reload_unbound(unbound_control_path) else puts "No DHCP lease changes detected." end diff --git a/modules/services/network-status.nix b/modules/services/network-status.nix new file mode 100644 index 0000000..e3fa3f7 --- /dev/null +++ b/modules/services/network-status.nix @@ -0,0 +1,64 @@ +{ + lib, + config, + pkgs, + ... +}: +let + cfg = config.modules.services.network-status; +in +{ + options.modules.services.network-status = { + enable = lib.mkEnableOption "Enable network status socket service"; + + port = lib.mkOption { + type = lib.types.int; + default = 8473; + description = "TCP port to listen on for network status requests"; + }; + + interface = lib.mkOption { + type = lib.types.str; + default = "koti"; + description = "Network interface to allow access from"; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.network-status = { + isSystemUser = true; + group = "network-status"; + description = "Network status socket service user"; + }; + + users.groups.network-status = { }; + + systemd.sockets.network-status = { + description = "Network Status Socket"; + wantedBy = [ "sockets.target" ]; + socketConfig = { + ListenStream = cfg.port; + Accept = true; + }; + }; + + systemd.services."network-status@" = { + description = "Network Status Service"; + serviceConfig = { + Type = "simple"; + User = "network-status"; + Group = "systemd-network"; + SupplementaryGroups = [ "systemd-network" ]; + StandardOutput = "socket"; + StandardInput = "socket"; + ExecStart = lib.concatStringsSep " " [ + (lib.getExe' pkgs.systemd "networkctl") + "status" + "--json=short" + ]; + }; + }; + + modules.firewall.interfaces.${cfg.interface} = lib.mkDefault [ "network-status" ]; + }; +}