Files
story/compose.yaml
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

400 lines
15 KiB
YAML

name: data-terminal-cloud
x-backend-environment: &backend-environment
# First onboarding is opt-in; existing devices retain the legacy protocol.
ONBOARDING_ENABLED: ${ONBOARDING_ENABLED:-false}
ONBOARDING_PAUSE_CLAIMS: ${ONBOARDING_PAUSE_CLAIMS:-false}
ONBOARDING_ACTIVATION_ENABLED: ${ONBOARDING_ACTIVATION_ENABLED:-false}
ONBOARDING_SMS_LOGIN_ENABLED: ${ONBOARDING_SMS_LOGIN_ENABLED:-false}
ONBOARDING_ALLOW_LOCAL_HTTP: ${ONBOARDING_ALLOW_LOCAL_HTTP:-false}
ONBOARDING_MASTER_KEY: ${ONBOARDING_MASTER_KEY:-}
ONBOARDING_ENTRY_URL: ${ONBOARDING_ENTRY_URL:-}
ONBOARDING_TRUSTED_PROXY_CIDRS: ${ONBOARDING_TRUSTED_PROXY_CIDRS:-}
ONBOARDING_SELF_SIGNUP_TENANT_IDS: ${ONBOARDING_SELF_SIGNUP_TENANT_IDS:-}
ONBOARDING_SMS_GATEWAY_URL: ${ONBOARDING_SMS_GATEWAY_URL:-}
ONBOARDING_SMS_GATEWAY_TOKEN: ${ONBOARDING_SMS_GATEWAY_TOKEN:-}
ONBOARDING_SMS_PROVIDER: ${ONBOARDING_SMS_PROVIDER:-gateway}
TENCENT_SMS_SECRET_ID: ${TENCENT_SMS_SECRET_ID:-}
TENCENT_SMS_CONFIG_FILE: ${TENCENT_SMS_CONFIG_FILE:-}
TENCENT_CAPTCHA_CONFIG_FILE: ${TENCENT_CAPTCHA_CONFIG_FILE:-}
TENCENT_SMS_SECRET_ID_FILE: ${TENCENT_SMS_SECRET_ID_FILE:-}
TENCENT_SMS_SECRET_KEY: ${TENCENT_SMS_SECRET_KEY:-}
TENCENT_SMS_SECRET_KEY_FILE: ${TENCENT_SMS_SECRET_KEY_FILE:-}
TENCENT_SMS_SDK_APP_ID: ${TENCENT_SMS_SDK_APP_ID:-}
TENCENT_SMS_SIGN_NAME: ${TENCENT_SMS_SIGN_NAME:-}
TENCENT_SMS_TEMPLATE_ID: ${TENCENT_SMS_TEMPLATE_ID:-}
VOLC_HARDWARE_INSTANCE_ID: ${VOLC_HARDWARE_INSTANCE_ID:-}
VOLC_HARDWARE_PRODUCT_KEY: ${VOLC_HARDWARE_PRODUCT_KEY:-}
VOLC_HARDWARE_PRODUCT_SECRET: ${VOLC_HARDWARE_PRODUCT_SECRET:-}
VOLC_HARDWARE_DEVICE_CREDENTIAL_MASTER_KEY: ${VOLC_HARDWARE_DEVICE_CREDENTIAL_MASTER_KEY:-}
VOLC_HARDWARE_REGISTER_URL: ${VOLC_HARDWARE_REGISTER_URL:-https://iot-cn-shanghai.iot.volces.com/2021-12-14/DynamicRegister?Action=DynamicRegister&Version=2021-12-14}
VOLC_HARDWARE_REGISTER_TIMEOUT: ${VOLC_HARDWARE_REGISTER_TIMEOUT:-10s}
APP_ENV: local
LOG_LEVEL: debug
DATABASE_URL: postgres://voice:voice_dev_only@postgres:5432/voice_platform?sslmode=disable
REDIS_ADDR: redis:6379
REDIS_PASSWORD: ""
REDIS_DB: "0"
DEPENDENCY_TIMEOUT: 2s
EXTERNAL_PROBE_INTERVAL: 30s
SHUTDOWN_TIMEOUT: 10s
AUTH_ISSUER_URL: http://localhost:58180/realms/voice-local
AUTH_JWKS_URL: http://keycloak:8080/realms/voice-local/protocol/openid-connect/certs
AUTH_AUDIENCE: voice-platform-web
OTEL_EXPORTER_OTLP_ENDPOINT: ${OTEL_EXPORTER_OTLP_ENDPOINT:-}
RATE_LIMIT_RPS: "50"
RATE_LIMIT_BURST: "100"
RATE_LIMIT_USER_RPS: "20"
RATE_LIMIT_USER_BURST: "40"
RATE_LIMIT_TENANT_RPS: "200"
RATE_LIMIT_TENANT_BURST: "400"
RATE_LIMIT_DEVICE_RPS: "10"
RATE_LIMIT_DEVICE_BURST: "20"
PROVIDER_START_RPS: "20"
PROVIDER_START_BURST: "40"
PROVIDER_MAX_ACTIVE_TASKS: "100"
MEMORY_TOOL_BASE_URL: ${MEMORY_TOOL_BASE_URL:-http://api:8080/internal/v1/tools/memory-search}
MEMORY_TOOL_SIGNING_SECRET: ${MEMORY_TOOL_SIGNING_SECRET:-local-memory-tool-signing-secret-32-bytes-minimum}
MEMORY_TOOL_TOKEN_TTL: 4h
MEMORY_TOOL_RATE_RPS: "2"
MEMORY_TOOL_RATE_BURST: "5"
VIKING_MEMORY_RESOURCE_ID: ${VIKING_MEMORY_RESOURCE_ID:-}
VIKING_MEMORY_COLLECTION_NAME: ${VIKING_MEMORY_COLLECTION_NAME:-}
VIKING_MEMORY_API_KEY: ${VIKING_MEMORY_API_KEY:-}
VIKING_MEMORY_EVENT_TYPE: ${VIKING_MEMORY_EVENT_TYPE:-event_v1}
VIKING_MEMORY_PROFILE_TYPE: ${VIKING_MEMORY_PROFILE_TYPE:-profile_v1}
PROVIDER_MEMORY_MODE: ${PROVIDER_MEMORY_MODE:-disabled}
PROVIDER_MEMORY_BASE_URL: ${PROVIDER_MEMORY_BASE_URL:-}
PROVIDER_MEMORY_API_KEY: ${PROVIDER_MEMORY_API_KEY:-}
PROVIDER_MEMORY_TIMEOUT: ${PROVIDER_MEMORY_TIMEOUT:-5s}
DEVICE_CLOCK_SKEW: 5m
DEVICE_CREDENTIAL_MASTER_KEY: ${DEVICE_CREDENTIAL_MASTER_KEY:-000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f}
MODEL_CREDENTIAL_MASTER_KEY: ${MODEL_CREDENTIAL_MASTER_KEY:-1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100}
RTC_APP_ID: local-public-rtc-app
RTC_APP_KEY: ${RTC_APP_KEY:-}
RTC_TOKEN_ISSUER: ${RTC_TOKEN_ISSUER:-local}
RTC_TOKEN_TTL: 5m
CURSOR_SIGNING_SECRET: local-signed-pagination-cursor-secret-32-bytes-minimum
SHARE_TOKEN_SIGNING_SECRET: local-share-token-signing-secret-32-bytes-minimum
CONVERSATION_ALLOCATION_TTL: 30s
HEARTBEAT_INTERVAL_SECONDS: "15"
PROVIDER_CALLBACK_CLOCK_SKEW: 5m
PROVIDER_REQUEST_TIMEOUT: 5s
VOICE_PROVIDER: ${VOICE_PROVIDER:-mock}
VOLC_RTC_BASE_URL: ${VOLC_RTC_BASE_URL:-https://rtc.volcengineapi.com}
VOLC_ACCESS_KEY_ID: ${VOLC_ACCESS_KEY_ID:-}
VOLC_SECRET_ACCESS_KEY: ${VOLC_SECRET_ACCESS_KEY:-}
VOLC_SESSION_TOKEN: ${VOLC_SESSION_TOKEN:-}
VOLC_RTC_REGION: ${VOLC_RTC_REGION:-cn-north-1}
VOLC_RTC_API_VERSION: ${VOLC_RTC_API_VERSION:-2024-12-01}
VOLC_VOICE_CHAT_TEMPLATE_FILE: ${VOLC_VOICE_CHAT_TEMPLATE_FILE:-}
VOLC_HARDWARE_AGENT_BOT_ID: ${VOLC_HARDWARE_AGENT_BOT_ID:-local-hardware-agent-bot}
VOLC_HARDWARE_AGENT_TRANSPORT: ${VOLC_HARDWARE_AGENT_TRANSPORT:-high_quality}
VOLC_HARDWARE_AGENT_MINIMUM_SDK_VERSION: ${VOLC_HARDWARE_AGENT_MINIMUM_SDK_VERSION:-1.0.0}
PROVIDER_FAILURE_THRESHOLD: "3"
PROVIDER_CIRCUIT_COOLDOWN: 1m
RECOVERY_SCAN_INTERVAL: 5s
RECOVERY_STARTING_AFTER: 20s
RECOVERY_STOPPING_AFTER: 20s
OBJECT_STORE_ENDPOINT: minio:9000
OBJECT_STORE_PUBLIC_ENDPOINT: localhost:59000
OBJECT_STORE_ACCESS_KEY: local_minio
OBJECT_STORE_SECRET_KEY: local_minio_password
OBJECT_STORE_EXPORT_BUCKET: private-exports
OBJECT_STORE_REGION: us-east-1
OBJECT_STORE_SECURE: "false"
FIRMWARE_DOWNLOAD_BASE_URL: ${FIRMWARE_DOWNLOAD_BASE_URL:-http://localhost:58180}
EXPORT_DOWNLOAD_TTL: 10m
EXPORT_OBJECT_TTL: 24h
MEDIA_LOCAL_DELETE_GRACE: ${MEDIA_LOCAL_DELETE_GRACE:-1s}
EMBEDDING_PROVIDER: ${EMBEDDING_PROVIDER:-local}
ARK_BASE_URL: ${ARK_BASE_URL:-https://ark.cn-beijing.volces.com/api/v3}
ARK_API_KEY: ${ARK_API_KEY:-}
CONTENT_GENERATOR: ${CONTENT_GENERATOR:-local}
ARK_CONTENT_MODEL: ${ARK_CONTENT_MODEL:-deepseek-v4-1-flash-260910}
ARK_MEMOIR_MODEL: ${ARK_MEMOIR_MODEL:-doubao-seed-evolving}
ARK_MEMOIR_IMAGE_MODEL: ${ARK_MEMOIR_IMAGE_MODEL:-doubao-seedream-5-0-pro-260628}
ARK_EMBEDDING_MODEL: ${ARK_EMBEDDING_MODEL:-}
ARK_REQUEST_TIMEOUT: ${ARK_REQUEST_TIMEOUT:-30s}
x-backend-build: &backend-build
context: .
dockerfile: deploy/backend.Dockerfile
args:
VERSION: ${PLATFORM_BUILD_VERSION:-dev}
COMMIT: ${PLATFORM_BUILD_COMMIT:-unknown}
BUILD_TIME: ${PLATFORM_BUILD_TIME:-unknown}
GOPROXY: ${GOPROXY:-https://proxy.golang.org,direct}
x-backend-dependencies: &backend-dependencies
postgres:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
migrate:
condition: service_completed_successfully
seed:
condition: service_completed_successfully
services:
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_DB: voice_platform
POSTGRES_USER: voice
POSTGRES_PASSWORD: voice_dev_only
ports:
- "${POSTGRES_PORT:-55432}:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U voice -d voice_platform"]
interval: 5s
timeout: 3s
retries: 20
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
ports:
- "${REDIS_PORT:-56379}:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 20
minio:
image: quay.io/minio/minio:latest
command: ["server", "/data", "--console-address", ":9001"]
environment:
MINIO_ROOT_USER: local_minio
MINIO_ROOT_PASSWORD: local_minio_password
ports:
- "${MINIO_API_PORT:-59000}:9000"
- "${MINIO_CONSOLE_PORT:-59001}:9001"
volumes:
- minio-data:/data
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:9000/minio/health/ready >/dev/null"]
interval: 5s
timeout: 3s
retries: 20
keycloak:
image: quay.io/keycloak/keycloak:26.2
command: ["start-dev", "--import-realm"]
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: local_admin_only
KC_HOSTNAME: http://localhost:58180
ports:
- "${KEYCLOAK_PORT:-58180}:8080"
volumes:
- ./deploy/keycloak:/opt/keycloak/data/import:ro
ollama:
profiles: [local-model]
image: ollama/ollama:latest
ports:
- "${OLLAMA_PORT:-11434}:11434"
volumes:
- ollama-data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
healthcheck:
test: ["CMD", "ollama", "list"]
interval: 10s
timeout: 5s
retries: 30
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otelcol-contrib/config.yaml"]
volumes:
- ./deploy/otel/collector.yaml:/etc/otelcol-contrib/config.yaml:ro
ports:
- "${OTEL_GRPC_PORT:-54317}:4317"
prometheus:
image: prom/prometheus:latest
command: ["--config.file=/etc/prometheus/prometheus.yml"]
volumes:
- ./deploy/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./deploy/prometheus/rules:/etc/prometheus/rules:ro
ports:
- "${PROMETHEUS_PORT:-59190}:9090"
grafana:
image: grafana/grafana:12.2.0
environment:
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_AUTH_ANONYMOUS_ORG_ROLE: Viewer
GF_USERS_DEFAULT_THEME: light
volumes:
- ./deploy/grafana/provisioning:/etc/grafana/provisioning:ro
- ./deploy/grafana/dashboards:/var/lib/grafana/dashboards:ro
ports:
- "${GRAFANA_PORT:-59300}:3000"
depends_on:
- prometheus
migrate:
image: migrate/migrate:v4.18.3
volumes:
- ./migrations:/migrations:ro
command:
- -path=/migrations
- -database=postgres://voice:voice_dev_only@postgres:5432/voice_platform?sslmode=disable
- up
depends_on:
postgres:
condition: service_healthy
restart: "no"
seed:
image: pgvector/pgvector:pg16
environment:
PGPASSWORD: voice_dev_only
volumes:
- ./test/fixtures:/fixtures:ro
command: ["psql", "-h", "postgres", "-U", "voice", "-d", "voice_platform", "-v", "ON_ERROR_STOP=1", "-f", "/fixtures/local_seed.sql"]
depends_on:
migrate:
condition: service_completed_successfully
restart: "no"
api:
build: *backend-build
entrypoint: ["/app/api"]
environment:
<<: *backend-environment
HTTP_ADDR: 0.0.0.0:8080
DEVICE_LOCAL_SECRET: local-simulated-device-secret-32-bytes-minimum
RTC_TOKEN_SIGNING_SECRET: local-simulated-rtc-token-secret-32-bytes-minimum
VOLC_HARDWARE_PROVISIONING_ENABLED: ${VOLC_HARDWARE_PROVISIONING_ENABLED:-false}
VOLC_HARDWARE_INSTANCE_ID: ${VOLC_HARDWARE_INSTANCE_ID:-}
VOLC_HARDWARE_PRODUCT_KEY: ${VOLC_HARDWARE_PRODUCT_KEY:-}
VOLC_HARDWARE_PRODUCT_SECRET: ${VOLC_HARDWARE_PRODUCT_SECRET:-}
VOLC_HARDWARE_DEVICE_CREDENTIAL_MASTER_KEY: ${VOLC_HARDWARE_DEVICE_CREDENTIAL_MASTER_KEY:-}
VOLC_HARDWARE_REGISTER_URL: ${VOLC_HARDWARE_REGISTER_URL:-https://iot-cn-shanghai.iot.volces.com/2021-12-14/DynamicRegister?Action=DynamicRegister&Version=2021-12-14}
VOLC_HARDWARE_REGISTER_TIMEOUT: ${VOLC_HARDWARE_REGISTER_TIMEOUT:-10s}
VOLC_HARDWARE_DEVICE_NAME_PREFIX: ${VOLC_HARDWARE_DEVICE_NAME_PREFIX:-dt-}
ports:
- "${API_PORT:-58080}:8080"
depends_on: *backend-dependencies
callbacks:
build: *backend-build
entrypoint: ["/app/callbacks"]
environment:
<<: *backend-environment
HTTP_ADDR: 0.0.0.0:8081
PROVIDER_CALLBACK_SECRET: local-provider-callback-secret-32-bytes-minimum
PLATFORM_HEALTH_CALLBACK_SECRET: local-platform-health-callback-secret-32-bytes-minimum
RATE_LIMIT_RPS: "1000"
RATE_LIMIT_BURST: "1500"
ports:
- "${CALLBACKS_PORT:-58081}:8081"
depends_on: *backend-dependencies
worker:
build: *backend-build
entrypoint: ["/app/worker"]
environment:
<<: *backend-environment
HTTP_ADDR: 0.0.0.0:8082
MOCK_PROVIDER_BASE_URL: http://mock-provider:8083
MOCK_PROVIDER_API_KEY: local-mock-provider-key
ALERT_WEBHOOK_URL: ${ALERT_WEBHOOK_URL:-http://mock-alert-receiver:8084/webhooks/alerts}
ALERT_WEBHOOK_SECRET: ${ALERT_WEBHOOK_SECRET:-local-alert-webhook-secret-32-bytes-minimum}
ALERT_WEBHOOK_TIMEOUT: ${ALERT_WEBHOOK_TIMEOUT:-5s}
CONTENT_GENERATOR: ${CONTENT_GENERATOR:-local}
CONTENT_SAFETY_PROVIDER: ${CONTENT_SAFETY_PROVIDER:-local}
CONTENT_SAFETY_ENDPOINT: ${CONTENT_SAFETY_ENDPOINT:-}
CONTENT_SAFETY_API_KEY: ${CONTENT_SAFETY_API_KEY:-}
CONTENT_SAFETY_POLICY_REF: ${CONTENT_SAFETY_POLICY_REF:-}
CONTENT_SAFETY_TIMEOUT: ${CONTENT_SAFETY_TIMEOUT:-5s}
ARK_BASE_URL: ${ARK_BASE_URL:-https://ark.cn-beijing.volces.com/api/v3}
ARK_API_KEY: ${ARK_API_KEY:-}
ARK_CONTENT_MODEL: ${ARK_CONTENT_MODEL:-deepseek-v4-1-flash-260910}
ARK_REQUEST_TIMEOUT: ${ARK_REQUEST_TIMEOUT:-30s}
ports:
- "${WORKER_PORT:-58082}:8082"
depends_on:
<<: *backend-dependencies
mock-provider:
condition: service_started
mock-alert-receiver:
condition: service_started
mock-provider:
build: *backend-build
entrypoint: ["/app/mock-provider"]
environment:
HTTP_ADDR: 0.0.0.0:8083
MOCK_PROVIDER_API_KEY: local-mock-provider-key
MOCK_PROVIDER_CALLBACK_URL: http://callbacks:8081/callbacks/v1/vertc/voice-chat
PROVIDER_CALLBACK_SECRET: local-provider-callback-secret-32-bytes-minimum
MOCK_PROVIDER_START_DELAY: 100ms
MOCK_PROVIDER_DUPLICATE_CALLBACKS: "2"
ports:
- "${MOCK_PROVIDER_PORT:-59200}:8083"
depends_on:
callbacks:
condition: service_started
mock-alert-receiver:
build: *backend-build
entrypoint: ["/app/mock-alert-receiver"]
environment:
HTTP_ADDR: 0.0.0.0:8084
ALERT_WEBHOOK_SECRET: ${ALERT_WEBHOOK_SECRET:-local-alert-webhook-secret-32-bytes-minimum}
ports:
- "${MOCK_ALERT_RECEIVER_PORT:-59210}:8084"
sim-device:
profiles: [simulation]
build: *backend-build
entrypoint: ["/app/sim-device"]
environment:
SIM_API_URL: http://api:8080
SIM_DEVICE_ID: 10000000-0000-0000-0000-000000000511
SIM_DEVICE_SECRET: local-simulated-device-secret-32-bytes-minimum
depends_on:
api:
condition: service_started
web:
build:
context: .
dockerfile: deploy/web.Dockerfile
args:
VERSION: ${PLATFORM_BUILD_VERSION:-dev}
COMMIT: ${PLATFORM_BUILD_COMMIT:-unknown}
BUILD_TIME: ${PLATFORM_BUILD_TIME:-unknown}
NPM_CONFIG_REGISTRY: ${NPM_CONFIG_REGISTRY:-https://registry.npmjs.org}
environment:
WEB_OIDC_AUTHORITY: ${WEB_OIDC_AUTHORITY:-http://localhost:58180/realms/voice-local}
WEB_OIDC_CLIENT_ID: ${WEB_OIDC_CLIENT_ID:-voice-platform-web}
ports:
- "${WEB_PORT:-3100}:8080"
depends_on:
api:
condition: service_started
volumes:
postgres-data:
redis-data:
minio-data:
ollama-data: