#cloud-config # Pangolin 加速节点一段式安装模板(与 #14 弹性开机共用)。 # # 渲染:控制面 provisioner 用 sed 替换所有 __PLACEHOLDER__(沿用 deploy/ 约定)。 # __CONTROL_PLANE__ 控制面 gRPC 地址 host:port # __SERVER_NAME__ 校验控制面证书的 TLS SNI(留空则取 host 部分) # __BOOTSTRAP_TOKEN__ 一次性引导 token(由 mtls.BootstrapTokenManager.IssueToken 预签发,15min TTL) # __DERIVE_KEY__ Hy2 口令派生密钥(与控制面同源,见 agentd.DeriveHy2Password) # __AGENT_VERSION__ pangolin-agent 版本(与产物命名/SHA 锁定一致) # __ARTIFACT_BASE__ 二进制与安装脚本下载根 URL # __INSTALL_SHA256__ install-node.sh 的 SHA-256(整链可复现校验) # # 安全:agent.env 内含 bootstrap token,权限 0600;token 一次性消费后即失效。 # 节点无状态:除 /etc/pangolin-agent/{node.key,node.crt,ca.crt,state.json} 与 # /etc/sing-box 外不落任何持久数据,且 state.json 仅含 dp_uuid + expires_at。 write_files: - path: /etc/pangolin-agent/agent.env permissions: '0600' owner: root:root content: | PANGOLIN_AGENT_CONTROL_PLANE=__CONTROL_PLANE__ PANGOLIN_AGENT_SERVER_NAME=__SERVER_NAME__ PANGOLIN_AGENT_BOOTSTRAP_TOKEN=__BOOTSTRAP_TOKEN__ PANGOLIN_AGENT_DERIVE_KEY=__DERIVE_KEY__ PANGOLIN_AGENT_STATE_DIR=/etc/pangolin-agent PANGOLIN_AGENT_SINGBOX_CONFIG=/etc/sing-box/config.json PANGOLIN_AGENT_SINGBOX_UNIT=sing-box PANGOLIN_AGENT_VERSION=__AGENT_VERSION__ runcmd: - | set -euo pipefail mkdir -p /opt/pangolin tmp="$(mktemp)" # 下载安装脚本并按锁定 SHA-256 校验后再执行(拒绝被篡改的脚本)。 curl -fsSL --retry 3 "__ARTIFACT_BASE__/install-node.sh" -o "$tmp" echo "__INSTALL_SHA256__ $tmp" | sha256sum -c - install -m 0755 "$tmp" /opt/pangolin/install-node.sh rm -f "$tmp" ARTIFACT_BASE="__ARTIFACT_BASE__" AGENT_VERSION="__AGENT_VERSION__" \ bash /opt/pangolin/install-node.sh # 整机 3–5 分钟内可服务:cloud-init 完成 → agent 首启 Enroll → Register 取全量配置 → # 渲染 sing-box config 并 restart sing-box → REALITY/Hy2 入站上线。 ## template: jinja-free Go text/template — rendered by ## server/internal/provision (CloudInitRenderer). Fields: ## {{.NodeUUID}} {{.BootstrapToken}} {{.Region}} {{.Role}} {{.Tier}} {{.ControlPlaneURL}} ## ## "Nodes are cattle, not pets" (doc/04 §2): this is the ENTIRE persistent ## footprint of a data-plane node — sing-box + agent binaries + a one-time ## bootstrap token. Zero user DB, zero logs, zero persistent state. ## ## SECURITY: the rendered output carries a one-time bootstrap token. It MUST NOT ## be logged. The token is consumed exactly once during mTLS enrollment (task #5) ## and is useless afterwards. #cloud-config write_files: # Node identity + enrollment parameters consumed by the agent on first boot. - path: /etc/pangolin/bootstrap.env permissions: "0600" owner: root:root content: | PANGOLIN_NODE_UUID={{.NodeUUID}} PANGOLIN_BOOTSTRAP_TOKEN={{.BootstrapToken}} PANGOLIN_CONTROL_PLANE_URL={{.ControlPlaneURL}} PANGOLIN_REGION={{.Region}} PANGOLIN_ROLE={{.Role}} PANGOLIN_TIER={{.Tier}} # systemd unit: agent enrolls (mTLS) then pulls runtime config from control plane. - path: /etc/systemd/system/pangolin-agent.service permissions: "0644" owner: root:root content: | [Unit] Description=Pangolin node agent (self-register + config pull) After=network-online.target Wants=network-online.target [Service] Type=simple EnvironmentFile=/etc/pangolin/bootstrap.env ExecStart=/usr/local/bin/pangolin-agent run # No-log node: agent does not persist connection logs. Restart=always RestartSec=5 [Install] WantedBy=multi-user.target runcmd: # 1. Harden: key-only SSH, no root password (doc/06 §3). - sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config - sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config - systemctl restart sshd || true # 2. Install the agent binary (pinned, checksum-verified by the install script, # delivered out-of-band by task #6). Placeholder URL resolved at build time. - install -d -m 0755 /etc/pangolin - /usr/local/bin/pangolin-agent-install || true # 3. Enroll + start. The agent reads bootstrap.env, performs mTLS enrollment, # and the control plane flips the node provisioning → probing → up. - systemctl daemon-reload - systemctl enable --now pangolin-agent.service # 4. Shred the one-time token from disk after enrollment (defence in depth). - bash -c 'sleep 60; shred -u /etc/pangolin/bootstrap.env 2>/dev/null || rm -f /etc/pangolin/bootstrap.env' # No swap file written to disk, no persistent data dirs — a destroyed node leaks nothing.