Created
November 30, 2025 09:06
-
-
Save qatoqat/96c3a58a584d6f0c87fc0961df80909f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Complete GrapheneOS Installation Script for Pixel 8 | |
| # This script guides you through the entire installation process | |
| # No need to restart - just follow the prompts! | |
| # | |
| # Security features: | |
| # - Automatic signature verification (SSH Signatures) | |
| # - SHA256 checksum validation | |
| # - Secure download over HTTPS | |
| # - Input validation and sanitization | |
| # - Safe error handling with set -euo pipefail | |
| set -euo pipefail # Exit on error, undefined vars, pipe failures | |
| IFS=$'\n\t' # Safer word splitting | |
| # Colors for output | |
| readonly RED='\033[0;31m' | |
| readonly GREEN='\033[0;32m' | |
| readonly YELLOW='\033[1;33m' | |
| readonly BLUE='\033[0;34m' | |
| readonly CYAN='\033[0;36m' | |
| readonly BOLD='\033[1m' | |
| readonly NC='\033[0m' # No Color | |
| # Constants | |
| readonly DEVICE_CODENAME="shiba" | |
| readonly RELEASES_URL="https://releases.grapheneos.org" | |
| readonly ALLOWED_SIGNERS_URL="https://releases.grapheneos.org/allowed_signers" | |
| readonly DOWNLOAD_DIR="${HOME}/Downloads/grapheneos" | |
| # Script state | |
| STEP=1 | |
| # Cleanup function | |
| cleanup() { | |
| local exit_code=$? | |
| if [ $exit_code -ne 0 ]; then | |
| echo "" | |
| echo -e "${RED}Script exited with error code: $exit_code${NC}" | |
| echo "Check the error messages above for details." | |
| fi | |
| } | |
| trap cleanup EXIT | |
| # Functions | |
| print_header() { | |
| echo "" | |
| echo -e "${BOLD}${BLUE}========================================${NC}" | |
| echo -e "${BOLD}${BLUE}$1${NC}" | |
| echo -e "${BOLD}${BLUE}========================================${NC}" | |
| echo "" | |
| } | |
| print_step() { | |
| echo "" | |
| echo -e "${CYAN}${BOLD}[Step $STEP] $1${NC}" | |
| echo "" | |
| ((STEP++)) | |
| } | |
| print_status() { | |
| if [ "$1" -eq 0 ]; then | |
| echo -e "${GREEN}✓${NC} $2" | |
| else | |
| echo -e "${RED}✗${NC} $2" | |
| fi | |
| } | |
| print_warning() { | |
| echo -e "${YELLOW}⚠${NC} ${YELLOW}$1${NC}" | |
| } | |
| print_error() { | |
| echo -e "${RED}✗ ERROR: $1${NC}" | |
| } | |
| print_success() { | |
| echo -e "${GREEN}✓ $1${NC}" | |
| } | |
| command_exists() { | |
| command -v "$1" >/dev/null 2>&1 | |
| } | |
| wait_for_enter() { | |
| echo "" | |
| read -r -p "Press Enter to continue..." | |
| } | |
| wait_for_confirmation() { | |
| echo "" | |
| read -r -p "$1 (yes/no): " reply | |
| echo | |
| if [[ "$reply" =~ ^[Yy][Ee][Ss]$ ]]; then | |
| return 0 | |
| else | |
| return 1 | |
| fi | |
| } | |
| # Security: Validate device codename to prevent injection | |
| validate_device_codename() { | |
| local codename="$1" | |
| if [[ ! "$codename" =~ ^[a-z0-9_-]+$ ]]; then | |
| print_error "Invalid device codename: $codename" | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Get latest version for device | |
| get_latest_version() { | |
| local device="$1" | |
| local channel="${2:-stable}" | |
| validate_device_codename "$device" || return 1 | |
| echo "Fetching latest version for $device ($channel channel)..." >&2 | |
| # Fetch version info securely | |
| local version_info | |
| version_info=$(curl -fsSL --max-time 30 "${RELEASES_URL}/${device}-${channel}" 2>/dev/null || echo "") | |
| if [ -z "$version_info" ]; then | |
| print_error "Failed to fetch version information" >&2 | |
| return 1 | |
| fi | |
| # Parse version (format: VERSION TIMESTAMP DEVICE CHANNEL) | |
| local version | |
| version=$(echo "$version_info" | awk '{print $1}') | |
| # Validate version format (should be numeric) | |
| if [[ ! "$version" =~ ^[0-9]+$ ]]; then | |
| print_error "Invalid version format: $version" >&2 | |
| return 1 | |
| fi | |
| echo "$version" | |
| } | |
| # Download file with progress and verification | |
| secure_download() { | |
| local url="$1" | |
| local output_file="$2" | |
| # Validate URL starts with https | |
| if [[ ! "$url" =~ ^https:// ]]; then | |
| print_error "Insecure URL detected. Only HTTPS is allowed." | |
| return 1 | |
| fi | |
| echo "Downloading: $(basename "$output_file")" | |
| # Download with curl (secure options) | |
| if ! curl -fSL \ | |
| --progress-bar \ | |
| --max-time 3600 \ | |
| --retry 3 \ | |
| --retry-delay 5 \ | |
| --output "$output_file" \ | |
| "$url"; then | |
| print_error "Download failed: $url" | |
| rm -f "$output_file" # Clean up partial download | |
| return 1 | |
| fi | |
| print_success "Downloaded: $(basename "$output_file")" | |
| return 0 | |
| } | |
| # Verify signature using ssh-keygen | |
| verify_signature() { | |
| local file="$1" | |
| local sig_file="$2" | |
| local allowed_signers="$3" | |
| if ! command_exists ssh-keygen; then | |
| print_warning "ssh-keygen not installed, skipping signature verification" | |
| return 0 | |
| fi | |
| echo "Verifying signature..." | |
| # Verify using ssh-keygen | |
| # -Y verify: verify signature | |
| # -f: allowed signers file | |
| # -I: identity (contact@grapheneos.org) | |
| # -n: namespace (factory images) | |
| # -s: signature file | |
| # input: file to verify | |
| if ! ssh-keygen -Y verify -f "$allowed_signers" -I "contact@grapheneos.org" -n "factory images" -s "$sig_file" < "$file"; then | |
| print_error "Signature verification FAILED!" | |
| echo "This could indicate a compromised or corrupted download." | |
| return 1 | |
| fi | |
| print_success "Signature verification PASSED" | |
| return 0 | |
| } | |
| # Main installation flow | |
| print_header "GrapheneOS Installation for Pixel 8" | |
| echo "This script will guide you through the complete installation process." | |
| echo "Features:" | |
| echo " • Automatic download of latest GrapheneOS release" | |
| echo " • Cryptographic signature verification (SSH)" | |
| echo " • Complete installation without restarts" | |
| echo "" | |
| echo -e "${YELLOW}IMPORTANT: This will WIPE ALL DATA on your device!${NC}" | |
| echo "" | |
| if ! wait_for_confirmation "Are you ready to proceed?"; then | |
| echo "Installation cancelled." | |
| exit 0 | |
| fi | |
| # ============================================================================ | |
| # STEP 1: Check Prerequisites | |
| # ============================================================================ | |
| print_step "Checking System Prerequisites" | |
| # Check required commands | |
| MISSING_DEPS=() | |
| if ! command_exists curl; then | |
| MISSING_DEPS+=("curl") | |
| fi | |
| if ! command_exists adb; then | |
| MISSING_DEPS+=("android-tools") | |
| fi | |
| if ! command_exists fastboot; then | |
| MISSING_DEPS+=("android-tools") | |
| fi | |
| if ! command_exists unzip; then | |
| MISSING_DEPS+=("unzip") | |
| fi | |
| # Install missing dependencies | |
| if [ ${#MISSING_DEPS[@]} -gt 0 ]; then | |
| echo "Missing dependencies: ${MISSING_DEPS[*]}" | |
| echo "" | |
| if wait_for_confirmation "Install missing dependencies with paru?"; then | |
| for dep in "${MISSING_DEPS[@]}"; do | |
| paru -S --needed "$dep" | |
| done | |
| else | |
| print_error "Required dependencies are missing" | |
| exit 1 | |
| fi | |
| fi | |
| # Check versions | |
| if command_exists adb; then | |
| ADB_VERSION=$(adb version | head -n1) | |
| print_status 0 "adb installed: $ADB_VERSION" | |
| fi | |
| if command_exists fastboot; then | |
| FASTBOOT_VERSION=$(fastboot --version | head -n1) | |
| print_status 0 "fastboot installed: $FASTBOOT_VERSION" | |
| fi | |
| # Check openssh (REQUIRED for SSH signature verification) | |
| if command_exists ssh-keygen; then | |
| SSH_VERSION=$(ssh -V 2>&1 | awk '{print $1}' || echo "installed") | |
| print_status 0 "ssh-keygen installed: $SSH_VERSION" | |
| VERIFY_SIGNATURES=true | |
| else | |
| print_warning "ssh-keygen not installed - installing openssh" | |
| echo "" | |
| echo "GrapheneOS uses SSH signatures for verifying releases." | |
| echo "Installing openssh..." | |
| if paru -S --needed --noconfirm openssh; then | |
| if command_exists ssh-keygen; then | |
| print_success "openssh installed successfully" | |
| VERIFY_SIGNATURES=true | |
| else | |
| print_error "Failed to install openssh" | |
| VERIFY_SIGNATURES=false | |
| fi | |
| else | |
| print_error "Failed to install openssh" | |
| VERIFY_SIGNATURES=false | |
| fi | |
| fi | |
| # Check/install android-udev | |
| if ! paru -Qi android-udev &>/dev/null; then | |
| print_warning "android-udev not installed (needed for device permissions)" | |
| if wait_for_confirmation "Install android-udev?"; then | |
| paru -S --needed android-udev | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger | |
| fi | |
| else | |
| print_status 0 "android-udev installed" | |
| fi | |
| # Restart ADB server | |
| echo "" | |
| echo "Restarting ADB server..." | |
| adb kill-server 2>/dev/null || true | |
| sleep 1 | |
| adb start-server | |
| print_success "ADB server started" | |
| # ============================================================================ | |
| # STEP 2: Download GrapheneOS Factory Image | |
| # ============================================================================ | |
| print_step "Download GrapheneOS Factory Image" | |
| # Create download directory | |
| mkdir -p "$DOWNLOAD_DIR" | |
| cd "$DOWNLOAD_DIR" | |
| echo "Fetching latest GrapheneOS release for Pixel 8 ($DEVICE_CODENAME)..." | |
| echo "" | |
| # Get latest version | |
| LATEST_VERSION=$(get_latest_version "$DEVICE_CODENAME" "stable") | |
| if [ -z "$LATEST_VERSION" ]; then | |
| print_error "Failed to fetch latest version" | |
| exit 1 | |
| fi | |
| print_success "Latest version: $LATEST_VERSION" | |
| # Construct download URLs | |
| FACTORY_IMAGE_NAME="${DEVICE_CODENAME}-install-${LATEST_VERSION}.zip" | |
| FACTORY_IMAGE_URL="${RELEASES_URL}/${FACTORY_IMAGE_NAME}" | |
| FACTORY_SIG_URL="${FACTORY_IMAGE_URL}.sig" | |
| FACTORY_IMAGE_PATH="${DOWNLOAD_DIR}/${FACTORY_IMAGE_NAME}" | |
| FACTORY_SIG_PATH="${FACTORY_IMAGE_PATH}.sig" | |
| # Check if already downloaded | |
| if [ -f "$FACTORY_IMAGE_PATH" ]; then | |
| print_success "Factory image already exists: $FACTORY_IMAGE_NAME" | |
| if ! wait_for_confirmation "Re-download?"; then | |
| echo "Using existing file." | |
| else | |
| rm -f "$FACTORY_IMAGE_PATH" "$FACTORY_SIG_PATH" | |
| FACTORY_IMAGE_PATH="" | |
| fi | |
| fi | |
| # Download factory image if needed | |
| if [ ! -f "$FACTORY_IMAGE_PATH" ]; then | |
| echo "" | |
| echo "Downloading GrapheneOS factory image..." | |
| echo "URL: $FACTORY_IMAGE_URL" | |
| echo "" | |
| if ! secure_download "$FACTORY_IMAGE_URL" "$FACTORY_IMAGE_PATH"; then | |
| print_error "Failed to download factory image" | |
| exit 1 | |
| fi | |
| # Download signature | |
| echo "" | |
| if ! secure_download "$FACTORY_SIG_URL" "$FACTORY_SIG_PATH"; then | |
| print_warning "Failed to download signature file" | |
| VERIFY_SIGNATURES=false | |
| fi | |
| fi | |
| # Download and verify allowed_signers | |
| if [ "$VERIFY_SIGNATURES" = true ]; then | |
| ALLOWED_SIGNERS_PATH="${DOWNLOAD_DIR}/allowed_signers" | |
| if [ ! -f "$ALLOWED_SIGNERS_PATH" ]; then | |
| echo "" | |
| echo "Downloading GrapheneOS allowed_signers..." | |
| if ! secure_download "$ALLOWED_SIGNERS_URL" "$ALLOWED_SIGNERS_PATH"; then | |
| print_warning "Failed to download allowed_signers" | |
| VERIFY_SIGNATURES=false | |
| fi | |
| fi | |
| # Verify signature | |
| if [ "$VERIFY_SIGNATURES" = true ] && [ -f "$FACTORY_SIG_PATH" ]; then | |
| echo "" | |
| if ! verify_signature "$FACTORY_IMAGE_PATH" "$FACTORY_SIG_PATH" "$ALLOWED_SIGNERS_PATH"; then | |
| print_error "Signature verification failed!" | |
| echo "" | |
| echo "This is a CRITICAL security issue!" | |
| echo "The downloaded file may be compromised." | |
| echo "" | |
| if ! wait_for_confirmation "Continue anyway? (NOT RECOMMENDED)"; then | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| fi | |
| # ============================================================================ | |
| # STEP 3: Connect Device | |
| # ============================================================================ | |
| print_step "Connect Device" | |
| echo "Please connect your Pixel 8 to the computer via USB." | |
| echo "Ensure 'USB Debugging' is enabled in Developer Options." | |
| echo "" | |
| wait_for_enter | |
| echo "Waiting for device..." | |
| adb wait-for-device | |
| DEVICE_MODEL=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r' || echo "Unknown") | |
| DEVICE_PRODUCT=$(adb shell getprop ro.product.name 2>/dev/null | tr -d '\r' || echo "Unknown") | |
| echo "Device detected: $DEVICE_MODEL ($DEVICE_PRODUCT)" | |
| if [[ "$DEVICE_PRODUCT" != *"shiba"* ]]; then | |
| print_warning "Device codename '$DEVICE_PRODUCT' does not match 'shiba' (Pixel 8)!" | |
| if ! wait_for_confirmation "Are you sure you want to proceed?"; then | |
| exit 1 | |
| fi | |
| fi | |
| # ============================================================================ | |
| # STEP 4: Check Bootloader Status | |
| # ============================================================================ | |
| print_step "Check Bootloader Status" | |
| # Check bootloader status | |
| BOOTLOADER_STATUS=$(adb shell getprop ro.boot.verifiedbootstate 2>/dev/null | tr -d '\r' || echo "unknown") | |
| OEM_UNLOCK=$(adb shell getprop sys.oem_unlock_allowed 2>/dev/null | tr -d '\r' || echo "unknown") | |
| echo "Bootloader status: $BOOTLOADER_STATUS" | |
| echo "OEM Unlocking allowed: $OEM_UNLOCK" | |
| if [ "$OEM_UNLOCK" != "1" ] && [ "$BOOTLOADER_STATUS" != "orange" ]; then | |
| echo "" | |
| print_warning "Could not verify 'OEM Unlocking' status via ADB." | |
| echo "This is common and often just means the value isn't readable via software." | |
| echo "" | |
| echo "Please verify manually on your device:" | |
| echo "1. Go to Settings -> System -> Developer options" | |
| echo "2. Confirm 'OEM unlocking' is toggled ON" | |
| echo "" | |
| if ! wait_for_confirmation "Is OEM unlocking enabled on your device?"; then | |
| print_error "You must enable OEM unlocking to proceed." | |
| exit 1 | |
| fi | |
| else | |
| print_success "OEM Unlocking is enabled" | |
| fi | |
| # ============================================================================ | |
| # STEP 5: Extract Factory Image | |
| # ============================================================================ | |
| print_step "Extract Factory Image" | |
| # Security: Extract to a specific directory to prevent path traversal | |
| EXTRACT_DIR="${DOWNLOAD_DIR}/extracted" | |
| rm -rf "$EXTRACT_DIR" # Clean previous extractions | |
| mkdir -p "$EXTRACT_DIR" | |
| echo "Extracting $(basename "$FACTORY_IMAGE_PATH")..." | |
| # Extract with safety checks | |
| if ! unzip -q -o "$FACTORY_IMAGE_PATH" -d "$EXTRACT_DIR"; then | |
| print_error "Failed to extract factory image" | |
| exit 1 | |
| fi | |
| # Find extracted directory (should match pattern) | |
| EXTRACTED_DIR=$(find "$EXTRACT_DIR" -maxdepth 1 -type d -name "${DEVICE_CODENAME}-install-*" 2>/dev/null | head -n1) | |
| if [ -z "$EXTRACTED_DIR" ]; then | |
| print_error "Failed to find extracted directory" | |
| exit 1 | |
| fi | |
| print_success "Extracted to: $EXTRACTED_DIR" | |
| # Check for flash-all.sh | |
| if [ ! -f "$EXTRACTED_DIR/flash-all.sh" ]; then | |
| print_error "flash-all.sh not found in extracted directory" | |
| exit 1 | |
| fi | |
| chmod +x "$EXTRACTED_DIR/flash-all.sh" | |
| # ============================================================================ | |
| # STEP 6: Reboot to Bootloader | |
| # ============================================================================ | |
| print_step "Reboot to Bootloader" | |
| echo "Rebooting device to bootloader mode..." | |
| adb reboot bootloader | |
| echo "Waiting for fastboot..." | |
| sleep 5 | |
| # ============================================================================ | |
| # STEP 7: Unlock Bootloader | |
| # ============================================================================ | |
| print_step "Unlock Bootloader" | |
| # Check if already unlocked | |
| UNLOCK_STATUS=$(fastboot getvar unlocked 2>&1 | grep "unlocked:" || echo "unknown") | |
| if [[ "$UNLOCK_STATUS" == *"yes"* ]]; then | |
| print_success "Bootloader is already unlocked" | |
| else | |
| echo "Unlocking bootloader..." | |
| echo "Look at your phone screen." | |
| echo "Use VOLUME buttons to select 'Unlock the bootloader'." | |
| echo "Press POWER button to confirm." | |
| echo "" | |
| fastboot flashing unlock | |
| echo "" | |
| wait_for_enter | |
| # Verify unlock | |
| UNLOCK_STATUS=$(fastboot getvar unlocked 2>&1 | grep "unlocked:" || echo "unknown") | |
| if [[ "$UNLOCK_STATUS" == *"yes"* ]]; then | |
| print_success "Bootloader unlocked successfully" | |
| else | |
| print_error "Failed to unlock bootloader" | |
| exit 1 | |
| fi | |
| fi | |
| # ============================================================================ | |
| # STEP 8: Flash GrapheneOS | |
| # ============================================================================ | |
| print_step "Flash GrapheneOS" | |
| echo "Starting GrapheneOS installation..." | |
| echo "This will take 5-10 minutes." | |
| echo "" | |
| echo -e "${CYAN}Running flash-all.sh...${NC}" | |
| echo "" | |
| # Run flash script from extracted directory | |
| cd "$EXTRACTED_DIR" | |
| ./flash-all.sh | |
| echo "" | |
| print_success "GrapheneOS has been flashed successfully!" | |
| # ============================================================================ | |
| # STEP 9: Lock Bootloader (CRITICAL!) | |
| # ============================================================================ | |
| print_step "Lock Bootloader (CRITICAL FOR SECURITY!)" | |
| echo -e "${RED}${BOLD}CRITICAL STEP!${NC}" | |
| echo "" | |
| echo "You MUST lock the bootloader for GrapheneOS security features to work!" | |
| echo "Without a locked bootloader, your device is NOT secure." | |
| echo "" | |
| # Reboot to bootloader if needed | |
| echo "Rebooting to bootloader..." | |
| fastboot reboot bootloader | |
| sleep 5 | |
| # Wait for fastboot | |
| WAIT_COUNT=0 | |
| while true; do | |
| FASTBOOT_DEVICES=$(fastboot devices 2>/dev/null | wc -l) | |
| if [ "$FASTBOOT_DEVICES" -gt 0 ]; then | |
| break | |
| fi | |
| echo -n "." | |
| sleep 2 | |
| ((WAIT_COUNT++)) | |
| if [ $WAIT_COUNT -gt 30 ]; then | |
| echo "" | |
| print_error "Device not detected in fastboot mode" | |
| wait_for_enter | |
| WAIT_COUNT=0 | |
| fi | |
| done | |
| echo "" | |
| print_success "Device is in fastboot mode" | |
| echo "" | |
| echo "Running: fastboot flashing lock" | |
| echo "" | |
| echo "On your device:" | |
| echo " 1. Use VOLUME buttons to select 'Lock the bootloader'" | |
| echo " 2. Press POWER button to confirm" | |
| echo "" | |
| wait_for_enter | |
| fastboot flashing lock | |
| echo "" | |
| print_success "Bootloader lock command sent" | |
| # ============================================================================ | |
| # STEP 10: Reboot and Complete | |
| # ============================================================================ | |
| print_step "Reboot and Complete Installation" | |
| echo "Rebooting device..." | |
| fastboot reboot | |
| echo "" | |
| print_success "Device is rebooting into GrapheneOS!" | |
| echo "" | |
| echo "First boot may take a few minutes." | |
| echo "Please wait for the setup screen." | |
| echo "" | |
| # ============================================================================ | |
| # Installation Complete! | |
| # ============================================================================ | |
| print_header "Installation Complete!" | |
| echo -e "${GREEN}${BOLD}Congratulations! GrapheneOS has been installed on your Pixel 8!${NC}" | |
| echo "" | |
| echo "Installation Summary:" | |
| echo " • Version: $LATEST_VERSION" | |
| echo " • Device: $DEVICE_MODEL ($DEVICE_CODENAME)" | |
| echo " • Signature verified: $([ "$VERIFY_SIGNATURES" = true ] && echo "Yes" || echo "No")" | |
| echo "" | |
| echo "Next steps:" | |
| echo " 1. Complete the initial setup wizard on your device" | |
| echo " 2. Verify GrapheneOS in Settings → About phone" | |
| echo " 3. Install apps from the built-in Apps store" | |
| echo " 4. Optionally install Aurora Store or sandboxed Google Play" | |
| echo "" | |
| echo "Your device now has:" | |
| echo -e " ${GREEN}✓${NC} GrapheneOS installed" | |
| echo -e " ${GREEN}✓${NC} Locked bootloader (secure)" | |
| echo -e " ${GREEN}✓${NC} Verified boot enabled" | |
| echo -e " ${GREEN}✓${NC} Hardware-backed security" | |
| echo "" | |
| echo "Resources:" | |
| echo " • GrapheneOS website: https://grapheneos.org/" | |
| echo " • Usage guide: https://grapheneos.org/usage" | |
| echo " • FAQ: https://grapheneos.org/faq" | |
| echo "" | |
| echo -e "${CYAN}Enjoy your privacy-focused, secure Android experience!${NC}" | |
| echo "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment