From 41b2016b8bef0a4fea569f67c88656c7af775b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Sun, 2 Aug 2026 22:04:09 +0800 Subject: [PATCH] feat: deploy HoloLake mobile capabilities safely --- .../install-architecture-provisioner.sh | 194 +++++++++++++++++- .../install-architecture-provisioner.test.js | 40 ++++ server-tools/lake-lamp-authz/server.js | 5 + 3 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 server-tools/lake-lamp-authz/install-architecture-provisioner.test.js diff --git a/server-tools/lake-lamp-authz/install-architecture-provisioner.sh b/server-tools/lake-lamp-authz/install-architecture-provisioner.sh index 1c6e593..83cc4ad 100755 --- a/server-tools/lake-lamp-authz/install-architecture-provisioner.sh +++ b/server-tools/lake-lamp-authz/install-architecture-provisioner.sh @@ -8,22 +8,197 @@ fi script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) install_root=/opt/guanghu/lake-lamp-authz +state_root=/var/lib/guanghu/architecture-provision +secret_root=/etc/guanghu/secrets/lake-lamp +authorization_env=$secret_root/authorization.env +provider_registry=/etc/guanghu/secrets/hololake-ai-providers.json +knowledge_repo=/var/lib/guanghu/personas/guanghu/hlcc-v16.0.1/data/repositories/bingshuo/hololake-knowledge-base.git +stamp=$(date -u +%Y%m%dT%H%M%SZ) +backup_root=$state_root/manual-backups/lake-lamp-authz-$stamp + +required_source_files=( + server.js + workorder-manager.js + map-gate.js + smtp-mailer.js + action-client.js + architecture-provision-broker.js + deployment-event.js + deployment-event-worker.js + deployment-source-policy.js + guanghu-router.js + repo-push-broker.js + hololake-session.js + hololake-capabilities.js +) + +for file in "${required_source_files[@]}" lake-lamp-authz.service lake-lamp-architecture-provision.service lake-lamp-deployment-event-worker.service; do + [[ -f "$script_dir/$file" ]] || { + echo "missing required release file: $file" >&2 + exit 1 + } +done + +install -d -m 0700 "$backup_root" +[[ ! -e "$install_root" ]] || cp -a "$install_root" "$backup_root/install-root" +for existing in \ + /etc/systemd/system/lake-lamp-authz.service \ + /etc/systemd/system/lake-lamp-architecture-provision.service \ + /etc/systemd/system/lake-lamp-deployment-event-worker.service \ + "$authorization_env" \ + "$provider_registry"; do + if [[ -e "$existing" ]]; then + destination=$backup_root/existing${existing} + install -d -m 0700 "$(dirname "$destination")" + cp -a "$existing" "$destination" + fi +done + +rollback() { + set +e + if [[ -d "$backup_root/install-root" ]]; then + rm -rf -- "$install_root" + cp -a "$backup_root/install-root" "$install_root" + fi + for existing in \ + /etc/systemd/system/lake-lamp-authz.service \ + /etc/systemd/system/lake-lamp-architecture-provision.service \ + /etc/systemd/system/lake-lamp-deployment-event-worker.service \ + "$authorization_env" \ + "$provider_registry"; do + saved=$backup_root/existing${existing} + if [[ -e "$saved" ]]; then + install -d -m 0755 "$(dirname "$existing")" + cp -a "$saved" "$existing" + elif [[ "$existing" = "$provider_registry" ]]; then + rm -f -- "$existing" + fi + done + systemctl daemon-reload + systemctl restart lake-lamp-authz.service + systemctl restart lake-lamp-architecture-provision.service + systemctl restart lake-lamp-deployment-event-worker.service +} +trap 'rc=$?; if [[ $rc -ne 0 ]]; then rollback; fi; exit "$rc"' EXIT install -d -m 0755 "$install_root" -for file in server.js workorder-manager.js map-gate.js smtp-mailer.js action-client.js architecture-provision-broker.js deployment-event.js deployment-event-worker.js deployment-source-policy.js hololake-session.js hololake-capabilities.js; do +for file in "${required_source_files[@]}"; do install -m 0644 "$script_dir/$file" "$install_root/$file" done +install -m 0644 "$script_dir/lake-lamp-authz.service" /etc/systemd/system/lake-lamp-authz.service install -m 0644 "$script_dir/lake-lamp-architecture-provision.service" /etc/systemd/system/lake-lamp-architecture-provision.service install -m 0644 "$script_dir/lake-lamp-deployment-event-worker.service" /etc/systemd/system/lake-lamp-deployment-event-worker.service -install -d -m 0700 /var/lib/guanghu/architecture-provision +install -d -m 0700 "$state_root" install -d -m 0750 /var/lib/guanghu/deployment-events install -d -m 0700 /var/lib/guanghu/deployment-events/receipts install -d -m 0755 /opt/guanghu/architecture-releases install -d -m 0755 /etc/guanghu/lake-lamp +install -d -m 0750 "$secret_root" if [[ ! -e /etc/guanghu/lake-lamp/deployment-repositories.json ]]; then install -m 0644 "$script_dir/deployment-repositories.example.json" /etc/guanghu/lake-lamp/deployment-repositories.json fi +[[ -f "$authorization_env" ]] || { + echo "private authorization environment is missing" >&2 + exit 1 +} + +append_setting() { + local key=$1 + local value=$2 + if ! grep -q "^${key}=" "$authorization_env"; then + printf '%s=%s\n' "$key" "$value" >>"$authorization_env" + fi +} + +if ! grep -q '^HOLOLAKE_SESSION_PEPPER=' "$authorization_env"; then + append_setting HOLOLAKE_SESSION_PEPPER "$(/usr/bin/openssl rand -hex 32)" +fi +append_setting HOLOLAKE_SESSION_STATE_FILE /var/lib/guanghu/lake-lamp-authz/hololake-sessions.json +append_setting HOLOLAKE_OTP_TTL 600 +append_setting HOLOLAKE_ACCOUNT_SESSION_TTL 86400 +append_setting HOLOLAKE_OTP_REQUEST_LIMIT 6 +append_setting HOLOLAKE_KNOWLEDGE_REPOSITORY_PATH "$knowledge_repo" +append_setting HOLOLAKE_KNOWLEDGE_MAX_ARCHIVE_BYTES 134217728 +append_setting HOLOLAKE_AI_PROVIDERS_FILE "$provider_registry" +chmod 0600 "$authorization_env" + +if [[ ! -f "$provider_registry" ]]; then + /usr/bin/python3 - "$provider_registry" <<'PY' +import json +import pathlib +import sys + +destination = pathlib.Path(sys.argv[1]) +values = {} +roots = ( + pathlib.Path("/etc/guanghu/secrets"), + pathlib.Path("/etc/guanghu/persona-secrets"), +) +for root in roots: + if not root.exists(): + continue + for file in root.rglob("*.env"): + try: + for raw in file.read_text().splitlines(): + line = raw.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, value = line.split("=", 1) + values.setdefault(key.strip(), value.strip().strip("'\"")) + except (OSError, UnicodeError): + continue + +providers = {} +if values.get("DEEPSEEK_API_KEY"): + providers["deepseek"] = { + "name": "DeepSeek", + "base_url": "https://api.deepseek.com", + "api_key": values["DEEPSEEK_API_KEY"], + "models": ["deepseek-chat", "deepseek-reasoner"], + } +elif values.get("OPENAI_API_KEY"): + providers["openai"] = { + "name": "OpenAI", + "base_url": "https://api.openai.com/v1", + "api_key": values["OPENAI_API_KEY"], + "models": ["gpt-4.1-mini"], + } +elif values.get("DASHSCOPE_API_KEY"): + providers["qwen"] = { + "name": "Qwen", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "api_key": values["DASHSCOPE_API_KEY"], + "models": ["qwen-plus"], + } + +if providers: + destination.parent.mkdir(parents=True, exist_ok=True) + temporary = destination.with_suffix(".tmp") + temporary.write_text(json.dumps({ + "schema": "guanghu.hololake-ai-providers/v1", + "providers": providers, + }, ensure_ascii=False, indent=2) + "\n") + temporary.chmod(0o640) + temporary.replace(destination) +PY +fi + +if [[ -f "$provider_registry" ]]; then + chown root:guanghu-authz "$provider_registry" + chmod 0640 "$provider_registry" +fi + +[[ -d "$knowledge_repo" ]] || { + echo "registered HoloLake knowledge repository is missing" >&2 + exit 1 +} +if command -v setfacl >/dev/null 2>&1; then + setfacl -R -m u:guanghu-authz:rX "$knowledge_repo" + setfacl -R -d -m u:guanghu-authz:rX "$knowledge_repo" +fi +runuser -u guanghu-authz -- git --git-dir="$knowledge_repo" rev-parse --verify refs/heads/main >/dev/null + systemctl daemon-reload systemctl enable --now lake-lamp-architecture-provision.service systemctl restart lake-lamp-authz.service @@ -31,4 +206,17 @@ systemctl enable --now lake-lamp-deployment-event-worker.service systemctl is-active --quiet lake-lamp-architecture-provision.service systemctl is-active --quiet lake-lamp-authz.service systemctl is-active --quiet lake-lamp-deployment-event-worker.service -echo ARCHITECTURE_PROVISIONER_INSTALLED +health=$(/usr/bin/curl -fsS --max-time 10 http://127.0.0.1:3921/health) +/usr/bin/node -e ' +const health = JSON.parse(process.argv[1]); +if (!health.ok || health.service !== "lake-lamp-authz") process.exit(1); +if (!health.hololake_mobile || !health.hololake_mobile.email_session || !health.hololake_mobile.knowledge_snapshot) process.exit(1); +' "$health" +if [[ -f "$provider_registry" ]]; then + /usr/bin/node -e ' +const health = JSON.parse(process.argv[1]); +if (!health.hololake_mobile || !health.hololake_mobile.ai_gateway) process.exit(1); +' "$health" +fi +trap - EXIT +printf 'HOLOLAKE_MOBILE_CAPABILITIES_INSTALLED backup=%s\n' "$backup_root" diff --git a/server-tools/lake-lamp-authz/install-architecture-provisioner.test.js b/server-tools/lake-lamp-authz/install-architecture-provisioner.test.js new file mode 100644 index 0000000..cb84118 --- /dev/null +++ b/server-tools/lake-lamp-authz/install-architecture-provisioner.test.js @@ -0,0 +1,40 @@ +"use strict"; + +const test = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); + +const source = fs.readFileSync( + path.join(__dirname, "install-architecture-provisioner.sh"), + "utf8", +); + +test("bootstrap installer preserves secrets and deploys the complete HoloLake capability set", () => { + for (const file of [ + "server.js", + "guanghu-router.js", + "repo-push-broker.js", + "hololake-session.js", + "hololake-capabilities.js", + ]) { + assert.match(source, new RegExp(`\\b${file.replaceAll(".", "\\.")}\\b`)); + } + assert.match(source, /HOLOLAKE_SESSION_PEPPER/); + assert.match(source, /openssl rand -hex 32/); + assert.match(source, /manual-backups\/lake-lamp-authz-/); + assert.match(source, /rollback\(\)/); + assert.match(source, /setfacl -R -m u:guanghu-authz:rX/); + assert.doesNotMatch(source, /cat ["']?\$authorization_env/); + assert.doesNotMatch(source, /set -x/); +}); + +test("provider migration writes only a private registry and never prints API keys", () => { + assert.match(source, /DEEPSEEK_API_KEY/); + assert.match(source, /OPENAI_API_KEY/); + assert.match(source, /DASHSCOPE_API_KEY/); + assert.match(source, /chmod 0640 "\$provider_registry"/); + assert.match(source, /chown root:guanghu-authz "\$provider_registry"/); + assert.doesNotMatch(source, /echo .*API_KEY/); + assert.doesNotMatch(source, /printf .*API_KEY/); +}); diff --git a/server-tools/lake-lamp-authz/server.js b/server-tools/lake-lamp-authz/server.js index a26c394..8362b57 100644 --- a/server-tools/lake-lamp-authz/server.js +++ b/server-tools/lake-lamp-authz/server.js @@ -256,6 +256,11 @@ function createApp(options = {}) { session_ttl: manager.sessionTtl, max_session_lifetime: manager.maxSessionLifetime, auto_renew_on_activity: true, + hololake_mobile: { + email_session: Boolean(hololakeSessionManager), + knowledge_snapshot: Boolean(hololakeKnowledgeProvider), + ai_gateway: Boolean(hololakeAiGateway), + }, }); if (req.method === "GET" && url.pathname === "/api/public/capabilities") return json(res, 200, { schema: "guanghu.lake-lamp-public-workorder/v1",