Compare commits

..

98 Commits

Author SHA1 Message Date
Haitao Pan
1819905253 chore(charts): ignore packaged app chart archives 2026-04-04 08:12:02 +08:00
Haitao Pan
54edf1d219 chore(charts): remove k3s platform chart 2026-04-04 08:09:29 +08:00
Haitao Pan
eb7f01e0ac fix(platform): use templated postgresql ingress domain 2026-04-04 08:04:34 +08:00
Haitao Pan
f14bcd8c4d fix(platform): unify postgresql tls naming 2026-04-04 08:02:35 +08:00
Haitao Pan
60d95acfd9 fix(platform): rename shared tls secret to postgresql-tls 2026-04-04 07:49:34 +08:00
Haitao Pan
fd4069e5cd fix(platform): bump chart version after tls sync changes 2026-04-04 07:30:27 +08:00
Haitao Pan
8de8726693 fix(platform): render postgresql tls sync resources 2026-04-04 07:20:40 +08:00
Haitao Pan
094593efb9 fix(observability): temporarily disable failing components 2026-04-02 20:38:23 +08:00
Haitao Pan
61c5ba3146 fix(observability): stabilize stack defaults 2026-04-02 20:37:55 +08:00
Haitao Pan
7e4b1061d9 feat(charts): add service wrapper charts 2026-04-02 17:59:59 +08:00
Haitao Pan
6cbca2e23b fix(charts): publish external-dns in platform stack 2026-04-02 16:36:03 +08:00
Haitao Pan
c3eb670c3c fix(charts): run apisix standalone as data plane 2026-04-02 16:14:18 +08:00
Haitao Pan
7765759e3b fix(charts): clean apisix standalone config 2026-04-02 16:10:47 +08:00
Haitao Pan
45f2945dde fix(charts): run apisix in standalone yaml mode 2026-04-02 16:04:34 +08:00
Haitao Pan
17186b6222 fix(charts): use default ghcr oci paths 2026-04-02 15:08:58 +08:00
Haitao Pan
446dd16c03 feat(charts): add OCI chart publishing and runtime charts 2026-04-02 15:01:47 +08:00
shenlan
2183d475a6
Merge pull request #179 from svc-design/codex/fix-packer-config-file-error 2025-11-21 16:49:41 +08:00
shenlan
a7b97d3130 Fix packer validate path for golden image workflow 2025-11-21 16:49:09 +08:00
Haitao Pan
befefc83f0 chore(workflow): update actionlint to raven-actions/actionlint@v2 2025-11-21 16:22:33 +08:00
Haitao Pan
fd31184f53 fix(workflow): correct actionlint action source 2025-11-21 16:15:34 +08:00
Haitao Pan
8f8f83da6d fix(workflow): resolve actionlint errors and correct outputs chain 2025-11-21 16:12:46 +08:00
Haitao Pan
6bce9d16a1 fix(workflow): clean actionlint errors (matrix context, SC2086, missing outputs) 2025-11-21 16:08:59 +08:00
shenlan
1655304e70
Merge pull request #178 from svc-design/codex/fix-yaml-parsing-issue-in-workflow
Fix workflow if expression formatting
2025-11-21 16:06:02 +08:00
shenlan
19bbff1052 Fix workflow if expression formatting 2025-11-21 16:05:49 +08:00
shenlan
fb1744cba0
Merge pull request #177 from svc-design/codex/add-actionlint-step-to-workflow
Add actionlint check to lint stage
2025-11-21 16:05:24 +08:00
shenlan
65cdef366a Add actionlint check to lint stage 2025-11-21 16:05:11 +08:00
shenlan
9178f9a2c8
Merge pull request #176 from svc-design/codex/fix-syntax-errors-in-workflow-file
Fix workflow matrix condition syntax
2025-11-21 15:51:01 +08:00
shenlan
a151cb8496 Fix matrix condition expression in workflow 2025-11-21 15:50:50 +08:00
shenlan
4c44439825
Merge pull request #175 from svc-design/codex/complete-ami-replicate.sh-based-on-workflow
Add AMI replication helper script
2025-11-21 15:44:55 +08:00
shenlan
248219298f
Merge pull request #174 from svc-design/codex/fix-yaml-syntax-error-on-line-149
Fix YAML syntax in cloud-neutra workflow
2025-11-21 15:44:41 +08:00
shenlan
3da8cf8a29 Add AMI replication helper script 2025-11-21 15:44:28 +08:00
shenlan
e2ed57e974 Fix YAML syntax in cloud-neutra workflow 2025-11-21 15:44:13 +08:00
Haitao Pan
14748088a1 feat: add Cloud-Neutra Golden Image pipeline and Packer template suite
- Added docs for environment setup and template usage
- Added Ubuntu 22.04 AWS builder template and edition templates
- Added flavor scripts: container, k3s, k3s-gpu, sealos, sealos-gpu
- Added base OS scripts (01_os_base.sh, 02_hardening.sh) and cleanup pipeline
- Added GitHub Actions workflow for AMI build, QA, and multi-region distribution
2025-11-21 15:26:43 +08:00
shenlan
03b7ba02fc
Merge pull request #173 from svc-design/codex/add-dockerfiles-and-helm-chart-for-models-qc3pt7
Add CUDA LLM images and multi-model Helm chart
2025-10-27 16:29:37 +08:00
shenlan
6d4de96a7d Add CUDA LLM base images and multi-model Helm chart 2025-10-27 16:29:19 +08:00
shenlan
f7fba192b1
Update offline-package-argocd-installer.yaml
update remote dir /data/update-server/offline-package/argocd

Signed-off-by: shenlan <manbuzhe2009@qq.com>
2025-10-15 12:58:03 +08:00
shenlan
86e8fadf75
Merge pull request #172 from svc-design/codex/provide-offline-packages-for-dify,-n8n,-ragflow,-flowise,-au
Add workflows to build offline packages for new AI apps
2025-10-11 19:02:14 +08:00
shenlan
3b1b4c234b Add offline package builders for new AI tooling 2025-10-11 19:01:58 +08:00
shenlan
5fbad3f82d
Merge pull request #170 from svc-design/codex/replace-long-run-sections-with-external-script-vp0eli
Refactor offline terraform workflow to use external scripts
2025-10-02 21:03:08 +08:00
shenlan
36fbbc9aaf
Merge branch 'main' into codex/replace-long-run-sections-with-external-script-vp0eli
Signed-off-by: shenlan <manbuzhe2009@qq.com>
2025-10-02 21:02:58 +08:00
shenlan
68967496c6
Merge pull request #171 from svc-design/codex/replace-long-run-sections-with-external-script-1i2ey1
Refactor Pulumi offline installer workflow scripts
2025-10-02 21:01:29 +08:00
shenlan
6b48d60354
Merge branch 'main' into codex/replace-long-run-sections-with-external-script-1i2ey1
Signed-off-by: shenlan <manbuzhe2009@qq.com>
2025-10-02 21:01:20 +08:00
shenlan
4b27cded56 Refactor pulumi installer workflow scripts 2025-10-02 20:57:32 +08:00
shenlan
909ec6b79b Refactor terraform installer workflow scripts 2025-10-02 20:57:19 +08:00
shenlan
8b500b8c9b
Merge pull request #169 from svc-design/codex/replace-long-run-sections-with-external-script
Refactor offline FluxCD workflow to use helper scripts
2025-10-02 20:51:49 +08:00
shenlan
a85263830b Refactor fluxcd offline workflow scripts 2025-10-02 20:49:18 +08:00
shenlan
07727ed086
Merge pull request #167 from svc-design/codex/replace-long-run-sections-with-external-script
Refactor workflow scripts
2025-10-02 20:48:52 +08:00
shenlan
95b2ea8362
Merge pull request #168 from svc-design/codex/replace-long-run-sections-with-external-script-6r98n0
Refactor workflow runs to external scripts
2025-10-02 20:48:14 +08:00
shenlan
6153128c5c
Merge pull request #166 from svc-design/codex/refactor-yml-to-use-external-scripts
Refactor offline GitLab workflow to externalize scripts
2025-10-02 20:47:33 +08:00
shenlan
8c17aeed82 Refactor workflow runs to external scripts 2025-10-02 20:47:32 +08:00
shenlan
167404f709 Refactor workflow scripts 2025-10-02 20:47:18 +08:00
shenlan
e2c50547b2 Refactor GitLab offline workflow scripts 2025-10-02 20:44:59 +08:00
shenlan
702087349c
Merge pull request #165 from svc-design/codex/verify-offline-package-installation-script
Add root installer script entrypoint for Gitea offline package
2025-10-02 20:39:12 +08:00
shenlan
5332ae70bc
Merge branch 'main' into codex/verify-offline-package-installation-script
Signed-off-by: shenlan <manbuzhe2009@qq.com>
2025-10-02 20:38:51 +08:00
shenlan
78edeaa474 Add top-level installer entrypoint for Gitea offline package 2025-10-02 20:37:48 +08:00
shenlan
ea29271b18
Merge pull request #163 from svc-design/codex/create-offline-package-verification
Align offline Gitea installer layout with usage instructions
2025-10-02 20:35:39 +08:00
shenlan
be28eba9e8
Merge pull request #164 from svc-design/codex/create-offline-package-for-argocd-installer
Align Argo CD offline package naming
2025-10-02 20:35:24 +08:00
shenlan
be07c921b8 Align Argo CD offline package naming 2025-10-02 20:29:34 +08:00
shenlan
96499a6325 Align offline Gitea installer layout 2025-10-02 20:25:12 +08:00
shenlan
f86f17eb4f
Merge pull request #162 from svc-design/codex/adapt-gitlab-setup-for-offline-installation
Add GitLab single-node offline installation tooling
2025-10-02 20:24:43 +08:00
shenlan
356ebc392e Add GitLab single-node offline installer 2025-10-02 20:22:32 +08:00
shenlan
6cc99943d8
Merge pull request #161 from svc-design/codex/create-workflow-for-offline-pulumi-installer
Align Pulumi offline package archive names
2025-10-02 20:19:54 +08:00
shenlan
b4f88166f9
Merge pull request #160 from svc-design/codex/match-offline-gitea-installer-packages
Align offline Gitea package naming with installer instructions
2025-10-02 20:19:34 +08:00
shenlan
916b480d3a Align Pulumi offline package archive names 2025-10-02 20:18:48 +08:00
shenlan
8381076e1a Align offline package naming and structure 2025-10-02 20:18:27 +08:00
shenlan
8c833b8ba7
Merge pull request #159 from svc-design/codex/create-offline-package-installers-e7rsw9
Add offline package workflow for FluxCD installer
2025-10-02 20:17:47 +08:00
shenlan
72b692e546 Add offline FluxCD installer workflow 2025-10-02 20:17:28 +08:00
shenlan
c9449f16eb
Merge pull request #158 from svc-design/codex/create-offline-package-installers
Add offline package workflows for Terraform, Pulumi, GitLab, Gitea, and Argo CD
2025-10-02 20:04:52 +08:00
shenlan
2d0458ab6f Add offline package workflows for Terraform, Pulumi, and Git tools 2025-10-02 20:02:42 +08:00
shenlan
890a93856a
Merge pull request #157 from svc-design/codex/create-sync-vultr-cli-workflow-file
Add GitHub Actions workflow to sync vultr-cli releases
2025-09-30 12:08:04 +08:00
shenlan
441e549678 Add workflow to sync vultr-cli releases 2025-09-30 12:00:01 +08:00
shenlan
b95025d2b5
Merge pull request #156 from svc-design/codex/update-pipeline-env-matrix-for-vps_host
Run sync jobs for both homepage VPS hosts
2025-09-29 12:24:36 +08:00
shenlan
f2dd39bcf2 Run sync workflows for both homepage VPS hosts 2025-09-29 12:24:20 +08:00
shenlan
fb36e17706
Merge pull request #155 from svc-design/codex/refactor-release-tag-resolution-script
Refactor release resolution steps to reusable script
2025-09-29 12:05:23 +08:00
shenlan
0f33753e69 Refactor release resolution into reusable script 2025-09-29 12:04:26 +08:00
shenlan
451302ef2b
Merge pull request #154 from svc-design/fix/sync-blackbox-exporter
chore(ci): remove unused blackbox-exporter assets (armv6/armv7/windows)
2025-09-29 11:42:41 +08:00
Haitao Pan
eb7bcf0be0 chore(ci): remove unused blackbox-exporter assets (armv6/armv7/windows) 2025-09-29 11:41:11 +08:00
shenlan
1dc8dbd9b2
Merge pull request #153 from svc-design/codex/create-sync-blackbox-exporter.yml-workflow
Add GitHub Actions workflow for syncing blackbox_exporter releases
2025-09-29 11:38:36 +08:00
shenlan
0602e036c1 Add workflow to sync blackbox exporter releases 2025-09-29 11:37:04 +08:00
shenlan
50a463f3f3
Merge pull request #152 from svc-design/codex/fix-apisix-gateway-deployment-errors
fix: expand ingress IP and guard namespace in APISIX deploy script
2025-09-14 20:46:20 +08:00
shenlan
d95b94e2cf fix: expand ingress IP and guard namespace 2025-09-14 20:41:59 +08:00
shenlan
50d9f1f1dd
Update deploy-apisix-gateway.sh
Signed-off-by: shenlan <manbuzhe2009@qq.com>
2025-09-14 20:32:55 +08:00
shenlan
b4c13aca30
Merge pull request #151 from svc-design/codex/fix-invalid-reference-format-in-workflow
fix: sanitize images extraction in APISIX offline workflow
2025-09-14 20:31:15 +08:00
shenlan
df31ec3aee fix: sanitize images extraction 2025-09-14 20:20:49 +08:00
shenlan
79afce267f
Merge pull request #150 from svc-design/svc-design-patch-5
Update deploy-apisix-gateway.sh
2025-09-14 20:09:47 +08:00
shenlan
394696a76f
Update deploy-apisix-gateway.sh
Signed-off-by: shenlan <manbuzhe2009@qq.com>
2025-09-14 20:08:23 +08:00
shenlan
0f996e0f01
Merge pull request #149 from svc-design/svc-design-patch-4
Update deploy-kong-gateway.sh
2025-09-14 20:06:48 +08:00
shenlan
ad2f702975
Update deploy-kong-gateway.sh
Signed-off-by: shenlan <manbuzhe2009@qq.com>
2025-09-14 20:06:28 +08:00
shenlan
69d210c2a4
Merge pull request #146 from svc-design/codex/fix-offline-setup-for-apisix-gateway
feat: add apisix offline package workflow
2025-09-14 20:04:57 +08:00
shenlan
0d3e307926
Merge pull request #148 from svc-design/codex/refactor-deploy-kong-gateway-script
refactor kong gateway scripts
2025-09-14 19:54:25 +08:00
shenlan
fc1402dc7e
Merge pull request #147 from svc-design/codex/fix-offline-setup-for-apisix-gateway-s1v6fh
feat: add apisix offline package workflow
2025-09-14 19:54:11 +08:00
shenlan
ee8e92d878 refactor kong gateway scripts 2025-09-14 19:51:51 +08:00
shenlan
484a486a5f feat: add apisix offline package workflow 2025-09-14 19:48:56 +08:00
shenlan
8e91f28dbd feat: add apisix offline package workflow 2025-09-14 19:48:43 +08:00
shenlan
ce0d0678e4
Merge pull request #145 from svc-design/codex/fix-empty-upload-issue-in-github-releases
fix: correct release asset paths for gateway offline packages
2025-09-14 19:32:37 +08:00
shenlan
0493c91057
Merge pull request #143 from svc-design/codex/fix-offline-package-nginx-ingress.yaml-skk2be
fix: run ingress installer with sudo in workflow
2025-09-14 19:11:30 +08:00
shenlan
50b6409579 fix: copy kubeconfig for root in workflow 2025-09-14 19:03:13 +08:00
shenlan
f3ac693487 fix: run ingress installer with sudo 2025-09-14 18:34:25 +08:00
146 changed files with 7889 additions and 170 deletions

View File

@ -0,0 +1,36 @@
name: build chart multi-model llm
on:
pull_request:
branches:
- main
paths:
- 'oci/multi-model-LLM/**'
- '.github/workflows/build-chart-multi-model-LLM.yaml'
workflow_dispatch:
branches:
- main
env:
CHART_DIR: oci/multi-model-LLM/charts/model-serving
jobs:
lint-and-package:
name: Lint and package Helm chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: v3.14.4
- name: Helm lint
run: helm lint $CHART_DIR
- name: Helm package
run: helm package $CHART_DIR --version 0.1.0 --app-version 0.1.0 -d oci/multi-model-LLM/charts
- name: Upload chart artifact
uses: actions/upload-artifact@v4
with:
name: model-serving-chart
path: oci/multi-model-LLM/charts/model-serving-0.1.0.tgz

View File

@ -0,0 +1,29 @@
name: build image ollama
on:
pull_request:
branches:
- main
paths:
- 'oci/base/cuda/Ollama/Dockerfile'
- '.github/workflows/build-ci-image-Ollama.yaml'
workflow_dispatch:
branches:
- main
env:
IMAGE_REPO: "artifact.svc.plus"
jobs:
build-ollama:
name: Build Ollama image
uses: svc-design/actions/.github/workflows/build-images.yaml@main
with:
method: 'docker'
registry_addr: "harbor.onwalk.net"
dockerfile_path: 'oci/base/cuda/Ollama'
image_name: 'public/base/cuda/ollama'
image_tag: 'latest'
secrets:
artifactory_sa: ${{ secrets.REPO_USER }}
artifactory_pw: ${{ secrets.HELM_REPO_PASSWORD }}

View File

@ -0,0 +1,29 @@
name: build image cuda sglang
on:
pull_request:
branches:
- main
paths:
- 'oci/base/cuda/SGLang/Dockerfile'
- '.github/workflows/build-ci-image-SGLang.yaml'
workflow_dispatch:
branches:
- main
env:
IMAGE_REPO: "artifact.svc.plus"
jobs:
build-sglang:
name: Build CUDA SGLang image
uses: svc-design/actions/.github/workflows/build-images.yaml@main
with:
method: 'docker'
registry_addr: "harbor.onwalk.net"
dockerfile_path: 'oci/base/cuda/SGLang'
image_name: 'public/base/cuda/sglang'
image_tag: 'cuda12'
secrets:
artifactory_sa: ${{ secrets.REPO_USER }}
artifactory_pw: ${{ secrets.HELM_REPO_PASSWORD }}

View File

@ -0,0 +1,29 @@
name: build image cuda vllm
on:
pull_request:
branches:
- main
paths:
- 'oci/base/cuda/vLLM/Dockerfile'
- '.github/workflows/build-ci-image-vLLM.yaml'
workflow_dispatch:
branches:
- main
env:
IMAGE_REPO: "artifact.svc.plus"
jobs:
build-vllm:
name: Build CUDA vLLM image
uses: svc-design/actions/.github/workflows/build-images.yaml@main
with:
method: 'docker'
registry_addr: "harbor.onwalk.net"
dockerfile_path: 'oci/base/cuda/vLLM'
image_name: 'public/base/cuda/vllm'
image_tag: 'cuda12'
secrets:
artifactory_sa: ${{ secrets.REPO_USER }}
artifactory_pw: ${{ secrets.HELM_REPO_PASSWORD }}

View File

