Last active
July 29, 2025 06:09
-
-
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 | |
| function List-AvailableDistros { | |
| # List available WSL distros and only include first column | |
| $distroList = wsl --list --online | Where-Object { $_ -ne "" } | Select-Object -Skip 3 | ForEach-Object { $_.Split(" ")[0] } | Where-Object { $_ -ne "" } | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Failed to list available WSL distros." | |
| exit 1 | |
| } | |
| return $distroList | |
| } | |
| # 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 ( | |
| [Parameter(Mandatory=$false)] | |
| [ValidateNotNullOrEmpty()] | |
| [string] | |
| $name = "ubuntu-$((Get-Date).ToString('yyyy-MM-dd'))", | |
| [Parameter(Mandatory=$false)] | |
| [ValidateNotNullOrEmpty()] | |
| [string] | |
| $user = ($env:USERNAME -replace '[^a-zA-Z0-9]', '-').ToLower(), | |
| [Parameter(Mandatory=$false)] | |
| [ValidateNotNullOrEmpty()] | |
| $distro = "Ubuntu", | |
| [string] | |
| $repo = "" | |
| ) | |
| # | |
| # Loggers | |
| # | |
| function Write-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 Write-Success { | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$Text | |
| ) | |
| Write-Log $Text -Color "Green" | |
| } | |
| function Write-Error { | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$Text | |
| ) | |
| Write-Log $Text -Color "Red" | |
| } | |
| function Write-Warning { | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$Text | |
| ) | |
| Write-Log $Text -Color "Yellow" | |
| } | |
| function Write-Info { | |
| param ( | |
| [Parameter(Mandatory=$true)] | |
| [string]$Text | |
| ) | |
| Write-Log $Text -Color "Cyan" | |
| } | |
| # | |
| # Core Functions | |
| # | |
| function Get-IsDistroInstalled() { | |
| # 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-IsDistroInstalled() { | |
| if (-not (Get-IsDistroInstalled)) { | |
| Write-Error "Container '$name' is not installed. Please install it first." | |
| exit 1 | |
| } | |
| } | |
| function Install-Distro { | |
| $exists = Get-IsDistroInstalled -name "$name" | |
| if ($exists) { | |
| return | |
| } | |
| Write-Info "Creating WSL2 Container: '$name'" | |
| wsl --install "$distro" --name "$name" | |
| Write-Success "Installation complete." | |
| return "$name" | |
| } | |
| function Update-Ubuntu { | |
| Assert-IsDistroInstalled | |
| Write-Info "Updating Container '$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) { | |
| Write-Error "Failed to update Container '$name'" | |
| exit 1 | |
| } | |
| Write-Success "Container '$name' updated successfully" | |
| } | |
| function Stop-Container() { | |
| Assert-IsDistroInstalled | |
| Write-Info "Shutting down Container '$name'" | |
| wsl --shutdown | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Failed to shut down Container '$name'" | |
| exit 1 | |
| } | |
| Write-Success "Container '$name' shut down successfully" | |
| } | |
| function Update-User() { | |
| Assert-IsDistroInstalled | |
| # 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-Info "User '$user' does not exist. Creating user." | |
| } else { | |
| Write-Info "User '$user' already exists. Skipping user creation." | |
| return | |
| } | |
| Write-Info "Creating user '$user' on Container '$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 Write-IntoShellProfile() { | |
| param ( | |
| [string]$profilePath = "/home/$user/.bashrc", | |
| [string]$label = "Custom Insertion", | |
| [string]$insertion = "" | |
| ) | |
| Assert-IsDistroInstalled | |
| # if no insertion is provided, return | |
| if (-not $insertion) { | |
| return | |
| } | |
| Write-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) { | |
| Write-Error "Failed to add $label to shell profile '$profilePath' on Container '$name' as '$user'" | |
| exit 1 | |
| } | |
| Write-Success "$label added to shell profile '$profilePath' on Container '$name' as '$user'" | |
| } | |
| # https://gist.github.com/onomatopellan/90024008a0d8c8a2ed6fa57e8b64df54 | |
| function Add-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)) { | |
| Write-Error "Alpine executable not found at $alpineExe" | |
| exit 1 | |
| } | |
| Write-Info "Running Alpine executable to create shared drive" | |
| Start-Process -FilePath $alpineExe -ArgumentList "--install" -Wait | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Failed to run Alpine executable to create shared drive" | |
| exit 1 | |
| } | |
| Write-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 | |
| Write-Success "Shared drive created successfully using Alpine Linux on WSL2" | |
| # modify our ~/.profile to automount the shared drive | |
| Write-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 }" | |
| Close-Container | |
| } | |
| # | |
| # Tasks | |
| # | |
| # Installs mise on Ubuntu WSL2 | |
| function Install-Mise() { | |
| Assert-IsDistroInstalled | |
| Write-Info "Installing mise on Container '$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" | |
| wsl -d "$name" ` | |
| --user "$user" ` | |
| --exec bash -c "mise generate config > ~/.mise/config.toml" | |
| wsl -d "$name" ` | |
| --user "$user" ` | |
| --exec bash -c "mise trust --all" | |
| $activationString = 'eval "$(mise activate bash --quiet)"' | |
| Write-IntoShellProfile ` | |
| -profilePath "/home/$user/.bashrc" ` | |
| -label "mise activation" ` | |
| -insertion $activationString | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Failed to install mise on Container '$name' as '$user'" | |
| exit 1 | |
| } | |
| Write-Success "mise installed successfully on Container '$name' as '$user'" | |
| } | |
| $FILE_MiseHuskyEnv = @" | |
| #!/usr/bin/env bash | |
| eval "\$\(mise activate bash --quiet)" | |
| "@ | |
| # we need a ~/.huskyrc file that activates mise | |
| function Install-MiseHuskyConfig() { | |
| Assert-IsDistroInstalled | |
| Write-Info "Creating Husky mise environment on Container '$name' for user '$user'" | |
| $huskyrcPath = "/home/$user/.huskyrc" | |
| Insert-IntoShellProfile ` | |
| -profilePath $huskyrcPath ` | |
| -label "Husky mise environment" ` | |
| -insertion $FILE_MiseHuskyEnv | |
| Write-Success "Husky mise environment created successfully at $huskyrcPath on Container '$name' as '$user'" | |
| } | |
| function Install-Comtrya(){ | |
| Assert-IsDistroInstalled | |
| Write-Info "Installing Comtrya" | |
| # if no repo is provided, prompt for it | |
| if (-not $repo) { | |
| $repo = Read-Host "Enter the Comtrya repository URL (or leave empty to skip)" | |
| if (-not $repo) { | |
| Write-Host "No repository URL provided. Skipping Comtrya installation." | |
| return | |
| } | |
| } | |
| wsl -d "$name" ` | |
| --user "$user" ` | |
| --exec bash -c "mise use ubi:comtrya/comtrya" | |
| wsl -d "$name" ` | |
| --user "$user" ` | |
| --exec bash -c "comtrya -d $repo apply" | |
| } | |
| function Install-SshKey() { | |
| Assert-IsDistroInstalled | |
| Write-Info "Installing SSH key for user '$user' on Container '$name'" | |
| # Check if the SSH key already exists | |
| $sshKeyPath = "/home/$user/.ssh/id_rsa" | |
| if (Test-Path -Path $sshKeyPath) { | |
| Write-Info "SSH key already exists at $sshKeyPath. Skipping key generation." | |
| return | |
| } | |
| # Generate a new SSH key | |
| wsl -d "$name" ` | |
| --user "$user" ` | |
| --exec bash -c "ssh-keygen -t rsa -b 4096 -f $sshKeyPath -N ''" | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Failed to generate SSH key for user '$user' on Container '$name'" | |
| exit 1 | |
| } | |
| Write-Success "SSH key generated successfully for user '$user' on Container '$name'" | |
| } | |
| function Run-Comtrya() { | |
| Assert-IsDistroInstalled | |
| Write-Info "Running Comtrya on Container '$name' for user '$user'" | |
| # if no repo is provided, prompt for it | |
| if (-not $repo) { | |
| $repo = Read-Host "Enter the Comtrya repository URL (or leave empty to skip)" | |
| if (-not $repo) { | |
| Write-Host "No repository URL provided. Skipping Comtrya run." | |
| return | |
| } | |
| } | |
| wsl -d "$name" ` | |
| --user "$user" ` | |
| --exec bash -c "comtrya -d $repo apply" | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Failed to run Comtrya on Container '$name' for user '$user'" | |
| exit 1 | |
| } | |
| Write-Success "Comtrya run completed successfully on Container '$name' for user '$user'" | |
| } | |
| # | |
| # Main script execution | |
| # | |
| if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) { | |
| Write-Host "WSL is not installed. Please install WSL first." | |
| exit 1 | |
| } | |
| function Provision(){ | |
| write-host "Provisioning WSL2 Ubuntu '$name' for user '$user'" | |
| write-host "Using repo '$repo'" | |
| Install-Distro | |
| Update-Ubuntu | |
| Update-User | |
| Install-Mise | |
| Install-MiseHuskyConfig | |
| Install-Comtrya | |
| } | |
| # Process command line positional arguments | |
| $command = $args[0] | |
| Write-Info "Provisioning Container '$name' for user '$user' with repo '$repo'" | |
| Write-Info "Command: $command" | |
| # if ($command) { | |
| # switch ($command) { | |
| # "install" { | |
| # Install-Distro | |
| # } | |
| # "update" { | |
| # Update-Ubuntu | |
| # } | |
| # "user" { | |
| # Update-User | |
| # } | |
| # "provision" { | |
| # Provision | |
| # } | |
| # "mise" { | |
| # Install-Mise | |
| # Install-MiseHuskyConfig | |
| # } | |
| # "comtrya" { | |
| # if (-not $repo) { | |
| # $repo = Read-Host "Enter the Comtrya repository URL (or leave empty to skip)" | |
| # } | |
| # Install-Comtrya | |
| # } | |
| # default { | |
| # Write-Info "Unknown command: $command" | |
| # Write-Host "Available commands: install, update, user, provision, mise, comtrya" | |
| # } | |
| # } | |
| # } else { | |
| # # Default action when no command is provided | |
| # Provision | |
| # } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment