Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ on:
- 'scripts/**'
- '.github/workflows/**'
- 'compose.yaml'
- 'docker-compose.yaml'

jobs:
test-build:
name: Build & smoke (no publish)
uses: ./.github/workflows/build.yml
permissions:
contents: read
packages: write
id-token: write
attestations: write
with:
version: '2.40.0'
commit: '35ff3a462e786bd5efc394dfb1a8a5cc946e454f'
version: '2.54.0'
commit: '979b005c219468903cff9c5cf3276eb15caad3fa'
publish: false
secrets: inherit
46 changes: 40 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@ WORKDIR /home/bun/app

# Upstream sources are fetched from the exact verified tag; the build context carries
# only this packaging repo (workflows, scripts, docs) — never a modified copy of OpenCodex.
ARG UPSTREAM_VERSION
# If UPSTREAM_VERSION is not provided, defaults to the latest stable tag (auto-resolved).
ARG UPSTREAM_VERSION=latest
ARG UPSTREAM_COMMIT
RUN git clone --depth 1 --branch "v${UPSTREAM_VERSION}" \
https://github.com/lidge-jun/opencodex.git /tmp/opencodex-src \
RUN set -euo pipefail \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a shell that supports pipefail

The Dockerfile does not set SHELL, so this RUN instruction uses Linux's default /bin/sh; in the Debian-based Bun image that is dash, which rejects set -o pipefail with Illegal option. Consequently, the new source-fetch layer aborts before resolving or cloning upstream on every local and CI image build. Invoke Bash explicitly or remove pipefail while retaining appropriate pipeline error handling.

Useful? React with 👍 / 👎.

&& if [ "${UPSTREAM_VERSION}" = "latest" ]; then \
UPSTREAM_VERSION="$(curl -fsSL https://registry.npmjs.org/@bitkyc08%2Fopencodex/latest | node -e 'let r=\"\";process.stdin.on(\"data\",c=>r+=c).on(\"end\",()=>{const p=JSON.parse(r);process.stdout.write(p.version)})')"; \
echo "Resolved latest upstream version: v${UPSTREAM_VERSION}"; \
fi \
&& git clone --depth 1 --branch "v${UPSTREAM_VERSION}" \
https://github.com/lidge-jun/opencodex.git /tmp/opencodex-src \
&& cd /tmp/opencodex-src \
&& test "$(node -p "require('./package.json').version")" = "${UPSTREAM_VERSION}" \
&& test "$(git rev-parse HEAD)" = "${UPSTREAM_COMMIT}" \
&& echo "upstream verified: v${UPSTREAM_VERSION} @ ${UPSTREAM_COMMIT}"
&& if [ -n "${UPSTREAM_COMMIT:-}" ]; then \
test "$(git rev-parse HEAD)" = "${UPSTREAM_COMMIT}"; \
fi \
&& echo "upstream verified: v${UPSTREAM_VERSION} @ $(git rev-parse HEAD)"

COPY --chown=bun:bun scripts/verify-version.sh /tmp/verify-version.sh
RUN chmod +x /tmp/verify-version.sh
Expand All @@ -46,7 +54,33 @@ RUN rm -rf /tmp/opencodex-src/node_modules/@typescript /tmp/opencodex-src/node_m

FROM ${BUN_IMAGE} AS runtime
# Apply Debian security updates to patch base image CVEs (e.g. openssl, util-linux)
RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
# Install nodejs, npm, zstd, and ca-certificates for coding CLI runtimes (codex, claude, grok)
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends \
nodejs \
npm \
zstd \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Install official AI coding CLIs into global PATH:
# 1. codex: @openai/codex
# 2. claude: @anthropic-ai/claude-code
# 3. grok: official xAI standalone multi-arch binary (linux-x86_64 / linux-aarch64)
ARG TARGETARCH
ARG GROK_VERSION=1.0.30
RUN npm install -g @openai/codex @anthropic-ai/claude-code \
&& rm -rf /root/.npm \
&& GROK_ARCH="$(case \"${TARGETARCH:-$(dpkg --print-architecture)}\" in amd64) echo x86_64 ;; arm64) echo aarch64 ;; *) echo \"unsupported: ${TARGETARCH}\" >&2; exit 1 ;; esac)" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the escaped quotes from the architecture selector

