3636 echo " "
3737}
3838
39+ # 输入源:兼容 `bash <(curl ...)` / 管道执行场景,强制从终端读取
40+ _INPUT_FD=" /dev/tty"
41+ if [[ ! -r " $_INPUT_FD " ]]; then
42+ _INPUT_FD=" /dev/stdin"
43+ fi
44+
3945# 读取用户输入,支持默认值
4046ask () {
4147 local prompt=" $1 " default=" $2 " varname=" $3 "
@@ -44,7 +50,7 @@ ask() {
4450 else
4551 printf " ${BOLD} %s${NC} : " " $prompt "
4652 fi
47- read -r input
53+ read -r input < " $_INPUT_FD "
4854 eval " $varname =\" ${input:- $default } \" "
4955}
5056
@@ -56,7 +62,7 @@ ask_secret() {
5662 else
5763 printf " ${BOLD} %s${NC} (留空则自动生成): " " $prompt "
5864 fi
59- read -rs input
65+ read -rs input < " $_INPUT_FD "
6066 echo " "
6167 eval " $varname =\" ${input:- $default } \" "
6268}
@@ -72,6 +78,61 @@ gen_secret() {
7278 fi
7379}
7480
81+ # ---------- 自举:确保位于 codex2api 仓库目录 ----------
82+ # 触发条件:
83+ # 1) 通过 `bash <(curl ...)` 远程拉起 (BASH_SOURCE 不是真实文件)
84+ # 2) 或当前目录缺少必要的 compose / deploy.sh 文件
85+ # 行为:
86+ # - 若已在仓库目录: 直接返回
87+ # - 否则: clone 仓库到 ./codex2api,切入并 exec ./deploy.sh
88+ REPO_URL=" ${CODEX2API_REPO_URL:- https:// github.com/ james-6-23/ codex2api.git} "
89+ REPO_BRANCH=" ${CODEX2API_REPO_BRANCH:- main} "
90+ REPO_DIR_NAME=" ${CODEX2API_DIR_NAME:- codex2api} "
91+
92+ is_codex2api_repo () {
93+ [[ -f " docker-compose.yml" ]] && [[ -f " deploy.sh" ]] \
94+ && grep -q ' ^name: codex2api' docker-compose.yml 2> /dev/null
95+ }
96+
97+ bootstrap_repo () {
98+ # 已经在仓库目录里:什么都不做
99+ if is_codex2api_repo; then
100+ success " 检测到当前目录为 codex2api 仓库"
101+ return 0
102+ fi
103+
104+ warn " 当前目录不是 codex2api 仓库,进入自动拉取流程"
105+
106+ if ! command -v git > /dev/null 2>&1 ; then
107+ error " 未找到 git,请先安装 git 后重试"
108+ fi
109+
110+ # 如果同名目录已存在且是仓库,直接复用
111+ if [[ -d " $REPO_DIR_NAME /.git" ]]; then
112+ info " 发现已有目录 $REPO_DIR_NAME ,尝试更新到最新代码..."
113+ (cd " $REPO_DIR_NAME " && git fetch --depth=1 origin " $REPO_BRANCH " && git reset --hard " origin/$REPO_BRANCH " ) \
114+ || warn " 拉取更新失败,将沿用已有代码继续部署"
115+ elif [[ -e " $REPO_DIR_NAME " ]]; then
116+ error " 目录 $REPO_DIR_NAME 已存在但不是 git 仓库,请手动处理后重试"
117+ else
118+ info " 克隆仓库: $REPO_URL ($REPO_BRANCH ) → ./$REPO_DIR_NAME "
119+ git clone --depth=1 --branch " $REPO_BRANCH " " $REPO_URL " " $REPO_DIR_NAME " \
120+ || error " git clone 失败,请检查网络或仓库地址"
121+ fi
122+
123+ cd " $REPO_DIR_NAME " || error " 无法进入 $REPO_DIR_NAME 目录"
124+
125+ if ! is_codex2api_repo; then
126+ error " 克隆后仍未识别为 codex2api 仓库,请手动检查"
127+ fi
128+
129+ success " 已切换到 $( pwd) "
130+ info " 重新运行 ./deploy.sh 完成部署..."
131+ echo " "
132+ # exec 掉本进程,避免远程脚本/旧上下文继续运行
133+ exec bash ./deploy.sh " $@ "
134+ }
135+
75136# ---------- 前置检查 ----------
76137preflight () {
77138 info " 检查运行环境..."
@@ -94,7 +155,7 @@ preflight() {
94155# ---------- 第一步:端口 ----------
95156step_port () {
96157 echo " "
97- printf " ${BOLD}${CYAN} ━━━ 1/5 服务端口 ━━━${NC} \n"
158+ printf " ${BOLD}${CYAN} ━━━ 1/6 服务端口 ━━━${NC} \n"
98159 ask " 服务监听端口" " 8080" PORT
99160
100161 if ! [[ " $PORT " =~ ^[0-9]+$ ]] || (( PORT < 1 || PORT > 65535 )) ; then
@@ -103,10 +164,37 @@ step_port() {
103164 success " 端口: $PORT "
104165}
105166
106- # ---------- 第二步:数据库模式 ----------
167+ # ---------- 第二步:监听范围 ----------
168+ step_bind () {
169+ echo " "
170+ printf " ${BOLD}${CYAN} ━━━ 2/6 监听范围 ━━━${NC} \n"
171+ echo " "
172+ echo " 1) 仅本机访问 — 绑定 127.0.0.1,外部无法访问 (内网/反向代理后端推荐)"
173+ echo " 2) 全部网络 — 绑定 0.0.0.0,可通过内网/公网 IP 访问 (默认)"
174+ echo " "
175+ ask " 请选择 (1 或 2)" " 2" BIND_CHOICE
176+
177+ case " $BIND_CHOICE " in
178+ 1|local|loopback|127* )
179+ BIND_HOST=" 127.0.0.1"
180+ BIND_MODE=" loopback"
181+ success " 监听范围: 仅本机 (127.0.0.1)"
182+ ;;
183+ 2|all|public|0* )
184+ BIND_HOST=" 0.0.0.0"
185+ BIND_MODE=" all"
186+ success " 监听范围: 全部网络 (0.0.0.0)"
187+ ;;
188+ * )
189+ error " 无效选择: $BIND_CHOICE "
190+ ;;
191+ esac
192+ }
193+
194+ # ---------- 第三步:数据库模式 ----------
107195step_database () {
108196 echo " "
109- printf " ${BOLD}${CYAN} ━━━ 2/5 数据库模式 ━━━${NC} \n"
197+ printf " ${BOLD}${CYAN} ━━━ 3/6 数据库模式 ━━━${NC} \n"
110198 echo " "
111199 echo " 1) SQLite — 轻量单文件,适合个人 / 测试"
112200 echo " 2) PG+Redis — PostgreSQL + Redis,适合生产 / 多并发"
@@ -151,10 +239,10 @@ step_pg_config() {
151239 ask_secret " Redis 密码 (留空则无密码)" " " REDIS_PASS
152240}
153241
154- # ---------- 第三步 :密钥 ----------
242+ # ---------- 第四步 :密钥 ----------
155243step_secrets () {
156244 echo " "
157- printf " ${BOLD}${CYAN} ━━━ 3/5 安全密钥 ━━━${NC} \n"
245+ printf " ${BOLD}${CYAN} ━━━ 4/6 安全密钥 ━━━${NC} \n"
158246 echo " "
159247
160248 ask_secret " 管理后台密钥 (ADMIN_SECRET)" " " ADMIN_SECRET
@@ -167,10 +255,10 @@ step_secrets() {
167255 ask " 下游 API 密钥 (CODEX_API_KEYS, 多个用逗号分隔, 留空不启用)" " " API_KEYS
168256}
169257
170- # ---------- 第四步 :构建方式 ----------
258+ # ---------- 第五步 :构建方式 ----------
171259step_build_mode () {
172260 echo " "
173- printf " ${BOLD}${CYAN} ━━━ 4/5 构建方式 ━━━${NC} \n"
261+ printf " ${BOLD}${CYAN} ━━━ 5/6 构建方式 ━━━${NC} \n"
174262 echo " "
175263 echo " 1) 拉取镜像 — 使用预构建镜像 (ghcr.io),部署快"
176264 echo " 2) 本地构建 — 从源码编译,适合自定义修改"
@@ -192,12 +280,17 @@ step_build_mode() {
192280 esac
193281}
194282
195- # ---------- 第五步 :确认 ----------
283+ # ---------- 第六步 :确认 ----------
196284step_confirm () {
197285 echo " "
198- printf " ${BOLD}${CYAN} ━━━ 5/5 配置确认 ━━━${NC} \n"
286+ printf " ${BOLD}${CYAN} ━━━ 6/6 配置确认 ━━━${NC} \n"
199287 echo " "
200288 echo " 端口: $PORT "
289+ if [[ " $BIND_MODE " == " loopback" ]]; then
290+ echo " 监听范围: 127.0.0.1 (仅本机访问)"
291+ else
292+ echo " 监听范围: 0.0.0.0 (全部网络)"
293+ fi
201294 echo " 数据库: $DB_MODE "
202295 if [[ " $DB_MODE " == " sqlite" ]]; then
203296 echo " 数据路径: $SQLITE_PATH "
@@ -208,7 +301,7 @@ step_confirm() {
208301 echo " Redis: 内置容器"
209302 fi
210303 echo " 构建方式: $( [[ " $BUILD_MODE " == " image" ]] && echo " 拉取镜像" || echo " 本地构建" ) "
211- echo " 管理密钥: ${ADMIN_SECRET: 0 : 6} ****** "
304+ echo " 管理密钥: ${ADMIN_SECRET} "
212305 if [[ -n " ${API_KEYS:- } " ]]; then
213306 echo " API 密钥: 已设置"
214307 else
@@ -242,6 +335,9 @@ generate_env() {
242335# 服务端口
243336CODEX_PORT=${PORT}
244337
338+ # 端口绑定地址 (127.0.0.1=仅本机, 0.0.0.0=全部网络)
339+ BIND_HOST=${BIND_HOST}
340+
245341# 管理后台密钥
246342ADMIN_SECRET=${ADMIN_SECRET}
247343
265361# 服务端口
266362CODEX_PORT=${PORT}
267363
364+ # 端口绑定地址 (127.0.0.1=仅本机, 0.0.0.0=全部网络)
365+ BIND_HOST=${BIND_HOST}
366+
268367# 管理后台密钥
269368ADMIN_SECRET=${ADMIN_SECRET}
270369
@@ -341,24 +440,73 @@ deploy() {
341440 echo " "
342441 success " 部署完成!"
343442 echo " "
443+
444+ local PUBLIC_IP=" " LAN_IP=" "
445+
446+ if [[ " $BIND_MODE " == " all" ]]; then
447+ # 仅在对外开放时才探测/展示对外 IP
448+ PUBLIC_IP=$( curl -fsS4 --max-time 3 https://ifconfig.me 2> /dev/null \
449+ || curl -fsS4 --max-time 3 https://api.ipify.org 2> /dev/null \
450+ || curl -fsS4 --max-time 3 https://ipinfo.io/ip 2> /dev/null \
451+ || true)
452+ PUBLIC_IP=$( echo " $PUBLIC_IP " | tr -d ' [:space:]' )
453+
454+ if command -v hostname > /dev/null 2>&1 ; then
455+ LAN_IP=$( hostname -I 2> /dev/null | awk ' {print $1}' || true)
456+ fi
457+ if [[ -z " $LAN_IP " ]] && command -v ip > /dev/null 2>&1 ; then
458+ LAN_IP=$( ip -4 route get 1.1.1.1 2> /dev/null | awk ' {for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}' )
459+ fi
460+ if [[ -z " $LAN_IP " ]] && command -v ifconfig > /dev/null 2>&1 ; then
461+ LAN_IP=$( ifconfig 2> /dev/null | awk ' /inet /{print $2}' | grep -v ' ^127\.' | head -n1)
462+ fi
463+ fi
464+
344465 echo " ┌──────────────────────────────────────────┐"
345- echo " │ │"
346- echo " │ 服务地址: http://localhost:${PORT} "
347- echo " │ 管理后台: http://localhost:${PORT} /admin "
348- echo " │ 管理密钥: ${ADMIN_SECRET: 0: 6} ****** "
349- echo " │ │"
350- echo " │ 查看日志: $COMPOSE_CMD -f $COMPOSE_FILE logs -f"
351- echo " │ 停止服务: $COMPOSE_CMD -f $COMPOSE_FILE down"
352- echo " │ │"
466+ echo " │ 部署信息 │"
353467 echo " └──────────────────────────────────────────┘"
354468 echo " "
469+ if [[ " $BIND_MODE " == " loopback" ]]; then
470+ echo " 监听范围 : 127.0.0.1 (仅本机访问)"
471+ echo " "
472+ echo " 本地访问 : http://127.0.0.1:${PORT} "
473+ echo " http://127.0.0.1:${PORT} /admin"
474+ else
475+ echo " 监听范围 : 0.0.0.0 (全部网络)"
476+ echo " "
477+ echo " 本地访问 : http://localhost:${PORT} "
478+ echo " http://localhost:${PORT} /admin"
479+ if [[ -n " $LAN_IP " ]]; then
480+ echo " 内网访问 : http://${LAN_IP} :${PORT} "
481+ echo " http://${LAN_IP} :${PORT} /admin"
482+ fi
483+ if [[ -n " $PUBLIC_IP " ]]; then
484+ echo " 公网访问 : http://${PUBLIC_IP} :${PORT} "
485+ echo " http://${PUBLIC_IP} :${PORT} /admin"
486+ fi
487+ fi
488+ echo " "
489+ echo " 管理密钥 : ${ADMIN_SECRET} "
490+ echo " "
491+ echo " 查看日志 : $COMPOSE_CMD -f $COMPOSE_FILE logs -f"
492+ echo " 停止服务 : $COMPOSE_CMD -f $COMPOSE_FILE down"
493+ echo " "
494+ if [[ " $BIND_MODE " == " all" && -n " $PUBLIC_IP " ]]; then
495+ warn " 服务对外开放,请确认防火墙/安全组已放行 ${PORT} 端口"
496+ fi
497+ if [[ " $BIND_MODE " == " loopback" ]]; then
498+ info " 如需对外暴露,可重新运行 deploy.sh 选择「全部网络」,或在 .env 中将 BIND_HOST 改为 0.0.0.0"
499+ fi
500+ echo " "
355501}
356502
357503# ---------- 主流程 ----------
358504main () {
359505 banner
506+ bootstrap_repo " $@ "
360507 preflight
361508 step_port
509+ step_bind
362510 step_database
363511 step_secrets
364512 step_build_mode
0 commit comments