rsocials-online/generate.sh

360 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# generate.sh — Generate per-space docker-compose files from spaces.yml
# =============================================================================
#
# Reads spaces.yml and produces:
# generated/docker-compose.space-<name>.yml (per-space Postiz stack)
# generated/tunnel-hostnames.yml (Cloudflare tunnel entries)
# generated/dns-commands.sh (Cloudflare DNS CNAME commands)
#
# Requirements: yq (https://github.com/mikefarah/yq) v4+
#
# Usage:
# ./generate.sh # Generate all spaces
# ./generate.sh crypto-commons # Generate a single space
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG="${SCRIPT_DIR}/spaces.yml"
TEMPLATE="${SCRIPT_DIR}/docker-compose.template.yml"
OUTDIR="${SCRIPT_DIR}/generated"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# ---------------------------------------------------------------------------
# Preflight checks
# ---------------------------------------------------------------------------
if ! command -v yq &>/dev/null; then
echo -e "${RED}Error: yq is required but not installed.${NC}"
echo " Install: https://github.com/mikefarah/yq#install"
echo " Quick: sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && sudo chmod +x /usr/local/bin/yq"
exit 1
fi
if [[ ! -f "$CONFIG" ]]; then
echo -e "${RED}Error: spaces.yml not found at ${CONFIG}${NC}"
exit 1
fi
if [[ ! -f "$TEMPLATE" ]]; then
echo -e "${RED}Error: docker-compose.template.yml not found at ${TEMPLATE}${NC}"
exit 1
fi
# ---------------------------------------------------------------------------
# Read defaults from spaces.yml
# ---------------------------------------------------------------------------
get_default() {
local key="$1"
yq ".defaults.postiz.${key}" "$CONFIG" 2>/dev/null || echo ""
}
get_space() {
local space="$1" key="$2"
yq ".spaces.${space}.${key}" "$CONFIG" 2>/dev/null || echo ""
}
get_space_override() {
local space="$1" key="$2"
local val
val=$(yq ".spaces.${space}.postiz.${key} // \"\"" "$CONFIG" 2>/dev/null)
if [[ -z "$val" || "$val" == "null" ]]; then
get_default "$key"
else
echo "$val"
fi
}
# ---------------------------------------------------------------------------
# Generate compose file for a single space
# ---------------------------------------------------------------------------
generate_space() {
local space="$1"
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")
postgres_image=$(get_space_override "$space" "postgres_image")
redis_image=$(get_space_override "$space" "redis_image")
temporal_image=$(get_space_override "$space" "temporal_image")
temporal_pg_image=$(get_space_override "$space" "temporal_postgres_image")
email_provider=$(get_space_override "$space" "email_provider")
email_from_name=$(get_space_override "$space" "email_from_name")
email_host=$(get_space_override "$space" "email_host")
email_port=$(get_space_override "$space" "email_port")
email_secure=$(get_space_override "$space" "email_secure")
email_user=$(get_space_override "$space" "email_user")
storage_provider=$(get_space_override "$space" "storage_provider")
upload_dir=$(get_space_override "$space" "upload_directory")
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
fallback_domain="${space}.rsocials.online"
fi
if [[ -z "$email_from" || "$email_from" == "null" ]]; then
email_from="$email_user"
fi
# -----------------------------------------------------------------------
# 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 using sed
sed \
-e "s|{{SPACE_NAME}}|${space}|g" \
-e "s|{{SPACE_SLUG}}|${slug}|g" \
-e "s|{{PRIMARY_DOMAIN}}|${primary_domain}|g" \
-e "s|{{FALLBACK_DOMAIN}}|${fallback_domain}|g" \
-e "s|{{POSTIZ_IMAGE}}|${postiz_image}|g" \
-e "s|{{POSTIZ_PORT}}|${postiz_port}|g" \
-e "s|{{POSTGRES_IMAGE}}|${postgres_image}|g" \
-e "s|{{REDIS_IMAGE}}|${redis_image}|g" \
-e "s|{{TEMPORAL_IMAGE}}|${temporal_image}|g" \
-e "s|{{TEMPORAL_PG_IMAGE}}|${temporal_pg_image}|g" \
-e "s|{{EMAIL_PROVIDER}}|${email_provider}|g" \
-e "s|{{EMAIL_FROM_NAME}}|${email_from_name}|g" \
-e "s|{{EMAIL_FROM}}|${email_from}|g" \
-e "s|{{EMAIL_HOST}}|${email_host}|g" \
-e "s|{{EMAIL_PORT}}|${email_port}|g" \
-e "s|{{EMAIL_SECURE}}|${email_secure}|g" \
-e "s|{{EMAIL_USER}}|${email_user}|g" \
-e "s|{{STORAGE_PROVIDER}}|${storage_provider}|g" \
-e "s|{{UPLOAD_DIR}}|${upload_dir}|g" \
-e "s|{{DISABLE_REG}}|${disable_reg}|g" \
-e "s|{{IS_GENERAL}}|${is_general}|g" \
-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
ALL_PRIMARY_DOMAINS+=("$primary_domain")
ALL_FALLBACK_DOMAINS+=("$fallback_domain")
ALL_SPACE_NAMES+=("$space")
}
# ---------------------------------------------------------------------------
# Generate Cloudflare tunnel hostname entries
# ---------------------------------------------------------------------------
generate_tunnel_config() {
local tunnel_cname
tunnel_cname=$(yq '.cloudflare.tunnel_cname' "$CONFIG" 2>/dev/null)
local tunnel_file="${OUTDIR}/tunnel-hostnames.yml"
cat > "$tunnel_file" <<'HEADER'
# Cloudflare Tunnel Hostname Entries
# Add these to /root/cloudflared/config.yml on Netcup
# Then restart: docker restart cloudflared
HEADER
for i in "${!ALL_PRIMARY_DOMAINS[@]}"; do
cat >> "$tunnel_file" <<EOF
# Space: ${ALL_SPACE_NAMES[$i]}
- hostname: ${ALL_PRIMARY_DOMAINS[$i]}
service: http://localhost:80
- hostname: ${ALL_FALLBACK_DOMAINS[$i]}
service: http://localhost:80
EOF
done
echo -e "${GREEN}${tunnel_file}${NC}"
# DNS commands
local dns_file="${OUTDIR}/dns-commands.sh"
cat > "$dns_file" <<HEADER
#!/usr/bin/env bash
# Cloudflare DNS CNAME commands for all spaces
# Run these from Netcup (or anywhere with CF API access)
# Requires: \$CLOUDFLARE_ZONE_TOKEN set in environment
#
# Tunnel CNAME target: ${tunnel_cname}
HEADER
for i in "${!ALL_PRIMARY_DOMAINS[@]}"; do
local domain="${ALL_PRIMARY_DOMAINS[$i]}"
local fallback="${ALL_FALLBACK_DOMAINS[$i]}"
cat >> "$dns_file" <<EOF
# Space: ${ALL_SPACE_NAMES[$i]}
echo "Adding DNS for ${domain}..."
# Add CNAME: ${domain} -> ${tunnel_cname}
# (Use Cloudflare dashboard or API - zone ID needed per domain)
echo "Adding DNS for ${fallback}..."
# Add CNAME: ${fallback} -> ${tunnel_cname}
# (Use Cloudflare dashboard or API - zone ID needed per domain)
EOF
done
chmod +x "$dns_file"
echo -e "${GREEN}${dns_file}${NC}"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
echo -e "${CYAN}╔═══════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ rSpace Config Generator ║${NC}"
echo -e "${CYAN}╚═══════════════════════════════════════════╝${NC}"
echo
mkdir -p "$OUTDIR"
# Collect all domains across spaces
declare -a ALL_PRIMARY_DOMAINS=()
declare -a ALL_FALLBACK_DOMAINS=()
declare -a ALL_SPACE_NAMES=()
local filter_space="${1:-}"
# Get list of spaces
local spaces
spaces=$(yq '.spaces | keys | .[]' "$CONFIG" 2>/dev/null)
if [[ -z "$spaces" ]]; then
echo -e "${RED}No spaces defined in spaces.yml${NC}"
exit 1
fi
echo -e "${YELLOW}Generating compose files...${NC}"
for space in $spaces; do
if [[ -n "$filter_space" && "$space" != "$filter_space" ]]; then
continue
fi
echo -e " ${CYAN}Space: ${space}${NC}"
generate_space "$space"
done
echo
echo -e "${YELLOW}Generating tunnel & DNS config...${NC}"
generate_tunnel_config
echo
echo -e "${GREEN}═══════════════════════════════════════════${NC}"
echo -e "${GREEN}Done! Generated files in: ${OUTDIR}/${NC}"
echo -e "${GREEN}═══════════════════════════════════════════${NC}"
echo
echo -e "Next steps:"
echo -e " 1. Review generated files in ${OUTDIR}/"
echo -e " 2. Copy compose files to server or use directly"
echo -e " 3. Add secrets (.env or Infisical) for each space"
echo -e " 4. Deploy: ${CYAN}docker compose -f generated/docker-compose.space-<name>.yml up -d${NC}"
echo -e " 5. Update Cloudflare tunnel config (see ${OUTDIR}/tunnel-hostnames.yml)"
echo -e " 6. Add DNS CNAMEs (see ${OUTDIR}/dns-commands.sh)"
}
main "$@"