Official Linux pull_request #2141
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Official Linux package build | |
| run-name: ${{ inputs.release_id && format('Official Linux campaign {0}', inputs.release_id) || format('Official Linux {0}', github.event_name) }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_id: | |
| description: Exact signed release identity selected by watchdog | |
| required: true | |
| type: string | |
| version: | |
| description: Expected signed package version | |
| required: true | |
| type: string | |
| amd64_repository_path: | |
| description: Expected signed amd64 repository path | |
| required: true | |
| type: string | |
| amd64_sha256: | |
| description: Expected signed amd64 SHA-256 | |
| required: true | |
| type: string | |
| arm64_repository_path: | |
| description: Expected signed arm64 repository path | |
| required: true | |
| type: string | |
| arm64_sha256: | |
| description: Expected signed arm64 SHA-256 | |
| required: true | |
| type: string | |
| pull_request: | |
| push: | |
| branches: [main] | |
| paths: | |
| - install.sh | |
| - Makefile | |
| - assets/openai-codex-linux-repository-key.gpg.base64 | |
| - launcher/** | |
| - scripts/build-*.sh | |
| - scripts/lib/package-common.sh | |
| - scripts/lib/upstream-linux-package.* | |
| - scripts/automation/upstream-linux-package-watchdog/** | |
| - scripts/patches/** | |
| - linux-features/** | |
| - packaging/** | |
| - updater/** | |
| - flake.nix | |
| - nix/** | |
| - .github/workflows/upstream-build-app.yml | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: official-linux-package-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| signed-baseline: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| architecture: [amd64, arm64] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version: 24 | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y dpkg-dev gnupg patchelf | |
| - name: Resolve signed package | |
| id: upstream | |
| run: | | |
| set -euo pipefail | |
| mkdir -p upstream | |
| package="$(node scripts/lib/upstream-linux-package.js \ | |
| --output-dir upstream/${{ matrix.architecture }} \ | |
| --metadata upstream/${{ matrix.architecture }}.json \ | |
| --key-base64 assets/openai-codex-linux-repository-key.gpg.base64 \ | |
| --arch ${{ matrix.architecture }})" | |
| echo "package=$package" >> "$GITHUB_OUTPUT" | |
| - name: Require the dispatched signed campaign | |
| if: github.event_name == 'workflow_dispatch' | |
| env: | |
| EXPECTED_RELEASE_ID: ${{ inputs.release_id }} | |
| EXPECTED_VERSION: ${{ inputs.version }} | |
| EXPECTED_REPOSITORY_PATH: ${{ matrix.architecture == 'amd64' && inputs.amd64_repository_path || inputs.arm64_repository_path }} | |
| EXPECTED_SHA256: ${{ matrix.architecture == 'amd64' && inputs.amd64_sha256 || inputs.arm64_sha256 }} | |
| run: | | |
| node - "upstream/${{ matrix.architecture }}.json" <<'NODE' | |
| const fs = require("node:fs"); | |
| const metadata = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); | |
| if (metadata.version !== process.env.EXPECTED_VERSION | |
| || metadata.repositoryPath !== process.env.EXPECTED_REPOSITORY_PATH | |
| || metadata.sha256 !== process.env.EXPECTED_SHA256) { | |
| throw new Error("resolved signed package does not match the dispatched campaign"); | |
| } | |
| if (!/^[0-9a-f]{64}$/.test(process.env.EXPECTED_RELEASE_ID)) { | |
| throw new Error("invalid dispatched release identity"); | |
| } | |
| NODE | |
| - name: Build byte-identical clean baseline | |
| env: | |
| CODEX_TARGET_ARCH: ${{ matrix.architecture }} | |
| CODEX_LINUX_FEATURES_CONFIG: ${{ github.workspace }}/linux-features/features.example.json | |
| CODEX_INSTALL_DIR: ${{ github.workspace }}/codex-app-${{ matrix.architecture }} | |
| run: | | |
| ./install.sh "${{ steps.upstream.outputs.package }}" | |
| upstream_root="$(mktemp -d)" | |
| dpkg-deb -x "${{ steps.upstream.outputs.package }}" "$upstream_root" | |
| cmp "$upstream_root/usr/lib/chatgpt/resources/app.asar" "$CODEX_INSTALL_DIR/resources/app.asar" | |
| test -x "$CODEX_INSTALL_DIR/ChatGPT" | |
| test -x "$CODEX_INSTALL_DIR/resources/codex" | |
| - name: Validate the Nix ELF contract against the official payload | |
| env: | |
| CODEX_INSTALL_DIR: ${{ github.workspace }}/codex-app-${{ matrix.architecture }} | |
| run: | | |
| set -euo pipefail | |
| node nix/elf-runtime.cjs validate-upstream \ | |
| --root "$CODEX_INSTALL_DIR" \ | |
| --arch "${{ matrix.architecture }}" | |
| fix_root="$(mktemp -d)" | |
| cp -a "$CODEX_INSTALL_DIR/." "$fix_root/" | |
| chmod -R u+w "$fix_root" | |
| if [ "${{ matrix.architecture }}" = amd64 ]; then | |
| dynamic_linker=/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-glibc/lib/ld-linux-x86-64.so.2 | |
| else | |
| dynamic_linker=/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-glibc/lib/ld-linux-aarch64.so.1 | |
| fi | |
| runtime_path=/nix/store/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-runtime/lib | |
| node nix/elf-runtime.cjs fix \ | |
| --root "$fix_root" \ | |
| --arch "${{ matrix.architecture }}" \ | |
| --dynamic-linker "$dynamic_linker" \ | |
| --runtime-library-path "$runtime_path" \ | |
| --patchelf "$(command -v patchelf)" \ | |
| --chatgpt-relocator nix/relocate-elf-interpreter.cjs | |
| node nix/elf-runtime.cjs audit \ | |
| --root "$fix_root" \ | |
| --arch "${{ matrix.architecture }}" \ | |
| --dynamic-linker "$dynamic_linker" \ | |
| --runtime-library-path "$runtime_path" \ | |
| --check-dependencies false \ | |
| --check-shebangs false | |
| - name: Audit every ASAR feature against the official bundle | |
| if: matrix.architecture == 'amd64' | |
| run: | | |
| set -euo pipefail | |
| upstream_root="$(mktemp -d)" | |
| extracted_asar="$(mktemp -d)" | |
| dpkg-deb -x "${{ steps.upstream.outputs.package }}" "$upstream_root" | |
| npx --yes @electron/asar extract \ | |
| "$upstream_root/usr/lib/chatgpt/resources/app.asar" \ | |
| "$extracted_asar" | |
| node scripts/ci/verify-official-linux-features.js "$extracted_asar" | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: official-linux-${{ matrix.architecture }}-metadata | |
| path: upstream/${{ matrix.architecture }}.json | |
| retention-days: 7 | |
| package-matrix: | |
| needs: signed-baseline | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - architecture: amd64 | |
| runner: ubuntu-latest | |
| - architecture: arm64 | |
| runner: ubuntu-24.04-arm | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version: 24 | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y dpkg-dev gnupg rpm | |
| - name: Resolve exact package matrix input | |
| id: upstream | |
| env: | |
| EXPECTED_VERSION: ${{ inputs.version }} | |
| EXPECTED_REPOSITORY_PATH: ${{ matrix.architecture == 'amd64' && inputs.amd64_repository_path || inputs.arm64_repository_path }} | |
| EXPECTED_SHA256: ${{ matrix.architecture == 'amd64' && inputs.amd64_sha256 || inputs.arm64_sha256 }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p upstream | |
| package="$(node scripts/lib/upstream-linux-package.js \ | |
| --output-dir upstream/${{ matrix.architecture }} \ | |
| --metadata upstream/${{ matrix.architecture }}.json \ | |
| --key-base64 assets/openai-codex-linux-repository-key.gpg.base64 \ | |
| --arch ${{ matrix.architecture }})" | |
| echo "package=$package" >> "$GITHUB_OUTPUT" | |
| node - "upstream/${{ matrix.architecture }}.json" <<'NODE' | |
| const fs = require("node:fs"); | |
| const metadata = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); | |
| if (process.env.GITHUB_EVENT_NAME === "workflow_dispatch" | |
| && (metadata.version !== process.env.EXPECTED_VERSION | |
| || metadata.repositoryPath !== process.env.EXPECTED_REPOSITORY_PATH | |
| || metadata.sha256 !== process.env.EXPECTED_SHA256)) { | |
| throw new Error("package matrix input does not match the dispatched campaign"); | |
| } | |
| NODE | |
| - name: Build baseline and package formats | |
| env: | |
| CODEX_LINUX_FEATURES_CONFIG: ${{ github.workspace }}/linux-features/features.example.json | |
| CODEX_TARGET_ARCH: ${{ matrix.architecture }} | |
| PACKAGE_WITH_UPDATER: '0' | |
| run: | | |
| ./install.sh "${{ steps.upstream.outputs.package }}" | |
| ./scripts/build-deb.sh | |
| ./scripts/build-rpm.sh | |
| PACMAN_STAGE_ONLY=1 ./scripts/build-pacman.sh | |
| APPIMAGE_STAGE_ONLY=1 ./scripts/build-appimage.sh | |
| dock-icon-feature-alone: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| with: | |
| ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version: 24 | |
| - name: Install build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y dpkg-dev gnupg | |
| - name: Build Dock icon feature alone from the signed package | |
| env: | |
| CODEX_LINUX_FEATURES_CONFIG: ${{ github.workspace }}/dock-icon-features.json | |
| CODEX_INSTALL_DIR: ${{ github.workspace }}/codex-app-dock-icon | |
| CODEX_PATCH_REPORT_JSON: ${{ github.workspace }}/dock-icon-report/patch-report.json | |
| CODEX_TARGET_ARCH: amd64 | |
| run: | | |
| set -euo pipefail | |
| cat > "$CODEX_LINUX_FEATURES_CONFIG" <<'JSON' | |
| { | |
| "enabled": ["ui-tweaks"], | |
| "settings": { | |
| "ui-tweaks": { | |
| "tweaks": { | |
| "appearance": { | |
| "dockIcon": { "enabled": true } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| JSON | |
| mkdir -p upstream | |
| package="$(node scripts/lib/upstream-linux-package.js \ | |
| --output-dir upstream/amd64 \ | |
| --metadata upstream/amd64.json \ | |
| --key-base64 assets/openai-codex-linux-repository-key.gpg.base64 \ | |
| --arch amd64)" | |
| ./install.sh "$package" | |
| node scripts/ci/validate-patch-report.js "$CODEX_PATCH_REPORT_JSON" \ | |
| --require-enabled-feature ui-tweaks \ | |
| --require-applied feature:ui-tweaks:appearance-dock-icon-main-process \ | |
| --require-applied feature:ui-tweaks:appearance-dock-icon-settings-row | |
| node - "$CODEX_PATCH_REPORT_JSON" <<'NODE' | |
| const fs = require("node:fs"); | |
| const report = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); | |
| const dock = report.patches.filter(({ name }) => name.includes(":appearance-dock-icon-")); | |
| if (dock.length !== 2 || dock.some(({ status }) => status !== "applied")) { | |
| throw new Error(`Expected Dock descriptors 2/2 applied, got ${JSON.stringify(dock)}`); | |
| } | |
| NODE | |
| payload="$CODEX_INSTALL_DIR/resources/dock-icon" | |
| diff -u <(printf '%s\n' \ | |
| icon-chatgpt.png \ | |
| icon-codex-dark-color.png \ | |
| icon-codex-light.png \ | |
| sync-desktop-icon.sh | sort) \ | |
| <(find "$payload" -mindepth 1 -maxdepth 1 -printf '%f\n' | sort) | |
| for resource in \ | |
| icon-chatgpt.png \ | |
| icon-codex-dark-color.png \ | |
| icon-codex-light.png \ | |
| sync-desktop-icon.sh; do | |
| test -f "$payload/$resource" | |
| test ! -L "$payload/$resource" | |
| done | |
| upstream_root="$(mktemp -d)" | |
| dpkg-deb -x "$package" "$upstream_root" | |
| cmp "$upstream_root/usr/lib/chatgpt/resources/icon-chatgpt.png" "$payload/icon-chatgpt.png" | |
| cmp assets/codex-linux.png "$payload/icon-codex-dark-color.png" | |
| cmp assets/codex-linux.png "$payload/icon-codex-light.png" | |
| cmp linux-features/ui-tweaks/sync-desktop-icon.sh "$payload/sync-desktop-icon.sh" | |
| test -x "$payload/sync-desktop-icon.sh" | |
| hook="$CODEX_INSTALL_DIR/.codex-linux/prelaunch.d/ui-tweaks-dock-icon-cleanup.sh" | |
| cmp linux-features/ui-tweaks/sync-desktop-icon.sh "$hook" | |
| test -x "$hook" | |
| watchdog: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 | |
| with: | |
| node-version: 24 | |
| - name: Resolve and bind the two-architecture campaign | |
| env: | |
| EXPECTED_RELEASE_ID: ${{ inputs.release_id }} | |
| EXPECTED_VERSION: ${{ inputs.version }} | |
| EXPECTED_AMD64_PATH: ${{ inputs.amd64_repository_path }} | |
| EXPECTED_AMD64_SHA256: ${{ inputs.amd64_sha256 }} | |
| EXPECTED_ARM64_PATH: ${{ inputs.arm64_repository_path }} | |
| EXPECTED_ARM64_SHA256: ${{ inputs.arm64_sha256 }} | |
| run: | | |
| set -euo pipefail | |
| result="$RUNNER_TEMP/official-linux-release.json" | |
| node scripts/automation/upstream-linux-package-watchdog/watchdog.js --json > "$result" | |
| cat "$result" | |
| node - "$result" <<'NODE' | |
| const fs = require("node:fs"); | |
| const actual = JSON.parse(fs.readFileSync(process.argv[2], "utf8")).release; | |
| const expected = process.env; | |
| if (process.env.GITHUB_EVENT_NAME === "workflow_dispatch" | |
| && (!actual || actual.releaseId !== expected.EXPECTED_RELEASE_ID | |
| || actual.version !== expected.EXPECTED_VERSION | |
| || actual.packages?.amd64?.repositoryPath !== expected.EXPECTED_AMD64_PATH | |
| || actual.packages?.amd64?.sha256 !== expected.EXPECTED_AMD64_SHA256 | |
| || actual.packages?.arm64?.repositoryPath !== expected.EXPECTED_ARM64_PATH | |
| || actual.packages?.arm64?.sha256 !== expected.EXPECTED_ARM64_SHA256)) { | |
| throw new Error("two-architecture signed release does not match dispatched campaign"); | |
| } | |
| NODE | |
| official-linux-gate: | |
| if: ${{ always() }} | |
| needs: | |
| - signed-baseline | |
| - package-matrix | |
| - dock-icon-feature-alone | |
| - watchdog | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Require every official Linux validation job | |
| env: | |
| SIGNED_BASELINE_RESULT: ${{ needs.signed-baseline.result }} | |
| PACKAGE_MATRIX_RESULT: ${{ needs.package-matrix.result }} | |
| DOCK_ICON_FEATURE_ALONE_RESULT: ${{ needs.dock-icon-feature-alone.result }} | |
| WATCHDOG_RESULT: ${{ needs.watchdog.result }} | |
| run: | | |
| test "$SIGNED_BASELINE_RESULT" = success | |
| test "$PACKAGE_MATRIX_RESULT" = success | |
| test "$DOCK_ICON_FEATURE_ALONE_RESULT" = success | |
| test "$WATCHDOG_RESULT" = success |