Skip to content

Official Linux pull_request #2141

Official Linux pull_request

Official Linux pull_request #2141

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