跳转至

Debian 安装 Codex CLI:一键 Bash 脚本与常用配置

在 Debian 上通过 nvm、Node.js 22 安装 Codex CLI,附可下载的 Bash 完整源码,整理自动审批、完全访问、Web SSH 复制、远程控制重启及 HTTP 传输配置。

OpenAI 官方 Codex CLI 文档封面

配图来源:OpenAI Codex CLI 官方文档。

脚本做了什么

这份脚本面向 Debian 12/13 的 amd64、arm64 环境,沿用 nvm → Node.js 22 → npm → Codex CLI 的安装路线。Node.js 22 是本文选用的版本,不是 Codex 唯一支持的 Node 版本;官方当前同时提供独立安装器与 npm 等安装方式,本文使用 npm 安装方式。

脚本会完成以下工作:

  • 安装 curl、git、ca-certificates、build-essential、bubblewrap,以及编辑配置用的 python3、python3-tomlkit。
  • 发现已有 nvm 时直接复用,否则从固定的 v0.40.8 标签安装 nvm,避免跟随 master 变化。
  • 安装 Node.js 22 的最新可用补丁版本,将其设为 nvm 默认版本,再安装当前最新版 @openai/codex。
  • 根据所选模式更新 config.toml;保留无关设置与注释,修改前生成备份,重复执行相同配置不会新增备份。
  • 为 Web SSH 关闭 Codex 全屏记录接管和备用屏幕模式,保留终端自身的滚动记录。

nvm 与 npm 包安装到执行脚本的用户目录;只有 apt 安装系统依赖时需要 root 或 sudo。使用普通账号时直接运行 bash,不需要 sudo bash。以 root 登录时,安装位置就是 /root。

下载并安装

下载 Bash 脚本 · 下载 SHA256 校验文件

curl -fL https://52ll.org/downloads/install-codex-debian.sh -o install-codex-debian.sh
curl -fL https://52ll.org/downloads/install-codex-debian.sh.sha256 -o install-codex-debian.sh.sha256
sha256sum -c install-codex-debian.sh.sha256
bash -n install-codex-debian.sh
bash install-codex-debian.sh

下载后可以先查看下文源码。默认使用 auto 模式:工作区写入沙箱配合自动审批。自动审批会按策略评估需要批准的操作,并不保证每个请求都会获准。自动审批机制说明

命令 模式
bash install-codex-debian.sh 工作区沙箱 + 自动审批
bash install-codex-debian.sh --mode manual 工作区沙箱 + 用户审批
bash install-codex-debian.sh --mode full 完全访问 + 不询问审批
bash install-codex-debian.sh --http 安装并使用 HTTPS/SSE 模型传输
bash install-codex-debian.sh --configure-only --mode auto 只更新配置,不安装软件

--http 可以与 --mode 组合;只改配置时需要系统已经安装 python3-tomlkit。脚本默认处理 ${CODEX_HOME:-$HOME/.codex}/config.toml。如果已有命名权限配置,或者名为 openai_http 的 provider 与脚本定义不一致,会停止更新并保留原配置。

让当前终端识别 Codex

安装脚本在子进程中运行,不能直接修改父终端的环境变量。重新打开 Bash,或在当前终端执行:

export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
[ ! -s "$NVM_DIR/nvm.sh" ] || . "$NVM_DIR/nvm.sh"
nvm use 22
node -v
npm -v
codex --version

脚本同时向 .bashrc 添加了带标记的 nvm 加载片段;同一片段不会重复添加。若已经使用其他 Node 版本,安装会把 nvm 的默认版本设为 22,原有版本仍保留。

首次使用还需要登录。SSH 环境可以执行:

codex login --device-auth
codex login status

按终端提示完成登录,再进入项目目录运行 codex。脚本不会保存账号密码、代替登录,也不会自动重启正在使用的远程控制进程。登录方式受账号和组织设置影响,参见官方认证说明。

完整 Bash 源码

以下内容与下载文件保持一致。配置编辑部分调用 Debian 的 python3-tomlkit,是为了正确处理 TOML 的顶层键、表格和注释,避免通过反复追加文本产生重复配置。

