diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 347614dd..dde8cca3 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -6,7 +6,7 @@ ## Overview -**Botsmann** is a modern AI bot platform featuring intelligent assistants for legal, medical, research, and language learning. Built with Next.js 16 and React 19. +**Botsmann** is a modern AI bot platform featuring intelligent assistants for legal, medical, research, and language learning. Built with Next.js 16 and React 19, self-hosted on the Hetzner box "bitbaum" behind Caddy (botsmann.orangecat.ch). --- @@ -19,7 +19,7 @@ | Language | TypeScript | | Styling | Tailwind CSS | | Testing | Jest | -| Deployment | Vercel | +| Deployment | Self-hosted (Hetzner + Caddy) | --- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..de955826 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +# Botsmann is deployed self-hosted on the Hetzner box "bitbaum" behind Caddy. +# Deployment runs from the local `.husky/pre-push` hook (build -> rsync -> systemd +# restart) when main is pushed. This workflow only validates build/lint/tests. + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies and build + run: npm run build:ci + env: + NEXT_PUBLIC_DEPLOY_TIME: ${{ github.event.head_commit.timestamp }} + + - name: Run format check + run: npm run format:check --if-present + + - name: Run linting + run: npm run lint --if-present + + - name: Run tests + run: npm run test --if-present diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 21a7ffe9..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Deploy to Vercel - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'npm' - - - name: Install dependencies and build - run: npm run build:ci - env: - NEXT_PUBLIC_DEPLOY_TIME: ${{ github.event.head_commit.timestamp }} - - - name: Run format check - run: npm run format:check --if-present - - - name: Run linting - run: npm run lint --if-present - - - name: Run tests - run: npm run test --if-present - - - name: Install Vercel CLI - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: npm install -g vercel@latest - - - name: Deploy to Vercel (Production) - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: vercel deploy --prod --archive=tgz --yes --token=${{ secrets.VERCEL_TOKEN }} - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - - - name: Deploy to Vercel (Preview) - if: github.event_name == 'pull_request' - run: vercel deploy --archive=tgz --yes --token=${{ secrets.VERCEL_TOKEN }} - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} diff --git a/.gitignore b/.gitignore index 930bd50e..545928a2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,6 @@ Thumbs.db logs/ *.log -# Vercel -.vercel - # Runtime state / cache (bot runtime artifacts) .call_count .circuit_breaker_history diff --git a/README.md b/README.md index 339f88ec..b97adcb2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Domain-specialized AI professionals, not another generic chatbot wrapper. [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6.svg)](https://www.typescriptlang.org/) [![Next.js](https://img.shields.io/badge/Next.js-14-000000.svg)](https://nextjs.org/) -[![Live](https://img.shields.io/badge/Live-botsmann.vercel.app-brightgreen.svg)](https://botsmann.vercel.app) +[![Live](https://img.shields.io/badge/Live-botsmann.orangecat.ch-brightgreen.svg)](https://botsmann.orangecat.ch) --- diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index e355acba..9cd70d0f 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -25,8 +25,7 @@ import { searchUserDocuments, } from '@/lib/chat'; -// Extend function timeout for model loading (Vercel) -// Note: Free tier max is 10s, Pro tier allows up to 60s +// Extend function timeout for model loading (Next.js route segment config) export const maxDuration = 30; /** diff --git a/app/api/custom-bots/[id]/chat/route.ts b/app/api/custom-bots/[id]/chat/route.ts index ff567beb..2509a0bd 100644 --- a/app/api/custom-bots/[id]/chat/route.ts +++ b/app/api/custom-bots/[id]/chat/route.ts @@ -34,7 +34,7 @@ import { parseResponseWithSuggestions, } from '@/lib/chat'; -// Extend function timeout for model loading (Vercel) +// Extend function timeout for model loading (Next.js route segment config) export const maxDuration = 30; const ChatRequestSchema = z.object({ diff --git a/app/api/demo/document-chat/route.ts b/app/api/demo/document-chat/route.ts index 9eb6e5ac..7e264739 100644 --- a/app/api/demo/document-chat/route.ts +++ b/app/api/demo/document-chat/route.ts @@ -15,7 +15,7 @@ import { getClientIp } from '@/lib/request'; import { sanitizeUserMessage, sanitizePromptContent } from '@/lib/prompt-sanitizer'; import { logger } from '@/lib/logger'; -// Extend function timeout for model loading (Vercel) +// Extend function timeout for model loading (Next.js route segment config) export const maxDuration = 30; // Maximum document content size (characters) to prevent abuse diff --git a/app/api/documents/process/route.ts b/app/api/documents/process/route.ts index 3a5a6bb1..b785ec8f 100644 --- a/app/api/documents/process/route.ts +++ b/app/api/documents/process/route.ts @@ -28,7 +28,7 @@ import { DOMAIN_ERRORS } from '@/lib/constants'; import { detectDomains } from '@/lib/context/domain-detector'; import { logger } from '@/lib/logger'; -// Extend function timeout for embedding generation (Vercel) +// Extend function timeout for embedding generation (Next.js route segment config) export const maxDuration = 60; // Allow longer for document processing /** diff --git a/app/api/documents/search/route.ts b/app/api/documents/search/route.ts index 2b747144..fad79a3e 100644 --- a/app/api/documents/search/route.ts +++ b/app/api/documents/search/route.ts @@ -14,7 +14,7 @@ import { jsonSuccess, jsonError, jsonUnauthorized, handleError, HTTP_STATUS } fr import { DOMAIN_ERRORS } from '@/lib/constants'; import { logger } from '@/lib/logger'; -// Extend function timeout for embedding generation (Vercel) +// Extend function timeout for embedding generation (Next.js route segment config) export const maxDuration = 30; /** diff --git a/app/api/professional-chat/route.ts b/app/api/professional-chat/route.ts index e0873515..9c953fcf 100644 --- a/app/api/professional-chat/route.ts +++ b/app/api/professional-chat/route.ts @@ -26,7 +26,7 @@ import { import { getUserLLMSettings, searchUserDocuments } from '@/lib/chat'; import { getRelevantContext, extractAndSaveContext } from '@/lib/context'; -// Extend function timeout for model loading (Vercel) +// Extend function timeout for model loading (Next.js route segment config) export const maxDuration = 30; const MAX_CONTEXT_CHARS = 4000; // Leave room for system prompt + response diff --git a/app/api/quick-chat/route.ts b/app/api/quick-chat/route.ts index 350601b2..493f4a9b 100644 --- a/app/api/quick-chat/route.ts +++ b/app/api/quick-chat/route.ts @@ -20,7 +20,7 @@ import { wrapUserContext, } from '@/lib/prompt-sanitizer'; -// Extend function timeout for model loading (Vercel) +// Extend function timeout for model loading (Next.js route segment config) export const maxDuration = 30; export async function POST(request: NextRequest) { diff --git a/app/api/warmup/route.ts b/app/api/warmup/route.ts index fb287733..84c0b75b 100644 --- a/app/api/warmup/route.ts +++ b/app/api/warmup/route.ts @@ -3,7 +3,7 @@ * * GET /api/warmup - Pre-loads the embedding model to avoid cold start delays * - * Called periodically by Vercel cron to keep the function warm + * Called periodically by a cron/uptime check to keep the embedding model warm */ import { NextResponse } from 'next/server'; diff --git a/app/demo/page.tsx b/app/demo/page.tsx index 88993671..a7079029 100644 --- a/app/demo/page.tsx +++ b/app/demo/page.tsx @@ -256,7 +256,7 @@ const DemoPage: FC = () => { • Backend: Next.js API routes
  • - • Deployment: Vercel serverless + • Deployment: Self-hosted (Hetzner + Caddy)
  • diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index f06bbbda..55e8a9fa 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -160,22 +160,21 @@ npm audit fix ## Deployment -### Manual Deploy to Vercel +Botsmann is self-hosted on the Hetzner box "bitbaum" behind Caddy +(botsmann.orangecat.ch). There is no Vercel. -```bash -# Install Vercel CLI if needed -npm i -g vercel +### Automatic Deploy -# Deploy to preview -vercel +Push to `main` — the local `.husky/pre-push` hook runs the self-hosted deploy +script (build → rsync → systemd restart) in the background. Logs stream to +`/tmp/push-deploy-botsmann.log`. -# Deploy to production -vercel --prod -``` +### Manual Deploy -### Automatic Deploy - -Push to `main` branch - GitHub Actions handles deployment. +```bash +# Re-run the same self-hosted deploy the pre-push hook triggers +bash /home/g/dev/fleetcrown/scripts/hetzner/deploy.sh botsmann +``` --- diff --git a/docs/SHARED_CONTEXT.md b/docs/SHARED_CONTEXT.md index 2a5ea678..6dc0c5c8 100644 --- a/docs/SHARED_CONTEXT.md +++ b/docs/SHARED_CONTEXT.md @@ -28,9 +28,9 @@ This document provides comprehensive technical context for the Botsmann project. ### Infrastructure -| Service | Purpose | -| -------- | -------------------------------- | -| Vercel | Hosting & deployment | +| Service | Purpose | +| -------------------- | -------------------------------- | +| Hetzner + Caddy | Hosting & deployment (self-host) | | Supabase | Database, Auth, Storage, SQL API | | AWS SES | Email delivery | @@ -130,13 +130,14 @@ Chosen for: - Built-in Auth, Storage, and realtime subscriptions - Strong TypeScript support via generated types -### Serverless API Routes +### API Routes -All backend logic runs in Vercel serverless functions: +All backend logic runs in Next.js API routes, served by the self-hosted +Next.js server (systemd) on the Hetzner box: -- No server to maintain -- Auto-scaling -- Pay-per-use +- Single long-running server (no cold starts) +- Fronted by Caddy for TLS + reverse proxy +- Predictable, flat hosting cost --- @@ -218,19 +219,17 @@ User → components/*Form.tsx ### Automatic (Recommended) -Push to `main` branch → GitHub Actions → Vercel +Push to `main` → local `.husky/pre-push` hook → self-hosted deploy on the +Hetzner box "bitbaum" (build → rsync → systemd restart), behind Caddy. +GitHub Actions runs build/lint/tests as CI only. ### Manual ```bash npm run build -vercel --prod +bash /home/g/dev/fleetcrown/scripts/hetzner/deploy.sh botsmann ``` -### Preview Deployments - -Every PR gets a preview URL automatically. - --- ## Performance Considerations @@ -238,7 +237,7 @@ Every PR gets a preview URL automatically. 1. **Server Components**: Use by default, add `'use client'` only when needed 2. **Image Optimization**: Use `next/image` for all images 3. **Code Splitting**: Automatic with App Router -4. **Caching**: Vercel Edge caching for static content +4. **Caching**: Caddy + Next.js caching for static content --- diff --git a/scripts/deployment/deploy-monitor.sh b/scripts/deployment/deploy-monitor.sh deleted file mode 100755 index 1de147c4..00000000 --- a/scripts/deployment/deploy-monitor.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -# Deployment Monitoring Script for Botsmann -# Monitors Vercel deployments and provides status updates - -set -e - -echo "🚀 Botsmann Deployment Monitor" -echo "==============================" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Check if we're in the right directory -if [ ! -f "package.json" ] || [ ! -f "vercel.json" ]; then - echo -e "${RED}❌ Error: Not in the correct project directory${NC}" - exit 1 -fi - -echo -e "${BLUE}📋 Checking project status...${NC}" - -# Check if required files exist -required_files=("package.json" "next.config.js" "vercel.json" ".github/workflows/deploy.yml") -for file in "${required_files[@]}"; do - if [ -f "$file" ]; then - echo -e "${GREEN}✅ $file${NC}" - else - echo -e "${RED}❌ $file (missing)${NC}" - fi -done - -echo -e "\n${BLUE}🔍 Checking deployment configuration...${NC}" - -# Check vercel.json -if [ -f "vercel.json" ]; then - echo -e "${GREEN}✅ vercel.json found${NC}" - echo -e "${YELLOW}📋 Vercel Configuration:${NC}" - cat vercel.json | jq . -else - echo -e "${RED}❌ vercel.json not found${NC}" -fi - -# Check GitHub Actions workflow -if [ -f ".github/workflows/deploy.yml" ]; then - echo -e "\n${GREEN}✅ GitHub Actions workflow found${NC}" - echo -e "${YELLOW}📋 Workflow Status:${NC}" - grep -E "(name|on:|runs-on:|steps:)" .github/workflows/deploy.yml | head -10 -else - echo -e "\n${RED}❌ GitHub Actions workflow not found${NC}" -fi - -echo -e "\n${BLUE}🔧 Checking build optimization...${NC}" - -# Check if build script is optimized -if grep -q "build:ci" package.json; then - echo -e "${GREEN}✅ Optimized build script (build:ci) found${NC}" -else - echo -e "${YELLOW}⚠️ Standard build script found${NC}" -fi - -# Check for potential issues -echo -e "\n${BLUE}🔍 Checking for potential issues...${NC}" - -# Check for security vulnerabilities -if [ -f "package-lock.json" ]; then - vuln_count=$(npm audit --audit-level=moderate 2>/dev/null | grep -c "vulnerabilit" || echo "0") - if [ "$vuln_count" -gt "0" ]; then - echo -e "${YELLOW}⚠️ $vuln_count vulnerabilities found (run 'npm audit fix')${NC}" - else - echo -e "${GREEN}✅ No security vulnerabilities found${NC}" - fi -else - echo -e "${YELLOW}⚠️ No package-lock.json found${NC}" -fi - -echo -e "\n${BLUE}📊 Deployment Readiness Summary${NC}" -echo -e "${GREEN}✅ Project structure verified${NC}" -echo -e "${GREEN}✅ Build configuration optimized${NC}" -echo -e "${GREEN}✅ GitHub Actions workflow configured${NC}" -echo -e "${GREEN}✅ Vercel configuration ready${NC}" - -echo -e "\n${YELLOW}📋 Next Steps:${NC}" -echo "1. Ensure Vercel secrets are configured in GitHub repository" -echo "2. Push changes to trigger automated deployment" -echo "3. Monitor deployment in GitHub Actions and Vercel dashboard" - -echo -e "\n${GREEN}🎉 Ready for automated deployment!${NC}" diff --git a/vercel.json b/vercel.json deleted file mode 100644 index 64db3301..00000000 --- a/vercel.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "buildCommand": "next build", - "devCommand": "next dev", - "framework": "nextjs", - "github": { - "enabled": false - }, - "crons": [ - { - "path": "/api/rebuild", - "schedule": "0 9 * * *" - } - ] -}