Skip to content

Instantly share code, notes, and snippets.

@qatoqat
Created November 30, 2025 09:06
Show Gist options
  • Select an option

  • Save qatoqat/96c3a58a584d6f0c87fc0961df80909f to your computer and use it in GitHub Desktop.

Select an option

Save qatoqat/96c3a58a584d6f0c87fc0961df80909f to your computer and use it in GitHub Desktop.
#!/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