Compare commits

...

9 Commits

Author SHA1 Message Date
e6643bdc4d
ci: honor AI_WORKSPACE_AUTH_TOKEN from input/Vault, pass through to host (#11)
Restore the Vault kv/CICD/AI_WORKSPACE_AUTH_TOKEN read in the deploy job
(the key now exists) and resolve the bootstrap token with a clear
precedence: workflow_dispatch input overrides, else Vault value, else
the on-host installer's resolve_unified_auth_token reuses the persisted
~/.ai_workspace_auth_token or generates a new one.

Also fix run-on-host-bootstrap.sh which silently dropped
AI_WORKSPACE_AUTH_TOKEN: it is now written to the remote env payload and
exported, so an input/Vault-provided token is actually honored on the
host instead of being regenerated. Empty stays empty so the no-arg
curl|bash install path still self-generates.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:02:33 +08:00
537315f0fc
ci: remove AI_WORKSPACE_AUTH_TOKEN from vault-action reads (#9)
vault-action ignoreNotFound only suppresses path-level 404, not missing
keys within an existing path. Token is now sourced exclusively from the
ai_workspace_auth_token workflow_dispatch input.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 16:55:38 +08:00
ddae3b3574
ci: simplify AI_WORKSPACE_AUTH_TOKEN input description for consistency (#7)
Remove openssl rand -hex 32 alternative (format inconsistent with UUID output).
Standardize to UUID-only generation hint matching existing input description style.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-28 16:45:09 +08:00
bfbe038ab2
Release/v1.1.5 (#6)
* ci: backport release/* source validation workflow to release/v1.1.5 (#3)

让现有 release/v1.1.5 分支自身包含门禁 workflow(pull_request_target 用 base 分支版本)。
详见 iac_modules/docs/tldr-github-branch-model.md

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* backport: support customizable AI_WORKSPACE_AUTH_TOKEN in deployment workflow

* ci: support customizable AI_WORKSPACE_AUTH_TOKEN in deployment workflow (#5)

- Add AI_WORKSPACE_AUTH_TOKEN to Vault KV secret reads (provision + deploy jobs)
- Add ai_workspace_auth_token as optional workflow_dispatch input parameter
- Allow runtime override of auth token (input takes precedence over Vault)
- Include TLDR token generation guidance in workflow description
- Wire token through all-in-one bootstrap with precedence: input > Vault

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:34:21 +08:00
b29b85025b
ci: support customizable AI_WORKSPACE_AUTH_TOKEN in deployment workflow (#4)
* ci: backport release/* source validation workflow to release/v1.1.5

让现有 release/v1.1.5 分支自身包含门禁 workflow(pull_request_target 用 base 分支版本)。
详见 iac_modules/docs/tldr-github-branch-model.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci: support customizable AI_WORKSPACE_AUTH_TOKEN in deployment workflow

- Add AI_WORKSPACE_AUTH_TOKEN to Vault KV secret reads (provision + deploy jobs)
- Add ai_workspace_auth_token as optional workflow_dispatch input parameter
- Allow runtime override of auth token (input takes precedence over Vault)
- Include TLDR token generation guidance in workflow description
- Wire token through all-in-one bootstrap with precedence: input > Vault

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:12:49 +08:00
6eb16afb14
ci: add release/* branch source validation workflow (#2)
release/* 仅接受 hotfix/* 或带 cherry-pick/backport 标签的 PR。
详见 iac_modules/docs/tldr-github-branch-model.md

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:12:21 +08:00
3ce3c6fb66 fix(iac): require Cloudflare DNS token 2026-06-27 13:48:20 +08:00
2d3289fbc5 fix(installer): resolve local macOS patcher after cwd changes 2026-06-27 09:02:08 +08:00
5093e21e35 fix(installer): use checked-in macOS patcher locally 2026-06-27 08:58:37 +08:00
5 changed files with 101 additions and 13 deletions

View File

@ -18,8 +18,8 @@ name: Deploy AI Workspace (IaC + Ansible + Cloudflare)
# VULTR_API_KEY → Vultr 账号 API keyprovision 创主机)
# SSH_PRIVATE_DEPLOY_KEY_B64 → 部署 SSH 私钥 base64deploy 登录主机,优先)
# SSH_PRIVATE_DEPLOY_KEY → 同上原始多行格式(回退,二选一必填)
# CLOUDFLARE_DNS_API_TOKEN → CF Zone DNS Edit tokendns 同步,兼容旧名
# CLOUDFLARE_API_TOKEN → 同上Cloudflare 角色兼容别名
# CLOUDFLARE_DNS_API_TOKEN → CF Zone DNS Edit tokendns 同步
# CLOUDFLARE_API_TOKEN → 兼容旧名DNS job 优先使用 CLOUDFLARE_DNS_API_TOKEN
# kv/openclaw:
# DEEPSEEK_API_KEY → LLM provider keydeploy 注入主机)
# NVIDIA_API_KEY → 同上
@ -43,6 +43,15 @@ name: Deploy AI Workspace (IaC + Ansible + Cloudflare)
# ai-workspace-infra/vultr-vps/config/resources/ai-workspace-hosts.yaml
# 的 ssh_keys[].public否则 Terraform 创机后 runner 无法 SSH 登录。
#
# 7. AI_WORKSPACE_AUTH_TOKEN统一服务 tokenLiteLLM master key / bridge / vault 等)
# - 三级优先级on-host installer resolve_unified_auth_token 统一解析):
# 1) workflow_dispatch 输入 ai_workspace_auth_token非空时覆盖
# 2) Vault kv/CICD/AI_WORKSPACE_AUTH_TOKEN输入留空时回退
# 3) 两者皆空installer 复用 ~/.ai_workspace_auth_token 或自动生成并持久化
# - 存储位置vault kv patch kv/CICD AI_WORKSPACE_AUTH_TOKEN=<your-token>
# - TLDR 生成python3 -c 'import uuid; print(uuid.uuid4())'
# - 经 run-on-host-bootstrap.sh 透传到主机 env注入 all-in-one 各 role
#
# ── 流水线结构 ───────────────────────────────────────────────────────────────
#
# provision : 批量起机模式开关terraform_action=apply / run_deploy
@ -114,6 +123,11 @@ on:
required: false
default: true
type: boolean
ai_workspace_auth_token:
description: "AI Workspace auth token 覆盖(留空则取 Vault kv/CICD/AI_WORKSPACE_AUTH_TOKEN生成: python3 -c 'import uuid; print(uuid.uuid4())')"
required: false
default: ""
type: string
# id-token: write 用于 Vault 的 GitHub OIDC(JWT) 认证contents: read 拉代码
permissions:
@ -316,7 +330,8 @@ jobs:
${{ env.VAULT_KV }} SSH_PRIVATE_DEPLOY_KEY_B64 | ANSIBLE_SSH_KEY_B64 ;
${{ env.VAULT_KV_OPENCLAW }} DEEPSEEK_API_KEY | DEEPSEEK_API_KEY ;
${{ env.VAULT_KV_OPENCLAW }} NVIDIA_API_KEY | NVIDIA_API_KEY ;
${{ env.VAULT_KV_OPENCLAW }} OLLAMA_API_KEY | OLLAMA_API_KEY
${{ env.VAULT_KV_OPENCLAW }} OLLAMA_API_KEY | OLLAMA_API_KEY ;
${{ env.VAULT_KV }} AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN
- name: Report provider key wiring
run: |
@ -403,6 +418,9 @@ jobs:
# 离线包重新发布后可设为 auto 恢复离线加速。
AI_WORKSPACE_OFFLINE_MODE: ${{ github.event.inputs.offline_mode || 'off' }}
XWORKMATE_BRIDGE_DOMAIN: ${{ github.event.inputs.bridge_domain }}
# input 非空则覆盖;否则取 Vault kv/CICD/AI_WORKSPACE_AUTH_TOKEN
# 两者皆空时由 on-host installer (resolve_unified_auth_token) 自动生成并持久化。
AI_WORKSPACE_AUTH_TOKEN: ${{ github.event.inputs.ai_workspace_auth_token != '' && github.event.inputs.ai_workspace_auth_token || steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}
DEEPSEEK_API_KEY: ${{ github.event.inputs.use_deepseek == 'false' && '' || steps.vault.outputs.DEEPSEEK_API_KEY }}
NVIDIA_API_KEY: ${{ github.event.inputs.use_nvidia == 'false' && '' || steps.vault.outputs.NVIDIA_API_KEY }}
OLLAMA_API_KEY: ${{ github.event.inputs.use_ollama == 'false' && '' || steps.vault.outputs.OLLAMA_API_KEY }}
@ -425,18 +443,16 @@ jobs:
jwtGithubAudience: vault
ignoreNotFound: true
secrets: |
${{ env.VAULT_KV }} CLOUDFLARE_DNS_API_TOKEN | CLOUDFLARE_DNS_API_TOKEN ;
${{ env.VAULT_KV }} CLOUDFLARE_API_TOKEN | CLOUDFLARE_API_TOKEN
${{ env.VAULT_KV }} CLOUDFLARE_DNS_API_TOKEN | CLOUDFLARE_DNS_API_TOKEN
- name: Validate required secrets
env:
CLOUDFLARE_DNS_API_TOKEN: ${{ steps.vault.outputs.CLOUDFLARE_DNS_API_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ steps.vault.outputs.CLOUDFLARE_API_TOKEN }}
run: |
set -euo pipefail
# 只校验 REQUIRED 机密非空不打印任何值仅判空INFRA_REPO_TOKEN 可选不校验。
missing=0
if [ -z "${CLOUDFLARE_DNS_API_TOKEN:-}" ] && [ -z "${CLOUDFLARE_API_TOKEN:-}" ]; then
if [ -z "${CLOUDFLARE_DNS_API_TOKEN:-}" ]; then
echo "::error::缺少必需机密 CLOUDFLARE_DNS_API_TOKEN (Vault: ${VAULT_KV}/CLOUDFLARE_DNS_API_TOKEN)"
missing=1
fi
@ -466,7 +482,6 @@ jobs:
working-directory: ${{ env.PLAYBOOKS_DIR }}
env:
CLOUDFLARE_DNS_API_TOKEN: ${{ steps.vault.outputs.CLOUDFLARE_DNS_API_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ steps.vault.outputs.CLOUDFLARE_API_TOKEN }}
run: |
set -euo pipefail
# 只为本次新建的 ai_workspace 组主机同步 A 记录(域名取各主机

View File

@ -0,0 +1,44 @@
name: Validate Release PR
# release/* 分支的发布策略门禁:仅接受 hotfix/* 或带 cherry-pick/backport 标签的 PR。
# 详见 iac_modules/docs/tldr-github-branch-model.md
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled, unlabeled]
permissions:
contents: read
pull-requests: read
jobs:
validate-release-source:
runs-on: ubuntu-latest
if: startsWith(github.base_ref, 'release/')
steps:
- name: Check PR source branch
run: |
SRC="${{ github.head_ref }}"
TGT="${{ github.base_ref }}"
LABELS="${{ join(github.event.pull_request.labels.*.name, ',') }}"
echo "🔍 Validating PR into release branch"
echo " source: $SRC"
echo " target: $TGT"
echo " labels: $LABELS"
if [[ "$SRC" =~ ^hotfix/ ]]; then
echo "✅ Allowed: hotfix/* branch"
exit 0
fi
if [[ "$LABELS" =~ (^|,)(cherry-pick|backport)(,|$) ]]; then
echo "✅ Allowed: cherry-pick/backport labeled PR"
exit 0
fi
echo "❌ Rejected."
echo "release/* 仅接受:"
echo " - 来自 hotfix/* 的 PR"
echo " - 带 cherry-pick 或 backport 标签的 PR已验证 feature 的 backport/cherry-pick"
echo "禁止从 main / develop / feature/* 直接合并到 release/*。"
exit 1

View File

@ -46,6 +46,8 @@ trap 'rm -f "$remote_payload"' EXIT
{
printf 'AI_WORKSPACE_OFFLINE_MODE=%q\n' "${AI_WORKSPACE_OFFLINE_MODE:-off}"
printf 'XWORKMATE_BRIDGE_DOMAIN=%q\n' "$domain"
# 空则不写,让 on-host installer 的 resolve_unified_auth_token 走"复用持久化/自动生成"分支。
printf 'AI_WORKSPACE_AUTH_TOKEN=%q\n' "${AI_WORKSPACE_AUTH_TOKEN:-}"
printf 'DEEPSEEK_API_KEY=%q\n' "${DEEPSEEK_API_KEY:-}"
printf 'NVIDIA_API_KEY=%q\n' "${NVIDIA_API_KEY:-}"
printf 'OLLAMA_API_KEY=%q\n' "${OLLAMA_API_KEY:-}"
@ -67,7 +69,7 @@ fi
(
set +e
source "$remote_env"
export AI_WORKSPACE_OFFLINE_MODE XWORKMATE_BRIDGE_DOMAIN DEEPSEEK_API_KEY NVIDIA_API_KEY OLLAMA_API_KEY
export AI_WORKSPACE_OFFLINE_MODE XWORKMATE_BRIDGE_DOMAIN AI_WORKSPACE_AUTH_TOKEN DEEPSEEK_API_KEY NVIDIA_API_KEY OLLAMA_API_KEY
bash -lc 'curl -sfL https://install.svc.plus/ai-workspace | bash -'
rc=$?
printf '%s\n' "$rc" > "$remote_rc"

View File

@ -1085,11 +1085,15 @@ patch_playbooks_for_macos() {
info "Fetching and running macOS playbook patches..."
local patch_script="/tmp/patch-macos-playbooks.py"
local raw_url="https://raw.githubusercontent.com/ai-workspace-lab/xworkspace-console/main/scripts/patch-macos-playbooks.py"
if command -v curl >/dev/null 2>&1; then
curl -sfL -o "$patch_script" "$raw_url"
local local_patch_script
local_patch_script="${XWORKSPACE_CONSOLE_DIR}/scripts/patch-macos-playbooks.py"
if [ -f "$local_patch_script" ]; then
cp "$local_patch_script" "$patch_script"
elif command -v curl >/dev/null 2>&1; then
curl -sfL -o "$patch_script" "${raw_url}?rev=$(date +%s)"
else
wget -qO "$patch_script" "$raw_url"
wget -qO "$patch_script" "${raw_url}?rev=$(date +%s)"
fi
if [ -f "$patch_script" ]; then

View File

@ -250,6 +250,27 @@ test_macos_plugin_patch_uses_stable_directory() {
fi
}
test_local_bootstrap_prefers_local_macos_patcher() {
local checkout workdir
checkout="$(mktemp -d)"
workdir="$(mktemp -d)"
mkdir -p "$checkout/scripts"
printf 'local-patcher-marker\n' > "$checkout/scripts/patch-macos-playbooks.py"
(
XWORKSPACE_CONSOLE_DIR="$checkout"
cd "$workdir"
# shellcheck disable=SC2329
python3() {
grep -q '^local-patcher-marker$' "$1" ||
fail "patch function did not execute the checked-in patcher"
}
patch_playbooks_for_macos
)
rm -rf "$checkout" "$workdir"
grep -Fq '"${raw_url}?rev=$(date +%s)"' "$BOOTSTRAP" ||
fail "remote macOS patcher download is not cache-busted"
}
test_root_does_not_require_sudo
printf 'ok - root execution does not require sudo\n'
test_non_root_uses_sudo
@ -283,3 +304,5 @@ test_provider_api_keys_use_secret_logging
printf 'ok - provider API keys use masked secret logging\n'
test_macos_plugin_patch_uses_stable_directory
printf 'ok - macOS plugin patch uses stable extension storage\n'
test_local_bootstrap_prefers_local_macos_patcher
printf 'ok - local bootstrap prefers the checked-in macOS patcher\n'