[
  {
    "path": ".dockerignore",
    "content": "tests\nREADME.md\n.git\n.gitignore\n.gitattributes\n.DS_Store\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Force checkout as Unix endline style\ntext eol=lf\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @jenkinsci/team-docker-packaging\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "community_bridge: jenkins\ncustom: [\"https://jenkins.io/donate/#why-donate\"]\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: docker\n  directory: \"/alpine\"\n  schedule:\n    interval: weekly\n  open-pull-requests-limit: 10\n- package-ecosystem: docker\n  directory: \"/debian\"\n  schedule:\n    interval: weekly\n  open-pull-requests-limit: 10\n- package-ecosystem: docker\n  directory: \"/windows/nanoserver\"\n  schedule:\n    interval: weekly\n  open-pull-requests-limit: 10\n- package-ecosystem: docker\n  directory: \"/windows/windowsservercore\"\n  schedule:\n    interval: weekly\n  open-pull-requests-limit: 10\n- package-ecosystem: \"github-actions\"\n  target-branch: master\n  directory: \"/\"\n  schedule:\n    # Check for updates to GitHub Actions every week\n    interval: \"weekly\"\n  labels:\n    - github-action\n    - dependencies\n    - skip-changelog\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "# https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc\n\n_extends: github:jenkinsci/.github:/.github/release-drafter.yml\n# Semantic versioning: https://semver.org/\nversion-template: $MAJOR.$MINOR.$PATCH\ntag-template: $NEXT_MINOR_VERSION\nname-template: $NEXT_MINOR_VERSION\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "# Automates creation of Release Drafts using Release Drafter\n# Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc\n\nname: Release Drafter (Changelog)\n\non:\n  push:\n    branches:\n      - master\n  workflow_dispatch:\n\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      # Drafts your next Release notes as Pull Requests are merged into the default branch\n      - uses: release-drafter/release-drafter@v7.3.0\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/updatecli.yaml",
    "content": "name: updatecli\non:\n  # Allow to be run manually\n  workflow_dispatch:\n  schedule:\n    - cron: '0 1 * * *' # Once a day at 01:00am UTC\n  push:\n  pull_request:\njobs:\n  updatecli:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Install Updatecli in the runner\n        uses: updatecli/updatecli-action@v3.2.0\n\n      - name: Run Updatecli in Dry Run mode\n        run: updatecli diff --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml --values ./updatecli/values.temurin.yaml\n        env:\n          UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Run Updatecli in Apply mode\n        if: github.ref == 'refs/heads/master'\n        run: updatecli apply --config ./updatecli/updatecli.d --values ./updatecli/values.github-action.yaml --values ./updatecli/values.temurin.yaml\n        env:\n          UPDATECLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nbats-core/\nbats/\ntarget/\n/.vscode/\nbuild-windows.yaml\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"tests/test_helper/bats-support\"]\n\tpath = tests/test_helper/bats-support\n\turl = https://github.com/bats-core/bats-support.git\n[submodule \"tests/test_helper/bats-assert\"]\n\tpath = tests/test_helper/bats-assert\n\turl = https://github.com/bats-core/bats-assert.git\n"
  },
  {
    "path": "CreateProfile.psm1",
    "content": "# Based on code developed by  Josh Rickard (@MS_dministrator) and Thom Schumacher (@driberif)\r\n# Location: https://gist.github.com/crshnbrn66/7e81bf20408c05ddb2b4fdf4498477d8\r\n\r\n#function to register a native method\r\nfunction Register-NativeMethod {\r\n    [CmdletBinding()]\r\n    [Alias()]\r\n    [OutputType([int])]\r\n    Param\r\n    (\r\n        # Param1 help description\r\n        [Parameter(Mandatory=$true,\r\n                   ValueFromPipelineByPropertyName=$true,\r\n                   Position=0)]\r\n        [string]$dll,\r\n\r\n        # Param2 help description\r\n        [Parameter(Mandatory=$true,\r\n                   ValueFromPipelineByPropertyName=$true,\r\n                   Position=1)]\r\n        [string]\r\n        $methodSignature\r\n    )\r\n\r\n    $script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; }\r\n}\r\n\r\n#function to add native method\r\nfunction Add-NativeMethods {\r\n    [CmdletBinding()]\r\n    [Alias()]\r\n    [OutputType([int])]\r\n    Param($typeName = 'NativeMethods')\r\n\r\n    $nativeMethodsCode = $script:nativeMethods | ForEach-Object { \"\r\n        [DllImport(`\"$($_.Dll)`\")]\r\n        public static extern $($_.Signature);\r\n    \" }\r\n\r\n    Add-Type @\"\r\n        using System;\r\n        using System.Text;\r\n        using System.Runtime.InteropServices;\r\n        public static class $typeName {\r\n            $nativeMethodsCode\r\n        }\r\n\"@\r\n}\r\n\r\n#Main function to create the new user profile\r\nfunction New-UserWithProfile {\r\n    [CmdletBinding()]\r\n    [Alias()]\r\n    [OutputType([int])]\r\n    Param\r\n    (\r\n        # Param1 help description\r\n        [Parameter(Mandatory=$true,\r\n                   ValueFromPipelineByPropertyName=$true,\r\n                   Position=0)]\r\n        [string]$UserName,\r\n\r\n        [Parameter(Mandatory=$false,\r\n                   ValueFromPipelineByPropertyName=$true,\r\n                   Position=1)]\r\n        [string]$Description = ''\r\n    )\r\n\r\n    Write-Verbose \"Creating local user $Username\";\r\n\r\n    try {\r\n        net user $UserName /ADD /ACTIVE:YES /EXPIRES:NEVER /FULLNAME:\"$Description\" /PASSWORDCHG:NO /PASSWORDREQ:NO\r\n        net localgroup Administrators /add $UserName\r\n    } catch {\r\n        Write-Error $_.Exception.Message;\r\n        break;\r\n    }\r\n\r\n    $localUser = New-Object System.Security.Principal.NTAccount($UserName)\r\n\r\n    $methodName = 'UserEnvCP'\r\n    $script:nativeMethods = @();\r\n\r\n    if (-not ([System.Management.Automation.PSTypeName]$MethodName).Type) {\r\n        Register-NativeMethod \"userenv.dll\" \"int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,`\r\n         [MarshalAs(UnmanagedType.LPWStr)] string pszUserName,`\r\n         [Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)\";\r\n\r\n        Add-NativeMethods -typeName $MethodName;\r\n    }\r\n\r\n    $userSID = $localUser.Translate([System.Security.Principal.SecurityIdentifier])\r\n    $sb = New-Object System.Text.StringBuilder(260)\r\n    $pathLen = $sb.Capacity\r\n\r\n    Write-Verbose \"Creating user profile for $UserName\";\r\n\r\n    try {\r\n        [UserEnvCP]::CreateProfile($userSID.Value, $UserName, $sb, $pathLen) | Out-Null;\r\n    } catch {\r\n        Write-Error $_.Exception.Message;\r\n        break;\r\n    }\r\n\r\n    $profilePath = $sb.ToString()\r\n    Write-Verbose \"Profile created at $profilePath\"\r\n    if(-not (Test-Path (Join-Path $profilePath \"NTUSER.DAT\"))) {\r\n        Copy-Item \"C:\\Users\\Default\\NTUSER.DAT\" $profilePath\r\n    }\r\n}"
  },
  {
    "path": "Jenkinsfile",
    "content": "final String cronExpr = env.BRANCH_IS_PRIMARY ? '@daily' : ''\n\nproperties([\n    buildDiscarder(logRotator(numToKeepStr: '10')),\n    disableConcurrentBuilds(abortPrevious: true),\n    pipelineTriggers([cron(cronExpr)]),\n])\n\ndef agentSelector(String imageType, retryCounter) {\n    def platform\n    switch (imageType) {\n        // nanoserver-ltsc2019 and windowservercore-ltsc2019\n        case ~/.*2019/:\n            platform = 'windows-2019'\n            break\n\n        // nanoserver-ltsc2022 and windowservercore-ltsc2022\n        case ~/.*2022/:\n            platform = 'windows-2022'\n            break\n\n        // All other Windows images\n        case ~/(nanoserver|windowsservercore).*/:\n            platform = 'windows-2025'\n            break\n\n        // Linux\n        default:\n            // Need Docker and a LOT of memory for faster builds (due to multi archs)\n            platform = 'docker-highmem'\n            break\n    }\n\n    // Defined in https://github.com/jenkins-infra/pipeline-library/blob/master/vars/infra.groovy\n    return infra.getBuildAgentLabel([\n        useContainerAgent: false,\n        platform: platform,\n        spotRetryCounter: retryCounter\n    ])\n}\n\n// Specify parallel stages\ndef parallelStages = [failFast: false]\n[\n    'linux',\n    'nanoserver-ltsc2019',\n    'nanoserver-ltsc2022',\n    'windowsservercore-ltsc2019',\n    'windowsservercore-ltsc2022'\n].each { imageType ->\n    parallelStages[imageType] = {\n        withEnv([\n          \"IMAGE_TYPE=${imageType}\", \n          \"REGISTRY_ORG=${infra.isTrusted() ? 'jenkins' : 'jenkins4eval'}\",\n        ]) {\n            int retryCounter = 0\n            retry(count: 2, conditions: [agent(), nonresumable()]) {\n                // Use local variable to manage concurrency and increment BEFORE spinning up any agent\n                final String resolvedAgentLabel = agentSelector(imageType, retryCounter)\n                retryCounter++\n                node(resolvedAgentLabel) {\n                    timeout(time: 60, unit: 'MINUTES') {\n                        checkout scm\n                        if (imageType == \"linux\") {\n                            stage('Prepare Docker') {\n                                sh 'make docker-init'\n                            }\n                        }\n                        // This function is defined in the jenkins-infra/pipeline-library\n                        if (infra.isTrusted()) {\n                            // trusted.ci.jenkins.io builds (e.g. publication to DockerHub)\n                            stage('Deploy to DockerHub') {\n                                withEnv([\n                                    \"ON_TAG=true\",\n                                    \"VERSION=${env.TAG_NAME}\",\n                                ]) {\n                                    // This function is defined in the jenkins-infra/pipeline-library\n                                    infra.withDockerCredentials {\n                                        if (isUnix()) {\n                                            sh 'make publish'\n                                        } else {\n                                            powershell '& ./build.ps1 build'\n                                            powershell '& ./build.ps1 publish'\n                                        }\n                                    }\n                                }\n                            }\n                        } else {\n                            // ci.jenkins.io builds (e.g. no publication)\n                            stage('Build') {\n                                if (isUnix()) {\n                                    sh 'make build'\n                                } else {\n                                    powershell '& ./build.ps1 build'\n                                    archiveArtifacts artifacts: 'build-windows.yaml', allowEmptyArchive: true\n                                }\n                            }\n                            stage('Test') {\n                                if (isUnix()) {\n                                    sh 'make test'\n                                } else {\n                                    powershell '& ./build.ps1 test'\n                                }\n                                junit(allowEmptyResults: true, keepLongStdio: true, testResults: 'target/**/junit-results.xml')\n                            }\n                            // If the tests are passing for Linux AMD64, then we can build all the CPU architectures\n                            if (isUnix()) {\n                                stage('Multi-Arch Build') {\n\n                                    sh 'make every-build'\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n// Execute parallel stages\nparallel parallelStages\n// // vim: ft=groovy\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2015-2019 Jenkins project contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "ROOT:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))\n\n## For Docker <=20.04\nexport DOCKER_BUILDKIT=1\n## For Docker <=20.04\nexport DOCKER_CLI_EXPERIMENTAL=enabled\n## Required to have docker build output always printed on stdout\nexport BUILDKIT_PROGRESS=plain\n\ncurrent_arch := $(shell uname -m)\nexport ARCH ?= $(shell case $(current_arch) in (x86_64) echo \"amd64\" ;; (i386) echo \"386\";; (aarch64|arm64) echo \"arm64\" ;; (armv6*) echo \"arm/v6\";; (armv7*) echo \"arm/v7\";; (s390*|riscv*|ppc64le) echo $(current_arch);; (*) echo \"UNKNOWN-CPU\";; esac)\n\nIMAGE_NAME:=jenkins4eval/ssh-agent\n\n# Set to the path of a specific test suite to restrict execution only to this\n# default is \"all test suites in the \"tests/\" directory\nTEST_SUITES ?= $(CURDIR)/tests\n\n##### Macros\n## Check the presence of a CLI in the current PATH\ncheck_cli = type \"$(1)\" >/dev/null 2>&1 || { echo \"Error: command '$(1)' required but not found. Exiting.\" ; exit 1 ; }\n## Check if a given image exists in the current manifest docker-bake.hcl\ncheck_image = make --silent list | grep -w '$(1)' >/dev/null 2>&1 || { echo \"Error: the image '$(1)' does not exist in manifest for the platform 'linux/$(ARCH)'. Please check the output of 'make list'. Exiting.\" ; exit 1 ; }\n## Base \"docker buildx base\" command to be reused everywhere\nbake_base_cli := docker buildx bake --file docker-bake.hcl\nbake_cli := $(bake_base_cli) --load\n\n.PHONY: build\n.PHONY: test test-alpine test-debian\n\ncheck-reqs:\n## Build requirements\n\t@$(call check_cli,bash)\n\t@$(call check_cli,git)\n\t@$(call check_cli,docker)\n\t@docker info | grep 'buildx:' >/dev/null 2>&1 || { echo \"Error: Docker BuildX plugin required but not found. Exiting.\" ; exit 1 ; }\n## Test requirements\n\t@$(call check_cli,curl)\n\t@$(call check_cli,jq)\n\n## This function is specific to Jenkins infrastructure and isn't required in other contexts\ndocker-init: check-reqs\nifeq ($(CI),true)\nifeq ($(wildcard /etc/buildkitd.toml),)\n\techo 'WARNING: /etc/buildkitd.toml not found, using default configuration.'\n\tdocker buildx create --use --bootstrap --driver docker-container\nelse\n\tdocker buildx create --use --bootstrap --driver docker-container --config /etc/buildkitd.toml\nendif\nelse\n\tdocker buildx create --use --bootstrap --driver docker-container\nendif\n\tdocker run --rm --privileged multiarch/qemu-user-static --reset -p yes\n\nbuild: check-reqs\n\t@set -x; $(bake_cli) $(shell make --silent list) --set '*.platform=linux/$(ARCH)'\n\nbuild-%:\n\t@$(call check_image,$*)\n\t@set -x; $(bake_cli) '$*' --set '*.platform=linux/$(ARCH)'\n\nevery-build: check-reqs\n\t@set -x; $(bake_base_cli) linux\n\nshow:\n\t@$(bake_base_cli) --progress=quiet linux --print | jq\n\ntags:\n\t@make show | jq -r '.target[].tags[]' | LC_ALL=C sort\n\nlist: check-reqs\n\t@set -x; make --silent show | jq -r '.target | path(.. | select(.platforms[] | contains(\"linux/$(ARCH)\"))?) | add'\n\nbats:\n\tgit clone --branch v1.13.0 https://github.com/bats-core/bats-core bats\n\nprepare-test: bats check-reqs\n\tgit submodule update --init --recursive\n\tmkdir -p target\n\npublish:\n\t@set -x; $(bake_base_cli) linux --push\n\n## Define bats options based on environment\n# common flags for all tests\nbats_flags := $(TEST_SUITES)\n# if DISABLE_PARALLEL_TESTS true, then disable parallel execution\nifneq (true,$(DISABLE_PARALLEL_TESTS))\n# If the GNU 'parallel' command line is absent, then disable parallel execution\nparallel_cli := $(shell command -v parallel 2>/dev/null)\nifneq (,$(parallel_cli))\n# If parallel execution is enabled, then set 2 tests per core available for the Docker Engine\ntest-%: PARALLEL_JOBS ?= $(shell echo $$(( $(shell docker run --rm alpine grep -c processor /proc/cpuinfo) * 2)))\ntest-%: bats_flags += --jobs $(PARALLEL_JOBS)\nendif\nendif\ntest-%: prepare-test\n# Check that the image exists in the manifest\n\t@$(call check_image,$*)\n# Ensure that the image is built\n\t@make --silent build-$*\nifeq ($(CI), true)\n# Execute the test harness and write result to a TAP file\n\tIMAGE=$* bats/bin/bats $(bats_flags) --formatter junit | tee target/junit-results-$*.xml\nelse\n# Execute the test harness\n\tIMAGE=$* bats/bin/bats $(bats_flags)\nendif\n\ntest: prepare-test\n\t@make --silent list | while read image; do make --silent \"test-$${image}\"; done\n"
  },
  {
    "path": "README.md",
    "content": "# Docker image for Jenkins agents connected over SSH\n\n[![Join the chat at https://gitter.im/jenkinsci/docker](https://badges.gitter.im/jenkinsci/docker.svg)](https://gitter.im/jenkinsci/docker?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![GitHub stars](https://img.shields.io/github/stars/jenkinsci/docker-ssh-agent?label=GitHub%20stars)](https://github.com/jenkinsci/docker-ssh-agent)\n[![Docker Pulls](https://img.shields.io/docker/pulls/jenkins/ssh-agent.svg)](https://hub.docker.com/r/jenkins/ssh-agent/)\n[![GitHub release](https://img.shields.io/github/release/jenkinsci/docker-ssh-agent.svg?label=changelog)](https://github.com/jenkinsci/docker-ssh-agent/releases)\n\nA [Jenkins](https://jenkins.io) agent image which allows using SSH to establish the connection.\nIt can be used together with the [SSH Build Agents plugin](https://plugins.jenkins.io/ssh-slaves) or other similar plugins.\n\nSee [Jenkins Distributed builds](https://wiki.jenkins-ci.org/display/JENKINS/Distributed+builds) for more info.\n\n## Running\n\n### Running with the SSH Build Agents plugin\n\nTo run a Docker container\n\n```bash\ndocker run -d --rm --name=agent --publish 2200:22 -e \"JENKINS_AGENT_SSH_PUBKEY=<public_key>\" jenkins/ssh-agent\n```\n\n - `-d`: To start a container in detached mode, use the `-d` option. Containers started in detached mode exit when the root process used to run the container exits, unless you also specify the --rm option.\n - `--rm`: If you use -d with --rm, the container is removed when it exits or when the daemon exits, whichever happens first.\n - `--name`: Assigns a name to the container. If you do not specify a name, Docker generates a random name.\n - `--publish 2200:22`: Publishes the host port 2200 to the agent container port 22 (SSH) to allow connection from the host with `ssh jenkins@localhost -p 2200`\n\nPlease note none of these options are mandatory, they are just examples.\n\nYou will then be able to connect this agent using the [SSH Build Agents plugin](https://plugins.jenkins.io/ssh-slaves) as \"jenkins\" with the matching private key.\n\nWhen using the Linux image, you have to set the value of the `Remote root directory` to `/home/jenkins/agent` in the agent configuration UI.\n\n![Remote root directory with a Linux agent](docs/ssh-plugin-remote-root-directory-linux.png \"Remote root directory with a Linux agent\")\n\nWhen using the Windows image, you have to set the value of the `Remote root directory` to `C:/Users/jenkins/Work` in the agent configuration UI.\n\n![Remote root directory with a Windows agent](docs/ssh-plugin-remote-root-directory-windows.png \"Remote root directory with a Windows agent\")\n\nIf you intend to use another directory than `/home/jenkins/agent` under Linux or `C:/Users/jenkins/Work` under Windows, don't forget to add it as a data volume.\n\n```bash\ndocker run -v docker-volume-for-jenkins-ssh-agent:/home/jenkins/agent:rw jenkins/ssh-agent \"<public key>\"\n```\n\n### How to use this image with Docker Plugin\n\nTo use this image with [Docker Plugin](https://plugins.jenkins.io/docker-plugin), you need to pass the public SSH key using environment variable `JENKINS_AGENT_SSH_PUBKEY` and not as a startup argument.\n\nIn _Environment_ field of the Docker Template (advanced section), just add:\n\n    JENKINS_AGENT_SSH_PUBKEY=<YOUR PUBLIC SSH KEY HERE>\n\nDon't put quotes around the public key.\n\nPlease note that you have to set the value of the `Remote File System Root` to `/home/jenkins/agent` in the Docker Agent Template configuration UI.\n\n![Remote File System Root](docs/docker-plugin-remote-filesystem-root.png \"Remote File System Root directory\")\n\nIf you intend to use another directory than `/home/jenkins/agent`, don't forget to add it as a data volume.\n\n![Docker Volumes mounts](docs/docker-plugin-volumes.png \"Docker Volumes mounts\")\n\nYou should be all set.\n\n## Extending the image\nShould you need to extend the image, you could use something along those lines:\n\n```Dockerfile\nFROM jenkins/ssh-agent:debian-jdk17 as ssh-agent\n# [...]\nCOPY --chown=jenkins mykey \"${JENKINS_AGENT_HOME}\"/.ssh/mykey\n# [...]\n```\n\n## Configurations\n\nThe image has several supported configurations, which can be accessed via the following tags:\n\n`${IMAGE_VERSION}` can be found on the [releases](https://github.com/jenkinsci/docker-ssh-agent/releases) page.\n\nList of tags can be consulted at https://github.com/jenkinsci/docker-ssh-agent/tree/master/tests/golden\n\n## Host keys\n\nHost keys are generated with `ssh-keygen -A` in `setup-httpd`. Host keys reside inside the container. When the container is recreated, different host keys are also generated. Jenkins master may need to re-trust new host keys.\n\nWe can preserve host keys in mounted volume.\n\nSteps:\n1. Copy host keys from the mounted volume to /etc/ssh/\n2. Run ssh-keygen -A to generate host keys\n3. Copy host keys from /etc/ssh/ to the mounted volume\n4. Run setup-sshd\n\nEntry point example:\n\n```\n#!/bin/bash\n\n# /mnt/agent is the mounted volume\nmkdir -p /mnt/agent/host_keys/\n\ncp -u /mnt/agent/host_keys/ssh_host*_key* /etc/ssh/\n\nssh-keygen -A\n\ncp -u /etc/ssh/ssh_host*_key* /mnt/agent/host_keys/\n\nsetup-ssd \"$@\"\n```\n\n## Building instructions\n\n### Pre-requisites\n\nShould you want to build this image on your machine (before submitting a pull request for example), please have a look at the pre-requisites:\n\n* A GNU/Linux machine with [Docker](https://docs.docker.com/engine/install/), a macOS machine with [Docker Desktop](https://docs.docker.com/desktop/install/mac-install/), or a Windows machine with [Docker for Windows](https://docs.docker.com/docker-for-windows/) installed\n* Docker BuildX plugin [installed](https://github.com/docker/buildx#installing) on older versions of Docker (from `19.03`). Docker Buildx is included in recent versions of Docker Desktop for Windows, macOS, and Linux. Docker Linux packages also include Docker Buildx when installed using the DEB or RPM packages.\n* [GNU Make](https://www.gnu.org/software/make/) [installed](https://command-not-found.com/make)\n* jq [installed](https://command-not-found.com/jq)\n* yq [installed](https://github.com/mikefarah/yq) (for Windows)\n* [GNU Bash](https://www.gnu.org/software/bash/) [installed](https://command-not-found.com/bash)\n* git [installed](https://command-not-found.com/git)\n* curl [installed](https://command-not-found.com/curl)\n\n### Building\n\n#### Target images\n\nIf you want to see the target images that will be built, you can issue the following command:\n\n```bash\nmake list\nalpine_jdk11\nalpine_jdk17\ndebian_jdk11\ndebian_jdk17\n```\n\n#### Building a specific image\n\nIf you want to build a specific image, you can issue the following command:\n\n```bash\nmake build-<OS>_<JDK_VERSION>\n```\n\nThat would give for JDK 17 on Alpine Linux:\n\n```bash\nmake build-alpine_jdk17\n```\n\n#### Building images supported by your current architecture\n\nThen, you can build the images supported by your current architecture by running:\n\n```bash\nmake build\n```\n\n#### Testing all images\n\nIf you want to test these images, you can run:\n\n```bash\nmake test\n```\n#### Testing a specific image\n\nIf you want to test a specific image, you can run:\n\n```bash\nmake test-<OS>_<JDK_VERSION>\n```\n\nThat would give for JDK 17 on Alpine Linux:\n\n```bash\nmake test-alpine_jdk17\n```\n\n#### Building all images\n\nYou can build all images (even those unsupported by your current architecture) by running:\n\n```bash\nmake every-build\n```\n\n#### Other `make` targets\n\n`show` gives us a detailed view of the images that will be built, with the tags, platforms, and Dockerfiles.\n\n```bash\nmake show\n{\n  \"group\": {\n    \"default\": {\n      \"targets\": [\n        \"alpine_jdk17\",\n        \"alpine_jdk11\",\n        \"debian_jdk11\",\n        \"debian_jdk17\",\n      ]\n    }\n  },\n  \"target\": {\n    \"alpine_jdk11\": {\n      \"context\": \".\",\n      \"dockerfile\": \"alpine/Dockerfile\",\n      \"tags\": [\n        \"docker.io/jenkins/ssh-agent:alpine-jdk11\",\n        \"docker.io/jenkins/ssh-agent:latest-alpine-jdk11\"\n      ],\n      \"platforms\": [\n        \"linux/amd64\"\n      ],\n      \"output\": [\n        \"type=docker\"\n      ]\n    },\n    [...]\n```\n\n`bats` is a dependency target. It will update the [`bats` submodule](https://github.com/bats-core/bats-core) and run the tests.\n\n```bash\nmake bats\nmake: 'bats' is up to date.\n```\n\n`publish` allows the publication of all images targeted by 'linux' to a registry.\n\n`docker-init` is dedicated to Jenkins infrastructure for initializing docker and isn't required in other contexts.\n\n### Building and testing on Windows\n\n#### Building all images\n\nRun `.\\build.ps1` to launch the build of the images corresponding to the \"windows\" target of docker-bake.hcl.\n\nInternally, the first time you'll run this script and if there is no build-windows.yaml file in your repository, it will use a combination of `docker buildx bake` and `yq` to generate a  build-windows.yaml docker compose file containing all Windows image definitions from docker-bake.hcl. Then it will run `docker compose` on this file to build these images.\n\nYou can modify this docker compose file as you want, then rerun `.\\build.ps1`.\nIt won't regenerate the docker compose file from docker-bake.hcl unless you add the `-OverwriteDockerComposeFile` build.ps1 parameter:  `.\\build.ps1 -OverwriteDockerComposeFile`.\n\nNote: you can generate this docker compose file from docker-bake.hcl yourself with the following command (require `docker buildx` and `yq`):\n\n```console\n# - Use docker buildx bake to output image definitions from the \"windows\" bake target\n# - Convert with yq to the format expected by docker compose\n# - Store the result in the docker compose file\n\n$ docker buildx bake --progress=plain --file=docker-bake.hcl windows --print `\n    | yq --prettyPrint '.target[] | del(.output) | {(. | key): {\\\"image\\\": .tags[0], \\\"build\\\": .}}' | yq '{\\\"services\\\": .}' `\n    | Out-File -FilePath build-windows.yaml\n```\n\nNote that you don't need build.ps1 to build (or to publish) your images from this docker compose file, you can use `docker compose --file=build-windows.yaml build`.\n\n#### Testing all images\n\nRun `.\\build.ps1 test` if you also want to run the tests harness suit.\n\nRun `.\\build.ps1 test -TestsDebug 'debug'` to also get commands & stderr of tests, displayed on top of them.\nYou can set it to `'verbose'` to also get stdout of every test command.\n\nNote that instead of passing `-TestsDebug` parameter to build.ps1, you can set the  $env:TESTS_DEBUG environment variable to the desired value.\n\nAlso note that contrary to the Linux part, you have to build the images before testing them.\n\n#### Dry run\n\nAdd the `-DryRun` parameter to print out any build, publish or tests commands instead of executing them: `.\\build.ps1 test -DryRun`\n\n#### Building and testing a specific image\n\nYou can build (and test) only one image type by setting `-ImageType` to a combination of Windows flavors (\"nanoserver\" & \"windowsservercore\") and Windows versions (\"ltsc2019\", \"ltsc2022\").\n\nEx: `.\\build.ps1 -ImageType 'nanoserver-ltsc2019'`\n\n## Changelog\n\nSee [GitHub Releases](https://github.com/jenkinsci/docker-ssh-agent/releases/latest).\nNote that the changelogs and release tags were introduced in Dec 2019, and there are no entries for previous releases.\nPlease consult with the commit history if needed.\n"
  },
  {
    "path": "alpine/Dockerfile",
    "content": "# MIT License\n#\n# Copyright (c) 2019-2022 Fabio Kruger and other contributors\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nARG JAVA_VERSION=17.0.19_10\nARG ALPINE_TAG=3.23.4\nFROM alpine:\"${ALPINE_TAG}\" AS jre-build\n\nSHELL [\"/bin/ash\", \"-eo\", \"pipefail\", \"-c\"]\n\n# This Build ARG is populated by Docker\n# Ref. https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope\nARG TARGETPLATFORM\n\nCOPY jdk-download-url.sh /usr/bin/local/jdk-download-url.sh\nCOPY jdk-download.sh /usr/bin/local/jdk-download.sh\n\nARG JAVA_VERSION=17.0.19_10\n# hadolint ignore=DL3018\nRUN apk add --no-cache \\\n    ca-certificates \\\n    jq \\\n    curl \\\n  && /usr/bin/local/jdk-download.sh alpine\n\nENV PATH=\"/opt/jdk-${JAVA_VERSION}/bin:${PATH}\"\n\nRUN case \"$(jlink --version 2>&1)\" in \\\n    \"17.\"*) set -- \"--compress=2\" --add-modules ALL-MODULE-PATH ;; \\\n    \"21.\"*) set -- \"--compress=zip-6\" --add-modules ALL-MODULE-PATH ;; \\\n    # JDK 25 switches to a minimal module set for smaller images\n    \"25\"*) set -- \"--compress=zip-6\" --add-modules \"java.base,java.logging,java.xml,java.management,java.net.http,jdk.crypto.ec\" ;; \\\n    *) echo \"ERROR: unmanaged jlink version pattern\" && exit 1 ;; \\\n  esac; \\\n  jlink \\\n    --strip-java-debug-attributes \\\n    \"$@\" \\\n    --no-man-pages \\\n    --no-header-files \\\n    --output /javaruntime\n\n# Downloading git-lfs from this intermediate stage as there is no wget/curl in the final one\nARG GIT_LFS_VERSION=3.7.1\nRUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g') \\\n  && curl -L -s -o git-lfs.tgz \"https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/git-lfs-linux-${arch}-v${GIT_LFS_VERSION}.tar.gz\"\n\nFROM alpine:\"${ALPINE_TAG}\" AS build\n\nARG user=jenkins\nARG group=jenkins\nARG uid=1000\nARG gid=1000\nARG JENKINS_AGENT_HOME=/home/${user}\n\nENV JENKINS_AGENT_HOME=${JENKINS_AGENT_HOME}\n\nARG AGENT_WORKDIR=\"${JENKINS_AGENT_HOME}\"/agent\n# Persist agent workdir path through an environment variable for people extending the image\nENV AGENT_WORKDIR=${AGENT_WORKDIR}\n\nRUN addgroup -g \"${gid}\" \"${group}\" \\\n    # Set the home directory (h), set user and group id (u, G), set the shell, don't ask for password (D)\n    && adduser -h \"${JENKINS_AGENT_HOME}\" -u \"${uid}\" -G \"${group}\" -s /bin/bash -D \"${user}\" \\\n    # Unblock user\n    && passwd -u \"${user}\" \\\n    # Prepare subdirectories\n    && mkdir -p \"${JENKINS_AGENT_HOME}/.ssh/\" \"${JENKINS_AGENT_HOME}/.jenkins/\" \"${AGENT_WORKDIR}\" \\\n    && chown -R \"${uid}\":\"${gid}\" \"${JENKINS_AGENT_HOME}\" \"${AGENT_WORKDIR}\"\n\n\nRUN apk add --no-cache \\\n        bash \\\n        git \\\n        less \\\n        musl-locales \\\n        netcat-openbsd \\\n        openssh \\\n        patch \\\n    # Cleanup SSH host keys if any\n    && rm -f /etc/ssh/ssh_host*_key*\n\n# Retrieve git-lfs from the build stage and install it\nCOPY --from=jre-build git-lfs.tgz git-lfs.tgz\nRUN tar xzf git-lfs.tgz \\\n  && bash git-lfs-*/install.sh \\\n  && rm -rf git-lfs*\n\n# setup SSH server\nRUN sed -i /etc/ssh/sshd_config \\\n        -e 's/#PermitRootLogin.*/PermitRootLogin no/' \\\n        -e 's/#PasswordAuthentication.*/PasswordAuthentication no/' \\\n        -e 's/#SyslogFacility.*/SyslogFacility AUTH/' \\\n        -e 's/#LogLevel.*/LogLevel INFO/' \\\n        -e 's/#PermitUserEnvironment.*/PermitUserEnvironment yes/' \\\n    && mkdir /var/run/sshd\n\n# Install JDK\n\nENV JAVA_HOME=/opt/java/openjdk\nCOPY --from=jre-build /javaruntime \"$JAVA_HOME\"\nENV PATH=\"${JAVA_HOME}/bin:${PATH}\"\n\n# VOLUME directive must happen after setting up permissions and content\nVOLUME \"${AGENT_WORKDIR}\" \"${JENKINS_AGENT_HOME}\"/.jenkins \"/tmp\" \"/run\" \"/var/run\"\nWORKDIR \"${JENKINS_AGENT_HOME}\"\n\n# Alpine's ssh doesn't use $PATH defined in /etc/environment, so we define `$PATH` in `~/.ssh/environment`\n# The file path has been created earlier in the file by `mkdir -p` and we also have configured sshd so that it will\n# allow environment variables to be sourced (see `sed` command related to `PermitUserEnvironment`)\nRUN echo \"PATH=${PATH}\" >> ${JENKINS_AGENT_HOME}/.ssh/environment\nCOPY setup-sshd /usr/local/bin/setup-sshd\n\nEXPOSE 22\n\nENTRYPOINT [\"setup-sshd\"]\n\nLABEL \\\n    org.opencontainers.image.vendor=\"Jenkins project\" \\\n    org.opencontainers.image.title=\"Official Jenkins SSH Agent Docker image\" \\\n    org.opencontainers.image.description=\"A Jenkins agent image which allows using SSH to establish the connection\" \\\n    org.opencontainers.image.url=\"https://www.jenkins.io/\" \\\n    org.opencontainers.image.source=\"https://github.com/jenkinsci/docker-ssh-agent\" \\\n    org.opencontainers.image.licenses=\"MIT\"\n"
  },
  {
    "path": "build.ps1",
    "content": "[CmdletBinding()]\nParam(\n    [Parameter(Position=1)]\n    # Default build.ps1 target\n    [String] $Target = 'build',\n    # Image version\n    [String] $VersionTag = '0.0.1',\n    # Windows flavor and windows version to build\n    [String] $ImageType = 'nanoserver-ltsc2019',\n    # Generate a docker compose file even if it already exists\n    [switch] $OverwriteDockerComposeFile = $false,\n    # Print the build and publish command instead of executing them if set\n    [switch] $DryRun = $false,\n    # Pester version to install and use for tests\n    [String] $PesterVersion = '5.7.1',\n    # Output debug info for tests: 'empty' (no additional test output), 'debug' (test cmd & stderr outputed), 'verbose' (test cmd, stderr, stdout outputed)\n    [String] $TestsDebug = ''\n)\n\n$ErrorActionPreference = 'Stop'\n$ProgressPreference = 'SilentlyContinue' # Disable Progress bar for faster downloads\n\n$dockerComposeFile = 'build-windows.yaml'\n$baseDockerCmd = 'docker-compose --file={0}' -f $dockerComposeFile\n$baseDockerBuildCmd = '{0} build --pull' -f $baseDockerCmd\n\n$Repository = 'ssh-agent'\n$Organisation = 'jenkins'\n\nif(![String]::IsNullOrWhiteSpace($env:TESTS_DEBUG)) {\n    $TestsDebug = $env:TESTS_DEBUG\n}\n$env:TESTS_DEBUG = $TestsDebug\n\nif(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_REPO)) {\n    $Repository = $env:DOCKERHUB_REPO\n}\n\nif(![String]::IsNullOrWhiteSpace($env:DOCKERHUB_ORGANISATION)) {\n    $Organisation = $env:DOCKERHUB_ORGANISATION\n}\n\nif(![String]::IsNullOrWhiteSpace($env:VERSION)) {\n    $VersionTag = $env:VERSION\n}\n\nif(![String]::IsNullOrWhiteSpace($env:IMAGE_TYPE)) {\n    $ImageType = $env:IMAGE_TYPE\n}\n\n# Ensure constant env vars used in the docker compose file are defined\n$env:DOCKERHUB_ORGANISATION = \"$Organisation\"\n$env:DOCKERHUB_REPO = \"$Repository\"\n$env:VERSION = \"$VersionTag\"\n\n# Check for required commands\nFunction Test-CommandExists {\n    # From https://devblogs.microsoft.com/scripting/use-a-powershell-function-to-see-if-a-command-exists/\n    Param (\n        [String] $command\n    )\n\n    $oldPreference = $ErrorActionPreference\n    $ErrorActionPreference = 'stop'\n    try {\n        # Special case to test \"docker buildx\"\n        if ($command.Contains(\" \")) {\n            Invoke-Expression $command | Out-Null\n            Write-Debug \"$command exists\"\n        } else {\n            if(Get-Command $command){\n                Write-Debug \"$command exists\"\n            }\n        }\n    }\n    Catch {\n        \"$command does not exist\"\n    }\n    Finally {\n        $ErrorActionPreference=$oldPreference\n    }\n}\n\nfunction Test-Image {\n    param (\n        $ImageNameAndJavaVersion\n    )\n\n    # Ex: docker.io/jenkins/ssh-agent:windowsservercore-ltsc2019-jdk21|21.0.3_9\n    $items = $ImageNameAndJavaVersion.Split('|')\n    $imageName = $items[0] -replace 'docker.io/', ''\n    $javaVersion = $items[1]\n    $imageNameItems = $imageName.Split(':')\n    $imageTag = $imageNameItems[1]\n\n    Write-Host \"= TEST: Testing ${ImageName} image\"\n\n    $env:IMAGE_NAME = $ImageName\n    $env:JAVA_VERSION = \"$javaVersion\"\n\n    $targetPath = '.\\target\\{0}' -f $imageTag\n    if(Test-Path $targetPath) {\n        Remove-Item -Recurse -Force $targetPath\n    }\n    New-Item -Path $targetPath -Type Directory | Out-Null\n    $configuration.TestResult.OutputPath = '{0}\\junit-results.xml' -f $targetPath\n    $TestResults = Invoke-Pester -Configuration $configuration\n    $failed = $false\n    if ($TestResults.FailedCount -gt 0) {\n        Write-Host \"There were $($TestResults.FailedCount) failed tests out of $($TestResults.TotalCount) in ${ImageName}\"\n        $failed = $true\n    } else {\n        Write-Host \"There were $($TestResults.PassedCount) passed tests in ${ImageName}\"\n    }\n    Remove-Item env:\\IMAGE_NAME\n    Remove-Item env:\\JAVA_VERSION\n\n    return $failed\n}\n\nfunction Initialize-DockerComposeFile {\n    $baseDockerBakeCmd = 'docker buildx bake --progress=plain --file=docker-bake.hcl'\n\n    $items = $ImageType.Split('-')\n    $windowsFlavor = $items[0]\n    $windowsVersion = $items[1]\n\n    # Override the list of Windows versions taken defined in docker-bake.hcl by the version from image type\n    $env:WINDOWS_VERSION_OVERRIDE = $windowsVersion\n\n    # Retrieve the targets from docker buildx bake --print output\n    # Remove the 'output' section (unsupported by docker compose)\n    # For each target name as service key, return a map consisting of:\n    # - 'image' set to the first tag value\n    # - 'build' set to the content of the bake target\n    $yqMainQuery = '''.target[]' + `\n        ' | del(.output)' + `\n        ' | {(. | key): {\\\"image\\\": .tags[0], \\\"build\\\": .}}'''\n    # Encapsulate under a top level 'services' map\n    $yqServicesQuery = '''{\\\"services\\\": .}'''\n\n    # - Use docker buildx bake to output image definitions from the \"<windowsFlavor>\" bake target\n    # - Convert with yq to the format expected by docker compose\n    # - Store the result in the docker compose file\n    $generateDockerComposeFileCmd = ' {0} {1} --print' -f $baseDockerBakeCmd, $windowsFlavor + `\n        ' | yq --prettyPrint {0} | yq {1}' -f $yqMainQuery, $yqServicesQuery + `\n        ' | Out-File -FilePath {0}' -f $dockerComposeFile\n\n    Write-Host \"= PREPARE: Docker compose file generation command`n$generateDockerComposeFileCmd\"\n\n    Invoke-Expression $generateDockerComposeFileCmd\n\n    # Remove override\n    Remove-Item env:\\WINDOWS_VERSION_OVERRIDE\n}\n\nTest-CommandExists 'docker'\nTest-CommandExists 'docker-compose'\nTest-CommandExists 'docker buildx'\nTest-CommandExists 'yq'\n\n# Generate the docker compose file if it doesn't exists or if the parameter OverwriteDockerComposeFile is set\nif ((Test-Path $dockerComposeFile) -and -not $OverwriteDockerComposeFile) {\n    Write-Host \"= PREPARE: The docker compose file '$dockerComposeFile' containing the image definitions already exists.\"\n} else {\n    Write-Host \"= PREPARE: Initialize the docker compose file '$dockerComposeFile' containing the image definitions.\"\n    Initialize-DockerComposeFile\n}\n\nWrite-Host '= PREPARE: List of images and tags to be processed:'\nInvoke-Expression \"$baseDockerCmd config\"\n\nif ($target -eq 'build') {\n    Write-Host '= BUILD: Building all images...'\n    switch ($DryRun) {\n        $true { Write-Host \"(dry-run) $baseDockerBuildCmd\" }\n        $false { Invoke-Expression $baseDockerBuildCmd }\n    }\n    Write-Host '= BUILD: Finished building all images.'\n\n    if($lastExitCode -ne 0) {\n        exit $lastExitCode\n    }\n}\n\nif($target -eq 'test') {\n    if ($DryRun) {\n        Write-Host '= TEST: (dry-run) test harness'\n    } else {\n        Write-Host '= TEST: Starting test harness'\n\n        $mod = Get-InstalledModule -Name Pester -MinimumVersion $PesterVersion -MaximumVersion $PesterVersion -ErrorAction SilentlyContinue\n        if($null -eq $mod) {\n            Write-Host \"= TEST: Pester $PesterVersion not found: installing...\"\n            Install-Module -Force -Name Pester -MaximumVersion $PesterVersion -Scope CurrentUser\n        }\n\n        Import-Module Pester\n        Write-Host '= TEST: Setting up Pester environment...'\n        $configuration = [PesterConfiguration]::Default\n        $configuration.Run.PassThru = $true\n        $configuration.Run.Path = '.\\tests'\n        $configuration.Run.Exit = $true\n        $configuration.TestResult.Enabled = $true\n        $configuration.TestResult.OutputFormat = 'JUnitXml'\n        $configuration.Output.Verbosity = 'Diagnostic'\n        $configuration.CodeCoverage.Enabled = $false\n\n        Write-Host '= TEST: Testing all images...'\n        # Only fail the run afterwards in case of any test failures\n        $testFailed = $false\n        $imageDefinitions = Invoke-Expression \"$baseDockerCmd config\" | yq --unwrapScalar --output-format json '.services' | ConvertFrom-Json\n        foreach ($imageDefinition in $imageDefinitions.PSObject.Properties) {\n            $testFailed = $testFailed -or (Test-Image ('{0}|{1}' -f $imageDefinition.Value.image, $imageDefinition.Value.build.args.JAVA_VERSION))\n        }\n\n        # Fail if any test failures\n        if($testFailed -ne $false) {\n            Write-Error '= TEST: stage failed!'\n            exit 1\n        } else {\n            Write-Host '= TEST: stage passed!'\n        }\n    }\n}\n\nif($target -eq 'publish') {\n    Write-Host '= PUBLISH: push all images and tags'\n    switch($DryRun) {\n        $true { Write-Host \"(dry-run) $baseDockerCmd push\" }\n        $false { Invoke-Expression \"$baseDockerCmd push\" }\n    }\n\n    # Fail if any issues when publising the docker images\n    if($lastExitCode -ne 0) {\n        Write-Error '= PUBLISH: failed!'\n        exit 1\n    }\n}\n\nif($lastExitCode -ne 0) {\n    Write-Error 'Build failed!'\n} else {\n    Write-Host 'Build finished successfully'\n}\nexit $lastExitCode\n"
  },
  {
    "path": "debian/Dockerfile",
    "content": "# The MIT License\n#\n#  Copyright (c) 2015-2024, CloudBees, Inc. and other Jenkins contributors\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to deal\n#  in the Software without restriction, including without limitation the rights\n#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n#  copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n#  THE SOFTWARE.\nARG DEBIAN_RELEASE=trixie-20260505\nFROM debian:\"${DEBIAN_RELEASE}\"-slim AS jre-build\n\nSHELL [\"/bin/bash\", \"-e\", \"-u\", \"-o\", \"pipefail\", \"-c\"]\n\n# This Build ARG is populated by Docker\n# Ref. https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope\nARG TARGETPLATFORM\n\nCOPY jdk-download-url.sh /usr/bin/local/jdk-download-url.sh\nCOPY jdk-download.sh /usr/bin/local/jdk-download.sh\n\nARG JAVA_VERSION=17.0.19_10\n# hadolint ignore=DL3008\nRUN set -x; apt-get update \\\n  && apt-get install --no-install-recommends -y \\\n    ca-certificates \\\n    jq \\\n    curl \\\n  && /usr/bin/local/jdk-download.sh\n\nENV PATH=\"/opt/jdk-${JAVA_VERSION}/bin:${PATH}\"\n\n# Generate smaller java runtime without unneeded files\n# for now we include the full module path to maintain compatibility\n# while still saving space (approx 200mb from the full distribution)\nRUN case \"$(jlink --version 2>&1)\" in \\\n    \"17.\"*) set -- \"--compress=2\" --add-modules ALL-MODULE-PATH ;; \\\n    \"21.\"*) set -- \"--compress=zip-6\" --add-modules ALL-MODULE-PATH ;; \\\n    # JDK 25 switches to a minimal module set for smaller images\n    \"25\"*) set -- \"--compress=zip-6\" --add-modules \"java.base,java.logging,java.xml,java.management,java.net.http,jdk.crypto.ec\" ;; \\\n    *) echo \"ERROR: unmanaged jlink version pattern\" && exit 1 ;; \\\n  esac; \\\n  jlink \\\n    --strip-java-debug-attributes \\\n    \"$@\" \\\n    --no-man-pages \\\n    --no-header-files \\\n    --output /javaruntime\n\n# Downloading git-lfs from this intermediate stage as there is no wget/curl in the final one\nARG GIT_LFS_VERSION=3.7.1\nRUN arch=$(uname -m | sed -e 's/x86_64/amd64/g' -e 's/aarch64/arm64/g') \\\n  && curl -L -s -o git-lfs.tgz \"https://github.com/git-lfs/git-lfs/releases/download/v${GIT_LFS_VERSION}/git-lfs-linux-${arch}-v${GIT_LFS_VERSION}.tar.gz\"\n\nFROM debian:\"${DEBIAN_RELEASE}\"\n\nARG user=jenkins\nARG group=jenkins\nARG uid=1000\nARG gid=1000\nARG JENKINS_AGENT_HOME=/home/${user}\n\nENV JENKINS_AGENT_HOME=${JENKINS_AGENT_HOME}\nARG AGENT_WORKDIR=\"${JENKINS_AGENT_HOME}/agent\"\n# Persist agent workdir path through an environment variable for people extending the image\nENV AGENT_WORKDIR=${AGENT_WORKDIR}\n\nRUN groupadd -g ${gid} ${group} \\\n    && useradd -d \"${JENKINS_AGENT_HOME}\" -u \"${uid}\" -g \"${gid}\" -m -s /bin/bash \"${user}\" \\\n    # Prepare subdirectories\n    && mkdir -p \"${JENKINS_AGENT_HOME}/.ssh/\" \"${AGENT_WORKDIR}\" \"${JENKINS_AGENT_HOME}/.jenkins\" \\\n    # Make sure that user 'jenkins' own these directories and their content\n    && chown -R \"${uid}\":\"${gid}\" \"${JENKINS_AGENT_HOME}\" \"${AGENT_WORKDIR}\"\n\nRUN apt-get update \\\n    && apt-get install --no-install-recommends -y \\\n        ca-certificates \\\n        git \\\n        less \\\n        netcat-traditional \\\n        openssh-server \\\n        patch \\\n    # Cleanup APT cache\n    && rm -rf /var/lib/apt/lists/* \\\n    # Cleanup SSH host keys if any\n    && rm -f /etc/ssh/ssh_host*_key*\n\n# Retrieve git-lfs from the build stage and install it\nCOPY --from=jre-build git-lfs.tgz git-lfs.tgz\nRUN tar xzf git-lfs.tgz \\\n  && bash git-lfs-*/install.sh \\\n  && rm -rf git-lfs*\n\n# setup SSH server\nRUN sed -i /etc/ssh/sshd_config \\\n        -e 's/#PermitRootLogin.*/PermitRootLogin no/' \\\n        -e 's/#RSAAuthentication.*/RSAAuthentication yes/'  \\\n        -e 's/#PasswordAuthentication.*/PasswordAuthentication no/' \\\n        -e 's/#SyslogFacility.*/SyslogFacility AUTH/' \\\n        -e 's/#LogLevel.*/LogLevel INFO/' && \\\n    mkdir -p /var/run/sshd && \\\n    sed -i /etc/pam.d/sshd \\\n        -e 's/\\(session\\s*\\)required\\(\\s*pam_loginuid.so\\)/\\1optional\\2/' \\\n        -e '/pam_motd/s/^/#/'\n\n# VOLUME directive must happen after setting up permissions and content\nVOLUME \"${AGENT_WORKDIR}\" \"${JENKINS_AGENT_HOME}\"/.jenkins \"/tmp\" \"/run\" \"/var/run\"\nWORKDIR \"${JENKINS_AGENT_HOME}\"\n\nENV LANG='C.UTF-8' LC_ALL='C.UTF-8'\n\nENV JAVA_HOME=/opt/java/openjdk\nENV PATH=\"${JAVA_HOME}/bin:${PATH}\"\nCOPY --from=jre-build /javaruntime $JAVA_HOME\n\nRUN echo \"PATH=${PATH}\" >> /etc/environment\nCOPY setup-sshd /usr/local/bin/setup-sshd\n\nEXPOSE 22\n\nENTRYPOINT [\"setup-sshd\"]\n\nLABEL \\\n    org.opencontainers.image.vendor=\"Jenkins project\" \\\n    org.opencontainers.image.title=\"Official Jenkins SSH Agent Docker image\" \\\n    org.opencontainers.image.description=\"A Jenkins agent image which allows using SSH to establish the connection\" \\\n    org.opencontainers.image.url=\"https://www.jenkins.io/\" \\\n    org.opencontainers.image.source=\"https://github.com/jenkinsci/docker-ssh-agent\" \\\n    org.opencontainers.image.licenses=\"MIT\"\n"
  },
  {
    "path": "docker-bake.hcl",
    "content": "## Variables\nvariable \"jdks_to_build\" {\n  default = [17, 21, 25]\n}\n\nvariable \"default_jdk\" {\n  default = 21\n}\n\nvariable \"REGISTRY\" {\n  default = \"docker.io\"\n}\n\nvariable \"JENKINS_REPO\" {\n  default = \"jenkins/ssh-agent\"\n}\n\nvariable \"ON_TAG\" {\n  default = \"false\"\n}\n\nvariable \"VERSION\" {\n  default = \"\"\n}\n\nvariable \"ALPINE_FULL_TAG\" {\n  default = \"3.23.4\"\n}\n\nvariable \"ALPINE_SHORT_TAG\" {\n  default = regex_replace(ALPINE_FULL_TAG, \"\\\\.\\\\d+$\", \"\")\n}\n\nvariable \"JAVA17_VERSION\" {\n  default = \"17.0.19_10\"\n}\n\nvariable \"JAVA21_VERSION\" {\n  default = \"21.0.11_10\"\n}\n\nvariable \"JAVA25_VERSION\" {\n  default = \"25.0.3_9\"\n}\n\nvariable \"DEBIAN_RELEASE\" {\n  default = \"trixie-20260505\"\n}\n\n# Set this value to a specific Windows version to override Windows versions to build returned by windowsversions function\nvariable \"WINDOWS_VERSION_OVERRIDE\" {\n  default = \"\"\n}\n\n## Targets\ntarget \"alpine\" {\n  matrix = {\n    jdk = jdks_to_build\n  }\n  name       = \"alpine_${jdk}\"\n  dockerfile = \"alpine/Dockerfile\"\n  context    = \".\"\n  args = {\n    ALPINE_TAG   = ALPINE_FULL_TAG\n    JAVA_VERSION = \"${javaversion(jdk)}\"\n  }\n  tags = [\n    # If there is a tag, add versioned tags suffixed by the jdk\n    equal(ON_TAG, \"true\") ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}-alpine-jdk${jdk}\" : \"\",\n    equal(ON_TAG, \"true\") ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}-alpine${ALPINE_SHORT_TAG}-jdk${jdk}\" : \"\",\n    # If the jdk is the default one, add Alpine short tags\n    is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:alpine\" : \"\",\n    is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:alpine${ALPINE_SHORT_TAG}\" : \"\",\n    is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:latest-alpine${ALPINE_SHORT_TAG}\" : \"\",\n    \"${REGISTRY}/${JENKINS_REPO}:alpine-jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:latest-alpine-jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:alpine${ALPINE_SHORT_TAG}-jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:latest-alpine${ALPINE_SHORT_TAG}-jdk${jdk}\",\n  ]\n  platforms = alpine_platforms(jdk)\n}\n\ntarget \"debian\" {\n  matrix = {\n    jdk = jdks_to_build\n  }\n  name       = \"debian_${jdk}\"\n  dockerfile = \"debian/Dockerfile\"\n  context    = \".\"\n  args = {\n    DEBIAN_RELEASE = DEBIAN_RELEASE\n    JAVA_VERSION   = \"${javaversion(jdk)}\"\n  }\n  tags = [\n    # If there is a tag, add versioned tag suffixed by the jdk\n    equal(ON_TAG, \"true\") ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}-jdk${jdk}\" : \"\",\n    # If there is a tag and if the jdk is the default one, add versioned short tag\n    equal(ON_TAG, \"true\") ? (is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}\" : \"\") : \"\",\n    # If the jdk is the default one, add latest short tag\n    is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:latest\" : \"\",\n    \"${REGISTRY}/${JENKINS_REPO}:trixie-jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:debian-jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:latest-trixie-jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:latest-debian-jdk${jdk}\",\n    \"${REGISTRY}/${JENKINS_REPO}:latest-jdk${jdk}\",\n  ]\n  platforms = debian_platforms(jdk)\n}\n\ntarget \"nanoserver\" {\n  matrix = {\n    jdk             = jdks_to_build\n    windows_version = windowsversions(\"nanoserver\")\n  }\n  name       = \"nanoserver-${windows_version}_jdk${jdk}\"\n  dockerfile = \"windows/nanoserver/Dockerfile\"\n  context    = \".\"\n  args = {\n    JAVA_HOME             = \"C:/openjdk-${jdk}\"\n    JAVA_VERSION          = \"${replace(javaversion(jdk), \"_\", \"+\")}\"\n    TOOLS_WINDOWS_VERSION = \"${toolsversion(windows_version)}\"\n    WINDOWS_VERSION_TAG   = windows_version\n  }\n  tags = [\n    # If there is a tag, add versioned tag suffixed by the jdk\n    equal(ON_TAG, \"true\") ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}-nanoserver-${windows_version}-jdk${jdk}\" : \"\",\n    # If there is a tag and if the jdk is the default one, add versioned and short tags\n    equal(ON_TAG, \"true\") ? (is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}-nanoserver-${windows_version}\" : \"\") : \"\",\n    equal(ON_TAG, \"true\") ? (is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:nanoserver-${windows_version}\" : \"\") : \"\",\n    \"${REGISTRY}/${JENKINS_REPO}:nanoserver-${windows_version}-jdk${jdk}\",\n  ]\n  platforms = [\"windows/amd64\"]\n}\n\ntarget \"windowsservercore\" {\n  matrix = {\n    jdk             = jdks_to_build\n    windows_version = windowsversions(\"windowsservercore\")\n  }\n  name       = \"windowsservercore-${windows_version}_jdk${jdk}\"\n  dockerfile = \"windows/windowsservercore/Dockerfile\"\n  context    = \".\"\n  args = {\n    JAVA_HOME             = \"C:/openjdk-${jdk}\"\n    JAVA_VERSION          = \"${replace(javaversion(jdk), \"_\", \"+\")}\"\n    TOOLS_WINDOWS_VERSION = \"${toolsversion(windows_version)}\"\n    WINDOWS_VERSION_TAG   = windows_version\n  }\n  tags = [\n    # If there is a tag, add versioned tag suffixed by the jdk\n    equal(ON_TAG, \"true\") ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}-windowsservercore-${windows_version}-jdk${jdk}\" : \"\",\n    # If there is a tag and if the jdk is the default one, add versioned and short tags\n    equal(ON_TAG, \"true\") ? (is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:${VERSION}-windowsservercore-${windows_version}\" : \"\") : \"\",\n    equal(ON_TAG, \"true\") ? (is_default_jdk(jdk) ? \"${REGISTRY}/${JENKINS_REPO}:windowsservercore-${windows_version}\" : \"\") : \"\",\n    \"${REGISTRY}/${JENKINS_REPO}:windowsservercore-${windows_version}-jdk${jdk}\",\n  ]\n  platforms = [\"windows/amd64\"]\n}\n\n## Groups\ngroup \"linux\" {\n  targets = [\n    \"alpine\",\n    \"debian\",\n  ]\n}\n\ngroup \"windows\" {\n  targets = [\n    \"nanoserver\",\n    \"windowsservercore\"\n  ]\n}\n\ngroup \"linux-arm64\" {\n  targets = [\n    \"debian\",\n    \"alpine_jdk21\",\n  ]\n}\n\ngroup \"linux-s390x\" {\n  targets = [\n    \"debian_jdk21\"\n  ]\n}\n\ngroup \"linux-ppc64le\" {\n  targets = [\n    \"debian\"\n  ]\n}\n\n## Common functions\n# Return \"true\" if the jdk passed as parameter is the same as the default jdk, \"false\" otherwise\nfunction \"is_default_jdk\" {\n  params = [jdk]\n  result = equal(default_jdk, jdk) ? \"true\" : \"false\"\n}\n\n# Return the complete Java version corresponding to the jdk passed as parameter\nfunction \"javaversion\" {\n  params = [jdk]\n  result = (equal(17, jdk)\n    ? \"${JAVA17_VERSION}\"\n    : equal(21, jdk)\n    ? \"${JAVA21_VERSION}\"\n  : \"${JAVA25_VERSION}\")\n}\n\n## Specific functions\n# Return an array of Alpine platforms to use depending on the jdk passed as parameter\nfunction \"alpine_platforms\" {\n  params = [jdk]\n  result = (equal(17, jdk)\n    ? [\"linux/amd64\"]\n  : [\"linux/amd64\", \"linux/arm64\"])\n}\n\n# Return an array of Debian platforms to use depending on the jdk passed as parameter\nfunction \"debian_platforms\" {\n  params = [jdk]\n  result = (equal(17, jdk)\n    ? [\"linux/amd64\", \"linux/arm64\", \"linux/ppc64le\"]\n  : [\"linux/amd64\", \"linux/arm64\", \"linux/ppc64le\", \"linux/s390x\", \"linux/riscv64\"])\n}\n\n# Return array of Windows version(s) to build\n# Can be overriden by setting WINDOWS_VERSION_OVERRIDE to a specific Windows version\n# Ex: WINDOWS_VERSION_OVERRIDE=ltsc2025 docker buildx bake windows\nfunction \"windowsversions\" {\n  params = [flavor]\n  result = (notequal(WINDOWS_VERSION_OVERRIDE, \"\")\n    ? [WINDOWS_VERSION_OVERRIDE]\n  : [\"ltsc2019\", \"ltsc2022\"])\n}\n\n# Return the Windows version to use as base image for the Windows version passed as parameter\n# There is no mcr.microsoft.com/powershell ltsc2019 base image, using a \"1809\" instead\nfunction \"toolsversion\" {\n  params = [version]\n  result = (equal(\"ltsc2019\", version)\n    ? \"1809\"\n  : version)\n}\n"
  },
  {
    "path": "jdk-download-url.sh",
    "content": "#!/bin/sh\n\n# Check if at least one argument was passed to the script\n# If one argument was passed and JAVA_VERSION is set, assign the argument to OS\n# If two arguments were passed, assign them to JAVA_VERSION and OS respectively\n# If three arguments were passed, assign them to JAVA_VERSION, OS and ARCHS respectively\n# If not, check if JAVA_VERSION and OS are already set. If they're not set, exit the script with an error message\nif [ $# -eq 1 ] && [ -n \"$JAVA_VERSION\" ]; then\n    OS=$1\nelif [ $# -eq 2 ]; then\n    JAVA_VERSION=$1\n    OS=$2\nelif [ $# -eq 3 ]; then\n    JAVA_VERSION=$1\n    OS=$2\n    ARCHS=$3\nelif [ -z \"$JAVA_VERSION\" ] && [ -z \"$OS\" ]; then\n    echo \"Error: No Java version and OS specified. Please set the JAVA_VERSION and OS environment variables or pass them as arguments.\" >&2\n    exit 1\nelif [ -z \"$JAVA_VERSION\" ]; then\n    echo \"Error: No Java version specified. Please set the JAVA_VERSION environment variable or pass it as an argument.\" >&2\n    exit 1\nelif [ -z \"$OS\" ]; then\n    OS=$1\n    if [ -z \"$OS\" ]; then\n        echo \"Error: No OS specified. Please set the OS environment variable or pass it as an argument.\" >&2\n        exit 1\n    fi\nfi\n\n# Check if ARCHS is set. If it's not set, assign the current architecture to it\nif [ -z \"$ARCHS\" ]; then\n    ARCHS=$(uname -m | sed -e 's/x86_64/x64/' -e 's/armv7l/arm/')\nelse\n    # Convert ARCHS to an array\n    OLD_IFS=$IFS\n    IFS=','\n    set -- \"$ARCHS\"\n    ARCHS=\"\"\n    for arch in \"$@\"; do\n        ARCHS=\"$ARCHS $arch\"\n    done\n    IFS=$OLD_IFS\nfi\n\n# Check if jq and curl are installed\n# If they are not installed, exit the script with an error message\nif ! command -v jq >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then\n    echo \"jq and curl are required but not installed. Exiting with status 1.\" >&2\n    exit 1\nfi\n\n# Replace underscores with plus signs in JAVA_VERSION\nARCHIVE_DIRECTORY=$(echo \"$JAVA_VERSION\" | tr '_' '+')\n\n# URL encode ARCHIVE_DIRECTORY\nENCODED_ARCHIVE_DIRECTORY=$(echo \"$ARCHIVE_DIRECTORY\" | xargs -I {} printf %s {} | jq \"@uri\" -jRr)\n\n# Determine the OS type for the URL\nOS_TYPE=\"linux\"\nif [ \"$OS\" = \"alpine\" ]; then\n    OS_TYPE=\"alpine-linux\"\nfi\nif [ \"$OS\" = \"windows\" ]; then\n    OS_TYPE=\"windows\"\nfi\n\n# Initialize a variable to store the URL for the first architecture\nFIRST_ARCH_URL=\"\"\n\n# Loop over the array of architectures\nfor ARCH in $ARCHS; do\n    # Fetch the download URL from the Adoptium API\n    URL=\"https://api.adoptium.net/v3/binary/version/jdk-${ENCODED_ARCHIVE_DIRECTORY}/${OS_TYPE}/${ARCH}/jdk/hotspot/normal/eclipse?project=jdk\"\n\n    if ! RESPONSE=$(curl -fsI \"$URL\"); then\n        echo \"Error: Failed to fetch the URL for architecture ${ARCH} from ${URL}. Exiting with status 1.\" >&2\n        echo \"Response: $RESPONSE\" >&2\n        exit 1\n    fi\n\n    # Extract the redirect URL from the HTTP response\n    REDIRECTED_URL=$(echo \"$RESPONSE\" | grep -i location | awk '{print $2}' | tr -d '\\r')\n\n    # If no redirect URL was found, exit the script with an error message\n    if [ -z \"$REDIRECTED_URL\" ]; then\n        echo \"Error: No redirect URL found for architecture ${ARCH} from ${URL}. Exiting with status 1.\" >&2\n        echo \"Response: $RESPONSE\" >&2\n        exit 1\n    fi\n\n    # Use curl to check if the URL is reachable\n    # If the URL is not reachable, print an error message and exit the script with status 1\n    if ! curl -v -fs \"$REDIRECTED_URL\" >/dev/null 2>&1; then\n        echo \"${REDIRECTED_URL}\" is not reachable for architecture \"${ARCH}\". >&2\n        exit 1\n    fi\n\n    # If FIRST_ARCH_URL is empty, store the current URL\n    if [ -z \"$FIRST_ARCH_URL\" ]; then\n        FIRST_ARCH_URL=$REDIRECTED_URL\n    fi\ndone\n\n# If all downloads are successful, print the URL for the first architecture\necho \"$FIRST_ARCH_URL\"\n"
  },
  {
    "path": "jdk-download.sh",
    "content": "#!/bin/sh\nset -x\n# Check if curl and tar are installed\nif ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1 ; then\n    echo \"curl and tar are required but not installed. Exiting with status 1.\" >&2\n    exit 1\nfi\n\n# Set the OS to \"standard\" by default\nOS=\"standard\"\n\n# If a second argument is provided, use it as the OS\nif [ $# -eq 1 ]; then\n    OS=$1\nfi\n\n# Call jdk-download-url.sh with JAVA_VERSION and OS as arguments\n# The two scripts should be in the same directory.\n# That's why we're trying to find the directory of the current script and use it to call the other script.\nSCRIPT_DIR=$(cd \"$(dirname \"$0\")\" || exit; pwd)\nif ! DOWNLOAD_URL=$(\"${SCRIPT_DIR}\"/jdk-download-url.sh \"${JAVA_VERSION}\" \"${OS}\"); then\n    echo \"Error: Failed to fetch the URL. Exiting with status 1.\" >&2\n    exit 1\nfi\n\n# Use curl to download the JDK archive from the URL\nif ! curl --silent --location --output /tmp/jdk.tar.gz \"${DOWNLOAD_URL}\"; then\n    echo \"Error: Failed to download the JDK archive. Exiting with status 1.\" >&2\n    exit 1\nfi\n\n# Extract the archive to the /opt/ directory\nif ! tar -xzf /tmp/jdk.tar.gz -C /opt/; then\n    echo \"Error: Failed to extract the JDK archive. Exiting with status 1.\" >&2\n    exit 1\nfi\n\n# Get the name of the extracted directory\nEXTRACTED_DIR=$(tar -tzf /tmp/jdk.tar.gz | head -n 1 | cut -f1 -d\"/\")\n\n# Rename the extracted directory to /opt/jdk-${JAVA_VERSION}\nif ! mv \"/opt/${EXTRACTED_DIR}\" \"/opt/jdk-${JAVA_VERSION}\"; then\n    echo \"Error: Failed to rename the extracted directory. Exiting with status 1.\" >&2\n    exit 1\nfi\n\n# Remove the downloaded archive\nif ! rm -f /tmp/jdk.tar.gz; then\n    echo \"Error: Failed to remove the downloaded archive. Exiting with status 1.\" >&2\n    exit 1\nfi\n"
  },
  {
    "path": "setup-sshd",
    "content": "#!/usr/bin/env bash\n\nset -ex\n\n# The MIT License\n#\n#  Copyright (c) 2015, CloudBees, Inc.\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to deal\n#  in the Software without restriction, including without limitation the rights\n#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n#  copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n#  THE SOFTWARE.\n\n# Usage:\n#  docker run jenkins/ssh-agent <public key>\n# or\n#  docker run -e \"JENKINS_AGENT_SSH_PUBKEY=<public key>\" jenkins/ssh-agent\n\nwrite_key() {\n  local ID_GROUP\n\n  # As user, group, uid, gid and JENKINS_AGENT_HOME can be overridden at build,\n  # we need to find the values for JENKINS_AGENT_HOME\n  # ID_GROUP contains the user:group of JENKINS_AGENT_HOME directory\n  ID_GROUP=$(stat -c '%U:%G' \"${JENKINS_AGENT_HOME}\")\n\n  mkdir -p \"${JENKINS_AGENT_HOME}/.ssh\"\n  echo \"$1\" > \"${JENKINS_AGENT_HOME}/.ssh/authorized_keys\"\n  chown -Rf \"${ID_GROUP}\" \"${JENKINS_AGENT_HOME}/.ssh\"\n  chmod 0700 -R \"${JENKINS_AGENT_HOME}/.ssh\"\n}\n\nif [[ ${JENKINS_AGENT_SSH_PUBKEY} == ssh-* ]]; then\n  write_key \"${JENKINS_AGENT_SSH_PUBKEY}\"\nfi\nif [[ ${JENKINS_SLAVE_SSH_PUBKEY} == ssh-* ]]; then\n  write_key \"${JENKINS_SLAVE_SSH_PUBKEY}\"\nfi\n\n# ensure variables passed to docker container are also exposed to ssh sessions\nenv | grep _ >> /etc/environment\n\nif [[ $# -gt 0 ]]; then\n  echo \"${0##*/} params: $@\"\n\n  if [[ $1 == ssh-* ]]; then\n    echo \"Authorizing ssh pubkey found in params.\"\n    write_key \"$1\"\n    shift 1\n  elif [[ \"$@\" == \"/usr/sbin/sshd -D -p 22\" ]]; then\n    # neutralize default jenkins docker-plugin command\n    # we will run sshd at the end anyway\n    echo \"Ignoring provided sshd command.\"\n\n    # if unquoted (4 tokens) shift extra 3\n    [[ \"$2\" == \"-D\" ]] && shift 3\n\n    shift 1\n  else\n    echo \"Executing params: '$@'\"\n    exec \"$@\"\n  fi\nfi\n\n# generate host keys if not present\nssh-keygen -A\n\n# do not detach (-D), log to stderr (-e), passthrough other arguments\nexec /usr/sbin/sshd -D -e \"${@}\"\n"
  },
  {
    "path": "setup-sshd.ps1",
    "content": "# The MIT License\n#\n#  Copyright (c) 2019-2020, Alex Earl\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to deal\n#  in the Software without restriction, including without limitation the rights\n#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n#  copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n#  THE SOFTWARE.\n\n# Usage:\n#  docker run jenkins/ssh-agent <public key>\n# or\n#  docker run -e \"JENKINS_AGENT_SSH_PUBKEY=<public key>\" jenkins/ssh-agent\n# or\n#  docker run -e \"JENKINS_AGENT_SSH_PUBKEY=<public key>\" -e \"JENKINS_AGENT_SSH_KNOWNHOST_0=<known host entry>\" -e \"JENKINS_AGENT_SSH_KNOWNHOST_n=<known host entry>\" jenkins/ssh-agent\n\n[CmdletBinding()]\nParam(\n    [Parameter(Position = 0, ValueFromRemainingArguments = $true)]\n    [string] $Cmd\n)\n\nfunction Get-SSHDir {\n    return Join-Path \"C:/Users/$env:JENKINS_AGENT_USER\" '.ssh'\n}\n\nfunction Check-SSHDir {\n    $sshDir = Get-SSHDir\n    if(-not (Test-Path $sshDir)) {\n        New-Item -Type Directory -Path $sshDir | Out-Null\n        icacls.exe $sshDir /setowner $env:JENKINS_AGENT_USER | Out-Null\n        icacls.exe $sshDir /grant $('{0}:(CI)(OI)(F)' -f $env:JENKINS_AGENT_USER) /grant \"administrators:(CI)(OI)(F)\" | Out-Null\n        icacls.exe $sshDir /inheritance:r | Out-Null\n    }\n}\n\nfunction Write-Key($Key) {\n    # this writes the key and sets the permissions correctly for pubkey auth\n    $authorizedKeys = Join-Path (Get-SSHDir) 'authorized_keys'\n    Set-Content -Path $authorizedKeys -Value \"$Key\" -Encoding UTF8\n\n    icacls.exe $authorizedKeys /setowner $env:JENKINS_AGENT_USER | Out-Null\n}\n\nfunction Write-HostKey($Key) {\n    # this writes the key and sets the permissions\n    $knownHosts = Join-Path (Get-SSHDir) 'known_hosts'\n    Set-Content -Path $knownHosts -Value \"$Key\" -Encoding UTF8\n\n    icacls.exe $knownHosts /setowner $env:JENKINS_AGENT_USER | Out-Null\n}\n\n# Give the user Full Access to the home directory\nicacls.exe \"C:/Users/$env:JENKINS_AGENT_USER\" /grant \"${env:JENKINS_AGENT_USER}:(CI)(OI)(F)\" | Out-Null\n\n# check the .ssh dir permissions\nCheck-SSHDir\n\nif($env:JENKINS_AGENT_SSH_PUBKEY -match \"^ssh-.*\") {\n    Write-Key $env:JENKINS_AGENT_SSH_PUBKEY\n}\n\n$index = 0\n$knownHostKeyVar = Get-ChildItem -Path \"env:JENKINS_AGENT_SSH_KNOWNHOST_$index\" -ErrorAction 'SilentlyContinue'\nwhile($null -ne $knownHostKeyVar) {\n    Write-HostKey $knownHostKeyVar.Value\n    $index++\n    $knownHostKeyVar = Get-ChildItem env: -Name \"JENKINS_AGENT_SSH_KNOWNHOST_$index\"\n}\n\n# ensure variables passed to docker container are also exposed to ssh sessions\nGet-ChildItem env: | ForEach-Object { setx /m $_.Name $_.Value | Out-Null }\n\nif(![System.String]::IsNullOrWhiteSpace($Cmd)) {\n    Write-Host \"$($MyInvocation.MyCommand.Name) param: '$Cmd'\"\n    if($Cmd -match \"^ssh-.*\") {\n        Write-Host \"Authorizing ssh pubkey found in params.\"\n        Write-Key $Cmd\n    } elseif($Cmd -match \"^/usr/sbin/sshd\") {\n        # neutralize default jenkins docker-plugin command\n        # we will run sshd at the end anyway\n        Write-Host \"Ignoring provided (linux) sshd command.\"\n    } else {\n        Write-Host \"Executing param: $Cmd\"\n        & $Cmd\n        exit\n    }\n}\n\nStart-Service sshd\n\n# dump network information\nipconfig\nnetstat -a\n\n# aside from forwarding ssh logs, this keeps the container open\nGet-Content -Path \"C:\\ProgramData\\ssh\\logs\\sshd.log\" -Wait\n"
  },
  {
    "path": "tests/golden/expected_tags.txt",
    "content": "docker.io/jenkins/ssh-agent:alpine\ndocker.io/jenkins/ssh-agent:alpine-jdk17\ndocker.io/jenkins/ssh-agent:alpine-jdk21\ndocker.io/jenkins/ssh-agent:alpine-jdk25\ndocker.io/jenkins/ssh-agent:alpine3.23\ndocker.io/jenkins/ssh-agent:alpine3.23-jdk17\ndocker.io/jenkins/ssh-agent:alpine3.23-jdk21\ndocker.io/jenkins/ssh-agent:alpine3.23-jdk25\ndocker.io/jenkins/ssh-agent:debian-jdk17\ndocker.io/jenkins/ssh-agent:debian-jdk21\ndocker.io/jenkins/ssh-agent:debian-jdk25\ndocker.io/jenkins/ssh-agent:jdk17\ndocker.io/jenkins/ssh-agent:jdk21\ndocker.io/jenkins/ssh-agent:jdk25\ndocker.io/jenkins/ssh-agent:latest\ndocker.io/jenkins/ssh-agent:latest-alpine-jdk17\ndocker.io/jenkins/ssh-agent:latest-alpine-jdk21\ndocker.io/jenkins/ssh-agent:latest-alpine-jdk25\ndocker.io/jenkins/ssh-agent:latest-alpine3.23\ndocker.io/jenkins/ssh-agent:latest-alpine3.23-jdk17\ndocker.io/jenkins/ssh-agent:latest-alpine3.23-jdk21\ndocker.io/jenkins/ssh-agent:latest-alpine3.23-jdk25\ndocker.io/jenkins/ssh-agent:latest-debian-jdk17\ndocker.io/jenkins/ssh-agent:latest-debian-jdk21\ndocker.io/jenkins/ssh-agent:latest-debian-jdk25\ndocker.io/jenkins/ssh-agent:latest-jdk17\ndocker.io/jenkins/ssh-agent:latest-jdk21\ndocker.io/jenkins/ssh-agent:latest-jdk25\ndocker.io/jenkins/ssh-agent:latest-trixie-jdk17\ndocker.io/jenkins/ssh-agent:latest-trixie-jdk21\ndocker.io/jenkins/ssh-agent:latest-trixie-jdk25\ndocker.io/jenkins/ssh-agent:trixie-jdk17\ndocker.io/jenkins/ssh-agent:trixie-jdk21\ndocker.io/jenkins/ssh-agent:trixie-jdk25\n"
  },
  {
    "path": "tests/keys.bash",
    "content": "PUBLIC_SSH_KEY=\"ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAvnRN27LdPPQq2OH3GiFFGWX/SH5TCPVePLR21ngMFV8nAthXgYrFkRi/t+Wafe3ByTu2XYUDlXHKGIPIoAKo4gz5dIjUFfoac1ZuCDIbEiqPEjkk4tkfc2qr/BnIZsOYQi4Mbu+Z40VZEsAQU7eBinnZaHE1qGMHjS1xfrRtp2rdeO1EBz92FJ8dfnkUnohTXo3qPVSFGIPbh7UKEoKcyCosRO1P41iWD1rVsH1SLLXYAh2t49L7IPiplg09Dep6H47LyQVbxU9eXY8yMtUrRuwEk9IUX/IqpxNhk5hngHPP3JjsP0hyyrYSPkZlbs3izd9kk3y09Wn/ElHidiEk0Q==\"\nPRIVATE_SSH_KEY=$(cat <<EOF\n-----BEGIN RSA PRIVATE KEY-----\nMIIEoQIBAAKCAQEAvnRN27LdPPQq2OH3GiFFGWX/SH5TCPVePLR21ngMFV8nAthX\ngYrFkRi/t+Wafe3ByTu2XYUDlXHKGIPIoAKo4gz5dIjUFfoac1ZuCDIbEiqPEjkk\n4tkfc2qr/BnIZsOYQi4Mbu+Z40VZEsAQU7eBinnZaHE1qGMHjS1xfrRtp2rdeO1E\nBz92FJ8dfnkUnohTXo3qPVSFGIPbh7UKEoKcyCosRO1P41iWD1rVsH1SLLXYAh2t\n49L7IPiplg09Dep6H47LyQVbxU9eXY8yMtUrRuwEk9IUX/IqpxNhk5hngHPP3Jjs\nP0hyyrYSPkZlbs3izd9kk3y09Wn/ElHidiEk0QIBJQKCAQEAlUZmiZoHWUnAt9Oz\n1jXAiYdLi9ih8kPGZu5PTia9XNvgTlaJxmXZHrKIbYpyK1l8NfCIBBwlZ0tZNc8S\n3kdGGPVpkrBu4MryIwxkFELyn4kkB104lh/MiuTnqeqx1AEWeQ9V2mjEuQzXHIiy\n2dUEqs40x3tTkdETwa3/AnG9upCsS8DpUmBa50hHvkc8pfmDrCbDAB7QjrgxAv7N\nTjZQz1BslDnqULBs0weqD/YG60Vxdbu8ULHcMKYHmlk06a2lxF2A+CbvC+eLyD5B\n+YHsD2CnpNhmBxLXfjnKuMhT6ybtop1hZW4zy0jLsyvAgM/kSb/iH9XJ17nfdlMm\nNChQcQKBgQDvKs+81jDhoP+fZXi7bnVwlo2UzuTXNkUO1fLCFHWpJXMXu4wY6iMY\nklEjXmN68Ijj0n3Enw7yM4/HBcnvRlw78zbDbKxwz5WRVc8w4/Ct4z8TX9Il1srR\nQa9vPhju8KazY1XxNMidMJmcR6cjG7glzKorE9faHc9aIskPP93y1wKBgQDL288f\ntk0F/RcikCnfq8Ligm3GkZfP7lyf0T9lXHg0Qe9d3esvVHe02blMGm0vgsKy4Aip\njlyyM8ExI5yF2zUbOqLxDhWWqL6EnlYXEI4s5h/4AJOPrERGdOU/Ix7G312mqcmi\nFlRVug8II64O7IgVU6pWyckOSMf6llyH/ItYlwKBgDotAhktLnwSZ7EmhSasKmd+\nkSQyU1bxhmtkeVHNoBRjDiheDVIrHUsqgnBjEUdq8N14Y8gLA6KymJgx1yxdOQep\n3ONtdg2aRvnWmi58olPPfguhr6hW12NVKqxbNn9PSyS3TEGXN7eIXLdPswiKM7Yq\n3Ui/ozUOK4SgrXJpey07AoGAG4xoGQrMI2dj/cB0XH7+qPzeZvEUg+Hw11OgyIIe\nFOZQx37al7F39dg7ooAcl7e5ch5GXBooM8HN/7i0SXCmT8mnUQHnPd9zsQ56ViTU\n8U+Hx5FgDH8QJTJkKyBr8Vx0cHfPI73UC5WvARmUD9rGSBI5nQaC9BesUkuro6yB\niIMCgYAnlf3vd9/s8izGoHH1K2MJgGQT06Wn4ESjKpqqayqiXHccHGgeXeAiONa1\nuiWcmBF4XtMTVXUGcS6DCm/jf/4JDI8B1eJCVQKLbZXZbENWnptDtj098NTt9NdV\nTUwLP4n7pK4J2sCIs6fRD5kEYms4BnddXeRuI2fGZHGH70Ci/Q==\n-----END RSA PRIVATE KEY-----\nEOF\n)\n"
  },
  {
    "path": "tests/sshAgent.Tests.ps1",
    "content": "Import-Module -DisableNameChecking -Force $PSScriptRoot/test_helpers.psm1\n\n$global:IMAGE_NAME = Get-EnvOrDefault 'IMAGE_NAME' '' # Ex: jenkins4eval/ssh-agent:nanoserver-ltsc2019-jdk17\n$global:JAVA_VERSION = Get-EnvOrDefault 'JAVA_VERSION' ''\n\nWrite-Host \"= TESTS: Preparing $global:IMAGE_NAME with Java $global:JAVA_VERSION\"\n\n$imageItems = $global:IMAGE_NAME.Split(':')\n$global:IMAGE_TAG = $imageItems[1]\n\n$items = $global:IMAGE_TAG.Split('-')\n# Remove the 'jdk' prefix\n$global:JAVAMAJORVERSION = $items[2].Remove(0,3)\n$global:WINDOWSFLAVOR = $items[0]\n$global:WINDOWSVERSIONTAG = $items[1]\n$global:TOOLSWINDOWSVERSION = $items[1]\n# There are no eclipse-temurin:*-ltsc2019 or mcr.microsoft.com/powershell:*-ltsc2019 docker images unfortunately, only \"1809\" ones\nif ($items[1] -eq 'ltsc2019') {\n    $global:TOOLSWINDOWSVERSION = '1809'\n}\n\n# TODO: make this name unique for concurency\n$global:CONTAINERNAME = 'pester-jenkins-ssh-agent-{0}' -f $global:IMAGE_TAG\n\n$global:CONTAINERSHELL = 'powershell.exe'\nif($global:WINDOWSFLAVOR -eq 'nanoserver') {\n    $global:CONTAINERSHELL = 'pwsh.exe'\n}\n\n$global:PUBLIC_SSH_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAvnRN27LdPPQq2OH3GiFFGWX/SH5TCPVePLR21ngMFV8nAthXgYrFkRi/t+Wafe3ByTu2XYUDlXHKGIPIoAKo4gz5dIjUFfoac1ZuCDIbEiqPEjkk4tkfc2qr/BnIZsOYQi4Mbu+Z40VZEsAQU7eBinnZaHE1qGMHjS1xfrRtp2rdeO1EBz92FJ8dfnkUnohTXo3qPVSFGIPbh7UKEoKcyCosRO1P41iWD1rVsH1SLLXYAh2t49L7IPiplg09Dep6H47LyQVbxU9eXY8yMtUrRuwEk9IUX/IqpxNhk5hngHPP3JjsP0hyyrYSPkZlbs3izd9kk3y09Wn/ElHidiEk0Q=='\n$global:PRIVATE_SSH_KEY = @\"\n-----BEGIN RSA PRIVATE KEY-----\nMIIEoQIBAAKCAQEAvnRN27LdPPQq2OH3GiFFGWX/SH5TCPVePLR21ngMFV8nAthX\ngYrFkRi/t+Wafe3ByTu2XYUDlXHKGIPIoAKo4gz5dIjUFfoac1ZuCDIbEiqPEjkk\n4tkfc2qr/BnIZsOYQi4Mbu+Z40VZEsAQU7eBinnZaHE1qGMHjS1xfrRtp2rdeO1E\nBz92FJ8dfnkUnohTXo3qPVSFGIPbh7UKEoKcyCosRO1P41iWD1rVsH1SLLXYAh2t\n49L7IPiplg09Dep6H47LyQVbxU9eXY8yMtUrRuwEk9IUX/IqpxNhk5hngHPP3Jjs\nP0hyyrYSPkZlbs3izd9kk3y09Wn/ElHidiEk0QIBJQKCAQEAlUZmiZoHWUnAt9Oz\n1jXAiYdLi9ih8kPGZu5PTia9XNvgTlaJxmXZHrKIbYpyK1l8NfCIBBwlZ0tZNc8S\n3kdGGPVpkrBu4MryIwxkFELyn4kkB104lh/MiuTnqeqx1AEWeQ9V2mjEuQzXHIiy\n2dUEqs40x3tTkdETwa3/AnG9upCsS8DpUmBa50hHvkc8pfmDrCbDAB7QjrgxAv7N\nTjZQz1BslDnqULBs0weqD/YG60Vxdbu8ULHcMKYHmlk06a2lxF2A+CbvC+eLyD5B\n+YHsD2CnpNhmBxLXfjnKuMhT6ybtop1hZW4zy0jLsyvAgM/kSb/iH9XJ17nfdlMm\nNChQcQKBgQDvKs+81jDhoP+fZXi7bnVwlo2UzuTXNkUO1fLCFHWpJXMXu4wY6iMY\nklEjXmN68Ijj0n3Enw7yM4/HBcnvRlw78zbDbKxwz5WRVc8w4/Ct4z8TX9Il1srR\nQa9vPhju8KazY1XxNMidMJmcR6cjG7glzKorE9faHc9aIskPP93y1wKBgQDL288f\ntk0F/RcikCnfq8Ligm3GkZfP7lyf0T9lXHg0Qe9d3esvVHe02blMGm0vgsKy4Aip\njlyyM8ExI5yF2zUbOqLxDhWWqL6EnlYXEI4s5h/4AJOPrERGdOU/Ix7G312mqcmi\nFlRVug8II64O7IgVU6pWyckOSMf6llyH/ItYlwKBgDotAhktLnwSZ7EmhSasKmd+\nkSQyU1bxhmtkeVHNoBRjDiheDVIrHUsqgnBjEUdq8N14Y8gLA6KymJgx1yxdOQep\n3ONtdg2aRvnWmi58olPPfguhr6hW12NVKqxbNn9PSyS3TEGXN7eIXLdPswiKM7Yq\n3Ui/ozUOK4SgrXJpey07AoGAG4xoGQrMI2dj/cB0XH7+qPzeZvEUg+Hw11OgyIIe\nFOZQx37al7F39dg7ooAcl7e5ch5GXBooM8HN/7i0SXCmT8mnUQHnPd9zsQ56ViTU\n8U+Hx5FgDH8QJTJkKyBr8Vx0cHfPI73UC5WvARmUD9rGSBI5nQaC9BesUkuro6yB\niIMCgYAnlf3vd9/s8izGoHH1K2MJgGQT06Wn4ESjKpqqayqiXHccHGgeXeAiONa1\nuiWcmBF4XtMTVXUGcS6DCm/jf/4JDI8B1eJCVQKLbZXZbENWnptDtj098NTt9NdV\nTUwLP4n7pK4J2sCIs6fRD5kEYms4BnddXeRuI2fGZHGH70Ci/Q==\n-----END RSA PRIVATE KEY-----\n\"@\n\n$global:GITLFSVERSION = '3.7.1'\n\nCleanup($global:CONTAINERNAME)\n\nDescribe \"[$global:IMAGE_TAG] image has setup-sshd.ps1 in the correct location\" {\n    BeforeAll {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"run --detach --tty --name=`\"$global:CONTAINERNAME`\" --publish-all `\"$global:IMAGE_NAME`\" `\"$global:CONTAINERSHELL`\"\"\n        $exitCode | Should -Be 0\n        Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue\n    }\n\n    It 'has setup-sshd.ps1 in C:/ProgramData/Jenkins' {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `\"if(Test-Path C:/ProgramData/Jenkins/setup-sshd.ps1) { exit 0 } else { exit 1}`\"\"\n        $exitCode | Should -Be 0\n    }\n\n    AfterAll {\n        Cleanup($global:CONTAINERNAME)\n    }\n}\n\nDescribe \"[$global:IMAGE_TAG] image has no pre-existing SSH host keys\" {\n    BeforeAll {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"run --detach --tty --name=`\"$global:CONTAINERNAME`\" --publish-all `\"$global:IMAGE_NAME`\" `\"$global:CONTAINERSHELL`\"\"\n        $exitCode | Should -Be 0\n        Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue\n    }\n\n    It 'has has no SSH host key present in C:\\ProgramData\\ssh' {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `\"if(Test-Path C:/ProgramData/ssh/ssh_host*_key*) { exit 0 } else { exit 1 }`\"\"\n        $exitCode | Should -Be 1\n    }\n\n    AfterAll {\n        Cleanup($global:CONTAINERNAME)\n    }\n}\n\nDescribe \"[$global:IMAGE_TAG] checking image metadata\" {\n    It 'has correct volumes' {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"inspect --format '{{.Config.Volumes}}' $global:IMAGE_NAME\"\n        $exitCode | Should -Be 0\n\n        $stdout | Should -Match 'C:/Users/jenkins/AppData/Local/Temp'\n        $stdout | Should -Match 'C:/Users/jenkins/Work'\n    }\n\n    It 'has the source GitHub URL in docker metadata' {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"inspect --format=`\"{{index .Config.Labels \\`\"org.opencontainers.image.source\\`\"}}`\" $global:IMAGE_NAME\"\n        $exitCode | Should -Be 0\n        $stdout.Trim() | Should -Match 'https://github.com/jenkinsci/docker-ssh-agent'\n    }\n}\n\nDescribe \"[$global:IMAGE_TAG] image has correct version of java and git-lfs installed and in the PATH\" {\n    BeforeAll {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"run --detach --tty --name=`\"$global:CONTAINERNAME`\" --publish-all `\"$global:IMAGE_NAME`\" `\"$global:PUBLIC_SSH_KEY`\"\"\n        $exitCode | Should -Be 0\n        Is-ContainerRunning $global:CONTAINERNAME\n    }\n\n    It 'has java installed and in the path' {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `\"if(`$null -eq (Get-Command java.exe -ErrorAction SilentlyContinue)) { exit -1 } else { exit 0 }`\"\"\n        $exitCode | Should -Be 0\n\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `\"`$version = java -version 2>&1 ; Write-Host `$version`\"\"\n        $r = [regex] \"^openjdk version `\"(?<major>\\d+)\"\n        $m = $r.Match($stdout)\n        $m | Should -Not -Be $null\n        $m.Groups['major'].ToString() | Should -Be \"$global:JAVAMAJORVERSION\"\n    }\n\n    It 'has git-lfs (and thus git) installed and in the path' {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `\"`& git lfs env`\"\"\n        $exitCode | Should -Be 0\n        $r = [regex] \"^git-lfs/(?<version>\\d+\\.\\d+\\.\\d+)\"\n        $m = $r.Match($stdout)\n        $m | Should -Not -Be $null\n        $m.Groups['version'].ToString() | Should -Be \"$global:GITLFSVERSION\"\n    }\n\n    AfterAll {\n        Cleanup($global:CONTAINERNAME)\n    }\n}\n\nDescribe \"[$global:IMAGE_TAG] create agent container with pubkey as argument\" {\n    BeforeAll {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"run --detach --tty --name=`\"$global:CONTAINERNAME`\" --publish-all `\"$global:IMAGE_NAME`\" `\"$global:PUBLIC_SSH_KEY`\"\"\n        $exitCode | Should -Be 0\n        Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue\n    }\n\n    It 'runs commands via ssh' {\n        $exitCode, $stdout, $stderr = Run-ThruSSH $global:CONTAINERNAME \"$global:PRIVATE_SSH_KEY\" \"$global:CONTAINERSHELL -NoLogo -C `\"Write-Host 'f00'`\"\"\n        $exitCode | Should -Be 0\n        $stdout | Should -Match 'f00'\n    }\n\n    AfterAll {\n        Cleanup($global:CONTAINERNAME)\n    }\n}\n\nDescribe \"[$global:IMAGE_TAG] create agent container with pubkey as envvar\" {\n    BeforeAll {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"run --detach --tty --name=`\"$global:CONTAINERNAME`\" --publish-all `\"$global:IMAGE_NAME`\" `\"$global:PUBLIC_SSH_KEY`\"\"\n        $exitCode | Should -Be 0\n        Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue\n    }\n\n    It 'runs commands via ssh' {\n        $exitCode, $stdout, $stderr = Run-ThruSSH $global:CONTAINERNAME \"$global:PRIVATE_SSH_KEY\" \"$global:CONTAINERSHELL -NoLogo -C `\"Write-Host 'f00'`\"\"\n        $exitCode | Should -Be 0\n        $stdout | Should -Match 'f00'\n    }\n\n    AfterAll {\n        Cleanup($global:CONTAINERNAME)\n    }\n}\n\n\n$global:DOCKER_PLUGIN_DEFAULT_ARG=\"/usr/sbin/sshd -D -p 22\"\nDescribe \"[$global:IMAGE_TAG] create agent container like docker-plugin with '$global:DOCKER_PLUGIN_DEFAULT_ARG' as argument\" {\n    BeforeAll {\n        [string]::IsNullOrWhiteSpace($global:DOCKER_PLUGIN_DEFAULT_ARG) | Should -BeFalse\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"run --detach --tty --name=`\"$global:CONTAINERNAME`\" --publish-all --env=`\"JENKINS_AGENT_SSH_PUBKEY=$global:PUBLIC_SSH_KEY`\" `\"$global:IMAGE_NAME`\" `\"$global:DOCKER_PLUGIN_DEFAULT_ARG`\"\"\n        $exitCode | Should -Be 0\n        Is-ContainerRunning $global:CONTAINERNAME | Should -BeTrue\n    }\n\n    It 'runs commands via ssh' {\n        $exitCode, $stdout, $stderr = Run-ThruSSH $global:CONTAINERNAME \"$global:PRIVATE_SSH_KEY\" \"$global:CONTAINERSHELL -NoLogo -C `\"Write-Host 'f00'`\"\"\n        $exitCode | Should -Be 0\n        $stdout | Should -Match 'f00'\n    }\n\n    AfterAll {\n        Cleanup($global:CONTAINERNAME)\n    }\n}\n\nDescribe \"[$global:IMAGE_TAG] image can be built\" {\n    It 'builds image' {\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"build --build-arg `\"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`\" --build-arg `\"TOOLS_WINDOWS_VERSION=${global:TOOLSWINDOWSVERSION}`\" --build-arg `\"JAVA_VERSION=${global:JAVA_VERSION}`\" --build-arg `\"JAVA_HOME=C:\\openjdk-${global:JAVAMAJORVERSION}`\" --tag=${global:IMAGE_TAG} --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile .\"\n        $exitCode | Should -Be 0\n    }\n}\n\nDescribe \"[$global:IMAGE_TAG] image can be built with custom build args\" {\n    BeforeAll {\n        Push-Location -StackName 'agent' -Path \"$PSScriptRoot/..\"\n    }\n\n    It 'uses build args correctly' {\n        $TEST_USER = 'testuser'\n        $TEST_JAW = 'C:/hamster'\n        $CUSTOM_IMAGE_NAME = \"custom-${IMAGE_NAME}\"\n\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"build --build-arg `\"WINDOWS_VERSION_TAG=${global:WINDOWSVERSIONTAG}`\" --build-arg `\"TOOLS_WINDOWS_VERSION=${global:TOOLSWINDOWSVERSION}`\" --build-arg `\"JAVA_VERSION=${global:JAVA_VERSION}`\" --build-arg `\"JAVA_HOME=C:\\openjdk-${global:JAVAMAJORVERSION}`\" --build-arg `\"user=$TEST_USER`\" --build-arg `\"JENKINS_AGENT_WORK=$TEST_JAW`\" --tag=$CUSTOM_IMAGE_NAME --file ./windows/${global:WINDOWSFLAVOR}/Dockerfile .\"\n        $exitCode | Should -Be 0\n\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"run --detach --tty --name=$global:CONTAINERNAME --publish-all $CUSTOM_IMAGE_NAME $global:CONTAINERSHELL\"\n        $exitCode | Should -Be 0\n        Is-ContainerRunning \"$global:CONTAINERNAME\" | Should -BeTrue\n\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"exec $global:CONTAINERNAME net user $TEST_USER\"\n        $exitCode | Should -Be 0\n        $stdout | Should -Match \"User name\\s*$TEST_USER\"\n\n        $exitCode, $stdout, $stderr = Run-Program 'docker' \"exec $global:CONTAINERNAME $global:CONTAINERSHELL -C `\"(Get-ChildItem env:\\ | Where-Object { `$_.Name -eq 'JENKINS_AGENT_WORK' }).Value`\"\"\n        $exitCode | Should -Be 0\n        $stdout.Trim() | Should -Match \"$TEST_JAW\"\n    }\n\n    AfterAll {\n        Cleanup($global:CONTAINERNAME)\n        Pop-Location -StackName 'agent'\n    }\n}\n"
  },
  {
    "path": "tests/tags.bats",
    "content": "#!/usr/bin/env bats\n\nload test_helpers\n\nSUT_DESCRIPTION=\"tags\"\n\n@test \"[${SUT_DESCRIPTION}] Default tags unchanged\" {\n  assert_matches_golden expected_tags make --silent tags\n}\n"
  },
  {
    "path": "tests/test_helpers.bash",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\n# check dependencies\n(\n    type docker &>/dev/null || ( echo \"docker is not available\"; exit 1 )\n    type curl &>/dev/null || ( echo \"curl is not available\"; exit 1 )\n    type ssh &>/dev/null || ( echo \"ssh is not available\"; exit 1 )\n)>&2\n\nfunction printMessage {\n  echo \"# ${@}\" >&3\n}\n\n# Assert that $1 is the output of a command $2\nfunction assert_run_cmd_output_equal {\n    local expected_output\n    local actual_output\n    expected_output=\"${1}\"\n    shift\n    run \"${@}\"\n    assert_output \"${expected_output}\"\n}\n\n# Assert that golden file $1 matches the output of a command $2\nassert_matches_golden() {\n    local golden=\"$1\"\n    shift\n    local golden_path=\"tests/golden/${golden}.txt\"\n\n    if [[ ! -f \"${golden_path}\" ]]; then\n        echo \"Golden file '${golden_path}' does not exist\"\n        return 1\n    fi\n\n    # Run the command passed as arguments and capture its output\n    local output\n    output=\"$(mktemp)\"\n    \"$@\" > \"${output}\"\n\n    # Compare with golden file\n    diff -u \"${golden_path}\" <(cat \"${output}\")\n}\n\nfunction get_sut_image {\n    test -n \"${IMAGE:?\"[sut_image] Please set the variable 'IMAGE' to the name of the image to test in 'docker-bake.hcl'.\"}\"\n    ## Retrieve the SUT image name from buildx\n    # Option --print for 'docker buildx bake' prints the JSON configuration on the stdout\n    # Option --silent for 'make' suppresses the echoing of command so the output is valid JSON\n    # The image name is the 1st of the \"tags\" array, on the first \"image\" found\n    make --silent show | jq -r \".target.${IMAGE}.tags[0]\"\n}\n\n# Retry a command $1 times until it succeeds. Wait $2 seconds between retries.\n# Command is passed as the \"rest\" of arguments: $3 $4 $5 ...\nfunction retry {\n    local attempts\n    local delay\n    local i\n    attempts=\"${1}\"\n    shift\n    delay=\"${1}\"\n    shift\n\n    for ((i=0; i < attempts; i++)); do\n        run \"${@}\"\n        if assert_success; then\n            return 0\n        fi\n        sleep \"${delay}\"\n    done\n\n    printMessage \"Command '${BATS_RUN_COMMAND}' failed ${attempts} times. Status: ${status}. Output: ${output}\"\n\n    false\n}\n\n# return the published port for given container port $1\nfunction get_port {\n  local agent_container_name=\"${1}\"\n  local port=\"${2}\"\n  docker port \"${agent_container_name}\" \"${port}\" | cut -d: -f2\n}\n\n# run a given command through ssh on the test container.\n# Use the $status, $output and $lines variables to make assertions\nfunction run_through_ssh {\n  local agent_container_name=\"${1}\"\n  shift 1\n  SSH_PORT=$(get_port \"${agent_container_name}\" 22)\n\tif [[ \"${SSH_PORT}\" = \"\" ]]; then\n\t\tprintMessage \"failed to get SSH port\"\n\t\tfalse\n\telse\n\t\tTMP_PRIV_KEY_FILE=$(mktemp \"${BATS_TMPDIR}\"/bats_private_ssh_key_XXXXXXX)\n\t\techo \"${PRIVATE_SSH_KEY}\" > \"${TMP_PRIV_KEY_FILE}\" \\\n\t\t\t&& chmod 0600 \"${TMP_PRIV_KEY_FILE}\"\n\n\t\techo \"[DEBUG] *** Running ssh command\"\n\t\trun ssh -i \"${TMP_PRIV_KEY_FILE}\" \\\n\t\t\t-o LogLevel=quiet \\\n\t\t\t-o UserKnownHostsFile=/dev/null \\\n\t\t\t-o StrictHostKeyChecking=no \\\n\t\t\t-l jenkins \\\n\t\t\t127.0.0.1 \\\n\t\t\t-p \"${SSH_PORT}\" \\\n\t\t\t\"${@}\"\n    echo \"[DEBUG] *** Command was: ${BATS_RUN_COMMAND}\"\n\n\t\trm -f \"${TMP_PRIV_KEY_FILE}\"\n\tfi\n}\n\nfunction clean_test_container {\n  local agent_container=$1\n  docker kill \"${agent_container}\" &>/dev/null ||:\n  docker rm --force --volumes \"${agent_container}\" &>/dev/null ||:\n}\n\nfunction is_agent_container_running {\n  local agent_container=$1\n  # 30s is considered enough for the SSH server to start, even under constraint\n\tretry 15 2 assert_run_cmd_output_equal healthy docker inspect -f '{{.State.Health.Status}}' \"${agent_container}\"\n}\n"
  },
  {
    "path": "tests/test_helpers.psm1",
    "content": "function Test-CommandExists($command) {\r\n    $oldPreference = $ErrorActionPreference\r\n    $ErrorActionPreference = 'stop'\r\n    $res = $false\r\n    try {\r\n        if(Get-Command $command) {\r\n            $res = $true\r\n        }\r\n    } catch {\r\n        $res = $false\r\n    } finally {\r\n        $ErrorActionPreference=$oldPreference\r\n    }\r\n    return $res\r\n}\r\n\r\n# check dependencies\r\nif(-Not (Test-CommandExists docker)) {\r\n    Write-Error 'docker is not available'\r\n}\r\n\r\nfunction Get-EnvOrDefault($name, $def) {\r\n    $entry = Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Select-Object -First 1\r\n    if(($null -ne $entry) -and ![System.String]::IsNullOrWhiteSpace($entry.Value)) {\r\n        return $entry.Value\r\n    }\r\n    return $def\r\n}\r\n\r\nfunction Retry-Command {\r\n    [CmdletBinding()]\r\n    param (\r\n        [parameter(Mandatory, ValueFromPipeline)]\r\n        [ValidateNotNullOrEmpty()]\r\n        [scriptblock] $ScriptBlock,\r\n        [int] $RetryCount = 3,\r\n        [int] $Delay = 30,\r\n        [string] $SuccessMessage = 'Command executed successfuly!',\r\n        [string] $FailureMessage = 'Failed to execute the command'\r\n        )\r\n\r\n    process {\r\n        $Attempt = 1\r\n        $Flag = $true\r\n\r\n        do {\r\n            try {\r\n                $PreviousPreference = $ErrorActionPreference\r\n                $ErrorActionPreference = 'Stop'\r\n                Invoke-Command -NoNewScope -ScriptBlock $ScriptBlock -OutVariable Result 4>&1\r\n                $ErrorActionPreference = $PreviousPreference\r\n\r\n                # flow control will execute the next line only if the command in the scriptblock executed without any errors\r\n                # if an error is thrown, flow control will go to the 'catch' block\r\n                Write-Verbose \"$SuccessMessage `n\"\r\n                $Flag = $false\r\n            }\r\n            catch {\r\n                if ($Attempt -gt $RetryCount) {\r\n                    Write-Verbose \"$FailureMessage! Total retry attempts: $RetryCount\"\r\n                    Write-Verbose \"[Error Message] $($_.exception.message) `n\"\r\n                    $Flag = $false\r\n                } else {\r\n                    Write-Verbose \"[$Attempt/$RetryCount] $FailureMessage. Retrying in $Delay seconds...\"\r\n                    Start-Sleep -Seconds $Delay\r\n                    $Attempt = $Attempt + 1\r\n                }\r\n            }\r\n        }\r\n        While ($Flag)\r\n    }\r\n}\r\n\r\nfunction Cleanup($name='') {\r\n    if([System.String]::IsNullOrWhiteSpace($name)) {\r\n        $name = Get-EnvOrDefault 'IMAGE_NAME' ''\r\n    }\r\n\r\n    if(![System.String]::IsNullOrWhiteSpace($name)) {\r\n        docker kill \"$name\" 2>&1 | Out-Null\r\n        docker rm -fv \"$name\" 2>&1 | Out-Null\r\n    }\r\n}\r\n\r\nfunction CleanupNetwork($name) {\r\n    docker network rm $name 2>&1 | Out-Null\r\n}\r\n\r\nfunction Is-ContainerRunning($container) {\r\n    Start-Sleep -Seconds 5\r\n    return Retry-Command -RetryCount 10 -Delay 2 -ScriptBlock {\r\n        $exitCode, $stdout, $stderr = Run-Program 'docker.exe' \"inspect --format `\"{{.State.Running}}`\" $container\"\r\n        if(($exitCode -ne 0) -or (-not $stdout.Contains('true')) ) {\r\n            throw('Exit code incorrect, or invalid value for running state')\r\n        }\r\n        return $true\r\n    }\r\n}\r\n\r\nfunction Run-Program($cmd, $params) {\r\n    $psi = New-Object System.Diagnostics.ProcessStartInfo\r\n    $psi.CreateNoWindow = $true\r\n    $psi.UseShellExecute = $false\r\n    $psi.RedirectStandardOutput = $true\r\n    $psi.RedirectStandardError = $true\r\n    $psi.WorkingDirectory = (Get-Location)\r\n    $psi.FileName = $cmd\r\n    $psi.Arguments = $params\r\n    $proc = New-Object System.Diagnostics.Process\r\n    $proc.StartInfo = $psi\r\n    [void]$proc.Start()\r\n    $stdout = $proc.StandardOutput.ReadToEnd()\r\n    $stderr = $proc.StandardError.ReadToEnd()\r\n    $proc.WaitForExit()\r\n    if(($env:TESTS_DEBUG -eq 'debug') -or ($env:TESTS_DEBUG -eq 'verbose')) {\r\n        Write-Host -ForegroundColor DarkBlue \"[cmd] $cmd $params\"\r\n        if ($env:TESTS_DEBUG -eq 'verbose') { Write-Host -ForegroundColor DarkGray \"[stdout] $stdout\" }\r\n        if($proc.ExitCode -ne 0){\r\n            Write-Host -ForegroundColor DarkRed \"[stderr] $stderr\"\r\n        }\r\n    }\r\n    return $proc.ExitCode, $stdout, $stderr\r\n}\r\n\r\n# return the published port for given container port $1\r\nfunction Get-Port($container, $port=22) {\r\n    $exitCode, $stdout, $stderr = Run-Program 'docker.exe' \"port $container $port\"\r\n    return ($stdout -split \":\" | Select-Object -Skip 1).Trim()\r\n}\r\n\r\n# run a given command through ssh on the test container.\r\nfunction Run-ThruSSH($container, $privateKeyVal, $cmd) {\r\n    $SSH_PORT = Get-Port $container 22\r\n    if([System.String]::IsNullOrWhiteSpace($SSH_PORT)) {\r\n        Write-Error 'Failed to get SSH port'\r\n        return -1, $null, $null\r\n    } else {\r\n        $TMP_PRIV_KEY_FILE = New-TemporaryFile\r\n        Set-Content -Path $TMP_PRIV_KEY_FILE -Value \"$privateKeyVal\"\r\n\r\n        $exitCode, $stdout, $stderr = Run-Program 'ssh.exe' \"-v -i `\"${TMP_PRIV_KEY_FILE}`\" -o LogLevel=quiet -o UserKnownHostsFile=NUL -o StrictHostKeyChecking=no -l jenkins localhost -p $SSH_PORT $cmd\"\r\n        Remove-Item -Force $TMP_PRIV_KEY_FILE\r\n\r\n        return $exitCode, $stdout, $stderr\r\n    }\r\n}\r\n"
  },
  {
    "path": "tests/tests.bats",
    "content": "#!/usr/bin/env bats\n\nload test_helpers\nload 'test_helper/bats-support/load' # this is required by bats-assert!\nload 'test_helper/bats-assert/load'\nload keys\n\nIMAGE=${IMAGE:-debian_jdk11}\nSUT_IMAGE=$(get_sut_image)\n\nARCH=${ARCH:-x86_64}\nAGENT_CONTAINER=bats-jenkins-ssh-agent\n\n# TODO: uncomment when git-lfs version is the same across all images\n# GIT_LFS_VERSION='3.7.1'\n\n# About the health CMD: the netcat command (`nc`) needs the options `-w1` to return 1s after reaches EOF. It's a portable option of `nc` (on BSD, Debian, Windows, busybox).\n# Of course, to reach EOF, you need to provide something to the stding: it's the reason of the `echo` piped command\ndocker_run_opts=('--detach' '--publish-all' '--health-cmd=echo | nc -w1 localhost 22' '--health-start-period=2s' '--health-interval=2s' '--health-retries=10' '--health-timeout=2s' \"${SUT_IMAGE}\")\n\n@test \"[${SUT_IMAGE}] test label in docker metadata\" {\n  local expected_source=\"https://github.com/jenkinsci/docker-ssh-agent\"\n\n  local actual_source\n  actual_source=$(docker inspect --format '{{ index .Config.Labels \"org.opencontainers.image.source\"}}' \"${SUT_IMAGE}\")\n\n  assert_equal \"${expected_source}\" \"${actual_source}\"\n}\n\n@test \"[${SUT_IMAGE}] checking image metadata\" {\n  local VOLUMES_MAP\n  VOLUMES_MAP=\"$(docker inspect -f '{{.Config.Volumes}}' \"${SUT_IMAGE}\")\"\n\n  echo \"${VOLUMES_MAP}\" | grep '/tmp'\n  echo \"${VOLUMES_MAP}\" | grep '/home/jenkins'\n  echo \"${VOLUMES_MAP}\" | grep '/run'\n  echo \"${VOLUMES_MAP}\" | grep '/var/run'\n}\n\n@test \"[${SUT_IMAGE}] image has bash and java installed and in the PATH\" {\n  local test_container_name=${AGENT_CONTAINER}-bash-java\n  clean_test_container \"${test_container_name}\"\n  docker run --name=\"${test_container_name}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\" \"${PUBLIC_SSH_KEY}\"\n\n  run docker exec \"${test_container_name}\" which bash\n  assert_success\n  run docker exec \"${test_container_name}\" bash --version\n  assert_success\n  run docker exec \"${test_container_name}\" which java\n  assert_success\n\n  run docker exec \"${test_container_name}\" sh -c \"java -version\"\n  assert_success\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] image has no pre-existing SSH host keys\" {\n  local test_container_name=${AGENT_CONTAINER}-ssh-hostkeys\n  clean_test_container \"${test_container_name}\"\n  docker run --name=\"${test_container_name}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\" \"${PUBLIC_SSH_KEY}\"\n\n  run docker exec \"${test_container_name}\" ls -l /etc/ssh/ssh_host*_key*\n  assert_failure\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] create agent container with pubkey as argument\" {\n  local test_container_name=${AGENT_CONTAINER}-pubkey-arg\n  clean_test_container \"${test_container_name}\"\n  docker run --name=\"${test_container_name}\" \"${docker_run_opts[@]}\" \"${PUBLIC_SSH_KEY}\"\n\n  is_agent_container_running \"${test_container_name}\"\n\n  run_through_ssh \"${test_container_name}\" echo f00\n  assert_success\n  assert_equal \"${output}\" \"f00\"\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] create agent container with pubkey as environment variable (legacy environment variable)\" {\n  local test_container_name=${AGENT_CONTAINER}-pubkey-legacy-env\n  clean_test_container \"${test_container_name}\"\n  docker run --env=\"JENKINS_SLAVE_SSH_PUBKEY=${PUBLIC_SSH_KEY}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\"\n\n  is_agent_container_running \"${test_container_name}\"\n\n  run_through_ssh \"${test_container_name}\" echo f00\n  assert_success\n  assert_equal \"${output}\" \"f00\"\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] create agent container with pubkey as environment variable (JENKINS_AGENT_SSH_PUBKEY)\" {\n  local test_container_name=${AGENT_CONTAINER}-pubkey-env\n  clean_test_container \"${test_container_name}\"\n  docker run --env=\"JENKINS_AGENT_SSH_PUBKEY=${PUBLIC_SSH_KEY}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\"\n\n  is_agent_container_running \"${test_container_name}\"\n\n  run_through_ssh \"${test_container_name}\" echo f00\n  assert_success\n  assert_equal \"${output}\" \"f00\"\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] Run Java in a SSH connection\" {\n  local test_container_name=${AGENT_CONTAINER}-java-in-ssh\n  clean_test_container \"${test_container_name}\"\n  docker run --env=\"JENKINS_AGENT_SSH_PUBKEY=${PUBLIC_SSH_KEY}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\"\n\n  is_agent_container_running \"${test_container_name}\"\n\n  run_through_ssh \"${test_container_name}\" java -version\n  assert_success\n  assert_output --regexp '^openjdk version \\\"[[:digit:]]+'\n\n  clean_test_container \"${test_container_name}\"\n}\n\nDOCKER_PLUGIN_DEFAULT_ARG=\"/usr/sbin/sshd -D -p 22\"\n@test \"[${SUT_IMAGE}] create agent container like docker-plugin with '${DOCKER_PLUGIN_DEFAULT_ARG}' (unquoted) as argument\" {\n  [ -n \"$DOCKER_PLUGIN_DEFAULT_ARG\" ]\n\n  local test_container_name=${AGENT_CONTAINER}-docker-plugin\n  clean_test_container \"${test_container_name}\"\n  docker run --env=\"JENKINS_AGENT_SSH_PUBKEY=${PUBLIC_SSH_KEY}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\" ${DOCKER_PLUGIN_DEFAULT_ARG}\n\n  is_agent_container_running \"${test_container_name}\"\n\n  run_through_ssh \"${test_container_name}\" echo f00\n  assert_success\n  assert_equal \"${output}\" \"f00\"\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] create agent container with '${DOCKER_PLUGIN_DEFAULT_ARG}' (quoted) as argument\" {\n  [ -n \"$DOCKER_PLUGIN_DEFAULT_ARG\" ]\n\n  local test_container_name=${AGENT_CONTAINER}-docker-plugin-quoted\n  clean_test_container \"${test_container_name}\"\n  docker run --env=\"JENKINS_AGENT_SSH_PUBKEY=${PUBLIC_SSH_KEY}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\" \"${DOCKER_PLUGIN_DEFAULT_ARG}\"\n\n  is_agent_container_running \"${test_container_name}\"\n\n  run_through_ssh \"${test_container_name}\" echo f00\n  assert_success\n  assert_equal \"${output}\" \"f00\"\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] use build args correctly\" {\n  cd \"${BATS_TEST_DIRNAME}\"/.. || false\n\n\tlocal TEST_USER=test-user\n\tlocal TEST_GROUP=test-group\n\tlocal TEST_UID=2000\n\tlocal TEST_GID=3000\n\tlocal TEST_JAH=/home/something\n\n\tlocal sut_image=\"${SUT_IMAGE}-tests-${BATS_TEST_NUMBER}\"\n\n  # false positive detecting platform\n  # shellcheck disable=SC2140\n  docker buildx bake \\\n    --set \"${IMAGE}\".args.user=\"${TEST_USER}\" \\\n    --set \"${IMAGE}\".args.group=\"${TEST_GROUP}\" \\\n    --set \"${IMAGE}\".args.uid=\"${TEST_UID}\" \\\n    --set \"${IMAGE}\".args.gid=\"${TEST_GID}\" \\\n    --set \"${IMAGE}\".args.JENKINS_AGENT_HOME=\"${TEST_JAH}\" \\\n    --set \"${IMAGE}\".platform=\"linux/${ARCH}\" \\\n    --set \"${IMAGE}\".tags=\"${sut_image}\" \\\n      --load `# Image should be loaded on the Docker engine`\\\n      \"${IMAGE}\"\n\n  local test_container_name=${AGENT_CONTAINER}-build-args\n  clean_test_container \"${test_container_name}\"\n  docker run --detach --name=\"${test_container_name}\" --publish-all \"${sut_image}\" \"${PUBLIC_SSH_KEY}\"\n\n  run docker exec \"${test_container_name}\" sh -c \"id -u -n ${TEST_USER}\"\n  assert_line --index 0 \"${TEST_USER}\"\n  run docker exec \"${test_container_name}\" sh -c \"id -g -n ${TEST_USER}\"\n  assert_line --index 0 \"${TEST_GROUP}\"\n  run docker exec \"${test_container_name}\" sh -c \"id -u ${TEST_USER}\"\n  assert_line --index 0 \"${TEST_UID}\"\n  run docker exec \"${test_container_name}\" sh -c \"id -g ${TEST_USER}\"\n  assert_line --index 0 \"${TEST_GID}\"\n  run docker exec \"${test_container_name}\" sh -c 'stat -c \"%U:%G\" \"${JENKINS_AGENT_HOME}\"'\n  assert_line --index 0 \"${TEST_USER}:${TEST_GROUP}\"\n\n  clean_test_container \"${test_container_name}\"\n}\n\n@test \"[${SUT_IMAGE}] has utf-8 locale\" {\n  run docker run --entrypoint sh --rm \"${SUT_IMAGE}\" -c 'locale charmap'\n  assert_equal \"${output}\" \"UTF-8\"\n}\n\n@test \"[${SUT_IMAGE}] the default 'jenkins' user is allowed to write in the default agent directory\" {\n  run docker run --user=jenkins --entrypoint='' --rm \"${SUT_IMAGE}\" sh -c 'touch \"${AGENT_WORKDIR}\"/test.txt'\n  assert_success\n}\n\n@test \"[${SUT_IMAGE}] image has required tools installed and present in the PATH, can clone a repo and list large files\" {\n  local test_container_name=${AGENT_CONTAINER}-bash-java\n  clean_test_container \"${test_container_name}\"\n  docker run --name=\"${test_container_name}\" --name=\"${test_container_name}\" \"${docker_run_opts[@]}\" \"${PUBLIC_SSH_KEY}\"\n\n  run docker exec \"${test_container_name}\" sh -c \"command -v ssh\"\n  assert_success\n  run docker exec \"${test_container_name}\" ssh -V\n  assert_success\n\n  run docker exec \"${test_container_name}\" sh -c \"command -v git\"\n  assert_success\n  run docker exec \"${test_container_name}\" git --version\n  assert_success\n\n  run docker exec \"${test_container_name}\" sh -c \"command -v less\"\n  assert_success\n  run docker exec \"${test_container_name}\" less -V\n  assert_success\n  run docker exec \"${test_container_name}\" sh -c \"command -v patch\"\n  assert_success\n  run docker exec \"${test_container_name}\" patch --version\n  assert_success\n\n  run docker exec \"${test_container_name}\" git clone https://github.com/jenkinsci/docker-ssh-agent.git\n  assert_success\n\n  run docker exec \"${test_container_name}\" git lfs env\n  assert_success\n  # TODO: replace assert_success with assert_output when git-lfs version is the same across all images\n  # assert_output --partial \"${GIT_LFS_VERSION}\"\n\n  clean_test_container \"${test_container_name}\"\n}\n"
  },
  {
    "path": "tests/update-golden-file.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# This script runs a specified command, captures its output,\n# and compares it against a \"golden file\" representing the expected output.\n# If the output differs, the script shows a diff and allows the user to update the golden file.\n# If the output matches, it reports that the golden file is up-to-date.\n#\n# Usage:\n#   ./update-golden-file.sh <test-name> <command...>\n#\n# Arguments:\n#   <test-name>    Name of the test, used to determine the golden file path.\n#                  The corresponding golden file will be stored as:\n#                     golden/<test-name>.txt\n#\n#   <command...>   Command to run, whose stdout will be compared to the golden file.\n#                  This can include arguments, e.g.:\n#                     ./update-golden-file.sh expected_tags make tags\n#\n# Notes:\n#   - Requires Bash 4+ for `BASH_SOURCE` handling.\n#   - The script is safe to run from any directory; golden files are always relative to the script's own location.\n\nif [[ $# -lt 2 ]]; then\n  echo \"Usage: $0 <test-name> <command...>\"\n  echo \"Example:\"\n  echo \"  $0 expected_tags make tags\"\n  exit 1\nfi\n\nname=\"$1\"\nshift\n\n# Ensure golden folder is always relative to this script\nscript_dir=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\ngolden_file=\"${name}.txt\"\ngolden_path=\"${script_dir}/golden/${golden_file}\"\ntmp=\"$(mktemp)\"\n\necho\necho \"Golden file path:\"\necho \"  ${golden_path}\"\necho\necho \"Running command:\"\necho \"  $*\"\necho\n\n\"$@\" > \"${tmp}\"\n\naction=\"create\"\nif [[ -f \"${golden_path}\" ]]; then\n    if diff -u \"${golden_path}\" \"${tmp}\" > /dev/null; then\n        echo \"Golden file '${golden_file}' is already up-to-date.\"\n        rm \"${tmp}\"\n        exit 0\n    fi\n    echo \"Diff against existing golden file '${golden_file}':\"\n    diff -u \"${golden_path}\" \"${tmp}\" || true\n    action=\"update\"\nelse\n    echo \"Golden file '${golden_file}' does not exist yet.\"\nfi\n\necho\necho \"Golden file to ${action}: '${golden_file}'\"\nread -rp \"Proceed? [y/N] \" answer\n\nif [[ \"${answer}\" =~ ^[Yy]$ ]]; then\n    mkdir -p \"$(dirname \"${golden_path}\")\"\n    mv \"${tmp}\" \"${golden_path}\"\n    echo \"Golden file '${golden_file}' ${action}d.\"\nelse\n    rm \"${tmp}\"\n    echo \"Aborted. Golden file '${golden_file}' unchanged.\"\nfi\n"
  },
  {
    "path": "updatecli/updatecli.d/alpine.yaml",
    "content": "---\nname: Bump Alpine version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  latestVersion:\n    kind: githubrelease\n    name: \"Get the latest Alpine Linux version\"\n    spec:\n      owner: \"alpinelinux\"\n      repository: \"aports\" # Its release process follows Alpine's\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      versionfilter:\n        kind: semver\n        pattern: \"~3\"\n    transformers:\n      - trimprefix: \"v\"\n\nconditions:\n  testDockerfileArg:\n    name: \"Does the Dockerfile have an ARG instruction for the Alpine Linux version?\"\n    kind: dockerfile\n    disablesourceinput: true\n    spec:\n      file: alpine/Dockerfile\n      instruction:\n        keyword: \"ARG\"\n        matcher: \"ALPINE_TAG\"\n  testDockerImageExists:\n    name: \"Does the Docker Image exist on the Docker Hub?\"\n    kind: dockerimage\n    sourceid: latestVersion\n    spec:\n      image: \"alpine\"\n      # tag come from the source\n      architecture: amd64\n\ntargets:\n  updateDockerfile:\n    name: \"Update the value of the base image (ARG ALPINE_TAG) in the Dockerfile\"\n    kind: dockerfile\n    spec:\n      file: alpine/Dockerfile\n      instruction:\n        keyword: \"ARG\"\n        matcher: \"ALPINE_TAG\"\n    scmid: default\n  updateDockerBake:\n    name: \"Update the value of the base image (ARG ALPINE_TAG) in the docker-bake.hcl\"\n    kind: hcl\n    spec:\n      file: docker-bake.hcl\n      path: variable.ALPINE_FULL_TAG.default\n    scmid: default\nactions:\n  default:\n    kind: github/pullrequest\n    scmid: default\n    title: Bump Alpine Linux Version to {{ source \"latestVersion\" }}\n    spec:\n      labels:\n        - dependencies\n"
  },
  {
    "path": "updatecli/updatecli.d/bats.yaml",
    "content": "---\nname: Bump `bats` version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  lastVersion:\n    kind: githubrelease\n    spec:\n      owner: bats-core\n      repository: bats-core\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      versionfilter:\n        kind: semver\n\ntargets:\n  updateMakefile:\n    name: \"Updates `bats` version in the Makefile\"\n    kind: file\n    spec:\n      file: Makefile\n      matchpattern: >-\n        git clone --branch (.*) https://github.com/bats-core/bats-core bats\n      replacepattern: >-\n        git clone --branch {{ source \"lastVersion\" }} https://github.com/bats-core/bats-core bats\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    scmid: default\n    title: 'chore(tests): Bump `bats` version to {{ source \"lastVersion\" }}'\n    spec:\n      labels:\n        - chore # Because bats is only used for testing\n        - bats\n"
  },
  {
    "path": "updatecli/updatecli.d/debian.yaml",
    "content": "---\nname: Bump Debian Trixie version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  trixieLatestVersion:\n    kind: dockerimage\n    name: \"Get latest Debian Trixie Linux version\"\n    spec:\n      image: \"debian\"\n      tagfilter: \"trixie-*\"\n      versionfilter:\n        kind: regex\n        pattern: >-\n          trixie-\\d+$\n\nconditions:\n  checkArchitecturesAvailability:\n    kind: dockerimage\n    name: Check if container image is available for all architectures\n    sourceid: trixieLatestVersion\n    spec:\n      image: \"debian\"\n      architectures:\n        - linux/amd64\n        - linux/arm64\n        - linux/s390x\n        - linux/ppc64le\n\ntargets:\n  updateDockerfile:\n    name: \"Update value of base image (ARG DEBIAN_RELEASE) in Dockerfile\"\n    kind: dockerfile\n    sourceid: trixieLatestVersion\n    spec:\n      file: debian/Dockerfile\n      instruction:\n        keyword: \"ARG\"\n        matcher: \"DEBIAN_RELEASE\"\n    scmid: default\n  updateDockerBake:\n    name: \"Update default value of variable DEBIAN_RELEASE in docker-bake.hcl\"\n    kind: hcl\n    sourceid: trixieLatestVersion\n    spec:\n      file: docker-bake.hcl\n      path: variable.DEBIAN_RELEASE.default\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    scmid: default\n    title: Bump Debian Trixie Linux version to {{ source \"trixieLatestVersion\" }}\n    spec:\n      labels:\n        - dependencies\n"
  },
  {
    "path": "updatecli/updatecli.d/git-lfs.yml",
    "content": "name: Bump `git-lfs` version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  lastVersion:\n    kind: githubrelease\n    name: Get latest `git-lfs` version\n    spec:\n      owner: git-lfs\n      repository: git-lfs\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      versionfilter:\n        kind: semver\n    transformers:\n      - trimprefix: \"v\"\n\ntargets:\n  setGitLfsVersion:\n    name: Update `git-lfs` version in Dockerfiles\n    kind: dockerfile\n    spec:\n      files:\n        - alpine/Dockerfile\n        - debian/Dockerfile\n        - windows/nanoserver/Dockerfile\n        - windows/windowsservercore/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: GIT_LFS_VERSION\n    scmid: default\n  setGitLfsVersionLinuxTests:\n    name: Update `git-lfs` version in Linux tests\n    kind: file\n    spec:\n      file: tests/tests.bats\n      matchpattern: >\n        GIT_LFS_VERSION=(.*)\n      replacepattern: >\n        GIT_LFS_VERSION='{{ source \"lastVersion\" }}'\n    scmid: default\n  setGitLfsVersionWindowsTests:\n    name: Update `git-lfs` version in Windows tests\n    kind: file\n    spec:\n      file: tests/sshAgent.Tests.ps1\n      matchpattern: >\n        global:GITLFSVERSION =(.*)\n      replacepattern: >\n        global:GITLFSVERSION = '{{ source \"lastVersion\" }}'\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    title: Bump `git-lfs` version to {{ source \"lastVersion\" }}\n    scmid: default\n    spec:\n      labels:\n        - enhancement\n        - git-lfs\n"
  },
  {
    "path": "updatecli/updatecli.d/git-windows.yml",
    "content": "---\nname: Bump Git version on Windows\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  lastVersion:\n    kind: githubrelease\n    name: Get the latest Git version\n    spec:\n      owner: \"git-for-windows\"\n      repository: \"git\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      versionfilter:\n        kind: regex\n        ## Latest stable v{x.y.z}.windows.<patch>\n        pattern: 'v(\\d*)\\.(\\d*)\\.(\\d*)\\.windows\\.(\\d*)$'\n    transformers:\n      - trimprefix: \"v\"\n\ntargets:\n  # Nanoserver\n  setGitVersionWindowsNanoserver:\n    name: Update the Git Windows version for Windows Nanoserver\n    transformers:\n      - findsubmatch:\n          pattern: '(.*).windows\\.(\\d*)$'\n          captureindex: 1\n    kind: dockerfile\n    spec:\n      file: windows/nanoserver/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: GIT_VERSION\n    scmid: default\n  setGitPackagePatchWindowsNanoserver:\n    name: Update the Git Package Windows patch for Windows Nanoserver\n    transformers:\n      - findsubmatch:\n          pattern: '(.*).windows\\.(\\d*)$'\n          captureindex: 2\n    kind: dockerfile\n    spec:\n      file: windows/nanoserver/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: GIT_PATCH_VERSION\n    scmid: default\n  # Windows Server Core\n  setGitVersionWindowsServer:\n    name: Update the Git Windows version for Windows Server Core\n    transformers:\n      - findsubmatch:\n          pattern: '(.*).windows\\.(\\d*)$'\n          captureindex: 1\n    kind: dockerfile\n    spec:\n      file: windows/windowsservercore/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: GIT_VERSION\n    scmid: default\n  setGitPackagePatchWindowsServer:\n    name: Update the Git Package Windows patch for Windows Server Core\n    transformers:\n      - findsubmatch:\n          pattern: '(.*).windows\\.(\\d*)$'\n          captureindex: 2\n    kind: dockerfile\n    spec:\n      file: windows/windowsservercore/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: GIT_PATCH_VERSION\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    title: Bump Git version on Windows to {{ source \"lastVersion\" }}\n    scmid: default\n    spec:\n      labels:\n        - enhancement\n        - git\n        - windows\n"
  },
  {
    "path": "updatecli/updatecli.d/jdk17.yaml",
    "content": "---\nname: Bump Temurin's JDK17 version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  jdk17LastVersion:\n    kind: temurin\n    name: Get the latest Adoptium JDK17 version\n    spec:\n      featureversion: 17\n    transformers:\n      - trimprefix: \"jdk-\"\n\nconditions:\n  checkTemurinAllReleases:\n    name: Check if the \"<lastVersion>\" is available for all platforms\n    kind: temurin\n    spec:\n      featureversion: 17\n      platforms:\n        - alpine-linux/x64\n        - linux/x64\n        - linux/aarch64\n        - linux/ppc64le\n        - linux/s390x\n        - linux/arm\n        - windows/x64\n\ntargets:\n  setJDK17VersionDockerBake:\n    name: \"Bump JDK17 version for Linux images in the docker-bake.hcl file\"\n    kind: hcl\n    transformers:\n      - replacer:\n          from: \"+\"\n          to: \"_\"\n    spec:\n      file: docker-bake.hcl\n      path: variable.JAVA17_VERSION.default\n    scmid: default\n  setJDK17VersionAlpine:\n    name: \"Bump JDK17 default ARG version on Alpine Dockerfile\"\n    kind: dockerfile\n    transformers:\n      - replacer:\n          from: \"+\"\n          to: \"_\"\n    spec:\n      file: alpine/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: JAVA_VERSION\n    scmid: default\n  setJDK17VersionDebian:\n    name: \"Bump JDK17 default ARG version on Debian Dockerfile\"\n    kind: dockerfile\n    transformers:\n      - replacer:\n          from: \"+\"\n          to: \"_\"\n    spec:\n      file: debian/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: JAVA_VERSION\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    scmid: default\n    title: Bump JDK17 version to {{ source \"jdk17LastVersion\" }}\n    spec:\n      labels:\n        - dependencies\n        - jdk17\n"
  },
  {
    "path": "updatecli/updatecli.d/jdk21.yaml",
    "content": "---\nname: Bump Temurin's JDK21 version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n  temurin21-binaries:\n    kind: \"github\"\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"adoptium\"\n      repository: \"temurin21-binaries\"\n      token: '{{ requiredEnv .github.token }}'\n      branch: \"main\"\n\nsources:\n  jdk21LastVersion:\n    kind: temurin\n    name: Get the latest Adoptium JDK21 version\n    spec:\n      featureversion: 21\n    transformers:\n      - trimprefix: \"jdk-\"\n\nconditions:\n  checkTemurinAllReleases:\n    name: Check if the \"<lastTemurin21Version>\" is available for all platforms\n    kind: temurin\n    spec:\n      featureversion: 21\n      platforms:\n        - alpine-linux/x64\n        - alpine-linux/aarch64\n        - linux/x64\n        - linux/aarch64\n        - linux/ppc64le\n        - linux/s390x\n        - windows/x64\n\ntargets:\n  setJDK21VersionDockerBake:\n    name: \"Bump JDK21 version for Linux images in the docker-bake.hcl file\"\n    kind: hcl\n    sourceid: jdk21LastVersion\n    transformers:\n      - replacer:\n          from: \"+\"\n          to: \"_\"\n    spec:\n      file: docker-bake.hcl\n      path: variable.JAVA21_VERSION.default\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    scmid: default\n    title: Bump JDK21 version to {{ source \"jdk21LastVersion\" }}\n    spec:\n      labels:\n        - dependencies\n        - jdk21\n"
  },
  {
    "path": "updatecli/updatecli.d/jdk25.yaml",
    "content": "---\nname: Bump JDK25 version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n  temurin25-binaries:\n    kind: \"github\"\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"adoptium\"\n      repository: \"temurin25-binaries\"\n      token: '{{ requiredEnv .github.token }}'\n      branch: \"main\"\n\nsources:\n  latestJDK25Version:\n    kind: temurin\n    name: Get the latest Adoptium JDK25 version via the API\n    spec:\n      featureversion: 25\n    transformers:\n      - trimprefix: \"jdk-\"\n\n# Architectures must match those of the targets in docker-bake.hcl\nconditions:\n  checkTemurinAllReleases:\n    name: Check if the \"<latestJDK25Version>\" is available for all platforms\n    kind: temurin\n    sourceid: latestJDK25Version\n    spec:\n      featureversion: 25\n      platforms:\n        - alpine-linux/x64\n        - alpine-linux/aarch64\n        - linux/x64\n        - linux/aarch64\n        - linux/ppc64le\n        - linux/s390x\n        - windows/x64\n\ntargets:\n  setJDK25VersionDockerBake:\n    name: \"Bump JDK25 version in the docker-bake.hcl file\"\n    kind: hcl\n    transformers:\n      - replacer:\n          from: \"+\"\n          to: \"_\"\n    spec:\n      file: docker-bake.hcl\n      path: variable.JAVA25_VERSION.default\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    scmid: default\n    title: Bump JDK25 version to {{ source \"latestJDK25Version\" }}\n    spec:\n      labels:\n        - dependencies\n        - jdk25\n"
  },
  {
    "path": "updatecli/updatecli.d/openssh.yml",
    "content": "name: Bump OpenSSH version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  lastVersion:\n    kind: githubrelease\n    name: Get the latest OpenSSH version\n    spec:\n      owner: PowerShell\n      repository: Win32-OpenSSH\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n\ntargets:\n  setOpenSSHVersionNanoserver:\n    name: Update the OpenSSH version for Windows Nanoserver\n    kind: dockerfile\n    spec:\n      file: windows/nanoserver/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: OPENSSH_VERSION\n    scmid: default\n  setOpenSSHVersionWindowsServerCore:\n    name: Update the OpenSSH version for Windows Server Core\n    kind: dockerfile\n    spec:\n      file: windows/windowsservercore/Dockerfile\n      instruction:\n        keyword: ARG\n        matcher: OPENSSH_VERSION\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    title: Bump OpenSSH version to {{ source \"lastVersion\" }}\n    scmid: default\n    spec:\n      labels:\n        - enhancement\n        - openssh\n        - windows\n"
  },
  {
    "path": "updatecli/updatecli.d/pester.yaml",
    "content": "name: Bump `pester` version\n\nscms:\n  default:\n    kind: github\n    spec:\n      user: \"{{ .github.user }}\"\n      email: \"{{ .github.email }}\"\n      owner: \"{{ .github.owner }}\"\n      repository: \"{{ .github.repository }}\"\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      branch: \"{{ .github.branch }}\"\n\nsources:\n  lastVersion:\n    kind: githubrelease\n    name: Get latest `pester` version\n    spec:\n      owner: pester\n      repository: pester\n      token: \"{{ requiredEnv .github.token }}\"\n      username: \"{{ .github.username }}\"\n      versionfilter:\n        kind: semver\n    transformers:\n      - trimprefix: \"v\"\n\ntargets:\n  setGitLfsVersion:\n    name: Update `pester` version in build.ps1\n    kind: file\n    spec:\n      file: build.ps1\n      matchpattern: >\n        PesterVersion = (.*)\n      replacepattern: >\n        PesterVersion = '{{ source \"lastVersion\" }}',\n    scmid: default\n\nactions:\n  default:\n    kind: github/pullrequest\n    title: Bump `pester` version to {{ source \"lastVersion\" }}\n    scmid: default\n    spec:\n      labels:\n        - dependencies\n        - pester\n"
  },
  {
    "path": "updatecli/values.github-action.yaml",
    "content": "github:\n  user: \"GitHub Actions\"\n  email: \"41898282+github-actions[bot]@users.noreply.github.com\"\n  username: \"github-actions\"\n  token: \"UPDATECLI_GITHUB_TOKEN\"\n  branch: \"master\"\n  owner: \"jenkinsci\"\n  repository: \"docker-ssh-agent\"\n"
  },
  {
    "path": "updatecli/values.temurin.yaml",
    "content": "temurin:\n  version_pattern: \"^jdk-[17|21].(\\\\d*).(\\\\d*).(\\\\d*)(.(\\\\d*))\\\\+(\\\\d*)?$\"\n"
  },
  {
    "path": "windows/nanoserver/Dockerfile",
    "content": "# escape=`\n\n# The MIT License\n#\n#  Copyright (c) 2019-2020, Alex Earl and other Jenkins Contributors\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to deal\n#  in the Software without restriction, including without limitation the rights\n#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n#  copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n#  THE SOFTWARE.\n\nARG WINDOWS_VERSION_TAG\nARG TOOLS_WINDOWS_VERSION\nFROM mcr.microsoft.com/windows/servercore:\"${WINDOWS_VERSION_TAG}\" AS jdk-core\n\n# $ProgressPreference: https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251261324\nSHELL [\"powershell.exe\", \"-Command\", \"$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';\"]\n\nARG JAVA_VERSION\nRUN New-Item -ItemType Directory -Path C:\\temp | Out-Null ; `\n    $javaMajorVersion = $env:JAVA_VERSION.substring(0,2) ; `\n    $msiUrl = 'https://api.adoptium.net/v3/installer/version/jdk-{0}/windows/x64/jdk/hotspot/normal/eclipse?project=jdk' -f $env:JAVA_VERSION ; `\n    Invoke-WebRequest $msiUrl -OutFile 'C:\\temp\\jdk.msi' ; `\n    $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList '/i', 'C:\\temp\\jdk.msi', '/L*V', 'C:\\temp\\OpenJDK.log', '/quiet', 'ADDLOCAL=FeatureEnvironment,FeatureJarFileRunWith,FeatureJavaHome',  \"INSTALLDIR=C:\\openjdk-${javaMajorVersion}\" -Wait -Passthru ; `\n    $proc.WaitForExit() ; `\n    Remove-Item -Path C:\\temp -Recurse | Out-Null\n\nFROM mcr.microsoft.com/powershell:nanoserver-\"${TOOLS_WINDOWS_VERSION}\" AS pwsh-source\n\nFROM mcr.microsoft.com/windows/nanoserver:\"${WINDOWS_VERSION_TAG}\"\n\nARG JAVA_HOME\nENV PSHOME=\"C:\\Program Files\\PowerShell\"\nENV PATH=\"C:\\Windows\\system32;C:\\Windows;${PSHOME};\"\n\n# The nanoserver image is nice and small, but we need a couple of things to get SSH working\nCOPY --from=jdk-core /windows/system32/netapi32.dll /windows/system32/netapi32.dll\nCOPY --from=jdk-core /windows/system32/whoami.exe /windows/system32/whoami.exe\nCOPY --from=jdk-core $JAVA_HOME $JAVA_HOME\nCOPY --from=pwsh-source $PSHOME $PSHOME\n\nSHELL [\"pwsh.exe\", \"-Command\", \"$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';\"]\nUSER ContainerAdministrator\n\n# Backward compatibility with version <= 5.x: create a symlink to the \"new\" JAVA_HOME\nRUN $javaMajorVersion = $env:JAVA_HOME.Substring($env:JAVA_HOME.Length - 2); `\n    New-Item -Path \"C:\\jdk-${javaMajorVersion}\" -ItemType SymbolicLink -Value \"${env:JAVA_HOME}\"\n\n# Install git\nARG GIT_VERSION=2.54.0\nARG GIT_PATCH_VERSION=1\nRUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `\n    # The patch \"windows.1\" always have a different URL than the subsequent patch (ZIP filename is different)\n    if($env:GIT_PATCH_VERSION -eq 1) { $url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION); } `\n    else {$url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}.{1}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION)} ; `\n    Write-Host \"Retrieving $url...\" ; `\n    Invoke-WebRequest $url -OutFile 'mingit.zip' -UseBasicParsing ; `\n    Expand-Archive mingit.zip -DestinationPath c:\\mingit ; `\n    Remove-Item mingit.zip -Force\n\n# Add java & git to PATH\nENV ProgramFiles=\"C:\\Program Files\"\nENV WindowsPATH=\"C:\\Windows\\system32;C:\\Windows\"\nENV PATH=\"${WindowsPATH};${ProgramFiles}\\PowerShell;${JAVA_HOME}\\bin;C:\\mingit\\cmd\"\n\n# Install git-lfs\nARG GIT_LFS_VERSION=3.7.1\nRUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `\n    $url = $('https://github.com/git-lfs/git-lfs/releases/download/v{0}/git-lfs-windows-amd64-v{0}.zip' -f $env:GIT_LFS_VERSION) ; `\n    Write-Host \"Retrieving $url...\" ; `\n    Invoke-WebRequest $url -OutFile 'GitLfs.zip' -UseBasicParsing ; `\n    Expand-Archive GitLfs.zip -DestinationPath c:\\mingit\\mingw64\\bin ; `\n    $gitLfsFolder = 'c:\\mingit\\mingw64\\bin\\git-lfs-{0}' -f $env:GIT_LFS_VERSION ; `\n    Move-Item -Path \"${gitLfsFolder}\\git-lfs.exe\" -Destination c:\\mingit\\mingw64\\bin\\ ; `\n    Remove-Item -Path $gitLfsFolder -Recurse -Force ; `\n    Remove-Item GitLfs.zip -Force ; `\n    & C:\\mingit\\cmd\\git.exe lfs install\n\nARG user=jenkins\nARG JENKINS_AGENT_WORK=\"C:/Users/${user}/Work\"\nENV JENKINS_AGENT_USER=${user}\nENV JENKINS_AGENT_WORK=${JENKINS_AGENT_WORK}\n\n# Setup SSH server\nARG OPENSSH_VERSION=10.0.0.0p2-Preview\nRUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `\n    $url = 'https://github.com/PowerShell/Win32-OpenSSH/releases/download/{0}/OpenSSH-Win64.zip' -f $env:OPENSSH_VERSION ; `\n    Write-Host \"Retrieving $url...\" ; `\n    Invoke-WebRequest -Uri $url -OutFile C:/openssh.zip -UseBasicParsing ; `\n    Expand-Archive c:/openssh.zip 'C:/Program Files' ; `\n    Remove-Item C:/openssh.zip ; `\n    $env:PATH = '{0};{1}' -f $env:PATH,'C:\\Program Files\\OpenSSH-Win64' ; `\n    if(!(Test-Path 'C:\\ProgramData\\ssh')) { New-Item -Type Directory -Path 'C:\\ProgramData\\ssh' | Out-Null } ; `\n    icacls 'C:\\ProgramData\\ssh' /inheritance:d ; `\n    icacls 'C:\\ProgramData\\ssh' /remove 'CREATOR OWNER' ; `\n    & 'C:/Program Files/OpenSSH-Win64/Install-SSHd.ps1' ; `\n    Copy-Item 'C:\\Program Files\\OpenSSH-Win64\\sshd_config_default' 'C:\\ProgramData\\ssh\\sshd_config' ; `\n    $content = Get-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" ; `\n    $content | ForEach-Object { $_ -replace '#PermitRootLogin.*','PermitRootLogin no' `\n                        -replace '#PasswordAuthentication.*','PasswordAuthentication no' `\n                        -replace '#PermitEmptyPasswords.*','PermitEmptyPasswords no' `\n                        -replace '#PubkeyAuthentication.*','PubkeyAuthentication yes' `\n                        -replace '#SyslogFacility.*','SyslogFacility LOCAL0' `\n                        -replace '#LogLevel.*','LogLevel INFO' `\n                        -replace 'Match Group administrators','' `\n                        -replace '(\\s*)AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys','' `\n                } | `\n    Set-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value 'ChallengeResponseAuthentication no' ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value 'HostKeyAgent \\\\.\\pipe\\openssh-ssh-agent' ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value ('Match User {0}' -f $env:JENKINS_AGENT_USER) ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value ('       AuthorizedKeysFile C:/Users/{0}/.ssh/authorized_keys' -f $env:JENKINS_AGENT_USER) ; `\n    New-Item -Path HKLM:\\SOFTWARE -Name OpenSSH -Force | Out-Null ; `\n    New-ItemProperty -Path HKLM:\\SOFTWARE\\OpenSSH -Name DefaultShell -Value 'C:\\Program Files\\Powershell\\pwsh.exe' -PropertyType string -Force | Out-Null ; `\n    Remove-Item -Path \"C:\\ProgramData\\ssh\\ssh_host*_key*\"\n\nCOPY CreateProfile.psm1 C:/\n\n# Create user and user directory\nRUN Import-Module -Force C:/CreateProfile.psm1 ; `\n    New-UserWithProfile -UserName $env:JENKINS_AGENT_USER -Description 'Jenkins Agent User' ; `\n    Remove-Item -Force C:/CreateProfile.psm1\n\nVOLUME \"${JENKINS_AGENT_WORK}\" \"C:/Users/${user}/AppData/Local/Temp\"\nWORKDIR \"${JENKINS_AGENT_WORK}\"\n\nCOPY setup-sshd.ps1 C:/ProgramData/Jenkins/setup-sshd.ps1\n\nEXPOSE 22\n\nLABEL `\n    org.opencontainers.image.vendor=\"Jenkins project\" `\n    org.opencontainers.image.title=\"Official Jenkins SSH Agent Docker image\" `\n    org.opencontainers.image.description=\"A Jenkins agent image which allows using SSH to establish the connection\" `\n    org.opencontainers.image.url=\"https://www.jenkins.io/\" `\n    org.opencontainers.image.source=\"https://github.com/jenkinsci/docker-ssh-agent\" `\n    org.opencontainers.image.licenses=\"MIT\"\n\nENTRYPOINT [\"pwsh.exe\", \"-NoExit\", \"-Command\", \"& C:/ProgramData/Jenkins/setup-sshd.ps1\"]\n"
  },
  {
    "path": "windows/windowsservercore/Dockerfile",
    "content": "# escape=`\n\n# The MIT License\n#\n#  Copyright (c) 2019-2020, Alex Earl\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the \"Software\"), to deal\n#  in the Software without restriction, including without limitation the rights\n#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n#  copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n#  THE SOFTWARE.\n\nARG WINDOWS_VERSION_TAG\nARG TOOLS_WINDOWS_VERSION\nFROM mcr.microsoft.com/windows/servercore:\"${WINDOWS_VERSION_TAG}\" AS jdk-core\n\n# $ProgressPreference: https://github.com/PowerShell/PowerShell/issues/2138#issuecomment-251261324\nSHELL [\"powershell\", \"-Command\", \"$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';\"]\n\nARG JAVA_VERSION\nRUN New-Item -ItemType Directory -Path C:\\temp | Out-Null ; `\n    $javaMajorVersion = $env:JAVA_VERSION.substring(0,2) ; `\n    $msiUrl = 'https://api.adoptium.net/v3/installer/version/jdk-{0}/windows/x64/jdk/hotspot/normal/eclipse?project=jdk' -f $env:JAVA_VERSION ; `\n    Invoke-WebRequest $msiUrl -OutFile 'C:\\temp\\jdk.msi' ; `\n    $proc = Start-Process -FilePath 'msiexec.exe' -ArgumentList '/i', 'C:\\temp\\jdk.msi', '/L*V', 'C:\\temp\\OpenJDK.log', '/quiet', 'ADDLOCAL=FeatureEnvironment,FeatureJarFileRunWith,FeatureJavaHome',  \"INSTALLDIR=C:\\openjdk-${javaMajorVersion}\" -Wait -Passthru ; `\n    $proc.WaitForExit() ; `\n    Remove-Item -Path C:\\temp -Recurse | Out-Null\n\nFROM mcr.microsoft.com/windows/servercore:\"${WINDOWS_VERSION_TAG}\"\n\nARG JAVA_HOME\nENV JAVA_HOME=${JAVA_HOME}\n\nCOPY --from=jdk-core $JAVA_HOME $JAVA_HOME\n\nSHELL [\"powershell\", \"-Command\", \"$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';\"]\n\nARG user=jenkins\nARG JENKINS_AGENT_WORK=\"C:/Users/${user}/Work\"\nENV JENKINS_AGENT_USER=${user}\nENV JENKINS_AGENT_WORK=${JENKINS_AGENT_WORK}\n\nUSER ContainerAdministrator\n\n# Install git\nARG GIT_VERSION=2.54.0\nARG GIT_PATCH_VERSION=1\nRUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `\n    # The patch \"windows.1\" always have a different URL than the subsequent patch (ZIP filename is different)\n    if($env:GIT_PATCH_VERSION -eq 1) { $url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION); } `\n    else {$url = $('https://github.com/git-for-windows/git/releases/download/v{0}.windows.{1}/MinGit-{0}.{1}-64-bit.zip' -f $env:GIT_VERSION, $env:GIT_PATCH_VERSION)} ; `\n    Write-Host \"Retrieving $url...\" ; `\n    Invoke-WebRequest $url -OutFile 'mingit.zip' -UseBasicParsing ; `\n    Expand-Archive mingit.zip -DestinationPath c:\\mingit ; `\n    Remove-Item mingit.zip -Force\n\n# Install git-lfs\nARG GIT_LFS_VERSION=3.7.1\nRUN $CurrentPath = (Get-Itemproperty -path 'hklm:\\system\\currentcontrolset\\control\\session manager\\environment' -Name Path).Path ; `\n    $NewPath = $CurrentPath + ';{0}\\bin;C:\\mingit\\cmd' -f $env:JAVA_HOME ; `\n    Set-ItemProperty -path 'hklm:\\system\\currentcontrolset\\control\\session manager\\environment' -Name Path -Value $NewPath ; `\n    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `\n    $url = $('https://github.com/git-lfs/git-lfs/releases/download/v{0}/git-lfs-windows-amd64-v{0}.zip' -f $env:GIT_LFS_VERSION) ; `\n    Write-Host \"Retrieving $url...\" ; `\n    Invoke-WebRequest $url -OutFile 'GitLfs.zip' -UseBasicParsing ; `\n    Expand-Archive GitLfs.zip -DestinationPath c:\\mingit\\mingw64\\bin ; `\n    $gitLfsFolder = 'c:\\mingit\\mingw64\\bin\\git-lfs-{0}' -f $env:GIT_LFS_VERSION ; `\n    Move-Item -Path \"${gitLfsFolder}\\git-lfs.exe\" -Destination c:\\mingit\\mingw64\\bin\\ ; `\n    Remove-Item -Path $gitLfsFolder -Recurse -Force ; `\n    Remove-Item GitLfs.zip -Force ; `\n    & C:\\mingit\\cmd\\git.exe lfs install\n\n# Setup SSH server\nARG OPENSSH_VERSION=10.0.0.0p2-Preview\nRUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; `\n    $url = 'https://github.com/PowerShell/Win32-OpenSSH/releases/download/{0}/OpenSSH-Win64.zip' -f $env:OPENSSH_VERSION ; `\n    Write-Host \"Retrieving $url...\" ; `\n    Invoke-WebRequest -Uri $url -OutFile C:/openssh.zip -UseBasicParsing ; `\n    Expand-Archive c:/openssh.zip 'C:/Program Files' ; `\n    Remove-Item C:/openssh.zip ; `\n    $env:PATH = '{0};{1}' -f $env:PATH,'C:\\Program Files\\OpenSSH-Win64' ; `\n    & 'C:/Program Files/OpenSSH-Win64/install-sshd.ps1' ; `\n    if(!(Test-Path 'C:\\ProgramData\\ssh')) { New-Item -Type Directory -Path 'C:\\ProgramData\\ssh' | Out-Null } ; `\n    Copy-Item 'C:\\Program Files\\OpenSSH-Win64\\sshd_config_default' 'C:\\ProgramData\\ssh\\sshd_config' ; `\n    $content = Get-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" ; `\n    $content | ForEach-Object { $_ -replace '#PermitRootLogin.*','PermitRootLogin no' `\n                        -replace '#PasswordAuthentication.*','PasswordAuthentication no' `\n                        -replace '#PermitEmptyPasswords.*','PermitEmptyPasswords no' `\n                        -replace '#PubkeyAuthentication.*','PubkeyAuthentication yes' `\n                        -replace '#SyslogFacility.*','SyslogFacility LOCAL0' `\n                        -replace '#LogLevel.*','LogLevel INFO' `\n                        -replace 'Match Group administrators','' `\n                        -replace '(\\s*)AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys','' `\n                } | `\n    Set-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value 'ChallengeResponseAuthentication no' ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value 'HostKeyAgent \\\\.\\pipe\\openssh-ssh-agent' ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value ('Match User {0}' -f $env:JENKINS_AGENT_USER) ; `\n    Add-Content -Path \"C:\\ProgramData\\ssh\\sshd_config\" -Value ('       AuthorizedKeysFile C:/Users/{0}/.ssh/authorized_keys' -f $env:JENKINS_AGENT_USER) ; `\n    New-Item -Path HKLM:\\SOFTWARE -Name OpenSSH -Force | Out-Null ; `\n    New-ItemProperty -Path HKLM:\\SOFTWARE\\OpenSSH -Name DefaultShell -Value 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' -PropertyType string -Force | Out-Null ; `\n    Remove-Item -Path \"C:\\ProgramData\\ssh\\ssh_host*_key*\"\n\nCOPY CreateProfile.psm1 C:/\n\n# Create user and user directory\nRUN Import-Module -Force C:/CreateProfile.psm1 ; `\n    New-UserWithProfile -UserName $env:JENKINS_AGENT_USER -Description 'Jenkins Agent User' ; `\n    Remove-Item -Force C:/CreateProfile.psm1\n\nVOLUME \"${JENKINS_AGENT_WORK}\" \"C:/Users/${user}/AppData/Local/Temp\"\nWORKDIR \"${JENKINS_AGENT_WORK}\"\n\nCOPY setup-sshd.ps1 C:/ProgramData/Jenkins/setup-sshd.ps1\n\nEXPOSE 22\n\nLABEL `\n    org.opencontainers.image.vendor=\"Jenkins project\" `\n    org.opencontainers.image.title=\"Official Jenkins SSH Agent Docker image\" `\n    org.opencontainers.image.description=\"A Jenkins agent image which allows using SSH to establish the connection\" `\n    org.opencontainers.image.url=\"https://www.jenkins.io/\" `\n    org.opencontainers.image.source=\"https://github.com/jenkinsci/docker-ssh-agent\" `\n    org.opencontainers.image.licenses=\"MIT\"\n\nENTRYPOINT [\"powershell.exe\", \"-NoExit\", \"-Command\", \"& C:/ProgramData/Jenkins/setup-sshd.ps1\"]\n"
  }
]