Every image build fails at this layer: the \" sequences are passed literally to /bin/sh, so the case subject becomes "amd64" (or "arm64") rather than amd64/arm64. It consequently reaches the default branch and exits with unsupported, blocking the amd64 smoke build in .github/workflows/build.yml as well as multi-arch publishing.

Useful? React with 👍 / 👎.

&& curl -fsSL "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-${GROK_VERSION}-linux-${GROK_ARCH}.zst" -o /tmp/grok.zst \
&& zstd -d /tmp/grok.zst -o /usr/local/bin/grok \
&& chmod +x /usr/local/bin/grok \
&& ln -s /usr/local/bin/grok /usr/local/bin/agent \
&& rm -f /tmp/grok.zst \
&& /usr/local/bin/grok --version

WORKDIR /home/bun/app

ARG UPSTREAM_VERSION
Expand Down
36 changes: 36 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
services:
# OpenCodex Hub — unified AI provider proxy with embedded coding CLIs (codex, claude, grok)
hub:
image: nordz0r/opencodex:latest
# Local build without args — automatically clones latest stable upstream:
build: .
container_name: opencodex-hub
restart: unless-stopped
ports:
# Data plane only: OpenAI-compatible proxy (10100)
# Management API (10101) stays loopback-internal inside the container
- "127.0.0.1:10100:10100"
environment:
- NODE_ENV=production
- OPENCODEX_HOME=/home/bun/.opencodex
- CODEX_HOME=/home/bun/.opencodex/codex-home
# Optional: set an explicit API token for proxy requests
# - OPENCODEX_API_AUTH_TOKEN=your-secret-token

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Supply an admission token in the new Compose configuration

On a fresh ocx-state volume, the entrypoint creates a 0.0.0.0 hub configuration, but this Compose file neither sets OPENCODEX_API_AUTH_TOKEN nor mounts the default /run/secrets/ocx_api_token token file. OpenCodex rejects non-loopback startup without one (as the existing smoke-test setup documents), so docker compose -f docker-compose.yaml up exits by default; the comment calling the token optional makes this especially likely. Make a token source required, as compose.yaml does.

Useful? React with 👍 / 👎.

volumes:
# Persistent state for accounts, catalog, and CLI home directories
- ocx-state:/home/bun/.opencodex
- ocx-codex:/home/bun/.codex
- ocx-claude:/home/bun/.claude
- ocx-grok:/home/bun/.grok
Comment on lines +22 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Initialize the CLI volumes with bun ownership

After a user supplies the required hub token and starts this Compose file with fresh default volumes, ocx-claude and ocx-grok are mounted root-owned at paths the image never creates or chowns. The service runs as bun, so those CLIs cannot persist their login, configuration, or cache in their default homes; the smoke test only checks which, so it does not expose this. Initialize these volume targets as UID 1000 or avoid mounting uninitialized CLI home directories.

Useful? React with 👍 / 👎.

tmpfs:
- /tmp:rw,noexec,nosuid,size=64m

volumes:
ocx-state:
name: opencodex-state
ocx-codex:
name: opencodex-codex-home
ocx-claude:
name: opencodex-claude-home
ocx-grok:
name: opencodex-grok-home
9 changes: 9 additions & 0 deletions scripts/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,13 @@ if [ -n "${label_version}" ]; then
echo "[smoke] PASS version label ${label_version}"
fi

echo "[smoke] checking embedded coding CLIs (codex, claude, grok)"
for tool in codex claude grok; do
if ! docker exec "${CTR}" which "${tool}" >/dev/null 2>&1; then
echo "[smoke] FATAL: embedded CLI '${tool}' not found in PATH" >&2
exit 1
fi
echo "[smoke] PASS CLI ${tool} is present in PATH"
done

echo "[smoke] ALL CHECKS PASSED for ${IMAGE}"