Compare commits

...

2 Commits

Author SHA1 Message Date
CHANGE
9a4138957c Add ngrok to example file 2026-03-06 12:34:46 -05:00
CHANGE
51ab4f3d73 Add local dev setup: docker-compose, .env.example, keycloak bootstrap
Adds local development tooling for running the enterprise stack locally:
- docker-compose.yml: Postgres/Redis/Keycloak stack
- .env.example: Local dev env template
- keycloak-config.sh: Keycloak realm bootstrap script
- ngrok.yml: Tunnel config (domains are placeholders — replace with your own)
- convert_to_env.py: Includes DC service class env var
2026-03-04 20:23:48 -05:00
5 changed files with 265 additions and 0 deletions

85
enterprise/.env.example Normal file
View File

@@ -0,0 +1,85 @@
# Copy this file to .env and fill in the blanks.
# Variables with defaults can be left as-is for local development.
# =============================================================================
# Keycloak (Docker Compose)
# =============================================================================
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_REALM_NAME=allhands
KEYCLOAK_PROVIDER_NAME=github
KEYCLOAK_CLIENT_ID=
KEYCLOAK_CLIENT_SECRET=
KEYCLOAK_SMTP_PASSWORD=
# URL used by the backend to reach Keycloak.
# Use http://localhost:8080 when running the backend outside Docker Compose.
KEYCLOAK_SERVER_URL=<auth_ngrok_url>
KEYCLOAK_SERVER_URL_EXT=<auth_ngrok_url>
# Host values substituted into the Keycloak realm template.
WEB_HOST=<app_ngrok_url>
AUTH_WEB_HOST=<auth_ngrok_url>
# =============================================================================
# GitHub OAuth App
# =============================================================================
GITHUB_APP_CLIENT_ID=
GITHUB_APP_CLIENT_SECRET=
GITHUB_APP_WEBHOOK_SECRET=
GITHUB_APP_PRIVATE_KEY=
GITHUB_APP_ID=
# =============================================================================
# GitLab OAuth App
# =============================================================================
GITLAB_APP_CLIENT_ID=
GITLAB_APP_CLIENT_SECRET=
# =============================================================================
# Bitbucket OAuth App
# =============================================================================
BITBUCKET_APP_CLIENT_ID=
BITBUCKET_APP_CLIENT_SECRET=
# =============================================================================
# Bitbucket Data Center OAuth App
# =============================================================================
BITBUCKET_DATA_CENTER_HOST=
BITBUCKET_DATA_CENTER_CLIENT_ID=
BITBUCKET_DATA_CENTER_CLIENT_SECRET=
# =============================================================================
# Database (Docker Compose uses these defaults)
# =============================================================================
DB_HOST=localhost
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=openhands
# =============================================================================
# Redis (Docker Compose starts redis on the default port)
# =============================================================================
REDIS_HOST=localhost
REDIS_PORT=6379
# =============================================================================
# LLM Proxy
# =============================================================================
LITE_LLM_API_URL=https://llm-proxy.eval.all-hands.dev
LITE_LLM_API_KEY=
LITELLM_DEFAULT_MODEL=litellm_proxy/claude-opus-4-5-20251101
# =============================================================================
# Backend app config
# =============================================================================
OPENHANDS_CONFIG_CLS=server.config.SaaSServerConfig
OPENHANDS_GITHUB_SERVICE_CLS=integrations.github.github_service.SaaSGitHubService
OPENHANDS_GITLAB_SERVICE_CLS=integrations.gitlab.gitlab_service.SaaSGitLabService
OPENHANDS_BITBUCKET_SERVICE_CLS=integrations.bitbucket.bitbucket_service.SaaSBitBucketService
OPENHANDS_BITBUCKET_DATA_CENTER_SERVICE_CLS=integrations.bitbucket_data_center.bitbucket_dc_service.SaaSBitbucketDCService
OPENHANDS_CONVERSATION_VALIDATOR_CLS=storage.saas_conversation_validator.SaasConversationValidator
SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/openhands/runtime:main-nikolaik
POSTHOG_CLIENT_KEY=test
ENABLE_PROACTIVE_CONVERSATION_STARTERS=true
MAX_CONCURRENT_CONVERSATIONS=10
LOCAL_DEPLOYMENT=true

View File

@@ -0,0 +1,77 @@
services:
postgres:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: openhands
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10
keycloak:
image: quay.io/keycloak/keycloak:26.3.0
ports:
- "8180:8080"
- "9000:9000"
environment:
KEYCLOAK_ADMIN: tmpadmin
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
KC_HEALTH_ENABLED: "true"
KC_HOSTNAME: ${AUTH_WEB_HOST:-localhost}
KC_PROXY_HEADERS: xforwarded
command: start-dev
keycloak-config:
image: alpine:3.19
entrypoint: ["/bin/sh", "-c"]
command:
- |
apk add --no-cache curl jq gettext && sh /app/keycloak-config.sh
volumes:
- ./keycloak-config.sh:/app/keycloak-config.sh:ro
- ./allhands-realm-github-provider.json.tmpl:/app/allhands-realm-github-provider.json.tmpl:ro
environment:
KEYCLOAK_SERVER_URL: http://keycloak:8080
KEYCLOAK_MANAGEMENT_URL: http://keycloak:9000
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin}
KEYCLOAK_REALM_NAME: ${KEYCLOAK_REALM_NAME:-}
KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID:-}
KEYCLOAK_CLIENT_SECRET: ${KEYCLOAK_CLIENT_SECRET:-}
KC_FEATURES: token-exchange,admin-fine-grained-authz
WEB_HOST: ${WEB_HOST:-}
AUTH_WEB_HOST: ${AUTH_WEB_HOST:-}
GITHUB_APP_CLIENT_ID: ${GITHUB_APP_CLIENT_ID:-}
GITHUB_APP_CLIENT_SECRET: ${GITHUB_APP_CLIENT_SECRET:-}
GITLAB_APP_CLIENT_ID: ${GITLAB_APP_CLIENT_ID:-}
GITLAB_APP_CLIENT_SECRET: ${GITLAB_APP_CLIENT_SECRET:-}
BITBUCKET_APP_CLIENT_ID: ${BITBUCKET_APP_CLIENT_ID:-}
BITBUCKET_APP_CLIENT_SECRET: ${BITBUCKET_APP_CLIENT_SECRET:-}
BITBUCKET_DATA_CENTER_HOST: ${BITBUCKET_DATA_CENTER_HOST:-localhost}
BITBUCKET_DATA_CENTER_CLIENT_ID: ${BITBUCKET_DATA_CENTER_CLIENT_ID:-}
BITBUCKET_DATA_CENTER_CLIENT_SECRET: ${BITBUCKET_DATA_CENTER_CLIENT_SECRET:-}
KEYCLOAK_SMTP_PASSWORD: ${KEYCLOAK_SMTP_PASSWORD:-}
GITHUB_PROXY: "0"
restart: "no"
volumes:
postgres_data:
redis_data:

View File

