Add smoke test script and expand manufacturing workflow
This commit is contained in:
@@ -10,6 +10,7 @@ This documents the *current* workflow and the *target* workflow once profiles +
|
|||||||
3) Update the system if needed.
|
3) Update the system if needed.
|
||||||
4) Run the prep scrub + verify:
|
4) Run the prep scrub + verify:
|
||||||
- `sudo ./pikit-prep.sh`
|
- `sudo ./pikit-prep.sh`
|
||||||
|
- `./pikit-smoke-test.sh`
|
||||||
- (optional) `sudo ./pikit-prep.sh --check-only`
|
- (optional) `sudo ./pikit-prep.sh --check-only`
|
||||||
5) Image the SD card with DietPi Imager.
|
5) Image the SD card with DietPi Imager.
|
||||||
6) Store it as the golden base (e.g., `images/base/pikit-base-YYYYMMDD.img.xz`).
|
6) Store it as the golden base (e.g., `images/base/pikit-base-YYYYMMDD.img.xz`).
|
||||||
@@ -26,6 +27,7 @@ This documents the *current* workflow and the *target* workflow once profiles +
|
|||||||
- `sudo ufw allow from <LAN subnet> to any port <port>`
|
- `sudo ufw allow from <LAN subnet> to any port <port>`
|
||||||
6) Run the prep scrub + verify:
|
6) Run the prep scrub + verify:
|
||||||
- `sudo ./pikit-prep.sh`
|
- `sudo ./pikit-prep.sh`
|
||||||
|
- `./pikit-smoke-test.sh`
|
||||||
- (optional) `sudo ./pikit-prep.sh --check-only`
|
- (optional) `sudo ./pikit-prep.sh --check-only`
|
||||||
7) Image the SD card via the QEMU DietPi VM:
|
7) Image the SD card via the QEMU DietPi VM:
|
||||||
- Insert the SD card into your desktop.
|
- Insert the SD card into your desktop.
|
||||||
@@ -54,6 +56,7 @@ This documents the *current* workflow and the *target* workflow once profiles +
|
|||||||
- Confirms services + ports match the profile + base.
|
- Confirms services + ports match the profile + base.
|
||||||
6) Run the prep scrub + verify:
|
6) Run the prep scrub + verify:
|
||||||
- `sudo ./pikit-prep.sh`
|
- `sudo ./pikit-prep.sh`
|
||||||
|
- `./pikit-smoke-test.sh`
|
||||||
- (optional) `sudo ./pikit-prep.sh --check-only`
|
- (optional) `sudo ./pikit-prep.sh --check-only`
|
||||||
7) Image the SD card with DietPi Imager.
|
7) Image the SD card with DietPi Imager.
|
||||||
|
|
||||||
@@ -74,7 +77,7 @@ Use the helper:
|
|||||||
- `sudo ./flash_sd.sh <image.img.xz> /dev/sdX`
|
- `sudo ./flash_sd.sh <image.img.xz> /dev/sdX`
|
||||||
|
|
||||||
## 4) Manufacturing / imaging checklist (production)
|
## 4) Manufacturing / imaging checklist (production)
|
||||||
1) Start from the golden base image.
|
1) Start from the golden base image (stored in `images/base/`).
|
||||||
2) Flash it to a known‑good SD card.
|
2) Flash it to a known‑good SD card.
|
||||||
3) Boot and verify:
|
3) Boot and verify:
|
||||||
- `http://pikit.local` and `https://pikit.local`
|
- `http://pikit.local` and `https://pikit.local`
|
||||||
@@ -83,9 +86,13 @@ Use the helper:
|
|||||||
4) Apply any required profile/services.
|
4) Apply any required profile/services.
|
||||||
5) Run prep + verify:
|
5) Run prep + verify:
|
||||||
- `sudo ./pikit-prep.sh`
|
- `sudo ./pikit-prep.sh`
|
||||||
|
- `./pikit-smoke-test.sh`
|
||||||
6) Power down cleanly.
|
6) Power down cleanly.
|
||||||
7) Image the SD card (DietPi Imager via QEMU or on‑device).
|
7) Image the SD card (DietPi Imager via QEMU or on‑device).
|
||||||
8) Label and archive the image (include date + profile name).
|
8) Name and archive the image:
|
||||||
|
- Base: `images/base/pikit-base-YYYYMMDD-dietpi9.20.1.img.xz`
|
||||||
|
- Profile: `images/profiles/pikit-<profile>-YYYYMMDD.img.xz`
|
||||||
|
- Testing/staging: `images/staging/pikit-<profile>-YYYYMMDD-rcN.img.xz`
|
||||||
9) Smoke test the flashed image on a second SD card:
|
9) Smoke test the flashed image on a second SD card:
|
||||||
- boot → first‑boot → dashboard → services
|
- boot → first‑boot → dashboard → services
|
||||||
|
|
||||||
|
|||||||
216
pikit-smoke-test.sh
Executable file
216
pikit-smoke-test.sh
Executable file
@@ -0,0 +1,216 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Pi-Kit post-prep smoke test (HTTP/HTTPS/API/firstboot/services)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PIKIT_HOST="${PIKIT_HOST:-pikit.local}"
|
||||||
|
PIKIT_USER="${PIKIT_USER:-dietpi}"
|
||||||
|
PIKIT_SSH_KEY="${PIKIT_SSH_KEY:-$HOME/.ssh/pikit}"
|
||||||
|
PIKIT_SSH_OPTS="${PIKIT_SSH_OPTS:-}"
|
||||||
|
PIKIT_HTTP_URL="${PIKIT_HTTP_URL:-http://$PIKIT_HOST}"
|
||||||
|
PIKIT_HTTPS_URL="${PIKIT_HTTPS_URL:-https://$PIKIT_HOST}"
|
||||||
|
|
||||||
|
LOCAL_ONLY=0
|
||||||
|
ERRORS=0
|
||||||
|
WARNINGS=0
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage: pikit-smoke-test.sh [--local]
|
||||||
|
|
||||||
|
Runs a quick post-prep smoke test:
|
||||||
|
- HTTP/HTTPS reachable
|
||||||
|
- API reachable and returns JSON
|
||||||
|
- firstboot state done
|
||||||
|
- core services active (nginx, pikit-api, dietpi-dashboard-frontend)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--local Run locally on the Pi (skip SSH)
|
||||||
|
--help Show this help
|
||||||
|
|
||||||
|
Env:
|
||||||
|
PIKIT_HOST, PIKIT_USER, PIKIT_SSH_KEY, PIKIT_SSH_OPTS
|
||||||
|
PIKIT_HTTP_URL, PIKIT_HTTPS_URL
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
printf '[%s] %s\n' "$level" "$*"
|
||||||
|
case "$level" in
|
||||||
|
FAIL) ERRORS=$((ERRORS + 1)) ;;
|
||||||
|
WARN) WARNINGS=$((WARNINGS + 1)) ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
section() {
|
||||||
|
printf '\n== %s ==\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_dietpi() {
|
||||||
|
grep -qi "dietpi" /etc/os-release 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--local) LOCAL_ONLY=1 ;;
|
||||||
|
--help|-h) usage; exit 0 ;;
|
||||||
|
*)
|
||||||
|
echo "[FAIL] Unknown argument: $arg" >&2
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
if [ "$LOCAL_ONLY" -eq 1 ] || is_dietpi; then
|
||||||
|
bash -c "$cmd"
|
||||||
|
else
|
||||||
|
ssh -i "$PIKIT_SSH_KEY" $PIKIT_SSH_OPTS -o ConnectTimeout=10 "${PIKIT_USER}@${PIKIT_HOST}" "$cmd"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
json_get() {
|
||||||
|
local key="$1"
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$key" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
key = sys.argv[1]
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
print("")
|
||||||
|
sys.exit(1)
|
||||||
|
val = data.get(key, "")
|
||||||
|
if isinstance(val, bool):
|
||||||
|
print("true" if val else "false")
|
||||||
|
else:
|
||||||
|
print(val)
|
||||||
|
PY
|
||||||
|
elif command -v jq >/dev/null 2>&1; then
|
||||||
|
jq -r --arg key "$key" '.[$key] // empty'
|
||||||
|
else
|
||||||
|
cat >/dev/null
|
||||||
|
echo ""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_http() {
|
||||||
|
local url="$1"
|
||||||
|
local label="$2"
|
||||||
|
if curl -fsS --max-time 5 "$url" >/dev/null; then
|
||||||
|
status OK "$label reachable"
|
||||||
|
else
|
||||||
|
status FAIL "$label not reachable"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_https() {
|
||||||
|
local url="$1"
|
||||||
|
local label="$2"
|
||||||
|
if curl -kfsS --max-time 5 "$url" >/dev/null; then
|
||||||
|
status OK "$label reachable"
|
||||||
|
else
|
||||||
|
status FAIL "$label not reachable"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_api() {
|
||||||
|
local url="$1"
|
||||||
|
local body
|
||||||
|
if ! body="$(curl -fsS --max-time 5 "$url")"; then
|
||||||
|
status FAIL "API not reachable: $url"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local ok
|
||||||
|
ok="$(printf "%s" "$body" | json_get "services" || true)"
|
||||||
|
if [ -n "$ok" ]; then
|
||||||
|
status OK "API responds with JSON"
|
||||||
|
else
|
||||||
|
status WARN "API response did not include expected fields"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_firstboot() {
|
||||||
|
local url="$1"
|
||||||
|
local body state error_present
|
||||||
|
if ! body="$(curl -fsS --max-time 5 "$url")"; then
|
||||||
|
status FAIL "firstboot API not reachable"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
state="$(printf "%s" "$body" | json_get "state" || true)"
|
||||||
|
error_present="$(printf "%s" "$body" | json_get "error_present" || true)"
|
||||||
|
if [ "$state" = "done" ] && [ "$error_present" != "true" ]; then
|
||||||
|
status OK "firstboot completed"
|
||||||
|
else
|
||||||
|
status FAIL "firstboot not complete (state=$state error=$error_present)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_services() {
|
||||||
|
local services=("nginx" "pikit-api" "dietpi-dashboard-frontend")
|
||||||
|
for svc in "${services[@]}"; do
|
||||||
|
if remote_cmd "systemctl is-active --quiet $svc"; then
|
||||||
|
status OK "$svc active"
|
||||||
|
else
|
||||||
|
status FAIL "$svc not active"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
check_ports() {
|
||||||
|
local cmd="ss -lnt | awk '{print \$4}' | grep -E ':(80|443|5252|5253)\$' | sort -u"
|
||||||
|
local out
|
||||||
|
if out="$(remote_cmd "$cmd" 2>/dev/null)"; then
|
||||||
|
if echo "$out" | grep -q ":80" && echo "$out" | grep -q ":443"; then
|
||||||
|
status OK "ports 80/443 listening"
|
||||||
|
else
|
||||||
|
status WARN "ports 80/443 not both listening"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
status WARN "unable to check ports"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
section "Summary"
|
||||||
|
status OK "warnings: $WARNINGS"
|
||||||
|
status OK "errors: $ERRORS"
|
||||||
|
if [ "$ERRORS" -gt 0 ]; then
|
||||||
|
echo "[FAIL] Smoke test failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[OK] Smoke test passed."
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
section "HTTP/HTTPS"
|
||||||
|
check_http "$PIKIT_HTTP_URL" "HTTP"
|
||||||
|
check_https "$PIKIT_HTTPS_URL" "HTTPS"
|
||||||
|
|
||||||
|
section "API"
|
||||||
|
check_api "$PIKIT_HTTP_URL/api/status"
|
||||||
|
|
||||||
|
section "Firstboot"
|
||||||
|
check_firstboot "$PIKIT_HTTP_URL/api/firstboot"
|
||||||
|
|
||||||
|
section "Services"
|
||||||
|
check_services
|
||||||
|
|
||||||
|
section "Ports"
|
||||||
|
check_ports
|
||||||
|
|
||||||
|
finalize
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user