name: SSH hostile hosts # Hostile-host matrix: the real client-side managed orcad deploy against container SSH targets # (old glibc, musl, no libstdc++, noexec home, no egress on either side) and against each macOS runner's own # loopback sshd, asserting the runtime each host resolves and whether orcad activates there. # Heavy (three slot builds plus eight images), so it runs only when the deploy, the runtime store # or the slot build changes, and on demand. on: pull_request: types: [opened, synchronize, reopened, ready_for_review, converted_to_draft] paths: # The managed deploy runs on these remote install and runtime primitives. - 'src/main/ssh/remote-*' - 'src/main/ssh/*runtime*' - 'src/main/ssh/orcad-*' - 'src/main/managed-server/**' - 'src/main/orcad/orcad-artifact-materializer.ts' - 'src/main/orcad/orcad-host-unavailable.ts' - 'src/main/node-runtime/**' - 'src/main/ssh/ssh-remote-*' - 'src/main/ssh/hostile-host-test-support/**' - 'src/main/ssh/sftp-*' - 'src/shared/node-runtime-pin.ts' - 'src/shared/orcad-artifacts.ts' - 'config/scripts/build-orcad-*.mjs' - 'config/scripts/orcad-prebuild-*.mjs' - 'config/scripts/verify-packaged-orcad-template.cjs' - 'config/patches/node-pty*' # The deploy runs over this SSH transport. - 'src/main/ssh/connection/**' - 'src/main/ssh/ssh-reconnect-error-classification.ts' - 'src/main/ssh/ssh-posix-command-wrapper.ts' - 'src/shared/shell-argument-quoting.ts' - '!src/**/*.test.ts' - 'src/main/ssh/hostile-host-test-support/ssh-hostile-hosts.docker.test.ts' - '.github/workflows/ssh-hostile-hosts.yml' - '.github/actions/restore-pnpm-verification/**' workflow_dispatch: inputs: cells: description: Comma-separated Docker cell ids from src/main/ssh/hostile-host-test-support/ssh-hostile-host-cells.ts; empty runs all. macOS cells always run on their own runners. required: false default: '' permissions: contents: read concurrency: group: ssh-hostile-hosts-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: glibc_slot: # A draft carries no verdict; readiness re-triggers this workflow. if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft != true }} runs-on: ubuntu-22.04 # Same glibc 2.28 builder as the headless-server floor lane, so the slot loads on Debian 10. container: quay.io/pypa/manylinux_2_28_x86_64@sha256:407f771c51a2c3e83ebe5a7970b4289ead3a6db21d9b9c089168775cad11d328 timeout-minutes: 25 env: ORCA_BACKGROUND_LAUNCH: '1' CC: gcc CXX: g++ PYTHON: /opt/python/cp312-cp312/bin/python3 steps: - name: Install glibc 2.28 prerequisites run: | missing_tool=false for tool in git ps unzip which xz; do if ! command -v "$tool" >/dev/null 2>&1; then missing_tool=true fi done if [ "$missing_tool" = true ]; then # The image's source-built Git needs no RPM; missing tools come from AlmaLinux. dnf --disablerepo='epel*' install -y git procps-ng unzip which xz fi - uses: actions/checkout@v6 with: persist-credentials: false - name: Trust the checked-out workspace run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - uses: ./.github/actions/install-node-dependencies - name: Build and smoke the linux-x64-glibc slot run: | pnpm build:orcad-prebuilds --slot=linux-x64-glibc pnpm build:orcad-prebuilds --require-slots linux-x64-glibc pnpm build:orcad-prebuilds --slot=linux-x64-glibc --smoke - uses: actions/upload-artifact@v7 with: name: hostile-hosts-glibc-slot path: out/orcad-prebuilds include-hidden-files: true retention-days: 1 if-no-files-found: error musl_slot: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft != true }} runs-on: ubuntu-22.04 timeout-minutes: 25 steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: ./.github/actions/restore-pnpm-verification id: pnpm-verification with: container-toolchain: 'true' - name: Build and smoke the linux-x64-musl slot on Alpine env: VERIFICATION_CACHE_PATH: ${{ steps.pnpm-verification.outputs.path }} run: | touch "$VERIFICATION_CACHE_PATH" # Same digest as the headless-server musl lane; re-resolve it whenever NODE_RUNTIME_PIN moves. docker run --rm --init -i \ -e ORCA_BACKGROUND_LAUNCH=1 \ -v "$VERIFICATION_CACHE_PATH:/root/.cache/pnpm/lockfile-verified.jsonl" \ -v "$GITHUB_WORKSPACE:/work" -w /work \ node:24.22.1-alpine@sha256:6a1c190e17215d844926b0677f4bf87273ff7d5e54db06d3b5b5863ab0b09a3d sh -s <<'MUSL_SLOT' set -eu apk add --no-cache bash git libstdc++ python3 make g++ git config --global --add safe.directory /work npm install -g "$(node -p "require('./package.json').packageManager.split('+')[0]")" pnpm install --frozen-lockfile --ignore-scripts pnpm build:orcad-prebuilds --slot=linux-x64-musl pnpm build:orcad-prebuilds --require-slots linux-x64-musl pnpm build:orcad-prebuilds --slot=linux-x64-musl --smoke MUSL_SLOT - uses: actions/upload-artifact@v7 with: name: hostile-hosts-musl-slot path: out/orcad-prebuilds include-hidden-files: true retention-days: 1 if-no-files-found: error # The CentOS 7 cell lands on the glibc 2.17 compat slot, built exactly as the # headless-server compat lane builds it (see linux_glibc217_compat in node-server-tests.yml). glibc217_slot: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft != true }} runs-on: ubuntu-22.04 timeout-minutes: 25 env: ORCA_BACKGROUND_LAUNCH: '1' steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: ./.github/actions/install-node-dependencies - name: Build and smoke the glibc 2.17 compat slot under the glibc-217 Node run: | compat_node="$(node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 --print-runtime)" case "$compat_node" in "$GITHUB_WORKSPACE"/*) ;; *) echo "glibc-217 Node cached outside the workspace: $compat_node" >&2; exit 1 ;; esac docker run --rm --init -i \ -e ORCA_BACKGROUND_LAUNCH=1 \ -e COMPAT_NODE="/work/${compat_node#"$GITHUB_WORKSPACE"/}" \ -e CC=gcc -e CXX=g++ \ -e PYTHON=/opt/python/cp312-cp312/bin/python3 \ -v "$GITHUB_WORKSPACE:/work" -w /work \ quay.io/pypa/manylinux2014_x86_64@sha256:6f74cabeac2432570aa4bfdb29f7c1f30313d4d6654d764c44e574b6ffdd4ed5 bash -s <<'GLIBC217_COMPAT_SLOT' set -eu export PATH="$(dirname "$COMPAT_NODE"):$PATH" node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 node config/scripts/build-orcad-prebuilds.mjs --require-slots linux-x64-glibc217 # The image's devtoolset LD_LIBRARY_PATH must not stand in for a host C++ runtime. env -u LD_LIBRARY_PATH node config/scripts/build-orcad-prebuilds.mjs --slot=linux-x64-glibc217 --smoke GLIBC217_COMPAT_SLOT - uses: actions/upload-artifact@v7 with: name: hostile-hosts-glibc217-slot path: out/orcad-prebuilds include-hidden-files: true retention-days: 1 if-no-files-found: error hosts: needs: [glibc_slot, musl_slot, glibc217_slot] runs-on: ubuntu-24.04 timeout-minutes: 60 env: ORCA_BACKGROUND_LAUNCH: '1' steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: ./.github/actions/install-node-dependencies with: native-runtime: node - uses: actions/download-artifact@v8 with: pattern: hostile-hosts-*-slot path: ${{ runner.temp }}/hostile-host-prebuild-lanes - name: Merge verified Linux slots run: | node config/scripts/merge-orcad-prebuilds.mjs "$RUNNER_TEMP"/hostile-host-prebuild-lanes/* pnpm build:orcad-prebuilds --require-slots linux-x64-glibc,linux-x64-musl,linux-x64-glibc217 # Only the x64 Linux slots exist here, so the template is built for those two, and # glibc217 is staged beside its base. - uses: ./.github/actions/build-mobile-web-bundle - name: Build the orcad template run: node config/scripts/build-orcad-template.mjs --targets linux-x64-glibc,linux-x64-musl - name: Run the hostile-host matrix env: ORCA_RUN_SSH_HOSTILE_HOSTS: '1' ORCA_SSH_HOSTILE_HOST_CELLS: ${{ github.event.inputs.cells || '' }} run: pnpm test:node src/main/ssh/hostile-host-test-support/ssh-hostile-hosts.docker.test.ts # macOS: the runner is the SSH host. A user-level sshd on a loopback port logs in # as the runner user with PATH cut to the toolchain shims and /usr/bin:/bin, so Homebrew's node # and npm are unreachable; see ssh-hostile-host-local-sshd.ts for what SIP keeps in /usr/bin. macos_hosts: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft != true }} strategy: fail-fast: false matrix: include: - os: macos-15 target: darwin-arm64 cell: macos-arm64-local-sshd - os: macos-15-intel target: darwin-x64 cell: macos-x64-local-sshd runs-on: ${{ matrix.os }} timeout-minutes: 40 env: ORCA_BACKGROUND_LAUNCH: '1' steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: ./.github/actions/install-node-dependencies with: native-runtime: node - uses: ./.github/actions/build-mobile-web-bundle - name: Build and smoke this runner's slot and the orcad template run: | pnpm build:orcad-prebuilds pnpm build:orcad-prebuilds --require-slots ${{ matrix.target }} pnpm build:orcad-prebuilds --smoke node config/scripts/build-orcad-template.mjs --targets ${{ matrix.target }} - name: Run the macOS hostile-host cell env: ORCA_RUN_SSH_HOSTILE_HOSTS: '1' ORCA_SSH_HOSTILE_HOST_CELLS: ${{ matrix.cell }} run: pnpm test:node src/main/ssh/hostile-host-test-support/ssh-hostile-hosts.docker.test.ts