summaryrefslogtreecommitdiff
path: root/scripts/release-check.sh
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
commit04d965d67a7264a1c7c211494aebda1953df7603 (patch)
tree0ebd700a6e219f84a26a656f4bee8778bc75da7b /scripts/release-check.sh
Initial push
Diffstat (limited to 'scripts/release-check.sh')
-rwxr-xr-xscripts/release-check.sh343
1 files changed, 343 insertions, 0 deletions
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
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com