@@ -20,8 +20,9 @@ use opencode_cloud_core::config::load_config_or_default;
2020use opencode_cloud_core:: docker:: update:: PREVIOUS_TAG ;
2121use opencode_cloud_core:: docker:: update:: tag_current_as_previous;
2222use 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
11771180async 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
11941201async 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+
13661460async fn resolve_remote_commit ( client : & DockerClient , target_ref : & str ) -> Option < String > {
13671461 let output = exec_command (
13681462 client,
0 commit comments