diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..3076a7fd25 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/devcontainers/javascript-node:22 + +# pnpm is pre-installed in the devcontainer image; ensure correct version +RUN corepack enable && corepack prepare pnpm@latest --activate + +# mise for runtime management +RUN curl https://mise.run | sh && \ + echo 'eval "$(~/.local/bin/mise activate bash)"' >> /home/node/.bashrc + +# System dependencies for Synapse (uses Docker) and node-gyp +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + && rm -rf /var/lib/apt/lists/* diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..812f835598 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,46 @@ +{ + "name": "Boxel PR Review", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/boxel", + + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "dockerDashComposeVersion": "v2" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "3.11" + }, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + + "forwardPorts": [4201, 4206, 8008], + "portsAttributes": { + "4201": { "label": "Realm Server", "onAutoForward": "silent" }, + "4206": { "label": "Icons Server", "onAutoForward": "silent" }, + "8008": { "label": "Matrix/Synapse", "onAutoForward": "silent" } + }, + + "postCreateCommand": "bash .devcontainer/setup.sh", + "postStartCommand": "bash .devcontainer/start-services.sh", + + "customizations": { + "vscode": { + "extensions": [ + "cardstack.boxel-tools", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "typed-ember.glint-vscode" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + } + } + }, + + "hostRequirements": { + "cpus": 4, + "memory": "8gb", + "storage": "64gb" + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000000..cea942af2a --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,33 @@ +services: + app: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - ..:/workspaces/boxel:cached + command: sleep infinity + environment: + - PGHOST=postgres + - PGPORT=5432 + - PGUSER=postgres + - PGDATABASE=boxel + - NODE_NO_WARNINGS=1 + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:16.3 + restart: unless-stopped + environment: + POSTGRES_HOST_AUTH_METHOD: trust + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 5s + timeout: 3s + retries: 5 + +volumes: + pgdata: diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 0000000000..f01ee9b48e --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# One-time setup after the container is created. +# Runs during Codespace build (or prebuild) — keep it idempotent. +# The host app is NOT built here; it's deployed via GitHub Actions. +set -euo pipefail + +echo "==> Installing dependencies..." +pnpm install --frozen-lockfile + +echo "==> Running database migrations..." +cd packages/postgres +PGHOST="${PGHOST:-postgres}" PGPORT="${PGPORT:-5432}" pnpm migrate up +cd /workspaces/boxel + +echo "==> Setting up skills realm..." +pnpm --dir=packages/skills-realm skills:setup + +echo "==> Setup complete. Backend services will start automatically." diff --git a/.devcontainer/start-services.sh b/.devcontainer/start-services.sh new file mode 100755 index 0000000000..09166c9875 --- /dev/null +++ b/.devcontainer/start-services.sh @@ -0,0 +1,126 @@ +#!/bin/bash +# Start backend services for PR review in Codespaces. +# The host app is NOT built here — a GitHub Actions workflow builds and +# deploys it to S3 with URLs pointing back at this Codespace. +set -euo pipefail + +cd /workspaces/boxel + +CODESPACE_NAME="${CODESPACE_NAME:?CODESPACE_NAME must be set}" +GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN="${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN:-app.github.dev}" + +# Derived Codespace URLs for forwarded ports +export REALM_SERVER_URL="https://${CODESPACE_NAME}-4201.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" +export MATRIX_URL="https://${CODESPACE_NAME}-8008.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" +export ICONS_URL="https://${CODESPACE_NAME}-4206.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}" + +# Common env vars for realm-server processes +export NODE_ENV=development +export NODE_NO_WARNINGS=1 +export PGHOST="${PGHOST:-postgres}" +export PGPORT="${PGPORT:-5432}" +export PGDATABASE="${PGDATABASE:-boxel}" +export LOG_LEVELS='*=info' +export REALM_SERVER_SECRET_SEED="mum's the word" +export REALM_SECRET_SEED="shhh! it's a secret" +export REALM_SERVER_MATRIX_USERNAME=realm_server +export ENABLE_FILE_WATCHER=true + +# ── Postgres is already running via docker-compose ── + +# ── Make forwarded ports public so the S3 preview can reach them ── +echo "==> Making forwarded ports public..." +gh codespace ports visibility 4201:public 4206:public 8008:public -c "$CODESPACE_NAME" 2>/dev/null || true + +# ── Matrix/Synapse ── +echo "==> Starting Matrix/Synapse..." +(cd packages/matrix && MATRIX_URL=http://localhost:8008 pnpm assert-synapse-running) & +SYNAPSE_PID=$! + +# ── SMTP (MailHog) ── +echo "==> Starting SMTP server..." +(cd packages/matrix && pnpm assert-smtp-running) & + +# ── Icons server ── +echo "==> Starting icons server..." +pnpm --dir=packages/realm-server run start:icons & + +# ── Prerender service ── +echo "==> Starting prerender services..." +pnpm --dir=packages/realm-server run start:prerender-dev & +pnpm --dir=packages/realm-server run start:prerender-manager-dev & + +# ── Worker ── +echo "==> Starting worker..." +pnpm --dir=packages/realm-server run start:worker-development & + +# Wait for Synapse before starting the realm server +wait $SYNAPSE_PID || true + +# ── Realm server ── +echo "==> Starting realm server..." +SKIP_EXPERIMENTS=true \ +SKIP_BOXEL_HOMEPAGE=true \ +SKIP_SUBMISSION=true \ +MATRIX_URL=http://localhost:8008 \ + pnpm --dir=packages/realm-server ts-node \ + --transpileOnly main \ + --port=4201 \ + --matrixURL=http://localhost:8008 \ + --realmsRootPath=./realms/codespaces \ + --prerendererUrl=http://localhost:4221 \ + --migrateDB \ + --workerManagerPort=4213 \ + \ + --path='../base' \ + --username='base_realm' \ + --fromUrl='https://cardstack.com/base/' \ + --toUrl="${REALM_SERVER_URL}/base/" \ + \ + --path='../catalog-realm' \ + --username='catalog_realm' \ + --fromUrl='@cardstack/catalog/' \ + --toUrl="${REALM_SERVER_URL}/catalog/" \ + \ + --path='../skills-realm/contents' \ + --username='skills_realm' \ + --fromUrl="${REALM_SERVER_URL}/skills/" \ + --toUrl="${REALM_SERVER_URL}/skills/" \ + \ + --path='../catalog-new/contents' \ + --username='catalog_new_realm' \ + --fromUrl="${REALM_SERVER_URL}/catalog-new/" \ + --toUrl="${REALM_SERVER_URL}/catalog-new/" & +REALM_PID=$! + +# ── Wait for realm server readiness ── +echo "==> Waiting for realm server to be ready..." +timeout 300 bash -c \ + 'until curl -sf "http://localhost:4201/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson" >/dev/null 2>&1; do sleep 2; done' \ + || echo "Warning: realm server readiness check timed out after 5 minutes" + +# ── Trigger host preview build via GitHub Actions ── +echo "==> Triggering host preview build pointed at this Codespace..." +BRANCH_NAME="$(git rev-parse --abbrev-ref HEAD)" +gh workflow run codespaces-preview.yml \ + --ref "$BRANCH_NAME" \ + -f codespace_name="$CODESPACE_NAME" \ + -f realm_server_url="$REALM_SERVER_URL" \ + -f matrix_url="$MATRIX_URL" \ + -f icons_url="$ICONS_URL" \ + || echo "Warning: could not trigger preview build. Run manually with: gh workflow run codespaces-preview.yml" + +echo "" +echo "============================================" +echo " Backend services running!" +echo "" +echo " Realm server: ${REALM_SERVER_URL}" +echo " Matrix: ${MATRIX_URL}" +echo " Icons: ${ICONS_URL}" +echo "" +echo " Host preview build triggered — check the" +echo " PR for a preview link once it completes." +echo "============================================" + +# Keep the script alive +wait diff --git a/.github/workflows/codespaces-preview.yml b/.github/workflows/codespaces-preview.yml new file mode 100644 index 0000000000..e8d9deee1c --- /dev/null +++ b/.github/workflows/codespaces-preview.yml @@ -0,0 +1,106 @@ +name: Codespaces Preview + +on: + workflow_dispatch: + inputs: + codespace_name: + description: "Codespace name (used to derive forwarded port URLs)" + required: true + type: string + realm_server_url: + description: "Codespace realm server URL (e.g. https://-4201.app.github.dev)" + required: true + type: string + matrix_url: + description: "Codespace Matrix URL (e.g. https://-8008.app.github.dev)" + required: true + type: string + icons_url: + description: "Codespace icons URL (e.g. https://-4206.app.github.dev)" + required: true + type: string + +permissions: + contents: read + issues: write + checks: write + pull-requests: write + id-token: write + statuses: write + +jobs: + deploy-codespaces-preview: + name: Build and deploy host preview for Codespaces + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + + - uses: ./.github/actions/init + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # 4.1.0 + with: + role-to-assume: arn:aws:iam::680542703984:role/boxel-host + aws-region: us-east-1 + + - name: Set environment variables for Codespaces backend + shell: bash + run: | + # Point the host build at the Codespace's backend services + echo "REALM_SERVER_DOMAIN=${{ inputs.realm_server_url }}/" >> $GITHUB_ENV + echo "RESOLVED_BASE_REALM_URL=${{ inputs.realm_server_url }}/base/" >> $GITHUB_ENV + echo "RESOLVED_CATALOG_REALM_URL=${{ inputs.realm_server_url }}/catalog/" >> $GITHUB_ENV + echo "RESOLVED_NEW_CATALOG_REALM_URL=${{ inputs.realm_server_url }}/catalog-new/" >> $GITHUB_ENV + echo "RESOLVED_SKILLS_REALM_URL=${{ inputs.realm_server_url }}/skills/" >> $GITHUB_ENV + echo "MATRIX_URL=${{ inputs.matrix_url }}" >> $GITHUB_ENV + echo "MATRIX_SERVER_NAME=localhost" >> $GITHUB_ENV + echo "ICONS_URL=${{ inputs.icons_url }}" >> $GITHUB_ENV + + - name: Set PR branch name for S3 prefix + shell: bash + run: | + RAW_BRANCH="${GITHUB_REF_NAME}" + echo "PR_BRANCH_NAME=$(echo "${RAW_BRANCH}" | tr _ - | tr '[:upper:]' '[:lower:]' | sed -e 's/-$//' | sed -e 's/[^a-z0-9\-]//g' | cut -c1-60)" >> $GITHUB_ENV + + - name: Build and deploy preview + shell: bash + env: + S3_PREVIEW_BUCKET_NAME: boxel-host-preview.stack.cards + AWS_S3_BUCKET: boxel-host-preview.stack.cards + AWS_REGION: us-east-1 + AWS_CLOUDFRONT_DISTRIBUTION: EU4RGLH4EOCHJ + run: pnpm deploy:boxel-host:preview-staging + + - name: Store preview URL + shell: bash + run: echo "PREVIEW_HOST=https://${PR_BRANCH_NAME}.boxel-host-preview.stack.cards/" >> $GITHUB_ENV + + - name: Post status check with preview link + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + HEAD_SHA: ${{ github.sha }} + run: | + curl \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/$REPOSITORY/statuses/$HEAD_SHA" \ + -d '{"context":"Preview boxel-host codespaces","description":"Host preview connected to Codespace backend","target_url":"'"$PREVIEW_HOST"'","state":"success"}' + + - name: Find associated PR and comment + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + PR_NUMBER=$(gh pr list --head "${GITHUB_REF_NAME}" --json number --jq '.[0].number' 2>/dev/null || true) + if [ -n "$PR_NUMBER" ]; then + gh pr comment "$PR_NUMBER" --body "### Codespaces Preview + + - [Host preview (connected to Codespace)]($PREVIEW_HOST) + - Realm server: ${{ inputs.realm_server_url }} + - Matrix: ${{ inputs.matrix_url }} + + > This preview build is connected to the Codespace \`${{ inputs.codespace_name }}\`. The preview will stop working when the Codespace is stopped." + fi diff --git a/packages/host/config/codespaces.env b/packages/host/config/codespaces.env new file mode 100644 index 0000000000..8daa202746 --- /dev/null +++ b/packages/host/config/codespaces.env @@ -0,0 +1,10 @@ +# These values are overridden at build time by the codespaces-preview workflow. +# They serve as documentation of what's needed. +REALM_SERVER_DOMAIN=http://localhost:4201/ +RESOLVED_BASE_REALM_URL=http://localhost:4201/base/ +RESOLVED_CATALOG_REALM_URL=http://localhost:4201/catalog/ +RESOLVED_NEW_CATALOG_REALM_URL=http://localhost:4201/catalog-new/ +RESOLVED_SKILLS_REALM_URL=http://localhost:4201/skills/ +MATRIX_URL=http://localhost:8008 +MATRIX_SERVER_NAME=localhost +ICONS_URL=http://localhost:4206