From 11701c603790f8ae58b1ffb208d1adad21cb8cc1 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Thu, 18 Jun 2026 11:33:42 +0000 Subject: [PATCH] fix(macos): patch vault role post-clone for macOS-standard dirs The vault role's 'Ensure standalone Vault directories exist' task creates /etc/vault.d and /opt/vault/data with owner: root and lacks the Darwin guard its sibling tasks have, so it fails under macOS become=false. Unlike the bridge dir (owned by the service user, fixable via -e), this owner: root is hardcoded and not overridable, so the role logic must change. Since the role lives in a separate playbooks repo, reuse the existing post-clone patch mechanism (cf. patch_playbook_user_systemd): add patch_playbook_vault_macos() that, on Darwin only, guards the directory task, makes vault dirs/binary OS-conditional (macOS -> ~/Library/Application Support/vault[/data], /opt/homebrew/bin/vault; Linux unchanged), and creates the user-owned data dir in macos.yml. Idempotent; verified against the real role. Documents TC-MAC-013. --- docs/case/macos_compatibility_tests.md | 13 ++++- docs/report/TC-MAC-012-26-06-18-19.md | 27 +++++++++ scripts/setup-ai-workspace-all-in-one.sh | 70 ++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/docs/case/macos_compatibility_tests.md b/docs/case/macos_compatibility_tests.md index 1b10cab..5452242 100644 --- a/docs/case/macos_compatibility_tests.md +++ b/docs/case/macos_compatibility_tests.md @@ -107,6 +107,16 @@ | **修复方案** | 双层:①`setup-ai-workspace-all-in-one.sh` 的 Darwin 分支注入 `-e xworkmate_bridge_base_dir="$HOME/Library/Application Support/cloud-neutral/xworkmate-bridge"`(`curl \| bash` 拉取的是本仓库脚本,playbooks 来自独立仓库,故脚本侧 `-e` 是该路径下唯一可生效的修复点);②role `defaults/main.yml` 将默认值改为按 `ansible_os_family` 的三元表达式,使离线/本地 playbook 路径亦正确 | | **生效前提** | `curl \| bash` 从 GitHub `main` 拉取脚本,修复必须先 push 到 `ai-workspace-lab/xworkspace-console` 的 `main`;否则远端仍是旧脚本(extra-vars 优先级最高,若 `-e` 已执行则绝不会回落到 `/opt`,由此可判定执行的是未修复的远端脚本) | +## TC-MAC-013: Vault standalone 目录写入系统路径被拒 + +| 项目 | 内容 | +|------|------| +| **触发文件** | `roles/vhosts/vault/tasks/main.yml`、`roles/vhosts/vault/vars/main.yml`、`roles/vhosts/vault/tasks/macos.yml` | +| **触发报错** | `TASK [roles/vhosts/vault/ : Ensure standalone Vault directories exist]` → `[Errno 13] Permission denied: b'/etc/vault.d'`、`b'/opt/vault'` | +| **根因** | “Ensure standalone Vault directories exist” 任务以 `owner: root` 创建 `/etc/vault.d` 与 `/opt/vault/data`,且**缺失** vault 角色其余 standalone 任务都带的 `ansible_os_family != 'Darwin'` 守卫。macOS 以 `become=false` 运行,既无权写 `/etc`、`/opt`,`owner: root` 的 chown 也无法完成。与 bridge 不同(其目录 owner 为服务用户,可由 `-e` 修复),该任务的 `owner: root` 为硬编码,无法用 extra-vars 覆盖,必须改 role 逻辑 | +| **目录策略** | Linux 保持 `/etc/vault.d`、`/opt/vault/data`;macOS 改用 Apple 标准 `~/Library/Application Support/vault`、`~/Library/Application Support/vault/data`;二进制路径 macOS 取 `/opt/homebrew/bin/vault`(brew 安装位置),免去需 sudo 的 `/usr/local/bin` 软链依赖 | +| **修复方案** | role 位于独立 playbooks 仓库,无法从本仓库直接提交;沿用脚本既有的“克隆后打补丁”机制(参见 `patch_playbook_user_systemd`),在 `setup-ai-workspace-all-in-one.sh` 新增 `patch_playbook_vault_macos()`,仅在 Darwin 下对克隆出的 vault 角色:①给目录创建任务追加 `ansible_os_family != 'Darwin'` 守卫;②把 `vault_config_dir`/`vault_data_dir`/`vault_binary_path` 改为按 OS 的三元表达式;③在 `macos.yml` 前置创建用户属主的数据目录(含 launchd 日志目录 `~/.local/state/xworkspace`)。该补丁对 `curl \| bash` 与本地执行两条路径均生效,幂等,且不改动 Linux 行为 | + --- ## 修复维度总结 @@ -116,7 +126,8 @@ | 组件获取方式替换 (brew vs binary) | TC-001 | | 权限收缩 (become: false) | TC-002, TC-006, TC-007, TC-008, TC-009 | | 用户组适配 (staff vs ubuntu) | TC-003, TC-010 | -| 目录路径降级 ($HOME vs /home/ubuntu, /opt) | TC-004, TC-006, TC-009, TC-010, TC-012 | +| 目录路径降级 ($HOME vs /home/ubuntu, /opt, /etc) | TC-004, TC-006, TC-009, TC-010, TC-012, TC-013 | +| 克隆后补丁注入 (post-clone patch) | TC-013 | | 包管理器绕过 (skip apt on Darwin) | TC-008, TC-010 | | 模板变量解耦 (remove nvm/nodejs_version) | TC-005 | | 路径空格兼容 (argv vs string) | TC-011 | diff --git a/docs/report/TC-MAC-012-26-06-18-19.md b/docs/report/TC-MAC-012-26-06-18-19.md index f0e754c..28d204a 100644 --- a/docs/report/TC-MAC-012-26-06-18-19.md +++ b/docs/report/TC-MAC-012-26-06-18-19.md @@ -90,3 +90,30 @@ curl -sfL https://raw.githubusercontent.com/ai-workspace-lab/xworkspace-console/ ``` > 注:方式 A 走 `curl | bash` 之外的本地执行路径,脚本侧 `-e` 覆盖会直接生效;role 默认的三元表达式则覆盖离线 `PLAYBOOK_DIR` 场景。 + +--- + +## 8. 续:vault 角色阻塞(TC-MAC-013,19:24) + +**进展**:bridge 修复生效后,部署从 145 → 154 个任务,新的阻塞点为 vault: + +``` +TASK [roles/vhosts/vault/ : Ensure standalone Vault directories exist] +failed (item=/etc/vault.d): Permission denied: b'/etc/vault.d' +failed (item=/opt/vault/data): Permission denied: b'/opt/vault' +``` + +**根因**:该目录创建任务以 `owner: root` 建 `/etc/vault.d`、`/opt/vault/data`,且缺失 vault 其余 standalone 任务都有的 `ansible_os_family != 'Darwin'` 守卫。`become=false` 下既无权写 `/etc`、`/opt`,`owner: root` 也无法 chown。 + +**与 bridge 的关键差异**:bridge 目录 owner 是服务用户(macOS 即当前用户),故可用脚本 `-e xworkmate_bridge_base_dir=...` 修复;vault 这里的 `owner: root` 是**硬编码**,extra-vars 无法覆盖,必须改 role 逻辑。而 role 在独立 playbooks 仓库,不在本仓库。 + +**解法(仍落在本仓库、对 `curl|bash` 与本地执行都生效)**:复用脚本既有的“克隆后打补丁”机制(`patch_playbook_user_systemd` 已有先例),新增 `patch_playbook_vault_macos()`,仅在 Darwin 下对克隆出的 vault 角色: +1. 给“Ensure standalone Vault directories exist”追加 `ansible_os_family != 'Darwin'` 守卫; +2. `vault_config_dir`/`vault_data_dir`/`vault_binary_path` 改为按 OS 的三元表达式(Linux 不变;macOS 用 `~/Library/Application Support/vault[/data]` 与 `/opt/homebrew/bin/vault`); +3. `macos.yml` 前置创建用户属主的数据目录(含 launchd 日志目录 `~/.local/state/xworkspace`)。 + +**目录策略(按用户确认)**:Linux → `/opt/vault/data`;macOS → `~/Library/Application Support/vault/data`。 + +**验证**:`bash -n` 通过;用真实 vault 角色副本跑补丁,YAML 合法、Darwin 守卫计数 5→6、二次执行幂等、Linux 渲染仍为原系统路径、macOS 渲染为 Apple 标准路径。 + +**注意(运行方式)**:上一轮 `bash scripts/setup-...sh | bash -` 的管道是错误用法(会把脚本日志再喂给第二个 bash)。本地执行应去掉管道:`bash scripts/setup-ai-workspace-all-in-one.sh`。 diff --git a/scripts/setup-ai-workspace-all-in-one.sh b/scripts/setup-ai-workspace-all-in-one.sh index 189b23c..96fc303 100755 --- a/scripts/setup-ai-workspace-all-in-one.sh +++ b/scripts/setup-ai-workspace-all-in-one.sh @@ -1110,6 +1110,73 @@ if updated != text: PY } +# On macOS the vault role's "Ensure standalone Vault directories exist" task +# targets /etc/vault.d and /opt/vault/data with owner: root. Those paths are not +# writable under become=false and are non-standard for macOS, so patch the +# cloned role to: (1) skip that root-owned directory task on Darwin, (2) point +# the vault dirs/binary at Apple-standard, user-writable locations, and (3) +# create the data dir (user-owned) in the macОS task path. Linux is untouched. +patch_playbook_vault_macos() { + local vars_file="roles/vhosts/vault/vars/main.yml" + local tasks_file="roles/vhosts/vault/tasks/main.yml" + local macos_file="roles/vhosts/vault/tasks/macos.yml" + [ -f "$vars_file" ] && [ -f "$tasks_file" ] && [ -f "$macos_file" ] || return 0 + python3 - <<'PY' +from pathlib import Path + +vars_path = Path("roles/vhosts/vault/vars/main.yml") +tasks_path = Path("roles/vhosts/vault/tasks/main.yml") +macos_path = Path("roles/vhosts/vault/tasks/macos.yml") + +# 1) Make vault dirs and binary path OS-conditional (Linux unchanged). +vars_text = vars_path.read_text() +vars_subs = { + "vault_binary_path: /usr/local/bin/vault": + "vault_binary_path: \"{{ '/opt/homebrew/bin/vault' if ansible_os_family == 'Darwin' else '/usr/local/bin/vault' }}\"", + "vault_config_dir: /etc/vault.d": + "vault_config_dir: \"{{ (ansible_env.HOME ~ '/Library/Application Support/vault') if ansible_os_family == 'Darwin' else '/etc/vault.d' }}\"", + "vault_data_dir: /opt/vault/data": + "vault_data_dir: \"{{ (ansible_env.HOME ~ '/Library/Application Support/vault/data') if ansible_os_family == 'Darwin' else '/opt/vault/data' }}\"", +} +for old, new in vars_subs.items(): + if old in vars_text: + vars_text = vars_text.replace(old, new) +vars_path.write_text(vars_text) + +# 2) Skip the root-owned directory creation task on macOS. +tasks_text = tasks_path.read_text() +dir_when_old = ( + ' loop:\n' + ' - "{{ vault_config_dir }}"\n' + ' - "{{ vault_data_dir }}"\n' + ' when:\n' + ' - vault_deploy_mode == "standalone"\n' +) +dir_when_new = dir_when_old + " - ansible_os_family != 'Darwin'\n" +if dir_when_old in tasks_text and " - ansible_os_family != 'Darwin'\n\n- name: Deploy standalone Vault systemd" not in tasks_text: + tasks_text = tasks_text.replace(dir_when_old, dir_when_new, 1) +tasks_path.write_text(tasks_text) + +# 3) Create the macOS vault dirs (user-owned) before the launchd plist is laid down. +macos_text = macos_path.read_text() +dir_task = ( + "- name: Ensure macOS Vault directories exist\n" + " ansible.builtin.file:\n" + " path: \"{{ item }}\"\n" + " state: directory\n" + " mode: \"0755\"\n" + " loop:\n" + " - \"{{ vault_config_dir }}\"\n" + " - \"{{ vault_data_dir }}\"\n" + " - \"{{ ansible_env.HOME }}/.local/state/xworkspace\"\n\n" +) +anchor = "- name: Install HashiCorp Tap\n" +if "Ensure macOS Vault directories exist" not in macos_text and anchor in macos_text: + macos_text = macos_text.replace(anchor, dir_task + anchor, 1) +macos_path.write_text(macos_text) +PY +} + ensure_core_skills_source() { if [ "${AI_WORKSPACE_PREFETCH_COMPLETED:-false}" = "true" ] && [ -d "$XWORKSPACE_CORE_SKILLS_DIR/skills" ]; then @@ -1889,6 +1956,9 @@ else fi patch_playbook_user_systemd +if [ "$(detect_os)" = "darwin" ]; then + patch_playbook_vault_macos +fi prefetch_independent_sources ensure_core_skills_source ensure_xworkmate_bridge_source