Install OpenSSH Server during Windows setup
Install it at first logon from the Win32-OpenSSH zip on the seed ISO via Configure-Windows.ps1 -InstallOpenSSH, replacing the manual 05/06 batch files. The administrator public key now comes from the host (SSH_PUBLIC_KEY_FILE or ~/.ssh/id_*.pub) instead of being hardcoded. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
552a292894
commit
65a5780e02
7 files changed
+78
-128
No files matched your search
@@ -174,14 +174,13 @@ VirtIO ISO with `pnputil` on arm64), without which the guest has no network on
|
|||||||
arm64. The installer includes the VirtIO drivers, SPICE vdagent, and QEMU Guest
|
arm64. The installer includes the VirtIO drivers, SPICE vdagent, and QEMU Guest
|
||||||
Agent. The launcher exposes the vdagent channel for host/guest clipboard
|
Agent. The launcher exposes the vdagent channel for host/guest clipboard
|
||||||
integration and the QEMU Guest Agent at `.vm/windows11/qemu-guest-agent.sock`.
|
integration and the QEMU Guest Agent at `.vm/windows11/qemu-guest-agent.sock`.
|
||||||
Change the password after first login. The OpenSSH Server is not installed
|
Change the password after first login. The OpenSSH Server is installed during
|
||||||
automatically. Run `05-Install-OpenSSH-Admin.bat` from the `Installers` folder on
|
the first logon from the Win32-OpenSSH zip for the host architecture, which the
|
||||||
the desktop to install it through Windows Update, which can be slow, or
|
seed ISO carries (downloaded by `vm-images/download-images.sh openssh-win`), so
|
||||||
`06-Install-OpenSSH-Zip-Admin.bat` to install it from the Win32-OpenSSH zip for
|
Windows Update is not needed. Connect with `ssh guofang@localhost -p 2222`; the
|
||||||
the host architecture, which the seed ISO carries (downloaded by
|
seed ISO also carries the host's SSH public key (`$SSH_PUBLIC_KEY_FILE`, else the
|
||||||
`vm-images/download-images.sh openssh-win`), as
|
first of `~/.ssh/id_ed25519.pub`, `id_ecdsa.pub`, `id_rsa.pub`) for passwordless
|
||||||
administrator, then connect with `ssh guofang@localhost -p 2222`; it also installs
|
administrator login.
|
||||||
the host's configured SSH public key for passwordless administrator login.
|
|
||||||
The Windows VM hardware (8G memory, 8 CPUs, system disk, devices)
|
The Windows VM hardware (8G memory, 8 CPUs, system disk, devices)
|
||||||
is defined in `qemu/windows11.ini`, with the accelerators and machine type in
|
is defined in `qemu/windows11.ini`, with the accelerators and machine type in
|
||||||
`qemu/windows11-<os>-<arch>.ini`; the launcher renders the former to
|
`qemu/windows11-<os>-<arch>.ini`; the launcher renders the former to
|
||||||
|
|||||||
@@ -304,6 +304,13 @@ if installing_from_iso:
|
|||||||
seed_sources = [seed_dir / "Autounattend.xml", seed_dir / "Configure-Windows.ps1",
|
seed_sources = [seed_dir / "Autounattend.xml", seed_dir / "Configure-Windows.ps1",
|
||||||
*sorted(seed_dir.glob("[0-9][0-9]-*.bat"))]
|
*sorted(seed_dir.glob("[0-9][0-9]-*.bat"))]
|
||||||
|
|
||||||
|
# build-seed.sh embeds the administrator SSH public key.
|
||||||
|
ssh_key = os.environ.get("SSH_PUBLIC_KEY_FILE") or next(
|
||||||
|
(str(k) for n in ("id_ed25519", "id_ecdsa", "id_rsa")
|
||||||
|
if (k := Path.home() / ".ssh" / f"{n}.pub").is_file()), None)
|
||||||
|
if ssh_key:
|
||||||
|
seed_sources.append(Path(ssh_key))
|
||||||
|
|
||||||
# The seed carries the OpenSSH zip for this architecture; build-seed.sh
|
# The seed carries the OpenSSH zip for this architecture; build-seed.sh
|
||||||
# downloads it when missing.
|
# downloads it when missing.
|
||||||
seed_sources.append(Path(capture(
|
seed_sources.append(Path(capture(
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal
|
|
||||||
title Install OpenSSH Server
|
|
||||||
|
|
||||||
fltmc >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo Please right-click this file and choose "Run as administrator".
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
rem The capability is downloaded through Windows Update, which can be slow.
|
|
||||||
echo Installing the OpenSSH Server Windows capability...
|
|
||||||
dism.exe /Online /Add-Capability /CapabilityName:OpenSSH.Server~~~~0.0.1.0
|
|
||||||
set "RC=%ERRORLEVEL%"
|
|
||||||
if not "%RC%"=="0" if not "%RC%"=="3010" (
|
|
||||||
echo dism exited with code %RC%.
|
|
||||||
pause
|
|
||||||
exit /b %RC%
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Starting the sshd service...
|
|
||||||
sc.exe config sshd start= auto
|
|
||||||
net.exe start sshd >nul 2>&1
|
|
||||||
sc.exe query sshd | find "RUNNING" >nul
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo The sshd service is not running.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
netsh.exe advfirewall firewall show rule name="OpenSSH-Server-In-TCP" >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
netsh.exe advfirewall firewall add rule name="OpenSSH-Server-In-TCP" ^
|
|
||||||
dir=in action=allow protocol=TCP localport=22 profile=private,public
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Installing the administrator authorized key...
|
|
||||||
set "KEYFILE=%ProgramData%\ssh\administrators_authorized_keys"
|
|
||||||
> "%KEYFILE%" echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCwsF2ZDSHDaXM9BdDq3Vxkp6T+DJEakKaVZQJUEHexS34mbrxYAxmGBUPCrddfE01dS31kGS4XHVBa+wGUYodY+nPwnrfD1Q4phKVSzXr65K1Y/sCcjSiANBPWpv2Xd7IMOfLlrdLtmf5QcKWroIqO2LaIDGECwzUUHrfprgeaF9N0FGcmrgbmm23JUk+f6guRQ4br88L4rWIWEBvL9bTdwErNkdfsYE2F/U8aIFpMcVy2eJIOgy8ZjZvkGdm4r730YkTDL1VmAjiHH3/m2R1EOvwLzFhyWKoa4ULk/IQW+8iNnG42QEcaWRM+hMwlVdnNhPJP6WzS+fAEbibD3zM0q2X/4ex+cXRNOCd2/0KhnD2+3p1BM5QeFTzDappkk8W2meQkDE3hvTjtIQCzygFO6LNWtEEa7BTvvlMqF3CZWh88J9XiP750SjESe3Ge5o9A0T2hcJa+KZ1LxVEmNt4nD6E/OD4EymoaLIJZ1fH5ksSFjHzJ+mJouSVjc/H/VSE= guofang@GUOFANG-OFFICE
|
|
||||||
icacls.exe "%KEYFILE%" /inheritance:r /grant "*S-1-5-32-544:F" /grant "*S-1-5-18:F" >nul
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo icacls exited with code %ERRORLEVEL%.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Done. SSH is available on port 22 of this machine.
|
|
||||||
pause
|
|
||||||
exit /b 0
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal
|
|
||||||
title Install OpenSSH Server from zip
|
|
||||||
|
|
||||||
fltmc >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo Please right-click this file and choose "Run as administrator".
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
set "ZIP="
|
|
||||||
for %%Z in ("%~dp0OpenSSH-*.zip") do set "ZIP=%%~fZ"
|
|
||||||
if not defined ZIP (
|
|
||||||
echo No OpenSSH-*.zip was found next to this file.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Installing the OpenSSH Server from %ZIP%...
|
|
||||||
if not exist "%ProgramFiles%\OpenSSH\install-sshd.ps1" (
|
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^
|
|
||||||
"$t = Join-Path $env:TEMP 'openssh'; Remove-Item $t -Recurse -Force -ErrorAction SilentlyContinue; Expand-Archive -LiteralPath $env:ZIP -DestinationPath $t; Remove-Item (Join-Path $env:ProgramFiles 'OpenSSH') -Recurse -Force -ErrorAction SilentlyContinue; Move-Item (Get-ChildItem $t -Directory | Select-Object -First 1).FullName (Join-Path $env:ProgramFiles 'OpenSSH'); Remove-Item $t -Recurse -Force"
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo Extracting the zip failed.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%ProgramFiles%\OpenSSH\install-sshd.ps1"
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo install-sshd.ps1 failed.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Starting the sshd service...
|
|
||||||
sc.exe config sshd start= auto
|
|
||||||
net.exe start sshd >nul 2>&1
|
|
||||||
sc.exe query sshd | find "RUNNING" >nul
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo The sshd service is not running.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
netsh.exe advfirewall firewall show rule name="OpenSSH-Server-In-TCP" >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
netsh.exe advfirewall firewall add rule name="OpenSSH-Server-In-TCP" ^
|
|
||||||
dir=in action=allow protocol=TCP localport=22 profile=private,public
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Installing the administrator authorized key...
|
|
||||||
set "KEYFILE=%ProgramData%\ssh\administrators_authorized_keys"
|
|
||||||
> "%KEYFILE%" echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCwsF2ZDSHDaXM9BdDq3Vxkp6T+DJEakKaVZQJUEHexS34mbrxYAxmGBUPCrddfE01dS31kGS4XHVBa+wGUYodY+nPwnrfD1Q4phKVSzXr65K1Y/sCcjSiANBPWpv2Xd7IMOfLlrdLtmf5QcKWroIqO2LaIDGECwzUUHrfprgeaF9N0FGcmrgbmm23JUk+f6guRQ4br88L4rWIWEBvL9bTdwErNkdfsYE2F/U8aIFpMcVy2eJIOgy8ZjZvkGdm4r730YkTDL1VmAjiHH3/m2R1EOvwLzFhyWKoa4ULk/IQW+8iNnG42QEcaWRM+hMwlVdnNhPJP6WzS+fAEbibD3zM0q2X/4ex+cXRNOCd2/0KhnD2+3p1BM5QeFTzDappkk8W2meQkDE3hvTjtIQCzygFO6LNWtEEa7BTvvlMqF3CZWh88J9XiP750SjESe3Ge5o9A0T2hcJa+KZ1LxVEmNt4nD6E/OD4EymoaLIJZ1fH5ksSFjHzJ+mJouSVjc/H/VSE= guofang@GUOFANG-OFFICE
|
|
||||||
icacls.exe "%KEYFILE%" /inheritance:r /grant "*S-1-5-32-544:F" /grant "*S-1-5-18:F" >nul
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo icacls exited with code %ERRORLEVEL%.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Done. SSH is available on port 22 of this machine.
|
|
||||||
pause
|
|
||||||
exit /b 0
|
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
<SynchronousCommand wcm:action="add">
|
<SynchronousCommand wcm:action="add">
|
||||||
<Order>1</Order>
|
<Order>1</Order>
|
||||||
<Description>Configure Windows from the answer-media script</Description>
|
<Description>Configure Windows from the answer-media script</Description>
|
||||||
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& ('{0}:\Configure-Windows.ps1' -f (Get-Volume -FileSystemLabel CIDATA).DriveLetter) -InstallVirtIO -EnableAutomaticLogon -DisableAutoUpdate -CopyInstallers -NoPause"</CommandLine>
|
<CommandLine>powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& ('{0}:\Configure-Windows.ps1' -f (Get-Volume -FileSystemLabel CIDATA).DriveLetter) -InstallVirtIO -EnableAutomaticLogon -DisableAutoUpdate -CopyInstallers -InstallOpenSSH -NoPause"</CommandLine>
|
||||||
<RequiresUserInput>false</RequiresUserInput>
|
<RequiresUserInput>false</RequiresUserInput>
|
||||||
</SynchronousCommand>
|
</SynchronousCommand>
|
||||||
</FirstLogonCommands>
|
</FirstLogonCommands>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ param(
|
|||||||
[switch]$EnableAutomaticLogon,
|
[switch]$EnableAutomaticLogon,
|
||||||
[switch]$DisableAutoUpdate,
|
[switch]$DisableAutoUpdate,
|
||||||
[switch]$CopyInstallers,
|
[switch]$CopyInstallers,
|
||||||
|
[switch]$InstallOpenSSH,
|
||||||
[switch]$NoPause
|
[switch]$NoPause
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -151,11 +152,52 @@ function Copy-InstallerScripts {
|
|||||||
$target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Installers'
|
$target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Installers'
|
||||||
New-Item -ItemType Directory -Path $target -Force | Out-Null
|
New-Item -ItemType Directory -Path $target -Force | Out-Null
|
||||||
Copy-Item -Path $scripts.FullName -Destination $target -Force
|
Copy-Item -Path $scripts.FullName -Destination $target -Force
|
||||||
|
}
|
||||||
|
|
||||||
# 05-Install-OpenSSH-Admin.bat installs from this zip when it is present.
|
# Installs the OpenSSH Server from the Win32-OpenSSH zip on the answer media,
|
||||||
$zips = @(Get-ChildItem -Path $PSScriptRoot -Filter 'OpenSSH-*.zip' -File)
|
# so no Windows Update download is needed.
|
||||||
if ($zips.Count -gt 0) {
|
function Install-OpenSSHServer {
|
||||||
Copy-Item -Path $zips.FullName -Destination $target -Force
|
$zip = Get-ChildItem -Path $PSScriptRoot -Filter 'OpenSSH-*.zip' -File |
|
||||||
|
Select-Object -First 1
|
||||||
|
if (-not $zip) {
|
||||||
|
throw "No OpenSSH-*.zip was found next to $PSCommandPath."
|
||||||
|
}
|
||||||
|
|
||||||
|
$installDir = Join-Path $env:ProgramFiles 'OpenSSH'
|
||||||
|
if (-not (Test-Path -LiteralPath (Join-Path $installDir 'install-sshd.ps1'))) {
|
||||||
|
$temp = Join-Path $env:TEMP 'openssh'
|
||||||
|
Remove-Item $temp -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Expand-Archive -LiteralPath $zip.FullName -DestinationPath $temp
|
||||||
|
Remove-Item $installDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Move-Item (Get-ChildItem $temp -Directory | Select-Object -First 1).FullName $installDir
|
||||||
|
Remove-Item $temp -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $installDir 'install-sshd.ps1') | Out-Host
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "install-sshd.ps1 failed (exit code $LASTEXITCODE)."
|
||||||
|
}
|
||||||
|
|
||||||
|
Set-Service -Name sshd -StartupType Automatic
|
||||||
|
Start-Service -Name sshd
|
||||||
|
|
||||||
|
if (-not (Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue)) {
|
||||||
|
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' `
|
||||||
|
-Direction Inbound -Action Allow -Protocol TCP -LocalPort 22 -Profile Private, Public | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# build-seed.sh puts the host's public key on the media; without it, only
|
||||||
|
# password login works.
|
||||||
|
$key = Join-Path $PSScriptRoot 'authorized_keys'
|
||||||
|
if (-not (Test-Path -LiteralPath $key)) {
|
||||||
|
Write-Warning "No authorized_keys was found next to $PSCommandPath; skipping the administrator key."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$keyFile = Join-Path $env:ProgramData 'ssh\administrators_authorized_keys'
|
||||||
|
Copy-Item -LiteralPath $key -Destination $keyFile -Force
|
||||||
|
& icacls.exe $keyFile /inheritance:r /grant '*S-1-5-32-544:F' /grant '*S-1-5-18:F' | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "icacls failed (exit code $LASTEXITCODE)."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +215,9 @@ try {
|
|||||||
if ($CopyInstallers) {
|
if ($CopyInstallers) {
|
||||||
Copy-InstallerScripts
|
Copy-InstallerScripts
|
||||||
}
|
}
|
||||||
|
if ($InstallOpenSSH) {
|
||||||
|
Install-OpenSSHServer
|
||||||
|
}
|
||||||
Write-Host 'Done.' -ForegroundColor Green
|
Write-Host 'Done.' -ForegroundColor Green
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ openssh_zip="$(VM_IMAGE_ARCH="$answer_arch" "$image_helper" --print-path openssh
|
|||||||
[[ -s "$openssh_zip" ]] || VM_IMAGE_ARCH="$answer_arch" "$image_helper" openssh-win
|
[[ -s "$openssh_zip" ]] || VM_IMAGE_ARCH="$answer_arch" "$image_helper" openssh-win
|
||||||
[[ -s "$openssh_zip" ]] || { printf 'Error: %s was not downloaded.\n' "$openssh_zip" >&2; exit 1; }
|
[[ -s "$openssh_zip" ]] || { printf 'Error: %s was not downloaded.\n' "$openssh_zip" >&2; exit 1; }
|
||||||
|
|
||||||
|
# The public key for administrator SSH login: SSH_PUBLIC_KEY_FILE, else the
|
||||||
|
# first default key of the invoking user. The guest skips it when absent.
|
||||||
|
ssh_key="${SSH_PUBLIC_KEY_FILE:-}"
|
||||||
|
if [[ -z "$ssh_key" ]]; then
|
||||||
|
for candidate in "$HOME"/.ssh/id_ed25519.pub "$HOME"/.ssh/id_ecdsa.pub "$HOME"/.ssh/id_rsa.pub; do
|
||||||
|
[[ -f "$candidate" ]] && { ssh_key="$candidate"; break; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
[[ -z "$ssh_key" || -f "$ssh_key" ]] || { printf 'Error: %s does not exist.\n' "$ssh_key" >&2; exit 1; }
|
||||||
|
|
||||||
# The answer file's components name the processor architecture, so render a
|
# The answer file's components name the processor architecture, so render a
|
||||||
# copy for the target architecture.
|
# copy for the target architecture.
|
||||||
work_dir="$(mktemp -d)"
|
work_dir="$(mktemp -d)"
|
||||||
@@ -29,6 +39,10 @@ seed_files=(
|
|||||||
"$script_dir/Configure-Windows.ps1"
|
"$script_dir/Configure-Windows.ps1"
|
||||||
"$openssh_zip"
|
"$openssh_zip"
|
||||||
)
|
)
|
||||||
|
if [[ -n "$ssh_key" ]]; then
|
||||||
|
cp -- "$ssh_key" "$work_dir/authorized_keys"
|
||||||
|
seed_files+=("$work_dir/authorized_keys")
|
||||||
|
fi
|
||||||
# Numbered installer scripts (10-Install-Git-Admin.bat, 20-..., in install order).
|
# Numbered installer scripts (10-Install-Git-Admin.bat, 20-..., in install order).
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
seed_files+=("$script_dir"/[0-9][0-9]-*.bat)
|
seed_files+=("$script_dir"/[0-9][0-9]-*.bat)
|
||||||
|
|||||||
Reference in new issue
Block a user