diff --git a/.forgejo/actions/build-push-image/action.yaml b/.forgejo/actions/build-push-image/action.yaml
new file mode 100644
index 0000000..ac6f711
--- /dev/null
+++ b/.forgejo/actions/build-push-image/action.yaml
@@ -0,0 +1,54 @@
+name: 'Build and Push Image'
+description: 'Build a container image with Docker and push to zot registry'
+
+# TODO: Investigate zot tag immutability to prevent overwriting released versions
+# See: https://zotregistry.dev/v2.1.1/articles/immutable-tags/
+
+inputs:
+ context:
+ description: 'Build context path'
+ required: true
+ dockerfile:
+ description: 'Dockerfile path (relative to context)'
+ required: false
+ default: 'Dockerfile'
+ image_name:
+ description: 'Image name (without registry, e.g. blumeops/devpi)'
+ required: true
+ version:
+ description: 'Version tag (e.g. v1.0.0)'
+ required: true
+ registry:
+ description: 'Registry URL'
+ required: false
+ default: 'registry.tail8d86e.ts.net'
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Build image with Docker
+ shell: bash
+ run: |
+ echo "Building ${{ inputs.image_name }}:${{ inputs.version }}"
+ docker build \
+ --tag ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.version }} \
+ --tag ${{ inputs.registry }}/${{ inputs.image_name }}:${{ github.sha }} \
+ --file ${{ inputs.context }}/${{ inputs.dockerfile }} \
+ ${{ inputs.context }}
+
+ - name: Push to registry
+ shell: bash
+ run: |
+ echo "Pushing ${{ inputs.image_name }}:${{ inputs.version }}"
+ docker push ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.version }}
+ docker push ${{ inputs.registry }}/${{ inputs.image_name }}:${{ github.sha }}
+
+ - name: Summary
+ shell: bash
+ run: |
+ echo "Built and pushed:"
+ echo " ${{ inputs.registry }}/${{ inputs.image_name }}:${{ inputs.version }}"
+ echo " ${{ inputs.registry }}/${{ inputs.image_name }}:${{ github.sha }}"
+ echo ""
+ echo "Registry tags:"
+ curl -sf "https://${{ inputs.registry }}/v2/${{ inputs.image_name }}/tags/list" | jq -r '.tags[]' | sort -V | tail -10 || true
diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml
index 134992b..6aa3236 100644
--- a/.forgejo/workflows/test.yaml
+++ b/.forgejo/workflows/test.yaml
@@ -1,3 +1,4 @@
+# Workflow to verify CI environment and available tools
name: Test CI
on:
@@ -16,22 +17,22 @@ jobs:
- name: Verify tools
run: |
echo "=== Node.js ==="
- node --version
- npm --version
+ node --version || echo "Node.js not available"
+ npm --version || echo "npm not available"
echo ""
echo "=== Git ==="
git --version
echo ""
echo "=== Build tools ==="
- make --version | head -1
- gcc --version | head -1
+ make --version 2>&1 | head -1 || echo "make not available"
+ gcc --version 2>&1 | head -1 || echo "gcc not available"
echo ""
- echo "=== Docker ==="
- docker --version
+ echo "=== Container tools (Docker) ==="
+ docker --version || echo "Docker CLI not available"
echo ""
echo "=== Other tools ==="
- curl --version | head -1
- jq --version
+ curl --version 2>&1 | head -1 || echo "curl not available"
+ jq --version || echo "jq not available"
- name: Show repo info
run: |
diff --git a/.github/README.md b/.github/README.md
new file mode 100644
index 0000000..bea6b15
--- /dev/null
+++ b/.github/README.md
@@ -0,0 +1,9 @@
+# .github directory
+
+This directory contains configuration for GitHub-ecosystem tooling only.
+
+**Workflows and actions belong in `.forgejo/`** - this repository uses Forgejo Actions, not GitHub Actions.
+
+## Contents
+
+- `actionlint.yaml` - Configuration for actionlint pre-commit hook (custom runner labels)
diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml
new file mode 100644
index 0000000..12f3259
--- /dev/null
+++ b/.github/actionlint.yaml
@@ -0,0 +1,5 @@
+self-hosted-runner:
+ labels:
+ - docker-builder
+ - ubuntu-latest
+ - ubuntu-22.04
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 421de65..d673cc3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -86,4 +86,5 @@ repos:
rev: v1.7.10
hooks:
- id: actionlint-system
+ args: ['-config-file', '.github/actionlint.yaml']
files: ^\.forgejo/workflows/
diff --git a/CLAUDE.md b/CLAUDE.md
index 82ed044..1399f9a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -134,6 +134,13 @@ When migrating a service from indri to k8s, the Tailscale hostname must be freed
Use `ssh indri 'tailscale serve status --json'` to check current serve entries (the non-JSON output may be empty even when entries exist).
+## Container Image Releases
+
+```fish
+mise run container-list # Show containers and recent tags
+mise run container-release runner v1.0.0 # Tag and trigger build workflow
+```
+
## Third-Party Projects
When a task requires cloning or using a third-party git repository (e.g., for building from source), **ask the user to mirror it on forge first**, then clone from the mirror:
diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml
index 6e962f1..b12e905 100644
--- a/ansible/playbooks/indri.yml
+++ b/ansible/playbooks/indri.yml
@@ -61,6 +61,23 @@
no_log: true
tags: [forgejo]
+ # Forgejo runner token (for indri-based runner)
+ - name: Fetch forgejo runner token
+ ansible.builtin.command:
+ cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w3663ffnvkewbftncqxtcpeavy --fields runner_reg --reveal
+ delegate_to: localhost
+ register: _forgejo_runner_token
+ changed_when: false
+ no_log: true
+ check_mode: false
+ tags: [forgejo_runner]
+
+ - name: Set forgejo runner token fact
+ ansible.builtin.set_fact:
+ forgejo_runner_token: "{{ _forgejo_runner_token.stdout }}"
+ no_log: true
+ tags: [forgejo_runner]
+
roles:
- role: alloy
tags: alloy
@@ -82,3 +99,5 @@
tags: plex_metrics
- role: tailscale_serve
tags: tailscale-serve
+ - role: forgejo_runner
+ tags: forgejo_runner
diff --git a/ansible/roles/forgejo_runner/defaults/main.yml b/ansible/roles/forgejo_runner/defaults/main.yml
new file mode 100644
index 0000000..75cbd0c
--- /dev/null
+++ b/ansible/roles/forgejo_runner/defaults/main.yml
@@ -0,0 +1,23 @@
+---
+# Forgejo Runner - host execution mode
+#
+# The runner daemon runs directly on indri and executes jobs on the host.
+# This avoids container networking complexity since it can reach Forgejo
+# at localhost:3001 directly.
+
+forgejo_runner_binary: /Users/erichblume/code/3rd/forgejo-runner/forgejo-runner
+forgejo_runner_data_dir: /Users/erichblume/.forgejo-runner
+forgejo_runner_config_dir: /Users/erichblume/.config/forgejo-runner
+forgejo_runner_log_dir: /Users/erichblume/Library/Logs
+
+# Runner registration - use localhost since we're running on indri
+forgejo_runner_instance_url: "http://localhost:3001"
+forgejo_runner_name: "indri-host-runner"
+
+# Labels format for host execution: label:host
+# Jobs run directly on the host, not in containers
+forgejo_runner_labels: "ubuntu-latest:host,ubuntu-22.04:host"
+
+# Runner config
+forgejo_runner_capacity: 2
+forgejo_runner_timeout: 3h
diff --git a/ansible/roles/forgejo_runner/handlers/main.yml b/ansible/roles/forgejo_runner/handlers/main.yml
new file mode 100644
index 0000000..8ace37c
--- /dev/null
+++ b/ansible/roles/forgejo_runner/handlers/main.yml
@@ -0,0 +1,7 @@
+---
+- name: Restart forgejo-runner
+ listen: Restart forgejo-runner
+ ansible.builtin.shell: |
+ launchctl unload ~/Library/LaunchAgents/mcquack.forgejo-runner.plist 2>/dev/null || true
+ launchctl load ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
+ changed_when: true
diff --git a/ansible/roles/forgejo_runner/tasks/main.yml b/ansible/roles/forgejo_runner/tasks/main.yml
new file mode 100644
index 0000000..0f28d88
--- /dev/null
+++ b/ansible/roles/forgejo_runner/tasks/main.yml
@@ -0,0 +1,57 @@
+---
+# Forgejo Runner - host execution mode
+#
+# The runner daemon runs directly on indri using a locally compiled binary.
+# Jobs execute on the host, reaching Forgejo at localhost:3001.
+
+- name: Ensure forgejo-runner directories exist
+ ansible.builtin.file:
+ path: "{{ item }}"
+ state: directory
+ mode: '0755'
+ loop:
+ - "{{ forgejo_runner_data_dir }}"
+ - "{{ forgejo_runner_config_dir }}"
+
+- name: Deploy forgejo-runner config
+ ansible.builtin.template:
+ src: config.yaml.j2
+ dest: "{{ forgejo_runner_config_dir }}/config.yaml"
+ mode: '0644'
+ notify: Restart forgejo-runner
+
+- name: Check if runner is registered
+ ansible.builtin.stat:
+ path: "{{ forgejo_runner_data_dir }}/.runner"
+ register: forgejo_runner_registered
+
+- name: Register runner with Forgejo
+ ansible.builtin.command:
+ cmd: >
+ {{ forgejo_runner_binary }} register
+ --instance "{{ forgejo_runner_instance_url }}"
+ --token "{{ forgejo_runner_token }}"
+ --name "{{ forgejo_runner_name }}"
+ --labels "{{ forgejo_runner_labels }}"
+ --no-interactive
+ chdir: "{{ forgejo_runner_data_dir }}"
+ when: not forgejo_runner_registered.stat.exists
+ changed_when: true
+
+- name: Deploy forgejo-runner launchd plist
+ ansible.builtin.template:
+ src: forgejo-runner.plist.j2
+ dest: ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
+ mode: '0644'
+ notify: Restart forgejo-runner
+
+- name: Check if forgejo-runner is loaded
+ ansible.builtin.command: launchctl list mcquack.forgejo-runner
+ register: forgejo_runner_launchctl_check
+ changed_when: false
+ failed_when: false
+
+- name: Load forgejo-runner if not loaded
+ ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.forgejo-runner.plist
+ when: forgejo_runner_launchctl_check.rc != 0
+ changed_when: true
diff --git a/ansible/roles/forgejo_runner/templates/config.yaml.j2 b/ansible/roles/forgejo_runner/templates/config.yaml.j2
new file mode 100644
index 0000000..07bdb8d
--- /dev/null
+++ b/ansible/roles/forgejo_runner/templates/config.yaml.j2
@@ -0,0 +1,13 @@
+# {{ ansible_managed }}
+log:
+ level: info
+
+runner:
+ file: {{ forgejo_runner_data_dir }}/.runner
+ capacity: {{ forgejo_runner_capacity }}
+ timeout: {{ forgejo_runner_timeout }}
+
+# Even in host execution mode, some actions run in containers.
+# Use host networking so containers can access localhost services.
+container:
+ network: "host"
diff --git a/ansible/roles/forgejo_runner/templates/forgejo-runner.plist.j2 b/ansible/roles/forgejo_runner/templates/forgejo-runner.plist.j2
new file mode 100644
index 0000000..e04fa0d
--- /dev/null
+++ b/ansible/roles/forgejo_runner/templates/forgejo-runner.plist.j2
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Label
+ mcquack.forgejo-runner
+ ProgramArguments
+
+ {{ forgejo_runner_binary }}
+ daemon
+ --config
+ {{ forgejo_runner_config_dir }}/config.yaml
+
+ WorkingDirectory
+ {{ forgejo_runner_data_dir }}
+ EnvironmentVariables
+
+ PATH
+ /Users/erichblume/.local/share/mise/shims:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+ HOME
+ /Users/erichblume
+
+ RunAtLoad
+
+ KeepAlive
+
+ StandardOutPath
+ {{ forgejo_runner_log_dir }}/mcquack.forgejo-runner.out.log
+ StandardErrorPath
+ {{ forgejo_runner_log_dir }}/mcquack.forgejo-runner.err.log
+
+
diff --git a/argocd/apps/forgejo-runner.yaml b/argocd/apps/forgejo-runner.yaml
deleted file mode 100644
index a584d33..0000000
--- a/argocd/apps/forgejo-runner.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-# Forgejo Actions Runner
-# Runs in k8s, polls Forgejo for workflow jobs
-#
-# Before syncing, create the runner token secret:
-# kubectl create namespace forgejo-runner
-# op inject -i argocd/manifests/forgejo-runner/secret-token.yaml.tpl | kubectl apply -f -
-apiVersion: argoproj.io/v1alpha1
-kind: Application
-metadata:
- name: forgejo-runner
- namespace: argocd
-spec:
- project: default
- source:
- repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git
- targetRevision: main
- path: argocd/manifests/forgejo-runner
- destination:
- server: https://kubernetes.default.svc
- namespace: forgejo-runner
- syncPolicy:
- syncOptions:
- - CreateNamespace=true
diff --git a/argocd/manifests/forgejo-runner/Dockerfile b/argocd/manifests/forgejo-runner/Dockerfile
deleted file mode 100644
index e511440..0000000
--- a/argocd/manifests/forgejo-runner/Dockerfile
+++ /dev/null
@@ -1,29 +0,0 @@
-FROM code.forgejo.org/forgejo/runner:3.5.1
-
-# Switch to root to install packages
-USER root
-
-# The base image is Alpine Linux
-# Install tools needed for GitHub Actions and builds
-RUN apk add --no-cache \
- # Required for actions/checkout and other Node-based actions
- nodejs \
- npm \
- # Build essentials
- git \
- curl \
- wget \
- jq \
- make \
- gcc \
- g++ \
- musl-dev \
- # For container builds
- ca-certificates \
- docker-cli
-
-# Verify tools are available
-RUN node --version && npm --version && docker --version
-
-# Switch back to non-root user
-USER 1000
diff --git a/argocd/manifests/forgejo-runner/configmap.yaml b/argocd/manifests/forgejo-runner/configmap.yaml
deleted file mode 100644
index 584efe0..0000000
--- a/argocd/manifests/forgejo-runner/configmap.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: forgejo-runner-config
- namespace: forgejo-runner
-data:
- config.yaml: |
- log:
- level: info
- runner:
- file: /data/.runner
- capacity: 1
- timeout: 3h
diff --git a/argocd/manifests/forgejo-runner/deployment.yaml b/argocd/manifests/forgejo-runner/deployment.yaml
deleted file mode 100644
index 0848e4a..0000000
--- a/argocd/manifests/forgejo-runner/deployment.yaml
+++ /dev/null
@@ -1,63 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: forgejo-runner
- namespace: forgejo-runner
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: forgejo-runner
- template:
- metadata:
- labels:
- app: forgejo-runner
- spec:
- serviceAccountName: forgejo-runner
- containers:
- - name: runner
- image: registry.tail8d86e.ts.net/blumeops/forgejo-runner:latest
- env:
- # Use internal k8s service via Tailscale operator egress
- - name: FORGEJO_INSTANCE_URL
- value: "http://forge.tailscale.svc.cluster.local:3001"
- - name: RUNNER_NAME
- value: "k8s-runner-1"
- - name: RUNNER_TOKEN
- valueFrom:
- secretKeyRef:
- name: forgejo-runner-token
- key: token
- command:
- - /bin/sh
- - -c
- - |
- # Register runner if not already registered
- if [ ! -f /data/.runner ]; then
- forgejo-runner register \
- --instance "$FORGEJO_INSTANCE_URL" \
- --token "$RUNNER_TOKEN" \
- --name "$RUNNER_NAME" \
- --labels "ubuntu-latest:host,ubuntu-22.04:host" \
- --no-interactive
- fi
- # Start the runner daemon with config
- forgejo-runner daemon --config /config/config.yaml
- volumeMounts:
- - name: runner-data
- mountPath: /data
- - name: runner-config
- mountPath: /config
- resources:
- requests:
- memory: "256Mi"
- cpu: "100m"
- limits:
- memory: "1Gi"
- cpu: "1000m"
- volumes:
- - name: runner-data
- emptyDir: {}
- - name: runner-config
- configMap:
- name: forgejo-runner-config
diff --git a/argocd/manifests/forgejo-runner/kustomization.yaml b/argocd/manifests/forgejo-runner/kustomization.yaml
deleted file mode 100644
index 332c49c..0000000
--- a/argocd/manifests/forgejo-runner/kustomization.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: kustomize.config.k8s.io/v1beta1
-kind: Kustomization
-namespace: forgejo-runner
-resources:
- - namespace.yaml
- - serviceaccount.yaml
- - configmap.yaml
- - deployment.yaml
diff --git a/argocd/manifests/forgejo-runner/namespace.yaml b/argocd/manifests/forgejo-runner/namespace.yaml
deleted file mode 100644
index 19441b1..0000000
--- a/argocd/manifests/forgejo-runner/namespace.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-apiVersion: v1
-kind: Namespace
-metadata:
- name: forgejo-runner
diff --git a/argocd/manifests/forgejo-runner/secret-token.yaml.tpl b/argocd/manifests/forgejo-runner/secret-token.yaml.tpl
deleted file mode 100644
index 427d8df..0000000
--- a/argocd/manifests/forgejo-runner/secret-token.yaml.tpl
+++ /dev/null
@@ -1,10 +0,0 @@
-# Template for op inject
-# Usage: op inject -i secret-token.yaml.tpl | kubectl apply -f -
-apiVersion: v1
-kind: Secret
-metadata:
- name: forgejo-runner-token
- namespace: forgejo-runner
-type: Opaque
-stringData:
- token: "op://blumeops/w3663ffnvkewbftncqxtcpeavy/runner_reg"
diff --git a/argocd/manifests/forgejo-runner/serviceaccount.yaml b/argocd/manifests/forgejo-runner/serviceaccount.yaml
deleted file mode 100644
index ef8cb25..0000000
--- a/argocd/manifests/forgejo-runner/serviceaccount.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: v1
-kind: ServiceAccount
-metadata:
- name: forgejo-runner
- namespace: forgejo-runner
diff --git a/mise-tasks/container-list b/mise-tasks/container-list
new file mode 100755
index 0000000..21a2ad9
--- /dev/null
+++ b/mise-tasks/container-list
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+#MISE description="List available containers and their recent tags"
+
+set -euo pipefail
+
+REGISTRY="registry.tail8d86e.ts.net"
+WORKFLOW_DIR=".forgejo/workflows"
+
+echo "Container Images"
+echo "================"
+echo ""
+
+# Find all build-*.yaml workflows
+for workflow in "$WORKFLOW_DIR"/build-*.yaml; do
+ [[ -f "$workflow" ]] || continue
+
+ # Extract container name from filename: build-runner.yaml -> runner
+ filename=$(basename "$workflow")
+ container="${filename#build-}"
+ container="${container%.yaml}"
+
+ # Skip if not a container build workflow (check for image_name)
+ if ! grep -q "image_name:" "$workflow" 2>/dev/null; then
+ continue
+ fi
+
+ # Extract image name from workflow
+ image=$(grep -E "^\s+image_name:" "$workflow" | head -1 | awk '{print $2}')
+
+ echo "📦 $container"
+ echo " Image: $REGISTRY/$image"
+ echo " Workflow: $workflow"
+
+ # Query zot for recent tags
+ tags=$(curl -sf "https://$REGISTRY/v2/$image/tags/list" 2>/dev/null | jq -r '.tags // [] | .[]' | grep -E '^v[0-9]' | sort -V | tail -4 || true)
+
+ if [[ -n "$tags" ]]; then
+ echo " Recent tags:"
+ echo "$tags" | while read -r tag; do
+ echo " - $tag"
+ done
+ else
+ echo " Recent tags: (none)"
+ fi
+ echo ""
+done
+
+echo "---"
+echo "To release a new version:"
+echo " mise run container-release "
+echo ""
+echo "Example:"
+echo " mise run container-release runner v1.0.0"
diff --git a/mise-tasks/container-release b/mise-tasks/container-release
new file mode 100755
index 0000000..9e8802b
--- /dev/null
+++ b/mise-tasks/container-release
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+#MISE description="Release a container image by creating a git tag"
+
+set -euo pipefail
+
+CONTAINER="${1:-}"
+VERSION="${2:-}"
+
+if [[ -z "$CONTAINER" || -z "$VERSION" ]]; then
+ echo "Usage: mise run container-release "
+ echo ""
+ echo "Run 'mise run container-list' to see available containers and recent tags."
+ exit 1
+fi
+
+# Validate version format
+if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "Error: Version must be in format vX.Y.Z (e.g. v1.0.0)"
+ exit 1
+fi
+
+TAG="${CONTAINER}-${VERSION}"
+
+echo "Creating release tag: $TAG"
+echo ""
+
+# Check if tag already exists
+if git rev-parse "$TAG" >/dev/null 2>&1; then
+ echo "Error: Tag '$TAG' already exists"
+ echo "Existing tags for $CONTAINER:"
+ git tag -l "${CONTAINER}-v*" | sort -V | tail -5
+ exit 1
+fi
+
+# Find the workflow file to determine image name
+WORKFLOW_FILE=".forgejo/workflows/build-${CONTAINER}.yaml"
+if [[ ! -f "$WORKFLOW_FILE" ]]; then
+ echo "Error: No workflow found for container '$CONTAINER'"
+ echo ""
+ echo "Run 'mise run container-list' to see available containers."
+ exit 1
+fi
+
+# Extract image name from workflow
+IMAGE=$(grep -E "^\s+image_name:" "$WORKFLOW_FILE" | head -1 | awk '{print $2}')
+if [[ -z "$IMAGE" ]]; then
+ echo "Error: Could not determine image name from $WORKFLOW_FILE"
+ exit 1
+fi
+
+echo "Container: $CONTAINER"
+echo "Workflow: $WORKFLOW_FILE"
+echo "Image: registry.tail8d86e.ts.net/$IMAGE:$VERSION"
+echo ""
+
+# Confirm
+read -p "Create tag and push? [y/N] " -n 1 -r
+echo
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ echo "Aborted."
+ exit 0
+fi
+
+# Create and push tag
+git tag "$TAG"
+git push origin "$TAG"
+
+echo ""
+echo "✅ Tag '$TAG' created and pushed"
+echo ""
+echo "The workflow will now build and push:"
+echo " registry.tail8d86e.ts.net/$IMAGE:$VERSION"
+echo ""
+echo "Monitor the build at:"
+echo " https://forge.tail8d86e.ts.net/eblume/blumeops/actions"
diff --git a/pulumi/policy.hujson b/pulumi/policy.hujson
index 7f18820..037f085 100644
--- a/pulumi/policy.hujson
+++ b/pulumi/policy.hujson
@@ -74,6 +74,7 @@
"dst": ["tag:homelab"],
"ip": ["tcp:3001", "tcp:2200"],
},
+
// Homelab can reach k8s services: PostgreSQL, CNPG metrics, Prometheus/Loki
{
"src": ["tag:homelab"],