#!/bin/sh # ─────────────────────────────────────────────────────────── # tty-menu.sh — Serial Console Menu for the Alpine Docker Host # # Launched by agetty on ttyS0. The user interacts directly # with the Ansible container from the serial console. # # Options: # 1-4 Run playbooks (docker run ansible-node) # 5 Download reports as tar.gz # 6 View latest report summary # 7 Shell into Ansible container # 8 Shell on Docker host # 0 Shutdown VM # ─────────────────────────────────────────────────────────── PLAYBOOKS_DIR="/ansible/playbooks" REPORTS_DIR="/ansible/reports" INVENTORY="/ansible/inventory/inventory.ini" IMAGE="ansible-node" WEB_PORT="8080" # ── Colour helpers ──────────────────────────────────────── GREEN='\033[0;32m' BLUE='\033[0;34m' YELLOW='\033[1;33m' RED='\033[0;31m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' clear while true; do # Determine IP for web UI hint IP=$(ip -4 addr show scope global 2>/dev/null | \ grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1) [ -z "$IP" ] && IP="(no network)" echo "" echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}${BOLD}║ IEC 62443-3-3 SL2 Compliance Validator ║${NC}" echo -e "${GREEN}${BOLD}╠══════════════════════════════════════════════════╣${NC}" echo -e "${GREEN}${BOLD}║${NC} Web UI: ${CYAN}http://${IP}:${WEB_PORT}${GREEN} ${NC}${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}╠══════════════════════════════════════════════════╣${NC}" echo -e "${GREEN}${BOLD}║${NC} ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}1)${NC} Run all tests (site.yml) ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}2)${NC} Run FR1 — Auth & Identification ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}3)${NC} Run FR2 — Use Control ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}4)${NC} Run FR5 — Restricted Data Flow ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}5)${NC} Create reports archive (tar.gz) ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}6)${NC} View latest report summary ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}7)${NC} Shell — Ansible container ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}8)${NC} Shell — Docker host ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}║${NC} ${BOLD}0)${NC} Shutdown VM ${GREEN}${BOLD}║${NC}" echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════╝${NC}" echo "" printf " Choice [0-8]: " read -r CHOICE docker_run() { local pb="$1"; shift echo "" echo -e "${YELLOW}Running: ${pb}${NC}" echo "═══════════════════════════════════════════" docker run --rm -it \ -v "${PLAYBOOKS_DIR}:/ansible/playbooks:ro" \ -v "${REPORTS_DIR}:/ansible/reports" \ -v "$(dirname "${INVENTORY}"):/ansible/inventory:ro" \ "${IMAGE}" \ "/ansible/playbooks/${pb}" \ -i /ansible/inventory/inventory.ini "$@" echo "" echo -e "${GREEN}Done. Reports: ${REPORTS_DIR}${NC}" echo "Press Enter to continue..." read -r _ } case "$CHOICE" in 1) docker_run "site.yml" ;; 2) docker_run "suites/fr1_auth.yml" ;; 3) docker_run "suites/fr2_use_control.yml" ;; 4) docker_run "suites/fr5_data_flow.yml" ;; 5) echo "" echo -e "${YELLOW}Available reports:${NC}" ls -lh "${REPORTS_DIR}"/*.json 2>/dev/null || echo " No reports yet." echo "" printf " Enter filename (or Enter for all as tar.gz): " read -r FN if [ -n "$FN" ]; then OUT="/tmp/${FN}" cp "${REPORTS_DIR}/${FN}" "$OUT" 2>/dev/null && \ echo -e " Written: ${GREEN}${OUT}${NC}" || \ echo -e "${RED} Not found: ${FN}${NC}" else OUTF="/tmp/reports-$(date +%Y%m%d-%H%M).tar.gz" cd "${REPORTS_DIR}" && tar czf "$OUTF" *.json 2>/dev/null && \ echo -e " Created: ${GREEN}${OUTF}${NC} ($(du -sh "$OUTF" | cut -f1))" fi echo " Transfer via: scp ansible@:/tmp/reports-*.tar.gz ." echo "" echo "Press Enter to continue..." read -r _ ;; 6) LATEST=$(ls -t "${REPORTS_DIR}"/*.json 2>/dev/null | head -1) if [ -z "$LATEST" ]; then echo -e "${RED} No reports yet.${NC}" else echo "" echo -e "${YELLOW}Latest: $(basename "$LATEST")${NC}" echo "═══════════════════════════════════════════" python3 -c " import json with open('$LATEST') as f: r = json.load(f) s = r['summary'] print(f'Total: {s[\"total\"]} | Passed: {s[\"passed\"]} | Failed: {s[\"failed\"]}') print(f'Compliance rate: {s[\"passed\"]/s[\"total\"]*100:.1f}%') print() for t in r['results']: icon = '\u2705' if t['passed'] == True else ('\u274c' if t['passed'] == False else '\U0001f50d') print(f' {icon} [{t[\"test_id\"]}] {t[\"description\"]}') print() if r.get('failures'): print('Failures:') for f in r['failures']: print(f' \u274c {f[\"test_id\"]}: {f[\"description\"]}') print(f' Expected: {f[\"expected\"]}') print(f' Actual: {f[\"actual\"]}') print(f' Fix: {f[\"remediation\"]}') " 2>/dev/null || echo " Error reading report" fi echo "" echo "Press Enter to continue..." read -r _ ;; 7) echo "" echo -e "${YELLOW}Ansible container shell (type 'exit' to return)${NC}" echo "═══════════════════════════════════════════" docker run --rm -it \ -v "${PLAYBOOKS_DIR}:/ansible/playbooks:ro" \ -v "${REPORTS_DIR}:/ansible/reports" \ -v "$(dirname "${INVENTORY}"):/ansible/inventory:ro" \ "${IMAGE}" /bin/bash 2>/dev/null || \ docker run --rm -it \ -v "${PLAYBOOKS_DIR}:/ansible/playbooks:ro" \ -v "${REPORTS_DIR}:/ansible/reports" \ -v "$(dirname "${INVENTORY}"):/ansible/inventory:ro" \ "${IMAGE}" /bin/sh ;; 8) echo "" echo -e "${YELLOW}Host shell (type 'exit' to return to menu)${NC}" echo "═══════════════════════════════════════════" /bin/bash 2>/dev/null || /bin/sh ;; 0) echo "" echo -e "${RED}Shutting down...${NC}" sudo poweroff 2>/dev/null || poweroff exit 0 ;; *) echo -e "${RED}Invalid choice${NC}" sleep 1 ;; esac done