From d84330d67c0af885c839dbf644bb073e0ccf8362 Mon Sep 17 00:00:00 2001 From: Jack O'Sullivan Date: Thu, 7 Sep 2023 00:05:03 +0100 Subject: [PATCH] nixos/kelder: Finish upgrade (add ddclient replacement) --- .../kelder/containers/spoder/default.nix | 2 +- nixos/boxes/kelder/default.nix | 29 +++++++++++-- nixos/boxes/kelder/dns_update.py | 43 +++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100755 nixos/boxes/kelder/dns_update.py diff --git a/nixos/boxes/kelder/containers/spoder/default.nix b/nixos/boxes/kelder/containers/spoder/default.nix index 092b8a7..8f2b3a2 100644 --- a/nixos/boxes/kelder/containers/spoder/default.nix +++ b/nixos/boxes/kelder/containers/spoder/default.nix @@ -91,7 +91,7 @@ in nextcloud = { enable = true; - package = pkgs.nextcloud26; + package = pkgs.nextcloud27; datadir = "/mnt/storage/nextcloud"; hostName = "cloud.${lib.my.kelder.domain}"; https = true; diff --git a/nixos/boxes/kelder/default.nix b/nixos/boxes/kelder/default.nix index 66a6a35..e3d6f84 100644 --- a/nixos/boxes/kelder/default.nix +++ b/nixos/boxes/kelder/default.nix @@ -119,8 +119,6 @@ in enable = true; }; - # TODO: replace ddclient with script to update local IP - samba = { enable = true; enableNmbd = true; @@ -240,6 +238,29 @@ in # For hardware acceleration in Jellyfin "char-drm rw" ]; + ddns-update = { + description = "DNS update script"; + after = [ "network.target" ]; + path = [ + (pkgs.python3.withPackages (ps: [ ps.cloudflare ])) + pkgs.iproute2 + ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = ''${./dns_update.py} -k ${config.age.secrets."kelder/ddclient-cloudflare.key".path} hentai.engineer kelder-local.hentai.engineer et1g0''; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; + timers = { + ddns-update = { + description = "Periodically update DNS"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "10min"; + OnUnitInactiveSec = "10min"; + }; + }; }; }; @@ -250,7 +271,7 @@ in }; #deploy.generate.system.mode = "boot"; - deploy.node.hostname = "10.16.9.21"; + #deploy.node.hostname = "10.16.9.21"; secrets = { key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOFvUdJshXkqmchEgkZDn5rgtZ1NO9vbd6Px+S6YioWi"; files = { @@ -290,7 +311,7 @@ in chain prerouting { type filter hook prerouting priority mangle; policy accept; ip daddr ${assignments.estuary.ipv4.address} ct state new ct mark set ${toString dnatMark} - ip saddr ${lib.my.kelder.prefixes.all.v4} ct mark != 0 meta mark set ct mark log + ip saddr ${lib.my.kelder.prefixes.all.v4} ct mark != 0 meta mark set ct mark } chain output { type filter hook output priority mangle; policy accept; diff --git a/nixos/boxes/kelder/dns_update.py b/nixos/boxes/kelder/dns_update.py new file mode 100755 index 0000000..3b5cbff --- /dev/null +++ b/nixos/boxes/kelder/dns_update.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import argparse +import json +import subprocess + +import CloudFlare + +def main(): + parser = argparse.ArgumentParser(description='Cloudflare DNS update script') + parser.add_argument('-k', '--api-token-file', help='Cloudflare API token file') + parser.add_argument('zone', help='Cloudflare Zone') + parser.add_argument('record', help='Cloudflare record name') + parser.add_argument('iface', help='Network interface to grab IP from') + args = parser.parse_args() + + cf_token = None + if args.api_token_file: + with open(args.api_token_file) as f: + cf_token = f.readline().strip() + + cf = CloudFlare.CloudFlare(token=cf_token) + zones = cf.zones.get(params={'name': args.zone}) + assert zones, f'Zone {args.zone} not found' + records = cf.zones.dns_records.get(zones[0]['id'], params={'name': args.record}) + assert records, f'Record {args.record} not found in zone {args.zone}' + + + ip_info = json.loads(subprocess.check_output( + ['ip', '-j', 'addr', 'show', 'dev', args.iface], encoding='utf8')) + for a_info in ip_info[0]['addr_info']: + if a_info['family'] == 'inet' and a_info['scope'] == 'global': + address = a_info['local'] + break + else: + assert False, f'No usable IP address found on interface {args.iface}' + + print(f'Updating {args.record} -> {address}') + cf.zones.dns_records.patch( + zones[0]['id'], records[0]['id'], + data={'type': 'A', 'name': args.record, 'content': address}) + +if __name__ == '__main__': + main() \ No newline at end of file