feat(zhulan): add restricted remote development cell
This commit is contained in:
parent
5f9e83e1b7
commit
366b8911e4
22 changed files with 4503 additions and 0 deletions
|
|
@ -0,0 +1,41 @@
|
|||
# BS-GZ-006 only: domain/TLS/reverse proxy. UI and all real services remain on BS-SG-003.
|
||||
# The local upstream port must be supplied by a dedicated restricted tunnel.
|
||||
location = /zhulan {
|
||||
return 308 /zhulan/;
|
||||
}
|
||||
|
||||
# Runtime-only verification is reachable from the BS-SG-003 loopback executor,
|
||||
# never through the Guangzhou public projection.
|
||||
location ^~ /zhulan/api/v1/runtime/ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ^~ /zhulan/ {
|
||||
proxy_pass http://127.0.0.1:17631/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Prefix /zhulan;
|
||||
# Do not inherit a client-supplied X-Forwarded-For chain. The runtime uses
|
||||
# this value for per-source rate limits.
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
client_max_body_size 1m;
|
||||
}
|
||||
|
||||
# RFC 8414 path-aware discovery aliases for issuer
|
||||
# https://guanghulab.com/zhulan. These are metadata projections only.
|
||||
location = /.well-known/oauth-authorization-server/zhulan {
|
||||
proxy_pass http://127.0.0.1:17631/.well-known/oauth-authorization-server;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
|
||||
location = /.well-known/oauth-protected-resource/zhulan/mcp {
|
||||
proxy_pass http://127.0.0.1:17631/.well-known/oauth-protected-resource;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
110
server-tools/zhulan-remote-cell/deploy/install-front-proxy.sh
Executable file
110
server-tools/zhulan-remote-cell/deploy/install-front-proxy.sh
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "INSTALL_REQUIRES_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR="${1:-}"
|
||||
TUNNEL_PUBLIC_KEY_FILE="${2:-}"
|
||||
if [[ -z "$SOURCE_DIR" || ! -f "$SOURCE_DIR/deploy/guanghulab-zhulan.nginx.conf" ]]; then
|
||||
echo "SOURCE_DIR_INVALID" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$TUNNEL_PUBLIC_KEY_FILE" || ! -f "$TUNNEL_PUBLIC_KEY_FILE" ]]; then
|
||||
echo "TUNNEL_PUBLIC_KEY_FILE_INVALID" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
EXPECTED_HOST="${ZHULAN_EXPECTED_HOST:-VM-0-16-ubuntu}"
|
||||
EXPECTED_DMI="${ZHULAN_EXPECTED_DMI:-b2ecf109-1999-4643-b223-e67e24d7667d}"
|
||||
ACTUAL_HOST="$(hostname)"
|
||||
ACTUAL_DMI="$(tr '[:upper:]' '[:lower:]' </sys/class/dmi/id/product_uuid)"
|
||||
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
|
||||
echo "TARGET_IDENTITY_MISMATCH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SITE_FILE="/etc/nginx/sites-enabled/guanghulab"
|
||||
SNIPPET_FILE="/etc/nginx/snippets/guanghu-zhulan.conf"
|
||||
SSHD_DROPIN="/etc/ssh/sshd_config.d/80-zhulan-proxy.conf"
|
||||
PROXY_HOME="/var/lib/guanghu/zhulan-proxy"
|
||||
AUTH_KEYS="$PROXY_HOME/.ssh/authorized_keys"
|
||||
BACKUP_ROOT="/var/backups/guanghu/zhulan-front"
|
||||
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
|
||||
if [[ ! -f "$SITE_FILE" ]]; then
|
||||
echo "GUANGHULAB_SITE_NOT_FOUND" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -q 'include /etc/nginx/snippets/guanghu-ai-discovery.conf;' "$SITE_FILE"; then
|
||||
echo "GUANGHULAB_INCLUDE_ANCHOR_NOT_FOUND" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
getent passwd zhulan-proxy >/dev/null || {
|
||||
echo "PROXY_USER_MUST_BE_PRECREATED" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
PUBLIC_KEY="$(tr -d '\r\n' <"$TUNNEL_PUBLIC_KEY_FILE")"
|
||||
if [[ "$PUBLIC_KEY" != ssh-ed25519\ * ]]; then
|
||||
echo "TUNNEL_PUBLIC_KEY_NOT_ED25519" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -d -o root -g root -m 0700 "$BACKUP_ROOT"
|
||||
install -m 0644 "$SITE_FILE" "$BACKUP_ROOT/guanghulab-$STAMP"
|
||||
[[ ! -f "$SNIPPET_FILE" ]] || install -m 0644 "$SNIPPET_FILE" "$BACKUP_ROOT/snippet-$STAMP"
|
||||
[[ ! -f "$SSHD_DROPIN" ]] || install -m 0600 "$SSHD_DROPIN" "$BACKUP_ROOT/sshd-$STAMP"
|
||||
|
||||
install -d -o zhulan-proxy -g zhulan-proxy -m 0700 "$PROXY_HOME" "$PROXY_HOME/.ssh"
|
||||
{
|
||||
printf '%s %s\n' \
|
||||
'restrict,port-forwarding,permitlisten="127.0.0.1:17631"' \
|
||||
"$PUBLIC_KEY"
|
||||
} >"$AUTH_KEYS.tmp"
|
||||
chown zhulan-proxy:zhulan-proxy "$AUTH_KEYS.tmp"
|
||||
chmod 0600 "$AUTH_KEYS.tmp"
|
||||
mv "$AUTH_KEYS.tmp" "$AUTH_KEYS"
|
||||
|
||||
cat >"$SSHD_DROPIN.tmp" <<'EOF'
|
||||
Match User zhulan-proxy
|
||||
AuthenticationMethods publickey
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding remote
|
||||
GatewayPorts no
|
||||
PermitListen 127.0.0.1:17631
|
||||
PermitOpen none
|
||||
PermitTTY no
|
||||
X11Forwarding no
|
||||
PermitTunnel no
|
||||
PermitUserRC no
|
||||
MaxSessions 0
|
||||
EOF
|
||||
chown root:root "$SSHD_DROPIN.tmp"
|
||||
chmod 0600 "$SSHD_DROPIN.tmp"
|
||||
mv "$SSHD_DROPIN.tmp" "$SSHD_DROPIN"
|
||||
sshd -t
|
||||
|
||||
install -o root -g root -m 0644 \
|
||||
"$SOURCE_DIR/deploy/guanghulab-zhulan.nginx.conf" "$SNIPPET_FILE"
|
||||
if ! grep -q 'include /etc/nginx/snippets/guanghu-zhulan.conf;' "$SITE_FILE"; then
|
||||
sed -i \
|
||||
'/include \/etc\/nginx\/snippets\/guanghu-ai-discovery.conf;/a\ include /etc/nginx/snippets/guanghu-zhulan.conf;' \
|
||||
"$SITE_FILE"
|
||||
fi
|
||||
|
||||
if ! nginx -t; then
|
||||
install -m 0644 "$BACKUP_ROOT/guanghulab-$STAMP" "$SITE_FILE"
|
||||
nginx -t
|
||||
echo "NGINX_VALIDATION_FAILED_ROLLED_BACK" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
systemctl reload ssh
|
||||
systemctl reload nginx
|
||||
printf '{"schema":"guanghu.zhulan-front-install-receipt/v1","development_id":"DEV-20260813-005","front_node":"BS-GZ-006","role":"DOMAIN_TLS_REVERSE_PROXY_ONLY","listener":"127.0.0.1:17631","installed_at":"%s"}\n' "$STAMP"
|
||||
191
server-tools/zhulan-remote-cell/deploy/install-runtime.sh
Executable file
191
server-tools/zhulan-remote-cell/deploy/install-runtime.sh
Executable file
|
|
@ -0,0 +1,191 @@
|
|||
#!/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:]' </sys/class/dmi/id/product_uuid)"
|
||||
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
|
||||
echo "TARGET_IDENTITY_MISMATCH host=$ACTUAL_HOST dmi=$ACTUAL_DMI" >&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" <<EOF
|
||||
{
|
||||
"schema": "guanghu.zhulan-runtime-install-receipt/v1",
|
||||
"development_id": "DEV-20260813-005",
|
||||
"runtime_node": "BS-SG-003",
|
||||
"front_node_role": "BS-GZ-006_PROXY_ONLY",
|
||||
"installed_at": "$STAMP",
|
||||
"source_tree_hash": "$SOURCE_SHA256",
|
||||
"source_commit": "$SOURCE_COMMIT",
|
||||
"service": "zhulan-remote-cell.service",
|
||||
"validation_executor": "zhulan-validation-executor.service",
|
||||
"listener": "127.0.0.1:17631",
|
||||
"health": "PASS"
|
||||
}
|
||||
EOF
|
||||
chown zhulan-runtime:zhulan-runtime "$STATE_ROOT/install-receipt.json.tmp"
|
||||
chmod 0600 "$STATE_ROOT/install-receipt.json.tmp"
|
||||
mv "$STATE_ROOT/install-receipt.json.tmp" "$STATE_ROOT/install-receipt.json"
|
||||
|
||||
cat "$STATE_ROOT/install-receipt.json"
|
||||
121
server-tools/zhulan-remote-cell/deploy/install-tunnel-service.sh
Executable file
121
server-tools/zhulan-remote-cell/deploy/install-tunnel-service.sh
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "INSTALL_REQUIRES_ROOT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FRONT_HOST="${1:-}"
|
||||
KNOWN_HOSTS_SOURCE="${2:-}"
|
||||
if [[ ! "$FRONT_HOST" =~ ^[0-9A-Fa-f:.]+$ ]]; then
|
||||
echo "FRONT_HOST_MUST_BE_LITERAL_IP" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$KNOWN_HOSTS_SOURCE" || ! -f "$KNOWN_HOSTS_SOURCE" ]]; then
|
||||
echo "KNOWN_HOSTS_SOURCE_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:]' </sys/class/dmi/id/product_uuid)"
|
||||
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
|
||||
echo "TARGET_IDENTITY_MISMATCH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONFIG_ROOT="/etc/guanghu/zhulan-front-tunnel"
|
||||
KEY_FILE="$CONFIG_ROOT/id_ed25519"
|
||||
KNOWN_HOSTS="$CONFIG_ROOT/known_hosts"
|
||||
SSH_CONFIG="$CONFIG_ROOT/ssh_config"
|
||||
SERVICE_FILE="/etc/systemd/system/zhulan-front-tunnel.service"
|
||||
if [[ ! -f "$KEY_FILE" || ! -f "$KEY_FILE.pub" ]]; then
|
||||
echo "TUNNEL_IDENTITY_NOT_PREPARED" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! getent passwd zhulan-tunnel >/dev/null; then
|
||||
echo "TUNNEL_USER_NOT_PREPARED" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -o root -g zhulan-tunnel -m 0640 "$KNOWN_HOSTS_SOURCE" "$KNOWN_HOSTS"
|
||||
if ! ssh-keygen -F "$FRONT_HOST" -f "$KNOWN_HOSTS" >/dev/null; then
|
||||
echo "PINNED_FRONT_HOST_KEY_MISSING" >&2
|
||||
exit 1
|
||||
fi
|
||||
EXPECTED_FRONT_HOST_KEY="${ZHULAN_EXPECTED_FRONT_HOST_KEY:-SHA256:P2EtuYFg8hptha4s0auP4yzp+Wg+H27bw4mdqzk1Cvk}"
|
||||
ACTUAL_FRONT_HOST_KEY="$(ssh-keygen -F "$FRONT_HOST" -f "$KNOWN_HOSTS" | awk 'NF && $1 !~ /^#/ {print $2, $3}' | ssh-keygen -lf - 2>/dev/null | awk '{print $2}' | head -1)"
|
||||
if [[ "$ACTUAL_FRONT_HOST_KEY" != "$EXPECTED_FRONT_HOST_KEY" ]]; then
|
||||
echo "PINNED_FRONT_HOST_KEY_MISMATCH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat >"$SSH_CONFIG.tmp" <<EOF
|
||||
Host guanghu-zhulan-front
|
||||
HostName $FRONT_HOST
|
||||
User zhulan-proxy
|
||||
Port 22
|
||||
IdentityFile $KEY_FILE
|
||||
IdentitiesOnly yes
|
||||
UserKnownHostsFile $KNOWN_HOSTS
|
||||
StrictHostKeyChecking yes
|
||||
BatchMode yes
|
||||
RequestTTY no
|
||||
ExitOnForwardFailure yes
|
||||
ServerAliveInterval 30
|
||||
ServerAliveCountMax 3
|
||||
RemoteForward 127.0.0.1:17631 127.0.0.1:17631
|
||||
EOF
|
||||
chown root:zhulan-tunnel "$SSH_CONFIG.tmp"
|
||||
chmod 0640 "$SSH_CONFIG.tmp"
|
||||
mv "$SSH_CONFIG.tmp" "$SSH_CONFIG"
|
||||
|
||||
cat >"$SERVICE_FILE.tmp" <<'EOF'
|
||||
[Unit]
|
||||
Description=Zhulan SG003 to GZ006 loopback-only reverse proxy tunnel
|
||||
After=network-online.target zhulan-remote-cell.service
|
||||
Wants=network-online.target
|
||||
Requires=zhulan-remote-cell.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=zhulan-tunnel
|
||||
Group=zhulan-tunnel
|
||||
ExecStart=/usr/bin/ssh -F /etc/guanghu/zhulan-front-tunnel/ssh_config -NT guanghu-zhulan-front
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
UMask=0077
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
ReadOnlyPaths=/etc/guanghu/zhulan-front-tunnel
|
||||
StateDirectory=guanghu/zhulan-front-tunnel
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
chown root:root "$SERVICE_FILE.tmp"
|
||||
chmod 0644 "$SERVICE_FILE.tmp"
|
||||
mv "$SERVICE_FILE.tmp" "$SERVICE_FILE"
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now zhulan-front-tunnel.service
|
||||
for _ in $(seq 1 20); do
|
||||
if systemctl is-active --quiet zhulan-front-tunnel.service; then
|
||||
systemctl show zhulan-front-tunnel.service -p ActiveState -p SubState -p MainPID
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
systemctl status zhulan-front-tunnel.service --no-pager >&2 || true
|
||||
exit 1
|
||||
36
server-tools/zhulan-remote-cell/deploy/prepare-tunnel-identity.sh
Executable file
36
server-tools/zhulan-remote-cell/deploy/prepare-tunnel-identity.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "INSTALL_REQUIRES_ROOT" >&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:]' </sys/class/dmi/id/product_uuid)"
|
||||
if [[ "$ACTUAL_HOST" != "$EXPECTED_HOST" || "$ACTUAL_DMI" != "$EXPECTED_DMI" ]]; then
|
||||
echo "TARGET_IDENTITY_MISMATCH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONFIG_ROOT="/etc/guanghu/zhulan-front-tunnel"
|
||||
KEY_FILE="$CONFIG_ROOT/id_ed25519"
|
||||
|
||||
getent passwd zhulan-tunnel >/dev/null || {
|
||||
echo "TUNNEL_USER_MUST_BE_PRECREATED" >&2
|
||||
exit 1
|
||||
}
|
||||
install -d -o root -g zhulan-tunnel -m 0750 "$CONFIG_ROOT"
|
||||
install -d -o zhulan-tunnel -g zhulan-tunnel -m 0700 /var/lib/guanghu/zhulan-front-tunnel
|
||||
|
||||
if [[ ! -f "$KEY_FILE" ]]; then
|
||||
ssh-keygen -q -t ed25519 -N '' -C 'zhulan-sg003-to-gz006-loopback-only' -f "$KEY_FILE"
|
||||
fi
|
||||
chown root:zhulan-tunnel "$KEY_FILE" "$KEY_FILE.pub"
|
||||
chmod 0640 "$KEY_FILE"
|
||||
chmod 0644 "$KEY_FILE.pub"
|
||||
|
||||
ssh-keygen -lf "$KEY_FILE.pub"
|
||||
echo "TUNNEL_PUBLIC_KEY_FILE=$KEY_FILE.pub"
|
||||
10
server-tools/zhulan-remote-cell/deploy/zhulan-bwrap.apparmor
Normal file
10
server-tools/zhulan-remote-cell/deploy/zhulan-bwrap.apparmor
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Ubuntu 24.04 restricts unprivileged user namespaces unless the executable
|
||||
# has an AppArmor profile that explicitly allows userns. This profile applies
|
||||
# only to the root-owned Zhulan bwrap copy; that binary is executable only by
|
||||
# root and the zhulan-runtime group.
|
||||
abi <abi/4.0>,
|
||||
include <tunables/global>
|
||||
|
||||
profile zhulan-remote-cell-bwrap /opt/guanghu/zhulan-remote-cell/runtime/zhulan-bwrap flags=(unconfined) {
|
||||
userns,
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
ZHULAN_BIND=127.0.0.1
|
||||
ZHULAN_PORT=17631
|
||||
ZHULAN_DB=/var/lib/guanghu/zhulan-remote-cell/state.sqlite3
|
||||
ZHULAN_SECRET_FILE=/etc/guanghu/secrets/zhulan-remote-cell.secret
|
||||
ZHULAN_POLICY=/opt/guanghu/zhulan-remote-cell/policy.json
|
||||
ZHULAN_UI_DIR=/opt/guanghu/zhulan-remote-cell/ui
|
||||
ZHULAN_WORKSPACE_ROOT=/srv/guanghu/zhulan-cell/workspaces
|
||||
ZHULAN_CANDIDATE_ROOT=/srv/guanghu/zhulan-cell/candidates
|
||||
ZHULAN_FORGEJO_VERIFY_URL=https://guanghulab.com/code/api/v1/user
|
||||
ZHULAN_OWNER_LOGIN=bingshuo
|
||||
ZHULAN_COOKIE_SECURE=1
|
||||
ZHULAN_COOKIE_PATH=/zhulan/
|
||||
ZHULAN_PUBLIC_CREATE_LIMIT=12
|
||||
ZHULAN_OWNER_LOGIN_LIMIT=8
|
||||
ZHULAN_OAUTH_REGISTER_LIMIT=20
|
||||
ZHULAN_BWRAP=/opt/guanghu/zhulan-remote-cell/runtime/zhulan-bwrap
|
||||
ZHULAN_VALIDATION_SOCKET=/run/guanghu/zhulan-validation/executor.sock
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
[Unit]
|
||||
Description=Guanghu Zhulan Restricted Remote Development Cell
|
||||
After=network-online.target zhulan-validation-executor.service
|
||||
Wants=network-online.target
|
||||
Requires=zhulan-validation-executor.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=zhulan-runtime
|
||||
Group=zhulan-runtime
|
||||
EnvironmentFile=/etc/guanghu/zhulan-remote-cell.env
|
||||
ExecStart=/usr/bin/python3 /opt/guanghu/zhulan-remote-cell/runtime/zhulan_cell.py
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
UMask=0077
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictNamespaces=user mnt pid net ipc uts
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
SystemCallArchitectures=native
|
||||
ReadOnlyPaths=/opt/guanghu/zhulan-remote-cell /etc/guanghu/zhulan-remote-cell.env /etc/guanghu/secrets/zhulan-remote-cell.secret
|
||||
ReadWritePaths=/var/lib/guanghu/zhulan-remote-cell
|
||||
ReadWritePaths=/srv/guanghu/zhulan-cell/workspaces
|
||||
ReadWritePaths=/srv/guanghu/zhulan-cell/candidates
|
||||
StateDirectory=guanghu/zhulan-remote-cell
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
[Unit]
|
||||
Description=Zhulan local-only allowlisted validation executor
|
||||
After=local-fs.target
|
||||
Before=zhulan-remote-cell.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=zhulan-runtime
|
||||
Group=zhulan-runtime
|
||||
EnvironmentFile=/etc/guanghu/zhulan-remote-cell.env
|
||||
ExecStart=/usr/bin/python3 /opt/guanghu/zhulan-remote-cell/runtime/zhulan_validation_executor.py
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
UMask=0077
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
PrivateNetwork=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||
RestrictNamespaces=user mnt pid net ipc uts
|
||||
SystemCallArchitectures=native
|
||||
RuntimeDirectory=guanghu/zhulan-validation
|
||||
RuntimeDirectoryMode=0700
|
||||
ReadOnlyPaths=/opt/guanghu/zhulan-remote-cell /etc/guanghu/zhulan-remote-cell.env
|
||||
ReadOnlyPaths=/srv/guanghu/zhulan-cell/workspaces
|
||||
InaccessiblePaths=/etc/guanghu/secrets
|
||||
ReadOnlyPaths=/var/lib/guanghu/zhulan-remote-cell
|
||||
InaccessiblePaths=/srv/guanghu/zhulan-cell/candidates
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Reference in a new issue