diff --git a/.gitignore b/.gitignore
index 1e4df31..c8acf44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,7 @@ __pycache__
infrastructure/
nginx/
prd/
-.vercel
\ No newline at end of file
+.vercel
+
+venv/
+.venv/
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..6ac2314
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v24.11.1
\ No newline at end of file
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..7acdc73
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.13.7
\ No newline at end of file
diff --git a/api/.env.example b/api/.env.example
index c9451a4..39d9040 100644
--- a/api/.env.example
+++ b/api/.env.example
@@ -5,14 +5,14 @@ LOG_LEVEL="DEBUG"
# Change these values if you deploy the backend and frontend
# on any hosting provider with some DNS. Please ensure to
# provide the URL with scheme like http or https
-# BACKEND_API_ENDPOINT: "http://localhost:8000"
-# UI_APP_URL: "http://localhost:3010"
+BACKEND_API_ENDPOINT="http://localhost:8000"
+UI_APP_URL="http://localhost:3000"
# Database Configuration
DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/postgres"
REDIS_URL="redis://:redissecret@localhost:6379"
-# AWS S3 Configuration (required for SaaS mode)
+# AWS S3 Configuration
ENABLE_AWS_S3="false"
# AWS_ACCESS_KEY_ID=""
# AWS_SECRET_ACCESS_KEY=""
diff --git a/api/routes/workflow.py b/api/routes/workflow.py
index 0a05ee2..e6ebde9 100644
--- a/api/routes/workflow.py
+++ b/api/routes/workflow.py
@@ -600,7 +600,7 @@ async def get_workflow_run(
"call_duration_seconds": int(
round(run.cost_info.get("call_duration_seconds"))
)
- if run.cost_info
+ if run.cost_info and run.cost_info.get("call_duration_seconds") is not None
else None,
}
if run.cost_info
diff --git a/docker-compose-local.yaml b/docker-compose-local.yaml
index 1145451..45a83ec 100644
--- a/docker-compose-local.yaml
+++ b/docker-compose-local.yaml
@@ -38,9 +38,31 @@ services:
networks:
- app-network
+ minio:
+ image: minio/minio
+ container_name: minio
+ command: server /data --console-address ":9001"
+ environment:
+ MINIO_ROOT_USER: minioadmin
+ MINIO_ROOT_PASSWORD: minioadmin
+ MINIO_API_CORS_ALLOW_ORIGIN: "*"
+ ports:
+ - "127.0.0.1:9000:9000" # Bind to localhost explicitly
+ - "127.0.0.1:9001:9001"
+ volumes:
+ - minio_data:/data
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ networks:
+ - app-network
+
volumes:
postgres_data:
redis_data:
+ minio_data:
networks:
app-network:
diff --git a/docs/contribution/introduction.mdx b/docs/contribution/introduction.mdx
new file mode 100644
index 0000000..0c8b45b
--- /dev/null
+++ b/docs/contribution/introduction.mdx
@@ -0,0 +1,4 @@
+---
+title: Contribution
+description: If you would like to set up the development environment and use a coding agent like Claude to make changes to the codebase, you can follow this document to help setup the right development environment for yourself.
+---
\ No newline at end of file
diff --git a/docs/contribution/setup.mdx b/docs/contribution/setup.mdx
new file mode 100644
index 0000000..c6c7ae8
--- /dev/null
+++ b/docs/contribution/setup.mdx
@@ -0,0 +1,56 @@
+---
+title: Setting Up
+description: You can use this document to setup the dev environment for yourself.
+---
+
+If the below steps do not work out for you, it would be great if you can open an issue on [Github](https://github.com/dograh-hq/dograh/issues).
+
+
+### 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)
+- Python 3.13 to run the backend
+- Docker to run the database and redis cache locally
+
+### Steps
+1. Fork the Dograh repository by going to https://github.com/dograh-hq/dograh
+2. Clone the forked repository on your machine
+```
+git clone https://github.com//dograh
+cd dograh
+```
+3. Create a python virtual environment
+```
+python3 -m venv venv
+source venv/bin/activate
+```
+4. Install the requirements
+```
+pip install -r api/requirements.txt
+```
+5. Ensure you are on right version of Node.js using `node --version`
+```
+nvm use 24
+```
+6. Install UI dependencies
+```
+cd ui && npm install
+```
+7. Start local docker services after making sure you dont have any other instance of postgres or redis running by checking `docker ps`
+```
+cd .. && docker compose -f docker-compose-local.yaml up
+```
+8. Setup environment variables
+``
+cp api/.env.example api/.env && cp ui/.env.example ui/.env
+``
+9. Setup pipecat
+```
+bash scripts/setup_pipecat.sh
+```
+10. Start backend services
+If you wish to start the services in debug mode, we ship a launch.json file which you can use in VSCode.
+```
+bash scripts/start_services.sh
+```
+11.
diff --git a/docs/docs.json b/docs/docs.json
index b9ff93a..e005bcd 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -54,6 +54,13 @@
}
]
},
+ {
+ "group": "Contribution",
+ "pages": [
+ "contribution/introduction",
+ "contribution/setup"
+ ]
+ },
{
"group": "Deployment",
"pages": [
diff --git a/scripts/start_services.sh b/scripts/start_services.sh
index 98d7239..670109f 100755
--- a/scripts/start_services.sh
+++ b/scripts/start_services.sh
@@ -1,6 +1,42 @@
#!/usr/bin/env bash
set -e # Exit on error
+###############################################################################
+### ARGUMENT PARSING
+###############################################################################
+
+DEV_MODE=false
+
+show_help() {
+ echo "Usage: $0 [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " --dev Enable development mode with auto-reload for API changes"
+ echo " --help Show this help message"
+ echo ""
+ echo "Examples:"
+ echo " $0 # Start in production mode"
+ echo " $0 --dev # Start in development mode with auto-reload"
+}
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --dev)
+ DEV_MODE=true
+ shift
+ ;;
+ --help|-h)
+ show_help
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1"
+ show_help
+ exit 1
+ ;;
+ esac
+done
+
###############################################################################
### CONFIGURATION
###############################################################################
@@ -21,7 +57,12 @@ ARQ_WORKERS=${ARQ_WORKERS:-1}
# Log startup
cd "$BASE_DIR"
-echo "Starting Dograh Services at $(date) in BASE_DIR: ${BASE_DIR}"
+if $DEV_MODE; then
+ echo "Starting Dograh Services (DEV MODE) at $(date) in BASE_DIR: ${BASE_DIR}"
+ echo "Auto-reload enabled for api/ directory changes"
+else
+ echo "Starting Dograh Services at $(date) in BASE_DIR: ${BASE_DIR}"
+fi
###############################################################################
### 1) Load environment variables
@@ -47,10 +88,19 @@ SERVICE_NAMES=(
"uvicorn"
)
+# Build uvicorn command based on mode
+if $DEV_MODE; then
+ # Dev mode: single worker with auto-reload (--reload is incompatible with --workers > 1)
+ UVICORN_CMD="uvicorn api.app:app --host 0.0.0.0 --port $FASTAPI_PORT --reload --reload-dir api"
+else
+ # Production mode: multiple workers, no reload
+ UVICORN_CMD="uvicorn api.app:app --host 0.0.0.0 --port $FASTAPI_PORT --workers $FASTAPI_WORKERS"
+fi
+
SERVICE_COMMANDS=(
"python -m api.services.telephony.ari_manager"
"python -m api.services.campaign.campaign_orchestrator"
- "uvicorn api.app:app --host 0.0.0.0 --port $FASTAPI_PORT --workers $FASTAPI_WORKERS"
+ "$UVICORN_CMD"
)
# Add ARQ workers dynamically
@@ -208,14 +258,21 @@ done
echo
echo "──────────────────────────────────────────────────"
+if $DEV_MODE; then
+ echo "Mode: DEVELOPMENT (auto-reload enabled)"
+else
+ echo "Mode: PRODUCTION"
+fi
+echo ""
for name in "${SERVICE_NAMES[@]}"; do
pid=$(<"$RUN_DIR/$name.pid")
echo "✓ $name (PID $pid) → $LOG_DIR/$name.log"
done
+echo ""
echo " Rotation: ${LOG_ROTATION_SIZE:-100 MB}"
echo " Retention: ${LOG_RETENTION:-7 days}"
echo " Compression: ${LOG_COMPRESSION:-gz}"
echo "Logs: tail -f $LOG_DIR/*.log"
echo "Rotated logs: ls $LOG_DIR/*.log.*"
-echo "To stop: run this script again or kill -TERM - for process groups"
+echo "To stop: ./scripts/stop_services.sh"
echo "──────────────────────────────────────────────────"
diff --git a/scripts/stop_services.sh b/scripts/stop_services.sh
new file mode 100755
index 0000000..887e1aa
--- /dev/null
+++ b/scripts/stop_services.sh
@@ -0,0 +1,146 @@
+#!/usr/bin/env bash
+set -e # Exit on error
+
+###############################################################################
+### CONFIGURATION
+###############################################################################
+
+# Determine BASE_DIR as parent of the scripts directory
+BASE_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)"
+
+RUN_DIR="$BASE_DIR/run" # Where we keep *.pid
+
+cd "$BASE_DIR"
+echo "Stopping Dograh Services at $(date) in BASE_DIR: ${BASE_DIR}"
+
+###############################################################################
+### HELPER FUNCTIONS
+###############################################################################
+
+# Function to get all descendant PIDs of a process (children, grandchildren, etc.)
+get_descendants() {
+ local parent_pid=$1
+ local descendants=""
+ local children
+
+ # Get direct children
+ children=$(pgrep -P "$parent_pid" 2>/dev/null || true)
+
+ for child in $children; do
+ # Recursively get descendants of each child
+ descendants="$descendants $child $(get_descendants "$child")"
+ done
+
+ echo "$descendants"
+}
+
+# Function to kill a process and all its descendants
+kill_process_tree() {
+ local pid=$1
+ local signal=$2
+ local descendants
+
+ descendants=$(get_descendants "$pid")
+
+ # Kill children first (bottom-up), then parent
+ for desc_pid in $descendants; do
+ if kill -0 "$desc_pid" 2>/dev/null; then
+ kill "$signal" "$desc_pid" 2>/dev/null || true
+ fi
+ done
+
+ # Kill the parent
+ if kill -0 "$pid" 2>/dev/null; then
+ kill "$signal" "$pid" 2>/dev/null || true
+ fi
+}
+
+###############################################################################
+### STOP SERVICES
+###############################################################################
+
+if [[ ! -d "$RUN_DIR" ]]; then
+ echo "No run directory found at $RUN_DIR"
+ echo "No services appear to be running."
+ exit 0
+fi
+
+# Find all PID files in the run directory
+pid_files=("$RUN_DIR"/*.pid)
+
+# Check if any PID files exist
+if [[ ! -e "${pid_files[0]}" ]]; then
+ echo "No PID files found in $RUN_DIR"
+ echo "No services appear to be running."
+ exit 0
+fi
+
+stopped_count=0
+failed_count=0
+
+for pidfile in "${pid_files[@]}"; do
+ # Extract service name from pidfile path
+ name=$(basename "$pidfile" .pid)
+
+ if [[ -f "$pidfile" ]]; then
+ oldpid=$(<"$pidfile")
+
+ if kill -0 "$oldpid" 2>/dev/null; then
+ echo "Stopping $name (PID $oldpid and all descendants)..."
+
+ # Kill the entire process tree (parent + all descendants)
+ kill_process_tree "$oldpid" "-TERM"
+ sleep 4
+
+ # Check if parent or any descendants are still alive
+ still_alive=false
+ if kill -0 "$oldpid" 2>/dev/null; then
+ still_alive=true
+ else
+ for desc_pid in $(get_descendants "$oldpid"); do
+ if kill -0 "$desc_pid" 2>/dev/null; then
+ still_alive=true
+ break
+ fi
+ done
+ fi
+
+ if $still_alive; then
+ echo " Warning: $name did not exit cleanly, forcing stop..."
+ kill_process_tree "$oldpid" "-KILL"
+ sleep 1
+
+ # Final check
+ if kill -0 "$oldpid" 2>/dev/null; then
+ echo " Error: Failed to stop $name (PID $oldpid)"
+ ((failed_count++))
+ else
+ echo " Stopped $name (forced)"
+ ((stopped_count++))
+ fi
+ else
+ echo " Stopped $name"
+ ((stopped_count++))
+ fi
+ else
+ echo "Service $name (PID $oldpid) is not running"
+ fi
+
+ rm -f "$pidfile"
+ fi
+done
+
+# Clean up any port tracking files for uvicorn
+rm -f "$RUN_DIR/uvicorn.port" "$RUN_DIR/uvicorn_new.port" "$RUN_DIR/uvicorn_old.pid"
+
+###############################################################################
+### SUMMARY
+###############################################################################
+
+echo
+echo "──────────────────────────────────────────────────"
+echo "Stopped $stopped_count service(s)"
+if [[ $failed_count -gt 0 ]]; then
+ echo "Failed to stop $failed_count service(s)"
+fi
+echo "──────────────────────────────────────────────────"
diff --git a/ui/.env.example b/ui/.env.example
new file mode 100644
index 0000000..284108b
--- /dev/null
+++ b/ui/.env.example
@@ -0,0 +1,6 @@
+NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
+BACKEND_URL=http://localhost:8000
+
+NEXT_PUBLIC_NODE_ENV=development
+NEXT_PUBLIC_DEPLOYMENT_MODE: "oss"
+NEXT_PUBLIC_AUTH_PROVIDER="local"