From 3abc8ca220a65654dca5fe6c1ec4f35a4ce0b570 Mon Sep 17 00:00:00 2001 From: Johnny Murray Date: Sun, 6 Sep 2026 10:51:39 +0100 Subject: [PATCH 1/2] Fix AxeBC2 Umbrel host metadata dependency --- .../workflows/validate-axebc2-core31-dev.yml | 4 + scripts/build-axebc2-umbrel.py | 78 ++++++ scripts/validate-axebc2-core31-dev.py | 5 +- tests/test_axebc2_umbrel.py | 141 +++++++++++ willitmod-dev-bc2/UMBREL-COMPATIBILITY.md | 60 +++++ willitmod-dev-bc2/docker-compose.yml.template | 135 ++++++++++ willitmod-dev-bc2/hooks/umbrel-ckpool | 15 ++ willitmod-dev-bc2/hooks/umbrel-init | 235 ++++++++++++++++++ willitmod-dev-bc2/umbrel-app.yml | 12 +- 9 files changed, 680 insertions(+), 5 deletions(-) create mode 100644 scripts/build-axebc2-umbrel.py create mode 100644 tests/test_axebc2_umbrel.py create mode 100644 willitmod-dev-bc2/UMBREL-COMPATIBILITY.md create mode 100644 willitmod-dev-bc2/docker-compose.yml.template create mode 100644 willitmod-dev-bc2/hooks/umbrel-ckpool create mode 100644 willitmod-dev-bc2/hooks/umbrel-init diff --git a/.github/workflows/validate-axebc2-core31-dev.yml b/.github/workflows/validate-axebc2-core31-dev.yml index 17c2b60..da7eade 100644 --- a/.github/workflows/validate-axebc2-core31-dev.yml +++ b/.github/workflows/validate-axebc2-core31-dev.yml @@ -8,6 +8,8 @@ on: - "tests/test_axebc2_platform_integration.py" - "tests/test_axebc2_dev_finalizer.py" - "tests/test_axebc2_release_state.py" + - "tests/test_axebc2_umbrel.py" + - "scripts/build-axebc2-umbrel.py" - "tests/fixtures/5tratumos_contract_4f979cb.py" - "scripts/validate-axebc2-core31-dev.py" - "scripts/axebc2_release_state.py" @@ -21,6 +23,8 @@ on: - "tests/test_axebc2_platform_integration.py" - "tests/test_axebc2_dev_finalizer.py" - "tests/test_axebc2_release_state.py" + - "tests/test_axebc2_umbrel.py" + - "scripts/build-axebc2-umbrel.py" - "tests/fixtures/5tratumos_contract_4f979cb.py" - "scripts/validate-axebc2-core31-dev.py" - "scripts/axebc2_release_state.py" diff --git a/scripts/build-axebc2-umbrel.py b/scripts/build-axebc2-umbrel.py new file mode 100644 index 0000000..6ebca95 --- /dev/null +++ b/scripts/build-axebc2-umbrel.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Generate Umbrel's native template without changing the accepted OS recipe. + +Umbrel renders top-level *.template files and updates hooks on app upgrades. +5tratumOS consumes docker-compose.yml directly and preserves seeded data. +Keep the accepted initializer as the common source for migration/config logic. +""" +import argparse +from pathlib import Path +import yaml + +ROOT = Path(__file__).resolve().parents[1] +APP = ROOT / "willitmod-dev-bc2" + + +def artifacts(): + compose = yaml.safe_load((APP / "docker-compose.yml").read_text()) + init = (APP / "data/init/init.sh").read_text() + start = init.index("# This check deliberately precedes every write") + end = init.index("\natomic_json_write()", start) + # This entrypoint is selected only by Umbrel's native template path. The + # default 5tratumOS initializer and its OS version check remain untouched. + init = init[:start] + ( + '# Umbrel owns the app lifecycle; the Core 31 migration checks below\n' + '# are app-owned and remain mandatory on this platform.\n' + '[ "${AXEBC2_PLATFORM:-}" = "umbrel" ] || fail "Umbrel entrypoint requires the Umbrel recipe"\n' + ) + init[end:] + init = init.replace( + "# Normal initialization starts only after the OS floor and migration policy exist.", + "# Normal initialization starts only after the migration policy exists.", + ) + init = init.replace( + 'build_file="${AXEBC2_BUILD_FILE:-/etc/5tratumos/build.json}"\n', "" + ) + generated = "# Generated by scripts/build-axebc2-umbrel.py; do not edit.\n" + init = init.replace("#!/bin/sh\n", "#!/bin/sh\n" + generated, 1) + services = compose["services"] + svc = services["init"] + svc["volumes"] = [v for v in svc["volumes"] if v["target"] != "/etc/5tratumos/build.json"] + for volume in svc["volumes"]: + if volume["target"] == "/opt/axebc2/init.sh": + volume["source"] = "${APP_DATA_DIR}/hooks/umbrel-init" + svc["environment"]["AXEBC2_PLATFORM"] = "umbrel" + svc["command"] = ["/bin/sh", "/opt/axebc2/init.sh"] + # No shell program is embedded in an envsubst template: unexported local + # variables would otherwise disappear before Docker sees the command. + pool = services["ckpool"] + pool_script = "#!/bin/sh\n" + generated + pool["entrypoint"][-1].replace("$$ARGS", "$ARGS") + "\n" + pool["entrypoint"] = ["/bin/sh", "/opt/axebc2/ckpool-entrypoint.sh"] + pool["volumes"].append({ + "type": "bind", "source": "${APP_DATA_DIR}/hooks/umbrel-ckpool", + "target": "/opt/axebc2/ckpool-entrypoint.sh", "read_only": True, + "bind": {"create_host_path": False}, + }) + return { + APP / "docker-compose.yml.template": generated + yaml.safe_dump(compose, sort_keys=False), + APP / "hooks/umbrel-init": init, + APP / "hooks/umbrel-ckpool": pool_script, + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + for path, text in artifacts().items(): + if args.check: + if not path.is_file() or path.read_text() != text: + raise SystemExit(f"Outdated generated Umbrel artifact: {path.relative_to(ROOT)}") + else: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text) + # These are container entrypoints, not executable host hooks. + path.chmod(0o644) + + +if __name__ == "__main__": + main() diff --git a/scripts/validate-axebc2-core31-dev.py b/scripts/validate-axebc2-core31-dev.py index 969d617..2064940 100644 --- a/scripts/validate-axebc2-core31-dev.py +++ b/scripts/validate-axebc2-core31-dev.py @@ -53,7 +53,8 @@ def require(condition, message): "DEV Compose content differs from the recipe accepted on 10.10.10.235", ) -require('version: "0.1.11-dev"' in manifest, "manifest must be 0.1.11-dev") +# Package revision; the unchanged runtime evidence below remains 0.1.11-dev. +require('version: "0.1.12-dev"' in manifest, "manifest must be 0.1.12-dev") require(evidence.get("app_version") == "0.1.11-dev", "evidence must name the 0.1.11 DEV app version") require( evidence.get("app_image") @@ -78,7 +79,7 @@ def require(condition, message): and evidence.get("core_candidate_run") == 33675068951, "evidence must retain the accepted Core 31 tag, digest, source revision, and candidate run", ) -require("Requires 5tratumOS 0.7.12" in manifest, "OS prerequisite must be disclosed") +require("on 5tratumOS, version 0.7.12 or newer is required" in manifest, "OS prerequisite must be disclosed") require(evidence.get("tested_os_version") == "v0.7.12-dev", "evidence must name the tested DEV OS release") require( evidence.get("tested_os_bundle_sha256") diff --git a/tests/test_axebc2_umbrel.py b/tests/test_axebc2_umbrel.py new file mode 100644 index 0000000..02a0c4d --- /dev/null +++ b/tests/test_axebc2_umbrel.py @@ -0,0 +1,141 @@ +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest + +import yaml + +ROOT = Path(__file__).resolve().parents[1] +APP = ROOT / "willitmod-dev-bc2" + + +class UmbrelPackagingTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix="axebc2-umbrel-") + self.root = Path(self.temp.name) + self.data = self.root / "data" + shutil.copytree(APP / "data", self.data) + shutil.copytree(APP / "hooks", self.root / "hooks") + self.env = os.environ.copy() + self.env.update({ + "AXEBC2_PLATFORM": "umbrel", "AXEBC2_DATA_DIR": str(self.data), + "AXEBC2_APPDATA_DIR": str(self.root), + "AXEBC2_TEMPLATES_DIR": str(self.data / "templates"), + "AXEBC2_TEST_SKIP_CHOWN": "true", "APP_DATA_DIR": str(self.root), + "APP_PASSWORD": "test-only", "JWT_SECRET": "test-only", + "NETWORK_IP": "10.21.0.0", "APPS_SUBNET": "10.21.0.0/16", + "RPC_USER": "btc2", "RPC_PASSWORD": "test-only", + "BTC2_RPC_PORT": "8337", "BTC2_P2P_PORT": "8338", + "BTC2_ZMQ_HASHBLOCK_PORT": "28336", + "PAYOUT_ADDRESS": "CHANGEME_BTC2_PAYOUT_ADDRESS", + "AXEBC2_BUILD_FILE": str(self.root / "does-not-exist.json"), + }) + + def tearDown(self): + self.temp.cleanup() + + def run_init(self, expected=0): + result = subprocess.run(["sh", str(self.root / "hooks/umbrel-init")], + env=self.env, text=True, capture_output=True) + self.assertEqual(result.returncode, expected, result.stderr) + return result + + def test_generated_artifacts_are_current(self): + subprocess.run([sys.executable, str(ROOT / "scripts/build-axebc2-umbrel.py"), "--check"], check=True) + + def test_accepted_5tratumos_recipe_remains_byte_identical(self): + self.assertEqual(hashlib.sha256((APP / "docker-compose.yml").read_bytes()).hexdigest(), + "93ceba92069947f47d650a5fb32205836fe070d83707f36912a2e0e83beb1244") + + def test_umbrel_envsubst_and_compose_keep_pins_and_auth_without_os_bind(self): + rendered = subprocess.check_output(["envsubst"], input=(APP / "docker-compose.yml.template").read_text(), + env=self.env, text=True) + self.assertNotIn("/etc/5tratumos", rendered) + config = yaml.safe_load(rendered) + config["services"]["app_proxy"]["image"] = "getumbrel/app-proxy:1.7.0" + path = self.root / "docker-compose.yml" + path.write_text(yaml.safe_dump(config)) + result = subprocess.run(["docker", "compose", "-f", str(path), "config", "--format", "json"], + capture_output=True, text=True, env=self.env) + self.assertEqual(result.returncode, 0, result.stderr) + services = json.loads(result.stdout)["services"] + accepted = yaml.safe_load((APP / "docker-compose.yml").read_text())["services"] + for service in ("app", "btc2d", "ckpool", "init"): + self.assertEqual(services[service]["image"], accepted[service]["image"]) + self.assertEqual(services["app_proxy"]["environment"]["JWT_SECRET"], "test-only") + self.assertEqual(services["app_proxy"]["environment"]["APP_HOST"], "axebc2-app") + self.assertEqual(services["btc2d"]["depends_on"]["init"]["condition"], "service_completed_successfully") + self.assertEqual(services["init"]["environment"]["AXEBC2_PLATFORM"], "umbrel") + for service in services.values(): + for volume in service.get("volumes", []): + if volume["type"] == "bind": + self.assertFalse(volume.get("bind", {}).get("create_host_path", False)) + self.assertTrue(Path(volume["source"]).exists(), volume) + + def test_fresh_umbrel_init_succeeds_without_5tratumos_metadata(self): + self.run_init() + policy = json.loads((self.data / ".5tratumos-rollback-policy.json").read_text()) + self.assertEqual(policy["minimum_base_version"], "0.1.10") + self.assertEqual(policy["minimum_5tratumos_version"], "0.7.12") + self.assertTrue((self.data / "node/bitcoinII.conf").is_file()) + + def test_missing_umbrel_recipe_identity_fails_before_writes(self): + self.env.pop("AXEBC2_PLATFORM") + self.run_init(78) + self.assertFalse((self.data / ".5tratumos-rollback-policy.json").exists()) + self.assertFalse((self.root / "settings.yml").exists()) + + def test_existing_chain_requires_reindex_and_keeps_data(self): + blocks = self.data / "node/blocks" + blocks.mkdir() + sentinel = blocks / "blk00000.dat" + sentinel.write_bytes(b"preserved chain") + self.run_init() + marker = json.loads((self.data / "node/.core31-full-reindex-required.json").read_text()) + self.assertEqual(marker["minimum_core_major"], 31) + self.assertEqual(marker["activation_height"], 57750) + self.assertEqual(sentinel.read_bytes(), b"preserved chain") + + def test_malformed_migration_marker_still_rejects_startup(self): + marker = self.data / "node/.core31-full-reindex-complete.json" + marker.write_text('{"migration":"not-accepted"}') + self.run_init(78) + self.assertEqual(marker.read_text(), '{"migration":"not-accepted"}') + self.assertFalse((self.data / "node/bitcoinII.conf").exists()) + + def test_valid_completion_survives_restart_without_reindex(self): + marker = self.data / "node/.core31-full-reindex-complete.json" + complete = { + "schema": 1, "migration": "bitcoinii-shockwave-core31-full-reindex", + "minimum_core_major": 31, "activation_height": 57750, + "completed_at": "2026-09-04T17:22:22Z", "validated_height": 58444, + "best_block_hash": "0" * 64, "core_version": 310100, + "checkpoint_height": 57752, + "checkpoint_hash": "000000000000000013ceffe797280c57f75a5b9f1d9e70c3503584058c322576", + "validated_chainwork": "0000000000000000000000000000000000000000000000959028194ff1139272", + } + marker.write_text(json.dumps(complete)) + (self.data / "node/chainstate").mkdir() + self.run_init() + self.run_init() + self.assertEqual(json.loads(marker.read_text()), complete) + self.assertFalse((self.data / "node/.core31-full-reindex-required.json").exists()) + + def test_preserved_data_initializer_is_not_executed_on_upgrade(self): + (self.data / "init/init.sh").write_text("#!/bin/sh\nexit 99\n") + self.run_init() + config = self.data / "pool/config/ckpool.conf" + saved = json.loads(config.read_text()) + saved["btcaddress"] = "retained-test-payout" + config.write_text(json.dumps(saved)) + self.run_init() + self.assertEqual(json.loads(config.read_text())["btcaddress"], "retained-test-payout") + + +if __name__ == "__main__": + unittest.main() diff --git a/willitmod-dev-bc2/UMBREL-COMPATIBILITY.md b/willitmod-dev-bc2/UMBREL-COMPATIBILITY.md new file mode 100644 index 0000000..b18f951 --- /dev/null +++ b/willitmod-dev-bc2/UMBREL-COMPATIBILITY.md @@ -0,0 +1,60 @@ +# Umbrel packaging + +Umbrel renders the top-level `docker-compose.yml.template` before installation +and every start. It refreshes top-level templates and `hooks/` on upgrades. +The generated template uses `hooks/umbrel-init` and `hooks/umbrel-ckpool` as +container entrypoints, so an older initializer preserved under `data/` cannot +block an upgraded Umbrel installation. + +5tratumOS consumes `docker-compose.yml` directly. That accepted recipe and +`data/init/init.sh` remain unchanged, including the minimum OS check. The +Umbrel template needs no 5tratumOS host file and uses the same pinned app, +Core 31, CKPool, and initialization images. + +The generated Umbrel initializer retains migration-marker validation, reindex +requirements, payout preservation, data ownership repair, and the persistent +application policy. The `.5tratumos-rollback-policy.json` filename is retained +because the app consumes it and it protects a later move to 5tratumOS. +Umbrel does not provide the 5tratumOS host rollback-policy enforcement API; +this package does not claim to add an Umbrel OS downgrade guard. + +Regenerate after changing the shared recipe or initializer: + +```sh +python3 scripts/build-axebc2-umbrel.py +python3 scripts/build-axebc2-umbrel.py --check +python3 scripts/validate-axebc2-core31-dev.py --phase finalized +``` + +Generated entrypoints live in `hooks/` because it is an upstream update +whitelist directory. They are non-executable on the host and are invoked +explicitly by `/bin/sh` inside their pinned containers. Shell programs must +not be embedded in the Compose template: Umbrel's `envsubst` would consume +their local shell variables before container startup. + +Runtime validation target: a fresh umbrelOS 1.7.4 amd64 VM. The existing Core 31 +acceptance record describes the unchanged 5tratumOS recipe; it is not evidence +that the new Umbrel path has passed runtime testing. + +Upstream contract: +https://github.com/getumbrel/umbrel/blob/1.7.4/packages/umbreld/source/modules/apps/legacy-compat/app-script + +## Packaging revision 0.1.12 + +The store version advances to 0.1.12 (0.1.12-dev on DEV) so existing Umbrel +installs can receive the recipe correction. The application binary and its +reported version remain 0.1.11; no application or node image is rebuilt. + +On 6 September 2026, both candidates passed the native installer and repeated +container initialization on 5tratumOS v0.7.12-dev at 10.10.10.235, using +isolated installation roots and disposable data. The original required mount +was also tested against an absent JSON source and reproduced the Docker bind +failure. Both generated Umbrel initializers passed without a host JSON bind. + +The isolated DEV node, pool, and app started; the app page and node API +responded, including after restarting the app. That smoke test used localhost +access, resource limits, and no blockchain peer connections. It does not +establish synchronized mining or Umbrel dashboard authentication. + +Automated validation: 33 DEV tests and 40 MAIN tests passed. Real Umbrel +installation, authenticated opening, and upgrade remain pending VM access. diff --git a/willitmod-dev-bc2/docker-compose.yml.template b/willitmod-dev-bc2/docker-compose.yml.template new file mode 100644 index 0000000..1857129 --- /dev/null +++ b/willitmod-dev-bc2/docker-compose.yml.template @@ -0,0 +1,135 @@ +# Generated by scripts/build-axebc2-umbrel.py; do not edit. +version: '3.7' +services: + app_proxy: + environment: + APP_HOST: axebc2-app + APP_PORT: 3000 + JWT_SECRET: ${JWT_SECRET} + networks: + - umbrel_main_network + init: + image: alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 + volumes: + - type: bind + source: ${APP_DATA_DIR}/data + target: /data + bind: + create_host_path: false + - type: bind + source: ${APP_DATA_DIR} + target: /appdata + bind: + create_host_path: false + - type: bind + source: ${APP_DATA_DIR}/data/templates + target: /data/templates + bind: + create_host_path: false + - type: bind + source: ${APP_DATA_DIR}/hooks/umbrel-init + target: /opt/axebc2/init.sh + read_only: true + bind: + create_host_path: false + environment: + JWT_SECRET: ${JWT_SECRET} + APPS_SUBNET: ${NETWORK_IP}/16 + RPC_USER: btc2 + RPC_PASSWORD: ${APP_PASSWORD} + BTC2_RPC_PORT: '8337' + BTC2_P2P_PORT: '8338' + BTC2_ZMQ_HASHBLOCK_PORT: '28336' + PAYOUT_ADDRESS: CHANGEME_BTC2_PAYOUT_ADDRESS + AXEBC2_PLATFORM: umbrel + command: + - /bin/sh + - /opt/axebc2/init.sh + btc2d: + image: ghcr.io/willitmod/bitcoinii-core:31.1.0-rc.cdf44542dde2@sha256:8875917ece57668fe9925d40a256ce8d429a3071511bb555d4ace1fa4370afc6 + user: 1000:1000 + restart: unless-stopped + stop_grace_period: 15m30s + depends_on: + init: + condition: service_completed_successfully + volumes: + - type: bind + source: ${APP_DATA_DIR}/data/node + target: /data + bind: + create_host_path: false + ckpool: + image: ghcr.io/willitmod/docker-ckpool-solo:590fb2a@sha256:8a9a7f10c8138d0f55533132ee7710a06715a42a49f75efb39be3350ada4fa6e + user: 1000:1000 + restart: on-failure + depends_on: + - btc2d + - init + stop_grace_period: 30s + init: true + ports: + - 2345:3333/tcp + volumes: + - type: bind + source: ${APP_DATA_DIR}/data/pool/config + target: /config + read_only: true + bind: + create_host_path: false + - type: bind + source: ${APP_DATA_DIR}/data/pool/www + target: /www + bind: + create_host_path: false + - type: bind + source: ${APP_DATA_DIR}/hooks/umbrel-ckpool + target: /opt/axebc2/ckpool-entrypoint.sh + read_only: true + bind: + create_host_path: false + entrypoint: + - /bin/sh + - /opt/axebc2/ckpool-entrypoint.sh + app: + image: ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.11-candidate.ecf6e2c8cfd0@sha256:23a7962e223da5549eba52697c6f4cfa16ab74cba935c68c48148a4c515302b4 + user: 1000:1000 + restart: on-failure + stop_grace_period: 30s + depends_on: + - btc2d + - ckpool + - init + networks: + default: + aliases: + - axebc2-app + umbrel_main_network: + aliases: + - axebc2-app + volumes: + - type: bind + source: ${APP_DATA_DIR}/data + target: /data + bind: + create_host_path: false + environment: + NETWORK_IP: ${NETWORK_IP} + STATIC_DIR: /app/static + APP_CHANNEL: ALPHA + APP_VERSION_SUFFIX: -dev + BTC2D_IMAGE: ghcr.io/willitmod/bitcoinii-core:31.1.0-rc.cdf44542dde2@sha256:8875917ece57668fe9925d40a256ce8d429a3071511bb555d4ace1fa4370afc6 + CKPOOL_IMAGE: ghcr.io/willitmod/docker-ckpool-solo:590fb2a@sha256:8a9a7f10c8138d0f55533132ee7710a06715a42a49f75efb39be3350ada4fa6e + SUPPORT_CHECKIN_ENABLED: 'false' + BTC2_RPC_HOST: btc2d + BTC2_RPC_PORT: '8337' + BTC2_RPC_USER: btc2 + BTC2_RPC_PASS: ${APP_PASSWORD} + CKPOOL_STATUS_DIR: /data/pool/www/pool + CKPOOL_USERS_DIR: /data/pool/www/users + CKPOOL_LOG_PATH: /data/pool/www/ckpool.log + CKPOOL_CONF_PATH: /data/pool/config/ckpool.conf +networks: + umbrel_main_network: + external: true + name: umbrel_main_network diff --git a/willitmod-dev-bc2/hooks/umbrel-ckpool b/willitmod-dev-bc2/hooks/umbrel-ckpool new file mode 100644 index 0000000..d7569a2 --- /dev/null +++ b/willitmod-dev-bc2/hooks/umbrel-ckpool @@ -0,0 +1,15 @@ +#!/bin/sh +# Generated by scripts/build-axebc2-umbrel.py; do not edit. +set -eu +rm -f /tmp/ckpool/*.pid 2>/dev/null || true +ARGS="$(cat /config/ckpool.args 2>/dev/null || true)" +if command -v ckpool >/dev/null 2>&1; then + exec ckpool -k -L -c /config/ckpool.conf $ARGS +elif [ -x /bin/ckpool ]; then + exec /bin/ckpool -k -L -c /config/ckpool.conf $ARGS +elif [ -x /usr/bin/ckpool ]; then + exec /usr/bin/ckpool -k -L -c /config/ckpool.conf $ARGS +else + echo "ckpool binary not found in image" + exit 127 +fi diff --git a/willitmod-dev-bc2/hooks/umbrel-init b/willitmod-dev-bc2/hooks/umbrel-init new file mode 100644 index 0000000..901f087 --- /dev/null +++ b/willitmod-dev-bc2/hooks/umbrel-init @@ -0,0 +1,235 @@ +#!/bin/sh +# Generated by scripts/build-axebc2-umbrel.py; do not edit. +set -eu + +data_dir="${AXEBC2_DATA_DIR:-/data}" +appdata_dir="${AXEBC2_APPDATA_DIR:-/appdata}" +templates_dir="${AXEBC2_TEMPLATES_DIR:-${data_dir}/templates}" +policy_file="${data_dir}/.5tratumos-rollback-policy.json" +node_dir="${data_dir}/node" +required_marker="${node_dir}/.core31-full-reindex-required.json" +complete_marker="${node_dir}/.core31-full-reindex-complete.json" +minimum_os="0.7.12" +minimum_app="0.1.10" +migration="bitcoinii-shockwave-core31-full-reindex" + +fail() { + echo "[axebc2-init] $*" >&2 + exit 78 +} + +if ! command -v jq >/dev/null 2>&1 || ! command -v envsubst >/dev/null 2>&1; then + # The digest-pinned Alpine image is intentionally kept generic. These tools + # come from its configured 3.22 repositories; a repository/network failure + # exits here, before the OS check and before any persistent-data write. + apk add --no-cache gettext-envsubst jq >/dev/null +fi + +version_normalize() { + printf '%s' "$1" | sed -nE 's/^v?([0-9]+(\.[0-9]+){1,3})([-+][0-9A-Za-z][0-9A-Za-z.-]*)?$/\1/p' +} + +version_ge() { + lhs="$(version_normalize "$1")" + rhs="$(version_normalize "$2")" + [ -n "$lhs" ] && [ -n "$rhs" ] || return 1 + component=1 + while [ "$component" -le 4 ]; do + left="$(printf '%s' "$lhs" | cut -d. -f"$component")" + right="$(printf '%s' "$rhs" | cut -d. -f"$component")" + [ "$left" != "$lhs" ] || [ "$component" -eq 1 ] || left=0 + [ "$right" != "$rhs" ] || [ "$component" -eq 1 ] || right=0 + left="$(printf '%s' "${left:-0}" | sed 's/^0*//')"; left="${left:-0}" + right="$(printf '%s' "${right:-0}" | sed 's/^0*//')"; right="${right:-0}" + if [ "${#left}" -gt "${#right}" ]; then return 0; fi + if [ "${#left}" -lt "${#right}" ]; then return 1; fi + if [ "$left" != "$right" ]; then + highest="$(printf '%s\n%s\n' "$left" "$right" | LC_ALL=C sort | tail -n 1)" + [ "$highest" = "$left" ] + return + fi + component=$((component + 1)) + done + return 0 +} + +# Umbrel owns the app lifecycle; the Core 31 migration checks below +# are app-owned and remain mandatory on this platform. +[ "${AXEBC2_PLATFORM:-}" = "umbrel" ] || fail "Umbrel entrypoint requires the Umbrel recipe" + +atomic_json_write() { + destination="$1" + payload="$2" + parent="$(dirname "$destination")" + [ -d "$parent" ] || mkdir -p "$parent" + temporary="${destination}.tmp.$$" + trap 'rm -f "$temporary"' EXIT HUP INT TERM + umask 077 + printf '%s\n' "$payload" >"$temporary" || fail "cannot write ${destination}" + chmod 600 "$temporary" || fail "cannot protect ${destination}" + if [ "${AXEBC2_TEST_SKIP_CHOWN:-false}" != "true" ]; then + chown 1000:1000 "$temporary" || fail "cannot assign ${destination} to the app user" + fi + mv "$temporary" "$destination" || fail "cannot install ${destination}" + trap - EXIT HUP INT TERM +} + +policy_app="$minimum_app" +policy_os="$minimum_os" +policy_height=57750 +if [ -e "$policy_file" ]; then + [ -r "$policy_file" ] || fail "existing release policy is unreadable" + jq -e ' + type == "object" and .schema == 1 and .app_id == "axebc2" and + (.minimum_base_version | type == "string" and test("^v?[0-9]+(\\.[0-9]+){1,3}([-+][0-9A-Za-z][0-9A-Za-z.-]*)?$")) and + (.minimum_5tratumos_version | type == "string" and test("^v?[0-9]+(\\.[0-9]+){1,3}([-+][0-9A-Za-z][0-9A-Za-z.-]*)?$")) and + (.reason | type == "string" and length > 0) and + (.recorded_at_height | type == "number" and floor == . and . >= 0) + ' "$policy_file" >/dev/null 2>&1 || fail "existing release policy is malformed" + existing_app="$(jq -r '.minimum_base_version' "$policy_file")" + existing_os="$(jq -r '.minimum_5tratumos_version' "$policy_file")" + existing_height="$(jq -r '.recorded_at_height' "$policy_file")" + if version_ge "$existing_app" "$policy_app"; then policy_app="$existing_app"; fi + if version_ge "$existing_os" "$policy_os"; then policy_os="$existing_os"; fi + if [ "$existing_height" -gt "$policy_height" ]; then policy_height="$existing_height"; fi +fi +policy_payload="$(jq -cn \ + --arg app "$policy_app" --arg os "$policy_os" --argjson height "$policy_height" \ + '{schema:1,app_id:"axebc2",minimum_base_version:$app,minimum_5tratumos_version:$os,reason:"ShockWave Core 31 consensus activation requires a non-downgradable app and OS floor",recorded_at_height:$height}')" +atomic_json_write "$policy_file" "$policy_payload" + +validate_migration_marker() { + marker="$1" + jq -e --arg migration "$migration" ' + type == "object" and .schema == 1 and .migration == $migration and + .minimum_core_major == 31 and .activation_height == 57750 + ' "$marker" >/dev/null 2>&1 +} + +validate_complete_marker() { + marker="$1" + validate_migration_marker "$marker" && + jq -e ' + (.completed_at | type == "string" and test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*(Z|[+]00:00)$")) and + (.validated_height | type == "number" and floor == . and . >= 57750) and + (.best_block_hash | type == "string" and test("^[0-9a-f]{64}$")) and + (.core_version | type == "number" and floor == . and . >= 310000) and + .checkpoint_height == 57752 and + .checkpoint_hash == "000000000000000013ceffe797280c57f75a5b9f1d9e70c3503584058c322576" and + (.validated_chainwork | type == "string" and test("^[0-9a-f]{64}$") and + . >= "0000000000000000000000000000000000000000000000959028194ff1139272") + ' "$marker" >/dev/null 2>&1 +} + +if [ -e "$complete_marker" ]; then + [ -r "$complete_marker" ] && validate_complete_marker "$complete_marker" || + fail "existing Core 31 completion marker is invalid" +fi +if [ -e "$required_marker" ]; then + [ -r "$required_marker" ] && validate_migration_marker "$required_marker" || + fail "existing Core 31 required marker is invalid" +fi + +if { [ -e "${node_dir}/blocks" ] || [ -e "${node_dir}/chainstate" ]; } && [ ! -e "$complete_marker" ]; then + required_payload='{"schema":1,"migration":"bitcoinii-shockwave-core31-full-reindex","minimum_core_major":31,"activation_height":57750}' + atomic_json_write "$required_marker" "$required_payload" +fi + +# Normal initialization starts only after the migration policy exists. +if [ -n "${JWT_SECRET:-}" ]; then + envfile="${appdata_dir}/.env" + tmp="${envfile}.tmp.$$" + if [ -f "$envfile" ]; then grep -v '^JWT_SECRET=' "$envfile" >"$tmp" || true; else : >"$tmp"; fi + printf 'JWT_SECRET=%s\n' "$JWT_SECRET" >>"$tmp" + chmod 600 "$tmp" + chown 1000:1000 "$tmp" 2>/dev/null || true + mv "$tmp" "$envfile" +fi + +mkdir -p "$node_dir" "${data_dir}/pool/config" "${data_dir}/pool/www/pool" "${data_dir}/pool/www/users" +touch "${appdata_dir}/settings.yml" +chown 1000:1000 "${appdata_dir}/settings.yml" 2>/dev/null || true + +if [ ! -f "${node_dir}/bitcoinII.conf" ]; then + envsubst <"${templates_dir}/bitcoinII.conf.template" >"${node_dir}/bitcoinII.conf" + chown -R 1000:1000 "$node_dir" 2>/dev/null || true +fi + +# Existing installs may retain the old upnp=1 setting. Core 31 uses NAT-PMP; +# explicitly disable both forms rather than relying only on the new template. +node_conf="${node_dir}/bitcoinII.conf" +[ -r "$node_conf" ] || fail "BitcoinII configuration is unreadable" +node_conf_tmp="${node_conf}.tmp.$$" +awk ' + !/^[[:space:]]*(upnp|natpmp)[[:space:]]*=/ { print } + END { print "natpmp=0" } +' "$node_conf" >"$node_conf_tmp" || fail "cannot disable automatic P2P port mapping" +chmod 600 "$node_conf_tmp" || fail "cannot protect BitcoinII configuration" +chown 1000:1000 "$node_conf_tmp" 2>/dev/null || true +mv "$node_conf_tmp" "$node_conf" || fail "cannot install BitcoinII configuration" + +ckpool_conf="${data_dir}/pool/config/ckpool.conf" +needs_ckpool_regen=0 +if [ -f "$ckpool_conf" ]; then + grep -qE '"btcd"[[:space:]]*:[[:space:]]*\[' "$ckpool_conf" || needs_ckpool_regen=1 + grep -q '"zmqblock"' "$ckpool_conf" || needs_ckpool_regen=1 +else + needs_ckpool_regen=1 +fi +if [ "$needs_ckpool_regen" -eq 1 ]; then + existing_addr="$(grep -oE '\"btcaddress\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' "$ckpool_conf" 2>/dev/null | head -n 1 | sed -E 's/.*\"btcaddress\"[[:space:]]*:[[:space:]]*\"([^\"]*)\".*/\1/' || true)" + if [ -f "$ckpool_conf" ]; then mv "$ckpool_conf" "${ckpool_conf}.bak.$(date +%s 2>/dev/null || echo 0)"; fi + envsubst <"${templates_dir}/ckpool.conf.template" >"$ckpool_conf" + if [ -n "$existing_addr" ] && [ "$existing_addr" != "CHANGEME_BTC2_PAYOUT_ADDRESS" ]; then + tmp="$(mktemp)" + jq --arg a "$existing_addr" '.btcaddress=$a' "$ckpool_conf" >"$tmp" || fail "cannot preserve payout address" + mv "$tmp" "$ckpool_conf" + fi +fi + +if [ ! -f "${data_dir}/pool/config/ckpool.args" ]; then + printf '%s\n' '-B' >"${data_dir}/pool/config/ckpool.args" + chown 1000:1000 "${data_dir}/pool/config/ckpool.args" 2>/dev/null || true +fi + +settings="${data_dir}/ui/state/pool_settings.json" +if [ -f "$settings" ]; then + addr="$(jq -r '.payoutAddress // empty' "$settings" 2>/dev/null || true)" + if [ -n "$addr" ] && [ -f "$ckpool_conf" ]; then + tmp="$(mktemp)" + jq --arg a "$addr" '.btcaddress=$a' "$ckpool_conf" >"$tmp" || fail "cannot apply saved payout address" + mv "$tmp" "$ckpool_conf" + chown 1000:1000 "$ckpool_conf" 2>/dev/null || true + fi +fi + +# The app atomically replaces files in pool/config as uid/gid 1000, so the +# directory itself must remain writable even when a fresh install seeded it as +# root. This tree is tiny and safe to repair on every initializer run. +# +# CKPool also runs as uid/gid 1000 and creates per-height sharelog directories +# directly under /www. Existing installs can already have a current config and +# therefore skip the regeneration branch above while /www remains root-owned. +# Check only the three known writable directories on normal starts; recursively +# repair the existing sharelog tree once if an upgrade left any of them behind. +if [ "${AXEBC2_TEST_SKIP_CHOWN:-false}" != "true" ]; then + chown -R 1000:1000 "${data_dir}/pool/config" 2>/dev/null || + fail "cannot assign CKPool config data to the app user" + + repair_sharelog_ownership=false + for writable_dir in \ + "${data_dir}/pool/www" \ + "${data_dir}/pool/www/pool" \ + "${data_dir}/pool/www/users" + do + owner_group="$(stat -c '%u:%g' "$writable_dir" 2>/dev/null || true)" + if [ "$owner_group" != "1000:1000" ]; then + repair_sharelog_ownership=true + break + fi + done + if [ "$repair_sharelog_ownership" = "true" ]; then + chown -R 1000:1000 "${data_dir}/pool/www" 2>/dev/null || + fail "cannot assign CKPool sharelog data to the app user" + fi +fi diff --git a/willitmod-dev-bc2/umbrel-app.yml b/willitmod-dev-bc2/umbrel-app.yml index fe6b8b0..9beb44d 100644 --- a/willitmod-dev-bc2/umbrel-app.yml +++ b/willitmod-dev-bc2/umbrel-app.yml @@ -2,7 +2,7 @@ manifestVersion: 1 id: willitmod-dev-bc2 category: bitcoin name: AxeBC2 -version: "0.1.11-dev" +version: "0.1.12-dev" tagline: BC2 node + solo pool description: >- ALPHA RELEASE (DEV CHANNEL) @@ -25,7 +25,7 @@ description: >- Notes: - Set your payout address in the app Settings tab. - - Requires 5tratumOS 0.7.12 or newer. + - Supports Umbrel; on 5tratumOS, version 0.7.12 or newer is required. - The node makes outbound peer connections; this app does not publish a public P2P port or request NAT-PMP mappings. - Initial sync can take a long time and a lot of bandwidth/storage (the node downloads and validates the chain). - Solo mining is extremely unlikely to find blocks without significant hashrate. @@ -47,6 +47,12 @@ path: "" defaultUsername: "" defaultPassword: "" releaseNotes: >- + Fixes Umbrel installation by removing its dependency on the 5tratumOS host + build.json file. 5tratumOS keeps its existing startup recipe and OS version + check. This packaging-only update retains the 0.1.11 application image, + Core 31 migration checks, payout address, and existing blockchain data. + + Included fixes from 0.1.11: Corrects payout-address validation: BitcoinII Core-accepted mainnet addresses beginning with 1, 3, or bc1 are supported, while invalid or wrong-network addresses are rejected without changing the saved pool configuration. Settings @@ -56,7 +62,7 @@ releaseNotes: >- conditional CKPool /www ownership repair so existing sharelogs remain writable. This app-only update retains the accepted BitcoinII Core 31.1 image, existing blockchain and pool data, and the configured payout address; it does not - trigger another blockchain reindex. Requires 5tratumOS 0.7.12 or newer. + trigger another blockchain reindex. On 5tratumOS, version 0.7.12 or newer is required. Stratum remains on TCP port 2345, telemetry remains disabled by default, and no public P2P port or NAT-PMP mapping is enabled. widgets: From 7d3098168e15793f0f57f2fae334b4fb82f81bd5 Mon Sep 17 00:00:00 2001 From: Johnny Murray Date: Sun, 6 Sep 2026 11:01:01 +0100 Subject: [PATCH 2/2] Release AxeBC2 beta UI without the general warning banner --- scripts/axebc2_release_state.py | 4 +-- scripts/validate-axebc2-core31-dev.py | 22 ++++++++++------ tests/test_axebc2_dev_finalizer.py | 6 +++++ tests/test_axebc2_umbrel.py | 9 +++++-- willitmod-dev-bc2/BETA-RELEASE-EVIDENCE.json | 26 +++++++++++++++++++ willitmod-dev-bc2/UMBREL-COMPATIBILITY.md | 15 ++++++++--- willitmod-dev-bc2/docker-compose.yml | 4 +-- willitmod-dev-bc2/docker-compose.yml.template | 4 +-- willitmod-dev-bc2/umbrel-app.yml | 16 +++++------- 9 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 willitmod-dev-bc2/BETA-RELEASE-EVIDENCE.json diff --git a/scripts/axebc2_release_state.py b/scripts/axebc2_release_state.py index 2daaa93..e13fc4b 100644 --- a/scripts/axebc2_release_state.py +++ b/scripts/axebc2_release_state.py @@ -1,7 +1,7 @@ from pathlib import Path -APP_TAG = "ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.11-candidate.ecf6e2c8cfd0" -APP_DIGEST = "sha256:23a7962e223da5549eba52697c6f4cfa16ab74cba935c68c48148a4c515302b4" +APP_TAG = "ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.12-candidate.3b893173de7b" +APP_DIGEST = "sha256:5defc8ac3c1d6e188959ed5c0642165e66108701c5ce3881e909c0994c8e3189" CORE_TAG = "ghcr.io/willitmod/bitcoinii-core:31.1.0-rc.cdf44542dde2" CORE_DIGEST = "sha256:8875917ece57668fe9925d40a256ce8d429a3071511bb555d4ace1fa4370afc6" diff --git a/scripts/validate-axebc2-core31-dev.py b/scripts/validate-axebc2-core31-dev.py index 2064940..9b912f2 100644 --- a/scripts/validate-axebc2-core31-dev.py +++ b/scripts/validate-axebc2-core31-dev.py @@ -12,7 +12,7 @@ import tempfile import unittest import argparse -from axebc2_release_state import validate as validate_release_state, validate_rendered_binds +from axebc2_release_state import validate as validate_release_state, validate_rendered_binds, APP_TAG as CURRENT_APP_TAG, APP_DIGEST as CURRENT_APP_DIGEST ROOT = Path(__file__).resolve().parents[1] @@ -42,18 +42,24 @@ def require(condition, message): node_config = (APP / "data/templates/bitcoinII.conf.template").read_text(encoding="utf-8") evidence = json.loads((APP / "DEV-ACCEPTANCE-EVIDENCE.json").read_text(encoding="utf-8")) -# Hash the exact finalized recipe in either lifecycle phase. In prefinalization -# there is exactly one sentinel; in finalization this replacement is a no-op. -finalized_compose_bytes = compose_bytes.replace( - b"APP_CANDIDATE_DIGEST_REQUIRED", APP_DIGEST.removeprefix("sha256:").encode() +# Preserve the original Core 31 acceptance binding. This release changes only +# the UI image and DEV stage; undo those two explicit changes for the baseline +# check, so any unrelated runtime/configuration change is still rejected. +finalized_compose = compose.replace( + "APP_CANDIDATE_DIGEST_REQUIRED", CURRENT_APP_DIGEST.removeprefix("sha256:") ) -computed_compose_sha256 = hashlib.sha256(finalized_compose_bytes).hexdigest() +require('APP_CHANNEL: "BETA"' in finalized_compose, "DEV stage must be BETA") +baseline_compose = finalized_compose.replace( + CURRENT_APP_TAG + "@" + CURRENT_APP_DIGEST, + "ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.11-candidate.ecf6e2c8cfd0@" + APP_DIGEST, +).replace('APP_CHANNEL: "BETA"', 'APP_CHANNEL: "ALPHA"') +computed_compose_sha256 = hashlib.sha256(baseline_compose.encode()).hexdigest() require( computed_compose_sha256 == DEV_COMPOSE_SHA256, - "DEV Compose content differs from the recipe accepted on 10.10.10.235", + "DEV runtime differs from its accepted baseline beyond the BETA UI image/stage", ) -# Package revision; the unchanged runtime evidence below remains 0.1.11-dev. +# BETA release; the historical Core 31 baseline evidence below remains 0.1.11-dev. require('version: "0.1.12-dev"' in manifest, "manifest must be 0.1.12-dev") require(evidence.get("app_version") == "0.1.11-dev", "evidence must name the 0.1.11 DEV app version") require( diff --git a/tests/test_axebc2_dev_finalizer.py b/tests/test_axebc2_dev_finalizer.py index 31abcfa..068b5a8 100644 --- a/tests/test_axebc2_dev_finalizer.py +++ b/tests/test_axebc2_dev_finalizer.py @@ -24,7 +24,13 @@ def setUp(self): self.root = Path(self.temp.name) (self.root / "scripts").mkdir(); (self.root / "willitmod-dev-bc2").mkdir() shutil.copy2(SCRIPT, self.root / "scripts" / SCRIPT.name) + # This finalizer belongs to 0.1.11. Restore that exact UI pin/stage + # before exercising its historical acceptance workflow. fixture = COMPOSE.read_text(encoding="utf-8") + fixture = fixture.replace( + "ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.12-candidate.3b893173de7b@sha256:5defc8ac3c1d6e188959ed5c0642165e66108701c5ce3881e909c0994c8e3189", + "ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.11-candidate.ecf6e2c8cfd0@" + APP_DIGEST, + ).replace('APP_CHANNEL: "BETA"', 'APP_CHANNEL: "ALPHA"') fixture = re.sub(r"(ghcr\.io/willitmod/axebc2-app-umbrel-dev:0\.1\.11-candidate\.ecf6e2c8cfd0@sha256:)[0-9a-f]{64}", r"\1APP_CANDIDATE_DIGEST_REQUIRED", fixture) (self.root / "willitmod-dev-bc2/docker-compose.yml").write_text(fixture, encoding="utf-8") self.assertEqual(fixture.count("APP_CANDIDATE_DIGEST_REQUIRED"), 1) diff --git a/tests/test_axebc2_umbrel.py b/tests/test_axebc2_umbrel.py index 02a0c4d..e48aca2 100644 --- a/tests/test_axebc2_umbrel.py +++ b/tests/test_axebc2_umbrel.py @@ -48,8 +48,13 @@ def run_init(self, expected=0): def test_generated_artifacts_are_current(self): subprocess.run([sys.executable, str(ROOT / "scripts/build-axebc2-umbrel.py"), "--check"], check=True) - def test_accepted_5tratumos_recipe_remains_byte_identical(self): - self.assertEqual(hashlib.sha256((APP / "docker-compose.yml").read_bytes()).hexdigest(), + def test_5tratumos_recipe_changes_only_the_beta_ui_image_and_stage(self): + source = (APP / "docker-compose.yml").read_text() + current = yaml.safe_load(source)["services"]["app"]["image"] + baseline = source.replace(current, + "ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.11-candidate.ecf6e2c8cfd0@sha256:23a7962e223da5549eba52697c6f4cfa16ab74cba935c68c48148a4c515302b4" + ).replace('APP_CHANNEL: "BETA"', 'APP_CHANNEL: "ALPHA"') + self.assertEqual(hashlib.sha256(baseline.encode()).hexdigest(), "93ceba92069947f47d650a5fb32205836fe070d83707f36912a2e0e83beb1244") def test_umbrel_envsubst_and_compose_keep_pins_and_auth_without_os_bind(self): diff --git a/willitmod-dev-bc2/BETA-RELEASE-EVIDENCE.json b/willitmod-dev-bc2/BETA-RELEASE-EVIDENCE.json new file mode 100644 index 0000000..386fafd --- /dev/null +++ b/willitmod-dev-bc2/BETA-RELEASE-EVIDENCE.json @@ -0,0 +1,26 @@ +{ + "image": "ghcr.io/willitmod/axebc2-app-umbrel-dev", + "candidate": "ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.12-candidate.3b893173de7b", + "digest": "sha256:5defc8ac3c1d6e188959ed5c0642165e66108701c5ce3881e909c0994c8e3189", + "revision": "3b893173de7b43c70bdddac4817a05e35ffa1957", + "version": "0.1.12", + "platforms": [ + "linux/amd64", + "linux/arm64" + ], + "provenance": true, + "sbom": true, + "candidate_run": 34026015018, + "tested_on": "10.10.10.235", + "scope": "UI-only candidate, separate writable test data, existing Core 31 node via read-only RPC methods", + "browser_verified": { + "version": "0.1.12-dev", + "channel": "BETA", + "legacy_alpha_environment": true, + "release_warning_visible": false, + "node_synchronized": true + }, + "application_tests_passed": 50, + "store_tests_passed": 33, + "umbrel_lifecycle_tested": false +} diff --git a/willitmod-dev-bc2/UMBREL-COMPATIBILITY.md b/willitmod-dev-bc2/UMBREL-COMPATIBILITY.md index b18f951..ef611e5 100644 --- a/willitmod-dev-bc2/UMBREL-COMPATIBILITY.md +++ b/willitmod-dev-bc2/UMBREL-COMPATIBILITY.md @@ -6,8 +6,9 @@ The generated template uses `hooks/umbrel-init` and `hooks/umbrel-ckpool` as container entrypoints, so an older initializer preserved under `data/` cannot block an upgraded Umbrel installation. -5tratumOS consumes `docker-compose.yml` directly. That accepted recipe and -`data/init/init.sh` remain unchanged, including the minimum OS check. The +5tratumOS consumes `docker-compose.yml` directly. Its platform integration and +`data/init/init.sh` remain unchanged, including the minimum OS check. The DEV +0.1.12 recipe updates only the application image and release-stage label. The Umbrel template needs no 5tratumOS host file and uses the same pinned app, Core 31, CKPool, and initialization images. @@ -42,8 +43,9 @@ https://github.com/getumbrel/umbrel/blob/1.7.4/packages/umbreld/source/modules/a ## Packaging revision 0.1.12 The store version advances to 0.1.12 (0.1.12-dev on DEV) so existing Umbrel -installs can receive the recipe correction. The application binary and its -reported version remain 0.1.11; no application or node image is rebuilt. +installs can receive the recipe correction. MAIN currently retains the 0.1.11 application image. DEV also includes the +0.1.12 BETA UI update: the general release banner is removed, and the release +stage is shown in the existing compact badge. Node and pool images are unchanged. On 6 September 2026, both candidates passed the native installer and repeated container initialization on 5tratumOS v0.7.12-dev at 10.10.10.235, using @@ -58,3 +60,8 @@ establish synchronized mining or Umbrel dashboard authentication. Automated validation: 33 DEV tests and 40 MAIN tests passed. Real Umbrel installation, authenticated opening, and upgrade remain pending VM access. + +The BETA application candidate is bound in `BETA-RELEASE-EVIDENCE.json`. +Historical Core 31 acceptance remains tied to 0.1.11. Validation permits only +the explicit BETA application-image and stage changes relative to that recipe; +any other change to the native recipe fails the baseline hash check. diff --git a/willitmod-dev-bc2/docker-compose.yml b/willitmod-dev-bc2/docker-compose.yml index b2e4391..aec09ea 100644 --- a/willitmod-dev-bc2/docker-compose.yml +++ b/willitmod-dev-bc2/docker-compose.yml @@ -126,7 +126,7 @@ services: fi app: - image: ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.11-candidate.ecf6e2c8cfd0@sha256:23a7962e223da5549eba52697c6f4cfa16ab74cba935c68c48148a4c515302b4 + image: ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.12-candidate.3b893173de7b@sha256:5defc8ac3c1d6e188959ed5c0642165e66108701c5ce3881e909c0994c8e3189 user: "1000:1000" restart: on-failure stop_grace_period: 30s @@ -150,7 +150,7 @@ services: environment: NETWORK_IP: "${NETWORK_IP}" STATIC_DIR: "/app/static" - APP_CHANNEL: "ALPHA" + APP_CHANNEL: "BETA" APP_VERSION_SUFFIX: "-dev" BTC2D_IMAGE: "ghcr.io/willitmod/bitcoinii-core:31.1.0-rc.cdf44542dde2@sha256:8875917ece57668fe9925d40a256ce8d429a3071511bb555d4ace1fa4370afc6" CKPOOL_IMAGE: "ghcr.io/willitmod/docker-ckpool-solo:590fb2a@sha256:8a9a7f10c8138d0f55533132ee7710a06715a42a49f75efb39be3350ada4fa6e" diff --git a/willitmod-dev-bc2/docker-compose.yml.template b/willitmod-dev-bc2/docker-compose.yml.template index 1857129..939ecbf 100644 --- a/willitmod-dev-bc2/docker-compose.yml.template +++ b/willitmod-dev-bc2/docker-compose.yml.template @@ -92,7 +92,7 @@ services: - /bin/sh - /opt/axebc2/ckpool-entrypoint.sh app: - image: ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.11-candidate.ecf6e2c8cfd0@sha256:23a7962e223da5549eba52697c6f4cfa16ab74cba935c68c48148a4c515302b4 + image: ghcr.io/willitmod/axebc2-app-umbrel-dev:0.1.12-candidate.3b893173de7b@sha256:5defc8ac3c1d6e188959ed5c0642165e66108701c5ce3881e909c0994c8e3189 user: 1000:1000 restart: on-failure stop_grace_period: 30s @@ -116,7 +116,7 @@ services: environment: NETWORK_IP: ${NETWORK_IP} STATIC_DIR: /app/static - APP_CHANNEL: ALPHA + APP_CHANNEL: BETA APP_VERSION_SUFFIX: -dev BTC2D_IMAGE: ghcr.io/willitmod/bitcoinii-core:31.1.0-rc.cdf44542dde2@sha256:8875917ece57668fe9925d40a256ce8d429a3071511bb555d4ace1fa4370afc6 CKPOOL_IMAGE: ghcr.io/willitmod/docker-ckpool-solo:590fb2a@sha256:8a9a7f10c8138d0f55533132ee7710a06715a42a49f75efb39be3350ada4fa6e diff --git a/willitmod-dev-bc2/umbrel-app.yml b/willitmod-dev-bc2/umbrel-app.yml index 9beb44d..a330074 100644 --- a/willitmod-dev-bc2/umbrel-app.yml +++ b/willitmod-dev-bc2/umbrel-app.yml @@ -5,14 +5,10 @@ name: AxeBC2 version: "0.1.12-dev" tagline: BC2 node + solo pool description: >- - ALPHA RELEASE (DEV CHANNEL) + BETA RELEASE (DEV CHANNEL) - AxeBC2 is functional and has successfully found blocks, but it is still under - active development. Expect rough edges, occasional breaking changes, and - mining/pool behavior that may still need tuning. - - Use this build if you are happy to test, mine, and send helpful feedback. - For known-good production setups, use stable/main channel apps. + AxeBC2 is a functional BitcoinII node and solo pool that has successfully + found blocks. This BETA build includes the latest fixes and refinements. Run a BitcoinII (BC2) full node and an integrated solo Stratum v1 pool (ckpool) as a single app. @@ -47,10 +43,12 @@ path: "" defaultUsername: "" defaultPassword: "" releaseNotes: >- + Moves the DEV release to BETA and removes the general ALPHA/BETA warning + banner. The release stage remains visible beside the version. Fixes Umbrel installation by removing its dependency on the 5tratumOS host build.json file. 5tratumOS keeps its existing startup recipe and OS version - check. This packaging-only update retains the 0.1.11 application image, - Core 31 migration checks, payout address, and existing blockchain data. + check. The 0.1.12 application update retains Core 31 migration checks, + the payout address, and existing blockchain data. Included fixes from 0.1.11: Corrects payout-address validation: BitcoinII Core-accepted mainnet addresses