diff --git a/.env.example b/.env.example index 1eccec9..6270c7f 100644 --- a/.env.example +++ b/.env.example @@ -2,11 +2,9 @@ # ECHODO — Environment Variables # ===================================================================== # Local dev: copy this file to .env and adjust values. -# Coolify (CT 107): paste these into the resource's "Environment Variables" -# tab. Coolify also accepts a .env file uploaded via the UI. -# -# See AGENT-DEPLOY.md for shared CT 102 service credentials and the -# CT 100 Traefik routing pattern. +# Production (CT 116 standalone LXC): paste into /opt/submatter/.env on the host. +# See docs/deployment-production.md and proxmox/ct116/README.md. +# See AGENT-DEPLOY.md for shared CT 102 service credentials and Traefik routing. # ===================================================================== # ----- Database (CT 102 shared Postgres) ----- @@ -44,6 +42,16 @@ REDIS_URL="redis://:CHANGE_ME@192.168.68.102:6379/11" # WORKER_CHECKPOINT_EVERY_N_STEPS=1 # WORKER_REPO_ROOT= # defaults to monorepo root (pnpm-workspace.yaml walk-up) # WORKER_WORKTREE_BASE=/tmp/echodo-worker-worktrees +# +# Phase B — OpenHands agent-server (optional; requires --profile agent-server) +# WORKER_SESSION_TYPE=minimal # set to `openhands` when agent-server is running +# AGENT_SERVER_URL=http://agent-server:8001 +# WORKER_OPENHANDS_WORKSPACE=/workspace/repo +# SESSION_API_KEY= # must match LOCAL_BACKEND_API_KEY on agent-server +# LOCAL_BACKEND_API_KEY= # generate: openssl rand -hex 32 +# OMNIROUTE_BASE_URL=https://omniroute.stillwell.cloud/v1 +# OMNIROUTE_API_KEY= +# OMNIROUTE_MODEL=auto/coding # ----- Auth (NextAuth v5) ----- # Local: http://localhost:3000 • Prod: https://echodo.stillwell.cloud @@ -72,10 +80,11 @@ AUTH_AUTHENTIK_ISSUER="https://auth.stillwell.cloud/application/o/echodo/" # ----- Realtime collab server (Hocuspocus / Yjs) ----- # Server-side reference (used by Next.js server actions, if any) COLLAB_SERVER_URL="ws://localhost:1234" -# Browser-facing — must be wss:// in production because the page is https +# Browser-facing — must be wss:// in production because the page is https. +# Baked into the web image at BUILD time (docker build args) — change requires rebuild. NEXT_PUBLIC_COLLAB_SERVER_URL="ws://localhost:1234" -# ----- Umami Analytics (CT 107 — analytics.stillwell.cloud) ----- +# ----- Umami Analytics (CT 112 — analytics.stillwell.cloud) ----- # In Umami: Settings → Websites → Add website. Copy the website UUID. NEXT_PUBLIC_UMAMI_SCRIPT="https://analytics.stillwell.cloud/script.js" NEXT_PUBLIC_UMAMI_WEBSITE_ID="" @@ -84,7 +93,7 @@ NEXT_PUBLIC_UMAMI_WEBSITE_ID="" NEXT_PUBLIC_LIBREDESK_URL="https://support.stillwell.cloud" NEXT_PUBLIC_LIBREDESK_WIDGET_ID="" -# ----- Directus Feedback (CT 107 — directus.stillwell.cloud) ----- +# ----- Directus Feedback (archived — was CT 107) ----- # Static token scoped write-only on the `feedback` collection NEXT_PUBLIC_DIRECTUS_URL="https://directus.stillwell.cloud" DIRECTUS_FEEDBACK_TOKEN="" @@ -110,20 +119,40 @@ ANTHROPIC_API_KEY="" GOOGLE_GENERATIVE_AI_API_KEY="" NVIDIA_API_KEY="" +# ----- OmniRoute (CT 108 — omniroute.stillwell.cloud) ----- +# Used by worker + web (Settings → AI provider tests) + Phase B agent-server. +OMNIROUTE_BASE_URL="https://omniroute.stillwell.cloud/v1" +OMNIROUTE_API_KEY="" +OMNIROUTE_MODEL=auto/coding + +# ----- Connected repos (Settings → Connected repos) ----- +# Token values live here; Settings stores only the env var NAME (e.g. FORGEJO_TOKEN). +# Forgejo self-hosted: +# Remote URL: https://forgejo.stillwell.cloud/rstillwell/Submatter.git +# Token env var name in UI: FORGEJO_TOKEN +FORGEJO_TOKEN="" +# GitHub.com: +# Remote URL: https://github.com/org/repo.git +# Token env var name in UI: GITHUB_TOKEN +GITHUB_TOKEN="" + +# ----- Forge webhooks (PR merged → convoy transition) ----- +FORGE_WEBHOOK_SECRET="" + # ----- MCP Server ----- MCP_SERVER_PORT=3001 # ===================================================================== -# Coolify Docker Compose build (8 GB hosts) +# Production Docker Compose build (8 GB hosts) # ===================================================================== # Serialize image builds to avoid kernel OOM (exit 255) during export. -# Coolify (8 GB host): set Custom Build Command to `bash scripts/build-coolify.sh` -# so compose builds collab → mcp → orchestrator → worker → web sequentially. -# COMPOSE_PARALLEL_LIMIT is documented for reference but the script is required. +# Dokploy / standalone: set Custom Build Command to `bash scripts/build-prod.sh` +# Local verify: pnpm docker:build:prod +# Legacy alias: pnpm docker:build:coolify # ===================================================================== # Local Docker Compose only (docker/docker-compose.yml) -# Not needed for Coolify (Coolify uses CT 102's shared Postgres/Redis). +# Not needed for production (uses CT 102 shared Postgres/Redis). # ===================================================================== POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..7cc9638 --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + check: + name: Lint, type-check, test + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint + + - name: Type-check + run: pnpm type-check + + - name: Test + run: pnpm test diff --git a/.forgejo/workflows/deploy-homelab.yml b/.forgejo/workflows/deploy-homelab.yml new file mode 100644 index 0000000..7adb765 --- /dev/null +++ b/.forgejo/workflows/deploy-homelab.yml @@ -0,0 +1,40 @@ +name: Deploy homelab (CT 116) + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: deploy-homelab-main + cancel-in-progress: true + +jobs: + deploy: + name: Deploy to CT 116 + runs-on: [self-hosted, linux, axiom] + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Sync and deploy over SSH + env: + DEPLOY_HOST: ${{ secrets.HOMELAB_DEPLOY_HOST }} + DEPLOY_USER: ${{ secrets.HOMELAB_DEPLOY_USER }} + DEPLOY_PATH: ${{ secrets.HOMELAB_DEPLOY_PATH }} + DEPLOY_SSH_KEY: ${{ secrets.HOMELAB_DEPLOY_SSH_KEY }} + run: | + set -euo pipefail + if [[ -z "${DEPLOY_HOST:-}" || -z "${DEPLOY_USER:-}" || -z "${DEPLOY_SSH_KEY:-}" ]]; then + echo "Skipping deploy: set HOMELAB_DEPLOY_HOST, HOMELAB_DEPLOY_USER, HOMELAB_DEPLOY_SSH_KEY repo secrets." + exit 0 + fi + deploy_path="${DEPLOY_PATH:-/opt/submatter}" + mkdir -p ~/.ssh + printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ + "${DEPLOY_USER}@${DEPLOY_HOST}" \ + "set -euo pipefail; cd '${deploy_path}' && bash scripts/git-sync-deploy.sh && bash scripts/deploy-prod.sh build" diff --git a/.github/workflows/deploy-homelab.yml b/.github/workflows/deploy-homelab.yml new file mode 100644 index 0000000..20e7a94 --- /dev/null +++ b/.github/workflows/deploy-homelab.yml @@ -0,0 +1,41 @@ +name: Deploy homelab (CT 116) + +on: + push: + branches: [main] + workflow_dispatch: + +# CI already ran on the PR; main deploy is serialized and can take 15–30 min on axiom. +concurrency: + group: deploy-homelab-main + cancel-in-progress: true + +jobs: + deploy: + name: Deploy to CT 116 + runs-on: [self-hosted, linux, axiom] + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Sync and deploy over SSH + env: + DEPLOY_HOST: ${{ secrets.HOMELAB_DEPLOY_HOST }} + DEPLOY_USER: ${{ secrets.HOMELAB_DEPLOY_USER }} + DEPLOY_PATH: ${{ secrets.HOMELAB_DEPLOY_PATH }} + DEPLOY_SSH_KEY: ${{ secrets.HOMELAB_DEPLOY_SSH_KEY }} + run: | + set -euo pipefail + if [[ -z "${DEPLOY_HOST:-}" || -z "${DEPLOY_USER:-}" || -z "${DEPLOY_SSH_KEY:-}" ]]; then + echo "Skipping deploy: set HOMELAB_DEPLOY_HOST, HOMELAB_DEPLOY_USER, HOMELAB_DEPLOY_SSH_KEY repo secrets." + exit 0 + fi + deploy_path="${DEPLOY_PATH:-/opt/submatter}" + mkdir -p ~/.ssh + printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=accept-new \ + "${DEPLOY_USER}@${DEPLOY_HOST}" \ + "set -euo pipefail; cd '${deploy_path}' && bash scripts/git-sync-deploy.sh && bash scripts/deploy-prod.sh build" diff --git a/apps/web/app/(app)/[workspaceSlug]/settings/repos/page.tsx b/apps/web/app/(app)/[workspaceSlug]/settings/repos/page.tsx index 034398d..cdbf095 100644 --- a/apps/web/app/(app)/[workspaceSlug]/settings/repos/page.tsx +++ b/apps/web/app/(app)/[workspaceSlug]/settings/repos/page.tsx @@ -73,6 +73,7 @@ export default function ConnectedReposSettingsPage() { setParsedPreview(null); }, }); + const [connectError, setConnectError] = React.useState(null); const disconnectMut = api.connectedRepos.disconnect.useMutation({ onSuccess: () => void utils.connectedRepos.list.invalidate(), @@ -98,6 +99,7 @@ export default function ConnectedReposSettingsPage() { const openCreate = () => { setForm(EMPTY_FORM); setParsedPreview(null); + setConnectError(null); setDialogOpen(true); }; @@ -112,12 +114,18 @@ export default function ConnectedReposSettingsPage() { if (!workspaceSlug || !form.remoteUrl.trim()) { return; } - connectMut.mutate({ - workspace: workspaceSlug, - remoteUrl: form.remoteUrl.trim(), - displayName: form.displayName.trim() || undefined, - tokenEnvVar: form.tokenEnvVar.trim() || null, - }); + setConnectError(null); + connectMut.mutate( + { + workspace: workspaceSlug, + remoteUrl: form.remoteUrl.trim(), + displayName: form.displayName.trim() || undefined, + tokenEnvVar: form.tokenEnvVar.trim() || null, + }, + { + onError: (err) => setConnectError(err.message), + }, + ); }; return ( @@ -126,9 +134,27 @@ export default function ConnectedReposSettingsPage() {

Connected repos

- Paste a git remote URL to link Forgejo, GitHub, or other forges. API tokens are - referenced by env var name — never stored in the database. + Link a git remote for worker automation (draft PRs, forge webhooks). Paste the clone + URL — OAuth repo pickers are planned; today you set the API token on the server and + reference its env var name here.

+
+ Examples +
    +
  • + Forgejo (self-hosted):{" "} + https://forgejo.stillwell.cloud/rstillwell/Submatter.git + {" — "}token env var FORGEJO_TOKEN in{" "} + /opt/submatter/.env, then click Test connection. +
  • +
  • + GitHub.com:{" "} + https://github.com/org/repo.git + {" — "}token env var GITHUB_TOKEN (PAT with repo + scope). +
  • +
+
{canManage && (