diff --git a/docs/case/macos_compatibility_tests.md b/docs/case/macos_compatibility_tests.md index 5452242..86672dd 100644 --- a/docs/case/macos_compatibility_tests.md +++ b/docs/case/macos_compatibility_tests.md @@ -117,6 +117,16 @@ | **目录策略** | 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 行为 | +## TC-MAC-014: common 角色 Linux 基线(timedatectl 等)在 macOS 失败 + +| 项目 | 内容 | +|------|------| +| **触发文件** | `roles/vhosts/common/tasks/main.yml` | +| **触发报错** | `TASK [common : Base | set timezone]` → `[Errno 2] No such file or directory: b'timedatectl'`(macOS 无 systemd 的 `timedatectl`) | +| **根因** | `common` 角色的 `Base | *` 系列任务是 Linux 服务器基线:`timedatectl` 设时区、改写 `/etc/hostname`、`/etc/hosts`、设主机名、加固 SSH、配置 fail2ban、调文件句柄上限、放行防火墙端口。全部 `become: true` 且依赖 Linux 专有工具/路径,在 macOS(`become=false`)下会逐条失败,`set timezone` 只是第一个 | +| **修复方案** | 经评估这些基线对 macOS 本机开发部署既不适用也无权限执行,故在 `setup-ai-workspace-all-in-one.sh` 新增 `patch_playbook_common_macos()`(同样走克隆后打补丁),仅在 Darwin 下为整个 `Base | *` 块追加 `ansible_os_family != 'Darwin'` 守卫(共 9 处:7 个任务追加 `when`,2 个已有 `when` 列表追加该条件)。`import_tasks` 的 `when` 会传播到子任务,因此 ssh 加固/fail2ban/limits/firewall 子任务一并跳过。幂等、YAML 合法、Linux 行为不变 | +| **备注** | 用户仅点名 `set timezone`,但其后的 Base 任务会以相同原因连环失败,故一并守卫以避免逐个往返 | + --- ## 修复维度总结 @@ -127,7 +137,8 @@ | 权限收缩 (become: false) | TC-002, TC-006, TC-007, TC-008, TC-009 | | 用户组适配 (staff vs ubuntu) | TC-003, TC-010 | | 目录路径降级 ($HOME vs /home/ubuntu, /opt, /etc) | TC-004, TC-006, TC-009, TC-010, TC-012, TC-013 | -| 克隆后补丁注入 (post-clone patch) | TC-013 | +| 克隆后补丁注入 (post-clone patch) | TC-013, TC-014 | +| Linux 基线整体跳过 (skip Linux baseline on Darwin) | TC-014 | | 包管理器绕过 (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 28d204a..a839cfc 100644 --- a/docs/report/TC-MAC-012-26-06-18-19.md +++ b/docs/report/TC-MAC-012-26-06-18-19.md @@ -117,3 +117,21 @@ failed (item=/opt/vault/data): Permission denied: b'/opt/vault' **验证**:`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`。 + +--- + +## 9. 续:common 角色 Linux 基线(TC-MAC-014,19:41) + +**进展**:vault 修复后部署推进到 161 个任务,新阻塞点为 `common : Base | set timezone`: + +``` +fatal: timedatectl set-timezone Asia/Shanghai -> [Errno 2] No such file or directory: b'timedatectl' +``` + +**根因**:`common` 的 `Base | *` 是 Linux 服务器基线(时区、hostname、/etc/hosts、SSH 加固、fail2ban、limits、防火墙),全部 `become: true` + Linux 专有工具/路径。`set timezone` 只是第一个,其余会连环失败。 + +**解法**:新增 `patch_playbook_common_macos()`(仍走克隆后打补丁),仅在 Darwin 下给整个 Base 块加 `ansible_os_family != 'Darwin'` 守卫(9 处)。`import_tasks` 的 `when` 自动传播到子任务,故 ssh/fail2ban/limits/firewall 一并跳过。 + +**验证**:`bash -n` 通过;用真实 `common/tasks/main.yml` 跑补丁,YAML 合法、守卫计数 9、二次执行幂等、Linux 段落(Debian/RedHat/addon)不变。 + +**说明**:用户仅点名 timezone,但为避免逐个往返,已一并守卫同源会失败的 Base 任务。 diff --git a/scripts/setup-ai-workspace-all-in-one.sh b/scripts/setup-ai-workspace-all-in-one.sh index 96fc303..c669ee2 100755 --- a/scripts/setup-ai-workspace-all-in-one.sh +++ b/scripts/setup-ai-workspace-all-in-one.sh @@ -1177,6 +1177,78 @@ macos_path.write_text(macos_text) PY } +# The common role's "Base | *" tasks configure a Linux server: set timezone via +# timedatectl, rewrite /etc/hostname + /etc/hosts, set the hostname, harden ssh, +# configure fail2ban, raise file limits and open firewall ports. All of them run +# with become: true and target Linux-only tooling/paths, so they fail on macOS +# (e.g. timedatectl is absent). Patch the cloned role to skip the entire Base +# baseline on Darwin. Linux is untouched. +patch_playbook_common_macos() { + local main_file="roles/vhosts/common/tasks/main.yml" + [ -f "$main_file" ] || return 0 + python3 - <<'PY' +from pathlib import Path + +path = Path("roles/vhosts/common/tasks/main.yml") +text = path.read_text() +guard = " when: ansible_os_family != 'Darwin'\n" + +# Tasks that end with a trailing attribute and have no `when:` yet -> append guard. +append_blocks = [ + ('- name: Base | set timezone\n' + ' ansible.builtin.command: "timedatectl set-timezone Asia/Shanghai"\n' + ' changed_when: false\n' + ' become: true\n'), + ('- name: Base | render /etc/hostname\n' + ' ansible.builtin.template:\n' + ' src: templates/hostname.j2\n' + ' dest: /etc/hostname\n' + ' owner: root\n' + ' group: root\n' + ' mode: "0644"\n' + ' become: true\n'), + ('- name: Base | set hostname\n' + ' ansible.builtin.hostname:\n' + ' name: "{{ inventory_hostname }}"\n' + ' become: true\n'), + ('- name: Base | update /etc/hosts\n' + ' ansible.builtin.template:\n' + ' src: templates/hosts\n' + ' dest: /etc/hosts\n' + ' owner: root\n' + ' group: root\n' + ' mode: "0644"\n' + ' become: true\n'), + ('- name: Base | harden ssh\n' + ' ansible.builtin.script: files/secure_ssh.sh\n' + ' become: true\n'), + ('- name: Base | harden ssh config\n' + ' ansible.builtin.import_tasks: harden_ssh.yml\n' + ' tags: [ssh, security]\n'), + ('- name: Base | configure fail2ban\n' + ' ansible.builtin.import_tasks: fail2ban.yml\n' + ' tags: [fail2ban, security]\n'), +] +for block in append_blocks: + if block in text and (block + guard) not in text: + text = text.replace(block, block + guard, 1) + +# Tasks that already have a `when:` list -> add the Darwin condition to it. +when_blocks = [ + (' when:\n' + ' - common_security_limits.enabled | default(true) | bool\n'), + (' when:\n' + ' - common_firewall.enabled | default(true) | bool\n'), +] +extra = " - ansible_os_family != 'Darwin'\n" +for block in when_blocks: + if block in text and (block + extra) not in text: + text = text.replace(block, block + extra, 1) + +path.write_text(text) +PY +} + ensure_core_skills_source() { if [ "${AI_WORKSPACE_PREFETCH_COMPLETED:-false}" = "true" ] && [ -d "$XWORKSPACE_CORE_SKILLS_DIR/skills" ]; then @@ -1958,6 +2030,7 @@ fi patch_playbook_user_systemd if [ "$(detect_os)" = "darwin" ]; then patch_playbook_vault_macos + patch_playbook_common_macos fi prefetch_independent_sources ensure_core_skills_source