mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(update): retain binary installation path for self-restart
Resolve and retain the executable path at startup so updates, rollbacks and restarts keep using the installation path after the running binary is renamed. Document binary self-restart configuration and cover renamed executable restarts. Refs #316 Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -241,9 +241,13 @@ pub struct ProcessSystemOperations {
|
||||
|
||||
impl ProcessSystemOperations {
|
||||
#[must_use]
|
||||
pub fn new(cancellation: CancellationToken, config: SystemUpdateConfig) -> Self {
|
||||
// 组合根在接收请求前创建服务,记录本次启动的发行文件,后续替换不能改写这份事实。
|
||||
let running_files = Arc::new(ReleaseFiles::installed(&config));
|
||||
pub fn new(cancellation: CancellationToken, mut config: SystemUpdateConfig) -> Self {
|
||||
// 组合根在接收请求前固定安装路径和发行文件;旧程序重命名后,
|
||||
// current_exe 可能指向备份,后续更新、回滚和重启不能再反查运行文件。
|
||||
let running_files = Arc::new(config.executable_path().and_then(|executable| {
|
||||
config.executable_path = Some(executable);
|
||||
ReleaseFiles::installed(&config)
|
||||
}));
|
||||
Self {
|
||||
cancellation,
|
||||
events: Arc::new(UpdateEvents::default()),
|
||||
|
||||
@@ -206,6 +206,73 @@ async fn restart_should_spawn_replacement_before_shutdown_outside_docker() {
|
||||
.expect("replacement executed before fixture cleanup");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[tokio::test]
|
||||
async fn restart_should_use_startup_path_after_running_executable_is_renamed() {
|
||||
const CHILD_ENV: &str = "CPR_TEST_RESTART_RENAMED_EXECUTABLE_CHILD";
|
||||
let executable = std::env::current_exe().expect("test executable");
|
||||
if std::env::var_os(CHILD_ENV).is_none() {
|
||||
// 只重命名隔离副本,避免修改 Cargo 的测试程序或影响并行测试。
|
||||
let directory = tempfile::tempdir().expect("isolated test directory");
|
||||
let child = directory.path().join("restart-test");
|
||||
fs::copy(&executable, &child).expect("copy test executable");
|
||||
let output = std::process::Command::new(child)
|
||||
.args([
|
||||
"--exact",
|
||||
"system_update::restart_should_use_startup_path_after_running_executable_is_renamed",
|
||||
])
|
||||
.env(CHILD_ENV, "1")
|
||||
.env("CPR_RESTART_DELAY_MS", "1200")
|
||||
.output()
|
||||
.expect("isolated restart test");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
if executable.file_name() == Some(std::ffi::OsStr::new("codex-proxy-rs.backup")) {
|
||||
// 误启动旧程序时留下可观察结果并退出,避免旧副本再次发起重启。
|
||||
fs::write(
|
||||
executable
|
||||
.parent()
|
||||
.expect("installation directory")
|
||||
.join("replacement-version"),
|
||||
"old",
|
||||
)
|
||||
.expect("old replacement marker");
|
||||
return;
|
||||
}
|
||||
|
||||
let fixture = Fixture::new();
|
||||
fs::rename(executable, fixture.executable()).expect("move isolated running executable");
|
||||
let mut config = fixture.config("http://127.0.0.1:1/repos");
|
||||
config.executable_path = None;
|
||||
config.self_restart_enabled = true;
|
||||
let service = ProcessSystemOperations::new(CancellationToken::new(), config);
|
||||
|
||||
let backup = fixture.root.path().join("codex-proxy-rs.backup");
|
||||
fs::rename(fixture.executable(), &backup).expect("back up running executable");
|
||||
fixture.write_executable("#!/bin/sh\nprintf new > \"${0%/*}/replacement-version\"\n");
|
||||
assert_eq!(std::env::current_exe().expect("renamed executable"), backup);
|
||||
|
||||
service.restart().await.expect("restart accepted");
|
||||
let marker = fixture.root.path().join("replacement-version");
|
||||
tokio::time::timeout(Duration::from_secs(5), async {
|
||||
while !marker.is_file() {
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("replacement executed");
|
||||
assert_eq!(
|
||||
fs::read_to_string(marker).expect("replacement version"),
|
||||
"new"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn restart_should_conflict_while_another_system_operation_is_running() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
@@ -526,6 +526,23 @@ alpha、beta、rc、exp 在 GitHub 标记为 Pre-release,不覆盖 GitHub Late
|
||||
回滚入口只接受与记录指纹一致的完整备份;旧格式记录没有指纹,或备份被替换、丢失时,不再提供在线回滚,
|
||||
但不会删除备份文件。此时需要按人工部署流程恢复已经核实的发行包。
|
||||
|
||||
直接运行二进制时,若未设置 `CPR_ENABLE_SELF_RESTART=true`,管理端自重启默认关闭。
|
||||
在现有 `deploy/config.yaml` 的 `host` 下合并以下配置:
|
||||
|
||||
```yaml
|
||||
host:
|
||||
system_update:
|
||||
deployment_mode: binary
|
||||
self_restart_enabled: true
|
||||
executable_path: '/opt/codex-proxy-rs/codex-proxy-rs'
|
||||
```
|
||||
|
||||
`executable_path` 为可选项:未指定时,服务在启动时解析并固定当前程序路径;
|
||||
显式配置时,将示例改为实际程序的绝对路径。`self_restart_enabled` 控制管理端的「立即重启」操作;
|
||||
修改配置后需手动重启当前服务一次才会生效。
|
||||
以上三个字段也可分别通过 `CPR_DEPLOYMENT_MODE`、`CPR_ENABLE_SELF_RESTART` 和 `CPR_UPDATE_EXE_PATH` 设置,
|
||||
YAML 中显式配置的值优先于对应环境变量。
|
||||
|
||||
Compose 提供以下在线更新运行参数:
|
||||
|
||||
- `CPR_UPDATE_REPOSITORY`:只接受 `owner/repository`;默认 `zyycn/codex-proxy-rs`。
|
||||
|
||||
@@ -29,6 +29,12 @@ host:
|
||||
directory: '../.runtime/logs'
|
||||
retention_days: 7
|
||||
max_file_size_mb: 20
|
||||
# 二进制部署需要管理端自重启时,取消以下注释;可选路径改为实际程序绝对路径。
|
||||
# Compose 已通过环境变量开启,保持本段注释即可;详情见 deploy/README.md。
|
||||
# system_update:
|
||||
# deployment_mode: 'binary'
|
||||
# self_restart_enabled: true
|
||||
# executable_path: '/opt/codex-proxy-rs/codex-proxy-rs' # 可选:覆盖启动时解析的程序路径。
|
||||
drain_timeout_seconds: 30
|
||||
worker_shutdown_timeout_seconds: 30
|
||||
|
||||
|
||||
Reference in New Issue
Block a user