diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index af5ce7b..b1f9f91 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -2,8 +2,8 @@ # ============================================ # Switchboard Core - CI/CD Pipeline (v0.17.3) # ============================================ -# Cluster deployments use SEPARATE FE + BE images. -# Unified image is for Docker Hub only (docker-compose use). +# Single unified image (Go backend + nginx frontend). +# v0.1.0: Dropped FE/BE image split per ROADMAP design decision. # # Pipeline: # 0. Detect changes (path-based gating for all downstream jobs) @@ -88,8 +88,7 @@ env: CERT_ISSUER: ${{ vars.CERT_ISSUER_PROD || 'letsencrypt-prod' }} POSTGRES_HOST: ${{ vars.POSTGRES_HOST || 'postgres-primary.postgres.svc.cluster.local' }} POSTGRES_PORT: ${{ vars.POSTGRES_PORT || '5432' }} - FE_IMAGE: ${{ vars.REGISTRY }}/xcaliber/switchboard-fe - BE_IMAGE: ${{ vars.REGISTRY }}/xcaliber/switchboard-be + IMAGE: ${{ vars.REGISTRY }}/switchboard/core DOCKERHUB_IMAGE: ${{ vars.DOCKERHUB_IMAGE || 'gobha/switchboard-core' }} jobs: @@ -297,12 +296,6 @@ jobs: env: GOPRIVATE: git.gobha.me/* GONOSUMCHECK: git.gobha.me/* - # Generic provider config for live integration tests - PROVIDER: ${{ vars.PROVIDER }} - PROVIDER_KEY: ${{ secrets.PROVIDER_KEY }} - PROVIDER_URL: ${{ vars.PROVIDER_URL }} - # Legacy compat: VENICE_API_KEY still works if PROVIDER_KEY is not set - VENICE_API_KEY: ${{ secrets.VENICE_API_KEY }} steps: - name: Checkout uses: actions/checkout@v4 @@ -415,16 +408,11 @@ jobs: echo "DEPLOY_SUFFIX=-dev" >> "$GITHUB_OUTPUT" echo "DB_NAME=switchboard_core_dev" >> "$GITHUB_OUTPUT" echo "DB_WIPE=true" >> "$GITHUB_OUTPUT" - echo "FE_REPLICAS=1" >> "$GITHUB_OUTPUT" - echo "BE_REPLICAS=1" >> "$GITHUB_OUTPUT" - echo "FE_MEMORY_REQUEST=64Mi" >> "$GITHUB_OUTPUT" - echo "FE_MEMORY_LIMIT=128Mi" >> "$GITHUB_OUTPUT" - echo "FE_CPU_REQUEST=25m" >> "$GITHUB_OUTPUT" - echo "FE_CPU_LIMIT=100m" >> "$GITHUB_OUTPUT" - echo "BE_MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT" - echo "BE_MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT" - echo "BE_CPU_REQUEST=50m" >> "$GITHUB_OUTPUT" - echo "BE_CPU_LIMIT=250m" >> "$GITHUB_OUTPUT" + echo "REPLICAS=1" >> "$GITHUB_OUTPUT" + echo "MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT" + echo "MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT" + echo "CPU_REQUEST=50m" >> "$GITHUB_OUTPUT" + echo "CPU_LIMIT=250m" >> "$GITHUB_OUTPUT" echo "env_label=dev (PR #${{ gitea.event.pull_request.number }})" >> "$GITHUB_OUTPUT" elif [[ "${{ gitea.ref }}" == refs/tags/v* ]]; then VERSION="${{ gitea.ref_name }}" @@ -436,16 +424,11 @@ jobs: echo "DEPLOY_SUFFIX=" >> "$GITHUB_OUTPUT" echo "DB_NAME=switchboard_core" >> "$GITHUB_OUTPUT" echo "DB_WIPE=false" >> "$GITHUB_OUTPUT" - echo "FE_REPLICAS=2" >> "$GITHUB_OUTPUT" - echo "BE_REPLICAS=2" >> "$GITHUB_OUTPUT" - echo "FE_MEMORY_REQUEST=64Mi" >> "$GITHUB_OUTPUT" - echo "FE_MEMORY_LIMIT=128Mi" >> "$GITHUB_OUTPUT" - echo "FE_CPU_REQUEST=50m" >> "$GITHUB_OUTPUT" - echo "FE_CPU_LIMIT=200m" >> "$GITHUB_OUTPUT" - echo "BE_MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT" - echo "BE_MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT" - echo "BE_CPU_REQUEST=100m" >> "$GITHUB_OUTPUT" - echo "BE_CPU_LIMIT=500m" >> "$GITHUB_OUTPUT" + echo "REPLICAS=2" >> "$GITHUB_OUTPUT" + echo "MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT" + echo "MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT" + echo "CPU_REQUEST=100m" >> "$GITHUB_OUTPUT" + echo "CPU_LIMIT=500m" >> "$GITHUB_OUTPUT" echo "is_release=true" >> "$GITHUB_OUTPUT" echo "env_label=production (${VERSION})" >> "$GITHUB_OUTPUT" else @@ -456,16 +439,11 @@ jobs: echo "DEPLOY_SUFFIX=-test" >> "$GITHUB_OUTPUT" echo "DB_NAME=switchboard_core_test" >> "$GITHUB_OUTPUT" echo "DB_WIPE=false" >> "$GITHUB_OUTPUT" - echo "FE_REPLICAS=1" >> "$GITHUB_OUTPUT" - echo "BE_REPLICAS=1" >> "$GITHUB_OUTPUT" - echo "FE_MEMORY_REQUEST=64Mi" >> "$GITHUB_OUTPUT" - echo "FE_MEMORY_LIMIT=128Mi" >> "$GITHUB_OUTPUT" - echo "FE_CPU_REQUEST=25m" >> "$GITHUB_OUTPUT" - echo "FE_CPU_LIMIT=100m" >> "$GITHUB_OUTPUT" - echo "BE_MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT" - echo "BE_MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT" - echo "BE_CPU_REQUEST=50m" >> "$GITHUB_OUTPUT" - echo "BE_CPU_LIMIT=250m" >> "$GITHUB_OUTPUT" + echo "REPLICAS=1" >> "$GITHUB_OUTPUT" + echo "MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT" + echo "MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT" + echo "CPU_REQUEST=50m" >> "$GITHUB_OUTPUT" + echo "CPU_LIMIT=250m" >> "$GITHUB_OUTPUT" echo "env_label=test (main)" >> "$GITHUB_OUTPUT" fi @@ -577,45 +555,33 @@ jobs: SQL echo "✓ Dev wipe complete — backend will rebuild schema on startup" - # ── Build Backend Image ────────────────────── - - name: Build backend image + # ── Build Unified Image ────────────────────── + - name: Build image run: | - docker build -f server/Dockerfile \ - -t ${BE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} \ - . - - # ── Build Frontend Image ───────────────────── - - name: Build frontend image - run: | - docker build -f Dockerfile.frontend \ - -t ${FE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} \ + docker build -f Dockerfile \ + -t ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} \ . # ── Tag release versions ───────────────────── - name: Tag release versions if: steps.setup.outputs.EXTRA_TAG != '' run: | - docker tag ${BE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${BE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} - docker tag ${FE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${FE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} + docker tag ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} # ── Push to Internal Registry ──────────────── - - name: Push images + - name: Push image run: | - docker push ${BE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} - docker push ${FE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} + docker push ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} if [[ -n "${{ steps.setup.outputs.EXTRA_TAG }}" ]]; then - docker push ${BE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} - docker push ${FE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} + docker push ${IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} fi - # ── Build + Push Unified to Docker Hub (release only) ── - - name: Build and push unified image + # ── Push to Docker Hub (release only) ──────── + - name: Push to Docker Hub if: steps.setup.outputs.is_release == 'true' run: | - docker build -f Dockerfile \ - -t ${DOCKERHUB_IMAGE}:latest \ - -t ${DOCKERHUB_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} \ - . + docker tag ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${DOCKERHUB_IMAGE}:latest + docker tag ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${DOCKERHUB_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} if [[ -n "${{ secrets.DOCKERHUB_TOKEN }}" ]]; then echo "${{ secrets.DOCKERHUB_TOKEN }}" | \ docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin @@ -692,21 +658,15 @@ jobs: DEPLOY_HOST: ${{ steps.setup.outputs.DEPLOY_HOST }} BASE_PATH: ${{ steps.setup.outputs.BASE_PATH }} DB_NAME: ${{ steps.setup.outputs.DB_NAME }} - FE_REPLICAS: ${{ steps.setup.outputs.FE_REPLICAS }} - BE_REPLICAS: ${{ steps.setup.outputs.BE_REPLICAS }} - FE_MEMORY_REQUEST: ${{ steps.setup.outputs.FE_MEMORY_REQUEST }} - FE_MEMORY_LIMIT: ${{ steps.setup.outputs.FE_MEMORY_LIMIT }} - FE_CPU_REQUEST: ${{ steps.setup.outputs.FE_CPU_REQUEST }} - FE_CPU_LIMIT: ${{ steps.setup.outputs.FE_CPU_LIMIT }} - BE_MEMORY_REQUEST: ${{ steps.setup.outputs.BE_MEMORY_REQUEST }} - BE_MEMORY_LIMIT: ${{ steps.setup.outputs.BE_MEMORY_LIMIT }} - BE_CPU_REQUEST: ${{ steps.setup.outputs.BE_CPU_REQUEST }} - BE_CPU_LIMIT: ${{ steps.setup.outputs.BE_CPU_LIMIT }} + REPLICAS: ${{ steps.setup.outputs.REPLICAS }} + MEMORY_REQUEST: ${{ steps.setup.outputs.MEMORY_REQUEST }} + MEMORY_LIMIT: ${{ steps.setup.outputs.MEMORY_LIMIT }} + CPU_REQUEST: ${{ steps.setup.outputs.CPU_REQUEST }} + CPU_LIMIT: ${{ steps.setup.outputs.CPU_LIMIT }} # Storage (v0.12.0+) STORAGE_CLASS: ${{ vars.STORAGE_CLASS }} STORAGE_SIZE: ${{ vars.STORAGE_SIZE || '10Gi' }} STORAGE_BACKEND: ${{ vars.STORAGE_BACKEND || 'pvc' }} - EXTRACTION_CONCURRENCY: ${{ vars.EXTRACTION_CONCURRENCY || '3' }} run: | # Render PVC first (must exist before backend references it) if [[ -n "${STORAGE_CLASS}" ]]; then @@ -714,7 +674,6 @@ jobs: fi envsubst < k8s/backend.yaml > /tmp/backend.yaml - envsubst < k8s/frontend.yaml > /tmp/frontend.yaml envsubst < k8s/middleware-retry.yaml > /tmp/middleware-retry.yaml envsubst < k8s/ingress.yaml > /tmp/ingress.yaml @@ -729,7 +688,6 @@ jobs: fi kubectl apply -f /tmp/backend.yaml - kubectl apply -f /tmp/frontend.yaml # Traefik retry middleware (v0.28.8) — requires RBAC for traefik.io CRDs. # First-time setup: kubectl apply -f k8s/rbac-traefik.yaml (cluster admin) @@ -760,23 +718,19 @@ jobs: SUFFIX: ${{ steps.setup.outputs.DEPLOY_SUFFIX }} ENV: ${{ steps.setup.outputs.ENVIRONMENT }} run: | - kubectl rollout restart deployment/switchboard-be${SUFFIX} -n ${NAMESPACE} - kubectl rollout restart deployment/switchboard-fe${SUFFIX} -n ${NAMESPACE} - - kubectl rollout status deployment/switchboard-be${SUFFIX} -n ${NAMESPACE} --timeout=180s - kubectl rollout status deployment/switchboard-fe${SUFFIX} -n ${NAMESPACE} --timeout=180s + kubectl rollout restart deployment/switchboard${SUFFIX} -n ${NAMESPACE} + kubectl rollout status deployment/switchboard${SUFFIX} -n ${NAMESPACE} --timeout=180s echo "" echo "━━━ Pod Status ━━━" kubectl get pods -n ${NAMESPACE} -l app=switchboard,env=${ENV} - BE_READY=$(kubectl get deployment switchboard-be${SUFFIX} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}') - FE_READY=$(kubectl get deployment switchboard-fe${SUFFIX} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}') + READY=$(kubectl get deployment switchboard${SUFFIX} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}') - if [[ "${BE_READY:-0}" -gt 0 ]] && [[ "${FE_READY:-0}" -gt 0 ]]; then - echo "✅ Healthy: BE=${BE_READY} FE=${FE_READY} pods ready" + if [[ "${READY:-0}" -gt 0 ]]; then + echo "✅ Healthy: ${READY} pods ready" else - echo "❌ Unhealthy: BE=${BE_READY:-0} FE=${FE_READY:-0}" + echo "❌ Unhealthy: ${READY:-0} pods ready" kubectl logs -n ${NAMESPACE} -l app=switchboard,env=${ENV} --tail=30 exit 1 fi diff --git a/ROADMAP.md b/ROADMAP.md index bc58559..615547c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,11 +14,11 @@ platform that extensions build on. | 2. Delete packages | ✅ | 15 Go packages, 29 handler files removed | | 3. Gut stores/models | ✅ | 40 → 20 store interfaces, kernel-only models | | 4. Fresh migrations | ✅ | 9 files × 2 dialects, 27 tables | -| 5. Fix compilation | 🔧 | ~95% done, ~5 compile errors remaining | -| 6. Fix tests | ⬜ | Prune 13K test lines to kernel-only | -| 7. Frontend gut | ⬜ | Shell + SDK only, vendor libs baked | -| 8. New ICD | ⬜ | Kernel-only API spec | -| 9. CI/CD + Dockerfile | ⬜ | Single image, new DB names | +| 5. Fix compilation | ✅ | `go build ./...` clean, 300+ stale route lines cut | +| 6. Fix tests | ✅ | 8 test packages pass, ~12K stale test lines pruned | +| 7. Frontend gut | ✅ | Shell + SDK only, 50+ files of chat/notes/projects code removed | +| 8. New ICD | 🔧 | Stub in place, full kernel-only spec TBD | +| 9. CI/CD + Dockerfile | ✅ | Single unified image, FE/BE split removed, DB names updated | | 10. Smoke test + tag | ⬜ | Deploy, verify, tag v0.1.0 | ## v0.2.0 — SDK & Triggers diff --git a/k8s/frontend.yaml b/k8s/frontend.yaml deleted file mode 100644 index 5e23be9..0000000 --- a/k8s/frontend.yaml +++ /dev/null @@ -1,99 +0,0 @@ -# k8s/frontend.yaml -# ============================================ -# Switchboard Core - Frontend Deployment -# ============================================ -# nginx serving the SPA + proxying page routes -# to the backend for Go template rendering. -# -# v0.22.6+: BACKEND_URL enables proxy_pass for -# /, /login, /admin, /editor, /notes, /settings. -# ============================================ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: switchboard-fe${DEPLOY_SUFFIX} - namespace: ${NAMESPACE} - labels: - app: switchboard - component: frontend - env: ${ENVIRONMENT} -spec: - replicas: ${FE_REPLICAS} - selector: - matchLabels: - app: switchboard - component: frontend - env: ${ENVIRONMENT} - template: - metadata: - labels: - app: switchboard - component: frontend - env: ${ENVIRONMENT} - spec: - containers: - - name: frontend - image: ${FE_IMAGE}:${IMAGE_TAG} - imagePullPolicy: Always - ports: - - containerPort: 80 - name: http - env: - - name: BASE_PATH - value: "${BASE_PATH}" - - name: ENVIRONMENT - value: "${ENVIRONMENT}" - - name: BACKEND_URL - value: "http://switchboard-be${DEPLOY_SUFFIX}:8080" - volumeMounts: - - name: branding - mountPath: /branding - readOnly: true - resources: - requests: - memory: "${FE_MEMORY_REQUEST}" - cpu: "${FE_CPU_REQUEST}" - limits: - memory: "${FE_MEMORY_LIMIT}" - cpu: "${FE_CPU_LIMIT}" - readinessProbe: - httpGet: - path: /healthz - port: 80 - initialDelaySeconds: 3 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 3 - livenessProbe: - httpGet: - path: /healthz - port: 80 - initialDelaySeconds: 5 - periodSeconds: 30 - timeoutSeconds: 5 - failureThreshold: 3 - volumes: - - name: branding - configMap: - name: switchboard-branding - optional: true ---- -apiVersion: v1 -kind: Service -metadata: - name: switchboard-fe${DEPLOY_SUFFIX} - namespace: ${NAMESPACE} - labels: - app: switchboard - component: frontend - env: ${ENVIRONMENT} -spec: - selector: - app: switchboard - component: frontend - env: ${ENVIRONMENT} - ports: - - protocol: TCP - port: 80 - targetPort: 80 - name: http diff --git a/server/Dockerfile b/server/Dockerfile deleted file mode 100644 index cd867f2..0000000 --- a/server/Dockerfile +++ /dev/null @@ -1,49 +0,0 @@ -# ========================================== -# Switchboard Core - Backend Dockerfile -# ========================================== -# Standalone Go API server for cluster deployment. -# Build context is the repo root (not ./server). -# ========================================== - -# Build stage -FROM golang:1.23-bookworm AS builder - -ARG APP_VERSION=dev - -WORKDIR /app - -# Copy module files and download deps -COPY server/go.mod server/go.sum* ./ -RUN go mod download - -# Copy source and tidy (resolves any go.sum gaps) -COPY server/ . -RUN go mod tidy - -# Copy VERSION from repo root — used by ldflags and copied to runtime image. -# If APP_VERSION build-arg was supplied by CI, it takes precedence via ldflags. -# If not, $(cat VERSION) reads the file directly so the binary always has a real version. -COPY VERSION ./ -RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=$(cat VERSION)" -o /bin/switchboard . - -# Runtime stage -FROM debian:bookworm-slim - -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /bin/switchboard /usr/local/bin/switchboard -COPY --from=builder /app/database/migrations /app/database/migrations - -# VERSION file at /VERSION — version.go init() reads it as fallback for local/dev runs -COPY --from=builder /app/VERSION /VERSION - -# Builtin extensions (seeded into DB on startup) -COPY extensions/builtin/ /app/extensions/builtin/ - -WORKDIR /app - -EXPOSE 8080 - -ENTRYPOINT ["switchboard"]