Quickstart Verify – make demo & one-line install #86
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |