Compare commits

..

No commits in common. "main" and "codex/adapt-gitlab-setup-for-offline-installation" have entirely different histories.

128 changed files with 389 additions and 5729 deletions

View File

@ -1,36 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,216 +0,0 @@
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

@ -53,49 +53,111 @@ jobs:
id: resolve
env:
OVERRIDE_CHART_VERSION: ${{ github.event.inputs.chart_version }}
run: bash scripts/offline-argocd/resolve_chart_version.sh
run: |
set -euo pipefail
if [ -n "${OVERRIDE_CHART_VERSION}" ]; then
CHART_VERSION="${OVERRIDE_CHART_VERSION}"
else
CHART_VERSION=$(helm search repo argo/argo-cd --versions | awk 'NR==2{print $2}')
fi
echo "chart_version=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
set -euo pipefail
rm -rf argocd-offline-package
mkdir -p argocd-offline-package/{images,charts,scripts,metadata}
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: bash scripts/offline-argocd/stage_installer.sh
run: |
set -euo pipefail
cat <<'SCRIPT' > offline-installer/scripts/install-argocd.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CHART_DIR="${ROOT_DIR}/charts/argo-cd"
IMAGES_DIR="${ROOT_DIR}/images"
RELEASE_NAME="${RELEASE_NAME:-argo-cd}"
NAMESPACE="${NAMESPACE:-argocd}"
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 Argo CD release ${RELEASE_NAME} in namespace ${NAMESPACE}"
helm upgrade --install "${RELEASE_NAME}" "${CHART_DIR}" \
--namespace "${NAMESPACE}" \
--create-namespace \
"$@"
SCRIPT
chmod +x offline-installer/scripts/install-argocd.sh
cat <<EOFMETA > offline-installer/metadata/INFO
chart: argo/argo-cd
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 argocd-offline-package/nerdctl.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: bash scripts/offline-argocd/pull_and_export_images.sh
run: |
set -euo pipefail
PLATFORM="linux/${{ matrix.arch }}"
helm template argo argo/argo-cd --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-installer/images/${safe}.tar"
done
- 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
helm pull argo/argo-cd --version "${CHART_VERSION}" --untar --untardir offline-installer/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
tar -czf offline-setup-argocd-${{ matrix.arch }}.tar.gz -C offline-installer .
ls -lh offline-setup-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
name: offline-setup-argocd-${{ matrix.arch }}
path: offline-setup-argocd-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
@ -107,14 +169,14 @@ jobs:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-argocd-${{ matrix.arch }}
name: offline-setup-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
tar -tzf offline-setup-argocd-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
@ -124,7 +186,7 @@ jobs:
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/
REMOTE_ROOT: /data/update-server/argocd
steps:
- uses: actions/checkout@v4
@ -142,13 +204,13 @@ jobs:
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-argocd-amd64
name: offline-setup-argocd-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-argocd-arm64
name: offline-setup-argocd-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
@ -156,8 +218,8 @@ jobs:
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
release-artifacts/amd64/offline-setup-argocd-amd64.tar.gz
release-artifacts/arm64/offline-setup-argocd-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -176,7 +238,15 @@ jobs:
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: bash scripts/offline-argocd/rsync_release_assets.sh
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-argocd-amd64.tar.gz \
release-artifacts/arm64/offline-setup-argocd-arm64.tar.gz \
"${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 3)
@ -197,4 +267,20 @@ jobs:
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
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-argocd-|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

@ -1,159 +0,0 @@
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

@ -1,192 +0,0 @@
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

@ -1,159 +0,0 @@
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

@ -1,98 +0,0 @@
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

