Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions pithead
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,33 @@ resolve_pull_policy() {
else printf 'missing'; fi
}

# #795: `compose up --remove-orphans` never removes the container of a service whose profile just
# went inactive — the service is still in the compose file, so compose does not count it as an
# orphan and leaves it running (a tari local→remote switch left the old minotari_node up, offline
# and re-syncing, against a remote-mode config). Reconcile from the committed .env instead: every
# profile-gated service whose profile token is absent gets its container stopped and removed.
# `compose rm` resolves an explicitly named service whatever the active profiles, and exits 0 when
# no container exists, so running this before every up is a cheap no-op in the steady state and
# also heals a box already stuck with a stale node container.
remove_deactivated_profile_containers() {
local profiles gone=()
profiles=",$(env_get COMPOSE_PROFILES),"
[[ "$profiles" == *,local_node,* ]] || gone+=(monerod)
[[ "$profiles" == *,local_tari,* ]] || gone+=(tari)
[[ "$profiles" == *,payout_confirm,* ]] || gone+=(wallet-rpc)
[[ "$profiles" == *,tari_payout_confirm,* ]] || gone+=(tari-wallet)
[ "${#gone[@]}" -eq 0 ] && return 0
docker compose rm -sf "${gone[@]}" >/dev/null 2>&1 ||
warn "Could not remove the deactivated container(s): ${gone[*]} — remove them manually with 'docker rm -f ${gone[*]}'."
}

# Run `docker compose up` with live output; on failure, explain a bridge-subnet collision (#180) if
# that's what Docker rejected. Returns compose's own exit code.
compose_up_checked() {
local tmp out rc
# Deactivated-profile containers go BEFORE the up (#795): the old local node must stop before
# p2pool (re)starts against the remote one, not linger beside it.
remove_deactivated_profile_containers
tmp="$(mktemp)"
docker compose up --pull "$(resolve_pull_policy)" "$@" 2>&1 | tee "$tmp"
rc=${PIPESTATUS[0]}
Expand Down Expand Up @@ -5224,8 +5247,9 @@ apply() {
# commit above (never before the operator said yes) and under the marker, so a failed move is
# retried; the recreate below then mounts the migrated directory.
migrate_dashboard_data
# Compose recreates only the services whose resolved config changed; --remove-orphans
# drops monerod when a local→remote switch deactivates the local_node profile.
# Compose recreates only the services whose resolved config changed. --remove-orphans covers
# services that left the compose file entirely; a profile-deactivated service is NOT an orphan
# to compose, so compose_up_checked removes those containers itself before the up (#795).
if ! compose_up_checked -d --remove-orphans; then
warn "Config files were updated but containers were NOT recreated ('docker compose up' failed)."
warn "Fix the cause shown above, then re-run '$0 apply' (it will retry the recreate) — or '$0 up'."
Expand Down
33 changes: 33 additions & 0 deletions tests/stack/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3384,6 +3384,39 @@ out="$(cd "$V" && PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
assert_rc "invalid tari.mode rejected" "$?" "1"
assert_contains "invalid tari.mode message" "$out" "tari.mode must be"

echo "== black-box: profile deactivation removes the old container (#795) =="
# `compose up --remove-orphans` does not remove a profile-deactivated service's container — the
# service is still in the compose file, so compose does not count it as an orphan — and a tari
# local→remote switch left the old node running (offline, re-syncing) against a remote-mode
# config. Apply must issue an explicit `compose rm -sf` for every profile-gated service whose
# profile is off, BEFORE the recreate up. Asserted through the docker-stub call log.
# (1) Baseline, both nodes local: the reconcile list is exactly the (off) payout-confirm wallets —
# neither node container is touched.
seed_env
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json"
SWLOG="$V/docker-switch.log"
: >"$SWLOG"
out="$(cd "$V" && DOCKER_LOG="$SWLOG" PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
assert_rc "baseline local-nodes apply exits 0" "$?" "0"
assert_contains "local nodes: only the off payout wallets reconcile" "$(cat "$SWLOG")" "compose rm -sf wallet-rpc tari-wallet"
# (2) tari local→remote (the #795 reproduction): the tari container joins the removal list. No
# re-seed — the committed local-mode .env from (1) makes this a real transition.
printf '{ "monero": {"mode":"local","wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T","mode":"remote","remote":{"host":"tari.example.com"}}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json"
: >"$SWLOG"
out="$(cd "$V" && DOCKER_LOG="$SWLOG" PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
assert_rc "tari local-to-remote switch applies cleanly" "$?" "0"
assert_contains "switch removes the deactivated tari container" "$(cat "$SWLOG")" "compose rm -sf tari wallet-rpc tari-wallet"
# The removal must precede the recreate up — the point is the old node never runs beside the
# remote-mode p2pool, not that it eventually disappears.
assert_contains "tari removal happens before the recreate up" "$(sed '/compose up --pull never -d --remove-orphans/q' "$SWLOG")" "compose rm -sf tari wallet-rpc tari-wallet"
# (3) monerod rides the same guard on a monero local→remote switch.
seed_env
printf '{ "monero": {"mode":"remote","remote":{"host":"node.example"},"wallet_address":"%s","node_username":"u","node_password":"p"}, "tari":{"wallet_address":"T"}, "p2pool":{"pool":"main"}, "dashboard":{"secure":true,"host":"box.lan"} }\n' "$WALLET" >"$V/config.json"
: >"$SWLOG"
out="$(cd "$V" && DOCKER_LOG="$SWLOG" PATH="$V/bin:$PATH" ./pithead apply -y 2>&1)"
assert_rc "monero local-to-remote switch applies cleanly" "$?" "0"
assert_contains "switch removes the deactivated monerod container" "$(cat "$SWLOG")" "compose rm -sf monerod wallet-rpc tari-wallet"

echo "== black-box: monero.rpc_lan_access + prep_blocks_threads reflect into .env (#523) =="
# The rendered .env must match the config input. rpc_lan_access gates the monerod RPC bind: default
# (unset) keeps it localhost-only; true opens it to the LAN. prep_blocks_threads overrides the
Expand Down