From c330c23cefbe9b6245afc605c0cb4b2753daa28d Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Tue, 14 Apr 2026 20:22:06 +0800 Subject: [PATCH] chore(build): add build script --- .gitea/README.md | 109 ++++++++++++++++++++++++++++++ deploy/values.user.yaml.example | 42 ++++++++++++ scripts/README.md | 107 ++++++++++++++++++++++++++++++ scripts/build.js | 69 +++++++++++++++++++ scripts/deploy.js | 114 ++++++++++++++++++++++++++++++++ scripts/push.js | 70 ++++++++++++++++++++ 6 files changed, 511 insertions(+) create mode 100644 .gitea/README.md create mode 100644 deploy/values.user.yaml.example create mode 100644 scripts/README.md create mode 100644 scripts/build.js create mode 100644 scripts/deploy.js create mode 100644 scripts/push.js diff --git a/.gitea/README.md b/.gitea/README.md new file mode 100644 index 0000000..5b3253f --- /dev/null +++ b/.gitea/README.md @@ -0,0 +1,109 @@ +# Gitea Actions 配置 + +## 目录结构 + +``` +.gitea/ +└── workflows/ + └── build.yaml # 构建流水线 +``` + +## 快速开始 + +### 1. 启用 Gitea Actions + +在 Gitea 管理面板中启用 Actions: + +```ini +[actions] +ENABLED = true +``` + +### 2. 注册 Runner + +在 Gitea 仓库设置中创建 Runner,获取 Token 后配置: + +```bash +# Docker 部署 +docker run -d \ + --name act-runner \ + -e GITEA_INSTANCE_URL=https://git.example.com \ + -e GITEA_RUNNER_TOKEN= \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v act-runner-data:/data \ + gitea/act-runner:latest + +# 或使用 Helm (见下方) +``` + +### 3. 添加 Secrets + +在 Gitea 仓库设置中添加: + +| Secret | 说明 | +|--------|------| +| `HARBOR_USERNAME` | Harbor 用户名 | +| `HARBOR_PASSWORD` | Harbor 密码 | + +## Helm 部署 Act Runner + +```bash +# values override +cat > act-runner-values.yaml << 'EOF' +actRunner: + enabled: true + replicaCount: 2 + capacity: 2 + labels: + - gitea + - docker + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 2000m + memory: 4Gi +EOF + +helm upgrade --install act-runner ./deploy \ + -f act-runner-values.yaml \ + -n c-----code \ + --create-namespace +``` + +## Workflow 说明 + +### build.yaml + +| Stage | 说明 | +|-------|------| +| `ci` | 格式化检查、Clippy lint、单元测试 | +| `docker` | x86_64 镜像构建并推送到 Harbor | +| `docker-arm64` | ARM64 镜像构建 (需 ARM64 runner) | +| `manifest` | 多架构镜像清单合并 | + +### Runner 标签 + +| Label | 说明 | +|-------|------| +| `gitea` | 默认 runner | +| `docker` | 支持 Docker-in-Docker | +| `arm64` | ARM64 架构 runner | + +### 触发条件 + +- Push 到 `main` 分支 +- Pull Request 到 `main` 分支 + +## 本地测试 + +使用 [nektos/act](https://github.com/nektos/act) 本地运行: + +```bash +# 安装 +curl -sSL https://raw.githubusercontent.com/nektos/act/master/install.sh | sh + +# 运行 workflow +act -W .gitea/workflows/build.yaml +``` diff --git a/deploy/values.user.yaml.example b/deploy/values.user.yaml.example new file mode 100644 index 0000000..ca96b5d --- /dev/null +++ b/deploy/values.user.yaml.example @@ -0,0 +1,42 @@ +# User overrides for Helm deployment +# Copy to values.user.yaml and customize + +# PostgreSQL +database: + existingSecret: c-----code-secrets + secretKeys: + url: APP_DATABASE_URL + +# Redis +redis: + existingSecret: c-----code-secrets + secretKeys: + url: APP_REDIS_URL + +# NATS (if using hook pool) +nats: + enabled: false + url: nats://nats:4222 + +# Qdrant (if using AI embeddings) +qdrant: + enabled: false + url: http://qdrant:6333 + +# App configuration +app: + replicaCount: 3 + ingress: + enabled: true + hosts: + - host: git.example.com + +# Gitserver persistence +gitserver: + persistence: + size: 100Gi + storageClass: fast-ssd + +# Act Runner +actRunner: + enabled: false diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..ee25f29 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,107 @@ +# 构建脚本 + +## 一键构建脚本 + +### build.js - 构建镜像 + +```bash +# 构建所有镜像 +node scripts/build.js + +# 构建指定服务 +node scripts/build.js app gitserver + +# 指定 tag +TAG=v1.0.0 node scripts/build.js + +# 指定架构 +TARGET=aarch64-unknown-linux-gnu node scripts/build.js +``` + +**环境变量:** + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 | +| `TAG` | `latest` | 镜像标签 | +| `TARGET` | `x86_64-unknown-linux-gnu` | Rust 交叉编译目标 | + +--- + +### push.js - 推送镜像 + +```bash +# 推送所有镜像 +HARBOR_USERNAME=user HARBOR_PASSWORD=pass node scripts/push.js + +# 推送指定服务 +HARBOR_USERNAME=user HARBOR_PASSWORD=pass TAG=sha-abc123 node scripts/push.js app +``` + +**环境变量:** + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 | +| `TAG` | `latest` 或 Git SHA | 镜像标签 | +| `HARBOR_USERNAME` | - | **必填** 仓库用户名 | +| `HARBOR_PASSWORD` | - | **必填** 仓库密码 | + +--- + +### deploy.js - 部署到 Kubernetes + +```bash +# 部署最新镜像 +node scripts/deploy.js + +# 干跑模式(不实际部署) +node scripts/deploy.js --dry-run + +# 部署并运行数据库迁移 +node scripts/deploy.js --migrate + +# 指定 tag +TAG=sha-abc123 node scripts/deploy.js + +# 指定命名空间 +NAMESPACE=staging node scripts/deploy.js +``` + +**环境变量:** + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 | +| `TAG` | `latest` 或 Git SHA | 镜像标签 | +| `NAMESPACE` | `c-----code` | K8s 命名空间 | +| `RELEASE` | `c-----code` | Helm Release 名称 | +| `KUBECONFIG` | `~/.kube/config` | Kubeconfig 路径 | + +--- + +## 完整 CI/CD 流程 + +```bash +# 1. 构建 +node scripts/build.js + +# 2. 推送 +HARBOR_USERNAME=user HARBOR_PASSWORD=pass node scripts/push.js + +# 3. 部署 +node scripts/deploy.js --migrate +``` + +## 本地开发 + +```bash +# 本地构建测试 +node scripts/build.js app + +# 使用本地 tag +TAG=dev node scripts/build.js + +# 部署到测试环境 +NAMESPACE=test node scripts/deploy.js +``` diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..dc92899 --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,69 @@ +#!/usr/bin/env node +/** + * Build Docker images for C-----code + * + * Usage: + * node scripts/build.js # Build all images + * node scripts/build.js app # Build specific service + * node scripts/build.js app gitserver + * + * Environment: + * REGISTRY - Docker registry (default: harbor.gitdata.me/gta_team) + * TAG - Image tag (default: latest) + * TARGET - Rust build target (default: x86_64-unknown-linux-gnu) + */ + +const { execSync } = require('child_process'); +const path = require('path'); + +const REGISTRY = process.env.REGISTRY || 'harbor.gitdata.me/gta_team'; +const TAG = process.env.TAG || 'latest'; +const BUILD_TARGET = process.env.TARGET || 'x86_64-unknown-linux-gnu'; + +const SERVICES = ['app', 'gitserver', 'email-worker', 'git-hook', 'migrate', 'operator']; + +const args = process.argv.slice(2); +const targets = args.length > 0 ? args : SERVICES; + +console.log(`\n=== Build Configuration ===`); +console.log(`Registry: ${REGISTRY}`); +console.log(`Tag: ${TAG}`); +console.log(`Target: ${BUILD_TARGET}`); +console.log(`Services: ${targets.join(', ')}\n`); + +for (const service of targets) { + if (!SERVICES.includes(service)) { + console.error(`Unknown service: ${service}`); + console.error(`Available: ${SERVICES.join(', ')}`); + process.exit(1); + } + + const dockerfile = path.join(__dirname, '..', 'docker', `${service}.Dockerfile`); + const image = `${REGISTRY}/${service}:${TAG}`; + + console.log(`\n==> Building ${image}`); + console.log(` Dockerfile: ${dockerfile}`); + + try { + execSync( + `docker buildx build ` + + `--build-arg BUILD_TARGET=${BUILD_TARGET} ` + + `-f "${dockerfile}" ` + + `-t "${image}" ` + + `--load ` + + `--progress=plain ` + + `.`, + { stdio: 'inherit', cwd: path.join(__dirname, '..') } + ); + console.log(` [OK] ${image}`); + } catch (error) { + console.error(` [FAIL] ${service}`); + process.exit(1); + } +} + +console.log(`\n=== Build Complete ===`); +for (const service of targets) { + console.log(` ${REGISTRY}/${service}:${TAG}`); +} +console.log(''); diff --git a/scripts/deploy.js b/scripts/deploy.js new file mode 100644 index 0000000..baa5285 --- /dev/null +++ b/scripts/deploy.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node +/** + * Deploy C-----code via Helm + * + * Usage: + * node scripts/deploy.js # Deploy with defaults + * node scripts/deploy.js --dry-run # Dry run only + * node scripts/deploy.js --migrate # Run migrations first + * + * Environment: + * REGISTRY - Docker registry (default: harbor.gitdata.me/gta_team) + * TAG - Image tag (default: latest) + * NAMESPACE - Kubernetes namespace (default: c-----code) + * RELEASE - Helm release name (default: c-----code) + * KUBECONFIG - Path to kubeconfig (default: ~/.kube/config) + */ + +const { execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); + +const REGISTRY = process.env.REGISTRY || 'harbor.gitdata.me/gta_team'; +const TAG = process.env.TAG || process.env.GITHUB_SHA?.substring(0, 8) || 'latest'; +const NAMESPACE = process.env.NAMESPACE || 'c-----code'; +const RELEASE = process.env.RELEASE || 'c-----code'; +const CHART_PATH = path.join(__dirname, '..', 'deploy'); +const KUBECONFIG = process.env.KUBECONFIG || path.join(process.env.HOME || process.env.USERPROFILE, '.kube', 'config'); + +const args = process.argv.slice(2); +const isDryRun = args.includes('--dry-run'); +const runMigrate = args.includes('--migrate'); + +const SERVICES = ['app', 'gitserver', 'email-worker', 'git-hook', 'operator']; + +// Validate kubeconfig +if (!fs.existsSync(KUBECONFIG)) { + console.error(`Error: kubeconfig not found at ${KUBECONFIG}`); + console.error('Set KUBECONFIG environment variable or ensure ~/.kube/config exists'); + process.exit(1); +} + +console.log(`\n=== Deploy Configuration ===`); +console.log(`Registry: ${REGISTRY}`); +console.log(`Tag: ${TAG}`); +console.log(`Namespace: ${NAMESPACE}`); +console.log(`Release: ${RELEASE}`); +console.log(`Dry Run: ${isDryRun}`); +console.log(`Run Migrate: ${runMigrate}`); +console.log(''); + +// Build helm values override +const valuesFile = path.join(CHART_PATH, 'values.yaml'); +const userValuesFile = path.join(CHART_PATH, 'values.user.yaml'); + +const setValues = [ + `image.registry=${REGISTRY}`, + `app.image.tag=${TAG}`, + `gitserver.image.tag=${TAG}`, + `emailWorker.image.tag=${TAG}`, + `gitHook.image.tag=${TAG}`, + `operator.image.tag=${TAG}`, +]; + +if (runMigrate) { + setValues.push('migrate.enabled=true'); +} + +const helmArgs = [ + 'upgrade', '--install', RELEASE, CHART_PATH, + '--namespace', NAMESPACE, + '--create-namespace', + '-f', valuesFile, + ...(fs.existsSync(userValuesFile) ? ['-f', userValuesFile] : []), + ...setValues.flatMap(v => ['--set', v]), + '--wait', + '--timeout', '5m', +]; + +if (isDryRun) { + helmArgs.push('--dry-run'); + console.log('==> Dry run mode - no changes will be made\n'); +} + +// Helm upgrade +console.log(`==> Running helm upgrade`); +console.log(` Command: helm ${helmArgs.join(' ')}\n`); + +try { + execSync(`helm ${helmArgs.join(' ')}`, { stdio: 'inherit', env: { ...process.env, KUBECONFIG } }); + console.log(`\n[OK] Deployment ${isDryRun ? '(dry-run) ' : ''}complete`); +} catch (error) { + console.error('\n[FAIL] Deployment failed'); + process.exit(1); +} + +// Rollout status +if (!isDryRun) { + console.log('\n==> Checking rollout status'); + for (const service of SERVICES) { + const deploymentName = `${RELEASE}-${service}`; + console.log(` Checking ${deploymentName}...`); + try { + execSync( + `kubectl rollout status deployment/${deploymentName} -n ${NAMESPACE} --timeout=120s`, + { stdio: 'pipe', env: { ...process.env, KUBECONFIG } } + ); + console.log(` [OK] ${deploymentName}`); + } catch (error) { + console.error(` [WARN] ${deploymentName} rollout timeout or failed`); + } + } +} + +console.log('\n=== Deploy Complete ===\n'); diff --git a/scripts/push.js b/scripts/push.js new file mode 100644 index 0000000..aa72f19 --- /dev/null +++ b/scripts/push.js @@ -0,0 +1,70 @@ +#!/usr/bin/env node +/** + * Push Docker images to registry + * + * Usage: + * node scripts/push.js # Push all images + * node scripts/push.js app # Push specific service + * + * Environment: + * REGISTRY - Docker registry (default: harbor.gitdata.me/gta_team) + * TAG - Image tag (default: latest) + * DOCKER_USER - Registry username + * DOCKER_PASS - Registry password + */ + +const { execSync } = require('child_process'); + +const REGISTRY = process.env.REGISTRY || 'harbor.gitdata.me/gta_team'; +const TAG = process.env.TAG || process.env.GITHUB_SHA?.substring(0, 8) || 'latest'; +const DOCKER_USER = process.env.DOCKER_USER || process.env.HARBOR_USERNAME; +const DOCKER_PASS = process.env.DOCKER_PASS || process.env.HARBOR_PASSWORD; + +const SERVICES = ['app', 'gitserver', 'email-worker', 'git-hook', 'migrate', 'operator']; + +const args = process.argv.slice(2); +const targets = args.length > 0 ? args : SERVICES; + +if (!DOCKER_USER || !DOCKER_PASS) { + console.error('Error: DOCKER_USER and DOCKER_PASS environment variables are required'); + console.error('Set HARBOR_USERNAME and HARBOR_PASSWORD as alternative'); + process.exit(1); +} + +console.log(`\n=== Push Configuration ===`); +console.log(`Registry: ${REGISTRY}`); +console.log(`Tag: ${TAG}`); +console.log(`Services: ${targets.join(', ')}\n`); + +// Login +console.log(`==> Logging in to ${REGISTRY}`); +try { + execSync(`docker login ${REGISTRY} -u "${DOCKER_USER}" -p "${DOCKER_PASS}"`, { + stdio: 'inherit' + }); +} catch (error) { + console.error('Login failed'); + process.exit(1); +} + +for (const service of targets) { + if (!SERVICES.includes(service)) { + console.error(`Unknown service: ${service}`); + process.exit(1); + } + + const image = `${REGISTRY}/${service}:${TAG}`; + + console.log(`\n==> Pushing ${image}`); + + try { + execSync(`docker push "${image}" --all-tags`, { stdio: 'inherit' }); + console.log(` [OK] ${image}`); + } catch (error) { + console.error(` [FAIL] ${service}`); + process.exit(1); + } +} + +console.log(`\n=== Push Complete ===`); +console.log('');