postiz: add Temporal sidecar; lock both stacks behind Authentik
Postiz backend was crashlooping on connect ECONNREFUSED ::1:7233 — Postiz needs Temporal for cron/scheduled posts and the Helm chart doesn't bundle it. Added a single-replica temporalio/auto-setup:1.28.1 Deployment in the postiz namespace, backed by the bundled postiz-postgresql (separate `temporal` + `temporal_visibility` databases pre-created via init container), ENABLE_ES=false (Postiz only uses the workflow engine, not visibility search). Skips DYNAMIC_CONFIG_FILE_PATH because that file isn't bundled in auto-setup. Auth audit: - postiz: ingress now `protected = true` (Authentik forward-auth). Postiz also has its own login on top, but registration is no longer exposed to the open internet. - instagram-poster: split into two ingresses on the same host. `/image/*` stays public (Meta + Telegram fetch the 9:16 derivatives). Everything else (/healthz, /queue, /scan, /enqueue, /reject, /post-next) sits behind Authentik. The protected ingress sets dns_type=none — the public one already created the CF DNS record.
This commit is contained in:
parent
7f7698991e
commit
8c4a370a34
2 changed files with 176 additions and 7 deletions
|
|
@ -268,17 +268,35 @@ resource "kubernetes_service" "instagram_poster" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Public ingress. No UI — entire host is API-only and Meta needs to fetch
|
# Two ingresses on the same host — Traefik picks the longest path prefix.
|
||||||
# /image/<asset_id> unauthenticated to render preview cards. We therefore
|
#
|
||||||
# leave `protected = false` so Authentik forward-auth doesn't run on any
|
# `/image/*` must be reachable WITHOUT auth so Meta's content fetcher (and
|
||||||
# path. Inbound auth is the API's own concern (Postiz webhook signature
|
# Telegram's photo preview) can render the 9:16 derivatives we produce.
|
||||||
# / shared secret as configured by the parallel agent).
|
# Everything else (/queue, /scan, /enqueue, /post-next, /reject, /healthz)
|
||||||
module "ingress" {
|
# sits behind Authentik forward-auth — same defense as every other UI on
|
||||||
|
# the cluster, no random caller can pop items off the approval queue.
|
||||||
|
module "ingress_image_public" {
|
||||||
source = "../../../../modules/kubernetes/ingress_factory"
|
source = "../../../../modules/kubernetes/ingress_factory"
|
||||||
dns_type = "proxied"
|
dns_type = "proxied"
|
||||||
namespace = kubernetes_namespace.instagram_poster.metadata[0].name
|
namespace = kubernetes_namespace.instagram_poster.metadata[0].name
|
||||||
name = "instagram-poster"
|
name = "instagram-poster-image"
|
||||||
|
host = "instagram-poster"
|
||||||
tls_secret_name = var.tls_secret_name
|
tls_secret_name = var.tls_secret_name
|
||||||
protected = false
|
protected = false
|
||||||
|
ingress_path = ["/image"]
|
||||||
port = 80
|
port = 80
|
||||||
|
service_name = "instagram-poster"
|
||||||
|
}
|
||||||
|
|
||||||
|
module "ingress_protected" {
|
||||||
|
source = "../../../../modules/kubernetes/ingress_factory"
|
||||||
|
dns_type = "none" # DNS record already created by the public ingress above
|
||||||
|
namespace = kubernetes_namespace.instagram_poster.metadata[0].name
|
||||||
|
name = "instagram-poster"
|
||||||
|
host = "instagram-poster"
|
||||||
|
tls_secret_name = var.tls_secret_name
|
||||||
|
protected = true
|
||||||
|
ingress_path = ["/"]
|
||||||
|
port = 80
|
||||||
|
service_name = "instagram-poster"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,8 @@ resource "helm_release" "postiz" {
|
||||||
DISABLE_REGISTRATION = "false"
|
DISABLE_REGISTRATION = "false"
|
||||||
IS_GENERAL = "true"
|
IS_GENERAL = "true"
|
||||||
NX_ADD_PLUGINS = "false"
|
NX_ADD_PLUGINS = "false"
|
||||||
|
# Postiz uses Temporal for cron/scheduling — bring our own; Helm chart doesn't.
|
||||||
|
TEMPORAL_ADDRESS = "temporal:7233"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Postiz reads DATABASE_URL/REDIS_URL from this Secret. The chart does
|
# Postiz reads DATABASE_URL/REDIS_URL from this Secret. The chart does
|
||||||
|
|
@ -212,6 +214,7 @@ module "ingress" {
|
||||||
host = var.host
|
host = var.host
|
||||||
service_name = "postiz" # chart Service name resolves to fullnameOverride
|
service_name = "postiz" # chart Service name resolves to fullnameOverride
|
||||||
port = 80
|
port = 80
|
||||||
|
protected = true # Authentik forward-auth — Postiz has its own login on top, but we don't expose registration to the open internet.
|
||||||
tls_secret_name = var.tls_secret_name
|
tls_secret_name = var.tls_secret_name
|
||||||
extra_annotations = {
|
extra_annotations = {
|
||||||
"gethomepage.dev/enabled" = "true"
|
"gethomepage.dev/enabled" = "true"
|
||||||
|
|
@ -222,3 +225,151 @@ module "ingress" {
|
||||||
"gethomepage.dev/pod-selector" = ""
|
"gethomepage.dev/pod-selector" = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Temporal — cron/workflow engine Postiz requires for scheduled posts.
|
||||||
|
#
|
||||||
|
# Lightweight single-replica deployment using temporalio/auto-setup, backed
|
||||||
|
# by the bundled postiz-postgresql (separate `temporal` database). Visibility
|
||||||
|
# search via Elasticsearch is disabled (ENABLE_ES=false) — Postiz only uses
|
||||||
|
# the workflow engine, not visibility, so SQL is enough.
|
||||||
|
#
|
||||||
|
# Important: temporalio/auto-setup creates schemas in the `temporal` and
|
||||||
|
# `temporal_visibility` databases on first boot. We pre-create them with an
|
||||||
|
# init container running psql against postiz-postgresql.
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
resource "kubernetes_deployment" "temporal" {
|
||||||
|
metadata {
|
||||||
|
name = "temporal"
|
||||||
|
namespace = kubernetes_namespace.postiz.metadata[0].name
|
||||||
|
labels = {
|
||||||
|
app = "temporal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
replicas = 1
|
||||||
|
strategy {
|
||||||
|
type = "Recreate"
|
||||||
|
}
|
||||||
|
selector {
|
||||||
|
match_labels = { app = "temporal" }
|
||||||
|
}
|
||||||
|
template {
|
||||||
|
metadata {
|
||||||
|
labels = { app = "temporal" }
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
# Pre-create the two databases Temporal expects on the bundled PG.
|
||||||
|
init_container {
|
||||||
|
name = "create-temporal-dbs"
|
||||||
|
image = "docker.io/bitnamilegacy/postgresql:16.4.0-debian-12-r7"
|
||||||
|
env {
|
||||||
|
name = "PGPASSWORD"
|
||||||
|
value = "postiz-password"
|
||||||
|
}
|
||||||
|
command = ["/bin/bash", "-c"]
|
||||||
|
args = [
|
||||||
|
<<-EOT
|
||||||
|
set -e
|
||||||
|
for db in temporal temporal_visibility; do
|
||||||
|
psql -h postiz-postgresql -U postiz -d postgres -tc "SELECT 1 FROM pg_database WHERE datname='$db'" | grep -q 1 \
|
||||||
|
|| psql -h postiz-postgresql -U postiz -d postgres -c "CREATE DATABASE \"$db\""
|
||||||
|
done
|
||||||
|
EOT
|
||||||
|
]
|
||||||
|
}
|
||||||
|
container {
|
||||||
|
name = "temporal"
|
||||||
|
image = "temporalio/auto-setup:1.28.1"
|
||||||
|
port {
|
||||||
|
container_port = 7233
|
||||||
|
name = "grpc"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "DB"
|
||||||
|
value = "postgres12"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "DB_PORT"
|
||||||
|
value = "5432"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "POSTGRES_USER"
|
||||||
|
value = "postiz"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "POSTGRES_PWD"
|
||||||
|
value = "postiz-password"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "POSTGRES_SEEDS"
|
||||||
|
value = "postiz-postgresql"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "DBNAME"
|
||||||
|
value = "temporal"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "VISIBILITY_DBNAME"
|
||||||
|
value = "temporal_visibility"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "ENABLE_ES"
|
||||||
|
value = "false"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "TEMPORAL_NAMESPACE"
|
||||||
|
value = "default"
|
||||||
|
}
|
||||||
|
# NOTE: not setting DYNAMIC_CONFIG_FILE_PATH — that file isn't
|
||||||
|
# bundled in temporalio/auto-setup. Defaults are fine for our
|
||||||
|
# use (Postiz only needs the workflow engine, not dynamic config).
|
||||||
|
resources {
|
||||||
|
requests = {
|
||||||
|
cpu = "50m"
|
||||||
|
memory = "256Mi"
|
||||||
|
}
|
||||||
|
limits = {
|
||||||
|
memory = "1Gi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Auto-setup runs schema migrations on first boot — give it time.
|
||||||
|
startup_probe {
|
||||||
|
tcp_socket {
|
||||||
|
port = 7233
|
||||||
|
}
|
||||||
|
failure_threshold = 30
|
||||||
|
period_seconds = 5
|
||||||
|
initial_delay_seconds = 10
|
||||||
|
}
|
||||||
|
liveness_probe {
|
||||||
|
tcp_socket {
|
||||||
|
port = 7233
|
||||||
|
}
|
||||||
|
period_seconds = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycle {
|
||||||
|
ignore_changes = [spec[0].template[0].spec[0].dns_config] # KYVERNO_LIFECYCLE_V1
|
||||||
|
}
|
||||||
|
depends_on = [helm_release.postiz]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_service" "temporal" {
|
||||||
|
metadata {
|
||||||
|
name = "temporal"
|
||||||
|
namespace = kubernetes_namespace.postiz.metadata[0].name
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
selector = { app = "temporal" }
|
||||||
|
port {
|
||||||
|
name = "grpc"
|
||||||
|
port = 7233
|
||||||
|
target_port = 7233
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue