Skip to content
Open
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
27 changes: 27 additions & 0 deletions dev-server-provision/coder/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ resource "coder_agent" "main" {
else
echo "[remotevibe] WARNING: $AGENT_ENV not found — AI agents may not be authenticated"
fi

# ── Codex CLI — non-interactive authentication ───────────────────────────
# If CODEX_OPENAI_AUTH_CODE is set, exchange it for an access token
# non-interactively so Codex does not hang waiting for browser input.
# This is equivalent to running `codex login --device-auth` in a browser.
if [ "$${ENABLE_AGENT_CODEX:-false}" = "true" ]; then
if [ -n "$${CODEX_OPENAI_AUTH_CODE:-}" ]; then
echo "[remotevibe] Codex: completing non-interactive login with CODEX_OPENAI_AUTH_CODE …"
if command -v codex >/dev/null 2>&1; then
codex login --auth-code "$CODEX_OPENAI_AUTH_CODE" 2>&1 \
&& echo "[remotevibe] Codex: login successful." \
|| echo "[remotevibe] WARNING: Codex login with auth code failed — you may need to run 'codex login' manually."
else
echo "[remotevibe] WARNING: codex CLI not found in PATH — skipping auth."
fi
elif [ -n "$${OPENAI_API_KEY:-}" ]; then
echo "[remotevibe] Codex: using OPENAI_API_KEY (API key mode)."
elif [ -n "$${GITHUB_TOKEN:-}" ]; then
echo "[remotevibe] Codex: using GITHUB_TOKEN (GitHub OAuth mode)."
else
echo "[remotevibe] WARNING: Codex is enabled but no auth credential is set."
echo "[remotevibe] To authenticate, run one of:"
echo "[remotevibe] codex login --device-auth (ChatGPT Plus/Pro — needs browser)"
echo "[remotevibe] export OPENAI_API_KEY=sk-..."
echo "[remotevibe] Device Flow URL: https://auth.openai.com/codex/device"
fi
fi
# ── Configure Coder CLI for workspace template management ────────────────
if [ -f /run/secrets/coder-token ]; then
_tok=$(cat /run/secrets/coder-token | tr -d '[:space:]')
Expand Down
10 changes: 10 additions & 0 deletions dev-server-provision/configurator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,16 @@ def _ask_agents(config: dict[str, Any]) -> None:
).execute().strip()

