Skip to content

Quickstart Verify – make demo & one-line install #86

Quickstart Verify – make demo & one-line install

Quickstart Verify – make demo & one-line install #86

name: Quickstart Verify – make demo & one-line install
# Verifies the two paths external users actually run, on a clean machine with
# working Docker (something a degraded local Docker Desktop can't prove). Both
# the seed-loader regression (demo/ not in the image) and the .env bootstrap
# are exercised end-to-end, so they cannot silently break again.
on:
workflow_dispatch:
pull_request:
push:
branches: [main]
schedule:
- cron: "30 6 * * *"
concurrency:
group: quickstart-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
make-demo:
name: make demo seeds a fresh clone
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
# The recipe must parse (tab-indented recipe + .env bootstrap shell).
- name: make demo parses
run: make -n demo
# Mirror `make demo` on a FRESH clone (no .env). The target bootstraps
# .env from .env.example; assert it did, and that the values compose
# guards with ${VAR:?} are non-empty (the bug: WEAVIATE_API_KEY shipped
# blank, so even `cp .env.example .env` aborted compose).
- name: Bootstrap .env like a fresh `make demo`
env:
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
run: |
rm -f .env
if [ ! -f .env ]; then cp .env.example .env; fi
test -f .env || { echo "::error::bootstrap did not create .env"; exit 1; }
grep -Eq '^WEAVIATE_API_KEY=.+' .env || { echo "::error::WEAVIATE_API_KEY blank — compose \${VAR:?} guard would abort make demo"; exit 1; }
grep -Eq '^NEO4J_PASSWORD=.+' .env || { echo "::error::NEO4J_PASSWORD blank"; exit 1; }
# Disable the boot-time dim guard for CI resilience.
sed -i 's/^EMBEDDING_DIM_GUARD=.*/EMBEDDING_DIM_GUARD=false/' .env
# Seeding itself is zero-key (precomputed fixtures). These keys are only
# for the /api/ask smoke below: Gemini answers, Jina embeds the query.
# Skip that smoke gracefully when secrets are absent (e.g. fork PRs).
if [ -n "$GOOGLE_API_KEY" ] && [ -n "$JINA_API_KEY" ]; then
grep -v -E '^(GOOGLE_API_KEY|JINA_API_KEY)=' .env > .env.tmp
{ printf 'GOOGLE_API_KEY=%s\n' "$GOOGLE_API_KEY"; printf 'JINA_API_KEY=%s\n' "$JINA_API_KEY"; } >> .env.tmp
mv .env.tmp .env
echo "ASK_SMOKE=1" >> "$GITHUB_ENV"
else
echo "ASK_SMOKE=0" >> "$GITHUB_ENV"
echo "::notice::No API-key secrets — skipping the /api/ask smoke (seed check still runs)."
fi
- name: Bring up the demo stack (detached mirror of `make demo`)
run: docker compose -f docker-compose.yml -f demo/docker-compose.demo.yml up -d --build
env:
COMPOSE_DOCKER_CLI_BUILD: 1
DOCKER_BUILDKIT: 1
- name: Wait for backend health
run: bash scripts/wait-for-health.sh
- name: Seed-loader completes (the regression we fixed)
run: |
set -e
cid=$(docker compose -f docker-compose.yml -f demo/docker-compose.demo.yml ps -q seed-loader)
if [ -z "$cid" ]; then echo "::error::seed-loader container not found"; exit 1; fi
# `docker wait` blocks until the one-shot loader exits, returns its code.
code=$(docker wait "$cid")
echo "seed-loader exit code: $code"
docker logs "$cid" 2>&1 | tail -40
if [ "$code" != "0" ]; then
echo "::error::seed-loader failed (exit $code) — demo data was NOT seeded"
exit 1
fi
if ! docker logs "$cid" 2>&1 | grep -q "Demo data seeded successfully"; then
echo "::error::seed-loader exited 0 but success marker missing"
exit 1
fi
echo "make demo seeded the demo corpus end-to-end"
- name: Ask a question end-to-end (/api/ask returns a real answer)
if: env.ASK_SMOKE == '1'
run: |
set -e
resp=$(curl -sS -N --max-time 120 -X POST \
"http://localhost:8000/api/channels/demo-wikipedia/ask" \
-H "Authorization: Bearer dev-key-change-me" \
-H "Content-Type: application/json" \
-d '{"question":"Who was Ada Lovelace?"}') || { echo "::error::/api/ask request failed"; exit 1; }
echo "----- /api/ask response (head) -----"
printf '%s\n' "$resp" | head -c 3000; echo
if printf '%s' "$resp" | grep -qiE 'channel_access_denied|not_found|"error"|Internal Server Error'; then
echo "::error::/api/ask returned an error — the demo channel is not answerable"; exit 1
fi
printf '%s' "$resp" | grep -qiE 'lovelace|analytical engine|mathematician|babbage' \
|| { echo "::error::/api/ask did not return a substantive answer about the seeded corpus"; exit 1; }
echo "make demo answered a question end-to-end ✅"
- name: Dump logs on failure
if: failure()
run: |
docker compose -f docker-compose.yml -f demo/docker-compose.demo.yml ps -a || true
docker compose -f docker-compose.yml -f demo/docker-compose.demo.yml logs --no-color --tail=all || true
- name: Tear down
if: always()
run: docker compose -f docker-compose.yml -f demo/docker-compose.demo.yml down -v
one-line-install:
name: one-line install (./atlas --non-interactive) is healthy
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
# ./atlas reuses an existing .env (it only copies when absent), so seed a
# CI-valid one first: no provider key in CI → disable the dim guard.
- name: Prepare .env (CI has no provider key)
run: |
cp .env.example .env
sed -i 's/^EMBEDDING_DIM_GUARD=.*/EMBEDDING_DIM_GUARD=false/' .env
- name: Run the one-line installer, unattended
run: ./atlas --non-interactive
env:
NO_COLOR: "1"
COMPOSE_DOCKER_CLI_BUILD: 1
DOCKER_BUILDKIT: 1
- name: Assert the stack is healthy
run: |
RESP=$(curl -sf http://localhost:8000/api/health)
echo "Health: $RESP"
[ "$(echo "$RESP" | jq -r '.status')" = "healthy" ] \
|| { echo "::error::stack not healthy after ./atlas --non-interactive"; exit 1; }
echo "one-line install produced a healthy stack"
- name: Dump logs on failure
if: failure()
run: |
docker compose ps -a || true
docker compose logs --no-color --tail=all || true
- name: Tear down
if: always()
run: docker compose down -v