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

配图来源: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。
下载并安装¶
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 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 包安装。修复网络后可以重新运行同一脚本。