@@ -109,6 +109,9 @@ lines.append(
lines.append(
'OPENHANDS_BITBUCKET_SERVICE_CLS=integrations.bitbucket.bitbucket_service.SaaSBitBucketService'
)
lines.append(
'OPENHANDS_BITBUCKET_DATA_CENTER_SERVICE_CLS=integrations.bitbucket_data_center.bitbucket_dc_service.SaaSBitbucketDCService'
)
lines.append(
'OPENHANDS_CONVERSATION_VALIDATOR_CLS=storage.saas_conversation_validator.SaasConversationValidator'
)

90
enterprise/keycloak-config.sh Executable file
View File

@@ -0,0 +1,90 @@
#!/bin/sh
set -e
keycloak_api_call() {
COMMAND=$1
export RESPONSE=$(eval $COMMAND)
ERROR=$(echo "$RESPONSE" | jq -r 'try if type == "array" then (.[0].error // .[0].errorMessage) else (.error // .errorMessage) end')
if [ -n "$ERROR" ] && [ "null" != "$ERROR" ]; then
echo "Error from Keycloak: $RESPONSE"
exit 1
fi
}
echo "Waiting for Keycloak to be ready..."
until curl --output /dev/null --silent --fail "${KEYCLOAK_MANAGEMENT_URL:-$KEYCLOAK_SERVER_URL}/health/ready"; do
echo '.'
sleep 5
done
ACCESS_TOKEN=$(curl -s -X POST "$KEYCLOAK_SERVER_URL/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "grant_type=password" -d "username=tmpadmin" -d "password=$KEYCLOAK_ADMIN_PASSWORD" | jq -r '.access_token')
if [ "$ACCESS_TOKEN" = "null" ]; then
NEW_ACCESS_TOKEN=$(curl -s -X POST "$KEYCLOAK_SERVER_URL/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "grant_type=password" -d "username=admin" -d "password=$KEYCLOAK_ADMIN_PASSWORD" | jq -r '.access_token')
if [ "$NEW_ACCESS_TOKEN" = "null" ]; then
echo "Couldn't login using either the \"admin\" or \"tmpadmin\" accounts."
exit 1;
fi
ACCESS_TOKEN=$NEW_ACCESS_TOKEN
fi
keycloak_api_call "curl -s \"$KEYCLOAK_SERVER_URL/admin/realms/master/users?username=admin&exact=true\" -H \"Authorization: Bearer $ACCESS_TOKEN\""
if [ "[]" = "$RESPONSE" ]; then
echo "Creating new admin user..."
keycloak_api_call "curl -s -X POST \"$KEYCLOAK_SERVER_URL/admin/realms/master/users\" \
-H \"Authorization: Bearer $ACCESS_TOKEN\" \
-H \"Content-Type: application/json\" \
-d \"{
\\\"username\\\": \\\"admin\\\",
\\\"enabled\\\": true,
\\\"emailVerified\\\": true,
\\\"firstName\\\": \\\"Keycloak\\\",
\\\"lastName\\\": \\\"Admin\\\",
\\\"email\\\": \\\"admin@all-hands.dev\\\",
\\\"credentials\\\": [{
\\\"type\\\": \\\"password\\\",
\\\"value\\\": \\\"$KEYCLOAK_ADMIN_PASSWORD\\\",
\\\"temporary\\\": false
}]
}\""
keycloak_api_call "curl -s \"$KEYCLOAK_SERVER_URL/admin/realms/master/users?username=admin&exact=true\" -H \"Authorization: Bearer $ACCESS_TOKEN\""
ADMIN_ID=$(echo "$RESPONSE" | jq -r '.[0].id')
keycloak_api_call "curl -s \"$KEYCLOAK_SERVER_URL/admin/realms/master/roles/admin\" -H \"Authorization: Bearer $ACCESS_TOKEN\" -H \"Content-Type: application/json\""
ADMIN_ROLE=$(echo "$RESPONSE" | sed 's/"/\\"/g')
keycloak_api_call "curl -s \"$KEYCLOAK_SERVER_URL/admin/realms/master/roles/create-realm\" -H \"Authorization: Bearer $ACCESS_TOKEN\" -H \"Content-Type: application/json\""
CREATE_ROLE=$(echo "$RESPONSE" | sed 's/"/\\"/g')
keycloak_api_call "curl -s -X POST \"$KEYCLOAK_SERVER_URL/admin/realms/master/users/$ADMIN_ID/role-mappings/realm\" \
-H \"Authorization: Bearer $ACCESS_TOKEN\" \
-H \"Content-Type: application/json\" \
-d \"[$ADMIN_ROLE]\""
keycloak_api_call "curl -s -X POST \"$KEYCLOAK_SERVER_URL/admin/realms/master/users/$ADMIN_ID/role-mappings/realm\" \
-H \"Authorization: Bearer $ACCESS_TOKEN\" \
-H \"Content-Type: application/json\" \
-d \"[$CREATE_ROLE]\""
keycloak_api_call "curl -s -X POST \"$KEYCLOAK_SERVER_URL/realms/master/protocol/openid-connect/token\" -H \"Content-Type: application/x-www-form-urlencoded\" -d \"client_id=admin-cli\" -d \"grant_type=password\" -d \"username=admin\" -d \"password=$KEYCLOAK_ADMIN_PASSWORD\""
ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r '.access_token')
keycloak_api_call "curl -s \"$KEYCLOAK_SERVER_URL/admin/realms/master/users?username=tmpadmin&exact=true\" -H \"Authorization: Bearer $ACCESS_TOKEN\""
TMPADMIN_ID=$(echo "$RESPONSE" | jq -r '.[0].id')
keycloak_api_call "curl -s -X DELETE \"$KEYCLOAK_SERVER_URL/admin/realms/master/users/$TMPADMIN_ID\" -H \"Authorization: Bearer $ACCESS_TOKEN\""
echo "Created new user \"admin\". The password is in KEYCLOAK_ADMIN_PASSWORD."
else
echo "User already exists: $RESPONSE"
fi
ERROR_MESSAGE=$(curl -s "$KEYCLOAK_SERVER_URL/admin/realms/$KEYCLOAK_REALM_NAME" -H "Authorization: Bearer $ACCESS_TOKEN" | jq -r '.error')
export GITHUB_BASE_URL=""
if [ "$GITHUB_PROXY" = "1" ]; then
export GITHUB_BASE_URL="https://${INGRESS_HOST:-localhost:3000}/github-proxy/test-auth-feat"
fi
if [ "$ERROR_MESSAGE" = "Realm not found." ]; then
echo "Creating allhands realm..."
envsubst '$WEB_HOST,$AUTH_WEB_HOST,$KEYCLOAK_REALM_NAME,$KEYCLOAK_PROVIDER_NAME,$KEYCLOAK_CLIENT_ID,$KEYCLOAK_CLIENT_SECRET,$GITHUB_APP_CLIENT_ID,$GITHUB_APP_CLIENT_SECRET,$GITLAB_APP_CLIENT_ID,$GITLAB_APP_CLIENT_SECRET,$BITBUCKET_APP_CLIENT_ID,$BITBUCKET_APP_CLIENT_SECRET,$GITHUB_BASE_URL,$KEYCLOAK_SMTP_PASSWORD,$BITBUCKET_DATA_CENTER_HOST,$BITBUCKET_DATA_CENTER_CLIENT_ID,$BITBUCKET_DATA_CENTER_CLIENT_SECRET' < /app/allhands-realm-github-provider.json.tmpl > /tmp/allhands-realm-github-provider.json
keycloak_api_call "jq -c '.' /tmp/allhands-realm-github-provider.json | curl -s -X POST \"$KEYCLOAK_SERVER_URL/admin/realms\" -H \"Authorization: Bearer $ACCESS_TOKEN\" -H \"Content-Type: application/json\" -d @-"
echo "Created allhands realm."
fi

10
enterprise/ngrok.yml Normal file
View File

@@ -0,0 +1,10 @@
version: "3"
tunnels:
openhands:
proto: http
addr: 3030
domain: your-name.ngrok.io
keycloak:
proto: http
addr: 8180
domain: auth.your-name.ngrok.io