#!/usr/bin/env bash set -Eeuo pipefail umask 022 # STEP010 - SERVER ATTESO: 215.6 / ax-accessi # Verifica finale della base demo presenze.distribuite: # - cartella applicativa; # - collegamenti alle librerie condivise; # - dati demo e dipendenze turno/ciclo; # - risposta HTTP del portale; # - configurazione Apache visibile. # Nessuna modifica a file, database o servizi. START_DIR="$PWD" STAMP="$(date '+%Y%m%d_%H%M%S')" BASE="STEP010_2156_DISTRIBUITE_FUNCTIONAL_READINESS_AUDIT_ONLY_${STAMP}" WORKDIR="${START_DIR}/.${BASE}_work_$$" OUTZIP="${START_DIR}/${BASE}.zip" LOGFILE="${WORKDIR}/run.log" REPORT="${WORKDIR}/report.txt" PACKAGED=0 FAILURES=0 WARNINGS=0 EXPECTED_HOST="${STEP_EXPECTED_HOST:-ax-accessi}" EXPECTED_IP="${STEP_EXPECTED_IP:-192.168.215.6}" SKIP_SERVER_CHECK="${STEP_SKIP_SERVER_CHECK:-0}" TARGET_DB="${STEP_TARGET_DB:-presenze_distribuite}" TARGET_DIR="${STEP_TARGET_DIR:-/var/www/html/presenze.distribuite}" PORTAL_URL="${STEP_PORTAL_URL:-https://presenze.distribuite.axuno.it/}" SHARED_RELEASE="${STEP_SHARED_RELEASE:-/var/www/presenze-shared-libs/releases/presenze-legacy-20260723-v1}" mkdir -p "$WORKDIR" : >"$LOGFILE" : >"$REPORT" log() { printf '%s\n' "$*" | tee -a "$LOGFILE"; } report() { printf '%s\n' "$*" >>"$REPORT"; } fail() { FAILURES=$((FAILURES + 1)); log "ERRORE: $*"; report "ERRORE: $*"; } warn() { WARNINGS=$((WARNINGS + 1)); log "ATTENZIONE: $*"; report "ATTENZIONE: $*"; } package_results() { local rc="${1:-0}" local zip_rc=0 [[ "$PACKAGED" -eq 1 ]] && return 0 PACKAGED=1 report "" report "Esito finale: errori=${FAILURES}, avvisi=${WARNINGS}, exit_code=${rc}" rm -f -- "$OUTZIP" if ! command -v zip >/dev/null 2>&1; then rm -rf -- "$WORKDIR" printf '%s\n' "ERRORE: comando zip non disponibile" >&2 return 127 fi (cd "$WORKDIR" && zip -qr "$OUTZIP" .) || zip_rc=$? if [[ "$zip_rc" -eq 0 ]]; then zip -T "$OUTZIP" >>"$LOGFILE" 2>&1 || zip_rc=$? fi rm -rf -- "$WORKDIR" if [[ "$zip_rc" -ne 0 ]]; then rm -f -- "$OUTZIP" printf '%s\n' "ERRORE: creazione/verifica ZIP fallita (rc=${zip_rc})" >&2 return "$zip_rc" fi printf '%s\n' "ZIP CREATO E VERIFICATO: $OUTZIP" } on_exit() { local rc=$? trap - EXIT INT TERM HUP package_results "$rc" || rc=$? exit "$rc" } trap on_exit EXIT trap 'exit 130' INT trap 'exit 143' TERM HUP log "$BASE" log "SERVER ATTESO: 215.6 / ax-accessi" log "MODALITA': AUDIT ONLY - NESSUNA MODIFICA" log "Avvio: $(date --iso-8601=seconds)" ACTUAL_HOST="$(hostname -s 2>/dev/null || hostname)" ACTUAL_IPS="$(hostname -I 2>/dev/null | xargs || true)" { echo "STEP: $BASE" echo "Server atteso: 215.6 / ax-accessi" echo "Modalita': AUDIT ONLY" echo "Data: $(date --iso-8601=seconds)" echo "Hostname: $ACTUAL_HOST" echo "IP: $ACTUAL_IPS" echo "Database: $TARGET_DB" echo "Portale: $PORTAL_URL" echo "Path: $TARGET_DIR" echo "Shared release: $SHARED_RELEASE" echo } >>"$REPORT" for cmd in grep sed awk wc sort zip hostname xargs tr cut readlink find stat; do command -v "$cmd" >/dev/null 2>&1 || fail "comando richiesto non disponibile: $cmd" done [[ "$FAILURES" -eq 0 ]] || exit 20 if [[ "$SKIP_SERVER_CHECK" != "1" ]]; then [[ "$ACTUAL_HOST" == "$EXPECTED_HOST" ]] \ || fail "hostname ${ACTUAL_HOST}; previsto ${EXPECTED_HOST}" if [[ -n "$EXPECTED_IP" ]] && ! grep -qw -- "$EXPECTED_IP" <<<"$ACTUAL_IPS"; then fail "IP ${EXPECTED_IP} non presente tra: ${ACTUAL_IPS}" fi fi [[ "$FAILURES" -eq 0 ]] || exit 21 log "[1/6] Path applicativo e librerie condivise" [[ -d "$TARGET_DIR" ]] || fail "cartella applicativa assente: $TARGET_DIR" [[ -d "$SHARED_RELEASE" ]] || fail "release condivisa assente: $SHARED_RELEASE" for lib in bower_components node_modules; do LINK_PATH="${TARGET_DIR}/${lib}" EXPECTED_TARGET="${SHARED_RELEASE}/${lib}" if [[ ! -L "$LINK_PATH" ]]; then fail "$LINK_PATH non e' un collegamento simbolico" continue fi RESOLVED="$(readlink -f "$LINK_PATH" 2>/dev/null || true)" printf '%s\t%s\t%s\n' "$lib" "$RESOLVED" "$EXPECTED_TARGET" \ >>"${WORKDIR}/01_SHARED_LINKS.tsv" [[ "$RESOLVED" == "$EXPECTED_TARGET" ]] \ || fail "$lib punta a ${RESOLVED}; previsto ${EXPECTED_TARGET}" [[ -d "$RESOLVED" ]] || fail "destinazione libreria assente: $RESOLVED" done BROKEN_LINKS="$(find "$TARGET_DIR" -xdev -type l ! -exec test -e {} \; -print 2>/dev/null \ | tee "${WORKDIR}/02_BROKEN_LINKS.txt" \ | wc -l | tr -d ' ')" [[ "$BROKEN_LINKS" == "0" ]] || warn "collegamenti simbolici interrotti: $BROKEN_LINKS" [[ "$FAILURES" -eq 0 ]] || exit 22 log "[2/6] Accesso MySQL e schema demo" MYSQL_BIN="${STEP_MYSQL_BIN:-}" if [[ -z "$MYSQL_BIN" ]]; then if command -v mysql >/dev/null 2>&1; then MYSQL_BIN="$(command -v mysql)" elif command -v mariadb >/dev/null 2>&1; then MYSQL_BIN="$(command -v mariadb)" else fail "client mysql/mariadb non trovato" exit 23 fi fi MYSQL=("$MYSQL_BIN" --batch --raw --default-character-set=utf8mb4) MYSQL_SCALAR=("$MYSQL_BIN" --batch --raw --skip-column-names --default-character-set=utf8mb4) if ! "${MYSQL_SCALAR[@]}" -e 'SELECT 1' >/dev/null 2>&1; then fail "accesso MySQL locale non riuscito" exit 24 fi sql_scalar() { "${MYSQL_SCALAR[@]}" -e "$1" 2>>"$LOGFILE" } DB_EXISTS="$(sql_scalar " SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='${TARGET_DB}';")" [[ "$DB_EXISTS" == "1" ]] || fail "database assente: $TARGET_DB" for table in \ ax_user \ ax_associazioni_temporali \ ax_turns_new \ ax_turns_cycle \ ax_bands \ ax_turn_adjust do EXISTS="$(sql_scalar " SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='${TARGET_DB}' AND TABLE_NAME='${table}';")" [[ "$EXISTS" == "1" ]] || fail "tabella assente: ${TARGET_DB}.${table}" done [[ "$FAILURES" -eq 0 ]] || exit 25 log "[3/6] Dati demo e coerenza turno/ciclo" USERS="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_user;")" ACTIVE_USERS="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_user WHERE usrActive='1';")" ASSOCIATIONS="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_associazioni_temporali;")" CURRENT_ASSOCIATIONS="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_associazioni_temporali WHERE attuale='1';")" ASSOCIATIONS_TURN_1="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_associazioni_temporali WHERE usrTurns='1';")" TURN_1="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_turns_new WHERE trn_id=1;")" BAND_1="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_bands WHERE bnd_id=1;")" ADJUST_0000="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_turn_adjust WHERE adj_code='0000';")" CYCLE_1="$(sql_scalar "SELECT COUNT(*) FROM \`${TARGET_DB}\`.ax_turns_cycle WHERE trn_cycle_id=1;")" TURN_JSON_VALID="$(sql_scalar " SELECT JSON_VALID(trn_work_days) FROM \`${TARGET_DB}\`.ax_turns_new WHERE trn_id=1;")" CYCLE_REPEAT_VALID="$(sql_scalar " SELECT JSON_VALID(trn_cycle_repeat) FROM \`${TARGET_DB}\`.ax_turns_cycle WHERE trn_cycle_id=1;")" CYCLE_MODEL_VALID="$(sql_scalar " SELECT JSON_VALID(trn_cycle_model) FROM \`${TARGET_DB}\`.ax_turns_cycle WHERE trn_cycle_id=1;")" TURN_BAND_1_REFS="$(sql_scalar " SELECT CAST( ( CHAR_LENGTH(trn_work_days) - CHAR_LENGTH(REPLACE(trn_work_days, '\"bands\":\"1\"', '')) ) / CHAR_LENGTH('\"bands\":\"1\"') AS UNSIGNED ) FROM \`${TARGET_DB}\`.ax_turns_new WHERE trn_id=1;")" TURN_BAND_3_REFS="$(sql_scalar " SELECT CAST( ( CHAR_LENGTH(trn_work_days) - CHAR_LENGTH(REPLACE(trn_work_days, '\"bands\":\"3\"', '')) ) / CHAR_LENGTH('\"bands\":\"3\"') AS UNSIGNED ) FROM \`${TARGET_DB}\`.ax_turns_new WHERE trn_id=1;")" CYCLE_TURN_1_REFS="$(sql_scalar " SELECT CAST( ( CHAR_LENGTH(trn_cycle_model) - CHAR_LENGTH(REPLACE(trn_cycle_model, '\"turn\":\"1\"', '')) ) / CHAR_LENGTH('\"turn\":\"1\"') AS UNSIGNED ) FROM \`${TARGET_DB}\`.ax_turns_cycle WHERE trn_cycle_id=1;")" CYCLE_2026_DATES="$(sql_scalar " SELECT CAST( ( CHAR_LENGTH(trn_cycle_model) - CHAR_LENGTH(REPLACE(trn_cycle_model, '\"from\":\"2026-', '')) ) / CHAR_LENGTH('\"from\":\"2026-') AS UNSIGNED ) FROM \`${TARGET_DB}\`.ax_turns_cycle WHERE trn_cycle_id=1;")" { printf 'controllo\tvalore\n' printf 'utenti\t%s\n' "$USERS" printf 'utenti_attivi\t%s\n' "$ACTIVE_USERS" printf 'associazioni\t%s\n' "$ASSOCIATIONS" printf 'associazioni_attuali\t%s\n' "$CURRENT_ASSOCIATIONS" printf 'associazioni_turno_1\t%s\n' "$ASSOCIATIONS_TURN_1" printf 'turno_1\t%s\n' "$TURN_1" printf 'fascia_1\t%s\n' "$BAND_1" printf 'adjust_0000\t%s\n' "$ADJUST_0000" printf 'ciclo_1\t%s\n' "$CYCLE_1" printf 'turn_json_valid\t%s\n' "$TURN_JSON_VALID" printf 'cycle_repeat_valid\t%s\n' "$CYCLE_REPEAT_VALID" printf 'cycle_model_valid\t%s\n' "$CYCLE_MODEL_VALID" printf 'turn_refs_band_1\t%s\n' "$TURN_BAND_1_REFS" printf 'turn_refs_band_3\t%s\n' "$TURN_BAND_3_REFS" printf 'cycle_turn_1_refs\t%s\n' "$CYCLE_TURN_1_REFS" printf 'cycle_2026_dates\t%s\n' "$CYCLE_2026_DATES" } >"${WORKDIR}/03_DATABASE_READINESS.tsv" [[ "$USERS" -gt 0 ]] || fail "nessun utente demo" [[ "$ACTIVE_USERS" -gt 0 ]] || fail "nessun utente demo attivo" [[ "$ASSOCIATIONS" -gt 0 ]] || fail "nessuna associazione temporale" [[ "$CURRENT_ASSOCIATIONS" -gt 0 ]] || fail "nessuna associazione attuale" [[ "$ASSOCIATIONS_TURN_1" -gt 0 ]] || fail "nessuna associazione usa il turno demo 1" [[ "$TURN_1" == "1" ]] || fail "turno demo 1 assente o duplicato" [[ "$BAND_1" == "1" ]] || fail "fascia demo 1 assente o duplicata" [[ "$ADJUST_0000" == "1" ]] || fail "adjust 0000 assente o duplicato" [[ "$CYCLE_1" == "1" ]] || fail "ciclo demo 1 assente o duplicato" [[ "$TURN_JSON_VALID" == "1" ]] || fail "JSON turno demo non valido" [[ "$CYCLE_REPEAT_VALID" == "1" ]] || fail "JSON repeat ciclo demo non valido" [[ "$CYCLE_MODEL_VALID" == "1" ]] || fail "JSON model ciclo demo non valido" [[ "$TURN_BAND_1_REFS" == "7" ]] || fail "riferimenti fascia 1 nel turno: $TURN_BAND_1_REFS" [[ "$TURN_BAND_3_REFS" == "0" ]] || fail "riferimenti residui fascia 3 nel turno: $TURN_BAND_3_REFS" [[ "$CYCLE_TURN_1_REFS" == "365" ]] || fail "giorni ciclo sul turno 1: $CYCLE_TURN_1_REFS" [[ "$CYCLE_2026_DATES" == "365" ]] || fail "giorni 2026 nel ciclo: $CYCLE_2026_DATES" [[ "$FAILURES" -eq 0 ]] || exit 26 log "[4/6] Risposta HTTP portale" CURL_BIN="${STEP_CURL_BIN:-}" if [[ -z "$CURL_BIN" ]]; then if command -v curl >/dev/null 2>&1; then CURL_BIN="$(command -v curl)" else fail "comando curl non disponibile" exit 27 fi fi HTTP_BODY="${WORKDIR}/04_HTTP_BODY.html" HTTP_HEADERS="${WORKDIR}/05_HTTP_HEADERS.txt" HTTP_CODE="$( "$CURL_BIN" \ -k \ -L \ --max-time 30 \ --connect-timeout 10 \ -D "$HTTP_HEADERS" \ -o "$HTTP_BODY" \ -sS \ -w '%{http_code}' \ "$PORTAL_URL" 2>>"$LOGFILE" || true )" HTTP_SIZE="$(wc -c <"$HTTP_BODY" 2>/dev/null | tr -d ' ' || printf '0')" { printf 'url\t%s\n' "$PORTAL_URL" printf 'http_code\t%s\n' "$HTTP_CODE" printf 'body_bytes\t%s\n' "$HTTP_SIZE" } >"${WORKDIR}/06_HTTP_RESULT.tsv" [[ "$HTTP_CODE" == "200" ]] || fail "HTTP finale ${HTTP_CODE}; previsto 200" [[ "$HTTP_SIZE" -gt 100 ]] || fail "risposta HTTP troppo piccola: ${HTTP_SIZE} byte" if grep -Eiq \ 'Fatal error|Parse error|Uncaught exception|Warning:|Notice:|SQLSTATE|mysqli?_connect|Access denied for user' \ "$HTTP_BODY" then fail "la pagina HTTP contiene errori PHP/SQL visibili" fi [[ "$FAILURES" -eq 0 ]] || exit 28 log "[5/6] Configurazione Apache" if command -v apache2ctl >/dev/null 2>&1; then apache2ctl -S >"${WORKDIR}/07_APACHE_VHOSTS.txt" 2>&1 || warn "apache2ctl -S ha restituito errore" grep -q 'presenze.distribuite.axuno.it' "${WORKDIR}/07_APACHE_VHOSTS.txt" \ || warn "vhost presenze.distribuite non individuato in apache2ctl -S" else warn "apache2ctl non disponibile" fi log "[6/6] Esito" { echo "AUDIT COMPLETATO" echo "Errori: $FAILURES" echo "Avvisi: $WARNINGS" echo "HTTP: $HTTP_CODE" echo "Utenti demo: $USERS" echo "Associazioni attuali: $CURRENT_ASSOCIATIONS" echo "Turno/Fascia/Adjust/Ciclo: OK" echo "Ciclo 2026: 365 giorni" echo "Librerie condivise: OK" echo "Nessuna modifica eseguita." } >"${WORKDIR}/99_SUMMARY.txt" report "HTTP finale: $HTTP_CODE" report "Utenti demo: $USERS" report "Associazioni attuali: $CURRENT_ASSOCIATIONS" report "Turno/Fascia/Adjust/Ciclo: coerenti." report "Ciclo demo 2026: 365 giorni." report "Librerie condivise: collegate correttamente." log "AUDIT COMPLETATO: errori=${FAILURES}, avvisi=${WARNINGS}"