feat: consume prebuilt workspace runtimes
This commit is contained in:
parent
e4b04f95fe
commit
c07d12b5fe
@ -8,6 +8,9 @@ forks = 10
|
|||||||
poll_interval = 10
|
poll_interval = 10
|
||||||
transport = smart
|
transport = smart
|
||||||
gathering = smart
|
gathering = smart
|
||||||
|
fact_caching = jsonfile
|
||||||
|
fact_caching_connection = /tmp/ansible_facts
|
||||||
|
fact_caching_timeout = 3600
|
||||||
|
|
||||||
# 输出配置:使用 ansible-core 内置 callback,避免在轻量 CI 环境里缺少额外插件
|
# 输出配置:使用 ansible-core 内置 callback,避免在轻量 CI 环境里缺少额外插件
|
||||||
stdout_callback = default
|
stdout_callback = default
|
||||||
@ -25,3 +28,6 @@ deprecation_warnings = False
|
|||||||
cache = True
|
cache = True
|
||||||
cache_plugin = jsonfile
|
cache_plugin = jsonfile
|
||||||
cache_timeout = 3600
|
cache_timeout = 3600
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
pipelining = True
|
||||||
|
|||||||
163
docs/ai-workspace-runtime-delivery-plan.md
Normal file
163
docs/ai-workspace-runtime-delivery-plan.md
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# AI Workspace Runtime 交付计划
|
||||||
|
|
||||||
|
## 1. 目标与边界
|
||||||
|
|
||||||
|
本计划定义 AI Workspace 核心运行时从源码仓库构建、发布、离线聚合到目标机部署的完整交付链路。
|
||||||
|
|
||||||
|
核心原则:
|
||||||
|
|
||||||
|
- LiteLLM、xworkspace-console、xworkmate-bridge、QMD 分别在各自源码仓库的 GitHub Actions build job 中构建。
|
||||||
|
- 每个组件独立发布 `runtime-*` GitHub Release 及其 SHA256 清单。
|
||||||
|
- offline package 只下载已发布产物,逐文件完成 SHA256 校验后再聚合。
|
||||||
|
- 目标机只允许校验、解包、安装、配置、启动和健康检查,禁止源码编译、依赖构建及镜像构建。
|
||||||
|
- 所有未经过 CI 或目标机矩阵实测的能力均保持 `TODO`,不得仅依据设计或局部实现标记完成。
|
||||||
|
|
||||||
|
## 2. 目标架构
|
||||||
|
|
||||||
|
```text
|
||||||
|
LiteLLM repository ---------- build job --> runtime-litellm-* ----------\
|
||||||
|
xworkspace-console repository build job --> runtime-xworkspace-console-* --\
|
||||||
|
xworkmate-bridge repository -- build job --> runtime-xworkmate-bridge-* -----+--> offline package job
|
||||||
|
QMD repository -------------- build job --> runtime-qmd-* ------------------/ |
|
||||||
|
| download
|
||||||
|
| SHA256 verify
|
||||||
|
| manifest aggregate
|
||||||
|
v
|
||||||
|
offline-package-*
|
||||||
|
|
|
||||||
|
v
|
||||||
|
target host: verify/install only
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 组件 Release
|
||||||
|
|
||||||
|
四个组件必须由各自仓库负责构建,聚合仓库不得从源码代建组件。
|
||||||
|
|
||||||
|
| 组件 | 构建责任 | Release 命名 | 必需产物 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| LiteLLM | LiteLLM 仓库 GitHub Actions build job | `runtime-litellm-*` | 固定版本 Python runtime/依赖包、启动入口、组件 manifest、SHA256 清单 |
|
||||||
|
| xworkspace-console | xworkspace-console 仓库 GitHub Actions build job | `runtime-xworkspace-console-*` | dashboard 静态产物、API 二进制、运行配置模板、组件 manifest、SHA256 清单 |
|
||||||
|
| xworkmate-bridge | xworkmate-bridge 仓库 GitHub Actions build job | `runtime-xworkmate-bridge-*` | bridge 二进制、systemd/运行配置模板、组件 manifest、SHA256 清单 |
|
||||||
|
| QMD | QMD 仓库 GitHub Actions build job | `runtime-qmd-*` | 已安装依赖和已构建 CLI/runtime、组件 manifest、SHA256 清单 |
|
||||||
|
|
||||||
|
每个组件 manifest 至少记录:组件名、源码 commit、版本、构建时间、目标 OS、目标架构、入口文件、文件列表及每个文件的 SHA256。
|
||||||
|
|
||||||
|
### 2.2 offline package 聚合
|
||||||
|
|
||||||
|
offline package job 必须:
|
||||||
|
|
||||||
|
1. 从四个组件的 `runtime-*` Release 下载与目标平台匹配的产物和 SHA256 清单。
|
||||||
|
2. 在聚合前执行 SHA256 校验;缺少清单、文件缺失或摘要不一致时立即失败。
|
||||||
|
3. 生成聚合 manifest,固定四个组件的 Release tag、源码 commit、资产 URL、资产大小和 SHA256。
|
||||||
|
4. 将已校验组件产物、部署 playbook 所需依赖及聚合 manifest 打包为 `offline-package-*`。
|
||||||
|
5. 对最终 offline package 再生成 SHA256,并在 CI 中执行一次解包与结构校验。
|
||||||
|
|
||||||
|
禁止以 `latest` 作为不可追溯的部署输入;重新聚合必须基于明确 tag 或不可变 commit。
|
||||||
|
|
||||||
|
### 2.3 目标机部署
|
||||||
|
|
||||||
|
目标机部署必须开启 prebuilt-only 约束。缺少任一预构建产物时直接失败,不得回退到以下行为:
|
||||||
|
|
||||||
|
- `git clone` 或源码 checkout;
|
||||||
|
- `npm install`、`npm run build`、`go build`、`go run`;
|
||||||
|
- `pip install` 从公网或源码解析构建依赖;
|
||||||
|
- `docker build`、`podman build` 或其他本地镜像构建;
|
||||||
|
- 任何需要编译器、SDK 或前端构建工具链的安装步骤。
|
||||||
|
|
||||||
|
部署仅执行:offline package SHA256 校验、manifest 校验、解包、文件安装、权限设置、配置渲染、服务启动、健康检查和结果汇总。
|
||||||
|
|
||||||
|
## 3. 资源与性能约束
|
||||||
|
|
||||||
|
### 3.1 并发控制
|
||||||
|
|
||||||
|
- 全局并发硬上限必须满足 `并发数 <= 2 * 在线 CPU 数`,在线 CPU 数以执行时实际可用 CPU 为准。
|
||||||
|
- 初始并发取任务上限、配置上限和 `2 * 在线 CPU 数` 三者最小值。
|
||||||
|
- 调度器必须随 load 动态收缩:负载超过阈值时停止发放新任务并逐级降低并发;负载恢复且持续稳定后再缓慢扩容。
|
||||||
|
- 动态收缩不得中断正在执行的不可重入安装步骤;只限制后续任务进入。
|
||||||
|
- 日志和最终摘要必须记录 CPU 数、load 采样、每次并发调整的时间、原因及调整前后值。
|
||||||
|
|
||||||
|
### 3.2 部署耗时分布
|
||||||
|
|
||||||
|
每次部署必须记录总耗时及至少以下阶段耗时:
|
||||||
|
|
||||||
|
- offline package 下载;
|
||||||
|
- SHA256 与 manifest 校验;
|
||||||
|
- 解包;
|
||||||
|
- 各组件安装;
|
||||||
|
- 配置渲染;
|
||||||
|
- 服务启动;
|
||||||
|
- 健康检查。
|
||||||
|
|
||||||
|
CI/验收报告按 OS、架构、冷启动/缓存命中、首次执行/幂等重跑分组,统计样本数、最小值、最大值、平均值以及 P50、P90、P95、P99。样本不足时保留原始数据并明确标注,不以单次耗时代替分布结论。
|
||||||
|
|
||||||
|
## 4. 支持矩阵与验收
|
||||||
|
|
||||||
|
目标支持以下全部组合:
|
||||||
|
|
||||||
|
| 发行版 | 版本 | 架构 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Debian | 11、12、13 | amd64、arm64 |
|
||||||
|
| Ubuntu | 22.04、24.04、26.04 | amd64、arm64 |
|
||||||
|
|
||||||
|
每个矩阵项必须验证:
|
||||||
|
|
||||||
|
1. offline package 下载和 SHA256 校验成功。
|
||||||
|
2. 目标机在无源码、无构建工具链、组件外网访问受限的条件下部署成功。
|
||||||
|
3. 四个组件版本与聚合 manifest 完全一致。
|
||||||
|
4. 服务启动、健康检查和关键 smoke test 成功。
|
||||||
|
5. 同一主机使用同一输入至少连续执行两次;第二次成功且无非预期变更、无重复资源、无凭据轮换、无构建行为。
|
||||||
|
6. 首次部署和幂等重跑均产出阶段耗时及完整摘要。
|
||||||
|
|
||||||
|
Ubuntu 26.04 在实际可用 runner/镜像和依赖生态完成验证前,只能保持计划支持状态,不得标记已验证。
|
||||||
|
|
||||||
|
## 5. 当前事实
|
||||||
|
|
||||||
|
以下状态只记录当前仓库或相邻交付文档能够证明的事实,不把目标设计视为完成:
|
||||||
|
|
||||||
|
- [x] 聚合入口已拆分为 preflight 与 runtime playbook;preflight 已校验 `docker`、`k3s`、`systemd` 运行模式组合。
|
||||||
|
- [x] xworkspace-console 与 QMD 的部署代码已出现预构建 archive 输入及 prebuilt-only 缺包失败入口。
|
||||||
|
- [x] 相邻一键部署文档已记录:xworkspace-console 离线包 `publish-release` 链路和 Release 产物上传曾核对完成。
|
||||||
|
- [x] 相邻一键部署文档已记录:一键安装脚本优先使用离线安装包。
|
||||||
|
- [ ] xworkspace-console 与 QMD 当前仍存在目标机源码 checkout/依赖安装/构建回退,尚未满足“目标机禁止构建”。
|
||||||
|
- [ ] LiteLLM 当前可覆盖 package spec,但未证明其独立 `runtime-litellm-*` Release 和完全离线、免构建安装链路。
|
||||||
|
- [ ] xworkmate-bridge 独立 `runtime-xworkmate-bridge-*` Release 和预构建消费链路尚未在本计划范围内验证。
|
||||||
|
- [ ] 四组件 Release 的一致命名、manifest 和 SHA256 契约尚未完成验证。
|
||||||
|
- [ ] offline package 的逐文件下载、SHA256 校验、聚合 manifest 和最终包校验尚未完成验证。
|
||||||
|
- [ ] 并发硬上限、基于 load 的动态收缩及调整日志尚未完成验证。
|
||||||
|
- [ ] 部署耗时分布统计尚未完成验证。
|
||||||
|
- [ ] 连续重复执行的幂等性验收尚未完成。
|
||||||
|
- [ ] Debian 11/12/13、Ubuntu 22.04/24.04/26.04 的 amd64/arm64 全矩阵尚未完成验证。
|
||||||
|
|
||||||
|
## 6. TODO
|
||||||
|
|
||||||
|
### P0:构建与发布闭环
|
||||||
|
|
||||||
|
- [ ] TODO:在 LiteLLM 仓库建立 build job,发布 `runtime-litellm-*` Release、组件 manifest 和 SHA256 清单。
|
||||||
|
- [ ] TODO:在 xworkspace-console 仓库固化 build job,确认每次发布 `runtime-xworkspace-console-*` Release、组件 manifest 和 SHA256 清单。
|
||||||
|
- [ ] TODO:在 xworkmate-bridge 仓库建立 build job,发布 `runtime-xworkmate-bridge-*` Release、组件 manifest 和 SHA256 清单。
|
||||||
|
- [ ] TODO:在 QMD 仓库建立 build job,发布 `runtime-qmd-*` Release、组件 manifest 和 SHA256 清单。
|
||||||
|
- [ ] TODO:为 amd64、arm64 分别产出可安装资产;若资产与发行版相关,则按支持矩阵拆分并在 manifest 中明确兼容范围。
|
||||||
|
- [ ] TODO:增加 Release 契约测试,拒绝缺失入口、manifest、SHA256 或架构资产的发布。
|
||||||
|
|
||||||
|
### P0:离线聚合与目标机免构建
|
||||||
|
|
||||||
|
- [ ] TODO:实现 offline package job,按固定 tag 下载四组件 Release,并在聚合前逐文件执行 SHA256 校验。
|
||||||
|
- [ ] TODO:生成可追溯聚合 manifest,并为最终 `offline-package-*` 生成和发布 SHA256。
|
||||||
|
- [ ] TODO:在目标机部署入口强制 prebuilt-only,删除或禁用四组件所有源码构建回退。
|
||||||
|
- [ ] TODO:增加“目标机禁止构建”守卫,检测到编译器调用、包构建命令、源码 checkout 或镜像构建即失败。
|
||||||
|
- [ ] TODO:在断网或仅允许访问 offline package 源的目标机上完成端到端部署验证。
|
||||||
|
|
||||||
|
### P1:并发、性能与可观测性
|
||||||
|
|
||||||
|
- [ ] TODO:实现在线 CPU 探测和 `<= 2 * 在线 CPU` 的全局并发硬限制。
|
||||||
|
- [ ] TODO:定义 load 采样窗口、收缩/恢复阈值、迟滞策略和最低并发,完成动态收缩测试。
|
||||||
|
- [ ] TODO:记录阶段级耗时、组件级耗时、并发变化和环境标签,产出结构化 JSON 及人类可读摘要。
|
||||||
|
- [ ] TODO:汇总部署耗时分布,至少输出 count/min/max/avg/P50/P90/P95/P99,并区分首次执行与幂等重跑。
|
||||||
|
|
||||||
|
### P1:幂等与平台矩阵
|
||||||
|
|
||||||
|
- [ ] TODO:为每个支持矩阵项连续执行至少两次,验证第二次无非预期 changed、服务中断、重复资源或凭据变化。
|
||||||
|
- [ ] TODO:覆盖 Debian 11/12/13 amd64/arm64。
|
||||||
|
- [ ] TODO:覆盖 Ubuntu 22.04/24.04/26.04 amd64/arm64。
|
||||||
|
- [ ] TODO:保存每个矩阵项的 Release tag、offline package SHA256、部署日志、耗时数据和验收结论。
|
||||||
|
- [ ] TODO:全部矩阵通过后,再把“计划支持”更新为“已验证支持”;部分通过时逐项记录,不做整体完成声明。
|
||||||
@ -8,9 +8,12 @@ litellm_version: "3ad385a8a46988b6a81fe6c0bc22ef58685baa58"
|
|||||||
litellm_debian_11_compat_version: "1.74.9"
|
litellm_debian_11_compat_version: "1.74.9"
|
||||||
litellm_package_spec: >-
|
litellm_package_spec: >-
|
||||||
{{
|
{{
|
||||||
|
lookup('ansible.builtin.env', 'LITELLM_PACKAGE_SPEC')
|
||||||
|
| default(
|
||||||
'litellm[proxy]==' ~ litellm_debian_11_compat_version
|
'litellm[proxy]==' ~ litellm_debian_11_compat_version
|
||||||
if ansible_facts.distribution == 'Debian' and ansible_facts.distribution_major_version == '11'
|
if ansible_facts.distribution == 'Debian' and ansible_facts.distribution_major_version == '11'
|
||||||
else 'litellm[proxy] @ git+' ~ litellm_source_repo ~ '@' ~ litellm_version
|
else 'litellm[proxy] @ git+' ~ litellm_source_repo ~ '@' ~ litellm_version,
|
||||||
|
true)
|
||||||
}}
|
}}
|
||||||
litellm_python_executable: "{{ lookup('ansible.builtin.env', 'LITELLM_PYTHON_EXECUTABLE') | default('python3', true) }}"
|
litellm_python_executable: "{{ lookup('ansible.builtin.env', 'LITELLM_PYTHON_EXECUTABLE') | default('python3', true) }}"
|
||||||
litellm_pip_executable: "{{ lookup('ansible.builtin.env', 'LITELLM_PIP_EXECUTABLE') | default('', true) }}"
|
litellm_pip_executable: "{{ lookup('ansible.builtin.env', 'LITELLM_PIP_EXECUTABLE') | default('', true) }}"
|
||||||
|
|||||||
@ -5,6 +5,9 @@ qmd_home: "/home/{{ qmd_user }}"
|
|||||||
qmd_source_repo: "https://github.com/ai-workspace-services/qmd.git"
|
qmd_source_repo: "https://github.com/ai-workspace-services/qmd.git"
|
||||||
qmd_version: "6021ea34ac27ac9b5c9a7d655500544917c801dd"
|
qmd_version: "6021ea34ac27ac9b5c9a7d655500544917c801dd"
|
||||||
qmd_source_dir: "{{ qmd_home }}/.local/src/qmd"
|
qmd_source_dir: "{{ qmd_home }}/.local/src/qmd"
|
||||||
|
qmd_runtime_archive: "{{ lookup('ansible.builtin.env', 'QMD_RUNTIME_ARCHIVE') | default('', true) }}"
|
||||||
|
qmd_runtime_marker: "{{ qmd_source_dir }}/.runtime-archive-sha256"
|
||||||
|
ai_workspace_prebuilt_components_required: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_PREBUILT_COMPONENTS_REQUIRED') | default('false', true) | bool }}"
|
||||||
qmd_binary_path: "{{ qmd_home }}/.bun/bin/qmd"
|
qmd_binary_path: "{{ qmd_home }}/.bun/bin/qmd"
|
||||||
qmd_config_dir: "{{ qmd_home }}/.config/qmd"
|
qmd_config_dir: "{{ qmd_home }}/.config/qmd"
|
||||||
qmd_cache_dir: "{{ qmd_home }}/.cache/qmd"
|
qmd_cache_dir: "{{ qmd_home }}/.cache/qmd"
|
||||||
|
|||||||
@ -62,6 +62,53 @@
|
|||||||
group: "{{ qmd_group }}"
|
group: "{{ qmd_group }}"
|
||||||
mode: "0755"
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Validate packaged QMD runtime
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ qmd_runtime_archive }}"
|
||||||
|
register: qmd_runtime_archive_stat
|
||||||
|
when: qmd_runtime_archive | length > 0
|
||||||
|
|
||||||
|
- name: Require packaged QMD runtime
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- qmd_runtime_archive | length > 0
|
||||||
|
- qmd_runtime_archive_stat.stat.exists | default(false)
|
||||||
|
fail_msg: "A valid QMD_RUNTIME_ARCHIVE is required in prebuilt-only mode."
|
||||||
|
when: ai_workspace_prebuilt_components_required
|
||||||
|
|
||||||
|
- name: Inspect installed QMD runtime marker
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
path: "{{ qmd_runtime_marker }}"
|
||||||
|
register: qmd_runtime_marker_content
|
||||||
|
failed_when: false
|
||||||
|
when: qmd_runtime_archive | length > 0
|
||||||
|
|
||||||
|
- name: Install packaged QMD runtime
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "{{ qmd_runtime_archive }}"
|
||||||
|
dest: "{{ qmd_source_dir | dirname }}"
|
||||||
|
remote_src: true
|
||||||
|
owner: "{{ qmd_user }}"
|
||||||
|
group: "{{ qmd_group }}"
|
||||||
|
when:
|
||||||
|
- qmd_runtime_archive | length > 0
|
||||||
|
- qmd_runtime_archive_stat.stat.exists | default(false)
|
||||||
|
- >-
|
||||||
|
(qmd_runtime_marker_content.content | default('') | b64decode | trim)
|
||||||
|
!= (qmd_runtime_archive_stat.stat.checksum | default(''))
|
||||||
|
or not ((qmd_source_dir ~ '/bin/qmd') is file)
|
||||||
|
|
||||||
|
- name: Record installed QMD runtime checksum
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "{{ qmd_runtime_marker }}"
|
||||||
|
owner: "{{ qmd_user }}"
|
||||||
|
group: "{{ qmd_group }}"
|
||||||
|
mode: "0644"
|
||||||
|
content: "{{ qmd_runtime_archive_stat.stat.checksum }}\n"
|
||||||
|
when:
|
||||||
|
- qmd_runtime_archive | length > 0
|
||||||
|
- qmd_runtime_archive_stat.stat.exists | default(false)
|
||||||
|
|
||||||
- name: Checkout pinned QMD source
|
- name: Checkout pinned QMD source
|
||||||
ansible.builtin.git:
|
ansible.builtin.git:
|
||||||
repo: "{{ qmd_source_repo }}"
|
repo: "{{ qmd_source_repo }}"
|
||||||
@ -71,6 +118,7 @@
|
|||||||
become: true
|
become: true
|
||||||
become_user: "{{ qmd_user }}"
|
become_user: "{{ qmd_user }}"
|
||||||
register: qmd_source_checkout
|
register: qmd_source_checkout
|
||||||
|
when: qmd_runtime_archive | length == 0
|
||||||
|
|
||||||
- name: Install QMD source dependencies
|
- name: Install QMD source dependencies
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
@ -79,6 +127,7 @@
|
|||||||
become: true
|
become: true
|
||||||
become_user: "{{ qmd_user }}"
|
become_user: "{{ qmd_user }}"
|
||||||
when:
|
when:
|
||||||
|
- qmd_runtime_archive | length == 0
|
||||||
- qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
- qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
||||||
|
|
||||||
- name: Build QMD from pinned source
|
- name: Build QMD from pinned source
|
||||||
@ -88,6 +137,7 @@
|
|||||||
become: true
|
become: true
|
||||||
become_user: "{{ qmd_user }}"
|
become_user: "{{ qmd_user }}"
|
||||||
when:
|
when:
|
||||||
|
- qmd_runtime_archive | length == 0
|
||||||
- qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
- qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
||||||
|
|
||||||
- name: Link pinned QMD binary
|
- name: Link pinned QMD binary
|
||||||
@ -99,7 +149,7 @@
|
|||||||
group: "{{ qmd_group }}"
|
group: "{{ qmd_group }}"
|
||||||
force: true
|
force: true
|
||||||
when:
|
when:
|
||||||
- qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
- qmd_runtime_archive | length > 0 or qmd_source_checkout.changed | default(false) or not (qmd_binary.stat.exists | default(false))
|
||||||
|
|
||||||
- name: Reinspect QMD binary after source install
|
- name: Reinspect QMD binary after source install
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
@ -147,7 +197,9 @@
|
|||||||
print("QMD fallback shim")
|
print("QMD fallback shim")
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
when: not (qmd_binary_after_source.stat.exists | default(false))
|
when:
|
||||||
|
- not ai_workspace_prebuilt_components_required
|
||||||
|
- not (qmd_binary_after_source.stat.exists | default(false))
|
||||||
|
|
||||||
- name: Reinspect QMD binary
|
- name: Reinspect QMD binary
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
|
|||||||
@ -27,59 +27,7 @@
|
|||||||
#
|
#
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
- name: Validate AI Workspace runtime modes
|
- import_playbook: setup-ai-workspace-preflight.yml
|
||||||
hosts: all
|
|
||||||
gather_facts: false
|
|
||||||
vars:
|
|
||||||
ai_workspace_runtime_modes: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_RUNTIME_MODES') | default('docker,systemd', true) }}"
|
|
||||||
tasks:
|
|
||||||
- name: Normalize runtime mode list
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
ai_workspace_runtime_mode_list: >-
|
|
||||||
{{
|
|
||||||
ai_workspace_runtime_modes.split(',')
|
|
||||||
| map('trim')
|
|
||||||
| reject('equalto', '')
|
|
||||||
| list
|
|
||||||
if ai_workspace_runtime_modes is string
|
|
||||||
else ai_workspace_runtime_modes
|
|
||||||
}}
|
|
||||||
|
|
||||||
- name: Validate runtime mode combination
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- ai_workspace_runtime_mode_list | length > 0
|
|
||||||
- not ('docker' in ai_workspace_runtime_mode_list and 'k3s' in ai_workspace_runtime_mode_list)
|
|
||||||
- ai_workspace_runtime_mode_list | difference(['docker', 'k3s', 'systemd']) | length == 0
|
|
||||||
fail_msg: "docker 与 k3s 互斥;请选择 docker/k3s/systemd 的合法组合。"
|
|
||||||
|
|
||||||
# 基础工作区与控制台
|
|
||||||
- import_playbook: setup-nodejs.yml
|
- import_playbook: setup-nodejs.yml
|
||||||
- import_playbook: setup-xworkspace-console.yaml
|
- import_playbook: setup-ai-workspace-runtime.yml
|
||||||
- import_playbook: setup-ai-agent-skills.yml
|
|
||||||
|
|
||||||
# 网关运行时先启动,供 xworkmate-bridge 健康检查聚合
|
|
||||||
- import_playbook: deploy_gateway_openclaw.yml
|
|
||||||
|
|
||||||
# 核心网关与桥接
|
|
||||||
- import_playbook: deploy_xworkmate_bridge_vhosts.yml
|
|
||||||
|
|
||||||
# 基础数据与密钥设施
|
|
||||||
- import_playbook: setup-vault.yaml
|
|
||||||
- import_playbook: setup-postgres-standalone.yaml
|
|
||||||
- import_playbook: setup-litellm.yaml
|
|
||||||
|
|
||||||
# 大模型与 AI Agents
|
|
||||||
- import_playbook: deploy_QMD.yml
|
|
||||||
|
|
||||||
# 可选服务
|
|
||||||
- import_playbook: setup-xfce-xrdp.yaml
|
|
||||||
|
|
||||||
# 最后的部署校验
|
|
||||||
- name: 最终部署状态检查
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
gather_facts: false
|
|
||||||
roles:
|
|
||||||
- role: roles/vhosts/validation
|
|
||||||
|
|
||||||
|
|||||||
26
setup-ai-workspace-preflight.yml
Normal file
26
setup-ai-workspace-preflight.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
- name: Validate AI Workspace runtime modes
|
||||||
|
hosts: all
|
||||||
|
gather_facts: false
|
||||||
|
vars:
|
||||||
|
ai_workspace_runtime_modes: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_RUNTIME_MODES') | default('docker,systemd', true) }}"
|
||||||
|
tasks:
|
||||||
|
- name: Normalize runtime mode list
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
ai_workspace_runtime_mode_list: >-
|
||||||
|
{{
|
||||||
|
ai_workspace_runtime_modes.split(',')
|
||||||
|
| map('trim')
|
||||||
|
| reject('equalto', '')
|
||||||
|
| list
|
||||||
|
if ai_workspace_runtime_modes is string
|
||||||
|
else ai_workspace_runtime_modes
|
||||||
|
}}
|
||||||
|
|
||||||
|
- name: Validate runtime mode combination
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- ai_workspace_runtime_mode_list | length > 0
|
||||||
|
- not ('docker' in ai_workspace_runtime_mode_list and 'k3s' in ai_workspace_runtime_mode_list)
|
||||||
|
- ai_workspace_runtime_mode_list | difference(['docker', 'k3s', 'systemd']) | length == 0
|
||||||
|
fail_msg: "docker 与 k3s 互斥;请选择 docker/k3s/systemd 的合法组合。"
|
||||||
29
setup-ai-workspace-runtime.yml
Normal file
29
setup-ai-workspace-runtime.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
# 基础工作区与控制台
|
||||||
|
- import_playbook: setup-xworkspace-console.yaml
|
||||||
|
- import_playbook: setup-ai-agent-skills.yml
|
||||||
|
|
||||||
|
# 网关运行时先启动,供 xworkmate-bridge 健康检查聚合
|
||||||
|
- import_playbook: deploy_gateway_openclaw.yml
|
||||||
|
|
||||||
|
# 核心网关与桥接
|
||||||
|
- import_playbook: deploy_xworkmate_bridge_vhosts.yml
|
||||||
|
|
||||||
|
# 基础数据与密钥设施
|
||||||
|
- import_playbook: setup-vault.yaml
|
||||||
|
- import_playbook: setup-postgres-standalone.yaml
|
||||||
|
- import_playbook: setup-litellm.yaml
|
||||||
|
|
||||||
|
# 大模型与 AI Agents
|
||||||
|
- import_playbook: deploy_QMD.yml
|
||||||
|
|
||||||
|
# 可选服务
|
||||||
|
- import_playbook: setup-xfce-xrdp.yaml
|
||||||
|
|
||||||
|
# 最后的部署校验
|
||||||
|
- name: 最终部署状态检查
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
gather_facts: false
|
||||||
|
roles:
|
||||||
|
- role: roles/vhosts/validation
|
||||||
@ -15,8 +15,16 @@
|
|||||||
xworkspace_console_repo_dir: /home/ubuntu/xworkspace-console
|
xworkspace_console_repo_dir: /home/ubuntu/xworkspace-console
|
||||||
xworkspace_console_source_repo: "https://github.com/ai-workspace-lab/xworkspace-console.git"
|
xworkspace_console_source_repo: "https://github.com/ai-workspace-lab/xworkspace-console.git"
|
||||||
xworkspace_console_source_version: "main"
|
xworkspace_console_source_version: "main"
|
||||||
|
xworkspace_console_runtime_archive: "{{ lookup('ansible.builtin.env', 'XWORKSPACE_CONSOLE_RUNTIME_ARCHIVE') | default('', true) }}"
|
||||||
|
ai_workspace_prebuilt_components_required: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_PREBUILT_COMPONENTS_REQUIRED') | default('false', true) | bool }}"
|
||||||
xworkspace_console_dashboard_dir: /home/ubuntu/xworkspace-console/dashboard
|
xworkspace_console_dashboard_dir: /home/ubuntu/xworkspace-console/dashboard
|
||||||
xworkspace_console_api_dir: /home/ubuntu/xworkspace-console/api
|
xworkspace_console_api_dir: /home/ubuntu/xworkspace-console/api
|
||||||
|
xworkspace_console_api_binary: /home/ubuntu/xworkspace-console/bin/xworkspace-api
|
||||||
|
xworkspace_console_runtime_marker: /home/ubuntu/xworkspace-console/.runtime-archive-sha256
|
||||||
|
xworkspace_console_api_working_dir: >-
|
||||||
|
{{ xworkspace_console_repo_dir if xworkspace_console_runtime_archive | length > 0 else xworkspace_console_api_dir }}
|
||||||
|
xworkspace_console_api_exec: >-
|
||||||
|
{{ xworkspace_console_api_binary if xworkspace_console_runtime_archive | length > 0 else '/usr/bin/env go run .' }}
|
||||||
xworkspace_console_scripts_dir: /home/ubuntu/xworkspace/scripts
|
xworkspace_console_scripts_dir: /home/ubuntu/xworkspace/scripts
|
||||||
xworkspace_console_config_dir: /home/ubuntu/.config/xworkspace
|
xworkspace_console_config_dir: /home/ubuntu/.config/xworkspace
|
||||||
xworkspace_console_url: http://127.0.0.1:17000
|
xworkspace_console_url: http://127.0.0.1:17000
|
||||||
@ -454,6 +462,54 @@
|
|||||||
--disable-sync \
|
--disable-sync \
|
||||||
--new-window
|
--new-window
|
||||||
|
|
||||||
|
- name: Validate packaged XWorkspace Console runtime
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ xworkspace_console_runtime_archive }}"
|
||||||
|
register: xworkspace_console_runtime_archive_stat
|
||||||
|
when: xworkspace_console_runtime_archive | length > 0
|
||||||
|
|
||||||
|
- name: Require packaged XWorkspace Console runtime
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- xworkspace_console_runtime_archive | length > 0
|
||||||
|
- xworkspace_console_runtime_archive_stat.stat.exists | default(false)
|
||||||
|
fail_msg: "A valid XWORKSPACE_CONSOLE_RUNTIME_ARCHIVE is required in prebuilt-only mode."
|
||||||
|
when: ai_workspace_prebuilt_components_required
|
||||||
|
|
||||||
|
- name: Inspect installed XWorkspace Console runtime marker
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
path: "{{ xworkspace_console_runtime_marker }}"
|
||||||
|
register: xworkspace_console_runtime_marker_content
|
||||||
|
failed_when: false
|
||||||
|
when: xworkspace_console_runtime_archive | length > 0
|
||||||
|
|
||||||
|
- name: Install packaged XWorkspace Console runtime
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "{{ xworkspace_console_runtime_archive }}"
|
||||||
|
dest: "{{ xworkspace_console_repo_dir | dirname }}"
|
||||||
|
remote_src: true
|
||||||
|
owner: "{{ xworkspace_console_user }}"
|
||||||
|
group: "{{ xworkspace_console_user }}"
|
||||||
|
when:
|
||||||
|
- xworkspace_console_runtime_archive | length > 0
|
||||||
|
- xworkspace_console_runtime_archive_stat.stat.exists | default(false)
|
||||||
|
- >-
|
||||||
|
(xworkspace_console_runtime_marker_content.content | default('') | b64decode | trim)
|
||||||
|
!= (xworkspace_console_runtime_archive_stat.stat.checksum | default(''))
|
||||||
|
or not (xworkspace_console_api_binary is file)
|
||||||
|
or not ((xworkspace_console_dashboard_dir ~ '/dist/index.html') is file)
|
||||||
|
|
||||||
|
- name: Record installed XWorkspace Console runtime checksum
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "{{ xworkspace_console_runtime_marker }}"
|
||||||
|
owner: "{{ xworkspace_console_user }}"
|
||||||
|
group: "{{ xworkspace_console_user }}"
|
||||||
|
mode: "0644"
|
||||||
|
content: "{{ xworkspace_console_runtime_archive_stat.stat.checksum }}\n"
|
||||||
|
when:
|
||||||
|
- xworkspace_console_runtime_archive | length > 0
|
||||||
|
- xworkspace_console_runtime_archive_stat.stat.exists | default(false)
|
||||||
|
|
||||||
- name: Clone xworkspace-console repository
|
- name: Clone xworkspace-console repository
|
||||||
ansible.builtin.git:
|
ansible.builtin.git:
|
||||||
repo: "{{ xworkspace_console_source_repo }}"
|
repo: "{{ xworkspace_console_source_repo }}"
|
||||||
@ -462,12 +518,28 @@
|
|||||||
depth: 1
|
depth: 1
|
||||||
force: true
|
force: true
|
||||||
become_user: "{{ xworkspace_console_user }}"
|
become_user: "{{ xworkspace_console_user }}"
|
||||||
|
register: xworkspace_console_source_checkout
|
||||||
|
when: xworkspace_console_runtime_archive | length == 0
|
||||||
|
|
||||||
- name: Build dashboard assets on target
|
- name: Build dashboard assets on target
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: |
|
||||||
|
set -euo pipefail
|
||||||
cd "{{ xworkspace_console_dashboard_dir }}"
|
cd "{{ xworkspace_console_dashboard_dir }}"
|
||||||
|
source_commit="$(git -C "{{ xworkspace_console_repo_dir }}" rev-parse HEAD)"
|
||||||
|
marker=".ai-workspace-build-commit"
|
||||||
|
if [ -f "dist/index.html" ] && [ "$(cat "$marker" 2>/dev/null || true)" = "$source_commit" ]; then
|
||||||
|
echo "build=unchanged"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
npm install && npm run build
|
npm install && npm run build
|
||||||
|
printf '%s\n' "$source_commit" > "$marker"
|
||||||
|
echo "build=changed"
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
become_user: "{{ xworkspace_console_user }}"
|
become_user: "{{ xworkspace_console_user }}"
|
||||||
|
register: xworkspace_console_dashboard_build
|
||||||
|
changed_when: "'build=changed' in (xworkspace_console_dashboard_build.stdout | default(''))"
|
||||||
|
when: xworkspace_console_runtime_archive | length == 0
|
||||||
|
|
||||||
- name: Deploy AI Workspace portal service configuration
|
- name: Deploy AI Workspace portal service configuration
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
@ -547,9 +619,9 @@
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
WorkingDirectory={{ xworkspace_console_api_dir }}
|
WorkingDirectory={{ xworkspace_console_api_working_dir }}
|
||||||
EnvironmentFile={{ xworkspace_console_config_dir }}/portal.env
|
EnvironmentFile={{ xworkspace_console_config_dir }}/portal.env
|
||||||
ExecStart=/usr/bin/env go run .
|
ExecStart={{ xworkspace_console_api_exec }}
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=2
|
RestartSec=2
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user