#!/usr/bin/env bash set -euo pipefail if [[ ${EUID} -ne 0 ]]; then echo "run as root" >&2 exit 1 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 "${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 "$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 } command -v setfacl >/dev/null 2>&1 || { echo "setfacl is required for private knowledge repository access" >&2 exit 1 } setfacl -R -m u:guanghu-authz:rX "$knowledge_repo" setfacl -R -d -m u:guanghu-authz:rX "$knowledge_repo" 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 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 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"