diff --git a/.agents/skills/unidesk-cicd/SKILL.md b/.agents/skills/unidesk-cicd/SKILL.md index 22d74cc4..e16764ca 100644 --- a/.agents/skills/unidesk-cicd/SKILL.md +++ b/.agents/skills/unidesk-cicd/SKILL.md @@ -354,6 +354,7 @@ bun scripts/cli.ts hwlab nodes control-plane legacy-cicd --help - k3s drop-in 未变化且节点容量已收敛时必须 `mutation=false`;禁止无条件重启、重装、强写或通过 CI 自动覆盖同一节点配置。 - WireGuard 配置一致时不得 restart;k3s API/容量暂时不可读时保持 unknown 并停止 mutation,禁止把控制通道抖动解释为配置漂移。 - worker agent 只在 YAML 派生摘要变化或 service 未运行时安装/恢复,不得在每次集群 apply 中先卸载再重装。 + - `Type=notify` agent 的 `activating` 且 MainPID 存在属于运行中;节点 Ready 时不得仅因 systemd 未进入 `active` 而重复重装。 - worker 必须先由集群入口按 `hostProxyConfigRef` 部署并验证 proxy client,再启动 agent;containerd 通过该 proxy 直接访问上游 registry,禁止用公共 registry mirror 代替出网链路。 - worker 安装使用短启动并由 control-plane Node Ready 轮询收口,禁止让 `trans`/SSH 长挂等待 systemd notify。 - worker 加入集群不改变 PaC consumer 的 source commit authority,也不授权人工 PipelineRun、Argo sync 或业务 rollout。 diff --git a/docs/reference/platform-infra.md b/docs/reference/platform-infra.md index 94d80199..1fd83980 100644 --- a/docs/reference/platform-infra.md +++ b/docs/reference/platform-infra.md @@ -42,6 +42,7 @@ - 内容一致时保持 `mutation=false`,不得无条件重启、重装或覆盖; - WireGuard 只在配置变化或 unit 未运行时重启/启动,避免短暂断网触发 control-plane 级联重启; - worker agent 以 owning YAML、版本、参数、集群内 registry 映射和 token 的脱敏摘要判断是否需要重装; + - `Type=notify` agent 在节点 Ready 时可能保持 `activating`;摘要一致、状态为 `active|activating` 且 MainPID 存在即视为运行中,禁止因此重复重装; - worker 安装只做短启动,终态由 control-plane 的 Node Ready 轮询判定,不得让宿主 SSH 会话等待 systemd notify; - k3s API 暂时不可读属于 unknown,不得当作容量漂移触发 restart; - 集群 CLI 只组合各自 owning domain,不借用全量基础设施入口扩大写入范围。 diff --git a/scripts/src/platform-infra-k3s-cluster.ts b/scripts/src/platform-infra-k3s-cluster.ts index ae10a5f8..ec3c4ea4 100644 --- a/scripts/src/platform-infra-k3s-cluster.ts +++ b/scripts/src/platform-infra-k3s-cluster.ts @@ -311,7 +311,9 @@ ping -c 3 -W 5 ${address(wg.controlPlaneAddress)} >/dev/null desired_hash=${shQuote(desiredHash)} desired_hash_file=/etc/unidesk/k3s-agent-desired.sha256 current_hash=$(cat "$desired_hash_file" 2>/dev/null || true) -if [ "$current_hash" = "$desired_hash" ] && systemctl is-active --quiet k3s-agent; then exit 0; fi +agent_state="$(systemctl show k3s-agent -p ActiveState --value 2>/dev/null || true)" +agent_pid="$(systemctl show k3s-agent -p MainPID --value 2>/dev/null || true)" +if [ "$current_hash" = "$desired_hash" ] && { [ "$agent_state" = active ] || [ "$agent_state" = activating ]; } && [ "\${agent_pid:-0}" -gt 0 ]; then exit 0; fi if [ -x /usr/local/bin/k3s-uninstall.sh ]; then /usr/local/bin/k3s-uninstall.sh; fi if [ -x /usr/local/bin/k3s-agent-uninstall.sh ]; then /usr/local/bin/k3s-agent-uninstall.sh; fi install -d -m 700 /etc/rancher/k3s @@ -362,14 +364,16 @@ PY function workerStatusScript(target: K3sClusterTarget): string { return `set -eu wg_active=false; systemctl is-active --quiet wg-quick@${target.wireguard.interfaceName} && wg_active=true -agent_active=false; systemctl is-active --quiet k3s-agent && agent_active=true +agent_state="$(systemctl show k3s-agent -p ActiveState --value 2>/dev/null || true)" +agent_pid="$(systemctl show k3s-agent -p MainPID --value 2>/dev/null || true)" old_server=false; systemctl is-active --quiet k3s && old_server=true ip_present=false; ip -4 address show dev ${target.wireguard.interfaceName} | grep -F ${shQuote(address(target.wireguard.workerAddress))} >/dev/null 2>&1 && ip_present=true -python3 - "$wg_active" "$agent_active" "$old_server" "$ip_present" <<'PY' +python3 - "$wg_active" "$agent_state" "$agent_pid" "$old_server" "$ip_present" <<'PY' import json, sys -wg, agent, old_server, ip_present = sys.argv[1:] -ok = wg == 'true' and agent == 'true' and old_server == 'false' and ip_present == 'true' -print(json.dumps({'ok': ok, 'wireguardActive': wg == 'true', 'agentActive': agent == 'true', 'standaloneServerActive': old_server == 'true', 'wireguardAddressPresent': ip_present == 'true'}, separators=(',', ':'))) +wg, agent_state, agent_pid, old_server, ip_present = sys.argv[1:] +agent_running = agent_state in ('active', 'activating') and int(agent_pid or 0) > 0 +ok = wg == 'true' and agent_running and old_server == 'false' and ip_present == 'true' +print(json.dumps({'ok': ok, 'wireguardActive': wg == 'true', 'agentActive': agent_state == 'active', 'agentRunning': agent_running, 'agentState': agent_state, 'agentMainPidPresent': int(agent_pid or 0) > 0, 'standaloneServerActive': old_server == 'true', 'wireguardAddressPresent': ip_present == 'true'}, separators=(',', ':'))) PY `; } @@ -564,6 +568,10 @@ function compactDomain(result: Record): Record } function parseFirstJson(value: string): Record { + const complete = value.trim(); + if (complete.startsWith("{")) { + try { return JSON.parse(complete) as Record; } catch { /* try line-delimited output */ } + } for (const line of value.split(/\r?\n/u)) { const text = line.trim(); if (!text.startsWith("{")) continue;