00001: ( set -Eeuo pipefail; export LC_ALL=C; export DEBIAN_FRONTEND=noninteractive; EXPECTED_HOST="Work"; EXPECTED_USER="ray"; APP="/home/ray/ARMOR/apps/shire-mobile"; PACKAGE="com.shire.mobile"; APP_BUILD="$APP/app/build.gradle.kts"; MANIFEST="$APP/app/src/main/AndroidManifest.xml"; MAIN_ACTIVITY="$APP/app/src/main/java/com/shire/mobile/MainActivity.kt"; SENTINEL_MOBILE="$APP/app/src/main/java/com/shire/mobile/SentinelMobileAlerts.kt"; GRADLE_PROPERTIES="$APP/gradle.properties"; APK="$APP/app/build/outputs/apk/debug/app-debug.apk"; GATEWAY_SCRIPT="/usr/local/lib/shire-mobile-gateway/server.py"; GATEWAY_ENV="/etc/shire-mobile-gateway/mobile-gateway.env"; GATEWAY_SERVICE="shire-mobile-gateway.service"; SENTINEL_BRIDGE="/usr/local/lib/shire-sentinel/alert_bridge.py"; SENTINEL_CONFIG="/etc/shire-sentinel/alert-bridge.json"; BACKUP_ROOT="/SHiREVault/Backup/OSBackups"; STAMP="$(date -u +%Y%m%dT%H%M%SZ)"; EVIDENCE="$BACKUP_ROOT/SHIRE-MOBILE-SENTINEL-PIXEL-FIXED-$STAMP"; WORK_DIR="$(mktemp -d /tmp/shire-mobile-sentinel-fixed.XXXXXX)"; SUCCESS=0; MOBILE_DEPLOYED=0; SENTINEL_FILE_EXISTED=0; finish() { rc=$?; if [[ "$SUCCESS" -ne 1 ]]; then echo; echo "============================================================"; echo " FIXED PIXEL DEPLOYMENT FAILED — SAFE ROLLBACK"; echo "============================================================"; for entry in "app-build.before:$APP_BUILD" "manifest.before:$MANIFEST" "main.before:$MAIN_ACTIVITY"; do backup="${entry%%:*}"; target="${entry#*:}"; if [[ -f "$WORK_DIR/$backup" ]]; then install -o ray -g ray -m 0644 "$WORK_DIR/$backup" "$target"; fi; done; if [[ "$SENTINEL_FILE_EXISTED" -eq 1 ]] && [[ -f "$WORK_DIR/sentinel.before" ]]; then install -o ray -g ray -m 0644 "$WORK_DIR/sentinel.before" "$SENTINEL_MOBILE"; else rm -f "$SENTINEL_MOBILE"; fi; if [[ -f "$WORK_DIR/gateway-env.before" ]]; then sudo install -o root -g root -m 0600 "$WORK_DIR/gateway-env.before" "$GATEWAY_ENV"; sudo systemctl restart "$GATEWAY_SERVICE" >/dev/null 2>&1 || true; fi; if [[ "$MOBILE_DEPLOYED" -eq 1 ]] && [[ -s "$WORK_DIR/old-mobile.apk" ]]; then adb install -r -d "$WORK_DIR/old-mobile.apk" >/dev/null 2>&1 || true; adb shell am force-stop "$PACKAGE" >/dev/null 2>&1 || true; adb shell am start -n "$PACKAGE/.MainActivity" >/dev/null 2>&1 || true; fi; echo "Rollback complete."; echo "Stage 1 dual-token access was restored."; echo "The previous Pixel application was restored when available."; echo "SENTiNEL, UFW, Tailscale, router and DNS were preserved."; fi; rm -rf "$WORK_DIR"; exit "$rc"; }; trap finish EXIT; echo "============================================================"; echo " SHiRE MOBILE — CORRECTED SENTINEL PIXEL DEPLOYMENT"; echo "============================================================"; if [[ "$(hostname -s)" != "$EXPECTED_HOST" ]]; then echo "STOP: Expected hostname '$EXPECTED_HOST'."; exit 1; fi; if [[ "$(id -un)" != "$EXPECTED_USER" ]]; then echo "STOP: Run this block as user '$EXPECTED_USER'."; exit 1; fi; for path in "$APP" "$APP_BUILD" "$MANIFEST" "$MAIN_ACTIVITY" "$GRADLE_PROPERTIES" "$GATEWAY_SCRIPT" "$GATEWAY_ENV" "$SENTINEL_BRIDGE" "$SENTINEL_CONFIG"; do if [[ ! -e "$path" ]]; then echo "STOP: Required path is missing:"; echo "$path"; exit 1; fi; done; if [[ ! -x "$APP/gradlew" ]]; then echo "STOP: Gradle wrapper is not executable."; exit 1; fi; if ! command -v adb >/dev/null 2>&1; then echo "STOP: adb is unavailable."; exit 1; fi; if [[ "$(adb get-state 2>/dev/null || true)" != "device" ]]; then echo "STOP: Pixel is not connected and authorised."; exit 1; fi; VAULT_REAL="$(readlink -f /SHiREVault 2>/dev/null || true)"; if [[ -z "$VAULT_REAL" || ! -e "$VAULT_REAL" ]]; then echo "STOP: /SHiREVault could not be resolved."; exit 1; fi; CIFS_INFO="$( 00005: )"; if [[ -z "$CIFS_INFO" ]]; then echo "STOP: SHiREVault is not backed by CIFS."; exit 1; fi; mkdir -p "$EVIDENCE"; sudo -v; WRITE_TEST="$EVIDENCE/.write-test-$$"; printf 'SHiRE Mobile corrected SENTiNEL deployment\n' > "$WRITE_TEST"; rm -f "$WRITE_TEST"; echo "Hostname: $(hostname -s)"; echo "Mobile project: $APP"; echo "SHiREVault: $CIFS_INFO"; echo "Evidence: $EVIDENCE"; echo; echo "=== VERIFYING STAGE 1 ==="; for service in shire-sentinel-suricata.service shire-sentinel-alert.service tailscaled.service "$GATEWAY_SERVICE"; do active="$(systemctl is-active "$service" 2>/dev/null || true)"; printf '%-42s %s\n' "$service" "$active"; if [[ "$active" != "active" ]]; then echo "STOP: Required service is not active: $service"; exit 1; fi; done | tee "$EVIDENCE/services-before.txt"; if ! sudo ufw status | grep -q '^Status: active'; then echo "STOP: UFW is not active."; exit 1; fi; if ! grep -q 'SHiREMobileGateway/0.7.3' "$GATEWAY_SCRIPT"; then echo "STOP: Stage 1 gateway version is missing."; exit 1; fi; if ! grep -q 'def mobile_sentinel_alerts()' "$GATEWAY_SCRIPT"; then echo "STOP: Stable SENTiNEL mobile feed is missing."; exit 1; fi; PRIMARY_TOKEN="$( 00061: )"; if [[ -z "$OLD_APK_REMOTE" ]]; then echo "STOP: Existing Pixel APK could not be located."; exit 1; fi; adb pull "$OLD_APK_REMOTE" "$WORK_DIR/old-mobile.apk" >/dev/null; if [[ ! -s "$WORK_DIR/old-mobile.apk" ]]; then echo "STOP: Existing Pixel rollback APK is empty."; exit 1; fi; mkdir -p "$EVIDENCE/source-before"; cp "$APP_BUILD" "$EVIDENCE/source-before/build.gradle.kts"; cp "$MANIFEST" "$EVIDENCE/source-before/AndroidManifest.xml"; cp "$MAIN_ACTIVITY" "$EVIDENCE/source-before/MainActivity.kt"; find "$EVIDENCE/source-before" -type f -print0 | sort -z | xargs -0 sha256sum > "$EVIDENCE/source-before.sha256"; echo "PASS: Source and Pixel rollback copies retained."; echo "NOTICE: The APK and token-bearing files remain only in private temporary storage."; echo; echo "=== INSTALLING CORRECTED ANDROID SENTINEL SERVICE ==="; 00944: r"versionCode\s*=\s*(\d+)", 00950: "Android versionCode was not found." 00956: r"versionCode\s*=\s*\d+", 00957: f"versionCode = {new_code}", 00963: r'versionName\s*=\s*"[^"]+"', 00964: 'versionName = "0.7.3-sentinel-mobile-alerts"', 00974: chown ray:ray "$MAIN_ACTIVITY" "$MANIFEST" "$APP_BUILD"; chmod 0644 "$MAIN_ACTIVITY" "$MANIFEST" "$APP_BUILD"; echo "PASS: MainActivity and manifest patched."; echo "PASS: Version set to 0.7.3-sentinel-mobile-alerts."; echo; echo "=== BUILDING CORRECTED APK ==="; cd "$APP"; ./gradlew --no-daemon clean assembleDebug 2>&1 | tee "$EVIDENCE/android-build.txt"; if [[ ! -s "$APK" ]]; then echo "STOP: APK was not produced."; exit 1; fi; sha256sum "$APK" | tee "$EVIDENCE/app-debug-apk.sha256"; echo "PASS: Corrected APK built successfully."; echo; echo "=== INSTALLING ON PIXEL ==="; adb install -r "$APK" 2>&1 | tee "$EVIDENCE/adb-install.txt"; MOBILE_DEPLOYED=1; adb shell pm grant "$PACKAGE" android.permission.POST_NOTIFICATIONS >/dev/null 2>&1 || true; adb shell am force-stop "$PACKAGE"; adb logcat -c; adb shell am start -W -n "$PACKAGE/.MainActivity" 2>&1 | tee "$EVIDENCE/pixel-launch.txt"; echo "Waiting for the first private SENTiNEL poll..."; FIRST_POLL=0; for attempt in $(seq 1 120); do if adb logcat -d -s SHiRESentinel:I '*:S' 2>/dev/null | grep -Eq 'POLL_SEEDED|POLL_READY'; then FIRST_POLL=1; break; fi; sleep 1; done; adb logcat -d -s SHiRESentinel:I '*:S' | tee "$EVIDENCE/pixel-first-poll.txt"; if [[ "$FIRST_POLL" -ne 1 ]]; then echo "STOP: Pixel did not complete its first private poll."; exit 1; fi; adb shell dumpsys activity services > "$WORK_DIR/services.txt"; if ! grep -q 'SentinelAlertLinkService' "$WORK_DIR/services.txt"; then echo "STOP: Live SENTiNEL foreground service is not running."; exit 1; fi; echo "PASS: Pixel connected to the private SENTiNEL feed."; echo "PASS: Existing alerts were seeded without flooding."; echo; echo "=== RUNNING MOBILE SELF-TEST ==="; echo "Watch the Pixel for:"; echo "'SENTiNEL mobile self-test'"; adb logcat -c; sudo /usr/bin/python3 "$SENTINEL_BRIDGE" --config "$SENTINEL_CONFIG" --self-test | tee "$EVIDENCE/self-test-source.txt"; SELF_TEST_RECEIVED=0; for attempt in $(seq 1 120); do if adb logcat -d -s SHiRESentinel:I '*:S' 2>/dev/null | grep -q 'MOBILE_SELF_TEST'; then SELF_TEST_RECEIVED=1; break; fi; sleep 1; done; adb logcat -d -s SHiRESentinel:I '*:S' | tee "$EVIDENCE/pixel-self-test.txt"; if [[ "$SELF_TEST_RECEIVED" -ne 1 ]]; then echo "STOP: Mobile self-test did not reach the Pixel."; exit 1; fi; echo "PASS: SENTiNEL mobile self-test reached the Pixel."; echo; echo "=== REVOKING OLD TRANSITION TOKEN ==="; python3 - "$GATEWAY_ENV" "$WORK_DIR/gateway-env.final" <<'PY' 01002: sudo install -o root -g root -m 0600 "$WORK_DIR/gateway-env.final" "$GATEWAY_ENV"; sudo systemctl restart "$GATEWAY_SERVICE"; SOCKET_READY=0; for attempt in $(seq 1 60); do if systemctl is-active --quiet "$GATEWAY_SERVICE" && sudo ss -H -lnt "sport = :$GATEWAY_PORT" 2>/dev/null | grep -q .; then SOCKET_READY=1; break; fi; sleep 1; done; if [[ "$SOCKET_READY" -ne 1 ]]; then echo "STOP: Gateway did not return after token revocation."; exit 1; fi; FINAL_NEW_HTTP="$( 01028: )"; { echo "Primary token: HTTP $FINAL_NEW_HTTP"; echo "Old token: HTTP $FINAL_OLD_HTTP"; echo "No token: HTTP $FINAL_NO_TOKEN_HTTP"; } | tee "$EVIDENCE/final-authentication.txt"; if [[ "$FINAL_NEW_HTTP" != "200" || "$FINAL_OLD_HTTP" != "401" || "$FINAL_NO_TOKEN_HTTP" != "401" ]]; then echo "STOP: Final token state is not correct."; exit 1; fi; echo "PASS: New Pixel token remains active."; echo "PASS: Old token is revoked."; echo "PASS: Unauthenticated access remains rejected."; echo; echo "=== VERIFYING PIXEL AFTER REVOCATION ==="; adb logcat -c; POST_REVOKE_POLL=0; for attempt in $(seq 1 120); do if adb logcat -d -s SHiRESentinel:I '*:S' 2>/dev/null | grep -q 'POLL_READY'; then POST_REVOKE_POLL=1; break; fi; sleep 1; done; adb logcat -d -s SHiRESentinel:I '*:S' | tee "$EVIDENCE/pixel-after-revocation.txt"; if [[ "$POST_REVOKE_POLL" -ne 1 ]]; then echo "STOP: Pixel stopped authenticating after revocation."; exit 1; fi; echo "PASS: Pixel continues polling with the new token."; echo; echo "=== FINAL PIXEL STATE ==="; adb shell dumpsys package "$PACKAGE" > "$WORK_DIR/package.txt"; grep -E 'versionCode=|versionName=|POST_NOTIFICATIONS|FOREGROUND_SERVICE' "$WORK_DIR/package.txt" | head -n 100 | tee "$EVIDENCE/pixel-package.txt"; if ! grep -q 'versionName=0.7.3-sentinel-mobile-alerts' "$WORK_DIR/package.txt"; then echo "STOP: Expected SHiRE Mobile version is not installed."; exit 1; fi; adb shell dumpsys activity services > "$WORK_DIR/services-final.txt"; if ! grep -q 'SentinelAlertLinkService' "$WORK_DIR/services-final.txt"; then echo "STOP: Live SENTiNEL link is not running."; exit 1; fi; adb shell dumpsys jobscheduler > "$WORK_DIR/jobs.txt"; if ! grep -q 'SentinelAlertPollJobService' "$WORK_DIR/jobs.txt"; then echo "STOP: Fifteen-minute fallback job is not scheduled."; exit 1; fi; echo "PASS: Correct SHiRE Mobile version installed."; echo "PASS: Live 30-second alert link is active."; echo "PASS: Fifteen-minute persisted fallback is scheduled."; echo; echo "=== FINAL SERVER SAFETY CHECK ==="; for service in shire-sentinel-suricata.service shire-sentinel-alert.service tailscaled.service "$GATEWAY_SERVICE"; do enabled="$(systemctl is-enabled "$service" 2>/dev/null || true)"; active="$(systemctl is-active "$service" 2>/dev/null || true)"; printf '%-42s enabled=%-12s active=%s\n' "$service" "$enabled" "$active"; if [[ "$active" != "active" ]]; then echo "STOP: Required service is not active: $service"; exit 1; fi; done | tee "$EVIDENCE/services-final.txt"; if ! sudo ufw status | grep -q '^Status: active'; then echo "STOP: UFW is no longer active."; exit 1; fi; echo; echo "=== EVIDENCE INTEGRITY ==="; { echo "Android compile fix: android.app.job imports"; echo "Mobile version: 0.7.3-sentinel-mobile-alerts"; echo "Pixel deployment: passed"; echo "Private foreground link: active"; echo "Polling interval: 30 seconds"; echo "Fallback interval: 15 minutes"; echo "Mobile self-test: passed"; echo "Primary token: active"; echo "Old transition token: revoked"; echo "Unauthenticated access: rejected"; echo "Public ports added: no"; echo "Router changed: no"; echo "DNS changed: no"; echo "Automatic blocking: not enabled"; } > "$EVIDENCE/deployment-summary.txt"; find "$EVIDENCE" -maxdepth 2 -type f ! -name 'all-files.sha256' -print0 | sort -z | xargs -0 sha256sum > "$EVIDENCE/all-files.sha256"; sha256sum "$EVIDENCE/all-files.sha256"; SUCCESS=1; echo; echo "============================================================"; echo " SHIRE MOBILE SENTINEL ALERTS ARE LIVE"; echo "============================================================"; echo "Evidence:"; echo "$EVIDENCE"; echo; echo "Mobile version: 0.7.3-sentinel-mobile-alerts"; echo "Private route: Pixel -> Tailscale -> Gateway -> SENTiNEL"; echo "Live alert checks: every 30 seconds"; echo "Fallback checks: every 15 minutes"; echo "Critical/high alerts: active"; echo "Family-safety alert categories: active"; echo "Duplicate suppression: active"; echo "Lock-screen details: private"; echo "End-to-end mobile self-test: passed"; echo "Old exposed token: revoked"; echo "Public ports: none added"; echo "Router and DNS: unchanged"; echo; echo "SENTiNEL can now alert you through SHiRE Mobile."; )