#!/usr/bin/env bash set -Eeuo pipefail umask 077 STEP_NAME="STEP031_2156_PRESENZE_DISTRIBUITE_GENERATOR_SOURCE_AUDIT_ONLY" EXPECTED_HOSTNAME="${EXPECTED_HOSTNAME:-ax-accessi}" EXPECTED_IP="${EXPECTED_IP:-192.168.215.6}" BASE_ROOT="${BASE_ROOT:-/var/www/html/presenze.distribuite}" DB_NAME="${DB_NAME:-presenze_distribuite}" APACHE_ROOT="${APACHE_ROOT:-/etc/apache2}" WEB_USER="${WEB_USER:-www-data}" OUTPUT_DIR="${OUTPUT_DIR:-$PWD}" STAMP="$(date +%Y%m%d_%H%M%S)" ZIP_NAME="${STEP_NAME}_${STAMP}.zip" WORKDIR="$(mktemp -d "/tmp/${STEP_NAME}.XXXXXX")" REPORT_DIR="$WORKDIR/report" ERRORS=0 WARNINGS=0 MYSQL_OK=0 BASE_EXISTS=0 DB_EXISTS=0 VHOST_MATCHES=0 SYMLINKS=0 BROKEN_SYMLINKS=0 IDENTITY_MATCHES=0 SILVELOX_MATCHES=0 cleanup() { rm -rf -- "$WORKDIR" } trap cleanup EXIT INT TERM HUP mkdir -p -- "$REPORT_DIR/redacted_configs" if [[ ! -d "$OUTPUT_DIR" ]]; then printf 'ERRORE: cartella output inesistente: %s\n' "$OUTPUT_DIR" >&2 exit 1 fi OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd -P)" ZIP_PATH="$OUTPUT_DIR/$ZIP_NAME" log() { printf '%s\n' "$*" } warn() { WARNINGS=$((WARNINGS + 1)) printf 'AVVISO: %s\n' "$*" | tee -a "$REPORT_DIR/run.log" >/dev/null } fail() { ERRORS=$((ERRORS + 1)) printf 'ERRORE: %s\n' "$*" | tee -a "$REPORT_DIR/run.log" >/dev/null } redact_stream() { sed -E \ -e '/(password|passwd|pwd|secret|token|api[_-]?key)[[:space:]]*(=>|=|:)/I s#.*#[REDACTED_SENSITIVE_LINE]#' \ -e "s#(mysql:host=[^;]+;dbname=)[^;[:space:]\"']+#\\1***REDACTED***#Ig" \ -e 's#(https?://)[^/@[:space:]]+:[^/@[:space:]]+@#\1***:***@#g' } redact_file() { local src="$1" local dst="$2" redact_stream <"$src" >"$dst" } safe_capture() { local outfile="$1" shift local rc=0 { printf '$' printf ' %q' "$@" printf '\n' if "$@"; then rc=0 else rc=$? fi printf '\n[exit_code=%s]\n' "$rc" } >"$outfile" 2>&1 return 0 } mysql_query() { mysql --protocol=socket --batch --raw --skip-column-names -e "$1" } log "$STEP_NAME" log "SERVER ATTESO: 215.6 / ax-accessi" log "BASE UFFICIALE IN SOLA LETTURA: $BASE_ROOT" log "DATABASE ATTESO IN SOLA LETTURA: $DB_NAME" log "SILVELOX: SOLO RIFERIMENTO NOMINALE, NON SORGENTE" log "MODALITA': AUDIT ONLY - NESSUNA MODIFICA" { printf '%s\n' "$STEP_NAME" printf 'SERVER ATTESO: 215.6 / ax-accessi\n' printf 'BASE UFFICIALE IN SOLA LETTURA: %s\n' "$BASE_ROOT" printf 'DATABASE ATTESO IN SOLA LETTURA: %s\n' "$DB_NAME" printf 'SILVELOX: SOLO RIFERIMENTO NOMINALE, NON SORGENTE\n' printf 'MODALITA\x27: AUDIT ONLY - NESSUNA MODIFICA\n' printf 'Avvio: %s\n' "$(date --iso-8601=seconds)" } >"$REPORT_DIR/run.log" CURRENT_HOSTNAME="$(hostname -s 2>/dev/null || hostname 2>/dev/null || true)" CURRENT_IPS="$(hostname -I 2>/dev/null || true)" if [[ "${AUDIT_TEST_MODE:-0}" != "1" ]]; then if [[ "$CURRENT_HOSTNAME" != "$EXPECTED_HOSTNAME" ]]; then fail "Hostname corrente '$CURRENT_HOSTNAME', atteso '$EXPECTED_HOSTNAME'" fi if [[ " $CURRENT_IPS " != *" $EXPECTED_IP "* ]]; then fail "IP atteso '$EXPECTED_IP' non presente tra: $CURRENT_IPS" fi fi if [[ -d "$BASE_ROOT" ]]; then BASE_EXISTS=1 else fail "Base ufficiale non trovata: $BASE_ROOT" fi log "[1/9] Ambiente server e runtime" { printf 'Hostname: %s\n' "$CURRENT_HOSTNAME" printf 'IP: %s\n' "$CURRENT_IPS" printf 'Utente lancio: %s\n' "$(id)" printf 'SUDO_USER: %s\n' "${SUDO_USER:-}" printf 'Data: %s\n' "$(date --iso-8601=seconds)" printf 'Kernel: %s\n' "$(uname -a 2>/dev/null || true)" printf '\nComandi disponibili:\n' for cmd in apache2ctl php mysql mysqldump zip unzip rsync findmnt getfacl lsattr runuser sudo grep awk sed find stat du sha256sum; do if command -v "$cmd" >/dev/null 2>&1; then printf '%-12s %s\n' "$cmd" "$(command -v "$cmd")" else printf '%-12s MANCANTE\n' "$cmd" fi done printf '\nPHP CLI:\n' php -v 2>&1 || true printf '\nPHP runtime flags:\n' php -r 'foreach (["disable_functions","open_basedir","max_execution_time","memory_limit"] as $k) { echo $k,"=",ini_get($k),PHP_EOL; }' 2>&1 || true } >"$REPORT_DIR/01_ENVIRONMENT.txt" if (( BASE_EXISTS == 1 )); then log "[2/9] Struttura fisica della base" { printf 'Base: %s\n' "$BASE_ROOT" stat -c 'stat=%A mode=%a owner=%U group=%G uid=%u gid=%g device=%d inode=%i path=%n' "$BASE_ROOT" 2>&1 || true printf 'physical_size='; du -sh -- "$BASE_ROOT" 2>/dev/null | awk '{print $1}' || true printf 'apparent_size='; du -sh --apparent-size -- "$BASE_ROOT" 2>/dev/null | awk '{print $1}' || true printf 'files='; find "$BASE_ROOT" -xdev -type f -printf '.' 2>/dev/null | wc -c printf 'directories='; find "$BASE_ROOT" -xdev -type d -printf '.' 2>/dev/null | wc -c printf 'symlinks='; find "$BASE_ROOT" -xdev -type l -printf '.' 2>/dev/null | wc -c printf '\n===== FILESYSTEM / MOUNT =====\n' if command -v findmnt >/dev/null 2>&1; then findmnt -T "$BASE_ROOT" -o TARGET,SOURCE,FSTYPE,OPTIONS,SIZE,USED,AVAIL 2>&1 || true fi printf '\n===== TOP LEVEL =====\n' find "$BASE_ROOT" -mindepth 1 -maxdepth 1 -printf '%y\t%m\t%u:%g\t%s\t%f\t%l\n' 2>/dev/null | sort printf '\n===== TOP LEVEL PHYSICAL SIZE =====\n' du -sh -- "$BASE_ROOT"/* "$BASE_ROOT"/.[!.]* "$BASE_ROOT"/..?* 2>/dev/null | sort -h || true } >"$REPORT_DIR/02_BASE_FILESYSTEM.txt" { printf '===== TOP LEVEL ACL =====\n' if command -v getfacl >/dev/null 2>&1; then getfacl -p -c -- "$BASE_ROOT" 2>&1 || true while IFS= read -r -d '' p; do printf '\n--- %s ---\n' "$p" getfacl -p -c -- "$p" 2>&1 || true done < <(find "$BASE_ROOT" -mindepth 1 -maxdepth 1 -print0 2>/dev/null | sort -z) else printf 'getfacl non disponibile.\n' fi printf '\n===== TOP LEVEL ATTRIBUTES =====\n' if command -v lsattr >/dev/null 2>&1; then lsattr -d -- "$BASE_ROOT" "$BASE_ROOT"/* "$BASE_ROOT"/.[!.]* "$BASE_ROOT"/..?* 2>&1 || true else printf 'lsattr non disponibile.\n' fi } >"$REPORT_DIR/03_BASE_PERMISSIONS_ACL.txt" log "[3/9] Symlink, dipendenze condivise e runtime" : >"$REPORT_DIR/04_SYMLINK_MAP.tsv" printf 'status\tmode\towner\tlink\ttarget\tresolved\n' >>"$REPORT_DIR/04_SYMLINK_MAP.tsv" while IFS= read -r -d '' link; do SYMLINKS=$((SYMLINKS + 1)) target="$(readlink -- "$link" 2>/dev/null || true)" resolved="$(readlink -f -- "$link" 2>/dev/null || true)" status="OK" if [[ ! -e "$link" ]]; then status="BROKEN" BROKEN_SYMLINKS=$((BROKEN_SYMLINKS + 1)) fi printf '%s\t%s\t%s\t%s\t%s\t%s\n' \ "$status" \ "$(stat -c '%a' "$link" 2>/dev/null || true)" \ "$(stat -c '%U:%G' "$link" 2>/dev/null || true)" \ "$link" "$target" "$resolved" >>"$REPORT_DIR/04_SYMLINK_MAP.tsv" done < <(find "$BASE_ROOT" -xdev -type l -print0 2>/dev/null | sort -z) { printf '===== COMPONENTI PESANTI / CONDIVISIBILI =====\n' find "$BASE_ROOT" -xdev \ \( -name node_modules -o -name bower_components -o -name vendor -o -name libs -o -name stimulsoft -o -name stimulsoft_legacy \) \ -printf '%y\t%m\t%u:%g\t%p\t%l\n' 2>/dev/null | sort printf '\n===== DIRECTORY RUNTIME / SCRIVIBILI =====\n' find "$BASE_ROOT" -xdev -type d \ \( -iname 'templates_c' -o -iname 'cache' -o -iname 'tmp' -o -iname 'temp' -o -iname 'logs' -o -iname 'log' -o -iname 'sessions' -o -iname 'uploads' -o -iname 'upload' \) \ -printf '%m\t%u:%g\t%p\n' 2>/dev/null | sort printf '\n===== ACCESSO EFFETTIVO UTENTE WEB =====\n' if id "$WEB_USER" >/dev/null 2>&1 && command -v runuser >/dev/null 2>&1; then while IFS= read -r -d '' p; do read_ok=NO; write_ok=NO; exec_ok=NO runuser -u "$WEB_USER" -- test -r "$p" && read_ok=YES || true runuser -u "$WEB_USER" -- test -w "$p" && write_ok=YES || true runuser -u "$WEB_USER" -- test -x "$p" && exec_ok=YES || true printf '%s\tread=%s\twrite=%s\texec=%s\n' "$p" "$read_ok" "$write_ok" "$exec_ok" done < <( { printf '%s\0' "$BASE_ROOT" find "$BASE_ROOT" -xdev -type d \ \( -iname 'templates_c' -o -iname 'cache' -o -iname 'tmp' -o -iname 'temp' -o -iname 'logs' -o -iname 'log' -o -iname 'sessions' -o -iname 'uploads' -o -iname 'upload' \) \ -print0 2>/dev/null } | sort -zu ) else printf 'Utente web o runuser non disponibile.\n' fi } >"$REPORT_DIR/05_COMPONENTS_AND_RUNTIME.txt" { printf 'size_bytes\towner\tmode\tpath\n' find "$BASE_ROOT" -xdev -type f -size +1M -printf '%s\t%u:%g\t%m\t%p\n' 2>/dev/null | sort -nr | head -n 500 || true } >"$REPORT_DIR/06_LARGE_FILES.tsv" log "[4/9] Configurazioni e riferimenti da parametrizzare" CONFIG_LIST="$WORKDIR/config_candidates.list" find "$BASE_ROOT" -xdev -type f \ \( -iname 'config.php' -o -iname 'configuration.php' -o -iname '*config*.php' -o -iname 'database.php' -o -iname 'db.php' -o -iname 'settings.php' -o -iname '.env' -o -iname '*.ini' -o -iname '*.conf' \) \ -size -2M \ ! -path '*/node_modules/*' ! -path '*/bower_components/*' ! -path '*/vendor/*' \ ! -path '*/libs/*' ! -path '*/stimulsoft*/*' ! -path '*/templates_c/*' \ -print 2>/dev/null | sort >"$CONFIG_LIST" printf 'path\tsize\tmode\towner\tsha256\n' >"$REPORT_DIR/07_CONFIG_CANDIDATES.tsv" while IFS= read -r src; do [[ -f "$src" ]] || continue rel="${src#"$BASE_ROOT"/}" safe_rel="$(printf '%s' "$rel" | sed 's#[^A-Za-z0-9._/-]#_#g')" dst="$REPORT_DIR/redacted_configs/$safe_rel" mkdir -p -- "$(dirname "$dst")" redact_file "$src" "$dst" printf '%s\t%s\t%s\t%s\t%s\n' \ "$rel" \ "$(stat -c '%s' "$src")" \ "$(stat -c '%a' "$src")" \ "$(stat -c '%U:%G' "$src")" \ "$(sha256sum "$src" | awk '{print $1}')" \ >>"$REPORT_DIR/07_CONFIG_CANDIDATES.tsv" done <"$CONFIG_LIST" IDENTITY_RAW="$WORKDIR/identity_raw.txt" : >"$IDENTITY_RAW" while IFS= read -r -d '' src; do grep -nEi \ 'presenze\.distribuite|presenze_distribuite|/var/www/html/presenze\.distribuite|presenze\.silvelox|silvelox|presenze\.dvmservice|ax-cloud\.it|axuno\.it|DB_(HOST|NAME|USER|PASS)|database|dbname|username|password' \ "$src" 2>/dev/null | sed "s#^#$src:#" >>"$IDENTITY_RAW" || true done < <( find "$BASE_ROOT" -xdev -type f \ \( -iname '*.php' -o -iname '*.js' -o -iname '*.json' -o -iname '*.html' -o -iname '*.htm' -o -iname '*.xml' -o -iname '*.ini' -o -iname '*.conf' -o -iname '*.sql' -o -iname '*.htaccess' \) \ -size -2M \ ! -path '*/node_modules/*' ! -path '*/bower_components/*' ! -path '*/vendor/*' \ ! -path '*/libs/*' ! -path '*/stimulsoft*/*' ! -path '*/templates_c/*' \ ! -path '*/cache/*' ! -path '*/tmp/*' ! -path '*/logs/*' \ -print0 2>/dev/null ) IDENTITY_MATCHES="$(wc -l <"$IDENTITY_RAW" | tr -d ' ')" SILVELOX_MATCHES="$(grep -Eic 'presenze\.silvelox|silvelox' "$IDENTITY_RAW" || true)" redact_stream <"$IDENTITY_RAW" >"$REPORT_DIR/08_IDENTITY_REFERENCES_REDACTED.txt" { printf '===== ROOT HTACCESS =====\n' if [[ -f "$BASE_ROOT/.htaccess" ]]; then redact_file "$BASE_ROOT/.htaccess" /dev/stdout else printf 'MANCANTE\n' fi printf '\n===== HTACCESS PRESENTI =====\n' find "$BASE_ROOT" -xdev -type f -name '.htaccess' -printf '%p\n' 2>/dev/null | sort printf '\n===== URL / HOST HARDCODED (REDACTED) =====\n' while IFS= read -r -d '' src; do grep -nEHo "https?://[^[:space:]\"'<>)]+" "$src" 2>/dev/null | sed "s#^#$src:#" || true done < <( find "$BASE_ROOT" -xdev -type f \ \( -iname '*.php' -o -iname '*.js' -o -iname '*.json' -o -iname '*.html' -o -iname '*.xml' -o -iname '*.conf' -o -iname '.htaccess' \) \ -size -2M \ ! -path '*/node_modules/*' ! -path '*/bower_components/*' ! -path '*/vendor/*' \ ! -path '*/libs/*' ! -path '*/stimulsoft*/*' ! -path '*/templates_c/*' \ -print0 2>/dev/null ) | redact_stream | sort -u | head -n 2000 || true } >"$REPORT_DIR/09_REWRITES_AND_URLS.txt" fi log "[5/9] Apache e PHP-FPM" { printf '===== APACHE CONFIG TEST =====\n' if command -v apache2ctl >/dev/null 2>&1; then apache2ctl configtest 2>&1 || true printf '\n===== APACHE VHOST MAP =====\n' apache2ctl -S 2>&1 || true printf '\n===== APACHE MODULES =====\n' apache2ctl -M 2>&1 || true else printf 'apache2ctl non disponibile.\n' fi printf '\n===== PHP-FPM POOLS =====\n' grep -RInE '^[[:space:]]*(user|group|listen|listen\.owner|listen\.group)[[:space:]]*=' /etc/php/*/fpm/pool.d 2>/dev/null || true } >"$REPORT_DIR/10_APACHE_RUNTIME.txt" { printf '===== VHOST / DISTRIBUITE MATCHES =====\n' if [[ -d "$APACHE_ROOT" ]]; then grep -RInE 'presenze\.distribuite|/var/www/html/presenze\.distribuite|presenze_distribuite' \ "$APACHE_ROOT/sites-available" "$APACHE_ROOT/sites-enabled" 2>/dev/null | sort -u || true printf '\n===== SITES ENABLED =====\n' find "$APACHE_ROOT/sites-enabled" -maxdepth 1 -type l -printf '%f\t%l\n' 2>/dev/null | sort || true printf '\n===== PRESENZE VHOST CANDIDATES =====\n' find "$APACHE_ROOT/sites-available" -maxdepth 1 -type f -iname '*presenze*' -printf '%f\n' 2>/dev/null | sort || true else printf 'Apache root non trovato: %s\n' "$APACHE_ROOT" fi } >"$REPORT_DIR/11_VHOST_MATCHES.txt" VHOST_MATCHES="$(grep -Ec 'presenze\.distribuite|/var/www/html/presenze\.distribuite|presenze_distribuite' "$REPORT_DIR/11_VHOST_MATCHES.txt" || true)" log "[6/9] Database sorgente in sola lettura" { printf 'Database atteso: %s\n' "$DB_NAME" if [[ "${MYSQL_AUDIT:-1}" == "0" ]]; then printf 'Audit MySQL disabilitato da MYSQL_AUDIT=0.\n' elif ! command -v mysql >/dev/null 2>&1; then printf 'mysql client non disponibile.\n' elif mysql_query 'SELECT 1;' >/dev/null 2>&1; then MYSQL_OK=1 printf 'socket_login=OK\n' printf 'server_version='; mysql_query 'SELECT VERSION();' 2>/dev/null || true db_exists="$(mysql_query "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='${DB_NAME//\'/\'\'}';" 2>/dev/null || printf '0')" if [[ "$db_exists" == "1" ]]; then DB_EXISTS=1 printf 'database_exists=YES\n' mysql_query "SHOW CREATE DATABASE \`${DB_NAME//\`/}\`;" 2>&1 || true printf '\n===== DATABASE SIZE =====\n' mysql --protocol=socket --table -e "SELECT TABLE_SCHEMA, COUNT(*) AS objects, ROUND(SUM(DATA_LENGTH+INDEX_LENGTH)/1024/1024,2) AS total_mb FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='${DB_NAME//\'/\'\'}' GROUP BY TABLE_SCHEMA;" 2>&1 || true printf '\n===== TABLE INVENTORY =====\n' mysql --protocol=socket --table -e "SELECT TABLE_NAME, TABLE_TYPE, ENGINE, TABLE_ROWS, ROUND(DATA_LENGTH/1024/1024,2) AS data_mb, ROUND(INDEX_LENGTH/1024/1024,2) AS index_mb, TABLE_COLLATION FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='${DB_NAME//\'/\'\'}' ORDER BY TABLE_TYPE, TABLE_NAME;" 2>&1 || true printf '\n===== VIEWS =====\n' mysql --protocol=socket --table -e "SELECT TABLE_NAME, DEFINER, SECURITY_TYPE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA='${DB_NAME//\'/\'\'}' ORDER BY TABLE_NAME;" 2>&1 || true printf '\n===== ROUTINES =====\n' mysql --protocol=socket --table -e "SELECT ROUTINE_NAME, ROUTINE_TYPE, DEFINER, SQL_DATA_ACCESS FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA='${DB_NAME//\'/\'\'}' ORDER BY ROUTINE_TYPE, ROUTINE_NAME;" 2>&1 || true printf '\n===== EVENTS =====\n' mysql --protocol=socket --table -e "SELECT EVENT_NAME, STATUS, DEFINER FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='${DB_NAME//\'/\'\'}' ORDER BY EVENT_NAME;" 2>&1 || true printf '\n===== TRIGGERS =====\n' mysql --protocol=socket --table -e "SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, DEFINER FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA='${DB_NAME//\'/\'\'}' ORDER BY EVENT_OBJECT_TABLE, TRIGGER_NAME;" 2>&1 || true else printf 'database_exists=NO\n' fi else printf 'socket_login=KO\n' fi } >"$REPORT_DIR/12_DATABASE_INVENTORY.txt" if [[ "${MYSQL_AUDIT:-1}" == "0" ]]; then : elif (( MYSQL_OK == 0 )); then warn "Accesso MySQL root via socket non disponibile: inventario DB incompleto" elif (( DB_EXISTS == 0 )); then warn "Database atteso non trovato: $DB_NAME" else if command -v mysqldump >/dev/null 2>&1; then if mysqldump --protocol=socket --no-data --skip-comments --skip-dump-date --skip-routines --skip-triggers --skip-events \ --single-transaction --default-character-set=utf8mb4 "$DB_NAME" >"$REPORT_DIR/13_DATABASE_SCHEMA_ONLY.sql" 2>"$REPORT_DIR/13_DATABASE_SCHEMA_ONLY.err"; then rm -f -- "$REPORT_DIR/13_DATABASE_SCHEMA_ONLY.err" else warn "mysqldump schema-only fallito" fi else warn "mysqldump non disponibile" fi fi log "[7/9] Manifest di clonazione candidato" if (( BASE_EXISTS == 1 )); then { printf 'type\tmode\towner\tsize\tpath\ttarget\n' find "$BASE_ROOT" -mindepth 1 -maxdepth 2 \ -printf '%y\t%m\t%u:%g\t%s\t%p\t%l\n' 2>/dev/null | sort } >"$REPORT_DIR/14_CLONE_MANIFEST_DEPTH2.tsv" { printf '===== ELEMENTI DA NON COPIARE ALLA CIECA =====\n' find "$BASE_ROOT" -xdev \ \( -type d \( -iname 'templates_c' -o -iname 'cache' -o -iname 'tmp' -o -iname 'temp' -o -iname 'logs' -o -iname 'log' -o -iname 'sessions' \) \ -o -type f \( -iname '*.log' -o -iname '*.tmp' -o -iname '*.pid' -o -iname '*.sock' \) \) \ -printf '%y\t%m\t%u:%g\t%s\t%p\n' 2>/dev/null | sort | head -n 5000 || true printf '\n===== ELEMENTI IDENTITARI DA PARAMETRIZZARE =====\n' printf 'Riferimenti trovati: %s\n' "$IDENTITY_MATCHES" printf 'Riferimenti Silvelox trovati: %s\n' "$SILVELOX_MATCHES" printf 'Dettaglio: 08_IDENTITY_REFERENCES_REDACTED.txt\n' printf '\n===== COMPONENTI CONDIVISI DA PRESERVARE COME SYMLINK =====\n' awk -F '\t' 'NR==1 || $1=="OK" {print}' "$REPORT_DIR/04_SYMLINK_MAP.tsv" 2>/dev/null || true } >"$REPORT_DIR/15_CLONE_RULE_INPUTS.txt" fi log "[8/9] Verifica assenza modifiche" { printf 'Audit eseguito in sola lettura.\n' printf 'Nessun rsync, cp, mv, rm, chmod, chown, a2ensite, reload Apache o query SQL modificativa eseguita.\n' printf 'Base controllata: %s\n' "$BASE_ROOT" printf 'Database interrogato solo tramite SELECT/SHOW/INFORMATION_SCHEMA e mysqldump --no-data.\n' printf 'Silvelox non letto come sorgente e non modificato.\n' } >"$REPORT_DIR/98_READ_ONLY_PROOF.txt" if (( BROKEN_SYMLINKS > 0 )); then warn "Symlink interrotti nella base: $BROKEN_SYMLINKS" fi if (( VHOST_MATCHES == 0 )); then warn "Nessun riferimento Apache alla base presenze.distribuite rilevato" fi if (( IDENTITY_MATCHES == 0 )); then warn "Nessun riferimento identitario rilevato: verificare configurazioni esterne o generate" fi log "[9/9] Riepilogo e ZIP" { if (( ERRORS > 0 )); then printf 'AUDIT COMPLETATO CON ERRORI\n' else printf 'AUDIT COMPLETATO\n' fi printf 'Server: 215.6 / ax-accessi\n' printf 'Base ufficiale: %s\n' "$BASE_ROOT" printf 'Base presente: %s\n' "$([[ $BASE_EXISTS -eq 1 ]] && printf YES || printf NO)" printf 'Database atteso: %s\n' "$DB_NAME" printf 'Database presente: %s\n' "$([[ $DB_EXISTS -eq 1 ]] && printf YES || printf NO)" printf 'Vhost matches: %s\n' "$VHOST_MATCHES" printf 'Symlink totali: %s\n' "$SYMLINKS" printf 'Symlink interrotti: %s\n' "$BROKEN_SYMLINKS" printf 'Riferimenti identitari da parametrizzare: %s\n' "$IDENTITY_MATCHES" printf 'Riferimenti Silvelox: %s\n' "$SILVELOX_MATCHES" printf 'Errori: %s\n' "$ERRORS" printf 'Avvisi: %s\n' "$WARNINGS" printf 'Base modificata: NO\n' printf 'Database modificato: NO\n' printf 'Apache modificato: NO\n' printf 'Silvelox toccato: NO\n' printf '\nObiettivo del prossimo step:\n' printf -- '- trasformare questo inventario in una procedura deterministica di creazione portale;\n' printf -- '- separare copia fisica, symlink condivisi, directory runtime e sostituzioni identitarie;\n' printf -- '- solo dopo collegare il worker del pannello DVM-SUITE dal 215.5.\n' } >"$REPORT_DIR/99_SUMMARY.txt" { printf '\nFine: %s\n' "$(date --iso-8601=seconds)" printf 'Errori: %s\n' "$ERRORS" printf 'Avvisi: %s\n' "$WARNINGS" } >>"$REPORT_DIR/run.log" ( cd "$REPORT_DIR" zip -q -r "$ZIP_PATH" . ) zip -T "$ZIP_PATH" >/dev/null log "Creato: $ZIP_PATH" if (( ERRORS > 0 )); then exit 1 fi exit 0