Stop deploy from overwriting manual FORCE_CLOSE settings.

Apply force-close defaults only when keys are missing so system-settings toggles survive pull_and_restart.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-15 11:54:31 +08:00
parent 1e765f4b2b
commit 8d2da921ee
3 changed files with 54 additions and 12 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ git pull
echo ">>> sync common trading env (binance + okx missing keys)"
python3 scripts/sync_common_trading_env.py "${DRY[@]}"
echo ">>> force-close policy: gate=ON, binance/okx=OFF"
echo ">>> force-close defaults only if missing (never overwrite manual)"
python3 scripts/sync_common_trading_env.py --apply-force-close-policy "${DRY[@]}"
if [[ ${#DRY[@]} -gt 0 ]]; then
+12 -11
View File
@@ -54,7 +54,8 @@ SHARED_DEFAULTS: dict[str, str] = {
"AI_TIMEOUT_SECONDS": "120",
}
# 仅 Gate 启用 0 点强制清仓;币安/OKX 须保持关闭
# 仅当某实例 .env 缺少 FORCE_CLOSE_* 时补默认:
# Gate 默认开 0 点强制清仓;币安/OKX 默认关.已有手调值绝不覆盖.
FORCE_CLOSE_POLICY: dict[str, dict[str, str]] = {
"crypto_monitor_gate": {
"FORCE_CLOSE_ENABLED": "true",
@@ -131,7 +132,7 @@ def sync_one(dir_name: str, *, dry_run: bool, force: bool) -> bool:
def apply_force_close_policy(*, dry_run: bool) -> bool:
"""Gate 开启强制清仓;币安/OKX 强制关闭(覆盖已有值)."""
"""仅在 FORCE_CLOSE_* 缺失时补默认值;已有手调值绝不覆盖."""
any_changed = False
for dir_name, values in FORCE_CLOSE_POLICY.items():
path = os.path.join(REPO, dir_name, ".env")
@@ -139,19 +140,19 @@ def apply_force_close_policy(*, dry_run: bool) -> bool:
print(f"skip (no .env): {dir_name}")
continue
lines = _parse_env(path)
changed_keys: list[str] = []
added_keys: list[str] = []
for key, val in values.items():
cur = _env_get(lines, key)
if cur != val:
if cur is None:
lines = _upsert(lines, key, val)
changed_keys.append(key)
if not changed_keys:
print(f"ok (force-close policy): {dir_name}")
added_keys.append(key)
if not added_keys:
print(f"ok (force-close unchanged): {dir_name}")
continue
any_changed = True
print(f"force-close policy: {dir_name}")
for key in changed_keys:
print(f" = {key}={values[key]}")
print(f"force-close fill-missing: {dir_name}")
for key in added_keys:
print(f" + {key}={values[key]}")
if not dry_run:
text = "\n".join(lines).rstrip() + "\n"
with open(path, "w", encoding="utf-8", newline="\n") as f:
@@ -166,7 +167,7 @@ def main() -> None:
ap.add_argument(
"--apply-force-close-policy",
action="store_true",
help="Gate 开启 0 点强制清仓,币安/OKX 强制关闭",
help="仅补全缺失的 FORCE_CLOSE_* 默认值(不覆盖手调)",
)
ap.add_argument(
"--instances",
+41
View File
@@ -0,0 +1,41 @@
"""FORCE_CLOSE 部署策略:只补缺失,不覆盖手调."""
import os
import tempfile
import unittest
from pathlib import Path
from unittest import mock
from scripts import sync_common_trading_env as sync
class TestForceCloseFillMissing(unittest.TestCase):
def test_does_not_overwrite_existing(self):
with tempfile.TemporaryDirectory() as td:
gate = Path(td) / "crypto_monitor_gate"
gate.mkdir()
(gate / ".env").write_text(
"FORCE_CLOSE_ENABLED=false\nFORCE_CLOSE_BJ_HOUR=1\n",
encoding="utf-8",
)
with mock.patch.object(sync, "REPO", td):
changed = sync.apply_force_close_policy(dry_run=False)
self.assertFalse(changed)
text = (gate / ".env").read_text(encoding="utf-8")
self.assertIn("FORCE_CLOSE_ENABLED=false", text)
self.assertIn("FORCE_CLOSE_BJ_HOUR=1", text)
def test_fills_missing_keys(self):
with tempfile.TemporaryDirectory() as td:
gate = Path(td) / "crypto_monitor_gate"
gate.mkdir()
(gate / ".env").write_text("APP_USERNAME=x\n", encoding="utf-8")
with mock.patch.object(sync, "REPO", td):
changed = sync.apply_force_close_policy(dry_run=False)
self.assertTrue(changed)
text = (gate / ".env").read_text(encoding="utf-8")
self.assertIn("FORCE_CLOSE_ENABLED=true", text)
self.assertIn("FORCE_CLOSE_BJ_HOUR=0", text)
if __name__ == "__main__":
unittest.main()