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