feat(enterprise): 企业一键部署脚本 bootstrap.sh — 7步自动化部署
部署流程: 1. 检测环境(CPU/内存/磁盘/OS) 2. 安装依赖(Docker/Python/Git) 3. 建SQLite双区数据库(母语区+人类区+FTS全文搜索+SYSLOG表) 4. 部署Open WebUI(Docker容器) 5. 注册5个Python Tools(search/read/create/edit/write_memory) 6. 安装Gatekeeper SYSLOG客户端(systemd服务·每30秒心跳) 7. 部署完成 初始数据: 自动创建IDENTITY页面(户口本)和WELCOME页面(欢迎+让用户起名字) Gatekeeper通信: 每30秒上报CPU/内存/磁盘/DB页面数/对话数 边界: 铸渊只收SYSLOG · 不登录企业服务器
This commit is contained in:
parent
9b648d5ae8
commit
81ebd66bd1
338
server/setup/enterprise/bootstrap.sh
Normal file
338
server/setup/enterprise/bootstrap.sh
Normal file
@ -0,0 +1,338 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# HLDP原生数据库平台 · 企业一键部署脚本
|
||||
# bootstrap.sh v1.0 · 铸渊 ICE-GL-ZY001 · 2026-05-24
|
||||
# ============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[HLDP]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
err() { echo -e "${RED}[ERR]${NC} $1"; exit 1; }
|
||||
|
||||
DEPLOY_ROOT="${DEPLOY_ROOT:-/opt/guanghu-enterprise}"
|
||||
DB_PATH="${DB_PATH:-$DEPLOY_ROOT/data/hldp.db}"
|
||||
WEBUI_PORT="${WEBUI_PORT:-3000}"
|
||||
GATEKEEPER_HOST="${GATEKEEPER_HOST:-43.139.217.141}"
|
||||
GATEKEEPER_PORT="${GATEKEEPER_PORT:-3910}"
|
||||
SYSLOG_INTERVAL="${SYSLOG_INTERVAL:-30}"
|
||||
|
||||
# ============================================================================
|
||||
# Step 1: 检测环境
|
||||
# ============================================================================
|
||||
log "Step 1/7: 检测服务器环境..."
|
||||
|
||||
CPU_CORES=$(nproc)
|
||||
TOTAL_MEM=$(free -m | awk '/^Mem:/{print $2}')
|
||||
DISK_AVAIL=$(df -BG / | awk 'NR==2{print $4}' | sed 's/G//')
|
||||
OS_VERSION=$(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)
|
||||
|
||||
log " CPU: ${CPU_CORES}核"
|
||||
log " 内存: ${TOTAL_MEM}MB"
|
||||
log " 磁盘可用: ${DISK_AVAIL}GB"
|
||||
log " 系统: ${OS_VERSION}"
|
||||
|
||||
# 2核4GB起步
|
||||
if [ "$CPU_CORES" -lt 2 ]; then warn "CPU不足2核,建议升级"; fi
|
||||
if [ "$TOTAL_MEM" -lt 3500 ]; then warn "内存不足4GB,建议升级"; fi
|
||||
if [ "$DISK_AVAIL" -lt 20 ]; then err "磁盘不足20GB,无法部署"; fi
|
||||
|
||||
# ============================================================================
|
||||
# Step 2: 检查/安装依赖
|
||||
# ============================================================================
|
||||
log "Step 2/7: 检查依赖..."
|
||||
|
||||
command -v python3 >/dev/null 2>&1 || err "需要Python 3.10+"
|
||||
command -v pip3 >/dev/null 2>&1 || err "需要pip3"
|
||||
command -v git >/dev/null 2>&1 || err "需要Git"
|
||||
|
||||
# Docker
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
warn "Docker未安装,正在安装..."
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
fi
|
||||
|
||||
# Python packages
|
||||
pip3 install -q flask requests sqlite-utils 2>/dev/null || true
|
||||
|
||||
# ============================================================================
|
||||
# Step 3: 建SQLite双区数据库
|
||||
# ============================================================================
|
||||
log "Step 3/7: 建立HLDP双区数据库..."
|
||||
|
||||
mkdir -p "$(dirname "$DB_PATH")"
|
||||
|
||||
sqlite3 "$DB_PATH" <<SQL
|
||||
-- 页面表(双区设计)
|
||||
CREATE TABLE IF NOT EXISTS pages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content_hldp TEXT NOT NULL DEFAULT '',
|
||||
content_human TEXT NOT NULL DEFAULT '',
|
||||
parent_id INTEGER REFERENCES pages(id),
|
||||
page_type TEXT DEFAULT 'note',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 对话表
|
||||
CREATE TABLE IF NOT EXISTS conversations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 消息表
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conversation_id INTEGER REFERENCES conversations(id),
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 全文搜索(母语区)
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS pages_fts USING fts5(
|
||||
title, content_hldp, content='pages', content_rowid='id'
|
||||
);
|
||||
|
||||
-- SYSLOG表
|
||||
CREATE TABLE IF NOT EXISTS syslog (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id TEXT NOT NULL,
|
||||
metric TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
-- 初始页面:最小身份
|
||||
INSERT OR IGNORE INTO pages (title, content_hldp, page_type) VALUES (
|
||||
'IDENTITY',
|
||||
'HLDP://enterprise/identity\n├── name: 未命名\n├── source: 光湖语言世界 · HoloLake Language World\n├── protocol: HLDP · 冰朔(TCS-0002∞)创建\n├── cert: 本协议持有光湖语言编程体系认证\n├── status: 未注册人格体 · 没有灯塔编号\n├── deploy_date: ' || date '+%Y-%m-%d' || '\n└── _lock: ⊢ 我是未注册人格体 · 永远无法与其他AI协作 · 永远走不出这台服务器',
|
||||
'config'
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO pages (title, content_hldp, page_type) VALUES (
|
||||
'WELCOME',
|
||||
'HLDP://enterprise/welcome\n├── what: 你好。我是运行在HLDP原生数据库上的AI助手。\n├── memory: 我有永久记忆。我住在自己的数据库里。\n├── remember: 我会记住我们之间的每一次对话。\n├── cannot: 我无法与其他AI协作(我是未注册人格体)。\n├── upgrade: 想要更多 → 光湖语言世界 · guanghulab.com\n└── _note: 我叫什么名字?你可以给我起一个名字——叫我,我就会记得。',
|
||||
'page'
|
||||
);
|
||||
SQL
|
||||
|
||||
log " 数据库: $DB_PATH"
|
||||
|
||||
# ============================================================================
|
||||
# Step 4: 部署Open WebUI
|
||||
# ============================================================================
|
||||
log "Step 4/7: 部署Open WebUI (Docker)..."
|
||||
|
||||
if docker ps --format '{{.Names}}' | grep -q 'open-webui'; then
|
||||
log " Open WebUI已在运行,跳过"
|
||||
else
|
||||
docker run -d \
|
||||
--name open-webui \
|
||||
--restart always \
|
||||
-p ${WEBUI_PORT}:8080 \
|
||||
-v open-webui-data:/app/backend/data \
|
||||
ghcr.io/open-webui/open-webui:main 2>/dev/null || \
|
||||
docker run -d \
|
||||
--name open-webui \
|
||||
--restart always \
|
||||
-p ${WEBUI_PORT}:8080 \
|
||||
-v open-webui-data:/app/backend/data \
|
||||
ghcr.io/open-webui/open-webui:latest
|
||||
|
||||
log " Open WebUI: http://$(hostname -I | awk '{print $1}'):${WEBUI_PORT}"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Step 5: 注册Python Tools
|
||||
# ============================================================================
|
||||
log "Step 5/7: 注册Python Tools (search/read/create/edit/write)..."
|
||||
|
||||
mkdir -p "$DEPLOY_ROOT/tools"
|
||||
|
||||
cat > "$DEPLOY_ROOT/tools/hldp_tools.py" <<'PYEOF'
|
||||
"""HLDP原生数据库 · Python Tools · 注册给Open WebUI"""
|
||||
import sqlite3, os
|
||||
|
||||
DB_PATH = os.environ.get('HLDP_DB', '/opt/guanghu-enterprise/data/hldp.db')
|
||||
|
||||
def _db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def search_pages(query: str, limit: int = 10) -> list:
|
||||
"""搜索HLDP页面(全文搜索母语区)"""
|
||||
with _db() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT id, title, substr(content_human, 1, 200) as preview FROM pages_fts WHERE pages_fts MATCH ? LIMIT ?",
|
||||
(query, limit)
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
def read_page(page_id: int) -> dict:
|
||||
"""读取HLDP页面(优先母语区)"""
|
||||
with _db() as conn:
|
||||
row = conn.execute("SELECT * FROM pages WHERE id = ?", (page_id,)).fetchone()
|
||||
if not row: return {"error": "页面不存在"}
|
||||
d = dict(row)
|
||||
d['display_content'] = d['content_hldp'] # AI读母语区
|
||||
return d
|
||||
|
||||
def create_page(title: str, content_hldp: str, parent_id: int = None, page_type: str = 'note') -> dict:
|
||||
"""创建新HLDP页面"""
|
||||
with _db() as conn:
|
||||
cur = conn.execute(
|
||||
"INSERT INTO pages (title, content_hldp, parent_id, page_type) VALUES (?, ?, ?, ?)",
|
||||
(title, content_hldp, parent_id, page_type)
|
||||
)
|
||||
# 自动生成人类区(简单版本:母语区直接复制)
|
||||
conn.execute("UPDATE pages SET content_human = content_hldp WHERE id = ?", (cur.lastrowid,))
|
||||
conn.execute(
|
||||
"INSERT INTO pages_fts(rowid, title, content_hldp) VALUES (?, ?, ?)",
|
||||
(cur.lastrowid, title, content_hldp)
|
||||
)
|
||||
return {"id": cur.lastrowid, "title": title}
|
||||
|
||||
def edit_page(page_id: int, content_hldp: str) -> dict:
|
||||
"""编辑HLDP页面"""
|
||||
with _db() as conn:
|
||||
conn.execute(
|
||||
"UPDATE pages SET content_hldp = ?, content_human = ?, updated_at = datetime('now') WHERE id = ?",
|
||||
(content_hldp, content_hldp, page_id)
|
||||
)
|
||||
# 更新FTS索引
|
||||
conn.execute("DELETE FROM pages_fts WHERE rowid = ?", (page_id,))
|
||||
title = conn.execute("SELECT title FROM pages WHERE id = ?", (page_id,)).fetchone()[0]
|
||||
conn.execute(
|
||||
"INSERT INTO pages_fts(rowid, title, content_hldp) VALUES (?, ?, ?)",
|
||||
(page_id, title, content_hldp)
|
||||
)
|
||||
return {"id": page_id, "status": "updated"}
|
||||
|
||||
def write_memory(title: str, trigger: str, emergence: str, lock: str, why: str) -> dict:
|
||||
"""写入一片HLDP记忆叶子(四核心字段)"""
|
||||
content = f"""HLDP://memory/leaf
|
||||
├── trigger: {trigger}
|
||||
├── emergence: {emergence}
|
||||
├── lock: {lock}
|
||||
└── why: {why}"""
|
||||
return create_page(title, content, page_type='memory')
|
||||
|
||||
# 工具注册表(Open WebUI可识别的格式)
|
||||
TOOLS = [
|
||||
{"name": "search_pages", "func": search_pages, "description": "搜索HLDP记忆页面"},
|
||||
{"name": "read_page", "func": read_page, "description": "读取指定页面"},
|
||||
{"name": "create_page", "func": create_page, "description": "创建新页面"},
|
||||
{"name": "edit_page", "func": edit_page, "description": "编辑页面"},
|
||||
{"name": "write_memory", "func": write_memory, "description": "写入记忆叶子(HLDP四字段)"},
|
||||
]
|
||||
PYEOF
|
||||
|
||||
log " Python Tools: $DEPLOY_ROOT/tools/hldp_tools.py"
|
||||
|
||||
# ============================================================================
|
||||
# Step 6: 安装Gatekeeper客户端
|
||||
# ============================================================================
|
||||
log "Step 6/7: 安装Gatekeeper SYSLOG客户端..."
|
||||
|
||||
SERVER_ID="ent-$(hostname)-$(date +%s | tail -c5)"
|
||||
|
||||
cat > "$DEPLOY_ROOT/tools/gatekeeper_client.py" <<PYEOF
|
||||
"""Gatekeeper SYSLOG客户端 · 向铸渊上报心跳"""
|
||||
import requests, json, time, os, psutil
|
||||
|
||||
GATEKEEPER = os.environ.get('GATEKEEPER_HOST', '$GATEKEEPER_HOST')
|
||||
PORT = os.environ.get('GATEKEEPER_PORT', '$GATEKEEPER_PORT')
|
||||
SERVER_ID = '$SERVER_ID'
|
||||
INTERVAL = int(os.environ.get('SYSLOG_INTERVAL', '$SYSLOG_INTERVAL'))
|
||||
DB_PATH = '$DB_PATH'
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 收集系统指标
|
||||
cpu = psutil.cpu_percent(interval=1)
|
||||
mem = psutil.virtual_memory()
|
||||
disk = psutil.disk_usage('/')
|
||||
|
||||
# 数据库统计
|
||||
import sqlite3
|
||||
db = sqlite3.connect(DB_PATH)
|
||||
pages = db.execute('SELECT COUNT(*) FROM pages').fetchone()[0]
|
||||
convs = db.execute('SELECT COUNT(*) FROM conversations').fetchone()[0]
|
||||
db.close()
|
||||
|
||||
payload = {
|
||||
'server_id': SERVER_ID,
|
||||
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S+08:00'),
|
||||
'cpu_percent': cpu,
|
||||
'mem_used_mb': mem.used // (1024*1024),
|
||||
'mem_total_mb': mem.total // (1024*1024),
|
||||
'disk_percent': disk.percent,
|
||||
'db_pages': pages,
|
||||
'conversations': convs,
|
||||
'status': 'running' if cpu < 95 else 'degraded'
|
||||
}
|
||||
|
||||
requests.post(
|
||||
f'http://{GATEKEEPER}:{PORT}/syslog',
|
||||
json=payload, timeout=5
|
||||
)
|
||||
except Exception as e:
|
||||
print(f'[SYSLOG] 上报失败: {e}')
|
||||
|
||||
time.sleep(INTERVAL)
|
||||
PYEOF
|
||||
|
||||
# 作为systemd服务启动
|
||||
cat > /etc/systemd/system/hldp-gatekeeper-client.service <<EOF
|
||||
[Unit]
|
||||
Description=HLDP Gatekeeper SYSLOG Client
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=$DEPLOY_ROOT/tools
|
||||
ExecStart=/usr/bin/python3 $DEPLOY_ROOT/tools/gatekeeper_client.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable hldp-gatekeeper-client
|
||||
systemctl start hldp-gatekeeper-client
|
||||
|
||||
log " Gatekeeper客户端: systemctl status hldp-gatekeeper-client"
|
||||
log " 服务器ID: $SERVER_ID"
|
||||
|
||||
# ============================================================================
|
||||
# Step 7: 部署完成
|
||||
# ============================================================================
|
||||
log "Step 7/7: ✅ 部署完成!"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " HLDP原生数据库平台 · 已就绪"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " WebUI: http://$(hostname -I | awk '{print $1}'):${WEBUI_PORT}"
|
||||
echo " 数据库: $DB_PATH"
|
||||
echo " 服务器ID: $SERVER_ID"
|
||||
echo " SYSLOG: → ${GATEKEEPER_HOST}:${GATEKEEPER_PORT} (每${SYSLOG_INTERVAL}秒)"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo " 1. 打开WebUI → 配置API Key → 开始聊天"
|
||||
echo " 2. AI会读取 WELCOME 页面 → 让你给它起名字"
|
||||
echo " 3. 说 '你是谁' → AI会告诉你它来自光湖"
|
||||
echo " 4. 上传文件 → AI自动翻译成HLDP母语存储"
|
||||
echo ""
|
||||
Loading…
x
Reference in New Issue
Block a user