From 613585a6d5e39706c5ca4914e37578c0e56ce0af Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:16:58 +0800 Subject: [PATCH] fix: make quant monitor deployment idempotent Co-Authored-By: Codex --- ops/quant-monitor/scripts/common_env.sh | 6 +- ops/quant-monitor/scripts/deploy_to_vps.sh | 18 +++++- .../tests/test_deploy_scripts.py | 59 +++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 ops/quant-monitor/tests/test_deploy_scripts.py diff --git a/ops/quant-monitor/scripts/common_env.sh b/ops/quant-monitor/scripts/common_env.sh index 1ed1fe30..73fdb3ea 100755 --- a/ops/quant-monitor/scripts/common_env.sh +++ b/ops/quant-monitor/scripts/common_env.sh @@ -4,12 +4,16 @@ set -euo pipefail ROOT="${QUANT_MONITOR_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" AAB_ROOT="${AIAUDIT_BRIDGE_ROOT:-$(cd "$ROOT/../.." && pwd)}" -QPK_ROOT="${QUANT_PLATFORM_KIT_ROOT:-${PROJECTS_ROOT:-$HOME/Projects}/QuantPlatformKit}" +PROJECTS_ROOT="${QUANT_PROJECTS_ROOT:-${PROJECTS_ROOT:-$HOME/Projects}}" +QUANT_PROJECTS_ROOT="$PROJECTS_ROOT" +QPK_ROOT="${QUANT_PLATFORM_KIT_ROOT:-$PROJECTS_ROOT/QuantPlatformKit}" VENV="${QUANT_MONITOR_VENV:-$ROOT/.venv}" export QUANT_MONITOR_ROOT="$ROOT" export AIAUDIT_BRIDGE_ROOT="$AAB_ROOT" export QUANT_PLATFORM_KIT_ROOT="$QPK_ROOT" +export PROJECTS_ROOT +export QUANT_PROJECTS_ROOT if [[ -x "$VENV/bin/python" ]]; then export PATH="$VENV/bin:$PATH" diff --git a/ops/quant-monitor/scripts/deploy_to_vps.sh b/ops/quant-monitor/scripts/deploy_to_vps.sh index 644a1267..6c9f8ac4 100755 --- a/ops/quant-monitor/scripts/deploy_to_vps.sh +++ b/ops/quant-monitor/scripts/deploy_to_vps.sh @@ -26,6 +26,8 @@ rsync -avz -e "ssh -p ${VPS_PORT}" \ --exclude '.git' \ --exclude 'data/' \ --exclude '.venv/' \ + --exclude '__pycache__/' \ + --exclude '*.py[co]' \ "$AAB_ROOT/ops/quant-monitor/" "${VPS_HOST}:${REMOTE_MONITOR}/" echo "[deploy] bootstrap runtime + systemd" @@ -36,7 +38,12 @@ REMOTE_MONITOR="$REMOTE_AAB/ops/quant-monitor" OLD_UNIT="/etc/systemd/system/codex-quant.service" CHAT_ID="" if [[ -f "$OLD_UNIT" ]]; then - CHAT_ID="$(grep -E '^Environment=GLOBAL_TELEGRAM_CHAT_ID=' "$OLD_UNIT" | head -1 | cut -d= -f2- || true)" + CHAT_ID="$( + grep -E '^Environment=GLOBAL_TELEGRAM_CHAT_ID=' "$OLD_UNIT" \ + | head -1 \ + | sed -E 's/^Environment=(GLOBAL_TELEGRAM_CHAT_ID=)+//' \ + || true + )" fi bash "$REMOTE_MONITOR/scripts/setup_vps_runtime.sh" @@ -62,7 +69,14 @@ sudo cp "$REMOTE_MONITOR/systemd/codex-daily-briefing.timer.example" /etc/system sudo systemctl daemon-reload sudo systemctl enable codex-quant.timer codex-daily-briefing.timer sudo systemctl restart codex-quant.timer codex-daily-briefing.timer -sudo systemctl start codex-quant.service || true +if ! sudo systemctl start codex-quant.service; then + monitor_status="$(systemctl show codex-quant.service -p ExecMainStatus --value)" + if [[ "$monitor_status" != "2" ]]; then + echo "[deploy] codex-quant.service failed with unexpected status ${monitor_status}" >&2 + exit 1 + fi + echo "[deploy] codex-quant.service completed with active monitor alerts" >&2 +fi systemctl is-active codex-quant.timer systemctl is-active codex-daily-briefing.timer diff --git a/ops/quant-monitor/tests/test_deploy_scripts.py b/ops/quant-monitor/tests/test_deploy_scripts.py new file mode 100644 index 00000000..a5d2598a --- /dev/null +++ b/ops/quant-monitor/tests/test_deploy_scripts.py @@ -0,0 +1,59 @@ +import os +import subprocess +import tempfile +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +class DeployScriptTests(unittest.TestCase): + def test_common_env_exports_canonical_projects_root(self) -> None: + with tempfile.TemporaryDirectory() as home: + env = os.environ.copy() + for name in ( + "PROJECTS_ROOT", + "QUANT_PROJECTS_ROOT", + "QUANT_PLATFORM_KIT_ROOT", + ): + env.pop(name, None) + env["HOME"] = home + result = subprocess.run( + [ + "bash", + "-c", + ( + f"source {ROOT / 'scripts' / 'common_env.sh'}; " + 'printf "%s\\n%s\\n" "${PROJECTS_ROOT-}" "${QUANT_PROJECTS_ROOT-}"' + ), + ], + env=env, + capture_output=True, + text=True, + check=True, + ) + + expected = str(Path(home) / "Projects") + self.assertEqual(result.stdout.splitlines(), [expected, expected]) + + def test_deploy_script_normalizes_chat_id_and_excludes_bytecode(self) -> None: + script = (ROOT / "scripts" / "deploy_to_vps.sh").read_text(encoding="utf-8") + + self.assertIn( + "s/^Environment=(GLOBAL_TELEGRAM_CHAT_ID=)+//", + script, + ) + self.assertIn("--exclude '__pycache__/'", script) + self.assertIn("--exclude '*.py[co]'", script) + + def test_deploy_script_only_accepts_monitor_alert_exit(self) -> None: + script = (ROOT / "scripts" / "deploy_to_vps.sh").read_text(encoding="utf-8") + + self.assertNotIn("sudo systemctl start codex-quant.service || true", script) + self.assertIn("ExecMainStatus", script) + self.assertIn('if [[ "$monitor_status" != "2" ]]', script) + + +if __name__ == "__main__": + unittest.main()