Compare commits

..

5 Commits

Author SHA1 Message Date
unkinben 5e204dba5f Merge branch 'main' into benvin/artifactapi-mail-images
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
2026-05-24 14:28:42 +10:00
unkinben 7fc2bdd746 feat(artifactapi): add valkey/valkey to dockerhub allowed patterns
ci/woodpecker/pr/kubeconform Pipeline was canceled
ci/woodpecker/pr/pre-commit Pipeline was canceled
Required for stalwart in-memory store (rate limiting, locks, OAuth codes).
2026-05-24 12:56:13 +10:00
unkinben af79d86db6 feat(artifactapi): cache stalwart webadmin zip (#157)
## Summary

- Adds \`stalwartlabs/webadmin/releases/latest/download/webadmin.zip\` to \`mutable_patterns\` in the \`github\` generic remote so the stalwart webadmin UI can be fetched through artifactapi rather than directly from GitHub.

## Notes

- Uses \`mutable_patterns\` (not \`immutable\`) because \`releases/latest\` resolves to whichever release is current and changes over time.
- Access URL: \`https://artifactapi.k8s.syd1.au.unkin.net/generic/github/stalwartlabs/webadmin/releases/latest/download/webadmin.zip\`

Reviewed-on: #157
2026-05-24 12:55:16 +10:00
unkinben 5f4c9225bb feat(artifactapi): add mail stack images to docker registry cache (#156)
- ghcr: stalwartlabs/stalwart (Stalwart mail server)
- dockerhub: rspamd/rspamd (spam filter), tozd/postfix (MTA gateway)

Reviewed-on: #156
2026-05-24 12:42:27 +10:00
unkinben 8ad76b0314 feat(artifactapi): add mail stack images to docker registry cache
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
- ghcr: stalwartlabs/stalwart (Stalwart mail server)
- dockerhub: rspamd/rspamd (spam filter), tozd/postfix (MTA gateway)
2026-05-24 12:38:13 +10:00
22 changed files with 5 additions and 586 deletions
@@ -9,6 +9,7 @@ remotes:
- "^kanidm/" - "^kanidm/"
- "^openvoxproject/" - "^openvoxproject/"
- "^stakater/reloader" - "^stakater/reloader"
- "^stalwartlabs/stalwart"
- "^voxpupuli/puppetboard" - "^voxpupuli/puppetboard"
- "^woodpecker-ci/helm" - "^woodpecker-ci/helm"
cache: cache:
@@ -35,7 +36,10 @@ remotes:
- "^hashicorp/vault" - "^hashicorp/vault"
- "^jfrog/" - "^jfrog/"
- "^rancher/" - "^rancher/"
- "^rspamd/rspamd"
- "^tozd/postfix"
- "^traefik/" - "^traefik/"
- "^valkey/valkey"
- "^ubi9/ubi-minimal" - "^ubi9/ubi-minimal"
- "^victoriametrics/" - "^victoriametrics/"
- "^woodpeckerci/" - "^woodpeckerci/"
@@ -5,6 +5,7 @@ remotes:
description: "GitHub releases and files" description: "GitHub releases and files"
mutable_patterns: mutable_patterns:
- ".*/archive/refs/heads/.*.tar.gz$" - ".*/archive/refs/heads/.*.tar.gz$"
- "stalwartlabs/webadmin/releases/latest/download/webadmin.zip$"
immutable_patterns: immutable_patterns:
- ".*/archive/refs/tags/.*.tar.gz$" - ".*/archive/refs/tags/.*.tar.gz$"
- "ahmetb/kubectx/.*/kubectx_.*_linux_x86_64.tar.gz$" - "ahmetb/kubectx/.*/kubectx_.*_linux_x86_64.tar.gz$"
-18
View File
@@ -1,18 +0,0 @@
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: postfix-smtp-tls
namespace: mailgateway
spec:
secretName: postfix-smtp-tls
issuerRef:
name: vault-issuer
kind: ClusterIssuer
commonName: mail.main.unkin.net
dnsNames:
- mail.main.unkin.net
- smtp-in.main.unkin.net
privateKey:
size: 4096
algorithm: RSA
-37
View File
@@ -1,37 +0,0 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
labels:
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: rspamd.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: rspamd.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
name: rspamd
namespace: mailgateway
spec:
gatewayClassName: traefik-internal
listeners:
- allowedRoutes:
namespaces:
from: Same
hostname: rspamd.k8s.syd1.au.unkin.net
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: Same
hostname: rspamd.k8s.syd1.au.unkin.net
name: https
port: 443
protocol: HTTPS
tls:
certificateRefs:
- group: ""
kind: Secret
name: rspamd-tls
mode: Terminate
-16
View File
@@ -1,16 +0,0 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: rspamd
namespace: mailgateway
spec:
parentRefs:
- name: rspamd
namespace: mailgateway
hostnames:
- rspamd.k8s.syd1.au.unkin.net
rules:
- backendRefs:
- name: rspamd
port: 11334
-36
View File
@@ -1,36 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- certificate.yaml
- gateway.yaml
- httproute.yaml
- namespace.yaml
- postfix-deployment.yaml
- postfix-hpa.yaml
- rspamd-deployment.yaml
- rspamd-hpa.yaml
- services.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
configMapGenerator:
- name: postfix-config
files:
- main.cf=resources/postfix/main.cf
- master.cf=resources/postfix/master.cf
options:
disableNameSuffixHash: true
- name: postfix-maps
files:
- transport=resources/postfix/transport
options:
disableNameSuffixHash: true
- name: rspamd-config
files:
- worker-proxy.inc=resources/rspamd/local.d/worker-proxy.inc
- dkim_signing.conf=resources/rspamd/local.d/dkim_signing.conf
- milter_headers.conf=resources/rspamd/local.d/milter_headers.conf
options:
disableNameSuffixHash: true
-5
View File
@@ -1,5 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: mailgateway
@@ -1,105 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postfix
namespace: mailgateway
spec:
selector:
matchLabels:
app: postfix
template:
metadata:
annotations:
reloader.stakater.com/auto: "true"
labels:
app: postfix
spec:
initContainers:
- name: postmap
image: tozd/postfix:alpine-322
command: ["/bin/sh", "-c"]
args:
- |
for f in /etc/postfix/maps/*; do
base=$(basename "$f")
cp "$f" /tmp/"$base"
postmap hash:/tmp/"$base"
cp /tmp/"${base}.db" /etc/postfix/db/
done
volumeMounts:
- name: postfix-maps
mountPath: /etc/postfix/maps
readOnly: true
- name: postfix-db
mountPath: /etc/postfix/db
containers:
- name: postfix
image: tozd/postfix:alpine-322
ports:
- containerPort: 25
name: smtp
protocol: TCP
- containerPort: 587
name: submission
protocol: TCP
env:
# Keep these in sync with main.cf so the tozd startup postconf calls are no-ops
- name: MAILNAME
value: "mail.main.unkin.net"
- name: MY_NETWORKS
value: "127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16"
- name: MY_DESTINATION
value: "localhost.localdomain, localhost"
- name: LOG_TO_STDOUT
value: "1"
livenessProbe:
tcpSocket:
port: 25
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 25
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
volumeMounts:
- name: postfix-config
mountPath: /etc/postfix/main.cf
subPath: main.cf
- name: postfix-config
mountPath: /etc/postfix/master.cf
subPath: master.cf
- name: postfix-db
mountPath: /etc/postfix/transport.db
subPath: transport.db
- name: postfix-tls
mountPath: /etc/postfix/tls
readOnly: true
- name: spool
mountPath: /var/spool/postfix
volumes:
- name: postfix-config
configMap:
name: postfix-config
- name: postfix-maps
configMap:
name: postfix-maps
- name: postfix-db
emptyDir: {}
- name: postfix-tls
secret:
secretName: postfix-smtp-tls
- name: spool
emptyDir: {}
-38
View File
@@ -1,38 +0,0 @@
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: postfix-hpa
namespace: mailgateway
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: postfix
minReplicas: 2
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
selectPolicy: Min
policies:
- type: Percent
value: 30
periodSeconds: 60
@@ -1,46 +0,0 @@
# Basic identity — kept in sync with MAILNAME/MY_NETWORKS/MY_DESTINATION env vars
# so the tozd startup script's postconf calls are no-ops
myhostname = mail.main.unkin.net
myorigin = main.unkin.net
mydestination = localhost.localdomain, localhost
mynetworks = 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
inet_protocols = ipv4
inet_interfaces = all
# No local delivery — we're a relay-only gateway
local_transport = error:no local delivery
alias_maps =
alias_database =
# Relay inbound mail for these domains to Stalwart
relay_domains = main.unkin.net unkin.net
transport_maps = hash:/etc/postfix/transport
# rspamd milter (same namespace — short DNS name resolves)
smtpd_milters = inet:rspamd:11332
non_smtpd_milters = inet:rspamd:11332
milter_default_action = accept
milter_protocol = 6
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
# Inbound TLS (cert from cert-manager Certificate resource)
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtpd_tls_cert_file = /etc/postfix/tls/tls.crt
smtpd_tls_key_file = /etc/postfix/tls/tls.key
smtpd_tls_loglevel = 1
# Outbound TLS (opportunistic)
smtp_tls_security_level = may
smtp_tls_loglevel = 1
# Message size limit (50 MiB)
message_size_limit = 52428800
mailbox_size_limit = 0
# Queue retention
maximal_queue_lifetime = 5d
bounce_queue_lifetime = 1d
# Log to stdout for k8s log collection
maillog_file = /dev/stdout
@@ -1,42 +0,0 @@
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
# SMTP inbound (port 25) — runs rspamd milter, relays to Stalwart via transport_maps
smtp inet n - n - - smtpd
# Submission (port 587) — TLS required, relay from trusted mynetworks only
submission inet n - n - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=no
-o smtpd_reject_unlisted_recipient=no
-o smtpd_relay_restrictions=permit_mynetworks,reject
-o milter_macro_daemon_name=ORIGINATING
# Internal postfix processes
pickup unix n - n 60 1 pickup
cleanup unix n - n - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - n 1000? 1 tlsmgr
rewrite unix - - n - - trivial-rewrite
bounce unix - - n - 0 bounce
defer unix - - n - 0 bounce
trace unix - - n - 0 bounce
verify unix - - n - 1 verify
flush unix n - n 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - n - - smtp
relay unix - - n - - smtp
showq unix n - n - - showq
error unix - - n - - error
retry unix - - n - - error
discard unix - - n - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - n - - lmtp
anvil unix - - n - 1 anvil
scache unix - - n - 1 scache
postlog unix-dgram n - n - 1 postlogd
@@ -1,2 +0,0 @@
.unkin.net smtp:[stalwart.stalwart.svc.cluster.local]:25
unkin.net smtp:[stalwart.stalwart.svc.cluster.local]:25
@@ -1,13 +0,0 @@
enabled = true;
selector = "mail";
domain {
main.unkin.net {
privkey = "/etc/rspamd/dkim/private_key";
selector = "mail";
}
unkin.net {
privkey = "/etc/rspamd/dkim/private_key";
selector = "mail";
}
}
@@ -1,2 +0,0 @@
extended_spam_headers = true;
use = ["x-spam-status", "x-spam-score", "authentication-results"];
@@ -1,7 +0,0 @@
milter = yes;
bind_socket = "*:11332";
upstream "local" {
default = yes;
self_scan = yes;
}
@@ -1,75 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rspamd
namespace: mailgateway
spec:
selector:
matchLabels:
app: rspamd
template:
metadata:
annotations:
reloader.stakater.com/auto: "true"
labels:
app: rspamd
spec:
securityContext:
runAsUser: 11333
runAsGroup: 11333
fsGroup: 11333
containers:
- name: rspamd
image: rspamd/rspamd:4.0.1
ports:
- containerPort: 11332
name: milter
protocol: TCP
- containerPort: 11333
name: worker
protocol: TCP
- containerPort: 11334
name: controller
protocol: TCP
livenessProbe:
httpGet:
path: /ping
port: 11334
initialDelaySeconds: 15
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ping
port: 11334
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
volumeMounts:
- name: rspamd-config
mountPath: /etc/rspamd/local.d
readOnly: true
- name: dkim-keys
mountPath: /etc/rspamd/dkim
readOnly: true
- name: rspamd-data
mountPath: /var/lib/rspamd
volumes:
- name: rspamd-config
configMap:
name: rspamd-config
- name: dkim-keys
secret:
secretName: dkim-keys
- name: rspamd-data
emptyDir: {}
-38
View File
@@ -1,38 +0,0 @@
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: rspamd-hpa
namespace: mailgateway
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: rspamd
minReplicas: 2
maxReplicas: 6
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 4
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
selectPolicy: Min
policies:
- type: Percent
value: 30
periodSeconds: 60
-64
View File
@@ -1,64 +0,0 @@
---
# Internal service for rspamd - used by postfix pods in the same namespace
apiVersion: v1
kind: Service
metadata:
name: rspamd
namespace: mailgateway
spec:
selector:
app: rspamd
ports:
- name: milter
port: 11332
targetPort: 11332
protocol: TCP
- name: worker
port: 11333
targetPort: 11333
protocol: TCP
- name: controller
port: 11334
targetPort: 11334
protocol: TCP
---
# Internal ClusterIP for postfix - used by stalwart for outbound relay
apiVersion: v1
kind: Service
metadata:
name: postfix
namespace: mailgateway
spec:
selector:
app: postfix
ports:
- name: smtp
port: 25
targetPort: 25
protocol: TCP
- name: submission
port: 587
targetPort: 587
protocol: TCP
---
# External LoadBalancer for inbound MX (internet → postfix)
apiVersion: v1
kind: Service
metadata:
name: postfix-external
namespace: mailgateway
annotations:
external-dns.alpha.kubernetes.io/hostname: smtp-in.main.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.199.1
purelb.io/service-group: dmz
purelb.io/addresses: 198.18.199.1
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: postfix
ports:
- name: smtp
port: 25
targetPort: 25
protocol: TCP
-18
View File
@@ -1,18 +0,0 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: mailgateway
spec:
allowedNamespaces:
- mailgateway
kubernetes:
audiences:
- vault
role: default
serviceAccount: default
tokenExpirationSeconds: 600
method: kubernetes
mount: k8s/au/syd1
vaultConnectionRef: vso-system/default
@@ -1,17 +0,0 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: dkim-keys
namespace: mailgateway
spec:
destination:
create: true
name: dkim-keys
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/mailgateway/default/dkim-keys
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
@@ -1,6 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/mailgateway
-1
View File
@@ -21,7 +21,6 @@ spec:
- path: apps/overlays/*/inteldeviceplugins-system - path: apps/overlays/*/inteldeviceplugins-system
- path: apps/overlays/*/jfrog - path: apps/overlays/*/jfrog
- path: apps/overlays/*/node-feature-discovery - path: apps/overlays/*/node-feature-discovery
- path: apps/overlays/*/mailgateway
- path: apps/overlays/*/puppet - path: apps/overlays/*/puppet
- path: apps/overlays/*/purelb - path: apps/overlays/*/purelb
- path: apps/overlays/*/reflector-system - path: apps/overlays/*/reflector-system