Sync Environment Skill 将 CI 环境的 Kubernetes kustomization 配置同步到 Staging,可选同步到 Production。 适用于 simplex-gitops 仓库。 Triggers : "sync ci to staging", "sync to staging", "sync to prod", "promote to production", "同步ci到staging", "同步到生产", "sync configs", "promote images" Promotion Path ⚠️ 部署策略 | 环境 | 策略 | 说明 | |------|------|------| | Staging | 可自动同步 | 推送后 ArgoCD 可自动检测并同步 | | Production | 必须手动 | 推送后需用户手动触发 ArgoCD sync | 永远不要自动执行 。 环境差异白名单(不同步) 以下配置项在各环境间预期不同,同步时 必须跳过 : | 白名单项 | CI | Staging | Prod | 原因 | |----------|-----|---------|------|------| | | CI RDS | Staging RDS | Prod RDS | 各环境独立数据库 | | | |…

, old_content, re.MULTILINE)\n new_match = re.search(rf'^\\s+{re.escape(key)}:\\s*(.+)

Sync Environment Skill 将 CI 环境的 Kubernetes kustomization 配置同步到 Staging,可选同步到 Production。 适用于 simplex-gitops 仓库。 Triggers : "sync ci to staging", "sync to staging", "sync to prod", "promote to production", "同步ci到staging", "同步到生产", "sync configs", "promote images" Promotion Path ⚠️ 部署策略 | 环境 | 策略 | 说明 | |------|------|------| | Staging | 可自动同步 | 推送后 ArgoCD 可自动检测并同步 | | Production | 必须手动 | 推送后需用户手动触发 ArgoCD sync | 永远不要自动执行 。 环境差异白名单(不同步) 以下配置项在各环境间预期不同,同步时 必须跳过 : | 白名单项 | CI | Staging | Prod | 原因 | |----------|-----|---------|------|------| | | CI RDS | Staging RDS | Prod RDS | 各环境独立数据库 | | | |…

, new_content, re.MULTILINE)\n \n if old_match and new_match:\n if old_match.group(1) != new_match.group(1):\n modified_count += 1\n \n if modified_count > 0:\n changes.append(f\"🔄 已修改: {modified_count} 个键更改了值\")\n else:\n # 计算总行差异\n old_lines = old_content.splitlines()\n new_lines = new_content.splitlines()\n diff_count = sum(1 for a, b in zip(old_lines, new_lines) if a != b)\n diff_count += abs(len(old_lines) - len(new_lines))\n changes.append(f\"🔄 {diff_count} 行不同\")\n \n except Exception as e:\n changes.append(f\"⚠️ 解析错误: {str(e)}\")\n \n return changes if changes else [\"内容不同但未识别特定更改\"]\n\n\ndef compare_kustomization_resources(ci_file: Path, staging_file: Path) -> Dict:\n \"\"\"比较 kustomization.yaml 中的 resources 和 patches 部分。\"\"\"\n ci_content = ci_file.read_text()\n staging_content = staging_file.read_text()\n \n def extract_section(content: str, section: str) -> Set[str]:\n \"\"\"从 YAML 部分提取列表项。\"\"\"\n items = set()\n in_section = False\n for line in content.splitlines():\n if re.match(rf'^{section}:\\s*

Sync Environment Skill 将 CI 环境的 Kubernetes kustomization 配置同步到 Staging,可选同步到 Production。 适用于 simplex-gitops 仓库。 Triggers : "sync ci to staging", "sync to staging", "sync to prod", "promote to production", "同步ci到staging", "同步到生产", "sync configs", "promote images" Promotion Path ⚠️ 部署策略 | 环境 | 策略 | 说明 | |------|------|------| | Staging | 可自动同步 | 推送后 ArgoCD 可自动检测并同步 | | Production | 必须手动 | 推送后需用户手动触发 ArgoCD sync | 永远不要自动执行 。 环境差异白名单(不同步) 以下配置项在各环境间预期不同,同步时 必须跳过 : | 白名单项 | CI | Staging | Prod | 原因 | |----------|-----|---------|------|------| | | CI RDS | Staging RDS | Prod RDS | 各环境独立数据库 | | | |…

, line):\n in_section = True\n continue\n if in_section:\n # 如果遇到另一个顶级键,则结束部分\n if line and not line.startswith(' ') and not line.startswith('-') and ':' in line:\n break\n # 提取列表项\n match = re.match(r'^\\s*-\\s*(.+)

Sync Environment Skill 将 CI 环境的 Kubernetes kustomization 配置同步到 Staging,可选同步到 Production。 适用于 simplex-gitops 仓库。 Triggers : "sync ci to staging", "sync to staging", "sync to prod", "promote to production", "同步ci到staging", "同步到生产", "sync configs", "promote images" Promotion Path ⚠️ 部署策略 | 环境 | 策略 | 说明 | |------|------|------| | Staging | 可自动同步 | 推送后 ArgoCD 可自动检测并同步 | | Production | 必须手动 | 推送后需用户手动触发 ArgoCD sync | 永远不要自动执行 。 环境差异白名单(不同步) 以下配置项在各环境间预期不同,同步时 必须跳过 : | 白名单项 | CI | Staging | Prod | 原因 | |----------|-----|---------|------|------| | | CI RDS | Staging RDS | Prod RDS | 各环境独立数据库 | | | |…

, line)\n if match:\n item = match.group(1).strip()\n items.add(item)\n return items\n \n results = {\n 'resources': {\n 'ci_only': [],\n 'staging_only': [],\n 'common': []\n },\n 'patches': {\n 'ci_only': [],\n 'staging_only': [],\n 'common': []\n }\n }\n \n # 比较 resources\n ci_resources = extract_section(ci_content, 'resources')\n staging_resources = extract_section(staging_content, 'resources')\n \n results['resources']['ci_only'] = sorted(ci_resources - staging_resources)\n results['resources']['staging_only'] = sorted(staging_resources - ci_resources)\n results['resources']['common'] = sorted(ci_resources & staging_resources)\n \n # 比较 patches(处理字符串和对象格式)\n def extract_patches(content: str) -> Set[str]:\n patches = set()\n in_patches = False\n for line in content.splitlines():\n if re.match(r'^patches:\\s*