@ -0,0 +1,216 @@
name: Cloud-Neutra Golden Image Pipeline
on:
workflow_dispatch:
inputs:
edition:
description: "Golden Image Edition"
type: choice
options: ["base", "container", "k3s", "sealos", "sealos-gpu"]
default: "container"
ubuntu_version:
description: "Ubuntu LTS version"
type: choice
options: ["2204", "2404"]
default: "2404"
cpu_arch:
description: "CPU Architecture"
type: choice
options: ["amd64", "arm64"]
default: "amd64"
schedule:
- cron: "0 18 1 * *"
env:
BASE_REGION: ap-northeast-1
TARGET_REGIONS: "ap-northeast-1 ap-east-1 us-west-1"
PROJECT_TAG: Cloud-Neutra
PACKER_TEMPLATE_ROOT: packer/Cloud-Neutra-VMs/templates
jobs:
##########################################################################
# Stage 1 — Lint / Validate / Security
##########################################################################
lint:
name: Lint & Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: actionlint
uses: raven-actions/actionlint@v2
with:
files: ".github/workflows/cloud-neutra-golden-image.yaml"
matcher: false
cache: false
fail-on-error: true
flags: "-ignore SC2086"
- name: Install tools
run: |
sudo apt-get update
sudo apt-get install -y shellcheck jq
- name: Packer FMT
run: packer fmt -recursive .
- name: Packer Validate
run: packer validate "${PACKER_TEMPLATE_ROOT}"
- name: gitleaks Scan
uses: gitleaks/gitleaks-action@v2
with:
args: detect --no-git -v
##########################################################################
# Stage 2 — Build Golden Image
##########################################################################
build:
name: Build Golden AMI
runs-on: ubuntu-latest
needs: lint
outputs:
ami_id: ${{ steps.packer_build.outputs.ami_id }}
strategy:
fail-fast: false
matrix:
include:
- edition: base
ubuntu_version: "2204"
cpu_arch: amd64
- edition: base
ubuntu_version: "2204"
cpu_arch: arm64
steps:
- uses: actions/checkout@v4
# must be step-level to allow matrix.*
- name: Skip matrix items not requested
if: >
github.event_name == 'schedule' ||
(
github.event_name == 'workflow_dispatch' &&
github.event.inputs.edition == matrix.edition &&
github.event.inputs.ubuntu_version == matrix.ubuntu_version &&
github.event.inputs.cpu_arch == matrix.cpu_arch
)
run: echo "Matrix item selected."
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ env.BASE_REGION }}
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-access-key-id: ${{ secrets.AWS_ROOT_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_ROOT_SECRET_ACCESS_KEY }}
mask-aws-account-id: true
- name: Setup Packer
uses: hashicorp/setup-packer@v3
- name: Build AMI
id: packer_build
env:
EDITION: ${{ matrix.edition }}
UBUNTU_VERSION: ${{ matrix.ubuntu_version }}
CPU_ARCH: ${{ matrix.cpu_arch }}
run: |
TEMPLATE="${PACKER_TEMPLATE_ROOT}/${EDITION}/ubuntu-${UBUNTU_VERSION}-${EDITION}.pkr.hcl"
echo "Using template: ${TEMPLATE}"
packer build \
-color=false \
-var "cpu_arch=${CPU_ARCH}" \
-var "edition=${EDITION}" \
-var "ubuntu_version=${UBUNTU_VERSION}" \
"${TEMPLATE}" | tee packer.log
AMI_ID=$(grep 'AMI:' packer.log | awk '{print $2}' | tail -n1 || true)
if [ -z "${AMI_ID}" ]; then
echo "ERROR: Cannot parse AMI ID"
exit 1
fi
echo "ami_id=${AMI_ID}" >> "${GITHUB_OUTPUT}"
- name: Upload Logs
uses: actions/upload-artifact@v4
with:
name: packer-build-log
path: packer.log
##########################################################################
# Stage 3 — QA Test
##########################################################################
test:
name: Test Built AMI
runs-on: ubuntu-latest
needs: build
# must re-expose build's output for downstream needs.*
outputs:
ami_id: ${{ needs.build.outputs.ami_id }}
steps:
- name: Placeholder test
run: |
echo "TODO: Future QA test"
##########################################################################
# Stage 4 — AMI Replication + Retention
##########################################################################
distribute:
name: Replicate & Retain AMI
runs-on: ubuntu-latest
needs: test
strategy:
matrix:
include:
- edition: base
ubuntu_version: "2204"
cpu_arch: amd64
- edition: base
ubuntu_version: "2204"
cpu_arch: arm64
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ env.BASE_REGION }}
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-access-key-id: ${{ secrets.AWS_ROOT_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_ROOT_SECRET_ACCESS_KEY }}
- name: Distribute AMI
env:
BASE_REGION: ${{ env.BASE_REGION }}
TARGET_REGIONS: ${{ env.TARGET_REGIONS }}
PROJECT_TAG: ${{ env.PROJECT_TAG }}
EDITION: ${{ matrix.edition }}
UBUNTU_VERSION: ${{ matrix.ubuntu_version }}
CPU_ARCH: ${{ matrix.cpu_arch }}
AMI_ID: ${{ needs.test.outputs.ami_id }}
run: |
bash packer/scripts/common/ami-replicate.sh \
"${AMI_ID}" "${EDITION}" "${UBUNTU_VERSION}" "${CPU_ARCH}" \
"${BASE_REGION}" "${TARGET_REGIONS}" "${PROJECT_TAG}"
- name: Retention
env:
TARGET_REGIONS: ${{ env.TARGET_REGIONS }}
PROJECT_TAG: ${{ env.PROJECT_TAG }}
EDITION: ${{ matrix.edition }}
UBUNTU_VERSION: ${{ matrix.ubuntu_version }}
CPU_ARCH: ${{ matrix.cpu_arch }}
run: |
bash packer/scripts/common/ami-retention.sh \
"${EDITION}" "${UBUNTU_VERSION}" "${CPU_ARCH}" "${PROJECT_TAG}" "${TARGET_REGIONS}"

View File

@ -3,7 +3,7 @@ name: Build Offline APISIX Gateway Installer
on:
push:
paths:
- 'gitops/scripts/kong-gateway/deploy-kong-gateway.sh'
- 'gitops/scripts/apisix-gateway/deploy-apisix-gateway.sh'
- '.github/workflows/offline-package-apisix-gateway.yaml'
workflow_dispatch:
inputs:
@ -12,7 +12,7 @@ on:
required: false
type: string
chart_version:
description: "Override helm chart version for kong/ingress (e.g., 1.2.3). Leave empty to auto-resolve"
description: "Override helm chart version for apisix (e.g., 1.2.3). Leave empty to auto-resolve"
required: false
type: string
@ -45,7 +45,7 @@ jobs:
- name: Add helm repo
run: |
set -euo pipefail
helm repo add kong https://charts.konghq.com
helm repo add apisix https://charts.apiseven.com
helm repo update
- name: Resolve latest chart version
@ -57,7 +57,7 @@ jobs:
if [ -n "${OVERRIDE_CHART_VERSION}" ]; then
CHART_VERSION="${OVERRIDE_CHART_VERSION}"
else
CHART_VERSION=$(helm search repo kong/ingress --versions | awk 'NR==2{print $2}')
CHART_VERSION=$(helm search repo apisix/apisix --versions | awk 'NR==2{print $2}')
fi
echo "chart_version=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
@ -76,26 +76,27 @@ jobs:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
helm template kong kong/ingress --version "${CHART_VERSION}" > manifest.yaml
images=$(grep -oP 'image:\s*\K[^\s]+' manifest.yaml | sort -u)
helm template apisix apisix/apisix --version "${CHART_VERSION}" > manifest.yaml
images=$(grep -oP 'image:\s*\K[^\s]+' manifest.yaml | tr -d '"' | sort -u)
for img in $images; do
[ -z "$img" ] && continue
docker pull "$img"
safe=$(echo $img | tr '/:' '-_')
safe=$(echo "$img" | tr '/:' '-_')
docker save "$img" -o offline-installer/images/${safe}.tar
done
- name: Download Helm chart (kong/ingress ${{ steps.resolve.outputs.chart_version }})
- name: Download Helm chart (apisix/apisix ${{ steps.resolve.outputs.chart_version }})
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
helm pull kong/ingress --version="${CHART_VERSION}" --untar --untardir offline-installer/charts
helm pull apisix/apisix --version="${CHART_VERSION}" --untar --untardir offline-installer/charts
- name: Stage deployment script
run: |
set -euo pipefail
mkdir -p offline-installer/scripts
cp gitops/scripts/kong-gateway/deploy-kong-gateway.sh offline-installer/scripts/
cp gitops/scripts/apisix-gateway/deploy-apisix-gateway.sh offline-installer/scripts/
- name: Pack offline installer
run: |

View File

@ -0,0 +1,200 @@
name: Build Offline Argo CD Installer
on:
push:
paths:
- '.github/workflows/offline-package-argocd-installer.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v2.11.0). Leave empty to use offline-argocd-<run_number>"
required: false
type: string
chart_version:
description: "Override Helm chart version for argo/argo-cd. Leave empty to auto-resolve"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-argocd
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
env:
NERDCTL_VERSION: "2.0.3"
outputs:
chart_version: ${{ steps.resolve.outputs.chart_version }}
steps:
- uses: actions/checkout@v4
- name: Install deps (curl, jq, helm)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y curl jq
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
- name: Add helm repo
run: |
set -euo pipefail
helm repo add argo https://argoproj.github.io/argo-helm --force-update
helm repo update
- name: Resolve chart version
id: resolve
env:
OVERRIDE_CHART_VERSION: ${{ github.event.inputs.chart_version }}
run: bash scripts/offline-argocd/resolve_chart_version.sh
- name: Prepare directories
run: |
set -euo pipefail
rm -rf argocd-offline-package
mkdir -p argocd-offline-package/{images,charts,scripts,metadata}
- name: Stage installer script
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: bash scripts/offline-argocd/stage_installer.sh
- name: Download nerdctl binary for ${{ matrix.arch }}
run: |
set -euo pipefail
wget https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${{ matrix.arch }}.tar.gz \
-O argocd-offline-package/nerdctl.tar.gz
- name: Pull & export required images
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
MATRIX_ARCH: ${{ matrix.arch }}
run: bash scripts/offline-argocd/pull_and_export_images.sh
- name: Download Helm chart
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
helm pull argo/argo-cd --version "${CHART_VERSION}" --untar --untardir argocd-offline-package/charts
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-package-argocd-${{ matrix.arch }}.tar.gz -C . argocd-offline-package
ls -lh offline-package-argocd-${{ matrix.arch }}.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-package-argocd-${{ matrix.arch }}
path: offline-package-argocd-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-argocd-${{ matrix.arch }}
path: offline-test
- name: Verify offline package integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-package-argocd-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-argocd-{0}', github.run_number) }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/offline-package/argocd/
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-argocd-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-argocd-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-argocd-amd64.tar.gz
release-artifacts/arm64/offline-package-argocd-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure deps (rsync, ssh)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y rsync openssh-client
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: bash scripts/offline-argocd/rsync_release_assets.sh
retention:
name: Remote retention (keep latest 3)
needs: publish-release
runs-on: ubuntu-latest
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/argocd
steps:
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 3)
run: bash scripts/offline-argocd/prune_remote_versions.sh

View File

@ -0,0 +1,159 @@
name: Build Offline AutoGen Studio Installer
on:
push:
paths:
- 'gitops/scripts/autogen-studio/**'
- '.github/workflows/offline-package-autogen-studio.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v0.1.0). Leave empty to use offline-autogen-studio-<run_number>"
required: false
type: string
autogen_tag:
description: "AutoGen Studio container tag (default: latest)"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-autogen-studio
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
outputs:
autogen_tag: ${{ steps.resolve.outputs.autogen_tag }}
steps:
- uses: actions/checkout@v4
- name: Resolve image tag
id: resolve
env:
INPUT_AUTOGEN_TAG: ${{ github.event.inputs.autogen_tag }}
run: |
set -euo pipefail
AUTOGEN_TAG=${INPUT_AUTOGEN_TAG:-latest}
echo "autogen_tag=${AUTOGEN_TAG}" >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
set -euo pipefail
rm -rf offline-installer
mkdir -p offline-installer/{images,scripts}
- name: Stage compose file and scripts
env:
AUTOGEN_TAG: ${{ steps.resolve.outputs.autogen_tag }}
run: |
set -euo pipefail
cp gitops/scripts/autogen-studio/docker-compose.yaml offline-installer/docker-compose.yaml
sed -i "s/__AUTOGEN_TAG__/${AUTOGEN_TAG}/g" offline-installer/docker-compose.yaml
cp gitops/scripts/autogen-studio/deploy-autogen-studio.sh offline-installer/scripts/
chmod +x offline-installer/scripts/deploy-autogen-studio.sh
cat <<'README' > offline-installer/README.md
# Offline AutoGen Studio Installer
This archive contains container images and helper assets for deploying AutoGen Studio with Docker Compose.
## Contents
- `docker-compose.yaml`: Reference deployment manifest configured for the packaged images.
- `images/`: Pre-pulled container images saved as tar archives.
- `scripts/deploy-autogen-studio.sh`: Helper script to load the images and manage the compose stack.
## Usage
1. Extract the archive on a host with Docker/nerdctl available.
2. (Optional) Run `IMAGE_LOAD_TOOL=nerdctl ./scripts/deploy-autogen-studio.sh load-images` to import the images with nerdctl.
3. Start the stack: `./scripts/deploy-autogen-studio.sh up`.
4. Access AutoGen Studio at http://localhost:9090.
Adjust the compose file as needed before running the helper script.
README
- name: Pull & export container images
env:
AUTOGEN_TAG: ${{ steps.resolve.outputs.autogen_tag }}
run: |
set -euo pipefail
image="autogenstudio/autogen-studio:${AUTOGEN_TAG}"
docker pull "$image"
safe=$(echo "$image" | tr '/:' '-_')
docker save "$image" -o "offline-installer/images/${safe}.tar"
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-package-autogen-studio-${{ matrix.arch }}.tar.gz -C offline-installer .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-package-autogen-studio-${{ matrix.arch }}
path: offline-package-autogen-studio-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-autogen-studio-${{ matrix.arch }}
path: offline-test
- name: Verify archive integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-package-autogen-studio-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-autogen-studio-{0}', github.run_number) }}
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-autogen-studio-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-autogen-studio-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-autogen-studio-amd64.tar.gz
release-artifacts/arm64/offline-package-autogen-studio-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,192 @@
name: Build Offline Dify Installer
on:
push:
paths:
- 'gitops/scripts/dify/**'
- '.github/workflows/offline-package-dify.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v0.1.0). Leave empty to use offline-dify-<run_number>"
required: false
type: string
dify_tag:
description: "Dify container tag (default: latest)"
required: false
type: string
postgres_tag:
description: "Postgres image tag (default: 15-alpine)"
required: false
type: string
redis_tag:
description: "Redis image tag (default: 7-alpine)"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-dify
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
outputs:
dify_tag: ${{ steps.resolve.outputs.dify_tag }}
postgres_tag: ${{ steps.resolve.outputs.postgres_tag }}
redis_tag: ${{ steps.resolve.outputs.redis_tag }}
steps:
- uses: actions/checkout@v4
- name: Resolve image tags
id: resolve
env:
INPUT_DIFY_TAG: ${{ github.event.inputs.dify_tag }}
INPUT_POSTGRES_TAG: ${{ github.event.inputs.postgres_tag }}
INPUT_REDIS_TAG: ${{ github.event.inputs.redis_tag }}
run: |
set -euo pipefail
DIFY_TAG=${INPUT_DIFY_TAG:-latest}
POSTGRES_TAG=${INPUT_POSTGRES_TAG:-15-alpine}
REDIS_TAG=${INPUT_REDIS_TAG:-7-alpine}
{
echo "dify_tag=${DIFY_TAG}"
echo "postgres_tag=${POSTGRES_TAG}"
echo "redis_tag=${REDIS_TAG}"
} >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
set -euo pipefail
rm -rf offline-installer
mkdir -p offline-installer/{images,scripts}
- name: Stage compose file and scripts
env:
DIFY_TAG: ${{ steps.resolve.outputs.dify_tag }}
POSTGRES_TAG: ${{ steps.resolve.outputs.postgres_tag }}
REDIS_TAG: ${{ steps.resolve.outputs.redis_tag }}
run: |
set -euo pipefail
cp gitops/scripts/dify/docker-compose.yaml offline-installer/docker-compose.yaml
sed -i "s/__DIFY_TAG__/${DIFY_TAG}/g" offline-installer/docker-compose.yaml
sed -i "s/__POSTGRES_TAG__/${POSTGRES_TAG}/g" offline-installer/docker-compose.yaml
sed -i "s/__REDIS_TAG__/${REDIS_TAG}/g" offline-installer/docker-compose.yaml
cp gitops/scripts/dify/deploy-dify.sh offline-installer/scripts/
chmod +x offline-installer/scripts/deploy-dify.sh
cat <<'README' > offline-installer/README.md
# Offline Dify Installer
This archive contains container images and helper assets for deploying Dify with Docker Compose.
## Contents
- `docker-compose.yaml`: Reference deployment manifest configured for the packaged images.
- `images/`: Pre-pulled container images saved as tar archives.
- `scripts/deploy-dify.sh`: Helper script to load the images and manage the compose stack.
## Usage
1. Extract the archive on a host with Docker/nerdctl available.
2. (Optional) Run `IMAGE_LOAD_TOOL=nerdctl ./scripts/deploy-dify.sh load-images` to import the images with nerdctl.
3. Start the stack: `./scripts/deploy-dify.sh up`.
4. Access the Dify web UI at http://localhost:8080.
Adjust the compose file as needed before running the helper script.
README
- name: Pull & export container images
env:
DIFY_TAG: ${{ steps.resolve.outputs.dify_tag }}
POSTGRES_TAG: ${{ steps.resolve.outputs.postgres_tag }}
REDIS_TAG: ${{ steps.resolve.outputs.redis_tag }}
run: |
set -euo pipefail
images=(
"langgenius/dify-api:${DIFY_TAG}"
"langgenius/dify-worker:${DIFY_TAG}"
"langgenius/dify-web:${DIFY_TAG}"
"langgenius/dify-nginx:${DIFY_TAG}"
"postgres:${POSTGRES_TAG}"
"redis:${REDIS_TAG}"
)
for image in "${images[@]}"; do
docker pull "$image"
safe=$(echo "$image" | tr '/:' '-_')
docker save "$image" -o "offline-installer/images/${safe}.tar"
done
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-package-dify-${{ matrix.arch }}.tar.gz -C offline-installer .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-package-dify-${{ matrix.arch }}
path: offline-package-dify-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-dify-${{ matrix.arch }}
path: offline-test
- name: Verify archive integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-package-dify-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-dify-{0}', github.run_number) }}
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-dify-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-dify-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-dify-amd64.tar.gz
release-artifacts/arm64/offline-package-dify-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,159 @@
name: Build Offline Flowise Installer
on:
push:
paths:
- 'gitops/scripts/flowise/**'
- '.github/workflows/offline-package-flowise.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v0.1.0). Leave empty to use offline-flowise-<run_number>"
required: false
type: string
flowise_tag:
description: "Flowise container tag (default: latest)"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-flowise
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
outputs:
flowise_tag: ${{ steps.resolve.outputs.flowise_tag }}
steps:
- uses: actions/checkout@v4
- name: Resolve image tag
id: resolve
env:
INPUT_FLOWISE_TAG: ${{ github.event.inputs.flowise_tag }}
run: |
set -euo pipefail
FLOWISE_TAG=${INPUT_FLOWISE_TAG:-latest}
echo "flowise_tag=${FLOWISE_TAG}" >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
set -euo pipefail
rm -rf offline-installer
mkdir -p offline-installer/{images,scripts}
- name: Stage compose file and scripts
env:
FLOWISE_TAG: ${{ steps.resolve.outputs.flowise_tag }}
run: |
set -euo pipefail
cp gitops/scripts/flowise/docker-compose.yaml offline-installer/docker-compose.yaml
sed -i "s/__FLOWISE_TAG__/${FLOWISE_TAG}/g" offline-installer/docker-compose.yaml
cp gitops/scripts/flowise/deploy-flowise.sh offline-installer/scripts/
chmod +x offline-installer/scripts/deploy-flowise.sh
cat <<'README' > offline-installer/README.md
# Offline Flowise Installer
This archive contains container images and helper assets for deploying Flowise with Docker Compose.
## Contents
- `docker-compose.yaml`: Reference deployment manifest configured for the packaged images.
- `images/`: Pre-pulled container images saved as tar archives.
- `scripts/deploy-flowise.sh`: Helper script to load the images and manage the compose stack.
## Usage
1. Extract the archive on a host with Docker/nerdctl available.
2. (Optional) Run `IMAGE_LOAD_TOOL=nerdctl ./scripts/deploy-flowise.sh load-images` to import the images with nerdctl.
3. Start the stack: `./scripts/deploy-flowise.sh up`.
4. Access the Flowise UI at http://localhost:3000.
Adjust the compose file as needed before running the helper script.
README
- name: Pull & export container images
env:
FLOWISE_TAG: ${{ steps.resolve.outputs.flowise_tag }}
run: |
set -euo pipefail
image="flowiseai/flowise:${FLOWISE_TAG}"
docker pull "$image"
safe=$(echo "$image" | tr '/:' '-_')
docker save "$image" -o "offline-installer/images/${safe}.tar"
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-package-flowise-${{ matrix.arch }}.tar.gz -C offline-installer .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-package-flowise-${{ matrix.arch }}
path: offline-package-flowise-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-flowise-${{ matrix.arch }}
path: offline-test
- name: Verify archive integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-package-flowise-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-flowise-{0}', github.run_number) }}
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-flowise-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-flowise-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-flowise-amd64.tar.gz
release-artifacts/arm64/offline-package-flowise-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,98 @@
name: Build Offline FluxCD Installer
on:
push:
paths:
- '.github/workflows/offline-package-fluxcd-installer.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v2.2.0). Leave empty to use offline-fluxcd-<run_number>"
required: false
type: string
chart_version:
description: "Override Helm chart version for fluxcd-community/flux2. Leave empty to auto-resolve"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-fluxcd
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
env:
NERDCTL_VERSION: "2.0.3"
outputs:
chart_version: ${{ steps.resolve.outputs.chart_version }}
steps:
- uses: actions/checkout@v4
- name: Install deps (curl, jq, helm)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y curl jq
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
- name: Add helm repo
run: |
set -euo pipefail
helm repo add fluxcd-community https://fluxcd-community.github.io/helm-charts --force-update
helm repo update
- name: Resolve chart version
id: resolve
env:
OVERRIDE_CHART_VERSION: ${{ github.event.inputs.chart_version }}
run: script/offline-fluxcd/resolve-chart-version.sh
- name: Prepare directories
run: |
set -euo pipefail
rm -rf offline-installer
mkdir -p offline-installer/{images,charts,scripts,metadata}
- name: Stage installer script
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: script/offline-fluxcd/stage-installer.sh
- name: Download nerdctl binary for ${{ matrix.arch }}
run: |
set -euo pipefail
wget https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${{ matrix.arch }}.tar.gz \
-O offline-installer/nerdctl.tar.gz
- name: Pull & export required images
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
MATRIX_ARCH: ${{ matrix.arch }}
run: script/offline-fluxcd/pull-and-export-images.sh
- name: Download Helm chart
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
helm pull fluxcd-community/flux2 --version "${CHART_VERSION}" --untar --untardir offline-installer/charts
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-setup-fluxcd-${{ matrix.arch }}.tar.gz -C offline-installer .
ls -lh offline-setup-fluxcd-${{ matrix.arch }}.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-setup-fluxcd-${{ matrix.arch }}
path: offline-setup-fluxcd-${{ matrix.arch }}.tar.gz

