From 04d965d67a7264a1c7c211494aebda1953df7603 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Fri, 7 Aug 2026 18:25:13 +0000 Subject: Initial push --- scripts/release-check.sh | 343 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100755 scripts/release-check.sh (limited to 'scripts/release-check.sh') diff --git a/scripts/release-check.sh b/scripts/release-check.sh new file mode 100755 index 0000000..53f6c61 --- /dev/null +++ b/scripts/release-check.sh @@ -0,0 +1,343 @@ +#!/usr/bin/env bash +# MainAX25-Stack (MAX25-Stack-v1.5.0) — release gates (offline, no root). +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 "======== MainAX25-Stack release-check ($(tr -d '[:space:]' < VERSION)) ========" + +# --- version & manifest --- +if [[ -f VERSION ]]; then + VER="$(tr -d '[:space:]' < VERSION)" + [[ -n "$VER" ]] && ok "VERSION=${VER}" || fail "VERSION file empty" +else + fail "VERSION file missing" +fi + +if [[ -f plugins/manifest.yaml ]]; then + ok "plugins/manifest.yaml" + grep -q 'default_betriebsform: standalone' plugins/manifest.yaml \ + && ok "default operating mode: standalone" \ + || fail "manifest missing default operating mode (default_betriebsform: standalone)" +else + fail "plugins/manifest.yaml missing" +fi + +# --- required docs --- +for doc in README.md CONTRIBUTING.md docs/README.md docs/ARCHITECTURE.md docs/DEVELOPMENT.md docs/HYBBX.md docs/PLATFORMS.md docs/V1.0.0-SCOPE.md docs/BAYCOM.md docs/CRDOP.md docs/MERGE-REPORT.md docs/MAX25-TERMINAL.md docs/MAX25-CLIENT.md docs/LINUX-HOST-SETUP.md docs/PACKET-RADIO.md stacks/daemon/README.md; do + [[ -f "$doc" ]] && ok "doc $doc" || fail "missing $doc" +done + +# --- scripts executable --- +for script in scripts/discover-plugins.sh scripts/build-all.sh scripts/build.sh scripts/test.sh scripts/clean.sh scripts/release-check.sh scripts/max25-ctl scripts/install-max25.sh scripts/tx-rx-test.sh scripts/terminology-check.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 + +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 + +if [[ -f stacks/daemon/kiss_bridge.py ]]; then + ok "stacks/daemon/kiss_bridge.py" +else + fail "missing stacks/daemon/kiss_bridge.py" +fi + +if [[ -f stacks/daemon/ax25_codec.py ]]; then + ok "stacks/daemon/ax25_codec.py (native AX.25 codec)" +else + fail "missing stacks/daemon/ax25_codec.py" +fi + +if [[ -f stacks/daemon/privilege_drop.py ]]; then + ok "stacks/daemon/privilege_drop.py" +else + fail "missing stacks/daemon/privilege_drop.py" +fi + +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 + +if [[ -f share/max25/max25d.ini.example ]]; then + ok "share/max25/max25d.ini.example" +else + fail "missing share/max25/max25d.ini.example" +fi + +if [[ -f share/max25/max25d.ini.host.example ]]; then + ok "share/max25/max25d.ini.host.example" +else + fail "missing share/max25/max25d.ini.host.example" +fi + +if [[ -f share/baycom/README.md ]] && { [[ -f stacks/max25-bcpr/share/max25-bcpr.ini.example ]] || [[ -f share/max25-bcpr/max25-bcpr.ini.example ]]; }; then + ok "BayCom/based bcpr example + share/baycom README" +else + fail "missing max25-bcpr.ini.example or share/baycom/README.md" +fi + +if [[ -f share/clients/index.yaml ]] && [[ -f share/clients/tnc2c.yaml ]]; then + ok "share/clients registry" +else + fail "missing share/clients/ (index.yaml or tnc2c.yaml)" +fi + +if [[ -f local/README.md ]]; then + ok "local/README.md (gitignored site overrides)" +else + fail "missing local/README.md" +fi + +if [[ -f include/max25/protocol.md ]]; then + ok "include/max25/protocol.md" +else + fail "missing include/max25/protocol.md" +fi + +if [[ -f include/max25/packet-radio.md ]]; then + ok "include/max25/packet-radio.md" +else + fail "missing include/max25/packet-radio.md" +fi + +# --- plugin count --- +YAML_COUNT="$(find plugins -name plugin.yaml | wc -l | tr -d ' ')" +if [[ "$YAML_COUNT" -ge 10 ]]; then + ok "plugin.yaml count: ${YAML_COUNT}" +else + fail "expected >=10 plugin.yaml, got ${YAML_COUNT}" +fi + +# --- v1 active devices in manifest --- +for dev in tnc2c soft-crdop; do + if grep -A6 "id: ${dev}" plugins/manifest.yaml | grep -q 'status: active'; then + ok "device ${dev} status: active" + else + fail "device ${dev} not active in manifest" + fi +done + +if grep -A6 "id: soft-modems" plugins/manifest.yaml | grep -q 'optional: true'; then + fail "hardware soft-modems should not be optional-only" +else + ok "hardware soft-modems is MAX25-SoftModem standard group" +fi + +# --- planned/scaffold devices stay out of v1 active set --- +for dev in pktnc2; do + if grep -A6 "id: ${dev}" plugins/manifest.yaml | grep -Eq 'status: (planned|scaffold)'; then + ok "device ${dev} correctly non-active" + else + warn "device ${dev} status unexpected (should be planned/scaffold)" + fi +done + +if grep -A6 "id: baycom-kiss" plugins/manifest.yaml | grep -q 'status: active'; then + ok "device baycom-kiss status: active (USB/async KISS)" +else + warn "device baycom-kiss status unexpected" +fi + +# kernel baycom-ser12 / baycom-par96 removed 2026-07-18 +if grep -q 'id: baycom-ser12' plugins/manifest.yaml || grep -q 'id: baycom-par96' plugins/manifest.yaml; then + fail "legacy kernel baycom-ser12/par96 still in manifest (use bcpr)" +else + ok "manifest free of kernel baycom-ser12/par96" +fi + +# --- HyBBX INI templates (stack-local) --- +for ini in stacks/tncs/hybbx-tnc2c.ini \ + stacks/tncs/hybbx-dual.ini \ + share/hybbx/tnc2c-host.ini.example \ + share/hybbx/crdop-host.ini.example; do + [[ -f "$ini" ]] && ok "ini $ini" || fail "missing $ini" +done +[[ -d stacks/max25-bcpr ]] && ok "stacks/max25-bcpr (BayCom/based)" || fail "missing stacks/max25-bcpr" + +# --- build artifacts (CMake) --- +BUILD_DIR="build" +if ! cmake -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE=Release \ + -DMAX25_BUNDLE_AX25=OFF >/dev/null 2>&1; then + BUILD_DIR="build-default" + cmake -B "${BUILD_DIR}" -DCMAKE_BUILD_TYPE=Release \ + -DMAX25_BUNDLE_AX25=OFF >/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/, CRDOP default ON)" +else + fail "cmake build" +fi +[[ -x "${BUILD_DIR}/bin/tnc2c-probe" ]] && ok "tnc2c-probe built" || fail "tnc2c-probe build" +if [[ -x "${BUILD_DIR}/bin/max25-bcprd" ]]; then + ok "max25-bcprd built" +else + ok "max25-bcprd not built (check cmake MAX25_BUILD_MAX25_BCPR)" +fi +[[ -x "${BUILD_DIR}/bin/max25-terminal" ]] && ok "max25-terminal built" || fail "max25-terminal build" +[[ -f stacks/crdop/share/crdop.ini.example ]] && ok "CRDOP scaffold (MAX25-SoftModem)" || fail "CRDOP scaffold missing" +[[ -f plugins/external/ardop/plugin.yaml ]] && ok "ARDOP-plugin metadata" || fail "missing plugins/external/ardop/plugin.yaml" +if [[ -x "${BUILD_DIR}/bin/crdopc" ]]; then + warn "vendor crdopc binary present (dev-only CRDOP_VENDOR_ARDOPCF build — not for release)" +else + ok "no vendor crdopc binary (native SoftModem default)" +fi +if [[ -x "${BUILD_DIR}/bin/audio-dummyd" ]]; then + ok "audio-dummyd binary (native SoftModem host)" +else + ok "audio-dummyd not in build dir (launcher uses source tree path)" +fi + +# --- ARDOP must not appear in default install tree --- +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 f -exec grep -l 'ardopcf' {} + 2>/dev/null | grep -q .; then + warn "install tree contains files mentioning ardopcf (review)" + else + ok "install tree has no ardopcf references" + 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 + ok "max25-bcpr dry-run skipped (not built)" +fi + +if [[ -x "${BUILD_DIR}/bin/crdopc" ]] && CRDOP_BIN="${PWD}/${BUILD_DIR}/bin/crdopc" bash stacks/crdop/scripts/test-smoke.sh >/dev/null 2>&1; then + ok "crdop smoke (dev vendor build only)" +elif [[ -x "${BUILD_DIR}/bin/crdopc" ]]; then + fail "crdop smoke (vendor crdopc present but smoke failed)" +else + ok "crdop vendor smoke skipped — native SoftModem default" +fi + +# --- discovery --- +if bash scripts/discover-plugins.sh --json >/dev/null 2>&1; then + ok "discover-plugins --json" +else + fail "discover-plugins --json" +fi + +# --- max25d + terminal --- +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 + +if bash scripts/tx-rx-test.sh >/dev/null 2>&1; then + ok "tx-rx-test L0 (TNC + BayCom/based)" +else + fail "tx-rx-test L0 (TNC + BayCom/based)" +fi + +if [[ -f docs/TX-RX-TEST.md ]]; then + ok "doc docs/TX-RX-TEST.md" +else + fail "missing docs/TX-RX-TEST.md" +fi + +if python3 stacks/tncs/test_tnc_serial_recovery.py >/dev/null 2>&1; then + ok "tnc serial recovery unit tests" +else + fail "tnc serial recovery unit tests" +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 + +[[ -x stacks/tncs/tnc2c-host-reset.sh ]] && ok "tnc2c-host-reset.sh" || fail "tnc2c-host-reset.sh" + +# --- 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" +fi + +# --- CI workflow --- +[[ -f .github/workflows/ci.yml ]] && ok "ci workflow" || fail "missing .github/workflows/ci.yml" + +# --- tncs probe (warn without hardware) --- +if "${BUILD_DIR}/bin/tnc2c-probe" >/dev/null 2>&1; then + ok "tnc2c-probe (serial found)" +else + warn "tnc2c-probe: no serial devices (OK for CI without hardware)" +fi + +echo "======== release-check: ${PASS} passed, ${FAIL} failed, ${WARN} warnings ========" +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi +exit 0 -- cgit v1.3.1