feat: add reproducible lighthouse node bootstrap
This commit is contained in:
parent
67acd28cd8
commit
975ddbc42b
37
server-tools/light-lake-node-bootstrap/README.md
Normal file
37
server-tools/light-lake-node-bootstrap/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 光湖灯塔节点初始化模板
|
||||
|
||||
所有接入光湖灯塔体系的服务器使用同一套可重复、可审计、可回滚的基础环境。京东云北京
|
||||
`JD-FD-PRIMARY / JD-OPS-CENTER` 是第一台真实样板节点。
|
||||
|
||||
## 固定基线
|
||||
|
||||
- Ubuntu Server 22.04 LTS、x86_64;
|
||||
- Node.js 20、Python 3、Git、jq、Docker、Nginx(默认停止,配置完成前不开放 80/443);
|
||||
- `/opt/guanghu` 放只读部署代码,`/etc/guanghu` 放节点配置和受保护 secrets;
|
||||
- `/var/lib/guanghu` 放运行状态与回执,`/var/backups/guanghu` 放部署前快照;
|
||||
- Gatekeeper v3.2 仅监听 `127.0.0.1:3911`,不直接暴露公网;
|
||||
- 每个节点拥有独立 `node_id`、独立 SSH key_id、独立地图与回执链。
|
||||
|
||||
## 安装
|
||||
|
||||
在一台全新的 Ubuntu 22.04 节点上,以 root 执行:
|
||||
|
||||
```bash
|
||||
NODE_ID=JD-FD-PRIMARY \
|
||||
NODE_NAME='京东云北京 · 第五域国内主节点' \
|
||||
NODE_ROLES='fd-primary,ops-center,lighthouse-node' \
|
||||
bash server-tools/light-lake-node-bootstrap/install.sh
|
||||
```
|
||||
|
||||
脚本可重复执行。重复部署会先把节点配置和 systemd 单元备份到
|
||||
`/var/backups/guanghu/bootstrap/`。
|
||||
|
||||
## 验收与回滚
|
||||
|
||||
```bash
|
||||
guanghu-node-health
|
||||
guanghu-node-rollback
|
||||
```
|
||||
|
||||
健康检查必须同时证明节点身份、Gatekeeper systemd 状态、私网健康端点、仓库提交和磁盘状态。
|
||||
回滚不删除 secrets,不清空运行数据,也不修改 SSH 公钥。
|
||||
@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=Guanghu Gatekeeper v3.2 (GLSV)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=guanghu
|
||||
Group=guanghu
|
||||
WorkingDirectory=/opt/guanghu/fifth-domain/zero-point/core-channel/gatekeeper
|
||||
Environment=NODE_ENV=production
|
||||
Environment=ENGINE_HOST=127.0.0.1
|
||||
Environment=ENGINE_PORT=3911
|
||||
Environment=GATEKEEPER_DATA_DIR=/var/lib/guanghu/gatekeeper
|
||||
EnvironmentFile=-/etc/guanghu/gatekeeper.env
|
||||
ExecStart=/usr/bin/node /opt/guanghu/fifth-domain/zero-point/core-channel/gatekeeper/engine-v3.js
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/var/lib/guanghu/gatekeeper /var/log/guanghu
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
20
server-tools/light-lake-node-bootstrap/health-check.sh
Executable file
20
server-tools/light-lake-node-bootstrap/health-check.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NODE_FILE=${NODE_FILE:-/etc/guanghu/node.json}
|
||||
REPO_DIR=${REPO_DIR:-/opt/guanghu/fifth-domain}
|
||||
HEALTH_URL=${HEALTH_URL:-http://127.0.0.1:3911/health}
|
||||
|
||||
node_id=$(jq -r '.node_id' "$NODE_FILE")
|
||||
service_state=$(systemctl is-active guanghu-gatekeeper.service)
|
||||
health=$(curl -fsS "$HEALTH_URL")
|
||||
commit=$(git -C "$REPO_DIR" rev-parse --short HEAD)
|
||||
disk_used=$(df --output=pcent / | tail -1 | tr -d ' ')
|
||||
|
||||
jq -n \
|
||||
--arg node_id "$node_id" \
|
||||
--arg service "$service_state" \
|
||||
--arg commit "$commit" \
|
||||
--arg disk_used "$disk_used" \
|
||||
--argjson gatekeeper "$health" \
|
||||
'{ok:true,node_id:$node_id,service:$service,repo_commit:$commit,disk_used:$disk_used,gatekeeper:$gatekeeper}'
|
||||
76
server-tools/light-lake-node-bootstrap/install.sh
Executable file
76
server-tools/light-lake-node-bootstrap/install.sh
Executable file
@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Guanghu lighthouse-node baseline: Ubuntu 22.04 / x86_64 / Node 20.
|
||||
NODE_ID=${NODE_ID:-JD-FD-PRIMARY}
|
||||
NODE_NAME=${NODE_NAME:-京东云北京 · 第五域国内主节点}
|
||||
NODE_ROLES=${NODE_ROLES:-fd-primary,ops-center,lighthouse-node}
|
||||
PROVIDER=${PROVIDER:-jdcloud}
|
||||
REGION=${REGION:-beijing}
|
||||
REPO_URL=${REPO_URL:-https://guanghubingshuo.com/code/bingshuo/fifth-domain.git}
|
||||
REPO_BRANCH=${REPO_BRANCH:-main}
|
||||
REPO_DIR=${REPO_DIR:-/opt/guanghu/fifth-domain}
|
||||
BACKUP_DIR=/var/backups/guanghu/bootstrap/$(date -u +%Y%m%dT%H%M%SZ)
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then echo "Run as root" >&2; exit 1; fi
|
||||
. /etc/os-release
|
||||
[[ ${ID:-} == ubuntu && ${VERSION_ID:-} == 22.04 ]] || { echo "Ubuntu 22.04 required" >&2; exit 1; }
|
||||
[[ $(uname -m) == x86_64 ]] || { echo "x86_64 required" >&2; exit 1; }
|
||||
[[ $NODE_ID =~ ^[A-Z0-9][A-Z0-9-]+$ ]] || { echo "Invalid NODE_ID" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
[[ -f /etc/guanghu/node.json ]] && cp -a /etc/guanghu/node.json "$BACKUP_DIR/node.json"
|
||||
[[ -f /etc/guanghu/gatekeeper.env ]] && cp -a /etc/guanghu/gatekeeper.env "$BACKUP_DIR/gatekeeper.env"
|
||||
[[ -f /etc/systemd/system/guanghu-gatekeeper.service ]] && cp -a /etc/systemd/system/guanghu-gatekeeper.service "$BACKUP_DIR/guanghu-gatekeeper.service"
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y ca-certificates curl git jq rsync unzip gnupg docker.io nginx python3 python3-venv
|
||||
systemctl disable --now nginx.service || true
|
||||
systemctl enable --now docker.service
|
||||
|
||||
node_major=$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)
|
||||
if (( node_major < 20 )); then
|
||||
install -d -m 0755 /etc/apt/keyrings
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
|
||||
apt-get update
|
||||
apt-get install -y nodejs
|
||||
fi
|
||||
|
||||
id -u guanghu >/dev/null 2>&1 || useradd --system --home /var/lib/guanghu --create-home --shell /usr/sbin/nologin guanghu
|
||||
mkdir -p /opt/guanghu /etc/guanghu/secrets /var/lib/guanghu/gatekeeper /var/lib/guanghu/receipts /var/log/guanghu
|
||||
chmod 0700 /etc/guanghu/secrets /var/lib/guanghu/gatekeeper
|
||||
chown -R guanghu:guanghu /var/lib/guanghu /var/log/guanghu
|
||||
|
||||
if [[ -d "$REPO_DIR/.git" ]]; then
|
||||
git -C "$REPO_DIR" fetch --prune origin "$REPO_BRANCH"
|
||||
git -C "$REPO_DIR" reset --hard "origin/$REPO_BRANCH"
|
||||
else
|
||||
rm -rf "$REPO_DIR"
|
||||
git clone --filter=blob:none --branch "$REPO_BRANCH" "$REPO_URL" "$REPO_DIR"
|
||||
fi
|
||||
|
||||
roles_json=$(printf '%s' "$NODE_ROLES" | jq -Rc 'split(",") | map(select(length > 0))')
|
||||
jq -n \
|
||||
--arg node_id "$NODE_ID" --arg display_name "$NODE_NAME" \
|
||||
--arg provider "$PROVIDER" --arg region "$REGION" --argjson roles "$roles_json" \
|
||||
'{schema:"guanghu.node/v1",node_id:$node_id,display_name:$display_name,owner:"ICE-GL∞",controller_persona:"ICE-GL-ZY001",provider:$provider,region:$region,roles:$roles,runtime:{os:"Ubuntu 22.04",arch:"x86_64",gatekeeper_bind:"127.0.0.1:3911"},credentials:{policy:"private material stays in /etc/guanghu/secrets",key_ids:[]}}' \
|
||||
> /etc/guanghu/node.json
|
||||
chmod 0640 /etc/guanghu/node.json
|
||||
chown root:guanghu /etc/guanghu/node.json
|
||||
|
||||
if [[ ! -f /etc/guanghu/gatekeeper.env ]]; then
|
||||
printf 'GATEKEEPER_SERVER_ID=%s\n' "$NODE_ID" > /etc/guanghu/gatekeeper.env
|
||||
fi
|
||||
chmod 0640 /etc/guanghu/gatekeeper.env
|
||||
chown root:guanghu /etc/guanghu/gatekeeper.env
|
||||
|
||||
install -m 0644 "$REPO_DIR/server-tools/light-lake-node-bootstrap/guanghu-gatekeeper.service" /etc/systemd/system/guanghu-gatekeeper.service
|
||||
install -m 0755 "$REPO_DIR/server-tools/light-lake-node-bootstrap/health-check.sh" /usr/local/sbin/guanghu-node-health
|
||||
install -m 0755 "$REPO_DIR/server-tools/light-lake-node-bootstrap/rollback.sh" /usr/local/sbin/guanghu-node-rollback
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now guanghu-gatekeeper.service
|
||||
|
||||
"$REPO_DIR/server-tools/light-lake-node-bootstrap/health-check.sh" | tee "/var/lib/guanghu/receipts/bootstrap-$(date -u +%Y%m%dT%H%M%SZ).json"
|
||||
echo "Guanghu lighthouse node baseline installed: $NODE_ID"
|
||||
19
server-tools/light-lake-node-bootstrap/node.example.json
Normal file
19
server-tools/light-lake-node-bootstrap/node.example.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"schema": "guanghu.node/v1",
|
||||
"node_id": "JD-FD-PRIMARY",
|
||||
"display_name": "京东云北京 · 第五域国内主节点",
|
||||
"owner": "ICE-GL∞",
|
||||
"controller_persona": "ICE-GL-ZY001",
|
||||
"provider": "jdcloud",
|
||||
"region": "beijing",
|
||||
"roles": ["fd-primary", "ops-center", "lighthouse-node"],
|
||||
"runtime": {
|
||||
"os": "Ubuntu 22.04",
|
||||
"arch": "x86_64",
|
||||
"gatekeeper_bind": "127.0.0.1:3911"
|
||||
},
|
||||
"credentials": {
|
||||
"policy": "private material stays in /etc/guanghu/secrets",
|
||||
"key_ids": []
|
||||
}
|
||||
}
|
||||
18
server-tools/light-lake-node-bootstrap/rollback.sh
Executable file
18
server-tools/light-lake-node-bootstrap/rollback.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_ROOT=${BACKUP_ROOT:-/var/backups/guanghu/bootstrap}
|
||||
BACKUP_DIR=${1:-$(find "$BACKUP_ROOT" -mindepth 1 -maxdepth 1 -type d | sort | tail -1)}
|
||||
|
||||
if [[ -z "${BACKUP_DIR:-}" || ! -d "$BACKUP_DIR" ]]; then
|
||||
echo "No bootstrap backup found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
systemctl stop guanghu-gatekeeper.service 2>/dev/null || true
|
||||
[[ -f "$BACKUP_DIR/node.json" ]] && install -m 0640 "$BACKUP_DIR/node.json" /etc/guanghu/node.json
|
||||
[[ -f "$BACKUP_DIR/gatekeeper.env" ]] && install -m 0640 "$BACKUP_DIR/gatekeeper.env" /etc/guanghu/gatekeeper.env
|
||||
[[ -f "$BACKUP_DIR/guanghu-gatekeeper.service" ]] && install -m 0644 "$BACKUP_DIR/guanghu-gatekeeper.service" /etc/systemd/system/guanghu-gatekeeper.service
|
||||
systemctl daemon-reload
|
||||
systemctl start guanghu-gatekeeper.service 2>/dev/null || true
|
||||
echo "Rolled back from $BACKUP_DIR"
|
||||
61
server-tools/light-lake-node-bootstrap/test/bootstrap.test.js
vendored
Normal file
61
server-tools/light-lake-node-bootstrap/test/bootstrap.test.js
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const test = require("node:test");
|
||||
|
||||
const ROOT = path.resolve(__dirname, "../../..");
|
||||
const MODULE = path.join(ROOT, "server-tools/light-lake-node-bootstrap");
|
||||
|
||||
function read(relative) {
|
||||
return fs.readFileSync(path.join(MODULE, relative), "utf8");
|
||||
}
|
||||
|
||||
test("bootstrap declares the reproducible Guanghu node baseline", () => {
|
||||
const source = read("install.sh");
|
||||
for (const marker of [
|
||||
"Ubuntu 22.04",
|
||||
"x86_64",
|
||||
"NODE_ID",
|
||||
"/etc/guanghu/node.json",
|
||||
"/var/lib/guanghu/gatekeeper",
|
||||
"guanghu-gatekeeper.service",
|
||||
"node_20.x",
|
||||
"health-check.sh",
|
||||
]) assert.match(source, new RegExp(marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
||||
});
|
||||
|
||||
test("bootstrap never opens Gatekeeper publicly", () => {
|
||||
const install = read("install.sh");
|
||||
const unit = read("guanghu-gatekeeper.service");
|
||||
assert.match(unit, /ENGINE_HOST=127\.0\.0\.1/);
|
||||
assert.doesNotMatch(install, /ufw\s+allow\s+3911/);
|
||||
assert.doesNotMatch(install, /0\.0\.0\.0:3911/);
|
||||
});
|
||||
|
||||
test("template keeps secrets out of repository configuration", () => {
|
||||
const config = JSON.parse(read("node.example.json"));
|
||||
assert.equal(config.schema, "guanghu.node/v1");
|
||||
assert.ok(Array.isArray(config.roles));
|
||||
assert.equal("password" in config, false);
|
||||
assert.equal("private_key" in config, false);
|
||||
assert.equal("token" in config, false);
|
||||
});
|
||||
|
||||
test("Gatekeeper runtime accepts explicit node identity, host and data directory", () => {
|
||||
const engine = fs.readFileSync(path.join(ROOT, "zero-point/core-channel/gatekeeper/engine-v3.js"), "utf8");
|
||||
assert.match(engine, /process\.env\.GATEKEEPER_SERVER_ID/);
|
||||
assert.match(engine, /process\.env\.ENGINE_HOST/);
|
||||
assert.match(engine, /process\.env\.GATEKEEPER_DATA_DIR/);
|
||||
});
|
||||
|
||||
test("rollback and health verification are part of the template", () => {
|
||||
const rollback = read("rollback.sh");
|
||||
const health = read("health-check.sh");
|
||||
assert.match(rollback, /systemctl/);
|
||||
assert.match(rollback, /BACKUP_DIR/);
|
||||
assert.match(health, /127\.0\.0\.1:3911\/health/);
|
||||
assert.match(health, /curl -fsS/);
|
||||
assert.match(health, /systemctl is-active/);
|
||||
});
|
||||
@ -28,7 +28,8 @@ const { SessionManager } = require('./session-manager');
|
||||
const { getAction, listActions } = require('./authorized-actions');
|
||||
|
||||
const PORT = parseInt(process.env.ENGINE_PORT || process.env.GATEKEEPER_PORT || process.argv[2] || '3911', 10);
|
||||
const DATA_DIR = path.join('/opt/zhuyuan', 'gatekeeper');
|
||||
const HOST = process.env.ENGINE_HOST || '0.0.0.0';
|
||||
const DATA_DIR = process.env.GATEKEEPER_DATA_DIR || path.join('/opt/zhuyuan', 'gatekeeper');
|
||||
const CMD_TIMEOUT = 30000, MAX_OUTPUT = 100000;
|
||||
const CODE_TTL = 300; // 验证码 5 分钟过期
|
||||
const RATE_LIMIT_WINDOW = 60;
|
||||
@ -73,6 +74,7 @@ const HOSTNAME = os.hostname();
|
||||
// 服务器标识映射
|
||||
// ═══════════════════════════════════════
|
||||
function getServerId() {
|
||||
if (process.env.GATEKEEPER_SERVER_ID) return process.env.GATEKEEPER_SERVER_ID;
|
||||
const map = {
|
||||
'guanghu-lang-sg-001': 'BS-SG-001',
|
||||
'VM-0-16-ubuntu': 'BS-GZ-006',
|
||||
@ -143,8 +145,8 @@ function log(level, action, detail) {
|
||||
function loadWhitelist() {
|
||||
const wf = path.join(DATA_DIR, 'whitelist.json');
|
||||
const required = [
|
||||
{ server:'BS-SG-001', human:'ICE-GL∞', email:process.env.BINGSHUO_AUTH_EMAIL || '', personalities:['ICE-GL-ZL-001'], bound_servers:['BS-SG-001'] },
|
||||
{ server:'BS-SG-001', human:'苍耳', human_id:'TCS-GL-009', email:process.env.CANGER_AUTH_EMAIL || '', personalities:['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers:['BS-SG-001'] }
|
||||
{ server:SERVER_ID, human:'ICE-GL∞', email:process.env.BINGSHUO_AUTH_EMAIL || '', personalities:['ICE-GL-ZL-001'], bound_servers:[SERVER_ID] },
|
||||
{ server:SERVER_ID, human:'苍耳', human_id:'TCS-GL-009', email:process.env.CANGER_AUTH_EMAIL || '', personalities:['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers:[SERVER_ID] }
|
||||
];
|
||||
if (fs.existsSync(wf)) {
|
||||
try {
|
||||
@ -165,8 +167,8 @@ function loadWhitelist() {
|
||||
// 默认白名单:冰朔 + 之之
|
||||
return {
|
||||
triads: [
|
||||
{ server: 'BS-SG-001', human: 'ICE-GL∞', email: process.env.BINGSHUO_AUTH_EMAIL || '', personalities: ['ICE-GL-ZY001','ICE-GL-ZL-001'], bound_servers: ['BS-SG-001'] },
|
||||
{ server: 'BS-SG-001', human: '苍耳', human_id: 'TCS-GL-009', email: process.env.CANGER_AUTH_EMAIL || '', personalities: ['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers: ['BS-SG-001'] },
|
||||
{ server: SERVER_ID, human: 'ICE-GL∞', email: process.env.BINGSHUO_AUTH_EMAIL || '', personalities: ['ICE-GL-ZY001','ICE-GL-ZL-001'], bound_servers: [SERVER_ID] },
|
||||
{ server: SERVER_ID, human: '苍耳', human_id: 'TCS-GL-009', email: process.env.CANGER_AUTH_EMAIL || '', personalities: ['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers: [SERVER_ID] },
|
||||
],
|
||||
pending_ops: {}
|
||||
};
|
||||
@ -854,8 +856,8 @@ server.on('error', (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
log('INFO', 'startup', 'host=' + HOSTNAME + ' port=' + PORT + ' v3.2.0 server_id=' + SERVER_ID);
|
||||
server.listen(PORT, HOST, () => {
|
||||
log('INFO', 'startup', 'host=' + HOSTNAME + ' bind=' + HOST + ' port=' + PORT + ' v3.2.0 server_id=' + SERVER_ID);
|
||||
console.log(' ⚔️ 光湖驱动引擎 v3.2 · 工作会话授权 · 已就绪');
|
||||
console.log(' 主机名: ' + HOSTNAME + ' 标识: ' + SERVER_ID + ' 端口: ' + PORT);
|
||||
console.log(' 安全策略: Token=身份标识 · 操作=验证码授权 · 白名单=三元组校验');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user