#!/usr/bin/env bash set -euo pipefail if [[ "$(id -u)" -ne 0 ]]; then echo "INSTALL_REQUIRES_ROOT" >&2 exit 1 fi SOURCE_DIR="${1:-}" if [[ -z "$SOURCE_DIR" || ! -f "$SOURCE_DIR/runtime/zhulan_cell.py" ]]; then echo "SOURCE_DIR_INVALID" >&2 exit 1 fi EXPECTED_HOST="${ZHULAN_EXPECTED_HOST:-VM-12-12-ubuntu}" EXPECTED_DMI="${ZHULAN_EXPECTED_DMI:-7a500f2d-9aed-4b93-b3b8-59c87c65d031}" ACTUAL_HOST="$(hostname)" ACTUAL_DMI="$(tr '[:upper:]' '[:lower:]' &2 exit 1 fi if ! command -v bwrap >/dev/null 2>&1 || ! command -v apparmor_parser >/dev/null 2>&1; then apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends apparmor bubblewrap fi INSTALL_ROOT="/opt/guanghu/zhulan-remote-cell" STATE_ROOT="/var/lib/guanghu/zhulan-remote-cell" WORKSPACE_ROOT="/srv/guanghu/zhulan-cell/workspaces" CANDIDATE_ROOT="/srv/guanghu/zhulan-cell/candidates" SECRET_ROOT="/etc/guanghu/secrets" ENV_FILE="/etc/guanghu/zhulan-remote-cell.env" SERVICE_FILE="/etc/systemd/system/zhulan-remote-cell.service" EXECUTOR_SERVICE_FILE="/etc/systemd/system/zhulan-validation-executor.service" APPARMOR_FILE="/etc/apparmor.d/zhulan-remote-cell-bwrap" BACKUP_ROOT="/var/backups/guanghu/zhulan-remote-cell" STAMP="$(date -u +%Y%m%dT%H%M%SZ)" getent passwd zhulan-runtime >/dev/null || { echo "RUNTIME_USER_MUST_BE_PRECREATED" >&2 exit 1 } install -d -o root -g root -m 0755 "$INSTALL_ROOT" "$BACKUP_ROOT" install -d -o zhulan-runtime -g zhulan-runtime -m 0700 "$STATE_ROOT" install -d -o zhulan-runtime -g zhulan-runtime -m 0700 "$WORKSPACE_ROOT" install -d -o zhulan-runtime -g zhulan-runtime -m 0700 "$CANDIDATE_ROOT" install -d -o root -g zhulan-runtime -m 0750 "$SECRET_ROOT" if [[ -d "$INSTALL_ROOT/runtime" ]]; then tar -C "$INSTALL_ROOT" -czf "$BACKUP_ROOT/runtime-$STAMP.tgz" runtime ui policy.json 2>/dev/null || true fi if [[ -f "$ENV_FILE" ]]; then install -m 0600 "$ENV_FILE" "$BACKUP_ROOT/env-$STAMP" fi if [[ -f "$SERVICE_FILE" ]]; then install -m 0644 "$SERVICE_FILE" "$BACKUP_ROOT/service-$STAMP" fi if [[ -f "$EXECUTOR_SERVICE_FILE" ]]; then install -m 0644 "$EXECUTOR_SERVICE_FILE" "$BACKUP_ROOT/executor-service-$STAMP" fi if [[ -f "$APPARMOR_FILE" ]]; then install -m 0644 "$APPARMOR_FILE" "$BACKUP_ROOT/apparmor-$STAMP" fi install -d -o root -g root -m 0755 "$INSTALL_ROOT/runtime" "$INSTALL_ROOT/ui" "$INSTALL_ROOT/contracts" install -o root -g root -m 0755 "$SOURCE_DIR/runtime/zhulan_cell.py" "$INSTALL_ROOT/runtime/zhulan_cell.py" install -o root -g root -m 0755 "$SOURCE_DIR/runtime/zhulan_code_gate.py" "$INSTALL_ROOT/runtime/zhulan_code_gate.py" install -o root -g root -m 0755 "$SOURCE_DIR/runtime/zhulan_validation_executor.py" "$INSTALL_ROOT/runtime/zhulan_validation_executor.py" # Keep the distro bwrap binary non-setuid. A dedicated group-executable copy # lets AppArmor grant userns only to this runtime path instead of weakening the # host-wide unprivileged-userns policy or opening /usr/bin/bwrap for all users. install -o root -g zhulan-runtime -m 0750 /usr/bin/bwrap "$INSTALL_ROOT/runtime/zhulan-bwrap" install -o root -g root -m 0644 "$SOURCE_DIR/ui/index.html" "$INSTALL_ROOT/ui/index.html" install -o root -g root -m 0644 "$SOURCE_DIR/ui/styles.css" "$INSTALL_ROOT/ui/styles.css" install -o root -g root -m 0644 "$SOURCE_DIR/ui/app.js" "$INSTALL_ROOT/ui/app.js" install -o root -g root -m 0644 "$SOURCE_DIR/policy.example.json" "$INSTALL_ROOT/policy.json" install -o root -g root -m 0644 "$SOURCE_DIR/README.md" "$INSTALL_ROOT/README.md" if [[ -d "$SOURCE_DIR/contracts" ]]; then find "$SOURCE_DIR/contracts" -maxdepth 1 -type f -print0 | while IFS= read -r -d '' file; do install -o root -g root -m 0444 "$file" "$INSTALL_ROOT/contracts/$(basename "$file")" done fi # Install current Fifth Domain brains and Zhulan entry as read-only runtime # contracts from the same exact repository checkout as this installer. REPO_ROOT="$(cd "$SOURCE_DIR/../.." && pwd)" SOURCE_COMMIT="${ZHULAN_SOURCE_COMMIT:-}" if [[ -z "$SOURCE_COMMIT" ]] && git -C "$REPO_ROOT" rev-parse HEAD >/dev/null 2>&1; then SOURCE_COMMIT="$(git -C "$REPO_ROOT" rev-parse HEAD)" fi if [[ ! "$SOURCE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then echo "SOURCE_COMMIT_REQUIRED" >&2 exit 1 fi for relative in \ "skills/shared/guanghu-ui-brain/SKILL.md" \ "skills/shared/guanghu-ui-brain/BRAIN.hdlp" \ "skills/shared/guanghu-client-ui-brain/SKILL.md" \ "skills/shared/guanghu-client-ui-brain/BRAIN.hdlp" \ "光之湖/ICE-GL-ZL-001-铸澜/WAKE.hdlp" \ "光之湖/ICE-GL-ZL-001-铸澜/INDEX.hdlp" \ "光之湖/ICE-GL-ZL-001-铸澜/CURRENT.hdlp" \ "光之湖/ICE-GL-ZL-001-铸澜/PERMANENT-MEMORY-WRITE-PROTOCOL.hdlp"; do source_file="$REPO_ROOT/$relative" if [[ ! -f "$source_file" ]]; then echo "CONTRACT_SOURCE_MISSING $relative" >&2 exit 1 fi safe_name="$(printf '%s' "$relative" | tr '/ ' '__')" install -o root -g root -m 0444 "$source_file" "$INSTALL_ROOT/contracts/$safe_name" done printf '%s\n' "$SOURCE_COMMIT" >"$INSTALL_ROOT/contracts/REPOSITORY-SOURCE-SHA" chmod 0444 "$INSTALL_ROOT/contracts/REPOSITORY-SOURCE-SHA" SECRET_FILE="$SECRET_ROOT/zhulan-remote-cell.secret" if [[ ! -f "$SECRET_FILE" ]]; then umask 0177 openssl rand -hex 48 >"$SECRET_FILE" fi chown root:zhulan-runtime "$SECRET_FILE" chmod 0640 "$SECRET_FILE" install -o root -g zhulan-runtime -m 0640 "$SOURCE_DIR/deploy/zhulan-remote-cell.env.example" "$ENV_FILE" install -o root -g root -m 0644 "$SOURCE_DIR/deploy/zhulan-remote-cell.service" "$SERVICE_FILE" install -o root -g root -m 0644 "$SOURCE_DIR/deploy/zhulan-validation-executor.service" "$EXECUTOR_SERVICE_FILE" install -o root -g root -m 0644 "$SOURCE_DIR/deploy/zhulan-bwrap.apparmor" "$APPARMOR_FILE" apparmor_parser -r "$APPARMOR_FILE" systemctl daemon-reload systemctl enable zhulan-validation-executor.service systemctl restart zhulan-validation-executor.service for _ in $(seq 1 30); do if [[ -S /run/guanghu/zhulan-validation/executor.sock ]]; then break fi sleep 1 done if [[ ! -S /run/guanghu/zhulan-validation/executor.sock ]]; then systemctl status zhulan-validation-executor.service --no-pager >&2 || true echo "VALIDATION_EXECUTOR_HEALTH_FAILED" >&2 exit 1 fi systemctl enable zhulan-remote-cell.service systemctl restart zhulan-remote-cell.service for _ in $(seq 1 30); do if curl --fail --silent --show-error --max-time 2 http://127.0.0.1:17631/health >"$STATE_ROOT/health.json.tmp"; then mv "$STATE_ROOT/health.json.tmp" "$STATE_ROOT/health.json" break fi sleep 1 done if [[ ! -s "$STATE_ROOT/health.json" ]]; then systemctl status zhulan-remote-cell.service --no-pager >&2 || true echo "RUNTIME_HEALTH_FAILED" >&2 exit 1 fi SOURCE_SHA256="$(find \ "$INSTALL_ROOT/runtime" \ "$INSTALL_ROOT/ui" \ "$INSTALL_ROOT/contracts" \ "$INSTALL_ROOT/policy.json" \ "$INSTALL_ROOT/README.md" \ -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')" cat >"$STATE_ROOT/install-receipt.json.tmp" <