Add TTY menu, Web UI, split Dockerfiles (host + ansible)

This commit is contained in:
2026-07-07 17:13:48 +00:00
parent 3db30a0a4d
commit 2217184b4d
+174
View File
@@ -0,0 +1,174 @@
#!/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@<host>:/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