#!/usr/bin/env bash
# Debian 12/13: nvm + Node.js 22 + Codex CLI. Run as the intended login user.
set -Eeuo pipefail

usage() {
  cat <<'HELP'
Usage: bash install-codex-debian.sh [--mode auto|manual|full] [--http] [--configure-only]
  --mode auto       Workspace sandbox + automatic approval review (default).
  --mode manual     Workspace sandbox + approval requests routed to you.
  --mode full       No sandbox and no approval prompts.
  --http            Select the HTTPS/SSE provider, disabling WebSockets.
  --configure-only  Update config only; requires python3 and python3-tomlkit.
  -h, --help        Print this help.
Existing config is backed up before changes. Login and daemon restart are manual.
HELP
}

fail() { printf 'Error: %s\n' "$*" >&2; exit 1; }

install_dependencies() {
  local elevate=()
  if (( EUID != 0 )); then
    command -v sudo >/dev/null || fail 'sudo is required to install Debian packages.'
    elevate=(sudo)
  fi
  "${elevate[@]}" apt-get update
  "${elevate[@]}" apt-get install -y curl git ca-certificates build-essential \
    bubblewrap python3 python3-tomlkit
}

install_runtime() {
  local nvm_tag='v0.40.8' download_dir
  export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
  if [[ ! -s "$NVM_DIR/nvm.sh" ]]; then
    download_dir="$(mktemp -d)"
    if ! curl --fail --silent --show-error --location --retry 3 \
      --connect-timeout 15 --max-time 120 \
      "https://raw.githubusercontent.com/nvm-sh/nvm/${nvm_tag}/install.sh" \
      -o "$download_dir/install-nvm.sh"; then
      rm -rf -- "$download_dir"
      fail 'Could not download nvm.'
    fi
    if ! PROFILE=/dev/null bash "$download_dir/install-nvm.sh"; then
      rm -rf -- "$download_dir"
      fail 'nvm installation failed.'
    fi
    rm -rf -- "$download_dir"
  fi
  # nvm does not promise compatibility with nounset.
  set +u
  # shellcheck disable=SC1091
  . "$NVM_DIR/nvm.sh"
  nvm install 22
  nvm use 22
  nvm alias default 22
  set -u
  npm install -g @openai/codex@latest
  node --version
  npm --version
  codex --version
  if ! grep -Fq '# >>> 52ll.org Codex nvm >>>' "$HOME/.bashrc" 2>/dev/null; then
    {
      printf '\n# >>> 52ll.org Codex nvm >>>\n'
      printf 'export NVM_DIR=%q\n' "$NVM_DIR"
      # shellcheck disable=SC2016
      printf '[ ! -s "$NVM_DIR/nvm.sh" ] || . "$NVM_DIR/nvm.sh"\n'
      printf '# <<< 52ll.org Codex nvm <<<\n'
    } >> "$HOME/.bashrc"
  fi
}

