Created
June 13, 2025 06:59
-
-
Save airtonix/ec97ec8a93d73405367857b5dab451d0 to your computer and use it in GitHub Desktop.
Quickly provision new wsl instances
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
#!/usr/bin/env pwsh | |
# get named args from command line | |
# -- name <name> --user <user> --repo <repo> | |
# leave repo empty to prompt for it | |
# if name is not provided, use "ubuntu-<date>" | |
param ( | |
[string]$name = "ubuntu-$((Get-Date).ToString('yyyy-MM-dd'))", | |
[string]$user = ($env:USERNAME -replace '[^a-zA-Z0-9]', '-').ToLower(), | |
) | |
# | |
# Loggers | |
# | |
function Log { | |
param ( | |
[Parameter(Mandatory=$true)] | |
[string]$Text, | |
[ValidateSet("Black", "DarkBlue", "DarkGreen", "DarkCyan", "DarkRed", "DarkMagenta", "DarkYellow", "Gray", "DarkGray", "Blue", "Green", "Cyan", "Red", "Magenta", "Yellow", "White")] | |
[string]$Color = "White" | |
) | |
# Writer multiline text to the console with color | |
Write-Host " " | |
Write-Host $Text -ForegroundColor $Color | |
Write-Host " " | |
} | |
function Success { | |
param ( | |
[Parameter(Mandatory=$true)] | |
[string]$Text | |
) | |
Log $Text -Color "Green" | |
} | |
function Error { | |
param ( | |
[Parameter(Mandatory=$true)] | |
[string]$Text | |
) | |
Log $Text -Color "Red" | |
} | |
function Warning { | |
param ( | |
[Parameter(Mandatory=$true)] | |
[string]$Text | |
) | |
Log $Text -Color "Yellow" | |
} | |
function Info { | |
param ( | |
[Parameter(Mandatory=$true)] | |
[string]$Text | |
) | |
Log $Text -Color "Cyan" | |
} | |
# | |
# Core Functions | |
# | |
function Is-Ubuntu-Installed() { | |
# Run wsl --list and capture output | |
$wslOutput = wsl --list --quiet | |
# Define the exact line to match (e.g., a specific distribution name) | |
$exactLine = "$name" | |
# remove (Default) from each line | |
$wslOutput = $wslOutput | | |
ForEach-Object { $_ -replace '\(Default\)', '' } | # Remove (Default) from each line | |
Where-Object { $_ -ne '' } | |
foreach ($line in $wslOutput) { | |
if ($line -eq $exactLine) { | |
return $true | |
} | |
} | |
return $false | |
} | |
function Assert-Is-Ubuntu-Installed() { | |
if (-not (Is-Ubuntu-Installed)) { | |
Error "Ubuntu WSL2 '$name' is not installed. Please install it first." | |
exit 1 | |
} | |
} | |
function Install-Ubuntu { | |
$exists = Is-Ubuntu-Installed -name "$name" | |
if ($exists) { | |
return | |
} | |
Info "Ubuntu WSL2 '$name' Installing." | |
Info "Installing Ubuntu WSL2 '$name'" | |
wsl --install "Ubuntu" --name "$name" | |
Success "Installation complete." | |
return "$name" | |
} | |
function Update-Ubuntu { | |
Assert-Is-Ubuntu-Installed | |
Info "Updating Ubuntu WSL2 '$name'" | |
wsl -d "$name" ` | |
--user root ` | |
--exec bash -c 'apt update && apt upgrade -y' | |
wsl -d "$name" ` | |
--user root ` | |
--exec bash -c 'apt install -y git curl wget zsh build-essential libssl-dev libffi-dev libbz2-dev libreadline-dev libreadline6-dev bzip2 zlib1g-dev' | |
if ($LASTEXITCODE -ne 0) { | |
Error "Failed to update Ubuntu WSL2 '$name'" | |
exit 1 | |
} | |
Success "Ubuntu WSL2 '$name' updated successfully" | |
} | |
function Shutdown-Ubuntu() { | |
Assert-Is-Ubuntu-Installed | |
Info "Shutting down Ubuntu WSL2 '$name'" | |
wsl --shutdown | |
if ($LASTEXITCODE -ne 0) { | |
Error "Failed to shut down Ubuntu WSL2 '$name'" | |
exit 1 | |
} | |
Success "Ubuntu WSL2 '$name' shut down successfully" | |
} | |
function Update-User() { | |
Assert-Is-Ubuntu-Installed | |
# does user exist? true or false | |
$exists = wsl -d "$name" ` | |
--user root ` | |
--exec bash -c "id $user" 2>&1 | |
if ($exists -match "no such user") { | |
Write-Host "User '$user' does not exist. Creating user." | |
} else { | |
Write-Host "User '$user' already exists. Skipping user creation." | |
return | |
} | |
write-host "Creating user '$user' on Ubuntu WSL2 '$name'" | |
wsl -d "$name" ` | |
--user root ` | |
--exec bash -c ' | |
useradd \ | |
--create-home \ | |
--shell /usr/bin/bash \ | |
--groups adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,netdev \ | |
--password $(read -sp Password: pw; echo $pw | openssl passwd -1 -stdin) \ | |
$user | |
' | |
wsl -d "$name" ` | |
--user root ` | |
--exec bash -c "echo '$user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers" | |
wsl -d "$name" ` | |
--user root ` | |
--exec bash -c "id $user && echo 'User $user created successfully' || echo 'Failed to create user $user'" | |
wsl -d "$name" ` | |
--user root ` | |
--exec bash -c "cat << EOF > /etc/wsl.conf | |
[user] | |
default=$user | |
EOF" | |
wsl --terminate "$name" | |
} | |
function Insert-IntoShellProfile() { | |
param ( | |
[string]$profilePath = "/home/$user/.bashrc", | |
[string]$label = "Custom Insertion", | |
[string]$insertion = "" | |
) | |
Assert-Is-Ubuntu-Installed | |
# if no insertion is provided, return | |
if (-not $insertion) { | |
return | |
} | |
Info "Adding $label to shell profile '$profilePath'" | |
# Check if the insertion already exists in the profile | |
wsl -d "$name" ` | |
--user "$user" ` | |
--exec bash -c "grep -q '$insertion' $profilePath 2>/dev/null || echo '$insertion' >> $profilePath" | |
if ($LASTEXITCODE -ne 0) { | |
Error "Failed to add $label to shell profile '$profilePath' on Ubuntu WSL2 '$name' as '$user'" | |
exit 1 | |
} | |
Success "$label added to shell profile '$profilePath' on Ubuntu WSL2 '$name' as '$user'" | |
} | |
# https://gist.github.com/onomatopellan/90024008a0d8c8a2ed6fa57e8b64df54 | |
function Create-SharedDrive() { | |
# download alpine linux image | |
url="https://github.com/yuk7/AlpineWSL/releases/download/3.11.5-1/Alpine.zip" | |
# download the file | |
$zipFile = "Alpine.zip" | |
if (-not (Test-Path $zipFile)) { | |
Info "Downloading Alpine Linux image from $url" | |
Invoke-WebRequest -Uri $url -OutFile $zipFile | |
} else { | |
Info "Alpine Linux image already downloaded." | |
} | |
# unzip it | |
Expand-Archive -Path $zipFile -DestinationPath "Alpine" -Force | |
# run Alpine.exe | |
$alpineExe = "Alpine\Alpine.exe" | |
if (-not (Test-Path $alpineExe)) { | |
Error "Alpine executable not found at $alpineExe" | |
exit 1 | |
} | |
Info "Running Alpine executable to create shared drive" | |
Start-Process -FilePath $alpineExe -ArgumentList "--install" -Wait | |
if ($LASTEXITCODE -ne 0) { | |
Error "Failed to run Alpine executable to create shared drive" | |
exit 1 | |
} | |
Info "Create your shared drive using Alpine Linux on WSL2" | |
Write-Host "You're about to create a new user in the Alpine Distro." | |
Write-Host "Please use the same username '$user':" | |
Write-Host "Press Enter to continue..." | |
Read-Host | |
Start-Process -FilePath $alpineExe -ArgumentList "--set-default" -Wait | |
Success "Shared drive created successfully using Alpine Linux on WSL2" | |
# modify our ~/.profile to automount the shared drive | |
Insert-IntoShellProfile ` | |
-profilePath "/home/$user/.profile" ` | |
-label "Alpine Shared Drive Mount" ` | |
-insertion "[ ! /mnt/Store ] && { mkdir -p /mnt/Store && wsl.exe -d Alpine mount --bind /home/$user /mnt/Store }" | |
Shutdown-Ubuntu | |
} | |
# | |
# Tasks | |
# | |
# Installs mise on Ubuntu WSL2 | |
function Task-InstallMise() { | |
Assert-Is-Ubuntu-Installed | |
Info "Installing mise on Ubuntu WSL2 '$name' as '$user'" | |
wsl -d "$name" ` | |
--user "$user" ` | |
--exec bash -c "curl https://mise.run | sh" | |
wsl -d "$name" ` | |
--user "$user" ` | |
--exec bash -c "mise settings experimental=true" | |
$activationString = 'eval "$(mise activate)"' | |
Insert-IntoShellProfile ` | |
-profilePath "/home/$user/.bashrc" ` | |
-label "mise activation" ` | |
-insertion $activationString | |
if ($LASTEXITCODE -ne 0) { | |
Error "Failed to install mise on Ubuntu WSL2 '$name' as '$user'" | |
exit 1 | |
} | |
Success "mise installed successfully on Ubuntu WSL2 '$name' as '$user'" | |
} | |
# Just needs to run tasks in tmp, but in recent wsl2 ubuntu versions | |
# there's an issue where tmp dir is not writable by the user | |
# so we point it to somewhere else | |
function Task-FixXdgJustfile() { | |
# add tmpdir to the .bashrc file | |
Assert-Is-Ubuntu-Installed | |
Info "Fixing xdg justfile on Ubuntu WSL2 '$name' for user '$user'" | |
$bashrcPath = "/home/$user/.bashrc" | |
$exportString = "export XDG_RUNTIME_DIR=`$(mktemp -d /tmp/xdg-runtime-XXXXXX)" | |
Insert-IntoShellProfile ` | |
-profilePath $bashrcPath ` | |
-label "XDG Runtime Dir Fix" ` | |
-insertion $exportString | |
} | |
$FILE_MiseHuskyEnv = @" | |
#!/usr/bin/env bash | |
eval "$(mise activate --quiet)" | |
"@ | |
# we need a ~/.huskyrc file that activates mise | |
function Task-HuskyMiseEnv() { | |
Assert-Is-Ubuntu-Installed | |
Info "Creating Husky mise environment on Ubuntu WSL2 '$name' for user '$user'" | |
$huskyrcPath = "/home/$user/.huskyrc" | |
Insert-IntoShellProfile ` | |
-profilePath $huskyrcPath ` | |
-label "Husky mise environment" ` | |
-insertion $FILE_MiseHuskyEnv | |
Success "Husky mise environment created successfully at $huskyrcPath on Ubuntu WSL2 '$name' as '$user'" | |
} | |
$FILE_StarshipUpdateCheck = @" | |
# Check for updates and notify if available | |
function check_updates() { | |
# if not apt is installed, return | |
if ! command -v apt &> /dev/null; then | |
return | |
fi | |
# Check for updates | |
updates=`$(apt list --upgradable 2>/dev/null | wc -l) | |
if [ "$updates" -gt 1 ]; then | |
echo "Updates available: `$((updates - 1))" | |
return 0 | |
fi | |
echo "No updates available." | |
} | |
function starship_precmd_user_func() { | |
check_updates | |
} | |
starship_precmd_user_func="starship_precmd_user_func" | |
"@ | |
# Creates a script that runs in the prompt | |
# each time this runs it tests if apt | |
function Task-CreateAptUpdatePrompt() { | |
Assert-Is-Ubuntu-Installed | |
# we're going to install starship and define | |
# a user prompt that checks for outstanding updates | |
Info "Creating update prompt for Ubuntu WSL2 '$name' as '$user'" | |
$bashrcPath = "/home/$user/.bashrc" | |
Insert-IntoShellProfile ` | |
-profilePath $bashrcPath ` | |
-label "Update Prompt" ` | |
-insertion $FILE_StarshipUpdateCheck | |
if ($LASTEXITCODE -ne 0) { | |
Error "Failed to create update prompt in shell profile '$bashrcPath' on Ubuntu WSL2 '$name' as '$user'" | |
exit 1 | |
} | |
Success "Update prompt created in shell profile '$bashrcPath' on Ubuntu WSL2 '$name' as '$user'" | |
} | |
# | |
# Main script execution | |
# | |
if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) { | |
Write-Host "WSL is not installed. Please install WSL first." | |
exit 1 | |
} | |
write-host "Provisioning WSL2 Ubuntu '$name' for user '$user'" | |
write-host "Using repo '$repo'" | |
Install-Ubuntu | |
Update-Ubuntu | |
Update-User | |
Task-InstallMise | |
Task-FixXdgJustfile |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment