diff --git a/Makefile b/Makefile index 999de8b..38d9e52 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ GO ?= go IGNITE ?= ignite BUF ?= buf +GOLANGCI_LINT ?= golangci-lint BUILD_DIR ?= build RELEASE_DIR ?= release @@ -114,10 +115,14 @@ release: ################################################### ### Tests and Simulation ### ################################################### -.PHONY: unit-tests integration-tests system-tests simulation-tests all-tests +.PHONY: unit-tests integration-tests system-tests simulation-tests all-tests lint all-tests: unit-tests integration-tests system-tests simulation-tests +lint: + @echo "Running linters..." + @${GOLANGCI_LINT} run ./... --timeout=5m + unit-tests: @echo "Running unit tests in x/..." ${GO} test ./x/... -v -coverprofile=coverage.out @@ -138,4 +143,3 @@ simulation-tests: systemex-tests: @echo "Running system tests..." cd ./tests/systemtests/ && go test -tags=system_test -v . - diff --git a/Makefile.devnet b/Makefile.devnet index 69dff9c..e21f80b 100644 --- a/Makefile.devnet +++ b/Makefile.devnet @@ -1,5 +1,5 @@ .PHONY: devnet-build devnet-up devnet-reset devnet-up-detach devnet-down devnet-stop devnet-clean devnet-deploy-tar devnet-upgrade devnet-new devnet-start -.PHONY: devnet-build-default _check-devnet-default-cfg devnet-upgrade-binaries devnet-update-scripts +.PHONY: devnet-build-default _check-devnet-default-cfg devnet-upgrade-binaries devnet-upgrade-binaries-default devnet-update-scripts ##### Devnet Makefile ######################################## # @@ -233,13 +233,16 @@ devnet-upgrade-binaries: fi; \ ./devnet/scripts/upgrade-binaries.sh "${BUILD_DIR}" +devnet-upgrade-binaries-default: + ./devnet/scripts/upgrade-binaries.sh "${DEVNET_BIN_DIR}" + devnet-update-scripts: @if [ ! -f "$(COMPOSE_FILE)" ]; then \ echo "Missing $(COMPOSE_FILE); run 'make devnet-build' first."; \ exit 1; \ fi @services="$$(docker compose -f $(COMPOSE_FILE) ps --services)"; \ - common_scripts="start.sh validator-setup.sh supernode-setup.sh network-maker-setup.sh"; \ + common_scripts="start.sh stop.sh restart.sh validator-setup.sh supernode-setup.sh network-maker-setup.sh"; \ updated=0; \ for svc in $$services; do \ container="$$(docker compose -f $(COMPOSE_FILE) ps -q $$svc)"; \ diff --git a/devnet/config/config.go b/devnet/config/config.go index 8bdf933..72c015e 100644 --- a/devnet/config/config.go +++ b/devnet/config/config.go @@ -37,6 +37,9 @@ type ChainConfig struct { NetworkMaker struct { MaxAccounts int `json:"max_accounts"` AccountBalance string `json:"account_balance"` + Enabled bool `json:"enabled"` + GRPCPort int `json:"grpc_port"` + HTTPPort int `json:"http_port"` } `json:"network-maker"` Hermes struct { Enabled bool `json:"enabled"` @@ -59,6 +62,12 @@ type Validator struct { AccountBalance string `json:"account_balance"` ValidatorStake string `json:"validator_stake"` } `json:"initial_distribution"` + + NetworkMaker struct { + Enabled bool `json:"enabled,omitempty"` + GRPCPort int `json:"grpc_port,omitempty"` + HTTPPort int `json:"http_port,omitempty"` + } `json:"network-maker,omitempty"` } func LoadConfigs(configPath, validatorsPath string) (*ChainConfig, []Validator, error) { diff --git a/devnet/config/config.json b/devnet/config/config.json index 35bbf81..7a6e5b3 100644 --- a/devnet/config/config.json +++ b/devnet/config/config.json @@ -26,7 +26,10 @@ "keyring_backend": "test" }, "network-maker": { - "max_accounts": 5, + "enabled": true, + "grpc_port": 50051, + "http_port": 8080, + "max_accounts": 3, "account_balance": "10000000ulume" }, "hermes": { diff --git a/devnet/config/validators.json b/devnet/config/validators.json index ae167e5..50c1dfe 100644 --- a/devnet/config/validators.json +++ b/devnet/config/validators.json @@ -42,7 +42,11 @@ "supernode_port": 7445, "supernode_p2p_port": 7446, "supernode_gateway_port": 18003, - "network-maker": true, + "network-maker": { + "enabled": true, + "grpc_port": 50051, + "http_port": 8080 + }, "initial_distribution": { "account_balance": "2000000000000ulume", "validator_stake": "1000000000000ulume" @@ -80,4 +84,4 @@ "validator_stake": "1000000000000ulume" } } -] \ No newline at end of file +] diff --git a/devnet/dockerfile b/devnet/dockerfile index 60c6ece..9ac44a5 100644 --- a/devnet/dockerfile +++ b/devnet/dockerfile @@ -22,23 +22,31 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ iputils-ping \ lnav \ mc \ + nginx-light \ && rm -rf /var/lib/apt/lists/* +# Install Node.js (for network-maker UI tooling) using NodeSource 25.x +RUN curl -fsSL https://deb.nodesource.com/setup_25.x | bash - && \ + apt-get update && apt-get install -y --no-install-recommends nodejs && \ + rm -rf /var/lib/apt/lists/* + # Update libraries cache & create directories RUN ldconfig && mkdir -p ${SCRIPTS_DEST_DIR} /root/.lumerad # Copy scripts with correct paths COPY --chmod=0755 \ ${SCRIPTS_SRC_DIR}/start.sh \ + ${SCRIPTS_SRC_DIR}/stop.sh \ + ${SCRIPTS_SRC_DIR}/restart.sh \ ${SCRIPTS_SRC_DIR}/validator-setup.sh \ ${SCRIPTS_SRC_DIR}/supernode-setup.sh \ ${SCRIPTS_SRC_DIR}/network-maker-setup.sh \ ${SCRIPTS_DEST_DIR}/ # Expose necessary ports -EXPOSE 26656 26657 1317 9090 4444 8002 +EXPOSE 26656 26657 1317 9090 4444 8002 50051 8080 8088 # Set working directory WORKDIR /root -ENTRYPOINT ["/bin/bash", "/root/scripts/start.sh"] \ No newline at end of file +ENTRYPOINT ["/bin/bash", "/root/scripts/start.sh"] diff --git a/devnet/generators/config.go b/devnet/generators/config.go index 467cddb..225b5a9 100644 --- a/devnet/generators/config.go +++ b/devnet/generators/config.go @@ -9,6 +9,9 @@ const ( DefaultSupernodePort = 4444 DefaultSupernodeP2PPort = 4445 DefaultSupernodeGatewayPort = 8002 + DefaultNetworkMakerGRPCPort = 50051 + DefaultNetworkMakerHTTPPort = 8080 + DefaultNetworkMakerUIPort = 8088 DefaultGRPCWebPort = 9091 DefaultHermesSimdHostP2PPort = 36656 DefaultHermesSimdHostRPCPort = 36657 @@ -16,6 +19,9 @@ const ( DefaultHermesSimdHostGRPCPort = 39090 DefaultHermesSimdHostGRPCWebPort = 39091 + EnvNMAPIBase = "VITE_API_BASE" + EnvNMAPIToken = "VITE_API_KEY" + FolderScripts = "/root/scripts" SubFolderShared = "shared" SubFolderConfig = "config" diff --git a/devnet/generators/docker-compose.go b/devnet/generators/docker-compose.go index 64e0f7b..a215cbe 100644 --- a/devnet/generators/docker-compose.go +++ b/devnet/generators/docker-compose.go @@ -9,6 +9,13 @@ import ( "gopkg.in/yaml.v2" ) +const ( + defaultNetworkDriver = "bridge" + defaultNetworkSubnet = "172.28.0.0/24" + defaultNetworkPrefix = "172.28.0." + defaultServiceIPStart = 10 +) + type DockerComposeLogging struct { Driver string `yaml:"driver"` Options map[string]string `yaml:"options,omitempty"` @@ -20,21 +27,36 @@ type DockerComposeConfig struct { } type DockerComposeService struct { - Build string `yaml:"build"` - Image string `yaml:"image,omitempty"` - ContainerName string `yaml:"container_name"` - Ports []string `yaml:"ports"` - Volumes []string `yaml:"volumes"` - Environment map[string]string `yaml:"environment,omitempty"` - Command string `yaml:"command,omitempty"` - DependsOn []string `yaml:"depends_on,omitempty"` - CapAdd []string `yaml:"cap_add,omitempty"` - SecurityOpt []string `yaml:"security_opt,omitempty"` - Logging *DockerComposeLogging `yaml:"logging,omitempty"` + Build string `yaml:"build"` + Image string `yaml:"image,omitempty"` + ContainerName string `yaml:"container_name"` + Ports []string `yaml:"ports"` + Volumes []string `yaml:"volumes"` + Environment map[string]string `yaml:"environment,omitempty"` + Command string `yaml:"command,omitempty"` + DependsOn []string `yaml:"depends_on,omitempty"` + Networks map[string]DockerComposeServiceNetwork `yaml:"networks,omitempty"` + CapAdd []string `yaml:"cap_add,omitempty"` + SecurityOpt []string `yaml:"security_opt,omitempty"` + Logging *DockerComposeLogging `yaml:"logging,omitempty"` } type DockerComposeNetwork struct { - Name string `yaml:"name"` + Name string `yaml:"name"` + Driver string `yaml:"driver,omitempty"` + IPAM *DockerComposeIPAM `yaml:"ipam,omitempty"` +} + +type DockerComposeIPAM struct { + Config []DockerComposeIPAMConfig `yaml:"config,omitempty"` +} + +type DockerComposeIPAMConfig struct { + Subnet string `yaml:"subnet,omitempty"` +} + +type DockerComposeServiceNetwork struct { + IPv4Address string `yaml:"ipv4_address,omitempty"` } func supernodeBinaryHostPath() (string, bool) { @@ -55,7 +77,15 @@ func GenerateDockerCompose(config *confg.ChainConfig, validators []confg.Validat Services: make(map[string]DockerComposeService), Networks: map[string]DockerComposeNetwork{ "default": { - Name: config.Docker.NetworkName, + Name: config.Docker.NetworkName, + Driver: defaultNetworkDriver, + IPAM: &DockerComposeIPAM{ + Config: []DockerComposeIPAMConfig{ + { + Subnet: defaultNetworkSubnet, + }, + }, + }, }, }, } @@ -63,6 +93,7 @@ func GenerateDockerCompose(config *confg.ChainConfig, validators []confg.Validat _, snPresent := supernodeBinaryHostPath() folderMount := fmt.Sprintf("/tmp/%s", config.Chain.ID) + validatorBaseIP := defaultServiceIPStart + 1 for index, validator := range validators { serviceName := fmt.Sprintf("%s-%s", config.Docker.ContainerPrefix, validator.Name) @@ -95,6 +126,11 @@ func GenerateDockerCompose(config *confg.ChainConfig, validators []confg.Validat CapAdd: []string{ "SYS_PTRACE", }, + Networks: map[string]DockerComposeServiceNetwork{ + "default": { + IPv4Address: fmt.Sprintf("%s%d", defaultNetworkPrefix, validatorBaseIP+index), + }, + }, SecurityOpt: []string{ "seccomp=unconfined", }, @@ -125,6 +161,29 @@ func GenerateDockerCompose(config *confg.ChainConfig, validators []confg.Validat service.DependsOn = []string{validators[0].Name} } + if validator.NetworkMaker.Enabled { + nmGrpc := validator.NetworkMaker.GRPCPort + if nmGrpc == 0 { + nmGrpc = DefaultNetworkMakerGRPCPort + } + nmHTTP := validator.NetworkMaker.HTTPPort + if nmHTTP == 0 { + nmHTTP = DefaultNetworkMakerHTTPPort + } + service.Ports = append(service.Ports, + fmt.Sprintf("%d:%d", nmGrpc, DefaultNetworkMakerGRPCPort), + fmt.Sprintf("%d:%d", nmHTTP, DefaultNetworkMakerHTTPPort), + fmt.Sprintf("%d:%d", DefaultNetworkMakerUIPort, DefaultNetworkMakerUIPort), + ) + + if config.NetworkMaker.GRPCPort > 0 { + env[EnvNMAPIBase] = fmt.Sprintf("http://localhost:%d", nmHTTP) + } + if config.NetworkMaker.AccountBalance != "" { + // reserve env slot for key if provided in config (optional) + } + } + compose.Services[validator.Name] = service } @@ -144,6 +203,11 @@ func GenerateDockerCompose(config *confg.ChainConfig, validators []confg.Validat fmt.Sprintf("%s/hermes-router:%s", folderMount, HermesStateHome), fmt.Sprintf("%s/shared:/shared", folderMount), }, + Networks: map[string]DockerComposeServiceNetwork{ + "default": { + IPv4Address: fmt.Sprintf("%s%d", defaultNetworkPrefix, defaultServiceIPStart), + }, + }, Environment: map[string]string{ "HERMES_CONFIG": "/root/.hermes/config.toml", }, diff --git a/devnet/scripts/configure.sh b/devnet/scripts/configure.sh index e521806..8038b6b 100755 --- a/devnet/scripts/configure.sh +++ b/devnet/scripts/configure.sh @@ -94,6 +94,8 @@ NM="network-maker" NM_CFG="${BIN_DIR}/nm-config.toml" SNCLI="sncli" SNCLI_CFG="${BIN_DIR}/sncli-config.toml" +NM_UI_SRC="${BIN_DIR}/nm-ui" +NM_UI_DST="${RELEASE_DIR}/nm-ui" install_supernode() { if [ -n "${BIN_DIR}" ] && [ -f "${BIN_DIR}/${SN}" ]; then @@ -113,6 +115,14 @@ install_nm() { echo "[CONFIGURE] Copying network-maker file from ${BIN_DIR} to ${RELEASE_DIR}" cp -f "${BIN_DIR}/${NM}" "${NM_CFG}" "${RELEASE_DIR}/" chmod 755 "${RELEASE_DIR}/${NM}" + + if [ -d "${NM_UI_SRC}" ]; then + echo "[CONFIGURE] Copying network-maker UI from ${NM_UI_SRC} to ${NM_UI_DST}" + rm -rf "${NM_UI_DST}" + cp -r "${NM_UI_SRC}" "${NM_UI_DST}" + else + echo "[CONFIGURE] network-maker UI not found at ${NM_UI_SRC}; skipping UI copy" + fi fi } @@ -137,4 +147,4 @@ echo "[CONFIGURE] Configuration files copied to ${CFG_DIR}" install_supernode install_sncli install_nm -echo "[CONFIGURE] Lumera configuration completed successfully." \ No newline at end of file +echo "[CONFIGURE] Lumera configuration completed successfully." diff --git a/devnet/scripts/network-maker-setup.sh b/devnet/scripts/network-maker-setup.sh index c1190db..3aff53d 100755 --- a/devnet/scripts/network-maker-setup.sh +++ b/devnet/scripts/network-maker-setup.sh @@ -44,6 +44,8 @@ NM_FILES_DIR_SHARED="/shared/nm-files" NM_LOG="${NM_LOG:-/root/logs/network-maker.log}" NM_TEMPLATE="${RELEASE_DIR}/nm-config.toml" # Your template in /shared/release (you said it's attached as config.toml) NM_CONFIG="${NM_HOME}/config.toml" +NM_GRPC_PORT="${NM_GRPC_PORT:-50051}" +NM_HTTP_PORT="${NM_HTTP_PORT:-8080}" NM_KEY_PREFIX="nm-account" NM_MNEMONIC_FILE_BASE="${NODE_STATUS_DIR}/nm_mnemonic" @@ -73,6 +75,10 @@ wait_for_file() { while [ ! -s "$1" ]; do sleep 1; done; } fail_soft() { echo "[NM] $*"; exit 0; } # exit 0 so container keeps running +version_ge() { + printf '%s\n' "$2" "$1" | sort -V | head -n1 | grep -q "^$2$" +} + # Fetch the latest block height from lumerad. latest_block_height() { local status @@ -156,7 +162,11 @@ fi VAL_REC_JSON="$(jq -c --arg m "$MONIKER" '[.[] | select(.moniker==$m)][0]' "${CFG_VALS}")" [ -n "${VAL_REC_JSON}" ] && [ "${VAL_REC_JSON}" != "null" ] || { echo "[NM] Validator moniker ${MONIKER} not found in validators.json"; exit 1; } -NM_ENABLED="$(echo "${VAL_REC_JSON}" | jq -r 'try .["network-maker"] // "false"')" +NM_ENABLED="$(echo "${VAL_REC_JSON}" | jq -r 'try .["network-maker"].enabled // .["network-maker"] // "false"')" +NM_GRPC_PORT="$(echo "${VAL_REC_JSON}" | jq -r 'try .["network-maker"].grpc_port // empty')" +NM_HTTP_PORT="$(echo "${VAL_REC_JSON}" | jq -r 'try .["network-maker"].http_port // empty')" +if [ -z "${NM_GRPC_PORT}" ] || [ "${NM_GRPC_PORT}" = "null" ]; then NM_GRPC_PORT="${NM_GRPC_PORT:-50051}"; fi +if [ -z "${NM_HTTP_PORT}" ] || [ "${NM_HTTP_PORT}" = "null" ]; then NM_HTTP_PORT="${NM_HTTP_PORT:-8080}"; fi # ----- short-circuits ----- if [ "${START_MODE}" = "wait" ]; then @@ -274,6 +284,10 @@ configure_nm() { crudini --set "$cfg" lumera chain_id "\"$CHAIN_ID\"" crudini --set "$cfg" lumera denom "\"$DENOM\"" + # monitor (grpc/http) listeners + crudini --set "$cfg" network-maker grpc_listen "\"0.0.0.0:${NM_GRPC_PORT}\"" + crudini --set "$cfg" network-maker http_gateway_listen "\"0.0.0.0:${NM_HTTP_PORT}\"" + # keyring section crudini --set "$cfg" keyring backend "\"$KEYRING_BACKEND\"" crudini --set "$cfg" keyring dir "\"${DAEMON_HOME}\"" diff --git a/devnet/scripts/restart.sh b/devnet/scripts/restart.sh new file mode 100755 index 0000000..8a5f2c6 --- /dev/null +++ b/devnet/scripts/restart.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# restart.sh — restart devnet services inside a validator container +# Usage: +# ./restart.sh # restart all known services +# ./restart.sh nm|sn|lumera|nginx +set -euo pipefail + +DAEMON="${DAEMON:-lumerad}" +DAEMON_HOME="${DAEMON_HOME:-/root/.lumera}" +SN_BASEDIR="${SN_BASEDIR:-/root/.supernode}" + +LOGS_DIR="${LOGS_DIR:-/root/logs}" +VALIDATOR_LOG="${VALIDATOR_LOG:-${LOGS_DIR}/validator.log}" +SN_LOG="${SN_LOG:-${LOGS_DIR}/supernode.log}" +NM_LOG="${NM_LOG:-${LOGS_DIR}/network-maker.log}" + +SHARED_DIR="/shared" +RELEASE_DIR="${SHARED_DIR}/release" +NM_UI_DIR="${NM_UI_DIR:-${RELEASE_DIR}/nm-ui}" +NM_UI_PORT="${NM_UI_PORT:-8088}" + +LUMERA_RPC_PORT="${LUMERA_RPC_PORT:-26657}" +LUMERA_RPC_ADDR="${LUMERA_RPC_ADDR:-http://localhost:${LUMERA_RPC_PORT}}" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +STOP_SCRIPT="${STOP_SCRIPT:-${SCRIPT_DIR}/stop.sh}" + +log() { + echo "[RESTART] $*" +} + +run_stop() { + if [ ! -f "${STOP_SCRIPT}" ]; then + log "stop.sh not found at ${STOP_SCRIPT}" + exit 1 + fi + bash "${STOP_SCRIPT}" "$@" +} + +ensure_logs_dir() { + mkdir -p "${LOGS_DIR}" +} + +start_lumera() { + local pattern="${DAEMON} start --home ${DAEMON_HOME}" + + if pgrep -f "${pattern}" >/dev/null 2>&1 || pgrep -x "${DAEMON}" >/dev/null 2>&1; then + log "${DAEMON} already running." + return 0 + fi + + if ! command -v "${DAEMON}" >/dev/null 2>&1; then + log "Binary ${DAEMON} not found in PATH." + return 1 + fi + + ensure_logs_dir + mkdir -p "$(dirname "${VALIDATOR_LOG}")" "${DAEMON_HOME}/config" + + log "Starting ${DAEMON}..." + "${DAEMON}" start --home "${DAEMON_HOME}" >"${VALIDATOR_LOG}" 2>&1 & + log "${DAEMON} start requested; logging to ${VALIDATOR_LOG}" +} + +start_supernode() { + local names=("supernode-linux-amd64" "supernode") + local running=0 + + for name in "${names[@]}"; do + if pgrep -x "${name}" >/dev/null 2>&1; then + running=1 + break + fi + done + + if (( running == 1 )); then + log "Supernode already running." + return 0 + fi + + local bin="" + for name in "${names[@]}"; do + if command -v "${name}" >/dev/null 2>&1; then + bin="${name}" + break + fi + done + + if [ -z "${bin}" ]; then + log "Supernode binary not found; skipping start." + return 0 + fi + + ensure_logs_dir + mkdir -p "$(dirname "${SN_LOG}")" "${SN_BASEDIR}" + + log "Starting supernode (${bin})..." + P2P_USE_EXTERNAL_IP=${P2P_USE_EXTERNAL_IP:-false} "${bin}" start -d "${SN_BASEDIR}" >"${SN_LOG}" 2>&1 & + log "Supernode start requested; logging to ${SN_LOG}" +} + +start_network_maker() { + local name="network-maker" + + if pgrep -x "${name}" >/dev/null 2>&1; then + log "network-maker already running." + return 0 + fi + + if ! command -v "${name}" >/dev/null 2>&1; then + log "network-maker binary not found; skipping start." + return 0 + fi + + ensure_logs_dir + mkdir -p "$(dirname "${NM_LOG}")" + + log "Starting network-maker..." + "${name}" >"${NM_LOG}" 2>&1 & + log "network-maker start requested; logging to ${NM_LOG}" +} + +start_nginx() { + if pgrep -x nginx >/dev/null 2>&1; then + log "nginx already running." + return 0 + fi + + if [ ! -d "${NM_UI_DIR}" ] || [ ! -f "${NM_UI_DIR}/index.html" ]; then + log "network-maker UI not found at ${NM_UI_DIR}; skipping nginx start." + return 0 + fi + + mkdir -p /etc/nginx/conf.d + cat >/etc/nginx/conf.d/network-maker-ui.conf <&2 + exit 1 +} + +target="${1:-all}" +case "${target}" in + nm|network-maker) + restart_nm + ;; + sn|supernode) + restart_sn + ;; + nginx|ui) + restart_nginx + ;; + lumera|lumerad|chain) + restart_lumera + ;; + all|"") + restart_all + ;; + *) + usage + ;; +esac diff --git a/devnet/scripts/start.sh b/devnet/scripts/start.sh index b1b03de..7a1188d 100755 --- a/devnet/scripts/start.sh +++ b/devnet/scripts/start.sh @@ -31,6 +31,7 @@ CFG_DIR="${SHARED_DIR}/config" CFG_CHAIN="${CFG_DIR}/config.json" CFG_VALS="${CFG_DIR}/validators.json" RELEASE_DIR="${SHARED_DIR}/release" +NM_UI_DIR="${RELEASE_DIR}/nm-ui" STATUS_DIR="${SHARED_DIR}/status" SETUP_COMPLETE="${STATUS_DIR}/setup_complete" SN="supernode-linux-amd64" @@ -51,6 +52,7 @@ SUPERNODE_LOG="${LOGS_DIR}/supernode.log" VALIDATOR_SETUP_OUT="${LOGS_DIR}/validator-setup.out" SUPERNODE_SETUP_OUT="${LOGS_DIR}/supernode-setup.out" NETWORK_MAKER_SETUP_OUT="${LOGS_DIR}/network-maker-setup.out" +NM_UI_PORT="${NM_UI_PORT:-8088}" LUMERA_RPC_PORT="${LUMERA_RPC_PORT:-26657}" LUMERA_GRPC_PORT="${LUMERA_GRPC_PORT:-9090}" @@ -95,6 +97,56 @@ wait_for_flag() { until [ -s "${f}" ]; do sleep 1; done } +inject_nm_ui_env() { + local api_base="${VITE_API_BASE:-}" + [ -z "${api_base}" ] && return 0 + [ -d "${NM_UI_DIR}" ] || return 0 + + local files + files="$(grep -rl "http://127.0.0.1:8080" "${NM_UI_DIR}" || true)" + if [ -z "${files}" ]; then + echo "[BOOT] network-maker UI: no API base placeholder found to inject." + return 0 + fi + + local escaped_base="${api_base//\//\\/}" + escaped_base="${escaped_base//&/\\&}" + echo "[BOOT] network-maker UI: injecting API base ${api_base}" + # Replace default API base baked into the static bundle with runtime value + while IFS= read -r f; do + sed -i "s|http://127.0.0.1:8080|${escaped_base}|g" "$f" + done <<<"${files}" +} + +start_nm_ui_if_present() { + if [ ! -d "${NM_UI_DIR}" ] || [ ! -f "${NM_UI_DIR}/index.html" ]; then + echo "[BOOT] network-maker UI not found at ${NM_UI_DIR}; skipping nginx" + return + fi + + inject_nm_ui_env + + cat >/etc/nginx/conf.d/network-maker-ui.conf </dev/null 2>&1; then + echo "[BOOT] nginx already running; skipping start for network-maker UI." + return + fi + + echo "[BOOT] Starting nginx to serve network-maker UI on port ${NM_UI_PORT}" + nginx +} + run() { echo "+ $*" "$@" @@ -235,14 +287,19 @@ tail_logs() { exec tail -F "${VALIDATOR_LOG}" "${SUPERNODE_LOG}" "${SUPERNODE_SETUP_OUT}" "${VALIDATOR_SETUP_OUT}" "${NETWORK_MAKER_SETUP_OUT}" } -case "${START_MODE}" in - auto|*) +run_auto_flow() { launch_network_maker_setup launch_supernode_setup launch_validator_setup wait_for_validator_setup start_lumera + start_nm_ui_if_present tail_logs +} + +case "${START_MODE}" in + auto|"") + run_auto_flow ;; bootstrap) @@ -257,6 +314,7 @@ case "${START_MODE}" in wait_for_validator_setup wait_for_n_blocks 3 || { echo "[SN] Lumera chain not producing blocks in time; exiting."; exit 1; } start_lumera + start_nm_ui_if_present tail_logs ;; @@ -264,4 +322,9 @@ case "${START_MODE}" in wait_for_validator_setup exit 0 ;; + + *) + echo "[BOOT] Unknown START_MODE='${START_MODE}', defaulting to auto." + run_auto_flow + ;; esac diff --git a/devnet/scripts/stop.sh b/devnet/scripts/stop.sh new file mode 100755 index 0000000..71fa091 --- /dev/null +++ b/devnet/scripts/stop.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# stop.sh — stop devnet services inside a validator container +# Usage: +# ./stop.sh # stop all known services +# ./stop.sh nm|sn|lumera|nginx +set -euo pipefail + +DAEMON="${DAEMON:-lumerad}" +DAEMON_HOME="${DAEMON_HOME:-/root/.lumera}" +SN_BASEDIR="${SN_BASEDIR:-/root/.supernode}" + +log() { + echo "[STOP] $*" +} + +stop_nm() { + local name="network-maker" + + if pgrep -x "${name}" >/dev/null 2>&1; then + log "Stopping network-maker..." + pkill -x "${name}" || true + log "network-maker stop requested." + else + log "network-maker is not running." + fi +} + +stop_sn() { + local stopped=0 + local names=("supernode-linux-amd64" "supernode") + + for name in "${names[@]}"; do + if pgrep -x "${name}" >/dev/null 2>&1; then + stopped=1 + log "Stopping supernode (${name})..." + if command -v "${name}" >/dev/null 2>&1; then + "${name}" stop -d "${SN_BASEDIR}" >/dev/null 2>&1 || pkill -x "${name}" || true + else + pkill -x "${name}" || true + fi + fi + done + + if (( stopped == 0 )); then + log "Supernode is not running." + else + log "Supernode stop requested." + fi +} + +stop_nginx() { + if pgrep -x nginx >/dev/null 2>&1; then + log "Stopping nginx..." + if command -v nginx >/dev/null 2>&1; then + nginx -s quit >/dev/null 2>&1 || nginx -s stop >/dev/null 2>&1 || pkill -x nginx || true + else + pkill -x nginx || true + fi + log "nginx stop requested." + else + log "nginx is not running." + fi +} + +stop_lumera() { + local pattern="${DAEMON} start --home ${DAEMON_HOME}" + + if pgrep -f "${pattern}" >/dev/null 2>&1; then + log "Stopping ${DAEMON}..." + pkill -f "${pattern}" || true + log "${DAEMON} stop requested." + return + fi + + if pgrep -x "${DAEMON}" >/dev/null 2>&1; then + log "Stopping ${DAEMON}..." + pkill -x "${DAEMON}" || true + log "${DAEMON} stop requested." + else + log "${DAEMON} is not running." + fi +} + +stop_all() { + stop_nm + stop_sn + stop_nginx + stop_lumera +} + +usage() { + echo "Usage: $0 [nm|sn|lumera|nginx|all]" >&2 + exit 1 +} + +target="${1:-all}" +case "${target}" in + nm|network-maker) + stop_nm + ;; + sn|supernode) + stop_sn + ;; + nginx|ui) + stop_nginx + ;; + lumera|lumerad|chain) + stop_lumera + ;; + all|"") + stop_all + ;; + *) + usage + ;; +esac