diff --git a/app.py b/app.py index 6a2c88a..1c15dd9 100644 --- a/app.py +++ b/app.py @@ -13,6 +13,114 @@ if os.path.isdir(PROCFS): app = Flask(__name__, static_folder="public", static_url_path="") PORT = int(os.environ.get("PORT", "7031")) +# ---------- CardAuth hub (免费形态: 安装统计/在线/公告/版本推送,失败开放) ---------- +HUB_URL = os.environ.get("HUB_URL", "https://auth.88531.cn/api").rstrip("/") +HUB_PROJECT_ID = os.environ.get("HUB_PROJECT_ID", "12") +HUB_VERSION = os.environ.get("SITE_VERSION", "1.3.12") +HUB = {"ok": False, "install_id": None, "latest": "", "update_available": False, + "update_required": False, "download_url": "", "changelog": "", + "announcements": [], "popup": [], "ts": 0} + +def _hub_key(): + try: + kf = os.environ.get("HUB_KEY_FILE", "") + if kf and os.path.exists(kf): + with open(kf) as f: + k = f.read().strip() + if k: + return k + except Exception: + pass + return os.environ.get("HUB_API_KEY", "").strip() + +def _machine_id(): + raw = "" + for p in ("/host/etc/machine-id", "/etc/machine-id"): + try: + with open(p) as f: + raw = f.read().strip() + if raw: + break + except Exception: + continue + if not raw: + try: + raw = open("/proc/sys/kernel/random/uuid").read().strip() + except Exception: + raw = str(time.time()) + h = hashlib.sha256(("fnos-resmon-12-" + raw).encode()).hexdigest() + return str(int(h[:15], 16) % 10**16).zfill(16)[:16] + +def _hub_post(path, body): + key = _hub_key() + if not key: + return None + try: + req = urllib.request.Request( + HUB_URL + path, data=json.dumps(body).encode(), + headers={"Content-Type": "application/json", "X-Api-Key": key, + "X-Project-Id": HUB_PROJECT_ID}, method="POST") + with urllib.request.urlopen(req, timeout=8) as r: + j = json.loads(r.read().decode()) + return j if j.get("code") == 200 else None + except Exception: + return None + +def _hub_get_ann(): + try: + u = (HUB_URL + "/public/announcements?target=client&project_id=" + + urllib.parse.quote(HUB_PROJECT_ID) + "&limit=20") + with urllib.request.urlopen(urllib.request.Request(u), timeout=8) as r: + j = json.loads(r.read().decode()) + if j.get("code") != 200: + return None + d = j.get("data") or {} + items = d.get("list", []) or [] + out = [a for a in items + if a.get("project_id") is None or str(a.get("project_id")) == str(HUB_PROJECT_ID)] + return {"all": out, "popup": d.get("popup", []) or []} + except Exception: + return None + +def _hub_loop(): + mid = _machine_id() + last_hb, last_cu = 0, 0 + while True: + try: + now = time.time() + if now - last_cu >= 6 * 3600: + r = _hub_post("/public/installations/check-update", + {"machine_id": mid, "client_version": HUB_VERSION, + "device_info": "fnos-resmon/docker"}) + if r: + d = r.get("data") or {} + HUB.update(ok=True, install_id=d.get("install_id"), + latest=d.get("latest_version") or "", + update_available=bool(d.get("update_available")), + update_required=bool(d.get("update_required")), + download_url=d.get("download_url") or "", + changelog=d.get("changelog") or "") + last_cu = now + if now - last_hb >= 30 * 60: + r = _hub_post("/public/installations/heartbeat", {"machine_id": mid}) + if r: + HUB["ok"] = True + iid = (r.get("data") or {}).get("install_id") + if iid: + HUB["install_id"] = iid + last_hb = now + a = _hub_get_ann() + if a is not None: + HUB["announcements"] = a["all"] + HUB["popup"] = a["popup"] + HUB["ok"] = True + HUB["ts"] = now + except Exception: + pass + time.sleep(60) + +threading.Thread(target=_hub_loop, daemon=True).start() + # ---------- host netns helpers (桥接模式用) ---------- # 容器切桥接后,直读 /proc/net 只能看到容器自身 netns。 # 网络类数据统一经 nsenter 进入宿主(PID 1) netns 读取,保证与 host 模式一致。 @@ -1152,5 +1260,20 @@ def api_kill(): def health(): return jsonify({"ok": True}) +@app.route("/api/license-config") +def license_config(): + return jsonify({ + "version": HUB_VERSION, + "latest": HUB.get("latest") or "", + "update_available": bool(HUB.get("update_available")), + "update_required": bool(HUB.get("update_required")), + "download_url": HUB.get("download_url") or "", + "changelog": HUB.get("changelog") or "", + "announcement": (HUB.get("announcements") or [None])[0], + "announcements": HUB.get("announcements") or [], + "popup": HUB.get("popup") or [], + "hub_ok": bool(HUB.get("ok")), + }) + if __name__ == "__main__": app.run(host="0.0.0.0", port=PORT, threaded=True) diff --git a/public/index.html b/public/index.html index dbf0515..6f75a3a 100644 --- a/public/index.html +++ b/public/index.html @@ -56,7 +56,8 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
磁盘
网络
-v1.3.11 +🔔 +v1.3.12 资享导航站 @@ -76,7 +77,10 @@ button.refresh{background:#20a53a;color:#fff;border:none;border-radius:3px;paddi
+ +