#!/usr/bin/env bash # STEP041 - Porting Presenze 215.6 # Correzione mirata Silvelox: credenziali DB licenze usate dal controller Angular # Obiettivo immediato: eliminare i fatal error che impediscono il salvataggio calendario # Server atteso: 215.6 / ax-accessi # Modifica esclusiva: # /var/www/html/presenze.silvelox/components/a000_angular_app/ctrl/controllerCtrl.php set -Eeuo pipefail umask 077 STEP_NAME="STEP041_2156_SILVELOX_LICENSE_DB_CREDENTIALS_CALENDAR_SAVE_FIX_PATCH_VERIFIED_20260730" EXPECTED_HOST="${EXPECTED_HOST:-ax-accessi}" EXPECTED_IP_FRAGMENT="${EXPECTED_IP_FRAGMENT:-192.168.215.6}" TARGET_ROOT="${TARGET_ROOT:-/var/www/html/presenze.silvelox}" TARGET_FILE="${TARGET_FILE:-$TARGET_ROOT/components/a000_angular_app/ctrl/controllerCtrl.php}" TARGET_HOST="${TARGET_HOST:-presenze.silvelox.axuno.it}" ERROR_LOG="${ERROR_LOG:-/var/log/apache2/presenze.silvelox.axuno.it_error.log}" LICENSE_DB_HOST="${LICENSE_DB_HOST:-localhost}" LICENSE_DB_NAME="${LICENSE_DB_NAME:-ax-master-license}" LICENSE_DB_USER="${LICENSE_DB_USER:-presenze_local}" LICENSE_DB_PASSWORD="${LICENSE_DB_PASSWORD:-MettiQuiPasswordForte}" OLD_LICENSE_DB_USER="${OLD_LICENSE_DB_USER:-user_local}" OLD_LICENSE_DB_PASSWORD="${OLD_LICENSE_DB_PASSWORD:-V7c@86e9z%5.!AGh7}" ALLOW_TEST="${ALLOW_TEST:-0}" SKIP_LIVE_DB_TEST="${SKIP_LIVE_DB_TEST:-0}" SKIP_HTTP_TEST="${SKIP_HTTP_TEST:-0}" FORCE_POST_PATCH_FAILURE="${FORCE_POST_PATCH_FAILURE:-0}" APACHE_GRACEFUL_BIN="${APACHE_GRACEFUL_BIN:-apache2ctl}" PHP_FPM_SERVICE="${PHP_FPM_SERVICE:-php5.6-fpm}" if command -v php5.6 >/dev/null 2>&1; then PHP_BIN="${PHP_BIN:-php5.6}" else PHP_BIN="${PHP_BIN:-php}" fi START_TS="$(date +%Y%m%d_%H%M%S)" OUTPUT_ZIP="$PWD/${STEP_NAME}_${START_TS}.zip" WORKDIR="$(mktemp -d "$PWD/.${STEP_NAME}.XXXXXX")" REPORT="$WORKDIR/${STEP_NAME}_REPORT.txt" BACKUP="$WORKDIR/controllerCtrl.php.before" PATCH_STATE="NOT_STARTED" PATCH_ACTIVE=0 ROLLBACK_DONE=0 cleanup() { rm -rf -- "$WORKDIR" } rollback() { [[ "$ROLLBACK_DONE" -eq 0 ]] || return 0 ROLLBACK_DONE=1 if [[ "$PATCH_ACTIVE" -eq 1 && -f "$BACKUP" ]]; then cp -a -- "$BACKUP" "$TARGET_FILE" touch -- "$TARGET_FILE" 2>/dev/null || true { echo echo "ROLLBACK: file originale ripristinato." } >> "$REPORT" 2>/dev/null || true if [[ "$ALLOW_TEST" != "1" ]]; then systemctl reload "$PHP_FPM_SERVICE" >/dev/null 2>&1 || true if command -v "$APACHE_GRACEFUL_BIN" >/dev/null 2>&1; then "$APACHE_GRACEFUL_BIN" graceful >/dev/null 2>&1 || true fi fi fi } on_error() { local status="$?" local line="${BASH_LINENO[0]:-sconosciuta}" { echo echo "ESITO: ERRORE" echo "CODICE: $status" echo "LINEA: $line" echo "STATO PATCH: $PATCH_STATE" } >> "$REPORT" 2>/dev/null || true rollback exit "$status" } trap on_error ERR trap cleanup EXIT INT TERM log() { printf '%s\n' "$*" | tee -a "$REPORT" } count_fixed() { local needle="$1" grep -Foc -- "$needle" "$TARGET_FILE" 2>/dev/null || true } HOSTNAME_NOW="$(hostname 2>/dev/null || true)" IPS_NOW="$(hostname -I 2>/dev/null || true)" if [[ "$ALLOW_TEST" != "1" && "$EUID" -ne 0 ]]; then echo "ERRORE: eseguire con sudo/root." >&2 exit 9 fi if [[ "$ALLOW_TEST" != "1" ]]; then if [[ "$HOSTNAME_NOW" != "$EXPECTED_HOST" && "$IPS_NOW" != *"$EXPECTED_IP_FRAGMENT"* ]]; then echo "ERRORE: server errato. Rilevato host='$HOSTNAME_NOW' IP='$IPS_NOW'; atteso 215.6 / ax-accessi." >&2 exit 10 fi fi if [[ ! -f "$TARGET_FILE" ]]; then echo "ERRORE: file non trovato: $TARGET_FILE" >&2 exit 11 fi if [[ -L "$TARGET_FILE" ]]; then echo "ERRORE: il file target e' un symlink inatteso: $TARGET_FILE" >&2 exit 12 fi { echo "$STEP_NAME" echo "DATA: $(date --iso-8601=seconds 2>/dev/null || date)" echo "SERVER ATTESO: 215.6 / ax-accessi" echo "PORTALE: Silvelox" echo "FILE: $TARGET_FILE" echo "DB LICENZE: $LICENSE_DB_HOST / $LICENSE_DB_NAME / $LICENSE_DB_USER" echo "PHP CLI DI VERIFICA: $PHP_BIN" echo "PHP-FPM: $PHP_FPM_SERVICE" echo "SCOPO: correggere il collegamento DB licenze che genera fatal error nel controller Angular" echo "NESSUNA MODIFICA A STAMPE, DATABASE, VHOST O ALTRI PORTALI" echo } > "$REPORT" BEFORE_HASH="$(sha256sum "$TARGET_FILE" | awk '{print $1}')" BEFORE_OWNER="$(stat -c '%U:%G' "$TARGET_FILE")" BEFORE_MODE="$(stat -c '%a' "$TARGET_FILE")" BEFORE_SIZE="$(stat -c '%s' "$TARGET_FILE")" log "Hash iniziale: $BEFORE_HASH" log "Proprietario iniziale: $BEFORE_OWNER" log "Permessi iniziali: $BEFORE_MODE" log "Dimensione iniziale: $BEFORE_SIZE" cp -a -- "$TARGET_FILE" "$BACKUP" CREDENTIALS_FILE="$WORKDIR/credentials.txt" printf '%s\n' \ "$LICENSE_DB_HOST" \ "$LICENSE_DB_NAME" \ "$LICENSE_DB_USER" \ "$LICENSE_DB_PASSWORD" \ "$OLD_LICENSE_DB_USER" \ "$OLD_LICENSE_DB_PASSWORD" > "$CREDENTIALS_FILE" chmod 0640 "$CREDENTIALS_FILE" if [[ "$ALLOW_TEST" != "1" ]]; then chown root:www-data "$CREDENTIALS_FILE" fi # Verifica reale delle nuove credenziali nello stesso contesto del processo web. if [[ "$SKIP_LIVE_DB_TEST" != "1" ]]; then DB_TEST_PHP="$WORKDIR/db_test.php" cat > "$DB_TEST_PHP" <<'PHP' connect_errno) { fwrite(STDERR, "Connessione DB fallita\n"); exit(21); } $result = $db->query("SELECT 1"); if (!$result) { fwrite(STDERR, "Query DB fallita\n"); exit(22); } echo "DB_OK\n"; PHP chmod 0750 "$WORKDIR" chown root:www-data "$WORKDIR" chmod 0640 "$DB_TEST_PHP" chown root:www-data "$DB_TEST_PHP" DB_TEST_OUT="$( runuser -u www-data -- "$PHP_BIN" "$DB_TEST_PHP" "$CREDENTIALS_FILE" 2>&1 )" if [[ "$DB_TEST_OUT" != "DB_OK" ]]; then log "ERRORE: le credenziali corrette non sono utilizzabili da www-data." exit 20 fi log "Test DB licenze come www-data: OK" else log "Test DB live: saltato esclusivamente in modalita' test." fi PATCHER="$WORKDIR/patch.php" cat > "$PATCHER" <<'PHP' \s*)'" . preg_quote($oldUser, '~') . "'(\s*,\s*'password'\s*=>\s*)'" . preg_quote($oldPassword, '~') . "'(\s*,\s*'database'\s*=>\s*'" . preg_quote($database, '~') . "')~s"; $newPattern = "~('username'\s*=>\s*)'" . preg_quote($newUser, '~') . "'(\s*,\s*'password'\s*=>\s*)'" . preg_quote($newPassword, '~') . "'(\s*,\s*'database'\s*=>\s*'" . preg_quote($database, '~') . "')~s"; $oldCount = preg_match_all($oldPattern, $source, $unusedOld); $newCount = preg_match_all($newPattern, $source, $unusedNew); if ($oldCount === 1 && $newCount === 0) { $updated = preg_replace_callback( $oldPattern, static function (array $m) use ($newUser, $newPassword) { return $m[1] . "'" . $newUser . "'" . $m[2] . "'" . $newPassword . "'" . $m[3]; }, $source, 1, $replaced ); if ($updated === null || $replaced !== 1) { fwrite(STDERR, "Sostituzione non riuscita\n"); exit(32); } if (file_put_contents($file, $updated, LOCK_EX) === false) { fwrite(STDERR, "Scrittura file fallita\n"); exit(33); } echo "PATCHED\n"; exit(0); } if ($oldCount === 0 && $newCount === 1) { echo "ALREADY_OK\n"; exit(0); } fwrite( STDERR, "Stato inatteso: blocchi vecchi=" . $oldCount . " blocchi corretti=" . $newCount . "\n" ); exit(34); PHP PATCH_RESULT="$( "$PHP_BIN" "$PATCHER" "$TARGET_FILE" "$CREDENTIALS_FILE" )" case "$PATCH_RESULT" in PATCHED) PATCH_STATE="PATCHED" PATCH_ACTIVE=1 log "Correzione credenziali applicata: OK" ;; ALREADY_OK) PATCH_STATE="ALREADY_OK" log "Correzione gia' presente: nessuna riscrittura necessaria." ;; *) log "ERRORE: risultato patch inatteso." exit 35 ;; esac "$PHP_BIN" -l "$TARGET_FILE" > "$WORKDIR/php_lint.txt" log "PHP lint: OK" AFTER_OWNER="$(stat -c '%U:%G' "$TARGET_FILE")" AFTER_MODE="$(stat -c '%a' "$TARGET_FILE")" if [[ "$AFTER_OWNER" != "$BEFORE_OWNER" || "$AFTER_MODE" != "$BEFORE_MODE" ]]; then log "ERRORE: proprietario o permessi del file sono cambiati." exit 36 fi VERIFY_RESULT="$( "$PHP_BIN" "$PATCHER" "$TARGET_FILE" "$CREDENTIALS_FILE" )" if [[ "$VERIFY_RESULT" != "ALREADY_OK" ]]; then log "ERRORE: verifica finale del blocco credenziali fallita." exit 37 fi log "Verifica blocco credenziali nel controller: OK" if [[ "$FORCE_POST_PATCH_FAILURE" == "1" ]]; then log "Errore post-patch forzato per test rollback." false fi # Rende immediatamente rilevabile la modifica e verifica la configurazione Apache. touch -- "$TARGET_FILE" if [[ "$ALLOW_TEST" != "1" ]]; then "$APACHE_GRACEFUL_BIN" configtest >> "$REPORT" 2>&1 systemctl is-active --quiet "$PHP_FPM_SERVICE" systemctl reload "$PHP_FPM_SERVICE" >> "$REPORT" 2>&1 "$APACHE_GRACEFUL_BIN" graceful >> "$REPORT" 2>&1 log "PHP-FPM reload, Apache configtest e graceful: OK" else log "PHP-FPM/Apache reload: simulati in modalita' test." fi HTTP_GET_CODE="SKIPPED" HTTP_POST_CODE="SKIPPED" ERRORS_BEFORE=0 ERRORS_AFTER=0 if [[ "$SKIP_HTTP_TEST" != "1" ]]; then if [[ -r "$ERROR_LOG" ]]; then ERRORS_BEFORE="$(grep -Fc "Could not connect to localhost" "$ERROR_LOG" 2>/dev/null || true)" fi HTTP_GET_CODE="$( curl -ksS --max-time 20 \ -o "$WORKDIR/http_get_body.txt" \ -w '%{http_code}' \ "http://127.0.0.1/ax_calendar.php" \ -H "Host: $TARGET_HOST" )" HTTP_POST_CODE="$( curl -ksS --max-time 20 \ -o "$WORKDIR/http_post_body.txt" \ -w '%{http_code}' \ -X POST \ "http://127.0.0.1/components/a000_angular_app/ctrl/controllerCtrl.php" \ -H "Host: $TARGET_HOST" \ -H 'Content-Type: application/json' \ --data '{}' )" sleep 1 if [[ -r "$ERROR_LOG" ]]; then ERRORS_AFTER="$(grep -Fc "Could not connect to localhost" "$ERROR_LOG" 2>/dev/null || true)" fi if [[ "$HTTP_GET_CODE" =~ ^5 || "$HTTP_POST_CODE" =~ ^5 ]]; then log "ERRORE: test HTTP restituisce errore server (GET=$HTTP_GET_CODE POST=$HTTP_POST_CODE)." exit 40 fi if (( ERRORS_AFTER > ERRORS_BEFORE )); then log "ERRORE: il test ha generato nuovi fatal error di connessione DB." exit 41 fi log "Test HTTP locale: GET=$HTTP_GET_CODE POST=$HTTP_POST_CODE" log "Nuovi fatal error DB durante il test: 0" else log "Test HTTP locale: saltato esclusivamente in modalita' test." fi AFTER_HASH="$(sha256sum "$TARGET_FILE" | awk '{print $1}')" AFTER_SIZE="$(stat -c '%s' "$TARGET_FILE")" { echo echo "Hash finale: $AFTER_HASH" echo "Dimensione finale: $AFTER_SIZE" echo "Proprietario finale: $AFTER_OWNER" echo "Permessi finali: $AFTER_MODE" echo "HTTP GET: $HTTP_GET_CODE" echo "HTTP POST controller: $HTTP_POST_CODE" echo "Fatal DB prima del test: $ERRORS_BEFORE" echo "Fatal DB dopo il test: $ERRORS_AFTER" echo echo "ESITO: OK" echo "CORREZIONE LIMITATA AL CONTROLLER ANGULAR DI SILVELOX" echo "NESSUNA MODIFICA AL DATABASE" echo "NESSUNA MODIFICA ALLE STAMPE" echo "NESSUNA MODIFICA AD ALTRI PORTALI" } >> "$REPORT" # La patch e' ormai verificata: da questo punto un errore di packaging non deve annullarla. PATCH_ACTIVE=0 # Rimuove dai corpi HTTP qualsiasi contenuto applicativo non necessario al report. rm -f -- "$WORKDIR/http_get_body.txt" "$WORKDIR/http_post_body.txt" \ "$WORKDIR/db_test.php" "$WORKDIR/patch.php" "$CREDENTIALS_FILE" "$BACKUP" ( cd "$WORKDIR" zip -q -9 "$OUTPUT_ZIP" ./* ) zip -T "$OUTPUT_ZIP" >/dev/null # Verifica che non siano rimaste directory di lavoro nella cartella di lancio. if find "$PWD" -maxdepth 1 -type d -name ".${STEP_NAME}.*" ! -path "$WORKDIR" | grep -q .; then log "ERRORE: rilevate workdir residue precedenti." exit 50 fi printf 'PATCH COMPLETATA\nZIP: %s\n' "$OUTPUT_ZIP"