feat: update spaces config to match all deployed Postiz instances
- Add all 3 active spaces: crypto-commons (cc), p2pfoundation (p2pf), bondingcurve (bcrg) with correct slugs matching container names - Add Sablier auto-sleep labels for resource conservation - Add Pocket ID OAuth config with per-space client credentials - Use multi-host routing (Host || Host) instead of redirect middleware - Switch to restart: unless-stopped matching server deployments - Generator now handles dynamic blocks (OAuth, Sablier, Traefik labels) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2bff030a92
commit
0d265ddf03
|
|
@ -3,13 +3,13 @@
|
|||
# =============================================================================
|
||||
# Generated by generate.sh from spaces.yml — DO NOT EDIT DIRECTLY.
|
||||
# To modify, edit spaces.yml and re-run: ./generate.sh
|
||||
# Domain: {{PRIMARY_DOMAIN}} (fallback: {{FALLBACK_DOMAIN}})
|
||||
# Primary: {{PRIMARY_DOMAIN}} | Fallback: {{FALLBACK_DOMAIN}}
|
||||
|
||||
services:
|
||||
postiz-{{SPACE_SLUG}}:
|
||||
image: {{POSTIZ_IMAGE}}
|
||||
container_name: postiz-{{SPACE_SLUG}}
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MAIN_URL: 'https://{{PRIMARY_DOMAIN}}'
|
||||
FRONTEND_URL: 'https://{{PRIMARY_DOMAIN}}'
|
||||
|
|
@ -22,6 +22,9 @@ services:
|
|||
IS_GENERAL: '{{IS_GENERAL}}'
|
||||
DISABLE_REGISTRATION: '{{DISABLE_REG}}'
|
||||
|
||||
# Pocket ID OAuth
|
||||
{{OAUTH_BLOCK}}
|
||||
|
||||
# Storage
|
||||
STORAGE_PROVIDER: '{{STORAGE_PROVIDER}}'
|
||||
UPLOAD_DIRECTORY: '{{UPLOAD_DIR}}'
|
||||
|
|
@ -48,11 +51,6 @@ services:
|
|||
MASTODON_URL: '${MASTODON_URL:-https://mastodon.social}'
|
||||
MASTODON_CLIENT_ID: '${MASTODON_CLIENT_ID:-}'
|
||||
MASTODON_CLIENT_SECRET: '${MASTODON_CLIENT_SECRET:-}'
|
||||
SLACK_ID: '${SLACK_ID:-}'
|
||||
SLACK_SECRET: '${SLACK_SECRET:-}'
|
||||
SLACK_SIGNING_SECRET: '${SLACK_SIGNING_SECRET:-}'
|
||||
PINTEREST_CLIENT_ID: '${PINTEREST_CLIENT_ID:-}'
|
||||
PINTEREST_CLIENT_SECRET: '${PINTEREST_CLIENT_SECRET:-}'
|
||||
|
||||
# Email
|
||||
EMAIL_PROVIDER: '{{EMAIL_PROVIDER}}'
|
||||
|
|
@ -76,19 +74,7 @@ services:
|
|||
- postiz-{{SPACE_SLUG}}-config:/config/
|
||||
- postiz-{{SPACE_SLUG}}-uploads:/uploads/
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# Primary domain -> Postiz
|
||||
- "traefik.http.routers.postiz-{{SPACE_SLUG}}.rule=Host(`{{PRIMARY_DOMAIN}}`)"
|
||||
- "traefik.http.routers.postiz-{{SPACE_SLUG}}.entrypoints=web"
|
||||
- "traefik.http.services.postiz-{{SPACE_SLUG}}.loadbalancer.server.port={{POSTIZ_PORT}}"
|
||||
# Redirect fallback domain -> primary domain
|
||||
- "traefik.http.routers.postiz-{{SPACE_SLUG}}-redirect.rule=Host(`{{FALLBACK_DOMAIN}}`)"
|
||||
- "traefik.http.routers.postiz-{{SPACE_SLUG}}-redirect.entrypoints=web"
|
||||
- "traefik.http.routers.postiz-{{SPACE_SLUG}}-redirect.middlewares=postiz-{{SPACE_SLUG}}-redirect"
|
||||
- "traefik.http.middlewares.postiz-{{SPACE_SLUG}}-redirect.redirectregex.regex=^https?://{{FALLBACK_ESCAPED}}(.*)"
|
||||
- "traefik.http.middlewares.postiz-{{SPACE_SLUG}}-redirect.redirectregex.replacement=https://{{PRIMARY_DOMAIN}}$${1}"
|
||||
- "traefik.http.middlewares.postiz-{{SPACE_SLUG}}-redirect.redirectregex.permanent=true"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
{{TRAEFIK_LABELS}}
|
||||
networks:
|
||||
- traefik-public
|
||||
- postiz-{{SPACE_SLUG}}-internal
|
||||
|
|
@ -102,7 +88,8 @@ services:
|
|||
postiz-{{SPACE_SLUG}}-postgres:
|
||||
image: {{POSTGRES_IMAGE}}
|
||||
container_name: postiz-{{SPACE_SLUG}}-postgres
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
{{SABLIER_LABELS_DB}}
|
||||
environment:
|
||||
POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}'
|
||||
POSTGRES_USER: postiz
|
||||
|
|
@ -116,20 +103,12 @@ services:
|
|||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
postiz-{{SPACE_SLUG}}-redis:
|
||||
image: {{REDIS_IMAGE}}
|
||||
container_name: postiz-{{SPACE_SLUG}}-redis
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
{{SABLIER_LABELS_REDIS}}
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 10s
|
||||
|
|
@ -139,18 +118,11 @@ services:
|
|||
- postiz-{{SPACE_SLUG}}-redis-data:/data
|
||||
networks:
|
||||
- postiz-{{SPACE_SLUG}}-internal
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SETGID
|
||||
- SETUID
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
postiz-{{SPACE_SLUG}}-temporal-postgres:
|
||||
image: {{TEMPORAL_PG_IMAGE}}
|
||||
container_name: postiz-{{SPACE_SLUG}}-temporal-postgres
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_PASSWORD: temporal
|
||||
POSTGRES_USER: temporal
|
||||
|
|
@ -158,20 +130,11 @@ services:
|
|||
- postiz-{{SPACE_SLUG}}-internal
|
||||
volumes:
|
||||
- postiz-{{SPACE_SLUG}}-temporal-postgres-data:/var/lib/postgresql/data
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- DAC_OVERRIDE
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
postiz-{{SPACE_SLUG}}-temporal:
|
||||
image: {{TEMPORAL_IMAGE}}
|
||||
container_name: postiz-{{SPACE_SLUG}}-temporal
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- postiz-{{SPACE_SLUG}}-temporal-postgres
|
||||
environment:
|
||||
|
|
|
|||
116
generate.sh
116
generate.sh
|
|
@ -73,33 +73,35 @@ get_space_override() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Escape dots for regex (used in Traefik redirect rules)
|
||||
escape_dots() {
|
||||
# Produce \\. for each dot (double-escaped for YAML double-quoted strings)
|
||||
# sed replacement needs 4 backslashes to produce 2 in output
|
||||
echo "$1" | sed 's/\./\\\\\\\\./g'
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generate compose file for a single space
|
||||
# ---------------------------------------------------------------------------
|
||||
generate_space() {
|
||||
local space="$1"
|
||||
|
||||
local primary_domain fallback_domain email_from
|
||||
local primary_domain fallback_domain email_from 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")
|
||||
|
||||
if [[ -z "$primary_domain" || "$primary_domain" == "null" ]]; then
|
||||
echo -e "${RED} Error: spaces.${space}.primary_domain is required${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Slug defaults to space key name
|
||||
if [[ -z "$slug" || "$slug" == "null" ]]; then
|
||||
slug="$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
|
||||
local storage_provider upload_dir disable_reg is_general api_limit
|
||||
local use_sablier use_oauth
|
||||
local oauth_url oauth_auth_url oauth_token_url oauth_userinfo_url
|
||||
local oauth_display_name oauth_logo_url
|
||||
|
||||
postiz_image=$(get_space_override "$space" "image")
|
||||
postiz_port=$(get_space_override "$space" "port")
|
||||
|
|
@ -118,6 +120,14 @@ generate_space() {
|
|||
disable_reg=$(get_space_override "$space" "disable_registration")
|
||||
is_general=$(get_space_override "$space" "is_general")
|
||||
api_limit=$(get_space_override "$space" "api_limit")
|
||||
use_sablier=$(get_space_override "$space" "sablier")
|
||||
use_oauth=$(get_space_override "$space" "oauth")
|
||||
oauth_url=$(get_space_override "$space" "oauth_url")
|
||||
oauth_auth_url=$(get_space_override "$space" "oauth_auth_url")
|
||||
oauth_token_url=$(get_space_override "$space" "oauth_token_url")
|
||||
oauth_userinfo_url=$(get_space_override "$space" "oauth_userinfo_url")
|
||||
oauth_display_name=$(get_space_override "$space" "oauth_display_name")
|
||||
oauth_logo_url=$(get_space_override "$space" "oauth_logo_url")
|
||||
|
||||
# Fallback domain defaults
|
||||
if [[ -z "$fallback_domain" || "$fallback_domain" == "null" ]]; then
|
||||
|
|
@ -127,18 +137,60 @@ generate_space() {
|
|||
email_from="$email_user"
|
||||
fi
|
||||
|
||||
local fallback_escaped
|
||||
fallback_escaped=$(escape_dots "$fallback_domain")
|
||||
# -----------------------------------------------------------------------
|
||||
# Build dynamic blocks
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
# OAuth block
|
||||
local oauth_block
|
||||
if [[ "$use_oauth" == "true" ]]; then
|
||||
oauth_block=" POSTIZ_GENERIC_OAUTH: 'true'
|
||||
NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME: '${oauth_display_name}'
|
||||
NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL: '${oauth_logo_url}'
|
||||
POSTIZ_OAUTH_URL: '${oauth_url}'
|
||||
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'"
|
||||
else
|
||||
oauth_block=" # OAuth disabled for this space"
|
||||
fi
|
||||
|
||||
# Traefik + Sablier labels
|
||||
local traefik_labels sablier_labels_db sablier_labels_redis
|
||||
if [[ "$use_sablier" == "true" ]]; then
|
||||
traefik_labels=" - \"traefik.enable=false\"
|
||||
- \"sablier.enable=true\"
|
||||
- \"sablier.group=postiz-${slug}\"
|
||||
- \"traefik.http.routers.postiz-${slug}.rule=Host(\`${primary_domain}\`) || Host(\`${fallback_domain}\`)\"
|
||||
- \"traefik.http.routers.postiz-${slug}.entrypoints=web,websecure\"
|
||||
- \"traefik.http.services.postiz-${slug}.loadbalancer.server.port=${postiz_port}\""
|
||||
sablier_labels_db=" labels:
|
||||
- \"sablier.enable=true\"
|
||||
- \"sablier.group=postiz-${slug}\""
|
||||
sablier_labels_redis=" labels:
|
||||
- \"sablier.enable=true\"
|
||||
- \"sablier.group=postiz-${slug}\""
|
||||
else
|
||||
traefik_labels=" - \"traefik.enable=true\"
|
||||
- \"traefik.http.routers.postiz-${slug}.rule=Host(\`${primary_domain}\`) || Host(\`${fallback_domain}\`)\"
|
||||
- \"traefik.http.routers.postiz-${slug}.entrypoints=web,websecure\"
|
||||
- \"traefik.http.services.postiz-${slug}.loadbalancer.server.port=${postiz_port}\"
|
||||
- \"traefik.docker.network=traefik-public\""
|
||||
sablier_labels_db=""
|
||||
sablier_labels_redis=""
|
||||
fi
|
||||
|
||||
local outfile="${OUTDIR}/docker-compose.space-${space}.yml"
|
||||
|
||||
# Perform template substitution
|
||||
# Perform template substitution using sed
|
||||
sed \
|
||||
-e "s|{{SPACE_NAME}}|${space}|g" \
|
||||
-e "s|{{SPACE_SLUG}}|${space}|g" \
|
||||
-e "s|{{SPACE_SLUG}}|${slug}|g" \
|
||||
-e "s|{{PRIMARY_DOMAIN}}|${primary_domain}|g" \
|
||||
-e "s|{{FALLBACK_DOMAIN}}|${fallback_domain}|g" \
|
||||
-e "s|{{FALLBACK_ESCAPED}}|${fallback_escaped}|g" \
|
||||
-e "s|{{POSTIZ_IMAGE}}|${postiz_image}|g" \
|
||||
-e "s|{{POSTIZ_PORT}}|${postiz_port}|g" \
|
||||
-e "s|{{POSTGRES_IMAGE}}|${postgres_image}|g" \
|
||||
|
|
@ -159,6 +211,28 @@ generate_space() {
|
|||
-e "s|{{API_LIMIT}}|${api_limit}|g" \
|
||||
"$TEMPLATE" > "$outfile"
|
||||
|
||||
# Replace multi-line blocks (sed can't do these well, use temp files)
|
||||
# OAuth block
|
||||
local tmpfile
|
||||
tmpfile=$(mktemp)
|
||||
awk -v block="$oauth_block" '{gsub(/{{OAUTH_BLOCK}}/, block); print}' "$outfile" > "$tmpfile"
|
||||
mv "$tmpfile" "$outfile"
|
||||
|
||||
# Traefik labels block
|
||||
tmpfile=$(mktemp)
|
||||
awk -v block="$traefik_labels" '{gsub(/{{TRAEFIK_LABELS}}/, block); print}' "$outfile" > "$tmpfile"
|
||||
mv "$tmpfile" "$outfile"
|
||||
|
||||
# Sablier labels for DB
|
||||
tmpfile=$(mktemp)
|
||||
awk -v block="$sablier_labels_db" '{gsub(/{{SABLIER_LABELS_DB}}/, block); print}' "$outfile" > "$tmpfile"
|
||||
mv "$tmpfile" "$outfile"
|
||||
|
||||
# Sablier labels for Redis
|
||||
tmpfile=$(mktemp)
|
||||
awk -v block="$sablier_labels_redis" '{gsub(/{{SABLIER_LABELS_REDIS}}/, block); print}' "$outfile" > "$tmpfile"
|
||||
mv "$tmpfile" "$outfile"
|
||||
|
||||
echo -e "${GREEN} ✓ ${outfile}${NC}"
|
||||
|
||||
# Collect domains for tunnel/dns generation
|
||||
|
|
@ -210,26 +284,14 @@ HEADER
|
|||
local domain="${ALL_PRIMARY_DOMAINS[$i]}"
|
||||
local fallback="${ALL_FALLBACK_DOMAINS[$i]}"
|
||||
|
||||
# Extract zone (last two parts of domain)
|
||||
local primary_zone
|
||||
primary_zone=$(echo "$domain" | rev | cut -d. -f1-2 | rev)
|
||||
local fallback_zone
|
||||
fallback_zone=$(echo "$fallback" | rev | cut -d. -f1-2 | rev)
|
||||
|
||||
# Extract record name (everything before the zone)
|
||||
local primary_name
|
||||
primary_name=$(echo "$domain" | sed "s/\\.${primary_zone}$//")
|
||||
local fallback_name
|
||||
fallback_name=$(echo "$fallback" | sed "s/\\.${fallback_zone}$//")
|
||||
|
||||
cat >> "$dns_file" <<EOF
|
||||
# Space: ${ALL_SPACE_NAMES[$i]}
|
||||
echo "Adding DNS for ${domain}..."
|
||||
# Add CNAME: ${primary_name}.${primary_zone} -> tunnel
|
||||
# Add CNAME: ${domain} -> ${tunnel_cname}
|
||||
# (Use Cloudflare dashboard or API - zone ID needed per domain)
|
||||
|
||||
echo "Adding DNS for ${fallback}..."
|
||||
# Add CNAME: ${fallback_name}.${fallback_zone} -> tunnel
|
||||
# Add CNAME: ${fallback} -> ${tunnel_cname}
|
||||
# (Use Cloudflare dashboard or API - zone ID needed per domain)
|
||||
|
||||
EOF
|
||||
|
|
|
|||
62
spaces.yml
62
spaces.yml
|
|
@ -8,7 +8,7 @@
|
|||
# Usage:
|
||||
# 1. Add a new space block under `spaces:`
|
||||
# 2. Run: ./generate.sh
|
||||
# 3. Add secrets to Infisical (or .env) for the new space
|
||||
# 3. Add secrets to Infisical for the new space
|
||||
# 4. Deploy: docker compose -f generated/docker-compose.space-<name>.yml up -d
|
||||
# 5. DNS: The script outputs the Cloudflare CNAME commands to run
|
||||
#
|
||||
|
|
@ -33,6 +33,16 @@ defaults:
|
|||
disable_registration: false
|
||||
is_general: true
|
||||
api_limit: 30
|
||||
# Sablier auto-sleep (saves resources for low-traffic spaces)
|
||||
sablier: true
|
||||
# Pocket ID OAuth (enabled by default, per-space client_id/secret in Infisical)
|
||||
oauth: true
|
||||
oauth_url: https://auth.jeffemmett.com
|
||||
oauth_auth_url: https://auth.jeffemmett.com/authorize
|
||||
oauth_token_url: https://auth.jeffemmett.com/api/oidc/token
|
||||
oauth_userinfo_url: https://auth.jeffemmett.com/api/oidc/userinfo
|
||||
oauth_display_name: Pocket ID
|
||||
oauth_logo_url: https://raw.githubusercontent.com/pocket-id/pocket-id/refs/heads/main/frontend/static/img/static-logo.svg
|
||||
|
||||
# Cloudflare tunnel target for DNS CNAME records
|
||||
cloudflare:
|
||||
|
|
@ -42,15 +52,46 @@ cloudflare:
|
|||
# =============================================================================
|
||||
# Community Spaces
|
||||
# =============================================================================
|
||||
# Each space gets a short `slug` used for container names (postiz-<slug>).
|
||||
# If omitted, slug defaults to the space key name.
|
||||
#
|
||||
# Infisical project for each space stores: JWT_SECRET, POSTGRES_PASSWORD,
|
||||
# EMAIL_PASS, POSTIZ_OAUTH_CLIENT_ID, POSTIZ_OAUTH_CLIENT_SECRET, and any
|
||||
# social media API keys.
|
||||
# =============================================================================
|
||||
|
||||
spaces:
|
||||
crypto-commons:
|
||||
# The primary domain users visit
|
||||
slug: cc
|
||||
primary_domain: socials.crypto-commons.org
|
||||
# Fallback domain on rsocials.online (redirects to primary)
|
||||
fallback_domain: socials.rsocials.online
|
||||
# Email sender for this space
|
||||
fallback_domain: socials.valleyofthecommons.com
|
||||
email_from: noreply@rmail.online
|
||||
# Services to deploy for this space
|
||||
infisical_project_id: a76d3d2c-205e-4356-a8ad-2a2c19724d8c
|
||||
postiz:
|
||||
disable_registration: true
|
||||
email_from_name: Crypto Commons
|
||||
services:
|
||||
- postiz
|
||||
|
||||
p2pfoundation:
|
||||
slug: p2pf
|
||||
primary_domain: p2pf.rsocials.online
|
||||
fallback_domain: socials.p2pfoundation.net
|
||||
email_from: noreply@rmail.online
|
||||
infisical_project_id: ea4b3b47-12d5-40d1-9a80-30bc48cdba7a
|
||||
postiz:
|
||||
email_from_name: P2P Foundation Socials
|
||||
services:
|
||||
- postiz
|
||||
|
||||
bondingcurve:
|
||||
slug: bcrg
|
||||
primary_domain: bondingcurve.rsocials.online
|
||||
fallback_domain: socials.bondingcurve.tech
|
||||
email_from: noreply@rmail.online
|
||||
infisical_project_id: 255cb27d-e8d3-459d-af2c-faff55b46d9f
|
||||
postiz:
|
||||
email_from_name: Bonding Curve Research
|
||||
services:
|
||||
- postiz
|
||||
|
||||
|
|
@ -58,12 +99,13 @@ spaces:
|
|||
# Example: Adding a new space
|
||||
# -----------------------------------------------------------------------
|
||||
# mycofi:
|
||||
# slug: mycofi # Short name for containers (postiz-mycofi)
|
||||
# primary_domain: socials.mycofi.earth
|
||||
# fallback_domain: mycofi.rsocials.online
|
||||
# email_from: noreply@mycofi.earth
|
||||
# # Override defaults if needed:
|
||||
# # postiz:
|
||||
# # disable_registration: true
|
||||
# # email_from_name: MycoFi Socials
|
||||
# infisical_project_id: <create in Infisical first>
|
||||
# postiz:
|
||||
# disable_registration: true
|
||||
# email_from_name: MycoFi Socials
|
||||
# services:
|
||||
# - postiz
|
||||
|
|
|
|||
Loading…
Reference in New Issue