Skip to content

Instantly share code, notes, and snippets.

@da-moon
Last active September 6, 2024 02:12
Show Gist options
  • Save da-moon/51f23971feb005d5389642e56ce33bff to your computer and use it in GitHub Desktop.
Save da-moon/51f23971feb005d5389642e56ce33bff to your computer and use it in GitHub Desktop.
Powershell script to setup SSH server in WSL so that Windows users can SSH into WSL. It can be useful for "remote" development with Editors such as Lapce or Zed
# Flexible WSL2 SSH Setup Script with Distribution Detection
# Ensure running as Administrator
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Warning "Please run this script as Administrator!"
Exit
}
# Function to get the default WSL distribution
function Get-DefaultWSLDistribution {
$wslOutput = wsl -l -v | Out-String
$defaultDistroLine = ($wslOutput -split "`n" | Where-Object { $_ -match '\*' }).Trim()
if ($defaultDistroLine) {
$distroName = ($defaultDistroLine -split '\s+')[1]
$distroName = $distroName -replace "`0", ""
return $distroName
}
return $null
}
# # Alternative function. I kept it just so that I have different approaches written down
# function Get-DefaultWSLDistribution {
# $wslOutput = wsl -l -v
# $defaultDistroLine = $wslOutput | Select-String -Pattern '\*'
# if ($defaultDistroLine) {
# # Convert the matched line to a string and trim it
# $defaultDistroLine = $defaultDistroLine.ToString().Trim()
# # Split the line by spaces and select the second element which should be the distro name
# $distroName = $defaultDistroLine -split '\s+' | Select-Object -Index 1
# # handle null characters
# $distroName = $wslDistro -replace "`0", ""
# return $distroName
# } else {
# Write-Error "No default WSL distribution found."
# return $null
# }
# }
# Function to get WSL2 IP address
function Get-WSL2IPAddress($distroName) {
wsl -d $distroName -e bash -c "ip addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'"
}
# Get the default WSL distribution or prompt user to enter one
$wslDistro = Get-DefaultWSLDistribution
if (-not $wslDistro) {
$wslDistro = Read-Host "Enter the name of your WSL distribution"
}
Write-Host "Using WSL distribution: $wslDistro"
wsl -u "root" -d "$($wslDistro)" /bin/bash -lc (@'
set -ex;
if command -v apt-get &> /dev/null; then
apt-get update -q && apt-get install -yq openssh-server
elif command -v pacman &> /dev/null; then
pacman -Syu --noconfirm && pacman -S --needed --noconfirm openssh
elif command -v dnf &> /dev/null; then
dnf update -y && dnf install -y openssh-server
else
echo 'Unsupported package manager. Please install OpenSSH server manually.'
exit 1
fi
ssh-keygen -A
sed -i -E 's,^#?Port.*$,Port 2022,' /etc/ssh/sshd_config
echo "PasswordAuthentication no" | tee -a /etc/ssh/sshd_config
sed -i "/.*PermitRootLogin.*/d" /etc/ssh/sshd_config
echo "PermitRootLogin no" | tee -a /etc/ssh/sshd_config
sed -i "/.*UsePAM.*/d" /etc/ssh/sshd_config
"UsePAM no" | tee -a /etc/ssh/sshd_config
sed -i "/.*PubkeyAuthentication.*/d" /etc/ssh/sshd_config
echo "PubkeyAuthentication yes" | tee -a /etc/ssh/sshd_config
sed -i -e '/^AcceptEnv/!{$s/$/\nAcceptEnv TERM_PROGRAM COLORTERM/}' -e '/^AcceptEnv/{ /TERM_PROGRAM/!s/$/ TERM_PROGRAM/; /COLORTERM/!s/$/ COLORTERM/ }' -e '/^AcceptEnv/h;/^AcceptEnv/d;$G' /etc/ssh/sshd_config
if command -v systemctl &> /dev/null; then
systemctl enable sshd || systemctl enable ssh
systemctl start sshd || systemctl start ssh
else
service sshd start || service ssh start
fi
'@) ;
# Get WSL2 IP Address
$wslIP = Get-WSL2IPAddress $wslDistro
# Configure Windows Firewall
New-NetFirewallRule -Name "WSL2 SSH" -DisplayName "WSL2 SSH" -Direction Inbound -Protocol TCP -LocalPort 2022 -Action Allow -RemoteAddress 127.0.0.1
# Set up port forwarding
netsh interface portproxy delete v4tov4 listenport=2022 listenaddress=127.0.0.1
netsh interface portproxy add v4tov4 listenport=2022 listenaddress=127.0.0.1 connectport=2022 connectaddress=$wslIP
# Verify the port proxy settings
netsh interface portproxy show v4tov4
# Display the WSL IP for verification
Write-Host "WSL IP: $wslIP"
# Create a startup script for persistent port forwarding
$scriptContent = @"
`$wslDistro = '$wslDistro'
`$wslIP = wsl -d `$wslDistro -e bash -c "ip addr show eth0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'"
netsh interface portproxy delete v4tov4 listenport=2022 listenaddress=127.0.0.1 | Out-Null
Write-Host -f darkgreen "[INFO] Refreshing Windows Firewall config for WSL [`$wslDistro] with IP [`$wslIP]"
netsh interface portproxy add v4tov4 listenport=2022 listenaddress=127.0.0.1 connectport=2022 connectaddress=`$wslIP
"@
$scriptContent | Out-File -FilePath "$env:USERPROFILE\ConfigureWSLSSHForwarding.ps1" -Encoding ASCII
# Create a scheduled task to run the script at startup
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File $env:USERPROFILE\ConfigureWSLSSHForwarding.ps1"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId (whoami) -RunLevel Highest
Register-ScheduledTask -TaskName "WSL2 SSH Setup" -Action $action -Trigger $trigger -Principal $principal -Force
# Optional: Set up SSH key authentication
$KEY_TYPE="ed25519" ;
$KEY_NAME="wsl_$($wslDistro)"
$sshKeyPath = "$env:USERPROFILE\.ssh\id_$($KEY_TYPE)_$($KEY_NAME)"
if (-not (Test-Path $sshKeyPath)) {
ssh-keygen -q -N '""' -t "$KEY_TYPE" -f "$sshKeyPath" -C "$($ENV:USERNAME)@$($ENV:COMPUTERNAME)"
}
$PUB_KEY = Get-Content "$sshKeyPath.pub"
wsl /bin/bash -c "$(
@'
set -x
mkdir -p "$HOME/.ssh"
printenv "PUB_KEY" >> "$HOME/.ssh/authorized_keys"
chmod 644 "$HOME/.ssh/authorized_keys"
cat "$HOME/.ssh/authorized_keys"
'@ -f ($PUB_KEY -replace "'", "''")
)"
$PUB_KEY = Get-Content "$sshKeyPath.pub"
wsl -- /bin/bash -c (
(@'
PUB_KEY='{0}';
if [ -n "${PUB_KEY}" ]; then
echo 'Public Key: ${PUB_KEY}'
mkdir -p '$HOME/.ssh'
echo 'Adding key to authorized_keys...'
echo "${PUB_KEY}" >> "${HOME}/.ssh/authorized_keys"
echo 'Setting permissions...'
chmod 644 "${HOME}/.ssh/authorized_keys"
echo 'Contents of authorized_keys:'
cat "${HOME}/.ssh/authorized_keys"
else
echo 'PUB_KEY is empty or not set'
exit 1;
fi
'@ -replace '\$\{(\w+)\}','$$${1}' -replace '"', '\"' -replace '\$(?!{)', '\$' -replace '\$\{(\w+)\}','\${${1}}'
) -f $PUB_KEY
)
$DEFAULT_WSL_USER=$(wsl /bin/bash -c "id -un")
$sshConfigPath = "$env:USERPROFILE\.ssh\config"
$wslHostEntry = @"
Host wsl_$($wslDistro)
HostName localhost
User $DEFAULT_WSL_USER
IdentityFile $sshKeyPath
Port 2022
RequestTTY yes
IdentitiesOnly yes
StrictHostKeyChecking no
CheckHostIP no
MACs hmac-sha2-256
UserKnownHostsFile /dev/null
"@
if (Test-Path $sshConfigPath) {
$configContent = Get-Content $sshConfigPath -Raw
if ($configContent -notmatch "Host wsl_$($wslDistro)") {
Add-Content $sshConfigPath "`n$wslHostEntry"
Write-Host "Added WSL host to SSH config."
} else {
Write-Host "WSL host already exists in SSH config. No changes made."
}
} else {
New-Item -Path $sshConfigPath -ItemType File -Force | Out-Null
Set-Content $sshConfigPath $wslHostEntry
Write-Host "Created SSH config with WSL host."
}
Write-Host "Setup complete. You can now SSH into your $wslDistro WSL2 instance using:"
Write-Host "ssh wsl_$($wslDistro)"
Write-Host ""
Write-Host -f darkcyan '[NOTE] you must run "powershell $env:USERPROFILE\ConfigureWSLSSHForwarding.ps1" after every WSL restart'
Write-Host ""
Write-Host -f darkyellow '
[WARN] Additionally, depending on your WSL/WSLg version,
there might be issues with your wayland/x11 session which can impact your SSH experience.
refer to the following issue for a solution:
https://github.com/microsoft/wslg/issues/1032#issuecomment-2310369848
'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment