From 3e7efb641ba4478b4221c3af61b4ca99721e03cb Mon Sep 17 00:00:00 2001 From: Ed Geraghty Date: Thu, 18 Jun 2026 12:17:53 +0100 Subject: [PATCH 1/3] Move smoke test to make target, reuse for CI --- .github/workflows/build-with-smoke-tests.yml | 73 +------------------- Makefile | 64 ++++++++++++++++- 2 files changed, 64 insertions(+), 73 deletions(-) diff --git a/.github/workflows/build-with-smoke-tests.yml b/.github/workflows/build-with-smoke-tests.yml index 6c4a4df..235ee67 100644 --- a/.github/workflows/build-with-smoke-tests.yml +++ b/.github/workflows/build-with-smoke-tests.yml @@ -24,74 +24,5 @@ jobs: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - - - name: Create cache directory - run: mkdir -p /tmp/.buildx-cache - - - name: Cache Docker layers - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Build Docker image - run: | - docker build -t headscale:latest . - - - name: Run container in detached mode - run: | - docker run -d --name headscale-container \ - -p 127.0.0.1:8008:8008 \ - --env LITESTREAM_REPLICA_URL=${{ env.LITESTREAM_REPLICA_URL }} \ - --env PUBLIC_SERVER_URL=${{ env.PUBLIC_SERVER_URL }} \ - --env HEADSCALE_DNS_BASE_DOMAIN=${{ env.HEADSCALE_DNS_BASE_DOMAIN }} \ - --env CADDY_FRONTEND=${{ env.CADDY_FRONTEND }} \ - headscale:latest - - - name: Run smoke tests - run: | - docker exec headscale-container headscale version - docker exec headscale-container litestream version - docker exec headscale-container caddy version - - - name: Check if container is listening on port 8008 - run: | - docker exec headscale-container sh -c "netstat -tuln | grep ':8008 '" - - - name: Wait for admin GUI to respond - run: | - for attempt in $(seq 1 30); do - if curl --silent --show-error --fail http://127.0.0.1:8008/admin/ > /tmp/admin-gui.html; then - exit 0 - fi - - sleep 2 - done - - echo "Admin GUI did not become ready in time" - docker logs headscale-container - exit 1 - - - name: Check admin GUI redirect and content - run: | - redirect_headers=$(mktemp) - - curl --silent --show-error --fail \ - --dump-header "$redirect_headers" \ - --output /dev/null \ - http://127.0.0.1:8008/admin - - tr -d '\r' < "$redirect_headers" | grep -qi '^location: /admin/$' - grep -qi '' /tmp/admin-gui.html - grep -qi 'data-sveltekit-preload-data="hover"' /tmp/admin-gui.html - grep -qi 'assets: "/admin"' /tmp/admin-gui.html - - - name: Stop and remove container - if: always() - run: | - docker stop headscale-container - docker rm headscale-container + - name: Run local-equivalent smoke test + run: make smoke-test diff --git a/Makefile b/Makefile index e8ff507..84a4f9b 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,20 @@ SHELL := /bin/bash +DEFAULT_SMOKE_TEST_IMAGE := headscale:latest +DEFAULT_SMOKE_TEST_CONTAINER := headscale-container +DEFAULT_SMOKE_TEST_HOST := 127.0.0.1 +DEFAULT_SMOKE_TEST_PORT := 8008 + .DEFAULT_GOAL := help -.PHONY: help check-envsubst render-fly-config render-azure-container-apps +.PHONY: help check-envsubst render-fly-config render-azure-container-apps smoke-test help: @printf '%s\n' \ 'Available targets:' \ ' make render-fly-config Render fly.toml from templates/fly.template.toml' \ - ' make render-azure-container-apps Render azure-container-apps.yaml from templates/azure-container-apps.template.yaml' + ' make render-azure-container-apps Render azure-container-apps.yaml from templates/azure-container-apps.template.yaml' \ + ' make smoke-test Build the image and run local smoke tests' check-envsubst: @command -v envsubst >/dev/null || { \ @@ -33,3 +39,57 @@ render-azure-container-apps: check-envsubst @: $${HEADSCALE_DNS_BASE_DOMAIN:?Set HEADSCALE_DNS_BASE_DOMAIN} @envsubst < templates/azure-container-apps.template.yaml > azure-container-apps.yaml @printf '%s\n' 'Wrote azure-container-apps.yaml' + +smoke-test: + @set -euo pipefail; \ + image="$${SMOKE_TEST_IMAGE:-$(DEFAULT_SMOKE_TEST_IMAGE)}"; \ + container="$${SMOKE_TEST_CONTAINER_NAME:-$(DEFAULT_SMOKE_TEST_CONTAINER)}"; \ + host="$${SMOKE_TEST_HOST:-$(DEFAULT_SMOKE_TEST_HOST)}"; \ + port="$${SMOKE_TEST_PORT:-$(DEFAULT_SMOKE_TEST_PORT)}"; \ + admin_gui_html="$$(mktemp)"; \ + redirect_headers="$$(mktemp)"; \ + cleanup() { \ + rm -f "$${admin_gui_html}" "$${redirect_headers}"; \ + docker stop "$${container}" >/dev/null 2>&1 || true; \ + docker rm "$${container}" >/dev/null 2>&1 || true; \ + }; \ + trap cleanup EXIT; \ + docker rm -f "$${container}" >/dev/null 2>&1 || true; \ + echo "Building Docker image: $${image}"; \ + docker build -t "$${image}" .; \ + echo "Starting container: $${container}"; \ + docker run -d --name "$${container}" \ + -p "$${host}:$${port}:8008" \ + --env LITESTREAM_REPLICA_URL="$${LITESTREAM_REPLICA_URL:-DISABLED_I_KNOW_WHAT_IM_DOING}" \ + --env PUBLIC_SERVER_URL="$${PUBLIC_SERVER_URL:-https://headscale.example.com}" \ + --env HEADSCALE_DNS_BASE_DOMAIN="$${HEADSCALE_DNS_BASE_DOMAIN:-example.com}" \ + --env CADDY_FRONTEND="$${CADDY_FRONTEND:-DISABLE_HTTPS}" \ + "$${image}" >/dev/null; \ + echo 'Running version checks'; \ + docker exec "$${container}" headscale version; \ + docker exec "$${container}" litestream version; \ + docker exec "$${container}" caddy version; \ + echo "Checking listener on port 8008 inside the container"; \ + docker exec "$${container}" sh -c "netstat -tuln | grep ':8008 '"; \ + echo "Waiting for admin GUI on http://$${host}:$${port}/admin/"; \ + for attempt in $$(seq 1 30); do \ + if curl --silent --show-error --fail "http://$${host}:$${port}/admin/" > "$${admin_gui_html}"; then \ + break; \ + fi; \ + sleep 2; \ + if [[ "$${attempt}" -eq 30 ]]; then \ + echo 'Admin GUI did not become ready in time'; \ + docker logs "$${container}"; \ + exit 1; \ + fi; \ + done; \ + echo 'Checking admin redirect and content'; \ + curl --silent --show-error --fail \ + --dump-header "$${redirect_headers}" \ + --output /dev/null \ + "http://$${host}:$${port}/admin"; \ + tr -d '\r' < "$${redirect_headers}" | grep -qi '^location: /admin/$$'; \ + grep -qi '' "$${admin_gui_html}"; \ + grep -qi 'data-sveltekit-preload-data="hover"' "$${admin_gui_html}"; \ + grep -qi 'assets: "/admin"' "$${admin_gui_html}"; \ + echo 'Smoke test passed' From 48ea99ead53256b3fb7d8f280e4bd674f16fb6a6 Mon Sep 17 00:00:00 2001 From: Ed Geraghty Date: Thu, 18 Jun 2026 14:54:09 +0100 Subject: [PATCH 2/3] Normalise environment var naming Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 84a4f9b..af4453b 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ render-azure-container-apps: check-envsubst smoke-test: @set -euo pipefail; \ image="$${SMOKE_TEST_IMAGE:-$(DEFAULT_SMOKE_TEST_IMAGE)}"; \ - container="$${SMOKE_TEST_CONTAINER_NAME:-$(DEFAULT_SMOKE_TEST_CONTAINER)}"; \ + container="$${SMOKE_TEST_CONTAINER:-$(DEFAULT_SMOKE_TEST_CONTAINER)}"; \ host="$${SMOKE_TEST_HOST:-$(DEFAULT_SMOKE_TEST_HOST)}"; \ port="$${SMOKE_TEST_PORT:-$(DEFAULT_SMOKE_TEST_PORT)}"; \ admin_gui_html="$$(mktemp)"; \ From f2bb1869f634b7926e6e98a69c16b243d01d2471 Mon Sep 17 00:00:00 2001 From: Ed Geraghty Date: Thu, 18 Jun 2026 14:57:58 +0100 Subject: [PATCH 3/3] Use separate smoke test litestream var Otherwise if we had a production setting in the environment it would reuse it for the smoke test Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index af4453b..4a3a0b7 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ smoke-test: echo "Starting container: $${container}"; \ docker run -d --name "$${container}" \ -p "$${host}:$${port}:8008" \ - --env LITESTREAM_REPLICA_URL="$${LITESTREAM_REPLICA_URL:-DISABLED_I_KNOW_WHAT_IM_DOING}" \ + --env LITESTREAM_REPLICA_URL="$${SMOKE_TEST_LITESTREAM_REPLICA_URL:-DISABLED_I_KNOW_WHAT_IM_DOING}" \ --env PUBLIC_SERVER_URL="$${PUBLIC_SERVER_URL:-https://headscale.example.com}" \ --env HEADSCALE_DNS_BASE_DOMAIN="$${HEADSCALE_DNS_BASE_DOMAIN:-example.com}" \ --env CADDY_FRONTEND="$${CADDY_FRONTEND:-DISABLE_HTTPS}" \