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/event_bus.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
event_bus.py - 安装事件总线 + Webhook 通知
|
|
3
|
+
============================================
|
|
4
|
+
|
|
5
|
+
灵感来源:ICE-OEM 的告警派发队列
|
|
6
|
+
|
|
7
|
+
安装过程中发布事件,支持:
|
|
8
|
+
1. 事件监听器:内部模块订阅事件
|
|
9
|
+
2. Webhook 推送:安装完成/失败时推送到外部(Slack/Discord/钉钉/企微)
|
|
10
|
+
3. 事件历史:记录所有事件供回溯
|
|
11
|
+
|
|
12
|
+
事件类型:
|
|
13
|
+
install.started, install.step_started, install.step_completed,
|
|
14
|
+
install.step_failed, install.completed, install.failed,
|
|
15
|
+
audit.warning, license.issue, watchdog.alert
|
|
16
|
+
|
|
17
|
+
配置:
|
|
18
|
+
GITINSTALL_WEBHOOK_URL=https://hooks.slack.com/services/xxx
|
|
19
|
+
GITINSTALL_WEBHOOK_SECRET=xxx
|
|
20
|
+
|
|
21
|
+
零外部依赖,纯 Python 标准库。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import hashlib
|
|
27
|
+
import hmac
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import threading
|
|
31
|
+
import time
|
|
32
|
+
import urllib.error
|
|
33
|
+
import urllib.request
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from typing import Callable, Optional
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ── 事件类型常量 ──
|
|
39
|
+
EVT_INSTALL_STARTED = "install.started"
|
|
40
|
+
EVT_STEP_STARTED = "install.step_started"
|
|
41
|
+
EVT_STEP_COMPLETED = "install.step_completed"
|
|
42
|
+
EVT_STEP_FAILED = "install.step_failed"
|
|
43
|
+
EVT_INSTALL_COMPLETED = "install.completed"
|
|
44
|
+
EVT_INSTALL_FAILED = "install.failed"
|
|
45
|
+
EVT_AUDIT_WARNING = "audit.warning"
|
|
46
|
+
EVT_LICENSE_ISSUE = "license.issue"
|
|
47
|
+
EVT_WATCHDOG_ALERT = "watchdog.alert"
|
|
48
|
+
EVT_CHECKPOINT_SAVED = "checkpoint.saved"
|
|
49
|
+
EVT_RESUME_STARTED = "resume.started"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class Event:
|
|
54
|
+
"""安装事件"""
|
|
55
|
+
event_type: str
|
|
56
|
+
timestamp: str = ""
|
|
57
|
+
project: str = ""
|
|
58
|
+
data: dict = field(default_factory=dict)
|
|
59
|
+
source: str = "gitinstall"
|
|
60
|
+
|
|
61
|
+
def __post_init__(self):
|
|
62
|
+
if not self.timestamp:
|
|
63
|
+
self.timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict:
|
|
66
|
+
return {
|
|
67
|
+
"event_type": self.event_type,
|
|
68
|
+
"timestamp": self.timestamp,
|
|
69
|
+
"project": self.project,
|
|
70
|
+
"data": self.data,
|
|
71
|
+
"source": self.source,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ── 事件监听器类型 ──
|
|
76
|
+
EventListener = Callable[[Event], None]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class EventBus:
|
|
80
|
+
"""事件总线"""
|
|
81
|
+
|
|
82
|
+
def __init__(self):
|
|
83
|
+
self._listeners: dict[str, list[EventListener]] = {}
|
|
84
|
+
self._history: list[Event] = []
|
|
85
|
+
self._lock = threading.Lock()
|
|
86
|
+
self._max_history = 1000
|
|
87
|
+
|
|
88
|
+
def subscribe(self, event_type: str, listener: EventListener):
|
|
89
|
+
"""订阅事件"""
|
|
90
|
+
with self._lock:
|
|
91
|
+
self._listeners.setdefault(event_type, []).append(listener)
|
|
92
|
+
|
|
93
|
+
def subscribe_all(self, listener: EventListener):
|
|
94
|
+
"""订阅所有事件"""
|
|
95
|
+
self.subscribe("*", listener)
|
|
96
|
+
|
|
97
|
+
def unsubscribe(self, event_type: str, listener: EventListener):
|
|
98
|
+
"""取消订阅"""
|
|
99
|
+
with self._lock:
|
|
100
|
+
listeners = self._listeners.get(event_type, [])
|
|
101
|
+
if listener in listeners:
|
|
102
|
+
listeners.remove(listener)
|
|
103
|
+
|
|
104
|
+
def publish(self, event: Event):
|
|
105
|
+
"""发布事件"""
|
|
106
|
+
with self._lock:
|
|
107
|
+
self._history.append(event)
|
|
108
|
+
if len(self._history) > self._max_history:
|
|
109
|
+
self._history = self._history[-self._max_history:]
|
|
110
|
+
|
|
111
|
+
# 通知特定类型的监听器
|
|
112
|
+
specific = list(self._listeners.get(event.event_type, []))
|
|
113
|
+
# 通知通配符监听器
|
|
114
|
+
wildcard = list(self._listeners.get("*", []))
|
|
115
|
+
|
|
116
|
+
# 在锁外执行回调
|
|
117
|
+
for listener in specific + wildcard:
|
|
118
|
+
try:
|
|
119
|
+
listener(event)
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
def emit(self, event_type: str, project: str = "", **data):
|
|
124
|
+
"""便捷发布事件"""
|
|
125
|
+
self.publish(Event(event_type=event_type, project=project, data=data))
|
|
126
|
+
|
|
127
|
+
def get_history(self, event_type: str = None,
|
|
128
|
+
limit: int = 50) -> list[Event]:
|
|
129
|
+
"""获取事件历史"""
|
|
130
|
+
with self._lock:
|
|
131
|
+
events = list(self._history)
|
|
132
|
+
if event_type:
|
|
133
|
+
events = [e for e in events if e.event_type == event_type]
|
|
134
|
+
return events[-limit:]
|
|
135
|
+
|
|
136
|
+
def clear_history(self):
|
|
137
|
+
"""清空历史"""
|
|
138
|
+
with self._lock:
|
|
139
|
+
self._history.clear()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ─────────────────────────────────────────────
|
|
143
|
+
# Webhook 推送器
|
|
144
|
+
# ─────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
class WebhookNotifier:
|
|
147
|
+
"""Webhook 通知器"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, url: str = None, secret: str = None):
|
|
150
|
+
self.url = url or os.environ.get("GITINSTALL_WEBHOOK_URL", "")
|
|
151
|
+
self.secret = secret or os.environ.get("GITINSTALL_WEBHOOK_SECRET", "")
|
|
152
|
+
self._send_lock = threading.Lock()
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def enabled(self) -> bool:
|
|
156
|
+
return bool(self.url)
|
|
157
|
+
|
|
158
|
+
def notify(self, event: Event):
|
|
159
|
+
"""发送 Webhook 通知(异步)"""
|
|
160
|
+
if not self.enabled:
|
|
161
|
+
return
|
|
162
|
+
thread = threading.Thread(target=self._send, args=(event,), daemon=True)
|
|
163
|
+
thread.start()
|
|
164
|
+
|
|
165
|
+
def _send(self, event: Event):
|
|
166
|
+
"""实际发送 Webhook"""
|
|
167
|
+
with self._send_lock:
|
|
168
|
+
try:
|
|
169
|
+
payload = json.dumps(event.to_dict(), ensure_ascii=False).encode()
|
|
170
|
+
headers = {
|
|
171
|
+
"Content-Type": "application/json",
|
|
172
|
+
"User-Agent": "gitinstall-webhook/1.0",
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# HMAC 签名(防篡改)
|
|
176
|
+
if self.secret:
|
|
177
|
+
sig = hmac.new(
|
|
178
|
+
self.secret.encode(), payload, hashlib.sha256
|
|
179
|
+
).hexdigest()
|
|
180
|
+
headers["X-Gitinstall-Signature"] = f"sha256={sig}"
|
|
181
|
+
|
|
182
|
+
req = urllib.request.Request(
|
|
183
|
+
self.url, data=payload, headers=headers, method="POST"
|
|
184
|
+
)
|
|
185
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
186
|
+
resp.read()
|
|
187
|
+
except (urllib.error.URLError, OSError):
|
|
188
|
+
pass # Webhook 失败不影响安装流程
|
|
189
|
+
|
|
190
|
+
def format_slack_message(self, event: Event) -> dict:
|
|
191
|
+
"""格式化为 Slack Block 消息"""
|
|
192
|
+
icons = {
|
|
193
|
+
EVT_INSTALL_COMPLETED: "✅",
|
|
194
|
+
EVT_INSTALL_FAILED: "❌",
|
|
195
|
+
EVT_AUDIT_WARNING: "🚨",
|
|
196
|
+
EVT_WATCHDOG_ALERT: "🐕",
|
|
197
|
+
}
|
|
198
|
+
icon = icons.get(event.event_type, "📦")
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"text": f"{icon} [{event.event_type}] {event.project}",
|
|
202
|
+
"blocks": [
|
|
203
|
+
{
|
|
204
|
+
"type": "section",
|
|
205
|
+
"text": {
|
|
206
|
+
"type": "mrkdwn",
|
|
207
|
+
"text": (
|
|
208
|
+
f"*{icon} {event.event_type}*\n"
|
|
209
|
+
f"项目:`{event.project}`\n"
|
|
210
|
+
f"时间:{event.timestamp}"
|
|
211
|
+
),
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ─────────────────────────────────────────────
|
|
219
|
+
# 全局实例
|
|
220
|
+
# ─────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
_bus: Optional[EventBus] = None
|
|
223
|
+
_webhook: Optional[WebhookNotifier] = None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def get_event_bus() -> EventBus:
|
|
227
|
+
"""获取全局事件总线"""
|
|
228
|
+
global _bus
|
|
229
|
+
if _bus is None:
|
|
230
|
+
_bus = EventBus()
|
|
231
|
+
# 自动注册 Webhook 监听器
|
|
232
|
+
wh = get_webhook_notifier()
|
|
233
|
+
if wh.enabled:
|
|
234
|
+
# 只推送关键事件
|
|
235
|
+
for evt in (EVT_INSTALL_COMPLETED, EVT_INSTALL_FAILED,
|
|
236
|
+
EVT_AUDIT_WARNING, EVT_WATCHDOG_ALERT):
|
|
237
|
+
_bus.subscribe(evt, wh.notify)
|
|
238
|
+
return _bus
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_webhook_notifier() -> WebhookNotifier:
|
|
242
|
+
"""获取全局 Webhook 通知器"""
|
|
243
|
+
global _webhook
|
|
244
|
+
if _webhook is None:
|
|
245
|
+
_webhook = WebhookNotifier()
|
|
246
|
+
return _webhook
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def emit(event_type: str, project: str = "", **data):
|
|
250
|
+
"""便捷发布事件(全局)"""
|
|
251
|
+
get_event_bus().emit(event_type, project=project, **data)
|