From 8ef5c678c2e52949f8304590b35380000f641f52 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Feb 2026 21:10:59 -0800 Subject: [PATCH] feat: wire Postiz spaces to pull secrets from Infisical at runtime - Template uses entrypoint-wrapper.sh to inject secrets at container start - Only INFISICAL_* credentials + POSTGRES_PASSWORD in .env (3 values) - All other secrets (JWT, EMAIL_PASS, OAuth, social API keys) from Infisical - Generator produces minimal .env templates per space - Added infisical_slug to spaces.yml for each Postiz project - Added missing EMAIL_PASS + POSTGRES_PASSWORD to Infisical projects Co-Authored-By: Claude Opus 4.6 --- ...Remove-plaintext-.env-files-from-server.md | 3 +- docker-compose.template.yml | 52 ++++++++----------- generate.sh | 31 +++++++++-- spaces.yml | 3 ++ 4 files changed, 54 insertions(+), 35 deletions(-) diff --git a/backlog/tasks/task-6 - Remove-plaintext-.env-files-from-server.md b/backlog/tasks/task-6 - Remove-plaintext-.env-files-from-server.md index 6fb3071..1aa8966 100644 --- a/backlog/tasks/task-6 - Remove-plaintext-.env-files-from-server.md +++ b/backlog/tasks/task-6 - Remove-plaintext-.env-files-from-server.md @@ -1,9 +1,10 @@ --- id: TASK-6 title: Remove plaintext .env files from server -status: To Do +status: In Progress assignee: [] created_date: '2026-02-25 05:02' +updated_date: '2026-02-25 05:04' labels: - security - infisical diff --git a/docker-compose.template.yml b/docker-compose.template.yml index e26c065..0227f5a 100644 --- a/docker-compose.template.yml +++ b/docker-compose.template.yml @@ -4,17 +4,31 @@ # Generated by generate.sh from spaces.yml — DO NOT EDIT DIRECTLY. # To modify, edit spaces.yml and re-run: ./generate.sh # Primary: {{PRIMARY_DOMAIN}} | Fallback: {{FALLBACK_DOMAIN}} +# +# Secrets (JWT_SECRET, EMAIL_PASS, social API keys, OAuth secrets) are +# injected at runtime by the Infisical entrypoint wrapper. +# Only INFISICAL_* credentials and POSTGRES_PASSWORD go in .env. services: postiz-{{SPACE_SLUG}}: image: {{POSTIZ_IMAGE}} container_name: postiz-{{SPACE_SLUG}} restart: unless-stopped + # Infisical wrapper injects secrets then runs original entrypoint + entrypoint: ["/infisical-entrypoint.sh"] + command: ["docker-entrypoint.sh", "sh", "-c", "nginx && pnpm run pm2"] environment: + # --- Infisical (credentials from .env) --- + INFISICAL_CLIENT_ID: '${INFISICAL_CLIENT_ID}' + INFISICAL_CLIENT_SECRET: '${INFISICAL_CLIENT_SECRET}' + INFISICAL_PROJECT_SLUG: '{{INFISICAL_SLUG}}' + INFISICAL_ENV: 'prod' + INFISICAL_URL: 'http://infisical:8080' + + # --- App Config (non-secret, stays in compose) --- MAIN_URL: 'https://{{PRIMARY_DOMAIN}}' FRONTEND_URL: 'https://{{PRIMARY_DOMAIN}}' NEXT_PUBLIC_BACKEND_URL: 'https://{{PRIMARY_DOMAIN}}/api' - JWT_SECRET: '${JWT_SECRET}' DATABASE_URL: 'postgresql://postiz:${POSTGRES_PASSWORD}@postiz-{{SPACE_SLUG}}-postgres:5432/postiz' REDIS_URL: 'redis://postiz-{{SPACE_SLUG}}-redis:6379' BACKEND_INTERNAL_URL: 'http://localhost:3000' @@ -22,7 +36,7 @@ services: IS_GENERAL: '{{IS_GENERAL}}' DISABLE_REGISTRATION: '{{DISABLE_REG}}' - # Pocket ID OAuth + # Pocket ID OAuth (config only — client_id/secret from Infisical) {{OAUTH_BLOCK}} # Storage @@ -30,29 +44,7 @@ services: UPLOAD_DIRECTORY: '{{UPLOAD_DIR}}' NEXT_PUBLIC_UPLOAD_DIRECTORY: '{{UPLOAD_DIR}}' - # Social Media API Settings (from .env or Infisical) - X_API_KEY: '${X_API_KEY:-}' - X_API_SECRET: '${X_API_SECRET:-}' - LINKEDIN_CLIENT_ID: '${LINKEDIN_CLIENT_ID:-}' - LINKEDIN_CLIENT_SECRET: '${LINKEDIN_CLIENT_SECRET:-}' - REDDIT_CLIENT_ID: '${REDDIT_CLIENT_ID:-}' - REDDIT_CLIENT_SECRET: '${REDDIT_CLIENT_SECRET:-}' - THREADS_APP_ID: '${THREADS_APP_ID:-}' - THREADS_APP_SECRET: '${THREADS_APP_SECRET:-}' - FACEBOOK_APP_ID: '${FACEBOOK_APP_ID:-}' - FACEBOOK_APP_SECRET: '${FACEBOOK_APP_SECRET:-}' - YOUTUBE_CLIENT_ID: '${YOUTUBE_CLIENT_ID:-}' - YOUTUBE_CLIENT_SECRET: '${YOUTUBE_CLIENT_SECRET:-}' - TIKTOK_CLIENT_ID: '${TIKTOK_CLIENT_ID:-}' - TIKTOK_CLIENT_SECRET: '${TIKTOK_CLIENT_SECRET:-}' - DISCORD_CLIENT_ID: '${DISCORD_CLIENT_ID:-}' - DISCORD_CLIENT_SECRET: '${DISCORD_CLIENT_SECRET:-}' - DISCORD_BOT_TOKEN_ID: '${DISCORD_BOT_TOKEN_ID:-}' - MASTODON_URL: '${MASTODON_URL:-https://mastodon.social}' - MASTODON_CLIENT_ID: '${MASTODON_CLIENT_ID:-}' - MASTODON_CLIENT_SECRET: '${MASTODON_CLIENT_SECRET:-}' - - # Email + # Email (config only — EMAIL_PASS from Infisical) EMAIL_PROVIDER: '{{EMAIL_PROVIDER}}' EMAIL_FROM_NAME: '{{EMAIL_FROM_NAME}}' EMAIL_FROM_ADDRESS: '{{EMAIL_FROM}}' @@ -60,17 +52,19 @@ services: EMAIL_PORT: '{{EMAIL_PORT}}' EMAIL_SECURE: '{{EMAIL_SECURE}}' EMAIL_USER: '{{EMAIL_USER}}' - EMAIL_PASS: '${EMAIL_PASS}' NODE_TLS_REJECT_UNAUTHORIZED: '0' - # AI - OPENAI_API_KEY: '${OPENAI_API_KEY:-}' - # Misc NX_ADD_PLUGINS: false API_LIMIT: {{API_LIMIT}} + # --- Secrets injected by Infisical at runtime --- + # JWT_SECRET, EMAIL_PASS, OPENAI_API_KEY, + # POSTIZ_OAUTH_CLIENT_ID, POSTIZ_OAUTH_CLIENT_SECRET, + # X_API_KEY, X_API_SECRET, LINKEDIN_CLIENT_ID, etc. + volumes: + - /opt/infisical/entrypoint-wrapper.sh:/infisical-entrypoint.sh:ro - postiz-{{SPACE_SLUG}}-config:/config/ - postiz-{{SPACE_SLUG}}-uploads:/uploads/ labels: diff --git a/generate.sh b/generate.sh index de8bd30..d09fe3c 100755 --- a/generate.sh +++ b/generate.sh @@ -79,11 +79,12 @@ get_space_override() { generate_space() { local space="$1" - local primary_domain fallback_domain email_from slug + local primary_domain fallback_domain email_from slug infisical_slug primary_domain=$(get_space "$space" "primary_domain") fallback_domain=$(get_space "$space" "fallback_domain") email_from=$(get_space "$space" "email_from") slug=$(get_space "$space" "slug") + infisical_slug=$(get_space "$space" "infisical_slug") if [[ -z "$primary_domain" || "$primary_domain" == "null" ]]; then echo -e "${RED} Error: spaces.${space}.primary_domain is required${NC}" @@ -95,6 +96,11 @@ generate_space() { slug="$space" fi + # Infisical slug defaults to postiz- + if [[ -z "$infisical_slug" || "$infisical_slug" == "null" ]]; then + infisical_slug="postiz-${space}" + fi + # Read defaults (with per-space overrides) local postiz_image postiz_port postgres_image redis_image temporal_image temporal_pg_image local email_provider email_from_name email_host email_port email_secure email_user @@ -141,7 +147,7 @@ generate_space() { # Build dynamic blocks # ----------------------------------------------------------------------- - # OAuth block + # OAuth block (config only — client_id/secret injected by Infisical) local oauth_block if [[ "$use_oauth" == "true" ]]; then oauth_block=" POSTIZ_GENERIC_OAUTH: 'true' @@ -151,9 +157,8 @@ generate_space() { POSTIZ_OAUTH_AUTH_URL: '${oauth_auth_url}' POSTIZ_OAUTH_TOKEN_URL: '${oauth_token_url}' POSTIZ_OAUTH_USERINFO_URL: '${oauth_userinfo_url}' - POSTIZ_OAUTH_CLIENT_ID: '\${POSTIZ_OAUTH_CLIENT_ID}' - POSTIZ_OAUTH_CLIENT_SECRET: '\${POSTIZ_OAUTH_CLIENT_SECRET}' - POSTIZ_OAUTH_SCOPE: 'openid profile email'" + POSTIZ_OAUTH_SCOPE: 'openid profile email' + # POSTIZ_OAUTH_CLIENT_ID + CLIENT_SECRET from Infisical" else oauth_block=" # OAuth disabled for this space" fi @@ -209,6 +214,7 @@ generate_space() { -e "s|{{DISABLE_REG}}|${disable_reg}|g" \ -e "s|{{IS_GENERAL}}|${is_general}|g" \ -e "s|{{API_LIMIT}}|${api_limit}|g" \ + -e "s|{{INFISICAL_SLUG}}|${infisical_slug}|g" \ "$TEMPLATE" > "$outfile" # Replace multi-line blocks (sed can't do these well, use temp files) @@ -235,6 +241,21 @@ generate_space() { echo -e "${GREEN} ✓ ${outfile}${NC}" + # Generate minimal .env template (only Infisical creds + postgres password) + local envfile="${OUTDIR}/env.space-${space}" + cat > "$envfile" <