Monorepo for Tangled

local-dev: add tmux orchestrator for offline development #9

open opened by nolith.dev targeting master from local-dev

Single command that launches a tmux session with 4 panes: VM, redis, appview (with DID polling and label env setup), and tailwind watcher.

The appview pane waits for the VM bootstrap to write the owner DID file to the 9p-shared nix/vm-data/atproto/owner-did, then constructs TANGLED_LABEL_DEFAULTS and TANGLED_LABEL_GFI from it before starting the appview watcher.

Sets TANGLED_DEV=true so the appview uses DevDirectory for .test TLD handle resolution via the local PDS.

Requires running from within nix develop with TANGLED_VM_LOCAL_DEV=1.

Signed-off-by: Alessio Caiazza code.git@caiazza.info

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:nzep3slobztdph3kxswzbing/sh.tangled.repo.pull/3mgmn4naung22
+210
Diff #1
+210
contrib/local-dev.sh
··· 1 + #!/usr/bin/env bash 2 + # 3 + # local-dev.sh — tmux orchestrator for fully offline Tangled development. 4 + # 5 + # Launches a tmux session with 4 horizontal panes: 6 + # ┌────────────────────────────────────────────┐ 7 + # │ VM │ 8 + # │ nix run --impure .#vm │ 9 + # │ │ 10 + # ├────────────────────────────────────────────┤ 11 + # │ Appview (waits for DID, starts appview) │ 12 + # │ │ 13 + # ├────────────────────────────────────────────┤ 14 + # │ Tailwind — nix run .#watch-tailwind │ 15 + # ├────────────────────────────────────────────┤ 16 + # │ Redis — redis-server │ 17 + # └────────────────────────────────────────────┘ 18 + # 19 + # Must be run from within `nix develop` with TANGLED_VM_LOCAL_DEV=1. 20 + set -euo pipefail 21 + 22 + SESSION="tangled-dev" 23 + TMUX_SOCKET="tangled-dev" 24 + tmux_cmd="tmux -L $TMUX_SOCKET" 25 + DID_FILE="nix/vm-data/atproto/owner-did" 26 + 27 + # ── Sub-commands (invoked by tmux panes) ───────────────────────────── 28 + 29 + run_appview() { 30 + echo "── Appview ──" 31 + echo "appview: waiting for VM bootstrap (DID file)..." 32 + echo "appview: this may take several minutes on first build" 33 + local timeout=600 34 + local elapsed=0 35 + while [[ ! -s "$DID_FILE" ]]; do 36 + sleep 2 37 + elapsed=$((elapsed + 2)) 38 + if ((elapsed % 30 == 0)); then 39 + echo "appview: still waiting... (${elapsed}s)" 40 + fi 41 + if [[ $elapsed -ge $timeout ]]; then 42 + echo "appview: ERROR: timed out waiting for DID file after ${timeout}s" 43 + echo "appview: check the VM pane for errors" 44 + return 1 45 + fi 46 + done 47 + 48 + local did 49 + did=$(cat "$DID_FILE") 50 + echo "appview: owner DID = $did" 51 + 52 + # Build TANGLED_LABEL_DEFAULTS from the owner DID 53 + local labels=() 54 + for rkey in wontfix good-first-issue duplicate documentation assignee; do 55 + labels+=("at://$did/sh.tangled.label.definition/$rkey") 56 + done 57 + local IFS=',' 58 + export TANGLED_LABEL_DEFAULTS="${labels[*]}" 59 + export TANGLED_LABEL_GFI="at://$did/sh.tangled.label.definition/good-first-issue" 60 + echo "appview: TANGLED_LABEL_DEFAULTS set" 61 + echo "" 62 + 63 + nix run .#watch-appview 64 + } 65 + 66 + # ── Sub-command / flag dispatch ─────────────────────────────────────── 67 + 68 + # When invoked as a sub-command from a tmux pane, dispatch immediately 69 + if [[ "${1:-}" == "--appview" ]]; then 70 + run_appview 71 + exit $? 72 + fi 73 + 74 + # --reset: wipe all persistent state and start fresh 75 + if [[ "${1:-}" == "--reset" ]]; then 76 + root_dir=$(jj --ignore-working-copy root 2>/dev/null || git rev-parse --show-toplevel 2>/dev/null) || { 77 + echo "error: can't find repo root" 78 + exit 1 79 + } 80 + cd "$root_dir" 81 + 82 + # Kill running session first 83 + if $tmux_cmd has-session -t "$SESSION" 2>/dev/null; then 84 + echo "Stopping running local-dev session..." 85 + for pid in $($tmux_cmd list-panes -s -t "$SESSION" -F '#{pane_pid}' 2>/dev/null); do 86 + pkill -TERM -P "$pid" 2>/dev/null || true 87 + done 88 + sleep 0.5 89 + $tmux_cmd kill-session -t "$SESSION" 2>/dev/null || true 90 + fi 91 + 92 + echo "This will delete ALL local-dev state:" 93 + echo " - nix/vm-data/knot/*" 94 + echo " - nix/vm-data/spindle/*" 95 + echo " - nix/vm-data/atproto/*" 96 + echo " - nixos.qcow2 (VM disk image)" 97 + echo " - appview.db* (appview database)" 98 + echo "" 99 + read -rp "Are you sure? [y/N] " confirm 100 + if [[ "$confirm" != [yY] ]]; then 101 + echo "Aborted." 102 + exit 0 103 + fi 104 + 105 + rm -rf nix/vm-data/knot/* 106 + rm -rf nix/vm-data/spindle/* 107 + rm -rf nix/vm-data/atproto/* 108 + rm -f nixos.qcow2 109 + rm -f appview.db appview.db-shm appview.db-wal 110 + echo "Done. All state has been reset." 111 + echo "Run 'nix run .#local-dev' to start fresh." 112 + exit 0 113 + fi 114 + 115 + # ── Set up local-dev environment ────────────────────────────────────── 116 + 117 + # This is the offline dev orchestrator — always enable local-dev mode 118 + export TANGLED_VM_LOCAL_DEV=1 119 + export TANGLED_DEV=true 120 + 121 + # AT Protocol endpoints (same as the devshell hook) 122 + export TANGLED_PLC_URL="http://localhost:2582" 123 + export TANGLED_PDS_HOST="http://localhost:2583" 124 + export TANGLED_PDS_ADMIN_SECRET="tangled-local-dev" 125 + export TANGLED_JETSTREAM_ENDPOINT="ws://localhost:6008/subscribe" 126 + 127 + # Check we're in the devshell (need go, air, etc. on PATH) 128 + if [[ -z "${TANGLED_OAUTH_CLIENT_KID:-}" ]]; then 129 + echo "error: must be run from within the nix devshell" 130 + echo "" 131 + echo " nix develop --impure" 132 + echo " nix run .#local-dev" 133 + exit 1 134 + fi 135 + 136 + # Check tmux is available 137 + if ! command -v tmux &>/dev/null; then 138 + echo "error: tmux is not installed" 139 + exit 1 140 + fi 141 + 142 + # Find repo root 143 + root_dir=$(jj --ignore-working-copy root 2>/dev/null || git rev-parse --show-toplevel 2>/dev/null) || { 144 + echo "error: can't find repo root" 145 + exit 1 146 + } 147 + cd "$root_dir" 148 + 149 + # Ensure atproto data dir exists 150 + mkdir -p nix/vm-data/atproto 151 + 152 + # Path to this script (for tmux pane sub-commands) 153 + self="$(realpath "$0")" 154 + 155 + # ── Kill existing session and its processes ─────────────────────────── 156 + if $tmux_cmd has-session -t "$SESSION" 2>/dev/null; then 157 + # List all pane PIDs in the session and kill their process trees 158 + for pid in $($tmux_cmd list-panes -s -t "$SESSION" -F '#{pane_pid}' 2>/dev/null); do 159 + pkill -TERM -P "$pid" 2>/dev/null || true 160 + done 161 + sleep 0.5 162 + $tmux_cmd kill-session -t "$SESSION" 2>/dev/null || true 163 + fi 164 + 165 + # ── Create tmux session ────────────────────────────────────────────── 166 + # 167 + # Layout (all horizontal splits): 168 + # ┌────────────────────────┐ 169 + # │ VM (%0) │ ← largest 170 + # ├────────────────────────┤ 171 + # │ Appview (%1) │ ← medium 172 + # ├────────────────────────┤ 173 + # │ Tailwind (%2) │ ← few lines 174 + # ├────────────────────────┤ 175 + # │ Redis (%3) │ ← few lines 176 + # └────────────────────────┘ 177 + # 178 + # We use a dedicated tmux socket (-L tangled-dev) so the server is 179 + # always started fresh from the nix devshell. All panes inherit the 180 + # correct environment (TANGLED_*, PATH, etc.) without any workarounds. 181 + # 182 + # We use the unique pane IDs (%N) returned by tmux to target splits 183 + # reliably, avoiding issues with session/window/pane index lookups. 184 + 185 + vm_pane=$($tmux_cmd new-session -d -s "$SESSION" -c "$root_dir" -P -F '#{pane_id}' \ 186 + "echo '── VM ──'; nix run --impure .#vm; exec \$SHELL") 187 + 188 + appview_pane=$($tmux_cmd split-window -t "$vm_pane" -v -P -F '#{pane_id}' \ 189 + "bash \"$self\" --appview; exec \$SHELL") 190 + 191 + tailwind_pane=$($tmux_cmd split-window -t "$appview_pane" -v -P -F '#{pane_id}' \ 192 + "echo '── Tailwind ──'; nix run .#watch-tailwind; exec \$SHELL") 193 + 194 + redis_pane=$($tmux_cmd split-window -t "$tailwind_pane" -v -P -F '#{pane_id}' \ 195 + "echo '── Redis ──'; redis-server; exec \$SHELL") 196 + 197 + # Give VM the most space; tailwind and redis just a few lines each 198 + $tmux_cmd resize-pane -t "$redis_pane" -y 5 199 + $tmux_cmd resize-pane -t "$tailwind_pane" -y 5 200 + 201 + $tmux_cmd select-pane -t "$appview_pane" 202 + 203 + echo "Tangled local-dev session starting..." 204 + echo " tmux session: $SESSION" 205 + echo " appview: http://localhost:3000" 206 + echo " PDS: http://localhost:2583" 207 + echo "" 208 + 209 + # Attach to the session (always attach since we use a dedicated socket) 210 + exec $tmux_cmd attach-session -t "$SESSION"

History

2 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
local-dev: add tmux orchestrator for offline development
no conflicts, ready to merge
expand 0 comments
1 commit
expand
local-dev: add tmux orchestrator for offline development
expand 0 comments