diff --git a/.github/workflows/cleanup-self-hosted-runners.yml b/.github/workflows/cleanup-self-hosted-runners.yml deleted file mode 100644 index f5ab5482..00000000 --- a/.github/workflows/cleanup-self-hosted-runners.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Cleanup Azure self hosted runners -run-name: Cleanup Azure self hosted runners - -on: - schedule: - # Run every 6 hours - - cron: "0 */6 * * *" - workflow_dispatch: - -permissions: - id-token: write # required for Azure login via OIDC - -# The following secrets are required for this workflow to run: -# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource -# group specifically for self-hosted Actions Runners, and to add a federated identity -# to authenticate as the currently-running GitHub workflow. -# az identity create --name -g -# az identity federated-credential create \ -# --identity-name \ -# --resource-group \ -# --name github-workflow \ -# --issuer https://token.actions.githubusercontent.com \ -# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \ -# --audiences api://AzureADTokenExchange -# MSYS_NO_PATHCONV=1 \ -# az role assignment create \ -# --assignee \ -# --scope '/subscriptions//resourceGroups/' \ -# --role 'Contributor' -# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which -# the Identity lives) -# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated -# (technically, this is not necessary for `az login --service-principal` with a -# managed identity, but `Azure/login` requires it anyway) -# AZURE_RESOURCE_GROUP - Resource group to find the runner(s) in. It's recommended to set up a resource -# group specifically for self-hosted Actions Runners. -jobs: - delete-runner: - if: github.repository_owner == 'git-for-windows' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Azure Login - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Discover VMs to delete - env: - GH_APP_ID: ${{ secrets.GH_APP_ID }} - GH_APP_PRIVATE_KEY: ${{ secrets.GH_APP_PRIVATE_KEY }} - run: | - active_vms=$(az vm list -g ${{ secrets.AZURE_RESOURCE_GROUP }} | jq -c '.[] | {name,timeCreated}') - current_time=$(date +%s) - one_hour_ago=$(($current_time - 3600)) - - if [ -z "$active_vms" ]; then - echo "No active VMs found, nothing to do." - exit 0 - else - echo "Found these active VMs:" - echo $active_vms - fi - - for active_vm in ${active_vms[@]}; do - vm_name=$(echo $active_vm | jq -r '.name') - # Use jq to extract and format the date-time string - vm_creation_time_string="$(echo $active_vm | - jq -r '.timeCreated | sub("\\.[0-9]+[+-][0-9]+:[0-9]+$"; "") | sub("T"; " ")')" - vm_creation_time=$(TZ=UTC date -d "$vm_creation_time_string" +%s) - - if [ "$one_hour_ago" -lt "$vm_creation_time" ]; then - echo "::notice::The VM ${vm_name} was created less then 1 hour ago and shouldn't be deleted yet. Skipping." - elif test true = "$(if test ! -f .cli-authenticated; then - ./gh-cli-auth-as-app.sh && - >.cli-authenticated # only authenticate once - fi && - gh api repos/$GITHUB_REPOSITORY/actions/runners \ - --jq '.runners[] | select(.name == "'$vm_name'") | .busy')"; then - echo "::notice::The VM ${vm_name} is still busy." - else - echo "::warning::The VM ${vm_name} was created more than 3 hours ago and wasn't deleted. Let's do that now." - az vm delete -n "$vm_name" -g ${{ secrets.AZURE_RESOURCE_GROUP }} --yes - az network nsg delete -n "$vm_name"-nsg -g ${{ secrets.AZURE_RESOURCE_GROUP }} - az network vnet delete -n "$vm_name"-vnet -g ${{ secrets.AZURE_RESOURCE_GROUP }} - az network public-ip delete -n "$vm_name"-ip -g ${{ secrets.AZURE_RESOURCE_GROUP }} - fi - done diff --git a/.github/workflows/create-azure-self-hosted-runners.yml b/.github/workflows/create-azure-self-hosted-runners.yml deleted file mode 100644 index f9c0e56d..00000000 --- a/.github/workflows/create-azure-self-hosted-runners.yml +++ /dev/null @@ -1,241 +0,0 @@ -name: create-azure-self-hosted-runners - -on: - workflow_dispatch: - inputs: - runner_scope: - type: choice - required: true - description: Scope of the runner. On personal accounts, only "repo-level" works - options: - - org-level - - repo-level - default: repo-level - runner_org: - type: string - required: false - description: Organization or personal account to deploy the runner to (defaults to the repository owner) - runner_repo: - type: string - required: false - description: Repo to deploy the runner to. Only needed if runner_scope is set to "repo-level" (defaults to current repository) - deallocate_immediately: - type: choice - options: - - false - - true - required: true - description: Deallocate the runner immediately after creating it (useful for spinning up runners preemptively) - default: "false" - ephemeral: - type: choice - options: - - false - - true - required: true - description: Start the runner in ephemeral mode (i.e. unregister after running one job) - default: "true" - -env: - ACTIONS_RUNNER_SCOPE: ${{ github.event.inputs.runner_scope }} - ACTIONS_RUNNER_ORG: "${{ github.event.inputs.runner_org || github.repository_owner }}" - ACTIONS_RUNNER_REPO: "${{ github.event.inputs.runner_repo || github.event.repository.name }}" - DEALLOCATE_IMMEDIATELY: ${{ github.event.inputs.deallocate_immediately }} - EPHEMERAL_RUNNER: ${{ github.event.inputs.ephemeral }} - # Note that you'll need "p" (arm64 processor) and ideally "d" (local temp disk). The number 4 stands for 4 CPU-cores. - # For a convenient overview of all arm64 VM types, see e.g. https://azureprice.net/?_cpuArchitecture=Arm64 - AZURE_VM_TYPE: Standard_D4plds_v5 - # At the time of writing, "eastus", "eastus2" and "westus2" were among the cheapest region for the VM type we're using. - # For more information, see https://learn.microsoft.com/en-us/azure/virtual-machines/dplsv5-dpldsv5-series (which - # unfortunately does not have more information about price by region) - AZURE_VM_REGION: westus2 - AZURE_VM_IMAGE: win11-24h2-ent - -permissions: - id-token: write # required for Azure login via OIDC - contents: read - -# The following secrets are required for this workflow to run: -# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource -# group specifically for self-hosted Actions Runners, and to add a federated identity -# to authenticate as the currently-running GitHub workflow. -# az identity create --name -g -# az identity federated-credential create \ -# --identity-name \ -# --resource-group \ -# --name github-workflow \ -# --issuer https://token.actions.githubusercontent.com \ -# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \ -# --audiences api://AzureADTokenExchange -# MSYS_NO_PATHCONV=1 \ -# az role assignment create \ -# --assignee \ -# --scope '/subscriptions//resourceGroups/' \ -# --role 'Contributor' -# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which -# the Identity lives) -# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated -# (technically, this is not necessary for `az login --service-principal` with a -# managed identity, but `Azure/login` requires it anyway) -# AZURE_RESOURCE_GROUP - Resource group to create the runner(s) in -# AZURE_VM_USERNAME - Username of the VM so you can RDP into it -# AZURE_VM_PASSWORD - Password of the VM so you can RDP into it -# GH_APP_ID - The ID of the GitHub App whose credentials are to be used to obtain the runner token -# GH_APP_PRIVATE_KEY - The private key of the GitHub App whose credentials are to be used to obtain the runner token -jobs: - create-runner: - runs-on: ubuntu-latest - outputs: - vm_name: ${{ steps.generate-vm-name.outputs.vm_name }} - steps: - - name: Generate VM name - id: generate-vm-name - run: | - VM_NAME="actions-runner-$(date +%Y%m%d%H%M%S%N)" - echo "Will be using $VM_NAME as the VM name" - echo "vm_name=$VM_NAME" >> $GITHUB_OUTPUT - - uses: actions/checkout@v4 - - name: Obtain installation token - id: setup - uses: actions/github-script@v7 - with: - script: | - const appId = ${{ secrets.GH_APP_ID }} - const privateKey = `${{ secrets.GH_APP_PRIVATE_KEY }}` - - const getAppInstallationId = require('./get-app-installation-id') - const installationId = await getAppInstallationId( - console, - appId, - privateKey, - process.env.ACTIONS_RUNNER_ORG, - process.env.ACTIONS_RUNNER_REPO - ) - - const getInstallationAccessToken = require('./get-installation-access-token') - const { token: accessToken } = await getInstallationAccessToken( - console, - appId, - privateKey, - installationId - ) - - core.setSecret(accessToken) - core.setOutput('token', accessToken) - # We can't use the octokit/request-action as we can't properly mask the runner token with it - # https://github.com/actions/runner/issues/475 - - name: Generate Actions Runner token and registration URL - run: | - # We need to URL-encode the user name because it usually is a GitHub App, which means that - # it has the suffix `[bot]`. If un-encoded, this would cause a cURL error "bad range in URL" - # because it would mistake this for an IPv6 address or something like that. - user_pwd="$(jq -n \ - --arg user '${{ github.actor }}' \ - --arg pwd '${{ secrets.GITHUB_TOKEN }}' \ - '$user | @uri + ":" + $pwd')" - case "$ACTIONS_RUNNER_SCOPE" in - "org-level") - ACTIONS_API_URL="https://$user_pwd@api.github.com/repos/$ACTIONS_RUNNER_ORG/actions/runners/registration-token" - ACTIONS_RUNNER_REGISTRATION_URL="https://$user_pwd@github.com/$ACTIONS_RUNNER_ORG" - ;; - "repo-level") - ACTIONS_API_URL="https://$user_pwd@api.github.com/repos/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO/actions/runners/registration-token" - ACTIONS_RUNNER_REGISTRATION_URL="https://$user_pwd@github.com/$ACTIONS_RUNNER_ORG/$ACTIONS_RUNNER_REPO" - ;; - *) - echo "Unsupported runner scope: $ACTIONS_RUNNER_SCOPE" - exit 1 - ;; - esac - - ACTIONS_RUNNER_TOKEN=$(curl \ - -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${{ steps.setup.outputs.token }}"\ - -H "X-GitHub-Api-Version: 2022-11-28" \ - $ACTIONS_API_URL \ - | jq --raw-output .token) - echo "::add-mask::$ACTIONS_RUNNER_TOKEN" - - # The Azure VM type we use has blazing-fast local, temporary storage available as the D:\ drive. - # The only downside is that, after dellocation, the contents of this disk (including the Actions Runner), - # are destroyed. Let's only use it when we don't immediately deallocate the VM. - if [[ "$DEALLOCATE_IMMEDIATELY" == "true" ]]; then - ACTIONS_RUNNER_PATH="C:\a" - else - ACTIONS_RUNNER_PATH="D:\a" - fi - - # Zip up and Base64-encode the post-deployment script; We used to provide a public URL - # for that script instead, but that does not work in private repositories (and we could - # not even use the `GITHUB_TOKEN` to access the file because it lacks the necessary - # scope to read repository contents). - POST_DEPLOYMENT_SCRIPT_ZIP_BASE64="$( - cd azure-self-hosted-runners && - zip -9 tmp.zip post-deployment-script.ps1 >&2 && - base64 -w 0 tmp.zip - )" - - PUBLIC_IP_ADDRESS_NAME1="${{ github.repository_visibility != 'private' && format('{0}-ip', steps.generate-vm-name.outputs.vm_name) || '' }}" - - AZURE_ARM_PARAMETERS=$(tr '\n' ' ' <<-END - githubActionsRunnerRegistrationUrl="$ACTIONS_RUNNER_REGISTRATION_URL" - githubActionsRunnerToken="$ACTIONS_RUNNER_TOKEN" - postDeploymentScriptZipBase64="$POST_DEPLOYMENT_SCRIPT_ZIP_BASE64" - postDeploymentScriptFileName="post-deployment-script.ps1" - virtualMachineImage="$AZURE_VM_IMAGE" - virtualMachineName="${{ steps.generate-vm-name.outputs.vm_name }}" - virtualMachineSize="$AZURE_VM_TYPE" - publicIpAddressName1="$PUBLIC_IP_ADDRESS_NAME1" - adminUsername="${{ secrets.AZURE_VM_USERNAME }}" - adminPassword="${{ secrets.AZURE_VM_PASSWORD }}" - ephemeral="$EPHEMERAL_RUNNER" - stopService="$DEALLOCATE_IMMEDIATELY" - githubActionsRunnerPath="$ACTIONS_RUNNER_PATH" - location="$AZURE_VM_REGION" - END - ) - - echo "AZURE_ARM_PARAMETERS=$AZURE_ARM_PARAMETERS" >> $GITHUB_ENV - - - name: Azure Login - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - uses: azure/arm-deploy@v2 - id: deploy-arm-template - with: - resourceGroupName: ${{ secrets.AZURE_RESOURCE_GROUP }} - deploymentName: deploy-${{ steps.generate-vm-name.outputs.vm_name }} - template: ./azure-self-hosted-runners/azure-arm-template.json - parameters: ./azure-self-hosted-runners/azure-arm-template-example-parameters.json ${{ env.AZURE_ARM_PARAMETERS }} - scope: resourcegroup - - - name: Show some more information on failure - if: failure() - run: | - echo "::group::VM status" - az vm get-instance-view --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --name ${{ steps.generate-vm-name.outputs.vm_name }} --query "instanceView.statuses" - az vm get-instance-view --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --name ${{ steps.generate-vm-name.outputs.vm_name }} --query "statuses" - echo "::endgroup::" - - echo "::group::Deployment logs" - az group deployment show --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --name deploy-${{ steps.generate-vm-name.outputs.vm_name }} - echo "::endgroup::" - - echo "::group::Extension logs" - az vm extension show --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --vm-name ${{ steps.generate-vm-name.outputs.vm_name }} --name CustomScriptExtension - echo "::endgroup::" - - - name: Show post-deployment script output - if: always() - env: - CUSTOM_SCRIPT_OUTPUT: ${{ steps.deploy-arm-template.outputs.customScriptInstanceView }} - run: echo "$CUSTOM_SCRIPT_OUTPUT" | jq -r '.substatuses[0].message' | sed 's/${{ secrets.GITHUB_TOKEN }}/***/g' - - - name: Deallocate the VM for later use - if: env.DEALLOCATE_IMMEDIATELY == 'true' - run: az vm deallocate -n ${{ steps.generate-vm-name.outputs.vm_name }} -g ${{ secrets.AZURE_RESOURCE_GROUP }} --verbose diff --git a/.github/workflows/delete-self-hosted-runner.yml b/.github/workflows/delete-self-hosted-runner.yml deleted file mode 100644 index bce1441a..00000000 --- a/.github/workflows/delete-self-hosted-runner.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: delete azure-self-hosted-runner -run-name: Delete ${{ inputs.runner_name }} - -on: - workflow_dispatch: - inputs: - runner_name: - type: string - required: true - description: The name of the runner that needs to be deleted - -env: - ACTIONS_RUNNER_NAME: ${{ github.event.inputs.runner_name }} - -permissions: - id-token: write # required for Azure login via OIDC - -# The following secrets are required for this workflow to run: -# AZURE_CLIENT_ID - The Client ID of an Azure Managed Identity. It is recommended to set up a resource -# group specifically for self-hosted Actions Runners, and to add a federated identity -# to authenticate as the currently-running GitHub workflow. -# az identity create --name -g -# az identity federated-credential create \ -# --identity-name \ -# --resource-group \ -# --name github-workflow \ -# --issuer https://token.actions.githubusercontent.com \ -# --subject repo:git-for-windows/git-for-windows-automation:ref:refs/heads/main \ -# --audiences api://AzureADTokenExchange -# MSYS_NO_PATHCONV=1 \ -# az role assignment create \ -# --assignee \ -# --scope '/subscriptions//resourceGroups/' \ -# --role 'Contributor' -# AZURE_TENANT_ID - The Tenant ID of the Azure Managed Identity (i.e. the Azure Active Directory in which -# the Identity lives) -# AZURE_SUBSCRIPTION_ID - The Subscription ID with which the Azure Managed Identity is associated -# (technically, this is not necessary for `az login --service-principal` with a -# managed identity, but `Azure/login` requires it anyway) -# AZURE_RESOURCE_GROUP - Resource group to find the runner in. It's recommended to set up a resource -# group specifically for self-hosted Actions Runners. -jobs: - delete-runner: - runs-on: ubuntu-latest - steps: - - name: Azure Login - uses: azure/login@v2 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Delete VM '${{ env.ACTIONS_RUNNER_NAME }}' - uses: azure/CLI@v2 - with: - azcliversion: 2.64.0 - inlineScript: | - az vm delete -n "$ACTIONS_RUNNER_NAME" -g ${{ secrets.AZURE_RESOURCE_GROUP }} --yes - az network nsg delete -n "$ACTIONS_RUNNER_NAME"-nsg -g ${{ secrets.AZURE_RESOURCE_GROUP }} - az network vnet delete -n "$ACTIONS_RUNNER_NAME"-vnet -g ${{ secrets.AZURE_RESOURCE_GROUP }} - az network public-ip delete -n "$ACTIONS_RUNNER_NAME"-ip -g ${{ secrets.AZURE_RESOURCE_GROUP }} diff --git a/azure-self-hosted-runners/azure-arm-template-example-parameters.json b/azure-self-hosted-runners/azure-arm-template-example-parameters.json deleted file mode 100644 index 5ee48118..00000000 --- a/azure-self-hosted-runners/azure-arm-template-example-parameters.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "enableAcceleratedNetworking": { - "value": true - }, - "networkSecurityGroupRules": { - "value": [ - { - "name": "RDP", - "properties": { - "priority": 300, - "protocol": "TCP", - "access": "Allow", - "direction": "Inbound", - "sourceAddressPrefix": "*", - "sourcePortRange": "*", - "destinationAddressPrefix": "*", - "destinationPortRange": "3389" - } - } - ] - }, - "subnetName": { - "value": "default" - }, - "addressPrefixes": { - "value": [ - "10.2.0.0/16" - ] - }, - "subnets": { - "value": [ - { - "name": "default", - "properties": { - "addressPrefix": "10.2.0.0/24" - } - } - ] - }, - "publicIpAddressType": { - "value": "Static" - }, - "publicIpAddressSku": { - "value": "Standard" - }, - "pipDeleteOption": { - "value": "Delete" - }, - "osDiskType": { - "value": "Premium_LRS" - }, - "osDiskDeleteOption": { - "value": "Delete" - }, - "nicDeleteOption": { - "value": "Delete" - }, - "patchMode": { - "value": "AutomaticByOS" - }, - "enableHotpatching": { - "value": false - }, - "zone": { - "value": "1" - }, - "computerName": { - "value": "actions-runner" - } - } -} diff --git a/azure-self-hosted-runners/azure-arm-template.json b/azure-self-hosted-runners/azure-arm-template.json deleted file mode 100644 index 4fa0727c..00000000 --- a/azure-self-hosted-runners/azure-arm-template.json +++ /dev/null @@ -1,294 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "githubActionsRunnerRegistrationUrl": { - "type": "string", - "minLength": 6, - "metadata": { - "description": "GitHub Actions Runner repo. E.g. https://github.com/MY_ORG (org-level) or https://github.com/MY_ORG/MY_REPO or (repo-level)" - } - }, - "githubActionsRunnerToken": { - "type": "securestring", - "minLength": 6, - "metadata": { - "description": "GitHub Actions Runner registration token for the org/repo. Note that these tokens are only valid for one hour after creation!" - } - }, - "githubActionsRunnerPath": { - "type": "string", - "metadata": { - "description": "Path to the Actions Runner. Keep this path short to prevent Long Path issues, e.g. D:\\a" - } - }, - "postDeploymentScriptZipBase64": { - "type": "string", - "minLength": 6, - "metadata": { - "description": "Base64-encoded .zip file containing the post-deployment script" - } - }, - "postDeploymentScriptFileName": { - "type": "string", - "minLength": 6, - "metadata": { - "description": "File name of the post-deployment script" - } - }, - "computerName": { - "type": "string", - "maxLength": 15, - "metadata": { - "description": "Windows Computer Name. Can be maximum 15 characters." - } - }, - "ephemeral": { - "type": "string", - "metadata": { - "description": "(optional) Whether to spin up an ephemeral runner or not." - } - }, - "stopService": { - "type": "string", - "metadata": { - "description": "(optional) Whether to stop the service immediately. Useful for spinning up runners preemptively." - } - }, - "location": { - "type": "string" - }, - "enableAcceleratedNetworking": { - "type": "bool" - }, - "networkSecurityGroupRules": { - "type": "array" - }, - "subnetName": { - "type": "string" - }, - "addressPrefixes": { - "type": "array" - }, - "subnets": { - "type": "array" - }, - "publicIpAddressName1": { - "type": "string" - }, - "publicIpAddressType": { - "type": "string" - }, - "publicIpAddressSku": { - "type": "string" - }, - "pipDeleteOption": { - "type": "string" - }, - "virtualMachineName": { - "type": "string" - }, - "osDiskType": { - "type": "string" - }, - "osDiskDeleteOption": { - "type": "string" - }, - "virtualMachineImage": { - "type": "string" - }, - "virtualMachineSize": { - "type": "string" - }, - "nicDeleteOption": { - "type": "string" - }, - "adminUsername": { - "type": "string" - }, - "adminPassword": { - "type": "securestring" - }, - "patchMode": { - "type": "string" - }, - "enableHotpatching": { - "type": "bool" - }, - "zone": { - "type": "string" - } - }, - "variables": { - "nsgName": "[concat(parameters('virtualMachineName'), '-nsg')]", - "nicName": "[concat(parameters('virtualMachineName'), '-nic')]", - "vnetName": "[concat(parameters('virtualMachineName'), '-vnet')]", - "vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', concat(parameters('virtualMachineName'), '-vnet'))]", - "subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]", - "postDeploymentScriptArguments": "[concat('-GitHubActionsRunnerToken ', parameters('githubActionsRunnerToken'), ' -GithubActionsRunnerRegistrationUrl ', parameters('githubActionsRunnerRegistrationUrl'), ' -GithubActionsRunnerName ', parameters('virtualMachineName'), ' -Ephemeral ', parameters('ephemeral'), ' -StopService ', parameters('stopService'), ' -GitHubActionsRunnerPath ', parameters('githubActionsRunnerPath'))]", - "publicIpAddressName1": "[if(equals(parameters('publicIpAddressName1'), ''), 'dummy', parameters('publicIpAddressName1'))]", - "publicIpAddressId": { - "id": "[resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName1'))]", - "properties": { - "deleteOption": "[parameters('pipDeleteOption')]" - } - } - }, - "resources": [ - { - "name": "[variables('nicName')]", - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2021-03-01", - "location": "[parameters('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Network/publicIpAddresses/', variables('publicIpAddressName1'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[variables('subnetRef')]" - }, - "privateIPAllocationMethod": "Dynamic", - "publicIpAddress": "[if(not(equals(parameters('publicIpAddressName1'), '')), variables('publicIpAddressId'), null())]" - } - } - ], - "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": { - "id": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]" - } - } - }, - { - "name": "[variables('nsgName')]", - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2019-02-01", - "location": "[parameters('location')]", - "properties": { - "securityRules": "[parameters('networkSecurityGroupRules')]" - } - }, - { - "name": "[variables('vnetName')]", - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2020-11-01", - "location": "[parameters('location')]", - "properties": { - "addressSpace": { - "addressPrefixes": "[parameters('addressPrefixes')]" - }, - "subnets": "[parameters('subnets')]" - } - }, - { - "condition": "[not(equals(parameters('publicIpAddressName1'), ''))]", - "name": "[variables('publicIpAddressName1')]", - "type": "Microsoft.Network/publicIpAddresses", - "apiVersion": "2020-08-01", - "location": "[parameters('location')]", - "properties": { - "publicIpAllocationMethod": "[parameters('publicIpAddressType')]" - }, - "sku": { - "name": "[parameters('publicIpAddressSku')]" - }, - "zones": [ - "1" - ] - }, - { - "name": "[parameters('virtualMachineName')]", - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2021-07-01", - "location": "[parameters('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[parameters('virtualMachineSize')]" - }, - "storageProfile": { - "osDisk": { - "createOption": "fromImage", - "managedDisk": { - "storageAccountType": "[parameters('osDiskType')]" - }, - "deleteOption": "[parameters('osDiskDeleteOption')]" - }, - "imageReference": { - "publisher": "microsoftwindowsdesktop", - "offer": "windows11preview-arm64", - "sku": "[parameters('virtualMachineImage')]", - "version": "latest" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]", - "properties": { - "deleteOption": "[parameters('nicDeleteOption')]" - } - } - ] - }, - "osProfile": { - "computerName": "[parameters('computerName')]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]", - "windowsConfiguration": { - "enableAutomaticUpdates": true, - "provisionVmAgent": true, - "patchSettings": { - "enableHotpatching": "[parameters('enableHotpatching')]", - "patchMode": "[parameters('patchMode')]" - } - } - }, - "licenseType": "Windows_Client", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true - } - } - }, - "zones": [ - "1" - ] - }, - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2020-12-01", - "name": "[concat(parameters('virtualMachineName'), '/CustomScriptExtension')]", - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/', parameters('virtualMachineName'))]" - ], - "location": "[parameters('location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "CustomScriptExtension", - "typeHandlerVersion": "1.9", - "autoUpgradeMinorVersion": true, - "protectedSettings": { - "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -Command \"[System.IO.File]::WriteAllBytes(\\\"tmp.zip\\\", [System.Convert]::FromBase64String(\\\"', parameters('postDeploymentScriptZipBase64'), '\\\")); Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory(\\\"tmp.zip\\\", \\\".\\\"); & .\\', parameters('postDeploymentScriptFileName'), ' ', variables('postDeploymentScriptArguments'), '\"')]" - } - } - } - ], - "outputs": { - "adminUsername": { - "type": "string", - "value": "[parameters('adminUsername')]" - }, - "customScriptInstanceView": { - "type": "object", - "value": "[reference(resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('virtualMachineName'), 'CustomScriptExtension')).instanceView]" - } - } -} diff --git a/azure-self-hosted-runners/post-deployment-script.ps1 b/azure-self-hosted-runners/post-deployment-script.ps1 deleted file mode 100644 index dc26eb58..00000000 --- a/azure-self-hosted-runners/post-deployment-script.ps1 +++ /dev/null @@ -1,330 +0,0 @@ -#Requires -RunAsAdministrator - -param ( - # https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners - [Parameter(Mandatory = $true, HelpMessage = "GitHub Actions Runner registration token. Note that these tokens are only valid for one hour after creation, so we always expect the user to provide one.")] - [string]$GitHubActionsRunnerToken, - - # GitHub Actions Runner repository. E.g. "https://github.com/MY_ORG" (org-level) or "https://github.com/MY_ORG/MY_REPO" (repo-level) - # https://docs.github.com/en/actions/hosting-your-own-runners/adding-self-hosted-runners - [Parameter(Mandatory = $true)] - [ValidateScript({ $_ -like "https://*" })] - [string]$GithubActionsRunnerRegistrationUrl, - - [Parameter(Mandatory = $true, HelpMessage = "Name of the runner. Needs to be unique in the org/repo")] - [ValidateNotNullOrEmpty()] - [string]$GithubActionsRunnerName, - - [Parameter(Mandatory = $false, HelpMessage = "Start an ephemeral runner (this is the default)")] - [ValidateSet('true', 'false')] - [string]$Ephemeral = 'true', - - [Parameter(Mandatory = $false, HelpMessage = "Stop Service immediately (useful for spinning up runners preemptively)")] - [ValidateSet('true', 'false')] - [string]$StopService = 'true', - - [Parameter(Mandatory = $true, HelpMessage = "Path to the Actions Runner. Keep this path short to prevent Long Path issues, e.g. D:\a")] - [ValidateNotNullOrEmpty()] - [string]$GitHubActionsRunnerPath -) - -Write-Output "Starting post-deployment script." - -# ================================= -# TOOL VERSIONS AND OTHER VARIABLES -# ================================= -# -# This header is used for both Git for Windows and GitHub Actions Runner -[hashtable]$GithubHeaders = @{ - "Accept" = "application/vnd.github.v3+json" - "X-GitHub-Api-Version" = "2022-11-28" -} - -# ================================= -# Get download and hash information for the latest release of Git for Windows -# ================================= -# -# This will return the latest release of Git for Windows download link, hash and the name of the outfile -# Everything will be saved in the object $GitHubGit -# -# url for Github API to get the latest release -[string]$GitHubUrl = "https://api.github.com/repos/git-for-windows/git/releases/latest" -# -# Name of the exe file that should be verified and downloaded -[string]$GithubExeName = "Git-.*-arm64.exe" - -try { - [System.Object]$GithubRestData = Invoke-RestMethod -Uri $GitHubUrl -Method Get -Headers $GithubHeaders -TimeoutSec 10 | Select-Object -Property assets, body - [System.Object]$GitHubAsset = $GithubRestData.assets | Where-Object { $_.name -match $GithubExeName } - $AssetNameEscaped = [Regex]::Escape($GitHubAsset.name) - if ($GithubRestData.body -match "\b${AssetNameEscaped}.*?\|.*?([a-zA-Z0-9]{64})" -eq $True) { - [System.Object]$GitHubGit = [PSCustomObject]@{ - DownloadUrl = [string]$GitHubAsset.browser_download_url - Hash = [string]$Matches[1].ToUpper() - OutFile = "./git-for-windows-installer.exe" - } - } - else { - Write-Error "Could not find hash for $GithubExeName" - exit 1 - } -} -catch { - Write-Error @" - "Message: "$($_.Exception.Message)`n - "Error Line: "$($_.InvocationInfo.Line)`n - "Line Number: "$($_.InvocationInfo.ScriptLineNumber)`n -"@ - exit 1 -} - -# ================================= -# Obtain the latest GitHub Actions Runner and other GitHub Actions information -# ================================= -# -# Note that the GitHub Actions Runner auto-updates itself by default, but do try to reference a relatively new version here. -# -# This will return the latest release of GitHub Actions Runner download link, hash, Tag, RunnerArch, RunnerLabels and the name of the outfile. -# Everything will be saved in the object $GitHubAction -# -# url for Github API to get the latest release of actions runner -[string]$GitHubActionUrl = "https://api.github.com/repos/actions/runner/releases/latest" - -try { - [System.Object]$GithubActionRestData = Invoke-RestMethod -Uri $GitHubActionUrl -Method Get -Headers $GithubHeaders -TimeoutSec 10 | Select-Object -Property assets, body, tag_name - if ($GithubActionRestData.body -match "(.*)" -eq $True) { - [string]$ActionZipName = "actions-runner-win-arm64-" + [string]$($GithubActionRestData.tag_name.Substring(1)) + ".zip" - - [System.Object]$GitHubAction = [PSCustomObject]@{ - Tag = $GithubActionRestData.tag_name.Substring(1) - Hash = $Matches[1].ToUpper() - RunnerArch = "arm64" - RunnerLabels = "self-hosted,Windows,ARM64" - DownloadUrl = $GithubActionRestData.assets | where-object { $_.name -match $ActionZipName } | Select-Object -ExpandProperty browser_download_url - OutFile = "$($GitHubActionsRunnerPath)\$($ActionZipName)" - } - } - else { - Write-Error "Error: Could not find hash for Github Actions Runner" - exit 1 - } -} -catch { - Write-Error @" - "Message: "$($_.Exception.Message)`n - "Error Line: "$($_.InvocationInfo.Line)`n - "Line Number: "$($_.InvocationInfo.ScriptLineNumber)`n -"@ - exit 1 -} - -# ================================= -# Obtain the latest pwsh binary and other pwsh information -# ================================= -# -# This will install pwsh on the machine, because it's not installed by default. -# It contains a bunch of new features compared to "powershell" and is sometimes more stable as well. -# -# url for Github API to get the latest release of pwsh -[string]$PwshUrl = "https://api.github.com/repos/PowerShell/PowerShell/releases/latest" - -# Name of the MSI file that should be verified and downloaded -[string]$PwshMsiName = "PowerShell-.*-win-arm64.msi" - -try { - [System.Object]$PwshRestData = Invoke-RestMethod -Uri $PwshUrl -Method Get -Headers $GithubHeaders -TimeoutSec 10 | Select-Object -Property assets, body - [System.Object]$PwshAsset = $PwshRestData.assets | Where-Object { $_.name -match $PwshMsiName } - if ($PwshRestData.body -match "\b$([Regex]::Escape($PwshAsset.name))\r\n.*?([a-zA-Z0-9]{64})" -eq $True) { - [System.Object]$GitHubPwsh = [PSCustomObject]@{ - DownloadUrl = [string]$PwshAsset.browser_download_url - Hash = [string]$Matches[1].ToUpper() - OutFile = "./pwsh-installer.msi" - } - } - else { - Write-Error "Could not find hash for $PwshMsiName" - exit 1 - } -} -catch { - Write-Error @" - "Message: "$($_.Exception.Message)`n - "Error Line: "$($_.InvocationInfo.Line)`n - "Line Number: "$($_.InvocationInfo.ScriptLineNumber)`n -"@ - exit 1 -} - -# ====================== -# WINDOWS DEVELOPER MODE -# ====================== - -# Needed for symlink support -Write-Output "Enabling Windows Developer Mode..." -Start-Process -Wait "reg" 'add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"' -Write-Output "Enabled Windows developer mode." - -# ============================= -# MICROSOFT DEFENDER EXCLUSIONS -# ============================= - -Write-Output "Adding Microsoft Defender Exclusions..." -Add-MpPreference -ExclusionPath "C:\" -Write-Output "Finished adding Microsoft Defender Exclusions." - -# ====================== -# GIT FOR WINDOWS -# ====================== - -Write-Output "Downloading Git for Windows..." -$ProgressPreference = 'SilentlyContinue' -Invoke-WebRequest -UseBasicParsing -Uri $GitHubGit.DownloadUrl -OutFile $GitHubGit.OutFile -$ProgressPreference = 'Continue' - -if ((Get-FileHash -Path $GitHubGit.OutFile -Algorithm SHA256).Hash.ToUpper() -ne $GitHubGit.Hash) { - Write-Error "Computed checksum for $($GitHubGit.OutFile) did not match $($GitHubGit.Hash)" - exit 1 -} - -Write-Output "Installing Git for Windows..." -@" -[Setup] -Lang=default -Dir=C:\Program Files\Git -Group=Git -NoIcons=0 -SetupType=default -Components=gitlfs,windowsterminal -Tasks= -EditorOption=VIM -CustomEditorPath= -DefaultBranchOption= -PathOption=CmdTools -SSHOption=OpenSSH -TortoiseOption=false -CURLOption=WinSSL -CRLFOption=CRLFAlways -BashTerminalOption=ConHost -GitPullBehaviorOption=FFOnly -UseCredentialManager=Core -PerformanceTweaksFSCache=Enabled -EnableSymlinks=Disabled -EnablePseudoConsoleSupport=Disabled -EnableFSMonitor=Disabled -"@ | Out-File -FilePath "./git-installer-config.inf" - -Start-Process -Wait $GitHubGit.OutFile '/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /LOADINF="./git-installer-config.inf"' - -Write-Output "Finished installing Git for Windows." - -# ====================== -# PWSH (PowerShell) -# ====================== - -Write-Output "Downloading pwsh..." - -$ProgressPreference = 'SilentlyContinue' -Invoke-WebRequest -UseBasicParsing -Uri $GitHubPwsh.DownloadUrl -OutFile $GitHubPwsh.OutFile -$ProgressPreference = 'Continue' - -if ((Get-FileHash -Path $GitHubPwsh.OutFile -Algorithm SHA256).Hash.ToUpper() -ne $GitHubPwsh.Hash) { - Write-Error "Computed checksum for $($GitHubPwsh.OutFile) did not match $($GitHubPwsh.Hash)" - exit 1 -} - -Write-Output "Installing pwsh..." - -# Get the full path to the MSI in the current working directory -$MsiPath = Resolve-Path $GitHubPwsh.OutFile - -# Define arguments for silent installation -$MsiArguments = "/qn /i `"$MsiPath`" ADD_PATH=1" - -# Install pwsh using msiexec -Start-Process msiexec.exe -Wait -ArgumentList $MsiArguments - -Write-Output "Finished installing pwsh." - -# ====================== -# GITHUB ACTIONS RUNNER -# ====================== - -Write-Output "Downloading GitHub Actions runner..." - -mkdir $GitHubActionsRunnerPath | Out-Null -$ProgressPreference = 'SilentlyContinue' -Invoke-WebRequest -UseBasicParsing -Uri $GitHubAction.DownloadUrl -OutFile $GitHubAction.OutFile -$ProgressPreference = 'Continue' - -if ((Get-FileHash -Path $GitHubAction.OutFile -Algorithm SHA256).Hash.ToUpper() -ne $GitHubAction.hash) { - Write-Error "Computed checksum for $($GitHubAction.OutFile) did not match $($GitHubAction.hash)" - exit 1 -} - -Write-Output "Installing GitHub Actions runner $($GitHubAction.Tag) as a Windows service with labels $($GitHubAction.RunnerLabels)..." - -Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::ExtractToDirectory($GitHubAction.OutFile, $GitHubActionsRunnerPath) - -If ($Ephemeral -ne 'true') { - $EphemeralOption = "" -} Else { - $EphemeralOption = "--ephemeral" - Write-Output "Configuring the runner to shut down automatically after running" - Set-Content -Path "${GitHubActionsRunnerPath}\shut-down.ps1" -Value "shutdown -s -t 60 -d p:4:0 -c `"workflow job is done`"" - [System.Environment]::SetEnvironmentVariable("ACTIONS_RUNNER_HOOK_JOB_COMPLETED", "${GitHubActionsRunnerPath}\shut-down.ps1", [System.EnvironmentVariableTarget]::Machine) -} - -Write-Output "Configuring the runner" -cmd.exe /c "${GitHubActionsRunnerPath}\config.cmd" --unattended $EphemeralOption --name ${GithubActionsRunnerName} --windowslogonaccount "NT AUTHORITY\SYSTEM" --runasservice --labels $($GitHubAction.RunnerLabels) --url ${GithubActionsRunnerRegistrationUrl} --token ${GitHubActionsRunnerToken} - -# Ensure that the service was created. If not, exit with error code. -if ($null -eq (Get-Service -Name "actions.runner.*")) { - Write-Output "Could not find service actions.runner.*, making three more attempts with a 3 second delay in between each attempt..." - - [int]$RetryCountService = 0 - do { - Write-Output "Attempt $($RetryCountService) of 3: Looking for service actions.runner.*..." - $RetryCountService++ - Start-Sleep -Seconds 3 - } - while ($null -eq (Get-Service -Name "actions.runner.*") -or $RetryCountService -gt 3) - - if ($RetryCountService -gt 3) { - Write-Error "GitHub Actions service not found (should start with actions.runner). Check the logs in ${GitHubActionsRunnerPath}\_diag for more details." - exit 1 - } - else { - Write-Output "Found service actions.runner.*" - } -} - -# Immediately stop the service as we want to leave the VM in a deallocated state for later use. The service will automatically be started when Windows starts. -if ($StopService -eq 'true') { - #Collects all running services named actions.runner.* - $GetActionRunnerServices = Get-Service -Name "actions.runner.*" | Where-Object { $_.Status -eq 'Running' } | Select-Object -ExpandProperty Name - - # Loops trough all services and stopping them one by one - foreach ($Service in $GetActionRunnerServices) { - Write-Output "Stopping service $Service" - Stop-Service -Name $Service - - # Making sure that all of the services has been stopped before moving forward - [int]$RetryCount = 0 - do { - Write-Output "Attempt: $($RetryCount) of 5: Waiting for service $Service to stop..." - $RetryCount++ - Start-Sleep -Seconds 5 - } - while ((Get-Service -Name $Service).Status -eq 'running' -or $RetryCount -gt 5) - - if ($RetryCount -gt 5) { - Write-Error "Service $Service failed to stop" - exit 1 - } - else { - Write-Output "Service $Service has been stopped" - } - } -} - -Write-Output "Finished installing GitHub Actions runner."