From fd26fbddc31de7b701d25d9784bd652c493b7ab3 Mon Sep 17 00:00:00 2001 From: ASuciuX <151519329+ASuciuX@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:29:00 +0200 Subject: [PATCH 01/15] feat: add mac os support --- Makefile | 83 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 67d9773..af8fa51 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,32 @@ -# List of binaries hacknet needs to function properly -COMMANDS := sudo tar zstd getent stress +# OS Detection and Cross-Platform Support +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + OS := macos + export UID := $(shell id -u) + export GID := $(shell id -g) + # macOS: use sysctl for CPU count + STRESS_CORES ?= $(shell sysctl -n hw.ncpu) + # List of binaries hacknet needs to function properly + COMMANDS := sudo tar zstd stress + TAR_EXTRACT_FLAGS := -xf +else + OS := linux + # Linux: use getent + export UID := $(shell getent passwd $$(whoami) | cut -d":" -f 3) + export GID := $(shell getent passwd $$(whoami) | cut -d":" -f 4) + # Linux: use /proc/cpuinfo for CPU count + STRESS_CORES ?= $(shell cat /proc/cpuinfo | grep processor | wc -l) + # List of binaries hacknet needs to function properly + COMMANDS := sudo tar zstd getent stress + TAR_EXTRACT_FLAGS := --same-owner -xf +endif + +# Verify required dependencies exist $(foreach bin,$(COMMANDS),\ $(if $(shell command -v $(bin) 2> /dev/null),$(info),$(error Missing required dependency: `$(bin)`))) TARGET := $(firstword $(MAKECMDGOALS)) PARAMS := $(filter-out $(TARGET),$(MAKECMDGOALS)) + # Hardcode the chainstate dir if we're booting from genesis ifeq ($(TARGET),up-genesis) export CHAINSTATE_DIR := $(PWD)/docker/chainstate/genesis @@ -12,9 +35,6 @@ ifeq ($(TARGET),genesis) export CHAINSTATE_DIR := $(PWD)/docker/chainstate/genesis endif -# UID and GID are not currently used, but may be later to ensure consistent file permissions -export UID := $(shell getent passwd $$(whoami) | cut -d":" -f 3) -export GID := $(shell getent passwd $$(whoami) | cut -d":" -f 4) EPOCH := $(shell date +%s) PWD = $(shell pwd) # Set a unique project name (used for checking if the network is running) @@ -26,22 +46,21 @@ SERVICES := $(shell CHAINSTATE_DIR="" docker compose -f docker/docker-compose.ym # Pauses the bitcoin miner script. Default is set to nearly 1 trillion blocks PAUSE_HEIGHT ?= 999999999999 # Used for the stress testing target. modifies how much cpu to consume for how long -STRESS_CORES ?= $(shell cat /proc/cpuinfo | grep processor | wc -l) STRESS_TIMEOUT ?= 120 # Create the chainstate dir and extract an archive to it when the "up" target is used -$(CHAINSTATE_DIR): /usr/bin/tar /usr/bin/zstd - @if [ ! -d "$(CHAINSTATE_DIR)" ]; then \ - mkdir -p $(CHAINSTATE_DIR) - @if [ "$(TARGET)" = "up" ]; then - if [ -f "$(CHAINSTATE_ARCHIVE)" ]; then - sudo tar --same-owner -xf $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || exit 1 - else - @echo "Chainstate archive ($(CHAINSTATE_ARCHIVE)) not found. Exiting" - rm -rf $(CHAINSTATE_DIR) - exit 1 - fi - fi +$(CHAINSTATE_DIR): + @if [ ! -d "$(CHAINSTATE_DIR)" ]; then \ + mkdir -p $(CHAINSTATE_DIR); \ + if [ "$(TARGET)" = "up" ]; then \ + if [ -f "$(CHAINSTATE_ARCHIVE)" ]; then \ + sudo tar $(TAR_EXTRACT_FLAGS) $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || exit 1; \ + else \ + echo "Chainstate archive ($(CHAINSTATE_ARCHIVE)) not found. Exiting"; \ + rm -rf $(CHAINSTATE_DIR); \ + exit 1; \ + fi; \ + fi; \ fi # Build the images with a cache if present @@ -75,7 +94,7 @@ check-running: exit 1; \ fi -# For targets that need an arg, check that *something* is provided. it not, exit +# For targets that need an arg, check that *something* is provided. if not, exit check-params: | check-running @if [ ! "$(PARAMS)" ]; then \ echo "No service defined. Exiting"; \ @@ -85,15 +104,17 @@ check-params: | check-running # Boot the network from a local chainstate archive up: check-not-running | build $(CHAINSTATE_DIR) @echo "Starting $(PROJECT) network from chainstate archive" + @echo " OS: $(OS)" @echo " Chainstate Dir: $(CHAINSTATE_DIR)" @echo " Chainstate Archive: $(CHAINSTATE_ARCHIVE)" echo "$(CHAINSTATE_DIR)" > .current-chainstate-dir docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) up -d # Boot the network from genesis -genesis: check-not-running | build $(CHAINSTATE_DIR) /usr/bin/sudo +genesis: check-not-running | build $(CHAINSTATE_DIR) @echo "Starting $(PROJECT) network from genesis" - @if [ -d "$(CHAINSTATE_DIR)" ]; then \ + @echo " OS: $(OS)" + @if [ -d "$(CHAINSTATE_DIR)" ]; then \ echo " Removing existing genesis chainstate dir: $(CHAINSTATE_DIR)"; \ sudo rm -rf $(CHAINSTATE_DIR); \ fi @@ -118,7 +139,7 @@ down: backup-logs current-chainstate-dir @echo "Shutting down $(PROJECT) network" docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) down @if [ -f .current-chainstate-dir ]; then \ - rm -f .current-chainstate-dir + rm -f .current-chainstate-dir; \ fi # Secondary name to bring down the genesis network @@ -129,7 +150,7 @@ down-force: @echo "Force Shutting down $(PROJECT) network" docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) down @if [ -f .current-chainstate-dir ]; then \ - rm -f .current-chainstate-dir + rm -f .current-chainstate-dir; \ fi # Stream specified service logs to STDOUT. Does not validate if PARAMS is supplied @@ -142,15 +163,15 @@ log-all: current-chainstate-dir docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) logs -t -f # Backup all service logs to $ACTIVE_CHAINSTATE_DIR/logs/.log -backup-logs: current-chainstate-dir /usr/bin/sudo +backup-logs: current-chainstate-dir @if [ -f .current-chainstate-dir ]; then \ - $(eval ACTIVE_CHAINSTATE_DIR=$(shell cat .current-chainstate-dir)) - if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)" ]; then \ - echo "Chainstate Dir ($(ACTIVE_CHAINSTATE_DIR)) not found";\ + $(eval ACTIVE_CHAINSTATE_DIR=$(shell cat .current-chainstate-dir)) \ + if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)" ]; then \ + echo "Chainstate Dir ($(ACTIVE_CHAINSTATE_DIR)) not found"; \ exit 1; \ fi; \ - if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ - mkdir -p $(ACTIVE_CHAINSTATE_DIR)/logs;\ + if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ + mkdir -p $(ACTIVE_CHAINSTATE_DIR)/logs; \ fi; \ echo "Backing up logs to $(ACTIVE_CHAINSTATE_DIR)/logs"; \ for service in $(SERVICES); do \ @@ -161,7 +182,7 @@ backup-logs: current-chainstate-dir /usr/bin/sudo # Replace the existing chainstate archive. Will be used with target `up` snapshot: current-chainstate-dir down @echo "Creating $(PROJECT) chainstate snapshot from $(ACTIVE_CHAINSTATE_DIR)" - @if [ -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ + @if [ -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ rm -rf $(ACTIVE_CHAINSTATE_DIR)/logs; \ fi @echo "Creating snapshot: $(CHAINSTATE_ARCHIVE)" @@ -220,5 +241,5 @@ monitor: clean: down-force sudo rm -rf ./docker/chainstate/* -.PHONY: build build-no-cache current-chainstate-dir check-not-running check-running check-params up genesis up-genesis down down-genesis down-force log log-all backup-logs snapshot pause unpause stop start restart stress test monitor clean +.PHONY: build build-no-cache current-chainstate-dir check-not-running check-running check-params up genesis up-genesis down down-genesis down-force log log-all backup-logs snapshot pause unpause stop start restart stress test monitor clean up-prom down-prom .ONESHELL: all-in-one-shell From 36ab6fea0f484157e998fda34a2d83ecad8f35ba Mon Sep 17 00:00:00 2001 From: ASuciuX <151519329+ASuciuX@users.noreply.github.com> Date: Wed, 3 Dec 2025 19:30:41 +0200 Subject: [PATCH 02/15] feat: support for running stacks node from commit --- docker/docker-compose.yml | 4 ++-- docker/stacks/Dockerfile | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 5add6c4..f78a93d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -78,7 +78,7 @@ x-common-vars: - &STACKS_20_HEIGHT ${STACKS_20_HEIGHT:-0} - &STACKS_2_05_HEIGHT ${STACKS_2_05_HEIGHT:-203} - &STACKS_21_HEIGHT ${STACKS_21_HEIGHT:-204} - - &STACKS_POX2_HEIGHT ${STACKS_POX2_HEIGHT:-205} # 104 is is stacks_block=1, 106 is stacks_block=3 + - &STACKS_POX2_HEIGHT ${STACKS_POX2_HEIGHT:-205} # 104 is stacks_block=1, 106 is stacks_block=3 - &STACKS_22_HEIGHT ${STACKS_22_HEIGHT:-206} - &STACKS_23_HEIGHT ${STACKS_23_HEIGHT:-207} - &STACKS_24_HEIGHT ${STACKS_24_HEIGHT:-208} @@ -94,7 +94,7 @@ x-common-vars: - &REWARD_RECIPIENT_1 ${REWARD_RECIPIENT_1:-ST1XVSVQN0KP5SDYFNT8E5TXWVW0XZVQEDBMCJ3XM} # priv: a6143d20cd73d0dce2179e2af7771372a95b9d6795924492bd4d15d17709531e01 - &REWARD_RECIPIENT_2 ${REWARD_RECIPIENT_2:-ST2FW15NGB4H76FMVXKHYYSM865YVS6V3SA1GNABC} # priv: fe3087801196d8027008146b13e6d365920c2e4b7bc9969729ec2f0f22ef74fc01 - &REWARD_RECIPIENT_3 ${REWARD_RECIPIENT_3:-ST2MES40ZEXTX9M4YXW9QSWHRVC9HYT419S198VPM} # priv: ed7eb063c61b8e892987228f1fcfb74eab5009568861613dc4b074b708a7893701 - - &STACKS_CORE_BASE_BRANCH ${STACKS_CORE_BASE_BRANCH:-3.4.0.0.1} + - &STACKS_CORE_BASE_BRANCH ${STACKS_CORE_BASE_BRANCH:-3.4.0.0.1} # branch, tag, or commit SHA - &PAUSE_HEIGHT ${PAUSE_HEIGHT:-999999999999} - &PAUSE_TIMER 86400000 diff --git a/docker/stacks/Dockerfile b/docker/stacks/Dockerfile index 5574bc7..fbc61bb 100644 --- a/docker/stacks/Dockerfile +++ b/docker/stacks/Dockerfile @@ -1,14 +1,25 @@ FROM rust:bookworm AS builder # This will be overridden by the value from docker-compose +# Supports: branch name, tag, or commit SHA +# Examples: +# STACKS_CORE_BASE_BRANCH=develop (branch) +# STACKS_CORE_BASE_BRANCH=3.3.0.0.1 (tag) +# STACKS_CORE_BASE_BRANCH=abc123def456 (commit) ARG STACKS_CORE_BASE_BRANCH=master -RUN echo "Building Stacks Core from branch: $STACKS_CORE_BASE_BRANCH" +RUN echo "Building Stacks Core from: $STACKS_CORE_BASE_BRANCH" + +# Clone efficiently: shallow for branches/tags, targeted fetch for commits +# This avoids downloading the full 2GB+ history +RUN git init /code/stacks-core && \ + cd /code/stacks-core && \ + git remote add origin https://github.com/stacks-network/stacks-core.git && \ + git fetch --depth=1 origin $STACKS_CORE_BASE_BRANCH && \ + git checkout FETCH_HEAD -# Clone the specified branch from GitHub -RUN git clone --branch $STACKS_CORE_BASE_BRANCH --single-branch --depth=1 https://github.com/stacks-network/stacks-core.git /code/stacks-core WORKDIR /code/stacks-core -RUN apt-get update && apt-get install -y git libclang-dev llvm +RUN apt-get update && apt-get install -y libclang-dev llvm # Run an build that we'll cache the result of and then build the code RUN cargo build --features monitoring_prom,slog_json --bin stacks-node --bin stacks-signer From ba0aaedda8f843c6862e6d837fc46fc4f689a2d8 Mon Sep 17 00:00:00 2001 From: ASuciuX <151519329+ASuciuX@users.noreply.github.com> Date: Thu, 4 Dec 2025 19:53:17 +0200 Subject: [PATCH 03/15] Update docker/stacks/Dockerfile Co-authored-by: Radu Bahmata <92028479+BowTiedRadone@users.noreply.github.com> --- docker/stacks/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/stacks/Dockerfile b/docker/stacks/Dockerfile index fbc61bb..791e96f 100644 --- a/docker/stacks/Dockerfile +++ b/docker/stacks/Dockerfile @@ -11,7 +11,6 @@ ARG STACKS_CORE_BASE_BRANCH=master RUN echo "Building Stacks Core from: $STACKS_CORE_BASE_BRANCH" # Clone efficiently: shallow for branches/tags, targeted fetch for commits -# This avoids downloading the full 2GB+ history RUN git init /code/stacks-core && \ cd /code/stacks-core && \ git remote add origin https://github.com/stacks-network/stacks-core.git && \ From b78fbfbb2093ce00ba97fd4acfaad4c54517a177 Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Tue, 12 May 2026 13:00:54 +0300 Subject: [PATCH 04/15] Run tar as same user on macOS --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index af8fa51..3c486b4 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,10 @@ ifeq ($(UNAME_S),Darwin) STRESS_CORES ?= $(shell sysctl -n hw.ncpu) # List of binaries hacknet needs to function properly COMMANDS := sudo tar zstd stress - TAR_EXTRACT_FLAGS := -xf + # macOS Docker Desktop maps host UID into its VM; running BSD tar via sudo + # restores archive ownership and leaves bind-mount sources unwritable. + # Extract as the current user so everything lands user-owned. + TAR_EXTRACT := tar -xf else OS := linux # Linux: use getent @@ -18,7 +21,7 @@ else STRESS_CORES ?= $(shell cat /proc/cpuinfo | grep processor | wc -l) # List of binaries hacknet needs to function properly COMMANDS := sudo tar zstd getent stress - TAR_EXTRACT_FLAGS := --same-owner -xf + TAR_EXTRACT := sudo tar --same-owner -xf endif # Verify required dependencies exist @@ -54,7 +57,7 @@ $(CHAINSTATE_DIR): mkdir -p $(CHAINSTATE_DIR); \ if [ "$(TARGET)" = "up" ]; then \ if [ -f "$(CHAINSTATE_ARCHIVE)" ]; then \ - sudo tar $(TAR_EXTRACT_FLAGS) $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || exit 1; \ + $(TAR_EXTRACT) $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || exit 1; \ else \ echo "Chainstate archive ($(CHAINSTATE_ARCHIVE)) not found. Exiting"; \ rm -rf $(CHAINSTATE_DIR); \ From b9c9fda4253580ea9d004890bc34b269385cb600 Mon Sep 17 00:00:00 2001 From: wileyj <2847772+wileyj@users.noreply.github.com> Date: Thu, 21 May 2026 10:33:12 -0700 Subject: [PATCH 05/15] Cleanup of Makefile/version to address comments in 39 --- Makefile | 58 ++++++++++----------------------------- docker/docker-compose.yml | 2 +- 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index 3c486b4..a871ad8 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,7 @@ else endif # Verify required dependencies exist -$(foreach bin,$(COMMANDS),\ - $(if $(shell command -v $(bin) 2> /dev/null),$(info),$(error Missing required dependency: `$(bin)`))) +$(foreach bin,$(COMMANDS),$(if $(shell command -v $(bin) 2> /dev/null),$(info),$(error Missing required dependency: `$(bin)`))) TARGET := $(firstword $(MAKECMDGOALS)) PARAMS := $(filter-out $(TARGET),$(MAKECMDGOALS)) @@ -53,16 +52,10 @@ STRESS_TIMEOUT ?= 120 # Create the chainstate dir and extract an archive to it when the "up" target is used $(CHAINSTATE_DIR): - @if [ ! -d "$(CHAINSTATE_DIR)" ]; then \ - mkdir -p $(CHAINSTATE_DIR); \ + @if [ ! -d "$(CHAINSTATE_DIR)" ]; then mkdir -p $(CHAINSTATE_DIR) && \ if [ "$(TARGET)" = "up" ]; then \ - if [ -f "$(CHAINSTATE_ARCHIVE)" ]; then \ - $(TAR_EXTRACT) $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || exit 1; \ - else \ - echo "Chainstate archive ($(CHAINSTATE_ARCHIVE)) not found. Exiting"; \ - rm -rf $(CHAINSTATE_DIR); \ - exit 1; \ - fi; \ + [ -f "$(CHAINSTATE_ARCHIVE)" ] && $(TAR_EXTRACT) $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || \ + { echo "Chainstate archive ($(CHAINSTATE_ARCHIVE)) not found. Exiting"; rm -rf $(CHAINSTATE_DIR); exit 1; }; \ fi; \ fi @@ -92,17 +85,11 @@ check-not-running: # If the network is not running, we need to exit (ex: trying to restart a container) check-running: - @if test ! `docker compose ls --filter name=$(PROJECT) -q`; then \ - echo "Network not running. exiting"; \ - exit 1; \ - fi + @test `docker compose ls --filter name=$(PROJECT) -q` || { echo "Network not running. exiting"; exit 1; } # For targets that need an arg, check that *something* is provided. if not, exit check-params: | check-running - @if [ ! "$(PARAMS)" ]; then \ - echo "No service defined. Exiting"; \ - exit 1; \ - fi + @[ "$(PARAMS)" ] || { echo "No service defined. Exiting"; exit 1; } # Boot the network from a local chainstate archive up: check-not-running | build $(CHAINSTATE_DIR) @@ -117,10 +104,7 @@ up: check-not-running | build $(CHAINSTATE_DIR) genesis: check-not-running | build $(CHAINSTATE_DIR) @echo "Starting $(PROJECT) network from genesis" @echo " OS: $(OS)" - @if [ -d "$(CHAINSTATE_DIR)" ]; then \ - echo " Removing existing genesis chainstate dir: $(CHAINSTATE_DIR)"; \ - sudo rm -rf $(CHAINSTATE_DIR); \ - fi + @[ -d "$(CHAINSTATE_DIR)" ] && { echo " Removing existing genesis chainstate dir: $(CHAINSTATE_DIR)"; sudo rm -rf $(CHAINSTATE_DIR); } @echo " Chainstate Dir: $(CHAINSTATE_DIR)" mkdir -p "$(CHAINSTATE_DIR)" echo "$(CHAINSTATE_DIR)" > .current-chainstate-dir @@ -141,9 +125,7 @@ down-prom: down: backup-logs current-chainstate-dir @echo "Shutting down $(PROJECT) network" docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) down - @if [ -f .current-chainstate-dir ]; then \ - rm -f .current-chainstate-dir; \ - fi + @[ -f .current-chainstate-dir ] && rm -f .current-chainstate-dir # Secondary name to bring down the genesis network down-genesis: down @@ -152,9 +134,7 @@ down-genesis: down down-force: @echo "Force Shutting down $(PROJECT) network" docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) down - @if [ -f .current-chainstate-dir ]; then \ - rm -f .current-chainstate-dir; \ - fi + @[ -f .current-chainstate-dir ] && rm -f .current-chainstate-dir # Stream specified service logs to STDOUT. Does not validate if PARAMS is supplied log: current-chainstate-dir @@ -169,28 +149,18 @@ log-all: current-chainstate-dir backup-logs: current-chainstate-dir @if [ -f .current-chainstate-dir ]; then \ $(eval ACTIVE_CHAINSTATE_DIR=$(shell cat .current-chainstate-dir)) \ - if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)" ]; then \ - echo "Chainstate Dir ($(ACTIVE_CHAINSTATE_DIR)) not found"; \ - exit 1; \ - fi; \ - if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ - mkdir -p $(ACTIVE_CHAINSTATE_DIR)/logs; \ - fi; \ + [ -d "$(ACTIVE_CHAINSTATE_DIR)" ] || { echo "Chainstate Dir ($(ACTIVE_CHAINSTATE_DIR)) not found"; exit 1; }; \ + mkdir -p $(ACTIVE_CHAINSTATE_DIR)/logs; \ echo "Backing up logs to $(ACTIVE_CHAINSTATE_DIR)/logs"; \ - for service in $(SERVICES); do \ - docker logs -t $$service > $(ACTIVE_CHAINSTATE_DIR)/logs/$$service.log 2>&1; \ - done; \ + for service in $(SERVICES); do docker logs -t $$service > $(ACTIVE_CHAINSTATE_DIR)/logs/$$service.log 2>&1; done; \ fi # Replace the existing chainstate archive. Will be used with target `up` snapshot: current-chainstate-dir down @echo "Creating $(PROJECT) chainstate snapshot from $(ACTIVE_CHAINSTATE_DIR)" - @if [ -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ - rm -rf $(ACTIVE_CHAINSTATE_DIR)/logs; \ - fi + @[ -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ] && rm -rf $(ACTIVE_CHAINSTATE_DIR)/logs @echo "Creating snapshot: $(CHAINSTATE_ARCHIVE)" - @echo "cd $(ACTIVE_CHAINSTATE_DIR); sudo tar --zstd -cf $(CHAINSTATE_ARCHIVE) *; cd $(PWD)" - cd $(ACTIVE_CHAINSTATE_DIR); sudo tar --zstd -cf $(CHAINSTATE_ARCHIVE) *; cd $(PWD) + (cd $(ACTIVE_CHAINSTATE_DIR) && sudo tar --zstd -cf $(CHAINSTATE_ARCHIVE) *) # Pause all services in the network (netork is down, but recoverably with target 'unpause') pause: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f78a93d..55378cb 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -94,7 +94,7 @@ x-common-vars: - &REWARD_RECIPIENT_1 ${REWARD_RECIPIENT_1:-ST1XVSVQN0KP5SDYFNT8E5TXWVW0XZVQEDBMCJ3XM} # priv: a6143d20cd73d0dce2179e2af7771372a95b9d6795924492bd4d15d17709531e01 - &REWARD_RECIPIENT_2 ${REWARD_RECIPIENT_2:-ST2FW15NGB4H76FMVXKHYYSM865YVS6V3SA1GNABC} # priv: fe3087801196d8027008146b13e6d365920c2e4b7bc9969729ec2f0f22ef74fc01 - &REWARD_RECIPIENT_3 ${REWARD_RECIPIENT_3:-ST2MES40ZEXTX9M4YXW9QSWHRVC9HYT419S198VPM} # priv: ed7eb063c61b8e892987228f1fcfb74eab5009568861613dc4b074b708a7893701 - - &STACKS_CORE_BASE_BRANCH ${STACKS_CORE_BASE_BRANCH:-3.4.0.0.1} # branch, tag, or commit SHA + - &STACKS_CORE_BASE_BRANCH ${STACKS_CORE_BASE_BRANCH:-3.4.0.0.2} # branch, tag, or commit SHA - &PAUSE_HEIGHT ${PAUSE_HEIGHT:-999999999999} - &PAUSE_TIMER 86400000 From 048630c51ce538fc993b9f1d33aea19b6c218570 Mon Sep 17 00:00:00 2001 From: wileyj <2847772+wileyj@users.noreply.github.com> Date: Thu, 21 May 2026 10:36:48 -0700 Subject: [PATCH 06/15] Add line about supported OS'es --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 034fec2..5e8afd7 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ - bind-mounts a local filesystem for data persistence - Uses a chainstate archive to boot the network quickly - Configurable signing weight across the 3 signers +- Designed to run Linux (tested on Debian-based) or MacOS ## Quickstart From ab8228503a62432f2dab5249f006b1dee8ac1d67 Mon Sep 17 00:00:00 2001 From: wileyj <2847772+wileyj@users.noreply.github.com> Date: Fri, 22 May 2026 08:03:53 -0700 Subject: [PATCH 07/15] Add qualifier word in the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e8afd7..bac2f83 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ - bind-mounts a local filesystem for data persistence - Uses a chainstate archive to boot the network quickly - Configurable signing weight across the 3 signers -- Designed to run Linux (tested on Debian-based) or MacOS +- Designed to run on Linux (tested on Debian-based) or MacOS ## Quickstart From 13cbe1f69c3290f825e9f81687868eafbd8e83e0 Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Wed, 27 May 2026 13:40:58 +0300 Subject: [PATCH 08/15] Format README.md using prettier --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bac2f83..8b8dc93 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# Hacknet +# Hacknet + - Configured for 3 stacks miners and signers - bind-mounts a local filesystem for data persistence - Uses a chainstate archive to boot the network quickly @@ -8,155 +9,208 @@ ## Quickstart ### Start network using a chainstate archive -*Note*: default chainstate archive at `./docker/chainstate.tar.zstd` will be used unless overridden by `CHAINSTATE_ARCHIVE` env var. + +_Note_: default chainstate archive at `./docker/chainstate.tar.zstd` will be used unless overridden by `CHAINSTATE_ARCHIVE` env var. Creates a dynamic chainstate folder at `./docker/chainstate/$(date +%s)` from a chainstate archive + ```sh make up ``` + To override the archive used to restore the network: + ```sh CHAINSTATE_ARCHIVE=./docker/chainsate_new.tar.zstd make up ``` + To override the chainstate dir and resume a stopped network: -*Note*: will not work for the `genesis` chainstate dir and absolute path is required +_Note_: will not work for the `genesis` chainstate dir and absolute path is required + ```sh CHAINSTATE_DIR=$(pwd)/docker/chainsate/ make up ``` ### Start network from genesis + Creates a static chainstate folder at `./docker/chainstate/genesis` + ```sh make genesis ``` ### Stop the network + ```sh make down ``` ## Full list of options + ### Logs + `docker logs -f ` will work, along with some defined Makefile targets #### Store logs from all services under the current chainstate folder + ```sh make backup-logs ``` #### Stream logs from all services + ```sh make log-all ``` + #### Stream single service logs + ```sh make log stacks-signer-1 -- -f ``` #### Log from a single service -*note* this will not follow the logs + +_note_ this will not follow the logs + ```sh make log stacks-signer-1 ``` ### Container management + #### Pause/Unpause service + To pause all services on the network + ```sh make pause ``` + To resume the network + ```sh make unpause ``` #### Restart a service + Used to simulate a node dropping off of the network + ```sh make restart ``` + ex: + ```sh make restart stacks-miner-3 61 ``` #### Stop/Start service (kill) + Stop a single service + ```sh make stop ``` + Restart the stopped service + ```sh make start ``` #### Force stop the hacknet network + If the network is in a "stuck" state where the Makefile targets are not stopping the services (i.e. the `.current-chainstate-dir` file was removed while network was running), `down-force` may be used to force stop the network. ```sh make down-force ``` -Additionally, `clean` target will call `down-force` *and also* delete any chainstates on disk in `./docker/chainstate/*` +Additionally, `clean` target will call `down-force` _and also_ delete any chainstates on disk in `./docker/chainstate/*` + ```sh make clean ``` ### Additional Features + #### Stress the CPU + To simulate CPU load. Can be modified with: + - `STRESS_CORES` to target how many worker threads (default will use all cores) - `STRESS_TIMEOUT` set a timeout (default of 120s) + ```sh make stress ``` + ```sh STRESS_CORES=10 STRESS_TIMEOUT=60 make stress ``` #### Monitor chain heights + Run a script outputting the current chain heights of each miner + ```sh make monitor ``` #### Create a chainstate snapshot + - Setting the env var `PAUSE_HEIGHT` is optional to pause the chain at a specific height, else a default of Bitcoin block `999999999999` is used. - Setting the env var `MINE_INTERVAL_EPOCH3` is recommended to reach the `PAUSE_HEIGHT` more quickly to create the snapshot - Optionally, the `CHAINSTATE_ARCHIVE` env var may be set to store the archive in a non-default location/name -**This operation will work with either the `up` or `genesis` targets** + **This operation will work with either the `up` or `genesis` targets** + ```sh make genesis ``` + or with env vars set: + ```sh MINE_INTERVAL_EPOCH3=10 PAUSE_HEIGHT=240 make genesis ``` + Followed by waiting until the Bitcoin miner reaches the specified height (ex: `docker logs -f bitcoin-miner`) Once the Bitcoin miner has reached the specified height: + ```sh make snapshot ``` + This will first bring down the network, then replace the existing `./docker/chainstate.tar.zstd` archive file used with the `up` Makefile target. -To create the chainstate archive in a non-default location/name *File path must be absolute*: +To create the chainstate archive in a non-default location/name _File path must be absolute_: + ```sh CHAINSTATE_ARCHIVE=$(pwd)/docker/chainstate_new.tar.zstd make snapshot ``` **Note**: `CHAINSTATE_ARCHIVE` must be defined to use with `make up` to use a non-default snapshot. ex: + ```sh CHAINSTATE_ARCHIVE=./docker/chainstate_new.tar.zstd make up ``` #### Prometheus sidecar + ##### Run prometheus and cadvisor + Runs a prometheus container to record data collected by `cadvisor` for tracking host/container metrics + ```sh make up-prom ``` + ##### Stop prometheus and cadvisor + ```sh make down-prom ``` @@ -177,7 +231,8 @@ make down-prom - **tx-broadcaster**: submits token transfer txs to ensure stacks block production during a sortition ## Bitcoin Miner -*Dedicated address for Bitcoin block production after initial setup (~200 blocks). This prevents conflicts with Stacks mining operations.* + +_Dedicated address for Bitcoin block production after initial setup (~200 blocks). This prevents conflicts with Stacks mining operations._ ```text ‣ Mnemonic: foot script pledge suit bread thing stage long auction craft label injury helmet drum ice govern glass tag lamp shield bike raccoon cloud hat @@ -308,13 +363,13 @@ make down-prom ‣ WIF: cMz2ZSsaVgWPFUkE44zHpJepB4NdwB9L938h53hQfFoot81AZFb3 ``` - ## Testing Accounts -*Unused but funded accounts that may be used to deploy contracts or other txs* + +_Unused but funded accounts that may be used to deploy contracts or other txs_ ### Deployer Account -*Unused but funded account that may be used to deploy contracts or other txs* +_Unused but funded account that may be used to deploy contracts or other txs_ ```text ‣ Mnemonic: keep can record bracket note hip face pudding castle detail few sunset review burger enhance foil lamp estate reopen butter then wasp pen kick From ed7e82d96ceebb7d16f33391626f0b06b2f56740 Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Thu, 28 May 2026 11:04:48 +0300 Subject: [PATCH 09/15] Add ci workflow that spins up hacknet on PRs --- .github/workflows/ci.yml | 58 ++++++++++++++++++++++++++++++++ docker/tests/hacknet-liveness.sh | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8a5f200 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI + +on: + pull_request: + workflow_dispatch: + +jobs: + dry-run: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y zstd stress + + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: brew install zstd stress + + - name: Verify Makefile parses and dependency check passes + run: make -n up + + # Linux-only: macOS runners lack Docker. The Darwin Makefile branch is + # already covered by dry-run; the live stack itself is OS-independent. + live-run: + needs: dry-run + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y zstd stress jq + + - name: Boot network from chainstate archive + run: make up + + - name: Wait for network to be live + run: | + for i in $(seq 1 30); do + if make test; then + echo "Network is live" + exit 0 + fi + echo "Attempt $i/30 — not ready yet, sleeping 10s" + sleep 10 + done + echo "Network did not become live within timeout" + exit 1 + + - name: Tear down + if: always() + run: make down-force diff --git a/docker/tests/hacknet-liveness.sh b/docker/tests/hacknet-liveness.sh index adfa2fc..4b21a8f 100755 --- a/docker/tests/hacknet-liveness.sh +++ b/docker/tests/hacknet-liveness.sh @@ -37,7 +37,7 @@ echo -e "| => (3) 🔬 TEST: [CHECK IF POSTGRES IS READY] |" echo -e " -----------------------------------------------" PG_READY_SUCCESS=false PG_READY_SUCCESS_FRMT=$(echo -e "\033[1;31m$PG_READY_SUCCESS\033[0m❌") -if (docker exec -it postgres pg_isready); then +if (docker exec postgres pg_isready); then PG_READY_SUCCESS=true PG_READY_SUCCESS_FRMT=$(echo -e "\033[1;32m$PG_READY_SUCCESS\033[0m ✅") fi From 353bf718eed45a219f5e9c5b9fffe9a949ed913e Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Thu, 28 May 2026 11:25:40 +0300 Subject: [PATCH 10/15] Fix `hacknet-liveness.sh` var naming --- docker/tests/hacknet-liveness.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docker/tests/hacknet-liveness.sh b/docker/tests/hacknet-liveness.sh index 4b21a8f..48de627 100755 --- a/docker/tests/hacknet-liveness.sh +++ b/docker/tests/hacknet-liveness.sh @@ -66,12 +66,12 @@ GET_STACKS_MINER_1_INFO_STATUS_CODE=$(curl --write-out %{http_code} --silent --o echo -e "\nGET STACKS MINER 1 STATUS: $GET_STACKS_MINER_1_INFO_STATUS_CODE" -STX_MINER_1_LIVENESS_SUCCESS=false -STACKS_MINER_1_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STX_MINER_1_LIVENESS_SUCCESS\033[0m❌") +STACKS_MINER_1_LIVENESS_SUCCESS=false +STACKS_MINER_1_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STACKS_MINER_1_LIVENESS_SUCCESS\033[0m❌") if [[ $GET_STACKS_MINER_1_INFO_STATUS_CODE == "200" ]]; then - STX_MINER_1_LIVENESS_SUCCESS=true - STACKS_MINER_1_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STX_MINER_1_LIVENESS_SUCCESS\033[0m ✅") + STACKS_MINER_1_LIVENESS_SUCCESS=true + STACKS_MINER_1_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_MINER_1_LIVENESS_SUCCESS\033[0m ✅") fi @@ -86,12 +86,12 @@ GET_STACKS_MINER_2_INFO_STATUS_CODE=$(curl --write-out %{http_code} --silent --o echo -e "\nGET STACKS MINER 2 STATUS: $GET_STACKS_MINER_2_INFO_STATUS_CODE" -STX_MINER_2_LIVENESS_SUCCESS=false -STACKS_MINER_2_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STX_MINER_2_LIVENESS_SUCCESS\033[0m❌") +STACKS_MINER_2_LIVENESS_SUCCESS=false +STACKS_MINER_2_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STACKS_MINER_2_LIVENESS_SUCCESS\033[0m❌") if [[ $GET_STACKS_MINER_2_INFO_STATUS_CODE == "200" ]]; then - STX_MINER_2_LIVENESS_SUCCESS=true - STACKS_MINER_2_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STX_MINER_2_LIVENESS_SUCCESS\033[0m ✅") + STACKS_MINER_2_LIVENESS_SUCCESS=true + STACKS_MINER_2_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_MINER_2_LIVENESS_SUCCESS\033[0m ✅") fi @@ -106,16 +106,16 @@ GET_STACKS_MINER_3_INFO_STATUS_CODE=$(curl --write-out %{http_code} --silent --o echo -e "\nGET STACKS MINER 3 STATUS: $GET_STACKS_MINER_3_INFO_STATUS_CODE" -STX_MINER_3_LIVENESS_SUCCESS=false -STACKS_MINER_3_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STX_MINER_3_LIVENESS_SUCCESS\033[0m❌") +STACKS_MINER_3_LIVENESS_SUCCESS=false +STACKS_MINER_3_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STACKS_MINER_3_LIVENESS_SUCCESS\033[0m❌") if [[ $GET_STACKS_MINER_3_INFO_STATUS_CODE == "200" ]]; then - STX_MINER_3_LIVENESS_SUCCESS=true - STACKS_MINER_3_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STX_MINER_3_LIVENESS_SUCCESS\033[0m ✅") + STACKS_MINER_3_LIVENESS_SUCCESS=true + STACKS_MINER_3_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_MINER_3_LIVENESS_SUCCESS\033[0m ✅") fi -echo -e "\033[1mSTACKS_MINER_2_LIVENESS_SUCCESS\033[0m: $STACKS_MINER_2_LIVENESS_SUCCESS_FRMT" +echo -e "\033[1mSTACKS_MINER_3_LIVENESS_SUCCESS\033[0m: $STACKS_MINER_3_LIVENESS_SUCCESS_FRMT" echo -e "\n" ############################################################################################################################### echo -e " ---------------------------------------------------------------" From d2f59b76e87c988019d9a9816d01cd63159b5bd8 Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Thu, 28 May 2026 11:32:43 +0300 Subject: [PATCH 11/15] Shrink `hacknet-liveness.sh` output --- docker/tests/hacknet-liveness.sh | 303 +++++++++---------------------- 1 file changed, 86 insertions(+), 217 deletions(-) diff --git a/docker/tests/hacknet-liveness.sh b/docker/tests/hacknet-liveness.sh index 48de627..e4cced0 100755 --- a/docker/tests/hacknet-liveness.sh +++ b/docker/tests/hacknet-liveness.sh @@ -1,220 +1,89 @@ #!/bin/bash - -echo -e " -----------------------------------------------" -echo -e "| => (1) 🔬 TEST: [CHECK BITCOIN NODE IS LIVE] |" -echo -e " -----------------------------------------------" - -CHECK_BTC_LIVENESS_RESULT=$(curl -s -u "hacknet:hacknet" --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockcount", "params": []}' -H 'content-type: text/plain;' "http://localhost:18443/" | jq) - -echo -e "\nGET BLOCKCOUNT RPC:" -echo -e $CHECK_BTC_LIVENESS_RESULT | jq - -BTC_LIVENESS_SUCCESS=$(echo -e $CHECK_BTC_LIVENESS_RESULT | jq -r '.error == null') -BTC_LIVENESS_SUCCESS_FRMT=$([ "$BTC_LIVENESS_SUCCESS" == "true" ] && echo -e "\033[1;32mtrue\033[0m ✅" || echo -e "\033[1;31mfalse\033[0m❌") - - -echo -e "\033[1mBTC_LIVENESS_SUCCESS\033[0m: $BTC_LIVENESS_SUCCESS_FRMT" -echo -e "\n" -######################################################################################## -echo -e " ------------------------------------------------------" -echo -e "| => (2) 🔬 TEST: [CHECK IF BTC MINER IS ABLE TO MINE] |" -echo -e " ------------------------------------------------------" - -echo -e "\nMINE 1 BLOCK RPC:" -MINER_ADDRESS="mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH" -CHECK_IF_BTC_MINEABLE_RESULT=$(curl -s -u "hacknet:hacknet" --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "generatetoaddress", "params": [1, "'$MINER_ADDRESS'"]}' -H 'content-type: text/plain;' "http://localhost:18443/" | jq) - -echo -e $CHECK_IF_BTC_MINEABLE_RESULT | jq - -BTC_MINEABLE_SUCCESS=$(echo -e $CHECK_IF_BTC_MINEABLE_RESULT | jq -r '.error == null') -BTC_MINEABLE_SUCCESS_FRMT=$([ "$BTC_MINEABLE_SUCCESS" == "true" ] && echo -e "\033[1;32mtrue\033[0m ✅" || echo -e "\033[1;31mfalse\033[0m❌") - -echo -e "\033[1mBTC_MINEABLE_SUCCESS\033[0m: $BTC_MINEABLE_SUCCESS_FRMT" -echo -e "\n" -######################################################################################## -echo -e " -----------------------------------------------" -echo -e "| => (3) 🔬 TEST: [CHECK IF POSTGRES IS READY] |" -echo -e " -----------------------------------------------" -PG_READY_SUCCESS=false -PG_READY_SUCCESS_FRMT=$(echo -e "\033[1;31m$PG_READY_SUCCESS\033[0m❌") -if (docker exec postgres pg_isready); then - PG_READY_SUCCESS=true - PG_READY_SUCCESS_FRMT=$(echo -e "\033[1;32m$PG_READY_SUCCESS\033[0m ✅") -fi - -echo -e "\033[1mPG_READY_SUCCESS\033[0m: $PG_READY_SUCCESS_FRMT" -echo -e "\n" - -NAKAMOTO_SIGNER_DOCKER_LOGS=$(docker logs stacks-signer-1 2>/dev/null) - -NAKAMOTO_SIGNER_READY_SUCCESS=false -NAKAMOTO_SIGNER_READY_SUCCESS_FRMT=$(echo -e "\033[1;31m$NAKAMOTO_SIGNER_READY_SUCCESS\033[0m❌") -if [[ $NAKAMOTO_SIGNER_DOCKER_LOGS == *"Signer spawned successfully"* ]]; then - NAKAMOTO_SIGNER_READY_SUCCESS=true - echo -e "Nakamoto Signer || Signer spawned successfully" - NAKAMOTO_SIGNER_READY_SUCCESS_FRMT=$(echo -e "\033[1;32m$NAKAMOTO_SIGNER_READY_SUCCESS\033[0m ✅") -fi - -echo -e "\033[1mNAKAMOTO_SIGNER_READY_SUCCESS\033[0m: $NAKAMOTO_SIGNER_READY_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e " --------------------------------------------------" -echo -e "| => (6) 🔬 TEST: [CHECK IF STACKS MINER 1 IS READY] |" -echo -e " --------------------------------------------------" -STX_MINER_1_PORT=20443 -GET_STACKS_MINER_1_INFO_STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null "http://localhost:${STX_MINER_1_PORT}/v2/info") - -echo -e "\nGET STACKS MINER 1 STATUS: $GET_STACKS_MINER_1_INFO_STATUS_CODE" - -STACKS_MINER_1_LIVENESS_SUCCESS=false -STACKS_MINER_1_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STACKS_MINER_1_LIVENESS_SUCCESS\033[0m❌") - -if [[ $GET_STACKS_MINER_1_INFO_STATUS_CODE == "200" ]]; then - STACKS_MINER_1_LIVENESS_SUCCESS=true - STACKS_MINER_1_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_MINER_1_LIVENESS_SUCCESS\033[0m ✅") -fi - - -echo -e "\033[1mSTACKS_MINER_1_LIVENESS_SUCCESS\033[0m: $STACKS_MINER_1_LIVENESS_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e " --------------------------------------------------" -echo -e "| => (6) 🔬 TEST: [CHECK IF STACKS MINER 2 IS READY] |" -echo -e " --------------------------------------------------" -STX_MINER_2_PORT=21443 -GET_STACKS_MINER_2_INFO_STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null "http://localhost:${STX_MINER_2_PORT}/v2/info") - -echo -e "\nGET STACKS MINER 2 STATUS: $GET_STACKS_MINER_2_INFO_STATUS_CODE" - -STACKS_MINER_2_LIVENESS_SUCCESS=false -STACKS_MINER_2_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STACKS_MINER_2_LIVENESS_SUCCESS\033[0m❌") - -if [[ $GET_STACKS_MINER_2_INFO_STATUS_CODE == "200" ]]; then - STACKS_MINER_2_LIVENESS_SUCCESS=true - STACKS_MINER_2_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_MINER_2_LIVENESS_SUCCESS\033[0m ✅") -fi - - -echo -e "\033[1mSTACKS_MINER_2_LIVENESS_SUCCESS\033[0m: $STACKS_MINER_2_LIVENESS_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e " --------------------------------------------------" -echo -e "| => (6) 🔬 TEST: [CHECK IF STACKS MINER 3 IS READY] |" -echo -e " --------------------------------------------------" -STX_MINER_3_PORT=22443 -GET_STACKS_MINER_3_INFO_STATUS_CODE=$(curl --write-out %{http_code} --silent --output /dev/null "http://localhost:${STX_MINER_3_PORT}/v2/info") - -echo -e "\nGET STACKS MINER 3 STATUS: $GET_STACKS_MINER_3_INFO_STATUS_CODE" - -STACKS_MINER_3_LIVENESS_SUCCESS=false -STACKS_MINER_3_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31m$STACKS_MINER_3_LIVENESS_SUCCESS\033[0m❌") - -if [[ $GET_STACKS_MINER_3_INFO_STATUS_CODE == "200" ]]; then - STACKS_MINER_3_LIVENESS_SUCCESS=true - STACKS_MINER_3_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_MINER_3_LIVENESS_SUCCESS\033[0m ✅") -fi - - -echo -e "\033[1mSTACKS_MINER_3_LIVENESS_SUCCESS\033[0m: $STACKS_MINER_3_LIVENESS_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e " ---------------------------------------------------------------" -echo -e "| => (7) 🔬 TEST: [CHECK IF STX NODE IS SYNCED WITH BTC UTXOs] |" -echo -e " ---------------------------------------------------------------" - -## (RPC APPROACH) -GET_STACKS_NODE_INFO=$(curl -s "http://localhost:20443/v2/info") - -echo -e "\nGET STACKS NODE INFO:" -echo -e $GET_STACKS_NODE_INFO | jq 'del(.stackerdbs)' -echo -e "\t\t.\n\t\t.\n \033[1;32m<<\033[0m \033[1;35mLong Output Supressed\033[0m \033[1;32m>>\033[0m \n\t\t.\n\t\t." - -STX_SYNC_WITH_BTC_UTXO_SUCCESS=$(echo -e $GET_STACKS_NODE_INFO | jq -r '.stacks_tip_height != 0') -STX_SYNC_WITH_BTC_UTXO_SUCCESS_FRMT=$([ "$STX_SYNC_WITH_BTC_UTXO_SUCCESS" == "true" ] && echo -e "\033[1;32mtrue\033[0m ✅" || echo -e "\033[1;31mfalse\033[0m❌") - -echo -e "\033[1mSTX_SYNC_WITH_BTC_UTXO_SUCCESS\033[0m: $STX_SYNC_WITH_BTC_UTXO_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e " ---------------------------------------------------------------" -echo -e "| => (8) 🔬 TEST: [CHECK STACKS API EVENT OBSERVER LIVENESS] |" -echo -e " ---------------------------------------------------------------" - -GET_STACKS_API_EVENT_OBSERVER_PING=$(curl -s "http://localhost:3700") - -echo -e "\nGET STACKS API EVENT OBSERVER PING:" -echo -e $GET_STACKS_API_EVENT_OBSERVER_PING | jq - -STACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS=$(echo -e $GET_STACKS_API_EVENT_OBSERVER_PING | jq -r '.status == "ready"') -STACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS_FRMT=$([ "$STACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS" == "true" ] && echo -e "\033[1;32mtrue\033[0m ✅" || echo -e "\033[1;31mfalse\033[0m❌") - -echo -e "\033[1mSTACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS\033[0m: $STACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e " ---------------------------------------------------------------" -echo -e "| => (9) 🔬 TEST: [CHECK STACKS PUBLIC API LIVENESS] |" -echo -e " ---------------------------------------------------------------" - -GET_STACKS_PUBLIC_API_PING=$(curl -s --write-out %{http_code} --silent --output /dev/null "http://localhost:3999/extended/") - -echo -e "\nGET STACKS PUBLIC API PING:" -echo -e $GET_STACKS_PUBLIC_API_PING | jq - -STACKS_PUBLIC_API_LIVENESS_SUCCESS=false -STACKS_PUBLIC_API_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;31mfalse\033[0m❌") - -if [[ $GET_STACKS_PUBLIC_API_PING == "200" ]]; then - STACKS_PUBLIC_API_LIVENESS_SUCCESS=true - STACKS_PUBLIC_API_LIVENESS_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_PUBLIC_API_LIVENESS_SUCCESS\033[0m ✅") -fi - -echo -e "\033[1mSTACKS_PUBLIC_API_LIVENESS_SUCCESS\033[0m: $STACKS_PUBLIC_API_LIVENESS_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e " -----------------------------------------------------------------" -echo -e "| => (10) 🔬 TEST: [CHECK IF STACKS-API IS CONNECTED TO POSTGRES] |" -echo -e " -----------------------------------------------------------------" - -STACKS_API_DOCKER_LOGS=$(docker logs stacks-api 2>/dev/null) - -STACKS_API_CONNECTED_TO_PG_SUCCESS=false -STACKS_API_CONNECTED_TO_PG_SUCCESS_FRMT=$(echo -e "\033[1;31m$STACKS_API_CONNECTED_TO_PG_SUCCESS\033[0m❌") -if [[ $STACKS_API_DOCKER_LOGS == *"PgNotifier connected"* ]]; then - STACKS_API_CONNECTED_TO_PG_SUCCESS=true - echo -e "Stacks-API || PgNotifier connected" - STACKS_API_CONNECTED_TO_PG_SUCCESS_FRMT=$(echo -e "\033[1;32m$STACKS_API_CONNECTED_TO_PG_SUCCESS\033[0m ✅") -fi - -echo -e "\033[1mSTACKS_API_CONNECTED_TO_PG_SUCCESS\033[0m: $STACKS_API_CONNECTED_TO_PG_SUCCESS_FRMT" -echo -e "\n" -############################################################################################################################### -echo -e "-----------------------------------------------------------------" -echo -e "| SUMMARY |" -echo -e "-----------------------------------------------------------------" -echo -e "| \033[1mBTC_LIVENESS_SUCCESS\033[0m: | \t $BTC_LIVENESS_SUCCESS_FRMT |" -echo -e "| \033[1mBTC_MINEABLE_SUCCESS\033[0m: | \t $BTC_MINEABLE_SUCCESS_FRMT |" -echo -e "| \033[1mPG_READY_SUCCESS\033[0m: | \t $PG_READY_SUCCESS_FRMT |" -echo -e "| \033[1mNAKAMOTO_SIGNER_READY_SUCCESS\033[0m: | \t $NAKAMOTO_SIGNER_READY_SUCCESS_FRMT |" -echo -e "| \033[1mSTACKS_MINER_1_LIVENESS_SUCCESS\033[0m: | \t $STACKS_MINER_1_LIVENESS_SUCCESS_FRMT |" -echo -e "| \033[1mSTACKS_MINER_2_LIVENESS_SUCCESS\033[0m: | \t $STACKS_MINER_2_LIVENESS_SUCCESS_FRMT |" -echo -e "| \033[1mSTACKS_MINER_3_LIVENESS_SUCCESS\033[0m: | \t $STACKS_MINER_3_LIVENESS_SUCCESS_FRMT |" -echo -e "| \033[1mSTX_SYNC_WITH_BTC_UTXO_SUCCESS\033[0m: | \t $STX_SYNC_WITH_BTC_UTXO_SUCCESS_FRMT |" -echo -e "| \033[1mSTACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS\033[0m: | \t $STACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS_FRMT |" -echo -e "| \033[1mSTACKS_PUBLIC_API_LIVENESS_SUCCESS\033[0m: | \t $STACKS_PUBLIC_API_LIVENESS_SUCCESS_FRMT |" -echo -e "| \033[1mSTACKS_API_CONNECTED_TO_PG_SUCCESS\033[0m: | \t $STACKS_API_CONNECTED_TO_PG_SUCCESS_FRMT |" -echo -e "-----------------------------------------------------------------" - -if [[ $BTC_LIVENESS_SUCCESS == true \ - && $BTC_MINEABLE_SUCCESS == true \ - && $PG_READY_SUCCESS == true \ - && $NAKAMOTO_SIGNER_READY_SUCCESS == true \ - && $STACKS_MINER_1_LIVENESS_SUCCESS == true \ - && $STACKS_MINER_2_LIVENESS_SUCCESS == true \ - && $STACKS_MINER_3_LIVENESS_SUCCESS == true \ - && $STX_SYNC_WITH_BTC_UTXO_SUCCESS == true \ - && $STACKS_API_EVENT_OBSERVER_LIVENESS_SUCCESS == true \ - && $STACKS_PUBLIC_API_LIVENESS_SUCCESS == true \ - && $STACKS_API_CONNECTED_TO_PG_SUCCESS == true ]]; then +# Hacknet liveness checks. One line per check. +# Set DEBUG=1 to see raw responses/log excerpts on success too. +set -uo pipefail + +DEBUG="${DEBUG:-0}" +FAILED=0 +TOTAL=0 + +G='\033[1;32m'; R='\033[1;31m'; D='\033[0;90m'; N='\033[0m' + +# check NAME PASS_BOOL [CONTEXT] +# PASS_BOOL: "1"/"true" = pass, anything else = fail +# CONTEXT: optional detail (printed on fail, or on pass when DEBUG=1) +check() { + local name="$1" ok="$2" ctx="${3:-}" + TOTAL=$((TOTAL + 1)) + if [ "$ok" = "1" ] || [ "$ok" = "true" ]; then + echo -e "${G}✓${N} $name" + [ "$DEBUG" = "1" ] && [ -n "$ctx" ] && echo -e "${D} $ctx${N}" + else + FAILED=$((FAILED + 1)) + echo -e "${R}✗${N} $name" + [ -n "$ctx" ] && echo -e "${D} $ctx${N}" | head -20 + fi +} + +# 1. Bitcoin RPC live +btc_resp=$(curl -sf -u "hacknet:hacknet" --data-binary '{"jsonrpc":"1.0","method":"getblockcount","params":[]}' -H 'content-type: text/plain;' "http://localhost:18443/" 2>&1 || true) +btc_height=$(echo "$btc_resp" | jq -r '.result // empty' 2>/dev/null) +[ -n "$btc_height" ] && check "bitcoin RPC live (height=$btc_height)" 1 "$btc_resp" || check "bitcoin RPC live" 0 "$btc_resp" + +# 2. Bitcoin mineable +mine_resp=$(curl -sf -u "hacknet:hacknet" --data-binary '{"jsonrpc":"1.0","method":"generatetoaddress","params":[1,"mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH"]}' -H 'content-type: text/plain;' "http://localhost:18443/" 2>&1 || true) +echo "$mine_resp" | jq -e '.error == null' >/dev/null 2>&1 +check "bitcoin mineable" $? "$mine_resp" + +# 3. Postgres ready +pg_out=$(docker exec postgres pg_isready 2>&1 || true) +echo "$pg_out" | grep -q "accepting connections" +check "postgres ready" $? "$pg_out" + +# 4. Nakamoto signer spawned +signer_logs=$(docker logs stacks-signer-1 2>/dev/null | grep -F "Signer spawned successfully" | tail -1) +[ -n "$signer_logs" ] +check "signer spawned" $? "(no 'Signer spawned successfully' in stacks-signer-1 logs)" + +# 5–7. Stacks miners /v2/info +for i in 1 2 3; do + port=$((19443 + i * 1000)) + body=$(curl -sf -m 5 "http://localhost:${port}/v2/info" 2>&1 || true) + if [ -n "$body" ] && echo "$body" | jq -e '.stacks_tip_height' >/dev/null 2>&1; then + h=$(echo "$body" | jq -r '.stacks_tip_height') + check "stacks miner $i live (tip=$h)" 1 "$body" + else + check "stacks miner $i live" 0 "$body" + fi +done + +# 8. Stacks tip > 0 (chain progressing past genesis) +info=$(curl -sf -m 5 "http://localhost:20443/v2/info" 2>&1 || true) +tip=$(echo "$info" | jq -r '.stacks_tip_height // 0' 2>/dev/null) +[ "${tip:-0}" -gt 0 ] 2>/dev/null +check "stacks tip > 0 (tip=$tip)" $? "$info" + +# 9. Stacks API event observer +evt=$(curl -sf -m 5 "http://localhost:3700" 2>&1 || true) +echo "$evt" | jq -e '.status == "ready"' >/dev/null 2>&1 +check "stacks-api event observer ready" $? "$evt" + +# 10. Stacks public API +api_code=$(curl -s -o /dev/null -m 5 -w '%{http_code}' "http://localhost:3999/extended/" || echo "000") +[ "$api_code" = "200" ] +check "stacks-api public endpoint (HTTP $api_code)" $? "expected 200, got $api_code" + +# 11. Stacks-API connected to postgres +api_pg=$(docker logs stacks-api 2>/dev/null | grep -F "PgNotifier connected" | tail -1) +[ -n "$api_pg" ] +check "stacks-api connected to postgres" $? "(no 'PgNotifier connected' in stacks-api logs)" + +# Summary +echo +if [ $FAILED -eq 0 ]; then + echo -e "${G}all $TOTAL checks passed${N}" exit 0 +else + echo -e "${R}$FAILED/$TOTAL checks failed${N}" + exit 1 fi - -exit 1 From cab2e7e0664e12d8588212f2f6636fa15cd8b71f Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Thu, 28 May 2026 11:36:43 +0300 Subject: [PATCH 12/15] Pin latest actions/checkout v6.0.2 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a5f200..bdd9838 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install dependencies (Linux) if: runner.os == 'Linux' From 2114df79da85008816905ad9ebe6eed00bad1c00 Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Thu, 28 May 2026 11:38:12 +0300 Subject: [PATCH 13/15] Use ref in comment --- docker/stacks/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/stacks/Dockerfile b/docker/stacks/Dockerfile index 791e96f..4d867fb 100644 --- a/docker/stacks/Dockerfile +++ b/docker/stacks/Dockerfile @@ -1,7 +1,7 @@ FROM rust:bookworm AS builder # This will be overridden by the value from docker-compose -# Supports: branch name, tag, or commit SHA +# Supports any git ref # Examples: # STACKS_CORE_BASE_BRANCH=develop (branch) # STACKS_CORE_BASE_BRANCH=3.3.0.0.1 (tag) From ba369e7991383372622e56cab5cd2828b2d035ad Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Thu, 28 May 2026 11:40:26 +0300 Subject: [PATCH 14/15] Revert "Pin latest actions/checkout v6.0.2" This reverts commit cab2e7e0664e12d8588212f2f6636fa15cd8b71f. Switching back to repo-level whitelisted action commit SHA. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdd9838..8a5f200 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install dependencies (Linux) if: runner.os == 'Linux' From 2572c6fbd35dbe5e5ed56924a05b4e25ff525d24 Mon Sep 17 00:00:00 2001 From: radu-stacks Date: Thu, 28 May 2026 12:17:53 +0300 Subject: [PATCH 15/15] Fix `hacknet-liveness.sh` checks --- docker/tests/hacknet-liveness.sh | 92 +++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/docker/tests/hacknet-liveness.sh b/docker/tests/hacknet-liveness.sh index e4cced0..3938d20 100755 --- a/docker/tests/hacknet-liveness.sh +++ b/docker/tests/hacknet-liveness.sh @@ -1,7 +1,7 @@ #!/bin/bash # Hacknet liveness checks. One line per check. -# Set DEBUG=1 to see raw responses/log excerpts on success too. -set -uo pipefail +# Set DEBUG=1 to see context on success too. +set -u DEBUG="${DEBUG:-0}" FAILED=0 @@ -9,41 +9,59 @@ TOTAL=0 G='\033[1;32m'; R='\033[1;31m'; D='\033[0;90m'; N='\033[0m' -# check NAME PASS_BOOL [CONTEXT] -# PASS_BOOL: "1"/"true" = pass, anything else = fail -# CONTEXT: optional detail (printed on fail, or on pass when DEBUG=1) +# check NAME RC [CONTEXT] +# RC: 0 = pass, non-zero = fail (shell convention) +# CONTEXT: detail printed on fail (or on pass when DEBUG=1) check() { - local name="$1" ok="$2" ctx="${3:-}" + local name="$1" rc="$2" ctx="${3-}" TOTAL=$((TOTAL + 1)) - if [ "$ok" = "1" ] || [ "$ok" = "true" ]; then + if [ "$rc" -eq 0 ]; then echo -e "${G}✓${N} $name" - [ "$DEBUG" = "1" ] && [ -n "$ctx" ] && echo -e "${D} $ctx${N}" + if [ "$DEBUG" = "1" ] && [ -n "$ctx" ]; then + echo "$ctx" | sed "s/^/ /" + fi else FAILED=$((FAILED + 1)) echo -e "${R}✗${N} $name" - [ -n "$ctx" ] && echo -e "${D} $ctx${N}" | head -20 + if [ -n "$ctx" ]; then + echo "$ctx" | head -20 | sed "s/^/ /" + fi fi + return 0 } # 1. Bitcoin RPC live btc_resp=$(curl -sf -u "hacknet:hacknet" --data-binary '{"jsonrpc":"1.0","method":"getblockcount","params":[]}' -H 'content-type: text/plain;' "http://localhost:18443/" 2>&1 || true) btc_height=$(echo "$btc_resp" | jq -r '.result // empty' 2>/dev/null) -[ -n "$btc_height" ] && check "bitcoin RPC live (height=$btc_height)" 1 "$btc_resp" || check "bitcoin RPC live" 0 "$btc_resp" +if [ -n "$btc_height" ]; then + check "bitcoin RPC live (height=$btc_height)" 0 "$btc_resp" +else + check "bitcoin RPC live" 1 "$btc_resp" +fi # 2. Bitcoin mineable mine_resp=$(curl -sf -u "hacknet:hacknet" --data-binary '{"jsonrpc":"1.0","method":"generatetoaddress","params":[1,"mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH"]}' -H 'content-type: text/plain;' "http://localhost:18443/" 2>&1 || true) -echo "$mine_resp" | jq -e '.error == null' >/dev/null 2>&1 -check "bitcoin mineable" $? "$mine_resp" +if echo "$mine_resp" | jq -e '.error == null' >/dev/null 2>&1; then + check "bitcoin mineable" 0 "$mine_resp" +else + check "bitcoin mineable" 1 "$mine_resp" +fi # 3. Postgres ready pg_out=$(docker exec postgres pg_isready 2>&1 || true) -echo "$pg_out" | grep -q "accepting connections" -check "postgres ready" $? "$pg_out" +if echo "$pg_out" | grep -q "accepting connections"; then + check "postgres ready" 0 "$pg_out" +else + check "postgres ready" 1 "$pg_out" +fi # 4. Nakamoto signer spawned -signer_logs=$(docker logs stacks-signer-1 2>/dev/null | grep -F "Signer spawned successfully" | tail -1) -[ -n "$signer_logs" ] -check "signer spawned" $? "(no 'Signer spawned successfully' in stacks-signer-1 logs)" +signer_logs=$(docker logs stacks-signer-1 2>/dev/null || true) +if echo "$signer_logs" | grep -qF "Signer spawned successfully"; then + check "signer spawned" 0 +else + check "signer spawned" 1 "(no 'Signer spawned successfully' in stacks-signer-1 logs)" +fi # 5–7. Stacks miners /v2/info for i in 1 2 3; do @@ -51,36 +69,48 @@ for i in 1 2 3; do body=$(curl -sf -m 5 "http://localhost:${port}/v2/info" 2>&1 || true) if [ -n "$body" ] && echo "$body" | jq -e '.stacks_tip_height' >/dev/null 2>&1; then h=$(echo "$body" | jq -r '.stacks_tip_height') - check "stacks miner $i live (tip=$h)" 1 "$body" + check "stacks miner $i live (tip=$h)" 0 "$body" else - check "stacks miner $i live" 0 "$body" + check "stacks miner $i live" 1 "$body" fi done -# 8. Stacks tip > 0 (chain progressing past genesis) +# 8. Stacks tip > 0 info=$(curl -sf -m 5 "http://localhost:20443/v2/info" 2>&1 || true) tip=$(echo "$info" | jq -r '.stacks_tip_height // 0' 2>/dev/null) -[ "${tip:-0}" -gt 0 ] 2>/dev/null -check "stacks tip > 0 (tip=$tip)" $? "$info" +tip="${tip:-0}" +if [ "$tip" -gt 0 ] 2>/dev/null; then + check "stacks tip > 0 (tip=$tip)" 0 "$info" +else + check "stacks tip > 0" 1 "$info" +fi # 9. Stacks API event observer evt=$(curl -sf -m 5 "http://localhost:3700" 2>&1 || true) -echo "$evt" | jq -e '.status == "ready"' >/dev/null 2>&1 -check "stacks-api event observer ready" $? "$evt" +if echo "$evt" | jq -e '.status == "ready"' >/dev/null 2>&1; then + check "stacks-api event observer ready" 0 "$evt" +else + check "stacks-api event observer ready" 1 "$evt" +fi # 10. Stacks public API -api_code=$(curl -s -o /dev/null -m 5 -w '%{http_code}' "http://localhost:3999/extended/" || echo "000") -[ "$api_code" = "200" ] -check "stacks-api public endpoint (HTTP $api_code)" $? "expected 200, got $api_code" +if curl -sf -m 5 -o /dev/null "http://localhost:3999/extended/"; then + check "stacks-api public endpoint" 0 +else + check "stacks-api public endpoint" 1 "GET http://localhost:3999/extended/ did not return 2xx" +fi # 11. Stacks-API connected to postgres -api_pg=$(docker logs stacks-api 2>/dev/null | grep -F "PgNotifier connected" | tail -1) -[ -n "$api_pg" ] -check "stacks-api connected to postgres" $? "(no 'PgNotifier connected' in stacks-api logs)" +api_logs=$(docker logs stacks-api 2>/dev/null || true) +if echo "$api_logs" | grep -qF "PgNotifier connected"; then + check "stacks-api connected to postgres" 0 +else + check "stacks-api connected to postgres" 1 "(no 'PgNotifier connected' in stacks-api logs)" +fi # Summary echo -if [ $FAILED -eq 0 ]; then +if [ "$FAILED" -eq 0 ]; then echo -e "${G}all $TOTAL checks passed${N}" exit 0 else