chore(project): Initialize project basic configuration and deployment template
Some checks reported errors
continuous-integration/drone/push Build encountered an error

This commit is contained in:
ZhenYi 2026-04-14 23:39:42 +08:00
parent c330c23cef
commit 88f58a65c0
40 changed files with 2119 additions and 733 deletions

221
.drone.yml Normal file
View File

@ -0,0 +1,221 @@
---
# =============================================================================
# Drone CI Pipeline
# =============================================================================
kind: pipeline
type: kubernetes
name: default
trigger:
event:
- push
- tag
branch:
- main
environment:
REGISTRY: harbor.gitdata.me/gta_team
CARGO_TERM_COLOR: always
BUILD_TARGET: x86_64-unknown-linux-gnu
steps:
# =============================================================================
# Clone
# =============================================================================
- name: clone
image: bitnami/git:latest
commands:
- |
if [ -n "${DRONE_TAG}" ]; then
git checkout ${DRONE_TAG}
fi
# =============================================================================
# Stage 1: Rust fmt + clippy + test
# =============================================================================
- name: rust-fmt
image: rust:1.94
commands:
- cargo fmt --all -- --check
- name: rust-clippy
image: rust:1.94
commands:
- apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl-dev libclang-dev libgit2-dev zlib1g-dev
- rustup component add clippy
- cargo clippy --workspace --all-targets -- -D warnings
- name: rust-test
image: rust:1.94
commands:
- apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl-dev libclang-dev libgit2-dev zlib1g-dev
- cargo test --workspace --all-features
# =============================================================================
# Stage 2: Frontend lint + typecheck + build
# =============================================================================
- name: frontend-deps
image: node:22-alpine
commands:
- corepack enable
- corepack prepare pnpm@10 --activate
- pnpm install --frozen-lockfile
- name: frontend-lint
image: node:22-alpine
commands:
- pnpm lint
depends_on: [ frontend-deps ]
- name: frontend-typecheck
image: node:22-alpine
commands:
- pnpm tsc -b --noEmit
depends_on: [ frontend-lint ]
- name: frontend-build
image: node:22-alpine
commands:
- pnpm build
depends_on: [ frontend-typecheck ]
# =============================================================================
# Stage 3: Docker build & push
# =============================================================================
- name: docker-login
image: docker:latest
commands:
- docker login ${REGISTRY} --username ${DRONE_SECRET_DOCKER_USERNAME} --password-stdin <<< ${DRONE_SECRET_DOCKER_PASSWORD}
when:
status: [ success ]
- name: docker-build-and-push
image: docker:latest
environment:
DOCKER_BUILDKIT: "1"
commands:
- |
TAG="${DRONE_TAG:-${DRONE_COMMIT_SHA:0:8}}"
echo "==> Building images with tag: ${TAG}"
docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f docker/app.Dockerfile -t ${REGISTRY}/app:${TAG} .
docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f docker/gitserver.Dockerfile -t ${REGISTRY}/gitserver:${TAG} .
docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f docker/email-worker.Dockerfile -t ${REGISTRY}/email-worker:${TAG} .
docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f docker/git-hook.Dockerfile -t ${REGISTRY}/git-hook:${TAG} .
docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f docker/migrate.Dockerfile -t ${REGISTRY}/migrate:${TAG} .
docker build --build-arg BUILD_TARGET=${BUILD_TARGET} -f docker/operator.Dockerfile -t ${REGISTRY}/operator:${TAG} .
docker build -f docker/static.Dockerfile -t ${REGISTRY}/static:${TAG} .
docker build -f docker/frontend.Dockerfile -t ${REGISTRY}/frontend:${TAG} .
echo "==> Pushing images"
for svc in app gitserver email-worker git-hook migrate operator static frontend; do
docker push ${REGISTRY}/${svc}:${TAG}
done
echo "==> Tagging as latest"
for svc in app gitserver email-worker git-hook migrate operator static frontend; do
docker tag ${REGISTRY}/${svc}:${TAG} ${REGISTRY}/${svc}:latest
docker push ${REGISTRY}/${svc}:latest
done
echo "==> All images pushed"
depends_on: [ docker-login, frontend-build ]
settings:
privileged: true
when:
status: [ success ]
# =============================================================================
# Stage 4: Deploy to Kubernetes
# =============================================================================
- name: prepare-kubeconfig
image: alpine:latest
commands:
- apk add --no-cache kubectl
- mkdir -p ~/.kube
- echo "${KUBECONFIG}" | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
- name: create-namespace
image: bitnami/kubectl:latest
commands:
- kubectl create namespace gitdataai --dry-run=client -o yaml | kubectl apply -f -
depends_on: [ prepare-kubeconfig ]
when:
branch: [ main ]
- name: deploy-configmap
image: bitnami/kubectl:latest
commands:
- kubectl apply -f deploy/configmap.yaml
depends_on: [ create-namespace ]
when:
branch: [ main ]
- name: helm-deploy
image: alpine/helm:latest
commands:
- apk add --no-cache curl kubectl
- curl -fsSL -o /tmp/helm.tar.gz https://get.helm.sh/helm-v3.15.0-linux-amd64.tar.gz
- tar -xzf /tmp/helm.tar.gz -C /tmp
- mv /tmp/linux-amd64/helm /usr/local/bin/helm && chmod +x /usr/local/bin/helm
- |
TAG="${DRONE_TAG:-${DRONE_COMMIT_SHA:0:8}}"
helm upgrade --install gitdata deploy/ \
--namespace gitdataai \
-f deploy/values.yaml \
-f deploy/secrets.yaml \
--set image.registry=${REGISTRY} \
--set app.image.tag=${TAG} \
--set gitserver.image.tag=${TAG} \
--set emailWorker.image.tag=${TAG} \
--set gitHook.image.tag=${TAG} \
--set operator.image.tag=${TAG} \
--set static.image.tag=${TAG} \
--set frontend.image.tag=${TAG} \
--wait \
--timeout 5m \
--atomic
depends_on: [ deploy-configmap ]
when:
status: [ success ]
branch: [ main ]
- name: verify-rollout
image: bitnami/kubectl:latest
commands:
- kubectl rollout status deployment/gitdata-frontend -n gitdataai --timeout=300s
- kubectl rollout status deployment/gitdata-app -n gitdataai --timeout=300s
- kubectl rollout status deployment/gitdata-gitserver -n gitdataai --timeout=300s
- kubectl rollout status deployment/gitdata-email-worker -n gitdataai --timeout=300s
- kubectl rollout status deployment/gitdata-git-hook -n gitdataai --timeout=300s
depends_on: [ helm-deploy ]
when:
status: [ success ]
branch: [ main ]
# =============================================================================
# Secrets (register via drone CLI)
#
# # Harbor username for docker login
# drone secret add \
# --repository <org/repo> \
# --name drone_secret_docker_username \
# --data <harbor-username>
#
# # Harbor password for docker login
# drone secret add \
# --repository <org/repo> \
# --name drone_secret_docker_password \
# --data <harbor-password>
#
# # kubeconfig (base64 encoded)
# drone secret add \
# --repository <org/repo> \
# --name kubeconfig \
# --data "$(cat ~/.kube/config | base64 -w 0)"
#
# # Local exec (for testing):
# drone exec --trusted \
# --secret=DRONE_SECRET_DOCKER_USERNAME=<username> \
# --secret=DRONE_SECRET_DOCKER_PASSWORD=<password> \
# --secret=KUBECONFIG=$(base64 -w 0 ~/.kube/config)
# =============================================================================

View File

