From 567907244b751c0fed438616fdf3b56c78f8908d Mon Sep 17 00:00:00 2001 From: RyanAI Date: Sat, 7 Mar 2026 15:39:39 +0800 Subject: [PATCH] feat: add Railway deployment config and CI/CD - Add Dockerfile with multi-stage build and healthcheck - Add railway.json for one-command Railway deployment - Add .env.example with all required environment variables - Add DEPLOY.md with step-by-step deployment guide - Add GitHub Actions CI pipeline (.github/workflows/ci.yml) - Add smoke-test.sh for post-deploy verification Closes: https://github.com/langoustine69/queryx/issues/4 --- .env.example | 35 +++++++++++ .github/workflows/ci.yml | 50 ++++++++++++++++ DEPLOY.md | 126 +++++++++++++++++++++++++++++++++++++++ Dockerfile | 35 +++++++++++ railway.json | 14 +++++ scripts/smoke-test.sh | 72 ++++++++++++++++++++++ 6 files changed, 332 insertions(+) create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 100644 DEPLOY.md create mode 100644 Dockerfile create mode 100644 railway.json create mode 100755 scripts/smoke-test.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..35d7e61 --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# Queryx Environment Variables + +## Required Configuration + +# USDC receivable address on Base mainnet +# Get your address from https://app.onchainden.com/ +PAYMENTS_RECEIVABLE_ADDRESS= + +# x402 Payment Facilitator URL +FACILITATOR_URL=https://facilitator.daydreams.systems + +# Blockchain Network +# Options: base, base-sepolia +NETWORK=base + +## API Keys + +# Brave Search API Key +# Get free key at: https://brave.com/search/api/ +BRAVE_API_KEY= + +# OpenAI API Key for GPT-4o-mini synthesis +# Get key at: https://platform.openai.com/api-keys +OPENAI_API_KEY= + +## Server Configuration + +# Server port (default: 3000) +PORT=3000 + +# Cache TTL in seconds (default: 300 = 5 minutes) +CACHE_TTL_SECONDS=300 + +## Optional: Custom Domain +# DOMAIN=queryx.run diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ead5ab9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run tests + run: bun test + + - name: Type check + run: bun run tsc --noEmit + + build: + name: Build Docker + runs-on: ubuntu-latest + needs: test + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: false + tags: queryx:latest + load: true diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..66de67c --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,126 @@ +# Queryx Deployment Guide + +This guide covers deploying Queryx to Railway with a single command. + +## Prerequisites + +- [Railway CLI](https://docs.railway.app/getting-started) installed +- Railway account +- Docker installed locally (for testing) + +## Quick Deploy + +### 1. Install Railway CLI + +```bash +# Install via npm +npm install -g @railway/cli + +# Or via brew +brew install railway +``` + +### 2. Login to Railway + +```bash +railway login +``` + +This opens your browser to authenticate. + +### 3. Initialize Project + +```bash +cd queryx +railway init +``` + +Follow the prompts: +- Project name: `queryx` +- Select "Deploy from a Dockerfile" + +### 4. Configure Environment Variables + +Set required environment variables via Railway dashboard or CLI: + +```bash +# Via CLI +railway variables set PAYMENTS_RECEIVABLE_ADDRESS=your_address +railway variables set FACILITATOR_URL=https://facilitator.daydreams.systems +railway variables set NETWORK=base +railway variables set BRAVE_API_KEY=your_brave_key +railway variables set OPENAI_API_KEY=your_openai_key +``` + +Or set them in the Railway dashboard under the project Settings > Variables. + +### 5. Deploy + +```bash +railway up +``` + +Your app will build via Dockerfile and deploy to Railway. + +### 6. Verify Deployment + +```bash +# Check status +railway status + +# View logs +railway logs + +# Test health endpoint +curl https://your-project-name.up.railway.app/health +``` + +## Custom Domain Setup + +### 1. Add Custom Domain + +```bash +railway domain add queryx.run +``` + +### 2. Configure DNS + +Add a CNAME record pointing to your Railway app URL. + +### 3. SSL + +Railway automatically provisions SSL via Let's Encrypt. + +## Local Development + +### Build Docker Image Locally + +```bash +docker build -t queryx . +docker run -p 3000:3000 queryx +``` + +### Run Smoke Test + +```bash +./scripts/smoke-test.sh +``` + +## Troubleshooting + +### Build Fails + +- Ensure all required environment variables are set +- Check Dockerfile syntax +- Run `docker build` locally to debug + +### Health Check Failing + +- Verify PORT is set to 3000 +- Check application logs: `railway logs` +- Ensure health endpoint exists at `/health` + +### Deployment Issues + +- Check Railway status page: https://status.railway.app +- Review deployment logs in Railway dashboard diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5a66e99 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +FROM oven/bun:1 AS base +WORKDIR /app + +# Install dependencies only when needed +FROM base AS deps +WORKDIR /app +COPY package.json bun.lockb ./ +RUN bun install --frozen-lockfile + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV="production" + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 bun + +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/src ./src + +EXPOSE 3000 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ + CMD curl -f http://localhost:3000/health || exit 1 + +USER bun + +CMD ["bun", "run", "src/index.ts"] diff --git a/railway.json b/railway.json new file mode 100644 index 0000000..5d0f5dd --- /dev/null +++ b/railway.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "DOCKERFILE", + "dockerfilePath": "Dockerfile" + }, + "deploy": { + "healthcheckPath": "/health", + "healthcheckTimeout": 10, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 3, + "numReplicas": 1 + } +} diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh new file mode 100755 index 0000000..c5a98e6 --- /dev/null +++ b/scripts/smoke-test.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# Queryx Smoke Test Script +# Tests deployment health and payment endpoints + +set -e + +# Configuration +BASE_URL="${1:-http://localhost:3000}" +TIMEOUT=10 + +echo "==========================================" +echo "Queryx Smoke Test" +echo "==========================================" +echo "Testing: $BASE_URL" +echo "" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test function +test_endpoint() { + local name="$1" + local url="$2" + local expected_status="$3" + + echo -n "Testing: $name... " + + status=$(curl -s -o /dev/null -w "%{http_code}" --max-time $TIMEOUT "$url" || echo "000") + + if [ "$status" = "$expected_status" ]; then + echo -e "${GREEN}PASS${NC} (HTTP $status)" + return 0 + else + echo -e "${RED}FAIL${NC} (Expected: $expected_status, Got: $status)" + return 1 + fi +} + +fail_count=0 + +# Test 1: Health endpoint returns 200 +if ! test_endpoint "Health Check" "$BASE_URL/health" "200"; then + ((fail_count++)) +fi + +# Test 2: Search without payment returns 402 +if ! test_endpoint "Search (No Payment)" "$BASE_URL/v1/search?q=test" "402"; then + ((fail_count++)) +fi + +# Test 3: Check for x402 payment headers +echo -n "Testing: Payment Headers... " +headers=$(curl -s -I --max-time $TIMEOUT "$BASE_URL/v1/search?q=test" || true) +if echo "$headers" | grep -qi "x-payments-required"; then + echo -e "${GREEN}PASS${NC}" +else + echo -e "${RED}FAIL${NC} (Missing x-payments-required header)" + ((fail_count++)) +fi + +echo "" +echo "==========================================" +if [ $fail_count -eq 0 ]; then + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +else + echo -e "${RED}$fail_count test(s) failed${NC}" + exit 1 +fi