Skip to content

Commit 0c229a6

Browse files
authored
feat(spin-node): --detach for persistent local devnets + --dockerWithSudo for genesis (#181)
* feat(spin-node): add --detach to keep local devnets running after exit Local docker mode ran each node in the foreground and tore the devnet down on exit (SIGINT/SIGTERM trap -> kill -9, plus --rm), so it never survived spin-node.sh returning. The opt-in --detach flag runs nodes with 'docker run -d --restart unless-stopped' and skips the wait/cleanup, leaving the devnet running once the script ends. The existing --stop already tears these down (docker rm -f overrides the restart policy); the detach summary now points at it for discoverability. * fix(genesis): add --dockerWithSudo so genesis gen works where docker needs root generate-genesis.sh called docker directly for hash-sig keygen and PK's genesis tool, so on hosts where the docker socket requires root (user not in the docker group) 'spin-node.sh --generateGenesis' failed at keygen with permission denied. Add a --dockerWithSudo flag that sudo-prefixes those docker calls (via $DOCKER_CMD), and forward it from set-up.sh when spin-node.sh receives --dockerWithSudo. * fix(spin-node): restrict --detach to docker mode and make detach hints sudo-aware Address PR review: - Fail fast if --detach is used with a binary node. --detach relies on docker's --restart unless-stopped to outlive the script; a binary has no supervisor, so it would run in the foreground (or die on exit). Guard it right after the client cmd is sourced (earliest point node_setup is known). - Make the detached-mode hints honor --dockerWithSudo: prefix docker logs/rm with sudo and forward --dockerWithSudo to the suggested --stop command, so copy-pasted hints work on hosts where the docker socket needs root.
1 parent f17682d commit 0c229a6

4 files changed

Lines changed: 78 additions & 18 deletions

File tree

generate-genesis.sh

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ VALIDATOR_CONFIG_FILE="$GENESIS_DIR/validator-config.yaml"
9090
SKIP_KEY_GEN="true"
9191
DEPLOYMENT_MODE="local" # Default to local mode
9292
GENESIS_TIME_OFFSET="" # Will be set based on mode or --offset flag
93+
DOCKER_CMD="docker" # Set to "sudo docker" via --dockerWithSudo (hosts where the docker socket needs root)
9394
shift
9495
while [[ $# -gt 0 ]]; do
9596
case "$1" in
@@ -128,6 +129,10 @@ while [[ $# -gt 0 ]]; do
128129
exit 1
129130
fi
130131
;;
132+
--dockerWithSudo)
133+
DOCKER_CMD="sudo docker"
134+
shift
135+
;;
131136
*)
132137
shift
133138
;;
@@ -274,9 +279,9 @@ else
274279
CURRENT_GID=$(id -g)
275280

276281
# Pull latest image first
277-
docker pull "$HASH_SIG_CLI_IMAGE" || true
282+
$DOCKER_CMD pull "$HASH_SIG_CLI_IMAGE" || true
278283

279-
docker run --rm --pull=never \
284+
$DOCKER_CMD run --rm --pull=never \
280285
--user "$CURRENT_UID:$CURRENT_GID" \
281286
-v "$GENESIS_DIR_ABS:/genesis" \
282287
"$HASH_SIG_CLI_IMAGE" \
@@ -469,9 +474,9 @@ echo " Executing docker command..."
469474

470475
# Pull latest image first
471476
echo " Pulling latest image: $PK_DOCKER_IMAGE"
472-
docker pull "$PK_DOCKER_IMAGE" || true
477+
$DOCKER_CMD pull "$PK_DOCKER_IMAGE" || true
473478

474-
docker run --rm --pull=never \
479+
$DOCKER_CMD run --rm --pull=never \
475480
--user "$CURRENT_UID:$CURRENT_GID" \
476481
-v "$PARENT_DIR_ABS:/data" \
477482
"$PK_DOCKER_IMAGE" \

parse-env.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ while [[ $# -gt 0 ]]; do
134134
enableLogs=true
135135
shift
136136
;;
137+
--detach)
138+
# Run local docker nodes detached (-d --restart unless-stopped) and let
139+
# spin-node.sh exit without tearing them down. Local docker mode only.
140+
detachNodes=true
141+
shift
142+
;;
137143
*) # unknown option
138144
shift # past argument
139145
;;
@@ -191,3 +197,4 @@ echo "dryRun = ${dryRun:-false}"
191197
echo "replaceWith = ${replaceWith:-<not set>}"
192198
echo "networkName = $networkName"
193199
echo "enableLogs = ${enableLogs:-false}"
200+
echo "detachNodes = ${detachNodes:-false}"

set-up.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,13 @@ if [ -n "$generateGenesis" ] || [ ! -f "$configDir/validators.yaml" ] || [ ! -f
3333
_validator_config_flag="--validator-config $validatorConfig"
3434
fi
3535

36+
# Forward the sudo preference so the genesis keygen/tool docker calls work on
37+
# hosts where the docker socket needs root (user not in the docker group).
38+
_docker_sudo_flag=""
39+
[ -n "$dockerWithSudo" ] && _docker_sudo_flag="--dockerWithSudo"
40+
3641
# Run the generator with deployment mode
37-
if ! $genesis_generator "$configDir" --mode "$deployment_mode" $FORCE_KEYGEN_FLAG $_validator_config_flag; then
42+
if ! $genesis_generator "$configDir" --mode "$deployment_mode" $FORCE_KEYGEN_FLAG $_validator_config_flag $_docker_sudo_flag; then
3843
echo "❌ Genesis generation failed!"
3944
exit 1
4045
fi

spin-node.sh

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,15 @@ for item in "${spin_nodes[@]}"; do
908908
echo "$sourceCmd"
909909
eval $sourceCmd
910910

911+
# --detach only supports docker nodes: it relies on `docker run -d
912+
# --restart unless-stopped` to outlive this script. A binary has no such
913+
# supervisor, so a detached binary would just run in the foreground here (or,
914+
# if backgrounded, die when the script exits). Fail fast instead.
915+
if [ "$detachNodes" == "true" ] && [ "$node_setup" == "binary" ]; then
916+
echo "❌ --detach is only supported for docker nodes; '$item' is configured for binary mode."
917+
exit 1
918+
fi
919+
911920
# spin nodes
912921
if [ "$node_setup" == "binary" ]
913922
then
@@ -927,7 +936,14 @@ for item in "${spin_nodes[@]}"; do
927936
else
928937
docker pull "$docker_image" || true
929938
fi
930-
execCmd="docker run --rm --pull=never"
939+
if [ "$detachNodes" == "true" ]; then
940+
# Detached: run in the background with a restart policy so the devnet
941+
# keeps running after spin-node.sh exits. No --rm, so the container is
942+
# kept for `docker logs` inspection.
943+
execCmd="docker run -d --restart unless-stopped --pull=never"
944+
else
945+
execCmd="docker run --rm --pull=never"
946+
fi
931947
if [ -n "$dockerWithSudo" ]
932948
then
933949
execCmd="sudo $execCmd"
@@ -961,6 +977,13 @@ for item in "${spin_nodes[@]}"; do
961977
if [ "$dryRun" == "true" ]; then
962978
echo "[DRY RUN] Would execute: $execCmd"
963979
pid=0
980+
elif [ "$detachNodes" == "true" ]; then
981+
# Detached `docker run -d` returns immediately after printing the
982+
# container id; logs are captured by the docker daemon (docker logs <node>).
983+
# There is no foreground process to track, so record a placeholder pid.
984+
echo "$execCmd"
985+
eval "$execCmd"
986+
pid=0
964987
else
965988
sed_remove_ansi='s/\x1b\[[0-9;]*[mJHG]//g'
966989
echo "$execCmd"
@@ -1066,17 +1089,37 @@ cleanup() {
10661089

10671090
_print_deployment_summary
10681091

1069-
trap "echo exit signal received;cleanup" SIGINT SIGTERM
1070-
echo -e "\n\nwaiting for nodes to exit"
1071-
printf '%*s' $(tput cols) | tr ' ' '-'
1072-
echo "press Ctrl+C to exit and cleanup..."
1073-
# Wait for background processes - use a compatible approach for all shells
1074-
if [ ${#spinned_pids[@]} -gt 0 ]; then
1075-
for pid in "${spinned_pids[@]}"; do
1076-
wait $pid 2>/dev/null || true
1077-
done
1092+
if [ "$detachNodes" == "true" ]; then
1093+
# Detached mode: leave the devnet running once spin-node.sh exits. Skip the
1094+
# trap/wait/cleanup entirely so the containers (started with
1095+
# --restart unless-stopped) survive independently of this script.
1096+
echo -e "\n\ndevnet started in detached mode; nodes keep running after this script exits"
1097+
printf '%*s' $(tput cols) | tr ' ' '-'
1098+
echo
1099+
# Mirror the sudo preference in the hints so copy-pasted commands work on
1100+
# hosts where the docker socket needs root (--dockerWithSudo).
1101+
_hint_docker="docker"
1102+
_hint_stop_flags=""
1103+
if [ -n "$dockerWithSudo" ]; then
1104+
_hint_docker="sudo docker"
1105+
_hint_stop_flags=" --dockerWithSudo"
1106+
fi
1107+
echo " view logs: $_hint_docker logs -f <node> (e.g. $_hint_docker logs -f ${spin_nodes[0]})"
1108+
echo " stop all: NETWORK_DIR=$NETWORK_DIR $0 --node all --stop$_hint_stop_flags"
1109+
echo " (or raw: $_hint_docker rm -f $container_names)"
10781110
else
1079-
# Fallback: wait for any background job
1080-
wait
1111+
trap "echo exit signal received;cleanup" SIGINT SIGTERM
1112+
echo -e "\n\nwaiting for nodes to exit"
1113+
printf '%*s' $(tput cols) | tr ' ' '-'
1114+
echo "press Ctrl+C to exit and cleanup..."
1115+
# Wait for background processes - use a compatible approach for all shells
1116+
if [ ${#spinned_pids[@]} -gt 0 ]; then
1117+
for pid in "${spinned_pids[@]}"; do
1118+
wait $pid 2>/dev/null || true
1119+
done
1120+
else
1121+
# Fallback: wait for any background job
1122+
wait
1123+
fi
1124+
cleanup
10811125
fi
1082-
cleanup

0 commit comments

Comments
 (0)