@ -1,109 +0,0 @@
# 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=<your-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
```

View File

@ -1,159 +0,0 @@
name: Build and Publish
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
REGISTRY: harbor.gitdata.me/gta_team
CARGO_TERM_COLOR: always
jobs:
# ---- Lint & Test ----
ci:
runs-on: gitea
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-action@stable
with:
toolchain: 1.94
- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Test
run: cargo test --workspace -- --test-threads=4
# ---- Docker Build (x86_64) ----
docker:
needs: ci
if: github.event_name == 'push'
runs-on: gitea
strategy:
matrix:
service:
- app
- gitserver
- email-worker
- git-hook
- migrate
- operator
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.gitdata.me
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ matrix.service }}
tags: |
type=sha,prefix=,format={{sha}}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: docker/${{ matrix.service }}.Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_TARGET=x86_64-unknown-linux-gnu
# ---- ARM64 Build ----
docker-arm64:
needs: ci
if: github.event_name == 'push'
runs-on: gitea-arm64
strategy:
matrix:
service:
- app
- gitserver
- email-worker
- git-hook
- migrate
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.gitdata.me
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: docker/${{ matrix.service }}.Dockerfile
platforms: linux/arm64
push: true
tags: |
${{ env.REGISTRY }}/${{ matrix.service }}:latest-arm64
${{ env.REGISTRY }}/${{ matrix.service }}:sha-${{ github.sha }}
build-args: |
BUILD_TARGET=aarch64-unknown-linux-gnu
# ---- Publish Manifest (multi-arch) ----
manifest:
needs: [docker, docker-arm64]
if: github.event_name == 'push'
runs-on: gitea
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.gitdata.me
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Create and push manifest
run: |
for service in app gitserver email-worker git-hook migrate; do
docker manifest create ${{ env.REGISTRY }}/$service:latest \
${{ env.REGISTRY }}/$service:latest \
${{ env.REGISTRY }}/$service:latest-arm64
docker manifest push ${{ env.REGISTRY }}/$service:latest
done

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ node_modules
.env
.env.local
dist
deploy/secrets.yaml
.codex
.qwen
.opencode

1
.idea/code.iml generated
View File

@ -16,6 +16,7 @@
<sourceFolder url="file://$MODULE_DIR$/apps/gitserver/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/apps/operator/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/libs/agent-tool-derive/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/apps/static/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />

170
Cargo.lock generated
View File

@ -75,6 +75,29 @@ dependencies = [
"smallvec",
]
[[package]]
name = "actix-files"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8c4f30e3272d7c345f88ae0aac3848507ef5ba871f9cc2a41c8085a0f0523b"
dependencies = [
"actix-http",
"actix-service",
"actix-utils",
"actix-web",
"bitflags",
"bytes",
"derive_more",
"futures-core",
"http-range",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"v_htmlescape",
]
[[package]]
name = "actix-http"
version = "3.12.0"
@ -2269,6 +2292,29 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "env_filter"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "equator"
version = "0.4.2"
@ -3239,6 +3285,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.10.1"
@ -3707,6 +3759,30 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jiff"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde_core",
]
[[package]]
name = "jiff-static"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "jobserver"
version = "0.1.34"
@ -4373,6 +4449,18 @@ dependencies = [
"unicase",
]
[[package]]
name = "mime_guess2"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca"
dependencies = [
"mime",
"phf",
"phf_shared",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -4997,6 +5085,50 @@ dependencies = [
"serde",
]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand 0.8.5",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.117",
"unicase",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
"unicase",
]
[[package]]
name = "pin-project"
version = "1.1.11"
@ -5153,6 +5285,15 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "portable-atomic-util"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3"
dependencies = [
"portable-atomic",
]
[[package]]
name = "potential_utf"
version = "0.1.4"
@ -6795,6 +6936,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slab"
version = "0.4.12"
@ -7144,6 +7291,23 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static-server"
version = "0.2.9"
dependencies = [
"actix-cors",
"actix-files",
"actix-web",
"anyhow",
"env_logger",
"mime",
"mime_guess2",
"serde",
"serde_json",
"slog",
"tokio",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -7909,6 +8073,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "v_htmlescape"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
[[package]]
name = "valuable"
version = "0.1.1"

View File

@ -23,6 +23,7 @@ members = [
"apps/gitserver",
"apps/email",
"apps/operator",
"apps/static",
]
resolver = "3"

22
apps/static/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "static-server"
version.workspace = true
edition.workspace = true
[dependencies]
actix-web = { workspace = true }
actix-files = { workspace = true }
actix-cors = { workspace = true }
tokio = { workspace = true, features = ["full"] }
serde = { workspace = true }
serde_json = { workspace = true }
mime = { workspace = true }
mime_guess2 = { workspace = true }
slog = { workspace = true }
anyhow = { workspace = true }
env_logger = { workspace = true }
[profile.release]
strip = true
lto = "thin"
opt-level = 3

110
apps/static/src/main.rs Normal file
View File

@ -0,0 +1,110 @@
use actix_cors::Cors;
use actix_files::Files;
use actix_web::{http::header, middleware::Logger, web, App, HttpResponse, HttpServer};
use std::path::PathBuf;
/// Static file server for avatar, blob, and other static files
/// Serves files from /data/{type} directories
#[derive(Clone)]
struct StaticConfig {
root: PathBuf,
cors_enabled: bool,
}
impl StaticConfig {
fn from_env() -> Self {
let root = std::env::var("STATIC_ROOT").unwrap_or_else(|_| "/data".to_string());
let cors = std::env::var("STATIC_CORS").unwrap_or_else(|_| "true".to_string());
Self {
root: PathBuf::from(root),
cors_enabled: cors == "true" || cors == "1",
}
}
fn ensure_dir(&self, name: &str) -> PathBuf {
let dir = self.root.join(name);
if !dir.exists() {
std::fs::create_dir_all(&dir).ok();
}
dir
}
}
async fn health() -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"status": "ok",
"service": "static-server"
}))
}
#[actix_web::main]
async fn main() -> anyhow::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let cfg = StaticConfig::from_env();
let bind = std::env::var("STATIC_BIND").unwrap_or_else(|_| "0.0.0.0:8081".to_string());
println!("Static file server starting...");
println!(" Root: {:?}", cfg.root);
println!(" Bind: {}", bind);
println!(" CORS: {}", if cfg.cors_enabled { "enabled" } else { "disabled" });
// Ensure all directories exist
for name in ["avatar", "blob", "media", "static"] {
let dir = cfg.ensure_dir(name);
println!(" {} dir: {:?}", name, dir);
}
let root = cfg.root.clone();
let cors_enabled = cfg.cors_enabled;
HttpServer::new(move || {
let root = root.clone();
let cors = if cors_enabled {
Cors::default()
.allow_any_origin()
.allowed_methods(vec!["GET", "HEAD", "OPTIONS"])
.allowed_headers(vec![
header::AUTHORIZATION,
header::ACCEPT,
header::CONTENT_TYPE,
])
.max_age(3600)
} else {
Cors::permissive()
};
App::new()
.wrap(cors)
.wrap(Logger::default())
.route("/health", web::get().to(health))
.service(
Files::new("/avatar", root.join("avatar"))
.prefer_utf8(true)
.index_file("index.html"),
)
.service(
Files::new("/blob", root.join("blob"))
.prefer_utf8(true)
.index_file("index.html"),
)
.service(
Files::new("/media", root.join("media"))
.prefer_utf8(true)
.index_file("index.html"),
)
.service(
Files::new("/static", root.join("static"))
.prefer_utf8(true)
.index_file("index.html"),
)
})
.bind(&bind)?
.run()
.await?;
Ok(())
}

View File

@ -1,5 +1,5 @@
apiVersion: v2
name: c-----code
name: gitdata
description: Self-hosted GitHub + Slack alternative platform
type: application
version: 0.1.0
@ -9,5 +9,6 @@ keywords:
- collaboration
- self-hosted
maintainers:
- name: C-----code Team
- name: gitdata Team
email: team@c.dev

66
deploy/configmap.yaml Normal file
View File

@ -0,0 +1,66 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: gitdata-config
namespace: gitdataai
labels:
app.kubernetes.io/name: gitdata
app.kubernetes.io/instance: gitdata
app.kubernetes.io/version: "0.1.0"
data:
# App Info
APP_NAME: "gitdata"
APP_VERSION: "0.1.0"
APP_STATIC_DOMAIN: "https://static.gitdata.ai"
APP_MEDIA_DOMAIN: "https://static.gitdata.ai"
APP_GIT_HTTP_DOMAIN: "https://git.gitdata.ai"
APP_AVATAR_PATH: "/data/avatar"
APP_REPOS_ROOT: "/data/repos"
APP_DATABASE_URL: "postgresql://gitdataai:gitdataai123@cnpg-cluster-rw.cnpg:5432/gitdataai?sslmode=disable"
APP_DATABASE_MAX_CONNECTIONS: "100"
APP_DATABASE_MIN_CONNECTIONS: "5"
APP_DATABASE_IDLE_TIMEOUT: "600"
APP_DATABASE_MAX_LIFETIME: "3600"
APP_DATABASE_CONNECTION_TIMEOUT: "30"
APP_DATABASE_SCHEMA_SEARCH_PATH: "public"
APP_DATABASE_HEALTH_CHECK_INTERVAL: "30"
APP_DATABASE_RETRY_ATTEMPTS: "3"
APP_DATABASE_RETRY_DELAY: "1"
APP_REDIS_URL: "redis://default:redis123@valkey-cluster.valkey-cluster.svc.cluster.local:6379"
APP_REDIS_POOL_SIZE: "16"
APP_REDIS_CONNECT_TIMEOUT: "5"
APP_REDIS_ACQUIRE_TIMEOUT: "1"
NATS_URL: "nats://nats-client.nats.svc.cluster.local:4222"
HOOK_POOL_MAX_CONCURRENT: "100"
HOOK_POOL_CPU_THRESHOLD: "80"
HOOK_POOL_REDIS_LIST_PREFIX: "{hook}"
HOOK_POOL_REDIS_LOG_CHANNEL: "hook:logs"
HOOK_POOL_REDIS_BLOCK_TIMEOUT: "5"
HOOK_POOL_REDIS_MAX_RETRIES: "3"
APP_LOG_LEVEL: "info"
APP_LOG_FORMAT: "json"
APP_LOG_FILE_ENABLED: "false"
APP_LOG_FILE_PATH: "/var/log/gitdata/app.log"
APP_LOG_FILE_ROTATION: "daily"
APP_LOG_FILE_MAX_FILES: "7"
APP_LOG_FILE_MAX_SIZE: "100"
APP_OTEL_ENABLED: "false"
APP_OTEL_ENDPOINT: ""
APP_OTEL_SERVICE_NAME: "gitdata"
APP_OTEL_SERVICE_VERSION: "0.1.0"
APP_SMTP_HOST: "smtp.exmail.qq.com"
APP_SMTP_PORT: "465"
APP_SMTP_USERNAME: "gitdata-bot@gitdata.ai"
APP_SMTP_PASSWORD: "Dha88YLtNicGUj4G"
APP_SMTP_FROM: "gitdata-bot@gitdata.ai"
APP_SMTP_TLS: "true"
APP_SMTP_TIMEOUT: "30"
APP_SSH_DOMAIN: "git.gitdata.ai"
APP_SSH_PORT: "22"
APP_AI_BASIC_URL: "https://axonhub.gitdata.me/v1"
APP_AI_API_KEY: "ah-629e2cfb5a58f6b7053cd890c6bd6c0de4537fa2f816ccc984090d022a50262e"
APP_EMBED_MODEL_BASE_URL: "https://api.siliconflow.cn/v1"
APP_EMBED_MODEL_API_KEY: "sk-xzehcnpedeijpgyiitcalzhlcdrmyujezubudvpacukyvzmo"
APP_EMBED_MODEL_NAME: "BAAI/bge-m3"
APP_EMBED_MODEL_DIMENSIONS: "1024"
APP_QDRANT_URL: "http://qdrant.qdrant.svc.cluster.local:6333"

View File

@ -0,0 +1,71 @@
# =============================================================================
# Secrets Configuration - 示例文件 (外部 Secret Manager)
# =============================================================================
# 生产环境使用 External Secrets Operator (ESO) 从 Vault/AWS SM/Azure KeyVault 同步
# https://external-secrets.io/
#
# 密钥管理器需要预先配置 SecretStore例如 Vault:
# apiVersion: external-secrets.io/v1beta1
# kind: SecretStore
# metadata:
# name: vault-backend
# namespace: gitdataai
# spec:
# vault:
# server: "https://vault.example.com"
# pathPrefix: /secret
# auth:
# kubernetes:
# mountPath: kubernetes
# role: gitdata
#
# 密钥路径约定:
# gitdata/database → { url: "postgresql://..." }
# gitdata/redis → { url: "redis://..." }
# gitdata/qdrant → { apiKey: "..." }
# =============================================================================
# -----------------------------------------------------------------------------
# External Secrets 配置
# -----------------------------------------------------------------------------
externalSecrets:
# SecretStore / ClusterSecretStore 名称 (集群预先配置)
storeName: "vault-backend"
storeKind: "SecretStore" # 或 ClusterSecretStore (跨 namespace)
# Vault 密钥路径
databaseKey: "gitdata/database"
redisKey: "gitdata/redis"
qdrantKey: "gitdata/qdrant"
# -----------------------------------------------------------------------------
# Secret 名称 (与 ExternalSecret target.name 对应)
# -----------------------------------------------------------------------------
database:
existingSecret: "gitdata-database-secret"
secretKeys:
url: APP_DATABASE_URL
redis:
existingSecret: "gitdata-redis-secret"
secretKeys:
url: APP_REDIS_URL
# -----------------------------------------------------------------------------
# Qdrant (启用 AI 功能时需要)
# -----------------------------------------------------------------------------
qdrant:
enabled: true
url: "http://qdrant.qdrant.svc.cluster.local:6333"
existingSecret: "gitdata-qdrant-secret"
secretKeys:
apiKey: APP_QDRANT_API_KEY
# -----------------------------------------------------------------------------
# 本地开发 / CI/CD 快速部署 (secrets.create: true)
# 生产环境请使用 externalSecrets 配置
# -----------------------------------------------------------------------------
# secrets:
# create: true
# databaseUrl: "postgresql://..."
# redisUrl: "redis://..."

View File

@ -26,7 +26,7 @@
Or set .Values.secrets in values.yaml.
🔄 To run database migrations:
helm upgrade {{ .Release.Name }} ./c-----code -n {{ .Release.Namespace }} \
helm upgrade {{ .Release.Name }} ./gitdata -n {{ .Release.Namespace }} \
--set migrate.enabled=true
📖 Useful commands:

View File

@ -2,29 +2,29 @@
Common helpers
============================================================================= */}}
{{- define "c-----code.fullname" -}}
{{- define "gitdata.fullname" -}}
{{- .Release.Name -}}
{{- end -}}
{{- define "c-----code.namespace" -}}
{{- define "gitdata.namespace" -}}
{{- .Values.namespace | default .Release.Namespace -}}
{{- end -}}
{{- define "c-----code.image" -}}
{{- define "gitdata.image" -}}
{{- $registry := .Values.image.registry -}}
{{- $pullPolicy := .Values.image.pullPolicy -}}
{{- printf "%s/%s:%s" $registry .image.repository .image.tag -}}
{{- end -}}
{{/* Inject image pull policy into sub-chart image dict */}}
{{- define "c-----code.mergeImage" -}}
{{- define "gitdata.mergeImage" -}}
{{- $merged := dict "pullPolicy" $.Values.image.pullPolicy -}}
{{- $merged = merge $merged .image -}}
{{- printf "%s/%s:%s" $.Values.image.registry $merged.repository $merged.tag -}}
{{- end -}}
{{/* Build a key-value env var list, optionally reading from a Secret */}}
{{- define "c-----code.envFromSecret" -}}
{{- define "gitdata.envFromSecret" -}}
{{- $secretName := .existingSecret -}}
{{- $keys := .secretKeys -}}
{{- $result := list -}}
@ -36,7 +36,7 @@
{{- end -}}
{{/* Merge two env lists (extra env over auto-injected) */}}
{{- define "c-----code.mergeEnv" -}}
{{- define "gitdata.mergeEnv" -}}
{{- $auto := .auto -}}
{{- $extra := .extra | default list -}}
{{- $merged := append $auto $extra | toJson | fromJson -}}

View File

@ -1,158 +0,0 @@
{{- if .Values.actRunner.enabled -}}
{{- $fullName := include "c-----code.fullname" . -}}
{{- $ns := include "c-----code.namespace" . -}}
{{- $runner := .Values.actRunner -}}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $fullName }}-act-runner
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-act-runner
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: {{ $runner.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $fullName }}-act-runner
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $fullName }}-act-runner
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: {{ $fullName }}-act-runner
containers:
- name: runner
image: "{{ .Values.image.registry }}/act-runner:{{ $runner.image.tag }}"
imagePullPolicy: {{ $runner.image.pullPolicy | default .Values.image.pullPolicy }}
args:
- --config
- /runner/config.yaml
- --replaces-self
env:
- name: CONFIG_FILE
value: /runner/config.yaml
{{- if .Values.nats.enabled }}
- name: HOOK_POOL_REDIS_LIST_PREFIX
value: "{hook}"
- name: HOOK_POOL_REDIS_LOG_CHANNEL
value: "hook:logs"
{{- end }}
{{- range $runner.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
volumeMounts:
- name: runner-config
mountPath: /runner
readOnly: true
- name: docker-socket
mountPath: /var/run/docker.sock
resources:
{{- toYaml $runner.resources | nindent 10 }}
volumes:
- name: runner-config
configMap:
name: {{ $fullName }}-act-runner-config
- name: docker-socket
hostPath:
path: /var/run/docker.sock
type: Socket
{{- with $runner.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $runner.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $runner.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ $fullName }}-act-runner-config
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-act-runner
app.kubernetes.io/instance: {{ .Release.Name }}
data:
config.yaml: |
# Act Runner Configuration
# Generated by Helm values
log:
level: {{ $runner.logLevel | default "info" }}
runner:
capacity: {{ $runner.capacity | default 2 }}
labels:
{{- range $runner.labels }}
- {{ . }}
{{- end }}
cache:
{{- if $runner.cache.enabled }}
enabled: true
dir: {{ $runner.cache.dir | default "/tmp/actions-cache" }}
{{- else }}
enabled: false
{{- end }}
docker:
host: unix:///var/run/docker.sock
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ $fullName }}-act-runner
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-act-runner
app.kubernetes.io/instance: {{ .Release.Name }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ $fullName }}-act-runner
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-act-runner
app.kubernetes.io/instance: {{ .Release.Name }}
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ $fullName }}-act-runner
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-act-runner
app.kubernetes.io/instance: {{ .Release.Name }}
subjects:
- kind: ServiceAccount
name: {{ $fullName }}-act-runner
namespace: {{ $ns }}
roleRef:
kind: Role
name: {{ $fullName }}-act-runner
apiGroup: rbac.authorization.k8s.io
{{- end }}

View File

@ -2,24 +2,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "c-----code.fullname" . }}-app
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-app
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-app
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: {{ .Values.app.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-app
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-app
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
image: "{{ .Values.image.registry }}/{{ .Values.app.image.repository }}:{{ .Values.app.image.tag }}"
@ -31,34 +32,52 @@ spec:
env:
- name: APP_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret | default (printf "%s-secrets" (include "c-----code.fullname" .)) }}
key: {{ .Values.database.secretKeys.url }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_DATABASE_URL
optional: true
- name: APP_REDIS_URL
valueFrom:
secretKeyRef:
name: {{ .Values.redis.existingSecret | default (printf "%s-secrets" (include "c-----code.fullname" .)) }}
key: {{ .Values.redis.secretKeys.url }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_REDIS_URL
optional: true
- name: NATS_URL
valueFrom:
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: NATS_URL
optional: true
{{- if .Values.nats.enabled }}
- name: HOOK_POOL_REDIS_LIST_PREFIX
value: "{hook}"
valueFrom:
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: HOOK_POOL_REDIS_LIST_PREFIX
optional: true
- name: HOOK_POOL_REDIS_LOG_CHANNEL
value: "hook:logs"
{{- end }}
{{- if .Values.qdrant.enabled }}
valueFrom:
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: HOOK_POOL_REDIS_LOG_CHANNEL
optional: true
- name: APP_QDRANT_URL
value: {{ .Values.qdrant.url }}
{{- if and .Values.qdrant.existingSecret .Values.qdrant.secretKeys.apiKey }}
valueFrom:
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_QDRANT_URL
optional: true
- name: APP_QDRANT_API_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.qdrant.existingSecret }}
key: {{ .Values.qdrant.secretKeys.apiKey }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_QDRANT_API_KEY
optional: true
- name: APP_AVATAR_PATH
valueFrom:
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_AVATAR_PATH
optional: true
{{- end }}
{{- end }}
{{- range .Values.app.env }}
- name: {{ .name }}
value: {{ .value | quote }}
@ -75,8 +94,28 @@ spec:
port: {{ .Values.app.readinessProbe.port }}
initialDelaySeconds: {{ .Values.app.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.app.readinessProbe.periodSeconds }}
{{- if .Values.app.startupProbe }}
startupProbe:
httpGet:
path: {{ .Values.app.startupProbe.path }}
port: {{ .Values.app.startupProbe.port }}
initialDelaySeconds: {{ .Values.app.startupProbe.initialDelaySeconds | default 0 }}
periodSeconds: {{ .Values.app.startupProbe.periodSeconds }}
failureThreshold: {{ .Values.app.startupProbe.failureThreshold }}
{{- end }}
resources:
{{- toYaml .Values.app.resources | nindent 10 }}
{{- if .Values.storage.enabled }}
volumeMounts:
- name: shared-data
mountPath: /data
{{- end }}
{{- if .Values.storage.enabled }}
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: {{ include "gitdata.fullname" . }}-shared-data
{{- end }}
{{- with .Values.app.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
@ -93,10 +132,10 @@ spec:
apiVersion: v1
kind: Service
metadata:
name: {{ include "c-----code.fullname" . }}-app
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-app
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-app
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: {{ .Values.app.service.type }}
@ -106,6 +145,6 @@ spec:
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-app
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@ -1,15 +1,51 @@
{{- /* Application configuration - non-sensitive values */ -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "c-----code.fullname" . }}-config
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-config
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
data:
{{- if .Values.app.config }}
{{- range $key, $value := .Values.app.config }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
APP_NAME: {{ .Values.app.name | default "gitdata" | quote }}
APP_VERSION: {{ .Chart.AppVersion | quote }}
APP_STATIC_DOMAIN: {{ .Values.config.staticDomain | default "" | quote }}
APP_MEDIA_DOMAIN: {{ .Values.config.mediaDomain | default "" | quote }}
APP_GIT_HTTP_DOMAIN: {{ .Values.config.gitHttpDomain | default "" | quote }}
APP_AVATAR_PATH: {{ .Values.config.avatarPath | default "/data/avatar" | quote }}
APP_REPOS_ROOT: {{ .Values.config.reposRoot | default "/data/repos" | quote }}
APP_LOG_LEVEL: {{ .Values.config.logLevel | default "info" | quote }}
APP_LOG_FORMAT: {{ .Values.config.logFormat | default "json" | quote }}
APP_LOG_FILE_ENABLED: {{ .Values.config.logFileEnabled | default "false" | quote }}
APP_LOG_FILE_PATH: {{ .Values.config.logFilePath | default "/var/log/gitdata/app.log" | quote }}
APP_LOG_FILE_ROTATION: {{ .Values.config.logFileRotation | default "daily" | quote }}
APP_LOG_FILE_MAX_FILES: {{ .Values.config.logFileMaxFiles | default "7" | quote }}
APP_LOG_FILE_MAX_SIZE: {{ .Values.config.logFileMaxSize | default "100" | quote }}
APP_OTEL_ENABLED: {{ .Values.config.otelEnabled | default "false" | quote }}
APP_OTEL_ENDPOINT: {{ .Values.config.otelEndpoint | default "" | quote }}
APP_OTEL_SERVICE_NAME: {{ .Values.config.otelServiceName | default "gitdata" | quote }}
APP_OTEL_SERVICE_VERSION: {{ .Chart.AppVersion | quote }}
APP_DATABASE_MAX_CONNECTIONS: {{ .Values.config.databaseMaxConnections | default "100" | quote }}
APP_DATABASE_MIN_CONNECTIONS: {{ .Values.config.databaseMinConnections | default "5" | quote }}
APP_DATABASE_IDLE_TIMEOUT: {{ .Values.config.databaseIdleTimeout | default "600" | quote }}
APP_DATABASE_MAX_LIFETIME: {{ .Values.config.databaseMaxLifetime | default "3600" | quote }}
APP_DATABASE_CONNECTION_TIMEOUT: {{ .Values.config.databaseConnectionTimeout | default "30" | quote }}
APP_DATABASE_SCHEMA_SEARCH_PATH: {{ .Values.config.databaseSchemaSearchPath | default "public" | quote }}
APP_DATABASE_HEALTH_CHECK_INTERVAL: {{ .Values.config.databaseHealthCheckInterval | default "30" | quote }}
APP_DATABASE_RETRY_ATTEMPTS: {{ .Values.config.databaseRetryAttempts | default "3" | quote }}
APP_DATABASE_RETRY_DELAY: {{ .Values.config.databaseRetryDelay | default "1" | quote }}
APP_REDIS_POOL_SIZE: {{ .Values.config.redisPoolSize | default "16" | quote }}
APP_REDIS_CONNECT_TIMEOUT: {{ .Values.config.redisConnectTimeout | default "5" | quote }}
APP_REDIS_ACQUIRE_TIMEOUT: {{ .Values.config.redisAcquireTimeout | default "1" | quote }}
HOOK_POOL_MAX_CONCURRENT: {{ .Values.config.hookPoolMaxConcurrent | default "100" | quote }}
HOOK_POOL_CPU_THRESHOLD: {{ .Values.config.hookPoolCpuThreshold | default "80" | quote }}
HOOK_POOL_REDIS_LIST_PREFIX: {{ .Values.config.hookPoolRedisListPrefix | default "{hook}" | quote }}
HOOK_POOL_REDIS_LOG_CHANNEL: {{ .Values.config.hookPoolRedisLogChannel | default "hook:logs" | quote }}
HOOK_POOL_REDIS_BLOCK_TIMEOUT: {{ .Values.config.hookPoolRedisBlockTimeout | default "5" | quote }}
HOOK_POOL_REDIS_MAX_RETRIES: {{ .Values.config.hookPoolRedisMaxRetries | default "3" | quote }}
APP_SMTP_PORT: {{ .Values.config.smtpPort | default "587" | quote }}
APP_SMTP_TLS: {{ .Values.config.smtpTls | default "true" | quote }}
APP_SMTP_TIMEOUT: {{ .Values.config.smtpTimeout | default "30" | quote }}
APP_SSH_PORT: {{ .Values.config.sshPort | default "22" | quote }}

View File

@ -2,22 +2,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "c-----code.fullname" . }}-email-worker
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-email-worker
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-email-worker
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-email-worker
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-email-worker
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-email-worker
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-email-worker
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-email-worker
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
@ -27,15 +27,15 @@ spec:
env:
- name: APP_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret | default (printf "%s-secrets" (include "c-----code.fullname" .)) }}
key: {{ .Values.database.secretKeys.url }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_DATABASE_URL
optional: true
- name: APP_REDIS_URL
valueFrom:
secretKeyRef:
name: {{ .Values.redis.existingSecret | default (printf "%s-secrets" (include "c-----code.fullname" .)) }}
key: {{ .Values.redis.secretKeys.url }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_REDIS_URL
optional: true
{{- range .Values.emailWorker.env }}
- name: {{ .name }}
@ -43,6 +43,30 @@ spec:
{{- end }}
resources:
{{- toYaml .Values.emailWorker.resources | nindent 10 }}
{{- if .Values.emailWorker.livenessProbe }}
livenessProbe:
exec:
command:
{{- range .Values.emailWorker.livenessProbe.exec.command }}
- {{ . | quote }}
{{- end }}
initialDelaySeconds: {{ .Values.emailWorker.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.emailWorker.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.emailWorker.livenessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.emailWorker.livenessProbe.failureThreshold }}
{{- end }}
{{- if .Values.emailWorker.readinessProbe }}
readinessProbe:
exec:
command:
{{- range .Values.emailWorker.readinessProbe.exec.command }}
- {{ . | quote }}
{{- end }}
initialDelaySeconds: {{ .Values.emailWorker.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.emailWorker.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.emailWorker.readinessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.emailWorker.readinessProbe.failureThreshold }}
{{- end }}
{{- with .Values.emailWorker.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
@ -55,4 +79,10 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.storage.enabled }}
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: {{ include "gitdata.fullname" . }}-shared-data
{{- end }}
{{- end }}

View File

@ -0,0 +1,76 @@
{{- /*
External Secrets - 从外部 Secret Manager 同步密钥
需要集群安装: External Secrets Operator (ESO)
https://external-secrets.io/
*/ -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- /* Database Secret */ -}}
{{- if .Values.database.existingSecret -}}
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ .Values.database.existingSecret }}
namespace: {{ $ns }}
spec:
refreshInterval: 1h
secretStoreRef:
name: {{ .Values.externalSecrets.storeName | default "vault-backend" }}
kind: {{ .Values.externalSecrets.storeKind | default "SecretStore" }}
target:
name: {{ .Values.database.existingSecret }}
creationPolicy: Owner
data:
- secretKey: {{ .Values.database.secretKeys.url }}
remoteRef:
key: {{ .Values.externalSecrets.databaseKey | default "gitdata/database" }}
property: url
{{- end }}
{{- /* Redis Secret */ -}}
{{- if .Values.redis.existingSecret -}}
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ .Values.redis.existingSecret }}
namespace: {{ $ns }}
spec:
refreshInterval: 1h
secretStoreRef:
name: {{ .Values.externalSecrets.storeName | default "vault-backend" }}
kind: {{ .Values.externalSecrets.storeKind | default "SecretStore" }}
target:
name: {{ .Values.redis.existingSecret }}
creationPolicy: Owner
data:
- secretKey: {{ .Values.redis.secretKeys.url }}
remoteRef:
key: {{ .Values.externalSecrets.redisKey | default "gitdata/redis" }}
property: url
{{- end }}
{{- /* Qdrant Secret */ -}}
{{- if and .Values.qdrant.enabled .Values.qdrant.existingSecret -}}
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ .Values.qdrant.existingSecret }}
namespace: {{ $ns }}
spec:
refreshInterval: 1h
secretStoreRef:
name: {{ .Values.externalSecrets.storeName | default "vault-backend" }}
kind: {{ .Values.externalSecrets.storeKind | default "SecretStore" }}
target:
name: {{ .Values.qdrant.existingSecret }}
creationPolicy: Owner
data:
- secretKey: {{ .Values.qdrant.secretKeys.apiKey }}
remoteRef:
key: {{ .Values.externalSecrets.qdrantKey | default "gitdata/qdrant" }}
property: apiKey
{{- end }}

View File

@ -0,0 +1,82 @@
{{- if .Values.frontend.enabled -}}
{{- $fullName := include "gitdata.fullname" . -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- $svc := .Values.frontend -}}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $fullName }}-frontend
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-frontend
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: {{ $svc.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $fullName }}-frontend
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $fullName }}-frontend
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: frontend
image: "{{ $.Values.image.registry }}/{{ $svc.image.repository }}:{{ $svc.image.tag }}"
imagePullPolicy: {{ $svc.image.pullPolicy | default $.Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
resources:
{{- toYaml $svc.resources | nindent 10 }}
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: {{ $svc.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ $svc.livenessProbe.periodSeconds }}
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: {{ $svc.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ $svc.readinessProbe.periodSeconds }}
{{- with $svc.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $svc.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $svc.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}-frontend
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-frontend
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: {{ $svc.service.type }}
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
selector:
app.kubernetes.io/name: {{ $fullName }}-frontend
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@ -0,0 +1,61 @@
{{- if .Values.frontend.ingress.enabled -}}
{{- $fullName := include "gitdata.fullname" . -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- $ing := .Values.frontend.ingress -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}-frontend
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-frontend
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
cert-manager.io/cluster-issuer: {{ $ing.clusterIssuer | default "cloudflare-acme-cluster-issuer" }}
{{- if $ing.annotations }}
{{ toYaml $ing.annotations | indent 4 }}
{{- end }}
{{- if not (hasKey ($ing.annotations | default dict) "nginx.ingress.kubernetes.io/proxy-body-size") }}
{{- if or (not $ing.className) (eq $ing.className "nginx") (contains "nginx" $ing.className) }}
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
{{- end }}
{{- end }}
spec:
{{- if $ing.className }}
ingressClassName: {{ $ing.className }}
{{- end }}
{{- if $ing.tls }}
tls:
{{- range $ing.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- else }}
tls:
{{- range $ing.hosts }}
- hosts:
- {{ .host | quote }}
secretName: {{ $fullName }}-frontend-tls
{{- end }}
{{- end }}
rules:
{{- range $ing.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $fullName }}-frontend
port:
number: 80
{{- end }}
{{- end }}
{{- end }}

View File

@ -2,22 +2,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "c-----code.fullname" . }}-git-hook
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-git-hook
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-git-hook
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-git-hook
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: {{ .Values.gitHook.replicaCount | default 2 }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-git-hook
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-git-hook
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-git-hook
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-git-hook
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
@ -27,28 +27,58 @@ spec:
env:
- name: APP_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret | default (printf "%s-secrets" (include "c-----code.fullname" .)) }}
key: {{ .Values.database.secretKeys.url }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_DATABASE_URL
optional: true
- name: APP_REDIS_URL
valueFrom:
secretKeyRef:
name: {{ .Values.redis.existingSecret | default (printf "%s-secrets" (include "c-----code.fullname" .)) }}
key: {{ .Values.redis.secretKeys.url }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_REDIS_URL
optional: true
{{- if .Values.nats.enabled }}
- name: HOOK_POOL_REDIS_LIST_PREFIX
value: "{hook}"
valueFrom:
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: HOOK_POOL_REDIS_LIST_PREFIX
optional: true
- name: HOOK_POOL_REDIS_LOG_CHANNEL
value: "hook:logs"
{{- end }}
valueFrom:
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: HOOK_POOL_REDIS_LOG_CHANNEL
optional: true
{{- range .Values.gitHook.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
resources:
{{- toYaml .Values.gitHook.resources | nindent 10 }}
{{- if .Values.gitHook.livenessProbe }}
livenessProbe:
exec:
command:
{{- range .Values.gitHook.livenessProbe.exec.command }}
- {{ . | quote }}
{{- end }}
initialDelaySeconds: {{ .Values.gitHook.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.gitHook.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.gitHook.livenessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.gitHook.livenessProbe.failureThreshold }}
{{- end }}
{{- if .Values.gitHook.readinessProbe }}
readinessProbe:
exec:
command:
{{- range .Values.gitHook.readinessProbe.exec.command }}
- {{ . | quote }}
{{- end }}
initialDelaySeconds: {{ .Values.gitHook.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.gitHook.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.gitHook.readinessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.gitHook.readinessProbe.failureThreshold }}
{{- end }}
{{- with .Values.gitHook.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
@ -61,4 +91,10 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.storage.enabled }}
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: {{ include "gitdata.fullname" . }}-shared-data
{{- end }}
{{- end }}

View File

@ -1,29 +1,9 @@
{{- if .Values.gitserver.enabled -}}
{{- $fullName := include "c-----code.fullname" . -}}
{{- $ns := include "c-----code.namespace" . -}}
{{- $fullName := include "gitdata.fullname" . -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- $svc := .Values.gitserver -}}
{{/* PersistentVolumeClaim for git repositories */}}
{{- if $svc.persistence.enabled }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ $fullName }}-repos
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-gitserver
app.kubernetes.io/instance: {{ $.Release.Name }}
spec:
accessModes:
- {{ $svc.persistence.accessMode | default "ReadWriteOnce" }}
resources:
requests:
storage: {{ $svc.persistence.size }}
{{- if $svc.persistence.storageClass }}
storageClassName: {{ $svc.persistence.storageClass }}
{{- end }}
{{- end }}
{{/* Uses shared PVC defined in storage.yaml */}}
---
apiVersion: apps/v1
@ -56,47 +36,70 @@ spec:
containerPort: {{ $svc.service.http.port }}
protocol: TCP
- name: ssh
containerPort: {{ $svc.ssh.port }}
containerPort: {{ $svc.service.ssh.port }}
protocol: TCP
env:
- name: APP_REPOS_ROOT
value: /data/repos
- name: APP_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ $.Values.database.existingSecret | default (printf "%s-secrets" $fullName) }}
key: {{ $.Values.database.secretKeys.url }}
configMapKeyRef:
name: {{ $fullName }}-config
key: APP_DATABASE_URL
optional: true
- name: APP_REDIS_URL
valueFrom:
secretKeyRef:
name: {{ $.Values.redis.existingSecret | default (printf "%s-secrets" $fullName) }}
key: {{ $.Values.redis.secretKeys.url }}
configMapKeyRef:
name: {{ $fullName }}-config
key: APP_REDIS_URL
optional: true
{{- if $svc.ssh.domain }}
- name: APP_SSH_DOMAIN
value: {{ $svc.ssh.domain }}
{{- end }}
{{- if $svc.ssh.port }}
valueFrom:
configMapKeyRef:
name: {{ $fullName }}-config
key: APP_SSH_DOMAIN
optional: true
- name: APP_SSH_PORT
value: {{ $svc.ssh.port | quote }}
{{- end }}
valueFrom:
configMapKeyRef:
name: {{ $fullName }}-config
key: APP_SSH_PORT
optional: true
{{- range $svc.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
resources:
{{- toYaml $svc.resources | nindent 10 }}
{{- if $svc.livenessProbe }}
livenessProbe:
tcpSocket:
port: {{ $svc.livenessProbe.tcpSocket.port }}
initialDelaySeconds: {{ $svc.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ $svc.livenessProbe.periodSeconds }}
timeoutSeconds: {{ $svc.livenessProbe.timeoutSeconds }}
failureThreshold: {{ $svc.livenessProbe.failureThreshold }}
{{- end }}
{{- if $svc.readinessProbe }}
readinessProbe:
tcpSocket:
port: {{ $svc.readinessProbe.tcpSocket.port }}
initialDelaySeconds: {{ $svc.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ $svc.readinessProbe.periodSeconds }}
timeoutSeconds: {{ $svc.readinessProbe.timeoutSeconds }}
failureThreshold: {{ $svc.readinessProbe.failureThreshold }}
{{- end }}
volumeMounts:
{{- if $svc.persistence.enabled }}
- name: repos
{{- if and $svc.persistence.enabled $.Values.storage.enabled }}
- name: shared-data
mountPath: /data/repos
subPath: repos/
{{- end }}
volumes:
{{- if $svc.persistence.enabled }}
- name: repos
{{- if and $svc.persistence.enabled $.Values.storage.enabled }}
- name: shared-data
persistentVolumeClaim:
claimName: {{ $fullName }}-repos
claimName: {{ $fullName }}-shared-data
{{- end }}
{{- with $svc.nodeSelector }}
nodeSelector:
@ -142,19 +145,40 @@ metadata:
labels:
app.kubernetes.io/name: {{ $fullName }}-gitserver
app.kubernetes.io/instance: {{ $.Release.Name }}
{{- if $svc.service.ssh.loadBalancerSourceRanges }}
annotations:
metallb.universe.tf/loadBalancerIPs: {{ $svc.service.ssh.loadBalancerIP | default "" }}
{{- end }}
spec:
type: {{ $svc.service.ssh.type }}
{{- if eq $svc.service.ssh.type "NodePort" }}
{{- if eq $svc.service.ssh.type "LoadBalancer" }}
ports:
- name: ssh
port: {{ $svc.ssh.port }}
port: {{ $svc.service.ssh.port }}
targetPort: ssh
nodePort: {{ $svc.service.ssh.nodePort }}
protocol: TCP
{{- if $svc.service.ssh.loadBalancerIP }}
loadBalancerIP: {{ $svc.service.ssh.loadBalancerIP }}
{{- end }}
{{- if $svc.service.ssh.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- range $svc.service.ssh.loadBalancerSourceRanges }}
- {{ . }}
{{- end }}
{{- end }}
{{- else if eq $svc.service.ssh.type "NodePort" }}
ports:
- name: ssh
port: {{ $svc.service.ssh.port }}
targetPort: ssh
nodePort: {{ $svc.service.ssh.nodePort | default 30222 }}
protocol: TCP
{{- else }}
ports:
- name: ssh
port: {{ $svc.ssh.port }}
port: {{ $svc.service.ssh.port }}
targetPort: ssh
protocol: TCP
{{- end }}
selector:
app.kubernetes.io/name: {{ $fullName }}-gitserver

View File

@ -0,0 +1,61 @@
{{- if .Values.gitserver.ingress.enabled -}}
{{- $fullName := include "gitdata.fullname" . -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- $ing := .Values.gitserver.ingress -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}-gitserver-http
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-gitserver
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
cert-manager.io/cluster-issuer: {{ $ing.clusterIssuer | default "cloudflare-acme-cluster-issuer" }}
{{- if $ing.annotations }}
{{ toYaml $ing.annotations | indent 4 }}
{{- end }}
{{- if not (hasKey ($ing.annotations | default dict) "nginx.ingress.kubernetes.io/proxy-body-size") }}
{{- if or (not $ing.className) (eq $ing.className "nginx") (contains "nginx" $ing.className) }}
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
{{- end }}
{{- end }}
spec:
{{- if $ing.className }}
ingressClassName: {{ $ing.className }}
{{- end }}
{{- if $ing.tls }}
tls:
{{- range $ing.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- else }}
tls:
{{- range $ing.hosts }}
- hosts:
- {{ .host | quote }}
secretName: {{ $fullName }}-gitserver-tls
{{- end }}
{{- end }}
rules:
{{- range $ing.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $fullName }}-gitserver-http
port:
number: {{ $.Values.gitserver.service.http.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -1,46 +1,61 @@
{{- if .Values.app.ingress.enabled -}}
{{- $svcName := printf "%s-app" (include "c-----code.fullname" .) -}}
{{- $ns := include "c-----code.namespace" . -}}
{{- $svcName := printf "%s-app" (include "gitdata.fullname" .) -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- $ing := .Values.app.ingress -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "c-----code.fullname" . }}-ingress
name: {{ include "gitdata.fullname" . }}-ingress
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-app
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with $ing.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if $ing.className }}
ingressClassName: {{ $ing.className }}
{{- end }}
{{- if $ing.tls }}
tls:
{{- range $ing.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range $ing.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $svcName }}
port:
number: {{ $.Values.app.service.port }}
{{- end }}
{{- end }}
cert-manager.io/cluster-issuer: {{ $ing.clusterIssuer | default "cloudflare-acme-cluster-issuer" }}
{{- if $ing.annotations }}
{{ toYaml $ing.annotations | indent 4 }}
{{- end }}
{{- if not (hasKey ($ing.annotations | default dict) "nginx.ingress.kubernetes.io/proxy-body-size") }}
{{- if or (not $ing.className) (eq $ing.className "nginx") (contains "nginx" $ing.className) }}
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
{{- end }}
{{- end }}
spec:
{{- if $ing.className }}
ingressClassName: {{ $ing.className }}
{{- end }}
{{- if $ing.tls }}
tls:
{{- range $ing.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- else }}
tls:
{{- range $ing.hosts }}
- hosts:
- {{ .host | quote }}
secretName: {{ include "gitdata.fullname" $ }}-app-tls
{{- end }}
{{- end }}
rules:
{{- range $ing.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $svcName }}
port:
number: {{ $.Values.app.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -2,10 +2,10 @@
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "c-----code.fullname" . }}-migrate
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-migrate
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-migrate
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-migrate
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/hook: post-install,post-upgrade
@ -15,7 +15,7 @@ spec:
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-migrate
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-migrate
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
restartPolicy: OnFailure
@ -32,9 +32,9 @@ spec:
env:
- name: APP_DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret | default (printf "%s-secrets" (include "c-----code.fullname" .)) }}
key: {{ .Values.database.secretKeys.url }}
configMapKeyRef:
name: {{ include "gitdata.fullname" . }}-config
key: APP_DATABASE_URL
{{- range .Values.migrate.env }}
- name: {{ .name }}
value: {{ .value | quote }}

View File

@ -0,0 +1,10 @@
{{- /* Unified namespace declaration */ -}}
apiVersion: v1
kind: Namespace
metadata:
name: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
helm.sh/resource-policy: keep

View File

@ -2,31 +2,53 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "c-----code.fullname" . }}-operator
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-operator
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-operator
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-operator
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-operator
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-operator
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-operator
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-operator
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: {{ include "c-----code.fullname" . }}-operator
serviceAccountName: {{ include "gitdata.fullname" . }}-operator
terminationGracePeriodSeconds: 10
volumes:
- name: tmp
emptyDir: {}
containers:
- name: operator
image: "{{ .Values.image.registry }}/{{ .Values.operator.image.repository }}:{{ .Values.operator.image.tag }}"
imagePullPolicy: {{ .Values.operator.image.pullPolicy | default .Values.image.pullPolicy }}
env:
- name: OPERATOR_IMAGE_PREFIX
value: {{ .Values.operator.imagePrefix | default (printf "%s/" (include "gitdata.fullname" .)) | quote }}
- name: OPERATOR_LOG_LEVEL
value: {{ .Values.operator.logLevel | default "info" | quote }}
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
resources:
{{- toYaml .Values.operator.resources | nindent 10 }}
volumeMounts:
- name: tmp
mountPath: /tmp
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
{{- with .Values.operator.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
@ -44,9 +66,51 @@ spec:
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "c-----code.fullname" . }}-operator
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-operator
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "c-----code.fullname" . }}-operator
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-operator
app.kubernetes.io/instance: {{ .Release.Name }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gitdata.fullname" . }}-operator
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-operator
app.kubernetes.io/instance: {{ .Release.Name }}
rules:
- apiGroups: ["code.dev"]
resources: ["apps", "gitservers", "emailworkers", "githooks", "migrates"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["code.dev"]
resources: ["apps/status", "gitservers/status", "emailworkers/status", "githooks/status", "migrates/status"]
verbs: ["get", "patch", "update"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services", "persistentvolumeclaims", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gitdata.fullname" . }}-operator
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-operator
app.kubernetes.io/instance: {{ .Release.Name }}
subjects:
- kind: ServiceAccount
name: {{ include "gitdata.fullname" . }}-operator
namespace: {{ include "gitdata.namespace" . }}
roleRef:
kind: Role
name: {{ include "gitdata.fullname" . }}-operator
apiGroup: rbac.authorization.k8s.io
{{- end }}

48
deploy/templates/pdb.yaml Normal file
View File

@ -0,0 +1,48 @@
{{- /* PodDisruptionBudgets for high-availability services */ -}}
{{- if and .Values.app.enabled .Values.app.pdb.enabled -}}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "gitdata.fullname" . }}-app
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
{{- if .Values.app.pdb.minAvailable }}
minAvailable: {{ .Values.app.pdb.minAvailable }}
{{- else if .Values.app.pdb.maxUnavailable }}
maxUnavailable: {{ .Values.app.pdb.maxUnavailable }}
{{- else }}
minAvailable: 1
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-app
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- if and .Values.gitHook.enabled .Values.gitHook.pdb.enabled -}}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "gitdata.fullname" . }}-git-hook
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-git-hook
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
{{- if .Values.gitHook.pdb.minAvailable }}
minAvailable: {{ .Values.gitHook.pdb.minAvailable }}
{{- else if .Values.gitHook.pdb.maxUnavailable }}
maxUnavailable: {{ .Values.gitHook.pdb.maxUnavailable }}
{{- else }}
minAvailable: 1
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}-git-hook
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@ -1,17 +1,63 @@
{{- /* Template for bootstrap secrets replace with external secret manager in prod */ -}}
{{- if .Values.secrets }}
{{- /*
Bootstrap secrets for development only.
In production, use an external secret manager (Vault, SealedSecrets, External Secrets).
*/ -}}
{{- /*
Bootstrap secrets for development only.
In production, use an external secret manager (Vault, SealedSecrets, External Secrets).
*/ -}}
{{- $secrets := .Values.secrets | default dict -}}
{{- if $secrets.create -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "c-----code.fullname" . }}-secrets
namespace: {{ include "c-----code.namespace" . }}
name: {{ include "gitdata.fullname" . }}-secrets
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
annotations:
"helm.sh/resource-policy": keep
type: Opaque
stringData:
{{- range $key, $value := .Values.secrets }}
{{- if $secrets.databaseUrl }}
APP_DATABASE_URL: {{ $secrets.databaseUrl | quote }}
{{- end }}
{{- if $secrets.redisUrl }}
APP_REDIS_URL: {{ $secrets.redisUrl | quote }}
{{- end }}
{{- if .Values.nats.enabled }}
NATS_URL: {{ .Values.nats.url | quote }}
{{- end }}
{{- if $secrets.aiApiKey }}
APP_AI_BASIC_URL: {{ $secrets.aiBasicUrl | quote }}
APP_AI_API_KEY: {{ $secrets.aiApiKey | quote }}
{{- end }}
{{- if $secrets.embedApiKey }}
APP_EMBED_MODEL_BASE_URL: {{ $secrets.embedBasicUrl | quote }}
APP_EMBED_MODEL_API_KEY: {{ $secrets.embedApiKey | quote }}
APP_EMBED_MODEL_NAME: {{ $secrets.embedModelName | quote }}
{{- end }}
{{- if and .Values.qdrant.enabled .Values.qdrant.url }}
APP_QDRANT_URL: {{ .Values.qdrant.url | quote }}
{{- end }}
{{- if .Values.qdrant.apiKey }}
APP_QDRANT_API_KEY: {{ .Values.qdrant.apiKey | quote }}
{{- end }}
{{- if $secrets.smtpHost }}
APP_SMTP_HOST: {{ $secrets.smtpHost | quote }}
APP_SMTP_PORT: {{ $secrets.smtpPort | default "587" | quote }}
APP_SMTP_USERNAME: {{ $secrets.smtpUsername | quote }}
APP_SMTP_PASSWORD: {{ $secrets.smtpPassword | quote }}
APP_SMTP_FROM: {{ $secrets.smtpFrom | default $secrets.smtpUsername | quote }}
APP_SMTP_TLS: {{ $secrets.smtpTls | default "true" | quote }}
{{- end }}
{{- if $secrets.sshDomain }}
APP_SSH_DOMAIN: {{ $secrets.sshDomain | quote }}
{{- end }}
{{- range $key, $value := $secrets.extra | default dict }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,112 @@
{{- if .Values.static.enabled -}}
{{- $fullName := include "gitdata.fullname" . -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- $svc := .Values.static -}}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $fullName }}-static
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-static
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
replicas: {{ $svc.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $fullName }}-static
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $fullName }}-static
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
containers:
- name: static
image: "{{ $.Values.image.registry }}/{{ $svc.image.repository }}:{{ $svc.image.tag }}"
imagePullPolicy: {{ $svc.image.pullPolicy | default $.Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ $svc.service.port }}
protocol: TCP
env:
- name: STATIC_ROOT
value: /data
- name: STATIC_BIND
value: {{ printf "0.0.0.0:%s" (print $svc.service.port) }}
- name: STATIC_CORS
value: {{ $svc.cors | default "true" | quote }}
{{- if $svc.logLevel }}
- name: STATIC_LOG_LEVEL
value: {{ $svc.logLevel }}
{{- end }}
{{- range $svc.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
resources:
{{- toYaml $svc.resources | nindent 10 }}
{{- if $svc.livenessProbe }}
livenessProbe:
httpGet:
path: /health
port: {{ $svc.service.port }}
initialDelaySeconds: {{ $svc.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ $svc.livenessProbe.periodSeconds }}
timeoutSeconds: {{ $svc.livenessProbe.timeoutSeconds }}
failureThreshold: {{ $svc.livenessProbe.failureThreshold }}
{{- end }}
{{- if $svc.readinessProbe }}
readinessProbe:
httpGet:
path: /health
port: {{ $svc.service.port }}
initialDelaySeconds: {{ $svc.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ $svc.readinessProbe.periodSeconds }}
timeoutSeconds: {{ $svc.readinessProbe.timeoutSeconds }}
failureThreshold: {{ $svc.readinessProbe.failureThreshold }}
{{- end }}
volumeMounts:
- name: shared-data
mountPath: /data
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: {{ $fullName }}-shared-data
{{- with $svc.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $svc.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $svc.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}-static
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-static
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
type: {{ $svc.service.type }}
ports:
- name: http
port: {{ $svc.service.port }}
targetPort: http
protocol: TCP
selector:
app.kubernetes.io/name: {{ $fullName }}-static
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@ -0,0 +1,61 @@
{{- if .Values.static.ingress.enabled -}}
{{- $fullName := include "gitdata.fullname" . -}}
{{- $ns := include "gitdata.namespace" . -}}
{{- $ing := .Values.static.ingress -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $fullName }}-static
namespace: {{ $ns }}
labels:
app.kubernetes.io/name: {{ $fullName }}-static
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
cert-manager.io/cluster-issuer: {{ $ing.clusterIssuer | default "cloudflare-acme-cluster-issuer" }}
{{- if $ing.annotations }}
{{ toYaml $ing.annotations | indent 4 }}
{{- end }}
{{- if not (hasKey ($ing.annotations | default dict) "nginx.ingress.kubernetes.io/proxy-body-size") }}
{{- if or (not $ing.className) (eq $ing.className "nginx") (contains "nginx" $ing.className) }}
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
{{- end }}
{{- end }}
spec:
{{- if $ing.className }}
ingressClassName: {{ $ing.className }}
{{- end }}
{{- if $ing.tls }}
tls:
{{- range $ing.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- else }}
tls:
{{- range $ing.hosts }}
- hosts:
- {{ .host | quote }}
secretName: {{ $fullName }}-static-tls
{{- end }}
{{- end }}
rules:
{{- range $ing.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $fullName }}-static
port:
number: {{ $.Values.static.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,18 @@
{{- /* Global shared PVC for all components */ -}}
{{- if .Values.storage.enabled -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "gitdata.fullname" . }}-shared-data
namespace: {{ include "gitdata.namespace" . }}
labels:
app.kubernetes.io/name: {{ include "gitdata.fullname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
accessModes:
- {{ .Values.storage.accessMode | default "ReadWriteMany" }}
storageClassName: {{ .Values.storage.storageClass }}
resources:
requests:
storage: {{ .Values.storage.size }}
{{- end }}

View File

@ -3,13 +3,13 @@
# PostgreSQL
database:
existingSecret: c-----code-secrets
existingSecret: gitdata-secrets
secretKeys:
url: APP_DATABASE_URL
# Redis
redis:
existingSecret: c-----code-secrets
existingSecret: gitdata-secrets
secretKeys:
url: APP_REDIS_URL
@ -31,12 +31,15 @@ app:
hosts:
- host: git.example.com
# Gitserver persistence
# Gitserver
gitserver:
persistence:
size: 100Gi
storageClass: fast-ssd
# Act Runner
actRunner:
enabled: false
ingress:
enabled: true
hosts:
- host: git-http.example.com
annotations:
# Override default proxy-body-size if needed
# nginx.ingress.kubernetes.io/proxy-body-size: "0" # 0 = unlimited

View File

@ -1,16 +1,104 @@
# =============================================================================
# Global / common settings
# =============================================================================
namespace: c-----code
releaseName: c-----code
namespace: gitdataai
releaseName: gitdata
image:
registry: harbor.gitdata.me/gta_team
pullPolicy: IfNotPresent
# PostgreSQL (required) set connection string via secret or values
# =============================================================================
# Cert-Manager Configuration (集群已安装 cert-manager)
# =============================================================================
certManager:
enabled: true
clusterIssuerName: cloudflare-acme-cluster-issuer # 引用集群已有的 ClusterIssuer
# =============================================================================
# External Secrets Configuration (需要集群安装 ESO)
# =============================================================================
externalSecrets:
storeName: "vault-backend"
storeKind: "SecretStore"
databaseKey: "gitdata/database"
redisKey: "gitdata/redis"
qdrantKey: "gitdata/qdrant"
# =============================================================================
# Shared persistent storage (aliyun-nfs)
# =============================================================================
storage:
enabled: true
storageClass: aliyun-nfs
size: 20Ti
accessMode: ReadWriteMany # NFS supports multiple readers/writers
# =============================================================================
# Application config (non-sensitive, shared via ConfigMap)
# =============================================================================
config:
# App info
name: gitdata
# Domain configuration
staticDomain: "https://static.gitdata.ai"
mediaDomain: ""
gitHttpDomain: "https://git.gitdata.ai"
# Storage paths
avatarPath: /data/avatar
reposRoot: /data/repos
# Logging
logLevel: info
logFormat: json
logFileEnabled: "false"
logFilePath: /var/log/gitdata/app.log
logFileRotation: daily
logFileMaxFiles: "7"
logFileMaxSize: "100"
# OpenTelemetry
otelEnabled: "false"
otelEndpoint: ""
otelServiceName: gitdata
# Database pool tuning
databaseMaxConnections: "100"
databaseMinConnections: "5"
databaseIdleTimeout: "600"
databaseMaxLifetime: "3600"
databaseConnectionTimeout: "30"
databaseSchemaSearchPath: public
databaseHealthCheckInterval: "30"
databaseRetryAttempts: "3"
databaseRetryDelay: "1"
# Redis tuning
redisPoolSize: "16"
redisConnectTimeout: "5"
redisAcquireTimeout: "1"
# Hook pool
hookPoolMaxConcurrent: "100"
hookPoolCpuThreshold: "80"
hookPoolRedisListPrefix: "{hook}"
hookPoolRedisLogChannel: hook:logs
hookPoolRedisBlockTimeout: "5"
hookPoolRedisMaxRetries: "3"
# SSH
sshPort: "22"
# SMTP (non-sensitive defaults)
smtpPort: "465"
smtpTls: "true"
smtpTimeout: "30"
# PostgreSQL (required)
database:
existingSecret: ""
existingSecret: "" # 留空则使用默认名 {release-name}-secrets
secretKeys:
url: APP_DATABASE_URL
@ -20,19 +108,64 @@ redis:
secretKeys:
url: APP_REDIS_URL
# NATS (optional required only if HOOK_POOL is enabled)
# NATS (optional)
nats:
enabled: false
url: nats://nats:4222
enabled: true
url: "nats://nats-client.nats.svc.cluster.local:4222"
# Qdrant (optional required only if AI embeddings are used)
# Qdrant (optional)
qdrant:
enabled: false
url: http://qdrant:6333
enabled: true
url: "http://qdrant.qdrant.svc.cluster.local:6333"
existingSecret: ""
secretKeys:
apiKey: APP_QDRANT_API_KEY
# =============================================================================
# Frontend - React SPA
# =============================================================================
frontend:
enabled: true
replicaCount: 2
image:
repository: frontend
tag: latest
service:
type: ClusterIP
ingress:
enabled: true
className: nginx
annotations: {}
hosts:
- host: gitdata.ai
paths:
- path: /
pathType: Prefix
tls: []
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
initialDelaySeconds: 5
periodSeconds: 5
nodeSelector: {}
tolerations: []
affinity: {}
# =============================================================================
# App main web/API service
# =============================================================================
@ -44,19 +177,26 @@ app:
repository: app
tag: latest
# Pod disruption budget
pdb:
enabled: true
minAvailable: 2 # Keep at least 2 pods available during disruptions
service:
type: ClusterIP
port: 8080
ingress:
enabled: false
className: cilium # Cilium Ingress (or envoy for EnvoyGateway)
enabled: true
className: nginx
annotations: {}
hosts:
- host: c-----.local
- host: gitdata.ai
paths:
- path: /
pathType: Prefix
- path: /api
pathType: Prefix
tls: []
resources:
@ -79,13 +219,76 @@ app:
initialDelaySeconds: 5
periodSeconds: 5
# Extra env vars (merge with auto-injected ones)
startupProbe:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 # Allow up to 5 minutes for slow starts
env: []
nodeSelector: {}
tolerations: []
affinity: {}
# =============================================================================
# Static server - avatar, blob, media files
# =============================================================================
static:
enabled: true
replicaCount: 2
image:
repository: static
tag: latest
service:
type: ClusterIP
port: 8081
ingress:
enabled: true
className: nginx
annotations: {}
hosts:
- host: static.gitdata.ai
paths:
- path: /
pathType: Prefix
cors: true
logLevel: info
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
path: /health
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
path: /health
port: 8081
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
env: []
nodeSelector: {}
tolerations: []
affinity: {}
# =============================================================================
# Gitserver git daemon / SSH + HTTP server
# =============================================================================
@ -102,8 +305,11 @@ gitserver:
type: ClusterIP
port: 8022
ssh:
type: NodePort
nodePort: 30222
type: LoadBalancer
port: 22
domain: ""
loadBalancerIP: ""
loadBalancerSourceRanges: []
resources:
requests:
@ -113,16 +319,38 @@ gitserver:
cpu: 500m
memory: 512Mi
# Storage for git repos
livenessProbe:
tcpSocket:
port: 8022
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 8022
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
persistence:
enabled: true
storageClass: ""
size: 50Gi
accessMode: ReadWriteOnce
ssh:
domain: ""
port: 22
ingress:
enabled: true
className: nginx
annotations: {}
hosts:
- host: git.gitdata.ai
paths:
- path: /
pathType: Prefix
tls: []
env: []
@ -140,6 +368,28 @@ emailWorker:
repository: email-worker
tag: latest
livenessProbe:
exec:
command:
- /bin/sh
- -c
- "pgrep email-worker || exit 1"
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command:
- /bin/sh
- -c
- "pgrep email-worker || exit 1"
initialDelaySeconds: 5
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 50m
@ -166,6 +416,32 @@ gitHook:
replicaCount: 2
pdb:
enabled: true
minAvailable: 1
livenessProbe:
exec:
command:
- /bin/sh
- -c
- "pgrep git-hook || exit 1"
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
exec:
command:
- /bin/sh
- -c
- "pgrep git-hook || exit 1"
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 50m
@ -196,15 +472,18 @@ migrate:
env: []
# =============================================================================
# Operator Kubernetes operator (manages custom App/GitServer CRDs)
# Operator Kubernetes operator
# =============================================================================
operator:
enabled: false # Enable only if running the custom operator
enabled: false
image:
repository: operator
tag: latest
imagePrefix: ""
logLevel: info
resources:
requests:
cpu: 50m
@ -216,47 +495,3 @@ operator:
nodeSelector: {}
tolerations: []
affinity: {}
# =============================================================================
# Act Runner Gitea Actions self-hosted runner
# =============================================================================
actRunner:
enabled: false
image:
repository: act-runner
tag: latest
replicaCount: 2
# Concurrency per runner instance
capacity: 2
# Runner labels (must match workflow `runs-on`)
labels:
- gitea
- docker
logLevel: info
cache:
enabled: true
dir: /tmp/actions-cache
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
env: []
nodeSelector: {}
tolerations:
- key: "runner"
operator: "Equal"
value: "true"
effect: "NoSchedule"
affinity: {}

View File

@ -0,0 +1,50 @@
# ---- Stage 1: Build ----
FROM node:22-alpine AS builder
WORKDIR /app
# Copy package files
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && corepack prepare pnpm@10 --activate
# Install dependencies
RUN pnpm install --frozen-lockfile
# Copy source
COPY . .
# Build
RUN pnpm build
# ---- Stage 2: Serve with nginx ----
FROM nginx:alpine AS runtime
# Copy built assets
COPY --from=builder /app/dist /usr/share/nginx/html
# nginx configuration for SPA
RUN echo 'server { \
listen 80; \
server_name _; \
root /usr/share/nginx/html; \
index index.html; \
location / { \
try_files $uri $uri/ /index.html; \
} \
location /api/ { \
proxy_pass http://app:8080/api/; \
proxy_set_header Host $host; \
proxy_set_header X-Real-IP $remote_addr; \
} \
location /ws/ { \
proxy_pass http://app:8080/ws/; \
proxy_http_version 1.1; \
proxy_set_header Upgrade $http_upgrade; \
proxy_set_header Connection "upgrade"; \
proxy_set_header Host $host; \
} \
}' > /etc/nginx/conf.d/default.conf
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]

41
docker/static.Dockerfile Normal file
View File

@ -0,0 +1,41 @@
# ---- Stage 1: Build ----
FROM rust:1.94-bookworm AS builder
ARG BUILD_TARGET=x86_64-unknown-linux-gnu
ENV TARGET=${BUILD_TARGET}
WORKDIR /build
# Copy workspace manifests
COPY Cargo.toml Cargo.lock ./
COPY libs/ libs/
COPY apps/static/ apps/static/
# Pre-build dependencies only
RUN cargo fetch
# Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=target \
cargo build --release --package static-server --target ${TARGET}
# ---- Stage 2: Runtime ----
FROM debian:bookworm-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /build/target/${TARGET}/release/static-server /app/static-server
ENV RUST_LOG=info
ENV STATIC_LOG_LEVEL=info
ENV STATIC_BIND=0.0.0.0:8081
ENV STATIC_ROOT=/data
ENV STATIC_CORS=true
EXPOSE 8081
ENTRYPOINT ["/app/static-server"]

View File

@ -20,11 +20,11 @@ TARGET=aarch64-unknown-linux-gnu node scripts/build.js
**环境变量:**
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 |
| `TAG` | `latest` | 镜像标签 |
| `TARGET` | `x86_64-unknown-linux-gnu` | Rust 交叉编译目标 |
| 变量 | 默认值 | 说明 |
|------------|------------------------------|-------------|
| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 |
| `TAG` | `latest` | 镜像标签 |
| `TARGET` | `x86_64-unknown-linux-gnu` | Rust 交叉编译目标 |
---
@ -40,12 +40,12 @@ HARBOR_USERNAME=user HARBOR_PASSWORD=pass TAG=sha-abc123 node scripts/push.js ap
**环境变量:**
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 |
| `TAG` | `latest` 或 Git SHA | 镜像标签 |
| `HARBOR_USERNAME` | - | **必填** 仓库用户名 |
| `HARBOR_PASSWORD` | - | **必填** 仓库密码 |
| 变量 | 默认值 | 说明 |
|-------------------|------------------------------|--------------|
| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 |
| `TAG` | `latest` 或 Git SHA | 镜像标签 |
| `HARBOR_USERNAME` | - | **必填** 仓库用户名 |
| `HARBOR_PASSWORD` | - | **必填** 仓库密码 |
---
@ -70,13 +70,13 @@ 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 路径 |
| 变量 | 默认值 | 说明 |
|--------------|------------------------------|-----------------|
| `REGISTRY` | `harbor.gitdata.me/gta_team` | 镜像仓库 |
| `TAG` | `latest` 或 Git SHA | 镜像标签 |
| `NAMESPACE` | `gitdata` | K8s 命名空间 |
| `RELEASE` | `gitdata` | Helm Release 名称 |
| `KUBECONFIG` | `~/.kube/config` | Kubeconfig 路径 |
---

View File

@ -20,7 +20,7 @@ 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 SERVICES = ['app', 'gitserver', 'email-worker', 'git-hook', 'migrate', 'operator', 'static', 'frontend'];
const args = process.argv.slice(2);
const targets = args.length > 0 ? args : SERVICES;

View File

@ -10,19 +10,19 @@
* 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)
* NAMESPACE - Kubernetes namespace (default: gitdata)
* RELEASE - Helm release name (default: gitdata)
* KUBECONFIG - Path to kubeconfig (default: ~/.kube/config)
*/
const { execSync } = require('child_process');
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 NAMESPACE = process.env.NAMESPACE || 'gitdata';
const RELEASE = process.env.RELEASE || 'gitdata';
const CHART_PATH = path.join(__dirname, '..', 'deploy');
const KUBECONFIG = process.env.KUBECONFIG || path.join(process.env.HOME || process.env.USERPROFILE, '.kube', 'config');
@ -34,9 +34,9 @@ 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.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 ===`);
@ -53,32 +53,32 @@ 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}`,
`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');
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',
'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');
helmArgs.push('--dry-run');
console.log('==> Dry run mode - no changes will be made\n');
}
// Helm upgrade
@ -86,29 +86,29 @@ 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`);
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);
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==> 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');