diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 8f22a0ca0..55662afb8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -38,4 +38,5 @@ WORKDIR /workspace # Expose the ports we're interested in EXPOSE 3000 -EXPOSE 3001 \ No newline at end of file +EXPOSE 3001 +EXPOSE 3002 \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ccc2b355d..5a959dbd3 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -17,6 +17,8 @@ services: depends_on: db: condition: service_healthy + realtime: + condition: service_healthy migrations: condition: service_completed_successfully ports: @@ -30,6 +32,29 @@ services: retries: 3 start_period: 10s + realtime: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + command: sleep infinity + environment: + - NODE_ENV=development + - DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio + - BETTER_AUTH_URL=http://localhost:3000 + - NEXT_PUBLIC_APP_URL=http://localhost:3000 + depends_on: + db: + condition: service_healthy + ports: + - "3002:3002" + working_dir: /workspace + healthcheck: + test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002'] + interval: 90s + timeout: 5s + retries: 3 + start_period: 10s + migrations: build: context: .. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bed929a7..76840a4c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,8 @@ jobs: image: ghcr.io/simstudioai/simstudio - dockerfile: ./docker/db.Dockerfile image: ghcr.io/simstudioai/migrations + - dockerfile: ./docker/realtime.Dockerfile + image: ghcr.io/simstudioai/realtime permissions: contents: read packages: write diff --git a/README.md b/README.md index f9b1ad131..b889c598f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ docker compose -f docker-compose.prod.yml up -d 1. Open VS Code with the [Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) 2. Open the project and click "Reopen in Container" when prompted -3. Run `bun run dev` in the terminal or use the `sim-start` alias +3. Run `bun run dev:full` in the terminal or use the `sim-start` alias ### Option 4: Manual Setup @@ -111,12 +111,26 @@ cp .env.example .env # Configure with required variables (DATABASE_URL, BETTER_ bunx drizzle-kit push ``` -4. Start the development server: +4. Start the development servers: + +Next.js app: ```bash bun run dev ``` +Start the realtime server: + +```bash +bun run dev:sockets +``` + +Run both together (recommended): + +```bash +bun run dev:full +``` + ## Tech Stack - **Framework**: [Next.js](https://nextjs.org/) (App Router) @@ -128,6 +142,7 @@ bun run dev - **Flow Editor**: [ReactFlow](https://reactflow.dev/) - **Docs**: [Fumadocs](https://fumadocs.vercel.app/) - **Monorepo**: [Turborepo](https://turborepo.org/) +- **Realtime**: [Socket.io](https://socket.io/) ## Contributing diff --git a/apps/sim/package.json b/apps/sim/package.json index 3e9be45f5..934a79e78 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -12,7 +12,7 @@ "dev": "next dev --turbo --port 3000", "dev:classic": "next dev", "dev:sockets": "bun run socket-server/index.ts", - "dev:full": "concurrently \"bun run dev\" \"bun run dev:sockets\"", + "dev:full": "concurrently -n \"NextJS,Realtime\" -c \"cyan,magenta\" \"bun run dev\" \"bun run dev:sockets\"", "build": "next build", "start": "next start", "prepare": "cd ../.. && bun husky", diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 80b9d2106..b898ac871 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -22,11 +22,14 @@ services: - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-placeholder} - RESEND_API_KEY=${RESEND_API_KEY:-placeholder} - OLLAMA_URL=${OLLAMA_URL:-http://localhost:11434} + - SOCKET_SERVER_URL=${SOCKET_SERVER_URL:-http://localhost:3002} depends_on: db: condition: service_healthy migrations: condition: service_completed_successfully + realtime: + condition: service_healthy healthcheck: test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000'] interval: 90s @@ -34,6 +37,32 @@ services: retries: 3 start_period: 10s + realtime: + build: + context: . + dockerfile: docker/realtime.Dockerfile + environment: + - DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio} + - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000} + - BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000} + - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here} + depends_on: + db: + condition: service_healthy + restart: unless-stopped + ports: + - '3002:3002' + deploy: + resources: + limits: + memory: 8G + healthcheck: + test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002'] + interval: 90s + timeout: 5s + retries: 3 + start_period: 10s + migrations: build: context: . diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 1596b84d1..c9088ea37 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -21,11 +21,14 @@ services: - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-placeholder} - RESEND_API_KEY=${RESEND_API_KEY:-placeholder} - OLLAMA_URL=${OLLAMA_URL:-http://localhost:11434} + - SOCKET_SERVER_URL=${SOCKET_SERVER_URL:-http://localhost:3002} depends_on: db: condition: service_healthy migrations: condition: service_completed_successfully + realtime: + condition: service_healthy healthcheck: test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000'] interval: 90s @@ -33,6 +36,30 @@ services: retries: 3 start_period: 10s + realtime: + image: ghcr.io/simstudioai/realtime:latest + restart: unless-stopped + ports: + - '3002:3002' + deploy: + resources: + limits: + memory: 4G + environment: + - DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio} + - NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000} + - BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:3000} + - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here} + depends_on: + db: + condition: service_healthy + healthcheck: + test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002'] + interval: 90s + timeout: 5s + retries: 3 + start_period: 10s + migrations: image: ghcr.io/simstudioai/migrations:latest environment: diff --git a/docker/realtime.Dockerfile b/docker/realtime.Dockerfile new file mode 100644 index 000000000..16ab789d4 --- /dev/null +++ b/docker/realtime.Dockerfile @@ -0,0 +1,51 @@ +# ======================================== +# Base Stage: Alpine Linux with Bun +# ======================================== +FROM oven/bun:alpine AS base + +# ======================================== +# Dependencies Stage: Install Dependencies +# ======================================== +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install turbo globally +RUN bun install -g turbo + +COPY package.json bun.lock ./ +RUN mkdir -p apps +COPY apps/sim/package.json ./apps/sim/package.json + +RUN bun install --omit dev --ignore-scripts + +# ======================================== +# Builder Stage: Build the Application +# ======================================== +FROM base AS builder +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# ======================================== +# Runner Stage: Run the Socket Server +# ======================================== +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +# Copy the entire sim app since socket-server has dependencies on other modules +COPY --from=builder /app/apps/sim ./apps/sim +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./package.json + +# Expose socket server port (default 3002, but configurable via PORT env var) +EXPOSE 3002 +ENV PORT=3002 \ + SOCKET_PORT=3002 \ + HOSTNAME="0.0.0.0" + +# Run the socket server directly +CMD ["bun", "apps/sim/socket-server/index.ts"] \ No newline at end of file diff --git a/package.json b/package.json index e97588f39..849306d75 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "scripts": { "build": "turbo run build", "dev": "turbo run dev", + "dev:sockets": "cd apps/sim && bun run dev:sockets", + "dev:full": "cd apps/sim && bun run dev:full", "test": "turbo run test", "format": "bunx biome format --write .", "format:check": "bunx biome format .", diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 9514aa4fc..c40b71880 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -11,6 +11,7 @@ import { Command } from 'commander' const NETWORK_NAME = 'simstudio-network' const DB_CONTAINER = 'simstudio-db' const MIGRATIONS_CONTAINER = 'simstudio-migrations' +const REALTIME_CONTAINER = 'simstudio-realtime' const APP_CONTAINER = 'simstudio-app' const DEFAULT_PORT = '3000' @@ -78,6 +79,7 @@ async function cleanupExistingContainers(): Promise { await stopAndRemoveContainer(APP_CONTAINER) await stopAndRemoveContainer(DB_CONTAINER) await stopAndRemoveContainer(MIGRATIONS_CONTAINER) + await stopAndRemoveContainer(REALTIME_CONTAINER) } async function main() { @@ -101,6 +103,7 @@ async function main() { if (options.pull) { await pullImage('ghcr.io/simstudioai/simstudio:latest') await pullImage('ghcr.io/simstudioai/migrations:latest') + await pullImage('ghcr.io/simstudioai/realtime:latest') await pullImage('pgvector/pgvector:pg17') } @@ -193,6 +196,34 @@ async function main() { process.exit(1) } + // Start the realtime server + console.log(chalk.blue('🔄 Starting Realtime Server...')) + const realtimeSuccess = await runCommand([ + 'docker', + 'run', + '-d', + '--name', + REALTIME_CONTAINER, + '--network', + NETWORK_NAME, + '-p', + '3002:3002', + '-e', + `DATABASE_URL=postgresql://postgres:postgres@${DB_CONTAINER}:5432/simstudio`, + '-e', + `BETTER_AUTH_URL=http://localhost:${port}`, + '-e', + `NEXT_PUBLIC_APP_URL=http://localhost:${port}`, + '-e', + 'BETTER_AUTH_SECRET=your_auth_secret_here', + 'ghcr.io/simstudioai/realtime:latest', + ]) + + if (!realtimeSuccess) { + console.error(chalk.red('❌ Failed to start Realtime Server')) + process.exit(1) + } + // Start the main application console.log(chalk.blue('🔄 Starting Sim Studio...')) const appSuccess = await runCommand([