|
#!/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 "$@" |