write_config() {
  /usr/bin/python3 - "$1" "$2" "$3" <<'PY'
from pathlib import Path
from collections.abc import MutableMapping
import datetime, os, shutil, sys, tempfile
import tomlkit

path = Path(sys.argv[1]).expanduser().absolute()
mode, use_http = sys.argv[2], sys.argv[3] == "1"
if path.is_symlink():
    raise SystemExit("Config is a symlink; update its target manually.")
original = path.read_text() if path.exists() else ""
doc = tomlkit.parse(original)
if "default_permissions" in doc or "permissions" in doc:
    raise SystemExit("Named permission profiles exist; configure them manually before using this installer.")

def table(parent, key):
    if key not in parent:
        parent[key] = tomlkit.table()
    value = parent[key]
    if not isinstance(value, MutableMapping):
        raise SystemExit(f"{key} must be a TOML table; existing config was not changed.")
    return value

# Change only the requested keys and preserve unrelated config and comments.
doc["sandbox_mode"] = "danger-full-access" if mode == "full" else "workspace-write"
doc["approval_policy"] = "never" if mode == "full" else "on-request"
doc["approvals_reviewer"] = "auto_review" if mode == "auto" else "user"
tui = table(doc, "tui")
tui["fullscreen_transcript"] = False
tui["alternate_screen"] = "never"
if use_http:
    providers = table(doc, "model_providers")
    provider = table(providers, "openai_http")
    desired = {
        "name": "OpenAI HTTP",
        "base_url": "https://chatgpt.com/backend-api/codex",
        "wire_api": "responses",
        "requires_openai_auth": True,
        "supports_websockets": False,
    }
    if any(k not in desired or provider[k] != desired[k] for k in provider):
        raise SystemExit("An incompatible openai_http provider exists; config was not changed.")
    for key, value in desired.items():
        provider[key] = value
    doc["model_provider"] = "openai_http"
rendered = tomlkit.dumps(doc)
tomlkit.parse(rendered)
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
if rendered == original:
    print(f"Config already matches: {path}")
    raise SystemExit(0)
if path.exists():
    stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
    backup = path.with_name(path.name + ".bak." + stamp)
    shutil.copy2(path, backup)
    backup.chmod(0o600)
    print(f"Config backup: {backup}")
fd, pending = tempfile.mkstemp(prefix=".config-", dir=path.parent)
try:
    with os.fdopen(fd, "w") as stream:
        stream.write(rendered)
    os.replace(pending, path)
finally:
    if os.path.exists(pending):
        os.unlink(pending)
print(f"Config saved: {path}")
PY
}

main() {
  local mode='auto' use_http=0 config_only=0
  while (( $# )); do
    case "$1" in
      --mode)
        (( $# >= 2 )) || fail '--mode needs auto, manual, or full.'
        mode="$2"; shift 2 ;;
      --http) use_http=1; shift ;;
      --configure-only) config_only=1; shift ;;
      -h|--help) usage; return ;;
      *) fail "Unknown argument: $1" ;;
    esac
  done
  [[ "$mode" =~ ^(auto|manual|full)$ ]] || fail 'Unknown mode.'
  [[ -r /etc/os-release ]] || fail 'Cannot identify this system.'
  # shellcheck disable=SC1091
  . /etc/os-release
  [[ "$ID" == debian ]] || fail 'This script targets Debian 12/13.'
  [[ "$VERSION_ID" == 12 || "$VERSION_ID" == 13 ]] || fail 'Use Debian 12 or 13.'
  case "$(uname -m)" in x86_64|aarch64) ;; *) fail 'Only amd64/arm64 are supported.' ;; esac
  if (( EUID == 0 )) && [[ -n "${SUDO_USER:-}" && "$SUDO_USER" != root ]]; then
    fail 'Run without sudo; the script uses sudo only for Debian packages.'
  fi
  local config_file="${CODEX_HOME:-$HOME/.codex}/config.toml"
  if (( ! config_only )); then
    install_dependencies
    install_runtime
  fi
  /usr/bin/python3 -c 'import tomlkit' 2>/dev/null || \
    fail 'Install python3-tomlkit first (apt-get install python3-tomlkit).'
  write_config "$config_file" "$mode" "$use_http"
  printf '\nDone. Reopen Bash or load nvm before running codex.\n'
  printf 'Then run: codex login --device-auth\n'
  printf 'Check login: codex login status\n'
  printf 'Mode: %s; config: %s\n' "$mode" "$config_file"
  if (( use_http )); then printf 'Model transport: HTTPS/SSE (WebSockets disabled).\n'; fi
  if [[ "$mode" == full ]]; then printf 'Full mode runs with your user permissions, without a sandbox or approval prompts.\n'; fi
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi

自动审批与完全访问如何选择

自动审批对应以下顶层配置:

sandbox_mode = "workspace-write"
approval_policy = "on-request"
approvals_reviewer = "auto_review"

这里的 approvals_reviewer 指定由谁评估需要批准的操作;它本身不扩大沙箱边界。配置参考

需要完全访问且不再询问时,执行:

bash install-codex-debian.sh --configure-only --mode full

对应的核心配置为:

sandbox_mode = "danger-full-access"
approval_policy = "never"