View File

@ -0,0 +1,302 @@
name: Build Offline Gitea Installer
on:
push:
paths:
- '.github/workflows/offline-package-gitea-installer.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v1.21.10). Leave empty to use offline-gitea-<run_number>"
required: false
type: string
chart_version:
description: "Override Helm chart version for gitea-charts/gitea. Leave empty to auto-resolve"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-gitea
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
env:
NERDCTL_VERSION: "2.0.3"
OFFLINE_DIR: gitea-offline-package
outputs:
chart_version: ${{ steps.resolve.outputs.chart_version }}
steps:
- uses: actions/checkout@v4
- name: Install deps (curl, jq, helm)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y curl jq
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
- name: Add helm repo
run: |
set -euo pipefail
helm repo add gitea-charts https://dl.gitea.io/charts/ --force-update
helm repo update
- name: Resolve chart version
id: resolve
env:
OVERRIDE_CHART_VERSION: ${{ github.event.inputs.chart_version }}
run: |
set -euo pipefail
if [ -n "${OVERRIDE_CHART_VERSION}" ]; then
CHART_VERSION="${OVERRIDE_CHART_VERSION}"
else
CHART_VERSION=$(helm search repo gitea-charts/gitea --versions | awk 'NR==2{print $2}')
fi
echo "chart_version=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
set -euo pipefail
rm -rf "${OFFLINE_DIR}"
mkdir -p "${OFFLINE_DIR}"/{images,charts,metadata}
- name: Stage installer script
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
cat <<'SCRIPT' > "${OFFLINE_DIR}/install-gitea.sh"
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CHART_DIR="${ROOT_DIR}/charts/gitea"
IMAGES_DIR="${ROOT_DIR}/images"
RELEASE_NAME="${RELEASE_NAME:-gitea}"
NAMESPACE="${NAMESPACE:-gitea}"
if command -v nerdctl >/dev/null 2>&1; then
LOADER="nerdctl"
elif command -v docker >/dev/null 2>&1; then
LOADER="docker"
else
echo "Either docker or nerdctl is required to load images." >&2
exit 1
fi
for tar in "${IMAGES_DIR}"/*.tar; do
[ -f "$tar" ] || continue
echo "Loading image: $tar"
"$LOADER" load -i "$tar"
done
echo "Installing/Upgrading Gitea release ${RELEASE_NAME} in namespace ${NAMESPACE}"
helm upgrade --install "${RELEASE_NAME}" "${CHART_DIR}" \
--namespace "${NAMESPACE}" \
--create-namespace \
"$@"
SCRIPT
chmod +x "${OFFLINE_DIR}/scripts/install-gitea.sh"
cat <<'ROOTSCRIPT' > "${OFFLINE_DIR}/install-gitea.sh"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec "${SCRIPT_DIR}/scripts/install-gitea.sh" "$@"
ROOTSCRIPT
chmod +x "${OFFLINE_DIR}/install-gitea.sh"
cat <<EOFMETA > "${OFFLINE_DIR}/metadata/INFO"
chart: gitea-charts/gitea
chart_version: ${CHART_VERSION}
created_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
EOFMETA
- name: Download nerdctl binary for ${{ matrix.arch }}
run: |
set -euo pipefail
wget https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${{ matrix.arch }}.tar.gz \
-O "${OFFLINE_DIR}/nerdctl.tar.gz"
- name: Pull & export required images
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
PLATFORM="linux/${{ matrix.arch }}"
helm template gitea gitea-charts/gitea --version "${CHART_VERSION}" > manifest.yaml
mapfile -t images < <(grep -oP 'image:\s*"?\K([^"\s]+)' manifest.yaml | sort -u || true)
rm -f manifest.yaml
for img in "${images[@]}"; do
[ -n "$img" ] || continue
if [[ "$img" == *"{{"* ]]; then
continue
fi
echo "Pulling $img for ${PLATFORM}"
if ! docker pull --platform "${PLATFORM}" "$img"; then
echo "::warning::Failed to pull $img for ${PLATFORM}, skipping" >&2
continue
fi
safe=$(echo "$img" | tr '/:' '-_')
docker save "$img" -o "${OFFLINE_DIR}/images/${safe}.tar"
done
- name: Download Helm chart
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
helm pull gitea-charts/gitea --version "${CHART_VERSION}" --untar --untardir "${OFFLINE_DIR}/charts"
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-package-gitea-${{ matrix.arch }}.tar.gz "${OFFLINE_DIR}"
ls -lh offline-package-gitea-${{ matrix.arch }}.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-package-gitea-${{ matrix.arch }}
path: offline-package-gitea-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-gitea-${{ matrix.arch }}
path: offline-test
- name: Verify offline package integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-package-gitea-${{ matrix.arch }}.tar.gz > /dev/null
- name: Verify install script layout
run: |
set -euo pipefail
cd offline-test
tar -xvpf offline-package-gitea-${{ matrix.arch }}.tar.gz
test -f gitea-offline-package/install-gitea.sh
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-gitea-{0}', github.run_number) }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/gitea
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-gitea-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-gitea-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-gitea-amd64.tar.gz
release-artifacts/arm64/offline-package-gitea-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure deps (rsync, ssh)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y rsync openssh-client
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: |
set -euo pipefail
REMOTE_DIR="${REMOTE_ROOT}/${TAG_NAME}"
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "mkdir -p '${REMOTE_DIR}'"
echo "Rsync -> ${VPS_HOST}:${REMOTE_DIR}/"
rsync -av -e "ssh -i ~/.ssh/id_rsa" \
release-artifacts/amd64/offline-package-gitea-amd64.tar.gz \
release-artifacts/arm64/offline-package-gitea-arm64.tar.gz \
"${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 3)
needs: publish-release
runs-on: ubuntu-latest
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/gitea
steps:
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 3)
run: |
set -euo pipefail
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" bash -lc '
set -euo pipefail
cd "'"${REMOTE_ROOT}"'" || exit 0
keep=3
mapfile -t all < <(ls -1 | grep -E "^(offline-gitea-|v[0-9]+\.)" | sort -V -r || true)
if [ "${#all[@]}" -le "$keep" ]; then
echo "Nothing to prune. Count=${#all[@]}"
exit 0
fi
to_delete=("${all[@]:keep}")
echo "Pruning old versions: ${to_delete[*]}"
for d in "${to_delete[@]}"; do
rm -rf -- "$d"
done
'

View File

@ -0,0 +1,219 @@
name: Build Offline GitLab Installer
on:
push:
paths:
- '.github/workflows/offline-package-gitlab-installer.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v16.11.0). Leave empty to use offline-gitlab-<run_number>"
required: false
type: string
chart_version:
description: "Override Helm chart version for gitlab/gitlab. Leave empty to auto-resolve"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-gitlab
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
env:
NERDCTL_VERSION: "2.0.3"
outputs:
chart_version: ${{ steps.resolve.outputs.chart_version }}
steps:
- uses: actions/checkout@v4
- name: Install deps (curl, jq, helm)
run: script/install-offline-gitlab-deps.sh
- name: Add helm repo
run: |
set -euo pipefail
helm repo add gitlab https://charts.gitlab.io --force-update
helm repo update
- name: Resolve chart version
id: resolve
env:
OVERRIDE_CHART_VERSION: ${{ github.event.inputs.chart_version }}
run: script/resolve-gitlab-chart-version.sh
- name: Prepare directories
run: |
set -euo pipefail
rm -rf offline-installer
mkdir -p offline-installer/{images,charts,scripts,metadata}
- name: Stage installer script
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: script/stage-gitlab-offline-installer.sh
- name: Download nerdctl binary for ${{ matrix.arch }}
run: |
set -euo pipefail
wget https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${{ matrix.arch }}.tar.gz \
-O offline-installer/nerdctl.tar.gz
- name: Pull & export required images
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
ARCH: ${{ matrix.arch }}
run: script/pull-and-export-gitlab-images.sh
- name: Download Helm chart
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
helm pull gitlab/gitlab --version "${CHART_VERSION}" --untar --untardir offline-installer/charts
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-setup-gitlab-${{ matrix.arch }}.tar.gz -C offline-installer .
ls -lh offline-setup-gitlab-${{ matrix.arch }}.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-setup-gitlab-${{ matrix.arch }}
path: offline-setup-gitlab-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-setup-gitlab-${{ matrix.arch }}
path: offline-test
- name: Verify offline package integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-setup-gitlab-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-gitlab-{0}', github.run_number) }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/gitlab
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-setup-gitlab-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-setup-gitlab-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-setup-gitlab-amd64.tar.gz
release-artifacts/arm64/offline-setup-gitlab-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure deps (rsync, ssh)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y rsync openssh-client
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: |
set -euo pipefail
REMOTE_DIR="${REMOTE_ROOT}/${TAG_NAME}"
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "mkdir -p '${REMOTE_DIR}'"
echo "Rsync -> ${VPS_HOST}:${REMOTE_DIR}/"
rsync -av -e "ssh -i ~/.ssh/id_rsa" \
release-artifacts/amd64/offline-setup-gitlab-amd64.tar.gz \
release-artifacts/arm64/offline-setup-gitlab-arm64.tar.gz \
"${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 3)
needs: publish-release
runs-on: ubuntu-latest
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/gitlab
steps:
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 3)
run: |
set -euo pipefail
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" bash -lc '
set -euo pipefail
cd "'"${REMOTE_ROOT}"'" || exit 0
keep=3
mapfile -t all < <(ls -1 | grep -E "^(offline-gitlab-|v[0-9]+\.)" | sort -V -r || true)
if [ "${#all[@]}" -le "$keep" ]; then
echo "Nothing to prune. Count=${#all[@]}"
exit 0
fi
to_delete=("${all[@]:keep}")
echo "Pruning old versions: ${to_delete[*]}"
for d in "${to_delete[@]}"; do
rm -rf -- "$d"
done
'

View File

@ -4,6 +4,7 @@ on:
push:
paths:
- 'gitops/scripts/kong-gateway/deploy-kong-gateway.sh'
- 'gitops/scripts/kong-gateway/configure-example-app.sh'
- '.github/workflows/offline-package-kong-gateway.yaml'
workflow_dispatch:
inputs:
@ -63,11 +64,12 @@ jobs:
set -euo pipefail
mkdir -p offline-installer/{images,charts,scripts,bin}
- name: Stage deploy script
- name: Stage scripts
run: |
set -euo pipefail
cp gitops/scripts/kong-gateway/deploy-kong-gateway.sh offline-installer/scripts/
chmod +x offline-installer/scripts/deploy-kong-gateway.sh
cp gitops/scripts/kong-gateway/configure-example-app.sh offline-installer/scripts/
chmod +x offline-installer/scripts/deploy-kong-gateway.sh offline-installer/scripts/configure-example-app.sh
- name: Download nerdctl binary for ${{ matrix.arch }}
run: |

View File

@ -0,0 +1,177 @@
name: Build Offline n8n Installer
on:
push:
paths:
- 'gitops/scripts/n8n/**'
- '.github/workflows/offline-package-n8n.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v0.1.0). Leave empty to use offline-n8n-<run_number>"
required: false
type: string
n8n_tag:
description: "n8n container tag (default: latest)"
required: false
type: string
postgres_tag:
description: "Postgres image tag (default: 15-alpine)"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-n8n
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
outputs:
n8n_tag: ${{ steps.resolve.outputs.n8n_tag }}
postgres_tag: ${{ steps.resolve.outputs.postgres_tag }}
steps:
- uses: actions/checkout@v4
- name: Resolve image tags
id: resolve
env:
INPUT_N8N_TAG: ${{ github.event.inputs.n8n_tag }}
INPUT_POSTGRES_TAG: ${{ github.event.inputs.postgres_tag }}
run: |
set -euo pipefail
N8N_TAG=${INPUT_N8N_TAG:-latest}
POSTGRES_TAG=${INPUT_POSTGRES_TAG:-15-alpine}
{
echo "n8n_tag=${N8N_TAG}"
echo "postgres_tag=${POSTGRES_TAG}"
} >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
set -euo pipefail
rm -rf offline-installer
mkdir -p offline-installer/{images,scripts}
- name: Stage compose file and scripts
env:
N8N_TAG: ${{ steps.resolve.outputs.n8n_tag }}
POSTGRES_TAG: ${{ steps.resolve.outputs.postgres_tag }}
run: |
set -euo pipefail
cp gitops/scripts/n8n/docker-compose.yaml offline-installer/docker-compose.yaml
sed -i "s/__N8N_TAG__/${N8N_TAG}/g" offline-installer/docker-compose.yaml
sed -i "s/__POSTGRES_TAG__/${POSTGRES_TAG}/g" offline-installer/docker-compose.yaml
cp gitops/scripts/n8n/deploy-n8n.sh offline-installer/scripts/
chmod +x offline-installer/scripts/deploy-n8n.sh
cat <<'README' > offline-installer/README.md
# Offline n8n Installer
This archive contains container images and helper assets for deploying n8n with Docker Compose.
## Contents
- `docker-compose.yaml`: Reference deployment manifest configured for the packaged images.
- `images/`: Pre-pulled container images saved as tar archives.
- `scripts/deploy-n8n.sh`: Helper script to load the images and manage the compose stack.
## Usage
1. Extract the archive on a host with Docker/nerdctl available.
2. (Optional) Run `IMAGE_LOAD_TOOL=nerdctl ./scripts/deploy-n8n.sh load-images` to import the images with nerdctl.
3. Start the stack: `./scripts/deploy-n8n.sh up`.
4. Access the n8n UI at http://localhost:5678.
Adjust the compose file as needed before running the helper script.
README
- name: Pull & export container images
env:
N8N_TAG: ${{ steps.resolve.outputs.n8n_tag }}
POSTGRES_TAG: ${{ steps.resolve.outputs.postgres_tag }}
run: |
set -euo pipefail
images=(
"n8nio/n8n:${N8N_TAG}"
"postgres:${POSTGRES_TAG}"
)
for image in "${images[@]}"; do
docker pull "$image"
safe=$(echo "$image" | tr '/:' '-_')
docker save "$image" -o "offline-installer/images/${safe}.tar"
done
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-package-n8n-${{ matrix.arch }}.tar.gz -C offline-installer .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-package-n8n-${{ matrix.arch }}
path: offline-package-n8n-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-n8n-${{ matrix.arch }}
path: offline-test
- name: Verify archive integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-package-n8n-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-n8n-{0}', github.run_number) }}
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-n8n-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-n8n-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-n8n-amd64.tar.gz
release-artifacts/arm64/offline-package-n8n-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -150,6 +150,8 @@ jobs:
mkdir -p $HOME/.kube
sudo cp /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
sudo chown $USER:$USER $HOME/.kube/config
sudo mkdir -p /root/.kube
sudo cp /etc/rancher/k3s/k3s.yaml /root/.kube/config
kubectl get nodes
kubectl version --client=true
@ -172,10 +174,10 @@ jobs:
run: |
set -euo pipefail
cd offline-test
bash scripts/ingress-installer.sh
sudo bash scripts/ingress-installer.sh
sleep 10
helm list -A
kubectl -n ingress get pods
sudo helm list -A
sudo kubectl -n ingress get pods
publish-release:
needs: test-offline-installer

View File

@ -0,0 +1,175 @@
name: Build Offline Pulumi Installer
on:
push:
paths:
- '.github/workflows/offline-package-pulumi-installer.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v3.127.0). Leave empty to use offline-pulumi-<run_number>"
required: false
type: string
pulumi_version:
description: "Override Pulumi version (e.g., 3.127.0). Leave empty to auto-resolve"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-pulumi
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Install deps (curl, jq, tar)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y curl jq tar
- name: Resolve Pulumi version
id: resolve
env:
OVERRIDE_VERSION: ${{ github.event.inputs.pulumi_version }}
run: script/resolve-pulumi-version.sh
- name: Build offline Pulumi package
env:
ARCH: ${{ matrix.arch }}
PULUMI_VERSION: ${{ steps.resolve.outputs.version }}
MATRIX_ARCH: ${{ matrix.arch }}
run: script/build-offline-pulumi-package.sh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: pulumi-offline-package-${{ matrix.arch }}
path: offline-package-pulumi-${{ matrix.arch }}.tar.gz
if-no-files-found: error
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: pulumi-offline-package-${{ matrix.arch }}
path: ./test-dir
- name: Extract package
run: |
set -euo pipefail
cd test-dir
tar -xzvf offline-package-pulumi-${{ matrix.arch }}.tar.gz
- name: Verify Pulumi bundle
env:
ARCH: ${{ matrix.arch }}
PULUMI_VERSION: ${{ needs.build-offline-installer.outputs.version }}
MATRIX_ARCH: ${{ matrix.arch }}
run: script/verify-pulumi-bundle.sh
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-pulumi-{0}', github.run_number) }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/pulumi
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: pulumi-offline-package-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: pulumi-offline-package-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-pulumi-amd64.tar.gz
release-artifacts/arm64/offline-package-pulumi-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure deps (rsync, ssh)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y rsync openssh-client
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: script/rsync-release-assets.sh
retention:
name: Remote retention (keep latest 3)
needs: publish-release
runs-on: ubuntu-latest
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/pulumi
steps:
- uses: actions/checkout@v4
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 3)
run: script/prune-remote-versions.sh

View File

@ -0,0 +1,189 @@
name: Build Offline RAGFlow Installer
on:
push:
paths:
- 'gitops/scripts/ragflow/**'
- '.github/workflows/offline-package-ragflow.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v0.1.0). Leave empty to use offline-ragflow-<run_number>"
required: false
type: string
ragflow_tag:
description: "RAGFlow container tag (default: latest)"
required: false
type: string
postgres_tag:
description: "Postgres image tag (default: 15-alpine)"
required: false
type: string
redis_tag:
description: "Redis image tag (default: 7-alpine)"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-ragflow
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
outputs:
ragflow_tag: ${{ steps.resolve.outputs.ragflow_tag }}
postgres_tag: ${{ steps.resolve.outputs.postgres_tag }}
redis_tag: ${{ steps.resolve.outputs.redis_tag }}
steps:
- uses: actions/checkout@v4
- name: Resolve image tags
id: resolve
env:
INPUT_RAGFLOW_TAG: ${{ github.event.inputs.ragflow_tag }}
INPUT_POSTGRES_TAG: ${{ github.event.inputs.postgres_tag }}
INPUT_REDIS_TAG: ${{ github.event.inputs.redis_tag }}
run: |
set -euo pipefail
RAGFLOW_TAG=${INPUT_RAGFLOW_TAG:-latest}
POSTGRES_TAG=${INPUT_POSTGRES_TAG:-15-alpine}
REDIS_TAG=${INPUT_REDIS_TAG:-7-alpine}
{
echo "ragflow_tag=${RAGFLOW_TAG}"
echo "postgres_tag=${POSTGRES_TAG}"
echo "redis_tag=${REDIS_TAG}"
} >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
set -euo pipefail
rm -rf offline-installer
mkdir -p offline-installer/{images,scripts}
- name: Stage compose file and scripts
env:
RAGFLOW_TAG: ${{ steps.resolve.outputs.ragflow_tag }}
POSTGRES_TAG: ${{ steps.resolve.outputs.postgres_tag }}
REDIS_TAG: ${{ steps.resolve.outputs.redis_tag }}
run: |
set -euo pipefail
cp gitops/scripts/ragflow/docker-compose.yaml offline-installer/docker-compose.yaml
sed -i "s/__RAGFLOW_TAG__/${RAGFLOW_TAG}/g" offline-installer/docker-compose.yaml
sed -i "s/__POSTGRES_TAG__/${POSTGRES_TAG}/g" offline-installer/docker-compose.yaml
sed -i "s/__REDIS_TAG__/${REDIS_TAG}/g" offline-installer/docker-compose.yaml
cp gitops/scripts/ragflow/deploy-ragflow.sh offline-installer/scripts/
chmod +x offline-installer/scripts/deploy-ragflow.sh
cat <<'README' > offline-installer/README.md
# Offline RAGFlow Installer
This archive contains container images and helper assets for deploying RAGFlow with Docker Compose.
## Contents
- `docker-compose.yaml`: Reference deployment manifest configured for the packaged images.
- `images/`: Pre-pulled container images saved as tar archives.
- `scripts/deploy-ragflow.sh`: Helper script to load the images and manage the compose stack.
## Usage
1. Extract the archive on a host with Docker/nerdctl available.
2. (Optional) Run `IMAGE_LOAD_TOOL=nerdctl ./scripts/deploy-ragflow.sh load-images` to import the images with nerdctl.
3. Start the stack: `./scripts/deploy-ragflow.sh up`.
4. Access the RAGFlow UI at http://localhost:3001.
Adjust the compose file as needed before running the helper script. Configure external vector stores or storage services as required by your deployment.
README
- name: Pull & export container images
env:
RAGFLOW_TAG: ${{ steps.resolve.outputs.ragflow_tag }}
POSTGRES_TAG: ${{ steps.resolve.outputs.postgres_tag }}
REDIS_TAG: ${{ steps.resolve.outputs.redis_tag }}
run: |
set -euo pipefail
images=(
"ragflow/ragflow:${RAGFLOW_TAG}"
"postgres:${POSTGRES_TAG}"
"redis:${REDIS_TAG}"
)
for image in "${images[@]}"; do
docker pull "$image"
safe=$(echo "$image" | tr '/:' '-_')
docker save "$image" -o "offline-installer/images/${safe}.tar"
done
- name: Package offline installer
run: |
set -euo pipefail
tar -czf offline-package-ragflow-${{ matrix.arch }}.tar.gz -C offline-installer .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: offline-package-ragflow-${{ matrix.arch }}
path: offline-package-ragflow-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-ragflow-${{ matrix.arch }}
path: offline-test
- name: Verify archive integrity
run: |
set -euo pipefail
cd offline-test
tar -tzf offline-package-ragflow-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-ragflow-{0}', github.run_number) }}
steps:
- uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-ragflow-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-ragflow-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/offline-package-ragflow-amd64.tar.gz
release-artifacts/arm64/offline-package-ragflow-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,167 @@
name: Build Offline Terraform Installer
on:
push:
paths:
- '.github/workflows/offline-package-terraform-installer.yaml'
workflow_dispatch:
inputs:
tag:
description: "Release tag to use/sync (e.g., v1.8.5). Leave empty to use offline-terraform-<run_number>"
required: false
type: string
terraform_version:
description: "Override Terraform version (e.g., 1.8.5). Leave empty to auto-resolve"
required: false
type: string
permissions:
contents: write
concurrency:
group: build-offline-terraform
cancel-in-progress: false
jobs:
build-offline-installer:
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Install deps (curl, jq, unzip)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y curl jq unzip
- name: Resolve Terraform version
id: resolve
env:
OVERRIDE_VERSION: ${{ github.event.inputs.terraform_version }}
run: script/resolve-terraform-version.sh
- name: Build offline Terraform package
env:
TERRAFORM_VERSION: ${{ steps.resolve.outputs.version }}
ARCH: ${{ matrix.arch }}
run: script/build-offline-terraform-package.sh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: terraform-offline-package-${{ matrix.arch }}
path: terraform-offline-package-${{ matrix.arch }}.tar.gz
if-no-files-found: error
test-offline-installer:
needs: build-offline-installer
strategy:
matrix:
arch: [amd64, arm64]
runs-on: ubuntu-latest
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: terraform-offline-package-${{ matrix.arch }}
path: ./test-dir
- name: Extract package
run: |
set -euo pipefail
cd test-dir
tar -xzvf terraform-offline-package-${{ matrix.arch }}.tar.gz
- name: Verify Terraform bundle
env:
TERRAFORM_VERSION: ${{ needs.build-offline-installer.outputs.version }}
ARCH: ${{ matrix.arch }}
run: script/verify-terraform-bundle.sh
publish-release:
needs: test-offline-installer
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-terraform-{0}', github.run_number) }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/terraform
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
release_name: Build ${{ env.TAG_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: terraform-offline-package-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: terraform-offline-package-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.TAG_NAME }}
files: |
release-artifacts/amd64/terraform-offline-package-amd64.tar.gz
release-artifacts/arm64/terraform-offline-package-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure deps (rsync, ssh)
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y rsync openssh-client
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: script/rsync-release-assets.sh
retention:
name: Remote retention (keep latest 3)
needs: publish-release
runs-on: ubuntu-latest
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/terraform
steps:
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 3)
run: script/prune-remote-versions.sh

View File

@ -0,0 +1,67 @@
name: release-oci-charts
on:
push:
branches:
- main
paths:
- "oci/charts/apps/app-service/**"
- "oci/charts/postgresql/**"
- "oci/charts/observability/**"
- ".github/workflows/release-oci-charts.yml"
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
- name: Setup Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4
with:
version: v3.15.4
- name: Log In To GHCR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Lint charts
run: |
set -euo pipefail
charts=(
"oci/charts/apps/app-service"
"oci/charts/postgresql"
"oci/charts/observability"
)
for chart in "${charts[@]}"; do
helm lint "./${chart}"
done
- name: Package charts
run: |
set -euo pipefail
mkdir -p dist
charts=(
"oci/charts/apps/app-service"
"oci/charts/postgresql"
"oci/charts/observability"
)
for chart in "${charts[@]}"; do
helm package "./${chart}" --destination dist
done
- name: Push charts to GHCR
run: |
set -euo pipefail
for pkg in dist/*.tgz; do
helm push "${pkg}" oci://ghcr.io/x-evor
done

View File

@ -0,0 +1,201 @@
name: Sync blackbox_exporter latest v0.* (matrix)
on:
workflow_dispatch:
schedule:
- cron: "0 2 * * *" # <-- 这是 UTC 02:00。若需 JST 02:00请改为 "0 17 * * *"
permissions:
contents: read
concurrency:
group: sync-blackbox-exporter-v0
cancel-in-progress: false
jobs:
prep:
name: Resolve latest tag & remote check (${{ matrix.vps_host }})
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
GH_REPO: prometheus/blackbox_exporter
GH_TOKEN: ${{ github.token }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/prometheus/blackbox_exporter
outputs:
tag: ${{ steps.latest.outputs.tag }}
version: ${{ steps.latest.outputs.version }}
exists: ${{ steps.remotecheck.outputs.exists }}
steps:
- uses: actions/checkout@v4
- name: Ensure GitHub CLI & deps
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y gh jq rsync
gh --version
jq --version
rsync --version | head -n1
- name: Resolve latest tag (v0.*)
id: latest
run: |
set -euo pipefail
TAG=$(./scripts/resolve_github_repo_release.sh "${GH_REPO}" '^v0(\.|$)' 'v0.*')
VERSION=${TAG#v}
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Latest tag: $TAG (version: $VERSION)"
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Check remote existing version dir
id: remotecheck
env:
VERSION: ${{ steps.latest.outputs.version }}
run: |
set -euo pipefail
REMOTE_DIR="${REMOTE_ROOT}/${VERSION}"
if ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "test -d '${REMOTE_DIR}'"; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Remote already has ${REMOTE_DIR}, skip whole sync."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Remote does not have ${REMOTE_DIR}, will sync."
fi
sync-one:
name: Sync ${{ matrix.asset_suffix }} for ${{ needs.prep.outputs.version }} (${{ matrix.vps_host }})
needs: prep
if: needs.prep.outputs.exists == 'false'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
asset_suffix:
- "linux-amd64.tar.gz"
- "linux-arm64.tar.gz"
- "darwin-amd64.tar.gz"
- "darwin-arm64.tar.gz"
env:
GH_REPO: prometheus/blackbox_exporter
GH_TOKEN: ${{ github.token }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/prometheus/blackbox_exporter
TAG: ${{ needs.prep.outputs.tag }}
VERSION: ${{ needs.prep.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Ensure GitHub CLI & deps
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y gh jq rsync
gh --version
- name: Check asset exists via GitHub CLI
id: has_asset
run: |
set -euo pipefail
ASSET="blackbox_exporter-${VERSION}.${{ matrix.asset_suffix }}"
echo "Checking asset $ASSET for tag ${TAG}"
if gh release view "${TAG}" --repo "${GH_REPO}" --json assets \
| jq -r '.assets[].name' | grep -Fxq "$ASSET"; then
echo "asset=$ASSET" >> "$GITHUB_OUTPUT"
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Asset $ASSET not found for ${TAG}, will skip."
fi
- name: Download asset
if: steps.has_asset.outputs.exists == 'true'
run: |
set -euo pipefail
mkdir -p "releases/${VERSION}"
gh release download "${TAG}" \
--repo "${GH_REPO}" \
--pattern "${{ steps.has_asset.outputs.asset }}" \
--dir "releases/${VERSION}"
- name: Init SSH
if: steps.has_asset.outputs.exists == 'true'
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync this asset to remote
if: steps.has_asset.outputs.exists == 'true'
run: |
set -euo pipefail
REMOTE_DIR="${REMOTE_ROOT}/${VERSION}"
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "mkdir -p '${REMOTE_DIR}'"
echo "Rsync releases/${VERSION}/${{ steps.has_asset.outputs.asset }} -> ${VPS_HOST}:${REMOTE_DIR}/"
rsync -av -e "ssh -i ~/.ssh/id_rsa" \
"releases/${VERSION}/${{ steps.has_asset.outputs.asset }}" "${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 10 v0.*) (${{ matrix.vps_host }})
needs: [prep, sync-one]
if: needs.prep.outputs.exists == 'false'
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/prometheus/blackbox_exporter
steps:
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 10)
run: |
set -euo pipefail
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" bash -lc '
set -euo pipefail
cd "'"${REMOTE_ROOT}"'" || exit 0
keep=10
mapfile -t all < <(ls -1 | grep -E "^[0-9]+\\.[0-9]+\\.[0-9]+$" | sort -V -r || true)
if [ "${#all[@]}" -le "$keep" ]; then
echo "Nothing to prune. Count=${#all[@]}"
exit 0
fi
to_delete=("${all[@]:keep}")
echo "Pruning old versions: ${to_delete[*]}"
for d in "${to_delete[@]}"; do
rm -rf -- "$d"
done
'

View File

@ -19,14 +19,19 @@ concurrency:
jobs:
prep:
name: Resolve version & remote check
name: Resolve version & remote check (${{ matrix.vps_host }})
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
GH_REPO: prometheus/node_exporter
GH_TOKEN: ${{ github.token }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/otel/node_exporter/
DEFAULT_TAG: 1.9.1 # <-- 无 v
ALLOWED_SERIES: "^(1\\.9|1\\.8)\\.[0-9]+\\.[0-9]+$"
@ -91,13 +96,16 @@ jobs:
fi
sync-one:
name: Sync ${{ matrix.asset_suffix }} for ${{ needs.prep.outputs.version }}
name: Sync ${{ matrix.asset_suffix }} for ${{ needs.prep.outputs.version }} (${{ matrix.vps_host }})
needs: prep
if: needs.prep.outputs.exists == 'false'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
asset_suffix:
- "linux-amd64.tar.gz"
- "linux-arm64.tar.gz"
@ -106,7 +114,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/prometheus/node_exporter
TAG: ${{ needs.prep.outputs.tag }} # v1.9.1
VERSION: ${{ needs.prep.outputs.version }} # 1.9.1
@ -166,14 +174,19 @@ jobs:
"releases/${VERSION}/${{ steps.has_asset.outputs.asset }}" "${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 10)
name: Remote retention (keep latest 10) (${{ matrix.vps_host }})
needs: [prep, sync-one]
if: needs.prep.outputs.exists == 'false'
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/prometheus/node_exporter/
steps:
- name: Init SSH

View File

@ -20,16 +20,20 @@ concurrency:
jobs:
prep:
name: Resolve tag & remote check
name: Resolve tag & remote check (${{ matrix.vps_host }})
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
GH_REPO: open-telemetry/opentelemetry-collector-releases
GH_TOKEN: ${{ github.token }} # 用内置 token无需自建 PAT
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/otel/OpenTelemetry/
DEFAULT_TAG: v0.133.0
outputs:
tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
@ -46,7 +50,7 @@ jobs:
jq --version
rsync --version | head -n1
- name: Resolve tag (use input or default)
- name: Resolve tag (use input or latest v0.*)
id: resolve
run: |
set -euo pipefail
@ -54,7 +58,7 @@ jobs:
if [ -n "$TAG_INPUT" ]; then
TAG="$TAG_INPUT"
else
TAG="$DEFAULT_TAG"
TAG=$(./scripts/resolve_github_repo_release.sh "${GH_REPO}" '^v0\.[0-9]+\.[0-9]+$' 'v0.*')
fi
# 基本校验:必须形如 v0.X.Y
@ -92,24 +96,36 @@ jobs:
fi
sync-one:
name: Sync ${{ matrix.asset_prefix }}_${{ matrix.asset_suffix }} for ${{ needs.prep.outputs.tag }}
name: Sync ${{ matrix.asset_prefix }}_${{ matrix.asset_suffix }} for ${{ needs.prep.outputs.tag }} (${{ matrix.vps_host }})
needs: prep
if: needs.prep.outputs.exists == 'false' # 远端已存在则整个矩阵跳过
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
asset:
- otelcol-contrib_linux_amd64
- otelcol-contrib_linux_arm64
- opampsupervisor_linux_amd64
- opampsupervisor_linux_arm64
include:
- asset_prefix: "otelcol-contrib"
- asset: otelcol-contrib_linux_amd64
asset_prefix: "otelcol-contrib"
asset_suffix: "linux_amd64.tar.gz"
release_tag_prefix: ""
- asset_prefix: "otelcol-contrib"
- asset: otelcol-contrib_linux_arm64
asset_prefix: "otelcol-contrib"
asset_suffix: "linux_arm64.tar.gz"
release_tag_prefix: ""
- asset_prefix: "opampsupervisor"
- asset: opampsupervisor_linux_amd64
asset_prefix: "opampsupervisor"
asset_suffix: "linux_amd64"
release_tag_prefix: "cmd/opampsupervisor/"
- asset_prefix: "opampsupervisor"
- asset: opampsupervisor_linux_arm64
asset_prefix: "opampsupervisor"
asset_suffix: "linux_arm64"
release_tag_prefix: "cmd/opampsupervisor/"
env:
@ -117,7 +133,7 @@ jobs:
GH_TOKEN: ${{ github.token }} # 继续使用内置 token
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/otel/OpenTelemetry
TAG: ${{ needs.prep.outputs.tag }}
VERSION: ${{ needs.prep.outputs.version }}
@ -178,14 +194,19 @@ jobs:
"releases/${TAG}/${{ steps.has_asset.outputs.asset }}" "${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 10 v0.*)
name: Remote retention (keep latest 10 v0.*) (${{ matrix.vps_host }})
needs: [prep, sync-one]
if: needs.prep.outputs.exists == 'false' # 只有新增版本时才清理
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/otel/OpenTelemetry/
steps:
- name: Init SSH

199
.github/workflows/sync-vultr-cli.yml vendored Normal file
View File

@ -0,0 +1,199 @@
name: Sync vultr-cli latest (matrix)
on:
workflow_dispatch:
schedule:
- cron: "0 2 * * *" # <-- 这是 UTC 02:00。若需 JST 02:00请改为 "0 17 * * *"
permissions:
contents: read
concurrency:
group: sync-vultr-cli-latest
cancel-in-progress: false
jobs:
prep:
name: Resolve latest tag & remote check (${{ matrix.vps_host }})
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
GH_REPO: vultr/vultr-cli
GH_TOKEN: ${{ github.token }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/iac/vultr-cli/
outputs:
tag: ${{ steps.latest.outputs.tag }}
version: ${{ steps.latest.outputs.version }}
exists: ${{ steps.remotecheck.outputs.exists }}
steps:
- uses: actions/checkout@v4
- name: Ensure GitHub CLI & deps
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y gh jq rsync
gh --version
jq --version
rsync --version | head -n1
- name: Resolve latest tag (semver)
id: latest
run: |
set -euo pipefail
TAG=$(./scripts/resolve_github_repo_release.sh "${GH_REPO}" '^v[0-9]+\.[0-9]+\.[0-9]+$' 'v*.*.*')
VERSION=${TAG#v}
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Latest tag: $TAG (version: $VERSION)"
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Check remote existing version dir
id: remotecheck
env:
VERSION: ${{ steps.latest.outputs.version }}
run: |
set -euo pipefail
REMOTE_DIR="${REMOTE_ROOT%/}/${VERSION}"
if ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "test -d '${REMOTE_DIR}'"; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Remote already has ${REMOTE_DIR}, skip whole sync."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Remote does not have ${REMOTE_DIR}, will sync."
fi
sync-one:
name: Sync ${{ matrix.asset_suffix }} for ${{ needs.prep.outputs.version }} (${{ matrix.vps_host }})
needs: prep
if: needs.prep.outputs.exists == 'false'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
asset_suffix:
- "linux_amd64.tar.gz"
- "macOS_arm64.tar.gz"
env:
GH_REPO: vultr/vultr-cli
GH_TOKEN: ${{ github.token }}
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/iac/vultr-cli/
TAG: ${{ needs.prep.outputs.tag }}
VERSION: ${{ needs.prep.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Ensure GitHub CLI & deps
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y gh jq rsync
gh --version
- name: Check asset exists via GitHub CLI
id: has_asset
run: |
set -euo pipefail
ASSET="vultr_${TAG}_${{ matrix.asset_suffix }}"
echo "Checking asset $ASSET for tag ${TAG}"
if gh release view "${TAG}" --repo "${GH_REPO}" --json assets \
| jq -r '.assets[].name' | grep -Fxq "$ASSET"; then
echo "asset=$ASSET" >> "$GITHUB_OUTPUT"
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Asset $ASSET not found for ${TAG}, will skip."
fi
- name: Download asset
if: steps.has_asset.outputs.exists == 'true'
run: |
set -euo pipefail
mkdir -p "releases/${VERSION}"
gh release download "${TAG}" \
--repo "${GH_REPO}" \
--pattern "${{ steps.has_asset.outputs.asset }}" \
--dir "releases/${VERSION}"
- name: Init SSH
if: steps.has_asset.outputs.exists == 'true'
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync this asset to remote
if: steps.has_asset.outputs.exists == 'true'
run: |
set -euo pipefail
REMOTE_DIR="${REMOTE_ROOT%/}/${VERSION}"
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "mkdir -p '${REMOTE_DIR}'"
echo "Rsync releases/${VERSION}/${{ steps.has_asset.outputs.asset }} -> ${VPS_HOST}:${REMOTE_DIR}/"
rsync -av -e "ssh -i ~/.ssh/id_rsa" \
"releases/${VERSION}/${{ steps.has_asset.outputs.asset }}" "${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 10) (${{ matrix.vps_host }})
needs: [prep, sync-one]
if: needs.prep.outputs.exists == 'false'
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/iac/vultr-cli/
steps:
- name: Init SSH
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 10)
run: |
set -euo pipefail
ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" bash -lc '
set -euo pipefail
cd "'"${REMOTE_ROOT}"'" || exit 0
keep=10
mapfile -t all < <(ls -1 | grep -E "^[0-9]+\\.[0-9]+\\.[0-9]+$" | sort -V -r || true)
if [ "${#all[@]}" -le "$keep" ]; then
echo "Nothing to prune. Count=${#all[@]}"
exit 0
fi
to_delete=("${all[@]:keep}")
echo "Pruning old versions: ${to_delete[*]}"
for d in "${to_delete[@]}"; do
rm -rf -- "$d"
done
'

View File

@ -15,14 +15,19 @@ concurrency:
jobs:
prep:
name: Resolve latest tag & remote check
name: Resolve latest tag & remote check (${{ matrix.vps_host }})
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
GH_REPO: XTLS/Xray-core
GH_TOKEN: ${{ github.token }} # 用内置 token无需自建 PAT
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/xray-core
outputs:
tag: ${{ steps.latest.outputs.tag }}
@ -43,19 +48,7 @@ jobs:
id: latest
run: |
set -euo pipefail
gh api -H "Accept: application/vnd.github+json" \
"/repos/${GH_REPO}/releases?per_page=100" \
| jq -r '.[].tag_name' \
| grep -E '^v25(\.|$)' \
| sort -V -r \
| head -n 1 > /tmp/tag.txt
if [ ! -s /tmp/tag.txt ]; then
echo "No v25.* tags found." >&2
exit 1
fi
TAG=$(cat /tmp/tag.txt)
TAG=$(./scripts/resolve_github_repo_release.sh "${GH_REPO}" '^v25(\.|$)' 'v25.*')
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "Latest tag: $TAG"
@ -82,20 +75,23 @@ jobs:
fi
sync-one:
name: Sync ${{ matrix.asset }} for ${{ needs.prep.outputs.tag }}
name: Sync ${{ matrix.asset }} for ${{ needs.prep.outputs.tag }} (${{ matrix.vps_host }})
needs: prep
if: needs.prep.outputs.exists == 'false' # 远端已存在则整个矩阵跳过
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
asset: [ "Xray-linux-64.zip", "Xray-macos-64.zip", "Xray-windows-64.zip" ]
env:
GH_REPO: XTLS/Xray-core
GH_TOKEN: ${{ github.token }} # 继续使用内置 token
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/xray-core
TAG: ${{ needs.prep.outputs.tag }}
steps:
@ -152,14 +148,19 @@ jobs:
"releases/${TAG}/${{ matrix.asset }}" "${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 10 v25.*)
name: Remote retention (keep latest 10 v25.*) (${{ matrix.vps_host }})
needs: [prep, sync-one]
if: needs.prep.outputs.exists == 'false' # 只有新增版本时才清理
runs-on: ubuntu-latest
strategy:
matrix:
vps_host:
- cn-homepage.svc.plus
- global-homepage.svc.plus
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }}
VPS_HOST: ${{ secrets.VPS_HOST }}
VPS_HOST: ${{ matrix.vps_host }}
REMOTE_ROOT: /data/update-server/xray-core
steps:
- name: Init SSH

3
.gitignore vendored
View File

@ -22,3 +22,6 @@
.build-harness
build-harness
dist/bin/*
# Packaged Helm charts generated in-place during release work
oci/charts/apps/*/charts/*.tgz

View File

@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
ingress_ip=$(hostname -I | awk '{print $1}')
cat > values.yaml <<EOF
service:
type: NodePort
externalIPs:
- ${ingress_ip}
http:
enabled: true
servicePort: 80
tls:
servicePort: 443
nodePort: 30443
# 仅部署网关数据面;不装 etcd、不装 ingress-controller
etcd:
enabled: false
ingress-controller:
enabled: false
apisix:
deployment:
# standalone = 无 etcd本地文件/ConfigMap 驱动;同时禁用 Admin API
mode: standalone
role: data_plane
role_data_plane:
# 需要时可用 yaml/json 作配置源;此处先保留 yaml
config_provider: yaml
# 基本特性可按需开启
ssl:
enabled: true
prometheus:
enabled: true
# (可选加固)即便 Helm 仍创建了 Admin Servicestandalone 下也不会有 Admin 监听;
# 这里进一步把 Admin 访问白名单收紧,避免误触。
admin:
allow:
ipList:
- 127.0.0.1/32
EOF
helm repo add apisix https://charts.apiseven.com || true
helm repo update
kubectl get ns ingress >/dev/null 2>&1 || kubectl create ns ingress
# 只安装 APISIX 网关(无 etcd / 无 admin / 无 ingress-controller
helm upgrade --install apisix apisix/apisix \
--namespace ingress \
-f values.yaml

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="AutoGen Studio"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
OFFLINE_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
COMPOSE_FILE="${OFFLINE_ROOT}/docker-compose.yaml"
IMAGES_DIR="${OFFLINE_ROOT}/images"
IMAGE_LOAD_TOOL="${IMAGE_LOAD_TOOL:-docker}"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
load_images() {
if ! command_exists "${IMAGE_LOAD_TOOL}"; then
echo "Error: image loader '${IMAGE_LOAD_TOOL}' not found in PATH" >&2
exit 1
fi
if [ ! -d "${IMAGES_DIR}" ]; then
echo "No images directory found at ${IMAGES_DIR}. Skipping image load." >&2
return
fi
shopt -s nullglob
local tarball
for tarball in "${IMAGES_DIR}"/*.tar; do
echo "Loading container images from ${tarball}"
"${IMAGE_LOAD_TOOL}" load -i "${tarball}"
done
shopt -u nullglob
}
compose() {
if command_exists docker && docker compose version >/dev/null 2>&1; then
docker compose "$@"
elif command_exists docker-compose; then
docker-compose "$@"
else
echo "Error: docker compose plugin or docker-compose binary is required" >&2
exit 1
fi
}
usage() {
cat <<USAGE
Usage: $(basename "$0") [command]
Commands:
up Load images (if available) and start ${APP_NAME}
down Stop ${APP_NAME}
load-images Only load container images from the images/ directory
status Show status of the compose application
Environment variables:
IMAGE_LOAD_TOOL Override the container image loader (default: docker)
COMPOSE_FILE Override docker compose file path (default: ${COMPOSE_FILE})
USAGE
}
cmd=${1:-up}
case "${cmd}" in
up)
load_images
compose -f "${COMPOSE_FILE}" up -d
;;
down)
compose -f "${COMPOSE_FILE}" down
;;
load-images)
load_images
;;
status)
compose -f "${COMPOSE_FILE}" ps
;;
-h|--help|help)
usage
;;
*)
echo "Unknown command: ${cmd}" >&2
usage
exit 1
;;
esac

View File

@ -0,0 +1,18 @@
version: "3.8"
services:
autogen-studio:
image: autogenstudio/autogen-studio:__AUTOGEN_TAG__
container_name: autogen-studio
restart: unless-stopped
ports:
- "9090:9090"
environment:
AUTOGEN_STUDIO_HOST: 0.0.0.0
AUTOGEN_STUDIO_PORT: 9090
AUTOGEN_STUDIO_LOG_LEVEL: info
volumes:
- autogen-data:/data
volumes:
autogen-data:

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="Dify"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
OFFLINE_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
COMPOSE_FILE="${OFFLINE_ROOT}/docker-compose.yaml"
IMAGES_DIR="${OFFLINE_ROOT}/images"
IMAGE_LOAD_TOOL="${IMAGE_LOAD_TOOL:-docker}"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
load_images() {
if ! command_exists "${IMAGE_LOAD_TOOL}"; then
echo "Error: image loader '${IMAGE_LOAD_TOOL}' not found in PATH" >&2
exit 1
fi
if [ ! -d "${IMAGES_DIR}" ]; then
echo "No images directory found at ${IMAGES_DIR}. Skipping image load." >&2
return
fi
shopt -s nullglob
local tarball
for tarball in "${IMAGES_DIR}"/*.tar; do
echo "Loading container images from ${tarball}"
"${IMAGE_LOAD_TOOL}" load -i "${tarball}"
done
shopt -u nullglob
}
compose() {
if command_exists docker && docker compose version >/dev/null 2>&1; then
docker compose "$@"
elif command_exists docker-compose; then
docker-compose "$@"
else
echo "Error: docker compose plugin or docker-compose binary is required" >&2
exit 1
fi
}
usage() {
cat <<USAGE
Usage: $(basename "$0") [command]
Commands:
up Load images (if available) and start ${APP_NAME}
down Stop ${APP_NAME}
load-images Only load container images from the images/ directory
status Show status of the compose application
Environment variables:
IMAGE_LOAD_TOOL Override the container image loader (default: docker)
COMPOSE_FILE Override docker compose file path (default: ${COMPOSE_FILE})
USAGE
}
cmd=${1:-up}
case "${cmd}" in
up)
load_images
compose -f "${COMPOSE_FILE}" up -d
;;
down)
compose -f "${COMPOSE_FILE}" down
;;
load-images)
load_images
;;
status)
compose -f "${COMPOSE_FILE}" ps
;;
-h|--help|help)
usage
;;
*)
echo "Unknown command: ${cmd}" >&2
usage
exit 1
;;
esac

View File

@ -0,0 +1,72 @@
version: "3.8"
services:
postgres:
image: postgres:__POSTGRES_TAG__
container_name: dify-postgres
restart: unless-stopped
environment:
POSTGRES_DB: dify
POSTGRES_USER: dify
POSTGRES_PASSWORD: dify
volumes:
- dify-postgres:/var/lib/postgresql/data
redis:
image: redis:__REDIS_TAG__
container_name: dify-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- dify-redis:/data
dify-api:
image: langgenius/dify-api:__DIFY_TAG__
container_name: dify-api
restart: unless-stopped
depends_on:
- postgres
- redis
environment:
DATABASE_URL: postgresql+psycopg://dify:dify@postgres:5432/dify
REDIS_URL: redis://redis:6379/0
WEB_URL: http://localhost:8080
WORKER_QUEUE_BROKER_URL: redis://redis:6379/1
WORKER_QUEUE_BACKEND_URL: redis://redis:6379/2
volumes:
- dify-storage:/app/storage
dify-worker:
image: langgenius/dify-worker:__DIFY_TAG__
container_name: dify-worker
restart: unless-stopped
depends_on:
- redis
- dify-api
environment:
REDIS_URL: redis://redis:6379/1
WORKER_QUEUE_BACKEND_URL: redis://redis:6379/2
API_URL: http://dify-api:5001
dify-web:
image: langgenius/dify-web:__DIFY_TAG__
container_name: dify-web
restart: unless-stopped
depends_on:
- dify-api
environment:
VITE_API_URL: http://dify-nginx
VITE_APP_ENV: production
dify-nginx:
image: langgenius/dify-nginx:__DIFY_TAG__
container_name: dify-nginx
restart: unless-stopped
ports:
- "8080:80"
depends_on:
- dify-api
- dify-web
environment:
API_HOST: dify-api:5001
WEB_HOST: dify-web:3000
volumes:
dify-postgres:
dify-redis:
dify-storage:

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="Flowise"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
OFFLINE_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
COMPOSE_FILE="${OFFLINE_ROOT}/docker-compose.yaml"
IMAGES_DIR="${OFFLINE_ROOT}/images"
IMAGE_LOAD_TOOL="${IMAGE_LOAD_TOOL:-docker}"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
load_images() {
if ! command_exists "${IMAGE_LOAD_TOOL}"; then
echo "Error: image loader '${IMAGE_LOAD_TOOL}' not found in PATH" >&2
exit 1
fi
if [ ! -d "${IMAGES_DIR}" ]; then
echo "No images directory found at ${IMAGES_DIR}. Skipping image load." >&2
return
fi
shopt -s nullglob
local tarball
for tarball in "${IMAGES_DIR}"/*.tar; do
echo "Loading container images from ${tarball}"
"${IMAGE_LOAD_TOOL}" load -i "${tarball}"
done
shopt -u nullglob
}
compose() {
if command_exists docker && docker compose version >/dev/null 2>&1; then
docker compose "$@"
elif command_exists docker-compose; then
docker-compose "$@"
else
echo "Error: docker compose plugin or docker-compose binary is required" >&2
exit 1
fi
}
usage() {
cat <<USAGE
Usage: $(basename "$0") [command]
Commands:
up Load images (if available) and start ${APP_NAME}
down Stop ${APP_NAME}
load-images Only load container images from the images/ directory
status Show status of the compose application
Environment variables:
IMAGE_LOAD_TOOL Override the container image loader (default: docker)
COMPOSE_FILE Override docker compose file path (default: ${COMPOSE_FILE})
USAGE
}
cmd=${1:-up}
case "${cmd}" in
up)
load_images
compose -f "${COMPOSE_FILE}" up -d
;;
down)
compose -f "${COMPOSE_FILE}" down
;;
load-images)
load_images
;;
status)
compose -f "${COMPOSE_FILE}" ps
;;
-h|--help|help)
usage
;;
*)
echo "Unknown command: ${cmd}" >&2
usage
exit 1
;;
esac

View File

@ -0,0 +1,19 @@
version: "3.8"
services:
flowise:
image: flowiseai/flowise:__FLOWISE_TAG__
container_name: flowise-app
restart: unless-stopped
ports:
- "3000:3000"
environment:
PORT: 3000
FLOWISE_USERNAME: admin
FLOWISE_PASSWORD: changeme
DATABASE_PATH: /data/flowise.sqlite
volumes:
- flowise-data:/data
volumes:
flowise-data:

View File

@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -euo pipefail
NODE_NAME=$(hostname)
kubectl label nodes "${NODE_NAME}" ingress-node=true --overwrite
cat <<'YEOF' | kubectl apply -f -
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: kong
annotations:
konghq.com/gatewayclass-unmanaged: 'true'
spec:
controllerName: konghq.com/kic-gateway-controller
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: default
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo-gateway
namespace: default
annotations:
konghq.com/publish-service: kong/kong-gateway-proxy
spec:
gatewayClassName: kong
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: "example.com"
tls:
mode: Terminate
certificateRefs:
- name: example-tls
allowedRoutes:
namespaces:
from: All
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: demo-route
namespace: default
spec:
parentRefs:
- name: demo-gateway
namespace: default
hostnames:
- example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: nginx-svc
port: 80
YEOF
EXTERNAL_IP=$(hostname -I | awk '{print $1}')
curl -ksv https://example.com --resolve example.com:443:${EXTERNAL_IP}

View File

@ -10,14 +10,21 @@ helm repo update
cat > kong-values.yaml <<'VEOF'
kong:
secretVolumes:
- onwalk-tls
- example-tls
env:
ssl_cert: /etc/secrets/onwalk-tls/tls.crt
ssl_cert_key: /etc/secrets/onwalk-tls/tls.key
ssl_cert: /etc/secrets/example-tls/tls.crt
ssl_cert_key: /etc/secrets/example-tls/tls.key
VEOF
kubectl create ns kong || true
kubectl create secret tls onwalk-tls --cert=/etc/ssl/onwalk.net.pem --key=/etc/ssl/onwalk.net.key -n kong
# Generate self-signed certificate for example.com
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-subj "/CN=example.com" \
-keyout example.com.key \
-out example.com.pem
kubectl create secret tls example-tls --cert=example.com.pem --key=example.com.key -n kong
helm upgrade --install kong kong/ingress -n kong --create-namespace -f kong-values.yaml
# Expose Kong proxy via NodePort and external IP
@ -32,132 +39,25 @@ kubectl patch svc kong-gateway-proxy -n kong \
"targetPort": 8000,
"protocol": "TCP",
"name": "http",
"nodePort": 80
"nodePort": 30080
},
{
"port": 443,
"targetPort": 8443,
"protocol": "TCP",
"name": "https",
"nodePort": 443
"nodePort": 30443
}
]
}
}'
EXTERNAL_IP=$(hostname -I | awk '{print $1}')
kubectl patch svc kong-gateway-proxy -n kong \
--type='merge' \
-p '{
"spec": {
"externalIPs": [
"47.120.61.35"
]
}
}'
-p "{\"spec\": {\"externalIPs\": [\"${EXTERNAL_IP}\"]}}"
NODE_NAME=$(hostname)
kubectl patch deployment kong-gateway -n kong \
--type='merge' \
-p '{
"spec": {
"template": {
"spec": {
"nodeName": "icp-aliyun.svc.plus"
}
}
}
}'
# Configure GatewayClass and example application
cat <<'YEOF' | kubectl apply -f -
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: kong
annotations:
konghq.com/gatewayclass-unmanaged: 'true'
spec:
controllerName: konghq.com/kic-gateway-controller
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: default
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo-gateway
namespace: default
annotations:
konghq.com/publish-service: kong/kong-gateway-proxy
spec:
gatewayClassName: kong
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: "demo.onwalk.net"
tls:
mode: Terminate
certificateRefs:
- name: onwalk-tls
allowedRoutes:
namespaces:
from: All
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: demo-route
namespace: default
spec:
parentRefs:
- name: demo-gateway
namespace: default
hostnames:
- demo.onwalk.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: nginx-svc
port: 80
YEOF
kubectl label nodes icp-aliyun.svc.plus ingress-node=true
curl -ksv https://demo.onwalk.net/ --resolve demo.onwalk.net:443:172.30.0.10
-p "{\"spec\": {\"template\": {\"spec\": {\"nodeName\": \"${NODE_NAME}\"}}}}"

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="n8n"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
OFFLINE_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
COMPOSE_FILE="${OFFLINE_ROOT}/docker-compose.yaml"
IMAGES_DIR="${OFFLINE_ROOT}/images"
IMAGE_LOAD_TOOL="${IMAGE_LOAD_TOOL:-docker}"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
load_images() {
if ! command_exists "${IMAGE_LOAD_TOOL}"; then
echo "Error: image loader '${IMAGE_LOAD_TOOL}' not found in PATH" >&2
exit 1
fi
if [ ! -d "${IMAGES_DIR}" ]; then
echo "No images directory found at ${IMAGES_DIR}. Skipping image load." >&2
return
fi
shopt -s nullglob
local tarball
for tarball in "${IMAGES_DIR}"/*.tar; do
echo "Loading container images from ${tarball}"
"${IMAGE_LOAD_TOOL}" load -i "${tarball}"
done
shopt -u nullglob
}
compose() {
if command_exists docker && docker compose version >/dev/null 2>&1; then
docker compose "$@"
elif command_exists docker-compose; then
docker-compose "$@"
else
echo "Error: docker compose plugin or docker-compose binary is required" >&2
exit 1
fi
}
usage() {
cat <<USAGE
Usage: $(basename "$0") [command]
Commands:
up Load images (if available) and start ${APP_NAME}
down Stop ${APP_NAME}
load-images Only load container images from the images/ directory
status Show status of the compose application
Environment variables:
IMAGE_LOAD_TOOL Override the container image loader (default: docker)
COMPOSE_FILE Override docker compose file path (default: ${COMPOSE_FILE})
USAGE
}
cmd=${1:-up}
case "${cmd}" in
up)
load_images
compose -f "${COMPOSE_FILE}" up -d
;;
down)
compose -f "${COMPOSE_FILE}" down
;;
load-images)
load_images
;;
status)
compose -f "${COMPOSE_FILE}" ps
;;
-h|--help|help)
usage
;;
*)
echo "Unknown command: ${cmd}" >&2
usage
exit 1
;;
esac

View File

@ -0,0 +1,37 @@
version: "3.8"
services:
postgres:
image: postgres:__POSTGRES_TAG__
container_name: n8n-postgres
restart: unless-stopped
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: n8n
volumes:
- n8n-postgres:/var/lib/postgresql/data
n8n:
image: n8nio/n8n:__N8N_TAG__
container_name: n8n-app
restart: unless-stopped
depends_on:
- postgres
ports:
- "5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: n8n
N8N_BASIC_AUTH_ACTIVE: "true"
N8N_BASIC_AUTH_USER: admin
N8N_BASIC_AUTH_PASSWORD: changeme
volumes:
- n8n-data:/home/node/.n8n
volumes:
n8n-postgres:
n8n-data:

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="RAGFlow"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
OFFLINE_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
COMPOSE_FILE="${OFFLINE_ROOT}/docker-compose.yaml"
IMAGES_DIR="${OFFLINE_ROOT}/images"
IMAGE_LOAD_TOOL="${IMAGE_LOAD_TOOL:-docker}"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
load_images() {
if ! command_exists "${IMAGE_LOAD_TOOL}"; then
echo "Error: image loader '${IMAGE_LOAD_TOOL}' not found in PATH" >&2
exit 1
fi
if [ ! -d "${IMAGES_DIR}" ]; then
echo "No images directory found at ${IMAGES_DIR}. Skipping image load." >&2
return
fi
shopt -s nullglob
local tarball
for tarball in "${IMAGES_DIR}"/*.tar; do
echo "Loading container images from ${tarball}"
"${IMAGE_LOAD_TOOL}" load -i "${tarball}"
done
shopt -u nullglob
}
compose() {
if command_exists docker && docker compose version >/dev/null 2>&1; then
docker compose "$@"
elif command_exists docker-compose; then
docker-compose "$@"
else
echo "Error: docker compose plugin or docker-compose binary is required" >&2
exit 1
fi
}
usage() {
cat <<USAGE
Usage: $(basename "$0") [command]
Commands:
up Load images (if available) and start ${APP_NAME}
down Stop ${APP_NAME}
load-images Only load container images from the images/ directory
status Show status of the compose application
Environment variables:
IMAGE_LOAD_TOOL Override the container image loader (default: docker)
COMPOSE_FILE Override docker compose file path (default: ${COMPOSE_FILE})
USAGE
}
cmd=${1:-up}
case "${cmd}" in
up)
load_images
compose -f "${COMPOSE_FILE}" up -d
;;
down)
compose -f "${COMPOSE_FILE}" down
;;
load-images)
load_images
;;
status)
compose -f "${COMPOSE_FILE}" ps
;;
-h|--help|help)
usage
;;
*)
echo "Unknown command: ${cmd}" >&2
usage
exit 1
;;
esac

View File

@ -0,0 +1,37 @@
version: "3.8"
services:
postgres:
image: postgres:__POSTGRES_TAG__
container_name: ragflow-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ragflow
POSTGRES_USER: ragflow
POSTGRES_PASSWORD: ragflow
volumes:
- ragflow-postgres:/var/lib/postgresql/data
redis:
image: redis:__REDIS_TAG__
container_name: ragflow-redis
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes"]
volumes:
- ragflow-redis:/data
ragflow:
image: ragflow/ragflow:__RAGFLOW_TAG__
container_name: ragflow-app
restart: unless-stopped
depends_on:
- postgres
- redis
ports:
- "3001:3000"
environment:
DATABASE_URL: postgresql://ragflow:ragflow@postgres:5432/ragflow
REDIS_URL: redis://redis:6379/0
SECRET_KEY: changeme
volumes:
ragflow-postgres:
ragflow-redis:

29
oci/base/cuda/Makefile Normal file
View File

@ -0,0 +1,29 @@
ORG ?= your-org
REGISTRY ?= ghcr.io/$(ORG)/model-serving
VLLM_TAG ?= cuda12
SGLANG_TAG ?= cuda12
OLLAMA_TAG ?= latest
.PHONY: docker-build docker-push docker-build-vllm docker-build-sglang docker-build-ollama docker-push-vllm docker-push-sglang docker-push-ollama
docker-build: docker-build-vllm docker-build-sglang docker-build-ollama
docker-push: docker-push-vllm docker-push-sglang docker-push-ollama
docker-build-vllm:
docker build -t $(REGISTRY)/vllm:$(VLLM_TAG) vLLM
docker-build-sglang:
docker build -t $(REGISTRY)/sglang:$(SGLANG_TAG) SGLang
docker-build-ollama:
docker build -t $(REGISTRY)/ollama:$(OLLAMA_TAG) Ollama
docker-push-vllm:
docker push $(REGISTRY)/vllm:$(VLLM_TAG)
docker-push-sglang:
docker push $(REGISTRY)/sglang:$(SGLANG_TAG)
docker-push-ollama:
docker push $(REGISTRY)/ollama:$(OLLAMA_TAG)

View File

@ -0,0 +1,28 @@
# Ollama runtime image that leans on host NVIDIA drivers via container toolkit
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates unzip gnupg \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://ollama.com/download/ollama-linux-amd64.tgz | tar -xz -C /usr/local/bin \
&& chmod +x /usr/local/bin/ollama
RUN useradd -m -u 10001 app && mkdir -p /home/app/.ollama && chown -R app:app /home/app
USER app
ENV OLLAMA_HOST=0.0.0.0:11434 \
OLLAMA_MODELS=/home/app/.ollama/models \
OLLAMA_MODEL="phi3:latest"
EXPOSE 11434
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s CMD curl -fsS http://127.0.0.1:11434/api/tags || exit 1
ENTRYPOINT ["bash","-lc","set -euo pipefail; \
ollama serve & \
for i in $(seq 1 30); do sleep 1; curl -fsS http://127.0.0.1:11434/api/tags && break || true; done; \
ollama pull \"$OLLAMA_MODEL\" || true; \
wait -n"]

View File

@ -0,0 +1,30 @@
# CUDA 12.1 runtime base for SGLang
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-venv python3-pip git curl ca-certificates build-essential \
&& rm -rf /var/lib/apt/lists/*
ENV PIP_NO_CACHE_DIR=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
RUN pip3 install --upgrade pip \
&& pip3 install --extra-index-url https://download.pytorch.org/whl/cu121 \
torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 \
&& pip3 install sglang==0.2.3 uvicorn fastapi
EXPOSE 30000
ENV SGLANG_MODEL="Qwen/Qwen2-7B-Instruct" \
SGLANG_PORT=30000 \
SGLANG_ARGS="--tp 1 --context-length 8192"
RUN useradd -m -u 10001 app && mkdir -p /models && chown -R app:app /models
USER app
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s CMD curl -fsS http://127.0.0.1:${SGLANG_PORT}/v1/models || exit 1
ENTRYPOINT ["bash","-lc","python3 -m sglang.launch_server --model \"$SGLANG_MODEL\" --port $SGLANG_PORT --trust-remote-code --enable-openai-compatible-api $SGLANG_ARGS"]

View File

@ -0,0 +1,33 @@
# CUDA 12.1 + cuDNN8 runtime base — tested with recent PyTorch wheels
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
ARG DEBIAN_FRONTEND=noninteractive
# System deps + Python
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-venv python3-pip git curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ENV PIP_NO_CACHE_DIR=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# Install CUDA-enabled PyTorch + vLLM
RUN pip3 install --upgrade pip \
&& pip3 install --extra-index-url https://download.pytorch.org/whl/cu121 \
torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 \
&& pip3 install vllm==0.5.2 uvicorn fastapi
EXPOSE 8000
ENV MODEL_PATH="meta-llama/Meta-Llama-3-8B-Instruct" \
VLLM_ARGS="--max-model-len 8192 --gpu-memory-utilization 0.9" \
HF_HOME=/models/.cache \
VLLM_WORKER_USE_GRAPH_EXECUTOR=1
RUN useradd -m -u 10001 app && mkdir -p /models && chown -R app:app /models
USER app
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s CMD curl -fsS http://127.0.0.1:8000/v1/models || exit 1
ENTRYPOINT ["bash","-lc","vllm serve \"$MODEL_PATH\" --port 8000 --api-key dummy $VLLM_ARGS"]

15
oci/charts/README.md Normal file
View File

@ -0,0 +1,15 @@
# OCI Charts
This repository stores reusable Helm charts published to `ghcr.io/x-evor`.
## Layout
- `apps/app-service`: reusable runtime chart for application services
- `postgresql`: PostgreSQL service chart with optional `stunnel` server/client
- `observability`: observability composition chart for server and agent components
## Release Model
- Registry: `oci://ghcr.io/x-evor`
- Each chart is versioned independently
- Runtime image tags are managed by GitOps values rather than chart versions

View File

@ -0,0 +1,6 @@
dependencies:
- name: app-service
repository: file://../app-service
version: 0.1.0
digest: sha256:29102607dbddc890cc60258ec869b75fd9e5f995fc8c5ee1f1a31b046b80e407
generated: "2026-04-02T17:55:26.238504+08:00"

View File

@ -0,0 +1,11 @@
apiVersion: v2
name: accounts-chart
description: Accounts service chart backed by the shared app-service subchart
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: app-service
version: 0.1.0
repository: file://../app-service
alias: service

View File

@ -0,0 +1,25 @@
service:
nameOverride: accounts
containerPort: 8080
service:
port: 80
global:
existingSecretName: accounts-env
repository: ghcr.io/x-evor/accounts
tag: latest
env:
PORT: "8080"
SERVICE_NAME: accounts
HEALTHCHECK_PATH: /healthz
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 30
periodSeconds: 20

View File

@ -0,0 +1,6 @@
apiVersion: v2
name: app-service
description: Reusable chart for core HTTP application services
type: application
version: 0.1.0
appVersion: "1.0.0"

View File

@ -0,0 +1,26 @@
{{- define "app-service.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "app-service.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- include "app-service.name" . -}}
{{- end -}}
{{- end -}}
{{- define "app-service.labels" -}}
app.kubernetes.io/name: {{ include "app-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
{{- end -}}
{{- define "app-service.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "app-service.fullname" .) .Values.serviceAccount.name -}}
{{- else -}}
{{- default "default" .Values.serviceAccount.name -}}
{{- end -}}
{{- end -}}

View File

@ -0,0 +1,128 @@
{{- $global := .Values.global | default dict -}}
{{- $globalRepository := $global.repository | default "" -}}
{{- $globalTag := $global.tag | default "" -}}
{{- $globalEnv := $global.env | default dict -}}
{{- $localEnv := .Values.env | default dict -}}
{{- $env := mergeOverwrite (deepCopy $globalEnv) $localEnv -}}
{{- $existingSecretName := .Values.existingSecretName | default ($global.existingSecretName | default "") -}}
{{- $imageRepository := default $globalRepository .Values.image.repository -}}
{{- $imageTag := default $globalTag .Values.image.tag -}}
{{- $globalEnvFromSecretRefs := $global.envFromSecretRefs | default list -}}
{{- $localEnvFromSecretRefs := .Values.envFromSecretRefs | default list -}}
{{- $envFromSecretRefs := concat $globalEnvFromSecretRefs $localEnvFromSecretRefs -}}
{{- $globalExternalServices := index $global "external-service" | default list -}}
{{- $localExternalServices := index .Values "external-service" | default list -}}
{{- $externalServices := concat $globalExternalServices $localExternalServices -}}
{{- if $externalServices -}}
{{- $_ := set $env "EXTERNAL_SERVICES" (join "," $externalServices) -}}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "app-service.fullname" . }}
labels:
{{- include "app-service.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
revisionHistoryLimit: 3
strategy:
type: {{ .Values.strategy.type }}
rollingUpdate:
maxUnavailable: {{ .Values.strategy.rollingUpdate.maxUnavailable }}
maxSurge: {{ .Values.strategy.rollingUpdate.maxSurge }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "app-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
{{- include "app-service.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
annotations:
{{- if and .Values.reloader.enabled $existingSecretName }}
secret.reloader.stakater.com/reload: {{ default $existingSecretName .Values.reloader.secretMatch | quote }}
{{- end }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
serviceAccountName: {{ include "app-service.serviceAccountName" . }}
{{- with .Values.image.pullSecrets }}
imagePullSecrets:
{{- range . }}
- name: {{ . }}
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.initContainers }}
initContainers:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: app
image: "{{ $imageRepository }}:{{ $imageTag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with .Values.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.workingDir }}
workingDir: {{ .Values.workingDir | quote }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.containerPort }}
{{- if $env }}
env:
{{- range $key := keys $env | sortAlpha }}
- name: {{ $key }}
value: {{ index $env $key | quote }}
{{- end }}
{{- end }}
{{- if or $existingSecretName $envFromSecretRefs }}
envFrom:
{{- if $existingSecretName }}
- secretRef:
name: {{ $existingSecretName }}
{{- end }}
{{- range $envFromSecretRefs }}
- secretRef:
name: {{ . }}
{{- end }}
{{- end }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
{{- with .Values.extraContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,35 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "app-service.fullname" . }}
labels:
{{- include "app-service.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- with .Values.ingress.tls }}
tls:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ default "Prefix" .pathType }}
backend:
service:
name: {{ include "app-service.fullname" $ }}
port:
number: {{ default $.Values.service.port .servicePort }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,14 @@
{{- if .Values.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "app-service.fullname" . }}
labels:
{{- include "app-service.labels" . | nindent 4 }}
spec:
minAvailable: {{ .Values.pdb.minAvailable }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "app-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "app-service.fullname" . }}
labels:
{{- include "app-service.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
selector:
app.kubernetes.io/name: {{ include "app-service.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http

View File

@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "app-service.serviceAccountName" . }}
labels:
{{- include "app-service.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,94 @@
nameOverride: ""
fullnameOverride: ""
replicaCount: 1
image:
repository: ""
tag: ""
pullPolicy: IfNotPresent
pullSecrets: []
command: []
args: []
workingDir: ""
containerPort: 8080
service:
port: 80
type: ClusterIP
annotations: {}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
podLabels: {}
podAnnotations: {}
serviceAccount:
create: false
name: ""
annotations: {}
global:
repository: ""
tag: ""
env: {}
existingSecretName: ""
external-service: []
envFromSecretRefs: []
# Local overrides remain available for backwards compatibility.
env: {}
existingSecretName: ""
external-service: []
envFromSecretRefs: []
initContainers: []
extraContainers: []
volumeMounts: []
volumes: []
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 30
periodSeconds: 20
pdb:
enabled: true
minAvailable: 1
ingress:
enabled: false
className: ""
annotations: {}
tls: []
hosts: []
nodeSelector: {}
tolerations: []
affinity: {}
reloader:
enabled: true
secretMatch: ""

View File

@ -0,0 +1,6 @@
dependencies:
- name: app-service
repository: file://../app-service
version: 0.1.0
digest: sha256:29102607dbddc890cc60258ec869b75fd9e5f995fc8c5ee1f1a31b046b80e407
generated: "2026-04-02T17:55:26.213216+08:00"

View File

@ -0,0 +1,11 @@
apiVersion: v2
name: console-chart
description: Console service chart backed by the shared app-service subchart
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: app-service
version: 0.1.0
repository: file://../app-service
alias: service

View File

@ -0,0 +1,31 @@
service:
nameOverride: console
containerPort: 3000
service:
port: 80
global:
existingSecretName: console-env
repository: ghcr.io/x-evor/console
tag: latest
env:
PORT: "3000"
SERVICE_NAME: console
HEALTHCHECK_PATH: /
DOCS_SERVICE_URL: https://docs.svc.plus
NEXT_PUBLIC_DOCS_BASE_URL: https://docs.svc.plus
external-service:
- docs.svc.plus
- xworkmate.svc.plus
- openclaw-gateway.svc.plus
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 20

View File

@ -0,0 +1,6 @@
dependencies:
- name: app-service
repository: file://../app-service
version: 0.1.0
digest: sha256:29102607dbddc890cc60258ec869b75fd9e5f995fc8c5ee1f1a31b046b80e407
generated: "2026-04-02T17:55:26.26398+08:00"

View File

@ -0,0 +1,11 @@
apiVersion: v2
name: rag-server-chart
description: RAG server chart backed by the shared app-service subchart
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: app-service
version: 0.1.0
repository: file://../app-service
alias: service

View File

@ -0,0 +1,25 @@
service:
nameOverride: rag-server
containerPort: 8080
service:
port: 80
global:
existingSecretName: rag-server-env
repository: ghcr.io/x-evor/rag-server
tag: latest
env:
PORT: "8080"
SERVICE_NAME: rag-server
HEALTHCHECK_PATH: /healthz
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 30
periodSeconds: 20

View File

@ -0,0 +1,18 @@
apiVersion: v2
name: observability
description: Observability composition chart for server and agent components
type: application
version: 0.1.0
appVersion: "1.0.0"
keywords:
- observability
- prometheus
- victoria
- grafana
- otel
home: https://github.com/cloud-neutral-toolkit/observability.svc.plus
sources:
- https://github.com/cloud-neutral-toolkit/observability.svc.plus
maintainers:
- name: Cloud-Neutral Toolkit
email: admin@svc.plus

View File

@ -0,0 +1,4 @@
{{- range .Values.extraObjects }}
---
{{ toYaml . }}
{{- end }}

View File

@ -0,0 +1,26 @@
{{- if .Values.server.grafana.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Values.server.grafana.releaseName }}
namespace: {{ .Values.namespaces.observability }}
spec:
interval: 10m0s
releaseName: {{ .Values.server.grafana.releaseName }}
chart:
spec:
chart: {{ .Values.server.grafana.chart.name }}
version: {{ .Values.server.grafana.chart.version | quote }}
sourceRef:
kind: {{ .Values.server.grafana.sourceRef.kind }}
name: {{ .Values.server.grafana.sourceRef.name }}
namespace: {{ .Values.server.grafana.sourceRef.namespace }}
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
{{- toYaml .Values.server.grafana.values | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,26 @@
{{- if .Values.agent.nodeExporter.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Values.agent.nodeExporter.releaseName }}
namespace: {{ .Values.namespaces.observability }}
spec:
interval: 10m0s
releaseName: {{ .Values.agent.nodeExporter.releaseName }}
chart:
spec:
chart: {{ .Values.agent.nodeExporter.chart.name }}
version: {{ .Values.agent.nodeExporter.chart.version | quote }}
sourceRef:
kind: {{ .Values.agent.nodeExporter.sourceRef.kind }}
name: {{ .Values.agent.nodeExporter.sourceRef.name }}
namespace: {{ .Values.agent.nodeExporter.sourceRef.namespace }}
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
{{- toYaml .Values.agent.nodeExporter.values | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,26 @@
{{- if .Values.server.otelConnector.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Values.server.otelConnector.releaseName }}
namespace: {{ .Values.namespaces.observability }}
spec:
interval: 10m0s
releaseName: {{ .Values.server.otelConnector.releaseName }}
chart:
spec:
chart: {{ .Values.server.otelConnector.chart.name }}
version: {{ .Values.server.otelConnector.chart.version | quote }}
sourceRef:
kind: {{ .Values.server.otelConnector.sourceRef.kind }}
name: {{ .Values.server.otelConnector.sourceRef.name }}
namespace: {{ .Values.server.otelConnector.sourceRef.namespace }}
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
{{- toYaml .Values.server.otelConnector.values | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,65 @@
{{- if .Values.agent.processExporter.enabled }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.agent.processExporter.serviceAccountName }}
namespace: {{ .Values.namespaces.observability }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: process-exporter-config
namespace: {{ .Values.namespaces.observability }}
data:
config.yaml: |
{{- .Values.agent.processExporter.config | nindent 4 }}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: process-exporter
namespace: {{ .Values.namespaces.observability }}
spec:
selector:
matchLabels:
app.kubernetes.io/name: process-exporter
template:
metadata:
labels:
app.kubernetes.io/name: process-exporter
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "{{ .Values.agent.processExporter.port }}"
spec:
serviceAccountName: {{ .Values.agent.processExporter.serviceAccountName }}
hostPID: true
containers:
- name: process-exporter
image: "{{ .Values.agent.processExporter.image.repository }}:{{ .Values.agent.processExporter.image.tag }}"
imagePullPolicy: {{ .Values.agent.processExporter.image.pullPolicy }}
args:
- --procfs
- /host/proc
- --config.path
- /etc/process-exporter/config.yaml
- --web.listen-address=:{{ .Values.agent.processExporter.port }}
ports:
- name: metrics
containerPort: {{ .Values.agent.processExporter.port }}
protocol: TCP
resources:
{{- toYaml .Values.agent.processExporter.resources | nindent 12 }}
volumeMounts:
- name: config
mountPath: /etc/process-exporter
- name: proc
mountPath: /host/proc
readOnly: true
volumes:
- name: config
configMap:
name: process-exporter-config
- name: proc
hostPath:
path: /proc
{{- end }}

View File

@ -0,0 +1,26 @@
{{- if .Values.server.prometheus.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Values.server.prometheus.releaseName }}
namespace: {{ .Values.namespaces.observability }}
spec:
interval: 10m0s
releaseName: {{ .Values.server.prometheus.releaseName }}
chart:
spec:
chart: {{ .Values.server.prometheus.chart.name }}
version: {{ .Values.server.prometheus.chart.version | quote }}
sourceRef:
kind: {{ .Values.server.prometheus.sourceRef.kind }}
name: {{ .Values.server.prometheus.sourceRef.name }}
namespace: {{ .Values.server.prometheus.sourceRef.namespace }}
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
{{- toYaml .Values.server.prometheus.values | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,69 @@
{{- if .Values.agent.vector.enabled }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.agent.vector.serviceAccountName }}
namespace: {{ .Values.namespaces.observability }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: vector-agent-config
namespace: {{ .Values.namespaces.observability }}
data:
vector.yaml: |
{{- .Values.agent.vector.config | nindent 4 }}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: vector-agent
namespace: {{ .Values.namespaces.observability }}
spec:
selector:
matchLabels:
app.kubernetes.io/name: vector-agent
template:
metadata:
labels:
app.kubernetes.io/name: vector-agent
spec:
serviceAccountName: {{ .Values.agent.vector.serviceAccountName }}
containers:
- name: vector
image: "{{ .Values.agent.vector.image.repository }}:{{ .Values.agent.vector.image.tag }}"
imagePullPolicy: {{ .Values.agent.vector.image.pullPolicy }}
resources:
{{- toYaml .Values.agent.vector.resources | nindent 12 }}
volumeMounts:
- name: config
mountPath: /etc/vector
{{- if .Values.agent.vector.volume.enabled }}
- name: {{ .Values.agent.vector.volume.name }}
mountPath: {{ .Values.agent.vector.volume.mountPath }}
{{- end }}
- name: var-log
mountPath: /var/log
readOnly: true
- name: machine-id
mountPath: /etc/machine-id
readOnly: true
volumes:
- name: config
configMap:
name: vector-agent-config
{{- if .Values.agent.vector.volume.enabled }}
- name: {{ .Values.agent.vector.volume.name }}
emptyDir:
{{- if .Values.agent.vector.volume.sizeLimit }}
sizeLimit: {{ .Values.agent.vector.volume.sizeLimit }}
{{- end }}
{{- end }}
- name: var-log
hostPath:
path: /var/log
- name: machine-id
hostPath:
path: /etc/machine-id
type: File
{{- end }}

View File

@ -0,0 +1,26 @@
{{- if .Values.server.victoriaLogs.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Values.server.victoriaLogs.releaseName }}
namespace: {{ .Values.namespaces.observability }}
spec:
interval: 10m0s
releaseName: {{ .Values.server.victoriaLogs.releaseName }}
chart:
spec:
chart: {{ .Values.server.victoriaLogs.chart.name }}
version: {{ .Values.server.victoriaLogs.chart.version | quote }}
sourceRef:
kind: {{ .Values.server.victoriaLogs.sourceRef.kind }}
name: {{ .Values.server.victoriaLogs.sourceRef.name }}
namespace: {{ .Values.server.victoriaLogs.sourceRef.namespace }}
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
{{- toYaml .Values.server.victoriaLogs.values | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,26 @@
{{- if .Values.server.victoriaMetrics.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Values.server.victoriaMetrics.releaseName }}
namespace: {{ .Values.namespaces.observability }}
spec:
interval: 10m0s
releaseName: {{ .Values.server.victoriaMetrics.releaseName }}
chart:
spec:
chart: {{ .Values.server.victoriaMetrics.chart.name }}
version: {{ .Values.server.victoriaMetrics.chart.version | quote }}
sourceRef:
kind: {{ .Values.server.victoriaMetrics.sourceRef.kind }}
name: {{ .Values.server.victoriaMetrics.sourceRef.name }}
namespace: {{ .Values.server.victoriaMetrics.sourceRef.namespace }}
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
{{- toYaml .Values.server.victoriaMetrics.values | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,26 @@
{{- if .Values.server.victoriaTraces.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Values.server.victoriaTraces.releaseName }}
namespace: {{ .Values.namespaces.observability }}
spec:
interval: 10m0s
releaseName: {{ .Values.server.victoriaTraces.releaseName }}
chart:
spec:
chart: {{ .Values.server.victoriaTraces.chart.name }}
version: {{ .Values.server.victoriaTraces.chart.version | quote }}
sourceRef:
kind: {{ .Values.server.victoriaTraces.sourceRef.kind }}
name: {{ .Values.server.victoriaTraces.sourceRef.name }}
namespace: {{ .Values.server.victoriaTraces.sourceRef.namespace }}
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
{{- toYaml .Values.server.victoriaTraces.values | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,199 @@
namespaces:
observability: observability
server:
prometheus:
enabled: true
releaseName: prometheus
sourceRef:
kind: HelmRepository
name: prometheus-community
namespace: flux-system
chart:
name: prometheus
version: ">=25.0.0 <26.0.0"
values:
server:
persistentVolume:
enabled: true
size: 20Gi
prometheus-node-exporter:
enabled: false
victoriaMetrics:
enabled: true
releaseName: victoria-metrics
sourceRef:
kind: HelmRepository
name: victoria-metrics
namespace: flux-system
chart:
name: victoria-metrics-single
version: ">=0.13.0 <1.0.0"
values:
server:
persistentVolume:
enabled: true
size: 50Gi
victoriaLogs:
enabled: true
releaseName: victoria-logs
sourceRef:
kind: HelmRepository
name: victoria-metrics
namespace: flux-system
chart:
name: victoria-logs-single
version: ">=0.9.0 <1.0.0"
values:
server:
persistentVolume:
enabled: true
size: 50Gi
victoriaTraces:
enabled: true
releaseName: victoria-traces
sourceRef:
kind: HelmRepository
name: victoria-metrics
namespace: flux-system
chart:
name: victoria-traces-single
version: ">=0.0.1 <1.0.0"
values: {}
grafana:
enabled: false
releaseName: grafana
sourceRef:
kind: HelmRepository
name: grafana
namespace: flux-system
chart:
name: grafana
version: ">=8.0.0 <9.0.0"
values:
initChownData:
enabled: false
podSecurityContext:
fsGroup: 472
securityContext:
runAsUser: 472
runAsGroup: 472
persistence:
enabled: true
size: 10Gi
otelConnector:
enabled: false
releaseName: otel-connector
sourceRef:
kind: HelmRepository
name: open-telemetry
namespace: flux-system
chart:
name: opentelemetry-collector
version: ">=0.104.0 <1.0.0"
values:
image:
repository: otel/opentelemetry-collector-contrib
mode: deployment
config:
receivers:
otlp:
protocols:
grpc: {}
http: {}
processors:
batch: {}
exporters:
debug: {}
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
agent:
nodeExporter:
enabled: true
releaseName: node-exporter
sourceRef:
kind: HelmRepository
name: prometheus-community
namespace: flux-system
chart:
name: prometheus-node-exporter
version: ">=4.30.0 <5.0.0"
values: {}
vector:
enabled: true
image:
repository: timberio/vector
tag: "0.36.0-distroless-libc"
pullPolicy: IfNotPresent
serviceAccountName: vector-agent
vlogsEndpoint: http://victoria-logs-victoria-logs-single-server.observability.svc.cluster.local:9428
config: |
data_dir: /vector-data-dir
sources:
journald:
type: journald
transforms:
normalize:
type: remap
inputs: ["journald"]
source: |
.cluster = "k3s"
.origin = "vector-agent"
sinks:
vlogs:
type: elasticsearch
inputs: ["normalize"]
endpoints:
- http://victoria-logs-victoria-logs-single-server.observability.svc.cluster.local:9428/insert/elasticsearch/
mode: bulk
compression: gzip
resources:
limits:
cpu: 300m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
volume:
enabled: true
name: vector-data-dir
mountPath: /vector-data-dir
sizeLimit: 1Gi
processExporter:
enabled: true
image:
repository: ncabatoff/process-exporter
tag: v0.8.3
pullPolicy: IfNotPresent
serviceAccountName: process-exporter
port: 9256
config: |
process_names:
- name: "{{.Comm}}"
cmdline:
- '.+'
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 25m
memory: 64Mi
extraObjects: []

View File

@ -0,0 +1,19 @@
apiVersion: v2
name: postgresql
description: PostgreSQL service chart with optional stunnel server and client for cloud-neutral deployments
type: application
version: 1.1.0
appVersion: "16.4"
keywords:
- postgresql
- database
- vector
- search
- queue
home: https://github.com/cloud-neutral-toolkit/postgresql.svc.plus
sources:
- https://github.com/cloud-neutral-toolkit/postgresql.svc.plus
maintainers:
- name: Cloud-Neutral Toolkit
email: admin@svc.plus
icon: https://www.postgresql.org/media/img/about/press/elephant.png

View File

@ -0,0 +1,82 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "postgresql.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "postgresql.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "postgresql.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "postgresql.labels" -}}
helm.sh/chart: {{ include "postgresql.chart" . }}
{{ include "postgresql.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "postgresql.selectorLabels" -}}
app.kubernetes.io/name: {{ include "postgresql.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "postgresql.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "postgresql.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Get the password secret name
*/}}
{{- define "postgresql.secretName" -}}
{{- if .Values.auth.existingSecret -}}
{{- .Values.auth.existingSecret -}}
{{- else -}}
{{- include "postgresql.fullname" . -}}
{{- end -}}
{{- end -}}
{{/*
Get the password key
*/}}
{{- define "postgresql.secretKey" -}}
{{- if .Values.auth.existingSecret -}}
{{- .Values.auth.secretKey -}}
{{- else -}}
password
{{- end -}}
{{- end -}}

