diff --git a/images/setup-ssh.ps1 b/images/setup-ssh.ps1 new file mode 100644 index 0000000000..f365b71e19 --- /dev/null +++ b/images/setup-ssh.ps1 @@ -0,0 +1,58 @@ + +# Credit to : https://github.com/chorrell/packer-aws-windows-openssh/blob/main/files/SetupSsh.ps1 + +# Don't display progress bars +# See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.3#progresspreference +$ProgressPreference = 'SilentlyContinue' +Set-ExecutionPolicy Unrestricted -Scope LocalMachine -Force -ErrorAction Ignore +# Don't set this before Set-ExecutionPolicy as it throws an error +$ErrorActionPreference = "stop" +# Install OpenSSH using Add-WindowsCapability +# See: https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=powershell#install-openssh-for-windows + +Write-Output 'Installing and starting ssh-agent' +Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 +Set-Service -Name ssh-agent -StartupType Automatic +Start-Service ssh-agent + +Write-Output 'Installing and starting sshd' +Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 +Set-Service -Name sshd -StartupType Automatic +Start-Service sshd + +# Confirm the Firewall rule is configured. It should be created automatically by setup. Run the following to verify +if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) { + Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..." + New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 +} else { + Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists." +} + +# Set default shell to Powershell +New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force + +$keyDownloadScript = Join-Path $env:ProgramData 'ssh\download-key.ps1' + +@' +# Download private key to $env:ProgramData\ssh\administrators_authorized_keys +$openSSHAuthorizedKeys = Join-Path $env:ProgramData 'ssh\administrators_authorized_keys' + +$keyUrl = "http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key" +Invoke-WebRequest $keyUrl -OutFile $openSSHAuthorizedKeys + +# Ensure ACL for administrators_authorized_keys is correct +# See https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration#authorizedkeysfile +icacls.exe $openSSHAuthorizedKeys /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" +'@ | Out-File $keyDownloadScript + +# Create Task +$taskName = "DownloadKey" +$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest +$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "-NoProfile -File ""$keyDownloadScript""" +$trigger = New-ScheduledTaskTrigger -AtStartup +Register-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -TaskName $taskName -Description $taskName + +# Fetch key via $keyDownloadScript +& Powershell.exe -ExecutionPolicy Bypass -File $keyDownloadScript + + diff --git a/images/windows-core-2019/bootstrap_win.ps1 b/images/windows-core-2019/bootstrap_winrm.ps1 similarity index 99% rename from images/windows-core-2019/bootstrap_win.ps1 rename to images/windows-core-2019/bootstrap_winrm.ps1 index 3cba59a089..ea9985790c 100644 --- a/images/windows-core-2019/bootstrap_win.ps1 +++ b/images/windows-core-2019/bootstrap_winrm.ps1 @@ -35,4 +35,4 @@ cmd.exe /c net stop winrm cmd.exe /c sc config winrm start= auto cmd.exe /c net start winrm - \ No newline at end of file + diff --git a/images/windows-core-2019/github_agent.windows.pkr.hcl b/images/windows-core-2019/github_agent.windows.pkr.hcl index 5a31604c01..a03403b5f9 100644 --- a/images/windows-core-2019/github_agent.windows.pkr.hcl +++ b/images/windows-core-2019/github_agent.windows.pkr.hcl @@ -18,10 +18,16 @@ variable "region" { default = "eu-west-1" } +variable "aws_account_number" { + description = "The AWS account number" + type = string + default = "" +} + variable "instance_type" { description = "The instance type Packer will use for the builder" type = string - default = "t3a.medium" + default = "c7i-flex.xlarge" } variable "ebs_delete_on_termination" { @@ -62,12 +68,21 @@ locals { source "amazon-ebs" "githubrunner" { ami_name = "github-runner-windows-core-2019-${formatdate("YYYYMMDDhhmm", timestamp())}" - communicator = "winrm" + ami_users = [ var.aws_account_number ] + ami_regions = var.aws_region_mirror_list + ami_description = "GitHub Actions runner AMI Windows Core 2019 Pro Video" + instance_type = var.instance_type region = var.region associate_public_ip_address = var.associate_public_ip_address temporary_security_group_source_public_ip = var.temporary_security_group_source_public_ip + user_data_file = "../setup-ssh.ps1" + communicator = "ssh" + ssh_username = "Administrator" + ssh_file_transfer_method = "sftp" + ssh_timeout = "15m" + source_ami_filter { filters = { name = "Windows_Server-2019-English-Core-ContainersLatest-*" @@ -77,19 +92,16 @@ source "amazon-ebs" "githubrunner" { most_recent = true owners = ["amazon"] } + tags = { OS_Version = "windows-core-2019" Release = "Latest" Base_AMI_Name = "{{ .SourceAMIName }}" } - user_data_file = "./bootstrap_win.ps1" - winrm_insecure = true - winrm_port = 5986 - winrm_use_ssl = true - winrm_username = "Administrator" launch_block_device_mappings { device_name = "/dev/sda1" + volume_size = 100 delete_on_termination = "${var.ebs_delete_on_termination}" } } @@ -114,8 +126,16 @@ build { }) ], var.custom_shell_commands) } + + # Needed to make the chocolatey install pathing changes stick for any subsequent provisioning script that you want to run. + provisioner "windows-restart" { + restart_check_command = "powershell -command \"& {Write-Output 'Restarted!'}\"" + restart_timeout = "5m" + } + post-processor "manifest" { output = "manifest.json" strip_path = true } + } diff --git a/images/windows-core-2019/windows-provisioner.ps1 b/images/windows-core-2019/windows-provisioner.ps1 index a192d7e983..778e8e6ce7 100644 --- a/images/windows-core-2019/windows-provisioner.ps1 +++ b/images/windows-core-2019/windows-provisioner.ps1 @@ -1,52 +1,73 @@ +# Set up preferences for output $ErrorActionPreference = "Continue" $VerbosePreference = "Continue" +$ProgressPreference = "SilentlyContinue" -# Install Chocolatey -[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -$env:chocolateyUseWindowsCompression = 'true' -Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression - -# Add Chocolatey to powershell profile -$ChocoProfileValue = @' -$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" -if (Test-Path($ChocolateyProfile)) { - Import-Module "$ChocolateyProfile" +# Print out our powershell version just so we know what the default is. +$PSVersionTable + +# Just checking disk space +Get-PSDrive C | Select-Object Used,Free + +# Make sure that we have a profile to set and write to. If not, create it. +$powershellProfile = "C:\Users\Administrator\Documents\WindowsPowerShell\profile.ps1" +Write-Output "Checking for profile at $powershellProfile" +if (!(Test-Path -Path $powershellProfile)) { + Write-Output "Creating $powershellProfile cause it doesn't exist yet" + New-Item -Type File -Path $powershellProfile -Force +} +Write-Output "Checking for profile at $profile" +if (!(Test-Path -Path $profile)) { + Write-Output "Creating $profile cause it doesn't exist yet" + New-Item -Type File -Path $profile -Force } -refreshenv -'@ -# Write it to the $profile location -Set-Content -Path "$PsHome\Microsoft.PowerShell_profile.ps1" -Value $ChocoProfileValue -Force -# Source it -. "$PsHome\Microsoft.PowerShell_profile.ps1" +# Install Chocolatey +Write-Output "Setting up Chocolatey install execution policy" +Get-ExecutionPolicy +Set-ExecutionPolicy Bypass -Scope Process -Force +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 +Write-Output "Downloading and running Chocolatey install script" +# NOTE : We use the System.Net.WebClient object, because using Invoke-Webrequest is ridiculously slow with larger files. Using this halves download times. +iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) +choco feature enable -n allowGlobalConfirmation -refreshenv +# NOTE / IMPORTANT : DO NOT SET THE POWERSHELL PROFILE. FOR SOME REASON IT BREAKS THE SFTP PROVISIONER. +# Likely due to the fact that the powershell profile will generate output when it is sourced, which causes +# a "Error processing command: Error uploading ps script containing env vars: sftpSession error: packet too long" +# failure when trying to upload the next script. -Write-Host "Installing cloudwatch agent..." -Invoke-WebRequest -Uri https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi -OutFile C:\amazon-cloudwatch-agent.msi +# Install cloudwatch agent +Write-Output "Installing cloudwatch agent..." +$cloudwatchURL = "https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi" +$cloudwatchLocation = "C:\amazon-cloudwatch-agent.msi" $cloudwatchParams = '/i', 'C:\amazon-cloudwatch-agent.msi', '/qn', '/L*v', 'C:\CloudwatchInstall.log' -Start-Process "msiexec.exe" $cloudwatchParams -Wait -NoNewWindow -Remove-Item C:\amazon-cloudwatch-agent.msi +$webClient = New-Object System.Net.WebClient +$webClient.Downloadfile($cloudwatchURL, $cloudwatchLocation) +Write-Output "Installing Cloudwatch agent from $cloudwatchLocation" +Start-Process "msiexec.exe" -ArgumentList $cloudwatchParams -Wait -NoNewWindow +Remove-Item $cloudwatchLocation # Install dependent tools -Write-Host "Installing additional development tools" +Write-Output "Installing additional development tools" choco install git awscli -y refreshenv -Write-Host "Creating actions-runner directory for the GH Action installtion" +# Install Github Actions runner +Write-Output "Creating actions-runner directory for the GH Action installtion" New-Item -ItemType Directory -Path C:\actions-runner ; Set-Location C:\actions-runner - -Write-Host "Downloading the GH Action runner from ${action_runner_url}" -Invoke-WebRequest -Uri ${action_runner_url} -OutFile actions-runner.zip - -Write-Host "Un-zip action runner" +Write-Output "Downloading the GH Action runner from ${action_runner_url}" +# Invoke-WebRequest -Uri ${action_runner_url} -OutFile actions-runner.zip +$webClient.Downloadfile("${action_runner_url}", "$PWD\actions-runner.zip") +Write-Output "Unzip action runner" Expand-Archive -Path actions-runner.zip -DestinationPath . - -Write-Host "Delete zip file" +Write-Output "Delete zip file" Remove-Item actions-runner.zip $action = New-ScheduledTaskAction -WorkingDirectory "C:\actions-runner" -Execute "PowerShell.exe" -Argument "-File C:\start-runner.ps1" $trigger = New-ScheduledTaskTrigger -AtStartup Register-ScheduledTask -TaskName "runnerinit" -Action $action -Trigger $trigger -User System -RunLevel Highest -Force -C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule \ No newline at end of file +# TODO : The line below failed on my instances. I removed it, and everything works just fine. Something is funky here. +C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule +Write-Output "Finished running windows provisioner." diff --git a/images/windows-core-2022/bootstrap_win.ps1 b/images/windows-core-2022/bootstrap_winrm.ps1 similarity index 99% rename from images/windows-core-2022/bootstrap_win.ps1 rename to images/windows-core-2022/bootstrap_winrm.ps1 index 3cba59a089..ea9985790c 100644 --- a/images/windows-core-2022/bootstrap_win.ps1 +++ b/images/windows-core-2022/bootstrap_winrm.ps1 @@ -35,4 +35,4 @@ cmd.exe /c net stop winrm cmd.exe /c sc config winrm start= auto cmd.exe /c net start winrm - \ No newline at end of file + diff --git a/images/windows-core-2022/github_agent.windows.pkr.hcl b/images/windows-core-2022/github_agent.windows.pkr.hcl index 18a5ee93c3..0e6f7c1dbd 100644 --- a/images/windows-core-2022/github_agent.windows.pkr.hcl +++ b/images/windows-core-2022/github_agent.windows.pkr.hcl @@ -18,6 +18,24 @@ variable "region" { default = "eu-west-1" } +variable "aws_account_number" { + description = "The AWS account number" + type = string + default = "" +} + +variable "aws_region_mirror_list" { + description = "The list of regions to mirror the AMI to" + type = list(string) + default = ["eu-west-1"] +} + +variable "instance_type" { + description = "The instance type Packer will use for the builder" + type = string + default = "c7i-flex.xlarge" +} + variable "security_group_id" { description = "The ID of the security group Packer will associate with the builder to enable access" type = string @@ -30,11 +48,6 @@ variable "subnet_id" { default = null } -variable "root_volume_size_gb" { - type = number - default = 30 -} - variable "ebs_delete_on_termination" { description = "Indicates whether the EBS volume is deleted on instance termination." type = bool @@ -72,15 +85,24 @@ locals { } source "amazon-ebs" "githubrunner" { - ami_name = "github-runner-windows-core-2022-${formatdate("YYYYMMDDhhmm", timestamp())}" - communicator = "winrm" - instance_type = "m4.xlarge" + ami_name = "github-runner-windows-core-2022-${formatdate("YYYYMMDDhhmm", timestamp())}" + ami_users = [ var.aws_account_number ] + ami_regions = var.aws_region_mirror_list + ami_description = "GitHub Actions runner AMI Windows Core 2019 Pro Video" + + instance_type = var.instance_type region = var.region security_group_id = var.security_group_id subnet_id = var.subnet_id associate_public_ip_address = var.associate_public_ip_address temporary_security_group_source_public_ip = var.temporary_security_group_source_public_ip + user_data_file = "../setup-ssh.ps1" + communicator = "ssh" + ssh_username = "Administrator" + ssh_file_transfer_method = "sftp" + ssh_timeout = "15m" + source_ami_filter { filters = { name = "Windows_Server-2022-English-Full-ECS_Optimized-*" @@ -90,20 +112,16 @@ source "amazon-ebs" "githubrunner" { most_recent = true owners = ["amazon"] } + tags = { OS_Version = "windows-core-2022" Release = "Latest" Base_AMI_Name = "{{ .SourceAMIName }}" } - user_data_file = "./bootstrap_win.ps1" - winrm_insecure = true - winrm_port = 5986 - winrm_use_ssl = true - winrm_username = "Administrator" launch_block_device_mappings { device_name = "/dev/sda1" - volume_size = "${var.root_volume_size_gb}" + volume_size = 100 delete_on_termination = "${var.ebs_delete_on_termination}" } } @@ -128,6 +146,12 @@ build { }) ], var.custom_shell_commands) } + + provisioner "windows-restart" { + restart_check_command = "powershell -command \"& {Write-Output 'Restarted!'}\"" + restart_timeout = "5m" + } + post-processor "manifest" { output = "manifest.json" strip_path = true diff --git a/images/windows-core-2022/windows-provisioner.ps1 b/images/windows-core-2022/windows-provisioner.ps1 index a192d7e983..78370f03aa 100644 --- a/images/windows-core-2022/windows-provisioner.ps1 +++ b/images/windows-core-2022/windows-provisioner.ps1 @@ -1,52 +1,74 @@ +# Set up preferences for output $ErrorActionPreference = "Continue" $VerbosePreference = "Continue" +$ProgressPreference = "SilentlyContinue" -# Install Chocolatey -[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -$env:chocolateyUseWindowsCompression = 'true' -Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression - -# Add Chocolatey to powershell profile -$ChocoProfileValue = @' -$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" -if (Test-Path($ChocolateyProfile)) { - Import-Module "$ChocolateyProfile" +# Print out our powershell version just so we know what the default is. +$PSVersionTable + +# Just checking disk space +Get-PSDrive C | Select-Object Used,Free + +# Make sure that we have a profile to set and write to. If not, create it. +$powershellProfile = "C:\Users\Administrator\Documents\WindowsPowerShell\profile.ps1" +Write-Output "Checking for profile at $powershellProfile" +if (!(Test-Path -Path $powershellProfile)) { + Write-Output "Creating $powershellProfile cause it doesn't exist yet" + New-Item -Type File -Path $powershellProfile -Force +} +Write-Output "Checking for profile at $profile" +if (!(Test-Path -Path $profile)) { + Write-Output "Creating $profile cause it doesn't exist yet" + New-Item -Type File -Path $profile -Force } -refreshenv -'@ -# Write it to the $profile location -Set-Content -Path "$PsHome\Microsoft.PowerShell_profile.ps1" -Value $ChocoProfileValue -Force -# Source it -. "$PsHome\Microsoft.PowerShell_profile.ps1" +# Install Chocolatey +Write-Output "Setting up Chocolatey install execution policy" +Get-ExecutionPolicy +Set-ExecutionPolicy Bypass -Scope Process -Force +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 +Write-Output "Downloading and running Chocolatey install script" +# NOTE : We use the System.Net.WebClient object, because using Invoke-Webrequest is ridiculously slow with larger files. Using this halves download times. +iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) +choco feature enable -n allowGlobalConfirmation -refreshenv +# NOTE / IMPORTANT : DO NOT SET THE POWERSHELL PROFILE. FOR SOME REASON IT BREAKS THE SFTP PROVISIONER. +# Likely due to the fact that the powershell profile will generate output when it is sourced, which causes +# a "Error processing command: Error uploading ps script containing env vars: sftpSession error: packet too long" +# failure when trying to upload the next script. -Write-Host "Installing cloudwatch agent..." -Invoke-WebRequest -Uri https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi -OutFile C:\amazon-cloudwatch-agent.msi +# Install cloudwatch agent +Write-Output "Installing cloudwatch agent..." +$cloudwatchURL = "https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi" +$cloudwatchLocation = "C:\amazon-cloudwatch-agent.msi" $cloudwatchParams = '/i', 'C:\amazon-cloudwatch-agent.msi', '/qn', '/L*v', 'C:\CloudwatchInstall.log' -Start-Process "msiexec.exe" $cloudwatchParams -Wait -NoNewWindow -Remove-Item C:\amazon-cloudwatch-agent.msi +$webClient = New-Object System.Net.WebClient +$webClient.Downloadfile($cloudwatchURL, $cloudwatchLocation) +Write-Output "Installing Cloudwatch agent from $cloudwatchLocation" +Start-Process "msiexec.exe" -ArgumentList $cloudwatchParams -Wait -NoNewWindow +Remove-Item $cloudwatchLocation # Install dependent tools -Write-Host "Installing additional development tools" +Write-Output "Installing additional development tools" choco install git awscli -y refreshenv -Write-Host "Creating actions-runner directory for the GH Action installtion" +# Install Github Actions runner +Write-Output "Creating actions-runner directory for the GH Action installtion" New-Item -ItemType Directory -Path C:\actions-runner ; Set-Location C:\actions-runner - -Write-Host "Downloading the GH Action runner from ${action_runner_url}" -Invoke-WebRequest -Uri ${action_runner_url} -OutFile actions-runner.zip - -Write-Host "Un-zip action runner" +Write-Output "Downloading the GH Action runner from ${action_runner_url}" +# Invoke-WebRequest -Uri ${action_runner_url} -OutFile actions-runner.zip +$webClient.Downloadfile("${action_runner_url}", "$PWD\actions-runner.zip") +Write-Output "Unzip action runner" Expand-Archive -Path actions-runner.zip -DestinationPath . - -Write-Host "Delete zip file" +Write-Output "Delete zip file" Remove-Item actions-runner.zip $action = New-ScheduledTaskAction -WorkingDirectory "C:\actions-runner" -Execute "PowerShell.exe" -Argument "-File C:\start-runner.ps1" $trigger = New-ScheduledTaskTrigger -AtStartup Register-ScheduledTask -TaskName "runnerinit" -Action $action -Trigger $trigger -User System -RunLevel Highest -Force -C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule \ No newline at end of file +# TODO : The line below failed on my instances. I removed it, and everything works just fine. Something is funky here. +# This doesn't work on Windows Server 2022. Only for Windows server 2019. Per this article https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2launch.html +# C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 -Schedule +Write-Output "Finished running windows provisioner." diff --git a/modules/runners/templates/start-runner.ps1 b/modules/runners/templates/start-runner.ps1 index 55a5b7e518..a6cd34cc9a 100644 --- a/modules/runners/templates/start-runner.ps1 +++ b/modules/runners/templates/start-runner.ps1 @@ -6,7 +6,7 @@ $token=Invoke-RestMethod -Method PUT -Uri "http://169.254.169.254/latest/api/tok if ( ! $token ) { $retrycount=0 do { - echo "Failed to retrieve token. Retrying in 5 seconds." + Write-Output "Failed to retrieve token. Retrying in 5 seconds." Start-Sleep 5 $token=Invoke-RestMethod -Method PUT -Uri "http://169.254.169.254/latest/api/token" -Headers @{"X-aws-ec2-metadata-token-ttl-seconds" = "180"} $retrycount=$retrycount + 1