@ -30,7 +30,6 @@ jobs:
runs-on: ubuntu-latest
env:
NERDCTL_VERSION: "2.0.3"
OFFLINE_DIR: gitea-offline-package
outputs:
chart_version: ${{ steps.resolve.outputs.chart_version }}
steps:
@ -66,19 +65,19 @@ jobs:
- name: Prepare directories
run: |
set -euo pipefail
rm -rf "${OFFLINE_DIR}"
mkdir -p "${OFFLINE_DIR}"/{images,charts,metadata}
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: |
set -euo pipefail
cat <<'SCRIPT' > "${OFFLINE_DIR}/install-gitea.sh"
cat <<'SCRIPT' > offline-installer/scripts/install-gitea.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CHART_DIR="${ROOT_DIR}/charts/gitea"
IMAGES_DIR="${ROOT_DIR}/images"
RELEASE_NAME="${RELEASE_NAME:-gitea}"
@ -105,16 +104,8 @@ helm upgrade --install "${RELEASE_NAME}" "${CHART_DIR}" \
--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"
chmod +x offline-installer/scripts/install-gitea.sh
cat <<EOFMETA > offline-installer/metadata/INFO
chart: gitea-charts/gitea
chart_version: ${CHART_VERSION}
created_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
@ -124,7 +115,7 @@ EOFMETA
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"
-O offline-installer/nerdctl.tar.gz
- name: Pull & export required images
env:
@ -146,7 +137,7 @@ EOFMETA
continue
fi
safe=$(echo "$img" | tr '/:' '-_')
docker save "$img" -o "${OFFLINE_DIR}/images/${safe}.tar"
docker save "$img" -o "offline-installer/images/${safe}.tar"
done
- name: Download Helm chart
@ -154,19 +145,19 @@ EOFMETA
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: |
set -euo pipefail
helm pull gitea-charts/gitea --version "${CHART_VERSION}" --untar --untardir "${OFFLINE_DIR}/charts"
helm pull gitea-charts/gitea --version "${CHART_VERSION}" --untar --untardir offline-installer/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
tar -czf offline-setup-gitea-${{ matrix.arch }}.tar.gz -C offline-installer .
ls -lh offline-setup-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
name: offline-setup-gitea-${{ matrix.arch }}
path: offline-setup-gitea-${{ matrix.arch }}.tar.gz
test-offline-installer:
needs: build-offline-installer
@ -178,21 +169,14 @@ EOFMETA
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: offline-package-gitea-${{ matrix.arch }}
name: offline-setup-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
tar -tzf offline-setup-gitea-${{ matrix.arch }}.tar.gz > /dev/null
publish-release:
needs: test-offline-installer
@ -220,13 +204,13 @@ EOFMETA
- name: Download amd64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-gitea-amd64
name: offline-setup-gitea-amd64
path: release-artifacts/amd64
- name: Download arm64 artifact
uses: actions/download-artifact@v4
with:
name: offline-package-gitea-arm64
name: offline-setup-gitea-arm64
path: release-artifacts/arm64
- name: Upload offline installers to GitHub Release
@ -234,8 +218,8 @@ EOFMETA
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
release-artifacts/amd64/offline-setup-gitea-amd64.tar.gz
release-artifacts/arm64/offline-setup-gitea-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -260,8 +244,8 @@ EOFMETA
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 \
release-artifacts/amd64/offline-setup-gitea-amd64.tar.gz \
release-artifacts/arm64/offline-setup-gitea-arm64.tar.gz \
"${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:

View File

@ -36,7 +36,12 @@ jobs:
- uses: actions/checkout@v4
- name: Install deps (curl, jq, helm)
run: script/install-offline-gitlab-deps.sh
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: |
@ -48,7 +53,14 @@ jobs:
id: resolve
env:
OVERRIDE_CHART_VERSION: ${{ github.event.inputs.chart_version }}
run: script/resolve-gitlab-chart-version.sh
run: |
set -euo pipefail
if [ -n "${OVERRIDE_CHART_VERSION}" ]; then
CHART_VERSION="${OVERRIDE_CHART_VERSION}"
else
CHART_VERSION=$(helm search repo gitlab/gitlab --versions | awk 'NR==2{print $2}')
fi
echo "chart_version=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
- name: Prepare directories
run: |
@ -59,7 +71,45 @@ jobs:
- name: Stage installer script
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
run: script/stage-gitlab-offline-installer.sh
run: |
set -euo pipefail
cat <<'SCRIPT' > offline-installer/scripts/install-gitlab.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CHART_DIR="${ROOT_DIR}/charts/gitlab"
IMAGES_DIR="${ROOT_DIR}/images"
RELEASE_NAME="${RELEASE_NAME:-gitlab}"
NAMESPACE="${NAMESPACE:-gitlab}"
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 GitLab release ${RELEASE_NAME} in namespace ${NAMESPACE}"
helm upgrade --install "${RELEASE_NAME}" "${CHART_DIR}" \
--namespace "${NAMESPACE}" \
--create-namespace \
"$@"
SCRIPT
chmod +x offline-installer/scripts/install-gitlab.sh
cat <<EOFMETA > offline-installer/metadata/INFO
chart: gitlab/gitlab
chart_version: ${CHART_VERSION}
created_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
EOFMETA
- name: Download nerdctl binary for ${{ matrix.arch }}
run: |
@ -70,8 +120,25 @@ jobs:
- name: Pull & export required images
env:
CHART_VERSION: ${{ steps.resolve.outputs.chart_version }}
ARCH: ${{ matrix.arch }}
run: script/pull-and-export-gitlab-images.sh
run: |
set -euo pipefail
PLATFORM="linux/${{ matrix.arch }}"
helm template gitlab gitlab/gitlab --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-installer/images/${safe}.tar"
done
- name: Download Helm chart
env:

View File

@ -1,177 +0,0 @@
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

@ -43,21 +43,78 @@ jobs:
id: resolve
env:
OVERRIDE_VERSION: ${{ github.event.inputs.pulumi_version }}
run: script/resolve-pulumi-version.sh
run: |
set -euo pipefail
if [ -n "${OVERRIDE_VERSION}" ]; then
VERSION="${OVERRIDE_VERSION}"
else
VERSION=$(curl -fsSL https://api.github.com/repos/pulumi/pulumi/releases?per_page=100 \
| jq -r '.[].tag_name' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
| sed 's/^v//' \
| sort -V \
| tail -n 1)
fi
if [ -z "${VERSION}" ]; then
echo "Failed to resolve Pulumi version" >&2
exit 1
fi
echo "Resolved Pulumi version: ${VERSION}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Build offline Pulumi package
env:
ARCH: ${{ matrix.arch }}
PULUMI_VERSION: ${{ steps.resolve.outputs.version }}
MATRIX_ARCH: ${{ matrix.arch }}
run: |
set -euo pipefail
ARCH="${{ matrix.arch }}"
case "$ARCH" in
amd64) ASSET_ARCH="x64" ;;
arm64) ASSET_ARCH="arm64" ;;
*) echo "Unsupported arch: $ARCH" >&2; exit 1 ;;
esac
WORKDIR="pulumi-offline-package"
rm -rf "${WORKDIR}"
mkdir -p "${WORKDIR}" "${WORKDIR}/scripts"
run: script/build-offline-pulumi-package.sh
ARCHIVE="pulumi-v${PULUMI_VERSION}-linux-${ASSET_ARCH}.tar.gz"
URL="https://get.pulumi.com/releases/sdk/${ARCHIVE}"
echo "Downloading ${URL}"
curl -fSL "${URL}" -o "${ARCHIVE}"
tar -xzvf "${ARCHIVE}" -C "${WORKDIR}" --strip-components=1
rm -f "${ARCHIVE}"
echo "${PULUMI_VERSION}" > "${WORKDIR}/VERSION"
cat <<'SCRIPT' > "${WORKDIR}/scripts/install-pulumi.sh"
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN_DIR="${ROOT_DIR}/bin"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
if [[ "${1:-}" == "--install" ]]; then
sudo install -m 0755 "${BIN_DIR}"/* "${INSTALL_DIR}/"
echo "Pulumi binaries installed to ${INSTALL_DIR}"
else
cat <<USAGE
Usage: $(basename "$0") --install
--install Copy Pulumi CLI binaries into ${INSTALL_DIR}
USAGE
fi
SCRIPT
chmod +x "${WORKDIR}/scripts/install-pulumi.sh"
tar -czf "pulumi-offline-package-${ARCH}.tar.gz" "${WORKDIR}"
ls -lh "pulumi-offline-package-${ARCH}.tar.gz"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: pulumi-offline-package-${{ matrix.arch }}
path: offline-package-pulumi-${{ matrix.arch }}.tar.gz
path: pulumi-offline-package-${{ matrix.arch }}.tar.gz
if-no-files-found: error
test-offline-installer:
@ -67,8 +124,6 @@ jobs:
arch: [amd64, arm64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
@ -79,15 +134,21 @@ jobs:
run: |
set -euo pipefail
cd test-dir
tar -xzvf offline-package-pulumi-${{ matrix.arch }}.tar.gz
tar -xzvf pulumi-offline-package-${{ 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
run: |
set -euo pipefail
cd test-dir/pulumi-offline-package
test -f VERSION
if [ "${{ matrix.arch }}" = "amd64" ]; then
./bin/pulumi version
./bin/pulumi version | grep "v${PULUMI_VERSION}"
else
file ./bin/pulumi | grep -E "ARM|aarch64"
fi
publish-release:
needs: test-offline-installer
@ -129,8 +190,8 @@ jobs:
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
release-artifacts/amd64/pulumi-offline-package-amd64.tar.gz
release-artifacts/arm64/pulumi-offline-package-arm64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -149,7 +210,15 @@ jobs:
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: script/rsync-release-assets.sh
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/pulumi-offline-package-amd64.tar.gz \
release-artifacts/arm64/pulumi-offline-package-arm64.tar.gz \
"${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 3)
@ -161,8 +230,6 @@ jobs:
VPS_HOST: ${{ secrets.VPS_HOST }}
REMOTE_ROOT: /data/update-server/pulumi
steps:
- uses: actions/checkout@v4
- name: Init SSH
run: |
set -euo pipefail
@ -172,4 +239,20 @@ jobs:
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 3)
run: script/prune-remote-versions.sh
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-pulumi-|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

@ -1,189 +0,0 @@
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

@ -43,13 +43,62 @@ jobs:
id: resolve
env:
OVERRIDE_VERSION: ${{ github.event.inputs.terraform_version }}
run: script/resolve-terraform-version.sh
run: |
set -euo pipefail
if [ -n "${OVERRIDE_VERSION}" ]; then
VERSION="${OVERRIDE_VERSION}"
else
VERSION=$(curl -fsSL https://checkpoint-api.hashicorp.com/v1/check/terraform | jq -r '.current_version')
fi
echo "Resolved Terraform version: ${VERSION}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Build offline Terraform package
env:
TERRAFORM_VERSION: ${{ steps.resolve.outputs.version }}
ARCH: ${{ matrix.arch }}
run: script/build-offline-terraform-package.sh
run: |
set -euo pipefail
ARCH="${{ matrix.arch }}"
WORKDIR="terraform-offline-package"
rm -rf "${WORKDIR}"
mkdir -p "${WORKDIR}/"{bin,scripts,docs}
BASE_URL="https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}"
ARCHIVE="terraform_${TERRAFORM_VERSION}_linux_${ARCH}.zip"
echo "Downloading ${BASE_URL}/${ARCHIVE}"
curl -fSL "${BASE_URL}/${ARCHIVE}" -o "${ARCHIVE}"
unzip -d "${WORKDIR}/bin" "${ARCHIVE}"
rm -f "${ARCHIVE}"
echo "${TERRAFORM_VERSION}" > "${WORKDIR}/VERSION"
cat <<'SCRIPT' > "${WORKDIR}/scripts/install-terraform.sh"
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN="${ROOT_DIR}/bin/terraform"
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
usage() {
cat <<USAGE
Usage: $(basename "$0") [--install]
--install Copy terraform binary into ${INSTALL_DIR}
USAGE
}
if [[ "${1:-}" == "--install" ]]; then
sudo install -m 0755 "$BIN" "${INSTALL_DIR}/terraform"
echo "Terraform installed to ${INSTALL_DIR}/terraform"
else
usage
fi
SCRIPT
chmod +x "${WORKDIR}/scripts/install-terraform.sh"
tar -czf "terraform-offline-package-${ARCH}.tar.gz" "${WORKDIR}"
ls -lh "terraform-offline-package-${ARCH}.tar.gz"
- name: Upload artifact
uses: actions/upload-artifact@v4
@ -80,8 +129,16 @@ jobs:
- name: Verify Terraform bundle
env:
TERRAFORM_VERSION: ${{ needs.build-offline-installer.outputs.version }}
ARCH: ${{ matrix.arch }}
run: script/verify-terraform-bundle.sh
run: |
set -euo pipefail
cd test-dir/terraform-offline-package
test -f VERSION
if [ "${{ matrix.arch }}" = "amd64" ]; then
./bin/terraform version
./bin/terraform version | grep "Terraform v${TERRAFORM_VERSION}"
else
file ./bin/terraform | grep -E "ARM|aarch64"
fi
publish-release:
needs: test-offline-installer
@ -143,7 +200,15 @@ jobs:
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Rsync release assets to remote
run: script/rsync-release-assets.sh
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/terraform-offline-package-amd64.tar.gz \
release-artifacts/arm64/terraform-offline-package-arm64.tar.gz \
"${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/"
retention:
name: Remote retention (keep latest 3)
@ -164,4 +229,20 @@ jobs:
ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts
- name: Prune old versions on remote (keep 3)
run: script/prune-remote-versions.sh
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-terraform-|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

@ -1,67 +0,0 @@
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

3
.gitignore vendored
View File

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

View File

@ -1,86 +0,0 @@
#!/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

@ -1,18 +0,0 @@
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

@ -1,86 +0,0 @@
#!/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

@ -1,72 +0,0 @@
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

@ -1,86 +0,0 @@
#!/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

@ -1,19 +0,0 @@
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

@ -1,86 +0,0 @@
#!/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

@ -1,37 +0,0 @@
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

@ -1,86 +0,0 @@
#!/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

@ -1,37 +0,0 @@
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:

View File

@ -1,29 +0,0 @@
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

@ -1,28 +0,0 @@
# 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

@ -1,30 +0,0 @@
# 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

@ -1,33 +0,0 @@
# 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"]

View File

@ -1,15 +0,0 @@
# 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

@ -1,6 +0,0 @@
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

@ -1,11 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,6 +0,0 @@
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

@ -1,26 +0,0 @@
{{- 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

@ -1,128 +0,0 @@
{{- $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

@ -1,35 +0,0 @@
{{- 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

@ -1,14 +0,0 @@
{{- 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

@ -1,19 +0,0 @@
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

@ -1,12 +0,0 @@
{{- 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

@ -1,94 +0,0 @@
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

@ -1,6 +0,0 @@
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

@ -1,11 +0,0 @@
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

@ -1,31 +0,0 @@
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

@ -1,6 +0,0 @@
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

@ -1,11 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,18 +0,0 @@
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

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

View File

@ -1,26 +0,0 @@
{{- 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

@ -1,26 +0,0 @@
{{- 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

@ -1,26 +0,0 @@
{{- 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

@ -1,65 +0,0 @@
{{- 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

@ -1,26 +0,0 @@
{{- 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

@ -1,69 +0,0 @@
{{- 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

@ -1,26 +0,0 @@
{{- 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

@ -1,26 +0,0 @@
{{- 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

@ -1,26 +0,0 @@
{{- 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

@ -1,199 +0,0 @@
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

@ -1,19 +0,0 @@
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

@ -1,82 +0,0 @@
{{/*
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

@ -1,13 +0,0 @@
{{- 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

@ -1,11 +0,0 @@
{{- 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

@ -1,25 +0,0 @@
{{- 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

@ -1,11 +0,0 @@
{{- 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

@ -1,21 +0,0 @@
{{- 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

@ -1,27 +0,0 @@
{{- 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

@ -1,12 +0,0 @@
{{- 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

@ -1,203 +0,0 @@
{{- 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

@ -1,11 +0,0 @@
{{- 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

@ -1,40 +0,0 @@
{{- 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

@ -1,23 +0,0 @@
{{- 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

@ -1,245 +0,0 @@
# 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

@ -1,35 +0,0 @@
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

@ -1,101 +0,0 @@
# 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

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

View File

@ -1,14 +0,0 @@
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

@ -1,14 +0,0 @@
{{- 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

@ -1,58 +0,0 @@
{{- $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

@ -1,58 +0,0 @@
{{- $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

@ -1,58 +0,0 @@
{{- $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

@ -1,28 +0,0 @@
{{- 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

@ -1,18 +0,0 @@
{{- $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

@ -1,61 +0,0 @@
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

@ -1,212 +0,0 @@
# 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

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

View File

@ -1,2 +0,0 @@
# Environment Setup
Instructions for setting up the environment for Packer builds.

View File

@ -1,2 +0,0 @@
# Packer Templates
This document explains the Packer templates and their configuration.

View File

@ -1,110 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<USAGE
Usage: $0 <AMI_ID> <EDITION> <UBUNTU_VERSION> <CPU_ARCH> <BASE_REGION> "<TARGET_REGIONS>" <PROJECT_TAG>
Example:
$0 ami-1234567890 base 2204 amd64 ap-northeast-1 "ap-northeast-1 ap-east-1" Cloud-Neutra
USAGE
}
if [ "$#" -ne 7 ]; then
echo "[ami-replicate] ERROR: Invalid arguments" >&2
usage
exit 1
fi
AMI_ID="$1"
EDITION="$2"
UBUNTU_VERSION="$3"
CPU_ARCH="$4"
BASE_REGION="$5"
TARGET_REGIONS_STR="$6"
PROJECT_TAG="$7"
if ! command -v aws >/dev/null 2>&1; then
echo "[ami-replicate] ERROR: aws CLI is required." >&2
exit 1
fi
if [ -z "$TARGET_REGIONS_STR" ]; then
echo "[ami-replicate] ERROR: TARGET_REGIONS cannot be empty." >&2
exit 1
fi
# Normalize target regions into an array
IFS=' ' read -r -a TARGET_REGIONS <<< "$TARGET_REGIONS_STR"
if [ "${#TARGET_REGIONS[@]}" -eq 0 ]; then
echo "[ami-replicate] ERROR: No target regions provided." >&2
exit 1
fi
# Fetch AMI metadata from base region
read -r IMAGE_NAME IMAGE_DESC <<< "$(aws ec2 describe-images \
--region "$BASE_REGION" \
--image-ids "$AMI_ID" \
--query 'Images[0].[Name,Description]' \
--output text)"
if [ -z "$IMAGE_NAME" ] || [ "$IMAGE_NAME" = "None" ]; then
echo "[ami-replicate] ERROR: Unable to resolve AMI name for $AMI_ID in $BASE_REGION" >&2
exit 1
fi
if [ -z "$IMAGE_DESC" ] || [ "$IMAGE_DESC" = "None" ]; then
IMAGE_DESC="Cloud-Neutra ${EDITION} image Ubuntu ${UBUNTU_VERSION} ${CPU_ARCH}"
fi
TAG_SET=(
Key=Name,Value="$IMAGE_NAME"
Key=Project,Value="$PROJECT_TAG"
Key=Edition,Value="$EDITION"
Key=UbuntuVersion,Value="$UBUNTU_VERSION"
Key=Architecture,Value="$CPU_ARCH"
Key=Role,Value=Golden-Image
Key=SourceRegion,Value="$BASE_REGION"
)
for REGION in "${TARGET_REGIONS[@]}"; do
if [ "$REGION" = "$BASE_REGION" ]; then
echo "[ami-replicate] Skip base region $BASE_REGION"
continue
fi
echo "[ami-replicate] Replicating $AMI_ID ($IMAGE_NAME) to $REGION ..."
NEW_AMI_ID=$(aws ec2 copy-image \
--region "$REGION" \
--source-region "$BASE_REGION" \
--source-image-id "$AMI_ID" \
--name "$IMAGE_NAME" \
--description "$IMAGE_DESC" \
--query 'ImageId' \
--output text)
echo "[ami-replicate] Waiting for AMI $NEW_AMI_ID in $REGION to become available ..."
aws ec2 wait image-available --region "$REGION" --image-ids "$NEW_AMI_ID"
echo "[ami-replicate] Tagging AMI $NEW_AMI_ID in $REGION"
aws ec2 create-tags --region "$REGION" --resources "$NEW_AMI_ID" --tags "${TAG_SET[@]}"
SNAPSHOT_IDS=$(aws ec2 describe-images \
--region "$REGION" \
--image-ids "$NEW_AMI_ID" \
--query 'Images[0].BlockDeviceMappings[].Ebs.SnapshotId' \
--output text)
if [ -n "$SNAPSHOT_IDS" ]; then
for SNAP_ID in $SNAPSHOT_IDS; do
echo "[ami-replicate] Tagging snapshot $SNAP_ID in $REGION"
aws ec2 create-tags --region "$REGION" --resources "$SNAP_ID" --tags "${TAG_SET[@]}"
done
fi
echo "[ami-replicate] Completed replication to $REGION (AMI: $NEW_AMI_ID)"
done
echo "[ami-replicate] Replication process finished."

View File

@ -1,60 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# 强制非交互模式(解决 debconf / dpkg-preconfigure 报错)
export DEBIAN_FRONTEND=noninteractive
export DEBCONF_NONINTERACTIVE_SEEN=true
echo "[Cloud-Neutra] OS Base Initialization"
##############################################
# Enable standard repositories
##############################################
sudo add-apt-repository universe -y || true
sudo add-apt-repository multiverse -y || true
sudo add-apt-repository restricted -y || true
sudo sed -i 's/# deb/deb/g' /etc/apt/sources.list
sudo apt-get update -y
##############################################
# Safe upgrade (no kernel updates)
##############################################
sudo apt-get dist-upgrade -y --no-install-recommends
##############################################
# Remove snapd
##############################################
if command -v snap >/dev/null 2>&1; then
sudo systemctl stop snapd.service || true
fi
sudo apt-get remove --purge -y snapd || true
sudo rm -rf /var/cache/snapd/ ~/snap /snap || true
##############################################
# Remove MOTD noise and useless packages
##############################################
sudo apt-get remove --purge -y \
landscape-common \
update-notifier-common \
motd-news-config \
apport \
whoopsie || true
sudo rm -rf /etc/update-motd.d/* || true
##############################################
# Add minimal essential tools
##############################################
sudo apt-get install -y --no-install-recommends \
jq curl unzip gnupg lsb-release ca-certificates \
software-properties-common net-tools iproute2 iptables
##############################################
# Disable auto-update timers
##############################################
sudo systemctl disable apt-daily.service apt-daily-upgrade.service || true
sudo systemctl disable apt-daily.timer apt-daily-upgrade.timer || true
echo "[Cloud-Neutra] Base OS setup completed."

View File

@ -1,35 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# 强制非交互模式(解决 debconf / dpkg-preconfigure 报错)
export DEBIAN_FRONTEND=noninteractive
export DEBCONF_NONINTERACTIVE_SEEN=true
echo "[Cloud-Neutra] System Hardening"
##############################################
# SSH hardening
##############################################
sudo sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/^X11Forwarding yes/X11Forwarding no/' /etc/ssh/sshd_config
##############################################
# Sysctl tuning (safe defaults)
##############################################
cat <<EOF | sudo tee /etc/sysctl.d/99-cloud-neutra.conf
fs.inotify.max_user_watches=524288
vm.swappiness=10
net.ipv4.ip_forward=1
net.ipv4.conf.all.rp_filter=1
EOF
sudo sysctl --system || true
##############################################
# Journald persistent logging
##############################################
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
echo "[Cloud-Neutra] Hardening complete."

View File

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[Cloud-Neutra] Cleanup phase"
sudo apt-get autoremove -y
sudo apt-get clean -y
sudo rm -rf /var/lib/apt/lists/*
sudo rm -rf /tmp/* /var/tmp/*
# Cloud images best practice
sudo truncate -s 0 /var/log/wtmp || true
sudo truncate -s 0 /var/log/lastlog || true
echo "[Cloud-Neutra] Cleanup complete."

View File

@ -1,33 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[Cloud-Neutra] Installing containerd + nerdctl"
ARCH="$(uname -m)"
NERDCTL_VERSION="2.2.0"
##############################################
# Install containerd
##############################################
sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo systemctl enable containerd
sudo systemctl restart containerd
##############################################
# Install nerdctl
##############################################
if [[ "$ARCH" == "x86_64" ]]; then
URL="https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-amd64.tar.gz"
else
URL="https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-arm64.tar.gz"
fi
curl -LO $URL
tar -xzf nerdctl-*.tar.gz
sudo mv nerdctl /usr/local/bin/nerdctl
rm -f nerdctl-*.tar.gz
echo "[Cloud-Neutra] container edition installed."

View File

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[Cloud-Neutra] Installing K3s (skip start)"
curl -sfL https://get.k3s.io -o install_k3s.sh
chmod +x install_k3s.sh
# Skip start (important for AMI)
sudo INSTALL_K3S_SKIP_START=true ./install_k3s.sh
sudo systemctl disable k3s || true
echo "[Cloud-Neutra] K3s installed (not started)."

View File

@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[Cloud-Neutra] Installing Sealos"
ARCH="$(uname -m)"
VERSION="5.0.0-alpha1"
if [[ "$ARCH" == "x86_64" ]]; then
URL="https://github.com/labring/sealos/releases/download/v${VERSION}/sealos_${VERSION}_linux_amd64.tar.gz"
else
URL="https://github.com/labring/sealos/releases/download/v${VERSION}/sealos_${VERSION}_linux_arm64.tar.gz"
fi
curl -LO $URL
tar -xzf sealos_*.tar.gz
sudo mv sealos /usr/local/bin/
rm -f sealos_*.tar.gz
echo "[Cloud-Neutra] Sealos installed."

View File

@ -1,54 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[Cloud-Neutra] Installing Sealos GPU edition"
##############################################
# Install NVIDIA drivers (AWS/AliCloud safe)
##############################################
if lspci | grep -i nvidia >/dev/null 2>&1; then
echo "[GPU] NVIDIA GPU detected"
sudo apt-get install -y nvidia-driver-535
else
echo "[GPU] No NVIDIA GPU detected, skip driver"
fi
##############################################
# Install containerd (if not installed)
##############################################
sudo apt-get install -y containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo systemctl restart containerd
##############################################
# Install NVIDIA container toolkit
##############################################
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list \
| sudo tee /etc/apt/sources.list.d/libnvidia-container.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=containerd
sudo systemctl restart containerd
##############################################
# Install Sealos
##############################################
ARCH="$(uname -m)"
VERSION="5.0.0-alpha1"
if [[ "$ARCH" == "x86_64" ]]; then
URL="https://github.com/labring/sealos/releases/download/v${VERSION}/sealos_${VERSION}_linux_amd64.tar.gz"
else
URL="https://github.com/labring/sealos/releases/download/v${VERSION}/sealos_${VERSION}_linux_arm64.tar.gz"
fi
curl -LO "$URL"
tar -xzf sealos_*.tar.gz
sudo mv sealos /usr/local/bin/
rm -f sealos_*.tar.gz
echo "[Cloud-Neutra] Sealos GPU edition installed."

View File

@ -1,124 +0,0 @@
###############################################################
# Cloud-Neutra AWS AMI Builder (Multi-Arch / Multi-LTS)
# This file is the COMMON builder template inherited by:
# base / container / k3s / sealos / sealos-gpu
###############################################################
packer {
required_plugins {
amazon = {
version = ">= 1.2.8"
source = "github.com/hashicorp/amazon"
}
}
}
###############################################################
# Input Variables
###############################################################
variable "cpu_arch" {
type = string
description = "CPU architecture (amd64 or arm64)"
default = "amd64"
}
###############################################################
# Locals override `edition` / `ubuntu_version` in edition-specific template
###############################################################
locals {
edition = lookup(var, "edition", "container")
ubuntu_version = lookup(var, "ubuntu_version", "2204")
arch_map = {
amd64 = "amd64"
arm64 = "arm64"
}
ubuntu_codename = lookup(
{
"2204" = "jammy"
"2404" = "noble"
},
local.ubuntu_version,
"unknown"
)
ubuntu_version_dot = lookup(
{
"2204" = "22.04"
"2404" = "24.04"
},
local.ubuntu_version,
"unknown"
)
}
###############################################################
# AMI Builder
###############################################################
source "amazon-ebs" "this" {
region = "ap-northeast-1"
# Arm = t4g, AMD64 = t3
instance_type = var.cpu_arch == "arm64" ? "t4g.micro" : "t3.micro"
ami_name = "Cloud-Neutra-${local.edition}-VM-${local.ubuntu_version}-${var.cpu_arch}-{{timestamp}}"
ami_description = "Cloud-Neutra ${local.edition} image Ubuntu ${local.ubuntu_version} ${var.cpu_arch}"
ssh_username = "ubuntu"
###############################################################
# Official Ubuntu AMI Filter (AWS official image name pattern)
#
# Example name pattern:
# ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20240229
###############################################################
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-${local.ubuntu_codename}-${local.ubuntu_version_dot}-${local.arch_map[var.cpu_arch]}-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["099720109477"] # Canonical
}
###############################################################
# Tags
###############################################################
tags = {
Project = "Cloud-Neutra"
OS = "Ubuntu ${local.ubuntu_version}"
Edition = local.edition
Architecture = var.cpu_arch
Role = "Golden-Image"
}
run_tags = {
Name = "CN-${local.edition}-${local.ubuntu_version}-${var.cpu_arch}"
}
}
###############################################################
# Build Script Pipeline (Standardized)
###############################################################
build {
name = "Cloud-Neutra-${local.edition}-VM-${local.ubuntu_version}"
sources = ["source.amazon-ebs.this"]
provisioner "shell" {
script = "packer/scripts/base/01_os_base.sh"
}
provisioner "shell" {
script = "packer/scripts/base/02_hardening.sh"
}
provisioner "shell" {
script = "packer/scripts/flavors/${local.edition}.sh"
}
provisioner "shell" {
script = "packer/scripts/common/cleanup.sh"
}
}

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