View File

@ -0,0 +1,13 @@
{{- if and .Values.server.enabled .Values.initScripts.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "postgresql.fullname" . }}-init-scripts
labels:
{{- include "postgresql.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.initScripts.scripts }}
{{ $key }}: |
{{- $value | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,11 @@
{{- if and .Values.server.enabled .Values.stunnel.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "postgresql.fullname" . }}-stunnel-config
labels:
{{- include "postgresql.labels" . | nindent 4 }}
data:
stunnel.conf: |
{{- .Values.stunnel.config | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,25 @@
{{- if .Values.server.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "postgresql.fullname" . }}-config
labels:
{{- include "postgresql.labels" . | nindent 4 }}
data:
postgresql.conf: |
{{- .Values.postgresql.config | nindent 4 }}
{{- if .Values.postgresql.pgHba }}
pg_hba.conf: |
# TYPE DATABASE USER ADDRESS METHOD
# Default entries
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
host all all 0.0.0.0/0 md5
host all all ::/0 md5
# Custom entries
{{- .Values.postgresql.pgHba | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,11 @@
{{- if and .Values.server.enabled (not .Values.auth.existingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "postgresql.fullname" . }}
labels:
{{- include "postgresql.labels" . | nindent 4 }}
type: Opaque
data:
password: {{ .Values.auth.password | b64enc | quote }}
{{- end }}

View File

@ -0,0 +1,21 @@
{{- if and .Values.server.enabled .Values.metrics.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "postgresql.fullname" . }}-metrics
labels:
{{- include "postgresql.labels" . | nindent 4 }}
{{- with .Values.metrics.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.metrics.service.type }}
ports:
- port: {{ .Values.metrics.service.port }}
targetPort: metrics
protocol: TCP
name: metrics
selector:
{{- include "postgresql.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,27 @@
{{- if .Values.server.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "postgresql.fullname" . }}
labels:
{{- include "postgresql.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: postgres
protocol: TCP
name: postgres
{{- if .Values.stunnel.enabled }}
- port: {{ .Values.stunnel.port }}
targetPort: stunnel
protocol: TCP
name: stunnel
{{- end }}
selector:
{{- include "postgresql.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,12 @@
{{- if and .Values.server.enabled .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "postgresql.serviceAccountName" . }}
labels:
{{- include "postgresql.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,203 @@
{{- if .Values.server.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "postgresql.fullname" . }}
labels:
{{- include "postgresql.labels" . | nindent 4 }}
spec:
serviceName: {{ include "postgresql.fullname" . }}
replicas: 1
selector:
matchLabels:
{{- include "postgresql.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "postgresql.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "postgresql.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: postgresql
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: postgres
containerPort: 5432
protocol: TCP
env:
- name: POSTGRES_USER
value: {{ .Values.auth.username | quote }}
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "postgresql.secretName" . }}
key: {{ include "postgresql.secretKey" . }}
- name: POSTGRES_DB
value: {{ .Values.auth.database | quote }}
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
{{- if .Values.livenessProbe.enabled }}
livenessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U {{ .Values.auth.username }}
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
successThreshold: {{ .Values.livenessProbe.successThreshold }}
{{- end }}
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U {{ .Values.auth.username }}
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
successThreshold: {{ .Values.readinessProbe.successThreshold }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
- name: config
mountPath: /etc/postgresql
{{- if .Values.initScripts.enabled }}
- name: init-scripts
mountPath: /docker-entrypoint-initdb.d
{{- end }}
{{- if .Values.tls.enabled }}
- name: tls-certs
mountPath: /etc/postgresql/certs
readOnly: true
{{- end }}
{{- if .Values.stunnel.enabled }}
- name: stunnel
image: "{{ .Values.stunnel.image.repository }}:{{ .Values.stunnel.image.tag }}"
imagePullPolicy: {{ .Values.stunnel.image.pullPolicy }}
ports:
- name: stunnel
containerPort: {{ .Values.stunnel.port }}
protocol: TCP
volumeMounts:
- name: stunnel-config
mountPath: /etc/stunnel/stunnel.conf
subPath: stunnel.conf
{{- if .Values.stunnel.certificatesSecret }}
- name: stunnel-certs
mountPath: /etc/stunnel/certs
readOnly: true
{{- end }}
{{- end }}
{{- if .Values.metrics.enabled }}
- name: metrics
image: "{{ .Values.metrics.image.repository }}:{{ .Values.metrics.image.tag }}"
imagePullPolicy: {{ .Values.metrics.image.pullPolicy }}
ports:
- name: metrics
containerPort: 9187
protocol: TCP
env:
- name: DATA_SOURCE_NAME
value: "postgresql://{{ .Values.auth.username }}:$(POSTGRES_PASSWORD)@localhost:5432/{{ .Values.auth.database }}?sslmode=disable"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "postgresql.secretName" . }}
key: {{ include "postgresql.secretKey" . }}
resources:
{{- toYaml .Values.metrics.resources | nindent 12 }}
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "postgresql.fullname" . }}-config
{{- if .Values.initScripts.enabled }}
- name: init-scripts
configMap:
name: {{ include "postgresql.fullname" . }}-init-scripts
{{- end }}
{{- if .Values.tls.enabled }}
- name: tls-certs
secret:
secretName: {{ .Values.tls.certificatesSecret }}
defaultMode: 0600
{{- end }}
{{- if .Values.stunnel.enabled }}
- name: stunnel-config
configMap:
name: {{ include "postgresql.fullname" . }}-stunnel-config
{{- if .Values.stunnel.certificatesSecret }}
- name: stunnel-certs
secret:
secretName: {{ .Values.stunnel.certificatesSecret }}
defaultMode: 0600
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: data
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 10 }}
{{- end }}
spec:
accessModes:
{{- range .Values.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- with .Values.persistence.selector }}
selector:
{{- toYaml . | nindent 10 }}
{{- end }}
{{- else }}
- name: data
emptyDir: {}
{{- end }}
{{- end }}

View File

@ -0,0 +1,11 @@
{{- if .Values.stunnelClient.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "postgresql.fullname" . }}-stunnel-client
labels:
{{- include "postgresql.labels" . | nindent 4 }}
data:
stunnel.conf: |
{{- .Values.stunnelClient.config | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,40 @@
{{- if .Values.stunnelClient.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "postgresql.fullname" . }}-stunnel-client
labels:
{{- include "postgresql.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.stunnelClient.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "postgresql.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: stunnel-client
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "postgresql.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: stunnel-client
spec:
containers:
- name: stunnel-client
image: "{{ .Values.stunnelClient.image.repository }}:{{ .Values.stunnelClient.image.tag }}"
imagePullPolicy: {{ .Values.stunnelClient.image.pullPolicy }}
ports:
- name: postgres
containerPort: {{ .Values.stunnelClient.service.port }}
protocol: TCP
resources:
{{- toYaml .Values.stunnelClient.resources | nindent 12 }}
volumeMounts:
- name: config
mountPath: /etc/stunnel/stunnel.conf
subPath: stunnel.conf
volumes:
- name: config
configMap:
name: {{ include "postgresql.fullname" . }}-stunnel-client
{{- end }}

View File

@ -0,0 +1,23 @@
{{- if .Values.stunnelClient.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "postgresql.fullname" . }}-stunnel-client
labels:
{{- include "postgresql.labels" . | nindent 4 }}
{{- with .Values.stunnelClient.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.stunnelClient.service.type }}
ports:
- name: postgres
port: {{ .Values.stunnelClient.service.port }}
targetPort: postgres
protocol: TCP
selector:
app.kubernetes.io/name: {{ include "postgresql.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: stunnel-client
{{- end }}

View File

@ -0,0 +1,245 @@
# Default values for postgresql chart
server:
enabled: true
# Image configuration
image:
repository: postgres-extensions
tag: "16"
pullPolicy: IfNotPresent
# Image pull secrets for private registries
imagePullSecrets: []
# Override name
nameOverride: ""
fullnameOverride: ""
# Service account
serviceAccount:
create: true
annotations: {}
name: ""
# Pod annotations
podAnnotations: {}
# Pod security context
podSecurityContext:
fsGroup: 999
# Container security context
securityContext:
runAsUser: 999
runAsNonRoot: true
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# Service configuration
service:
type: ClusterIP
port: 5432
annotations: {}
# Ingress (not typically used for PostgreSQL, but available)
ingress:
enabled: false
className: ""
annotations: {}
hosts: []
tls: []
# PostgreSQL authentication
auth:
username: postgres
password: "" # Set this or use existingSecret
database: postgres
existingSecret: "" # Name of existing secret with password
secretKey: "password" # Key in the secret
# PostgreSQL configuration
postgresql:
# Custom postgresql.conf settings
config: |
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 16MB
maintenance_work_mem = 64MB
max_connections = 100
wal_buffers = 16MB
checkpoint_completion_target = 0.9
random_page_cost = 1.1
effective_io_concurrency = 200
log_min_duration_statement = 1000
# Custom pg_hba.conf entries (appended to defaults)
pgHba: |
# Custom entries
# host all all 0.0.0.0/0 md5
# Initialization scripts
initScripts:
enabled: true
# Scripts will be created from the scripts below
scripts:
01-init-extensions.sql: |
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_jieba;
CREATE EXTENSION IF NOT EXISTS pgmq;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
# Persistence
persistence:
enabled: true
storageClass: "" # Use default storage class
accessModes:
- ReadWriteOnce
size: 10Gi
annotations: {}
selector: {}
# Resource limits
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
# Liveness probe
livenessProbe:
enabled: true
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
# Readiness probe
readinessProbe:
enabled: true
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
successThreshold: 1
# Node selector
nodeSelector: {}
# Tolerations
tolerations: []
# Affinity
affinity: {}
# Metrics (for Prometheus)
metrics:
enabled: false
image:
repository: prometheuscommunity/postgres-exporter
tag: v0.15.0
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 9187
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9187"
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
# Backup configuration (optional)
backup:
enabled: false
schedule: "0 2 * * *" # Daily at 2 AM
retention: 7 # Keep 7 days of backups
storageClass: ""
size: 20Gi
# TLS/SSL configuration
tls:
enabled: false
certificatesSecret: "" # Name of secret containing tls.crt and tls.key
certFilename: "tls.crt"
certKeyFilename: "tls.key"
certCAFilename: "ca.crt"
# Stunnel sidecar for TLS over TCP
stunnel:
enabled: false
image:
repository: dweomer/stunnel
tag: latest
pullPolicy: IfNotPresent
port: 5433
certificatesSecret: "" # Name of secret containing stunnel certificates
config: |
[postgres-tunnel]
client = no
accept = 0.0.0.0:5433
connect = 127.0.0.1:5432
cert = /etc/stunnel/certs/server-cert.pem
key = /etc/stunnel/certs/server-key.pem
sslVersion = TLSv1.2
options = NO_SSLv2
options = NO_SSLv3
ciphers = HIGH:!aNULL:!MD5
# NetworkPolicy
networkPolicy:
enabled: false
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}
ports:
- protocol: TCP
port: 5432
# PodDisruptionBudget
podDisruptionBudget:
enabled: false
minAvailable: 1
# maxUnavailable: 1
stunnelClient:
enabled: false
replicaCount: 1
image:
repository: dweomer/stunnel
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 5432
annotations: {}
config: |
[postgres-client]
client = yes
accept = 0.0.0.0:5432
connect = postgresql.database.svc.cluster.local:5433
verifyChain = no
sslVersion = TLSv1.2
options = NO_SSLv2
options = NO_SSLv3
ciphers = HIGH:!aNULL:!MD5
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 25m
memory: 64Mi

View File

@ -0,0 +1,35 @@
ORG ?= your-org
IMAGE_REGISTRY ?= ghcr.io/$(ORG)/model-serving
CHART_NAME ?= model-serving
CHART_DIR := charts/$(CHART_NAME)
VERSION ?= 0.1.0
.PHONY: docker-build docker-push helm-lint helm-package helm-push install uninstall template
docker-build:
$(MAKE) -C ../base/cuda docker-build REGISTRY=$(IMAGE_REGISTRY)
docker-push:
$(MAKE) -C ../base/cuda docker-push REGISTRY=$(IMAGE_REGISTRY)
helm-lint:
helm lint $(CHART_DIR)
helm-package:
helm package $(CHART_DIR) --version $(VERSION) --app-version $(VERSION) -d charts/
helm-push: helm-package
helm push charts/$(CHART_NAME)-$(VERSION).tgz oci://ghcr.io/$(ORG)/helm
RELEASE ?= ms
NAMESPACE ?= llm
install:
kubectl create ns $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f -
helm upgrade --install $(RELEASE) $(CHART_DIR) -n $(NAMESPACE)
uninstall:
helm uninstall $(RELEASE) -n $(NAMESPACE) || true
template:
helm template $(RELEASE) $(CHART_DIR)

View File

@ -0,0 +1,101 @@
# CUDA LLM Serving — vLLM / SGLang / Ollama (Kubernetes)
This package bundles three CUDA-ready images plus a single Helm chart that can serve **multiple models** behind one host with **path-based routing** such as:
- `https://api.svc.plus/v1/llama3` → vLLM (OpenAI-compatible)
- `https://api.svc.plus/v1/qwen2` → SGLang (OpenAI-compatible)
- `https://api.svc.plus/v1/phi3` → Ollama `/api/*`
The Dockerfiles live under [`oci/base/cuda`](../base/cuda/), while the Helm chart is in [`charts/model-serving`](charts/model-serving/).
## Prerequisites
- Kubernetes ≥ 1.25
- NVIDIA GPUs on worker nodes + NVIDIA Container Toolkit
- Ingress Controller (e.g. NGINX) and TLS secret if using HTTPS
- (Optional) GitHub Container Registry (GHCR) for distributing images and charts
## Build & Publish
```bash
# 1) Build and push images to GHCR (adjust ORG)
make -C oci/base/cuda ORG=svc-design docker-build docker-push
# 2) Lint & package the chart
make -C oci/multi-model-LLM helm-lint helm-package VERSION=0.1.0
# 3) Push chart as OCI to GHCR
make -C oci/multi-model-LLM ORG=svc-design VERSION=0.1.0 helm-push
```
> Authenticate GHCR first:
>
> ```bash
> echo $GHCR_TOKEN | docker login ghcr.io -u <GITHUB_USER> --password-stdin
> helm registry login ghcr.io -u <GITHUB_USER> -p $GHCR_TOKEN
> ```
## Install
```bash
# install into namespace llm with release name ms
make -C oci/multi-model-LLM install RELEASE=ms NAMESPACE=llm
```
## Configure Models
Edit [`charts/model-serving/values.yaml`](charts/model-serving/values.yaml) and extend the `models:` list. Example:
```yaml
models:
- name: llama3-8b-vllm
engine: vllm
image: "model-serving/vllm"
tag: "cuda12"
path: v1/llama3
env:
- name: MODEL_PATH
value: meta-llama/Meta-Llama-3-8B-Instruct
- name: VLLM_ARGS
value: --max-model-len 8192 --gpu-memory-utilization 0.9
resources:
limits:
nvidia.com/gpu: 1
- name: qwen2-7b-sglang
engine: sglang
image: "model-serving/sglang"
tag: "cuda12"
path: v1/qwen2
env:
- name: SGLANG_MODEL
value: Qwen/Qwen2-7B-Instruct
- name: phi3-ollama
engine: ollama
image: "model-serving/ollama"
tag: latest
path: v1/phi3
env:
- name: OLLAMA_MODEL
value: phi3:latest
```
Deployments and services are generated per model, and a single ingress exposes them under unique paths.
## Runtime Notes
* **GPU scheduling**: Templates set `runtimeClassName: nvidia` and default GPU limits. Ensure the cluster has the NVIDIA device plugin and RuntimeClass defined, or override `runtimeClassName` per model.
* **Storage**: vLLM/SGLang cache defaults to the container filesystem. Mount an external volume by extending the template if persistence is required.
* **Authentication**: vLLM launches with a dummy API key. Place an API gateway or ingress authentication in front for production.
* **Scaling**: Increase `replicas` per model and add engine-specific flags through environment variables for tensor parallelism or sharding.
## Uninstall
```bash
make -C oci/multi-model-LLM uninstall RELEASE=ms NAMESPACE=llm
```
## License
MIT

View File

@ -0,0 +1,6 @@
*.tgz
*.swp
*.swo
.DS_Store
.git/
.github/

View File

@ -0,0 +1,14 @@
apiVersion: v2
name: model-serving
version: 0.1.0
kubeVersion: ">=1.25.0"
description: Multi-model LLM serving (vLLM / SGLang / Ollama) with one API host & path routing
home: https://github.com/svc-design/artifacts
keywords:
- llm
- vllm
- sglang
- ollama
- cuda
- gpu
type: application

View File

@ -0,0 +1,14 @@
{{- define "model-serving.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "model-serving.svcname" -}}
{{- printf "%s-svc" (include "model-serving.fullname" .) -}}
{{- end -}}
{{- define "model-serving.image" -}}
{{- $reg := .Values.global.imageRegistry -}}
{{- $img := .image -}}
{{- $tag := .tag | default "latest" -}}
{{- printf "%s/%s:%s" $reg $img $tag -}}
{{- end -}}

View File

@ -0,0 +1,58 @@
{{- $root := . -}}
{{- range $m := .Values.models }}
{{- if eq $m.engine "ollama" }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "model-serving.fullname" $root }}-{{ $m.name }}
labels:
app.kubernetes.io/name: {{ $m.name }}
app.kubernetes.io/engine: ollama
spec:
replicas: {{ $m.replicas | default 1 }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $m.name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $m.name }}
app.kubernetes.io/engine: ollama
spec:
containers:
- name: {{ $m.name }}
image: {{ include "model-serving.image" (dict "Values" $root.Values "image" $m.image "tag" $m.tag) }}
imagePullPolicy: IfNotPresent
{{- if $m.env }}
env:
{{- range $m.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
ports:
- containerPort: 11434
{{- if $m.resources }}
resources:
{{ toYaml $m.resources | nindent 12 }}
{{- else }}
resources: {}
{{- end }}
runtimeClassName: {{ $m.runtimeClassName | default "nvidia" }}
{{- if $m.nodeSelector }}
nodeSelector:
{{ toYaml $m.nodeSelector | nindent 8 }}
{{- end }}
{{- if $m.tolerations }}
tolerations:
{{ toYaml $m.tolerations | nindent 8 }}
{{- end }}
{{- if $root.Values.imagePullSecrets }}
imagePullSecrets:
{{- range $root.Values.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
---
{{- end }}
{{- end }}

View File

@ -0,0 +1,58 @@
{{- $root := . -}}
{{- range $m := .Values.models }}
{{- if eq $m.engine "sglang" }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "model-serving.fullname" $root }}-{{ $m.name }}
labels:
app.kubernetes.io/name: {{ $m.name }}
app.kubernetes.io/engine: sglang
spec:
replicas: {{ $m.replicas | default 1 }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $m.name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $m.name }}
app.kubernetes.io/engine: sglang
spec:
containers:
- name: {{ $m.name }}
image: {{ include "model-serving.image" (dict "Values" $root.Values "image" $m.image "tag" $m.tag) }}
imagePullPolicy: IfNotPresent
{{- if $m.env }}
env:
{{- range $m.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
ports:
- containerPort: 30000
{{- if $m.resources }}
resources:
{{ toYaml $m.resources | nindent 12 }}
{{- else }}
resources: {}
{{- end }}
runtimeClassName: {{ $m.runtimeClassName | default "nvidia" }}
{{- if $m.nodeSelector }}
nodeSelector:
{{ toYaml $m.nodeSelector | nindent 8 }}
{{- end }}
{{- if $m.tolerations }}
tolerations:
{{ toYaml $m.tolerations | nindent 8 }}
{{- end }}
{{- if $root.Values.imagePullSecrets }}
imagePullSecrets:
{{- range $root.Values.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
---
{{- end }}
{{- end }}

View File

@ -0,0 +1,58 @@
{{- $root := . -}}
{{- range $m := .Values.models }}
{{- if eq $m.engine "vllm" }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "model-serving.fullname" $root }}-{{ $m.name }}
labels:
app.kubernetes.io/name: {{ $m.name }}
app.kubernetes.io/engine: vllm
spec:
replicas: {{ $m.replicas | default 1 }}
selector:
matchLabels:
app.kubernetes.io/name: {{ $m.name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ $m.name }}
app.kubernetes.io/engine: vllm
spec:
containers:
- name: {{ $m.name }}
image: {{ include "model-serving.image" (dict "Values" $root.Values "image" $m.image "tag" $m.tag) }}
imagePullPolicy: IfNotPresent
{{- if $m.env }}
env:
{{- range $m.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
ports:
- containerPort: 8000
{{- if $m.resources }}
resources:
{{ toYaml $m.resources | nindent 12 }}
{{- else }}
resources: {}
{{- end }}
runtimeClassName: {{ $m.runtimeClassName | default "nvidia" }}
{{- if $m.nodeSelector }}
nodeSelector:
{{ toYaml $m.nodeSelector | nindent 8 }}
{{- end }}
{{- if $m.tolerations }}
tolerations:
{{ toYaml $m.tolerations | nindent 8 }}
{{- end }}
{{- if $root.Values.imagePullSecrets }}
imagePullSecrets:
{{- range $root.Values.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
---
{{- end }}
{{- end }}

View File

@ -0,0 +1,28 @@
{{- if .Values.global.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "model-serving.fullname" . }}
annotations:
kubernetes.io/ingress.class: {{ .Values.global.ingress.className | quote }}
spec:
{{- if .Values.global.ingress.tls }}
tls:
- hosts:
- {{ .Values.global.ingress.host | quote }}
secretName: {{ .Values.global.ingress.tlsSecretName | quote }}
{{- end }}
rules:
- host: {{ .Values.global.ingress.host | quote }}
http:
paths:
{{- range .Values.models }}
- path: /{{ .path }}
pathType: Prefix
backend:
service:
name: {{ include "model-serving.fullname" $ }}-{{ .name }}
port:
number: {{ $.Values.service.port | default 80 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,18 @@
{{- $root := . -}}
{{- range $m := .Values.models }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "model-serving.fullname" $root }}-{{ $m.name }}
labels:
app.kubernetes.io/name: {{ $m.name }}
spec:
type: {{ $root.Values.service.type | default "ClusterIP" }}
selector:
app.kubernetes.io/name: {{ $m.name }}
ports:
- name: http
port: {{ $root.Values.service.port | default 80 }}
targetPort: {{ if eq $m.engine "vllm" }}8000{{ else if eq $m.engine "sglang" }}30000{{ else }}11434{{ end }}
---
{{- end }}

View File

@ -0,0 +1,61 @@
global:
imageRegistry: ghcr.io/your-org
namespace: default
ingress:
enabled: true
className: nginx
host: api.svc.plus
tls: true
tlsSecretName: model-serving-tls
models:
- name: llama3-8b-vllm
engine: vllm
image: "model-serving/vllm"
tag: "cuda12"
replicas: 1
path: v1/llama3
env:
- name: MODEL_PATH
value: "meta-llama/Meta-Llama-3-8B-Instruct"
- name: VLLM_ARGS
value: "--max-model-len 8192 --gpu-memory-utilization 0.9"
resources:
limits:
nvidia.com/gpu: 1
nodeSelector: {}
tolerations: []
- name: qwen2-7b-sglang
engine: sglang
image: "model-serving/sglang"
tag: "cuda12"
replicas: 1
path: v1/qwen2
env:
- name: SGLANG_MODEL
value: "Qwen/Qwen2-7B-Instruct"
- name: SGLANG_ARGS
value: "--tp 1 --context-length 8192"
resources:
limits:
nvidia.com/gpu: 1
- name: phi3-ollama
engine: ollama
image: "model-serving/ollama"
tag: "latest"
replicas: 1
path: v1/phi3
env:
- name: OLLAMA_MODEL
value: "phi3:latest"
resources:
limits:
nvidia.com/gpu: 1
service:
type: ClusterIP
port: 80
imagePullSecrets: []

View File

@ -0,0 +1,212 @@
# Cloud-Neutra Golden Image Pipeline
Cloud-Neutra Golden Image Pipeline 为多云环境构建一套统一、可靠、可自动化的 Ubuntu Golden Image 家族。
该体系覆盖 Ubuntu LTS 双版本22.04 / 24.04、双架构amd64 / arm64 以及多个容器/集群运行时的变种。
Pipeline 包含:
- Packer 自动构建 AMI
- GitHub Actions 全自动流水线(构建 → 多 Region 复制 → 过期清理)
- Terraform 模块自动引用最新 Golden Image
- 完全统一的脚本与硬化规范
## 0. Overall Goals
Ubuntu LTS Baseline
- Ubuntu 22.04 LTS
- Ubuntu 24.04 LTS
CPU Architectures
- amd64
- arm64
### Golden Image Editions
- Edition 内容说明
- base 干净操作系统 + 基础硬化(去 snap去 MOTD去不必要服务
- container containerd + nerdctl作为通用 Container VM
- k3s 预装 K3s可在运行时决定 server/agent
- sealos 预装 sealos CLI + containerd
- sealos-gpu 适用于 GPU 节点sealos + NVIDIA 驱动 + nvidia-container-toolkit
### Pipeline 统一要求
- 完整统一脚本结构base → flavor
- 去除 snap / MOTD / landscape / update-notifier 等非必要组件
- 无 amazon-import 误用(使用 amazon-ebs 构建 AMI
GitHub Actions 统一构建 + 多 Region 复制
- 每 Edition / Version / Arch 每月仅保留 1 个 AMI
- Terraform 自动检索“最新且合法”的 Golden Image
## 1. Naming Conventions & Tagging
### AMI 命名规范
Cloud-Neutra-${edition}-VM-${ubuntu_version}-${arch}-${timestamp}
示例:
- Cloud-Neutra-base-VM-2204-amd64-20251121-120000
- Cloud-Neutra-container-VM-2404-arm64-20251121-123000
- Cloud-Neutra-k3s-VM-2404-amd64-20251121-130000
- Cloud-Neutra-sealos-gpu-VM-2404-amd64-20251121-133000
### 统一标签Tags
- Key Value
- Project Cloud-Neutra
- OS Ubuntu 22.04 / Ubuntu 24.04
- Edition base / container / k3s / sealos / sealos-gpu
- Architecture amd64 / arm64
- Role Golden-Image
这些标签用于:
GitHub Actions Retention 策略过滤
Terraform AMI 检索
多 Region 管理
生产审计与溯源
## 2 . Directory Layout
```
packer/
templates/
base/
ubuntu-2204-base.pkr.hcl
ubuntu-2404-base.pkr.hcl
container/
ubuntu-2204-container.pkr.hcl
ubuntu-2404-container.pkr.hcl
k3s/
ubuntu-2204-k3s.pkr.hcl
ubuntu-2404-k3s.pkr.hcl
sealos/
ubuntu-2204-sealos.pkr.hcl
ubuntu-2404-sealos.pkr.hcl
sealos-gpu/
ubuntu-2204-sealos-gpu.pkr.hcl
ubuntu-2404-sealos-gpu.pkr.hcl
scripts/
base/
01_os_base.sh # 开源仓库、更新系统、移除 snap / motd 等
02_hardening.sh # 可选sysctl / sshd / journald 硬化
flavors/
container.sh
k3s.sh
sealos.sh
sealos_gpu.sh
common/
cleanup.sh # apt autoremove + 清理临时文件
```
模板结构说明
- 每个 flavor 模板只负责:
- 指定 Ubuntu 版本与 CPU 架构
- 引用 base 脚本01_os_base.sh / 02_hardening.sh
- 引用 flavor 脚本(如 container.sh / k3s.sh
- 最后引用 cleanup.sh
## 3. Script Architecture
Base Scripts (scripts/base/)
### 01_os_base.sh
启用 universe/multiverse
dist-upgrade禁内核升级风险
移除 snapd / resolvconf / landscape / MOTD-news
安装基础工具curl、jq、lsb-release、net-tools、iptables
关闭 apt-daily 自动更新
### 02_hardening.sh
可选的系统硬化sysctl、sshd、journald 持久化等)
Flavor Scripts (scripts/flavors/)
container.sh
containerd + nerdctl 安装
containerd config 自动生成
k3s.sh 安装 K3sskip-start 运行时可作为 server 或 agent
sealos.sh 安装 sealos CLI 依赖 containerd可复用 container flavor
sealos_gpu.sh 安装 NVIDIA 驱动(可扩展到不同云平台) 安装 nvidia-container-toolkit
安装 sealos
Common Scripts (scripts/common/)
cleanup.sh
apt autoremove
清理 apt lists
清理 tmp
packer build -var cpu_arch=amd64 packer/templates/container/ubuntu-2404-container.pkr.hcl
packer build -var cpu_arch=arm64 packer/templates/k3s/ubuntu-2404-k3s.pkr.hcl
4. GitHub Actions Pipeline
Pipeline 负责:
Packer 构建 AMI按 edition + Ubuntu version + arch
AMI 复制到多 Region如 Tokyo/HK/US-West
Tag AMI
按 edition/version/arch 筛选 → 每 Region 仅保留 1 个 AMI
输出 AMI Map JSON供 Terraform & Dashboard 使用)
支持矩阵
edition: base / container / k3s / sealos / sealos-gpu
ubuntu_version: 2204 / 2404
cpu_arch: amd64 / arm64
GitHub Actions 会自动组合出所有 Golden Image 变种。
5. Terraform: Auto-Select Latest Golden Image
模块位置:
modules/cloud_neutra_ami/
main.tf
variables.tf
outputs.tf
使用方式:
module "cn_container_2404_amd64" {
source = "../../modules/cloud_neutra_ami"
ubuntu_version = "2404"
cpu_arch = "amd64"
edition = "container"
}
输出:
module.cn_container_2404_amd64.id # 最新 AMI ID
module.cn_container_2404_amd64.name # AMI 名称
Terraform 会自动从目标 Region 检索最 新 Golden Image即使你复制了多 Region。
6. Status
Cloud-Neutra Golden Image Pipeline 已具备:
完整家族命名体系base / container / k3s / sealos / sealos-gpu
双 LTS / 双架构覆盖
完整 Packer 模板体系
完整统一脚本base + flavors
GitHub Actions 自动构建、多 Region 复制、Retention
Terraform 自动引用最新 AMI 的可重用模块
整个体系作为 Cloud-Neutra IAC/GitOps 的底座,可直接扩展到:
EKS 节点GPU/ARM
K3s 边缘节点
Sealos 容器云节点
大模型推理 GPU 节点
通用 Container VM
DevOps 工具链

View File

@ -0,0 +1,2 @@
# Cloud-Neutra VM Project
This directory contains documentation for building custom VM images using Packer.

Some files were not shown because too many files have changed in this diff Show More