@@ -13,7 +13,8 @@ set -euo pipefail
1313COMPOSE_URL=" https://raw.githubusercontent.com/pRizz/opencode-cloud/main/docker-compose.yml"
1414COMPOSE_FILE=" docker-compose.yml"
1515CONTAINER_NAME=" opencode-cloud-sandbox"
16- # Must match entrypoint.sh greppable_iotp_prefix
16+ # Coupling: must match entrypoint.sh greppable_iotp_prefix (line 104).
17+ # Do not change this string without updating entrypoint.sh and vice versa.
1718IOTP_GREP_PATTERN=" INITIAL ONE-TIME PASSWORD (IOTP): "
1819IOTP_MAX_WAIT_SECONDS=120
1920IOTP_POLL_INTERVAL=3
@@ -298,6 +299,7 @@ ensure_docker() {
298299
299300 if command -v docker > /dev/null 2>&1 && docker info > /dev/null 2>&1 ; then
300301 success " Docker is installed and running"
302+ info " Docker version: $( docker version --format ' {{.Server.Version}}' 2> /dev/null || echo ' unknown' ) "
301303 return 0
302304 fi
303305
@@ -311,6 +313,7 @@ ensure_docker() {
311313 || run_privileged service docker start 2> /dev/null \
312314 || die " Could not start Docker. Start it manually and re-run this script."
313315 wait_for_docker
316+ info " Docker version: $( docker version --format ' {{.Server.Version}}' 2> /dev/null || echo ' unknown' ) "
314317 return 0
315318 fi
316319
@@ -328,6 +331,7 @@ ensure_docker() {
328331
329332 install_docker_linux
330333 wait_for_docker
334+ info " Docker version: $( docker version --format ' {{.Server.Version}}' 2> /dev/null || echo ' unknown' ) "
331335}
332336
333337# ---------------------------------------------------------------------------
@@ -343,7 +347,9 @@ ensure_compose_command() {
343347 die " Neither 'docker compose' (plugin) nor 'docker-compose' (standalone) found.
344348 Install Docker Compose: https://docs.docker.com/compose/install/"
345349 fi
346- success " Compose command: $COMPOSE_CMD "
350+ local compose_version
351+ compose_version=" $( $COMPOSE_CMD version --short 2> /dev/null || echo ' unknown' ) "
352+ success " Compose command: $COMPOSE_CMD (v$compose_version )"
347353}
348354
349355download_compose_file () {
@@ -370,15 +376,39 @@ download_compose_file() {
370376 success " Downloaded $COMPOSE_FILE "
371377}
372378
379+ # ---------------------------------------------------------------------------
380+ # Container helpers
381+ # ---------------------------------------------------------------------------
382+
383+ get_container_id () {
384+ docker ps --filter " name=^${CONTAINER_NAME} $" --format ' {{.ID}}' 2> /dev/null || true
385+ }
386+
387+ # Coupling: queries opencode-cloud-bootstrap status inside the container.
388+ # The JSON contract (active, reason fields) is defined in
389+ # opencode-cloud-bootstrap.sh emit_status(). Do not change that contract
390+ # without updating this function.
391+ query_bootstrap_status () {
392+ docker exec " $CONTAINER_NAME " \
393+ /usr/local/bin/opencode-cloud-bootstrap status 2> /dev/null || true
394+ }
395+
373396# ---------------------------------------------------------------------------
374397# Service lifecycle
375398# ---------------------------------------------------------------------------
376399
377400start_services () {
378401 header " Starting opencode-cloud"
379402
380- if docker ps --format ' {{.Names}}' 2> /dev/null | grep -qxF " $CONTAINER_NAME " ; then
403+ local container_id
404+ container_id=" $( get_container_id) "
405+
406+ if [ -n " $container_id " ]; then
407+ local image
408+ image=" $( docker inspect --format ' {{.Config.Image}}' " $container_id " 2> /dev/null || echo " unknown" ) "
381409 info " Container '$CONTAINER_NAME ' is already running"
410+ info " Container ID: $container_id "
411+ info " Image: $image "
382412 info " Restart: $COMPOSE_CMD restart"
383413 info " Stop: $COMPOSE_CMD down"
384414 return 0
@@ -390,28 +420,80 @@ start_services() {
390420 fi
391421
392422 $COMPOSE_CMD up -d
393- success " Services started"
423+ container_id=" $( get_container_id) "
424+ success " Services started (container: ${container_id:- unknown} )"
394425}
395426
396427# ---------------------------------------------------------------------------
397- # IOTP extraction
428+ # Status check and IOTP extraction
398429# ---------------------------------------------------------------------------
399430
400- wait_for_iotp () {
401- header " Waiting for Initial One-Time Password (IOTP)"
402- info " The container is starting up. This may take a moment..."
431+ check_status_and_iotp () {
432+ header " Checking Setup Status"
403433
404- local elapsed=0
405- local iotp=" "
434+ local elapsed=0 status_json=" " active=" " reason=" "
435+
436+ # Phase 1: poll bootstrap status via docker exec until a terminal state
437+ info " Waiting for container to initialize..."
438+ while [ " $elapsed " -lt " $IOTP_MAX_WAIT_SECONDS " ]; do
439+ status_json=" $( query_bootstrap_status) "
440+ if [ -n " $status_json " ]; then
441+ active=" $( printf ' %s' " $status_json " | jq -r ' .active // empty' 2> /dev/null || true) "
442+ reason=" $( printf ' %s' " $status_json " | jq -r ' .reason // empty' 2> /dev/null || true) "
443+
444+ if [ " $active " = " true" ]; then
445+ break # IOTP is active — proceed to phase 2
446+ fi
447+
448+ case " $reason " in
449+ user_exists)
450+ printf ' \n' >&2
451+ display_already_configured
452+ return 0
453+ ;;
454+ completed)
455+ printf ' \n' >&2
456+ display_setup_complete
457+ return 0
458+ ;;
459+ not_initialized)
460+ ;; # container still starting up, keep polling
461+ invalid_state|invalid_secret)
462+ printf ' \n' >&2
463+ warn " Bootstrap state is corrupted (reason: $reason )."
464+ warn " Reset with: docker exec $CONTAINER_NAME /usr/local/bin/opencode-cloud-bootstrap reset"
465+ display_ready_generic
466+ return 0
467+ ;;
468+ * )
469+ ;; # unknown reason, keep polling
470+ esac
471+ fi
472+
473+ sleep " $IOTP_POLL_INTERVAL "
474+ elapsed=$(( elapsed + IOTP_POLL_INTERVAL))
475+ printf ' .' >&2
476+ done
406477
478+ if [ " $active " != " true" ]; then
479+ printf ' \n' >&2
480+ warn " Container did not produce bootstrap status within ${IOTP_MAX_WAIT_SECONDS} s."
481+ warn " Check container logs: docker logs $CONTAINER_NAME "
482+ return 0
483+ fi
484+
485+ # Phase 2: IOTP is active — extract the actual value from container logs
486+ printf ' \n' >&2
487+ info " IOTP is active. Extracting from container logs..."
488+ local iotp=" "
407489 while [ " $elapsed " -lt " $IOTP_MAX_WAIT_SECONDS " ]; do
408- iotp=" $( $COMPOSE_CMD logs 2>&1 \
490+ iotp=" $( docker logs " $CONTAINER_NAME " 2>&1 \
409491 | grep -F " $IOTP_GREP_PATTERN " \
410492 | tail -n1 \
411493 | sed " s/.*${IOTP_GREP_PATTERN} //" || true) "
412494
413495 if [ -n " $iotp " ]; then
414- display_success " $iotp "
496+ display_fresh_setup " $iotp "
415497 return 0
416498 fi
417499
@@ -421,15 +503,27 @@ wait_for_iotp() {
421503 done
422504
423505 printf ' \n' >&2
424- warn " Timed out waiting for IOTP after ${IOTP_MAX_WAIT_SECONDS} s."
425- warn " The container may still be starting. Check logs manually:"
426- warn " $COMPOSE_CMD logs | grep -F \" INITIAL ONE-TIME PASSWORD (IOTP): \" "
427- warn " "
428- warn " If a user was already configured, no IOTP is emitted."
429- warn " Open $SERVICE_URL and sign in with your existing credentials."
506+ warn " Could not extract IOTP from logs within ${IOTP_MAX_WAIT_SECONDS} s."
507+ warn " The IOTP should be in the container logs:"
508+ warn " docker logs $CONTAINER_NAME | grep -F \" INITIAL ONE-TIME PASSWORD (IOTP): \" "
430509}
431510
432- display_success () {
511+ # ---------------------------------------------------------------------------
512+ # Display banners
513+ # ---------------------------------------------------------------------------
514+
515+ display_useful_commands () {
516+ printf ' %sUseful commands:%s\n' " $COLOR_BOLD " " $COLOR_RESET "
517+ printf ' View logs: docker logs -f %s\n' " $CONTAINER_NAME "
518+ printf ' Stop service: %s down\n' " $COMPOSE_CMD "
519+ printf ' Restart: %s restart\n' " $COMPOSE_CMD "
520+ printf ' Update image: %s pull && %s up -d\n' " $COMPOSE_CMD " " $COMPOSE_CMD "
521+ printf ' \n'
522+ printf ' Docs: https://github.com/pRizz/opencode-cloud\n'
523+ printf ' \n'
524+ }
525+
526+ display_fresh_setup () {
433527 local iotp=" $1 "
434528 printf ' \n'
435529 printf ' %s%s\n' " $COLOR_GREEN " " $COLOR_BOLD "
@@ -449,14 +543,49 @@ display_success() {
449543 printf ' \n'
450544 printf ' The IOTP is deleted after successful setup.\n'
451545 printf ' \n'
452- printf ' %sUseful commands:%s\n' " $COLOR_BOLD " " $COLOR_RESET "
453- printf ' View logs: %s logs -f\n' " $COMPOSE_CMD "
454- printf ' Stop service: %s down\n' " $COMPOSE_CMD "
455- printf ' Restart: %s restart\n' " $COMPOSE_CMD "
456- printf ' Update image: %s pull && %s up -d\n' " $COMPOSE_CMD " " $COMPOSE_CMD "
546+ display_useful_commands
547+ }
548+
549+ display_already_configured () {
457550 printf ' \n'
458- printf ' Docs: https://github.com/pRizz/opencode-cloud\n'
551+ printf ' %s%s\n' " $COLOR_GREEN " " $COLOR_BOLD "
552+ printf ' ========================================================\n'
553+ printf ' opencode-cloud is ready!\n'
554+ printf ' ========================================================\n'
555+ printf ' %s\n' " $COLOR_RESET "
556+ printf ' \n'
557+ printf ' A user account is already configured.\n'
558+ printf ' No Initial One-Time Password is needed.\n'
559+ printf ' \n'
560+ printf ' Open %s%s%s and sign in with your existing credentials.\n' " $COLOR_CYAN " " $SERVICE_URL " " $COLOR_RESET "
561+ printf ' \n'
562+ display_useful_commands
563+ }
564+
565+ display_setup_complete () {
566+ printf ' \n'
567+ printf ' %s%s\n' " $COLOR_GREEN " " $COLOR_BOLD "
568+ printf ' ========================================================\n'
569+ printf ' opencode-cloud is ready!\n'
570+ printf ' ========================================================\n'
571+ printf ' %s\n' " $COLOR_RESET "
572+ printf ' \n'
573+ printf ' First-time setup was previously completed.\n'
574+ printf ' No Initial One-Time Password is needed.\n'
575+ printf ' \n'
576+ printf ' Open %s%s%s and sign in with your credentials.\n' " $COLOR_CYAN " " $SERVICE_URL " " $COLOR_RESET "
577+ printf ' \n'
578+ printf ' To reset and generate a new IOTP:\n'
579+ printf ' docker exec %s /usr/local/bin/opencode-cloud-bootstrap reset\n' " $CONTAINER_NAME "
580+ printf ' \n'
581+ display_useful_commands
582+ }
583+
584+ display_ready_generic () {
585+ printf ' \n'
586+ printf ' Open %s%s%s to access opencode-cloud.\n' " $COLOR_CYAN " " $SERVICE_URL " " $COLOR_RESET "
459587 printf ' \n'
588+ display_useful_commands
460589}
461590
462591# ---------------------------------------------------------------------------
@@ -480,7 +609,7 @@ main() {
480609 ensure_compose_command
481610 download_compose_file
482611 start_services
483- wait_for_iotp
612+ check_status_and_iotp
484613}
485614
486615main " $@ "
0 commit comments