此模式下,命令按当前账号的权限直接执行,沙箱和审批不再提供限制;若使用 root 登录,就是 root 权限。自动审批模式与完全访问模式应按用途选择。沙箱与审批说明

配置文件路径通常为 ~/.codex/config.toml,也可以使用编辑器手动调整:

vim ~/.codex/config.toml

注意把以上键放在 TOML 顶层,位于 [tui] 等表头之前。项目级配置、启动参数及组织托管策略也可能影响最终生效值。

Web SSH 中保留滚动记录和复制体验

原笔记里的 fullscreen_transcript 仍出现在官方配置 Schema 中。它控制 Codex 是否接管全屏记录的滚动、选择和搜索;alternate_screen 控制是否使用终端备用屏幕。本文脚本同时设置两项:

[tui]
fullscreen_transcript = false
alternate_screen = "never"

这两项可减少 Codex 对终端滚动和选取的接管,但 Web SSH 自身的鼠标选择行为还取决于客户端。字段定义见官方配置 Schema及备用屏幕配置。

只想临时恢复最近的会话,可以继续使用原笔记的写法:

codex resume --last -c tui.fullscreen_transcript=false

或者同时禁用备用屏幕:

codex --no-alt-screen resume --last -c tui.fullscreen_transcript=false

修改配置后重启远程控制

OpenAI 官方远程控制界面的主机、项目和 worktree 选择示例

配图来源:OpenAI Remote 工作流文章。图中版本与模型名称是原文截图,不代表本文安装时的默认值。

只有正在使用远程控制后台时,才需要按实际情况重启它:

codex remote-control stop
codex remote-control start

这组命令停止、启动的是本机 app-server 后台进程,不是重启 Debian。停止进程会影响正在连接该后台的客户端,选择合适的时机操作。remote-control 当前仍标为实验功能,参见官方命令参考。

需要时改用 HTTP 传输

这里的“HTTP”是指以 HTTPS/SSE 代替 Responses API 的 WebSocket 传输,URL 仍然使用 https://,不涉及降级为明文连接。

持久配置可以使用脚本:

bash install-codex-debian.sh --configure-only --mode auto --http

这会选择 openai_http provider。该模式沿用 ChatGPT 登录认证;需要先登录。普通重复执行不会自动撤销已选 provider,若要恢复其他 provider,需要在配置文件中调整 model_provider。

只想临时尝试,可按原笔记的方式传入本次运行的配置:

codex \
  -c 'model_provider="openai_http"' \
  -c 'model_providers.openai_http.name="OpenAI HTTP"' \
  -c 'model_providers.openai_http.base_url="https://chatgpt.com/backend-api/codex"' \
  -c 'model_providers.openai_http.wire_api="responses"' \
  -c 'model_providers.openai_http.requires_openai_auth=true' \
  -c 'model_providers.openai_http.supports_websockets=false'

其中 supports_websockets 和 wire_api 是官方支持的 provider 配置项。上面的 ChatGPT 后端地址来自本文所依据的原笔记,是故障排查用的自定义 provider 示例;不要把它当作通用 OpenAI API 地址。若账号或客户端版本不适用,先恢复默认 provider 再排查。

验证与恢复

本文在 Debian 13、Node.js 22、Codex CLI 0.163.0 的环境中核对命令。脚本通过 bash -n、ShellCheck,以及 13 项隔离配置测试,包括三种模式、HTTP provider、重复执行、保留旧设置、备份与错误配置保护。没有在本机重新安装 Codex 或切换本机正在使用的配置;全新系统的实际安装仍取决于 apt、GitHub、Node.js 与 npm 网络可达性。

如果修改后想恢复原配置,找到脚本输出的备份路径,然后在相同账号下执行:

cp -p ~/.codex/config.toml.bak.实际时间戳 ~/.codex/config.toml

使用自定义 CODEX_HOME 时,将路径替换为对应目录。安装失败时先看报错来自 apt、nvm 还是 npm:GitHub 不通会影响 nvm 下载,Node.js 下载失败会影响 Node 安装,npm 不通会影响 Codex 包安装。修复网络后可以重新运行同一脚本。

参考资料与配图来源