# TawiPost = the Postiz engine with one bounded L2 auth hardening patch # (ADR-055), plus Tawi branding applied at the service layer. # Replaces the former tawi-product-surface Go health-stub. Built FROM the # digest-pinned upstream Postiz image mirrored into the internal registry by the # mirror-img pattern (.woodpecker/08-build.yaml, same as tawidrive=oCIS / # tawiplan=Vikunja). Postiz ships an nginx proxy on :5000 fronting the Node app, # configured via env (DATABASE_URL/REDIS_URL/JWT_SECRET/MAIN_URL...) supplied by # deploy/k8s/tawipost.yaml and tawipost-secrets-externalsecret.yaml. The sole # upstream-source change makes LOCAL registration fail closed when registration # is disabled while preserving Postiz's Provider.GENERIC OIDC fast path. The # Kubernetes manifest deliberately overrides the upstream command to omit its # destructive startup migration while retaining the upstream nginx/PM2 process # contract. # docs: https://docs.postiz.com/installation/docker-compose # image: ghcr.io/gitroomhq/postiz-app # source/patch/build offer: # https://go.tawi.ai/oss/tawipost/v2.21.8-f8421ebce7bb0648/source-offer.json ARG POSTIZ_ENGINE_IMAGE=ghcr.io/gitroomhq/postiz-app:v2.21.8@sha256:3ec7bc8ecf61f08c22e0954291702b097f944def515dc259cb3a441aba4046c0 FROM ${POSTIZ_ENGINE_IMAGE} LABEL org.opencontainers.image.title="TawiPost" LABEL org.opencontainers.image.description="TawiPost campaign/social-publishing/scheduling — Postiz engine with the ADR-055 LOCAL-registration hardening patch" LABEL org.opencontainers.image.source="https://go.tawi.ai/oss/tawipost/v2.21.8-f8421ebce7bb0648/source-offer.json" LABEL org.opencontainers.image.licenses="AGPL-3.0-only" LABEL tawi.ai.product="TawiPost" LABEL tawi.ai.service="tawipost" LABEL tawi.ai.engine="postiz" LABEL tawi.ai.engine-reference="ghcr.io/gitroomhq/postiz-app@sha256:3ec7bc8ecf61f08c22e0954291702b097f944def515dc259cb3a441aba4046c0" LABEL tawi.ai.upstream-revision="9a7d9deafcf373c759e2ec7955206a2a31b003f3" LABEL tawi.ai.patch-policy="ADR-055" LABEL tawi.ai.corresponding-source="https://go.tawi.ai/oss/tawipost/v2.21.8-f8421ebce7bb0648/source-offer.json" # Source-backed USER exception (security_lint Category 36): the upstream # nginx/PM2 startup contract requires root for bounded initialization and # privilege changes; the Deployment makes the exception explicit (uid/gid 0, # allowPrivilegeEscalation:false, seccomp RuntimeDefault, minimal capabilities, # deploy/k8s/tawipost.yaml) and the Prisma migration stays non-root. LABEL tawi.ai.security.exception="upstream-root-entrypoint" LABEL tawi.ai.security.exception.source="docs/adr/055-tawipost-l2-auth-hardening.md + deploy/k8s/tawipost.yaml securityContext" ENV TAWI_SERVICE_ID=tawipost ENV TAWI_PRODUCT_NAME=TawiPost COPY scripts/ci/patch_tawipost_auth.py /tmp/patch_tawipost_auth.py # Second bounded patch: native Temporal client mTLS + JWT minting. The t0 # Temporal frontend requires mutual TLS AND a JWT bearer token (authorizer: # default + jwtKeyProvider). Upstream Postiz only speaks server-TLS + a static # api-key; this rewrites the SDK connection (temporal.module.ts) and the health # probe (health.controller.ts) to present a client cert and mint a fresh RS256 # token per call. Patched before both builds so the shared nestjs-libraries copy # compiled into backend AND orchestrator picks it up. COPY scripts/ci/patch_tawipost_temporal_mtls.py /tmp/patch_tawipost_temporal_mtls.py # Third bounded patch: TawiPost page-title branding. Upstream computes every # page metadata.title from `isGeneralServerSide() ? 'Postiz' : 'Gitroom'` at # request time (env-driven, not overridable), so post.tawi.ai served # "Postiz Register" (live 2026-07-21). The patch rewrites that # exact ternary to the literal 'TawiPost' across the 14 title sites in both # the TypeScript source (fail-closed pinned count) AND the prebuilt compiled # .next/server JS bundles (`next start` serves from .next, but `next build` # cannot run inside Kaniko — it downloads Google Fonts during compilation). # The patch_pawipost_branding.py script handles both phases atomically. COPY scripts/ci/patch_tawipost_branding.py /tmp/patch_tawipost_branding.py RUN python3 /tmp/patch_tawipost_auth.py \ /app/apps/backend/src/services/auth/auth.service.ts \ && python3 /tmp/patch_tawipost_temporal_mtls.py \ /app/libraries/nestjs-libraries/src/temporal/temporal.module.ts \ /app/apps/orchestrator/src/health.controller.ts \ && python3 /tmp/patch_tawipost_branding.py /app/apps/frontend \ && NODE_OPTIONS=--max-old-space-size=4096 pnpm run build:backend \ && NODE_OPTIONS=--max-old-space-size=4096 pnpm run build:orchestrator \ && test -s /app/apps/backend/dist/apps/backend/src/services/auth/auth.service.js \ && test -s /app/apps/backend/dist/apps/backend/src/main.js \ && test -s /app/apps/orchestrator/dist/apps/orchestrator/src/health.controller.js \ && test -s /app/apps/orchestrator/dist/apps/orchestrator/src/main.js \ # Anti-falsification: fail the build if either patch did not reach the # compiled orchestrator dist (mTLS clientCertPair + JWT minting marker). && grep -q clientCertPair /app/apps/orchestrator/dist/apps/orchestrator/src/health.controller.js \ && grep -q TEMPORAL_JWT_PRIVATE_KEY_PATH /app/apps/orchestrator/dist/apps/orchestrator/src/health.controller.js \ && grep -q allowConnectionFailure /app/apps/orchestrator/dist/libraries/nestjs-libraries/src/temporal/temporal.module.js \ && grep -q clientCertPair /app/apps/orchestrator/dist/libraries/nestjs-libraries/src/temporal/temporal.module.js \ # Anti-falsification: the branding literal must reach both the compiled # .next/server bundles AND the source must be free of 'Postiz' titles. && grep -rq "TawiPost" /app/apps/frontend/.next/server \ && ! grep -rq "isGeneralServerSide() ? 'Postiz'" /app/apps/frontend/src \ && ! grep -rq 'isGeneralServerSide()?"Postiz"' /app/apps/frontend/.next/server \ && rm -f /tmp/patch_tawipost_auth.py /tmp/patch_tawipost_temporal_mtls.py /tmp/patch_tawipost_branding.py # Do not declare USER here. The upstream nginx/PM2 startup contract requires # root for bounded initialization and privilege changes. The Deployment makes # that exception explicit (uid/gid 0, no privilege escalation, seccomp, and a # minimal capability set); the Prisma migration remains non-root.