From d623ef8470f1117c6f341b178cc587c9193849cf Mon Sep 17 00:00:00 2001 From: kurogeek Date: Thu, 17 Sep 2026 06:34:27 +0000 Subject: [PATCH] clanService/phonebox: global decentralized number directory over yggdrasil Replace the static prefix+extension numbering with random 6 digit box numbers (100000-999999) signed by the machine's yggdrasil key. A serf gossip cluster on yggdrasil port 7946 exchanges the signed records; phonebox-sync verifies key -> address derivation and the signature and writes the number -> address map to /run/phonebox/numbers for the asterisk dialplan. Any yggdrasil node running the service can reach any other; clan-core's ygg-input filter is opened for SIP, RTP and gossip. Drop the server-prefix-number and ata-local-number vars, add a two-node VM test that proves directory convergence and a call over yggdrasil. --- modules/clan/phonebox/README.md | 42 ++ modules/clan/phonebox/default.nix | 560 ++++++++++-------- modules/clan/phonebox/phonebox-sync.py | 111 ++++ modules/clan/phonebox/tests/vm/default.nix | 43 +- .../tests/vm/sops/machines/nodeA/key.json | 6 + .../tests/vm/sops/machines/server/key.json | 2 +- .../vm/sops/secrets/nodeA-age.key/secret | 14 + .../vm/sops/secrets/nodeA-age.key/users/admin | 1 + .../vm/sops/secrets/server-age.key/secret | 12 +- .../per-machine/nodeA/phonebox/number/value | 1 + .../phonebox/owner-name}/value | 0 .../nodeA/phonebox/public-key/value | 1 + .../nodeA/phonebox/signature/value | 1 + .../nodeA/state-version/version/value | 1 + .../per-machine/nodeA/yggdrasil/address/value | 1 + .../nodeA/yggdrasil/privateKey/machines/nodeA | 1 + .../nodeA/yggdrasil/privateKey/secret | 18 + .../yggdrasil/privateKey}/users/admin | 0 .../nodeA/yggdrasil/publicKey/value | 1 + .../phonebox/ata-password/machines/server | 1 - .../server/phonebox/ata-password/secret | 18 - .../per-machine/server/phonebox/number/value | 1 + .../server/phonebox/public-key/value | 1 + .../phonebox/server-prefix-number/value | 1 - .../server/phonebox/signature/value | 1 + .../server/state-version/version/value | 2 +- .../server/yggdrasil/address/value | 2 +- .../server/yggdrasil/privateKey/secret | 16 +- .../server/yggdrasil/publicKey/value | 4 +- .../server/yggdrasil/yggdrasil-ip/value | 1 - .../yggdrasil-secret/machines/server | 1 - .../server/yggdrasil/yggdrasil-secret/secret | 18 - .../yggdrasil/yggdrasil-secret/users/admin | 1 - .../server/yggdrasil/yggdrasil-subnet/value | 1 - .../adhil/phonebox/ata-local-number/value | 1 - .../adhil/phonebox/server-prefix-number/value | 1 - .../almach/phonebox/ata-local-number/value | 1 - .../phonebox/server-prefix-number/value | 1 - .../alpheratz/phonebox/ata-local-number/value | 1 - .../phonebox/server-prefix-number/value | 1 - .../buna/phonebox/ata-local-number/value | 1 - .../buna/phonebox/server-prefix-number/value | 1 - .../mirach/phonebox/ata-local-number/value | 1 - .../phonebox/server-prefix-number/value | 1 - .../neptune/phonebox/ata-local-number/value | 1 - .../phonebox/server-prefix-number/value | 1 - .../rigel/phonebox/ata-local-number/value | 1 - .../rigel/phonebox/server-prefix-number/value | 1 - 48 files changed, 554 insertions(+), 345 deletions(-) create mode 100644 modules/clan/phonebox/README.md create mode 100644 modules/clan/phonebox/phonebox-sync.py create mode 100755 modules/clan/phonebox/tests/vm/sops/machines/nodeA/key.json create mode 100644 modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/secret create mode 120000 modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/users/admin create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/number/value rename modules/clan/phonebox/tests/vm/vars/per-machine/{server/phonebox/ata-local-number => nodeA/phonebox/owner-name}/value (100%) create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/public-key/value create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/signature/value create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/state-version/version/value create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/address/value create mode 120000 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/machines/nodeA create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/secret rename modules/clan/phonebox/tests/vm/vars/per-machine/{server/phonebox/ata-password => nodeA/yggdrasil/privateKey}/users/admin (100%) create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/publicKey/value delete mode 120000 modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/machines/server delete mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/secret create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/number/value create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/public-key/value delete mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/server-prefix-number/value create mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/signature/value delete mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-ip/value delete mode 120000 modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/machines/server delete mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/secret delete mode 120000 modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/users/admin delete mode 100644 modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-subnet/value delete mode 100644 vars/per-machine/adhil/phonebox/ata-local-number/value delete mode 100644 vars/per-machine/adhil/phonebox/server-prefix-number/value delete mode 100644 vars/per-machine/almach/phonebox/ata-local-number/value delete mode 100644 vars/per-machine/almach/phonebox/server-prefix-number/value delete mode 100644 vars/per-machine/alpheratz/phonebox/ata-local-number/value delete mode 100644 vars/per-machine/alpheratz/phonebox/server-prefix-number/value delete mode 100644 vars/per-machine/buna/phonebox/ata-local-number/value delete mode 100644 vars/per-machine/buna/phonebox/server-prefix-number/value delete mode 100644 vars/per-machine/mirach/phonebox/ata-local-number/value delete mode 100644 vars/per-machine/mirach/phonebox/server-prefix-number/value delete mode 100644 vars/per-machine/neptune/phonebox/ata-local-number/value delete mode 100644 vars/per-machine/neptune/phonebox/server-prefix-number/value delete mode 100644 vars/per-machine/rigel/phonebox/ata-local-number/value delete mode 100644 vars/per-machine/rigel/phonebox/server-prefix-number/value diff --git a/modules/clan/phonebox/README.md b/modules/clan/phonebox/README.md new file mode 100644 index 0000000..0cf8469 --- /dev/null +++ b/modules/clan/phonebox/README.md @@ -0,0 +1,42 @@ +A peer to peer phone relay network built on top of yggdrasil. + +Every box is reachable from any yggdrasil node running this service, not only +from members of the same clan. + +## Numbers + +Each box gets a random 6 digit number (100000-999999) when its vars are +generated. The number is signed with the box's yggdrasil private key, so a +number is cryptographically bound to the yggdrasil address it belongs to; +nobody can claim a number for an address they do not own. Regenerate the +`phonebox` vars to get a new number. + +Dialing from a phone plugged into the ATA: + +| dialed | reaches | +| ------------- | ------------------------------------------ | +| `NNNNNN` | the phone on box `NNNNNN` (line `00`) | +| `NNNNNNXX` | line `XX` on box `NNNNNN` | +| `00`, `01`... | local lines on this box | +| `888` | fax of the current number directory | +| `000` | fax echo test | +| `999` | hello world | + +Caller ID on the callee is the caller's box number followed by its line, so +calling it back works. + +## Directory + +There is no central registry. Each box runs a serf agent on yggdrasil port +7946 that gossips its signed number record (`number`, `key`, `sig`, `owner` +tags) to every other box it knows about. On every membership change the +`phonebox-sync` handler verifies all records and writes the resulting +`number -> yggdrasil address` map to `/run/phonebox/numbers/`, which the +asterisk dialplan reads at call time. + +Bootstrapping: boxes of the same clan join each other automatically. To +connect to boxes outside the clan add one of their yggdrasil addresses to +`extraPeers`; after the first successful join the agent remembers the whole +membership in `/var/lib/phonebox/serf.snapshot` and rejoins on its own. + +`serf members -rpc-addr 127.0.0.1:7373` shows the live directory. diff --git a/modules/clan/phonebox/default.nix b/modules/clan/phonebox/default.nix index 462910f..563dcec 100644 --- a/modules/clan/phonebox/default.nix +++ b/modules/clan/phonebox/default.nix @@ -6,7 +6,7 @@ _class = "clan.service"; manifest.name = "phonebox"; manifest.description = "A peer to peer phone relay network built on top of yggdrasil."; - manifest.readme = "A peer to peer phone relay network built on top of yggdrasil."; + manifest.readme = builtins.readFile ./README.md; manifest.categories = [ "System" ]; roles.default = { @@ -19,11 +19,6 @@ description = "An Ethernet interface that connect to ATA box."; default = "enp2s0"; }; - options.ownerName = lib.mkOption { - type = lib.types.str; - description = ""; - default = ""; - }; options.extraClientNumbers = lib.mkOption { type = with lib.types; listOf str; description = "List of client suffix number."; @@ -47,6 +42,7 @@ } ); description = "Extra client to be added to pjsip config as a fixed IP auth"; + default = { }; example = { "01" = { ip = "192.168.1.3"; @@ -54,6 +50,17 @@ }; }; }; + + options.extraPeers = lib.mkOption { + type = with lib.types; listOf str; + description = '' + Yggdrasil addresses of phonebox nodes outside this clan to bootstrap + the number directory from. Any single reachable node is enough; the + directory is gossiped from there and remembered across restarts. + ''; + default = [ ]; + example = [ "200:1234:5678:9abc:def0:1234:5678:9abc" ]; + }; }; perInstance = { @@ -75,6 +82,7 @@ propagatedNativeBuildInputs = [ pkgs.spandsp3 ]; }); + machineName = config.clan.core.settings.machine.name; machines = lib.attrNames roles.default.machines; user = "asterisk"; @@ -83,62 +91,72 @@ rtpPortTo = 20000; ata-interface = settings.ata-ethernet-iface; - contactList = builtins.map (machineName: { - name = "${clanLib.getPublicValue { - flake = config.clan.core.settings.directory; - machine = machineName; - generator = "phonebox"; - file = "owner-name"; - default = null; - }}"; - number = "${ + # The ATA's own line. Remote callers reach it by dialing the 6 + # digit box number alone or with this extension appended. + ataLine = "00"; + sipPort = 5060; + + # Well-known port of the phonebox directory gossip. Every phonebox + # node worldwide must agree on it, so it is not configurable. + directoryPort = 7946; + directoryRpc = "127.0.0.1:7373"; + directoryUser = "phonebox"; + directoryDir = "/run/phonebox"; + + phoneboxVars = config.clan.core.vars.generators.phonebox.files; + ownNumber = phoneboxVars.number.value; + ownerName = lib.trim phoneboxVars.owner-name.value; + + getYggdrasilIP = + name: + lib.trim ( clanLib.getPublicValue { flake = config.clan.core.settings.directory; - machine = machineName; - generator = "phonebox"; - file = "server-prefix-number"; - default = null; + machine = name; + generator = "yggdrasil"; + file = "address"; + default = ""; } - }${ - clanLib.getPublicValue { - flake = config.clan.core.settings.directory; - machine = machineName; - generator = "phonebox"; - file = "ata-local-number"; - default = null; - } - }"; - }) machines; + ); - createContactListTiff = - let - contactTXT = lib.concatStringsSep "\n" ( - builtins.map (contact: "${contact.number}\t\t: \t\t${contact.name}") contactList - ); - in - pkgs.writeShellApplication { - name = "create-contact-tiff"; - text = '' - magick -background white -fill black -pointsize 20 -font DejaVu-Sans label:"${contactTXT}" "$1" - magick "$1" -border 20x50 -bordercolor white "$1" - magick "$1" -resize 1728x -units PixelsPerInch -compress Group4 -density 204x196 -monochrome -depth 1 "$1" - ''; - runtimeInputs = [ pkgs.imagemagick ]; - }; + ownYggdrasilIP = + if config.clan.core.vars.generators.yggdrasil.files ? address then + config.clan.core.vars.generators.yggdrasil.files.address.value + else + throw "clanService/yggdrasil is required"; - genServerSIPEndpoint = - { hostname, address }: - '' - [${hostname}](internal_endpoint) - aors=${hostname} + seedPeers = lib.unique ( + lib.filter (ip: ip != "") (map getYggdrasilIP (lib.remove machineName machines)) + ++ settings.extraPeers + ); - [${hostname}](ip_auth) - endpoint=${hostname} - match=[${address}] + # Rebuilds the number -> yggdrasil address directory from the + # gossip membership. Every record is verified: the yggdrasil + # address is derived from the record's public key, so a node can + # only publish numbers for the address it actually owns, and the + # number is signed with the matching private key. + phoneboxSync = pkgs.writers.writePython3Bin "phonebox-sync" { + libraries = [ pkgs.python3Packages.cryptography ]; + flakeIgnore = [ "E501" ]; + } (builtins.readFile ./phonebox-sync.py); - [${hostname}](dynamiic_aor) - contact=sip:[${address}] + phoneboxSyncWrapped = pkgs.writeShellApplication { + name = "phonebox-sync"; + runtimeInputs = [ pkgs.serfdom ]; + text = '' + exec ${lib.getExe phoneboxSync} ${directoryRpc} ${directoryDir} ''; + }; + + createContactListTiff = pkgs.writeShellApplication { + name = "create-contact-tiff"; + text = '' + magick -background white -fill black -pointsize 20 -font DejaVu-Sans label:"$(cat ${directoryDir}/contacts.txt)" "$1" + magick "$1" -border 20x50 -bordercolor white "$1" + magick "$1" -resize 1728x -units PixelsPerInch -compress Group4 -density 204x196 -monochrome -depth 1 "$1" + ''; + runtimeInputs = [ pkgs.imagemagick ]; + }; genLocalSIPEndpoint = { localNumber }: @@ -151,7 +169,7 @@ username=${localNumber} password=${localNumber} - [${localNumber}](dynamiic_aor) + [${localNumber}](dynamic_aor) max_contacts=1 remove_existing=yes ''; @@ -168,7 +186,7 @@ username=${localNumber} password=${localNumber} - [${localNumber}](dynamiic_aor) + [${localNumber}](dynamic_aor) max_contacts=1 ''; @@ -181,7 +199,7 @@ contact_deny=::/0 contact_permit=${settings.extraFixedIPClient.${number}.ip}/128 - [${number}](dynamiic_aor) + [${number}](dynamic_aor) max_contacts=1 remove_existing=yes @@ -196,63 +214,58 @@ '' exten => ${localNumber},1,Dial(PJSIP/${localNumber},20) ''; - genExtentConf = - { - prefixNumber, - hostname, - localNumber, - }: - let - replaceWithX = - ln: builtins.concatStringsSep "" (builtins.genList (_: "X") (builtins.stringLength ln)); - in - '' - exten => _${prefixNumber}${replaceWithX localNumber},1,Dial(PJSIP/''${EXTEN:${builtins.toString (builtins.stringLength prefixNumber)}}@${hostname},30) - ''; - getYggdrasilIP = - machineName: - if config.clan.core.vars.generators.yggdrasil.files.address ? value then - clanLib.getPublicValue { - flake = config.clan.core.settings.directory; - machine = machineName; - generator = "yggdrasil"; - file = "address"; - default = null; - } - else - throw "clanService/yggdrasil is required"; + localNumbers = [ + ataLine + ] + ++ settings.extraClientNumbers + ++ lib.attrNames settings.extraFixedIPClient; in { - clan.core.vars.generators.phonebox = builtins.break { + assertions = [ + { + assertion = !config.networking.nftables.enable; + message = "phonebox: opening the yggdrasil interface to non-clan nodes is only implemented for the iptables firewall backend"; + } + ]; + + clan.core.vars.generators.phonebox = { files = { - server-prefix-number.secret = false; - ata-local-number.secret = false; + number.secret = false; + public-key.secret = false; + signature.secret = false; owner-name.secret = false; }; - prompts = { - server-prefix-number = { - type = "line"; - persist = true; - description = "Server prefix number: indicate server to connect to [10XX]"; - }; - ata-local-number = { - persist = true; - type = "line"; - description = "Local suffix number: indicate local number on the server [XX00]"; - }; - owner-name = { - persist = true; - type = "line"; - description = "The owner's name for this unit"; - }; + dependencies = [ "yggdrasil" ]; + + prompts.owner-name = { + persist = true; + type = "line"; + description = "The owner's name for this unit"; }; + runtimeInputs = with pkgs; [ + coreutils + openssl + xxd + ]; + + # A random 6 digit number (100000-999999), bound to this + # machine's yggdrasil identity by signing it with the yggdrasil + # private key. Other nodes accept the number only if the + # signature checks out and the key derives to the yggdrasil + # address the record is gossiped from. Regenerate to re-roll. script = '' - cat $prompts/server-prefix-number > $out/server-prefix-number - cat $prompts/ata-local-number > $out/ata-local-number cat $prompts/owner-name > $out/owner-name + shuf -i 100000-999999 -n 1 | tr -d '\n' > $out/number + openssl pkey -in $in/yggdrasil/privateKey -pubout -outform DER \ + | tail -c 32 | xxd -p -c 64 | tr -d '\n' > $out/public-key + # openssl needs a regular file for one-shot ed25519 signing + printf 'phonebox:%s' "$(cat $out/number)" > $out/message + openssl pkeyutl -sign -rawin -inkey $in/yggdrasil/privateKey -in $out/message \ + | base64 -w0 > $out/signature + rm $out/message ''; }; @@ -312,188 +325,223 @@ networking.firewall.allowedUDPPorts = [ 53 67 - 5060 + sipPort ]; networking.firewall.allowedTCPPorts = [ 53 ]; - networking.firewall.interfaces = - let - matchAll = if !config.networking.nftables.enable then "zt+" else "zt*"; - in - { - "${matchAll}".allowedTCPPorts = [ 80 ]; + networking.firewall.interfaces = { + "zt+".allowedTCPPorts = [ 80 ]; + ygg = { + allowedTCPPorts = [ directoryPort ]; + allowedUDPPorts = [ directoryPort ]; }; + }; + + # clanService/yggdrasil drops everything on the ygg interface that + # does not come from a clan member. Phonebox is a global network, + # so let SIP, RTP and the directory gossip through from anyone. + networking.firewall.extraCommands = lib.mkAfter '' + if ip6tables -n -L ygg-input >/dev/null 2>&1; then + ip6tables -I ygg-input 1 -i ygg -p udp --dport ${toString sipPort} -j RETURN + ip6tables -I ygg-input 1 -i ygg -p udp --dport ${toString rtpPortFrom}:${toString rtpPortTo} -j RETURN + ip6tables -I ygg-input 1 -i ygg -p udp --dport ${toString directoryPort} -j RETURN + ip6tables -I ygg-input 1 -i ygg -p tcp --dport ${toString directoryPort} -j RETURN + fi + ''; + + users.users.${directoryUser} = { + isSystemUser = true; + group = directoryUser; + }; + users.groups.${directoryUser} = { }; + + # Decentralized number directory: a serf gossip cluster over + # yggdrasil. Each node advertises its signed number record as + # tags; membership changes trigger phonebox-sync, which writes the + # verified number -> address map to ${directoryDir}/numbers/ for + # the dialplan. The snapshot lets a node rejoin from previously + # seen members, so seeds are only needed for the very first join. + systemd.services.phonebox-directory = { + description = "Phonebox number directory (serf gossip over yggdrasil)"; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ + "network-online.target" + "yggdrasil.service" + ]; + serviceConfig = { + User = directoryUser; + Group = directoryUser; + RuntimeDirectory = "phonebox"; + RuntimeDirectoryMode = "0755"; + RuntimeDirectoryPreserve = "yes"; + StateDirectory = "phonebox"; + Restart = "always"; + RestartSec = 5; + ExecStart = lib.concatStringsSep " " ( + [ + (lib.getExe pkgs.serfdom) + "agent" + "-node=${machineName}-${ownNumber}" + "-bind=[::]:${toString directoryPort}" + "-advertise=[${ownYggdrasilIP}]:${toString directoryPort}" + "-rpc-addr=${directoryRpc}" + "-profile=wan" + "-snapshot=/var/lib/phonebox/serf.snapshot" + "-rejoin" + "-retry-max=0" + "-retry-interval=30s" + "-tag number=${ownNumber}" + "-tag key=${phoneboxVars.public-key.value}" + "-tag sig=${phoneboxVars.signature.value}" + "-tag ${lib.escapeShellArg "owner=${ownerName}"}" + "-event-handler=member-join,member-leave,member-failed,member-update,member-reap=${lib.getExe phoneboxSyncWrapped}" + ] + ++ map (ip: "-retry-join=[${ip}]:${toString directoryPort}") seedPeers + ); + }; + }; services.asterisk = { enable = lib.mkDefault true; package = lib.mkDefault asterisk; - confFiles = - let - nodes = builtins.foldl' ( - nodes: name: - nodes - ++ [ - { - hostname = name; - address = getYggdrasilIP name; - prefixNumber = clanLib.getPublicValue { - flake = config.clan.core.settings.directory; - machine = name; - generator = "phonebox"; - file = "server-prefix-number"; - default = null; - }; + confFiles = { + "logger.conf" = '' + [general] + dateformat = %F %T.%3q ; ISO 8601 date format with milliseconds + use_callids = yes + appendhostname = no + queue_log = yes + queue_log_to_file = no + queue_log_name = queue_log + queue_log_realtime_use_gmt = no + rotatestrategy = rotate + exec_after_rotate=gzip -9 $\{filename\}.2 + [logfiles] + console => notice,warning,error + security => security + messages => notice,warning,error + full => notice,warning,error,verbose,dtmf,fax + syslog.local0 => notice,warning,error + ''; - localNumber = clanLib.getPublicValue { - flake = config.clan.core.settings.directory; - machine = name; - generator = "phonebox"; - file = "ata-local-number"; - default = null; - }; - } - ] - ) [ ] machines; - in - { - "logger.conf" = '' - [general] - dateformat = %F %T.%3q ; ISO 8601 date format with milliseconds - use_callids = yes - appendhostname = no - queue_log = yes - queue_log_to_file = no - queue_log_name = queue_log - queue_log_realtime_use_gmt = no - rotatestrategy = rotate - exec_after_rotate=gzip -9 $\{filename\}.2 - [logfiles] - console => notice,warning,error - security => security - messages => notice,warning,error - full => notice,warning,error,verbose,dtmf,fax - syslog.local0 => notice,warning,error - ''; + "modules.conf" = '' + [modules] + autoload=yes - "modules.conf" = '' - [modules] - autoload=yes + load => res_fax_spandsp.so + ''; - load => res_fax_spandsp.so - ''; + # Dial plan config + "extensions.conf" = '' + [from-internal] + exten => 999,1,Answer() + same => n,Playback(hello-world) + same => n,Hangup() - # Dial plan config - "extensions.conf" = - let - serverConf = builtins.foldl' ( - config: node: - config - + (genExtentConf { - inherit (node) prefixNumber hostname localNumber; - }) - ) "" nodes; - in - '' - [from-internal] - exten => 999,1,Answer() - same => n,Playback(hello-world) - same => n,Hangup() + exten => 000,1,Answer() + same => n,ReceiveFAX(${faxDir}/echo-''${UNIQUEID}.tiff) + same => n,Set(FAXFILE=${faxDir}/echo-''${UNIQUEID}.tiff) + same => n,Set(FAXECHO=true) - exten => 000,1,Answer() - same => n,ReceiveFAX(${faxDir}/echo-''${UNIQUEID}.tiff) - same => n,Set(FAXFILE=${faxDir}/echo-''${UNIQUEID}.tiff) - same => n,Set(FAXECHO=true) + exten => 888,1,Answer() + same => n,Set(FAXFILE=${faxDir}/contact.tiff) + same => n,System(${lib.getExe createContactListTiff} ''${FAXFILE}) + same => n,Set(FAXECHO=true) + same => n,Playback(vm-goodbye) + same => n,Wait(3) - exten => 888,1,Answer() - same => n,Set(FAXFILE=${faxDir}/contact.tiff) - same => n,System(${lib.getExe createContactListTiff} ''${FAXFILE}) - same => n,Set(FAXECHO=true) - same => n,Playback(vm-goodbye) - same => n,Wait(3) + exten => h,1,GotoIf($[''${FAXECHO}]?sendfax) + same => n,Hangup() + same => n(sendfax),Originate(PJSIP/${ataLine},app,SendFAX,''${FAXFILE}) + same => n,Set(FAXECHO=false) - exten => h,1,GotoIf($[''${FAXECHO}]?sendfax) - same => n,Hangup() - same => n(sendfax),Originate(PJSIP/00,app,SendFAX,''${FAXFILE}) - same => n,Set(FAXECHO=false) + ; 6 digit box number, optionally followed by a 2 digit line + exten => _[1-9]XXXXX,1,Goto(to-yggdrasil,''${EXTEN},1) + exten => _[1-9]XXXXXXX,1,Goto(to-yggdrasil,''${EXTEN},1) - '' - + (genLocalExtenConf { - localNumber = config.clan.core.vars.generators.phonebox.files.ata-local-number.value; - }) - + lib.concatStringsSep "\n" ( - builtins.map (number: genLocalExtenConf { localNumber = number; }) settings.extraClientNumbers - ) - + lib.concatStringsSep "\n" ( - lib.mapAttrsToList ( - number: _: genLocalExtenConf { localNumber = number; } - ) settings.extraFixedIPClient - ) - + serverConf; + '' + + lib.concatMapStrings (number: genLocalExtenConf { localNumber = number; }) localNumbers + + '' - "rtp.conf" = '' - [general] - rtpstart=${builtins.toString rtpPortFrom} - rtpend=${builtins.toString rtpPortTo} - ''; + ; Outbound: resolve the box number through the directory and + ; send the call to that node over yggdrasil. + [to-yggdrasil] + exten => _[1-9]X.,1,Set(PEER=''${FILE(${directoryDir}/numbers/''${EXTEN:0:6})}) + same => n,GotoIf($["''${PEER}" = ""]?unknown) + same => n,Set(CALLERID(num)=${ownNumber}''${CALLERID(num)}) + same => n,Set(CALLERID(name)=${ownerName}) + same => n,Dial(PJSIP/yggdrasil/sip:''${EXTEN}@[''${PEER}]:${toString sipPort},30) + same => n,Hangup() + same => n(unknown),Playback(ss-noservice) + same => n,Hangup() - "pjsip.conf" = - let - serverConf = builtins.foldl' ( - conf: node: - conf - + (genServerSIPEndpoint { - hostname = node.hostname; - address = node.address; - }) - ) "" nodes; - in - '' - [transport-udp] - type=transport - protocol=udp - bind=0.0.0.0 - [transport-udp6] - type=transport - protocol=udp - bind=:: + ; Inbound from any yggdrasil node: only our own number is served. + [from-yggdrasil] + exten => ${ownNumber},1,Dial(PJSIP/${ataLine},20) + exten => _${ownNumber}XX,1,Dial(PJSIP/''${EXTEN:6},20) + ''; - [base_endpoint](!) - type=endpoint - disallow=all - allow=ulaw,alaw,g722,gsm - direct_media=no + "rtp.conf" = '' + [general] + rtpstart=${toString rtpPortFrom} + rtpend=${toString rtpPortTo} + ''; - [internal_endpoint](!,base_endpoint) - context=from-internal + "pjsip.conf" = '' + [global] + type=global + ; Local lines authenticate by username; everything else from + ; the yggdrasil range is a remote phonebox node. + endpoint_identifier_order=username,ip,anonymous - [userpass_auth](!) - type=auth - auth_type=userpass + [transport-udp] + type=transport + protocol=udp + bind=0.0.0.0 + [transport-udp6] + type=transport + protocol=udp + bind=:: - [ip_auth](!) - type=identify - endpoint=external + [base_endpoint](!) + type=endpoint + disallow=all + allow=ulaw,alaw,g722,gsm + direct_media=no - [dynamiic_aor](!) - type=aor + [internal_endpoint](!,base_endpoint) + context=from-internal - '' - + (genLocalSIPEndpoint { - localNumber = config.clan.core.vars.generators.phonebox.files.ata-local-number.value; - }) - + lib.concatStringsSep "\n" ( - builtins.map (number: genLocalSIPEndpointV6 { localNumber = number; }) settings.extraClientNumbers - ) - + lib.concatStringsSep "\n" ( - lib.mapAttrsToList (number: _: genLocalSIPIPEndpoint number) settings.extraFixedIPClient - ) - + serverConf; - }; + [userpass_auth](!) + type=auth + auth_type=userpass + + [dynamic_aor](!) + type=aor + + [yggdrasil](base_endpoint) + transport=transport-udp6 + context=from-yggdrasil + + [yggdrasil] + type=identify + endpoint=yggdrasil + match=200::/7 + + '' + + (genLocalSIPEndpoint { localNumber = ataLine; }) + + lib.concatMapStrings ( + number: genLocalSIPEndpointV6 { localNumber = number; } + ) settings.extraClientNumbers + + lib.concatMapStrings genLocalSIPIPEndpoint (lib.attrNames settings.extraFixedIPClient); + }; }; environment.systemPackages = [ createContactListTiff + phoneboxSyncWrapped ]; systemd.tmpfiles.rules = [ diff --git a/modules/clan/phonebox/phonebox-sync.py b/modules/clan/phonebox/phonebox-sync.py new file mode 100644 index 0000000..a1add7b --- /dev/null +++ b/modules/clan/phonebox/phonebox-sync.py @@ -0,0 +1,111 @@ +"""Rebuild the phonebox number directory from serf membership. + +Usage: phonebox-sync + +Runs as a serf event handler. Every alive member carries its number record +as tags (number, key, sig, owner). A record is accepted only when the +yggdrasil address it is gossiped from derives from `key` and `sig` is a +valid ed25519 signature of "phonebox:" under that key, so a number +can only ever be bound to the address of the node that signed it. + +Output, consumed by the asterisk dialplan: + /numbers/ the yggdrasil address, no trailing newline + /contacts.txt " : " per line, for the fax +""" + +import base64 +import ipaddress +import json +import os +import subprocess +import sys +import syslog +import tempfile + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + + +def yggdrasil_address(public_key: bytes) -> ipaddress.IPv6Address: + """Port of yggdrasil-go address.AddrForKey. + + Invert the key; the address is 0x02, the count of leading one bits, + then the bits following the first zero, truncated to 128 bits. + """ + bits = "".join(f"{b ^ 0xFF:08b}" for b in public_key) + ones = len(bits) - len(bits.lstrip("1")) + body = int(bits[ones + 1:ones + 113], 2).to_bytes(14, "big") + return ipaddress.IPv6Address(bytes([0x02, ones]) + body) + + +def verified_record(member: dict) -> tuple[str, str, str, str]: + """Return (number, key hex, address, owner) or raise ValueError.""" + tags = member.get("tags", {}) + try: + number, key_hex, sig = tags["number"], tags["key"], tags["sig"] + key = bytes.fromhex(key_hex) + host, _ = member["addr"].rsplit(":", 1) + address = ipaddress.IPv6Address(host.strip("[]")) + signature = base64.b64decode(sig, validate=True) + except (KeyError, ValueError) as e: + raise ValueError(f"malformed record: {e}") + if not (len(number) == 6 and number.isdigit() and number[0] != "0"): + raise ValueError(f"invalid number {number!r}") + if yggdrasil_address(key) != address: + raise ValueError(f"key does not derive to address {address}") + try: + Ed25519PublicKey.from_public_bytes(key).verify(signature, b"phonebox:" + number.encode()) + except (ValueError, InvalidSignature): + raise ValueError(f"bad signature for number {number}") + return number, key_hex, str(address), tags.get("owner", "") + + +def directory(members: list[dict]) -> dict[str, tuple[str, str, str]]: + """number -> (key hex, address, owner); on a collision the lowest key wins.""" + book = {} + for member in members: + try: + number, key, address, owner = verified_record(member) + except ValueError as e: + syslog.syslog(syslog.LOG_WARNING, f"ignoring {member.get('name')}: {e}") + continue + if number in book: + syslog.syslog(syslog.LOG_WARNING, f"number {number} claimed by keys {book[number][0]} and {key}") + if book[number][0] <= key: + continue + book[number] = (key, address, owner) + return book + + +def write_atomic(path: str, content: str) -> None: + fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path)) + with os.fdopen(fd, "w") as f: + f.write(content) + os.chmod(tmp, 0o644) + os.replace(tmp, path) + + +def main() -> None: + rpc_addr, state_dir = sys.argv[1:] + syslog.openlog("phonebox-sync") + out = subprocess.run( + ["serf", "members", "-format=json", "-status=alive", f"-rpc-addr={rpc_addr}"], + check=True, capture_output=True, text=True, + ).stdout + book = directory(json.loads(out)["members"]) + + numbers_dir = os.path.join(state_dir, "numbers") + os.makedirs(numbers_dir, exist_ok=True) + for number, (_, address, _) in book.items(): + write_atomic(os.path.join(numbers_dir, number), address) + for stale in set(os.listdir(numbers_dir)) - book.keys(): + os.unlink(os.path.join(numbers_dir, stale)) + write_atomic( + os.path.join(state_dir, "contacts.txt"), + "".join(f"{number}\t\t: \t\t{book[number][2]}\n" for number in sorted(book)), + ) + syslog.syslog(syslog.LOG_INFO, f"directory has {len(book)} numbers") + + +if __name__ == "__main__": + main() diff --git a/modules/clan/phonebox/tests/vm/default.nix b/modules/clan/phonebox/tests/vm/default.nix index 5de9498..3d15d35 100644 --- a/modules/clan/phonebox/tests/vm/default.nix +++ b/modules/clan/phonebox/tests/vm/default.nix @@ -2,7 +2,6 @@ self, hostPkgs, config, - inputs, lib, ... }: @@ -25,36 +24,54 @@ directory = ./.; inventory = { machines.server = { }; + machines.nodeA = { }; instances = { yggdrasil = { module.name = "yggdrasil"; roles.default.machines.server = { }; + roles.default.machines.nodeA = { }; }; phonebox-test = { module.name = "@clan/phonebox"; module.input = "self"; - roles.default.machines."server".settings = { - ata-ethernet-iface = "enp2s0"; - }; + roles.default.machines.server.settings.ata-ethernet-iface = "enp2s0"; + roles.default.machines.nodeA.settings.ata-ethernet-iface = "enp2s0"; }; }; }; }; nodes = { - server = { - services.asterisk = { - }; - }; + server = { }; + nodeA = { }; }; - testScript = '' - start_all() + testScript = + { nodes, ... }: + let + number = node: nodes.${node}.clan.core.vars.generators.phonebox.files.number.value; + address = node: nodes.${node}.clan.core.vars.generators.yggdrasil.files.address.value; + in + '' + start_all() - server.wait_for_unit("asterisk") + for node in [server, nodeA]: + node.wait_for_unit("asterisk.service") + node.wait_for_unit("phonebox-directory.service") - server.succeed("systemctl status asterisk") + # Directory converges: each node learns the other's number and address. + server.wait_until_succeeds("test -f /run/phonebox/numbers/${number "nodeA"}", timeout=300) + nodeA.wait_until_succeeds("test -f /run/phonebox/numbers/${number "server"}", timeout=300) + assert server.succeed("cat /run/phonebox/numbers/${number "nodeA"}") == "${address "nodeA"}" + assert nodeA.succeed("cat /run/phonebox/numbers/${number "server"}") == "${address "server"}" + assert "${number "nodeA"}" in server.succeed("cat /run/phonebox/contacts.txt") - ''; + # A call from server to nodeA's number is routed over yggdrasil and + # accepted by nodeA's asterisk (no phone is registered, so it fails + # after being identified, which is enough to prove the path). + nodeA.succeed("asterisk -rx 'pjsip set logger on'") + server.succeed("asterisk -rx 'channel originate Local/${number "nodeA"}@from-internal application Wait 3'") + nodeA.wait_until_succeeds("grep -q 'INVITE sip:${number "nodeA"}@' /var/log/asterisk/full", timeout=60) + ''; } diff --git a/modules/clan/phonebox/tests/vm/sops/machines/nodeA/key.json b/modules/clan/phonebox/tests/vm/sops/machines/nodeA/key.json new file mode 100755 index 0000000..0daa768 --- /dev/null +++ b/modules/clan/phonebox/tests/vm/sops/machines/nodeA/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1r2lq949hmh0xr74p7r53ksxvgdvljpxcdqnx2xr0gk2dgsvcg56qapalmt", + "type": "age" + } +] \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/sops/machines/server/key.json b/modules/clan/phonebox/tests/vm/sops/machines/server/key.json index c3d29b1..00c7184 100755 --- a/modules/clan/phonebox/tests/vm/sops/machines/server/key.json +++ b/modules/clan/phonebox/tests/vm/sops/machines/server/key.json @@ -1,6 +1,6 @@ [ { - "publickey": "age1fdkan6n20swmut0sa86g5a6gxrj8qj2sgqe8hxtw32c0u9rr4drqlyr5mf", + "publickey": "age1sstuuf7rzdwd88h2vcz89n9ctnarwx7e4uncyxaq3s4kcpag8a8qc7qaxj", "type": "age" } ] \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/secret b/modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/secret new file mode 100644 index 0000000..178083d --- /dev/null +++ b/modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/secret @@ -0,0 +1,14 @@ +{ + "data": "ENC[AES256_GCM,data:ZDGbL/Ohsv8Y/w1O0dLrBsUu/i1JRP7SS+NgPuCJL5M1fwUEJteXS7ztaTCnbohVszWPli/5KJl9PDgdpx1s42BDHCKOJNJ2umw=,iv:1D1iC4h4nshIkz8BuJlUNib9lCsdfGdq7BFUkTg9pII=,tag:qskMx9qsD4rTQvX5XlZYDA==,type:str]", + "sops": { + "age": [ + { + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWWDM3TGV4eDNZZXJzTW1i\nSk1xVHNKNjBTcEd2NTRyK0tIdXpMcmJOVndBCnNNTmNLYjNNTWZYNnJQWDBRMmxC\nbkFsenFCNmxpWWRubXhxWGMzQ3ZtRTQKLS0tIDBwU2xrV1FJNmR6MDFzVGIyYm0x\nTjJhK0ttYThoK2lqN3NxeEZ0L1FYUEkKZOyJ+eEgQfTCfC6inGESCR991GbHWQAu\nWXJz164ezx4t2M/8ZqKHcKBEgHPDNjnUekrCwCECwN2lYI9zZ8qEbw==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg" + } + ], + "lastmodified": "2026-09-17T04:58:57Z", + "mac": "ENC[AES256_GCM,data:Bt/dflNdH9UxmVO9tN69GApoQtlugXqMk3pMiXhT5vSeGzlFWZF7M+vewkSp/mLAE9sPspZJ8iYy4l+WjzBQ/x0yq/QZndSYVVt1Nnx24BBBiUqrJvaViGvEo1GLIwu1aJvRXRtoQZtYUGO9mogL09dNRMPTViBRdFPtpRhv5Jo=,iv:0F9mr7xwU1v1XuVPwNCj+kjH0pcchPimqdplpLnHMo8=,tag:1N68pGuIEPIQDX5t2/YWcg==,type:str]", + "version": "3.13.3" + } +} diff --git a/modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/users/admin b/modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/users/admin new file mode 120000 index 0000000..9e21a99 --- /dev/null +++ b/modules/clan/phonebox/tests/vm/sops/secrets/nodeA-age.key/users/admin @@ -0,0 +1 @@ +../../../users/admin \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/sops/secrets/server-age.key/secret b/modules/clan/phonebox/tests/vm/sops/secrets/server-age.key/secret index e9574d9..aaf46e5 100644 --- a/modules/clan/phonebox/tests/vm/sops/secrets/server-age.key/secret +++ b/modules/clan/phonebox/tests/vm/sops/secrets/server-age.key/secret @@ -1,14 +1,14 @@ { - "data": "ENC[AES256_GCM,data:t+zYfcA2f1sdUNBAl+bRGyhPEl5HZFIu+au6heH3+SoHf0zy+Deh25gaVay/oswIg2q806ozjEnTw9q2eaq0VXwMSAWOoTWteI0=,iv:sXoOmfzoCv6GHe21n2Elxr/GTdViI5vfLzMwOLvf1F4=,tag:Fmo9MjEaKV3BehR/bYRdVw==,type:str]", + "data": "ENC[AES256_GCM,data:YhOnYArQxZhufQNUwHKNDghgYar5X06pPv7zyJ37ejutbE/rpZ0SnQQcmT8MFxHNzh+j0iVqVzBqoPM26/EK9DJM82nW8WjhPS8=,iv:NKfGXblgZNui60bwsShx/DheYIKHZjwEJu2KG06NT6A=,tag:GVuh6nhFehlv8ibU1i1iMQ==,type:str]", "sops": { "age": [ { - "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzVlo0YjZFVFF2VkFlSzBL\ndHp4YTQ5RlNxV2FWNnNScTh5d0hXR05RL0NVCnBVUENNNjg0dTFVcmx3N3djZ25l\ncmxYTVVrNGhGSkdwVThoaC9UVTQ3L2sKLS0tIG1uREdFcXNubGxzUUQ1Rkh5Wnlx\nRG5OZmJwaGh2dkt6RSttb0gxZ3FBaEkKZzwUuQmOeBk5kfRfVVdqgNvsTU1Ssb/I\nx9Iv9w/YKHDmmcFLcAbGAHbS0/Js0YqBKZonxEMDdWP/+/F+Pv8LqQ==\n-----END AGE ENCRYPTED FILE-----\n" + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGZVpldFNheTVGY1AwSitC\neXo3S1NVNnZCcnVhaVk5WTBLRVdrUjlTV0hRCi9ZOFBrQ0t3bGFwOHVEZWU2YWdi\nOUpnNGdwU0VSUkxZa3I0TUVEUENsU3MKLS0tIGVVRlpsZHN6a28vRG9JVTREUkZR\nQm40dnZxVUZMcFo2S3VOTUN2dzhkYlEKw3vn0del6gEimd7p4ASzhmEOqY4c7j+m\nELoY+j0568iROX9g9Ebt8GWbx8Yi81DX9RdCiZ4GdTB88sYURpsxIQ==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg" } ], - "lastmodified": "2025-10-31T07:08:30Z", - "mac": "ENC[AES256_GCM,data:CBuTnVIta0eFqlB7ZpDkzPOEGbIQbb5oCpksI4umscB3uE0HM3j4N5r6bCPEcYpehB3qWXuCxlj4NfH7zmp6CDq6Be6bfpB1f8MCwTlPvQUho2f31so3U/g99q6ZyWI2rJO50dzn77bdma3JXo9VQb3uRqJ0mk72IYXFwdHvO9s=,iv:9uwjtGYpqsLcXRoBFmjJjfHUq7R47ZqkrKEXMulw4OY=,tag:4Auq3mnhlusogyW44SJfqg==,type:str]", - "version": "3.11.0" + "lastmodified": "2026-09-17T04:59:39Z", + "mac": "ENC[AES256_GCM,data:GMWIElHv1WaRy8oE5pZSC5W6hP5vm+TcM/BFIWevDpPyFH16yLDxKsSYE9JbEZirFxwyQ9v0tX/DZs3M7kTmdZ6m3qB1Sh2068H5DKV8hSQc4FhbhsyjTL2wSTOqV5HihnoLxjg5Lg+WfnQIygwsYz/3UZrmdElUh1Il01+fXys=,iv:9Jz3v5f5KB9ivy0LNMC9HnLCKnoEI1uTE/gFiCzjLNw=,tag:E+fhqyswcFU7XqHyeKaz5w==,type:str]", + "version": "3.13.3" } } diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/number/value b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/number/value new file mode 100644 index 0000000..6c55f76 --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/number/value @@ -0,0 +1 @@ +651944 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-local-number/value b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/owner-name/value similarity index 100% rename from modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-local-number/value rename to modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/owner-name/value diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/public-key/value b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/public-key/value new file mode 100644 index 0000000..8cc53da --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/public-key/value @@ -0,0 +1 @@ +52460c3f17375aef0b8baa1e20be141901d9b99affb6a5b0dac90684d0047acc \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/signature/value b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/signature/value new file mode 100644 index 0000000..25eaa4c --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/phonebox/signature/value @@ -0,0 +1 @@ +COjaD1KTdEbR3H/0z5VT3DCEBs7WFsejnMhT6OLYMzeWTvZlJ+0Da3a9NvrFfSaa3GiMYFIp7dDHV3Xqba78AA== \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/state-version/version/value b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/state-version/version/value new file mode 100644 index 0000000..fbadb25 --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/state-version/version/value @@ -0,0 +1 @@ +26.11 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/address/value b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/address/value new file mode 100644 index 0000000..d6faa8f --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/address/value @@ -0,0 +1 @@ +201:b6e7:cf03:a322:9443:d1d1:5787:7d07 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/machines/nodeA b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/machines/nodeA new file mode 120000 index 0000000..7982851 --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/machines/nodeA @@ -0,0 +1 @@ +../../../../../../sops/machines/nodeA \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/secret b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/secret new file mode 100644 index 0000000..0ccbb7c --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/secret @@ -0,0 +1,18 @@ +{ + "data": "ENC[AES256_GCM,data:VeeLshHQC8jAWAo9u1TnVL7C6rWvh9hgRViLwhOk5uvOdQn9FvmydPKphlFD0/V65PBLN8fwt/pyhDfaItmNluW7bQBHmvoZE20voVM9srWAcwuyr4gUawJlI1QeCYL7wsGobZ/FH0ID7JglrX4fOaPkQUnhdHI=,iv:hEn5z2s7bDuZtQKaHhwmQXoRPop3S9jv/KSzPsYvHN0=,tag:hM/ElS6ZzaVyjjzqy4gm0g==,type:str]", + "sops": { + "age": [ + { + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrdkI1cFRXdXRQM3RtbmY1\nTTZGSFBwbkJETUw4ZUJHVEdPYnhyN2lVSDFJClkrSjJWbFJRYnd1Smt4dXVqd1hS\nSkI0UzhwQStkeXprWHZ3RnFQOUh0RDAKLS0tIENNTjdTNVdqbUh3TENFUnpFbWc5\nYisxZVptWXQ3b21ZLy8zbHBxaC82S1UKRDuRZ1IUHatj4IvMl8d7ixzwX1F17Nm3\nYS+cQ5W3gc/lCle5bguACeGYlGvqTUWMl2EDVX+TDp054pYsKtecJg==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg" + }, + { + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKSGxvdllTWnpEU0FxYkx4\nSnNiR0VOMzhZR1FDclM5RkdpOHk3emxnTkdrCkt4KzBDczhtNDIyUElrQ1EyL3Q3\ndjhNSUxUaFVDd3Fqa1NJS1dwSHl5dVkKLS0tIDI4anhsSjB6bmlYUlVudktSMnI3\nMlRKMUNkaS95bjQ4cXdiQVIzSWlPY0UKcMwMWNX8t1KKOfjJl1i8GJvDbnDY68X6\nz4j4BFSDJiZ1wTaKO0tZMb3NaDDVr4TZUPJwaKwepSqjVY0BizTuNw==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1r2lq949hmh0xr74p7r53ksxvgdvljpxcdqnx2xr0gk2dgsvcg56qapalmt" + } + ], + "lastmodified": "2026-09-17T04:58:58Z", + "mac": "ENC[AES256_GCM,data:HOLDDIfvnmzYCuqPYGgcMeD0AZHl8BFcY6gbu/2WZiO9roce1EcTtiGAzZyBYK0eqvNbxOxCOx7qhvM+8KKHwqPctMmpyWuPu/hpEPPHQ6ufF14jsIEyJJeOJAJfmx7t1tfen1lsyM1EoGNszp7h8ANjzfitN7VWUw+yTrlDsgY=,iv:KhwlvAzinbT2YBqRHdLDzr4lnlqhNQoJQ/gqDDQ7TMk=,tag:/Bu6SEVMG9Sn2pxIOR7NxQ==,type:str]", + "version": "3.13.3" + } +} diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/users/admin b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/users/admin similarity index 100% rename from modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/users/admin rename to modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/privateKey/users/admin diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/publicKey/value b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/publicKey/value new file mode 100644 index 0000000..8cc53da --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/nodeA/yggdrasil/publicKey/value @@ -0,0 +1 @@ +52460c3f17375aef0b8baa1e20be141901d9b99affb6a5b0dac90684d0047acc \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/machines/server b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/machines/server deleted file mode 120000 index 2bd819e..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/machines/server +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/server \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/secret b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/secret deleted file mode 100644 index 6fc40d6..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/ata-password/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:L4fWAVQdQP2tgYPzUnDY5X0=,iv:fWeTc1buW/JI/8qngZwzDp+wq2OTZPGdItqG0Up5eZ8=,tag:RJt6PQdCisDhtbr3ph3fWA==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1fdkan6n20swmut0sa86g5a6gxrj8qj2sgqe8hxtw32c0u9rr4drqlyr5mf", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFK2x0SkcxK2RxTDRvOEpZ\nTS9QNGJxbmZIL2xtUWxyNjA2VDNVWEQzQVc4CkkxeEZDQm1mQVdUUzVIQThDVkV3\nU2lWbnlnb3lqWDZ0NWRqa2RJV0pVK1UKLS0tIHc5eUJBUUtiL0NwZDRnN004UEtr\nYnU2SWlFOGlucTdydGVmZCtGK3NjS28KkNAxwz9MesicLWtViL302AwZYdiTHmd5\nppbwelisNVlsYHSa5ybVDYER4IUz1d8AKO0jtS7qEDfT53R36swSAA==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArbEpzdk0xQTFkM2V0K2xz\nRFhDak0ybkhZUjJJdVI4YTl3cStXT2hTZDBFCmRvQ3ZXYkQ1NktCb004ZE1FS1FF\naSt4RlVXSlRhYWdlQ3htTkdkb2dyVDAKLS0tIG16VFNQM0dnaGVSRmlFRFB0WWtW\nN0dPL08rV01sY3ovRnEvUnNMaWVhd2sKvQIZIo5pPMXKh9Ea3ZgHj99Dn1X3JkmB\noscG8S7HOJh/cw+uITmkuv00TyIA9pid6L1kXvfcfv+tcuY9H1Vg4w==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2025-10-31T07:08:30Z", - "mac": "ENC[AES256_GCM,data:bmvshL97XNvNYYg0EjkUAQViHpKJ/+A+CGE90/uSM2WXooJF5yzvVJSHLAuYxWM295awN8ygTRUZ2VJ0SyfjzjAyUIH4SxqlrgywqMouyVsiFDAB6R4AmMmbvvC6rSs/TSMOwYC5pchrISbpsE2kmJnAXCqcev6Q0fl8Sa1PSxo=,iv:BuBQCFb3EWfG+bPzgaRDseDq6MJQ/Fs8okvksvEj1bA=,tag:n14VDbQk+zl+XbJPkTRAGg==,type:str]", - "version": "3.11.0" - } -} diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/number/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/number/value new file mode 100644 index 0000000..5f1669b --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/number/value @@ -0,0 +1 @@ +917612 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/public-key/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/public-key/value new file mode 100644 index 0000000..22b9e0f --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/public-key/value @@ -0,0 +1 @@ +95f093cb4b0b727fb38ddbfc8a3364d72d96b25882e44c5ca555644fc18fc2b2 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/server-prefix-number/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/server-prefix-number/value deleted file mode 100644 index d8e8508..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -fake_line_value \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/signature/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/signature/value new file mode 100644 index 0000000..6034a45 --- /dev/null +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/server/phonebox/signature/value @@ -0,0 +1 @@ +77s68Xqs8EgJWQx/vyjvivDPV8w0PF0HUr1WdveoE5ErpY+7Im5c8LFbcoQDVjt2OwaorF3G0NUQoBI8yavPCw== \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/state-version/version/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/state-version/version/value index 115ab7a..fbadb25 100644 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/state-version/version/value +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/server/state-version/version/value @@ -1 +1 @@ -25.11 \ No newline at end of file +26.11 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/address/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/address/value index 90866c6..0243b38 100644 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/address/value +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/address/value @@ -1 +1 @@ -202:fe87:1ca6:3cfd:e095:c2b1:321c:c391 \ No newline at end of file +200:d41e:d869:69e9:1b00:98e4:4806:eb99 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/privateKey/secret b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/privateKey/secret index 59bccb6..46c4d35 100644 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/privateKey/secret +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/privateKey/secret @@ -1,18 +1,18 @@ { - "data": "ENC[AES256_GCM,data:0LwQxArH6fpIYpGIEzPtjh8elyKSaed7L+KqgnIlPVneR0UbsbOM6p5kVGTRPh5LWH6jmkURCqc2c5oT5CNLBePLQcgvtfLDSFWyyPrD9WtBSPu+YGF7K51JUUIkX07//LqTQQaM5Uu/STQRIoL1BD0Tofk2woA=,iv:3rSDqBj8N9RsSLijZm7mUUjUhiLHc2yidGkil8NvCD8=,tag:F8zlw9pfMjZdJSObDePLsQ==,type:str]", + "data": "ENC[AES256_GCM,data:bZpxvXJIZy3xCyjzrZV/YtiQMs2RU/oeRA4wnbYi9YKh/Vz8sSw6ECkrmmP/aG76t6m4+3c1DOo5PBcf+SPbIsMld7DXSlLTa5Y7fd03QqNy4bhXhHqebARZGAy3G5KoNNq1+e73UQuqe9531dDbyZ5y6pcuSPA=,iv:Pp/0BJoZCsuao7qY6bv+AnSI6MVy1ionei8MlSsBt20=,tag:Kfs0pDp3TCpk4gW9LOvcFg==,type:str]", "sops": { "age": [ { - "recipient": "age1fdkan6n20swmut0sa86g5a6gxrj8qj2sgqe8hxtw32c0u9rr4drqlyr5mf", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0NnVzT0lOWnFTUU85c0d5\nZ0Q1b0xkS1NWbWVsc0o1emxLOW5NRU52REFZCjRkUGJHaDhyazdFdkxWaW1XVW53\nb25pUXlDRmhrTHlIWGl6RDJBZ2hTOW8KLS0tIGsxQmlQejkvQk9wTmtMQzZJa2dB\na0piOGlyMzVrTmNOUEhtaDNTWHdwTDAKroQG8KlnWZ6gwu1y0mr0gGezDF1jsS0Y\nC1LUAarHl+lY51sw+HJT88Y9mDfLjvYIMHKS33zdJuBXbNpoIfWyzA==\n-----END AGE ENCRYPTED FILE-----\n" + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNZTI0MWQ4cjQ4YmYvT2Fj\nTEp5VHQ2NkFKVFhIV0dzVXNmSUVnWTZabVcwCmFvT25WTTMvcUVFY2xmWjVHRUx2\nRGRySVpYaGY4MFhiSDI3bzFxWUUwV2cKLS0tIGplZHUvWE50aFNvQmFzME9YUTIz\nYUZVQnJQZTNWV2FNWEx6WFp4WkFiRkUKmvikszfBTgfqTDEIQZwBBU+nhCYYEMu0\n6J8BPnH6ZoX6nFm1K34BkTGCLzbvM1lQL27hiP5oto5E2rBzpLgi6g==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg" }, { - "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqQVJhYkI1eEFYL0d5OE1F\nSStqV0xPb0pybyt2QWd4SEpiaDhlNDJicTA0CkgrVU9FcThuQktaMThTaXBmWHNV\nd2tpMVVYT21TWUFvMS9wakl4RyszTGsKLS0tIHVVeDdoMUJQaURMOGFkM3dNa3ZG\nMmVPdEg3bmdZZHgwaGpnbFFKUTdvcUUKTaxEfp19+9AJihqx51m0cLz6IuR5pvnT\nt90kZxq+BH3/6gjiDlhwzqztnMbdqQYcVuCDVp/1aVWfThABUZ92aQ==\n-----END AGE ENCRYPTED FILE-----\n" + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSbDV1UHJYdS9DcmR4QmJU\nTVNUeFpuTzYxWmR5WDFEUkswekZjQXVqTXdVCkZBUHc3Nk9veFBCWnhkTCtxQVBs\nTjdCbVFQVnc4NnJCWEZKc3VvZG5TUDgKLS0tIDk3dE85VFFId2pNb21HVktuN2V5\nTDVvRlVFaUdNRHdXZ3RaRTRoWDhEcFkKU16K1nNPuOJUpPTXx3dirg2r8SPYtUT+\nsv3htIRb/kfweRKx331vsC19V0hbsqXLatM+yPzYx8ITx4VUDXB4kA==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1sstuuf7rzdwd88h2vcz89n9ctnarwx7e4uncyxaq3s4kcpag8a8qc7qaxj" } ], - "lastmodified": "2025-12-05T04:42:06Z", - "mac": "ENC[AES256_GCM,data:1/u3c/wf2Gh6PPeVTKKGBxG1FWvN7hyuzx3Qa7yU3yKCD7LDHrrzTbQkFHDo1ZrXixK5NICCw48BWA0Jao0kItu6aU1Dbk5PexZI9ls1eyaDS7nwtZuHKDSEtDYu/kx5kZgQKH9tsokLqKlcoeocS0Mp1tjvUSQsiZFaYsuKaM4=,iv:gCPqFgpwjSONn/JN3dpJOk1BXtdd9cvIAz/NKPuAdKg=,tag:j8upk9sgDiYj5lmfU4V+OQ==,type:str]", - "version": "3.11.0" + "lastmodified": "2026-09-17T04:59:40Z", + "mac": "ENC[AES256_GCM,data:1FucJy4iHy3ZTUNwsZMCUAT8AZeIlfZS57wzgnozrFroHW7wO5uDiHbNwtnaSvvvsax4VrClLMHExkbwg9IgRRrvY6LN19UsLq1Ia504N8bxW/WAvknVIBKqijYT24joysqjtQKHefs4f8mboGFnF7cPcwtHggHrqs+F+ET1rdc=,iv:Vg0lSMsHslgVlm5n7r5jDEVJw+6rJKC6EE740sGkDz0=,tag:uy5xgN/iSjo//euKTbPoFQ==,type:str]", + "version": "3.13.3" } } diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/publicKey/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/publicKey/value index 7a2ebd2..22b9e0f 100644 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/publicKey/value +++ b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/publicKey/value @@ -1,3 +1 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAIC8cazhgQ+1Hqdm8Z43J5ooymP2ytrBEvdfXYz0ryp8= ------END PUBLIC KEY----- +95f093cb4b0b727fb38ddbfc8a3364d72d96b25882e44c5ca555644fc18fc2b2 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-ip/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-ip/value deleted file mode 100644 index f82f151..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-ip/value +++ /dev/null @@ -1 +0,0 @@ -201:b9d:4329:71c2:79ca:3648:e86d:4236 \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/machines/server b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/machines/server deleted file mode 120000 index 2bd819e..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/machines/server +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/server \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/secret b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/secret deleted file mode 100644 index f4cb4f3..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/secret +++ /dev/null @@ -1,18 +0,0 @@ -{ - "data": "ENC[AES256_GCM,data:bAJv40t1hkgMBz4boOEIw9GIY/4UFYI2WZidRrU88ky0JDBwSmHDnxuBFRpdjfAx7sO9d6vSHxAmaf45NlTKwnkUJnaIW0NK+979lXbLV+AG0suc4Vci6fsdbAHofR//3DfTYs03HoALkRUgRemTl7kQtdMB6E6LN02OE19HSVuFjMiEipwosmFfMoR69ZZuPSzNg1uVnw==,iv:LbZZGbwkXc71PAKgjD2CvXJVtyiqgi+cNsBzbulPyIk=,tag:VPESc2Y8SdU5CovQinZleA==,type:str]", - "sops": { - "age": [ - { - "recipient": "age1fdkan6n20swmut0sa86g5a6gxrj8qj2sgqe8hxtw32c0u9rr4drqlyr5mf", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2L3FzSkh3UTkvdTR6eEVE\nNnYrcnAydUl4QnJSbWJRbFFQODFmSld1YkJZCnNFUHp6YjQ3WW0vVHh5dXZBMzFJ\nYWRnZWNHN2dkL1JBWHNrZTcvTHI5S3MKLS0tIGpnekxneURuMWFkQ1RLTEszTFhi\nUUkzR2ljTXFhb1c2RDhpeFJXaXpYakUK/fLOqjNR2LML7uN3fiB9GdhWTDcr0wn4\n37ESeS1kx0EobRMaDVu8GPZovcdypFOOPiuUpEu6hIEdwvl736oDSA==\n-----END AGE ENCRYPTED FILE-----\n" - }, - { - "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", - "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjQWE2L3F1RW9oNlQzR0I3\nZDJoZ1hIM2N0Mm1aQ1IrN1gweWZxeWVGdUdzCmlIZXdKYm8rUGkvVVpiY1BJZmlD\nN0MwRHN3dUNrRU9McjNFMXpranJGU1UKLS0tIG1yMmJGaEp1cU1iZGdqdzRUTWZW\nZzZrVkRuOTBTcnFuaTE1Um1ISUxBaTQKf842rL3N7Gl1QfrIURWiu26LwO0ERkP4\nvfXN2HH1jjp2pblQF9qb+5vmUsaX1pPSY1R+YMvUK7wIwOb9zyIfmQ==\n-----END AGE ENCRYPTED FILE-----\n" - } - ], - "lastmodified": "2025-10-31T07:50:02Z", - "mac": "ENC[AES256_GCM,data:/c1jqMSLd9/fxm1PxtRITr3qkjtrlJ/5wJgEVkraAqU6XTuzSyseMhdeWLL2Fy2OunT/+alM6VpaliBmcZqRkSSGngbuvMsujDiJLgUXQ8wNhMcK7ln/dqFMcA+RYGxihGEMwuPKs3yOVKj9PDzFYnm6TUCFkU/heotI/hQ1lQI=,iv:EsWsi4r4DctMLvi4WmqKe0D1NTHwYVKCcxKQ+6grzYg=,tag:28dmpzF/6dbvGIriaBVKPw==,type:str]", - "version": "3.11.0" - } -} diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/users/admin b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/users/admin deleted file mode 120000 index ca714e1..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-secret/users/admin +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/admin \ No newline at end of file diff --git a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-subnet/value b/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-subnet/value deleted file mode 100644 index 05251df..0000000 --- a/modules/clan/phonebox/tests/vm/vars/per-machine/server/yggdrasil/yggdrasil-subnet/value +++ /dev/null @@ -1 +0,0 @@ -301:b9d:4329:71c2::/64 \ No newline at end of file diff --git a/vars/per-machine/adhil/phonebox/ata-local-number/value b/vars/per-machine/adhil/phonebox/ata-local-number/value deleted file mode 100644 index 857f065..0000000 --- a/vars/per-machine/adhil/phonebox/ata-local-number/value +++ /dev/null @@ -1 +0,0 @@ -00 \ No newline at end of file diff --git a/vars/per-machine/adhil/phonebox/server-prefix-number/value b/vars/per-machine/adhil/phonebox/server-prefix-number/value deleted file mode 100644 index 2b82dfe..0000000 --- a/vars/per-machine/adhil/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -60 \ No newline at end of file diff --git a/vars/per-machine/almach/phonebox/ata-local-number/value b/vars/per-machine/almach/phonebox/ata-local-number/value deleted file mode 100644 index 857f065..0000000 --- a/vars/per-machine/almach/phonebox/ata-local-number/value +++ /dev/null @@ -1 +0,0 @@ -00 \ No newline at end of file diff --git a/vars/per-machine/almach/phonebox/server-prefix-number/value b/vars/per-machine/almach/phonebox/server-prefix-number/value deleted file mode 100644 index 8580e7b..0000000 --- a/vars/per-machine/almach/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -30 \ No newline at end of file diff --git a/vars/per-machine/alpheratz/phonebox/ata-local-number/value b/vars/per-machine/alpheratz/phonebox/ata-local-number/value deleted file mode 100644 index 857f065..0000000 --- a/vars/per-machine/alpheratz/phonebox/ata-local-number/value +++ /dev/null @@ -1 +0,0 @@ -00 \ No newline at end of file diff --git a/vars/per-machine/alpheratz/phonebox/server-prefix-number/value b/vars/per-machine/alpheratz/phonebox/server-prefix-number/value deleted file mode 100644 index 86ee83a..0000000 --- a/vars/per-machine/alpheratz/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -40 \ No newline at end of file diff --git a/vars/per-machine/buna/phonebox/ata-local-number/value b/vars/per-machine/buna/phonebox/ata-local-number/value deleted file mode 100644 index 857f065..0000000 --- a/vars/per-machine/buna/phonebox/ata-local-number/value +++ /dev/null @@ -1 +0,0 @@ -00 \ No newline at end of file diff --git a/vars/per-machine/buna/phonebox/server-prefix-number/value b/vars/per-machine/buna/phonebox/server-prefix-number/value deleted file mode 100644 index d7765fe..0000000 --- a/vars/per-machine/buna/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -70 \ No newline at end of file diff --git a/vars/per-machine/mirach/phonebox/ata-local-number/value b/vars/per-machine/mirach/phonebox/ata-local-number/value deleted file mode 100644 index 857f065..0000000 --- a/vars/per-machine/mirach/phonebox/ata-local-number/value +++ /dev/null @@ -1 +0,0 @@ -00 \ No newline at end of file diff --git a/vars/per-machine/mirach/phonebox/server-prefix-number/value b/vars/per-machine/mirach/phonebox/server-prefix-number/value deleted file mode 100644 index c5b431b..0000000 --- a/vars/per-machine/mirach/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -50 \ No newline at end of file diff --git a/vars/per-machine/neptune/phonebox/ata-local-number/value b/vars/per-machine/neptune/phonebox/ata-local-number/value deleted file mode 100644 index 857f065..0000000 --- a/vars/per-machine/neptune/phonebox/ata-local-number/value +++ /dev/null @@ -1 +0,0 @@ -00 \ No newline at end of file diff --git a/vars/per-machine/neptune/phonebox/server-prefix-number/value b/vars/per-machine/neptune/phonebox/server-prefix-number/value deleted file mode 100644 index 9a03714..0000000 --- a/vars/per-machine/neptune/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -10 \ No newline at end of file diff --git a/vars/per-machine/rigel/phonebox/ata-local-number/value b/vars/per-machine/rigel/phonebox/ata-local-number/value deleted file mode 100644 index 857f065..0000000 --- a/vars/per-machine/rigel/phonebox/ata-local-number/value +++ /dev/null @@ -1 +0,0 @@ -00 \ No newline at end of file diff --git a/vars/per-machine/rigel/phonebox/server-prefix-number/value b/vars/per-machine/rigel/phonebox/server-prefix-number/value deleted file mode 100644 index 2edeafb..0000000 --- a/vars/per-machine/rigel/phonebox/server-prefix-number/value +++ /dev/null @@ -1 +0,0 @@ -20 \ No newline at end of file