#!/usr/bin/env bash set -Eeuo pipefail PRIMARY_BASE_URL="https://code.aiserverai.online/v1" SECONDARY_BASE_URL="https://cx.aiserverai.online/v1" XMUMAC_BASE_URL="https://code.xmumac.com/v1" BASE_URL="$PRIMARY_BASE_URL" NODE_MIRROR="https://npmmirror.com/mirrors/node" NPM_MIRROR="https://registry.npmmirror.com" SUPPORT_EMAIL="alexisty233@gmail.com" INSTALL_ROOT="$HOME/.local" BIN_DIR="$INSTALL_ROOT/bin" LIB_DIR="$INSTALL_ROOT/lib" CODEX_HOME_DIR="$HOME/.codex" TMP_DIR="$(mktemp -d)" cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT fail() { trap - ERR if command -v zenity >/dev/null 2>&1 && [ -n "${DISPLAY:-}" ]; then zenity --error --title="Codex 安装失败" --text="$1\n\n请附完整失败截图联系管理员:$SUPPORT_EMAIL\n我们会在 72 小时内更新脚本,请更新后重试。" >/dev/null 2>&1 || true fi printf '安装失败:%s\n请附完整失败截图联系管理员:%s\n我们会在 72 小时内更新脚本,请更新后重试。\n' "$1" "$SUPPORT_EMAIL" >&2 exit 1 } handle_error() { local status="$?" local line="$1" fail "安装步骤执行失败(第 $line 行,退出码 $status)" } trap 'handle_error "$LINENO"' ERR download_text() { if command -v curl >/dev/null 2>&1; then curl -fsSL --retry 3 "$1" elif command -v wget >/dev/null 2>&1; then wget -q -t 3 -O - "$1" else fail "系统缺少 curl 或 wget,请先安装其中一个下载工具" fi } download_file() { if command -v curl >/dev/null 2>&1; then curl -fsSL --retry 3 -o "$2" "$1" elif command -v wget >/dev/null 2>&1; then wget -q -t 3 -O "$2" "$1" else fail "系统缺少 curl 或 wget,请先安装其中一个下载工具" fi } ensure_node() { if command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then NODE_MAJOR="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || printf '0')" if [ "$NODE_MAJOR" -ge 20 ]; then return fi fi printf '正在安装 Node.js LTS…\n' if ! NODE_INDEX="$(download_text "$NODE_MIRROR/index.tab")"; then NODE_INDEX="$(download_text "https://nodejs.org/dist/index.tab")" fi NODE_VERSION="$(awk 'NR > 1 && $10 != "-" { print $1; exit }' <<< "$NODE_INDEX")" [ -n "$NODE_VERSION" ] || fail "无法获取 Node.js LTS 版本" case "$(uname -m)" in aarch64|arm64) NODE_ARCH="arm64" ;; x86_64|amd64) NODE_ARCH="x64" ;; *) fail "暂不支持此 Linux 架构:$(uname -m)" ;; esac ARCHIVE="$TMP_DIR/node.tar.gz" ARCHIVE_NAME="node-$NODE_VERSION-linux-$NODE_ARCH.tar.gz" if ! download_file "$NODE_MIRROR/$NODE_VERSION/$ARCHIVE_NAME" "$ARCHIVE"; then download_file "https://nodejs.org/dist/$NODE_VERSION/$ARCHIVE_NAME" "$ARCHIVE" fi if ! CHECKSUMS="$(download_text "$NODE_MIRROR/$NODE_VERSION/SHASUMS256.txt")"; then CHECKSUMS="$(download_text "https://nodejs.org/dist/$NODE_VERSION/SHASUMS256.txt")" fi EXPECTED_HASH="$(awk -v file="$ARCHIVE_NAME" '$2 == file { print $1; exit }' <<< "$CHECKSUMS")" ACTUAL_HASH="$(sha256sum "$ARCHIVE" | awk '{ print $1 }')" [ -n "$EXPECTED_HASH" ] && [ "$EXPECTED_HASH" = "$ACTUAL_HASH" ] || fail "Node.js 安装包校验失败" tar -xzf "$ARCHIVE" -C "$TMP_DIR" EXTRACTED="$TMP_DIR/node-$NODE_VERSION-linux-$NODE_ARCH" NODE_HOME="$LIB_DIR/node-$NODE_VERSION-linux-$NODE_ARCH" mkdir -p "$LIB_DIR" "$BIN_DIR" if [ ! -d "$NODE_HOME" ]; then mv "$EXTRACTED" "$NODE_HOME" fi ln -sf "$NODE_HOME/bin/node" "$BIN_DIR/node" ln -sf "$NODE_HOME/bin/npm" "$BIN_DIR/npm" ln -sf "$NODE_HOME/bin/npx" "$BIN_DIR/npx" export PATH="$BIN_DIR:$PATH" } persist_path() { PROFILE_FILE="$HOME/.profile" PATH_LINE='export PATH="$HOME/.local/bin:$PATH"' touch "$PROFILE_FILE" if ! grep -Fq "$PATH_LINE" "$PROFILE_FILE"; then printf '\n%s\n' "$PATH_LINE" >> "$PROFILE_FILE" fi export PATH="$BIN_DIR:$PATH" } prompt_key() { if command -v zenity >/dev/null 2>&1 && [ -n "${DISPLAY:-}" ]; then zenity --password --title="Codex 一键配置" --text="请输入你的 Sub2API Key" return fi printf '请输入你的 Sub2API Key:' >&2 read -r -s API_KEY_INPUT printf '\n' >&2 printf '%s' "$API_KEY_INPUT" } choose_base_url() { if command -v zenity >/dev/null 2>&1 && [ -n "${DISPLAY:-}" ]; then BASE_URL_CHOICE="$(zenity --list --radiolist --title="选择 API URL" --text="三个入口连接同一服务,请选择当前网络访问更稳定的一个" --column="选择" --column="API URL" TRUE "$PRIMARY_BASE_URL" FALSE "$SECONDARY_BASE_URL" FALSE "$XMUMAC_BASE_URL")" || fail "用户取消了 API URL 选择" [ -n "$BASE_URL_CHOICE" ] || fail "必须选择一个 API URL" BASE_URL="$BASE_URL_CHOICE" return fi printf '请选择 API URL:\n 1) %s\n 2) %s\n 3) %s\n请输入 1、2 或 3(默认 1):' "$PRIMARY_BASE_URL" "$SECONDARY_BASE_URL" "$XMUMAC_BASE_URL" >&2 read -r BASE_URL_CHOICE case "$BASE_URL_CHOICE" in ""|1) BASE_URL="$PRIMARY_BASE_URL" ;; 2) BASE_URL="$SECONDARY_BASE_URL" ;; 3) BASE_URL="$XMUMAC_BASE_URL" ;; *) fail "API URL 选项无效,请输入 1、2 或 3" ;; esac } write_config() { mkdir -p "$CODEX_HOME_DIR" chmod 700 "$CODEX_HOME_DIR" STAMP="$(date +%Y%m%d-%H%M%S)" MAIN_CONFIG="$CODEX_HOME_DIR/config.toml" [ ! -f "$MAIN_CONFIG" ] || cp "$MAIN_CONFIG" "$MAIN_CONFIG.backup-$STAMP" PRESERVED_CONFIG="$TMP_DIR/config.preserved.toml" if [ -f "$MAIN_CONFIG" ]; then awk ' BEGIN { in_table = 0 } /^\[/ { in_table = 1 skip_managed = ($0 ~ /^\[model_providers\.(aiserver|cch)(\.|\])/) || ($0 ~ /^\[sandbox_workspace_write(\.|\])/) } skip_managed { next } !in_table && /^[[:space:]]*(model|model_provider|model_reasoning_effort|disable_response_storage|sandbox_mode)[[:space:]]*=/ { next } { print } ' "$MAIN_CONFIG" > "$PRESERVED_CONFIG" else : > "$PRESERVED_CONFIG" fi MAIN_TMP="$TMP_DIR/config.toml" { cat <<'CONFIG' model = "gpt-5.6-sol" model_provider = "cch" model_reasoning_effort = "medium" disable_response_storage = true sandbox_mode = "workspace-write" CONFIG cat "$PRESERVED_CONFIG" cat < "$MAIN_TMP" mv "$MAIN_TMP" "$MAIN_CONFIG" chmod 600 "$MAIN_CONFIG" } write_auth() { AUTH_FILE="$CODEX_HOME_DIR/auth.json" [ ! -f "$AUTH_FILE" ] || cp "$AUTH_FILE" "$AUTH_FILE.backup-$STAMP" AUTH_TMP="$TMP_DIR/auth.json" printf '%s' "$1" | node -e ' let key = "" process.stdin.setEncoding("utf8") process.stdin.on("data", (chunk) => { key += chunk }) process.stdin.on("end", () => { process.stdout.write(JSON.stringify({ OPENAI_API_KEY: key }, null, 2) + "\n") }) ' > "$AUTH_TMP" mv "$AUTH_TMP" "$AUTH_FILE" chmod 600 "$AUTH_FILE" } printf '正在准备 Codex…\n' ensure_node persist_path mkdir -p "$INSTALL_ROOT/cache/npm" CODEX_BIN="$BIN_DIR/codex" if ! npm install -g --prefix "$INSTALL_ROOT" --cache="$INSTALL_ROOT/cache/npm" --registry="$NPM_MIRROR" --include=optional --no-audit --no-fund @openai/codex@latest || ! "$CODEX_BIN" --version >/dev/null 2>&1; then printf '中国 npm 镜像安装不完整,正在使用官方源修复…\n' npm install -g --force --prefix "$INSTALL_ROOT" --cache="$INSTALL_ROOT/cache/npm" --registry="https://registry.npmjs.org" --include=optional --no-audit --no-fund @openai/codex@latest fi "$CODEX_BIN" --version || fail "Codex CLI 或当前系统对应的原生组件安装未完成" choose_base_url API_KEY="$(prompt_key)" [ -n "$API_KEY" ] || fail "API Key 不能为空" curl -fsS --retry 2 -o /dev/null -H "Authorization: Bearer $API_KEY" "$BASE_URL/models" || fail "API Key 或网络验证失败" write_config write_auth "$API_KEY" unset API_KEY if command -v zenity >/dev/null 2>&1 && [ -n "${DISPLAY:-}" ]; then zenity --info --title="配置完成" --text="Codex 已安装并完成 AIServer 配置。\n打开新的终端,进入项目目录运行 codex。" >/dev/null 2>&1 || true fi printf '配置完成。打开新的终端并运行 codex。\n'