diff --git a/.env.example b/.env.example index 4f273f9..e53ce88 100644 --- a/.env.example +++ b/.env.example @@ -28,8 +28,8 @@ OPENBRAIN__QUERY__TEXT_WEIGHT=0.4 # Authentication (optional) OPENBRAIN__AUTH__ENABLED=false -# Comma-separated list of API keys -# OPENBRAIN__AUTH__API_KEYS=key1,key2,key3 +# Comma-separated list of persistent API keys +# OPENBRAIN__AUTH__API_KEYS=prod_live_key,smoke_test_key # Logging RUST_LOG=info,openbrain_mcp=debug diff --git a/.gitea/workflows/ci-cd.yaml b/.gitea/workflows/ci-cd.yaml index 8ee2b96..24e2eed 100644 --- a/.gitea/workflows/ci-cd.yaml +++ b/.gitea/workflows/ci-cd.yaml @@ -18,6 +18,7 @@ jobs: OPENBRAIN__DATABASE__USER: ${{ secrets.OPENBRAIN__DATABASE__USER }} OPENBRAIN__DATABASE__PASSWORD: ${{ secrets.OPENBRAIN__DATABASE__PASSWORD }} OPENBRAIN__DATABASE__POOL_SIZE: ${{ secrets.OPENBRAIN__DATABASE__POOL_SIZE }} + OPENBRAIN__AUTH__API_KEYS: ${{ secrets.OPENBRAIN__AUTH__API_KEYS }} DEPLOY_DIR: /opt/openbrain-mcp SERVICE_NAME: openbrain-mcp steps: @@ -67,6 +68,14 @@ jobs: cargo build --release test -x target/release/openbrain-mcp + - name: Generate ephemeral e2e API key + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + run: | + set -euxo pipefail + install -d -m 700 .ci + umask 077 + openssl rand -hex 32 > .ci/openbrain_e2e_key + - name: Setup SSH auth if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' run: | @@ -103,6 +112,11 @@ jobs: : "${VPS_HOST:?Set repository secret VPS_HOST}" : "${VPS_USER:=root}" SSH="ssh -i $HOME/.ssh/deploy_key -o IdentitiesOnly=yes" + EPHEMERAL_E2E_KEY="$(cat .ci/openbrain_e2e_key)" + EFFECTIVE_OPENBRAIN__AUTH__API_KEYS="$EPHEMERAL_E2E_KEY" + if [[ -n "${OPENBRAIN__AUTH__API_KEYS:-}" ]]; then + EFFECTIVE_OPENBRAIN__AUTH__API_KEYS="${OPENBRAIN__AUTH__API_KEYS},${EPHEMERAL_E2E_KEY}" + fi : "${OPENBRAIN__DATABASE__HOST:?Set repository secret OPENBRAIN__DATABASE__HOST}" : "${OPENBRAIN__DATABASE__NAME:?Set repository secret OPENBRAIN__DATABASE__NAME}" @@ -122,6 +136,7 @@ jobs: OPENBRAIN__DATABASE__USER='$OPENBRAIN__DATABASE__USER' \ OPENBRAIN__DATABASE__PASSWORD='$OPENBRAIN__DATABASE__PASSWORD' \ OPENBRAIN__DATABASE__POOL_SIZE='$OPENBRAIN__DATABASE__POOL_SIZE' \ + OPENBRAIN__AUTH__API_KEYS='$EFFECTIVE_OPENBRAIN__AUTH__API_KEYS' \ bash -s" <<'EOS' set -euo pipefail DEPLOY_DIR="${DEPLOY_DIR:-/opt/openbrain-mcp}" @@ -186,6 +201,10 @@ jobs: upsert_env "OPENBRAIN__DATABASE__USER" "$OPENBRAIN__DATABASE__USER" upsert_env "OPENBRAIN__DATABASE__PASSWORD" "$OPENBRAIN__DATABASE__PASSWORD" upsert_env "OPENBRAIN__DATABASE__POOL_SIZE" "$OPENBRAIN__DATABASE__POOL_SIZE" + if [[ -n "${OPENBRAIN__AUTH__API_KEYS:-}" ]]; then + upsert_env "OPENBRAIN__AUTH__ENABLED" "true" + upsert_env "OPENBRAIN__AUTH__API_KEYS" "$OPENBRAIN__AUTH__API_KEYS" + fi upsert_env "OPENBRAIN__EMBEDDING__MODEL_PATH" "$DEPLOY_DIR/models/all-MiniLM-L6-v2" upsert_env "ORT_DYLIB_PATH" "$DEPLOY_DIR/lib/libonnxruntime.so" upsert_env "OPENBRAIN__SERVER__HOST" "0.0.0.0" @@ -219,14 +238,54 @@ jobs: env: OPENBRAIN_E2E_REMOTE: "true" OPENBRAIN_E2E_BASE_URL: http://${{ secrets.VPS_HOST }}:3100 - OPENBRAIN_E2E_API_KEY: ${{ secrets.OPENBRAIN_E2E_API_KEY }} OPENBRAIN__AUTH__ENABLED: "true" run: | set -euxo pipefail - : "${OPENBRAIN_E2E_API_KEY:?Set repository secret OPENBRAIN_E2E_API_KEY}" + export OPENBRAIN_E2E_API_KEY="$(cat .ci/openbrain_e2e_key)" . "$HOME/.cargo/env" cargo test --test e2e_mcp -- --test-threads=1 + - name: Remove ephemeral e2e key and restart service + if: always() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + run: | + set -euxo pipefail + : "${VPS_HOST:?Set repository secret VPS_HOST}" + : "${VPS_USER:=root}" + SSH="ssh -i $HOME/.ssh/deploy_key -o IdentitiesOnly=yes" + EPHEMERAL_E2E_KEY="$(cat .ci/openbrain_e2e_key)" + BASE_AUTH_KEYS="${OPENBRAIN__AUTH__API_KEYS:-}" + + $SSH "$VPS_USER@$VPS_HOST" "\ + DEPLOY_DIR=$DEPLOY_DIR \ + OPENBRAIN__AUTH__API_KEYS='$BASE_AUTH_KEYS' \ + bash -s" <<'EOS' + set -euo pipefail + DEPLOY_DIR="${DEPLOY_DIR:-/opt/openbrain-mcp}" + ENV_FILE="$DEPLOY_DIR/.env" + + upsert_env() { + local key="$1" + local value="$2" + local escaped_value + escaped_value="$(printf '%s' "$value" | sed -e 's/[\\&|]/\\&/g')" + if grep -qE "^${key}=" "$ENV_FILE"; then + sed -i "s|^${key}=.*|${key}=${escaped_value}|" "$ENV_FILE" + else + printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE" + fi + } + + if [[ -n "${OPENBRAIN__AUTH__API_KEYS:-}" ]]; then + upsert_env "OPENBRAIN__AUTH__ENABLED" "true" + upsert_env "OPENBRAIN__AUTH__API_KEYS" "$OPENBRAIN__AUTH__API_KEYS" + else + upsert_env "OPENBRAIN__AUTH__ENABLED" "false" + sed -i '/^OPENBRAIN__AUTH__API_KEYS=/d' "$ENV_FILE" + fi + EOS + + $SSH "$VPS_USER@$VPS_HOST" "systemctl restart $SERVICE_NAME" + - name: Cleanup SSH key if: always() run: | diff --git a/README.md b/README.md index 00bf5f9..d876f03 100644 --- a/README.md +++ b/README.md @@ -83,11 +83,21 @@ Recommended env for VPS-backed runs: ```bash OPENBRAIN_E2E_REMOTE=true OPENBRAIN_E2E_BASE_URL=https://ob.ingwaz.work -OPENBRAIN_E2E_API_KEY=your_live_api_key OPENBRAIN__AUTH__ENABLED=true ``` -The CI workflow uses this remote mode after `main` deploys so e2e coverage validates the VPS deployment rather than the local runner host. +The CI workflow uses this remote mode after `main` deploys so e2e coverage validates the VPS deployment rather than the local runner host. It now generates a random per-run e2e key, temporarily appends it to the deployed `OPENBRAIN__AUTH__API_KEYS`, runs the suite, then removes the key and restarts the service. + +For live deployments, keep `OPENBRAIN__AUTH__API_KEYS` for persistent non-test access only. The server accepts a comma-separated key list, so a practical split is: + +- `prod_live_key` for normal agent traffic +- `smoke_test_key` for ad hoc diagnostics + +In Gitea Actions, that means: + +- repo secret `OPENBRAIN__AUTH__API_KEYS=prod_live_key,smoke_test_key` + +If you want prod e2e coverage without leaving a standing CI key on the server, the workflow-generated ephemeral key handles that automatically. ## Agent Zero Developer Prompt