From a81f7df2a00ccf57d3e4d9a726896b371ca193d2 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Mon, 6 Apr 2026 14:27:01 +0300 Subject: [PATCH] feat(diun): add auto-update infrastructure - Custom DIUN image with git/ssh for script notifier - Auto-update script: detects new image versions, updates .tf files, pushes - ESO secret for git credentials, persistent repo clone PVC - GHA workflow to build custom DIUN image - Skips databases and CI/CD-managed images automatically --- .github/workflows/build-diun.yml | 28 ++++ stacks/diun/Dockerfile | 2 + stacks/diun/main.tf | 269 ++++++++++++++++++++++++++----- 3 files changed, 262 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/build-diun.yml create mode 100644 stacks/diun/Dockerfile diff --git a/.github/workflows/build-diun.yml b/.github/workflows/build-diun.yml new file mode 100644 index 00000000..e3061ee0 --- /dev/null +++ b/.github/workflows/build-diun.yml @@ -0,0 +1,28 @@ +name: Build Custom DIUN Image + +on: + push: + branches: [master] + paths: + - 'stacks/diun/Dockerfile' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: stacks/diun + platforms: linux/amd64 + push: true + tags: viktorbarzin/diun:latest diff --git a/stacks/diun/Dockerfile b/stacks/diun/Dockerfile new file mode 100644 index 00000000..cd0dd02f --- /dev/null +++ b/stacks/diun/Dockerfile @@ -0,0 +1,2 @@ +FROM crazymax/diun:latest +RUN apk add --no-cache git openssh-client sed diff --git a/stacks/diun/main.tf b/stacks/diun/main.tf index 3bf07df2..24ad6e14 100644 --- a/stacks/diun/main.tf +++ b/stacks/diun/main.tf @@ -41,6 +41,44 @@ resource "kubernetes_manifest" "external_secret" { depends_on = [kubernetes_namespace.diun] } +resource "kubernetes_manifest" "external_secret_git" { + manifest = { + apiVersion = "external-secrets.io/v1beta1" + kind = "ExternalSecret" + metadata = { + name = "diun-git-secrets" + namespace = "diun" + } + spec = { + refreshInterval = "15m" + secretStoreRef = { + name = "vault-kv" + kind = "ClusterSecretStore" + } + target = { + name = "diun-git-secrets" + } + data = [ + { + secretKey = "git_token" + remoteRef = { + key = "viktor" + property = "webhook_handler_git_token" + } + }, + { + secretKey = "git_user" + remoteRef = { + key = "viktor" + property = "webhook_handler_git_user" + } + } + ] + } + } + depends_on = [kubernetes_namespace.diun] +} + module "tls_secret" { source = "../../modules/kubernetes/setup_tls_secret" namespace = kubernetes_namespace.diun.metadata[0].name @@ -89,6 +127,28 @@ module "nfs_data" { nfs_path = "/mnt/main/diun" } +resource "kubernetes_persistent_volume_claim" "repo" { + wait_until_bound = false + metadata { + name = "diun-repo" + namespace = kubernetes_namespace.diun.metadata[0].name + annotations = { + "resize.topolvm.io/threshold" = "80%" + "resize.topolvm.io/increase" = "100%" + "resize.topolvm.io/storage_limit" = "5Gi" + } + } + spec { + access_modes = ["ReadWriteOnce"] + storage_class_name = "proxmox-lvm" + resources { + requests = { + storage = "1Gi" + } + } + } +} + resource "kubernetes_persistent_volume_claim" "data_proxmox" { wait_until_bound = false metadata { @@ -111,6 +171,81 @@ resource "kubernetes_persistent_volume_claim" "data_proxmox" { } } +resource "kubernetes_config_map_v1" "auto_update_script" { + metadata { + name = "diun-auto-update-script" + namespace = kubernetes_namespace.diun.metadata[0].name + } + data = { + "auto-update.sh" = <<-SCRIPT + #!/bin/sh + set -e + + # Only act on updates (not new or unchanged) + [ "$$DIUN_ENTRY_STATUS" = "update" ] || exit 0 + + IMAGE="$$DIUN_ENTRY_IMAGE" + NEW_TAG="$$DIUN_ENTRY_IMAGETAG" + + echo "[auto-update] Detected update: $$IMAGE -> $$NEW_TAG" + + # Skip databases + case "$$IMAGE" in + *postgres*|*mysql*|*redis*|*clickhouse*|*etcd*) echo "[auto-update] Skipping database image"; exit 0 ;; + esac + + # Skip custom images (handled by CI/CD) + case "$$IMAGE" in + viktorbarzin/*|registry.viktorbarzin.me/*|ancamilea/*|mghee/*) echo "[auto-update] Skipping CI/CD-managed image"; exit 0 ;; + esac + + # Skip kube-system / infrastructure images + case "$$IMAGE" in + registry.k8s.io/*|quay.io/tigera/*|quay.io/metallb/*|nvcr.io/*|reg.kyverno.io/*) echo "[auto-update] Skipping infrastructure image"; exit 0 ;; + esac + + # Acquire lock (serialize concurrent DIUN notifications) + exec 200>/tmp/auto-update.lock + flock -n 200 || { echo "[auto-update] Another update in progress, skipping"; exit 0; } + + cd /repo + + # Configure git + git config user.email "diun@viktorbarzin.me" + git config user.name "DIUN Auto-Update" + + # Pull latest using HTTPS with token + git remote set-url origin "https://$${GIT_USER}:$${GIT_TOKEN}@github.com/ViktorBarzin/infra.git" + git pull --rebase origin master || { echo "[auto-update] git pull failed"; exit 1; } + + # Find .tf files containing this image + MATCHES=$$(grep -rl "\"$${IMAGE}:" stacks/ --include="*.tf" 2>/dev/null || true) + [ -z "$$MATCHES" ] && { echo "[auto-update] No .tf file found for $$IMAGE"; exit 0; } + + # Update the image tag in each matching file + UPDATED=0 + for FILE in $$MATCHES; do + if sed -i "s|\"$${IMAGE}:[^\"]*\"|\"$${IMAGE}:$${NEW_TAG}\"|g" "$$FILE"; then + echo "[auto-update] Updated $$FILE" + UPDATED=1 + fi + done + + # Check if anything actually changed + if git diff --quiet; then + echo "[auto-update] No changes after update for $$IMAGE:$$NEW_TAG (already up to date)" + exit 0 + fi + + # Commit and push + git add -A stacks/ + git commit -m "auto-update: $${IMAGE} -> $${NEW_TAG}" + git push origin master + echo "[auto-update] Pushed update: $${IMAGE}:$${NEW_TAG}" + SCRIPT + } +} + resource "kubernetes_deployment" "diun" { metadata { name = "diun" @@ -142,8 +277,52 @@ resource "kubernetes_deployment" "diun" { } spec { service_account_name = "diun" + init_container { + name = "clone-repo" + image = "alpine/git:latest" + command = ["/bin/sh", "-c"] + args = [<<-EOF + if [ -d /repo/.git ]; then + cd /repo && git pull --rebase origin master || true + else + git clone https://$${GIT_USER}:$${GIT_TOKEN}@github.com/ViktorBarzin/infra.git /repo + fi + EOF + ] + env { + name = "GIT_USER" + value_from { + secret_key_ref { + name = "diun-git-secrets" + key = "git_user" + } + } + } + env { + name = "GIT_TOKEN" + value_from { + secret_key_ref { + name = "diun-git-secrets" + key = "git_token" + } + } + } + volume_mount { + name = "repo" + mount_path = "/repo" + } + resources { + requests = { + cpu = "10m" + memory = "64Mi" + } + limits = { + memory = "128Mi" + } + } + } container { - image = "crazymax/diun:latest" + image = "viktorbarzin/diun:latest" name = "diun" args = ["serve"] env { @@ -166,18 +345,9 @@ resource "kubernetes_deployment" "diun" { name = "DIUN_PROVIDERS_KUBERNETES" value = "true" } - # env { - # name = "DIUN_DEFAULTS_EXCLUDETAGS" - # value = "^.*nightly.*$" - # } - # env { - # name = "DIUN_DEFAULTS_INCLUDETAGS" - # value = "^\\d+\\.\\d+\\.\\d+$" - # } env { name = "DIUN_DEFAULTS_WATCHREPO" value = "true" - # value = "false" } env { name = "DIUN_DEFAULTS_MAXTAGS" @@ -187,21 +357,12 @@ resource "kubernetes_deployment" "diun" { name = "DIUN_DEFAULTS_SORTTAGS" value = "reverse" } - # DIUN_PROVIDERS_KUBERNETES_WATCHBYDEFAULT = "true" ?? - - // ntfy settings - # env { // disabled as if this fails, no other notifications are sent - # name = "DIUN_NOTIF_NTFY_ENDPOINT" - # value = "https://ntfy.viktorbarzin.me" - # } - # env { - # name = "DIUN_NOTIF_NTFY_TOPIC" - # value = "diun-updates" - # } - # env { - # name = "DIUN_NOTIF_NTFY_TOKEN" - # value = data.vault_kv_secret_v2.secrets.data["nfty_token"] - # } + # Script notifier for auto-updates + env { + name = "DIUN_NOTIF_SCRIPT_CMD" + value = "/scripts/auto-update.sh" + } + # Slack notifier (kept alongside script notifier) env { name = "DIUN_NOTIF_SLACK_WEBHOOKURL" value_from { @@ -211,30 +372,48 @@ resource "kubernetes_deployment" "diun" { } } } + # Git credentials for auto-update script env { - name = "LOG_LEVEL" - # value = "info" + name = "GIT_USER" + value_from { + secret_key_ref { + name = "diun-git-secrets" + key = "git_user" + } + } + } + env { + name = "GIT_TOKEN" + value_from { + secret_key_ref { + name = "diun-git-secrets" + key = "git_token" + } + } + } + env { + name = "LOG_LEVEL" value = "debug" } - # env { - # name = "DIUN_WATCH_FIRSTCHECKNOTIF" - # value = "true" # send notfication on start; subsequent checks check for newer versions and is what you need - # } - # env { - # name = "DIUN_NOTIF_NTFY_TIMEOUT" - # value = "10s" - # } volume_mount { name = "data" mount_path = "/data" } + volume_mount { + name = "scripts" + mount_path = "/scripts" + } + volume_mount { + name = "repo" + mount_path = "/repo" + } resources { requests = { cpu = "10m" - memory = "64Mi" + memory = "128Mi" } limits = { - memory = "64Mi" + memory = "256Mi" } } } @@ -244,7 +423,23 @@ resource "kubernetes_deployment" "diun" { claim_name = kubernetes_persistent_volume_claim.data_proxmox.metadata[0].name } } + volume { + name = "scripts" + config_map { + name = kubernetes_config_map_v1.auto_update_script.metadata[0].name + default_mode = "0755" + } + } + volume { + name = "repo" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim.repo.metadata[0].name + } + } } } } + lifecycle { + ignore_changes = [spec[0].template[0].spec[0].dns_config] + } }