Sync Environment Skill 将 CI 环境的 Kubernetes kustomization 配置同步到 Staging,可选同步到 Production。 适用于 simplex-gitops 仓库。 Triggers : "sync ci to staging", "sync to staging", "sync to prod", "promote to production", "同步ci到staging", "同步到生产", "sync configs", "promote images" Promotion Path ⚠️ 部署策略 | 环境 | 策略 | 说明 | |------|------|------| | Staging | 可自动同步 | 推送后 ArgoCD 可自动检测并同步 | | Production | 必须手动 | 推送后需用户手动触发 ArgoCD sync | 永远不要自动执行 。 环境差异白名单(不同步) 以下配置项在各环境间预期不同,同步时 必须跳过 : | 白名单项 | CI | Staging | Prod | 原因 | |----------|-----|---------|------|------| | | CI RDS | Staging RDS | Prod RDS | 各环境独立数据库 | | | |…

, line):\n in_patches = True\n continue\n if in_patches:\n if line and not line.startswith(' ') and not line.startswith('-') and ':' in line:\n break\n # 字符串格式: - patches/file.yaml\n match = re.match(r'^\\s*-\\s*patches/(.+\\.yaml)', line)\n if match:\n patches.add(f\"patches/{match.group(1)}\")\n continue\n # 对象格式: path: patches/file.yaml\n match = re.match(r'^\\s+path:\\s*patches/(.+\\.yaml)', line)\n if match:\n patches.add(f\"patches/{match.group(1)}\")\n return patches\n \n ci_patches = extract_patches(ci_content)\n staging_patches = extract_patches(staging_content)\n \n results['patches']['ci_only'] = sorted(ci_patches - staging_patches)\n results['patches']['staging_only'] = sorted(staging_patches - ci_patches)\n results['patches']['common'] = sorted(ci_patches & staging_patches)\n \n return results\n\n\ndef print_results(patch_results: Dict, kust_results: Dict, detailed: bool):\n \"\"\"以格式化方式打印比较结果。\"\"\"\n print(\"\\n\" + \"=\" * 80)\n print(\"配置比较:CI vs Staging\")\n print(\"=\" * 80)\n \n # Patches 目录比较\n print(\"\\n📁 Patches 目录比较\")\n print(\"-\" * 80)\n \n if patch_results['different']:\n print(f\"\\n🔄 配置不同 ({len(patch_results['different'])} 个文件):\")\n for item in patch_results['different']:\n safe_icon = \"✅\" if item['safe_to_sync'] else \"⚠️\"\n print(f\"\\n {safe_icon} {item['filename']} ({item['resource_type']})\")\n print(f\" {item['reason']}\")\n \n if 'changes' in item and item['changes']:\n for change in item['changes']:\n print(f\" {change}\")\n \n if detailed and 'diff' in item:\n print(\"\\n\" + \"-\" * 60)\n print(item['diff'])\n print(\"-\" * 60)\n \n if patch_results['ci_only']:\n print(f\"\\n➕ CI 中的新配置 ({len(patch_results['ci_only'])} 个文件):\")\n for filename in patch_results['ci_only']:\n safe, reason = is_safe_to_sync(filename)\n safe_icon = \"✅\" if safe else \"⚠️\"\n print(f\" {safe_icon} {filename} - {reason}\")\n \n if patch_results['staging_only']:\n print(f\"\\n➖ CI 中已删除 ({len(patch_results['staging_only'])} 个文件):\")\n for filename in patch_results['staging_only']:\n print(f\" ⚠️ {filename}\")\n \n if patch_results['identical']:\n print(f\"\\n✅ 相同 ({len(patch_results['identical'])} 个文件):\")\n for filename in patch_results['identical']:\n print(f\" {filename}\")\n \n # Kustomization resources 比较\n print(\"\\n\\n📦 Kustomization Resources 比较\")\n print(\"-\" * 80)\n \n if kust_results['resources']['ci_only']:\n print(f\"\\n➕ CI 中的新资源 ({len(kust_results['resources']['ci_only'])}):\")\n for resource in kust_results['resources']['ci_only']:\n print(f\" {resource}\")\n \n if kust_results['resources']['staging_only']:\n print(f\"\\n➖ 仅 STAGING 中的资源 ({len(kust_results['resources']['staging_only'])}):\")\n for resource in kust_results['resources']['staging_only']:\n print(f\" {resource}\")\n \n if kust_results['patches']['ci_only']:\n print(f\"\\n➕ CI 中的新 Patches ({len(kust_results['patches']['ci_only'])}):\")\n for patch in kust_results['patches']['ci_only']:\n print(f\" {patch}\")\n \n if kust_results['patches']['staging_only']:\n print(f\"\\n➖ 仅 STAGING 中的 Patches ({len(kust_results['patches']['staging_only'])}):\")\n for patch in kust_results['patches']['staging_only']:\n print(f\" {patch}\")\n \n print(\"\\n\" + \"=\" * 80)\n \n # 摘要和建议\n print(\"\\n💡 建议:\")\n print(\"-\" * 80)\n \n safe_to_sync = [item for item in patch_results['different'] \n if item['safe_to_sync']]\n unsafe_to_sync = [item for item in patch_results['different'] \n if not item['safe_to_sync']]\n \n if safe_to_sync:\n print(f\"\\n✅ 安全可审查以同步 ({len(safe_to_sync)} 个文件):\")\n for item in safe_to_sync:\n print(f\" • {item['filename']}\")\n print(\"\\n 审查这些文件,如果合适则同步:\")\n print(\" python3 ~/.cursor/skills/sync-env/scripts/compare_configs.py --file \u003cfilename> --detailed\")\n \n if unsafe_to_sync:\n print(f\"\\n⚠️ 环境特定配置 ({len(unsafe_to_sync)} 个文件):\")\n for item in unsafe_to_sync:\n print(f\" • {item['filename']} - {item['reason']}\")\n print(\"\\n 这些通常不应在环境之间同步。\")\n \n if patch_results['ci_only']:\n print(f\"\\n➕ 要添加到 staging 的新配置:\")\n for filename in patch_results['ci_only']:\n safe, reason = is_safe_to_sync(filename)\n if safe:\n print(f\" • {filename}\")\n \n print(\"\\n\" + \"=\" * 80)\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description='比较 CI 和 staging 之间的配置'\n )\n parser.add_argument('--detailed', action='store_true', \n help='显示每个文件的详细差异')\n parser.add_argument('--file', type=str, \n help='仅比较特定文件')\n parser.add_argument('--safe-only', action='store_true',\n help='只显示安全可同步的配置差异')\n args = parser.parse_args()\n \n try:\n root = find_gitops_root()\n except FileNotFoundError as e:\n print(f\"错误: {e}\", file=sys.stderr)\n sys.exit(1)\n \n ci_dir = root / \"kubernetes\" / \"overlays\" / \"aws-ci\"\n staging_dir = root / \"kubernetes\" / \"overlays\" / \"aws-staging\"\n \n # 比较 patches 目录\n patch_results = compare_patches_dir(\n ci_dir / \"patches\",\n staging_dir / \"patches\",\n detailed=args.detailed,\n target_file=args.file,\n safe_only=args.safe_only\n )\n \n # 比较 kustomization.yaml resources\n kust_results = compare_kustomization_resources(\n ci_dir / \"kustomization.yaml\",\n staging_dir / \"kustomization.yaml\"\n )\n \n # 打印结果\n print_results(patch_results, kust_results, args.detailed)\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":15440,"content_sha256":"7f199f88a99a42c2a2cd2a94948e168546a831915faa57ecb58992332cd71c45"},{"filename":"scripts/sync_images.py","content":"#!/usr/bin/env python3\n\"\"\"\nSync image tags between environments (CI -> Staging -> Production).\n\nDefault: sync from CI to Staging.\n\nUsage:\n python sync_images.py --diff\n python sync_images.py --all [--target staging|prod|all] [--dry-run]\n python sync_images.py --images front,agent [--target staging|prod|all] [--dry-run]\n\nOptions:\n --diff Show image tag differences between environments\n --images Comma-separated image names to sync (partial match)\n --all Sync all images\n --dry-run Show changes without modifying files\n --target Target environment: staging (default), prod, or all\n --source Source environment: ci (default) or staging (only for --target prod)\n\"\"\"\n\nimport argparse\nimport re\nimport sys\nfrom pathlib import Path\nfrom typing import Dict, List, Optional, Tuple\n\n\nOVERLAY_MAP = {\n \"ci\": \"aws-ci\",\n \"staging\": \"aws-staging\",\n \"prod\": \"aws-prod\",\n}\n\n\ndef find_gitops_root() -> Path:\n \"\"\"查找 simplex-gitops 仓库根目录。\"\"\"\n candidates = [\n Path.cwd(),\n Path.cwd() / \"simplex-gitops\",\n Path.home() / \"Code\" / \"all-code-in-mba\" / \"simplex-gitops\",\n ]\n\n for candidate in candidates:\n if (candidate / \"kubernetes\" / \"overlays\").exists():\n return candidate\n\n current = Path.cwd()\n while current != current.parent:\n if (current / \"kubernetes\" / \"overlays\").exists():\n return current\n current = current.parent\n\n raise FileNotFoundError(\"无法找到 simplex-gitops 仓库根目录\")\n\n\ndef kustomization_path(root: Path, env: str) -> Path:\n overlay = OVERLAY_MAP[env]\n return root / \"kubernetes\" / \"overlays\" / overlay / \"kustomization.yaml\"\n\n\ndef parse_images_section(content: str) -> Tuple[Dict[str, dict], int, int]:\n \"\"\"从 kustomization.yaml 解析 images 部分。\"\"\"\n lines = content.split(\"\\n\")\n images: Dict[str, dict] = {}\n in_images_section = False\n images_start = -1\n images_end = -1\n current_image: str | None = None\n\n for i, line in enumerate(lines):\n if re.match(r\"^images:\\s*(#.*)?$\", line):\n in_images_section = True\n images_start = i\n continue\n\n if in_images_section:\n stripped = line.strip()\n if (\n line\n and not line.startswith(\" \")\n and not line.startswith(\"-\")\n and not stripped.startswith(\"#\")\n and \":\" in line\n ):\n images_end = i\n break\n\n name_match = re.match(r\"^\\s*-\\s*name:\\s*(.+)$\", line)\n if name_match:\n current_image = name_match.group(1).strip()\n images[current_image] = {\"name\": current_image}\n continue\n\n if current_image:\n new_name_match = re.match(r\"^\\s+newName:\\s*(.+)$\", line)\n if new_name_match:\n images[current_image][\"newName\"] = new_name_match.group(1).strip()\n continue\n\n new_tag_match = re.match(r\"^\\s+newTag:\\s*(.+)$\", line)\n if new_tag_match:\n images[current_image][\"newTag\"] = new_tag_match.group(1).strip()\n continue\n\n if images_end == -1:\n images_end = len(lines)\n\n return images, images_start, images_end\n\n\ndef extract_service_name(image_name: str) -> str:\n \"\"\"从完整镜像路径提取服务名称。\"\"\"\n parts = image_name.split(\"/\")\n return parts[-1] if parts else image_name\n\n\ndef build_service_lookup(images: Dict[str, dict]) -> Dict[str, dict]:\n \"\"\"按服务名称构建查找表(优先 ECR 镜像)。\"\"\"\n by_service: Dict[str, dict] = {}\n for name, info in images.items():\n service = extract_service_name(name)\n if \"ecr\" in name or service not in by_service:\n by_service[service] = {\"full_name\": name, **info}\n return by_service\n\n\ndef compare_images(\n src_images: Dict[str, dict],\n dst_images: Dict[str, dict],\n src_label: str,\n dst_label: str,\n) -> List[dict]:\n \"\"\"比较源和目标环境的镜像。\"\"\"\n differences = []\n src_by_svc = build_service_lookup(src_images)\n dst_by_svc = build_service_lookup(dst_images)\n all_services = sorted(set(src_by_svc) | set(dst_by_svc))\n\n for service in all_services:\n src_info = src_by_svc.get(service)\n dst_info = dst_by_svc.get(service)\n\n if src_info and dst_info:\n src_tag = src_info.get(\"newTag\", \"N/A\")\n dst_tag = dst_info.get(\"newTag\", \"N/A\")\n if src_tag != dst_tag:\n differences.append(\n {\n \"service\": service,\n \"src_image\": src_info[\"full_name\"],\n \"src_tag\": src_tag,\n \"dst_image\": dst_info[\"full_name\"],\n \"dst_tag\": dst_tag,\n \"status\": \"different\",\n }\n )\n else:\n differences.append(\n {\n \"service\": service,\n \"src_tag\": src_tag,\n \"dst_tag\": dst_tag,\n \"status\": \"same\",\n }\n )\n elif src_info:\n differences.append(\n {\n \"service\": service,\n \"src_image\": src_info[\"full_name\"],\n \"src_tag\": src_info.get(\"newTag\", \"N/A\"),\n \"status\": \"src_only\",\n }\n )\n elif dst_info:\n differences.append(\n {\n \"service\": service,\n \"dst_image\": dst_info[\"full_name\"],\n \"dst_tag\": dst_info.get(\"newTag\", \"N/A\"),\n \"status\": \"dst_only\",\n }\n )\n\n return differences\n\n\ndef update_target_images(\n target_content: str,\n src_images: Dict[str, dict],\n target_services: Optional[List[str]] = None,\n) -> Tuple[str, List[dict]]:\n \"\"\"用源环境镜像标签更新目标 kustomization。\"\"\"\n changes: List[dict] = []\n lines = target_content.split(\"\\n\")\n src_by_svc = build_service_lookup(src_images)\n\n current_image: str | None = None\n current_service: str | None = None\n\n for i, line in enumerate(lines):\n name_match = re.match(r\"^(\\s*)-\\s*name:\\s*(.+)$\", line)\n if name_match:\n current_image = name_match.group(2).strip()\n current_service = extract_service_name(current_image)\n continue\n\n if current_service:\n tag_match = re.match(r\"^(\\s+)newTag:\\s*(.+)$\", line)\n if tag_match:\n indent = tag_match.group(1)\n old_tag = tag_match.group(2).strip()\n\n if target_services is None or any(\n t.lower() in current_service.lower() for t in target_services\n ):\n src_info = src_by_svc.get(current_service)\n if src_info and \"newTag\" in src_info:\n new_tag = src_info[\"newTag\"]\n if old_tag != new_tag:\n lines[i] = f\"{indent}newTag: {new_tag}\"\n changes.append(\n {\n \"service\": current_service,\n \"image\": current_image,\n \"old_tag\": old_tag,\n \"new_tag\": new_tag,\n }\n )\n\n return \"\\n\".join(lines), changes\n\n\ndef print_diff(differences: List[dict], src_label: str, dst_label: str):\n \"\"\"打印格式化的差异表。\"\"\"\n print(\"\\n\" + \"=\" * 80)\n print(f\"镜像标签比较:{src_label} vs {dst_label}\")\n print(\"=\" * 80)\n\n different = [d for d in differences if d[\"status\"] == \"different\"]\n same = [d for d in differences if d[\"status\"] == \"same\"]\n src_only = [d for d in differences if d[\"status\"] == \"src_only\"]\n dst_only = [d for d in differences if d[\"status\"] == \"dst_only\"]\n\n if different:\n print(f\"\\n🔄 标签不同 ({len(different)} 个服务):\")\n print(\"-\" * 80)\n print(f\"{'服务':\u003c30} {src_label + ' 标签':\u003c25} {dst_label + ' 标签':\u003c25}\")\n print(\"-\" * 80)\n for d in different:\n print(f\"{d['service']:\u003c30} {d['src_tag']:\u003c25} {d['dst_tag']:\u003c25}\")\n\n if same:\n print(f\"\\n✅ 标签相同 ({len(same)} 个服务):\")\n print(\"-\" * 80)\n for d in same:\n print(f\" {d['service']}: {d['src_tag']}\")\n\n if src_only:\n print(f\"\\n⚠️ 仅 {src_label} ({len(src_only)} 个服务):\")\n for d in src_only:\n print(f\" {d['service']}: {d['src_tag']}\")\n\n if dst_only:\n print(f\"\\n⚠️ 仅 {dst_label} ({len(dst_only)} 个服务):\")\n for d in dst_only:\n print(f\" {d['service']}: {d['dst_tag']}\")\n\n print(\"\\n\" + \"=\" * 80)\n\n\ndef sync_one_target(\n root: Path,\n source: str,\n target: str,\n src_images: Dict[str, dict],\n target_services: Optional[List[str]],\n dry_run: bool,\n) -> bool:\n \"\"\"同步一个目标环境。返回是否有变更。\"\"\"\n src_label = source.upper()\n dst_label = target.upper()\n\n target_path = kustomization_path(root, target)\n if not target_path.exists():\n print(f\"错误: 未找到 {dst_label} kustomization: {target_path}\", file=sys.stderr)\n return False\n\n target_content = target_path.read_text()\n target_images, _, _ = parse_images_section(target_content)\n\n differences = compare_images(src_images, target_images, src_label, dst_label)\n print_diff(differences, src_label, dst_label)\n\n updated_content, changes = update_target_images(\n target_content, src_images, target_services\n )\n\n if not changes:\n print(f\"\\n✅ 无需更改 - {dst_label} 已经同步!\")\n return False\n\n print(f\"\\n📝 将应用到 {dst_label} 的更改 ({len(changes)} 个更新):\")\n print(\"-\" * 60)\n for change in changes:\n print(f\" {change['service']}:\")\n print(f\" {change['old_tag']} → {change['new_tag']}\")\n\n if dry_run:\n print(f\"\\n🔍 DRY RUN ({dst_label}) - 未写入更改\")\n return False\n\n target_path.write_text(updated_content)\n print(f\"\\n✅ 已更新 {target_path}\")\n return True\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"Sync image tags between environments (CI -> Staging -> Production)\"\n )\n parser.add_argument(\"--dry-run\", action=\"store_true\", help=\"显示更改但不应用\")\n parser.add_argument(\"--images\", type=str, help=\"要同步的镜像(逗号分隔)\")\n parser.add_argument(\"--all\", action=\"store_true\", help=\"同步所有镜像\")\n parser.add_argument(\"--diff\", action=\"store_true\", help=\"仅显示差异\")\n parser.add_argument(\n \"--target\",\n choices=[\"staging\", \"prod\", \"all\"],\n default=\"staging\",\n help=\"目标环境:staging(默认)、prod、all\",\n )\n parser.add_argument(\n \"--source\",\n choices=[\"ci\", \"staging\"],\n default=None,\n help=\"源环境:ci(默认)或 staging(仅用于 --target prod)\",\n )\n args = parser.parse_args()\n\n # Resolve source: default is \"ci\"; for --target prod without explicit --source, use \"staging\"\n if args.source is None:\n if args.target == \"prod\":\n args.source = \"staging\"\n else:\n args.source = \"ci\"\n\n if args.source == \"staging\" and args.target == \"staging\":\n print(\"错误: 源和目标不能都是 staging\", file=sys.stderr)\n sys.exit(1)\n\n try:\n root = find_gitops_root()\n except FileNotFoundError as e:\n print(f\"错误: {e}\", file=sys.stderr)\n sys.exit(1)\n\n src_path = kustomization_path(root, args.source)\n if not src_path.exists():\n print(f\"错误: 未找到 {args.source.upper()} kustomization: {src_path}\", file=sys.stderr)\n sys.exit(1)\n\n src_content = src_path.read_text()\n src_images, _, _ = parse_images_section(src_content)\n\n # Determine targets\n if args.target == \"all\":\n targets = [\"staging\", \"prod\"]\n else:\n targets = [args.target]\n\n # Diff-only mode\n if args.diff:\n for t in targets:\n t_path = kustomization_path(root, t)\n if not t_path.exists():\n print(f\"警告: 未找到 {t.upper()} kustomization: {t_path}\", file=sys.stderr)\n continue\n t_content = t_path.read_text()\n t_images, _, _ = parse_images_section(t_content)\n src_label = args.source.upper()\n if args.target == \"all\" and t == \"prod\" and args.source == \"ci\":\n src_label = \"CI\"\n differences = compare_images(src_images, t_images, src_label, t.upper())\n print_diff(differences, src_label, t.upper())\n return\n\n # Determine target services\n target_services = None\n if args.images:\n target_services = [s.strip() for s in args.images.split(\",\")]\n elif not args.all:\n print(\"\\n未指定镜像。使用 --all 同步所有,或使用 --images 指定服务。\")\n print(\"示例: --images front,anotherme-agent,simplex-api\")\n return\n\n any_changed = False\n for t in targets:\n # For --target all, staging uses CI as source; prod uses CI as source too\n # (user can override with --source)\n changed = sync_one_target(\n root, args.source, t, src_images, target_services, args.dry_run\n )\n if changed:\n any_changed = True\n\n if any_changed:\n print(\"\\n下一步:\")\n if \"staging\" in targets and \"prod\" in targets:\n print(\n \" 1. 查看更改: git diff kubernetes/overlays/aws-staging/ kubernetes/overlays/aws-prod/\"\n )\n print(\n \" 2. 提交: git add -A && git commit -m 'chore: 从 CI 推广镜像到 staging 和 prod'\"\n )\n elif \"prod\" in targets:\n print(\" 1. 查看更改: git diff kubernetes/overlays/aws-prod/\")\n print(\n \" 2. 提交: git add -A && git commit -m 'chore: 推广镜像到 prod'\"\n )\n else:\n print(\" 1. 查看更改: git diff kubernetes/overlays/aws-staging/\")\n print(\n \" 2. 提交: git add -A && git commit -m 'chore: 从 CI 推广镜像到 staging'\"\n )\n print(\" 3. 推送以触发 ArgoCD 同步\")\n\n if \"prod\" in targets:\n print(\"\\n ⛔ Production 部署需要手动触发 ArgoCD sync:\")\n print(\" argocd app sync simplex-aws-prod\")\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":14881,"content_sha256":"0ca81f577df141686ded1536155adb667f86819b82e73da556cc2d73effaf255"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Sync Environment Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"将 CI 环境的 Kubernetes kustomization 配置同步到 Staging,可选同步到 Production。 适用于 simplex-gitops 仓库。","type":"text"}]},{"type":"paragraph","content":[{"text":"Triggers","type":"text","marks":[{"type":"strong"}]},{"text":": \"sync ci to staging\", \"sync to staging\", \"sync to prod\", \"promote to production\", \"同步ci到staging\", \"同步到生产\", \"sync configs\", \"promote images\"","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Promotion Path","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"CI (aws-ci)\n ↓ 默认:同步到 staging\nStaging (aws-staging)\n ↓ 可选:同步到 production(需明确指定 --target prod 或 --target all)\nProduction (aws-prod)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"⚠️ 部署策略","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"环境","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"策略","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"说明","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Staging","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"可自动同步","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"推送后 ArgoCD 可自动检测并同步","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Production","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"必须手动","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"推送后需用户手动触发 ArgoCD sync","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"永远不要自动执行 ","type":"text","marks":[{"type":"strong"}]},{"text":"argocd app sync simplex-aws-prod","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":"。","type":"text","marks":[{"type":"strong"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"环境差异白名单(不同步)","type":"text"}]},{"type":"paragraph","content":[{"text":"以下配置项在各环境间预期不同,同步时","type":"text"},{"text":"必须跳过","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"白名单项","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Staging","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"原因","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"database.default.link","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI RDS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Staging RDS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod RDS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"各环境独立数据库","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"redis.default.address","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"k8s-ci","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"k8s-staging","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"k8s-prod","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"各环境独立 MemoryDB","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mongodb.default.uri","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI DocDB","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Staging DocDB","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod DocDB","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"各环境独立 DocumentDB","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"rabbitmq.default.url","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI MQ","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Staging MQ","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod MQ","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"各环境独立 MQ broker","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"stripe.publishableKey","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pk_test_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pk_live_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pk_live_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI 用测试密钥","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"stripe.secretKey","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sk_test_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sk_live_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sk_live_*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI 用测试密钥","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"stripe.webhookSecret","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI endpoint","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Staging endpoint","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod endpoint","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"各环境独立 webhook","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"payment.*.stripePriceId","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"test Price ID","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"live Price ID","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"live Price ID","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"不同 Stripe 账户","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"campaign.sendEmails.redirectEnabled","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"true","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"false","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI 安全重定向","type":"text"}]}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"kubernetes/scripts/compare-configs.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 已内置此白名单,diff 时自动标记为 \"expected\"。","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"文件位置","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"kubernetes/overlays/aws-ci/kustomization.yaml # CI 配置\nkubernetes/overlays/aws-staging/kustomization.yaml # Staging 配置\nkubernetes/overlays/aws-prod/kustomization.yaml # Production 配置\nkubernetes/overlays/aws-ci/configs/ # CI 服务配置文件\nkubernetes/overlays/aws-staging/configs/ # Staging 服务配置文件\nkubernetes/overlays/aws-prod/configs/ # Production 服务配置文件","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"快速命令","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"查看 YAML 配置差异(推荐,支持白名单)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd kubernetes\n\n# 对比 simplex-api 配置(默认,只显示差异)\nmake config-diff\n\n# 对比指定服务\nmake config-diff SVC=simplex-gateway\nmake config-diff SVC=simplex-router-backend\n\n# 显示白名单项详情\npython3 scripts/compare-configs.py --diff-only --show-expected\n\n# JSON 格式输出\npython3 scripts/compare-configs.py --json --diff-only\n\n# 列出所有可对比的服务\nmake list-services","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"查看镜像差异","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# CI vs Staging(默认)\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --diff\n\n# CI vs Production\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --diff --target prod\n\n# Staging vs Production\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --diff --target prod --source staging","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"查看配置差异(patches/目录级别)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 比较所有配置(CI vs Staging)\npython3 ~/.cursor/skills/sync-env/scripts/compare_configs.py\n\n# 详细差异(包含文件内容变更)\npython3 ~/.cursor/skills/sync-env/scripts/compare_configs.py --detailed\n\n# 只显示安全可同步的配置(排除 secrets, ingress)\npython3 ~/.cursor/skills/sync-env/scripts/compare_configs.py --detailed --safe-only\n\n# 比较特定文件\npython3 ~/.cursor/skills/sync-env/scripts/compare_configs.py --file anotherme-agent-env-configmap.yaml --detailed","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"同步镜像","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 同步特定服务到 staging(默认)\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --images front,anotherme-agent\n\n# 同步所有镜像到 staging(先 dry-run)\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --all --dry-run\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --all\n\n# 同步到 production\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --all --target prod --dry-run\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --all --target prod\n\n# 同时同步到 staging 和 production\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --all --target all --dry-run\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --all --target all","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"同步工作流","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 0:全面对比(必须)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd kubernetes\n\n# 查看 3 环境配置差异(自动应用白名单)\npython3 scripts/compare-configs.py --diff-only\n\n# 如果有 \"需要关注的差异项\"(红色),说明有业务配置不一致\n# 如果只有 \"白名单内预期不同\"(蓝色),说明已同步完毕\n\n# 查看镜像差异\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --diff\n\n# 查看配置差异(仅安全可审查的)\npython3 ~/.cursor/skills/sync-env/scripts/compare_configs.py --detailed --safe-only","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1:确认同步范围","type":"text"}]},{"type":"paragraph","content":[{"text":"基于 Step 0 的输出,确认哪些差异需要同步。","type":"text"}]},{"type":"paragraph","content":[{"text":"典型的可同步项(业务功能):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"邮件模板 ID(","type":"text"},{"text":"resend.welcomeTemplateId","type":"text","marks":[{"type":"code_inline"}]},{"text":" 等)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"积分费率(","type":"text"},{"text":"credit.rates.*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"credit.signalRates.*","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"新用户赠送(","type":"text"},{"text":"credit.newUserGrant","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"credit.newUserGrantWithInvite","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"套餐结构(","type":"text"},{"text":"payment.plans[]","type":"text","marks":[{"type":"code_inline"}]},{"text":" 结构、金额、描述、并发数)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"RabbitMQ 队列定义(","type":"text"},{"text":"rabbitmq.default.queues.*","type":"text","marks":[{"type":"code_inline"}]},{"text":" 新队列)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Worker 并发(","type":"text"},{"text":"worker.concurrency","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"不可同步项:","type":"text","marks":[{"type":"strong"}]},{"text":" 见上方白名单表格。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2:审查并选择服务","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 推广单个关键服务\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --images front --dry-run\n\n# 推广前端服务\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --images front,front-homepage --dry-run\n\n# 推广所有 AI 服务\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --images anotherme-agent,anotherme-api,anotherme-search,anotherme-worker --dry-run\n\n# 推广所有内容\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --all --dry-run","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3:应用变更","type":"text"}]},{"type":"paragraph","content":[{"text":"审查 dry-run 输出后,应用变更:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 只同步到 staging(默认)\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --images \u003cservices>\n\n# 同步到 production\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --images \u003cservices> --target prod\n\n# 同时同步到 staging 和 production\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --images \u003cservices> --target all","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4:手动同步配置(如果需要)","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"方式 A: 手动编辑 configs/ 下的 YAML 文件(推荐)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 打开文件对比编辑\n# CI (源): kubernetes/overlays/aws-ci/configs/simplex-api/config.yaml\n# Staging: kubernetes/overlays/aws-staging/configs/simplex-api/config.yaml\n# Prod: kubernetes/overlays/aws-prod/configs/simplex-api/config.yaml","type":"text"}]},{"type":"paragraph","content":[{"text":"注意事项:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"复制业务逻辑配置时,保持目标文件的白名单值不变","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"stripePriceId","type":"text","marks":[{"type":"code_inline"}]},{"text":": Staging/Prod 使用各自的 live Price ID,不要覆盖","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"新增的 pro 套餐需要先在 Stripe Live Dashboard 创建对应 Price","type":"text"}]}]}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"方式 B: 手动同步 ConfigMap Patches","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. 查看详细差异\npython3 ~/.cursor/skills/sync-env/scripts/compare_configs.py \\\n --file anotherme-agent-env-configmap.yaml --detailed\n\n# 2. 手动编辑特定键(推荐)\nvim kubernetes/overlays/aws-staging/patches/anotherme-agent-env-configmap.yaml\n\n# 3. 验证变更\ngit diff kubernetes/overlays/aws-staging/patches/","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5:提交并推送","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd /path/to/simplex-gitops\n\n# 只同步了 staging\ngit add kubernetes/overlays/aws-staging/\ngit commit -m \"chore: 从 CI 推广 \u003cservices> 到 staging\"\ngit push\n\n# 同时同步了 staging 和 prod\ngit add kubernetes/overlays/aws-staging/ kubernetes/overlays/aws-prod/\ngit commit -m \"chore: sync CI business configs to staging and production\n\nSynced: \u003c列出同步的配置项>\nWhitelisted (not synced): infra connections, stripe keys, stripePriceId, campaign.redirectEnabled\"\ngit push","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 6:部署","type":"text"}]},{"type":"paragraph","content":[{"text":"Staging(可自动):","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"argocd app get simplex-aws-staging\nargocd app diff simplex-aws-staging\n# 如需手动同步:\nargocd app sync simplex-aws-staging","type":"text"}]},{"type":"paragraph","content":[{"text":"Production(必须手动确认):","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 查看变更(只读,安全)\nargocd app get simplex-aws-prod\nargocd app diff simplex-aws-prod\n\n# ⛔ 用户明确要求后才执行\nargocd app sync simplex-aws-prod","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"可能需要同步的配置部分","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"自动配置差异检测","type":"text"}]},{"type":"paragraph","content":[{"text":"compare_configs.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 脚本自动识别以下内容的差异:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"类别","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"示例","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"同步建议","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":"安全可审查","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"*-env-configmap.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"仔细审查,可能需要选择性同步","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🔐 ","type":"text"},{"text":"Secrets","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"*-secrets.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"永不同步 - 环境特定","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"🌐 ","type":"text"},{"text":"Ingress","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ingress.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"永不同步 - 域名不同","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"⚙️ ","type":"text"},{"text":"基础设施","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"gateway-cm0-*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"router-cm0-*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"api-cm0-*","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"通常是环境特定的","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"不应同步的配置","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"配置","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"原因","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"副本数","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI 运行更少的副本,Staging/Prod 使用 base 默认值","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"节点池分配","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI: ","type":"text"},{"text":"ci","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"singleton-ci","type":"text","marks":[{"type":"code_inline"}]},{"text":", Staging: ","type":"text"},{"text":"staging","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"singleton-staging","type":"text","marks":[{"type":"code_inline"}]},{"text":", Prod: ","type":"text"},{"text":"production","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"singleton-production","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"存储类","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod 使用 ","type":"text"},{"text":"gp3","type":"text","marks":[{"type":"code_inline"}]},{"text":", Staging 使用 ","type":"text"},{"text":"ebs-gp3-auto","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"高可用设置","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod 有 ","type":"text"},{"text":"topologySpreadConstraints","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"terminationGracePeriodSeconds: 60","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"同步后验证","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"检查 ArgoCD 状态(只读,安全)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Staging\nargocd app get simplex-aws-staging\nargocd app diff simplex-aws-staging\n\n# Production\nargocd app get simplex-aws-prod\nargocd app diff simplex-aws-prod","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"手动同步(用户必须明确请求)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Staging\nargocd app sync simplex-aws-staging\n\n# ⛔ Production - 仅在用户明确要求时执行\nargocd app sync simplex-aws-prod","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"检查部署的版本","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# CI namespace\nk1 get pods -n ci -o jsonpath='{range .items[*]}{.metadata.name}{\"\\t\"}{.spec.containers[*].image}{\"\\n\"}{end}'\n\n# Staging namespace\nk2 get pods -n staging -o jsonpath='{range .items[*]}{.metadata.name}{\"\\t\"}{.spec.containers[*].image}{\"\\n\"}{end}'\n\n# Production namespace\nk1 get pods -n production -o jsonpath='{range .items[*]}{.metadata.name}{\"\\t\"}{.spec.containers[*].image}{\"\\n\"}{end}'","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"验证清单","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"kubectl kustomize kubernetes/overlays/aws-ci > /tmp/ci-manifests.yaml\nkubectl kustomize kubernetes/overlays/aws-staging > /tmp/staging-manifests.yaml\nkubectl kustomize kubernetes/overlays/aws-prod > /tmp/prod-manifests.yaml\ndiff /tmp/ci-manifests.yaml /tmp/staging-manifests.yaml\ndiff /tmp/staging-manifests.yaml /tmp/prod-manifests.yaml","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"常用 Make 命令","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd kubernetes\n\n# 对比所有服务配置\nmake config-diff # simplex-api(默认)\nmake config-diff SVC=simplex-gateway\nmake config-diff SVC=simplex-router-backend\nmake list-services # 列出所有可对比的服务\n\n# 镜像版本对比\nmake compare-images # 快速 bash 对比\nmake compare-images-detail # 详细 Python 对比","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"配置文件映射","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"服务","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI 配置路径","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Staging 配置路径","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Prod 配置路径","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"simplex-api","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-ci/configs/simplex-api/config.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-staging/configs/simplex-api/config.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-prod/configs/simplex-api/config.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"simplex-gateway","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-ci/configs/gateway/config.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-staging/configs/gateway/config.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-prod/configs/gateway/config.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"simplex-router-backend","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-ci/configs/simplex-router-backend/config-backend.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-staging/configs/simplex-router-backend/config-backend.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"overlays/aws-prod/configs/simplex-router-backend/config-backend.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"故障排除","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"脚本未找到仓库","type":"text"}]},{"type":"paragraph","content":[{"text":"确保你在 simplex-gitops 目录中或明确设置路径:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"cd /path/to/simplex-gitops\npython3 ~/.cursor/skills/sync-env/scripts/sync_images.py --diff","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"CI 中未找到镜像","type":"text"}]},{"type":"paragraph","content":[{"text":"服务可能使用不同的镜像名称格式(Aliyun vs ECR)。检查 kustomization 文件中的两种格式。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"ArgoCD 未同步","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 查看应用状态(只读)\nargocd app get simplex-aws-staging --show-operation\nargocd app get simplex-aws-prod --show-operation\n\n# 刷新应用检测最新变更(只读,安全)\nargocd app refresh simplex-aws-staging\nargocd app refresh simplex-aws-prod\n\n# 手动同步 - staging\nargocd app sync simplex-aws-staging\n\n# ⛔ 手动同步 production - 仅在用户明确要求时执行\nargocd app sync simplex-aws-prod","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"服务类别参考","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"类别","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"服务","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AI 核心","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"anotherme-agent","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"anotherme-api","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"anotherme-search","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"anotherme-worker","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"前端","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"front","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"front-homepage","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"后端","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"simplex-cron","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"simplex-gateway-api","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"simplex-gateway-worker","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"数据","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"data-search-api","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"crawler","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"基础设施","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"litellm","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"node-server","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"simplex-router","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"simplex-router-backend","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"simplex-router-fronted","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"sync-env","author":"@skillopedia","source":{"stars":3,"repo_name":"skills","origin_url":"https://github.com/oldwinter/skills/blob/HEAD/devops-skills/sync-env/SKILL.md","repo_owner":"oldwinter","body_sha256":"2c39eb9f75c9573ae88cee30d8bdd4fa1a88300a181dffaab504424cdc00fd98","cluster_key":"371b0a14031bfc67d5345fc5d8960b8ed235f3c41d0942c8acfbe7dd3565802f","clean_bundle":{"format":"clean-skill-bundle-v1","source":"oldwinter/skills/devops-skills/sync-env/SKILL.md","attachments":[{"id":"9a21900e-eb9b-561e-a782-749dab9d7075","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9a21900e-eb9b-561e-a782-749dab9d7075/attachment.py","path":"scripts/compare_configs.py","size":15440,"sha256":"7f199f88a99a42c2a2cd2a94948e168546a831915faa57ecb58992332cd71c45","contentType":"text/x-python; charset=utf-8"},{"id":"087415e6-d10f-5f4a-b821-e8d02cf7ab30","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/087415e6-d10f-5f4a-b821-e8d02cf7ab30/attachment.py","path":"scripts/sync_images.py","size":14881,"sha256":"0ca81f577df141686ded1536155adb667f86819b82e73da556cc2d73effaf255","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"2933a4e3536b8b0b7211357b8a03a7aba193497e725bd6047cfbc8ccb2748c98","attachment_count":2,"text_attachments":2,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"devops-skills/sync-env/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"general","category_label":"General"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"general","import_tag":"clean-skills-v1","description":"Sync CI environment configurations to staging and optionally to production, with safety gates and whitelist-aware diffing."}},"renderedAt":1782979490040}

Sync Environment Skill 将 CI 环境的 Kubernetes kustomization 配置同步到 Staging,可选同步到 Production。 适用于 simplex-gitops 仓库。 Triggers : "sync ci to staging", "sync to staging", "sync to prod", "promote to production", "同步ci到staging", "同步到生产", "sync configs", "promote images" Promotion Path ⚠️ 部署策略 | 环境 | 策略 | 说明 | |------|------|------| | Staging | 可自动同步 | 推送后 ArgoCD 可自动检测并同步 | | Production | 必须手动 | 推送后需用户手动触发 ArgoCD sync | 永远不要自动执行 。 环境差异白名单(不同步) 以下配置项在各环境间预期不同,同步时 必须跳过 : | 白名单项 | CI | Staging | Prod | 原因 | |----------|-----|---------|------|------| | | CI RDS | Staging RDS | Prod RDS | 各环境独立数据库 | | | |…