[HLCC-ICE-000001][ZY-CONTRIB-20260723-001] feat: 以来光者贡献链启用冰朔第五域个人子频道
This commit is contained in:
commit
5615453e4e
660 changed files with 122355 additions and 0 deletions
13
server-tools/jd-app-hub/README.md
Normal file
13
server-tools/jd-app-hub/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# JD application hub
|
||||
|
||||
The first web application running on `JD-FD-PRIMARY`. It binds only to
|
||||
`127.0.0.1:8088`; `BS-GZ-006` reaches it through a restricted persistent SSH
|
||||
tunnel and publishes it below `https://guanghulab.com/jd/`.
|
||||
|
||||
Version 2 keeps the application page on loopback port `8088` and adds one
|
||||
`/code/` reverse-proxy boundary to the isolated HoloLake Code Channel on
|
||||
`127.0.0.1:3340`. This reuses the existing permitopen-restricted Guangzhou
|
||||
application tunnel; it does not broaden the Forgejo tunnel key or expose a new
|
||||
JD port.
|
||||
|
||||
No server address, credential or private key belongs in this directory.
|
||||
56
server-tools/jd-app-hub/activate-staged-unit.py
Normal file
56
server-tools/jd-app-hub/activate-staged-unit.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Activate the already staged JD app hub unit without root shell access."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
|
||||
SERVICE = "jd-app-hub.service"
|
||||
STATUS_URL = "http://127.0.0.1:8088/api/status"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
result = subprocess.run(
|
||||
["/usr/bin/systemctl", "show", "--property=MainPID", "--value", SERVICE],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
pid = int(result.stdout.strip())
|
||||
if pid <= 1:
|
||||
raise RuntimeError("jd app hub has no active main process")
|
||||
|
||||
process_root = pathlib.Path("/proc") / str(pid)
|
||||
if process_root.stat().st_uid != os.getuid():
|
||||
raise RuntimeError("refusing to signal a process owned by another user")
|
||||
command = (process_root / "cmdline").read_bytes().replace(b"\0", b" ").decode("utf-8", "replace")
|
||||
if "/jd-app-hub/server.js" not in command:
|
||||
raise RuntimeError("refusing to signal an unexpected process")
|
||||
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
for _attempt in range(40):
|
||||
try:
|
||||
with urllib.request.urlopen(STATUS_URL, timeout=2) as response:
|
||||
payload = json.load(response)
|
||||
if (
|
||||
payload.get("ok") is True
|
||||
and payload.get("service") == "jd-app-hub"
|
||||
and payload.get("version") == "2.0.1"
|
||||
and payload.get("code_channel_proxy") == "loopback-only"
|
||||
):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(1)
|
||||
raise RuntimeError("staged JD app hub did not become version 2.0.1")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
14
server-tools/jd-app-hub/app.js
Normal file
14
server-tools/jd-app-hub/app.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
fetch("api/status", { cache: "no-store" })
|
||||
.then((response) => response.ok ? response.json() : Promise.reject(new Error("status unavailable")))
|
||||
.then((status) => {
|
||||
const live = document.querySelector("#live-status");
|
||||
const node = document.querySelector("#node-state");
|
||||
live.textContent = "运行正常";
|
||||
live.classList.add("ok");
|
||||
node.textContent = `${status.node_id} · 在线`;
|
||||
node.classList.add("ok");
|
||||
})
|
||||
.catch(() => {
|
||||
document.querySelector("#live-status").textContent = "状态暂不可用";
|
||||
document.querySelector("#node-state").textContent = "节点状态暂不可用";
|
||||
});
|
||||
19
server-tools/jd-app-hub/guanghu-jd-web-tunnel.service
Normal file
19
server-tools/jd-app-hub/guanghu-jd-web-tunnel.service
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=Guanghu BS-GZ-006 to JD application hub tunnel
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/bin/ssh -NT -F /etc/guanghu/jd-web-tunnel-ssh-config jd-web-target
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=read-only
|
||||
ProtectSystem=strict
|
||||
ReadOnlyPaths=/etc/guanghu/jd-web-tunnel-ssh-config /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_web_proxy /root/.ssh/known_hosts
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
26
server-tools/jd-app-hub/hlcc-jd-app-hub-activator.service
Normal file
26
server-tools/jd-app-hub/hlcc-jd-app-hub-activator.service
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[Unit]
|
||||
Description=Activate the staged HoloLake Code Channel JD app hub
|
||||
After=jd-app-hub.service
|
||||
Requires=jd-app-hub.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=guanghu
|
||||
Group=guanghu
|
||||
ExecStart=/usr/bin/python3 __RELEASE_ROOT__/server-tools/jd-app-hub/activate-staged-unit.py
|
||||
RemainAfterExit=true
|
||||
TimeoutStartSec=60
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
ReadOnlyPaths=__RELEASE_ROOT__
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
31
server-tools/jd-app-hub/index.html
Normal file
31
server-tools/jd-app-hub/index.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>京东云主节点 · 光湖</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<nav><a href="/">← 返回光湖国内入口</a><span id="node-state">正在读取节点状态</span></nav>
|
||||
<header>
|
||||
<p class="eyebrow">JD-FD-PRIMARY · BEIJING</p>
|
||||
<h1>京东云主节点</h1>
|
||||
<p>这里是光湖第五域的国内运行节点。广州负责备案前门,这里负责应用、计算、迁移与服务器调度。</p>
|
||||
</header>
|
||||
<section class="status" aria-label="节点状态">
|
||||
<div><span>节点</span><strong>JD-FD-PRIMARY</strong></div>
|
||||
<div><span>角色</span><strong>第五域国内主节点</strong></div>
|
||||
<div><span>连接</span><strong id="live-status">检查中</strong></div>
|
||||
</section>
|
||||
<section class="cards">
|
||||
<a href="https://guanghubingshuo.com/" target="_top"><small>WORLD</small><h2>光湖世界</h2><p>进入光湖世界主入口与第五域路由。</p><b>↗</b></a>
|
||||
<a href="https://guanghulab.com/fifth-domain/bingshuo/fifth-domain" target="_top"><small>REPO-001</small><h2>第五域国内主仓</h2><p>当前系统结构、人格路径、编号地图和部署回执事实源。</p><b>↗</b></a>
|
||||
<a href="https://guanghulab.com/api/ai/" target="_top"><small>FD-REPO-MAP-001</small><h2>AI 编号检索</h2><p>通过 REPO 编号和中文关键词寻找国内主路径。</p><b>↗</b></a>
|
||||
<div class="coming"><small>NEXT</small><h2>Tolaria 服务</h2><p>桌面软件相关服务迁移完成后从这里接入。</p><b>建设中</b></div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
33
server-tools/jd-app-hub/jd-app-hub.service
Normal file
33
server-tools/jd-app-hub/jd-app-hub.service
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
[Unit]
|
||||
Description=Guanghu JD Application Hub
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=guanghu
|
||||
Group=guanghu
|
||||
WorkingDirectory=__RELEASE_ROOT__/server-tools/jd-app-hub
|
||||
Environment=NODE_ENV=production
|
||||
Environment=HUB_HOST=127.0.0.1
|
||||
Environment=HUB_PORT=8088
|
||||
Environment=GUANGHU_NODE_ID=JD-FD-PRIMARY
|
||||
Environment=CODE_CHANNEL_HOST=127.0.0.1
|
||||
Environment=CODE_CHANNEL_PORT=3340
|
||||
ExecStart=/usr/bin/node __RELEASE_ROOT__/server-tools/jd-app-hub/server.js
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=strict
|
||||
PrivateDevices=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
ReadOnlyPaths=__RELEASE_ROOT__
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
124
server-tools/jd-app-hub/server.js
Normal file
124
server-tools/jd-app-hub/server.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"use strict";
|
||||
|
||||
const fs = require("node:fs");
|
||||
const http = require("node:http");
|
||||
const path = require("node:path");
|
||||
const os = require("node:os");
|
||||
|
||||
const ROOT = __dirname;
|
||||
const TYPES = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "application/javascript; charset=utf-8" };
|
||||
const APP_VERSION = "2.0.1";
|
||||
|
||||
function json(response, status, value) {
|
||||
response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
|
||||
response.end(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function proxyCodeChannel(request, response, options = {}) {
|
||||
const internalPath = request.url.replace(/^\/code(?=\/|$)/, "") || "/";
|
||||
const upstream = http.request({
|
||||
host: options.codeChannelHost || process.env.CODE_CHANNEL_HOST || "127.0.0.1",
|
||||
port: Number(options.codeChannelPort || process.env.CODE_CHANNEL_PORT || 3340),
|
||||
method: request.method,
|
||||
path: internalPath,
|
||||
headers: {
|
||||
...request.headers,
|
||||
host: request.headers.host || "guanghulab.com",
|
||||
"x-forwarded-host": request.headers["x-forwarded-host"] || request.headers.host || "guanghulab.com",
|
||||
"x-forwarded-proto": request.headers["x-forwarded-proto"] || "https"
|
||||
},
|
||||
timeout: 300000
|
||||
}, upstreamResponse => {
|
||||
response.writeHead(upstreamResponse.statusCode || 502, upstreamResponse.headers);
|
||||
upstreamResponse.pipe(response);
|
||||
});
|
||||
upstream.on("timeout", () => upstream.destroy(new Error("upstream_timeout")));
|
||||
upstream.on("error", () => {
|
||||
if (!response.headersSent) return json(response, 502, { ok: false, error: "code_channel_unavailable" });
|
||||
response.destroy();
|
||||
});
|
||||
request.pipe(upstream);
|
||||
}
|
||||
|
||||
function candidateFailureCategory(message) {
|
||||
const value = String(message || "").toLowerCase();
|
||||
if (/urlopen|timed out|http error|temporary failure|name or service/.test(value)) return "relay_download_failed";
|
||||
if (/no such file.*gpg|gpg.*no such file/.test(value)) return "signature_verifier_unavailable";
|
||||
if (/fingerprint|signature|gpg/.test(value)) return "signature_verification_failed";
|
||||
if (/candidate exited|candidate stopped/.test(value)) return "candidate_process_exited";
|
||||
if (/readiness timeout/.test(value)) return "candidate_readiness_timeout";
|
||||
return value ? "bootstrap_failed" : "";
|
||||
}
|
||||
|
||||
function readCandidateStatus(response, options = {}) {
|
||||
const request = http.get({
|
||||
host: options.codeChannelHealthHost || "127.0.0.1",
|
||||
port: Number(options.codeChannelHealthPort || 3341),
|
||||
path: "/health",
|
||||
timeout: 3000
|
||||
}, upstreamResponse => {
|
||||
let raw = "";
|
||||
upstreamResponse.setEncoding("utf8");
|
||||
upstreamResponse.on("data", chunk => {
|
||||
raw += chunk;
|
||||
if (raw.length > 8192) upstreamResponse.destroy(new Error("status_too_large"));
|
||||
});
|
||||
upstreamResponse.on("end", () => {
|
||||
try {
|
||||
const source = JSON.parse(raw);
|
||||
return json(response, 200, {
|
||||
ok: source.ok === true,
|
||||
version: String(source.version || ""),
|
||||
code: String(source.code || ""),
|
||||
ready: source.ready === true,
|
||||
stage: String(source.stage || "unknown"),
|
||||
failure_category: source.ok === true ? "" : candidateFailureCategory(source.error)
|
||||
});
|
||||
} catch {
|
||||
return json(response, 502, { ok: false, ready: false, error: "invalid_candidate_status" });
|
||||
}
|
||||
});
|
||||
});
|
||||
request.on("timeout", () => request.destroy(new Error("status_timeout")));
|
||||
request.on("error", () => json(response, 502, { ok: false, ready: false, error: "candidate_status_unavailable" }));
|
||||
}
|
||||
|
||||
function createApp(options = {}) {
|
||||
return http.createServer((request, response) => {
|
||||
const url = new URL(request.url, "http://localhost");
|
||||
if (url.pathname === "/code") {
|
||||
response.writeHead(308, { Location: "/code/", "Cache-Control": "no-store" });
|
||||
return response.end();
|
||||
}
|
||||
if (url.pathname.startsWith("/code/")) return proxyCodeChannel(request, response, options);
|
||||
if (request.method !== "GET" && request.method !== "HEAD") return json(response, 405, { ok: false, error: "method_not_allowed" });
|
||||
if (url.pathname === "/api/code-channel-status") return readCandidateStatus(response, options);
|
||||
if (url.pathname === "/api/status") {
|
||||
return json(response, 200, {
|
||||
ok: true,
|
||||
node_id: process.env.GUANGHU_NODE_ID || "JD-FD-PRIMARY",
|
||||
role: "fifth-domain-primary",
|
||||
service: "jd-app-hub",
|
||||
version: APP_VERSION,
|
||||
code_channel_proxy: "loopback-only",
|
||||
uptime_seconds: Math.floor(process.uptime()),
|
||||
load_1m: Number(os.loadavg()[0].toFixed(2)),
|
||||
memory_used_percent: Number((((os.totalmem() - os.freemem()) / os.totalmem()) * 100).toFixed(1)),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
const relative = url.pathname === "/" ? "index.html" : url.pathname.replace(/^\/+/, "");
|
||||
if (!/^(index\.html|styles\.css|app\.js)$/.test(relative)) return json(response, 404, { ok: false, error: "not_found" });
|
||||
const file = path.join(ROOT, relative);
|
||||
response.writeHead(200, { "Content-Type": TYPES[path.extname(file)] || "application/octet-stream", "Cache-Control": relative === "index.html" ? "no-cache" : "public, max-age=3600" });
|
||||
fs.createReadStream(file).pipe(response);
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const host = process.env.HUB_HOST || "127.0.0.1";
|
||||
const port = Number(process.env.HUB_PORT || 8088);
|
||||
createApp().listen(port, host, () => console.log(`JD app hub listening on ${host}:${port}`));
|
||||
}
|
||||
|
||||
module.exports = { createApp };
|
||||
26
server-tools/jd-app-hub/styles.css
Normal file
26
server-tools/jd-app-hub/styles.css
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
:root { color-scheme: dark; --bg:#081112; --panel:#102526; --line:rgba(174,233,216,.16); --text:#eff9f6; --muted:#9cafaa; --lake:#78dfc4; --warm:#efbd79; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; min-height:100vh; color:var(--text); background:radial-gradient(circle at 75% 0,rgba(62,154,136,.18),transparent 32rem),var(--bg); font-family:Inter,"PingFang SC",system-ui,sans-serif; }
|
||||
main { width:min(1080px,calc(100% - 36px)); margin:auto; padding-bottom:70px; }
|
||||
nav { height:84px; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--line); color:var(--muted); font-size:13px; }
|
||||
nav a { color:var(--text); text-decoration:none; }
|
||||
header { padding:90px 0 58px; max-width:780px; }
|
||||
.eyebrow { color:var(--lake); font:800 11px/1 ui-monospace,monospace; letter-spacing:.18em; }
|
||||
h1 { margin:20px 0; font-size:clamp(48px,8vw,86px); line-height:1; letter-spacing:-.055em; }
|
||||
header>p:last-child { max-width:700px; color:var(--muted); font-size:18px; line-height:1.8; }
|
||||
.status { display:grid; grid-template-columns:repeat(3,1fr); border:1px solid var(--line); border-radius:18px; overflow:hidden; background:rgba(15,35,36,.72); }
|
||||
.status div { padding:22px; display:flex; flex-direction:column; gap:8px; border-right:1px solid var(--line); }
|
||||
.status div:last-child { border:0; }
|
||||
.status span { color:var(--muted); font-size:12px; }
|
||||
.status strong { font-size:15px; }
|
||||
.cards { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; padding-top:30px; }
|
||||
.cards>a,.cards>div { position:relative; min-height:210px; padding:28px; border:1px solid var(--line); border-radius:18px; background:var(--panel); text-decoration:none; color:var(--text); }
|
||||
.cards>a:hover { border-color:rgba(120,223,196,.48); transform:translateY(-3px); }
|
||||
.cards>a { transition:.2s ease; }
|
||||
.cards small { color:var(--lake); font-weight:800; letter-spacing:.16em; }
|
||||
.cards h2 { margin:34px 0 10px; font-size:25px; }
|
||||
.cards p { margin:0; color:var(--muted); line-height:1.7; }
|
||||
.cards b { position:absolute; right:25px; top:25px; color:var(--warm); font-size:13px; }
|
||||
.coming { opacity:.62; }
|
||||
#live-status.ok,#node-state.ok { color:var(--lake); }
|
||||
@media(max-width:700px){ nav{height:70px}.status,.cards{grid-template-columns:1fr}.status div{border-right:0;border-bottom:1px solid var(--line)}header{padding-top:65px}.cards>a,.cards>div{min-height:190px} }
|
||||
21
server-tools/jd-app-hub/test/activator.test.js
Normal file
21
server-tools/jd-app-hub/test/activator.test.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = path.join(__dirname, "..");
|
||||
const script = fs.readFileSync(path.join(root, "activate-staged-unit.py"), "utf8");
|
||||
const unit = fs.readFileSync(path.join(root, "hlcc-jd-app-hub-activator.service"), "utf8");
|
||||
|
||||
assert.match(script, /systemctl", "show", "--property=MainPID"/);
|
||||
assert.match(script, /process_root\.stat\(\)\.st_uid != os\.getuid\(\)/);
|
||||
assert.match(script, /"\/jd-app-hub\/server\.js" not in command/);
|
||||
assert.match(script, /os\.kill\(pid, signal\.SIGTERM\)/);
|
||||
assert.doesNotMatch(script, /shell=True|systemctl", "(?:restart|stop|start)"/);
|
||||
assert.match(unit, /^User=guanghu$/m);
|
||||
assert.match(unit, /^NoNewPrivileges=true$/m);
|
||||
assert.match(unit, /^ProtectSystem=strict$/m);
|
||||
assert.match(unit, /^ReadOnlyPaths=__RELEASE_ROOT__$/m);
|
||||
|
||||
console.log("JD app hub staged-unit activator: PASS");
|
||||
130
server-tools/jd-app-hub/test/server.test.js
Normal file
130
server-tools/jd-app-hub/test/server.test.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
const http = require("node:http");
|
||||
const { createApp } = require("../server");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
test("JD hub exposes a minimal non-secret status response", async (t) => {
|
||||
const server = createApp().listen(0, "127.0.0.1");
|
||||
t.after(() => server.close());
|
||||
await new Promise((resolve) => server.once("listening", resolve));
|
||||
const { port } = server.address();
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/status`);
|
||||
assert.equal(response.status, 200);
|
||||
const body = await response.json();
|
||||
assert.equal(body.ok, true);
|
||||
assert.equal(body.node_id, "JD-FD-PRIMARY");
|
||||
assert.equal("ip" in body, false);
|
||||
assert.equal("token" in body, false);
|
||||
});
|
||||
|
||||
test("JD hub serves its application page", async (t) => {
|
||||
const server = createApp().listen(0, "127.0.0.1");
|
||||
t.after(() => server.close());
|
||||
await new Promise((resolve) => server.once("listening", resolve));
|
||||
const { port } = server.address();
|
||||
const response = await fetch(`http://127.0.0.1:${port}/`);
|
||||
assert.equal(response.status, 200);
|
||||
assert.match(await response.text(), /京东云主节点/);
|
||||
});
|
||||
|
||||
test("JD hub strips its public route before proxying to the code channel", async (t) => {
|
||||
const upstream = http.createServer((request, response) => {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
host: request.headers.host,
|
||||
proto: request.headers["x-forwarded-proto"]
|
||||
}));
|
||||
});
|
||||
upstream.listen(0, "127.0.0.1");
|
||||
t.after(() => upstream.close());
|
||||
await new Promise((resolve) => upstream.once("listening", resolve));
|
||||
|
||||
const server = createApp({ codeChannelPort: upstream.address().port }).listen(0, "127.0.0.1");
|
||||
t.after(() => server.close());
|
||||
await new Promise((resolve) => server.once("listening", resolve));
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const request = http.get({
|
||||
host: "127.0.0.1",
|
||||
port: server.address().port,
|
||||
path: "/code/api/v1/version",
|
||||
headers: { host: "guanghulab.com", "x-forwarded-proto": "https" }
|
||||
}, resolve);
|
||||
request.on("error", reject);
|
||||
});
|
||||
const chunks = [];
|
||||
for await (const chunk of response) chunks.push(chunk);
|
||||
assert.equal(response.statusCode, 200);
|
||||
assert.deepEqual(JSON.parse(Buffer.concat(chunks).toString("utf8")), {
|
||||
path: "/api/v1/version",
|
||||
method: "GET",
|
||||
host: "guanghulab.com",
|
||||
proto: "https"
|
||||
});
|
||||
});
|
||||
|
||||
test("JD hub preserves nested code-channel paths and queries without duplicating the public prefix", async (t) => {
|
||||
const upstream = http.createServer((request, response) => {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({ path: request.url }));
|
||||
});
|
||||
upstream.listen(0, "127.0.0.1");
|
||||
t.after(() => upstream.close());
|
||||
await new Promise((resolve) => upstream.once("listening", resolve));
|
||||
|
||||
const server = createApp({ codeChannelPort: upstream.address().port }).listen(0, "127.0.0.1");
|
||||
t.after(() => server.close());
|
||||
await new Promise((resolve) => server.once("listening", resolve));
|
||||
|
||||
const nested = await fetch(`http://127.0.0.1:${server.address().port}/code/user/login?redirect_to=%2Fjd%2Fcode%2F`);
|
||||
assert.equal(nested.status, 200);
|
||||
assert.deepEqual(await nested.json(), {
|
||||
path: "/user/login?redirect_to=%2Fjd%2Fcode%2F"
|
||||
});
|
||||
|
||||
const root = await fetch(`http://127.0.0.1:${server.address().port}/code/`);
|
||||
assert.equal(root.status, 200);
|
||||
assert.deepEqual(await root.json(), { path: "/" });
|
||||
});
|
||||
|
||||
test("JD hub exposes only a categorized candidate failure", async (t) => {
|
||||
const upstream = http.createServer((_request, response) => {
|
||||
response.writeHead(200, { "content-type": "application/json" });
|
||||
response.end(JSON.stringify({
|
||||
ok: false,
|
||||
version: "16.0.1",
|
||||
code: "HLCC-JD-CANDIDATE-01",
|
||||
ready: false,
|
||||
stage: "failed",
|
||||
error: "[Errno 2] No such file or directory: gpg /private/server/path"
|
||||
}));
|
||||
});
|
||||
upstream.listen(0, "127.0.0.1");
|
||||
t.after(() => upstream.close());
|
||||
await new Promise((resolve) => upstream.once("listening", resolve));
|
||||
|
||||
const server = createApp({ codeChannelHealthPort: upstream.address().port }).listen(0, "127.0.0.1");
|
||||
t.after(() => server.close());
|
||||
await new Promise((resolve) => server.once("listening", resolve));
|
||||
const response = await fetch(`http://127.0.0.1:${server.address().port}/api/code-channel-status`);
|
||||
assert.equal(response.status, 200);
|
||||
assert.deepEqual(await response.json(), {
|
||||
ok: false,
|
||||
version: "16.0.1",
|
||||
code: "HLCC-JD-CANDIDATE-01",
|
||||
ready: false,
|
||||
stage: "failed",
|
||||
failure_category: "signature_verifier_unavailable"
|
||||
});
|
||||
});
|
||||
|
||||
test("tunnel service keeps private addresses outside the repository", () => {
|
||||
const unit = fs.readFileSync(path.join(__dirname, "..", "guanghu-jd-web-tunnel.service"), "utf8");
|
||||
assert.match(unit, /\/etc\/guanghu\/jd-web-tunnel-ssh-config/);
|
||||
assert.doesNotMatch(unit, /(?:\d{1,3}\.){3}\d{1,3}/);
|
||||
});
|
||||
Loading…
Reference in a new issue