From 19d79b7eb6bde21c215b96de1acf0c8151c3cf36 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Feb 2026 21:54:59 -0800 Subject: [PATCH] chore: add Infisical migration deploy script and compose files Three new Infisical-wired compose files (one per Postiz space) plus a single migrate-to-infisical.sh script that handles the full switchover: extract existing POSTGRES_PASSWORD, backup old files, install new compose, create minimal .env, restart, verify health. Co-Authored-By: Claude Opus 4.6 --- deploy/docker-compose.bcrg.yml | 156 ++++++++++++++++++++++++ deploy/docker-compose.cc.yml | 182 ++++++++++++++++++++++++++++ deploy/docker-compose.p2pf.yml | 156 ++++++++++++++++++++++++ deploy/migrate-to-infisical.sh | 213 +++++++++++++++++++++++++++++++++ 4 files changed, 707 insertions(+) create mode 100644 deploy/docker-compose.bcrg.yml create mode 100644 deploy/docker-compose.cc.yml create mode 100644 deploy/docker-compose.p2pf.yml create mode 100755 deploy/migrate-to-infisical.sh diff --git a/deploy/docker-compose.bcrg.yml b/deploy/docker-compose.bcrg.yml new file mode 100644 index 0000000..2397ca4 --- /dev/null +++ b/deploy/docker-compose.bcrg.yml @@ -0,0 +1,156 @@ +services: + postiz-bcrg: + image: ghcr.io/gitroomhq/postiz-app:latest + container_name: postiz-bcrg + restart: unless-stopped + entrypoint: ["/infisical-entrypoint.sh"] + command: ["docker-entrypoint.sh", "sh", "-c", "nginx && pnpm run pm2"] + environment: + # === Infisical secret injection === + INFISICAL_CLIENT_ID: "${INFISICAL_CLIENT_ID}" + INFISICAL_CLIENT_SECRET: "${INFISICAL_CLIENT_SECRET}" + INFISICAL_PROJECT_SLUG: "postiz-bondingcurve" + INFISICAL_ENV: "prod" + INFISICAL_URL: "http://infisical:8080" + # === Config (not secrets) === + MAIN_URL: "https://bondingcurve.rsocials.online" + FRONTEND_URL: "https://bondingcurve.rsocials.online" + NEXT_PUBLIC_BACKEND_URL: "https://bondingcurve.rsocials.online/api" + DATABASE_URL: "postgresql://postiz:${POSTGRES_PASSWORD}@postiz-bcrg-postgres:5432/postiz" + REDIS_URL: "redis://postiz-bcrg-redis:6379" + BACKEND_INTERNAL_URL: "http://localhost:3000" + TEMPORAL_ADDRESS: "postiz-bcrg-temporal:7233" + IS_GENERAL: "true" + DISABLE_REGISTRATION: "false" + STORAGE_PROVIDER: "local" + UPLOAD_DIRECTORY: "/uploads" + NEXT_PUBLIC_UPLOAD_DIRECTORY: "/uploads" + # === OAuth config (client_id/secret come from Infisical) === + POSTIZ_GENERIC_OAUTH: "true" + NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME: "Pocket ID" + NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL: "https://raw.githubusercontent.com/pocket-id/pocket-id/refs/heads/main/frontend/static/img/static-logo.svg" + POSTIZ_OAUTH_URL: "https://auth.jeffemmett.com" + POSTIZ_OAUTH_AUTH_URL: "https://auth.jeffemmett.com/authorize" + POSTIZ_OAUTH_TOKEN_URL: "https://auth.jeffemmett.com/api/oidc/token" + POSTIZ_OAUTH_USERINFO_URL: "https://auth.jeffemmett.com/api/oidc/userinfo" + # === Email config (EMAIL_PASS comes from Infisical) === + EMAIL_PROVIDER: "nodemailer" + EMAIL_FROM_NAME: "Bonding Curve Research" + EMAIL_FROM_ADDRESS: "noreply@rmail.online" + EMAIL_HOST: "mailcowdockerized-postfix-mailcow-1" + EMAIL_PORT: "587" + EMAIL_SECURE: "false" + EMAIL_USER: "noreply@rmail.online" + NODE_TLS_REJECT_UNAUTHORIZED: "0" + API_LIMIT: 30 + NX_ADD_PLUGINS: false + volumes: + - postiz-bcrg-config:/config/ + - postiz-bcrg-uploads:/uploads/ + - /opt/infisical/entrypoint-wrapper.sh:/infisical-entrypoint.sh:ro + labels: + - "traefik.enable=false" + - "sablier.enable=true" + - "sablier.group=postiz-bcrg" + - "traefik.http.routers.postiz-bcrg.rule=Host(`bondingcurve.rsocials.online`) || Host(`socials.bondingcurve.tech`)" + - "traefik.http.routers.postiz-bcrg.entrypoints=web,websecure" + - "traefik.http.services.postiz-bcrg.loadbalancer.server.port=5000" + networks: + - traefik-public + - postiz-bcrg-internal + - mailcow-network + depends_on: + postiz-bcrg-postgres: + condition: service_healthy + postiz-bcrg-redis: + condition: service_healthy + postiz-bcrg-temporal: + condition: service_started + + postiz-bcrg-postgres: + image: postgres:17-alpine + container_name: postiz-bcrg-postgres + labels: + - "sablier.enable=true" + - "sablier.group=postiz-bcrg" + restart: unless-stopped + environment: + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" + POSTGRES_USER: postiz + POSTGRES_DB: postiz + volumes: + - postiz-bcrg-postgres-data:/var/lib/postgresql/data + networks: + - postiz-bcrg-internal + healthcheck: + test: pg_isready -U postiz -d postiz + interval: 10s + timeout: 3s + retries: 3 + + postiz-bcrg-redis: + image: redis:7.2 + container_name: postiz-bcrg-redis + labels: + - "sablier.enable=true" + - "sablier.group=postiz-bcrg" + restart: unless-stopped + healthcheck: + test: redis-cli ping + interval: 10s + timeout: 3s + retries: 3 + volumes: + - postiz-bcrg-redis-data:/data + networks: + - postiz-bcrg-internal + + postiz-bcrg-temporal-postgres: + image: postgres:16 + container_name: postiz-bcrg-temporal-postgres + labels: + - "sablier.enable=true" + - "sablier.group=postiz-bcrg" + restart: unless-stopped + environment: + POSTGRES_PASSWORD: temporal + POSTGRES_USER: temporal + networks: + - postiz-bcrg-internal + volumes: + - postiz-bcrg-temporal-postgres-data:/var/lib/postgresql/data + + postiz-bcrg-temporal: + image: temporalio/auto-setup:1.28.1 + container_name: postiz-bcrg-temporal + labels: + - "sablier.enable=true" + - "sablier.group=postiz-bcrg" + restart: unless-stopped + depends_on: + - postiz-bcrg-temporal-postgres + environment: + - DB=postgres12 + - DB_PORT=5432 + - POSTGRES_USER=temporal + - POSTGRES_PWD=temporal + - POSTGRES_SEEDS=postiz-bcrg-temporal-postgres + - TEMPORAL_NAMESPACE=default + networks: + - postiz-bcrg-internal + +volumes: + postiz-bcrg-postgres-data: + postiz-bcrg-redis-data: + postiz-bcrg-config: + postiz-bcrg-uploads: + postiz-bcrg-temporal-postgres-data: + +networks: + traefik-public: + external: true + postiz-bcrg-internal: + driver: bridge + mailcow-network: + external: true + name: mailcowdockerized_mailcow-network diff --git a/deploy/docker-compose.cc.yml b/deploy/docker-compose.cc.yml new file mode 100644 index 0000000..9e69e06 --- /dev/null +++ b/deploy/docker-compose.cc.yml @@ -0,0 +1,182 @@ +services: + postiz-cc: + image: ghcr.io/gitroomhq/postiz-app:latest + container_name: postiz-cc + restart: unless-stopped + entrypoint: ["/infisical-entrypoint.sh"] + command: ["docker-entrypoint.sh", "sh", "-c", "nginx && pnpm run pm2"] + environment: + # === Infisical secret injection === + INFISICAL_CLIENT_ID: "${INFISICAL_CLIENT_ID}" + INFISICAL_CLIENT_SECRET: "${INFISICAL_CLIENT_SECRET}" + INFISICAL_PROJECT_SLUG: "postiz-crypto-commons" + INFISICAL_ENV: "prod" + INFISICAL_URL: "http://infisical:8080" + # === Config (not secrets) === + MAIN_URL: "https://socials.crypto-commons.org" + FRONTEND_URL: "https://socials.crypto-commons.org" + NEXT_PUBLIC_BACKEND_URL: "https://socials.crypto-commons.org/api" + DATABASE_URL: "postgresql://postiz:${POSTGRES_PASSWORD}@postiz-cc-postgres:5432/postiz" + REDIS_URL: "redis://postiz-cc-redis:6379" + BACKEND_INTERNAL_URL: "http://localhost:3000" + TEMPORAL_ADDRESS: "postiz-cc-temporal:7233" + IS_GENERAL: "true" + DISABLE_REGISTRATION: "true" + STORAGE_PROVIDER: "local" + UPLOAD_DIRECTORY: "/uploads" + NEXT_PUBLIC_UPLOAD_DIRECTORY: "/uploads" + # === OAuth config (client_id/secret come from Infisical) === + POSTIZ_GENERIC_OAUTH: "true" + NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME: "Pocket ID" + NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL: "https://raw.githubusercontent.com/pocket-id/pocket-id/refs/heads/main/frontend/static/img/static-logo.svg" + POSTIZ_OAUTH_URL: "https://auth.jeffemmett.com" + POSTIZ_OAUTH_AUTH_URL: "https://auth.jeffemmett.com/authorize" + POSTIZ_OAUTH_TOKEN_URL: "https://auth.jeffemmett.com/api/oidc/token" + POSTIZ_OAUTH_USERINFO_URL: "https://auth.jeffemmett.com/api/oidc/userinfo" + # === Email config (EMAIL_PASS comes from Infisical) === + EMAIL_PROVIDER: "nodemailer" + EMAIL_FROM_NAME: "Crypto Commons" + EMAIL_FROM_ADDRESS: "noreply@rmail.online" + EMAIL_HOST: "mailcowdockerized-postfix-mailcow-1" + EMAIL_PORT: "587" + EMAIL_SECURE: "false" + EMAIL_USER: "noreply@rmail.online" + NODE_TLS_REJECT_UNAUTHORIZED: "0" + API_LIMIT: 30 + NX_ADD_PLUGINS: false + volumes: + - postiz-cc-config:/config/ + - postiz-cc-uploads:/uploads/ + - /opt/infisical/entrypoint-wrapper.sh:/infisical-entrypoint.sh:ro + labels: + - "traefik.enable=false" + - "sablier.enable=true" + - "sablier.group=postiz-cc" + - "traefik.http.routers.postiz-cc.rule=Host(`socials.crypto-commons.org`) || Host(`socials.valleyofthecommons.com`)" + - "traefik.http.routers.postiz-cc.entrypoints=web,websecure" + - "traefik.http.services.postiz-cc.loadbalancer.server.port=5000" + networks: + - traefik-public + - postiz-cc-internal + - mailcow-network + depends_on: + postiz-cc-postgres: + condition: service_healthy + postiz-cc-redis: + condition: service_healthy + postiz-cc-temporal: + condition: service_started + + postiz-cc-postgres: + image: postgres:17-alpine + container_name: postiz-cc-postgres + labels: + - "sablier.enable=true" + - "sablier.group=postiz-cc" + restart: unless-stopped + environment: + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" + POSTGRES_USER: postiz + POSTGRES_DB: postiz + volumes: + - postiz-cc-postgres-data:/var/lib/postgresql/data + networks: + - postiz-cc-internal + healthcheck: + test: pg_isready -U postiz -d postiz + interval: 10s + timeout: 3s + retries: 3 + + postiz-cc-redis: + image: redis:7.2 + container_name: postiz-cc-redis + labels: + - "sablier.enable=true" + - "sablier.group=postiz-cc" + restart: unless-stopped + healthcheck: + test: redis-cli ping + interval: 10s + timeout: 3s + retries: 3 + volumes: + - postiz-cc-redis-data:/data + networks: + - postiz-cc-internal + + postiz-cc-elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.24 + container_name: postiz-cc-elasticsearch + labels: + - "sablier.enable=true" + - "sablier.group=postiz-cc" + restart: unless-stopped + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + volumes: + - postiz-cc-elasticsearch-data:/usr/share/elasticsearch/data + networks: + - postiz-cc-internal + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9200/_cluster/health"] + interval: 30s + timeout: 10s + retries: 5 + + postiz-cc-temporal-postgres: + image: postgres:16 + container_name: postiz-cc-temporal-postgres + labels: + - "sablier.enable=true" + - "sablier.group=postiz-cc" + restart: unless-stopped + environment: + POSTGRES_PASSWORD: temporal + POSTGRES_USER: temporal + networks: + - postiz-cc-internal + volumes: + - postiz-cc-temporal-postgres-data:/var/lib/postgresql/data + + postiz-cc-temporal: + image: temporalio/auto-setup:1.28.1 + container_name: postiz-cc-temporal + labels: + - "sablier.enable=true" + - "sablier.group=postiz-cc" + restart: unless-stopped + depends_on: + - postiz-cc-temporal-postgres + - postiz-cc-elasticsearch + environment: + - DB=postgres12 + - DB_PORT=5432 + - POSTGRES_USER=temporal + - POSTGRES_PWD=temporal + - POSTGRES_SEEDS=postiz-cc-temporal-postgres + - ENABLE_ES=true + - ES_SEEDS=postiz-cc-elasticsearch + - ES_VERSION=v7 + - TEMPORAL_NAMESPACE=default + networks: + - postiz-cc-internal + +volumes: + postiz-cc-postgres-data: + postiz-cc-redis-data: + postiz-cc-config: + postiz-cc-uploads: + postiz-cc-temporal-postgres-data: + postiz-cc-elasticsearch-data: + +networks: + traefik-public: + external: true + postiz-cc-internal: + driver: bridge + mailcow-network: + external: true + name: mailcowdockerized_mailcow-network diff --git a/deploy/docker-compose.p2pf.yml b/deploy/docker-compose.p2pf.yml new file mode 100644 index 0000000..4254636 --- /dev/null +++ b/deploy/docker-compose.p2pf.yml @@ -0,0 +1,156 @@ +services: + postiz-p2pf: + image: ghcr.io/gitroomhq/postiz-app:latest + container_name: postiz-p2pf + restart: unless-stopped + entrypoint: ["/infisical-entrypoint.sh"] + command: ["docker-entrypoint.sh", "sh", "-c", "nginx && pnpm run pm2"] + environment: + # === Infisical secret injection === + INFISICAL_CLIENT_ID: "${INFISICAL_CLIENT_ID}" + INFISICAL_CLIENT_SECRET: "${INFISICAL_CLIENT_SECRET}" + INFISICAL_PROJECT_SLUG: "postiz-p2pfoundation" + INFISICAL_ENV: "prod" + INFISICAL_URL: "http://infisical:8080" + # === Config (not secrets) === + MAIN_URL: "https://p2pf.rsocials.online" + FRONTEND_URL: "https://p2pf.rsocials.online" + NEXT_PUBLIC_BACKEND_URL: "https://p2pf.rsocials.online/api" + DATABASE_URL: "postgresql://postiz:${POSTGRES_PASSWORD}@postiz-p2pf-postgres:5432/postiz" + REDIS_URL: "redis://postiz-p2pf-redis:6379" + BACKEND_INTERNAL_URL: "http://localhost:3000" + TEMPORAL_ADDRESS: "postiz-p2pf-temporal:7233" + IS_GENERAL: "true" + DISABLE_REGISTRATION: "false" + STORAGE_PROVIDER: "local" + UPLOAD_DIRECTORY: "/uploads" + NEXT_PUBLIC_UPLOAD_DIRECTORY: "/uploads" + # === OAuth config (client_id/secret come from Infisical) === + POSTIZ_GENERIC_OAUTH: "true" + NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME: "Pocket ID" + NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL: "https://raw.githubusercontent.com/pocket-id/pocket-id/refs/heads/main/frontend/static/img/static-logo.svg" + POSTIZ_OAUTH_URL: "https://auth.jeffemmett.com" + POSTIZ_OAUTH_AUTH_URL: "https://auth.jeffemmett.com/authorize" + POSTIZ_OAUTH_TOKEN_URL: "https://auth.jeffemmett.com/api/oidc/token" + POSTIZ_OAUTH_USERINFO_URL: "https://auth.jeffemmett.com/api/oidc/userinfo" + # === Email config (EMAIL_PASS comes from Infisical) === + EMAIL_PROVIDER: "nodemailer" + EMAIL_FROM_NAME: "P2P Foundation Socials" + EMAIL_FROM_ADDRESS: "noreply@rmail.online" + EMAIL_HOST: "mailcowdockerized-postfix-mailcow-1" + EMAIL_PORT: "587" + EMAIL_SECURE: "false" + EMAIL_USER: "noreply@rmail.online" + NODE_TLS_REJECT_UNAUTHORIZED: "0" + API_LIMIT: 30 + NX_ADD_PLUGINS: false + volumes: + - postiz-p2pf-config:/config/ + - postiz-p2pf-uploads:/uploads/ + - /opt/infisical/entrypoint-wrapper.sh:/infisical-entrypoint.sh:ro + labels: + - "traefik.enable=false" + - "sablier.enable=true" + - "sablier.group=postiz-p2pf" + - "traefik.http.routers.postiz-p2pf.rule=Host(`p2pf.rsocials.online`) || Host(`socials.p2pfoundation.net`)" + - "traefik.http.routers.postiz-p2pf.entrypoints=web,websecure" + - "traefik.http.services.postiz-p2pf.loadbalancer.server.port=5000" + networks: + - traefik-public + - postiz-p2pf-internal + - mailcow-network + depends_on: + postiz-p2pf-postgres: + condition: service_healthy + postiz-p2pf-redis: + condition: service_healthy + postiz-p2pf-temporal: + condition: service_started + + postiz-p2pf-postgres: + image: postgres:17-alpine + container_name: postiz-p2pf-postgres + labels: + - "sablier.enable=true" + - "sablier.group=postiz-p2pf" + restart: unless-stopped + environment: + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" + POSTGRES_USER: postiz + POSTGRES_DB: postiz + volumes: + - postiz-p2pf-postgres-data:/var/lib/postgresql/data + networks: + - postiz-p2pf-internal + healthcheck: + test: pg_isready -U postiz -d postiz + interval: 10s + timeout: 3s + retries: 3 + + postiz-p2pf-redis: + image: redis:7.2 + container_name: postiz-p2pf-redis + labels: + - "sablier.enable=true" + - "sablier.group=postiz-p2pf" + restart: unless-stopped + healthcheck: + test: redis-cli ping + interval: 10s + timeout: 3s + retries: 3 + volumes: + - postiz-p2pf-redis-data:/data + networks: + - postiz-p2pf-internal + + postiz-p2pf-temporal-postgres: + image: postgres:16 + container_name: postiz-p2pf-temporal-postgres + labels: + - "sablier.enable=true" + - "sablier.group=postiz-p2pf" + restart: unless-stopped + environment: + POSTGRES_PASSWORD: temporal + POSTGRES_USER: temporal + networks: + - postiz-p2pf-internal + volumes: + - postiz-p2pf-temporal-postgres-data:/var/lib/postgresql/data + + postiz-p2pf-temporal: + image: temporalio/auto-setup:1.28.1 + container_name: postiz-p2pf-temporal + labels: + - "sablier.enable=true" + - "sablier.group=postiz-p2pf" + restart: unless-stopped + depends_on: + - postiz-p2pf-temporal-postgres + environment: + - DB=postgres12 + - DB_PORT=5432 + - POSTGRES_USER=temporal + - POSTGRES_PWD=temporal + - POSTGRES_SEEDS=postiz-p2pf-temporal-postgres + - TEMPORAL_NAMESPACE=default + networks: + - postiz-p2pf-internal + +volumes: + postiz-p2pf-postgres-data: + postiz-p2pf-redis-data: + postiz-p2pf-config: + postiz-p2pf-uploads: + postiz-p2pf-temporal-postgres-data: + +networks: + traefik-public: + external: true + postiz-p2pf-internal: + driver: bridge + mailcow-network: + external: true + name: mailcowdockerized_mailcow-network diff --git a/deploy/migrate-to-infisical.sh b/deploy/migrate-to-infisical.sh new file mode 100755 index 0000000..bc7d638 --- /dev/null +++ b/deploy/migrate-to-infisical.sh @@ -0,0 +1,213 @@ +#!/bin/bash +# ============================================================================= +# Migrate Postiz spaces from plaintext .env to Infisical secret injection +# ============================================================================= +# +# Run this ON the Netcup server as root: +# ssh netcup-full +# bash /path/to/migrate-to-infisical.sh +# +# What it does: +# 1. Extracts POSTGRES_PASSWORD from each existing .env +# 2. Backs up old docker-compose.yml and .env +# 3. Copies new Infisical-wired compose files into place +# 4. Creates minimal .env (INFISICAL_CLIENT_ID/SECRET + POSTGRES_PASSWORD) +# 5. Restarts each space one at a time +# 6. Verifies containers come up healthy +# 7. Moves old .env to .env.removed (you delete after confirming) +# +# Prerequisites: +# - Machine identities created in Infisical for each project +# - /opt/infisical/entrypoint-wrapper.sh exists +# - This script is in the same directory as docker-compose.{cc,p2pf,bcrg}.yml +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +POSTIZ_BASE="/opt/postiz" +TIMESTAMP=$(date +%Y%m%d-%H%M%S) + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +log() { echo -e "${CYAN}[migrate]${NC} $*"; } +ok() { echo -e "${GREEN}[ OK ]${NC} $*"; } +warn() { echo -e "${YELLOW}[ WARN ]${NC} $*"; } +err() { echo -e "${RED}[ERROR ]${NC} $*"; } + +# ── Preflight checks ────────────────────────────────────────────────────────── + +log "Preflight checks..." + +if [ "$(id -u)" -ne 0 ]; then + err "Must run as root (ssh netcup-full)" + exit 1 +fi + +if [ ! -f /opt/infisical/entrypoint-wrapper.sh ]; then + err "Infisical entrypoint wrapper not found at /opt/infisical/entrypoint-wrapper.sh" + exit 1 +fi + +for slug in cc p2pf bcrg; do + if [ ! -f "$SCRIPT_DIR/docker-compose.${slug}.yml" ]; then + err "Missing $SCRIPT_DIR/docker-compose.${slug}.yml" + exit 1 + fi +done + +ok "Preflight passed" + +# ── Space definitions ───────────────────────────────────────────────────────── + +declare -A SPACE_DIR +SPACE_DIR[cc]="crypto-commons" +SPACE_DIR[p2pf]="p2pfoundation" +SPACE_DIR[bcrg]="bondingcurve" + +declare -A SPACE_INFISICAL_SLUG +SPACE_INFISICAL_SLUG[cc]="postiz-crypto-commons" +SPACE_INFISICAL_SLUG[p2pf]="postiz-p2pfoundation" +SPACE_INFISICAL_SLUG[bcrg]="postiz-bondingcurve" + +# ── Collect Infisical credentials ───────────────────────────────────────────── +# You can either enter them interactively or set them as env vars before running: +# export INFISICAL_CC_CLIENT_ID=... INFISICAL_CC_CLIENT_SECRET=... +# export INFISICAL_P2PF_CLIENT_ID=... INFISICAL_P2PF_CLIENT_SECRET=... +# export INFISICAL_BCRG_CLIENT_ID=... INFISICAL_BCRG_CLIENT_SECRET=... + +declare -A INF_CLIENT_ID +declare -A INF_CLIENT_SECRET + +for slug in cc p2pf bcrg; do + upper=$(echo "$slug" | tr '[:lower:]' '[:upper:]') + id_var="INFISICAL_${upper}_CLIENT_ID" + secret_var="INFISICAL_${upper}_CLIENT_SECRET" + + if [ -n "${!id_var:-}" ] && [ -n "${!secret_var:-}" ]; then + INF_CLIENT_ID[$slug]="${!id_var}" + INF_CLIENT_SECRET[$slug]="${!secret_var}" + ok "Using env var credentials for ${SPACE_DIR[$slug]}" + else + echo "" + log "Enter Infisical machine identity for ${SPACE_DIR[$slug]} (${SPACE_INFISICAL_SLUG[$slug]}):" + read -rp " Client ID: " INF_CLIENT_ID[$slug] + read -rp " Client Secret: " INF_CLIENT_SECRET[$slug] + if [ -z "${INF_CLIENT_ID[$slug]}" ] || [ -z "${INF_CLIENT_SECRET[$slug]}" ]; then + err "Missing credentials for ${SPACE_DIR[$slug]}" + exit 1 + fi + fi +done + +echo "" +log "Starting migration..." +echo "" + +# ── Migrate each space ──────────────────────────────────────────────────────── + +for slug in cc p2pf bcrg; do + dir="${POSTIZ_BASE}/${SPACE_DIR[$slug]}" + log "━━━ Migrating ${SPACE_DIR[$slug]} (postiz-${slug}) ━━━" + + # 1. Extract POSTGRES_PASSWORD from existing .env + if [ -f "$dir/.env" ]; then + PG_PASS=$(grep '^POSTGRES_PASSWORD=' "$dir/.env" | cut -d'=' -f2- | tr -d '"' | tr -d "'") + if [ -z "$PG_PASS" ]; then + err "Could not extract POSTGRES_PASSWORD from $dir/.env" + err "Check the file manually and set it in the new .env" + exit 1 + fi + ok "Extracted POSTGRES_PASSWORD from existing .env" + else + err "No .env found at $dir/.env" + exit 1 + fi + + # 2. Backup old files + log "Backing up old files..." + cp "$dir/docker-compose.yml" "$dir/docker-compose.yml.pre-infisical-${TIMESTAMP}" + cp "$dir/.env" "$dir/.env.pre-infisical-${TIMESTAMP}" + ok "Backed up to *.pre-infisical-${TIMESTAMP}" + + # 3. Copy new compose file + log "Installing new Infisical-wired compose..." + cp "$SCRIPT_DIR/docker-compose.${slug}.yml" "$dir/docker-compose.yml" + ok "Installed new docker-compose.yml" + + # 4. Create minimal .env + log "Creating minimal .env (3 values only)..." + cat > "$dir/.env" << ENVEOF +# Infisical machine identity (fetches all other secrets at container start) +INFISICAL_CLIENT_ID=${INF_CLIENT_ID[$slug]} +INFISICAL_CLIENT_SECRET=${INF_CLIENT_SECRET[$slug]} + +# Postgres password (needed at compose interpolation time for DB containers) +POSTGRES_PASSWORD=${PG_PASS} +ENVEOF + chmod 600 "$dir/.env" + ok "Created minimal .env (600 perms)" + + # 5. Restart containers + log "Restarting ${SPACE_DIR[$slug]}..." + cd "$dir" + docker compose down 2>&1 | sed 's/^/ /' + docker compose up -d 2>&1 | sed 's/^/ /' + + # 6. Wait for health + log "Waiting for postiz-${slug} to be healthy (up to 90s)..." + healthy=false + for i in $(seq 1 18); do + sleep 5 + state=$(docker inspect --format='{{.State.Running}}' "postiz-${slug}" 2>/dev/null || echo "false") + if [ "$state" = "true" ]; then + # Check if the entrypoint wrapper ran successfully by looking at logs + if docker logs "postiz-${slug}" 2>&1 | grep -q "\[infisical-wrapper\] Fetching secrets"; then + healthy=true + break + fi + fi + echo -n "." + done + echo "" + + if $healthy; then + ok "postiz-${slug} is running with Infisical secrets" + else + warn "postiz-${slug} may not be healthy yet. Check logs:" + warn " docker logs postiz-${slug} 2>&1 | head -30" + warn "Continuing with next space..." + fi + + # 7. Move old .env backup to .removed + mv "$dir/.env.pre-infisical-${TIMESTAMP}" "$dir/.env.REMOVED-${TIMESTAMP}" + ok "Old .env renamed to .env.REMOVED-${TIMESTAMP}" + echo "" +done + +# ── Summary ─────────────────────────────────────────────────────────────────── + +echo "" +log "━━━ Migration Complete ━━━" +echo "" +log "Verify each space is working:" +echo " curl -s https://socials.crypto-commons.org | head -5" +echo " curl -s https://p2pf.rsocials.online | head -5" +echo " curl -s https://bondingcurve.rsocials.online | head -5" +echo "" +log "Check container logs:" +echo " docker logs postiz-cc 2>&1 | grep infisical" +echo " docker logs postiz-p2pf 2>&1 | grep infisical" +echo " docker logs postiz-bcrg 2>&1 | grep infisical" +echo "" +log "Once confirmed working, delete the old .env backups:" +echo " rm /opt/postiz/crypto-commons/.env.REMOVED-*" +echo " rm /opt/postiz/p2pfoundation/.env.REMOVED-*" +echo " rm /opt/postiz/bondingcurve/.env.REMOVED-*" +echo " rm /opt/postiz/*/docker-compose.yml.pre-infisical-*" +echo "" +ok "Done! Secrets are now managed by Infisical."