Skip to content

Commit 7578a1a

Browse files
committed
Refresh quick-deploy compose with conditional GET and refactor flow
1 parent 4b4c151 commit 7578a1a

5 files changed

Lines changed: 133 additions & 16 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This project uses the opencode fork at https://github.com/pRizz/opencode, which
2121

2222
## Quick Deploy (Docker)
2323

24-
Deploy opencode-cloud with one command. Installs Docker if needed (Linux), downloads the Docker Compose config, starts the service, and prints the login credentials:
24+
Deploy opencode-cloud with one command. Installs Docker if needed (Linux), downloads or refreshes the Docker Compose config, starts the service, and prints the login credentials:
2525

2626
```bash
2727
curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/quick-deploy.sh | bash
@@ -35,6 +35,8 @@ Then open [http://localhost:3000](http://localhost:3000) and enter the Initial O
3535
3636
> **Interactive mode:** Add `--interactive` to be prompted before each step: `curl -fsSL .../scripts/quick-deploy.sh | bash -s -- --interactive`
3737
38+
> **Compose refresh behavior:** By default, the script fetches the latest upstream `docker-compose.yml`. If your local file differs, it is replaced and a backup is written as `docker-compose.yml.bak.<timestamp>`.
39+
3840
## Quick install (cargo)
3941

4042
```bash
@@ -116,7 +118,7 @@ SSH into an Ubuntu 24.04 Droplet and run:
116118
curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/quick-deploy.sh | bash
117119
```
118120

119-
This installs Docker, downloads the Compose file, starts the service, and prints the IOTP.
121+
This installs Docker, by default refreshes the Compose file from upstream (with backup if your local copy differs), starts the service, and prints the IOTP.
120122

121123
Access via SSH tunnel: `ssh -L 3000:localhost:3000 root@<droplet-ip>`, then open `http://localhost:3000`.
122124

docs/deploy/digitalocean-droplet.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ Use this if you don't want `occ` installed on the Droplet.
234234

235235
Docker Compose configures all 6 named volumes automatically.
236236

237-
The quickest way is the one-liner script, which installs Docker, downloads the Compose file, starts the service, and prints the IOTP:
237+
The quickest way is the one-liner script, which installs Docker, by default refreshes the Compose file from upstream (with backup if your local copy differs), starts the service, and prints the IOTP:
238238

239239
```bash
240240
curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/quick-deploy.sh | bash

docs/deploy/docker-desktop.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ No cloud account or `occ` CLI installation is required.
1515

1616
## Quick Start (One-Liner)
1717

18-
The fastest path — installs Docker if needed, downloads the Compose config, starts the service, and prints the IOTP:
18+
The fastest path — installs Docker if needed, downloads or refreshes the Compose config, starts the service, and prints the IOTP:
1919

2020
```bash
2121
curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/quick-deploy.sh | bash
@@ -25,6 +25,8 @@ curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/q
2525
2626
> **Interactive mode:** `curl -fsSL .../scripts/quick-deploy.sh | bash -s -- --interactive`
2727
28+
> By default, if local `docker-compose.yml` differs from upstream, the script replaces it and writes a backup as `docker-compose.yml.bak.<timestamp>`.
29+
2830
## Quick Start (Docker Compose)
2931

3032
The project includes a `docker-compose.yml` that configures all persistent

packages/core/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This project uses the opencode fork at https://github.com/pRizz/opencode, which
2121

2222
## Quick Deploy (Docker)
2323

24-
Deploy opencode-cloud with one command. Installs Docker if needed (Linux), downloads the Docker Compose config, starts the service, and prints the login credentials:
24+
Deploy opencode-cloud with one command. Installs Docker if needed (Linux), downloads or refreshes the Docker Compose config, starts the service, and prints the login credentials:
2525

2626
```bash
2727
curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/quick-deploy.sh | bash
@@ -35,6 +35,8 @@ Then open [http://localhost:3000](http://localhost:3000) and enter the Initial O
3535
3636
> **Interactive mode:** Add `--interactive` to be prompted before each step: `curl -fsSL .../scripts/quick-deploy.sh | bash -s -- --interactive`
3737
38+
> **Compose refresh behavior:** By default, the script fetches the latest upstream `docker-compose.yml`. If your local file differs, it is replaced and a backup is written as `docker-compose.yml.bak.<timestamp>`.
39+
3840
## Quick install (cargo)
3941

4042
```bash
@@ -116,7 +118,7 @@ SSH into an Ubuntu 24.04 Droplet and run:
116118
curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/quick-deploy.sh | bash
117119
```
118120

119-
This installs Docker, downloads the Compose file, starts the service, and prints the IOTP.
121+
This installs Docker, by default refreshes the Compose file from upstream (with backup if your local copy differs), starts the service, and prints the IOTP.
120122

121123
Access via SSH tunnel: `ssh -L 3000:localhost:3000 root@<droplet-ip>`, then open `http://localhost:3000`.
122124

