1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
#!/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
|