#!/usr/bin/env bash # release-check.sh — MAX25-Stack-Lite offline release gates (no root, no live RF TX). set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" PASS=0 FAIL=0 WARN=0 ok() { echo "OK: $*"; PASS=$((PASS + 1)); } fail() { echo "FAIL: $*" >&2; FAIL=$((FAIL + 1)); } warn() { echo "WARN: $*"; WARN=$((WARN + 1)); } echo "======== MAX25-Stack-Lite release-check ($(tr -d '[:space:]' < VERSION 2>/dev/null || echo '?')) ========" # --- version --- if [[ -f VERSION ]]; then VER="$(tr -d '[:space:]' < VERSION)" if [[ "$VER" == "1.8.0" ]]; then ok "VERSION=${VER}" elif [[ -n "$VER" ]]; then fail "VERSION=${VER} (expected 1.8.0)" else fail "VERSION file empty" fi else fail "VERSION file missing" fi # --- required docs (Lite ships a stub docs/, depth lives in the research vault) --- for doc in README.md CONTRIBUTING.md CHANGELOG.md docs/README.md local/README.md stacks/daemon/README.md stacks/max25-bcpr/README.md include/max25/protocol.md include/max25/packet-radio.md; do [[ -f "$doc" ]] && ok "doc $doc" || fail "missing $doc" done # --- scripts present + executable (Lite scope only) --- for script in scripts/max25-lite-setup.sh scripts/test-wizard.sh scripts/run-max25d.sh scripts/run-max25-terminal.sh scripts/max25-ctl scripts/discover-plugins.sh scripts/build.sh scripts/build-all.sh scripts/clean.sh scripts/test.sh scripts/release-check.sh scripts/tx-rx-test.sh scripts/terminology-check.sh scripts/install-max25.sh; do if [[ -x "$script" ]]; then ok "executable $script" else fail "not executable: $script" fi done # --- public terminology gate (§0.16 / §0.19) --- if [[ -x scripts/terminology-check.sh ]]; then if scripts/terminology-check.sh; then ok "terminology-check" else fail "terminology-check" fi else fail "scripts/terminology-check.sh missing or not executable" fi # --- daemon core files --- if [[ -x stacks/daemon/max25d ]] && [[ -f stacks/daemon/max25d.py ]]; then ok "executable stacks/daemon/max25d launcher + max25d.py" else fail "stacks/daemon/max25d launcher or max25d.py missing" fi for f in kiss_bridge.py ax25_codec.py privilege_drop.py; do [[ -f "stacks/daemon/${f}" ]] && ok "stacks/daemon/${f}" || fail "missing stacks/daemon/${f}" done if python3 stacks/daemon/test_ax25_codec.py >/dev/null 2>&1; then ok "ax25_codec unit tests" else fail "ax25_codec unit tests" fi # --- Lite INI examples (localhost-only model) --- for ini in share/max25/max25d.ini.example share/max25/max25d.lite.ini.example share/max25/max25d.lite.bcpr.ini.example share/max25/max25d.lite.crdop.ini.example share/max25-bcpr/max25-bcpr.ini.example stacks/max25-bcpr/share/max25-bcpr.ini.example stacks/crdop/share/crdop.ini.example; do [[ -f "$ini" ]] && ok "ini $ini" || fail "missing $ini" done # --- localhost TCP in shipped max25d examples --- for ini in share/max25/max25d.ini.example share/max25/max25d.lite.ini.example share/max25/max25d.lite.bcpr.ini.example share/max25/max25d.lite.crdop.ini.example; do if grep -Eq '^[[:space:]]*tcp_host[[:space:]]*=[[:space:]]*0\.0\.0\.0[[:space:]]*$' "$ini"; then fail "ini $ini has tcp_host=0.0.0.0 (Lite localhost-only)" elif grep -Eq '^[[:space:]]*tcp_host[[:space:]]*=[[:space:]]*127\.0\.0\.1[[:space:]]*$' "$ini"; then ok "ini $ini tcp_host=127.0.0.1" else fail "ini $ini missing tcp_host=127.0.0.1" fi done if [[ -f share/clients/index.yaml ]] && [[ -f share/clients/tnc2c.yaml ]]; then ok "share/clients registry" else warn "share/clients/ (index.yaml / tnc2c.yaml) missing (informational only in Lite)" fi # --- out-of-scope surfaces must stay inert (HyBBX co-host, hardware TNC stacks, plugin tree) --- if [[ -d plugins ]]; then fail "plugins/ present — Lite configures devices directly in max25d.ini, no plugin tree expected" else ok "no plugins/ tree (Lite: direct INI device config)" fi if [[ -d stacks/tncs ]]; then fail "stacks/tncs/ present — TNC2C/pktnc2 hardware stack is out of scope for Lite" else ok "no stacks/tncs/ (hardware TNC stack out of scope for Lite)" fi if grep -rq 'hybbx' CMakeLists.txt 2>/dev/null; then fail "CMakeLists.txt references hybbx — HyBBX co-host build hooks are out of scope for Lite" else ok "CMakeLists.txt free of hybbx references" fi if [[ -f share/hybbx/README.md ]]; then ok "share/hybbx/README.md scope note present (kept for upstream sync, not a Lite path)" else warn "share/hybbx/ present without a scope note (see design-principles.md sync policy)" fi # --- build artifacts (CMake, Lite target set) --- BUILD_DIR="build" if ! cmake -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1; then BUILD_DIR="build-default" cmake -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 \ || fail "cmake configure" fi if cmake --build "${BUILD_DIR}" -j"$(nproc 2>/dev/null || echo 2)" >/dev/null 2>&1; then ok "cmake build (${BUILD_DIR}/bin/)" else fail "cmake build" fi [[ -x "${BUILD_DIR}/bin/max25-terminal" ]] && ok "max25-terminal built" || fail "max25-terminal build" if [[ -x "${BUILD_DIR}/bin/max25-bcprd" ]] && [[ -x "${BUILD_DIR}/bin/max25-bcprd-init" ]]; then ok "max25-bcprd + max25-bcprd-init built (default ON in Lite)" else fail "max25-bcprd / max25-bcprd-init not built (expected default ON — see cmake/MAX25Platform.cmake)" fi [[ -f stacks/crdop/share/crdop.ini.example ]] && ok "CRDOP scaffold" || fail "CRDOP scaffold missing" if [[ -x "${BUILD_DIR}/bin/audio-dummyd" ]]; then ok "audio-dummyd binary" else ok "audio-dummyd not in build dir (launcher uses source tree path)" fi # --- install tree hygiene: no vendor/ARDOP artifacts, no out-of-scope leakage --- INSTALL_DIR="${BUILD_DIR}/install-release-check" rm -rf "${INSTALL_DIR}" if cmake --install "${BUILD_DIR}" --prefix "${INSTALL_DIR}" >/dev/null 2>&1; then ok "cmake --install prefix" if find "${INSTALL_DIR}" \( -path '*/vendor/ardopcf/*' -o -name 'ardopcf' -o -name 'crdopc' \) 2>/dev/null | grep -q .; then fail "ARDOP vendor artifacts in install tree (forbidden in release)" else ok "install tree free of ARDOP vendor binaries/sources" fi if find "${INSTALL_DIR}" -type d -iname 'hybbx' 2>/dev/null | grep -q .; then fail "hybbx/ present in install tree (out of scope for Lite)" else ok "install tree free of hybbx/ directories" fi else warn "cmake --install skipped (non-fatal for dev trees)" fi # --- offline tests --- if [[ -x "${BUILD_DIR}/bin/max25-bcprd" ]]; then if "${BUILD_DIR}/bin/max25-bcprd" -c stacks/max25-bcpr/share/max25-bcpr.ini.example --dry-run --once >/dev/null 2>&1; then ok "max25-bcpr dry-run once" else fail "max25-bcpr dry-run once" fi else warn "max25-bcpr dry-run skipped (not built)" fi if python3 stacks/daemon/test_bcpr_backend.py >/dev/null 2>&1; then ok "bcpr backend unit tests" else fail "bcpr backend unit tests" fi if python3 stacks/daemon/test_crdop_backend.py >/dev/null 2>&1; then ok "crdop backend unit tests" else fail "crdop backend unit tests" fi if python3 stacks/daemon/test_paths.py >/dev/null 2>&1; then ok "daemon path layout unit tests" else fail "daemon path layout unit tests" fi if bash scripts/test-wizard.sh >/dev/null 2>&1; then ok "wizard non-interactive smoke (serial/baycom/crdop)" else fail "wizard non-interactive smoke" fi if bash scripts/discover-plugins.sh --type device >/dev/null 2>&1; then ok "discover-plugins.sh --type device" else warn "discover-plugins.sh --type device (no manifest — expected in Lite, see script output)" fi chmod +x stacks/daemon/max25d stacks/daemon/max25d.py 2>/dev/null || true [[ -x stacks/daemon/max25d ]] && [[ -f stacks/daemon/max25d.py ]] && ok "max25d ready" || fail "max25d" if [[ -L "${BUILD_DIR}/bin/max25-client" ]] || [[ -x "${BUILD_DIR}/bin/max25-client" ]]; then ok "max25-client symlink" else warn "max25-client symlink missing until cmake --install" fi if MAX25_TERMINAL="${PWD}/${BUILD_DIR}/bin/max25-terminal" \ bash stacks/terminal/test-terminal.sh >/dev/null 2>&1; then ok "max25-terminal TCP probe" else fail "max25-terminal TCP probe" fi if python3 stacks/daemon/test_proto.py >/dev/null 2>&1; then ok "max25d protocol smoke" else fail "max25d protocol smoke" fi if python3 stacks/daemon/test_auth.py >/dev/null 2>&1; then ok "max25d TCP auth smoke" else fail "max25d TCP auth smoke" fi # --- RX before TX gate (§0.20) — offline check only, no live RF TX here --- if bash scripts/tx-rx-test.sh >/dev/null 2>&1; then ok "tx-rx-test L0 offline (RX before TX posture)" else fail "tx-rx-test L0 offline" fi if python3 stacks/daemon/test_serial_watch.py >/dev/null 2>&1; then ok "max25d serial watch tests" else fail "max25d serial watch tests" fi # --- AmigaOS terminal (optional cross-build) --- if [[ -x /opt/amiga/bin/m68k-amigaos-gcc ]]; then if bash scripts/build-amiga-terminal.sh >/dev/null 2>&1; then ok "max25-terminal AmigaOS cross-build" else fail "max25-terminal AmigaOS cross-build" fi else warn "Amiga SDK not at /opt/amiga; skip Amiga terminal build (optional)" fi # --- CI workflow --- [[ -f .github/workflows/ci.yml ]] && ok "ci workflow" || warn "missing .github/workflows/ci.yml" echo "======== release-check: ${PASS} passed, ${FAIL} failed, ${WARN} warnings ========" if [[ "$FAIL" -gt 0 ]]; then exit 1 fi exit 0