Files
computer-playbook/scripts/system/worktree/up.sh
T
kevinveenbirkenbachandClaude Opus 5 96df7ea0eb feat(env): give every checkout its own stack domain
Every stack answered to *.infinito.test, and the host resolver pointed that
zone at 127.0.0.1, so a worktree's stack on its own bind IP could not be opened
in a browser while the primary stack existed.

INFINITO_DOMAIN is now derived per checkout: main.infinito.test for the
primary one, and for a linked worktree its branch, lowercased with everything
outside a-z0-9_- turned into "-", below INFINITO_DNS_DOMAIN
(feature/store-i18n -> feature-store-i18n.infinito.test). The branch is read
from the worktree's gitdir HEAD instead of running git, because git refuses the
mounted checkout inside the stack container. GitHub and act runs keep the
static default, custom.env still wins.

The handler overrides the caller's value on purpose: inside the stack
container that value is the previous .env, which make setup rewrote on every
deploy, so the old domain kept feeding itself back into the next compose up.

make network-dns-setup routes each stack's domain to the INFINITO_BIND_IP in
that checkout's .env, with a local= line so AAAA answers NODATA instead of the
primary stack's ::1. It skips domains outside the base zone, which a stale
.env still carries, and fails when two checkouts claim one domain.
worktree-up prints the domain and the follow-up command.

Existing checkouts switch after make dotenv-force and a redeploy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 17:44:38 +02:00

120 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# shellcheck shell=bash
#
# Create an isolated git worktree for a branch and pin it to a free slot, so
# its compose stack no longer collides with the primary checkout on subnet,
# host ports, container names or volumes. The cache stack is not duplicated:
# the worktree joins the running one over an external network.
#
# Use `make worktree-up` rather than calling this script directly.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
# shellcheck source=scripts/system/worktree/lib.sh
source "${SCRIPT_DIR}/lib.sh"
branch="${1:?branch name required}"
base="${2:-}"
max_slot=255
cd "${REPO_ROOT}"
base="${base:-$(worktree_default_base)}"
slug="$(worktree_slug "${branch}")"
if [ -z "${slug}" ]; then
echo "FAILURE: branch '${branch}' slugs to an empty name" >&2
exit 1
fi
path="$(worktree_path "${branch}" "${base}")"
mkdir -p "$(dirname "${path}")"
if [ -e "${path}" ]; then
echo "FAILURE: ${path} already exists; run 'make worktree-down branch=${branch}' first" >&2
exit 1
fi
slot="$(worktree_next_slot)"
if [ "${slot}" -gt "${max_slot}" ]; then
echo "FAILURE: no free slot left (max ${max_slot}); tear down an existing worktree" >&2
exit 1
fi
cache_network="$(worktree_cache_network)"
if [ -z "${cache_network}" ]; then
echo ">>> WARNING: no running cache stack found; the worktree will start without a shared cache"
fi
echo ">>> Creating worktree for '${branch}' at ${path} (slot ${slot})"
git worktree add "${path}" "${branch}"
git_common_dir="$(git rev-parse --path-format=absolute --git-common-dir)"
lab_octet="$((244 + slot))"
echo ">>> Pinning slot ${slot} in ${path}/custom.env"
cat >"${path}/custom.env" <<PINS
INFINITO_INSTANCE=${slot}
INFINITO_GIT_COMMON_DIR=${git_common_dir}
INFINITO_SUBNET=172.30.${slot}.0/24
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_RUNNER_PREFIX=infinito-${slug}
INFINITO_CONTAINER=infinito_nexus_${slug//[.-]/_}
INFINITO_CACHE_NETWORK=${cache_network}
PINS
echo ">>> Generating .env from the pins"
env -i -C "${path}" \
HOME="${HOME}" \
PATH="${PATH}" \
INFINITO_INSTANCE="${slot}" \
INFINITO_GIT_COMMON_DIR="${git_common_dir}" \
INFINITO_SUBNET="172.30.${slot}.0/24" \
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_RUNNER_PREFIX="infinito-${slug}" \
INFINITO_CACHE_NETWORK="${cache_network}" \
python3 -m cli.meta.env
cat <<SUMMARY
Worktree ready.
branch ${branch}
path ${path}
slot ${slot}
subnet 172.30.${slot}.0/24
bind IP 127.0.0.$((slot + 1))
container infinito_nexus_${slug//[.-]/_}
cache net ${cache_network:-<none>}
domain $(awk -F= '$1 == "INFINITO_DOMAIN" { print $2 }' "${path}/.env")
Next:
cd ${path}
make compose-up
make network-dns-setup # routes the domain above to the bind IP
Release with:
make worktree-down branch=${branch}
SUMMARY