if config["enable_agent_codex"]:
print(
f"\n {_BOLD}Codex CLI authentication{_RESET}\n"
f" Codex requires authentication before it can run.\n"
f" In a headless server environment the Device Flow URL is printed\n"
f" to the provisioning log — open it in your browser to complete auth.\n"
f" Alternatively, authenticate here and the auth code is embedded in\n"
f" the generated config so the workspace authenticates automatically.\n"
f" Device Flow URL (for reference): "
f"{_CYAN}https://auth.openai.com/codex/device{_RESET}\n"
)
codex_auth = inquirer.select(
message="How would you like to authenticate Codex CLI?",
choices=[
Expand Down
1 change: 1 addition & 0 deletions dev-server-provision/configurator/tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def _full_config(self, **overrides):
"email": "admin@example.com",
"cloudflare_api_token": "a" * 40,
"cloudflare_zone_id": "a" * 32,
"coder_admin_password": "securepass1",
"enable_agent_copilot": False,
"enable_agent_claude": False,
"enable_agent_gemini": False,
Expand Down
2 changes: 1 addition & 1 deletion dev-server-provision/configurator/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def validate_coder_password(value: str) -> str | bool:
return True



def validate_api_key_optional(value: str) -> str | bool:
"""Accept empty or any non-whitespace string."""

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

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

validate_api_key_optional() currently returns True for any input (including whitespace-only strings), which contradicts its docstring (“Accept empty or any non-whitespace string”) and makes the validator ineffective. Consider stripping the value and returning an error string when value is non-empty but value.strip() is empty, and add a test case for whitespace-only input.

Suggested change
"""Accept empty or any non-whitespace string."""
"""Accept empty or any non-whitespace string."""
if value == "":
return True
if not value.strip():
return "API key cannot be whitespace only."

Copilot uses AI. Check for mistakes.
return True

Expand Down
64 changes: 64 additions & 0 deletions dev-server-provision/docs/oauth-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,70 @@ or GitHub token for your chosen provider.

---

## 6. Codex CLI in headless / SSH environments

When `ENABLE_AGENT_CODEX=true` is set and the server is provisioned via
`cloud-init` or `install.sh`, there is no interactive terminal available for
browser-based OAuth flows. The Codex CLI would hang waiting for user input
if no credential is pre-configured.

### How authentication is resolved (in order)

| Priority | Credential | How to obtain |
|----------|------------|---------------|
| 1 (best) | `CODEX_OPENAI_AUTH_CODE` | Run the configurator before provisioning and complete the OpenAI Device Flow there. The code is embedded in the generated config. |
| 2 | `OPENAI_API_KEY` | Create a key at <https://platform.openai.com/api-keys> |
| 3 | `GITHUB_TOKEN` | GitHub OAuth Device Flow (same as Copilot) |
| — | None set | `agents.sh` prints a warning and skips; workspace startup script prints the Device Flow URL |

### Path 1 — pre-obtain the auth code with the configurator (recommended)

```bash
# On your local machine, before provisioning:
python -m configurator
# → Enable Codex
# → Choose "Sign in with ChatGPT (Device Flow)"
# → Complete auth in browser
# The code is saved as CODEX_OPENAI_AUTH_CODE in cloud-init.yaml / RVSconfig.yml
```

At workspace start, the startup script runs:
```bash
codex login --auth-code "$CODEX_OPENAI_AUTH_CODE"
```
This completes the PKCE exchange non-interactively — no browser needed on the
server side.

### Path 2 — API key

Add to `/etc/dev-server/env`:
```
OPENAI_API_KEY=sk-...
```

Comment on lines +280 to +286

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

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

The instructions in “Path 2 — API key” imply that adding OPENAI_API_KEY to /etc/dev-server/env is sufficient, but workspaces actually read credentials from /etc/dev-server/agent-env (bind-mounted to /run/secrets/agent-env). After editing /etc/dev-server/env, you’ll need to regenerate agent-env (e.g., re-run /opt/dev-server-provision/infra/agents.sh or setup.sh) and restart the workspace for the new key to take effect.

Copilot uses AI. Check for mistakes.
### Path 3 — post-provisioning manual auth

If the workspace is already running, open a terminal inside it and run:
```bash
codex login --device-auth
# Follow the URL that is printed:
# https://auth.openai.com/codex/device
```

### Reading the Device Flow URL from the log

When no credential is set at provisioning time, `agents.sh` logs the following
(visible in `/var/log/dev-server-provision.log`):

```
[agents] WARN: Device Flow URL (for manual auth): https://auth.openai.com/codex/device
```

The workspace startup script also echoes the URL on first start so it appears
in the Coder workspace log panel.

---

## Adding a new provider

To add OAuth for a new AI agent:
Expand Down
61 changes: 61 additions & 0 deletions dev-server-provision/infra/agents.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ set -euo pipefail

LOG_FILE="${LOG_FILE:-/var/log/dev-server-provision.log}"
log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] [agents] $*" | tee -a "$LOG_FILE"; }
warn() { log "WARN: $*"; }

ENV_FILE="${ENV_FILE:-/etc/dev-server/env}"
AGENT_ENV_FILE="/etc/dev-server/agent-env"
Expand All @@ -26,6 +27,12 @@ if [[ ! -f "$ENV_FILE" ]]; then
exit 0
fi

# Source the env file so we can inspect individual variable values below.
set -a
# shellcheck source=/dev/null
source "$ENV_FILE"
set +a

# ---------------------------------------------------------------------------
# Extract agent-relevant keys only
# ---------------------------------------------------------------------------
Expand All @@ -49,3 +56,57 @@ chmod 0644 "$AGENT_ENV_FILE"
log "Agent key file written ($(wc -l < "$AGENT_ENV_FILE") lines)."
log "Note: AI agent CLIs are installed inside the workspace Docker image."

# ---------------------------------------------------------------------------
# Codex CLI — credential validation
# ---------------------------------------------------------------------------
# Codex CLI authenticates via one of three methods (checked in order):
# 1. CODEX_OPENAI_AUTH_CODE — pre-obtained OpenAI Device Flow auth code
# (obtained via the configurator or `codex login --device-auth`).
# The workspace startup script will exchange it non-interactively.
# 2. OPENAI_API_KEY — direct API key (billing required).
# 3. GITHUB_TOKEN — Codex can authenticate via GitHub OAuth.
#
# If none of the above are set, Codex would try to launch an interactive
# Device Flow in the terminal — which hangs silently in headless/SSH
# environments. We warn early and skip instead of letting it hang.
# ---------------------------------------------------------------------------
if [[ "${ENABLE_AGENT_CODEX:-false}" == "true" ]]; then
if [[ -n "${CODEX_OPENAI_AUTH_CODE:-}" ]]; then
log "Codex: CODEX_OPENAI_AUTH_CODE is set — workspace startup will complete"
log " non-interactive authentication via 'codex login --auth-code'."
elif [[ -n "${OPENAI_API_KEY:-}" ]]; then
Comment on lines +73 to +77

Copilot AI Apr 10, 2026

Copy link

Choose a reason for hiding this comment

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

PR description says infra/agents.sh runs codex login --auth-code non-interactively when CODEX_OPENAI_AUTH_CODE is set, but this script only logs messages and never invokes codex. Either update the PR description to reflect that login happens in the workspace startup script (coder/main.tf), or implement the described behavior here if that was the intent.

Copilot uses AI. Check for mistakes.
log "Codex: OPENAI_API_KEY is set — Codex CLI will use API key mode."
elif [[ -n "${GITHUB_TOKEN:-}" ]]; then
log "Codex: GITHUB_TOKEN is set — Codex CLI will use GitHub OAuth mode."
else
warn "Codex is enabled but no authentication credential is configured."
warn "Codex CLI would hang waiting for interactive input in a headless environment."
warn ""
warn "To fix this, obtain an auth code BEFORE provisioning:"
warn " 1. Run the configurator: python -m configurator"
warn " 2. Select 'OpenAI Codex CLI' → 'Sign in with ChatGPT (Device Flow)'"
warn " 3. Authenticate in your browser — the configurator saves the code"
warn " as CODEX_OPENAI_AUTH_CODE in your cloud-init.yaml / RVSconfig.yml"
warn ""
warn "Alternatively, set one of these in /etc/dev-server/env and re-run:"
warn " CODEX_OPENAI_AUTH_CODE=<auth_code> (ChatGPT Plus/Pro plan)"
warn " OPENAI_API_KEY=sk-... (API billing plan)"
warn " GITHUB_TOKEN=ghp_... (GitHub OAuth)"
warn ""
warn "Device Flow URL (for manual auth): https://auth.openai.com/codex/device"
warn "Skipping Codex credential setup — Codex may prompt for login in the workspace."
fi
fi

# ---------------------------------------------------------------------------
# OpenCode — log provider info
# ---------------------------------------------------------------------------
if [[ "${ENABLE_AGENT_OPENCODE:-false}" == "true" ]]; then
if [[ -n "${OPENCODE_PROVIDER:-}" ]]; then
log "OpenCode: provider(s) configured: ${OPENCODE_PROVIDER}"
else
warn "OpenCode is enabled but OPENCODE_PROVIDER is not set."
warn "OpenCode may prompt for a provider selection in the workspace."
fi
fi