Add dynamic analyst runtime updates and deployment guides
This commit is contained in:
602
deploy/install-production.sh
Normal file
602
deploy/install-production.sh
Normal file
@@ -0,0 +1,602 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
NON_INTERACTIVE=false
|
||||
AUTO_INSTALL_DEPS=""
|
||||
AUTO_INSTALL_SYSTEMD=""
|
||||
AUTO_START_SYSTEMD=""
|
||||
AUTO_INSTALL_NGINX=""
|
||||
AUTO_RELOAD_NGINX=""
|
||||
AUTO_USE_TLS=""
|
||||
AUTO_USE_DOCKER=""
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[bigtime]${NC} $*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[bigtime]${NC} $*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo -e "${RED}[bigtime]${NC} $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ask() {
|
||||
local prompt="$1"
|
||||
local default="${2:-}"
|
||||
if ${NON_INTERACTIVE}; then
|
||||
printf '%s' "${default}"
|
||||
return
|
||||
fi
|
||||
local value
|
||||
if [[ -n "${default}" ]]; then
|
||||
read -r -p "${prompt} [${default}]: " value
|
||||
printf '%s' "${value:-$default}"
|
||||
else
|
||||
read -r -p "${prompt}: " value
|
||||
printf '%s' "${value}"
|
||||
fi
|
||||
}
|
||||
|
||||
ask_required() {
|
||||
local prompt="$1"
|
||||
local default="${2:-}"
|
||||
local value=""
|
||||
while [[ -z "${value}" ]]; do
|
||||
value="$(ask "${prompt}" "${default}")"
|
||||
if [[ -z "${value}" ]]; then
|
||||
warn "该项不能为空,请重新输入。"
|
||||
fi
|
||||
done
|
||||
printf '%s' "${value}"
|
||||
}
|
||||
|
||||
validate_domain_like() {
|
||||
local value="$1"
|
||||
[[ -z "${value}" ]] && return 1
|
||||
[[ "${value}" =~ ^[A-Za-z0-9.-]+$ ]]
|
||||
}
|
||||
|
||||
validate_file_parent_exists_or_rootable() {
|
||||
local value="$1"
|
||||
local parent
|
||||
parent="$(dirname "${value}")"
|
||||
[[ -d "${parent}" ]] || [[ "${parent}" == "/etc/bigtime" ]] || [[ "${parent}" == "/etc/nginx/conf.d" ]]
|
||||
}
|
||||
|
||||
validate_numeric() {
|
||||
local value="$1"
|
||||
[[ "${value}" =~ ^[0-9]+([.][0-9]+)?$ ]]
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local prompt="$1"
|
||||
local default="${2:-Y}"
|
||||
local override="${3:-}"
|
||||
if [[ -n "${override}" ]]; then
|
||||
[[ "${override}" =~ ^[Yy]([Ee][Ss])?$|^true$|^1$ ]]
|
||||
return
|
||||
fi
|
||||
if ${NON_INTERACTIVE}; then
|
||||
[[ "${default}" == "Y" ]]
|
||||
return
|
||||
fi
|
||||
local suffix="[Y/n]"
|
||||
[[ "${default}" == "N" ]] && suffix="[y/N]"
|
||||
local value
|
||||
read -r -p "${prompt} ${suffix}: " value
|
||||
value="${value:-$default}"
|
||||
[[ "${value}" =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
detect_pkg_manager() {
|
||||
if command_exists apt-get; then
|
||||
echo "apt"
|
||||
return
|
||||
fi
|
||||
if command_exists dnf; then
|
||||
echo "dnf"
|
||||
return
|
||||
fi
|
||||
if command_exists yum; then
|
||||
echo "yum"
|
||||
return
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
local pkg_manager="$1"
|
||||
case "${pkg_manager}" in
|
||||
apt)
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3 python3-venv python3-pip nginx curl git build-essential nodejs npm
|
||||
;;
|
||||
dnf)
|
||||
sudo dnf install -y python3 python3-pip nginx curl git gcc-c++ make nodejs npm
|
||||
;;
|
||||
yum)
|
||||
sudo yum install -y python3 python3-pip nginx curl git gcc-c++ make nodejs npm
|
||||
;;
|
||||
*)
|
||||
warn "未识别包管理器,跳过依赖安装。请手动安装 python3、venv、pip、nginx、node、npm。"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
render_systemd_unit() {
|
||||
local service_name="$1"
|
||||
local app_module="$2"
|
||||
local port="$3"
|
||||
local workers="$4"
|
||||
local memory_max="$5"
|
||||
local unit_path="$6"
|
||||
|
||||
sudo tee "${unit_path}" >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=BigTime ${service_name}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${SERVICE_USER}
|
||||
Group=${SERVICE_GROUP}
|
||||
WorkingDirectory=${APP_DIR}
|
||||
EnvironmentFile=${ENV_FILE}
|
||||
ExecStart=${PYTHON_BIN} -m uvicorn ${app_module} --host 127.0.0.1 --port ${port} --workers ${workers} --log-level warning --no-access-log
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
TimeoutStopSec=30
|
||||
KillMode=mixed
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
LimitNOFILE=65535
|
||||
TasksMax=4096
|
||||
MemoryMax=${memory_max}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
}
|
||||
|
||||
render_nginx_conf() {
|
||||
local target="$1"
|
||||
local use_tls="$2"
|
||||
if [[ "${use_tls}" == "yes" ]]; then
|
||||
sudo tee "${target}" >/dev/null <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${DOMAIN};
|
||||
|
||||
root ${APP_DIR}/frontend/dist;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ${DOMAIN};
|
||||
|
||||
root ${APP_DIR}/frontend/dist;
|
||||
index index.html;
|
||||
|
||||
ssl_certificate ${SSL_CERT_PATH};
|
||||
ssl_certificate_key ${SSL_KEY_PATH};
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:8765;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/runtime/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/dynamic-team/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/trading/ {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/news/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
else
|
||||
sudo tee "${target}" >/dev/null <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${DOMAIN};
|
||||
|
||||
root ${APP_DIR}/frontend/dist;
|
||||
index index.html;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:8765;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/runtime/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/dynamic-team/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/trading/ {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/news/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
write_env_file() {
|
||||
sudo mkdir -p "$(dirname "${ENV_FILE}")"
|
||||
sudo tee "${ENV_FILE}" >/dev/null <<EOF
|
||||
AGENT_SERVICE_URL=http://127.0.0.1:8000
|
||||
TRADING_SERVICE_URL=http://127.0.0.1:8001
|
||||
NEWS_SERVICE_URL=http://127.0.0.1:8002
|
||||
RUNTIME_SERVICE_URL=http://127.0.0.1:8003
|
||||
|
||||
TICKERS=${TICKERS}
|
||||
FIN_DATA_SOURCE=${FIN_DATA_SOURCE}
|
||||
FINANCIAL_DATASETS_API_KEY=${FINANCIAL_DATASETS_API_KEY}
|
||||
FINNHUB_API_KEY=${FINNHUB_API_KEY}
|
||||
POLYGON_API_KEY=${POLYGON_API_KEY}
|
||||
OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||
OPENAI_BASE_URL=${OPENAI_BASE_URL}
|
||||
DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
|
||||
MODEL_NAME=${MODEL_NAME}
|
||||
MEMORY_API_KEY=${MEMORY_API_KEY}
|
||||
SKILL_SANDBOX_MODE=${SKILL_SANDBOX_MODE}
|
||||
MAX_COMM_CYCLES=${MAX_COMM_CYCLES}
|
||||
MARGIN_REQUIREMENT=${MARGIN_REQUIREMENT}
|
||||
EOF
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
./deploy/install-production.sh [options]
|
||||
|
||||
Options:
|
||||
--non-interactive Run with defaults / env overrides only
|
||||
--app-dir PATH Application directory
|
||||
--service-user USER systemd service user
|
||||
--service-group GROUP systemd service group
|
||||
--domain DOMAIN Public domain
|
||||
--env-file PATH Environment file path
|
||||
--python-bin PATH Python executable path
|
||||
--tickers CSV Default tickers
|
||||
--fin-data-source NAME finnhub/yfinance/financial_datasets
|
||||
--model-name NAME Default model name
|
||||
--max-comm-cycles N Conference rounds
|
||||
--margin-requirement NUM Margin requirement
|
||||
--use-docker-sandbox Set SKILL_SANDBOX_MODE=docker
|
||||
--no-docker-sandbox Set SKILL_SANDBOX_MODE=none
|
||||
--with-tls Generate HTTPS nginx config
|
||||
--without-tls Generate HTTP nginx config
|
||||
--install-deps Auto install dependencies
|
||||
--skip-install-deps Skip dependency installation
|
||||
--install-systemd Install systemd units
|
||||
--skip-install-systemd Skip systemd unit installation
|
||||
--start-systemd Enable/start services
|
||||
--skip-start-systemd Do not start services
|
||||
--install-nginx Install nginx config
|
||||
--skip-install-nginx Skip nginx config installation
|
||||
--reload-nginx Run nginx -t and reload
|
||||
--skip-reload-nginx Skip nginx reload
|
||||
--ssl-cert-path PATH TLS certificate path
|
||||
--ssl-key-path PATH TLS key path
|
||||
--help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--non-interactive) NON_INTERACTIVE=true ;;
|
||||
--app-dir) APP_DIR="${2:?missing value}"; shift ;;
|
||||
--service-user) SERVICE_USER="${2:?missing value}"; shift ;;
|
||||
--service-group) SERVICE_GROUP="${2:?missing value}"; shift ;;
|
||||
--domain) DOMAIN="${2:?missing value}"; shift ;;
|
||||
--env-file) ENV_FILE="${2:?missing value}"; shift ;;
|
||||
--python-bin) PYTHON_BIN="${2:?missing value}"; shift ;;
|
||||
--tickers) TICKERS="${2:?missing value}"; shift ;;
|
||||
--fin-data-source) FIN_DATA_SOURCE="${2:?missing value}"; shift ;;
|
||||
--model-name) MODEL_NAME="${2:?missing value}"; shift ;;
|
||||
--max-comm-cycles) MAX_COMM_CYCLES="${2:?missing value}"; shift ;;
|
||||
--margin-requirement) MARGIN_REQUIREMENT="${2:?missing value}"; shift ;;
|
||||
--use-docker-sandbox) AUTO_USE_DOCKER="Y" ;;
|
||||
--no-docker-sandbox) AUTO_USE_DOCKER="N" ;;
|
||||
--with-tls) AUTO_USE_TLS="Y" ;;
|
||||
--without-tls) AUTO_USE_TLS="N" ;;
|
||||
--install-deps) AUTO_INSTALL_DEPS="Y" ;;
|
||||
--skip-install-deps) AUTO_INSTALL_DEPS="N" ;;
|
||||
--install-systemd) AUTO_INSTALL_SYSTEMD="Y" ;;
|
||||
--skip-install-systemd) AUTO_INSTALL_SYSTEMD="N" ;;
|
||||
--start-systemd) AUTO_START_SYSTEMD="Y" ;;
|
||||
--skip-start-systemd) AUTO_START_SYSTEMD="N" ;;
|
||||
--install-nginx) AUTO_INSTALL_NGINX="Y" ;;
|
||||
--skip-install-nginx) AUTO_INSTALL_NGINX="N" ;;
|
||||
--reload-nginx) AUTO_RELOAD_NGINX="Y" ;;
|
||||
--skip-reload-nginx) AUTO_RELOAD_NGINX="N" ;;
|
||||
--ssl-cert-path) SSL_CERT_PATH="${2:?missing value}"; shift ;;
|
||||
--ssl-key-path) SSL_KEY_PATH="${2:?missing value}"; shift ;;
|
||||
--help) usage; exit 0 ;;
|
||||
*) fail "Unknown option: $1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
main() {
|
||||
echo -e "${CYAN}BigTime 生产部署向导${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}说明:${NC} 这个脚本适合从一台空机器开始部署当前项目。"
|
||||
echo -e "${YELLOW}默认推荐:${NC} split-service + systemd + nginx + 静态前端。"
|
||||
echo ""
|
||||
|
||||
if confirm "尝试自动安装基础依赖(python3/nginx/node 等)?" "Y" "${AUTO_INSTALL_DEPS}"; then
|
||||
PKG_MANAGER="$(detect_pkg_manager)"
|
||||
install_packages "${PKG_MANAGER}"
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}基础配置${NC}"
|
||||
APP_DIR="${APP_DIR:-$(ask_required '应用部署目录(仓库根目录,建议绝对路径)' "${REPO_ROOT}")}"
|
||||
[[ -d "${APP_DIR}" ]] || fail "应用目录不存在: ${APP_DIR}"
|
||||
|
||||
SERVICE_USER="${SERVICE_USER:-$(ask_required 'systemd 运行用户' "$(id -un)")}"
|
||||
id "${SERVICE_USER}" >/dev/null 2>&1 || warn "用户 ${SERVICE_USER} 当前不存在,请确认后续 systemd 配置。"
|
||||
|
||||
SERVICE_GROUP="${SERVICE_GROUP:-$(ask_required 'systemd 运行用户组' "$(id -gn)")}"
|
||||
|
||||
DOMAIN="${DOMAIN:-$(ask_required '部署域名(可填写 IP 或 localhost)' 'localhost')}"
|
||||
validate_domain_like "${DOMAIN}" || warn "域名/IP 形态看起来不标准,请再次确认: ${DOMAIN}"
|
||||
|
||||
ENV_FILE="${ENV_FILE:-$(ask_required '环境变量文件路径' '/etc/bigtime/bigtime.env')}"
|
||||
validate_file_parent_exists_or_rootable "${ENV_FILE}" || warn "环境文件父目录当前不存在,脚本会尝试创建: $(dirname "${ENV_FILE}")"
|
||||
|
||||
PYTHON_BIN="${PYTHON_BIN:-$(ask 'Python 可执行文件路径' "${APP_DIR}/.venv/bin/python")}"
|
||||
[[ -n "${PYTHON_BIN}" ]] || fail "Python 路径不能为空"
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}运行参数${NC}"
|
||||
TICKERS="${TICKERS:-$(ask '默认股票池(逗号分隔)' 'AAPL,MSFT,GOOGL,AMZN,NVDA,META,TSLA,AMD,NFLX,AVGO,PLTR,COIN')}"
|
||||
FIN_DATA_SOURCE="${FIN_DATA_SOURCE:-$(ask '行情数据源(finnhub/yfinance/financial_datasets)' 'finnhub')}"
|
||||
MODEL_NAME="${MODEL_NAME:-$(ask '默认模型名' 'qwen3-max')}"
|
||||
MAX_COMM_CYCLES="${MAX_COMM_CYCLES:-$(ask_required '最大讨论轮数' '2')}"
|
||||
validate_numeric "${MAX_COMM_CYCLES}" || fail "最大讨论轮数必须是数字: ${MAX_COMM_CYCLES}"
|
||||
MARGIN_REQUIREMENT="${MARGIN_REQUIREMENT:-$(ask_required '保证金比例' '0.5')}"
|
||||
validate_numeric "${MARGIN_REQUIREMENT}" || fail "保证金比例必须是数字: ${MARGIN_REQUIREMENT}"
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}密钥配置${NC}"
|
||||
FINANCIAL_DATASETS_API_KEY="${FINANCIAL_DATASETS_API_KEY:-$(ask 'FINANCIAL_DATASETS_API_KEY(可留空)' '')}"
|
||||
FINNHUB_API_KEY="${FINNHUB_API_KEY:-$(ask 'FINNHUB_API_KEY(live 模式建议填写)' '')}"
|
||||
POLYGON_API_KEY="${POLYGON_API_KEY:-$(ask 'POLYGON_API_KEY(可留空)' '')}"
|
||||
OPENAI_API_KEY="${OPENAI_API_KEY:-$(ask 'OPENAI_API_KEY(可留空)' '')}"
|
||||
OPENAI_BASE_URL="${OPENAI_BASE_URL:-$(ask 'OPENAI_BASE_URL(可留空)' '')}"
|
||||
DASHSCOPE_API_KEY="${DASHSCOPE_API_KEY:-$(ask 'DASHSCOPE_API_KEY(可留空)' '')}"
|
||||
MEMORY_API_KEY="${MEMORY_API_KEY:-$(ask 'MEMORY_API_KEY(可留空)' '')}"
|
||||
|
||||
if [[ "${FIN_DATA_SOURCE}" == "finnhub" && -z "${FINNHUB_API_KEY}" ]]; then
|
||||
warn "你选择了 finnhub 作为数据源,但 FINNHUB_API_KEY 为空。live 模式下通常会失败。"
|
||||
fi
|
||||
if [[ -z "${OPENAI_API_KEY}" && -z "${DASHSCOPE_API_KEY}" ]]; then
|
||||
warn "OPENAI_API_KEY 和 DASHSCOPE_API_KEY 都为空,模型调用可能无法工作。"
|
||||
fi
|
||||
|
||||
if confirm "使用 Docker 沙盒执行技能?" "N" "${AUTO_USE_DOCKER}"; then
|
||||
SKILL_SANDBOX_MODE="docker"
|
||||
else
|
||||
SKILL_SANDBOX_MODE="none"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}当前部署摘要${NC}"
|
||||
echo " 应用目录: ${APP_DIR}"
|
||||
echo " 运行用户: ${SERVICE_USER}:${SERVICE_GROUP}"
|
||||
echo " 域名: ${DOMAIN}"
|
||||
echo " 环境文件: ${ENV_FILE}"
|
||||
echo " Python: ${PYTHON_BIN}"
|
||||
echo " 数据源: ${FIN_DATA_SOURCE}"
|
||||
echo " 模型: ${MODEL_NAME}"
|
||||
echo " 沙盒模式: ${SKILL_SANDBOX_MODE}"
|
||||
echo ""
|
||||
|
||||
if ! confirm "确认以上配置并继续写入系统文件?" "Y"; then
|
||||
fail "用户取消部署。"
|
||||
fi
|
||||
|
||||
if [[ ! -x "${PYTHON_BIN}" ]]; then
|
||||
warn "未找到 ${PYTHON_BIN},准备创建虚拟环境。"
|
||||
python3 -m venv "${APP_DIR}/.venv"
|
||||
"${APP_DIR}/.venv/bin/python" -m pip install --upgrade pip
|
||||
PYTHON_BIN="${APP_DIR}/.venv/bin/python"
|
||||
fi
|
||||
|
||||
log "安装后端依赖"
|
||||
"${PYTHON_BIN}" -m pip install -e "${APP_DIR}"
|
||||
|
||||
log "构建前端"
|
||||
(cd "${APP_DIR}/frontend" && npm ci && npm run build)
|
||||
|
||||
log "写入环境变量文件 ${ENV_FILE}"
|
||||
write_env_file
|
||||
|
||||
if confirm "生成并安装 systemd unit?" "Y" "${AUTO_INSTALL_SYSTEMD}"; then
|
||||
render_systemd_unit "Agent Service" "backend.apps.agent_service:app" "8000" "1" "1024M" "/etc/systemd/system/bigtime-agent.service"
|
||||
render_systemd_unit "Trading Service" "backend.apps.trading_service:app" "8001" "1" "768M" "/etc/systemd/system/bigtime-trading.service"
|
||||
render_systemd_unit "News Service" "backend.apps.news_service:app" "8002" "1" "768M" "/etc/systemd/system/bigtime-news.service"
|
||||
render_systemd_unit "Runtime Service" "backend.apps.runtime_service:app" "8003" "1" "1536M" "/etc/systemd/system/bigtime-runtime.service"
|
||||
sudo systemctl daemon-reload
|
||||
if confirm "立即启用并启动 bigtime-* 服务?" "Y" "${AUTO_START_SYSTEMD}"; then
|
||||
sudo systemctl enable --now bigtime-agent.service
|
||||
sudo systemctl enable --now bigtime-trading.service
|
||||
sudo systemctl enable --now bigtime-news.service
|
||||
sudo systemctl enable --now bigtime-runtime.service
|
||||
fi
|
||||
fi
|
||||
|
||||
if confirm "生成并安装 nginx 配置?" "Y" "${AUTO_INSTALL_NGINX}"; then
|
||||
local use_tls="no"
|
||||
if confirm "使用 HTTPS/Let's Encrypt 证书路径?" "N" "${AUTO_USE_TLS}"; then
|
||||
use_tls="yes"
|
||||
SSL_CERT_PATH="${SSL_CERT_PATH:-$(ask_required 'SSL 证书 fullchain.pem 路径' "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem")}"
|
||||
SSL_KEY_PATH="${SSL_KEY_PATH:-$(ask_required 'SSL 私钥 privkey.pem 路径' "/etc/letsencrypt/live/${DOMAIN}/privkey.pem")}"
|
||||
[[ -f "${SSL_CERT_PATH}" ]] || warn "证书文件当前不存在: ${SSL_CERT_PATH}"
|
||||
[[ -f "${SSL_KEY_PATH}" ]] || warn "私钥文件当前不存在: ${SSL_KEY_PATH}"
|
||||
else
|
||||
SSL_CERT_PATH=""
|
||||
SSL_KEY_PATH=""
|
||||
fi
|
||||
NGINX_TARGET="/etc/nginx/conf.d/bigtime.conf"
|
||||
render_nginx_conf "${NGINX_TARGET}" "${use_tls}"
|
||||
if confirm "立即执行 nginx -t 并 reload?" "Y" "${AUTO_RELOAD_NGINX}"; then
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log "部署向导完成"
|
||||
echo "应用目录: ${APP_DIR}"
|
||||
echo "环境文件: ${ENV_FILE}"
|
||||
echo "Python: ${PYTHON_BIN}"
|
||||
echo "沙盒模式: ${SKILL_SANDBOX_MODE}"
|
||||
echo ""
|
||||
echo "验证建议:"
|
||||
echo " curl http://127.0.0.1:8003/health"
|
||||
echo " curl http://127.0.0.1:8003/api/runtime/current"
|
||||
echo " sudo systemctl status bigtime-runtime.service"
|
||||
echo " tail -f ${APP_DIR}/runs/<run_id>/logs/gateway.log"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -2,8 +2,9 @@ server {
|
||||
listen 80;
|
||||
server_name bigtime.cillinn.com;
|
||||
|
||||
root /opt/bigtime/app/frontend/dist;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/bigtime/current;
|
||||
allow all;
|
||||
}
|
||||
|
||||
@@ -16,7 +17,7 @@ server {
|
||||
listen 443 ssl http2;
|
||||
server_name bigtime.cillinn.com;
|
||||
|
||||
root /var/www/bigtime/current;
|
||||
root /opt/bigtime/app/frontend/dist;
|
||||
index index.html;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/bigtime.cillinn.com/fullchain.pem;
|
||||
@@ -36,6 +37,56 @@ server {
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/runtime/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/dynamic-team/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/trading/ {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/news/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,75 @@ server {
|
||||
listen 80;
|
||||
server_name bigtime.cillinn.com;
|
||||
|
||||
root /var/www/bigtime/current;
|
||||
root /opt/bigtime/app/frontend/dist;
|
||||
index index.html;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:8765;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/runtime/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/dynamic-team/ {
|
||||
proxy_pass http://127.0.0.1:8003;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/trading/ {
|
||||
proxy_pass http://127.0.0.1:8001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/news/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
134
deploy/production-deployment.md
Normal file
134
deploy/production-deployment.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Production Deployment
|
||||
|
||||
This is the recommended production deployment mode for the current repository.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Use:
|
||||
|
||||
- split FastAPI services
|
||||
- `systemd` as the process supervisor
|
||||
- `nginx` as TLS terminator and reverse proxy
|
||||
- static frontend build served by `nginx`
|
||||
- Docker-based skill sandbox
|
||||
|
||||
This matches the current architecture better than a monolithic process and is
|
||||
lower-risk than introducing Kubernetes at the current stage.
|
||||
|
||||
## Why This Mode Fits Best
|
||||
|
||||
1. The repository already uses a split-service runtime model.
|
||||
2. `runtime_service` is the correct control-plane entrypoint for starting and
|
||||
stopping Gateway subprocesses.
|
||||
3. The Gateway is run-scoped and ephemeral, which fits `systemd` + subprocess
|
||||
management better than forcing everything into a single service binary.
|
||||
4. Skill execution has security requirements; Docker sandboxing is the practical
|
||||
production default.
|
||||
|
||||
## Service Layout
|
||||
|
||||
| Component | Bind |
|
||||
|----------|------|
|
||||
| `agent_service` | `127.0.0.1:8000` |
|
||||
| `trading_service` | `127.0.0.1:8001` |
|
||||
| `news_service` | `127.0.0.1:8002` |
|
||||
| `runtime_service` | `127.0.0.1:8003` |
|
||||
| gateway websocket | spawned by `runtime_service` |
|
||||
| `nginx` | `:80` / `:443` |
|
||||
|
||||
## Frontend
|
||||
|
||||
Recommended frontend mode:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then point `nginx` root at:
|
||||
|
||||
```text
|
||||
/opt/bigtime/app/frontend/dist
|
||||
```
|
||||
|
||||
This is preferred over running `backend.apps.frontend_service` in production,
|
||||
because static serving via `nginx` is simpler and more reliable.
|
||||
|
||||
## Environment
|
||||
|
||||
Create a shared environment file, for example:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/bigtime
|
||||
sudo cp .env /etc/bigtime/bigtime.env
|
||||
```
|
||||
|
||||
Required production settings:
|
||||
|
||||
```bash
|
||||
AGENT_SERVICE_URL=http://127.0.0.1:8000
|
||||
TRADING_SERVICE_URL=http://127.0.0.1:8001
|
||||
NEWS_SERVICE_URL=http://127.0.0.1:8002
|
||||
RUNTIME_SERVICE_URL=http://127.0.0.1:8003
|
||||
|
||||
SKILL_SANDBOX_MODE=docker
|
||||
SKILL_SANDBOX_MEMORY_LIMIT=512m
|
||||
SKILL_SANDBOX_CPU_LIMIT=1.0
|
||||
SKILL_SANDBOX_NETWORK=none
|
||||
SKILL_SANDBOX_TIMEOUT=60
|
||||
```
|
||||
|
||||
Also supply the required market/model API keys in the same environment file or
|
||||
through your secret-management system.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
Persist these paths on durable storage:
|
||||
|
||||
- `runs/`
|
||||
- `logs/` if you keep service logs on disk
|
||||
- optional `.env`-backed secrets should not live inside the repo working tree
|
||||
|
||||
The key runtime source of truth is:
|
||||
|
||||
- `runs/<run_id>/state/runtime_state.json`
|
||||
- `runs/<run_id>/state/server_state.json`
|
||||
- `runs/<run_id>/logs/gateway.log`
|
||||
|
||||
## nginx Pattern
|
||||
|
||||
Recommended routing:
|
||||
|
||||
- `/` -> static frontend
|
||||
- `/api/runtime/*` -> `127.0.0.1:8003`
|
||||
- `/api/dynamic-team/*` -> `127.0.0.1:8003`
|
||||
- `/api/trading/*` -> `127.0.0.1:8001`
|
||||
- `/api/news/*` -> `127.0.0.1:8002`
|
||||
- `/api/*` -> `127.0.0.1:8000`
|
||||
- `/ws` -> gateway websocket
|
||||
|
||||
The checked-in nginx config should be treated as a starting point, not a full
|
||||
multi-service production config.
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Use `workers=1` for `runtime_service` unless you deliberately redesign the
|
||||
runtime manager around multi-process coordination.
|
||||
- Keep the other API services stateless and scale them separately if needed.
|
||||
- Monitor:
|
||||
- `runtime_service`
|
||||
- run-scoped `gateway.log`
|
||||
- Docker daemon health
|
||||
- Rotate logs outside the app, e.g. with journald or logrotate.
|
||||
|
||||
## Best Next Step
|
||||
|
||||
Deploy with:
|
||||
|
||||
- `systemd` units from [deploy/systemd](/Users/cillin/workspeace/evotraders/deploy/systemd)
|
||||
- `nginx` in front
|
||||
- one VM first
|
||||
|
||||
Only move to containers/orchestration after the runtime/gateway operational
|
||||
behavior is stable in that simpler topology.
|
||||
47
deploy/systemd/README.md
Normal file
47
deploy/systemd/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# systemd Units
|
||||
|
||||
This directory contains recommended `systemd` unit templates for the current
|
||||
split-service production topology.
|
||||
|
||||
## Recommended Topology
|
||||
|
||||
- `agent_service` on `127.0.0.1:8000`
|
||||
- `trading_service` on `127.0.0.1:8001`
|
||||
- `news_service` on `127.0.0.1:8002`
|
||||
- `runtime_service` on `127.0.0.1:8003`
|
||||
- `nginx` serves `frontend/dist` and proxies `/api/*` + `/ws`
|
||||
- `runtime_service` spawns the run-scoped Gateway subprocess on demand
|
||||
- skill execution runs with `SKILL_SANDBOX_MODE=docker`
|
||||
|
||||
## Install
|
||||
|
||||
Adjust these placeholders before installing:
|
||||
|
||||
- `/opt/bigtime/app` -> repository root on the server
|
||||
- `/opt/bigtime/app/.venv/bin/python` -> Python interpreter
|
||||
- `/etc/bigtime/bigtime.env` -> shared environment file
|
||||
- `bigtime` -> service user
|
||||
|
||||
Then copy units:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/bigtime
|
||||
sudo cp .env /etc/bigtime/bigtime.env
|
||||
|
||||
sudo cp deploy/systemd/bigtime-*.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now bigtime-agent.service
|
||||
sudo systemctl enable --now bigtime-trading.service
|
||||
sudo systemctl enable --now bigtime-news.service
|
||||
sudo systemctl enable --now bigtime-runtime.service
|
||||
```
|
||||
|
||||
## Frontend
|
||||
|
||||
Recommended production frontend mode:
|
||||
|
||||
- build with `cd frontend && npm ci && npm run build`
|
||||
- let `nginx` serve `frontend/dist` directly
|
||||
|
||||
The repository also contains `backend.apps.frontend_service`, but for
|
||||
production the lower-complexity path is static hosting via `nginx`.
|
||||
25
deploy/systemd/bigtime-agent.service
Normal file
25
deploy/systemd/bigtime-agent.service
Normal file
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=BigTime Agent Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=bigtime
|
||||
Group=bigtime
|
||||
WorkingDirectory=/opt/bigtime/app
|
||||
EnvironmentFile=/etc/bigtime/bigtime.env
|
||||
ExecStart=/opt/bigtime/app/.venv/bin/python -m uvicorn backend.apps.agent_service:app --host 127.0.0.1 --port 8000 --workers 1 --log-level warning --no-access-log
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
TimeoutStopSec=20
|
||||
KillMode=mixed
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
LimitNOFILE=65535
|
||||
TasksMax=4096
|
||||
MemoryMax=1024M
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
25
deploy/systemd/bigtime-news.service
Normal file
25
deploy/systemd/bigtime-news.service
Normal file
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=BigTime News Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=bigtime
|
||||
Group=bigtime
|
||||
WorkingDirectory=/opt/bigtime/app
|
||||
EnvironmentFile=/etc/bigtime/bigtime.env
|
||||
ExecStart=/opt/bigtime/app/.venv/bin/python -m uvicorn backend.apps.news_service:app --host 127.0.0.1 --port 8002 --workers 1 --log-level warning --no-access-log
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
TimeoutStopSec=20
|
||||
KillMode=mixed
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
LimitNOFILE=65535
|
||||
TasksMax=4096
|
||||
MemoryMax=768M
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
25
deploy/systemd/bigtime-runtime.service
Normal file
25
deploy/systemd/bigtime-runtime.service
Normal file
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=BigTime Runtime Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=bigtime
|
||||
Group=bigtime
|
||||
WorkingDirectory=/opt/bigtime/app
|
||||
EnvironmentFile=/etc/bigtime/bigtime.env
|
||||
ExecStart=/opt/bigtime/app/.venv/bin/python -m uvicorn backend.apps.runtime_service:app --host 127.0.0.1 --port 8003 --workers 1 --log-level warning --no-access-log
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
TimeoutStopSec=30
|
||||
KillMode=mixed
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
LimitNOFILE=65535
|
||||
TasksMax=4096
|
||||
MemoryMax=1536M
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
25
deploy/systemd/bigtime-trading.service
Normal file
25
deploy/systemd/bigtime-trading.service
Normal file
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=BigTime Trading Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=bigtime
|
||||
Group=bigtime
|
||||
WorkingDirectory=/opt/bigtime/app
|
||||
EnvironmentFile=/etc/bigtime/bigtime.env
|
||||
ExecStart=/opt/bigtime/app/.venv/bin/python -m uvicorn backend.apps.trading_service:app --host 127.0.0.1 --port 8001 --workers 1 --log-level warning --no-access-log
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
TimeoutStopSec=20
|
||||
KillMode=mixed
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
LimitNOFILE=65535
|
||||
TasksMax=4096
|
||||
MemoryMax=768M
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
47
deploy/uninstall-production.sh
Normal file
47
deploy/uninstall-production.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[bigtime]${NC} $*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[bigtime]${NC} $*"
|
||||
}
|
||||
|
||||
SYSTEMD_UNITS=(
|
||||
bigtime-agent.service
|
||||
bigtime-trading.service
|
||||
bigtime-news.service
|
||||
bigtime-runtime.service
|
||||
)
|
||||
|
||||
NGINX_CONF="/etc/nginx/conf.d/bigtime.conf"
|
||||
ENV_FILE="/etc/bigtime/bigtime.env"
|
||||
|
||||
for unit in "${SYSTEMD_UNITS[@]}"; do
|
||||
if systemctl list-unit-files "${unit}" >/dev/null 2>&1; then
|
||||
warn "Stopping ${unit}"
|
||||
sudo systemctl disable --now "${unit}" || true
|
||||
sudo rm -f "/etc/systemd/system/${unit}"
|
||||
fi
|
||||
done
|
||||
|
||||
sudo systemctl daemon-reload || true
|
||||
|
||||
if [[ -f "${NGINX_CONF}" ]]; then
|
||||
warn "Removing nginx config ${NGINX_CONF}"
|
||||
sudo rm -f "${NGINX_CONF}"
|
||||
sudo nginx -t && sudo systemctl reload nginx || true
|
||||
fi
|
||||
|
||||
if [[ -f "${ENV_FILE}" ]]; then
|
||||
warn "Keeping env file ${ENV_FILE}"
|
||||
warn "Delete it manually if you want a full cleanup."
|
||||
fi
|
||||
|
||||
log "BigTime production service uninstall finished."
|
||||
Reference in New Issue
Block a user