gitinstall 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitinstall/__init__.py +61 -0
- gitinstall/_sdk.py +541 -0
- gitinstall/academic.py +831 -0
- gitinstall/admin.html +327 -0
- gitinstall/auto_update.py +384 -0
- gitinstall/autopilot.py +349 -0
- gitinstall/badge.py +476 -0
- gitinstall/checkpoint.py +330 -0
- gitinstall/cicd.py +499 -0
- gitinstall/clawhub.html +718 -0
- gitinstall/config_schema.py +353 -0
- gitinstall/db.py +984 -0
- gitinstall/db_backend.py +445 -0
- gitinstall/dep_chain.py +337 -0
- gitinstall/dependency_audit.py +1153 -0
- gitinstall/detector.py +542 -0
- gitinstall/doctor.py +493 -0
- gitinstall/education.py +869 -0
- gitinstall/enterprise.py +802 -0
- gitinstall/error_fixer.py +953 -0
- gitinstall/event_bus.py +251 -0
- gitinstall/executor.py +577 -0
- gitinstall/feature_flags.py +138 -0
- gitinstall/fetcher.py +921 -0
- gitinstall/huggingface.py +922 -0
- gitinstall/hw_detect.py +988 -0
- gitinstall/i18n.py +664 -0
- gitinstall/installer_registry.py +362 -0
- gitinstall/knowledge_base.py +379 -0
- gitinstall/license_check.py +605 -0
- gitinstall/llm.py +569 -0
- gitinstall/log.py +236 -0
- gitinstall/main.py +1408 -0
- gitinstall/mcp_agent.py +841 -0
- gitinstall/mcp_server.py +386 -0
- gitinstall/monorepo.py +810 -0
- gitinstall/multi_source.py +425 -0
- gitinstall/onboard.py +276 -0
- gitinstall/planner.py +222 -0
- gitinstall/planner_helpers.py +323 -0
- gitinstall/planner_known_projects.py +1010 -0
- gitinstall/planner_templates.py +996 -0
- gitinstall/remote_gpu.py +633 -0
- gitinstall/resilience.py +608 -0
- gitinstall/run_tests.py +572 -0
- gitinstall/skills.py +476 -0
- gitinstall/tool_schemas.py +324 -0
- gitinstall/trending.py +279 -0
- gitinstall/uninstaller.py +415 -0
- gitinstall/validate_top100.py +607 -0
- gitinstall/watchdog.py +180 -0
- gitinstall/web.py +1277 -0
- gitinstall/web_ui.html +2277 -0
- gitinstall-1.1.0.dist-info/METADATA +275 -0
- gitinstall-1.1.0.dist-info/RECORD +59 -0
- gitinstall-1.1.0.dist-info/WHEEL +5 -0
- gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
- gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
- gitinstall-1.1.0.dist-info/top_level.txt +1 -0
gitinstall/watchdog.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
watchdog.py - 安装看门狗系统
|
|
3
|
+
===============================
|
|
4
|
+
|
|
5
|
+
灵感来源:ICE-cluade-SCompany 的 Watchdog 线程
|
|
6
|
+
|
|
7
|
+
监控安装步骤执行,处理以下情况:
|
|
8
|
+
1. 步骤超时:pip install 卡死 → 自动 kill + 切换策略
|
|
9
|
+
2. 资源溢出:检测磁盘/内存不足 → 及时中止
|
|
10
|
+
3. 挂起检测:进程无输出超过 N 秒 → 判定挂起
|
|
11
|
+
|
|
12
|
+
零外部依赖,纯 Python 标准库。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import signal
|
|
20
|
+
import subprocess
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from typing import Optional, Callable
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ── 默认超时配置(秒)──
|
|
28
|
+
DEFAULT_STEP_TIMEOUT = 600 # 单步最大 10 分钟
|
|
29
|
+
DEFAULT_IDLE_TIMEOUT = 120 # 无输出最大 2 分钟
|
|
30
|
+
GIT_CLONE_TIMEOUT = 300 # git clone 最大 5 分钟
|
|
31
|
+
PIP_INSTALL_TIMEOUT = 900 # pip install 最大 15 分钟
|
|
32
|
+
NPM_INSTALL_TIMEOUT = 600 # npm install 最大 10 分钟
|
|
33
|
+
DISK_MIN_MB = 500 # 最少剩余 500 MB
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class WatchdogAlert:
|
|
38
|
+
"""看门狗告警"""
|
|
39
|
+
alert_type: str # timeout, idle, disk_low, memory_high, killed
|
|
40
|
+
step_index: int
|
|
41
|
+
command: str
|
|
42
|
+
message: str
|
|
43
|
+
elapsed_sec: float = 0.0
|
|
44
|
+
threshold_sec: float = 0.0
|
|
45
|
+
action_taken: str = "" # killed, skipped, retried, warned
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class WatchdogConfig:
|
|
50
|
+
"""看门狗配置"""
|
|
51
|
+
step_timeout: int = DEFAULT_STEP_TIMEOUT
|
|
52
|
+
idle_timeout: int = DEFAULT_IDLE_TIMEOUT
|
|
53
|
+
disk_min_mb: int = DISK_MIN_MB
|
|
54
|
+
enabled: bool = True
|
|
55
|
+
on_alert: Optional[Callable] = None # 告警回调
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StepWatchdog:
|
|
59
|
+
"""单步执行看门狗"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, config: WatchdogConfig = None):
|
|
62
|
+
self.config = config or WatchdogConfig()
|
|
63
|
+
self.alerts: list[WatchdogAlert] = []
|
|
64
|
+
self._timer: Optional[threading.Timer] = None
|
|
65
|
+
self._process: Optional[subprocess.Popen] = None
|
|
66
|
+
self._killed = False
|
|
67
|
+
|
|
68
|
+
def get_timeout_for_command(self, command: str) -> int:
|
|
69
|
+
"""根据命令类型智能选择超时时间"""
|
|
70
|
+
cmd_lower = command.lower()
|
|
71
|
+
if "git clone" in cmd_lower or "git pull" in cmd_lower:
|
|
72
|
+
return GIT_CLONE_TIMEOUT
|
|
73
|
+
if "pip install" in cmd_lower or "pip3 install" in cmd_lower:
|
|
74
|
+
return PIP_INSTALL_TIMEOUT
|
|
75
|
+
if "npm install" in cmd_lower or "npm ci" in cmd_lower:
|
|
76
|
+
return NPM_INSTALL_TIMEOUT
|
|
77
|
+
if "cargo build" in cmd_lower:
|
|
78
|
+
return PIP_INSTALL_TIMEOUT # Rust 编译也可能很慢
|
|
79
|
+
if "make" in cmd_lower or "cmake" in cmd_lower:
|
|
80
|
+
return PIP_INSTALL_TIMEOUT
|
|
81
|
+
if "docker" in cmd_lower:
|
|
82
|
+
return PIP_INSTALL_TIMEOUT
|
|
83
|
+
if "conda install" in cmd_lower:
|
|
84
|
+
return PIP_INSTALL_TIMEOUT
|
|
85
|
+
return self.config.step_timeout
|
|
86
|
+
|
|
87
|
+
def check_disk_space(self, path: str = None) -> Optional[WatchdogAlert]:
|
|
88
|
+
"""检查磁盘空间"""
|
|
89
|
+
try:
|
|
90
|
+
target = path or os.path.expanduser("~")
|
|
91
|
+
usage = shutil.disk_usage(target)
|
|
92
|
+
free_mb = usage.free / (1024 * 1024)
|
|
93
|
+
if free_mb < self.config.disk_min_mb:
|
|
94
|
+
alert = WatchdogAlert(
|
|
95
|
+
alert_type="disk_low",
|
|
96
|
+
step_index=-1, command="",
|
|
97
|
+
message=f"磁盘空间不足:仅剩 {free_mb:.0f} MB(阈值 {self.config.disk_min_mb} MB)",
|
|
98
|
+
action_taken="warned",
|
|
99
|
+
)
|
|
100
|
+
self.alerts.append(alert)
|
|
101
|
+
return alert
|
|
102
|
+
except OSError:
|
|
103
|
+
pass
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def start_timer(self, process: subprocess.Popen, step_index: int,
|
|
107
|
+
command: str, timeout: int = None):
|
|
108
|
+
"""启动超时定时器"""
|
|
109
|
+
if not self.config.enabled:
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
self._process = process
|
|
113
|
+
self._killed = False
|
|
114
|
+
timeout = timeout or self.get_timeout_for_command(command)
|
|
115
|
+
|
|
116
|
+
def _on_timeout():
|
|
117
|
+
if process.poll() is None: # 还在运行
|
|
118
|
+
self._killed = True
|
|
119
|
+
alert = WatchdogAlert(
|
|
120
|
+
alert_type="timeout",
|
|
121
|
+
step_index=step_index,
|
|
122
|
+
command=command,
|
|
123
|
+
message=f"步骤 {step_index + 1} 超时({timeout}s),已终止",
|
|
124
|
+
elapsed_sec=timeout,
|
|
125
|
+
threshold_sec=timeout,
|
|
126
|
+
action_taken="killed",
|
|
127
|
+
)
|
|
128
|
+
self.alerts.append(alert)
|
|
129
|
+
if self.config.on_alert:
|
|
130
|
+
self.config.on_alert(alert)
|
|
131
|
+
# 优雅终止:先 SIGTERM,再 SIGKILL
|
|
132
|
+
try:
|
|
133
|
+
process.terminate()
|
|
134
|
+
try:
|
|
135
|
+
process.wait(timeout=5)
|
|
136
|
+
except subprocess.TimeoutExpired:
|
|
137
|
+
process.kill()
|
|
138
|
+
except OSError:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
self._timer = threading.Timer(timeout, _on_timeout)
|
|
142
|
+
self._timer.daemon = True
|
|
143
|
+
self._timer.start()
|
|
144
|
+
|
|
145
|
+
def cancel_timer(self):
|
|
146
|
+
"""取消超时定时器"""
|
|
147
|
+
if self._timer:
|
|
148
|
+
self._timer.cancel()
|
|
149
|
+
self._timer = None
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def was_killed(self) -> bool:
|
|
153
|
+
"""进程是否被看门狗终止"""
|
|
154
|
+
return self._killed
|
|
155
|
+
|
|
156
|
+
def format_alerts(self) -> str:
|
|
157
|
+
"""格式化告警列表"""
|
|
158
|
+
if not self.alerts:
|
|
159
|
+
return " 🐕 看门狗:无告警"
|
|
160
|
+
|
|
161
|
+
lines = [f" 🐕 看门狗告警(共 {len(self.alerts)} 条):"]
|
|
162
|
+
icons = {"timeout": "⏰", "idle": "💤", "disk_low": "💾",
|
|
163
|
+
"memory_high": "🧠", "killed": "☠️"}
|
|
164
|
+
for a in self.alerts:
|
|
165
|
+
icon = icons.get(a.alert_type, "⚠️")
|
|
166
|
+
lines.append(f" {icon} [{a.alert_type}] {a.message}")
|
|
167
|
+
if a.action_taken:
|
|
168
|
+
lines.append(f" 操作:{a.action_taken}")
|
|
169
|
+
return "\n".join(lines)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ─────────────────────────────────────────────
|
|
173
|
+
# 便捷函数
|
|
174
|
+
# ─────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
def create_watchdog(enabled: bool = True,
|
|
177
|
+
on_alert: Callable = None) -> StepWatchdog:
|
|
178
|
+
"""创建看门狗实例"""
|
|
179
|
+
config = WatchdogConfig(enabled=enabled, on_alert=on_alert)
|
|
180
|
+
return StepWatchdog(config)
|