diff --git a/package/default.nix b/package/default.nix index b289679..21c4508 100644 --- a/package/default.nix +++ b/package/default.nix @@ -109,6 +109,9 @@ }; dbToolPkgs = pkgs.callPackage ./db-tool { inherit self; }; linuxDevShellPkgs = pkgs.callPackage ./linux-devshell {}; + windowsDevShellPkgs = pkgs.callPackage ./windows-devshell { + inherit (linuxDevShellPkgs) linux-devshell-standalone; + }; in { py3-datetime = pkgs.callPackage ./py3-datetime.nix {}; py3-marzban = pkgs.callPackage ./py3-marzban.nix { inherit self; }; @@ -149,6 +152,7 @@ in { "hectic-inheritance" = dbToolPkgs."hectic-inheritance"; "linux-devshell" = linuxDevShellPkgs.linux-devshell; "linux-devshell-standalone" = linuxDevShellPkgs.linux-devshell-standalone; + "windows-devshell-standalone" = windowsDevShellPkgs.windows-devshell-standalone; nbt2json = pkgs.callPackage ./nbt2json {}; hemar-parser = pkgs.callPackage ./hemar/parser {}; AstroTuxLauncher = pkgs.callPackage ./AstroTuxLauncher.nix {}; diff --git a/package/windows-devshell/default.nix b/package/windows-devshell/default.nix new file mode 100644 index 0000000..562bd1e --- /dev/null +++ b/package/windows-devshell/default.nix @@ -0,0 +1,26 @@ +{ pkgs, writeTextFile, lib, linux-devshell-standalone }: +let + psScriptTemplate = builtins.readFile ./windows-devshell.ps1; + + # Get the linux-devshell standalone script content and base64 encode it + linuxDevShellBase64 = lib.removeSuffix "\n" + (builtins.readFile + (pkgs.runCommand "base64-linux-devshell" {} + '' + ${pkgs.coreutils}/bin/base64 -w 0 ${linux-devshell-standalone} > $out + '')); + + # Standalone PowerShell script (single file for Windows) + windowsDevShellStandalone = writeTextFile { + name = "windows-devshell.ps1"; + executable = false; + text = lib.replaceStrings ["@LINUX_DEVSHELL_BASE64@"] [linuxDevShellBase64] psScriptTemplate; + meta = { + description = "Standalone windows-devshell PowerShell script (single file)"; + }; + }; + +in +{ + windows-devshell-standalone = windowsDevShellStandalone; +} diff --git a/package/windows-devshell/windows-devshell.ps1 b/package/windows-devshell/windows-devshell.ps1 new file mode 100644 index 0000000..5ece8a9 --- /dev/null +++ b/package/windows-devshell/windows-devshell.ps1 @@ -0,0 +1,173 @@ +# windows-devshell.ps1 +# Install WSL (if needed) and enter Nix development shell + +function Write-Info($msg) { + Write-Host "[INFO] $msg" -ForegroundColor Cyan +} + +function Write-Success($msg) { + Write-Host "[OK] $msg" -ForegroundColor Green +} + +function Write-Error($msg) { + Write-Host "[ERROR] $msg" -ForegroundColor Red +} + +function Write-Warning($msg) { + Write-Host "[WARN] $msg" -ForegroundColor Yellow +} + +function Test-Admin { + $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) + return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +# Check if WSL is installed +Write-Info "Checking WSL installation..." +$wslInstalled = $false +try { + $wslCheck = wsl --status 2>&1 + if ($LASTEXITCODE -eq 0) { + $wslInstalled = $true + Write-Success "WSL is already installed" + } +} catch {} + +# Check if a distro is installed +$distroInstalled = $false +if ($wslInstalled) { + Write-Info "Checking WSL distributions..." + try { + $distros = wsl --list --quiet 2>$null + if ($distros) { + $distroInstalled = $true + Write-Success "WSL distribution found" + } + } catch {} +} + +# If WSL or distro is missing, we need admin to install +if (-not $wslInstalled -or -not $distroInstalled) { + if (-not (Test-Admin)) { + Write-Error "WSL setup requires Administrator privileges." + Write-Info "Please run PowerShell as Administrator for first-time setup." + exit 1 + } +} + +# Install WSL if not present +if (-not $wslInstalled) { + Write-Info "Installing WSL..." + + Write-Info "Enabling WSL feature..." + dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart | Out-Null + + Write-Info "Enabling Virtual Machine Platform..." + dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart | Out-Null + + Write-Info "Downloading WSL2 kernel update..." + $kernelUrl = "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi" + $kernelInstaller = "$env:TEMP\wsl_update_x64.msi" + try { + Invoke-WebRequest -Uri $kernelUrl -OutFile $kernelInstaller -UseBasicParsing + } catch { + Write-Warning "Could not download WSL2 kernel update. Will try to continue..." + } + + if (Test-Path $kernelInstaller) { + Write-Info "Installing WSL2 kernel update..." + Start-Process -FilePath msiexec.exe -ArgumentList "/i", $kernelInstaller, "/quiet", "/norestart" -Wait + } + + Write-Info "Setting WSL default version to 2..." + wsl --set-default-version 2 2>$null + + Write-Info "Installing WSL core..." + wsl --install --no-distribution + + Write-Success "WSL installed. You may need to restart your computer." + Write-Info "Please restart and run this script again." + exit 0 +} + +# Install Ubuntu if no distro +if (-not $distroInstalled) { + Write-Info "No WSL distribution found. Installing Ubuntu..." + wsl --install -d Ubuntu --no-launch + + Write-Info "Waiting for Ubuntu installation to complete..." + $maxAttempts = 60 + $attempt = 0 + while ($attempt -lt $maxAttempts) { + Start-Sleep -Seconds 5 + $attempt++ + try { + $distros = wsl --list --quiet 2>$null + if ($distros) { + break + } + } catch {} + Write-Info "Waiting... ($attempt/$maxAttempts)" + } + + if (-not $distros) { + Write-Error "Ubuntu installation timed out. Please check WSL status manually." + exit 1 + } + Write-Success "Ubuntu installed" +} + +# Check if a distro is installed +Write-Info "Checking WSL distributions..." +$distros = @() +try { + $distros = wsl --list --quiet 2>$null +} catch {} + +if (-not $distros) { + Write-Info "No WSL distribution found. Installing Ubuntu..." + wsl --install -d Ubuntu --no-launch + + # Wait for installation + Write-Info "Waiting for Ubuntu installation to complete..." + $maxAttempts = 60 + $attempt = 0 + while ($attempt -lt $maxAttempts) { + Start-Sleep -Seconds 5 + $attempt++ + try { + $distros = wsl --list --quiet 2>$null + if ($distros) { + break + } + } catch {} + Write-Info "Waiting... ($attempt/$maxAttempts)" + } + + if (-not $distros) { + Write-Error "Ubuntu installation timed out. Please check WSL status manually." + exit 1 + } + Write-Success "Ubuntu installed" +} else { + Write-Success "WSL distribution found" +} + +# Create the linux-devshell script inside WSL +Write-Info "Preparing linux-devshell script..." + +$linuxDevShellBase64 = "@LINUX_DEVSHELL_BASE64@" + +$wslTempPath = "/tmp/windows-devshell.sh" +$psTempPath = wsl wslpath -w $wslTempPath + +# Decode base64 and write to temp file +$decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($linuxDevShellBase64)) +Set-Content -Path $psTempPath -Value $decoded -Encoding UTF8 -NoNewline + +# Make it executable and run +Write-Info "Running linux-devshell inside WSL..." +Write-Info "This will install Nix (if needed) and enter the development shell." +Write-Host "" + +wsl -e bash -c "chmod +x $wslTempPath && exec $wslTempPath" diff --git a/test/package/default.nix b/test/package/default.nix index a0305ea..47bc54f 100644 --- a/test/package/default.nix +++ b/test/package/default.nix @@ -3,4 +3,5 @@ (import ./hemar { inherit system inputs self pkgs; }) // (import (./. + "/sentinèlla") { inherit system inputs self pkgs; }) // (import ./db-tool { inherit system inputs self pkgs; }) // - (import ./linux-devshell { inherit system inputs self pkgs; }) + (import ./linux-devshell { inherit system inputs self pkgs; }) // + (import ./windows-devshell { inherit system inputs self pkgs; }) diff --git a/test/package/windows-devshell/default.nix b/test/package/windows-devshell/default.nix new file mode 100644 index 0000000..235ba8c --- /dev/null +++ b/test/package/windows-devshell/default.nix @@ -0,0 +1,44 @@ +{ system, inputs, self, pkgs }: +let + lib = inputs.nixpkgs.lib; + + windowsDevShellStandalone = self.packages.${system}.windows-devshell-standalone; + + mkTest = testName: testDrv: pkgs.runCommand "windows-devshell-test-${testName}" + { + nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep ]; + windowsDevShellStandalone = windowsDevShellStandalone; + } '' + ${builtins.readFile self.legacyPackages.${system}.helpers.posix-shell.log} + test=${testDrv} + ${builtins.readFile ./launch.sh} + mkdir -p "$out" + ''; + + testDir = builtins.readDir ./test; + testDrvs = + lib.mapAttrs' (n: v: + lib.nameValuePair (lib.removeSuffix ".sh" n) v + ) (lib.filterAttrs (_: v: v != null) + (lib.mapAttrs (n: t: + if t == "directory" then + pkgs.runCommand "test-${n}" {} '' + if ! [ -f ${./test + "/${n}" + /run.sh} ]; then + echo "no run.sh in test/${n}" + exit 1 + fi + mkdir -p "$out" + cp -r ${./test + "/${n}"}/* "$out/" + chmod +x "$out/run.sh" + '' + else if lib.hasSuffix ".sh" n then + pkgs.runCommand "test-${lib.removeSuffix ".sh" n}" {} '' + mkdir -p "$out" + install -Dm755 ${./test + "/${n}"} "$out/run.sh" + '' + else + null + ) testDir)); + +in + (lib.mapAttrs' (name: drv: lib.nameValuePair "windows-${name}" (mkTest "windows-${name}" drv)) testDrvs) diff --git a/test/package/windows-devshell/launch.sh b/test/package/windows-devshell/launch.sh new file mode 100644 index 0000000..a3ec8cc --- /dev/null +++ b/test/package/windows-devshell/launch.sh @@ -0,0 +1,3 @@ +set -eu + +. "$test/run.sh" diff --git a/test/package/windows-devshell/test/standalone-structure.sh b/test/package/windows-devshell/test/standalone-structure.sh new file mode 100644 index 0000000..3f43ec6 --- /dev/null +++ b/test/package/windows-devshell/test/standalone-structure.sh @@ -0,0 +1,64 @@ +#!/bin/dash +set -eu + +log info "Checking windows-devshell standalone script structure..." + +# Check file exists +if ! [ -f "${windowsDevShellStandalone}" ]; then + log error "windows-devshell standalone script not found" + exit 1 +fi + +# Check it's a PowerShell script (should NOT have hardcoded #Requires -RunAsAdministrator) +if head -1 "${windowsDevShellStandalone}" | grep -q "#Requires -RunAsAdministrator"; then + log error "Script should not hardcode #Requires -RunAsAdministrator" + exit 1 +fi + +# Check it contains base64 placeholder replacement (should be actual base64 now) +if grep -q "@LINUX_DEVSHELL_BASE64@" "${windowsDevShellStandalone}"; then + log error "Base64 placeholder was not replaced" + exit 1 +fi + +# Check it contains the base64 decode logic +if ! grep -q "FromBase64String" "${windowsDevShellStandalone}"; then + log error "Script missing base64 decode logic" + exit 1 +fi + +# Check it contains WSL installation logic +if ! grep -q "wsl --status" "${windowsDevShellStandalone}"; then + log error "Script missing WSL status check" + exit 1 +fi + +if ! grep -q "wsl --install" "${windowsDevShellStandalone}"; then + log error "Script missing WSL install command" + exit 1 +fi + +# Check it contains admin check +if ! grep -q "Administrator" "${windowsDevShellStandalone}"; then + log error "Script missing admin privilege check" + exit 1 +fi + +log success "Standalone script has correct structure" + +# Verify the embedded base64 is valid by checking it's non-empty and only contains base64 chars +base64_content=$(grep '^\$linuxDevShellBase64 = ' "${windowsDevShellStandalone}" | sed 's/.*= "//;s/"$//') +if [ -z "$base64_content" ]; then + log error "Embedded base64 content is empty" + exit 1 +fi + +# Check base64 content length is reasonable (should be at least a few hundred chars for the linux script) +content_len=${#base64_content} +if [ "$content_len" -lt 100 ]; then + log error "Embedded base64 content too short ($content_len chars)" + exit 1 +fi + +log success "Embedded base64 content looks valid ($content_len chars)" +log success "Standalone structure test passed" diff --git a/test/package/windows-devshell/test/syntax-check.sh b/test/package/windows-devshell/test/syntax-check.sh new file mode 100644 index 0000000..5329f42 --- /dev/null +++ b/test/package/windows-devshell/test/syntax-check.sh @@ -0,0 +1,40 @@ +#!/bin/dash +set -eu + +log info "Checking PowerShell script syntax (basic validation)..." + +# Since we can't run PowerShell in Nix, we do basic structural validation + +# Check for balanced braces +open_braces=$(grep -o '{' "${windowsDevShellStandalone}" | wc -l) +close_braces=$(grep -o '}' "${windowsDevShellStandalone}" | wc -l) +if [ "$open_braces" -ne "$close_braces" ]; then + log error "Unbalanced braces: $open_braces open, $close_braces close" + exit 1 +fi + +# Check for balanced parentheses +open_parens=$(grep -o '(' "${windowsDevShellStandalone}" | wc -l) +close_parens=$(grep -o ')' "${windowsDevShellStandalone}" | wc -l) +if [ "$open_parens" -ne "$close_parens" ]; then + log error "Unbalanced parentheses: $open_parens open, $close_parens close" + exit 1 +fi + +# Check no obvious syntax errors (unclosed strings) +# Count quotes - should be even +quotes=$(grep -o '"' "${windowsDevShellStandalone}" | wc -l) +if [ $((quotes % 2)) -ne 0 ]; then + log error "Unbalanced quotes: $quotes total (should be even)" + exit 1 +fi + +# Check script has reasonable length +lines=$(wc -l < "${windowsDevShellStandalone}") +if [ "$lines" -lt 50 ]; then + log error "Script too short ($lines lines)" + exit 1 +fi + +log success "PowerShell script passes basic structural validation" +log success "Script is $lines lines, braces/parentheses/quotes balanced"