Skip to content

Instantly share code, notes, and snippets.

@startergo
Last active September 6, 2025 15:31
Show Gist options
  • Select an option

  • Save startergo/b7b87fbb086a8cdc8891b510705a3891 to your computer and use it in GitHub Desktop.

Select an option

Save startergo/b7b87fbb086a8cdc8891b510705a3891 to your computer and use it in GitHub Desktop.
SSH in macOS Recovery mode

macOS Recovery Mode SSH Setup

This repository contains tools and scripts to enable SSH access in macOS Recovery Mode for remote system recovery and administration.

🚀 Quick Start

Automated Setup (Recommended)

In Recovery Mode, you typically run as root by default. Simply execute:

# If you're already root (most common in Recovery Mode):
./ssh_recovery_setup.sh

# If for some reason you're not root and sudo is available:
sudo ./ssh_recovery_setup.sh

This script will:

  • ✅ Configure network if needed
  • ✅ Create a writable RAM disk for SSH configuration
  • ✅ Automatically locate and install your SSH public key
  • ✅ Start SSH daemon with secure configuration
  • ✅ Display connection information

Prerequisites

  • Your SSH public key must exist in your user's ~/.ssh/id_rsa.pub on the main system
  • Network connectivity (script can help configure this)
  • macOS Recovery Mode access
  • Note: Recovery Mode typically runs as root by default, so sudo may not be necessary

📁 Files

  • ssh_recovery_setup.sh - Automated SSH setup script
  • Readme.md - This documentation

🔧 Manual Setup Steps

If you prefer manual setup or need to troubleshoot:

1. Start SSH Daemon

# Start sshd with PAM disabled
sudo /usr/sbin/sshd -o UsePAM=no

# Or with additional options for recovery mode:
sudo /usr/sbin/sshd -o UsePAM=no -o PermitRootLogin=yes -o PasswordAuthentication=no

2. Check Network Configuration

# Verify network is up
ifconfig en0

# If no IP, try DHCP
sudo dhclient en0
# or
sudo ipconfig set en0 DHCP

3. Set Up SSH Access

Since /var/root is read-only in Recovery Mode, you need to create a writable ramdisk:

# Create a 32MB ramdisk and mount it over /var/root
rdsize=$((32*1024*1024/512)) # 32 megabytes
dev=`hdik -drivekey system-image=yes -nomount "ram://$rdsize"`
echo $?; echo $dev # check for errors!
newfs_hfs "$dev"
eval `/usr/bin/stat -s /var/root` # store perms for old mountpoint
mount -t hfs -o union -o nobrowse "$dev" /var/root # magic happens here
chown "$st_uid:$st_gid" /var/root
chmod "$st_mode" /var/root

# Now create .ssh directory and add your public key
mkdir /var/root/.ssh
chmod 700 /var/root/.ssh

# Copy your public key from mounted drive or USB
# Option 1: From main system partition
mount -uw /Volumes/Macintosh\ HD
cat /Volumes/Macintosh\ HD/Users/[your-username]/.ssh/id_rsa.pub > /var/root/.ssh/authorized_keys

# Option 2: From USB drive (more reliable)
cat /Volumes/THUMBDRIVE/id_rsa.pub > /var/root/.ssh/authorized_keys

chmod 600 /var/root/.ssh/authorized_keys

4. Alternative: Enable Password Authentication Temporarily

# If you can't access your keys, enable password auth temporarily
sudo /usr/sbin/sshd -o UsePAM=no -o PasswordAuthentication=yes -o PermitRootLogin=yes

# Set a temporary root password
sudo passwd root

5. Connect from Remote Machine

# Using your existing private key
ssh -i ~/.ssh/id_rsa root@[recovery-machine-ip]

# Or if using password auth
ssh root@[recovery-machine-ip]

6. Find the Recovery Machine's IP

# In Recovery Mode terminal
ifconfig | grep inet

🔐 Security Features

The automated script implements several security best practices:

  • Public Key Authentication: Prioritizes SSH key authentication over passwords
  • Secure Permissions: Sets proper file permissions (700 for .ssh, 600 for authorized_keys)
  • No PAM: Disables PAM which can be problematic in Recovery Mode
  • Fallback Security: If key auth fails, enables password auth as fallback
  • Minimal Attack Surface: Only enables necessary SSH options

🛠 Troubleshooting

Network Issues

  • Ensure Ethernet or Wi-Fi is connected
  • Try manual network configuration: sudo ipconfig set en0 DHCP
  • Check cable connections and network availability

SSH Key Not Found

  • Ensure your public key exists in ~/.ssh/id_rsa.pub on the main system
  • Try copying the key to a USB drive as id_rsa.pub
  • Use password authentication as fallback

Permission Denied

  • Ensure script is executable: chmod +x ssh_recovery_setup.sh
  • Run with sudo: sudo ./ssh_recovery_setup.sh
  • Check that Recovery Mode has proper disk access

Connection Refused

  • Verify SSH daemon is running: ps aux | grep sshd
  • Check IP address: ifconfig | grep inet
  • Ensure firewall isn't blocking port 22

📋 Script Features

The ssh_recovery_setup.sh script includes:

  • Colored Output: Easy-to-read status messages
  • Error Handling: Graceful failure handling with helpful messages
  • Auto-Detection: Automatically finds your SSH public key
  • Multi-Source Support: Checks main drive, USB drives, and external media
  • Fallback Options: Password authentication if key auth fails
  • Recovery Mode Detection: Warns if not running in actual Recovery Mode
  • Network Auto-Configuration: Attempts to configure networking automatically

⚠️ Important Notes

  • Recovery Mode typically runs as root: In most cases, you don't need sudo in Recovery Mode
  • Recovery Mode runs as a different environment from your main macOS installation
  • The /var/root directory is read-only by default, requiring RAM disk setup
  • SSH configuration doesn't persist between Recovery Mode sessions
  • Always use secure authentication methods when possible
  • This setup is intended for emergency recovery situations
  • sudo availability varies in Recovery Mode - the script handles both scenarios

🔄 Recovery Mode Access

To boot into Recovery Mode:

  • Intel Macs: Hold Cmd + R during startup
  • Apple Silicon Macs: Hold power button, then select "Options"

📞 Support

If you encounter issues:

  1. Check the troubleshooting section above
  2. Verify prerequisites are met
  3. Try manual setup steps for debugging
  4. Ensure network connectivity and proper Recovery Mode access

The key insight is that Recovery Mode runs as a different environment, so even though your pubkey is set up on the main system, you need to make it accessible to the Recovery Mode's SSH configuration.

#!/bin/bash
# SSH Recovery Console Setup Script
# This script sets up SSH server in macOS Recovery Mode for remote access
# Assumes public key exists in user's .ssh/id_rsa.pub on main system
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if running in Recovery Mode
check_recovery_mode() {
if [[ ! -d "/Volumes/Recovery" ]] && [[ ! -f "/System/Installation/CDIS/macOS Installer.app" ]]; then
print_warning "This doesn't appear to be Recovery Mode. Continuing anyway..."
else
print_status "Running in Recovery Mode detected"
fi
}
# Function to setup network if needed
setup_network() {
print_status "Checking network configuration..."
# Check if we have an IP address
if ifconfig en0 | grep -q "inet "; then
local ip=$(ifconfig en0 | grep "inet " | awk '{print $2}')
print_success "Network is configured. IP address: $ip"
return 0
fi
print_warning "No IP address found. Attempting to configure network..."
# Try DHCP configuration (in Recovery Mode, we might already be root)
if command -v ipconfig >/dev/null 2>&1; then
if [[ $EUID -eq 0 ]]; then
ipconfig set en0 DHCP
else
sudo ipconfig set en0 DHCP
fi
sleep 3
elif command -v dhclient >/dev/null 2>&1; then
if [[ $EUID -eq 0 ]]; then
dhclient en0
else
sudo dhclient en0
fi
sleep 3
else
print_error "Cannot configure network automatically. Please configure manually."
return 1
fi
# Check again
if ifconfig en0 | grep -q "inet "; then
local ip=$(ifconfig en0 | grep "inet " | awk '{print $2}')
print_success "Network configured successfully. IP address: $ip"
else
print_error "Failed to configure network"
return 1
fi
}
# Function to create RAM disk for writable /var/root
setup_ramdisk() {
print_status "Setting up RAM disk for writable /var/root..."
# Check if already mounted
if mount | grep -q "/var/root"; then
print_warning "/var/root already has a mount. Skipping RAM disk creation."
return 0
fi
# Create 32MB RAM disk (increased size for better reliability)
local rdsize=$((32*1024*1024/512))
local dev=$(hdik -drivekey system-image=yes -nomount "ram://$rdsize" 2>/dev/null)
if [[ $? -ne 0 ]] || [[ -z "$dev" ]]; then
print_error "Failed to create RAM disk"
return 1
fi
print_status "Created RAM disk: $dev"
# Format the RAM disk
if ! newfs_hfs "$dev" >/dev/null 2>&1; then
print_error "Failed to format RAM disk"
return 1
fi
# Store original permissions
eval $(stat -s /var/root)
# Mount the RAM disk over /var/root
if ! mount -t hfs -o union -o nobrowse "$dev" /var/root; then
print_error "Failed to mount RAM disk over /var/root"
return 1
fi
# Restore permissions
chown "$st_uid:$st_gid" /var/root
chmod "$st_mode" /var/root
print_success "RAM disk mounted successfully over /var/root"
}
# Function to mount system disk if needed
mount_system_disk() {
print_status "Checking and mounting system disk..."
# First, check if we're running from a mounted system disk
local script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
local script_volume=""
# Check if script is on a volume that might need mounting
if [[ "$script_path" =~ ^/Volumes/ ]]; then
script_volume=$(echo "$script_path" | sed 's|^/Volumes/\([^/]*\).*|\1|')
print_status "Script is running from volume: $script_volume"
# If script volume exists and has Users directory, it's likely already mounted
if [[ -d "/Volumes/$script_volume/Users" ]]; then
print_success "System disk appears to be already mounted at: /Volumes/$script_volume"
# Try to mount read-write
if [[ $EUID -eq 0 ]]; then
mount -uw "/Volumes/$script_volume" 2>/dev/null || true
else
sudo mount -uw "/Volumes/$script_volume" 2>/dev/null || true
fi
return 0
fi
fi
# Look for common system disk names and identifiers
local system_disk=""
local mount_point=""
# Method 1: Try to find the main system disk using diskutil
if command -v diskutil >/dev/null 2>&1; then
# Get list of APFS volumes that might be the system disk
local apfs_volumes=$(diskutil list | grep "Apple_APFS" | awk '{print $NF}' 2>/dev/null || true)
for disk_id in $apfs_volumes; do
local volume_name=$(diskutil info "$disk_id" | grep "Volume Name" | sed 's/.*: *//' 2>/dev/null || true)
if [[ "$volume_name" =~ (Macintosh HD|System|macOS|Data) ]]; then
system_disk="$disk_id"
print_status "Found potential system disk: $disk_id ($volume_name)"
break
fi
done
# If we found a system disk, try to mount it
if [[ -n "$system_disk" ]]; then
print_status "Attempting to mount system disk: $system_disk"
if diskutil mount "$system_disk" 2>/dev/null; then
# Find where it got mounted
mount_point=$(diskutil info "$system_disk" | grep "Mount Point" | sed 's/.*: *//' 2>/dev/null || true)
if [[ -n "$mount_point" ]] && [[ -d "$mount_point" ]]; then
print_success "System disk mounted at: $mount_point"
# Try to mount read-write if it's read-only
if [[ $EUID -eq 0 ]]; then
mount -uw "$mount_point" 2>/dev/null || true
else
sudo mount -uw "$mount_point" 2>/dev/null || true
fi
return 0
fi
fi
fi
fi
# Method 2: Check if volumes are already mounted in /Volumes
print_status "Checking existing mounted volumes..."
if [[ -d "/Volumes" ]]; then
for volume in /Volumes/*; do
if [[ -d "$volume" ]] && [[ "$(basename "$volume")" != "Recovery" ]]; then
if [[ -d "$volume/Users" ]] || [[ -d "$volume/System" ]]; then
print_success "Found system volume already mounted: $(basename "$volume")"
# Try to mount read-write
if [[ $EUID -eq 0 ]]; then
mount -uw "$volume" 2>/dev/null || true
else
sudo mount -uw "$volume" 2>/dev/null || true
fi
return 0
fi
fi
done
fi
# Method 3: Try common disk identifiers
print_status "Trying to mount common system disk identifiers..."
local common_disks=("disk1s1" "disk1s5" "disk2s1" "disk2s5" "disk0s2" "disk0s1")
for disk_id in "${common_disks[@]}"; do
if diskutil info "$disk_id" >/dev/null 2>&1; then
print_status "Attempting to mount /dev/$disk_id..."
if diskutil mount "$disk_id" 2>/dev/null; then
mount_point=$(diskutil info "$disk_id" | grep "Mount Point" | sed 's/.*: *//' 2>/dev/null || true)
if [[ -n "$mount_point" ]] && [[ -d "$mount_point/Users" ]]; then
print_success "Successfully mounted system disk at: $mount_point"
# Try to mount read-write
if [[ $EUID -eq 0 ]]; then
mount -uw "$mount_point" 2>/dev/null || true
else
sudo mount -uw "$mount_point" 2>/dev/null || true
fi
return 0
fi
fi
fi
done
print_warning "Could not automatically mount system disk"
print_status "You may need to manually mount your system disk using:"
print_status " diskutil list"
print_status " diskutil mount disk1s1 # (replace with your system disk)"
return 1
}
# Function to find and copy public key
setup_ssh_keys() {
print_status "Setting up SSH keys..."
# Create .ssh directory
mkdir -p /var/root/.ssh
chmod 700 /var/root/.ssh
# Try to find the public key from various locations
local pubkey_found=false
local pubkey_content=""
# Location 1: Main system partition (most common)
if [[ -d "/Volumes" ]]; then
for volume in /Volumes/*; do
if [[ -d "$volume" ]] && [[ "$(basename "$volume")" != "Recovery" ]]; then
print_status "Checking volume: $(basename "$volume")"
# Look for user directories
if [[ -d "$volume/Users" ]]; then
for user_dir in "$volume/Users"/*; do
if [[ -d "$user_dir" ]] && [[ "$(basename "$user_dir")" != "Shared" ]]; then
local pubkey_path="$user_dir/.ssh/id_rsa.pub"
if [[ -f "$pubkey_path" ]]; then
print_status "Found public key for user: $(basename "$user_dir")"
pubkey_content=$(cat "$pubkey_path")
pubkey_found=true
break 2
fi
fi
done
fi
fi
done
fi
# Location 2: USB drives or external media
if [[ "$pubkey_found" == false ]]; then
print_status "Checking USB drives and external media..."
for volume in /Volumes/*; do
if [[ -d "$volume" ]]; then
for pubkey_file in "$volume/id_rsa.pub" "$volume"/*.pub "$volume/.ssh/id_rsa.pub"; do
if [[ -f "$pubkey_file" ]]; then
print_status "Found public key on external media: $pubkey_file"
pubkey_content=$(cat "$pubkey_file")
pubkey_found=true
break 2
fi
done
fi
done
fi
if [[ "$pubkey_found" == true ]]; then
echo "$pubkey_content" > /var/root/.ssh/authorized_keys
chmod 600 /var/root/.ssh/authorized_keys
print_success "SSH public key installed successfully"
return 0
else
print_error "Could not find SSH public key"
print_status "Please manually copy your public key to /var/root/.ssh/authorized_keys"
return 1
fi
}
# Function to start SSH daemon
start_ssh_daemon() {
print_status "Starting SSH daemon..."
# Kill any existing sshd processes
pkill -f sshd 2>/dev/null || true
# Start sshd with appropriate options for recovery mode
local sshd_cmd="/usr/sbin/sshd"
local sshd_opts="-o UsePAM=no -o PermitRootLogin=yes -o PasswordAuthentication=no -o PubkeyAuthentication=yes -o AuthorizedKeysFile=/var/root/.ssh/authorized_keys -o StrictModes=no -o Port=22"
if [[ $EUID -eq 0 ]]; then
if $sshd_cmd $sshd_opts; then
print_success "SSH daemon started successfully"
else
print_error "Failed to start SSH daemon"
return 1
fi
else
if sudo $sshd_cmd $sshd_opts; then
print_success "SSH daemon started successfully"
else
print_error "Failed to start SSH daemon"
return 1
fi
fi
}
# Function to display connection information
display_connection_info() {
print_status "SSH Server Setup Complete!"
echo ""
echo "Connection Information:"
echo "======================"
# Get IP addresses
local ips=$(ifconfig | grep "inet " | grep -v "127.0.0.1" | awk '{print $2}')
if [[ -n "$ips" ]]; then
echo "IP Addresses:"
while IFS= read -r ip; do
echo " $ip"
done <<< "$ips"
echo ""
echo "To connect from a remote machine:"
echo " ssh -i ~/.ssh/id_rsa root@<IP_ADDRESS>"
echo ""
echo "Example:"
local first_ip=$(echo "$ips" | head -n1)
echo " ssh -i ~/.ssh/id_rsa root@$first_ip"
else
print_warning "No IP addresses found. Please check network configuration."
fi
echo ""
echo "SSH daemon is running on port 22"
echo "Authentication: Public key only"
echo "User: root"
}
# Function to enable password authentication as fallback
enable_password_auth() {
print_warning "Enabling password authentication as fallback..."
# Kill existing sshd
pkill -f sshd 2>/dev/null || true
# Start sshd with password authentication
local sshd_cmd="/usr/sbin/sshd"
local sshd_opts="-o UsePAM=no -o PermitRootLogin=yes -o PasswordAuthentication=yes -o PubkeyAuthentication=yes -o StrictModes=no -o Port=22"
if [[ $EUID -eq 0 ]]; then
if $sshd_cmd $sshd_opts; then
print_status "Setting temporary root password..."
echo "Please enter a temporary password for root user:"
passwd root
print_success "Password authentication enabled"
echo "You can now connect using: ssh root@<IP_ADDRESS>"
else
print_error "Failed to enable password authentication"
return 1
fi
else
if sudo $sshd_cmd $sshd_opts; then
print_status "Setting temporary root password..."
echo "Please enter a temporary password for root user:"
sudo passwd root
print_success "Password authentication enabled"
echo "You can now connect using: ssh root@<IP_ADDRESS>"
else
print_error "Failed to enable password authentication"
return 1
fi
fi
}
# Main execution
main() {
echo ""
print_status "macOS Recovery Mode SSH Setup Script"
print_status "====================================="
echo ""
# Check if running as root (Recovery Mode typically runs as root by default)
if [[ $EUID -ne 0 ]]; then
# In Recovery Mode, we're usually already root, but check for sudo availability
if command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
print_warning "Not running as root, but sudo is available. Continuing..."
else
print_error "This script requires root privileges"
print_status "In Recovery Mode, you're usually already root by default"
print_status "If not, try running as root or with sudo if available"
exit 1
fi
else
print_status "Running as root - perfect for Recovery Mode"
fi
# Step 1: Check Recovery Mode
check_recovery_mode
# Step 2: Setup network
if ! setup_network; then
print_error "Network setup failed. Cannot continue."
exit 1
fi
# Step 3: Mount system disk
mount_system_disk # This may fail, but we continue anyway
# Step 4: Setup RAM disk
if ! setup_ramdisk; then
print_error "RAM disk setup failed. Cannot continue."
exit 1
fi
# Step 5: Setup SSH keys
if setup_ssh_keys; then
# Step 6: Start SSH daemon with key authentication
if start_ssh_daemon; then
display_connection_info
else
print_warning "SSH daemon failed to start with key auth. Trying password auth..."
enable_password_auth
fi
else
print_warning "Could not setup SSH keys. Enabling password authentication..."
enable_password_auth
fi
echo ""
print_success "Setup complete! SSH server is now running."
}
# Run the main function
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment