Add config validation and deployment docs
This commit is contained in:
+79
-85
@@ -1,75 +1,84 @@
|
||||
# Telegram Bot
|
||||
TELEGRAM_BOT_TOKEN=your_bot_token_here
|
||||
TELEGRAM_CHAT_ID=your_chat_id_here
|
||||
# Optional multi-chat target (comma-separated). If set, it will be merged with TELEGRAM_CHAT_ID.
|
||||
# Example: TELEGRAM_CHAT_IDS=-1003586303099,-1003539418691
|
||||
# PolyWeather backend/bot minimal reproducible config
|
||||
# Full configuration guide:
|
||||
# docs/CONFIGURATION_ZH.md
|
||||
# Sensitive-only template:
|
||||
# .env.secrets.example
|
||||
|
||||
########################################
|
||||
# 1) Runtime paths and base behavior
|
||||
########################################
|
||||
ENV=production
|
||||
LOG_LEVEL=INFO
|
||||
POLYWEATHER_MAP_URL=https://polyweather-pro.vercel.app/
|
||||
POLYWEATHER_RUNTIME_DATA_DIR=/var/lib/polyweather
|
||||
POLYWEATHER_DB_PATH=/var/lib/polyweather/polyweather.db
|
||||
OPEN_METEO_DISK_CACHE_PATH=/var/lib/polyweather/open_meteo_cache.json
|
||||
# Optional: host user/group mapping for Docker on Linux.
|
||||
# Windows / macOS can usually keep the defaults.
|
||||
UID=1000
|
||||
GID=1000
|
||||
|
||||
########################################
|
||||
# 2) Telegram bot minimal
|
||||
########################################
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
TELEGRAM_CHAT_ID=
|
||||
TELEGRAM_CHAT_IDS=
|
||||
# Optional: route /city and /deb outputs to a fixed forum topic.
|
||||
# If TELEGRAM_QUERY_TOPIC_CHAT_ID is empty, fallback to command source chat.
|
||||
TELEGRAM_QUERY_TOPIC_CHAT_ID=
|
||||
TELEGRAM_QUERY_TOPIC_ID=
|
||||
# Optional per-group topic routing (higher priority than fixed topic above):
|
||||
# format: chat_id:topic_id,chat_id:topic_id
|
||||
# Example: TELEGRAM_QUERY_TOPIC_MAP=-1003586303099:25513,-1003539418691:25514
|
||||
TELEGRAM_QUERY_TOPIC_MAP=
|
||||
TELEGRAM_ALERT_PUSH_ENABLED=true
|
||||
TELEGRAM_ALERT_PUSH_INTERVAL_SEC=300
|
||||
TELEGRAM_ALERT_PUSH_COOLDOWN_SEC=1800
|
||||
TELEGRAM_ALERT_MIN_TRIGGER_COUNT=2
|
||||
TELEGRAM_ALERT_MIN_SEVERITY=medium
|
||||
# Mispricing radar: skip push when YES buy price is above this cap (10c = 0.10)
|
||||
TELEGRAM_ALERT_MISPRICING_MAX_YES_BUY=0.10
|
||||
TELEGRAM_ALERT_CITIES=ankara,london,paris,seoul,hong kong,shanghai,singapore,tokyo,tel aviv,toronto,buenos aires,wellington,new york,chicago,dallas,miami,atlanta,seattle,lucknow,sao paulo,munich
|
||||
POLYWEATHER_BOT_GROUP_INVITE_URL=
|
||||
|
||||
# Open-Meteo (forecast data changes ~hourly, no need to refresh more often)
|
||||
########################################
|
||||
# 3) Weather + cache
|
||||
########################################
|
||||
OPEN_METEO_CACHE_TTL_SEC=7200
|
||||
OPEN_METEO_ENSEMBLE_CACHE_TTL_SEC=7200
|
||||
OPEN_METEO_MULTI_MODEL_CACHE_TTL_SEC=7200
|
||||
OPEN_METEO_MULTI_MODEL_CACHE_VERSION=v2
|
||||
OPEN_METEO_RATE_LIMIT_COOLDOWN_SEC=900
|
||||
OPEN_METEO_RATE_CACHE_TTL_SEC=3600
|
||||
OPEN_METEO_MIN_CALL_INTERVAL_SEC=3
|
||||
METAR_CACHE_TTL_SEC=600
|
||||
METEOBLUE_CACHE_TTL_SEC=7200
|
||||
|
||||
# Proxy Setting (optional)
|
||||
HTTPS_PROXY=http://127.0.0.1:7890
|
||||
HTTP_PROXY=http://127.0.0.1:7890
|
||||
|
||||
# Other Settings
|
||||
LOG_LEVEL=INFO
|
||||
ENV=production
|
||||
POLYWEATHER_MAP_URL=https://polyweather-pro.vercel.app/
|
||||
# Runtime data directory (host path mounted into container at /var/lib/polyweather and /app/data)
|
||||
POLYWEATHER_RUNTIME_DATA_DIR=/var/lib/polyweather
|
||||
# Recommended: keep SQLite outside repository workspace
|
||||
POLYWEATHER_DB_PATH=/var/lib/polyweather/polyweather.db
|
||||
# Recommended disk cache/state paths (optional; defaults are still /app/data/*)
|
||||
OPEN_METEO_DISK_CACHE_PATH=/var/lib/polyweather/open_meteo_cache.json
|
||||
# Unified Auth (Supabase + Google/Email)
|
||||
########################################
|
||||
# 4) Auth / entitlement
|
||||
########################################
|
||||
POLYWEATHER_AUTH_ENABLED=false
|
||||
# If true: website APIs require login; if false: guest access, login optional.
|
||||
POLYWEATHER_AUTH_REQUIRED=false
|
||||
# If true, authenticated users must also have an active row in `subscriptions`.
|
||||
POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION=false
|
||||
POLYWEATHER_REQUIRE_ENTITLEMENT=false
|
||||
SUPABASE_URL=
|
||||
SUPABASE_ANON_KEY=
|
||||
SUPABASE_SERVICE_ROLE_KEY=
|
||||
SUPABASE_HTTP_TIMEOUT_SEC=8
|
||||
SUPABASE_AUTH_CACHE_TTL_SEC=30
|
||||
SUPABASE_SUB_CACHE_TTL_SEC=60
|
||||
# Frontend wallet connection (WalletConnect v2)
|
||||
# Apply in Vercel env as NEXT_PUBLIC_*
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||
|
||||
########################################
|
||||
# 5) Alerts / operations
|
||||
########################################
|
||||
TELEGRAM_ALERT_PUSH_ENABLED=true
|
||||
TELEGRAM_ALERT_PUSH_INTERVAL_SEC=300
|
||||
TELEGRAM_ALERT_PUSH_COOLDOWN_SEC=1800
|
||||
TELEGRAM_ALERT_MIN_TRIGGER_COUNT=2
|
||||
TELEGRAM_ALERT_MIN_SEVERITY=medium
|
||||
TELEGRAM_ALERT_MISPRICING_MAX_YES_BUY=0.10
|
||||
TELEGRAM_ALERT_CITIES=ankara,london,paris,seoul,hong kong,shanghai,singapore,tokyo,tel aviv,toronto,buenos aires,wellington,new york,chicago,dallas,miami,atlanta,seattle,lucknow,sao paulo,munich
|
||||
|
||||
########################################
|
||||
# 6) Frontend-facing shared values
|
||||
########################################
|
||||
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
|
||||
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
|
||||
# Bot command access guard (/city, /deb):
|
||||
# - Pro entitlement removed
|
||||
# - user only needs to be a member of any configured group (TELEGRAM_CHAT_IDS / TELEGRAM_CHAT_ID)
|
||||
POLYWEATHER_BOT_GROUP_INVITE_URL=
|
||||
# Group message points (anti-spam + ranking)
|
||||
POLYWEATHER_BOT_MESSAGE_POINTS=4
|
||||
POLYWEATHER_BOT_MESSAGE_DAILY_CAP=40
|
||||
POLYWEATHER_BOT_MESSAGE_MIN_LENGTH=3
|
||||
POLYWEATHER_BOT_MESSAGE_COOLDOWN_SEC=30
|
||||
POLYWEATHER_BOT_CITY_QUERY_COST=1
|
||||
POLYWEATHER_BOT_DEB_QUERY_COST=1
|
||||
# Weekly leaderboard reward settlement
|
||||
# settle_weekday: 1=Mon ... 7=Sun
|
||||
|
||||
########################################
|
||||
# 7) Optional modules
|
||||
########################################
|
||||
|
||||
# Weekly reward / leaderboard
|
||||
POLYWEATHER_WEEKLY_REWARD_ENABLED=true
|
||||
POLYWEATHER_WEEKLY_REWARD_TIMEZONE=Asia/Shanghai
|
||||
POLYWEATHER_WEEKLY_REWARD_SETTLE_WEEKDAY=1
|
||||
@@ -78,24 +87,22 @@ POLYWEATHER_WEEKLY_REWARD_SETTLE_MINUTE=5
|
||||
POLYWEATHER_WEEKLY_REWARD_CHECK_INTERVAL_SEC=300
|
||||
POLYWEATHER_WEEKLY_REWARD_HTTP_TIMEOUT_SEC=10
|
||||
POLYWEATHER_WEEKLY_REWARD_ANNOUNCE_ENABLED=true
|
||||
# Backend entitlement guard (for /api/cities, /api/city/*, /api/history/*)
|
||||
POLYWEATHER_REQUIRE_ENTITLEMENT=false
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||
|
||||
# P1 Contract Checkout (MetaMask + Polygon USDC)
|
||||
# Group message points
|
||||
POLYWEATHER_BOT_MESSAGE_POINTS=4
|
||||
POLYWEATHER_BOT_MESSAGE_DAILY_CAP=40
|
||||
POLYWEATHER_BOT_MESSAGE_MIN_LENGTH=3
|
||||
POLYWEATHER_BOT_MESSAGE_COOLDOWN_SEC=30
|
||||
POLYWEATHER_BOT_CITY_QUERY_COST=1
|
||||
POLYWEATHER_BOT_DEB_QUERY_COST=1
|
||||
|
||||
# Payments
|
||||
POLYWEATHER_PAYMENT_ENABLED=false
|
||||
POLYWEATHER_PAYMENT_CHAIN_ID=137
|
||||
POLYWEATHER_PAYMENT_RPC_URL=https://polygon-rpc.com
|
||||
# Legacy single-token fallback (still supported)
|
||||
POLYWEATHER_PAYMENT_RECEIVER_CONTRACT=
|
||||
POLYWEATHER_PAYMENT_TOKEN_ADDRESS=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174
|
||||
POLYWEATHER_PAYMENT_TOKEN_DECIMALS=6
|
||||
# Recommended multi-token config (supports USDC.e + Native USDC at the same time)
|
||||
# Example:
|
||||
# [
|
||||
# {"code":"usdc_e","symbol":"USDC.e","name":"USDC.e (PoS)","address":"0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174","decimals":6,"receiver_contract":"0xYourCheckoutContract","is_default":true},
|
||||
# {"code":"usdc","symbol":"USDC","name":"Native USDC","address":"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359","decimals":6,"receiver_contract":"0xYourCheckoutContract"}
|
||||
# ]
|
||||
POLYWEATHER_PAYMENT_ACCEPTED_TOKENS_JSON=
|
||||
POLYWEATHER_PAYMENT_CONFIRMATIONS=2
|
||||
POLYWEATHER_PAYMENT_INTENT_TTL_SEC=1800
|
||||
@@ -104,18 +111,13 @@ POLYWEATHER_PAYMENT_HTTP_TIMEOUT_SEC=10
|
||||
POLYWEATHER_PAYMENT_POLL_INTERVAL_SEC=4
|
||||
POLYWEATHER_PAYMENT_MAX_WAIT_SEC=50
|
||||
POLYWEATHER_PAYMENT_TELEGRAM_NOTIFY_ENABLED=true
|
||||
# Payment points redemption
|
||||
POLYWEATHER_PAYMENT_POINTS_ENABLED=true
|
||||
POLYWEATHER_PAYMENT_POINTS_PER_USDC=500
|
||||
POLYWEATHER_PAYMENT_POINTS_MAX_DISCOUNT_USDC=3
|
||||
# Comma-separated allowed plans for checkout UI + backend validation.
|
||||
# Default is monthly-only launch.
|
||||
POLYWEATHER_PAYMENT_ALLOWED_PLAN_CODES=pro_monthly
|
||||
# JSON object
|
||||
# Example: {"pro_monthly":{"plan_id":101,"amount_usdc":"5","duration_days":30}}
|
||||
POLYWEATHER_PAYMENT_PLAN_CATALOG_JSON=
|
||||
|
||||
# Polymarket P0 Read-Only Market Layer
|
||||
# Polymarket market scan
|
||||
POLYMARKET_MARKET_SCAN_ENABLED=true
|
||||
POLYMARKET_GAMMA_URL=https://gamma-api.polymarket.com
|
||||
POLYMARKET_CLOB_URL=https://clob.polymarket.com
|
||||
@@ -128,10 +130,10 @@ POLYMARKET_DISCOVERY_LIMIT=200
|
||||
POLYMARKET_SIGNAL_MIN_LIQUIDITY=500
|
||||
POLYMARKET_SIGNAL_EDGE_PCT=2
|
||||
|
||||
# Polygon Wallet Watcher (Single Chain P0)
|
||||
# Polygon watcher
|
||||
POLYGON_WALLET_WATCH_ENABLED=false
|
||||
POLYGON_RPC_URL=https://polygon-rpc.com
|
||||
POLYGON_WALLET_WATCH_ADDRESSES=0x0000000000000000000000000000000000000000
|
||||
POLYGON_WALLET_WATCH_ADDRESSES=
|
||||
POLYGON_WALLET_WATCH_INTERVAL_SEC=8
|
||||
POLYGON_WALLET_WATCH_CONFIRMATIONS=2
|
||||
POLYGON_WALLET_WATCH_MAX_BLOCKS_PER_CYCLE=30
|
||||
@@ -141,24 +143,15 @@ POLYGON_WALLET_WATCH_TX_BASE=https://polygonscan.com/tx
|
||||
POLYGON_WALLET_WATCH_ADDR_BASE=https://polygonscan.com/address
|
||||
POLYGON_WALLET_WATCH_POLYMARKET_ONLY=true
|
||||
POLYGON_WALLET_WATCH_INCLUDE_DEFAULT_PM_CONTRACTS=true
|
||||
# Optional custom Polymarket contracts, format: LABEL:0x...,LABEL2:0x...
|
||||
POLYGON_WALLET_WATCH_POLYMARKET_CONTRACTS=
|
||||
|
||||
# Polymarket Wallet Activity Watcher (all markets, not weather-only)
|
||||
# Polymarket wallet activity
|
||||
POLYMARKET_WALLET_ACTIVITY_ENABLED=false
|
||||
POLYMARKET_WALLET_ACTIVITY_USERS=0x0000000000000000000000000000000000000000
|
||||
# Optional dedicated chat targets for wallet activity push (recommended)
|
||||
# If unset, fallback to TELEGRAM_CHAT_IDS / TELEGRAM_CHAT_ID.
|
||||
POLYMARKET_WALLET_ACTIVITY_USERS=
|
||||
POLYMARKET_WALLET_ACTIVITY_CHAT_ID=
|
||||
POLYMARKET_WALLET_ACTIVITY_CHAT_IDS=
|
||||
# Optional: mirror wallet activity push to a forum topic, while keeping existing chat targets unchanged.
|
||||
POLYMARKET_WALLET_ACTIVITY_TOPIC_CHAT_ID=
|
||||
POLYMARKET_WALLET_ACTIVITY_TOPIC_ID=
|
||||
# Optional wallet nicknames:
|
||||
# - CSV: 0xabc...=Whale_A,0xdef...=Main_Account
|
||||
# - JSON: {"0xabc...":"Whale A","0xdef...":"Main Account"}
|
||||
# - Env key: POLYMARKET_WALLET_ACTIVITY_USER_ALIASES
|
||||
# (legacy typo POLYMARKET_WALLET_ACTIVITY_USERS_ALIASES is also accepted)
|
||||
POLYMARKET_WALLET_ACTIVITY_USER_ALIASES=
|
||||
POLYMARKET_WALLET_ACTIVITY_DATA_API_URL=https://data-api.polymarket.com
|
||||
POLYMARKET_WALLET_ACTIVITY_INTERVAL_SEC=20
|
||||
@@ -177,10 +170,11 @@ POLYMARKET_WALLET_ACTIVITY_UPDATE_DEBOUNCE_SEC=30
|
||||
POLYMARKET_WALLET_ACTIVITY_UPDATE_MAX_HOLD_SEC=120
|
||||
POLYMARKET_WALLET_ACTIVITY_AVG_PRICE_SHOW_MIN=0.01
|
||||
POLYMARKET_WALLET_ACTIVITY_AVG_PRICE_SHOW_MAX=0.99
|
||||
# Skip wallet activity pushes when position value is below this floor (USD).
|
||||
# Set 0 to disable.
|
||||
POLYMARKET_WALLET_ACTIVITY_MIN_POSITION_VALUE_USD=0
|
||||
# Comma-separated wallet addresses exempt from min value floor.
|
||||
# Example: 0x849d9a4dd64829b8b0141ea53e7caca7e99529ec
|
||||
POLYMARKET_WALLET_ACTIVITY_MIN_VALUE_EXEMPT_USERS=
|
||||
|
||||
########################################
|
||||
# 8) Optional proxies
|
||||
########################################
|
||||
HTTPS_PROXY=
|
||||
HTTP_PROXY=
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# PolyWeather secrets-only template
|
||||
# Copy the required lines into your real `.env` / platform secret manager.
|
||||
# Never commit actual values.
|
||||
|
||||
########################################
|
||||
# Telegram
|
||||
########################################
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
|
||||
########################################
|
||||
# Supabase
|
||||
########################################
|
||||
SUPABASE_URL=
|
||||
SUPABASE_ANON_KEY=
|
||||
SUPABASE_SERVICE_ROLE_KEY=
|
||||
NEXT_PUBLIC_SUPABASE_URL=
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||
|
||||
########################################
|
||||
# Entitlement / dashboard
|
||||
########################################
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||
POLYWEATHER_DASHBOARD_ACCESS_TOKEN=
|
||||
|
||||
########################################
|
||||
# Meteoblue / third-party APIs
|
||||
########################################
|
||||
METEOBLUE_API_KEY=
|
||||
|
||||
########################################
|
||||
# Wallet / payments
|
||||
########################################
|
||||
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
|
||||
POLYWEATHER_PAYMENT_RECEIVER_CONTRACT=
|
||||
POLYWEATHER_PAYMENT_ACCEPTED_TOKENS_JSON=
|
||||
POLYWEATHER_PAYMENT_PLAN_CATALOG_JSON=
|
||||
|
||||
########################################
|
||||
# Optional exchange / market secrets
|
||||
########################################
|
||||
POLYMARKET_API_KEY=
|
||||
POLYMARKET_SECRET_KEY=
|
||||
POLYMARKET_PASSPHRASE=
|
||||
POLYMARKET_WALLET_ADDRESS=
|
||||
@@ -127,6 +127,8 @@ docker compose logs -f polyweather | egrep "polymarket wallet activity watcher s
|
||||
- 商业化说明:[docs/COMMERCIALIZATION.md](docs/COMMERCIALIZATION.md)
|
||||
- Open-Core 边界:[docs/OPEN_CORE_POLICY.md](docs/OPEN_CORE_POLICY.md)
|
||||
- Supabase 接入:[docs/SUPABASE_SETUP_ZH.md](docs/SUPABASE_SETUP_ZH.md)
|
||||
- 配置与密钥管理:[docs/CONFIGURATION_ZH.md](docs/CONFIGURATION_ZH.md)
|
||||
- 前端部署(Vercel):[docs/FRONTEND_DEPLOYMENT_ZH.md](docs/FRONTEND_DEPLOYMENT_ZH.md)
|
||||
- 技术债(中文镜像):[docs/TECH_DEBT_ZH.md](docs/TECH_DEBT_ZH.md)
|
||||
- 技术债(主文档):[docs/TECH_DEBT.md](docs/TECH_DEBT.md)
|
||||
- 支付合约验证:[docs/payments/POLYGONSCAN_VERIFY.md](docs/payments/POLYGONSCAN_VERIFY.md)
|
||||
|
||||
+5
-1
@@ -7,11 +7,13 @@ services:
|
||||
- .env
|
||||
volumes:
|
||||
# Persist runtime data outside git workspace.
|
||||
# Host path defaults to /var/lib/polyweather and can be overridden by POLYWEATHER_RUNTIME_DATA_DIR.
|
||||
# Host path defaults to /var/lib/polyweather and can be overridden in .env.
|
||||
- ${POLYWEATHER_RUNTIME_DATA_DIR:-/var/lib/polyweather}:/var/lib/polyweather
|
||||
# Keep /app/data compatibility for existing cache/state defaults.
|
||||
- ${POLYWEATHER_RUNTIME_DATA_DIR:-/var/lib/polyweather}:/app/data
|
||||
- ./bot.log:/app/bot.log # 挂载日志文件
|
||||
# UID/GID are mainly useful on Linux hosts to avoid root-owned output files.
|
||||
# Windows / macOS can usually keep the fallback values.
|
||||
user: "${UID:-1000}:${GID:-1000}"
|
||||
|
||||
polyweather_web:
|
||||
@@ -22,8 +24,10 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
# Web service shares the same runtime data directory as bot/state tasks.
|
||||
- ${POLYWEATHER_RUNTIME_DATA_DIR:-/var/lib/polyweather}:/var/lib/polyweather
|
||||
- ${POLYWEATHER_RUNTIME_DATA_DIR:-/var/lib/polyweather}:/app/data
|
||||
ports:
|
||||
- "8000:8000"
|
||||
# UID/GID are mainly useful on Linux hosts to avoid root-owned output files.
|
||||
user: "${UID:-1000}:${GID:-1000}"
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
# 配置与密钥管理(中文)
|
||||
|
||||
## 1. 目标
|
||||
|
||||
PolyWeather 的环境变量很多,但不是所有变量都属于同一层级。
|
||||
|
||||
当前推荐做法是把配置拆成三类:
|
||||
|
||||
1. 可复现基础配置
|
||||
放在:[.env.example](/E:/web/PolyWeather/.env.example)
|
||||
|
||||
2. 敏感密钥模板
|
||||
放在:[.env.secrets.example](/E:/web/PolyWeather/.env.secrets.example)
|
||||
|
||||
3. 平台侧真实密钥
|
||||
放在:
|
||||
- VPS / Docker `.env`
|
||||
- Vercel Environment Variables
|
||||
- GitHub Secrets(如需要)
|
||||
|
||||
## 2. 为什么要拆
|
||||
|
||||
如果把所有变量都平铺在一个 `.env` 里,会有三个问题:
|
||||
|
||||
1. 新环境很难知道“最小启动到底需要哪些变量”
|
||||
2. 敏感密钥和普通开关混在一起,容易误泄露
|
||||
3. 调优参数太多时,团队很难区分“必须填”和“保持默认即可”
|
||||
|
||||
所以正确做法不是“减少变量数量”,而是:
|
||||
|
||||
- 保留变量能力
|
||||
- 按职责分层
|
||||
- 给出最小启动路径
|
||||
|
||||
## 3. 文件职责
|
||||
|
||||
### 3.1 根 `.env.example`
|
||||
|
||||
文件:
|
||||
|
||||
- [.env.example](/E:/web/PolyWeather/.env.example)
|
||||
|
||||
用途:
|
||||
|
||||
- 后端 / Bot / Docker 的可复现配置模板
|
||||
- 只放变量名、默认值、开关与非敏感示例
|
||||
|
||||
### 3.2 根 `.env.secrets.example`
|
||||
|
||||
文件:
|
||||
|
||||
- [.env.secrets.example](/E:/web/PolyWeather/.env.secrets.example)
|
||||
|
||||
用途:
|
||||
|
||||
- 只列敏感项
|
||||
- 帮助运维明确哪些值必须从密钥系统注入
|
||||
|
||||
### 3.3 前端 `.env.example`
|
||||
|
||||
文件:
|
||||
|
||||
- [frontend/.env.example](/E:/web/PolyWeather/frontend/.env.example)
|
||||
|
||||
用途:
|
||||
|
||||
- 前端本地开发与 Vercel 环境变量模板
|
||||
|
||||
## 4. 配置分级
|
||||
|
||||
### 4.1 L1:最小启动必需项
|
||||
|
||||
这是“服务能跑起来”的最小集合。
|
||||
|
||||
后端 / Bot:
|
||||
|
||||
- `TELEGRAM_BOT_TOKEN`
|
||||
- `TELEGRAM_CHAT_ID`
|
||||
- `POLYWEATHER_RUNTIME_DATA_DIR`
|
||||
- `POLYWEATHER_DB_PATH`
|
||||
|
||||
前端:
|
||||
|
||||
- `POLYWEATHER_API_BASE_URL`
|
||||
|
||||
如果启用登录:
|
||||
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
- `SUPABASE_URL`
|
||||
- `SUPABASE_ANON_KEY`
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`
|
||||
|
||||
### 4.2 L2:功能开关
|
||||
|
||||
这些变量一般不敏感,但会决定功能是否启用。
|
||||
|
||||
例如:
|
||||
|
||||
- `POLYWEATHER_AUTH_ENABLED`
|
||||
- `POLYWEATHER_AUTH_REQUIRED`
|
||||
- `POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION`
|
||||
- `POLYWEATHER_PAYMENT_ENABLED`
|
||||
- `POLYMARKET_MARKET_SCAN_ENABLED`
|
||||
- `POLYGON_WALLET_WATCH_ENABLED`
|
||||
- `POLYMARKET_WALLET_ACTIVITY_ENABLED`
|
||||
|
||||
### 4.3 L3:运行调优项
|
||||
|
||||
这些一般不需要在第一天就改。
|
||||
|
||||
例如:
|
||||
|
||||
- 各类 `*_TTL_SEC`
|
||||
- 各类 `*_TIMEOUT_SEC`
|
||||
- 各类 `*_COOLDOWN_SEC`
|
||||
- 各类 `*_INTERVAL_SEC`
|
||||
|
||||
策略:
|
||||
|
||||
- 先用默认值
|
||||
- 出现性能或运维问题时再调
|
||||
|
||||
### 4.4 L4:敏感项
|
||||
|
||||
这些变量不应写进公开文档截图,也不应提交到仓库。
|
||||
|
||||
例如:
|
||||
|
||||
- `TELEGRAM_BOT_TOKEN`
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`
|
||||
- `POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN`
|
||||
- `POLYWEATHER_DASHBOARD_ACCESS_TOKEN`
|
||||
- `METEOBLUE_API_KEY`
|
||||
- `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID`
|
||||
- `POLYMARKET_SECRET_KEY`
|
||||
|
||||
## 5. 推荐部署矩阵
|
||||
|
||||
### 5.1 VPS / Docker(后端 + Bot)
|
||||
|
||||
建议放这些:
|
||||
|
||||
- 根 `.env` 的后端项
|
||||
- 所有 secrets
|
||||
- Bot / 支付 / watcher 配置
|
||||
|
||||
### 5.2 Vercel(前端)
|
||||
|
||||
建议只放前端真正需要的变量:
|
||||
|
||||
- `POLYWEATHER_API_BASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
- `POLYWEATHER_AUTH_ENABLED`
|
||||
- `POLYWEATHER_AUTH_REQUIRED`
|
||||
- `POLYWEATHER_DASHBOARD_ACCESS_TOKEN`
|
||||
- `POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN`
|
||||
- `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID`
|
||||
- `NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL`
|
||||
|
||||
不要把后端专用密钥全搬进 Vercel。
|
||||
|
||||
### 5.3 GitHub Actions
|
||||
|
||||
当前 CI 不需要大规模 secrets。
|
||||
|
||||
如果未来要做自动部署,再考虑:
|
||||
|
||||
- `VERCEL_TOKEN`
|
||||
- `VERCEL_ORG_ID`
|
||||
- `VERCEL_PROJECT_ID`
|
||||
|
||||
## 6. 最小部署示例
|
||||
|
||||
### 6.1 前端最小变量
|
||||
|
||||
```env
|
||||
POLYWEATHER_API_BASE_URL=https://your-backend.example.com
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
|
||||
POLYWEATHER_AUTH_ENABLED=true
|
||||
POLYWEATHER_AUTH_REQUIRED=true
|
||||
```
|
||||
|
||||
### 6.2 后端最小变量
|
||||
|
||||
```env
|
||||
TELEGRAM_BOT_TOKEN=...
|
||||
TELEGRAM_CHAT_ID=...
|
||||
POLYWEATHER_RUNTIME_DATA_DIR=/var/lib/polyweather
|
||||
POLYWEATHER_DB_PATH=/var/lib/polyweather/polyweather.db
|
||||
UID=1000
|
||||
GID=1000
|
||||
POLYWEATHER_AUTH_ENABLED=true
|
||||
POLYWEATHER_AUTH_REQUIRED=false
|
||||
SUPABASE_URL=https://your-project.supabase.co
|
||||
SUPABASE_ANON_KEY=...
|
||||
SUPABASE_SERVICE_ROLE_KEY=...
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=...
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `UID` / `GID` 主要给 Linux Docker 主机用,避免容器把运行文件写成 root 所有。
|
||||
- Windows / macOS 一般可以直接保留默认值。
|
||||
- `POLYWEATHER_RUNTIME_DATA_DIR` 建议放在仓库外,例如 `/var/lib/polyweather`。
|
||||
- `docker-compose.yml` 会把这个目录同时挂载到容器内的 `/var/lib/polyweather` 和 `/app/data`,兼容现有缓存与 SQLite 路径。
|
||||
|
||||
## 7. 当前建议的运维规则
|
||||
|
||||
### 7.1 仓库中允许存在
|
||||
|
||||
- `.env.example`
|
||||
- `.env.secrets.example`
|
||||
- `frontend/.env.example`
|
||||
|
||||
### 7.2 仓库中不应提交
|
||||
|
||||
- `.env`
|
||||
- `.env.local`
|
||||
- 任何带真实 token / key 的配置文件
|
||||
|
||||
### 7.3 截图与共享规则
|
||||
|
||||
以下值一旦出现在截图或聊天里,建议视为泄露并轮换:
|
||||
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`
|
||||
- `POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN`
|
||||
- `TELEGRAM_BOT_TOKEN`
|
||||
- 第三方私有 API Key
|
||||
|
||||
## 8. 如何收口配置复杂度
|
||||
|
||||
如果你觉得变量仍然太多,正确的做法不是一刀删掉,而是:
|
||||
|
||||
1. 把“功能开关”和“调优参数”分开看
|
||||
2. 保持 `.env.example` 中:
|
||||
- 最小启动项
|
||||
- 常用功能开关
|
||||
- 默认调优值
|
||||
3. 让不常改的高阶参数继续留默认
|
||||
|
||||
也就是说:
|
||||
|
||||
- 使用者只需要先关心 10-20 个关键变量
|
||||
- 其余变量保持默认即可
|
||||
|
||||
## 9. 推荐的下一步
|
||||
|
||||
当前已经完成:
|
||||
|
||||
1. 根 `.env.example` 收口
|
||||
2. `.env.secrets.example` 新增
|
||||
3. 本文档新增
|
||||
4. 运行时配置校验脚本新增
|
||||
|
||||
## 10. 配置校验命令
|
||||
|
||||
在不启动服务的情况下,你可以直接检查配置:
|
||||
|
||||
检查 Web:
|
||||
|
||||
```bash
|
||||
python scripts/validate_runtime_env.py --component web
|
||||
```
|
||||
|
||||
检查 Bot:
|
||||
|
||||
```bash
|
||||
python scripts/validate_runtime_env.py --component bot
|
||||
```
|
||||
|
||||
返回规则:
|
||||
|
||||
- 退出码 `0`:当前配置通过
|
||||
- 退出码 `1`:当前配置存在关键缺失
|
||||
|
||||
如果某个功能已启用但缺关键变量,脚本会直接报错。
|
||||
|
||||
## 11. 推荐的下一步
|
||||
|
||||
后续最值得继续做的是:
|
||||
|
||||
1. 在 GitHub / Vercel / VPS 三侧固化同一套变量命名
|
||||
2. 给生产部署增加一次性配置审计清单
|
||||
@@ -0,0 +1,178 @@
|
||||
# 前端部署配置(Vercel)
|
||||
|
||||
本文只覆盖 `frontend` 目录对应的 Next.js 前端部署。
|
||||
|
||||
## 一、部署目标
|
||||
|
||||
推荐方案:
|
||||
|
||||
1. GitHub Actions 负责 `CI`
|
||||
2. Vercel 负责前端 `CD`
|
||||
3. FastAPI 后端单独部署在 VPS / Docker 主机
|
||||
|
||||
前端本身不直接访问天气源,而是通过 Next Route Handlers 转发到后端:
|
||||
|
||||
1. 浏览器 -> Vercel 上的 Next.js 前端
|
||||
2. Next `/api/*` -> `POLYWEATHER_API_BASE_URL`
|
||||
3. FastAPI 后端 -> 分析 / 支付 / 鉴权服务
|
||||
|
||||
## 二、Vercel 项目设置
|
||||
|
||||
在 Vercel 导入 GitHub 仓库后,使用下面的设置:
|
||||
|
||||
- Framework Preset: `Next.js`
|
||||
- Root Directory: `frontend`
|
||||
- Build Command: `npm run build`
|
||||
- Install Command: `npm install`
|
||||
|
||||
如果仓库已经连接过 Vercel,通常只需要确认 `Root Directory` 仍然是 `frontend`。
|
||||
|
||||
## 三、最小必填环境变量
|
||||
|
||||
只部署天气看板和基础登录时,先填下面 4 项:
|
||||
|
||||
```env
|
||||
POLYWEATHER_API_BASE_URL=https://<your-fastapi-host>
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://<your-project>.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-anon-key>
|
||||
POLYWEATHER_AUTH_ENABLED=true
|
||||
```
|
||||
|
||||
建议显式补:
|
||||
|
||||
```env
|
||||
POLYWEATHER_AUTH_REQUIRED=true
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `POLYWEATHER_API_BASE_URL`:前端所有 `/api/*` Route Handler 转发时依赖它,没填会直接返回 500。
|
||||
- `NEXT_PUBLIC_SUPABASE_URL` / `NEXT_PUBLIC_SUPABASE_ANON_KEY`:Supabase 客户端依赖它们。
|
||||
- `POLYWEATHER_AUTH_ENABLED`:关闭时,前端不会启用登录能力。
|
||||
- `POLYWEATHER_AUTH_REQUIRED`:控制 middleware 是否强制登录。
|
||||
|
||||
## 四、按功能启用的可选环境变量
|
||||
|
||||
### 1. 分享式看板
|
||||
|
||||
```env
|
||||
POLYWEATHER_DASHBOARD_ACCESS_TOKEN=
|
||||
```
|
||||
|
||||
设置后,可通过 `/?access_token=<token>` 打开带令牌的看板入口。
|
||||
|
||||
### 2. 前后端 entitlement 校验
|
||||
|
||||
```env
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||
```
|
||||
|
||||
仅当后端开启 entitlement / 订阅校验时需要。
|
||||
|
||||
### 3. 钱包支付
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
|
||||
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
|
||||
```
|
||||
|
||||
如果不启用钱包支付,可以留空。
|
||||
|
||||
### 4. Telegram 入口
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_TELEGRAM_GROUP_URL=https://t.me/<your_group>
|
||||
NEXT_PUBLIC_TELEGRAM_BOT_URL=https://t.me/WeatherQuant_bot
|
||||
```
|
||||
|
||||
只影响按钮跳转,不影响核心页面加载。
|
||||
|
||||
## 五、推荐的三套配置口径
|
||||
|
||||
### 1. 公开游客模式
|
||||
|
||||
```env
|
||||
POLYWEATHER_API_BASE_URL=https://api.example.com
|
||||
POLYWEATHER_AUTH_ENABLED=false
|
||||
POLYWEATHER_AUTH_REQUIRED=false
|
||||
```
|
||||
|
||||
适合公开演示站。
|
||||
|
||||
### 2. 正常登录模式
|
||||
|
||||
```env
|
||||
POLYWEATHER_API_BASE_URL=https://api.example.com
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
|
||||
POLYWEATHER_AUTH_ENABLED=true
|
||||
POLYWEATHER_AUTH_REQUIRED=true
|
||||
```
|
||||
|
||||
适合正式前端站点。
|
||||
|
||||
### 3. 登录 + entitlement 联动
|
||||
|
||||
```env
|
||||
POLYWEATHER_API_BASE_URL=https://api.example.com
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://<project>.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
|
||||
POLYWEATHER_AUTH_ENABLED=true
|
||||
POLYWEATHER_AUTH_REQUIRED=true
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=<shared-token>
|
||||
```
|
||||
|
||||
适合前后端都启用了会员/订阅保护的生产环境。
|
||||
|
||||
## 六、不要放进 Vercel 的变量
|
||||
|
||||
这些属于后端私密配置,不应该放到前端项目:
|
||||
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`
|
||||
- `TELEGRAM_BOT_TOKEN`
|
||||
- `POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN` 以外的后端 secret
|
||||
- 支付签名私钥 / 交易私钥 / 任何 bot 凭据
|
||||
|
||||
特别注意:
|
||||
|
||||
- `NEXT_PUBLIC_*` 会暴露给浏览器
|
||||
- 只有明确允许前端公开使用的值,才应加 `NEXT_PUBLIC_`
|
||||
|
||||
## 七、上线前检查
|
||||
|
||||
Vercel 部署前至少确认:
|
||||
|
||||
1. `POLYWEATHER_API_BASE_URL` 指向可访问的后端生产地址
|
||||
2. `frontend/.env.example` 和 Vercel Project Settings 中的实际值一致
|
||||
3. GitHub Actions 中 `frontend-quality` 已通过
|
||||
4. 如果启用鉴权,Supabase redirect URL 已包含前端域名
|
||||
|
||||
## 八、常见问题
|
||||
|
||||
### 1. 页面打开后 API 全部 500
|
||||
|
||||
先检查:
|
||||
|
||||
```env
|
||||
POLYWEATHER_API_BASE_URL
|
||||
```
|
||||
|
||||
这是最常见原因。
|
||||
|
||||
### 2. Vercel 构建通过,但登录失败
|
||||
|
||||
先检查:
|
||||
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
|
||||
- Supabase 项目里的站点 URL / redirect URL
|
||||
|
||||
### 3. 钱包入口显示未配置
|
||||
|
||||
先检查:
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID
|
||||
```
|
||||
|
||||
这是钱包连接的必需项。
|
||||
+26
-5
@@ -1,12 +1,33 @@
|
||||
# PolyWeather 前端最小配置(本地 / Vercel)
|
||||
# 只部署天气看板时,先填下面 4 项即可。
|
||||
|
||||
# 必填:后端 FastAPI 基础地址
|
||||
POLYWEATHER_API_BASE_URL=http://127.0.0.1:8000
|
||||
# Supabase Auth (Google + Email)
|
||||
|
||||
# 必填:Supabase 前端公钥(鉴权开启时必须)
|
||||
NEXT_PUBLIC_SUPABASE_URL=
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||
|
||||
# 常用:前端鉴权开关
|
||||
# true: 启用 Supabase 登录
|
||||
# false: 关闭登录能力,访客模式
|
||||
POLYWEATHER_AUTH_ENABLED=false
|
||||
# If true: middleware forces login; if false: optional login mode (guests allowed).
|
||||
|
||||
# 常用:是否强制登录
|
||||
# true: middleware 强制登录后才能访问主页面
|
||||
# false: 登录可选,访客可浏览
|
||||
POLYWEATHER_AUTH_REQUIRED=false
|
||||
# Optional dashboard guard (Next.js middleware)
|
||||
# If set, open dashboard with: /?access_token=<token>
|
||||
|
||||
# 可选:分享式看板访问令牌
|
||||
# 设置后,可通过 /?access_token=<token> 打开受保护看板
|
||||
POLYWEATHER_DASHBOARD_ACCESS_TOKEN=
|
||||
# Shared secret forwarded by Next API routes to backend
|
||||
|
||||
# 可选:前端 API Route 转发到后端时附带的共享令牌
|
||||
# 仅当后端启用了 entitlement / 订阅校验时需要
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||
|
||||
# 可选:钱包支付 / Telegram 入口
|
||||
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
|
||||
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
|
||||
NEXT_PUBLIC_TELEGRAM_GROUP_URL=https://t.me/your_group
|
||||
NEXT_PUBLIC_TELEGRAM_BOT_URL=https://t.me/WeatherQuant_bot
|
||||
+34
-11
@@ -28,30 +28,53 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 必需环境变量
|
||||
## Vercel 最小部署配置
|
||||
|
||||
只跑看板和基础鉴权时,先填这 4 项:
|
||||
|
||||
```env
|
||||
POLYWEATHER_API_BASE_URL=https://<your-fastapi-host>
|
||||
NEXT_PUBLIC_SUPABASE_URL=
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://<your-supabase-project>.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-anon-key>
|
||||
POLYWEATHER_AUTH_ENABLED=true
|
||||
POLYWEATHER_AUTH_REQUIRED=false
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||
```
|
||||
|
||||
WalletConnect:
|
||||
建议显式补:
|
||||
|
||||
```env
|
||||
POLYWEATHER_AUTH_REQUIRED=true
|
||||
```
|
||||
|
||||
如果你只是开放游客浏览,可改成:
|
||||
|
||||
```env
|
||||
POLYWEATHER_AUTH_ENABLED=false
|
||||
POLYWEATHER_AUTH_REQUIRED=false
|
||||
```
|
||||
|
||||
## 可选环境变量
|
||||
|
||||
仅在对应功能启用时填写:
|
||||
|
||||
```env
|
||||
# 看板分享令牌
|
||||
POLYWEATHER_DASHBOARD_ACCESS_TOKEN=
|
||||
|
||||
# 前端 API 转发到后端时使用的共享令牌
|
||||
POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN=
|
||||
|
||||
# 钱包支付
|
||||
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=
|
||||
NEXT_PUBLIC_WALLETCONNECT_POLYGON_RPC_URL=https://polygon-bor-rpc.publicnode.com
|
||||
```
|
||||
|
||||
浮层链接:
|
||||
|
||||
```env
|
||||
# 社群入口
|
||||
NEXT_PUBLIC_TELEGRAM_GROUP_URL=https://t.me/<your_group>
|
||||
NEXT_PUBLIC_TELEGRAM_BOT_URL=https://t.me/WeatherQuant_bot
|
||||
```
|
||||
|
||||
更完整的 Vercel 配置说明见:
|
||||
- [docs/FRONTEND_DEPLOYMENT_ZH.md](/E:/web/PolyWeather/docs/FRONTEND_DEPLOYMENT_ZH.md)
|
||||
|
||||
## 路由处理器
|
||||
|
||||
天气:
|
||||
@@ -90,4 +113,4 @@ NEXT_PUBLIC_TELEGRAM_GROUP_URL=https://t.me/<your_group>
|
||||
|
||||
详见根目录策略文档:`docs/OPEN_CORE_POLICY.md`
|
||||
|
||||
最后更新:`2026-03-14`
|
||||
最后更新:`2026-03-20`
|
||||
@@ -0,0 +1,28 @@
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if PROJECT_ROOT not in sys.path:
|
||||
sys.path.insert(0, PROJECT_ROOT)
|
||||
|
||||
from src.utils.config_validation import validate_runtime_env # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Validate PolyWeather runtime environment variables.")
|
||||
parser.add_argument(
|
||||
"--component",
|
||||
choices=["bot", "web"],
|
||||
default="web",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
report = validate_runtime_env(args.component)
|
||||
print(json.dumps(report.as_dict(), ensure_ascii=False, indent=2))
|
||||
raise SystemExit(0 if report.ok else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -16,6 +16,7 @@ from src.bot.io_layer import BotIOLayer
|
||||
from src.bot.runtime_coordinator import StartupCoordinator
|
||||
from src.bot.services.city_command_service import CityCommandService
|
||||
from src.bot.services.deb_command_service import DebCommandService
|
||||
from src.utils.config_validation import validate_or_raise
|
||||
from src.utils.telegram_chat_ids import get_telegram_chat_ids_from_env
|
||||
|
||||
|
||||
@@ -58,6 +59,7 @@ def start_bot() -> None:
|
||||
from src.database.db_manager import DBManager
|
||||
from src.utils.config_loader import load_config
|
||||
|
||||
validate_or_raise("bot")
|
||||
config = load_config()
|
||||
token = os.getenv("TELEGRAM_BOT_TOKEN")
|
||||
if not token:
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Iterable, List
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
TRUTHY = {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _env(name: str) -> str:
|
||||
return str(os.getenv(name) or "").strip()
|
||||
|
||||
|
||||
def _env_bool(name: str, default: bool = False) -> bool:
|
||||
raw = _env(name)
|
||||
if not raw:
|
||||
return default
|
||||
return raw.lower() in TRUTHY
|
||||
|
||||
|
||||
def _has(name: str) -> bool:
|
||||
value = _env(name)
|
||||
if not value:
|
||||
return False
|
||||
lowered = value.lower()
|
||||
return "your_" not in lowered and lowered not in {"changeme", "example"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConfigValidationReport:
|
||||
component: str
|
||||
errors: List[str] = field(default_factory=list)
|
||||
warnings: List[str] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def ok(self) -> bool:
|
||||
return not self.errors
|
||||
|
||||
def as_dict(self) -> Dict[str, object]:
|
||||
return {
|
||||
"component": self.component,
|
||||
"ok": self.ok,
|
||||
"errors": list(self.errors),
|
||||
"warnings": list(self.warnings),
|
||||
}
|
||||
|
||||
|
||||
def _missing(required_keys: Iterable[str]) -> List[str]:
|
||||
return [key for key in required_keys if not _has(key)]
|
||||
|
||||
|
||||
def validate_runtime_env(
|
||||
component: str,
|
||||
*,
|
||||
load_env_file: bool = True,
|
||||
) -> ConfigValidationReport:
|
||||
if load_env_file:
|
||||
load_dotenv()
|
||||
component_key = str(component or "").strip().lower()
|
||||
report = ConfigValidationReport(component=component_key or "unknown")
|
||||
|
||||
auth_enabled = _env_bool("POLYWEATHER_AUTH_ENABLED", False)
|
||||
auth_required = _env_bool("POLYWEATHER_AUTH_REQUIRED", auth_enabled)
|
||||
auth_require_subscription = _env_bool(
|
||||
"POLYWEATHER_AUTH_REQUIRE_SUBSCRIPTION",
|
||||
False,
|
||||
)
|
||||
entitlement_guard = _env_bool("POLYWEATHER_REQUIRE_ENTITLEMENT", False)
|
||||
payment_enabled = _env_bool("POLYWEATHER_PAYMENT_ENABLED", False)
|
||||
weekly_reward_enabled = _env_bool("POLYWEATHER_WEEKLY_REWARD_ENABLED", False)
|
||||
wallet_activity_enabled = _env_bool("POLYMARKET_WALLET_ACTIVITY_ENABLED", False)
|
||||
polygon_watch_enabled = _env_bool("POLYGON_WALLET_WATCH_ENABLED", False)
|
||||
|
||||
if component_key == "bot":
|
||||
missing = _missing(["TELEGRAM_BOT_TOKEN"])
|
||||
if missing:
|
||||
report.errors.append(f"Bot 启动缺少必填变量: {', '.join(missing)}")
|
||||
if not (_has("TELEGRAM_CHAT_ID") or _has("TELEGRAM_CHAT_IDS")):
|
||||
report.warnings.append("未配置 TELEGRAM_CHAT_ID / TELEGRAM_CHAT_IDS,机器人推送目标为空")
|
||||
|
||||
if auth_enabled:
|
||||
missing = _missing(["SUPABASE_URL", "SUPABASE_ANON_KEY"])
|
||||
if missing:
|
||||
report.errors.append(f"已启用鉴权,但缺少变量: {', '.join(missing)}")
|
||||
if auth_required or auth_require_subscription or weekly_reward_enabled:
|
||||
missing = _missing(["SUPABASE_SERVICE_ROLE_KEY"])
|
||||
if missing:
|
||||
report.errors.append(f"当前鉴权/订阅能力需要变量: {', '.join(missing)}")
|
||||
|
||||
if entitlement_guard:
|
||||
missing = _missing(["POLYWEATHER_BACKEND_ENTITLEMENT_TOKEN"])
|
||||
if missing:
|
||||
report.errors.append(f"已启用 backend entitlement guard,但缺少变量: {', '.join(missing)}")
|
||||
|
||||
if payment_enabled:
|
||||
payment_missing = _missing(["POLYWEATHER_PAYMENT_RPC_URL"])
|
||||
if payment_missing:
|
||||
report.errors.append(f"已启用支付,但缺少变量: {', '.join(payment_missing)}")
|
||||
has_receiver = _has("POLYWEATHER_PAYMENT_RECEIVER_CONTRACT")
|
||||
has_tokens_json = _has("POLYWEATHER_PAYMENT_ACCEPTED_TOKENS_JSON")
|
||||
if not (has_receiver or has_tokens_json):
|
||||
report.errors.append(
|
||||
"已启用支付,但未配置 POLYWEATHER_PAYMENT_RECEIVER_CONTRACT 或 POLYWEATHER_PAYMENT_ACCEPTED_TOKENS_JSON"
|
||||
)
|
||||
|
||||
if wallet_activity_enabled:
|
||||
if not _has("POLYMARKET_WALLET_ACTIVITY_USERS"):
|
||||
report.warnings.append("已启用 wallet activity watcher,但未配置 POLYMARKET_WALLET_ACTIVITY_USERS")
|
||||
|
||||
if polygon_watch_enabled:
|
||||
if not _has("POLYGON_WALLET_WATCH_ADDRESSES"):
|
||||
report.warnings.append("已启用 polygon watcher,但未配置 POLYGON_WALLET_WATCH_ADDRESSES")
|
||||
|
||||
if component_key == "web":
|
||||
if auth_enabled and not auth_required:
|
||||
report.warnings.append("当前 Web 鉴权为 optional 模式,未强制登录")
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def validate_or_raise(component: str) -> ConfigValidationReport:
|
||||
report = validate_runtime_env(component, load_env_file=True)
|
||||
if report.errors:
|
||||
raise RuntimeError(" | ".join(report.errors))
|
||||
return report
|
||||
@@ -0,0 +1,31 @@
|
||||
from src.utils.config_validation import validate_runtime_env
|
||||
|
||||
|
||||
def test_validate_runtime_env_bot_requires_telegram_token(monkeypatch):
|
||||
monkeypatch.delenv("TELEGRAM_BOT_TOKEN", raising=False)
|
||||
report = validate_runtime_env("bot", load_env_file=False)
|
||||
assert not report.ok
|
||||
assert any("TELEGRAM_BOT_TOKEN" in err for err in report.errors)
|
||||
|
||||
|
||||
def test_validate_runtime_env_web_auth_requires_supabase(monkeypatch):
|
||||
monkeypatch.setenv("POLYWEATHER_AUTH_ENABLED", "true")
|
||||
monkeypatch.delenv("SUPABASE_URL", raising=False)
|
||||
monkeypatch.delenv("SUPABASE_ANON_KEY", raising=False)
|
||||
|
||||
report = validate_runtime_env("web", load_env_file=False)
|
||||
|
||||
assert not report.ok
|
||||
assert any("SUPABASE_URL" in err for err in report.errors)
|
||||
|
||||
|
||||
def test_validate_runtime_env_payment_requires_receiver_or_tokens(monkeypatch):
|
||||
monkeypatch.setenv("POLYWEATHER_PAYMENT_ENABLED", "true")
|
||||
monkeypatch.setenv("POLYWEATHER_PAYMENT_RPC_URL", "https://polygon-rpc.com")
|
||||
monkeypatch.delenv("POLYWEATHER_PAYMENT_RECEIVER_CONTRACT", raising=False)
|
||||
monkeypatch.delenv("POLYWEATHER_PAYMENT_ACCEPTED_TOKENS_JSON", raising=False)
|
||||
|
||||
report = validate_runtime_env("web", load_env_file=False)
|
||||
|
||||
assert not report.ok
|
||||
assert any("POLYWEATHER_PAYMENT_RECEIVER_CONTRACT" in err for err in report.errors)
|
||||
@@ -11,6 +11,7 @@ from pydantic import BaseModel, Field
|
||||
from loguru import logger
|
||||
|
||||
from src.utils.config_loader import load_config
|
||||
from src.utils.config_validation import validate_runtime_env
|
||||
from src.data_collection.weather_sources import WeatherDataCollector
|
||||
from src.data_collection.city_risk_profiles import CITY_RISK_PROFILES # noqa: F401
|
||||
from src.data_collection.polymarket_readonly import PolymarketReadOnlyLayer
|
||||
@@ -34,6 +35,11 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
_config = load_config()
|
||||
_config_validation = validate_runtime_env("web")
|
||||
for _warning in _config_validation.warnings:
|
||||
logger.warning(f"[config:web] {_warning}")
|
||||
if _config_validation.errors:
|
||||
raise RuntimeError(" | ".join(_config_validation.errors))
|
||||
_weather = WeatherDataCollector(_config)
|
||||
_market_layer = PolymarketReadOnlyLayer()
|
||||
_account_db = DBManager()
|
||||
|
||||
Reference in New Issue
Block a user