fix(ci): inject bcrypt hash via compose environment with $$ escaping, remove dynamic hash generation

This commit is contained in:
Pouzor
2026-03-28 13:03:28 +01:00
parent c9d6642b26
commit d9f3477780
2 changed files with 20 additions and 23 deletions
+11 -22
View File
@@ -12,19 +12,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# ── Generate a bcrypt hash for the CI test password ─────────────────── # ── Write a minimal .env required by env_file: .env in docker-compose.yml
- name: Generate CI password hash # AUTH_PASSWORD_HASH is NOT set here — it's injected via docker-compose.ci.yml
id: pwhash # environment section using $$ escaping to avoid docker-compose $VAR expansion.
run: |
pip install --quiet passlib bcrypt
HASH=$(python3 -c "from passlib.context import CryptContext; print(CryptContext(schemes=['bcrypt']).hash('ci-password-123'))")
echo "hash=$HASH" >> "$GITHUB_OUTPUT"
# ── Write a minimal .env consumed by docker-compose.yml ───────────────
- name: Write .env - name: Write .env
# PW_HASH is set as an env var so bash does not expand the '$' chars in the bcrypt hash
env:
PW_HASH: ${{ steps.pwhash.outputs.hash }}
run: | run: |
{ {
echo "SECRET_KEY=ci-only-secret-key-not-for-production" echo "SECRET_KEY=ci-only-secret-key-not-for-production"
@@ -34,19 +25,17 @@ jobs:
echo "STATUS_CHECKER_INTERVAL=300" echo "STATUS_CHECKER_INTERVAL=300"
echo "MCP_API_KEY=ci-mcp-key" echo "MCP_API_KEY=ci-mcp-key"
echo "MCP_SERVICE_KEY=ci-svc-key" echo "MCP_SERVICE_KEY=ci-svc-key"
echo "AUTH_USERNAME=admin"
printf 'AUTH_PASSWORD_HASH=%s\n' "$PW_HASH"
} > .env } > .env
# ── Build + start backend and frontend (skip mcp) ───────────────────── # ── Build + start backend and frontend (skip mcp) ─────────────────────────
# docker-compose.ci.yml adds ports: 8000:8000 so the runner can reach the backend # docker-compose.ci.yml: exposes port 8000 + injects AUTH_* env vars
- name: Build images - name: Build images
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml build backend frontend run: docker compose -f docker-compose.yml -f docker-compose.ci.yml build backend frontend
- name: Start stack - name: Start stack
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d backend frontend run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d backend frontend
# ── Wait for backend to be healthy (max 60 s) ───────────────────────── # ── Wait for backend to be healthy (max 60 s) ─────────────────────────────
- name: Wait for backend health - name: Wait for backend health
run: | run: |
echo "Waiting for backend..." echo "Waiting for backend..."
@@ -58,16 +47,16 @@ jobs:
sleep 2 sleep 2
done done
echo "Backend did not become healthy in time" >&2 echo "Backend did not become healthy in time" >&2
docker compose logs backend docker compose -f docker-compose.yml -f docker-compose.ci.yml logs backend
exit 1 exit 1
# ── Smoke: frontend serves HTML ──────────────────────────────────────── # ── Smoke: frontend serves HTML ───────────────────────────────────────────
- name: Smoke — frontend returns 200 - name: Smoke — frontend returns 200
run: | run: |
STATUS=$(curl -so /dev/null -w "%{http_code}" http://localhost:3000/) STATUS=$(curl -so /dev/null -w "%{http_code}" http://localhost:3000/)
[ "$STATUS" = "200" ] || { echo "Frontend returned $STATUS"; exit 1; } [ "$STATUS" = "200" ] || { echo "Frontend returned $STATUS"; exit 1; }
# ── Tier 3: integration tests against the live stack ────────────────── # ── Tier 3: integration tests against the live stack ──────────────────────
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.11'
@@ -79,12 +68,12 @@ jobs:
env: env:
INTEGRATION_BASE_URL: http://localhost:8000 INTEGRATION_BASE_URL: http://localhost:8000
INTEGRATION_USERNAME: admin INTEGRATION_USERNAME: admin
INTEGRATION_PASSWORD: ci-password-123 INTEGRATION_PASSWORD: admin
run: | run: |
cd backend cd backend
pytest tests/test_integration.py -v pytest tests/test_integration.py -v
# ── Teardown ─────────────────────────────────────────────────────────── # ── Teardown ──────────────────────────────────────────────────────────────
- name: Dump logs on failure - name: Dump logs on failure
if: failure() if: failure()
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml logs run: docker compose -f docker-compose.yml -f docker-compose.ci.yml logs
+9 -1
View File
@@ -1,6 +1,14 @@
# CI override — exposes backend port 8000 so smoke/integration tests can reach it. # CI override — exposes backend port 8000 and injects credentials.
# Usage: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d backend frontend # Usage: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d backend frontend
#
# AUTH_PASSWORD_HASH uses $$ escaping: docker-compose converts $$ → $ before
# passing to the container, so the backend receives a valid bcrypt hash.
# This avoids the project .env file being subject to docker-compose $VAR expansion.
services: services:
backend: backend:
ports: ports:
- "8000:8000" - "8000:8000"
environment:
AUTH_USERNAME: admin
# bcrypt hash of "admin" — $$ is docker-compose escape for literal $
AUTH_PASSWORD_HASH: $$2b$$12$$RtMbyw17l4N5UGzeXMNAWuzCaVV.XFBY7ZetWheQhxcBDcxahapkG