From c24a9223b92be61107a4edb6ea81a5837e484106 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sat, 13 Sep 2025 09:26:30 +0800 Subject: [PATCH] feat(ci): migrate offline nginx ingress workflow and scripts - replace build-offline-package.yaml with offline-package-nginx-ingress.yaml - move ingress-installer.sh to scripts/ - add resolve_nginx_ingress_versions.sh --- .github/workflows/build-offline-package.yaml | 152 --------- .../offline-package-nginx-ingress.yaml | 293 ++++++++++++++++++ gitops/scripts/ingress-installer.sh | 176 ----------- scripts/ingress-installer.sh | 187 +++++++++++ scripts/resolve_nginx_ingress_versions.sh | 74 +++++ 5 files changed, 554 insertions(+), 328 deletions(-) delete mode 100644 .github/workflows/build-offline-package.yaml create mode 100644 .github/workflows/offline-package-nginx-ingress.yaml delete mode 100644 gitops/scripts/ingress-installer.sh create mode 100644 scripts/ingress-installer.sh create mode 100644 scripts/resolve_nginx_ingress_versions.sh diff --git a/.github/workflows/build-offline-package.yaml b/.github/workflows/build-offline-package.yaml deleted file mode 100644 index d9cc932..0000000 --- a/.github/workflows/build-offline-package.yaml +++ /dev/null @@ -1,152 +0,0 @@ -name: Build Offline Nginx Ingress Installer - -on: - push: - paths: - - 'scripts/ingress-installer.sh' - - '.github/workflows/build-offline-package.yaml' - workflow_dispatch: - -jobs: - build-offline-installer: - strategy: - matrix: - arch: [amd64, arm64] - runs-on: ubuntu-latest - outputs: - artifact-name: ${{ steps.upload-artifact.outputs.artifact-name }} - steps: - - uses: actions/checkout@v4 - - - name: Prepare directories - run: | - mkdir -p offline-installer/{images,charts,scripts,bin} - - - name: Download nerdctl binary for ${{ matrix.arch }} - run: | - wget https://github.com/containerd/nerdctl/releases/download/v2.0.3/nerdctl-2.0.3-linux-${{ matrix.arch }}.tar.gz \ - -O offline-installer/nerdctl.tar.gz - - - name: Pull & export required images - run: | - docker pull nginx/nginx-ingress:2.4.0 - docker pull registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407 - - docker save nginx/nginx-ingress:2.4.0 \ - -o offline-installer/images/nginx-ingress.tar - - docker save registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407 \ - -o offline-installer/images/kube-webhook-certgen.tar - - - name: Download Helm Chart (nginx-stable/nginx-ingress v0.15.0) - run: | - helm repo add nginx-stable https://helm.nginx.com/stable - helm repo update - helm pull nginx-stable/nginx-ingress --version=0.15.0 --untar --untardir offline-installer/charts - - - name: Copy installer script - run: | - cp scripts/ingress-installer.sh offline-installer/scripts/ - chmod +x offline-installer/scripts/ingress-installer.sh - - - name: Package offline installer - run: | - cd offline-installer - tar czvf ../offline-setup-nginx-ingress-${{ matrix.arch }}.tar.gz ./ - cd .. - - - name: Upload artifact - id: upload-artifact - uses: actions/upload-artifact@v4 - with: - name: offline-setup-nginx-ingress-${{ matrix.arch }} - path: offline-setup-nginx-ingress-${{ matrix.arch }}.tar.gz - - test-offline-installer: - needs: build-offline-installer - strategy: - matrix: - arch: [amd64] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Download offline installer artifact for ${{ matrix.arch }} - uses: actions/download-artifact@v4 - with: - name: offline-setup-nginx-ingress-${{ matrix.arch }} - path: offline-test - - - name: Setup K3s and KUBECONFIG for user - run: | - curl -sfL https://get.k3s.io | sudo sh - - # 配置当前用户的 kubeconfig - mkdir -p $HOME/.kube - sudo cp /etc/rancher/k3s/k3s.yaml $HOME/.kube/config - sudo chown $USER:$USER $HOME/.kube/config - # 测试 kubectl 可用性(不需要 sudo) - kubectl get nodes - kubectl version - - - name: Install Helm - run: | - curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash - - - name: Load offline installer package - run: | - cd offline-test - tar -xzvf offline-setup-nginx-ingress-${{ matrix.arch }}.tar.gz - sudo tar xzvf nerdctl.tar.gz -C /usr/local/bin/ - docker load -i images/nginx-ingress.tar - docker load -i images/kube-webhook-certgen.tar - cd .. - - - name: Run offline installer in K3S cluster - run: | - cd offline-test - bash scripts/ingress-installer.sh # ❗不要用 sudo,除非你传入 KUBECONFIG - sleep 10 - helm list -A - kubectl -n ingress get pods - - publish-release: - needs: test-offline-installer - runs-on: ubuntu-latest - env: - tag_name: offline-nginx-ingress-${{ 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: Daily Build ${{ env.tag_name }} - draft: false - prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Download amd64 artifact - uses: actions/download-artifact@v4 - with: - name: offline-setup-nginx-ingress-amd64 - path: release-artifacts - - - name: Download arm64 artifact - uses: actions/download-artifact@v4 - with: - name: offline-setup-nginx-ingress-arm64 - path: release-artifacts - - - name: Upload offline installers to GitHub Release - uses: softprops/action-gh-release@v1 - with: - tag_name: ${{ env.tag_name }} - files: | - release-artifacts/offline-setup-nginx-ingress-amd64.tar.gz - release-artifacts/offline-setup-nginx-ingress-arm64.tar.gz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/offline-package-nginx-ingress.yaml b/.github/workflows/offline-package-nginx-ingress.yaml new file mode 100644 index 0000000..962e625 --- /dev/null +++ b/.github/workflows/offline-package-nginx-ingress.yaml @@ -0,0 +1,293 @@ +name: Build Offline Nginx Ingress Installer + +on: + push: + paths: + - 'scripts/ingress-installer.sh' + - 'scripts/resolve_nginx_ingress_versions.sh' + - '.github/workflows/offline-package-nginx-ingress.yaml' + workflow_dispatch: + inputs: + tag: + description: "Release tag to use/sync (e.g., v0.133.0). Leave empty to use offline-nginx-ingress-" + required: false + type: string + image_tag: + description: "Override image tag for nginx/nginx-ingress (e.g., 5.1.1). Leave empty to auto-resolve" + required: false + type: string + chart_version: + description: "Override helm chart version for nginx-stable/nginx-ingress (e.g., 1.2.3). Leave empty to auto-resolve" + required: false + type: string + major_whitelist: + description: "Allowed major versions for image tag (regex alternation). Default: 5 (i.e., ^5\\.)" + required: false + type: string + default: "5" + +permissions: + contents: write # 需要创建/上传 Release 资产 + +concurrency: + group: build-offline-nginx-ingress + cancel-in-progress: false + +jobs: + build-offline-installer: + strategy: + matrix: + arch: [amd64, arm64] + runs-on: ubuntu-latest + outputs: + image_tag: ${{ steps.resolve.outputs.image_tag }} + chart_version: ${{ steps.resolve.outputs.chart_version }} + artifact-name: ${{ steps.upload-artifact.outputs.artifact-name }} + 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 nginx-stable https://helm.nginx.com/stable + helm repo update + + - name: Resolve latest versions + id: resolve + env: + OVERRIDE_IMAGE_TAG: ${{ github.event.inputs.image_tag }} + OVERRIDE_CHART_VERSION: ${{ github.event.inputs.chart_version }} + MAJOR_WHITELIST: ${{ github.event.inputs.major_whitelist }} + run: | + set -euo pipefail + bash scripts/resolve_nginx_ingress_versions.sh + + - name: Prepare directories + run: | + set -euo pipefail + mkdir -p offline-installer/{images,charts,scripts,bin} + + # 复制并“盖章”版本号到安装脚本 + - name: Stage installer script and stamp resolved image tag + env: + IMG_TAG: ${{ steps.resolve.outputs.image_tag }} + run: | + set -euo pipefail + cp scripts/ingress-installer.sh offline-installer/scripts/ + # 替换默认的 NGINX_IC_IMAGE 与 OCI_NGINX_REF 中的 2.4.0 为解析到的 ${IMG_TAG} + sed -i -E "s|(NGINX_IC_IMAGE:=nginx/nginx-ingress:)[0-9]+\.[0-9]+\.[0-9]+|\1${IMG_TAG}|" offline-installer/scripts/ingress-installer.sh + sed -i -E "s|(OCI_NGINX_REF:=nginx-ingress-)[0-9]+\.[0-9]+\.[0-9]+|\1${IMG_TAG}|" offline-installer/scripts/ingress-installer.sh + chmod +x offline-installer/scripts/ingress-installer.sh + + - name: Download nerdctl binary for ${{ matrix.arch }} + run: | + set -euo pipefail + wget https://github.com/containerd/nerdctl/releases/download/v2.0.3/nerdctl-2.0.3-linux-${{ matrix.arch }}.tar.gz \ + -O offline-installer/nerdctl.tar.gz + + - name: Pull & export required images (nginx/nginx-ingress:${{ steps.resolve.outputs.image_tag }}) + env: + IMG_TAG: ${{ steps.resolve.outputs.image_tag }} + run: | + set -euo pipefail + docker pull "nginx/nginx-ingress:${IMG_TAG}" + docker pull "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407" + + docker save "nginx/nginx-ingress:${IMG_TAG}" \ + -o offline-installer/images/nginx-ingress.tar + + docker save "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407" \ + -o offline-installer/images/kube-webhook-certgen.tar + + - name: Download Helm Chart (nginx-stable/nginx-ingress ${{ steps.resolve.outputs.chart_version }}) + env: + CHART_VERSION: ${{ steps.resolve.outputs.chart_version }} + run: | + set -euo pipefail + helm pull nginx-stable/nginx-ingress --version="${CHART_VERSION}" --untar --untardir offline-installer/charts + + - name: Copy installer script + run: | + set -euo pipefail + cp scripts/ingress-installer.sh offline-installer/scripts/ + chmod +x offline-installer/scripts/ingress-installer.sh + + - name: Package offline installer + run: | + set -euo pipefail + cd offline-installer + tar czvf ../offline-setup-nginx-ingress-${{ matrix.arch }}.tar.gz ./ + cd .. + + - name: Upload artifact + id: upload-artifact + uses: actions/upload-artifact@v4 + with: + name: offline-setup-nginx-ingress-${{ matrix.arch }} + path: offline-setup-nginx-ingress-${{ matrix.arch }}.tar.gz + + test-offline-installer: + needs: build-offline-installer + strategy: + matrix: + arch: [amd64] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download offline installer artifact for ${{ matrix.arch }} + uses: actions/download-artifact@v4 + with: + name: offline-setup-nginx-ingress-${{ matrix.arch }} + path: offline-test + + - name: Setup K3s and KUBECONFIG for user + run: | + set -euo pipefail + curl -sfL https://get.k3s.io | sudo sh - + mkdir -p $HOME/.kube + sudo cp /etc/rancher/k3s/k3s.yaml $HOME/.kube/config + sudo chown $USER:$USER $HOME/.kube/config + kubectl get nodes + kubectl version --client=true + + - name: Install Helm + run: | + set -euo pipefail + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + + - name: Load offline installer package + run: | + set -euo pipefail + cd offline-test + tar -xzvf offline-setup-nginx-ingress-${{ matrix.arch }}.tar.gz + sudo tar xzvf nerdctl.tar.gz -C /usr/local/bin/ + docker load -i images/nginx-ingress.tar + docker load -i images/kube-webhook-certgen.tar + cd .. + + - name: Run offline installer in K3S cluster + run: | + set -euo pipefail + cd offline-test + bash scripts/ingress-installer.sh + sleep 10 + helm list -A + kubectl -n ingress get pods + + publish-release: + needs: test-offline-installer + runs-on: ubuntu-latest + env: + TAG_NAME: ${{ github.event.inputs.tag != '' && github.event.inputs.tag || format('offline-nginx-ingress-{0}', github.run_number) }} + RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }} + RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }} + VPS_HOST: ${{ secrets.VPS_HOST }} + REMOTE_ROOT: /data/update-server/nginx-ingress + steps: + - uses: actions/checkout@v4 + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + with: + tag_name: ${{ env.TAG_NAME }} + release_name: Build ${{ env.TAG_NAME }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Download amd64 artifact + uses: actions/download-artifact@v4 + with: + name: offline-setup-nginx-ingress-amd64 + path: release-artifacts + + - name: Download arm64 artifact + uses: actions/download-artifact@v4 + with: + name: offline-setup-nginx-ingress-arm64 + path: release-artifacts + + - name: Upload offline installers to GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ env.TAG_NAME }} + files: | + release-artifacts/offline-setup-nginx-ingress-amd64.tar.gz + release-artifacts/offline-setup-nginx-ingress-arm64.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # === Rsync 到远端 === + - name: Ensure deps (rsync, ssh) + run: | + set -euo pipefail + sudo apt-get update -y + sudo apt-get install -y rsync openssh-client + + - name: Init SSH + run: | + set -euo pipefail + mkdir -p ~/.ssh + echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts + + - name: Rsync release assets to remote + run: | + set -euo pipefail + REMOTE_DIR="${REMOTE_ROOT}/${TAG_NAME}" + ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" "mkdir -p '${REMOTE_DIR}'" + echo "Rsync -> ${VPS_HOST}:${REMOTE_DIR}/" + rsync -av -e "ssh -i ~/.ssh/id_rsa" \ + release-artifacts/offline-setup-nginx-ingress-amd64.tar.gz \ + release-artifacts/offline-setup-nginx-ingress-arm64.tar.gz \ + "${RSYNC_SSH_USER}@${VPS_HOST}:${REMOTE_DIR}/" + + retention: + name: Remote retention (keep latest 3) + needs: publish-release + runs-on: ubuntu-latest + env: + RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }} + RSYNC_SSH_USER: ${{ secrets.RSYNC_SSH_USER }} + VPS_HOST: ${{ secrets.VPS_HOST }} + REMOTE_ROOT: /data/update-server/nginx-ingress + steps: + - name: Init SSH + run: | + set -euo pipefail + mkdir -p ~/.ssh + echo "$RSYNC_SSH_KEY" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts + + - name: Prune old versions on remote (keep 3) + run: | + set -euo pipefail + ssh -i ~/.ssh/id_rsa "${RSYNC_SSH_USER}@${VPS_HOST}" bash -lc ' + set -euo pipefail + cd "'"${REMOTE_ROOT}"'" || exit 0 + keep=3 + # 同时兼容两类目录命名:offline-nginx-ingress-* 和 v + mapfile -t all < <(ls -1 | grep -E "^(offline-nginx-ingress-|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 + ' diff --git a/gitops/scripts/ingress-installer.sh b/gitops/scripts/ingress-installer.sh deleted file mode 100644 index cb827b4..0000000 --- a/gitops/scripts/ingress-installer.sh +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env bash -# gitops/scripts/ingress-installer.sh -set -euo pipefail - -# ====================== -# Config & Defaults -# ====================== -: "${NGINX_IC_IMAGE:=nginx/nginx-ingress:2.4.0}" -: "${CERT_IMG:=registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407}" - -# 这些是我们在构建离线 OCI 归档时写入的“内部引用名”(ref.name) -# 若你的打包工作流改了它们,这里也要相应修改 -: "${OCI_NGINX_REF:=nginx-ingress-2.4.0}" -: "${OCI_CERT_REF:=kube-webhook-certgen}" - -: "${CHART_DIR:=./charts/nginx-ingress}" -: "${NAMESPACE:=ingress}" -: "${OCI_ARCHIVE:=images/oci-archive.tar}" -: "${NERDCTL_TAR:=nerdctl.tar.gz}" - -# 1st arg: Ingress 对外 IP(默认取本机第一个 IP) -INGRESS_IP="${1:-$(hostname -I 2>/dev/null | awk '{print $1}')}" -# 2nd arg: 节点标签(形如 "node-role=ingress") -NODE_LABEL="${2:-}" - -SUDO="" -if [ "$(id -u)" -ne 0 ]; then - SUDO="sudo" -fi - -log() { echo -e "$@"; } -die() { echo "❌ $*" >&2; exit 1; } -ok() { echo "✅ $*"; } -warn() { echo "⚠️ $*"; } - -have_cmd() { command -v "$1" &>/dev/null; } - -# ====================== -# Nerdctl Install (wrapper+多平台) -# ====================== -install_nerdctl() { - if [ -f "${NERDCTL_TAR}" ]; then - log "📦 安装 nerdctl(多平台 + wrapper)..." - $SUDO tar xzf "${NERDCTL_TAR}" -C /usr/local/bin/ - $SUDO chmod +x /usr/local/bin/nerdctl || true - else - warn "未找到 ${NERDCTL_TAR},跳过解包(确保系统已有 nerdctl/ctr)。" - fi -} - -# ====================== -# Import OCI images -# ====================== -import_images_from_oci() { - [ -f "${OCI_ARCHIVE}" ] || die "未找到 OCI 归档: ${OCI_ARCHIVE}" - - log "📦 准备从 OCI 归档导入镜像: ${OCI_ARCHIVE}" - - # 情况 A:Docker 环境 - if have_cmd docker && docker info &>/dev/null; then - log "🔎 检测到 Docker 运行中。" - if ! have_cmd skopeo; then - die "检测到 Docker,但未安装 skopeo。Docker 无法直接导入 OCI Layout,请安装 skopeo 或在 containerd 环境执行。" - fi - log "🔁 使用 skopeo 将归档中的两个镜像导入 docker-daemon ..." - # 必须显式指定 oci-archive 内部的 ref.name - skopeo --insecure-policy copy --all "oci-archive:${OCI_ARCHIVE}:${OCI_NGINX_REF}" "docker-daemon:${NGINX_IC_IMAGE}" - skopeo --insecure-policy copy --all "oci-archive:${OCI_ARCHIVE}:${OCI_CERT_REF}" "docker-daemon:${CERT_IMG}" - ok "已导入到 Docker 本地镜像:${NGINX_IC_IMAGE}, ${CERT_IMG}" - return - fi - - # 情况 B:K3s 的 containerd - if [ -S /run/k3s/containerd/containerd.sock ]; then - log "🔎 检测到 K3s containerd,使用 ctr 导入(含多架构)..." - $SUDO ctr -n k8s.io images import --all-platforms "${OCI_ARCHIVE}" - # retag 成 chart 会使用的镜像名 - $SUDO ctr -n k8s.io images tag "${OCI_NGINX_REF}" "${NGINX_IC_IMAGE}" || true - $SUDO ctr -n k8s.io images tag "${OCI_CERT_REF}" "${CERT_IMG}" || true - ok "已导入并完成 tag:${NGINX_IC_IMAGE}, ${CERT_IMG}" - return - fi - - # 情况 C:通用 containerd - if [ -S /run/containerd/containerd.sock ]; then - log "🔎 检测到系统 containerd,使用 ctr 导入(含多架构)..." - $SUDO ctr -n k8s.io images import --all-platforms "${OCI_ARCHIVE}" - $SUDO ctr -n k8s.io images tag "${OCI_NGINX_REF}" "${NGINX_IC_IMAGE}" || true - $SUDO ctr -n k8s.io images tag "${OCI_CERT_REF}" "${CERT_IMG}" || true - ok "已导入并完成 tag:${NGINX_IC_IMAGE}, ${CERT_IMG}" - return - fi - - die "未检测到可用的容器运行时(docker 或 containerd)。" -} - -# ====================== -# Kubernetes & Helm -# ====================== -ensure_namespace() { - log "📁 创建命名空间 ${NAMESPACE}(如已存在忽略)" - kubectl create namespace "${NAMESPACE}" 2>/dev/null || true -} - -generate_values() { - log "🧾 生成 Helm values.yaml" - cat > values.yaml <> values.yaml <}" -log " Namespace: ${NAMESPACE}" -log " Chart Dir: ${CHART_DIR}" -log " Images: ${NGINX_IC_IMAGE} , ${CERT_IMG}" - -install_nerdctl -import_images_from_oci -ensure_namespace -generate_values -install_chart -apply_configmap_tuning -ok "离线安装完成,Ingress IP: ${INGRESS_IP}" diff --git a/scripts/ingress-installer.sh b/scripts/ingress-installer.sh new file mode 100644 index 0000000..4b4bd04 --- /dev/null +++ b/scripts/ingress-installer.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash +# gitops/scripts/ingress-installer.sh +# 目标:最小化参数/分支,专注“一键离线安装” +set -euo pipefail + +# ====================== +# Config & Defaults(仅支持环境变量覆盖) +# ====================== +: "${NGINX_IC_IMAGE:=nginx/nginx-ingress:2.4.0}" +: "${CERT_IMG:=registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407}" + +# 打包阶段写入的 OCI layout 内部引用名(如果你改了打包 ref.name,这里相应改) +: "${OCI_NGINX_REF:=nginx-ingress-2.4.0}" +: "${OCI_CERT_REF:=kube-webhook-certgen}" + +# 目录布局固定:charts、images、脚本位于离线包根目录 +: "${CHART_DIR:=./charts/nginx-ingress}" +: "${NAMESPACE:=ingress}" +: "${OCI_ARCHIVE:=images/oci-archive.tar}" # 优先使用 +: "${DOCKER_IMG_TAR:=images/nginx-ingress.tar}" # 回退(docker save) +: "${DOCKER_CERT_TAR:=images/kube-webhook-certgen.tar}" +: "${NERDCTL_TAR:=nerdctl.tar.gz}" + +# Ingress 暴露 IP(默认取本机第一块网卡 IP),节点选择器可选(key=value) +INGRESS_IP="${INGRESS_IP:-$(hostname -I 2>/dev/null | awk '{print $1}')}" +NODE_LABEL="${NODE_LABEL:-}" + +SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" + +log() { echo -e "$@"; } +die() { echo "❌ $*" >&2; exit 1; } +ok() { echo "✅ $*"; } +warn() { echo "⚠️ $*"; } +have() { command -v "$1" &>/dev/null; } + +# ====================== +# Nerdctl Install (wrapper) +# ====================== +install_nerdctl() { + if [ -f "${NERDCTL_TAR}" ]; then + log "📦 安装 nerdctl(wrapper)..." + $SUDO tar xzf "${NERDCTL_TAR}" -C /usr/local/bin/ + $SUDO chmod +x /usr/local/bin/nerdctl || true + fi +} + +# ====================== +# Import images(优先 OCI,其次 docker save tar) +# ====================== +import_images_from_oci() { + # 首选:OCI 归档 + if [ -f "${OCI_ARCHIVE}" ]; then + log "📦 从 OCI 归档导入镜像:${OCI_ARCHIVE}" + # A) Docker 环境(需要 skopeo) + if have docker && docker info &>/dev/null; then + if have skopeo; then + skopeo --insecure-policy copy --all "oci-archive:${OCI_ARCHIVE}:${OCI_NGINX_REF}" "docker-daemon:${NGINX_IC_IMAGE}" + skopeo --insecure-policy copy --all "oci-archive:${OCI_ARCHIVE}:${OCI_CERT_REF}" "docker-daemon:${CERT_IMG}" + ok "OCI → docker-daemon 导入完成" + return + else + warn "docker 环境未安装 skopeo,改用 docker-archive 回退(需 images/*.tar)" + fi + fi + # B) containerd 环境(K3s 或系统 containerd) + if [ -S /run/k3s/containerd/containerd.sock ] || [ -S /run/containerd/containerd.sock ]; then + local ns="k8s.io" + $SUDO ctr -n "${ns}" images import --all-platforms "${OCI_ARCHIVE}" + $SUDO ctr -n "${ns}" images tag "${OCI_NGINX_REF}" "${NGINX_IC_IMAGE}" || true + $SUDO ctr -n "${ns}" images tag "${OCI_CERT_REF}" "${CERT_IMG}" || true + ok "OCI → containerd 导入完成" + return + fi + warn "未检测到 docker/skopo 或 containerd 可直接用 OCI 导入,尝试 docker-archive 回退。" + fi + + # 回退:docker save 的 tar 包 + if [ -f "${DOCKER_IMG_TAR}" ] && [ -f "${DOCKER_CERT_TAR}" ]; then + log "📦 从 docker-archive tar 回退导入 images/*.tar" + if have docker && docker info &>/dev/null; then + docker load -i "${DOCKER_IMG_TAR}" + docker load -i "${DOCKER_CERT_TAR}" + ok "docker load 完成" + return + fi + if have nerdctl; then + nerdctl load -i "${DOCKER_IMG_TAR}" + nerdctl load -i "${DOCKER_CERT_TAR}" + ok "nerdctl load 完成" + return + fi + if [ -S /run/k3s/containerd/containerd.sock ] || [ -S /run/containerd/containerd.sock ]; then + $SUDO ctr -n k8s.io images import --all-platforms "${DOCKER_IMG_TAR}" + $SUDO ctr -n k8s.io images import --all-platforms "${DOCKER_CERT_TAR}" + ok "ctr import 完成" + return + fi + die "找不到可用容器运行时导入 images/*.tar" + fi + + die "未发现可用的镜像来源(缺少 ${OCI_ARCHIVE} 或 ${DOCKER_IMG_TAR}/${DOCKER_CERT_TAR)})" +} + +# ====================== +# Kubernetes & Helm +# ====================== +ensure_namespace() { + log "📁 创建命名空间 ${NAMESPACE}(如已存在忽略)" + kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - +} + +generate_values() { + log "🧾 生成 Helm values.yaml" + local repo tag + repo="${NGINX_IC_IMAGE%:*}" + tag="${NGINX_IC_IMAGE##*:}" + + cat > values.yaml <> values.yaml <}" +log " Namespace: ${NAMESPACE}" +log " Chart Dir: ${CHART_DIR}" +log " Images: ${NGINX_IC_IMAGE} , ${CERT_IMG}" + +install_nerdctl +import_images_from_oci +ensure_namespace +generate_values +install_chart +apply_configmap_tuning +ok "离线安装完成,Ingress IP: ${INGRESS_IP}" diff --git a/scripts/resolve_nginx_ingress_versions.sh b/scripts/resolve_nginx_ingress_versions.sh new file mode 100644 index 0000000..b136b91 --- /dev/null +++ b/scripts/resolve_nginx_ingress_versions.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# scripts/resolve_nginx_ingress_versions.sh +# 输出:写入 $GITHUB_OUTPUT -> image_tag, chart_version +# 环境变量: +# OVERRIDE_IMAGE_TAG 可选,手工覆盖镜像版本(如 5.1.1) +# OVERRIDE_CHART_VERSION 可选,手工覆盖 Helm Chart 版本(如 1.2.3) +# MAJOR_WHITELIST 可选,主版本白名单(默认 5),示例:'5' 或 '5|4' + +set -euo pipefail + +OVERRIDE_IMAGE_TAG="${OVERRIDE_IMAGE_TAG:-}" +OVERRIDE_CHART_VERSION="${OVERRIDE_CHART_VERSION:-}" +MAJOR_WHITELIST="${MAJOR_WHITELIST:-5}" + +resolve_image_tag() { + # 手工覆盖优先 + if [[ -n "${OVERRIDE_IMAGE_TAG}" ]]; then + echo "${OVERRIDE_IMAGE_TAG}" + return + fi + + tmp="$(mktemp)" + # 拉 3 页(足够覆盖最近更新) + for page in 1 2 3; do + curl -fsSL "https://hub.docker.com/v2/repositories/nginx/nginx-ingress/tags/?page_size=100&page=${page}&ordering=last_updated" \ + | jq -r '.results[].name' >> "${tmp}" + done + + # 仅保留纯 semver,过滤预发布;再按主版本白名单过滤;语义排序取最大 + latest="$(grep -E "^[0-9]+\.[0-9]+\.[0-9]+$" "${tmp}" \ + | grep -E "^(${MAJOR_WHITELIST})\." \ + | sort -V \ + | tail -n1 || true)" + rm -f "${tmp}" + + if [[ -z "${latest}" ]]; then + echo "Failed to resolve nginx/nginx-ingress image tag (major_whitelist=${MAJOR_WHITELIST})" >&2 + exit 1 + fi + echo "${latest}" +} + +resolve_chart_version() { + # 手工覆盖优先 + if [[ -n "${OVERRIDE_CHART_VERSION}" ]]; then + echo "${OVERRIDE_CHART_VERSION}" + return + fi + + # helm repo 已由 workflow 预先 add & update + chart_json="$(helm search repo nginx-stable/nginx-ingress --versions -o json)" + latest="$(echo "${chart_json}" \ + | jq -r '.[].version' \ + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -V \ + | tail -n1 || true)" + + if [[ -z "${latest}" ]]; then + echo "Failed to resolve helm chart version for nginx-stable/nginx-ingress" >&2 + exit 1 + fi + echo "${latest}" +} + +IMAGE_TAG="$(resolve_image_tag)" +CHART_VERSION="$(resolve_chart_version)" + +{ + echo "image_tag=${IMAGE_TAG}" + echo "chart_version=${CHART_VERSION}" +} >> "$GITHUB_OUTPUT" + +echo "Resolved => nginx/nginx-ingress:${IMAGE_TAG} ; chart=${CHART_VERSION} ; major_whitelist=${MAJOR_WHITELIST}" +