1#!/bin/sh
2set -eu
3. "$(dirname "$0")/../../../ci/sub/lib.sh"
4cd -- "$(dirname "$0")/../../.."
5
6help() {
7 cat <<EOF
8usage: $0 [--dry-run] [--skip-create] [--skip-init] [--copy-id=id.pub]
9 [--run=jobregex]
10
11$0 creates and ensures the d2 builders in AWS.
12EOF
13}
14
15main() {
16 while flag_parse "$@"; do
17 case "$FLAG" in
18 h|help)
19 help
20 return 0
21 ;;
22 x)
23 flag_noarg && shift "$FLAGSHIFT"
24 set -x
25 export TRACE=1
26 ;;
27 dry-run)
28 flag_noarg && shift "$FLAGSHIFT"
29 export DRY_RUN=1
30 ;;
31 copy-id)
32 flag_nonemptyarg && shift "$FLAGSHIFT"
33 ID_PUB_PATH=$FLAGARG
34 ;;
35 run)
36 flag_reqarg && shift "$FLAGSHIFT"
37 JOBFILTER="$FLAGARG"
38 ;;
39 *)
40 flag_errusage "unrecognized flag $FLAGRAW"
41 ;;
42 esac
43 done
44 shift "$FLAGSHIFT"
45 if [ $# -gt 0 ]; then
46 flag_errusage "no arguments are accepted"
47 fi
48 if [ -z "${ID_PUB_PATH-}" ]; then
49 flag_errusage "--copy-id is required"
50 fi
51
52 JOBNAME=create runjob_filter create_remote_hosts
53 JOBNAME=init runjob_filter init_remote_hosts
54
55 FGCOLOR=2 header summary
56 echo "export CI_D2_LINUX_AMD64=$CI_D2_LINUX_AMD64"
57 echo "export CI_D2_LINUX_ARM64=$CI_D2_LINUX_ARM64"
58 echo "export CI_D2_MACOS_AMD64=$CI_D2_MACOS_AMD64"
59 echo "export CI_D2_MACOS_ARM64=$CI_D2_MACOS_ARM64"
60 echo "export CI_D2_WINDOWS_AMD64=$CI_D2_WINDOWS_AMD64"
61}
62
63create_remote_hosts() {
64 bigheader create_remote_hosts
65
66 KEY_NAME=$(aws ec2 describe-key-pairs | jq -r .KeyPairs[0].KeyName)
67 KEY_NAME_WINDOWS=windows
68 VPC_ID=$(aws ec2 describe-vpcs | jq -r .Vpcs[0].VpcId)
69
70 JOBNAME=$JOBNAME/security-groups runjob_filter create_security_groups
71 JOBNAME=$JOBNAME/linux/amd64 runjob_filter create_linux_amd64
72 JOBNAME=$JOBNAME/linux/arm64 runjob_filter create_linux_arm64
73 JOBNAME=$JOBNAME/macos/amd64 runjob_filter create_macos_amd64
74 JOBNAME=$JOBNAME/macos/arm64 runjob_filter create_macos_arm64
75 JOBNAME=$JOBNAME/windows/amd64 runjob_filter create_windows_amd64
76}
77
78create_security_groups() {
79 header security-group
80 SG_ID=$(aws ec2 describe-security-groups --group-names ssh 2>/dev/null \
81 | jq -r .SecurityGroups[0].GroupId)
82 if [ -z "$SG_ID" ]; then
83 SG_ID=$(sh_c aws ec2 create-security-group \
84 --group-name ssh \
85 --description ssh \
86 --vpc-id "$VPC_ID" | jq -r .GroupId)
87 fi
88
89 header security-group-ingress
90 SG_RULES_COUNT=$(aws ec2 describe-security-groups --group-names ssh \
91 | jq -r '.SecurityGroups[0].IpPermissions | length')
92 if [ "$SG_RULES_COUNT" -eq 0 ]; then
93 sh_c aws ec2 authorize-security-group-ingress \
94 --group-id "$SG_ID" \
95 --protocol tcp \
96 --port 22 \
97 --cidr 0.0.0.0/0 >/dev/null
98 fi
99
100 header windows-security-group
101 SG_ID=$(aws ec2 describe-security-groups --group-names windows 2>/dev/null \
102 | jq -r .SecurityGroups[0].GroupId)
103 if [ -z "$SG_ID" ]; then
104 SG_ID=$(sh_c aws ec2 create-security-group \
105 --group-name windows \
106 --description windows \
107 --vpc-id "$VPC_ID" | jq -r .GroupId)
108 fi
109
110 header windows-security-group-ingress
111 SG_RULES_COUNT=$(aws ec2 describe-security-groups --group-names windows \
112 | jq -r '.SecurityGroups[0].IpPermissions | length')
113 if [ "$SG_RULES_COUNT" -ne 2 ]; then
114 sh_c aws ec2 authorize-security-group-ingress \
115 --group-id "$SG_ID" \
116 --protocol tcp \
117 --port 22 \
118 --cidr 0.0.0.0/0 >/dev/null
119 sh_c aws ec2 authorize-security-group-ingress \
120 --group-id "$SG_ID" \
121 --protocol tcp \
122 --port 3389 \
123 --cidr 0.0.0.0/0 >/dev/null
124 fi
125}
126
127create_linux_amd64() {
128 header linux-amd64
129 REMOTE_NAME=ci-d2-linux-amd64
130 state=$(aws ec2 describe-instances --filters \
131 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-linux-amd64' \
132 | jq -r '.Reservations[].Instances[].State.Name')
133 if [ -z "$state" ]; then
134 sh_c aws ec2 run-instances \
135 --image-id=ami-0ecc74eca1d66d8a6 \
136 --count=1 \
137 --instance-type=t3.small \
138 --security-groups=ssh \
139 "--key-name=$KEY_NAME" \
140 --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \
141 --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=64,VolumeType=gp3}"' \
142 --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-linux-amd64}]"' \
143 '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-linux-amd64}]"' >/dev/null
144 fi
145 wait_remote_host_ip
146 log "CI_D2_LINUX_AMD64=ubuntu@$ip"
147 export CI_D2_LINUX_AMD64=ubuntu@$ip
148}
149
150create_linux_arm64() {
151 header linux-arm64
152 REMOTE_NAME=ci-d2-linux-arm64
153 state=$(aws ec2 describe-instances --filters \
154 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-linux-arm64' \
155 | jq -r '.Reservations[].Instances[].State.Name')
156 if [ -z "$state" ]; then
157 sh_c aws ec2 run-instances \
158 --image-id=ami-06e2dea2cdda3acda \
159 --count=1 \
160 --instance-type=t4g.small \
161 --security-groups=ssh \
162 "--key-name=$KEY_NAME" \
163 --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \
164 --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=64,VolumeType=gp3}"' \
165 --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-linux-arm64}]"' \
166 '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-linux-arm64}]"' >/dev/null
167 fi
168 wait_remote_host_ip
169 log "CI_D2_LINUX_ARM64=ubuntu@$ip"
170 export CI_D2_LINUX_ARM64=ubuntu@$ip
171}
172
173create_macos_amd64() {
174 header macos-amd64-host
175 MACOS_AMD64_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=ci-d2-macos-amd64' | jq -r '.Hosts[].HostId')
176 if [ -z "$MACOS_AMD64_ID" ]; then
177 MACOS_AMD64_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac1.metal --quantity 1 --availability-zone us-west-2a \
178 --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=ci-d2-macos-amd64}]"' \
179 | jq -r .HostIds[0])
180 fi
181
182 header macos-amd64
183 REMOTE_NAME=ci-d2-macos-amd64
184 state=$(aws ec2 describe-instances --filters \
185 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-macos-amd64' \
186 | jq -r '.Reservations[].Instances[].State.Name')
187 if [ -z "$state" ]; then
188 sh_c aws ec2 run-instances \
189 --image-id=ami-0dd2ded7568750663 \
190 --count=1 \
191 --instance-type=mac1.metal \
192 --security-groups=ssh \
193 "--key-name=$KEY_NAME" \
194 --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \
195 --placement "Tenancy=host,HostId=$MACOS_AMD64_ID" \
196 --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=100,VolumeType=gp3}"' \
197 --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-macos-amd64}]"' \
198 '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-macos-amd64}]"' >/dev/null
199 fi
200 wait_remote_host_ip
201 log "CI_D2_MACOS_AMD64=ec2-user@$ip"
202 export CI_D2_MACOS_AMD64=ec2-user@$ip
203}
204
205create_macos_arm64() {
206 header macos-arm64-host
207 MACOS_ARM64_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=ci-d2-macos-arm64' | jq -r '.Hosts[].HostId')
208 if [ -z "$MACOS_ARM64_ID" ]; then
209 MACOS_ARM64_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac2.metal --quantity 1 --availability-zone us-west-2a \
210 --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=ci-d2-macos-arm64}]"' \
211 | jq -r .HostIds[0])
212 fi
213
214 header macos-arm64
215 REMOTE_NAME=ci-d2-macos-arm64
216 state=$(aws ec2 describe-instances --filters \
217 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=ci-d2-macos-arm64' \
218 | jq -r '.Reservations[].Instances[].State.Name')
219 if [ -z "$state" ]; then
220 sh_c aws ec2 run-instances \
221 --image-id=ami-0af0516ff2c43dbbe \
222 --count=1 \
223 --instance-type=mac2.metal \
224 --security-groups=ssh \
225 "--key-name=$KEY_NAME" \
226 --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \
227 --placement "Tenancy=host,HostId=$MACOS_ARM64_ID" \
228 --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=100,VolumeType=gp3}"' \
229 --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=ci-d2-macos-arm64}]"' \
230 '"ResourceType=volume,Tags=[{Key=Name,Value=ci-d2-macos-arm64}]"' >/dev/null
231 fi
232 wait_remote_host_ip
233 log "CI_D2_MACOS_ARM64=ec2-user@$ip"
234 export CI_D2_MACOS_ARM64=ec2-user@$ip
235}
236
237create_windows_amd64() {
238 header windows-amd64
239 REMOTE_NAME=ci-d2-windows-amd64
240 state=$(aws ec2 describe-instances --filters \
241 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \
242 | jq -r '.Reservations[].Instances[].State.Name')
243 if [ -z "$state" ]; then
244 sh_c aws ec2 run-instances \
245 --image-id=ami-0c5300e833c2b32f3 \
246 --count=1 \
247 --instance-type=t3.medium \
248 --security-groups=windows \
249 "--key-name=$KEY_NAME_WINDOWS" \
250 --iam-instance-profile 'Name=AmazonSSMRoleForInstancesQuickSetup' \
251 --block-device-mappings '"DeviceName=/dev/sda1,Ebs={VolumeSize=64,VolumeType=gp3}"' \
252 --tag-specifications "'ResourceType=instance,Tags=[{Key=Name,Value=$REMOTE_NAME}]'" \
253 "'ResourceType=volume,Tags=[{Key=Name,Value=$REMOTE_NAME}]'" >/dev/null
254 fi
255 wait_remote_host_ip
256 log "CI_D2_WINDOWS_AMD64=Administrator@$ip"
257 export CI_D2_WINDOWS_AMD64=Administrator@$ip
258}
259
260wait_remote_host_ip() {
261 while true; do
262 ip=$(sh_c aws ec2 describe-instances \
263 --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \
264 | jq -r '.Reservations[].Instances[].PublicIpAddress')
265 if [ -n "$ip" ]; then
266 alloc_static_ip
267 ip=$(sh_c aws ec2 describe-instances \
268 --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \
269 | jq -r '.Reservations[].Instances[].PublicIpAddress')
270 ssh-keygen -R "$ip"
271 break
272 fi
273 sleep 5
274 done
275}
276
277alloc_static_ip() {
278 allocation_id=$(aws ec2 describe-addresses --filters "Name=tag:Name,Values=$REMOTE_NAME" | jq -r '.Addresses[].AllocationId')
279 if [ -z "$allocation_id" ]; then
280 sh_c aws ec2 allocate-address --tag-specifications "'ResourceType=elastic-ip,Tags=[{Key=Name,Value=$REMOTE_NAME}]'"
281 allocation_id=$(aws ec2 describe-addresses --filters "Name=tag:Name,Values=$REMOTE_NAME" | jq -r '.Addresses[].AllocationId')
282 fi
283
284 instance_id=$(aws ec2 describe-instances \
285 --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \
286 | jq -r '.Reservations[].Instances[].InstanceId')
287 aws ec2 associate-address --instance-id "$instance_id" --allocation-id "$allocation_id"
288}
289
290init_remote_hosts() {
291 bigheader init_remote_hosts
292
293 JOBNAME=$JOBNAME/linux/amd64 runjob_filter REMOTE_HOST=$CI_D2_LINUX_AMD64 REMOTE_NAME=ci-d2-linux-amd64 init_remote_linux
294 JOBNAME=$JOBNAME/linux/arm64 runjob_filter REMOTE_HOST=$CI_D2_LINUX_ARM64 REMOTE_NAME=ci-d2-linux-arm64 init_remote_linux
295 JOBNAME=$JOBNAME/macos/amd64 runjob_filter REMOTE_HOST=$CI_D2_MACOS_AMD64 REMOTE_NAME=ci-d2-macos-amd64 init_remote_macos
296 JOBNAME=$JOBNAME/macos/arm64 runjob_filter REMOTE_HOST=$CI_D2_MACOS_ARM64 REMOTE_NAME=ci-d2-macos-arm64 init_remote_macos
297 JOBNAME=$JOBNAME/windows/amd64 runjob_filter REMOTE_HOST=$CI_D2_WINDOWS_AMD64 REMOTE_NAME=ci-d2-windows-amd64 init_remote_windows
298
299 # Windows and AWS SSM both defeated me.
300 FGCOLOR=3 bigheader "WARNING: WINDOWS INITIALIZATION MUST BE COMPLETED MANUALLY OVER RDP AND POWERSHELL!"
301}
302
303init_remote_linux() {
304 header "$REMOTE_NAME"
305 wait_remote_host
306
307 sh_c ssh_copy_id -i="$ID_PUB_PATH" "$REMOTE_HOST"
308
309 sh_c ssh "$REMOTE_HOST" sh -s -- <<EOF
310set -eux
311export DEBIAN_FRONTEND=noninteractive
312
313sudo -E apt-get update -y
314sudo -E apt-get dist-upgrade -y
315sudo -E apt-get update -y
316sudo -E apt-get install -y build-essential rsync
317
318# Docker from https://docs.docker.com/engine/install/ubuntu/
319sudo -E apt-get -y install \
320 ca-certificates \
321 curl \
322 gnupg \
323 lsb-release
324sudo mkdir -p /etc/apt/keyrings
325curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --yes --dearmor -o /etc/apt/keyrings/docker.gpg
326echo \
327 "deb [arch=\$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
328 \$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
329sudo -E apt-get update -y
330sudo -E apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
331sudo groupadd docker || true
332sudo usermod -aG docker \$USER
333
334printf %s '$CI_DOCKER_TOKEN' | docker login -u terrastruct --password-stdin
335
336# For building images cross platform from the arm64 instance.
337# We could use QEMU with:
338# sudo -E apt-get install -y qemu qemu-user-static
339# But we don't as playwright dependencies do not install on QEMU on either arm64 or amd64.
340if [ "\$(uname -m)" = aarch64 ]; then
341 if [ "\$(stat -c '%a' ~/.ssh/id_ed25519 2>/dev/null)" != 600 ]; then
342 echo '$CI_TSTRUCT_ID_ED25519' >~/.ssh/id_ed25519
343 chmod 600 ~/.ssh/id_ed25519
344 fi
345 if ! docker context ls | grep -qF ci-d2-linux-amd64; then
346 docker context create ci-d2-linux-amd64 --docker "host=ssh://$CI_D2_LINUX_AMD64"
347 fi
348 if ! docker buildx ls | grep -qF 'd2 *'; then
349 docker buildx create --use --name d2 --platform linux/arm64 default
350 fi
351 if ! docker buildx inspect d2 | grep -qF ci-d2-linux-amd64; then
352 docker buildx create --append --name d2 --platform linux/amd64 ci-d2-linux-amd64
353 fi
354fi
355
356mkdir -p \$HOME/.local/bin
357mkdir -p \$HOME/.local/share/man
358EOF
359 init_remote_env
360
361 sh_c ssh "$REMOTE_HOST" sh -s -- <<EOF
362set -eux
363export DEBIAN_FRONTEND=noninteractive
364sudo -E apt-get autoremove -y
365EOF
366 sh_c ssh "$REMOTE_HOST" 'sudo reboot' || true
367}
368
369init_remote_macos() {
370 header "$REMOTE_NAME"
371 wait_remote_host
372
373 sh_c ssh_copy_id -i="$ID_PUB_PATH" "$REMOTE_HOST"
374
375 sh_c ssh "$REMOTE_HOST" '"/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""'
376
377 if sh_c ssh "$REMOTE_HOST" uname -m | grep -qF arm64; then
378 shellenv=$(sh_c ssh "$REMOTE_HOST" /opt/homebrew/bin/brew shellenv)
379 else
380 shellenv=$(sh_c ssh "$REMOTE_HOST" /usr/local/bin/brew shellenv)
381 fi
382 if ! echo "$shellenv" | sh_c ssh "$REMOTE_HOST" "IFS= read -r regex\; \"grep -qF \\\"\\\$regex\\\" ~/.zshrc\""; then
383 echo "$shellenv" | sh_c ssh "$REMOTE_HOST" "\"(echo && cat) >> ~/.zshrc\""
384 fi
385 if ! sh_c ssh "$REMOTE_HOST" "'grep -qF \\\$HOME/.local ~/.zshrc'"; then
386 sh_c ssh "$REMOTE_HOST" "\"(echo && cat) >> ~/.zshrc\"" <<EOF
387PATH=\$HOME/.local/bin:\$PATH
388MANPATH=\$HOME/.local/share/man:\$MANPATH
389EOF
390 fi
391 init_remote_env
392 sh_c ssh "$REMOTE_HOST" brew update
393 sh_c ssh "$REMOTE_HOST" brew upgrade
394 sh_c ssh "$REMOTE_HOST" brew install go rsync
395
396 sh_c ssh "$REMOTE_HOST" 'sudo reboot' || true
397}
398
399init_remote_env() {
400 sh_c ssh "$REMOTE_HOST" '"rm -f ~/.ssh/environment"'
401 sh_c ssh "$REMOTE_HOST" '"echo PATH=\$(echo \"echo \\\$PATH\" | \"\$SHELL\" -ils) >\$HOME/.ssh/environment"'
402 sh_c ssh "$REMOTE_HOST" '"echo MANPATH=\$(echo \"echo \\\$MANPATH\" | \"\$SHELL\" -ils) >>\$HOME/.ssh/environment"'
403
404 sh_c ssh "$REMOTE_HOST" "sudo sed -i.bak '\"s/#PermitUserEnvironment no/PermitUserEnvironment yes/\"' /etc/ssh/sshd_config"
405
406 if sh_c ssh "$REMOTE_HOST" uname | grep -qF Darwin; then
407 sh_c ssh "$REMOTE_HOST" "sudo launchctl stop com.openssh.sshd"
408 else
409 sh_c ssh "$REMOTE_HOST" "sudo systemctl restart sshd"
410 # ubuntu has $PATH hard coded in /etc/environment for some reason. It takes precedence
411 # over ~/.ssh/environment.
412 sh_c ssh "$REMOTE_HOST" "sudo rm -f /etc/environment"
413 fi
414}
415
416wait_remote_host() {
417 while true; do
418 if sh_c ssh "$REMOTE_HOST" true; then
419 break
420 fi
421 sleep 5
422 done
423}
424
425wait_remote_host_windows() {
426 instance_id=$(aws ec2 describe-instances \
427 --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" \
428 | jq -r '.Reservations[].Instances[].InstanceId')
429
430 while true; do
431 if sh_c aws ssm start-session --target "$instance_id" \
432 --document-name 'AWS-StartNonInteractiveCommand' \
433 --parameters "'{\"command\": [\"echo true\"]}'"; then
434 break
435 fi
436 sleep 5
437 done
438}
439
440init_remote_windows() {
441 header "$REMOTE_NAME"
442 wait_remote_host_windows
443
444 init_ps1=$(cat <<EOF
445\$ProgressPreference = 'SilentlyContinue'
446
447# Bootstrap PowerShell v7
448if ((\$PSVersionTable.PSVersion).Major -eq 5) {
449 Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.3 -OutFile .\microsoft.ui.xaml.2.7.3.zip
450 Expand-Archive -Force .\microsoft.ui.xaml.2.7.3.zip
451 Add-AppxPackage .\microsoft.ui.xaml.2.7.3\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx
452
453 Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.3.2691/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -OutFile .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
454 Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/download/v1.3.2691/7bcb1a0ab33340daa57fa5b81faec616_License1.xml -OutFile .\7bcb1a0ab33340daa57fa5b81faec616_License1.xml
455 Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
456 Add-AppxProvisionedPackage -online -PackagePath .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -LicensePath .\7bcb1a0ab33340daa57fa5b81faec616_License1.xml -DependencyPackagePath Microsoft.VCLibs.x64.14.00.Desktop.appx
457 Add-AppxPackage .\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
458
459 winget install --silent --accept-package-agreements --accept-source-agreements Microsoft.DotNet.SDK.7
460 # Refresh env.
461 \$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
462 dotnet tool install --global PowerShell --version 7.3.1
463 pwsh -c 'Enable-ExperimentalFeature PSNativeCommandErrorActionPreference'
464 pwsh .\Desktop\init.ps1
465 Exit
466}
467
468Set-StrictMode -Version Latest
469\$ErrorActionPreference = "Stop"
470\$PSNativeCommandUseErrorActionPreference = \$true
471
472if (-Not (Get-Command wix -errorAction SilentlyContinue)) {
473 dotnet tool install --global wix --version 4.0.0-preview.1
474}
475
476Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
477Start-Service sshd
478Set-Service -Name sshd -StartupType 'Automatic'
479
480New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\msys64\usr\bin\bash.exe" -PropertyType String -Force
481
482ConvertFrom-Json -InputObject @'
483$(perl -pe 's#\n#\r\n#' "$ID_PUB_PATH" | jq -Rs .)
484'@ | Out-File -Encoding utf8 "\$env:ProgramData\ssh\administrators_authorized_keys"
485# utf8BOM -> utf8: https://stackoverflow.com/a/34969243/4283659
486\$null = New-Item -Force "\$env:ProgramData\ssh\administrators_authorized_keys" -Value (Get-Content -Path "\$env:ProgramData\ssh\administrators_authorized_keys" | Out-String)
487get-acl "\$env:ProgramData\ssh\ssh_host_rsa_key" | set-acl "\$env:ProgramData\ssh\administrators_authorized_keys"
488
489if (-Not (Test-Path -Path C:\msys64)) {
490 Invoke-WebRequest -Uri "https://github.com/msys2/msys2-installer/releases/download/2022-10-28/msys2-x86_64-20221028.exe" -OutFile "./msys2-x86_64.exe"
491 ./msys2-x86_64.exe install --default-answer --confirm-command --root C:\msys64
492}
493C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c 'pacman -Sy --noconfirm base-devel vim rsync man'
494C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c 'curl -fsSL https://d2lang.com/install.sh | sh -s -- --tala'
495C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c 'd2 --version'
496
497\$path = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path
498if (\$path -notlike '*C:\msys64\usr\bin*') {
499 \$path = "\$path;C:\msys64\usr\bin"
500 Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path -Value \$path
501}
502if (\$path -notlike '*C:\msys64\usr\local\bin*') {
503 \$path = "\$path;C:\msys64\usr\local\bin"
504 Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path -Value \$path
505}
506(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path
507
508Restart-Computer
509EOF
510
511# To run a POSIX script:
512# ssh "$CI_D2_WINDOWS_AMD64" sh -s -- <<EOF
513# wix --version
514# EOF
515# To run a command in a pure MSYS2 shell:
516# ssh "$CI_D2_WINDOWS_AMD64" 'C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64 -c "\"d2 --version\""'
517# To run a pure MSYS2 shell:
518# ssh -t "$CI_D2_WINDOWS_AMD64" 'C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64'
519
520# In case MSYS2 improves in the future and allows for noninteractive commands the
521# following will set the OpenSSH shell to MSYS2 instead of PowerShell.
522#
523# Right now, setting MSYS2 to the DefaultShell like this will make it start bash in
524# interactive mode always. Even for ssh "$CI_D2_WINDOWS_AMD64" echo hi. And so you'll end
525# up with a blank prompt on which to input commands instead of having it execute the
526# command you passed in via ssh.
527#
528# PowerShell as the default is better anyway as it gives us access to both the UNIX
529# userspace and Windows tools like wix/dotnet/winget.
530#
531# To set:
532# <<EOF
533# echo '@C:\msys64\msys2_shell.cmd -defterm -here -no-start -mingw64' | Out-File C:\msys64\sshd_default_shell.cmd
534# # utf8BOM -> utf8: https://stackoverflow.com/a/34969243/4283659
535# \$null = New-Item -Force C:\msys64\sshd_default_shell.cmd -Value (Get-Content -Path C:\msys64\sshd_default_shell.cmd | Out-String)
536# Set-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH -Name DefaultShell -Value C:\msys64\sshd_default_shell.cmd
537# EOF
538#
539# To undo:
540# <<EOF
541# Remove-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH -Name DefaultShell
542# rm C:\msys64\sshd_default_shell.cmd
543# EOF
544)
545
546 gen_init_ps1=$(cat <<EOF
547ConvertFrom-Json -InputObject @'
548$(printf %s "$init_ps1" | perl -pe 'chomp if eof' | perl -pe 's#\n#\r\n#' | jq -Rs .)
549'@ | Out-File -Encoding utf8 C:\Users\Administrator\Desktop\init.ps1; C:\Users\Administrator\Desktop\init.ps1
550EOF
551)
552
553 # Windows and AWS SSM both defeated me.
554 FGCOLOR=3 bigheader "WARNING: WINDOWS INITIALIZATION MUST BE COMPLETED MANUALLY OVER RDP AND POWERSHELL!"
555
556 warn '1. Obtain Windows RDP password with:'
557 echo " aws ec2 get-password-data --instance-id \$(aws ec2 describe-instances --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' "Name=tag:Name,Values=$REMOTE_NAME" | jq -r '.Reservations[].Instances[].InstanceId') --priv-launch-key windows.pem | jq -r .PasswordData" >&2
558 warn "2. RDP into $REMOTE_HOST and open PowerShell."
559 warn '3. Generate and execute C:\Users\Administrator\Desktop\init.ps1 with:'
560 printf '%s\n' "$gen_init_ps1" >&2
561 warn '4. Run the following to be notified once installation is successful:'
562 cat <<EOF
563 until ssh $REMOTE_HOST d2 --version; do echo 'failed: retrying in 5s' && sleep 5; done && printf 'success\a\n'
564EOF
565}
566
567main "$@"
View as plain text