diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml new file mode 100644 index 0000000..61efbb4 --- /dev/null +++ b/.forgejo/workflows/deploy.yml @@ -0,0 +1,100 @@ +name: Deploy to Production + +on: + push: + branches: [ main ] + +jobs: + deploy: + name: Deploy to Server + runs-on: ubuntu-latest + + steps: + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.1.0 + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + set -e + + echo "πŸš€ Starting deployment..." + + # Navigate to project directory + cd /root/docfast + + # Check current git status + echo "πŸ“‹ Current status:" + git status --short || true + + # Pull latest changes + echo "πŸ“₯ Pulling latest changes..." + git fetch origin + git pull origin main + + # Tag current running image for rollback + echo "🏷️ Tagging current image for rollback..." + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + docker tag docfast-docfast:latest docfast-docfast:rollback-$TIMESTAMP || echo "No existing image to tag" + + # Build new image + echo "πŸ”¨ Building new Docker image..." + docker compose build --no-cache + + # Stop services gracefully + echo "⏹️ Stopping services..." + docker compose down --timeout 30 + + # Start services + echo "▢️ Starting services..." + docker compose up -d + + # Wait for service to be ready + echo "⏱️ Waiting for service to be ready..." + for i in {1..30}; do + if curl -f -s http://127.0.0.1:3100/health > /dev/null; then + echo "βœ… Service is healthy!" + break + fi + if [ $i -eq 30 ]; then + echo "❌ Service failed to start - initiating rollback..." + docker compose down + + # Try to rollback to previous image + ROLLBACK_IMAGE=$(docker images --format "table {{.Repository}}:{{.Tag}}" | grep "docfast-docfast:rollback-" | head -n1 | tr -s ' ' | cut -d' ' -f1) + if [ ! -z "$ROLLBACK_IMAGE" ]; then + echo "πŸ”„ Rolling back to $ROLLBACK_IMAGE" + docker tag $ROLLBACK_IMAGE docfast-docfast:latest + docker compose up -d + + # Wait for rollback to be healthy + sleep 10 + if curl -f -s http://127.0.0.1:3100/health > /dev/null; then + echo "βœ… Rollback successful" + else + echo "❌ Rollback failed - manual intervention required" + fi + else + echo "❌ No rollback image available" + fi + + exit 1 + fi + echo "⏳ Attempt $i/30 - waiting 5 seconds..." + sleep 5 + done + + # Cleanup old rollback images (keep last 5) + echo "🧹 Cleaning up old rollback images..." + docker images --format "table {{.Repository}}:{{.Tag}}" | grep "docfast-docfast:rollback-" | tail -n +6 | awk '{print $1":"$2}' | xargs -r docker rmi || true + + # Final health check and status + echo "πŸ” Final health check..." + HEALTH_STATUS=$(curl -f -s http://127.0.0.1:3100/health || echo "UNHEALTHY") + echo "Health status: $HEALTH_STATUS" + + echo "πŸ“Š Service status:" + docker compose ps + + echo "πŸŽ‰ Deployment completed successfully!" \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..f9ca614 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,77 @@ +# DocFast CI/CD Deployment + +This repository uses Forgejo Actions for automated deployment to production. + +## Setup Instructions + +### 1. Repository Secrets + +Go to repository settings β†’ Actions β†’ Secrets and add these secrets: + +- **SERVER_HOST**: `167.235.156.214` +- **SERVER_USER**: `root` +- **SSH_PRIVATE_KEY**: The private SSH key content from `/home/openclaw/.ssh/docfast` + +### 2. How Deployment Works + +**Trigger**: Push to `main` branch +**Process**: +1. SSH to production server +2. Pull latest code from git +3. Tag current Docker image for rollback +4. Build new Docker image +5. Stop current services +6. Start new services +7. Health check at `http://127.0.0.1:3100/health` +8. Rollback automatically if health check fails + +### 3. Rollback Procedure + +**Automatic Rollback**: +- Happens automatically if deployment fails health checks +- Reverts to the previously tagged image + +**Manual Rollback**: +```bash +# On the production server +cd /root/docfast +./scripts/rollback.sh +``` + +**Emergency Rollback via SSH**: +```bash +ssh root@167.235.156.214 +cd /root/docfast +docker compose down +docker tag docfast-docfast:rollback-YYYYMMDD-HHMMSS docfast-docfast:latest +docker compose up -d +``` + +### 4. Monitoring + +- **Health Check**: `curl http://127.0.0.1:3100/health` +- **Service Status**: `docker compose ps` +- **Logs**: `docker compose logs -f` + +### 5. File Structure + +``` +.forgejo/workflows/deploy.yml # Main deployment workflow +scripts/rollback.sh # Manual rollback script +scripts/setup-secrets.sh # Helper for setting up secrets +DEPLOYMENT.md # This documentation +``` + +### 6. Testing the Pipeline + +1. Make a small change (e.g., bump version comment) +2. Commit and push to main branch +3. Check Actions tab in Forgejo to see deployment progress +4. Verify service is running with `curl http://127.0.0.1:3100/health` + +## Troubleshooting + +- **SSH Issues**: Ensure SSH key is properly added to secrets +- **Docker Build Issues**: Check server has enough disk space and memory +- **Health Check Fails**: Check if service is binding to correct port (3100) +- **Permission Issues**: Ensure user has Docker privileges on server \ No newline at end of file diff --git a/bugs.md b/bugs.md new file mode 100644 index 0000000..8ebec70 --- /dev/null +++ b/bugs.md @@ -0,0 +1,24 @@ +# DocFast Bugs + +## Open + +### BUG-030: Email change backend not implemented +- **Severity:** High +- **Found:** 2026-02-14 QA session +- **Description:** Frontend UI for email change is deployed (modal, form, JS handlers), but no backend routes exist. Frontend calls `/v1/email-change` and `/v1/email-change/verify` which return 404. +- **Impact:** Users see "Change Email" link in footer but the feature doesn't work. +- **Fix:** Implement `src/routes/email-change.ts` with verification code flow similar to signup/recover. + +### BUG-031: Stray file "\001@" in repository +- **Severity:** Low +- **Found:** 2026-02-14 +- **Description:** An accidental file named `\001@` was committed to the repo. +- **Fix:** `git rm "\001@"` and commit. + +### BUG-032: Swagger UI content not rendered via web_fetch +- **Severity:** Low (cosmetic) +- **Found:** 2026-02-14 +- **Description:** /docs page loads (200) and has swagger-ui assets, but content is JS-rendered so web_fetch can't verify full render. Needs browser-based QA for full verification. + +## Fixed +(none yet - this is first QA session) diff --git a/decisions.md b/decisions.md new file mode 100644 index 0000000..a68912d --- /dev/null +++ b/decisions.md @@ -0,0 +1,21 @@ +# DocFast Decisions Log + +## 2026-02-14: Mandatory QA After Every Deployment + +**Rule:** Every deployment MUST be followed by a full QA session. No exceptions. + +**QA Checklist:** +- Landing page loads, zero console errors +- Signup flow works (email verification) +- Key recovery flow works +- Email change flow works (when backend is implemented) +- Swagger UI loads at /docs +- API endpoints work (HTMLβ†’PDF, Markdownβ†’PDF, URLβ†’PDF) +- Health endpoint returns ok +- All previous features still working + +**Rationale:** Code was deployed to production without verification multiple times, leading to broken features being live. QA catches regressions before users do. + +## 2026-02-14: Code Must Be Committed Before Deployment + +Changes were found uncommitted on the production server. All code changes must be committed and pushed to Forgejo before deploying. diff --git a/public/index.html b/public/index.html index d529ad3..6a4f860 100644 --- a/public/index.html +++ b/public/index.html @@ -144,6 +144,27 @@ footer .container { display: flex; align-items: center; justify-content: space-b .trust-grid { gap: 24px; } } +/* Fix mobile terminal gaps at 375px and smaller */ +@media (max-width: 375px) { + .container { + padding: 0 12px !important; + } + .code-section { + margin: 32px auto 0; + max-width: calc(100vw - 24px) !important; + } + .code-header { + padding: 8px 12px; + } + .code-block { + padding: 12px !important; + font-size: 0.7rem; + } + .hero { + padding: 56px 0 40px; + } +} + /* Additional mobile overflow fixes */ html, body { overflow-x: hidden !important; diff --git a/public/openapi.json b/public/openapi.json index 811ada6..f3e5023 100644 --- a/public/openapi.json +++ b/public/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "DocFast API", "version": "1.0.0", - "description": "Convert HTML, Markdown, and URLs to pixel-perfect PDFs. Built-in invoice & receipt templates.\n\n## Authentication\nAll conversion and template endpoints require an API key via `Authorization: Bearer ` or `X-API-Key: ` header.\n\n## Rate Limits\n- Free tier: 100 PDFs/month, 10 req/min\n- Pro tier: 10,000 PDFs/month\n\n## Getting Started\n1. Sign up at [docfast.dev](https://docfast.dev) or via `POST /v1/signup/free`\n2. Verify your email with the 6-digit code\n3. Use your API key to convert documents", + "description": "Convert HTML, Markdown, and URLs to pixel-perfect PDFs. Built-in invoice & receipt templates.\n\n## Authentication\nAll conversion and template endpoints require an API key via `Authorization: Bearer ` or `X-API-Key: ` header.\n\n## Rate Limits\n- Free tier: 100 PDFs/month, 10 req/min\n- Pro tier: 10,000 PDFs/month, 30 req/min\n\n## Getting Started\n1. Sign up at [docfast.dev](https://docfast.dev) or via `POST /v1/signup/free`\n2. Verify your email with the 6-digit code\n3. Use your API key to convert documents", "contact": { "name": "DocFast", "url": "https://docfast.dev" } }, "servers": [{ "url": "https://docfast.dev", "description": "Production" }], diff --git a/scripts/rollback.sh b/scripts/rollback.sh new file mode 100755 index 0000000..4004a31 --- /dev/null +++ b/scripts/rollback.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -e + +echo "πŸ”„ DocFast Rollback Script" +echo "==========================" + +# Check if we're on the server +if [ ! -d "/root/docfast" ]; then + echo "❌ This script should be run on the production server" + exit 1 +fi + +cd /root/docfast + +# List available rollback images +echo "πŸ“‹ Available rollback images:" +ROLLBACK_IMAGES=$(docker images --format "table {{.Repository}}:{{.Tag}}\t{{.CreatedAt}}" | grep "docfast-docfast:rollback-" | head -10) + +if [ -z "$ROLLBACK_IMAGES" ]; then + echo "❌ No rollback images available" + exit 1 +fi + +echo "$ROLLBACK_IMAGES" +echo "" + +# Get the most recent rollback image +LATEST_ROLLBACK=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep "docfast-docfast:rollback-" | head -n1) + +if [ -z "$LATEST_ROLLBACK" ]; then + echo "❌ No rollback image found" + exit 1 +fi + +echo "🎯 Will rollback to: $LATEST_ROLLBACK" +echo "" + +# Confirm rollback +read -p "⚠️ Are you sure you want to rollback? (y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "❌ Rollback cancelled" + exit 1 +fi + +echo "πŸ›‘ Stopping current services..." +docker compose down --timeout 30 + +echo "πŸ”„ Rolling back to $LATEST_ROLLBACK..." +docker tag $LATEST_ROLLBACK docfast-docfast:latest + +echo "▢️ Starting services..." +docker compose up -d + +echo "⏱️ Waiting for service to be ready..." +for i in {1..20}; do + if curl -f -s http://127.0.0.1:3100/health > /dev/null; then + echo "βœ… Rollback successful! Service is healthy." + break + fi + if [ $i -eq 20 ]; then + echo "❌ Rollback failed - service is not responding" + exit 1 + fi + echo "⏳ Attempt $i/20 - waiting 3 seconds..." + sleep 3 +done + +echo "πŸ“Š Service status:" +docker compose ps + +echo "πŸŽ‰ Rollback completed successfully!" \ No newline at end of file diff --git a/scripts/setup-secrets.sh b/scripts/setup-secrets.sh new file mode 100755 index 0000000..90c7cae --- /dev/null +++ b/scripts/setup-secrets.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +echo "πŸ” Forgejo Repository Secrets Setup" +echo "====================================" + +# Source credentials to get Forgejo token +source /home/openclaw/.openclaw/workspace/.credentials/docfast.env + +# Repository secrets to set up +REPO_URL="https://git.cloonar.com/api/v1/repos/openclawd/docfast/actions/secrets" + +echo "Setting up repository secrets for CI/CD..." + +# Server host +echo "πŸ“‘ Setting SERVER_HOST..." +curl -X PUT "$REPO_URL/SERVER_HOST" \ + -H "Authorization: token $FORGEJO_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"data":"167.235.156.214"}' \ + --silent + +# Server user +echo "πŸ‘€ Setting SERVER_USER..." +curl -X PUT "$REPO_URL/SERVER_USER" \ + -H "Authorization: token $FORGEJO_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"data":"root"}' \ + --silent + +# SSH Private Key +echo "πŸ”‘ Setting SSH_PRIVATE_KEY..." +SSH_KEY_CONTENT=$(cat /home/openclaw/.ssh/docfast | jq -Rs .) +curl -X PUT "$REPO_URL/SSH_PRIVATE_KEY" \ + -H "Authorization: token $FORGEJO_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"data\":$SSH_KEY_CONTENT}" \ + --silent + +echo "βœ… Repository secrets have been configured!" +echo "" +echo "πŸ” To verify, check: https://git.cloonar.com/openclawd/docfast/settings/actions/secrets" \ No newline at end of file diff --git a/sessions.md b/sessions.md new file mode 100644 index 0000000..436b3bf --- /dev/null +++ b/sessions.md @@ -0,0 +1,37 @@ +# DocFast Sessions Log + +## 2026-02-14 22:14 UTC β€” Deployment + QA Session + +**Trigger:** Latest code changes (Swagger UI, key recovery UI, email change) were not deployed despite being in the working tree. + +**Actions:** +1. SSH'd into server (167.235.156.214) +2. Found uncommitted changes in working tree (email change UI, Swagger UI, key recovery link) +3. Committed all changes: `d859e9f` β€” "feat: email change UI, Swagger UI improvements, key recovery link on landing page" +4. Pushed to Forgejo (openclawd/docfast) +5. Rebuilt container with `docker compose build --no-cache` +6. Restarted: `docker compose up -d` +7. Verified server healthy: 15-page browser pool, version 0.2.1 + +**QA Results:** +| Test | Result | +|------|--------| +| Landing page loads | βœ… 200 OK | +| Key recovery link on landing | βœ… Present | +| Email change link in footer | βœ… Present | +| Swagger UI at /docs | βœ… 200 OK | +| Signup endpoint | βœ… Works (verification_required) | +| Key recovery endpoint | βœ… Works (recovery_sent) | +| Email change backend | ❌ NOT IMPLEMENTED (BUG-030) | +| HTMLβ†’PDF conversion | βœ… Valid PDF | +| Markdownβ†’PDF conversion | βœ… Valid PDF | +| URLβ†’PDF conversion | βœ… Valid PDF | +| Health endpoint | βœ… Pool: 15 pages, 0 active | +| Browser pool | βœ… 1 browser Γ— 15 pages | + +**Bugs Found:** +- BUG-030: Email change backend not implemented (frontend-only) +- BUG-031: Stray `\001@` file in repo +- BUG-032: Swagger UI needs browser QA for full verification + +**Note:** Browser-based QA not available (openclaw browser service unreachable). Console error check, mobile responsive test, and full Swagger UI render verification deferred. diff --git a/src/index.ts b/src/index.ts index cc94be3..87a0d84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,7 +56,7 @@ app.use(express.text({ limit: "2mb", type: "text/*" })); // Trust nginx proxy app.set("trust proxy", 1); -// Global rate limiting +// Global rate limiting - reduced from 10,000 to reasonable limit const limiter = rateLimit({ windowMs: 60_000, max: 100, diff --git a/state.json b/state.json new file mode 100644 index 0000000..90f9593 --- /dev/null +++ b/state.json @@ -0,0 +1,35 @@ +{ + "project": "DocFast", + "domain": "docfast.dev", + "server": "167.235.156.214", + "sshKey": "/home/openclaw/.ssh/docfast", + "repo": "openclawd/docfast", + "status": "live", + "version": "0.2.1", + "lastDeployment": "2026-02-14T22:17:00Z", + "lastQA": "2026-02-14T22:18:00Z", + "features": { + "htmlToPdf": true, + "markdownToPdf": true, + "urlToPdf": true, + "templates": true, + "signup": true, + "emailVerification": true, + "keyRecovery": true, + "emailChange": "frontend-only", + "swaggerDocs": true, + "browserPool": "1 browser Γ— 15 pages", + "stripeIntegration": true + }, + "infrastructure": { + "webServer": "nginx", + "ssl": "letsencrypt", + "container": "docker-compose", + "email": "postfix + opendkim" + }, + "todos": [ + "Implement email change backend route (/v1/email-change + /v1/email-change/verify)", + "Set up staging environment for pre-production testing", + "Remove obsolete \\001@ file from repo" + ] +}