Replace the stub dev-env with the real, working implementation that grew
in the reference machine config — de-identified for the public scaffold.
Nix layer:
- options.nix: full project schema (url/upstream/fork/category/
worktreeRoot/worktrees{branch,path,remote}/isClone/deployFlakeInput),
deploy.targets, github.forkUser, writeDirenvHints. Drops the
forgejo-URL block + deploy-flake auto-derivation (incoherent in a
scaffold that uses explicit per-project urls).
- lib.nix: mkProject + worktreePath/bareRepoPath/projectRemotes,
generalized to the explicit-url model (origin falls back to upstream).
- config.nix: renders /etc/dev-env/{config.sh,projects.json,
tmux-sessions.json}, installs helpers via writeShellScriptBin, loads
shell functions into interactive shells, wires the git pre-commit hook.
Scripts (config-driven, read /etc/dev-env at runtime):
- bootstrap.sh, nav.sh, worktree.sh, pr-helpers.sh, rebase.sh,
status.sh, deploy.sh, regtest.sh, tmux-launch.sh.
- Stripped aiolabs/forgejo/bitspire/lamassu/webapp hardcoding; the
github-fork remote is renamed 'fork' to match git.remotes vocabulary.
- Removes the dev.sh stub (the matured impl uses discrete commands +
shell functions, not a unified 'dev' CLI).
presets/example.nix: a worked, generic project list replacing the
identity-specific aiolabs preset. tests/smoke.nix + flake checks
exercise the schema; 'nix flake check' is green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
131 lines
4.2 KiB
Bash
131 lines
4.2 KiB
Bash
#!/usr/bin/env bash
|
|
# dev-tm: launch a declared tmux session
|
|
#
|
|
# Reads /etc/dev-env/tmux-sessions.json (rendered by config.nix) and
|
|
# either attaches to an existing session or creates one with the
|
|
# declared windows. Window cwds are resolved relative to $DEV_ROOT.
|
|
#
|
|
# This is a single generic launcher; the window layouts come from Nix
|
|
# (or a runtime override at ~/.config/dev-env/tmux-sessions.json).
|
|
|
|
set -euo pipefail
|
|
|
|
if [[ -r /etc/dev-env/config.sh ]]; then
|
|
# shellcheck disable=SC1091
|
|
source /etc/dev-env/config.sh
|
|
fi
|
|
|
|
DEV_ROOT="${DEV_ROOT:-$HOME/dev}"
|
|
|
|
# Prefer a user override if present
|
|
SESSIONS_JSON=""
|
|
if [[ -r "$HOME/.config/dev-env/tmux-sessions.json" ]]; then
|
|
SESSIONS_JSON="$HOME/.config/dev-env/tmux-sessions.json"
|
|
elif [[ -r /etc/dev-env/tmux-sessions.json ]]; then
|
|
SESSIONS_JSON=/etc/dev-env/tmux-sessions.json
|
|
else
|
|
echo "No tmux-sessions.json found" >&2
|
|
exit 1
|
|
fi
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
dev-tm — declarative tmux session launcher
|
|
|
|
USAGE:
|
|
dev-tm list available sessions
|
|
dev-tm <session> start or attach to <session>
|
|
dev-tm -k <session> kill session
|
|
dev-tm -k all kill all dev sessions
|
|
dev-tm -l list available sessions
|
|
EOF
|
|
}
|
|
|
|
list_sessions() {
|
|
echo "Defined sessions (from $SESSIONS_JSON):"
|
|
jq -r 'keys[]' "$SESSIONS_JSON" | sed 's/^/ /'
|
|
echo ""
|
|
echo "Active tmux sessions:"
|
|
tmux list-sessions 2>/dev/null | sed 's/^/ /' || echo " (none)"
|
|
}
|
|
|
|
resolve_path() {
|
|
local p="$1"
|
|
[[ -z "$p" || "$p" == "null" ]] && { echo "$DEV_ROOT"; return; }
|
|
[[ "$p" = /* ]] && { echo "$p"; return; }
|
|
echo "$DEV_ROOT/$p"
|
|
}
|
|
|
|
start_session() {
|
|
local name="$1"
|
|
local session_name="dev-$name"
|
|
|
|
if tmux has-session -t "$session_name" 2>/dev/null; then
|
|
tmux attach-session -t "$session_name"
|
|
return
|
|
fi
|
|
|
|
local session_cwd
|
|
session_cwd="$(resolve_path "$(jq -r --arg s "$name" '.[$s].cwd // empty' "$SESSIONS_JSON")")"
|
|
|
|
local nwindows
|
|
nwindows="$(jq --arg s "$name" '.[$s].windows | length' "$SESSIONS_JSON")"
|
|
if (( nwindows == 0 )); then
|
|
echo "Session '$name' has no windows defined" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# First window
|
|
local first_name first_cwd first_cmd
|
|
first_name="$(jq -r --arg s "$name" '.[$s].windows[0].name' "$SESSIONS_JSON")"
|
|
first_cwd="$(resolve_path "$(jq -r --arg s "$name" '.[$s].windows[0].cwd // empty' "$SESSIONS_JSON")")"
|
|
first_cmd="$(jq -r --arg s "$name" '.[$s].windows[0].cmd // empty' "$SESSIONS_JSON")"
|
|
|
|
tmux new-session -d -s "$session_name" -n "$first_name" -c "$first_cwd"
|
|
[[ -n "$first_cmd" && "$first_cmd" != "null" ]] && \
|
|
tmux send-keys -t "$session_name:0" "$first_cmd" C-m
|
|
|
|
# Remaining windows
|
|
for ((i = 1; i < nwindows; i++)); do
|
|
local wn wcwd wcmd
|
|
wn="$(jq -r --arg s "$name" --argjson i "$i" '.[$s].windows[$i].name' "$SESSIONS_JSON")"
|
|
wcwd="$(resolve_path "$(jq -r --arg s "$name" --argjson i "$i" '.[$s].windows[$i].cwd // empty' "$SESSIONS_JSON")")"
|
|
wcmd="$(jq -r --arg s "$name" --argjson i "$i" '.[$s].windows[$i].cmd // empty' "$SESSIONS_JSON")"
|
|
tmux new-window -t "$session_name" -n "$wn" -c "$wcwd"
|
|
[[ -n "$wcmd" && "$wcmd" != "null" ]] && \
|
|
tmux send-keys -t "$session_name:$i" "$wcmd" C-m
|
|
done
|
|
|
|
tmux select-window -t "$session_name:0"
|
|
tmux attach-session -t "$session_name"
|
|
}
|
|
|
|
kill_session() {
|
|
local target="$1"
|
|
if [[ "$target" == "all" ]]; then
|
|
tmux list-sessions -F '#{session_name}' 2>/dev/null \
|
|
| grep '^dev-' \
|
|
| xargs -r -n1 tmux kill-session -t \
|
|
|| echo "No dev sessions to kill"
|
|
return
|
|
fi
|
|
tmux kill-session -t "dev-$target" 2>/dev/null || echo "Session 'dev-$target' not found"
|
|
}
|
|
|
|
# --- entry ---
|
|
case "${1:-}" in
|
|
-h|--help) usage ;;
|
|
-l|--list|"") list_sessions ;;
|
|
-k|--kill)
|
|
[[ -z "${2:-}" ]] && { echo "Usage: dev-tm -k <session|all>"; exit 1; }
|
|
kill_session "$2"
|
|
;;
|
|
*)
|
|
if ! jq -e --arg s "$1" 'has($s)' "$SESSIONS_JSON" >/dev/null; then
|
|
echo "Unknown session: $1" >&2
|
|
list_sessions
|
|
exit 1
|
|
fi
|
|
start_session "$1"
|
|
;;
|
|
esac
|