Skip to content

Commit 65c2b26

Browse files
committed
Handle opencode update when container stops
1 parent 13e9f02 commit 65c2b26

1 file changed

Lines changed: 109 additions & 15 deletions

File tree

packages/cli-rust/src/commands/update.rs

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ use opencode_cloud_core::config::load_config_or_default;
2020
use opencode_cloud_core::docker::update::PREVIOUS_TAG;
2121
use opencode_cloud_core::docker::update::tag_current_as_previous;
2222
use opencode_cloud_core::docker::{
23-
CONTAINER_NAME, DockerClient, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT, ImageState, ProgressReporter,
24-
build_image, container_exists, container_is_running, docker_supports_systemd, exec_command,
23+
CONTAINER_NAME, DockerClient, DockerError, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT, ImageState,
24+
ProgressReporter, build_image, container_exists, container_is_running, docker_supports_systemd,
25+
exec_command,
2526
exec_command_with_status, get_cli_version, get_image_version, get_registry_latest_version,
2627
has_previous_image, image_exists, pull_image, rollback_image, save_state, setup_and_start,
2728
stop_service,
@@ -1069,6 +1070,8 @@ pub(crate) async fn cmd_update_opencode(
10691070

10701071
let spinner = CommandSpinner::new_maybe("Updating opencode...", quiet);
10711072

1073+
ensure_container_running_for_update(&client, &config, quiet).await?;
1074+
10721075
stop_opencode_for_update(&client, quiet).await?;
10731076

10741077
let update_script = format!(
@@ -1175,20 +1178,24 @@ async fn get_current_opencode_commit(client: &DockerClient) -> Option<String> {
11751178
}
11761179

11771180
async fn stop_opencode_for_update(client: &DockerClient, quiet: bool) -> Result<()> {
1181+
let status = check_opencode_status(client).await?;
1182+
if !status.container_running {
1183+
return Err(container_not_running_update_error());
1184+
}
1185+
11781186
let pid1_comm = get_pid1_comm(client).await;
11791187
let is_systemd = pid1_comm == "systemd";
1180-
let (running, pids) = check_opencode_running(client).await?;
11811188

11821189
if is_systemd {
1183-
return stop_opencode_systemd(client, quiet, running).await;
1190+
return stop_opencode_systemd(client, quiet, status.process_running).await;
11841191
}
11851192

1186-
if !running {
1193+
if !status.process_running {
11871194
print_already_stopped(quiet);
11881195
return Ok(());
11891196
}
11901197

1191-
stop_opencode_non_systemd(client, quiet, &pid1_comm, pids).await
1198+
stop_opencode_non_systemd(client, quiet, &pid1_comm, status.pids).await
11921199
}
11931200

11941201
async fn stop_opencode_systemd(client: &DockerClient, quiet: bool, running: bool) -> Result<()> {
@@ -1257,12 +1264,15 @@ async fn wait_for_opencode_exit(
12571264
let mut last_pids = initial_pids;
12581265
for _ in 0..10 {
12591266
sleep(Duration::from_millis(300)).await;
1260-
let (still_running, current_pids) = check_opencode_running(client).await?;
1261-
if !still_running {
1267+
let status = check_opencode_status(client).await?;
1268+
if !status.container_running {
1269+
return Err(container_not_running_update_error());
1270+
}
1271+
if !status.process_running {
12621272
return Ok(None);
12631273
}
1264-
if !current_pids.is_empty() {
1265-
last_pids = current_pids;
1274+
if !status.pids.is_empty() {
1275+
last_pids = status.pids;
12661276
}
12671277
}
12681278

@@ -1337,14 +1347,41 @@ async fn get_pid1_comm(client: &DockerClient) -> String {
13371347
}
13381348
}
13391349

1340-
async fn check_opencode_running(client: &DockerClient) -> Result<(bool, String)> {
1341-
let (output, status) = exec_command_with_status(
1350+
struct OpencodeProcessStatus {
1351+
container_running: bool,
1352+
process_running: bool,
1353+
pids: String,
1354+
}
1355+
1356+
async fn check_opencode_status(client: &DockerClient) -> Result<OpencodeProcessStatus> {
1357+
let container_running = container_is_running(client, CONTAINER_NAME).await?;
1358+
if !container_running {
1359+
return Ok(OpencodeProcessStatus {
1360+
container_running: false,
1361+
process_running: false,
1362+
pids: String::new(),
1363+
});
1364+
}
1365+
1366+
let (output, status) = match exec_command_with_status(
13421367
client,
13431368
CONTAINER_NAME,
13441369
vec!["bash", "-lc", "pgrep -f \"/opt/opencode/bin/opencode\""],
13451370
)
13461371
.await
1347-
.map_err(|e| anyhow!("Failed to check opencode process status: {e}"))?;
1372+
{
1373+
Ok(result) => result,
1374+
Err(err) => {
1375+
if is_container_not_running_error(&err) {
1376+
return Ok(OpencodeProcessStatus {
1377+
container_running: false,
1378+
process_running: false,
1379+
pids: String::new(),
1380+
});
1381+
}
1382+
return Err(anyhow!("Failed to check opencode process status: {err}"));
1383+
}
1384+
};
13481385

13491386
match status {
13501387
0 => {
@@ -1354,15 +1391,72 @@ async fn check_opencode_running(client: &DockerClient) -> Result<(bool, String)>
13541391
.filter(|l| !l.is_empty())
13551392
.collect();
13561393
let joined = pids.join(" ");
1357-
Ok((!joined.is_empty(), joined))
1394+
Ok(OpencodeProcessStatus {
1395+
container_running: true,
1396+
process_running: !joined.is_empty(),
1397+
pids: joined,
1398+
})
13581399
}
1359-
1 => Ok((false, String::new())),
1400+
1 => Ok(OpencodeProcessStatus {
1401+
container_running: true,
1402+
process_running: false,
1403+
pids: String::new(),
1404+
}),
13601405
_ => Err(anyhow!(
13611406
"Failed to check opencode process status (exit {status}).\n{output}"
13621407
)),
13631408
}
13641409
}
13651410

1411+
async fn ensure_container_running_for_update(
1412+
client: &DockerClient,
1413+
config: &opencode_cloud_core::config::Config,
1414+
quiet: bool,
1415+
) -> Result<()> {
1416+
if container_is_running(client, CONTAINER_NAME).await? {
1417+
return Ok(());
1418+
}
1419+
1420+
if !quiet {
1421+
eprintln!(
1422+
"{} Container is not running; attempting to start it before updating.",
1423+
style("Warning:").yellow().bold()
1424+
);
1425+
}
1426+
1427+
let systemd_enabled = docker_supports_systemd(client).await?;
1428+
setup_and_start(
1429+
client,
1430+
Some(config.opencode_web_port),
1431+
None,
1432+
Some(&config.bind_address),
1433+
Some(config.cockpit_port),
1434+
Some(config.cockpit_enabled && COCKPIT_EXPOSED),
1435+
Some(systemd_enabled),
1436+
None,
1437+
)
1438+
.await
1439+
.map_err(|e| anyhow!("Failed to start container: {e}"))?;
1440+
1441+
if container_is_running(client, CONTAINER_NAME).await? {
1442+
return Ok(());
1443+
}
1444+
1445+
Err(container_not_running_update_error())
1446+
}
1447+
1448+
fn container_not_running_update_error() -> anyhow::Error {
1449+
anyhow!(
1450+
"Container is not running; cannot update opencode.\n\
1451+
Run:\n occ start\n\
1452+
If it exits immediately, run:\n occ logs"
1453+
)
1454+
}
1455+
1456+
fn is_container_not_running_error(err: &DockerError) -> bool {
1457+
matches!(err, DockerError::Container(msg) if msg.contains("is not running") || msg.contains("not running"))
1458+
}
1459+
13661460
async fn resolve_remote_commit(client: &DockerClient, target_ref: &str) -> Option<String> {
13671461
let output = exec_command(
13681462
client,

0 commit comments

Comments
 (0)