From 66b085dde28323854e73bd620d50270c461739a8 Mon Sep 17 00:00:00 2001 From: Muhammad Qasim <114048264+mqasim41@users.noreply.github.com> Date: Fri, 3 Apr 2026 06:34:13 +0500 Subject: [PATCH] Feat/add developer docs for windows (#213) * docs: add windows commands for developer setup * feat: add windows scripts * fix(ui): make dev script cross-platform with cross-env * feat(scripts): enhance migration scripts for Alembic environment setup and add virtual environment activation --- AGENTS.md | 6 + docs/contribution/setup.mdx | 52 ++++++-- docs/getting-started/troubleshooting.mdx | 10 +- scripts/makemigrate.ps1 | 58 +++++++++ scripts/migrate.ps1 | 36 ++++++ scripts/setup_pipecat.ps1 | 24 ++++ scripts/start_services_dev.ps1 | 151 +++++++++++++++++++++++ scripts/stop_services.ps1 | 92 ++++++++++++++ ui/package-lock.json | 24 +++- ui/package.json | 3 +- 10 files changed, 443 insertions(+), 13 deletions(-) create mode 100644 scripts/makemigrate.ps1 create mode 100644 scripts/migrate.ps1 create mode 100644 scripts/setup_pipecat.ps1 create mode 100644 scripts/start_services_dev.ps1 create mode 100644 scripts/stop_services.ps1 diff --git a/AGENTS.md b/AGENTS.md index ac941b6..5236f89 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,12 @@ dograh/ ./scripts/stop_services.sh ``` +On Windows (PowerShell): +```powershell +.\scripts\start_services_dev.ps1 +.\scripts\stop_services.ps1 +``` + ## Environment Configuration - `api/.env` - Backend environment variables diff --git a/docs/contribution/setup.mdx b/docs/contribution/setup.mdx index 1182ddc..7054883 100644 --- a/docs/contribution/setup.mdx +++ b/docs/contribution/setup.mdx @@ -8,10 +8,14 @@ If the below steps do not work out for you, it would be great if you can open an ### System Requirements - git to clone the forked repository -- Node.js 24 to run the UI (we recommend using [NVM](https://github.com/nvm-sh/nvm) to manage your node versions locally) +- Node.js 24 to run the UI (we recommend using [NVM](https://github.com/nvm-sh/nvm) on macOS/Linux or [NVM for Windows](https://github.com/coreybutler/nvm-windows) on Windows to manage your node versions locally) - Python 3.13 to run the backend - Docker to run the database and redis cache locally + +All commands below are shown for **macOS / Linux**. Expand the **Windows** tab for the PowerShell equivalent where it differs. + + ### Steps 1. Fork the Dograh repository by going to https://github.com/dograh-hq/dograh 2. Clone the forked repository on your machine @@ -20,10 +24,16 @@ git clone https://github.com//dograh cd dograh ``` 3. Create a python virtual environment -``` + +```bash macOS/Linux python3 -m venv venv source venv/bin/activate ``` +```powershell Windows +python -m venv venv +.\venv\Scripts\Activate.ps1 +``` + 4. Install the requirements ``` pip install -r api/requirements.txt @@ -50,25 +60,51 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS a57e3e92b02c minio/minio "/usr/bin/docker-ent…" 18 seconds ago Up 18 seconds (healthy) 127.0.0.1:9000-9001->9000-9001/tcp dograh-minio-1 ``` 8. Setup environment variables -`` + +```bash macOS/Linux cp api/.env.example api/.env && cp ui/.env.example ui/.env -`` -9. Setup pipecat git submodule ``` +```powershell Windows +Copy-Item api/.env.example api/.env +Copy-Item ui/.env.example ui/.env +``` + +9. Setup pipecat git submodule + +```bash macOS/Linux bash scripts/setup_pipecat.sh ``` -10. Start backend services +```powershell Windows +.\scripts\setup_pipecat.ps1 ``` + +10. Start backend services + +```bash macOS/Linux bash scripts/start_services_dev.sh ``` +```powershell Windows +.\scripts\start_services_dev.ps1 +``` + Verify that your backend server is running + +```bash macOS/Linux +curl -X GET localhost:8000/api/v1/health ``` -curl -X GET localhost:8000/api/v1/health +```powershell Windows +curl.exe http://localhost:8000/api/v1/health ``` + You would be able to see the logs in logs/ directory. -``` + +```bash macOS/Linux tail -f logs/latest/*.log ``` +```powershell Windows +Get-Content logs/latest/*.log -Wait +``` + 11. Start the UI ``` cd ui && npm run dev diff --git a/docs/getting-started/troubleshooting.mdx b/docs/getting-started/troubleshooting.mdx index 7c0fcf1..13d064d 100644 --- a/docs/getting-started/troubleshooting.mdx +++ b/docs/getting-started/troubleshooting.mdx @@ -9,11 +9,17 @@ description: "Common issues and solutions for running Dograh AI" ### When a port is already in use: ##### Check what's using the port first and then kill the process (may require sudo on Linux) -```bash + +```bash macOS/Linux lsof -i :3010 - kill -9 $(lsof -t -i :3010) ``` +```powershell Windows +netstat -ano | findstr :3010 +# Find the PID in the last column, then: +Stop-Process -Id -Force +``` + ### When Docker containers are using the ports (with auto-restart enabled): diff --git a/scripts/makemigrate.ps1 b/scripts/makemigrate.ps1 new file mode 100644 index 0000000..35685d5 --- /dev/null +++ b/scripts/makemigrate.ps1 @@ -0,0 +1,58 @@ +#!/usr/bin/env pwsh +# Create a new Alembic migration with autogenerate (Windows) + +Param( + [string]$MigrationName, + [switch]$DryRun +) + +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BaseDir = Split-Path -Parent $ScriptDir +Set-Location $BaseDir + +# Ensure repository root is importable for Alembic env/module resolution. +if ($env:PYTHONPATH) { + $env:PYTHONPATH = "$BaseDir;$($env:PYTHONPATH)" +} else { + $env:PYTHONPATH = $BaseDir +} + +$EnvFile = Join-Path $BaseDir 'api/.env' + +# Load environment variables +if (Test-Path $EnvFile) { + Get-Content $EnvFile | ForEach-Object { + $line = $_.Trim() + if ($line -and -not $line.StartsWith('#')) { + $parts = $line -split '=', 2 + if ($parts.Count -eq 2) { + [Environment]::SetEnvironmentVariable($parts[0].Trim(), $parts[1].Trim().Trim('"'), 'Process') + } + } + } +} else { + Write-Host "Error: Environment file $EnvFile not found." -ForegroundColor Red + exit 1 +} + +# Prompt for migration name when not provided via parameter +if (-not $MigrationName) { + $MigrationName = Read-Host "Enter the migration name (minimum 5 characters)" +} + +if (-not $MigrationName -or $MigrationName.Length -lt 5) { + Write-Host "Error: Migration name must be at least 5 characters long." -ForegroundColor Red + exit 1 +} + +# Generate the Alembic revision +$cmd = "alembic -c api/alembic.ini revision --autogenerate -m `"$MigrationName`"" + +if ($DryRun) { + Write-Host "Dry run: $cmd" + exit 0 +} + +alembic -c api/alembic.ini revision --autogenerate -m $MigrationName diff --git a/scripts/migrate.ps1 b/scripts/migrate.ps1 new file mode 100644 index 0000000..9147147 --- /dev/null +++ b/scripts/migrate.ps1 @@ -0,0 +1,36 @@ +#!/usr/bin/env pwsh +# Run Alembic database migrations (Windows) + +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BaseDir = Split-Path -Parent $ScriptDir +Set-Location $BaseDir + +# Ensure repository root is importable for Alembic env/module resolution. +if ($env:PYTHONPATH) { + $env:PYTHONPATH = "$BaseDir;$($env:PYTHONPATH)" +} else { + $env:PYTHONPATH = $BaseDir +} + +$EnvFile = Join-Path $BaseDir 'api/.env' + +# Load environment variables +if (Test-Path $EnvFile) { + Get-Content $EnvFile | ForEach-Object { + $line = $_.Trim() + if ($line -and -not $line.StartsWith('#')) { + $parts = $line -split '=', 2 + if ($parts.Count -eq 2) { + [Environment]::SetEnvironmentVariable($parts[0].Trim(), $parts[1].Trim().Trim('"'), 'Process') + } + } + } +} else { + Write-Host "Error: Environment file $EnvFile not found." -ForegroundColor Red + exit 1 +} + +# Run migrations +alembic -c api/alembic.ini upgrade head diff --git a/scripts/setup_pipecat.ps1 b/scripts/setup_pipecat.ps1 new file mode 100644 index 0000000..e682413 --- /dev/null +++ b/scripts/setup_pipecat.ps1 @@ -0,0 +1,24 @@ +#!/usr/bin/env pwsh +# Setup script for using pipecat as a git submodule (Windows) + +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BaseDir = Split-Path -Parent $ScriptDir +Set-Location $BaseDir + +Write-Host "Setting up pipecat as a git submodule..." + +# Initialize and update submodules +Write-Host "Initializing git submodules..." +git submodule update --init --recursive + +# Install pipecat in editable mode with all extras +Write-Host "Installing pipecat dependencies..." +pip install -e './pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb]' + +# Install other requirements +Write-Host "Installing dograh API requirements..." +pip install -r api/requirements.txt + +Write-Host "Setup complete! Pipecat is now available as a git submodule." diff --git a/scripts/start_services_dev.ps1 b/scripts/start_services_dev.ps1 new file mode 100644 index 0000000..a9470c4 --- /dev/null +++ b/scripts/start_services_dev.ps1 @@ -0,0 +1,151 @@ +#!/usr/bin/env pwsh +# Start Dograh services in development mode (Windows) +# Usage: .\scripts\start_services_dev.ps1 [-ArqWorkers 2] [-NoMigrations] [-IncludeTelephonyWorkers] +# +# Note: Telephony workers (ari_manager, campaign_orchestrator) are disabled by +# default on Windows because they use Unix signal handlers not supported by the +# Windows asyncio event loop. + +Param( + [int]$ArqWorkers = 1, + [switch]$NoMigrations, + [switch]$IncludeTelephonyWorkers +) + +$ErrorActionPreference = 'Stop' + +############################################################################### +### CONFIGURATION +############################################################################### + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BaseDir = Split-Path -Parent $ScriptDir +Set-Location $BaseDir + +$EnvFile = Join-Path $BaseDir 'api/.env' +$RunDir = Join-Path $BaseDir 'run' +$LogsRoot = Join-Path $BaseDir 'logs' +$LatestDir = Join-Path $LogsRoot 'latest' +$VenvPath = Join-Path $BaseDir 'venv' + +Write-Host "Starting Dograh Services (DEV MODE) in BASE_DIR: $BaseDir" +Write-Host "Auto-reload enabled for api/ directory changes" + +############################################################################### +### 1) Load environment variables +############################################################################### + +if (Test-Path $EnvFile) { + Get-Content $EnvFile | ForEach-Object { + $line = $_.Trim() + if ($line -and -not $line.StartsWith('#')) { + $parts = $line -split '=', 2 + if ($parts.Count -eq 2) { + [Environment]::SetEnvironmentVariable($parts[0].Trim(), $parts[1].Trim().Trim('"'), 'Process') + } + } + } +} + +if (-not $env:UVICORN_BASE_PORT) { $env:UVICORN_BASE_PORT = '8000' } + +############################################################################### +### 2) Define services +############################################################################### + +$serviceSpecs = @() + +if ($IncludeTelephonyWorkers) { + $serviceSpecs += @{ Name = 'ari_manager'; Cmd = "python -m api.services.telephony.ari_manager" } + $serviceSpecs += @{ Name = 'campaign_orchestrator'; Cmd = "python -m api.services.campaign.campaign_orchestrator" } +} + +$serviceSpecs += @{ Name = 'uvicorn'; Cmd = "uvicorn api.app:app --host 0.0.0.0 --port $($env:UVICORN_BASE_PORT) --reload --reload-dir api" } + +for ($i = 1; $i -le $ArqWorkers; $i++) { + $serviceSpecs += @{ Name = "arq$i"; Cmd = "python -m arq api.tasks.arq.WorkerSettings --custom-log-dict api.tasks.arq.LOG_CONFIG" } +} + +############################################################################### +### 3) Activate virtual environment +############################################################################### + +$VenvActivateScript = Join-Path $VenvPath 'Scripts/Activate.ps1' + +if (Test-Path $VenvActivateScript) { + . $VenvActivateScript + Write-Host "Virtual environment activated: $VenvPath" +} else { + Write-Host "Warning: Virtual environment not found at $VenvPath" + Write-Host "Continuing without virtual environment activation..." +} + +############################################################################### +### 4) Stop old services +############################################################################### + +New-Item -ItemType Directory -Path $RunDir -Force | Out-Null + +foreach ($spec in $serviceSpecs) { + $pidFile = Join-Path $RunDir "$($spec.Name).pid" + if (Test-Path $pidFile) { + $oldPid = (Get-Content $pidFile -Raw).Trim() + if ($oldPid) { + $prev = $ErrorActionPreference; $ErrorActionPreference = 'SilentlyContinue' + & taskkill /PID $oldPid /T /F 2>&1 | Out-Null + $ErrorActionPreference = $prev + } + Remove-Item $pidFile -Force -ErrorAction SilentlyContinue + } +} + +############################################################################### +### 5) Run migrations +############################################################################### + +if (-not $NoMigrations) { + alembic -c (Join-Path $BaseDir 'api/alembic.ini') upgrade head +} + +############################################################################### +### 6) Prepare logs +############################################################################### + +New-Item -ItemType Directory -Path $LatestDir -Force | Out-Null +Get-ChildItem $LatestDir -Filter '*.log' -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + +############################################################################### +### 7) Start services +############################################################################### + +foreach ($spec in $serviceSpecs) { + $name = $spec.Name + $logPath = Join-Path $LatestDir "$name.log" + $pidFile = Join-Path $RunDir "$name.pid" + + Write-Host "-> Starting $name" + + $wrapped = "cd /d `"$BaseDir`" && $($spec.Cmd) >> `"$logPath`" 2>&1" + $proc = Start-Process cmd.exe -ArgumentList '/c', $wrapped -PassThru -WindowStyle Hidden + + Set-Content -Path $pidFile -Value $proc.Id + Write-Host " PID $($proc.Id) -> $logPath" +} + +############################################################################### +### 8) Summary +############################################################################### + +Write-Host "" +Write-Host "------------------------------------------------------" +Write-Host "Mode: DEVELOPMENT (auto-reload enabled)" +Write-Host "" +foreach ($spec in $serviceSpecs) { + $procId = (Get-Content (Join-Path $RunDir "$($spec.Name).pid") -Raw).Trim() + Write-Host " $($spec.Name) (PID $procId) -> logs/latest/$($spec.Name).log" +} +Write-Host "" +Write-Host "Health: curl.exe http://localhost:$($env:UVICORN_BASE_PORT)/api/v1/health" +Write-Host "Logs: Get-Content logs/latest/*.log -Wait" +Write-Host "Stop: .\scripts\stop_services.ps1" +Write-Host "------------------------------------------------------" diff --git a/scripts/stop_services.ps1 b/scripts/stop_services.ps1 new file mode 100644 index 0000000..d5006a4 --- /dev/null +++ b/scripts/stop_services.ps1 @@ -0,0 +1,92 @@ +#!/usr/bin/env pwsh +# Stop Dograh services started by start_services_dev.ps1 (Windows) + +$ErrorActionPreference = 'Stop' + +############################################################################### +### CONFIGURATION +############################################################################### + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BaseDir = Split-Path -Parent $ScriptDir +$RunDir = Join-Path $BaseDir 'run' + +Set-Location $BaseDir +Write-Host "Stopping Dograh Services in BASE_DIR: $BaseDir" + +############################################################################### +### HELPER +############################################################################### + +function Stop-ProcessTree([int]$ProcessId) { + # taskkill /T kills the entire process tree. Temporarily relax error + # preference so that a "process not found" message on stderr does not + # terminate the script. + $prev = $ErrorActionPreference + try { + $ErrorActionPreference = 'SilentlyContinue' + & taskkill /PID $ProcessId /T /F 2>&1 | Out-Null + } finally { + $ErrorActionPreference = $prev + } +} + +############################################################################### +### STOP SERVICES +############################################################################### + +if (-not (Test-Path $RunDir)) { + Write-Host "No run directory found at $RunDir" + Write-Host "No services appear to be running." + exit 0 +} + +$pidFiles = Get-ChildItem $RunDir -Filter '*.pid' -ErrorAction SilentlyContinue +if (-not $pidFiles) { + Write-Host "No PID files found in $RunDir" + Write-Host "No services appear to be running." + exit 0 +} + +$stoppedCount = 0 +$failedCount = 0 + +foreach ($pidFile in $pidFiles) { + $name = $pidFile.BaseName + $oldPid = (Get-Content $pidFile.FullName -Raw).Trim() + + $proc = Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "Stopping $name (PID $oldPid)..." + Stop-ProcessTree ([int]$oldPid) + Start-Sleep -Seconds 2 + + $still = Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue + if ($still) { + Write-Host " Warning: $name did not exit cleanly" + $failedCount++ + } else { + Write-Host " Stopped $name" + $stoppedCount++ + } + } else { + # The tracked cmd.exe may have exited but child processes may still run. + # Best-effort cleanup via taskkill tree kill. + Stop-ProcessTree ([int]$oldPid) + Write-Host "Service $name (PID $oldPid) is not running" + } + + Remove-Item $pidFile.FullName -Force -ErrorAction SilentlyContinue +} + +############################################################################### +### SUMMARY +############################################################################### + +Write-Host "" +Write-Host "------------------------------------------------------" +Write-Host "Stopped $stoppedCount service(s)" +if ($failedCount -gt 0) { + Write-Host "Failed to stop $failedCount service(s)" +} +Write-Host "------------------------------------------------------" diff --git a/ui/package-lock.json b/ui/package-lock.json index 379da05..32e0c67 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "ui", - "version": "1.16.0", + "version": "1.19.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ui", - "version": "1.16.0", + "version": "1.19.2", "dependencies": { "@dagrejs/dagre": "^1.1.4", "@hey-api/client-fetch": "^0.10.0", @@ -62,6 +62,7 @@ "@types/react": "^19", "@types/react-dom": "^19", "@types/source-map-support": "^0.5.10", + "cross-env": "^7.0.3", "eslint": "^9", "eslint-config-next": "^15.3.3", "eslint-plugin-simple-import-sort": "^12.1.1", @@ -12900,6 +12901,25 @@ } } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/ui/package.json b/ui/package.json index 04e7ce8..fd4d2d7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -3,7 +3,7 @@ "version": "1.20.0", "private": true, "scripts": { - "dev": "NODE_OPTIONS='--enable-source-maps' next dev --turbopack", + "dev": "cross-env NODE_OPTIONS=--enable-source-maps next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint", @@ -60,6 +60,7 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@hey-api/openapi-ts": "^0.66.2", + "cross-env": "^7.0.3", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19",