...

Text file src/oss.terrastruct.com/d2/ci/release/aws/ensure.sh

Documentation: oss.terrastruct.com/d2/ci/release/aws

     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