feat(dev-env): backport matured dev-env implementation from /etc/nixos
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>
This commit is contained in:
parent
773632562e
commit
e38d313db2
17 changed files with 2925 additions and 147 deletions
267
modules/dev-env/scripts/regtest.sh
Normal file
267
modules/dev-env/scripts/regtest.sh
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
#!/usr/bin/env bash
|
||||
# dev-env: Bitcoin/Lightning regtest docker environment
|
||||
#
|
||||
# Provides:
|
||||
# regtest-start [worktree|pr:<branch>] [--path <dir>] [--seed|--keep]
|
||||
# regtest-stop
|
||||
# regtest-status
|
||||
# regtest-logs [service]
|
||||
# regtest-cli # source CLI helpers
|
||||
# regtest # cd to regtest dir
|
||||
#
|
||||
# Reads paths from /etc/dev-env/config.sh. Wraps the
|
||||
# `lnbits/legend-regtest-enviroment` compose stack (or your fork of it,
|
||||
# set via devEnv.regtest.repoUrl). Container names and ports below assume
|
||||
# that stack's layout — adjust if you've customised it.
|
||||
#
|
||||
# This file is sourced into interactive shells (function definitions) AND
|
||||
# dropped into the system path as standalone wrapper scripts so
|
||||
# `regtest-start` works from a fresh non-interactive shell.
|
||||
|
||||
_devenv_load_config() {
|
||||
if [[ -r /etc/dev-env/config.sh ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/dev-env/config.sh
|
||||
fi
|
||||
REGTEST_DIR="${LOCAL_DIR:-$DEV_ROOT/local}/docker/regtest"
|
||||
LNBITS_DIR="${LNBITS_DIR:-$DEV_ROOT/lnbits}"
|
||||
UPSTREAM_PRS_DIR="${UPSTREAM_PRS_DIR:-$DEV_ROOT/upstream-prs}"
|
||||
# Node used for readiness/balance checks. Override if your stack
|
||||
# names containers differently.
|
||||
REGTEST_LND="${REGTEST_LND:-lnbits-lnd-4-1}"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Status helpers
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
_regtest_is_running() {
|
||||
docker ps --filter "name=$REGTEST_LND" --format '{{.Names}}' 2>/dev/null \
|
||||
| grep -q "$REGTEST_LND"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Start
|
||||
#-------------------------------------------------------------------------------
|
||||
# Usage: regtest-start [worktree|pr:<branch>] [--path <dir>] [--seed|--keep]
|
||||
#
|
||||
# worktree an lnbits worktree name (under ~/dev/lnbits/<name>)
|
||||
# pr:<branch> ~/dev/upstream-prs/lnbits-<branch>
|
||||
# --path <dir> arbitrary lnbits directory
|
||||
# --seed copy lnbits-seed/ to lnbits/ before starting
|
||||
# --keep keep existing data
|
||||
regtest-start() {
|
||||
_devenv_load_config
|
||||
|
||||
local env="" data_mode="fresh" custom_path=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--seed) data_mode="seed" ;;
|
||||
--keep) data_mode="keep" ;;
|
||||
--path) shift; custom_path="$1" ;;
|
||||
pr:*)
|
||||
local pr_branch="${1#pr:}"
|
||||
custom_path="$UPSTREAM_PRS_DIR/lnbits-$pr_branch"
|
||||
;;
|
||||
-*)
|
||||
cat <<USAGE
|
||||
Unknown argument: $1
|
||||
Usage: regtest-start [worktree|pr:<branch>] [--path <dir>] [--seed|--keep]
|
||||
|
||||
worktree an lnbits worktree name (under ~/dev/lnbits/<name>)
|
||||
pr:<branch> ~/dev/upstream-prs/lnbits-<branch>
|
||||
--path <dir> arbitrary lnbits directory
|
||||
USAGE
|
||||
return 1
|
||||
;;
|
||||
*) env="$1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ ! -d "$REGTEST_DIR" ]]; then
|
||||
echo "Regtest environment not found at $REGTEST_DIR"
|
||||
echo "Run dev-env-bootstrap to clone it."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Determine lnbits source dir
|
||||
local lnbits_path=""
|
||||
if [[ -n "$custom_path" ]]; then
|
||||
lnbits_path="$custom_path"
|
||||
[[ -d "$lnbits_path" ]] || { echo "lnbits dir not found: $lnbits_path"; return 1; }
|
||||
else
|
||||
[[ -z "$env" ]] && env="dev"
|
||||
lnbits_path="$LNBITS_DIR/$env"
|
||||
[[ -d "$lnbits_path" ]] || { echo "lnbits worktree not found: $env"; return 1; }
|
||||
fi
|
||||
|
||||
local lnbits_data="$REGTEST_DIR/data/lnbits"
|
||||
local lnbits_seed="$REGTEST_DIR/data/lnbits-seed"
|
||||
|
||||
case "$data_mode" in
|
||||
fresh)
|
||||
if [[ -d "$lnbits_data" ]] && [[ -n "$(ls -A "$lnbits_data" 2>/dev/null)" ]]; then
|
||||
echo "Existing lnbits data at $lnbits_data"
|
||||
read -r -p "Wipe it? [y/N] " -n 1 REPLY; echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]] || { echo "Aborted."; return 1; }
|
||||
docker run --rm -v "$lnbits_data":/data alpine sh -c "rm -rf /data/* /data/.*" 2>/dev/null || true
|
||||
rmdir "$lnbits_data" 2>/dev/null || true
|
||||
fi
|
||||
mkdir -p "$lnbits_data"
|
||||
;;
|
||||
seed)
|
||||
[[ -d "$lnbits_seed" ]] || { echo "Seed data not found at $lnbits_seed"; return 1; }
|
||||
if [[ -d "$lnbits_data" ]] && [[ -n "$(ls -A "$lnbits_data" 2>/dev/null)" ]]; then
|
||||
read -r -p "Overwrite with seed? [y/N] " -n 1 REPLY; echo
|
||||
[[ $REPLY =~ ^[Yy]$ ]] || { echo "Aborted."; return 1; }
|
||||
docker run --rm -v "$lnbits_data":/data alpine sh -c "rm -rf /data/* /data/.*" 2>/dev/null || true
|
||||
rmdir "$lnbits_data" 2>/dev/null || true
|
||||
fi
|
||||
cp -r "$lnbits_seed" "$lnbits_data"
|
||||
;;
|
||||
keep)
|
||||
mkdir -p "$lnbits_data"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "Starting regtest..."
|
||||
echo " lnbits path: $lnbits_path"
|
||||
echo " data mode: $data_mode"
|
||||
echo ""
|
||||
|
||||
echo "Building lnbits Docker image from $lnbits_path..."
|
||||
docker build -t lnbits/lnbits "$lnbits_path"
|
||||
|
||||
(cd "$REGTEST_DIR" && ./start-regtest)
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Regtest running:
|
||||
LNbits: http://localhost:5001/
|
||||
Mempool: http://localhost:8080/
|
||||
Boltz: http://localhost:9001/
|
||||
|
||||
EOF
|
||||
|
||||
echo "Commands: regtest-stop, regtest-logs, regtest-cli, regtest-status"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Stop
|
||||
#-------------------------------------------------------------------------------
|
||||
regtest-stop() {
|
||||
_devenv_load_config
|
||||
|
||||
if [[ ! -d "$REGTEST_DIR" ]]; then
|
||||
echo "Regtest environment not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Stopping regtest environment..."
|
||||
(cd "$REGTEST_DIR"
|
||||
# shellcheck disable=SC1091
|
||||
source ./docker-scripts.sh
|
||||
docker compose down -v)
|
||||
echo "Regtest stopped"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Status / logs / CLI
|
||||
#-------------------------------------------------------------------------------
|
||||
regtest-status() {
|
||||
_devenv_load_config
|
||||
|
||||
echo "=== Regtest Environment ==="
|
||||
if _regtest_is_running; then
|
||||
echo "Status: RUNNING"
|
||||
echo ""
|
||||
docker ps --filter "name=lnbits-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | head -15
|
||||
|
||||
local balance
|
||||
balance="$(docker exec "$REGTEST_LND" lncli --network=regtest walletbalance 2>/dev/null \
|
||||
| grep -oP '"total_balance":\s*"\K[0-9]+' || echo 0)"
|
||||
echo ""
|
||||
echo "$REGTEST_LND balance: $balance sats"
|
||||
else
|
||||
echo "Status: STOPPED"
|
||||
echo "Start with: regtest-start"
|
||||
fi
|
||||
}
|
||||
|
||||
regtest-logs() {
|
||||
_devenv_load_config
|
||||
local service="${1:-}"
|
||||
[[ -d "$REGTEST_DIR" ]] || { echo "Regtest not found"; return 1; }
|
||||
if [[ -n "$service" ]]; then
|
||||
(cd "$REGTEST_DIR" && docker compose logs -f "$service")
|
||||
else
|
||||
(cd "$REGTEST_DIR" && docker compose logs -f)
|
||||
fi
|
||||
}
|
||||
|
||||
regtest-cli() {
|
||||
_devenv_load_config
|
||||
[[ -f "$REGTEST_DIR/docker-scripts.sh" ]] || { echo "docker-scripts.sh missing"; return 1; }
|
||||
echo "Sourcing regtest CLI helpers..."
|
||||
# shellcheck disable=SC1091
|
||||
source "$REGTEST_DIR/docker-scripts.sh"
|
||||
cat <<EOF
|
||||
|
||||
Available commands:
|
||||
bitcoin-cli-sim Bitcoin Core CLI
|
||||
lncli-sim <1-4> LND node CLI
|
||||
lightning-cli-sim <1-3> C-Lightning node CLI
|
||||
boltzcli-sim Boltz client CLI
|
||||
elements-cli-sim Elements/Liquid CLI
|
||||
|
||||
Examples:
|
||||
bitcoin-cli-sim -generate 1
|
||||
lncli-sim 1 getinfo
|
||||
EOF
|
||||
}
|
||||
|
||||
regtest() {
|
||||
_devenv_load_config
|
||||
[[ -d "$REGTEST_DIR" ]] && cd "$REGTEST_DIR" || { echo "Regtest not found at $REGTEST_DIR"; return 1; }
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Rebuild / restart the lnbits service (docker-compose.dev.yml workflow)
|
||||
#-------------------------------------------------------------------------------
|
||||
# regtest-lnbits-rebuild build (cached) + recreate the lnbits container
|
||||
# regtest-lnbits-rebuild --clean build --no-cache + recreate (truly fresh image)
|
||||
# regtest-lnbits-restart restart the container without rebuilding
|
||||
#
|
||||
# The cached build is the happy path: docker invalidates the source COPY layer
|
||||
# when LNBITS_SRC content changes, so most rebuilds are fast. Use --clean when
|
||||
# you've been mucking with the image itself.
|
||||
|
||||
_regtest_lnbits_compose_file() {
|
||||
echo "$REGTEST_DIR/docker-compose.dev.yml"
|
||||
}
|
||||
|
||||
regtest-lnbits-rebuild() {
|
||||
_devenv_load_config
|
||||
local compose_file build_args=()
|
||||
compose_file="$(_regtest_lnbits_compose_file)"
|
||||
[[ -f "$compose_file" ]] || { echo "Compose file not found: $compose_file"; return 1; }
|
||||
|
||||
if [[ "${1:-}" == "--clean" ]]; then
|
||||
build_args+=(--no-cache)
|
||||
fi
|
||||
|
||||
(cd "$REGTEST_DIR" \
|
||||
&& docker compose -f "$compose_file" build "${build_args[@]}" lnbits \
|
||||
&& docker compose -f "$compose_file" up -d --force-recreate lnbits)
|
||||
}
|
||||
|
||||
regtest-lnbits-restart() {
|
||||
_devenv_load_config
|
||||
local compose_file
|
||||
compose_file="$(_regtest_lnbits_compose_file)"
|
||||
[[ -f "$compose_file" ]] || { echo "Compose file not found: $compose_file"; return 1; }
|
||||
docker compose -f "$compose_file" restart lnbits
|
||||
}
|
||||
Reference in a new issue