fix(worktree): derive the swarm lab subnet without an octet overflow

scripts/system/worktree/up.sh pinned INFINITO_SWARM_LAB_SUBNET to
192.168.$((244 + slot)).0/24. From slot 12 on that third octet exceeds
255, so every worktree past slot 11 carries an invalid subnet
(192.168.358.0/24 for slot 114), and make swarm-zombie dies at the lab
network creation with `invalid subnet: netip.ParsePrefix(...)`.

The lab prefix is now 10.244.<slot>, valid and distinct for every slot
up to max_slot 255, and outside both the primary lab subnet
192.168.244.0/24 and the 172.30.0.0/16 worktree subnets. The host
daemon allocates nothing from 10.0.0.0/8.

A new lint renders the prefix for every slot and fails on an invalid,
repeated or overlapping subnet; against the old derivation it fails 247
of 255 slots.

Worktrees that were pinned before this fix keep their custom.env; the
ones at slot 12 or above need their INFINITO_SWARM_*_IP and
INFINITO_SWARM_LAB_SUBNET pins rewritten to 10.244.<slot>.x and a
make dotenv before they can run swarm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-28 22:38:54 +01:00
1 parent 4f641f0322
commit a7685e3b21
2 files changed
+74 -13

No files matched your search

+13 -13
View File
@@ -53,7 +53,7 @@ git worktree add "${path}" "${branch}"
git_common_dir="$(git rev-parse --path-format=absolute --git-common-dir)"
lab_octet="$((244 + slot))"
lab_prefix="10.244.${slot}"
echo ">>> Pinning slot ${slot} in ${path}/custom.env"
cat >"${path}/custom.env" <<PINS
@@ -64,12 +64,12 @@ INFINITO_GATEWAY=172.30.${slot}.1
INFINITO_DNS_IP=172.30.${slot}.53
INFINITO_IP4=172.30.${slot}.10
INFINITO_BIND_IP=127.0.0.$((slot + 1))
INFINITO_SWARM_LAB_SUBNET=192.168.${lab_octet}.0/24
INFINITO_SWARM_MGR_IP=192.168.${lab_octet}.10
INFINITO_SWARM_WRK1_IP=192.168.${lab_octet}.11
INFINITO_SWARM_WRK2_IP=192.168.${lab_octet}.12
INFINITO_SWARM_NFS_IP=192.168.${lab_octet}.13
INFINITO_SWARM_BACKUP_IP=192.168.${lab_octet}.14
INFINITO_SWARM_LAB_SUBNET=${lab_prefix}.0/24
INFINITO_SWARM_MGR_IP=${lab_prefix}.10
INFINITO_SWARM_WRK1_IP=${lab_prefix}.11
INFINITO_SWARM_WRK2_IP=${lab_prefix}.12
INFINITO_SWARM_NFS_IP=${lab_prefix}.13
INFINITO_SWARM_BACKUP_IP=${lab_prefix}.14
INFINITO_RUNNER_PREFIX=infinito-${slug}
INFINITO_CONTAINER=infinito_nexus_${slug//[.-]/_}
INFINITO_CACHE_NETWORK=${cache_network}
@@ -86,12 +86,12 @@ env -i -C "${path}" \
INFINITO_DNS_IP="172.30.${slot}.53" \
INFINITO_IP4="172.30.${slot}.10" \
INFINITO_BIND_IP="127.0.0.$((slot + 1))" \
INFINITO_SWARM_LAB_SUBNET="192.168.${lab_octet}.0/24" \
INFINITO_SWARM_MGR_IP="192.168.${lab_octet}.10" \
INFINITO_SWARM_WRK1_IP="192.168.${lab_octet}.11" \
INFINITO_SWARM_WRK2_IP="192.168.${lab_octet}.12" \
INFINITO_SWARM_NFS_IP="192.168.${lab_octet}.13" \
INFINITO_SWARM_BACKUP_IP="192.168.${lab_octet}.14" \
INFINITO_SWARM_LAB_SUBNET="${lab_prefix}.0/24" \
INFINITO_SWARM_MGR_IP="${lab_prefix}.10" \
INFINITO_SWARM_WRK1_IP="${lab_prefix}.11" \
INFINITO_SWARM_WRK2_IP="${lab_prefix}.12" \
INFINITO_SWARM_NFS_IP="${lab_prefix}.13" \
INFINITO_SWARM_BACKUP_IP="${lab_prefix}.14" \
INFINITO_RUNNER_PREFIX="infinito-${slug}" \
INFINITO_CACHE_NETWORK="${cache_network}" \
python3 -m cli.meta.env
@@ -0,0 +1,61 @@
"""Every worktree slot must pin a valid swarm lab subnet of its own.
`scripts/system/worktree/up.sh` derives `INFINITO_SWARM_LAB_SUBNET` from the
slot. A derivation that overflows an octet yields an address Docker rejects at
network creation, so `make swarm-zombie` dies in every worktree past that slot.
"""
from __future__ import annotations
import ipaddress
import re
import unittest
from utils.cache.files import read_text
from utils.env.parser import parse_static_env
from . import PROJECT_ROOT
_UP_SH = PROJECT_ROOT / "scripts/system/worktree/up.sh"
_MAX_SLOT_RE = re.compile(r"^max_slot=(?P<value>\d+)$", re.MULTILINE)
_LAB_PREFIX_RE = re.compile(r'^lab_prefix="(?P<template>[^"]+)"$', re.MULTILINE)
_LAB_SUBNET_RE = re.compile(
r"^INFINITO_SWARM_LAB_SUBNET=\$\{lab_prefix\}\.0/24$", re.MULTILINE
)
class TestWorktreeSwarmLabSubnet(unittest.TestCase):
def setUp(self) -> None:
source = read_text(str(_UP_SH))
max_slot = _MAX_SLOT_RE.search(source)
lab_prefix = _LAB_PREFIX_RE.search(source)
self.assertIsNotNone(max_slot, f"{_UP_SH} declares no max_slot")
self.assertIsNotNone(lab_prefix, f"{_UP_SH} declares no lab_prefix")
self.assertRegex(source, _LAB_SUBNET_RE)
self.max_slot = int(max_slot.group("value"))
self.template = lab_prefix.group("template")
def _subnet(self, slot: int) -> ipaddress.IPv4Network:
prefix = self.template.replace("${slot}", str(slot))
return ipaddress.ip_network(f"{prefix}.0/24")
def test_every_slot_pins_a_valid_distinct_lab_subnet(self) -> None:
primary = ipaddress.ip_network(
parse_static_env(PROJECT_ROOT / "default.env")["INFINITO_SWARM_LAB_SUBNET"]
)
worktree_subnets = ipaddress.ip_network("172.30.0.0/16")
seen: dict[ipaddress.IPv4Network, int] = {}
for slot in range(1, self.max_slot + 1):
with self.subTest(slot=slot):
subnet = self._subnet(slot)
self.assertFalse(subnet.overlaps(primary), f"slot {slot} overlaps {primary}")
self.assertFalse(
subnet.overlaps(worktree_subnets),
f"slot {slot} overlaps the worktree subnets {worktree_subnets}",
)
self.assertNotIn(subnet, seen, f"slot {slot} repeats slot {seen.get(subnet)}")
seen[subnet] = slot
if __name__ == "__main__":
unittest.main()