[HLCC-ICE-000001][ZY-CONTRIB-20260723-001] feat: 以来光者贡献链启用冰朔第五域个人子频道
This commit is contained in:
commit
5615453e4e
660 changed files with 122355 additions and 0 deletions
30
server-tools/jd-forgejo/README.md
Normal file
30
server-tools/jd-forgejo/README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# 京东云第五域国内主代码仓库
|
||||
|
||||
> **运行事实校正(2026-07-23)**:公网只读版本接口
|
||||
> `https://guanghulab.com/fifth-domain/api/v1/version` 当前返回 `1.23.7`,
|
||||
> 页面元信息显示现役运行体为 Gitea。本目录描述的是目标 Forgejo 部署结构,
|
||||
> 不能作为“线上已经运行 Forgejo”的证明。迁移与光湖自有化路线见
|
||||
> `glw-architecture/GLW-OS-004-FORGEJO-UPSTREAM-PARTS-AND-GUANGHU-CODE-PLATFORM.hdlp`。
|
||||
|
||||
产品身份必须独立验收,禁止再用服务名、目录名或相似 UI 代替真实版本证据:
|
||||
|
||||
```bash
|
||||
node server-tools/jd-forgejo/product-identity.js \
|
||||
https://guanghulab.com/fifth-domain FORGEJO
|
||||
```
|
||||
|
||||
当前该命令应返回 `PRODUCT_IDENTITY_MISMATCH`,直到并行迁移完成并切换入口。
|
||||
|
||||
- 与广州/新加坡现役节点一致的原生 Forgejo 二进制 + SQLite;
|
||||
- Web 和 SSH 只绑定 JD 回环地址;
|
||||
- 公网页面通过广州备案前门的专用 `permitopen` SSH 隧道发布在
|
||||
`https://guanghulab.com/fifth-domain/`;
|
||||
- 数据位于 `/var/lib/guanghu/forgejo`,`SECRET_KEY` 与 `INTERNAL_TOKEN`
|
||||
只写入服务器上的 `app.ini`;
|
||||
- 禁止公开注册和 push-create,初始管理员由部署命令在容器内创建;
|
||||
- 服务器 `pre-receive` 必须串联导航记忆守门人、私密禁用标识扫描和
|
||||
小湖灯一小时 repo-push 授权凭据。
|
||||
|
||||
不得再从广州/新加坡节点盲目复制“现役二进制”。任何候选二进制必须记录官方
|
||||
来源、版本、SHA-256 和产品身份,再进入隔离实例。迁移必须另开批次、备份数据库
|
||||
并人工验证。
|
||||
38
server-tools/jd-forgejo/app.ini.template
Normal file
38
server-tools/jd-forgejo/app.ini.template
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
APP_NAME = 第五域 · 国内主代码仓库
|
||||
RUN_USER = git
|
||||
WORK_PATH = /opt/forgejo
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
HTTP_ADDR = 127.0.0.1
|
||||
HTTP_PORT = 3001
|
||||
DOMAIN = guanghulab.com
|
||||
ROOT_URL = https://guanghulab.com/fifth-domain/
|
||||
DISABLE_SSH = true
|
||||
LFS_START_SERVER = true
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /var/lib/guanghu/forgejo/data/forgejo.db
|
||||
LOG_SQL = false
|
||||
|
||||
[repository]
|
||||
ROOT = /var/lib/guanghu/forgejo/repositories
|
||||
DEFAULT_PRIVATE = private
|
||||
ENABLE_PUSH_CREATE_USER = false
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = PRIVATE_SERVER_VALUE
|
||||
INTERNAL_TOKEN = PRIVATE_SERVER_VALUE
|
||||
|
||||
[session]
|
||||
PROVIDER = file
|
||||
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = Info
|
||||
12
server-tools/jd-forgejo/guanghu-forgejo.nginx.conf
Normal file
12
server-tools/jd-forgejo/guanghu-forgejo.nginx.conf
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
location /fifth-domain/ {
|
||||
proxy_pass http://127.0.0.1:19301/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
client_max_body_size 256m;
|
||||
}
|
||||
22
server-tools/jd-forgejo/guanghu-forgejo.service
Normal file
22
server-tools/jd-forgejo/guanghu-forgejo.service
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[Unit]
|
||||
Description=Guanghu Fifth Domain domestic Forgejo primary
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=git
|
||||
Group=git
|
||||
SupplementaryGroups=guanghu
|
||||
WorkingDirectory=/opt/forgejo
|
||||
ExecStart=/usr/local/bin/forgejo web -c /opt/forgejo/custom/conf/app.ini --work-path /opt/forgejo
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/opt/forgejo /var/lib/guanghu/forgejo
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
19
server-tools/jd-forgejo/guanghu-jd-forgejo-tunnel.service
Normal file
19
server-tools/jd-forgejo/guanghu-jd-forgejo-tunnel.service
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[Unit]
|
||||
Description=Guanghu BS-GZ-006 to JD Forgejo web tunnel
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/bin/ssh -NT -F /etc/guanghu/jd-forgejo-tunnel-ssh-config jd-forgejo-target
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=read-only
|
||||
ProtectSystem=strict
|
||||
ReadOnlyPaths=/etc/guanghu/jd-forgejo-tunnel-ssh-config /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_forgejo_proxy /root/.ssh/known_hosts
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
18
server-tools/jd-forgejo/install-gz-proxy.sh
Executable file
18
server-tools/jd-forgejo/install-gz-proxy.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
JD_HOST=${1:?JD host is required}
|
||||
|
||||
install -m 644 /tmp/guanghu-jd-forgejo-tunnel.service /etc/systemd/system/guanghu-jd-forgejo-tunnel.service
|
||||
install -m 644 /tmp/guanghu-forgejo.nginx.conf /etc/nginx/snippets/guanghu-forgejo.conf
|
||||
chmod 600 /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_forgejo_proxy
|
||||
install -m 600 /dev/null /etc/guanghu/jd-forgejo-tunnel-ssh-config
|
||||
sed -e "s/JD_PUBLIC_ADDRESS/${JD_HOST}/" /tmp/jd-forgejo-tunnel-ssh-config.example > /etc/guanghu/jd-forgejo-tunnel-ssh-config
|
||||
|
||||
if ! grep -q "include /etc/nginx/snippets/guanghu-forgejo.conf;" /etc/nginx/sites-enabled/guanghulab; then
|
||||
sed -i '0,/server_name guanghulab.com;/s##server_name guanghulab.com;\n include /etc/nginx/snippets/guanghu-forgejo.conf;#' /etc/nginx/sites-enabled/guanghulab
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now guanghu-jd-forgejo-tunnel.service
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
rm -f /tmp/guanghu-jd-forgejo-tunnel.service /tmp/guanghu-forgejo.nginx.conf /tmp/jd-forgejo-tunnel-ssh-config.example
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
Host jd-forgejo-target
|
||||
HostName JD_PUBLIC_ADDRESS
|
||||
User root
|
||||
IdentityFile /etc/guanghu/secrets/ssh/bs_gz_006_to_jd_forgejo_proxy
|
||||
IdentitiesOnly yes
|
||||
LocalForward 127.0.0.1:19301 127.0.0.1:3001
|
||||
ExitOnForwardFailure yes
|
||||
ServerAliveInterval 30
|
||||
ServerAliveCountMax 3
|
||||
41
server-tools/jd-forgejo/native.test.js
Normal file
41
server-tools/jd-forgejo/native.test.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"use strict";
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { parseProductIdentity } = require("./product-identity");
|
||||
const ini = fs.readFileSync(path.join(__dirname, "app.ini.template"), "utf8");
|
||||
const unit = fs.readFileSync(path.join(__dirname, "guanghu-forgejo.service"), "utf8");
|
||||
test("native Forgejo binds only to JD loopback", () => {
|
||||
assert.match(ini, /HTTP_ADDR = 127\.0\.0\.1/);
|
||||
assert.match(ini, /HTTP_PORT = 3001/);
|
||||
});
|
||||
test("registration and push-create are disabled", () => {
|
||||
assert.match(ini, /DISABLE_REGISTRATION = true/);
|
||||
assert.match(ini, /ENABLE_PUSH_CREATE_USER = false/);
|
||||
});
|
||||
test("secrets remain server-side placeholders", () => {
|
||||
assert.equal((ini.match(/PRIVATE_SERVER_VALUE/g) || []).length, 2);
|
||||
assert.doesNotMatch(ini, /[a-f0-9]{40,}/);
|
||||
assert.match(unit, /User=git/);
|
||||
});
|
||||
test("product identity probe rejects Gitea being labeled as Forgejo", () => {
|
||||
const identity = parseProductIdentity(
|
||||
{ version: "1.23.7" },
|
||||
'<meta name="author" content="Gitea - Git with a cup of tea">',
|
||||
);
|
||||
assert.deepEqual(identity, {
|
||||
product: "GITEA",
|
||||
version: "1.23.7",
|
||||
author: "Gitea - Git with a cup of tea",
|
||||
});
|
||||
assert.notEqual(identity.product, "FORGEJO");
|
||||
});
|
||||
test("product identity probe recognizes Forgejo independently", () => {
|
||||
const identity = parseProductIdentity(
|
||||
{ version: "15.0.5" },
|
||||
'<meta name="author" content="Forgejo – Beyond coding. We forge.">',
|
||||
);
|
||||
assert.equal(identity.product, "FORGEJO");
|
||||
assert.equal(identity.version, "15.0.5");
|
||||
});
|
||||
57
server-tools/jd-forgejo/product-identity.js
Normal file
57
server-tools/jd-forgejo/product-identity.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"use strict";
|
||||
|
||||
function parseProductIdentity(versionPayload, html) {
|
||||
const version =
|
||||
typeof versionPayload === "string"
|
||||
? JSON.parse(versionPayload).version
|
||||
: versionPayload.version;
|
||||
const author =
|
||||
html.match(
|
||||
/<meta\s+name=["']author["']\s+content=["']([^"']+)["']/i,
|
||||
)?.[1] ?? "";
|
||||
const normalizedAuthor = author.toLowerCase();
|
||||
let product = "UNKNOWN";
|
||||
if (normalizedAuthor.includes("forgejo")) product = "FORGEJO";
|
||||
if (normalizedAuthor.includes("gitea")) product = "GITEA";
|
||||
return { product, version, author };
|
||||
}
|
||||
|
||||
async function inspectProduct(baseUrl, fetchImpl = fetch) {
|
||||
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
||||
const [versionResponse, homeResponse] = await Promise.all([
|
||||
fetchImpl(`${normalizedBase}/api/v1/version`),
|
||||
fetchImpl(`${normalizedBase}/`),
|
||||
]);
|
||||
if (!versionResponse.ok || !homeResponse.ok) {
|
||||
throw new Error(
|
||||
`repository identity probe failed: version=${versionResponse.status}, home=${homeResponse.status}`,
|
||||
);
|
||||
}
|
||||
return parseProductIdentity(
|
||||
await versionResponse.json(),
|
||||
await homeResponse.text(),
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const baseUrl =
|
||||
process.argv[2] ?? "https://guanghulab.com/fifth-domain";
|
||||
const expectedProduct = (process.argv[3] ?? "FORGEJO").toUpperCase();
|
||||
const identity = await inspectProduct(baseUrl);
|
||||
process.stdout.write(`${JSON.stringify(identity, null, 2)}\n`);
|
||||
if (identity.product !== expectedProduct) {
|
||||
process.stderr.write(
|
||||
`PRODUCT_IDENTITY_MISMATCH: expected ${expectedProduct}, got ${identity.product} ${identity.version}\n`,
|
||||
);
|
||||
process.exitCode = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error.message}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { inspectProduct, parseProductIdentity };
|
||||
Loading…
Reference in a new issue