Skip to content

Commit dd88fef

Browse files
pRizzclaude
andcommitted
improve quick-deploy.sh: state-aware IOTP detection and better messaging
Use `docker exec` to query the bootstrap status helper inside the container instead of blindly polling logs for 120s. This correctly handles three scenarios: - Fresh setup (IOTP active): extract and display the IOTP from logs - Already configured (user_exists): display "already configured" immediately - Setup complete (IOTP consumed): display "setup complete" immediately Also adds Docker/Compose version logging, container ID and image info when an existing container is detected, and extracts display helpers into distinct banners for each outcome. Switches from `docker compose logs` to `docker logs <container>` so IOTP extraction works regardless of how the container was started (compose, occ, just run, etc.). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9592353 commit dd88fef

1 file changed

Lines changed: 155 additions & 26 deletions

File tree

scripts/quick-deploy.sh

Lines changed: 155 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ set -euo pipefail
1313
COMPOSE_URL="https://raw.githubusercontent.com/pRizz/opencode-cloud/main/docker-compose.yml"
1414
COMPOSE_FILE="docker-compose.yml"
1515
CONTAINER_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.
1718
IOTP_GREP_PATTERN="INITIAL ONE-TIME PASSWORD (IOTP): "
1819
IOTP_MAX_WAIT_SECONDS=120
1920
IOTP_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

349355
download_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

377400
start_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

486615
main "$@"

0 commit comments

Comments
 (0)