scripts/quick-deploy.sh

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
set -euo pipefail
33

44
# opencode-cloud quick deploy
5-
# Downloads docker-compose.yml and starts the service with persistent volumes.
5+
# Downloads or refreshes docker-compose.yml and starts the service with persistent volumes.
66
# Usage: curl -fsSL https://raw.githubusercontent.com/pRizz/opencode-cloud/main/scripts/quick-deploy.sh | bash
77
# Interactive: curl -fsSL .../scripts/quick-deploy.sh | bash -s -- --interactive
88

@@ -12,6 +12,8 @@ set -euo pipefail
1212

1313
COMPOSE_URL="https://raw.githubusercontent.com/pRizz/opencode-cloud/main/docker-compose.yml"
1414
COMPOSE_FILE="docker-compose.yml"
15+
# Stores upstream ETag so reruns can use If-None-Match and skip unchanged downloads.
16+
COMPOSE_ETAG_FILE=".docker-compose.yml.etag"
1517
CONTAINER_NAME="opencode-cloud-sandbox"
1618
IOTP_MAX_WAIT_SECONDS=120
1719
IOTP_POLL_INTERVAL=3
@@ -349,28 +351,137 @@ ensure_compose_command() {
349351
success "Compose command: $COMPOSE_CMD (v$compose_version)"
350352
}
351353

352-
download_compose_file() {
353-
header "Docker Compose File"
354+
cleanup_temp_files() {
355+
local temp_file="${1:-}" headers_file="${2:-}"
356+
[ -n "$temp_file" ] && rm -f "$temp_file"
357+
[ -n "$headers_file" ] && rm -f "$headers_file"
358+
}
354359

355-
if [ -f "$COMPOSE_FILE" ]; then
356-
info "$COMPOSE_FILE already exists — using existing file"
360+
read_compose_etag() {
361+
[ -s "$COMPOSE_ETAG_FILE" ] || return 0
362+
tr -d '\r\n' < "$COMPOSE_ETAG_FILE"
363+
}
364+
365+
extract_header_etag() {
366+
local headers_file="$1"
367+
awk 'BEGIN{IGNORECASE=1} /^etag:/{sub(/^[^:]*:[[:space:]]*/, ""); gsub(/\r/,""); print; exit}' "$headers_file"
368+
}
369+
370+
save_compose_etag() {
371+
local remote_etag="${1:-}"
372+
[ -n "$remote_etag" ] || return 0
373+
printf '%s\n' "$remote_etag" > "$COMPOSE_ETAG_FILE"
374+
}
375+
376+
curl_fetch_compose() {
377+
local temp_file="$1" headers_file="$2" etag="${3:-}" http_code
378+
if [ -n "$etag" ]; then
379+
http_code="$(curl -sSL -D "$headers_file" -o "$temp_file" -w '%{http_code}' -H "If-None-Match: $etag" "$COMPOSE_URL")" || return 1
380+
else
381+
http_code="$(curl -sSL -D "$headers_file" -o "$temp_file" -w '%{http_code}' "$COMPOSE_URL")" || return 1
382+
fi
383+
printf '%s\n' "$http_code"
384+
}
385+
386+
reconcile_compose_file() {
387+
local temp_file="$1" remote_etag="${2:-}"
388+
389+
if [ ! -f "$COMPOSE_FILE" ]; then
390+
mv "$temp_file" "$COMPOSE_FILE"
391+
save_compose_etag "$remote_etag"
392+
success "Downloaded $COMPOSE_FILE"
393+
return 0
394+
fi
395+
396+
if cmp -s "$temp_file" "$COMPOSE_FILE"; then
397+
rm -f "$temp_file"
398+
save_compose_etag "$remote_etag"
399+
success "$COMPOSE_FILE is already up to date"
357400
return 0
358401
fi
359402

360-
if ! confirm "Download $COMPOSE_FILE from GitHub?"; then
403+
if [ "$INTERACTIVE" = true ] && ! confirm "$COMPOSE_FILE differs from upstream. Replace it? (A backup will be created.)"; then
404+
rm -f "$temp_file"
405+
warn "Keeping existing $COMPOSE_FILE (it may be stale)."
406+
return 0
407+
fi
408+
409+
local backup_file
410+
backup_file="${COMPOSE_FILE}.bak.$(date +%Y%m%d%H%M%S)"
411+
cp "$COMPOSE_FILE" "$backup_file"
412+
mv "$temp_file" "$COMPOSE_FILE"
413+
save_compose_etag "$remote_etag"
414+
success "Updated $COMPOSE_FILE (backup: $backup_file)"
415+
}
416+
417+
download_compose_file() {
418+
header "Docker Compose File"
419+
420+
if ! confirm "Download latest $COMPOSE_FILE from GitHub?"; then
421+
if [ -f "$COMPOSE_FILE" ]; then
422+
warn "Skipping download — using existing $COMPOSE_FILE (it may be stale)."
423+
return 0
424+
fi
361425
die "$COMPOSE_FILE is required. Download it manually:
362426
curl -fsSL -o $COMPOSE_FILE $COMPOSE_URL"
363427
fi
364428

365-
info "Downloading $COMPOSE_FILE..."
429+
local temp_file headers_file remote_etag current_etag http_code
430+
temp_file="$(mktemp "${COMPOSE_FILE}.tmp.XXXXXX")"
431+
headers_file="$(mktemp "${COMPOSE_FILE}.headers.XXXXXX")"
432+
info "Downloading latest $COMPOSE_FILE..."
433+
366434
if command -v curl >/dev/null 2>&1; then
367-
curl -fsSL -o "$COMPOSE_FILE" "$COMPOSE_URL"
435+
current_etag="$(read_compose_etag)"
436+
if [ -n "$current_etag" ]; then
437+
info "Checking for upstream changes (conditional GET)..."
438+
if ! http_code="$(curl_fetch_compose "$temp_file" "$headers_file" "$current_etag")"; then
439+
cleanup_temp_files "$temp_file" "$headers_file"
440+
die "Failed to download $COMPOSE_FILE from GitHub."
441+
fi
442+
443+
case "$http_code" in
444+
304)
445+
cleanup_temp_files "$temp_file" "$headers_file"
446+
if [ -f "$COMPOSE_FILE" ]; then
447+
success "$COMPOSE_FILE is already up to date (HTTP 304)"
448+
return 0
449+
fi
450+
warn "Received HTTP 304 but $COMPOSE_FILE is missing. Retrying without ETag..."
451+
;;
452+
200)
453+
;;
454+
*)
455+
cleanup_temp_files "$temp_file" "$headers_file"
456+
die "Failed to download $COMPOSE_FILE from GitHub (HTTP $http_code)."
457+
;;
458+
esac
459+
fi
460+
461+
if [ ! -s "$temp_file" ]; then
462+
if ! http_code="$(curl_fetch_compose "$temp_file" "$headers_file")"; then
463+
cleanup_temp_files "$temp_file" "$headers_file"
464+
die "Failed to download $COMPOSE_FILE from GitHub."
465+
fi
466+
if [ "$http_code" != "200" ]; then
467+
cleanup_temp_files "$temp_file" "$headers_file"
468+
die "Failed to download $COMPOSE_FILE from GitHub (HTTP $http_code)."
469+
fi
470+
fi
471+
472+
remote_etag="$(extract_header_etag "$headers_file")"
368473
elif command -v wget >/dev/null 2>&1; then
369-
wget -qO "$COMPOSE_FILE" "$COMPOSE_URL"
474+
warn "curl not found; falling back to wget without conditional GET."
475+
if ! wget -qO "$temp_file" "$COMPOSE_URL"; then
476+
cleanup_temp_files "$temp_file" "$headers_file"
477+
die "Failed to download $COMPOSE_FILE from GitHub."
478+
fi
370479
else
480+
cleanup_temp_files "$temp_file" "$headers_file"
371481
die "Neither curl nor wget found. Install one and re-run."
372482
fi
373-
success "Downloaded $COMPOSE_FILE"
483+
cleanup_temp_files "" "$headers_file"
484+
reconcile_compose_file "$temp_file" "${remote_etag:-}"
374485
}
375486

376487
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)