#!/bin/sh # service_run.sh — Docker 入口 # # 单进程跑:FastAPI dashboard(uvicorn)+ 进程内 scheduler(worker) # 用 uvicorn 主进程 + 一个 daemon thread 跑 worker 的 scheduler 循环。 # # 设计原因:docker-compose 一容器一服务最简单,systemd timer 的事交给 # 进程内 scheduler(避免 docker 里跑 systemd)。原来 .env 里的 schedule # 表就是为这个准备的。 set -eu # 等 PG 就绪(避免 race:market_sync 比 postgres 早启) if [ -n "${PG_HOST:-}" ]; then echo "[service] wait for PG ${PG_HOST}:${PG_PORT:-5432}..." for i in $(seq 1 30); do if python -c " import socket, sys s = socket.socket() s.settimeout(2) try: s.connect(('${PG_HOST}', ${PG_PORT:-5432})) print('pg ready') except Exception as e: sys.exit(1) " >/dev/null 2>&1; then echo "[service] PG up after ${i}s" break fi sleep 1 done fi # seed datasource + sync registry + schedule(幂等) python -c " from app.core.datasource.registry import build_default_registry, seed_datasource_configs from app.core.sync.registry import seed_sync_registry, recover_interrupted_syncs from app.core.scheduler.scheduler import seed_schedule_configs build_default_registry() seed_datasource_configs() seed_sync_registry() recover_interrupted_syncs() seed_schedule_configs() print('[service] seed 完成') " # 后台启动 scheduler(独立线程) python -c " from app.entrypoints.worker import start_scheduler_thread start_scheduler_thread() print('[service] scheduler thread started') import time while True: time.sleep(60) " & SCHEDULER_PID=$! # trap SIGTERM 优雅退出 trap "echo '[service] stopping scheduler'; kill ${SCHEDULER_PID} 2>/dev/null || true; wait" TERM INT # 前台跑 uvicorn(主进程) echo "[service] starting uvicorn on ${API_HOST:-0.0.0.0}:${API_PORT:-8100}" exec uvicorn app.api.main:app \ --host "${API_HOST:-0.0.0.0}" \ --port "${API_PORT:-8100}" \ --workers 1 \ --log-level "${LOG_LEVEL:-info}" \ --no-access-log