From 2d11185ba8fa6667a62e7fb77b0baced8d6df96a Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Thu, 7 May 2026 15:17:10 +1000 Subject: [PATCH 01/21] fix: upgrade linux dockerfile to debian 12 --- docker/linux/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index 75525462a..1fc87fb20 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:11-slim +FROM debian:12-slim ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true RUN apt-get update && \ apt-get install -y --no-install-recommends \ From c0540dd16a0f39e435e566aa4ec1980b9a248587 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Thu, 7 May 2026 15:54:52 +1000 Subject: [PATCH 02/21] fix: upgrade apt deps libicu72 and libssl3 --- docker/linux/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index 1fc87fb20..659cce5d4 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -6,8 +6,8 @@ RUN apt-get update && \ libc6 \ libgcc1 \ libgssapi-krb5-2 \ - libicu67 \ - libssl1.1 \ + libicu72 \ + libssl3 \ libstdc++6 \ zlib1g && \ rm -rf /var/lib/apt/lists/* From 1f08e49fb980034c5192ff2bb9c4795ea4dc5b1c Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Thu, 7 May 2026 16:12:13 +1000 Subject: [PATCH 03/21] fix: add apt-utils to deps to avoid warning => debconf: delaying package configuration, since apt-utils is not installed --- docker/linux/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index 659cce5d4..0b789a1c5 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -2,6 +2,7 @@ FROM debian:12-slim ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true RUN apt-get update && \ apt-get install -y --no-install-recommends \ + apt-utils \ ca-certificates \ libc6 \ libgcc1 \ From d39a5b13904b2904238112ca77c68b7eef107548 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Thu, 7 May 2026 16:28:41 +1000 Subject: [PATCH 04/21] tidy: remove explicit install of things already installed by default --- docker/linux/Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index 0b789a1c5..01cff5cca 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -1,16 +1,14 @@ FROM debian:12-slim +ENV DEBIAN_FRONTEND=noninteractive ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true RUN apt-get update && \ apt-get install -y --no-install-recommends \ apt-utils \ ca-certificates \ - libc6 \ libgcc1 \ libgssapi-krb5-2 \ libicu72 \ - libssl3 \ - libstdc++6 \ - zlib1g && \ + libssl3 && \ rm -rf /var/lib/apt/lists/* ARG BUILD_NUMBER From 04369654d13addcfa07e97b8ec07bfa077ec68f0 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Sat, 9 May 2026 11:55:18 +1000 Subject: [PATCH 05/21] fix: suppress stderr warning of dpkg interactive config --- docker/linux/Dockerfile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index 01cff5cca..d4f791760 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -1,11 +1,9 @@ FROM debian:12-slim -ENV DEBIAN_FRONTEND=noninteractive ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true RUN apt-get update && \ - apt-get install -y --no-install-recommends \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ apt-utils \ ca-certificates \ - libgcc1 \ libgssapi-krb5-2 \ libicu72 \ libssl3 && \ @@ -15,7 +13,7 @@ ARG BUILD_NUMBER ARG BUILD_DATE RUN apt-get update && \ - apt-get install -y \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ curl \ dos2unix \ jq \ @@ -42,7 +40,7 @@ RUN /install-scripts/install-docker.sh # Install Tentacle COPY _artifacts/deb/tentacle_${BUILD_NUMBER}_amd64.deb /tmp/ RUN apt-get update && \ - apt install ./tentacle_${BUILD_NUMBER}_amd64.deb && \ + DEBIAN_FRONTEND=noninteractive apt-get install ./tentacle_${BUILD_NUMBER}_amd64.deb && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ ln -s /opt/octopus/tentacle/Tentacle /usr/bin/tentacle From 9313b9fccc84c47636bd8ad0f9e2795af1e8a1aa Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Mon, 11 May 2026 11:39:30 +1000 Subject: [PATCH 06/21] revert: doesn't help --- docker/linux/Dockerfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index d4f791760..f8c998261 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -1,8 +1,7 @@ FROM debian:12-slim ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - apt-utils \ + apt-get install -y --no-install-recommends \ ca-certificates \ libgssapi-krb5-2 \ libicu72 \ @@ -13,7 +12,7 @@ ARG BUILD_NUMBER ARG BUILD_DATE RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y \ + apt-get install -y \ curl \ dos2unix \ jq \ @@ -40,7 +39,7 @@ RUN /install-scripts/install-docker.sh # Install Tentacle COPY _artifacts/deb/tentacle_${BUILD_NUMBER}_amd64.deb /tmp/ RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install ./tentacle_${BUILD_NUMBER}_amd64.deb && \ + apt-get install ./tentacle_${BUILD_NUMBER}_amd64.deb && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ ln -s /opt/octopus/tentacle/Tentacle /usr/bin/tentacle From 3b4c6962c52d37846d3d90c09ad2ccf94ce0e9be Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Tue, 12 May 2026 11:14:05 +1000 Subject: [PATCH 07/21] test: add E2E smoke test for Linux Tentacle Docker image Builds the image from the local .deb, brings up a local Octopus Server, registers the Tentacle as a worker, and runs a hello-world AdHocScript via the REST API to verify the Debian 12 upgrade end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 244 +++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100755 scripts/smoke-test-linux-tentacle.sh diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh new file mode 100755 index 000000000..c9ed56588 --- /dev/null +++ b/scripts/smoke-test-linux-tentacle.sh @@ -0,0 +1,244 @@ +#!/usr/bin/env bash +# +# End-to-end smoke test for the Linux Tentacle Docker image (EFT-3311). +# +# Builds the image from the .deb in _artifacts/deb, brings up a local Octopus +# Server in the sibling OctopusDeploy repo, registers the Tentacle as a worker, +# runs a hello-world AdHocScript on it, and asserts success. +# +# Required tools: docker, op (signed in), curl, jq. +# Required state: a built .deb in ../_artifacts/deb/tentacle_*_amd64.deb and the +# OctopusDeploy repo checked out alongside OctopusTentacle. + +set -euo pipefail + +TENTACLE_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SERVER_REPO="${SERVER_REPO:-$(cd "$TENTACLE_REPO/../OctopusDeploy" && pwd)}" +ENV_FILE="$SERVER_REPO/.env" +ENV_BACKUP="$ENV_FILE.smoke-test.bak" +# Transient compose override: disables Docker-in-Docker on the Tentacle. The +# default tentacle entrypoint launches a dockerd daemon, which requires the +# container to run with `--privileged`; without that the daemon fails and its +# wrapper script kills the Tentacle agent. Setting DISABLE_DIND=Y skips it. +OVERRIDE_COMPOSE="$SERVER_REPO/docker-compose.smoke.yml" + +API="http://localhost:8065/api" +API_KEY="API-APIKEY01" +H="X-Octopus-ApiKey: $API_KEY" +IMAGE_TAG="smoke-debian12" +ONEPASSWORD_LICENSE_REF="op://software licencing/octopus deploy ultimate license key base64/value" + +log() { printf '\033[1;34m[smoke]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[smoke]\033[0m %s\n' "$*" >&2; } +die() { printf '\033[1;31m[smoke]\033[0m %s\n' "$*" >&2; exit 1; } + +require() { command -v "$1" >/dev/null || die "Missing required tool: $1"; } +require docker +require op +require curl +require jq + +teardown() { + local exit_code=$? + log "--- teardown ---" + if [[ -f "$OVERRIDE_COMPOSE" ]]; then + (cd "$SERVER_REPO" && docker compose -f docker-compose.yml -f docker-compose.smoke.yml --profile tentacle down 2>/dev/null) || true + rm -f "$OVERRIDE_COMPOSE" + fi + (cd "$SERVER_REPO" && docker compose down 2>/dev/null) || true + if [[ -f "$ENV_BACKUP" ]]; then + mv "$ENV_BACKUP" "$ENV_FILE" + log "Restored $ENV_FILE" + fi + exit "$exit_code" +} +trap teardown EXIT + +############################################################################### +# Step 1: Build the Linux Tentacle image from the local .deb +############################################################################### +log "--- Step 1: build Tentacle image ---" +cd "$TENTACLE_REPO" + +shopt -s nullglob +DEBS=(_artifacts/deb/tentacle_*_amd64.deb) +shopt -u nullglob +[[ ${#DEBS[@]} -ge 1 ]] || die "No .deb found in _artifacts/deb/. Build it first." +[[ ${#DEBS[@]} -eq 1 ]] || die "Multiple .debs in _artifacts/deb/; expected one: ${DEBS[*]}" +DEB_FILE="${DEBS[0]}" +DEB_BASENAME="$(basename "$DEB_FILE")" +BUILD_NUMBER="${DEB_BASENAME#tentacle_}" +BUILD_NUMBER="${BUILD_NUMBER%_amd64.deb}" +export BUILD_NUMBER +export BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + +log "BUILD_NUMBER=$BUILD_NUMBER" +# Use `docker build` directly rather than `docker compose -f docker-compose.build.yml` +# because that compose file also defines kubernetes/windows tentacle services which +# require extra env vars (BUILD_ARCH, BUILD_VARIANT) we don't care about here. +DST_IMAGE="octopusdeploy/tentacle:${IMAGE_TAG}" +docker build \ + --platform linux/amd64 \ + --build-arg BUILD_NUMBER="$BUILD_NUMBER" \ + --build-arg BUILD_DATE="$BUILD_DATE" \ + -f docker/linux/Dockerfile \ + -t "$DST_IMAGE" \ + . +log "Built $DST_IMAGE" + +############################################################################### +# Step 2: Fetch license from 1Password & patch .env +############################################################################### +log "--- Step 2: fetch license and patch .env ---" +[[ -f "$ENV_FILE" ]] || die "Expected $ENV_FILE to exist." + +if ! op account list >/dev/null 2>&1; then + die "1Password CLI is not signed in. Run: eval \$(op signin)" +fi + +LICENSE_BASE64="$(op read "$ONEPASSWORD_LICENSE_REF" 2>/dev/null || true)" +[[ -n "$LICENSE_BASE64" ]] || die "Could not read license from 1Password at: $ONEPASSWORD_LICENSE_REF" +log "Fetched license from 1Password (${#LICENSE_BASE64} bytes)" + +cp "$ENV_FILE" "$ENV_BACKUP" + +upsert_env_var() { + local key="$1" value="$2" + if grep -q "^${key}=" "$ENV_FILE"; then + # Use a sed delimiter unlikely to appear in base64 + python3 - "$ENV_FILE" "$key" "$value" <<'PY' +import sys, re +path, key, value = sys.argv[1], sys.argv[2], sys.argv[3] +with open(path) as f: txt = f.read() +txt = re.sub(rf'^{re.escape(key)}=.*$', f'{key}={value}', txt, count=1, flags=re.M) +with open(path, 'w') as f: f.write(txt) +PY + else + printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE" + fi +} + +upsert_env_var TENTACLE_TAG "$IMAGE_TAG" +upsert_env_var OCTOPUS_SERVER_BASE64_LICENSE "$LICENSE_BASE64" + +############################################################################### +# Step 3: Bring up Octopus Server and wait for /api to respond +############################################################################### +log "--- Step 3: start octopus-server ---" +cd "$SERVER_REPO" +docker compose up -d octopus-server + +log "Waiting for $API/octopusservernodes/ping ..." +for i in {1..120}; do + if curl -fsS -H "$H" "$API/octopusservernodes/ping" >/dev/null 2>&1; then + log "Server is up after ${i}s" + break + fi + [[ $i -eq 120 ]] && die "Server did not become ready in 120s" + sleep 1 +done + +############################################################################### +# Step 4: Bring up the Tentacle (Worker, polling mode, DIND disabled) +############################################################################### +log "--- Step 4: start tentacle ---" +cat > "$OVERRIDE_COMPOSE" <<'YAML' +services: + tentacle: + environment: + DISABLE_DIND: "Y" +YAML + +COMPOSE=(docker compose -f docker-compose.yml -f docker-compose.smoke.yml --profile tentacle) + +# --no-deps because octopus-server may lack a healthcheck; we already polled +# its API ping above and know it's ready. +"${COMPOSE[@]}" up -d --no-deps tentacle + +log "Waiting for Tentacle 'Configuration successful.' in logs ..." +for i in {1..60}; do + if "${COMPOSE[@]}" logs --no-color tentacle 2>/dev/null | grep -q "Configuration successful."; then + log "Tentacle registered after ${i}s" + break + fi + [[ $i -eq 60 ]] && die "Tentacle did not register in 60s. Logs: +$("${COMPOSE[@]}" logs --no-color --tail=80 tentacle)" + sleep 1 +done + +# Make sure the agent is still running (the wrapper script can exit shortly +# after registration if a sidecar like dockerd dies). +if ! "${COMPOSE[@]}" ps --status running --services 2>/dev/null | grep -qx tentacle; then + die "Tentacle container exited shortly after registration. Logs: +$("${COMPOSE[@]}" logs --no-color --tail=80 tentacle)" +fi + +############################################################################### +# Step 5: Verify worker is registered & run hello-world AdHocScript +############################################################################### +log "--- Step 5: verify registration and run hello-world ---" + +# Find the worker we just registered. The Tentacle picks its container hostname +# as the default name, so we can't filter by name reliably. Instead, take the +# worker whose Id is the largest "Workers-N" — i.e. the most recent registration. +WORKER_ID="" +WORKER_NAME="" +for i in {1..60}; do + WORKERS_JSON="$(curl -fsS -H "$H" "$API/workers?take=1000" 2>/dev/null || echo '{"Items":[]}')" + WORKER_ID="$(echo "$WORKERS_JSON" \ + | jq -r '[.Items[] | select(.Id | startswith("Workers-"))] | sort_by(.Id | ltrimstr("Workers-") | tonumber) | last | .Id // empty')" + WORKER_NAME="$(echo "$WORKERS_JSON" | jq -r --arg id "$WORKER_ID" '.Items[] | select(.Id == $id) | .Name // empty')" + [[ -n "$WORKER_ID" ]] && break + sleep 1 +done +if [[ -z "$WORKER_ID" ]]; then + warn "No worker appeared. Diagnostic dump of $API/workers:" + curl -fsS -H "$H" "$API/workers" || true + warn "Tentacle container logs (tail 80):" + docker compose --profile tentacle logs --no-color --tail=80 tentacle || true + die "Worker did not appear after 60s" +fi +log "Registered worker: $WORKER_ID (name='$WORKER_NAME')" + +ADHOC_BODY="$(jq -nc \ + --arg id "$WORKER_ID" \ + '{ + Name: "AdHocScript", + Description: "EFT-3311 Debian 12 smoke test", + Arguments: { + ScriptBody: "echo Hello from $(hostname); cat /etc/os-release | head -2", + Syntax: "Bash", + WorkerIds: [$id] + } + }')" + +TASK_RESP="$(curl -fsS -X POST -H "$H" -H "Content-Type: application/json" \ + "$API/tasks" -d "$ADHOC_BODY")" +TASK_ID="$(echo "$TASK_RESP" | jq -r '.Id')" +[[ -n "$TASK_ID" && "$TASK_ID" != "null" ]] || die "Could not submit AdHocScript task. Response: $TASK_RESP" +log "Submitted task: $TASK_ID" + +STATE="" +for i in {1..120}; do + STATE="$(curl -fsS -H "$H" "$API/tasks/$TASK_ID" | jq -r '.State')" + echo " task=$TASK_ID state=$STATE" + case "$STATE" in + Success|Failed|Canceled|TimedOut) break ;; + esac + sleep 2 +done + +log "--- Task log ---" +curl -fsS -H "$H" "$API/tasks/$TASK_ID/raw" || true +log "--- end task log ---" + +if [[ "$STATE" != "Success" ]]; then + die "Task finished in state '$STATE' (expected Success)" +fi + +# Sanity: the printed log should mention Debian 12. +if ! curl -fsS -H "$H" "$API/tasks/$TASK_ID/raw" | grep -q 'Debian GNU/Linux 12'; then + warn "Task succeeded but the log does NOT mention 'Debian GNU/Linux 12'. Inspect output above." +fi + +log "PASS — Tentacle (Debian 12) registered and executed hello-world." From f732c6d327f957e488d3d01c0f7b9eae70e8ee3c Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Tue, 12 May 2026 11:18:59 +1000 Subject: [PATCH 08/21] +semver: major From 61cf3f6e48cc281e88f884c401b85a4a448b721a Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Tue, 12 May 2026 13:56:50 +1000 Subject: [PATCH 09/21] include `-y --no-install-recommends` even though it likely doesn't matter Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docker/linux/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index f8c998261..f1109c7c7 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -39,7 +39,7 @@ RUN /install-scripts/install-docker.sh # Install Tentacle COPY _artifacts/deb/tentacle_${BUILD_NUMBER}_amd64.deb /tmp/ RUN apt-get update && \ - apt-get install ./tentacle_${BUILD_NUMBER}_amd64.deb && \ + apt-get install -y --no-install-recommends ./tentacle_${BUILD_NUMBER}_amd64.deb && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ ln -s /opt/octopus/tentacle/Tentacle /usr/bin/tentacle From 4754e74e7252fbb07b3d866e22eca9d75c22de3c Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Tue, 12 May 2026 14:01:27 +1000 Subject: [PATCH 10/21] fix: drop undeclared python3 dep from smoke test Replace upsert_env_var's Python heredoc with a pure-bash implementation. The previous version invoked python3 without listing it in the script's required-tools check, so the failure mode on a machine without Python was a confusing "python3: command not found" mid-run instead of an upfront "Missing required tool" message. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index c9ed56588..03e65b613 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -103,19 +103,22 @@ log "Fetched license from 1Password (${#LICENSE_BASE64} bytes)" cp "$ENV_FILE" "$ENV_BACKUP" upsert_env_var() { + # Pure-bash: avoids sed/awk escape headaches with a base64 value (which + # contains '/' and '=' but not '\' or '&'). Matches the line by literal + # "KEY=" prefix, not regex, so unusual keys won't bite us. local key="$1" value="$2" - if grep -q "^${key}=" "$ENV_FILE"; then - # Use a sed delimiter unlikely to appear in base64 - python3 - "$ENV_FILE" "$key" "$value" <<'PY' -import sys, re -path, key, value = sys.argv[1], sys.argv[2], sys.argv[3] -with open(path) as f: txt = f.read() -txt = re.sub(rf'^{re.escape(key)}=.*$', f'{key}={value}', txt, count=1, flags=re.M) -with open(path, 'w') as f: f.write(txt) -PY - else - printf '\n%s=%s\n' "$key" "$value" >> "$ENV_FILE" - fi + local tmp="$ENV_FILE.tmp" line found= + : > "$tmp" + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" == "${key}="* ]]; then + printf '%s=%s\n' "$key" "$value" >> "$tmp" + found=1 + else + printf '%s\n' "$line" >> "$tmp" + fi + done < "$ENV_FILE" + [[ -z "$found" ]] && printf '%s=%s\n' "$key" "$value" >> "$tmp" + mv "$tmp" "$ENV_FILE" } upsert_env_var TENTACLE_TAG "$IMAGE_TAG" From 0ac4f490e873a7a90d3b8e05b126e0f96208c76b Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Tue, 12 May 2026 14:35:04 +1000 Subject: [PATCH 11/21] fix: use mktemp for transient compose override The previous version wrote and deleted a fixed-name `docker-compose.smoke.yml` in the sibling OctopusDeploy repo. If a user already had a file by that name (now or in the future), this script would silently clobber it and remove it on teardown. Switch to `mktemp` in $TMPDIR so the path is unique-per-run, and only delete it in teardown if this run actually created one. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index 03e65b613..bc9a4201c 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -20,7 +20,9 @@ ENV_BACKUP="$ENV_FILE.smoke-test.bak" # default tentacle entrypoint launches a dockerd daemon, which requires the # container to run with `--privileged`; without that the daemon fails and its # wrapper script kills the Tentacle agent. Setting DISABLE_DIND=Y skips it. -OVERRIDE_COMPOSE="$SERVER_REPO/docker-compose.smoke.yml" +# Created via mktemp in Step 4 so we never clobber an unrelated file the user +# may already have in the sibling repo. +OVERRIDE_COMPOSE="" API="http://localhost:8065/api" API_KEY="API-APIKEY01" @@ -41,8 +43,8 @@ require jq teardown() { local exit_code=$? log "--- teardown ---" - if [[ -f "$OVERRIDE_COMPOSE" ]]; then - (cd "$SERVER_REPO" && docker compose -f docker-compose.yml -f docker-compose.smoke.yml --profile tentacle down 2>/dev/null) || true + if [[ -n "$OVERRIDE_COMPOSE" && -f "$OVERRIDE_COMPOSE" ]]; then + (cd "$SERVER_REPO" && docker compose -f docker-compose.yml -f "$OVERRIDE_COMPOSE" --profile tentacle down 2>/dev/null) || true rm -f "$OVERRIDE_COMPOSE" fi (cd "$SERVER_REPO" && docker compose down 2>/dev/null) || true @@ -145,6 +147,7 @@ done # Step 4: Bring up the Tentacle (Worker, polling mode, DIND disabled) ############################################################################### log "--- Step 4: start tentacle ---" +OVERRIDE_COMPOSE="$(mktemp "${TMPDIR:-/tmp}/docker-compose.smoke-tentacle.XXXXXX.yml")" cat > "$OVERRIDE_COMPOSE" <<'YAML' services: tentacle: @@ -152,7 +155,7 @@ services: DISABLE_DIND: "Y" YAML -COMPOSE=(docker compose -f docker-compose.yml -f docker-compose.smoke.yml --profile tentacle) +COMPOSE=(docker compose -f docker-compose.yml -f "$OVERRIDE_COMPOSE" --profile tentacle) # --no-deps because octopus-server may lack a healthcheck; we already polled # its API ping above and know it's ready. From 1573bfe928a05668941ad1bd7eff73612390c28f Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Tue, 12 May 2026 14:38:13 +1000 Subject: [PATCH 12/21] fix: use mktemp for .env backup path The previous fixed `.env.smoke-test.bak` path would silently overwrite a stale backup from a previously-crashed run, losing the original .env. Switch to `mktemp` in $TMPDIR so the backup path is unique-per-run, log the path so it's discoverable from script output if anything goes wrong, and guard the teardown restore on the variable being set so an early failure (before the backup is made) doesn't try to mv an empty path. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index bc9a4201c..b16a98fa1 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -15,7 +15,10 @@ set -euo pipefail TENTACLE_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SERVER_REPO="${SERVER_REPO:-$(cd "$TENTACLE_REPO/../OctopusDeploy" && pwd)}" ENV_FILE="$SERVER_REPO/.env" -ENV_BACKUP="$ENV_FILE.smoke-test.bak" +# .env backup path is assigned via mktemp in Step 2 (after we know $ENV_FILE +# exists). Using a unique per-run path avoids clobbering a stale backup from +# a previously-crashed run. +ENV_BACKUP="" # Transient compose override: disables Docker-in-Docker on the Tentacle. The # default tentacle entrypoint launches a dockerd daemon, which requires the # container to run with `--privileged`; without that the daemon fails and its @@ -48,7 +51,7 @@ teardown() { rm -f "$OVERRIDE_COMPOSE" fi (cd "$SERVER_REPO" && docker compose down 2>/dev/null) || true - if [[ -f "$ENV_BACKUP" ]]; then + if [[ -n "$ENV_BACKUP" && -f "$ENV_BACKUP" ]]; then mv "$ENV_BACKUP" "$ENV_FILE" log "Restored $ENV_FILE" fi @@ -102,7 +105,9 @@ LICENSE_BASE64="$(op read "$ONEPASSWORD_LICENSE_REF" 2>/dev/null || true)" [[ -n "$LICENSE_BASE64" ]] || die "Could not read license from 1Password at: $ONEPASSWORD_LICENSE_REF" log "Fetched license from 1Password (${#LICENSE_BASE64} bytes)" +ENV_BACKUP="$(mktemp "${TMPDIR:-/tmp}/octopus-server.env.smoke-tentacle.XXXXXX.bak")" cp "$ENV_FILE" "$ENV_BACKUP" +log "Backed up .env to $ENV_BACKUP (will be restored on exit)" upsert_env_var() { # Pure-bash: avoids sed/awk escape headaches with a base64 value (which From a60a983cd5080b05cbbfa1f8b27f572197c2b1a9 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Tue, 12 May 2026 15:43:45 +1000 Subject: [PATCH 13/21] fix: put X's at end of mktemp templates for BSD portability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BSD mktemp (macOS) silently leaves Xs unsubstituted when they aren't the final characters of the template — so the previous templates ("...XXXXXX.bak" and "...XXXXXX.yml") actually produced fixed-name files with literal Xs, defeating the unique-per-run guarantee. Verified by inspecting the logged backup path. Move the Xs to the end and drop the .bak/.yml suffixes (docker compose parses by content not extension, and the suffix was cosmetic for the backup). Verified end-to-end against a local Octopus Server. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index b16a98fa1..58d4a3fc8 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -105,7 +105,7 @@ LICENSE_BASE64="$(op read "$ONEPASSWORD_LICENSE_REF" 2>/dev/null || true)" [[ -n "$LICENSE_BASE64" ]] || die "Could not read license from 1Password at: $ONEPASSWORD_LICENSE_REF" log "Fetched license from 1Password (${#LICENSE_BASE64} bytes)" -ENV_BACKUP="$(mktemp "${TMPDIR:-/tmp}/octopus-server.env.smoke-tentacle.XXXXXX.bak")" +ENV_BACKUP="$(mktemp "${TMPDIR:-/tmp}/octopus-server-env-smoke-tentacle-XXXXXX")" cp "$ENV_FILE" "$ENV_BACKUP" log "Backed up .env to $ENV_BACKUP (will be restored on exit)" @@ -152,7 +152,7 @@ done # Step 4: Bring up the Tentacle (Worker, polling mode, DIND disabled) ############################################################################### log "--- Step 4: start tentacle ---" -OVERRIDE_COMPOSE="$(mktemp "${TMPDIR:-/tmp}/docker-compose.smoke-tentacle.XXXXXX.yml")" +OVERRIDE_COMPOSE="$(mktemp "${TMPDIR:-/tmp}/docker-compose-smoke-tentacle-XXXXXX")" cat > "$OVERRIDE_COMPOSE" <<'YAML' services: tentacle: From f01217fb1d601520a7409ba60b604eef800e0296 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Wed, 20 May 2026 14:28:04 +1000 Subject: [PATCH 14/21] fix: use grep -qF for literal "Configuration successful." match The period in the search string is a regex wildcard with plain grep -q; -F (--fixed-strings) makes the match literal, which is what's intended. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index 58d4a3fc8..f6d1edd98 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -168,7 +168,7 @@ COMPOSE=(docker compose -f docker-compose.yml -f "$OVERRIDE_COMPOSE" --profile t log "Waiting for Tentacle 'Configuration successful.' in logs ..." for i in {1..60}; do - if "${COMPOSE[@]}" logs --no-color tentacle 2>/dev/null | grep -q "Configuration successful."; then + if "${COMPOSE[@]}" logs --no-color tentacle 2>/dev/null | grep -qF "Configuration successful."; then log "Tentacle registered after ${i}s" break fi From 15a08f559777fa06e0c7f0705babd3d97a542147 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Wed, 20 May 2026 14:29:35 +1000 Subject: [PATCH 15/21] docs: clarify that API_KEY is the sibling repo's dev-only sentinel Adds a sentence to the header comment explaining that API-APIKEY01 is the well-known dev key provisioned by the sibling OctopusDeploy repo's compose stack for its local Server, not a real secret. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index f6d1edd98..a835bbbce 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -9,6 +9,10 @@ # Required tools: docker, op (signed in), curl, jq. # Required state: a built .deb in ../_artifacts/deb/tentacle_*_amd64.deb and the # OctopusDeploy repo checked out alongside OctopusTentacle. +# +# Note on $API_KEY below: "API-APIKEY01" is the well-known dev sentinel API key +# provisioned by the sibling OctopusDeploy repo's docker-compose stack for its +# local-only Server instance. It is not a real secret and is safe to commit. set -euo pipefail From 2895556c02b36b47cd71cffaf3d2196d9c51c486 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Wed, 20 May 2026 17:10:34 +1000 Subject: [PATCH 16/21] fix: hard-fail when Debian 12 is missing from AdHocScript output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the load-bearing assertion of the smoke test — if the Tentacle isn't actually running on Debian 12, the test should fail, not warn. Also switches the grep to -qF so the period is treated literally. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index a835bbbce..ef722fc4f 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -251,9 +251,11 @@ if [[ "$STATE" != "Success" ]]; then die "Task finished in state '$STATE' (expected Success)" fi -# Sanity: the printed log should mention Debian 12. -if ! curl -fsS -H "$H" "$API/tasks/$TASK_ID/raw" | grep -q 'Debian GNU/Linux 12'; then - warn "Task succeeded but the log does NOT mention 'Debian GNU/Linux 12'. Inspect output above." +# Load-bearing assertion: the whole point of this smoke test is to prove the +# Debian 12 base image is what's actually running on the Tentacle, so a missing +# os-release line is a hard failure, not a warning. +if ! curl -fsS -H "$H" "$API/tasks/$TASK_ID/raw" | grep -qF 'Debian GNU/Linux 12'; then + die "Task succeeded but the log does NOT mention 'Debian GNU/Linux 12'. Inspect output above." fi log "PASS — Tentacle (Debian 12) registered and executed hello-world." From a9ac166a908ac9d500f2998111a0fcbd9be237a4 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Wed, 20 May 2026 17:11:25 +1000 Subject: [PATCH 17/21] fix: use mktemp for upsert_env_var temp file Avoids writing a sibling \$ENV_FILE.tmp that two concurrent runs could race on. Uses the same XXXXXX template convention as the other mktemp calls in this script. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index ef722fc4f..24d3b0004 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -118,8 +118,8 @@ upsert_env_var() { # contains '/' and '=' but not '\' or '&'). Matches the line by literal # "KEY=" prefix, not regex, so unusual keys won't bite us. local key="$1" value="$2" - local tmp="$ENV_FILE.tmp" line found= - : > "$tmp" + local tmp line found= + tmp="$(mktemp "${TMPDIR:-/tmp}/octopus-server-env-smoke-upsert-XXXXXX")" while IFS= read -r line || [[ -n "$line" ]]; do if [[ "$line" == "${key}="* ]]; then printf '%s=%s\n' "$key" "$value" >> "$tmp" From 6000ef21ab8845b1855d4a491957fa94ed84c7ab Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Wed, 20 May 2026 17:12:07 +1000 Subject: [PATCH 18/21] feat: allow OCTOPUS_LICENSE_BASE64 to bypass 1Password lookup Lets CI runners (which can't use op) inject the license via env var, while keeping op read as the local-dev default. The op tool is only required when the env var is unset. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index 24d3b0004..9279b4f52 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -6,10 +6,15 @@ # Server in the sibling OctopusDeploy repo, registers the Tentacle as a worker, # runs a hello-world AdHocScript on it, and asserts success. # -# Required tools: docker, op (signed in), curl, jq. +# Required tools: docker, curl, jq. # Required state: a built .deb in ../_artifacts/deb/tentacle_*_amd64.deb and the # OctopusDeploy repo checked out alongside OctopusTentacle. # +# License source: set $OCTOPUS_LICENSE_BASE64 to a base64-encoded Octopus license +# to skip the 1Password lookup (this is the path CI runners should use). When +# the env var is unset, the script falls back to `op read` against 1Password +# for local-dev use, in which case `op` must be installed and signed in. +# # Note on $API_KEY below: "API-APIKEY01" is the well-known dev sentinel API key # provisioned by the sibling OctopusDeploy repo's docker-compose stack for its # local-only Server instance. It is not a real secret and is safe to commit. @@ -43,9 +48,10 @@ die() { printf '\033[1;31m[smoke]\033[0m %s\n' "$*" >&2; exit 1; } require() { command -v "$1" >/dev/null || die "Missing required tool: $1"; } require docker -require op require curl require jq +# `op` is only required when OCTOPUS_LICENSE_BASE64 is not pre-set (local-dev path). +[[ -n "${OCTOPUS_LICENSE_BASE64:-}" ]] || require op teardown() { local exit_code=$? @@ -96,19 +102,23 @@ docker build \ log "Built $DST_IMAGE" ############################################################################### -# Step 2: Fetch license from 1Password & patch .env +# Step 2: Resolve license & patch .env ############################################################################### -log "--- Step 2: fetch license and patch .env ---" +log "--- Step 2: resolve license and patch .env ---" [[ -f "$ENV_FILE" ]] || die "Expected $ENV_FILE to exist." -if ! op account list >/dev/null 2>&1; then - die "1Password CLI is not signed in. Run: eval \$(op signin)" +if [[ -n "${OCTOPUS_LICENSE_BASE64:-}" ]]; then + LICENSE_BASE64="$OCTOPUS_LICENSE_BASE64" + log "Using license from \$OCTOPUS_LICENSE_BASE64 (${#LICENSE_BASE64} bytes)" +else + if ! op account list >/dev/null 2>&1; then + die "1Password CLI is not signed in. Run: eval \$(op signin) — or pre-set \$OCTOPUS_LICENSE_BASE64." + fi + LICENSE_BASE64="$(op read "$ONEPASSWORD_LICENSE_REF" 2>/dev/null || true)" + [[ -n "$LICENSE_BASE64" ]] || die "Could not read license from 1Password at: $ONEPASSWORD_LICENSE_REF" + log "Fetched license from 1Password (${#LICENSE_BASE64} bytes)" fi -LICENSE_BASE64="$(op read "$ONEPASSWORD_LICENSE_REF" 2>/dev/null || true)" -[[ -n "$LICENSE_BASE64" ]] || die "Could not read license from 1Password at: $ONEPASSWORD_LICENSE_REF" -log "Fetched license from 1Password (${#LICENSE_BASE64} bytes)" - ENV_BACKUP="$(mktemp "${TMPDIR:-/tmp}/octopus-server-env-smoke-tentacle-XXXXXX")" cp "$ENV_FILE" "$ENV_BACKUP" log "Backed up .env to $ENV_BACKUP (will be restored on exit)" From 44922e97b02f78dc8411058c1f563bb9120d5183 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Wed, 20 May 2026 17:13:01 +1000 Subject: [PATCH 19/21] fix: look up worker by per-run TargetName instead of "highest Workers-N" Generates a unique per-run worker name (smoke-tentacle--) and passes it to the Tentacle as TargetName via the override compose. The Server-side lookup then filters by Name == $WORKER_TARGET_NAME, which is robust against reused DB volumes growing the workers list and against any future Workers-N renumbering. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index 9279b4f52..ef2f94746 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -42,6 +42,12 @@ H="X-Octopus-ApiKey: $API_KEY" IMAGE_TAG="smoke-debian12" ONEPASSWORD_LICENSE_REF="op://software licencing/octopus deploy ultimate license key base64/value" +# Per-run worker name. Tagging the worker with a unique name (rather than +# relying on the container hostname / a "highest Workers-N" heuristic) keeps +# the test idempotent across reused Server DB volumes and lets teardown find +# the exact worker this run registered. +WORKER_TARGET_NAME="smoke-tentacle-$(date +%Y%m%d-%H%M%S)-$$" + log() { printf '\033[1;34m[smoke]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[smoke]\033[0m %s\n' "$*" >&2; } die() { printf '\033[1;31m[smoke]\033[0m %s\n' "$*" >&2; exit 1; } @@ -167,11 +173,12 @@ done ############################################################################### log "--- Step 4: start tentacle ---" OVERRIDE_COMPOSE="$(mktemp "${TMPDIR:-/tmp}/docker-compose-smoke-tentacle-XXXXXX")" -cat > "$OVERRIDE_COMPOSE" <<'YAML' +cat > "$OVERRIDE_COMPOSE" </dev/null || echo '{"Items":[]}')" + WORKERS_JSON="$(curl -fsS -H "$H" --data-urlencode "name=$WORKER_TARGET_NAME" -G "$API/workers" 2>/dev/null || echo '{"Items":[]}')" WORKER_ID="$(echo "$WORKERS_JSON" \ - | jq -r '[.Items[] | select(.Id | startswith("Workers-"))] | sort_by(.Id | ltrimstr("Workers-") | tonumber) | last | .Id // empty')" - WORKER_NAME="$(echo "$WORKERS_JSON" | jq -r --arg id "$WORKER_ID" '.Items[] | select(.Id == $id) | .Name // empty')" + | jq -r --arg name "$WORKER_TARGET_NAME" '.Items[] | select(.Name == $name) | .Id' \ + | head -n1)" [[ -n "$WORKER_ID" ]] && break sleep 1 done if [[ -z "$WORKER_ID" ]]; then - warn "No worker appeared. Diagnostic dump of $API/workers:" + warn "No worker named '$WORKER_TARGET_NAME' appeared. Diagnostic dump of $API/workers:" curl -fsS -H "$H" "$API/workers" || true warn "Tentacle container logs (tail 80):" docker compose --profile tentacle logs --no-color --tail=80 tentacle || true - die "Worker did not appear after 60s" + die "Worker '$WORKER_TARGET_NAME' did not appear after 60s" fi -log "Registered worker: $WORKER_ID (name='$WORKER_NAME')" +log "Registered worker: $WORKER_ID (name='$WORKER_TARGET_NAME')" ADHOC_BODY="$(jq -nc \ --arg id "$WORKER_ID" \ From 3e392f057f39f6facfa47568f7f662b95348f588 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Wed, 20 May 2026 17:13:48 +1000 Subject: [PATCH 20/21] fix: deregister worker in teardown to keep test idempotent When the Server's DB volume is reused across runs (CI especially), the workers list otherwise grows monotonically. Teardown now issues a best-effort DELETE /api/workers/\$WORKER_ID before bringing the stack down, guarded on \$WORKER_ID being populated (so an early failure before Step 5 is a no-op). Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke-test-linux-tentacle.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/smoke-test-linux-tentacle.sh b/scripts/smoke-test-linux-tentacle.sh index ef2f94746..f5bf087ed 100755 --- a/scripts/smoke-test-linux-tentacle.sh +++ b/scripts/smoke-test-linux-tentacle.sh @@ -47,6 +47,10 @@ ONEPASSWORD_LICENSE_REF="op://software licencing/octopus deploy ultimate license # the test idempotent across reused Server DB volumes and lets teardown find # the exact worker this run registered. WORKER_TARGET_NAME="smoke-tentacle-$(date +%Y%m%d-%H%M%S)-$$" +# Populated in Step 5 once the Server confirms registration; used by teardown +# to deregister the worker via DELETE so the workers list doesn't grow +# monotonically across runs that share a Server DB volume. +WORKER_ID="" log() { printf '\033[1;34m[smoke]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[smoke]\033[0m %s\n' "$*" >&2; } @@ -62,6 +66,13 @@ require jq teardown() { local exit_code=$? log "--- teardown ---" + # Deregister the worker first, while the Server is still up. Best-effort: + # if the Server is already dead or the worker never registered, we just + # move on — the goal is to keep the workers list clean across runs. + if [[ -n "$WORKER_ID" ]]; then + log "Deregistering worker $WORKER_ID" + curl -fsS -X DELETE -H "$H" "$API/workers/$WORKER_ID" >/dev/null 2>&1 || true + fi if [[ -n "$OVERRIDE_COMPOSE" && -f "$OVERRIDE_COMPOSE" ]]; then (cd "$SERVER_REPO" && docker compose -f docker-compose.yml -f "$OVERRIDE_COMPOSE" --profile tentacle down 2>/dev/null) || true rm -f "$OVERRIDE_COMPOSE" @@ -213,7 +224,6 @@ log "--- Step 5: verify registration and run hello-world ---" # Find the worker we just registered by its per-run TargetName. This is # robust against reused Server DB volumes (where workers list grows across # runs) and avoids the previous "highest Workers-N" heuristic. -WORKER_ID="" for i in {1..60}; do WORKERS_JSON="$(curl -fsS -H "$H" --data-urlencode "name=$WORKER_TARGET_NAME" -G "$API/workers" 2>/dev/null || echo '{"Items":[]}')" WORKER_ID="$(echo "$WORKERS_JSON" \ From dca6d6a37c8f7be392bbcb830e6b3716d9502669 Mon Sep 17 00:00:00 2001 From: Tod Thomson Date: Thu, 21 May 2026 09:59:42 +1000 Subject: [PATCH 21/21] cleanup: combine two apt-get steps into one --- docker/linux/Dockerfile | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/docker/linux/Dockerfile b/docker/linux/Dockerfile index f1109c7c7..1d98a7c6e 100644 --- a/docker/linux/Dockerfile +++ b/docker/linux/Dockerfile @@ -5,23 +5,14 @@ RUN apt-get update && \ ca-certificates \ libgssapi-krb5-2 \ libicu72 \ - libssl3 && \ + libssl3 \ + xxd && \ + apt-get clean && \ rm -rf /var/lib/apt/lists/* ARG BUILD_NUMBER ARG BUILD_DATE -RUN apt-get update && \ - apt-get install -y \ - curl \ - dos2unix \ - jq \ - sudo \ - xxd \ - && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - EXPOSE 10933 WORKDIR /tmp @@ -43,7 +34,7 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* && \ ln -s /opt/octopus/tentacle/Tentacle /usr/bin/tentacle - + WORKDIR / # We know this won't reduce the image size at all. It's just to make the filesystem a little tidier.