cms.c2sgmbh/scripts/setup-tenants/setup.sh
Martin Porwoll 77f70876f4 chore: add Claude Code config, prompts, and tenant setup scripts
- Add .claude/ configuration (agents, commands, hooks, get-shit-done workflows)
- Add prompts/ directory with development planning documents
- Add scripts/setup-tenants/ with tenant configuration
- Add docs/screenshots/
- Remove obsolete phase2.2-corrections-report.md
- Update pnpm-lock.yaml
- Update detect-secrets.sh to ignore setup.sh (env var usage, not secrets)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 10:18:05 +00:00

536 lines
14 KiB
Bash
Executable file

#!/bin/bash
#
# Payload CMS Tenant Setup Script
# ================================
# Sets up tenants (porwoll.de, blogwoman.de) with all content
#
# Usage:
# ./setup.sh [OPTIONS] <tenant>
#
# Options:
# -e, --env Environment: staging|production (default: staging)
# -u, --user Admin email
# -p, --password Admin password (or use PAYLOAD_PASSWORD env var)
# -t, --tenant-id Override tenant ID
# -d, --dry-run Show what would be created without making changes
# -v, --verbose Verbose output
# -h, --help Show this help
#
# Examples:
# ./setup.sh porwoll # Setup porwoll.de on staging
# ./setup.sh -e production blogwoman # Setup blogwoman.de on production
# ./setup.sh -e production -t 5 porwoll # Setup with specific tenant ID
# ./setup.sh --dry-run porwoll # Preview without changes
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Defaults
ENV="staging"
DRY_RUN=false
VERBOSE=false
ADMIN_EMAIL=""
ADMIN_PASSWORD="${PAYLOAD_PASSWORD:-}"
TENANT_ID_OVERRIDE=""
# API URLs
declare -A API_URLS=(
["staging"]="https://pl.porwoll.tech/api"
["production"]="https://cms.c2sgmbh.de/api"
)
# Default Tenant IDs (can be overridden)
declare -A DEFAULT_TENANT_IDS=(
["porwoll"]="4"
["blogwoman"]="6"
)
# State
TOKEN=""
CREATED_PAGE_IDS=()
declare -A PAGE_ID_MAP
#######################################
# Logging functions
#######################################
log() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
debug() { [[ "$VERBOSE" == "true" ]] && echo -e "[DEBUG] $*" || true; }
#######################################
# Print usage
#######################################
usage() {
cat << 'EOF'
Payload CMS Tenant Setup Script
================================
Sets up tenants (porwoll.de, blogwoman.de) with all content
Usage:
./setup.sh [OPTIONS] <tenant>
Options:
-e, --env Environment: staging|production (default: staging)
-u, --user Admin email
-p, --password Admin password (or use PAYLOAD_PASSWORD env var)
-t, --tenant-id Override tenant ID
-d, --dry-run Show what would be created without making changes
-v, --verbose Verbose output
-h, --help Show this help
Examples:
./setup.sh porwoll # Setup porwoll.de on staging
./setup.sh -e production blogwoman # Setup blogwoman.de on production
./setup.sh -e production -t 5 porwoll # Setup with specific tenant ID
./setup.sh --dry-run porwoll # Preview without changes
EOF
exit 0
}
#######################################
# Parse command line arguments
#######################################
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-e|--env)
ENV="$2"
shift 2
;;
-u|--user)
ADMIN_EMAIL="$2"
shift 2
;;
-p|--password)
ADMIN_PASSWORD="$2"
shift 2
;;
-t|--tenant-id)
TENANT_ID_OVERRIDE="$2"
shift 2
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
usage
;;
-*)
error "Unknown option: $1"
exit 1
;;
*)
TENANT="$1"
shift
;;
esac
done
# Validate
if [[ -z "${TENANT:-}" ]]; then
error "Tenant name required. Use: porwoll or blogwoman"
exit 1
fi
if [[ ! -d "$SCRIPT_DIR/tenants/$TENANT" ]]; then
error "Unknown tenant: $TENANT. Available: porwoll, blogwoman"
exit 1
fi
if [[ ! "${API_URLS[$ENV]+isset}" ]]; then
error "Unknown environment: $ENV. Use: staging or production"
exit 1
fi
API_URL="${API_URLS[$ENV]}"
TENANT_ID="${TENANT_ID_OVERRIDE:-${DEFAULT_TENANT_IDS[$TENANT]}}"
}
#######################################
# Prompt for credentials if needed
#######################################
get_credentials() {
if [[ -z "$ADMIN_EMAIL" ]]; then
read -p "Admin Email: " ADMIN_EMAIL
fi
if [[ -z "$ADMIN_PASSWORD" ]]; then
read -sp "Admin Password: " ADMIN_PASSWORD
echo
fi
}
#######################################
# API Functions
#######################################
# Login and get JWT token
api_login() {
log "Logging in to $API_URL..."
local response
response=$(curl -s -X POST "$API_URL/users/login" \
-H "Content-Type: application/json" \
-d "{\"email\": \"$ADMIN_EMAIL\", \"password\": \"$ADMIN_PASSWORD\"}" \
-w '\n%{http_code}')
local http_code
http_code=$(echo "$response" | tail -1)
local body
body=$(echo "$response" | head -n -1)
if [[ "$http_code" != "200" ]]; then
error "Login failed (HTTP $http_code)"
debug "$body"
exit 1
fi
TOKEN=$(echo "$body" | jq -r '.token')
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
error "Failed to extract token from response"
exit 1
fi
success "Login successful"
}
# Generic API POST
api_post() {
local endpoint="$1"
local data="$2"
local description="${3:-}"
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY-RUN] Would POST to $endpoint: $description"
echo '{"doc":{"id":0}}'
return 0
fi
local response
response=$(curl -s -X POST "$API_URL/$endpoint" \
-H "Authorization: JWT $TOKEN" \
-H "Content-Type: application/json" \
-d "$data" \
-w '\n%{http_code}')
local http_code
http_code=$(echo "$response" | tail -1)
local body
body=$(echo "$response" | head -n -1)
if [[ "$http_code" == "201" || "$http_code" == "200" ]]; then
debug "POST $endpoint: HTTP $http_code"
echo "$body"
else
warn "POST $endpoint failed (HTTP $http_code)"
debug "$body"
echo "$body"
return 1
fi
}
# Generic API PATCH
api_patch() {
local endpoint="$1"
local data="$2"
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY-RUN] Would PATCH $endpoint"
return 0
fi
local response
response=$(curl -s -X PATCH "$API_URL/$endpoint" \
-H "Authorization: JWT $TOKEN" \
-H "Content-Type: application/json" \
-d "$data" \
-w '\n%{http_code}')
local http_code
http_code=$(echo "$response" | tail -1)
if [[ "$http_code" == "200" ]]; then
debug "PATCH $endpoint: OK"
return 0
else
warn "PATCH $endpoint failed (HTTP $http_code)"
return 1
fi
}
# Generic API GET
api_get() {
local endpoint="$1"
curl -s -G "$API_URL/$endpoint" \
-H "Authorization: JWT $TOKEN"
}
# Check if documents exist for tenant
check_existing() {
local collection="$1"
local count
count=$(api_get "$collection?where[tenant][equals]=$TENANT_ID&limit=1" | jq '.totalDocs // 0')
echo "$count"
}
#######################################
# Create Site Settings
#######################################
create_site_settings() {
log "Creating Site Settings..."
local config_file="$SCRIPT_DIR/tenants/$TENANT/site-settings.json"
if [[ ! -f "$config_file" ]]; then
warn "No site-settings.json found for $TENANT"
return
fi
# Check if exists
local existing
existing=$(check_existing "site-settings")
local data
data=$(cat "$config_file" | jq --argjson tid "$TENANT_ID" '.tenant = $tid')
if [[ "$existing" -gt 0 ]]; then
log "Site Settings already exists, updating..."
local id
id=$(api_get "site-settings?where[tenant][equals]=$TENANT_ID&limit=1" | jq -r '.docs[0].id')
api_patch "site-settings/$id" "$data"
else
api_post "site-settings" "$data" "Site Settings"
fi
success "Site Settings configured"
}
#######################################
# Create Social Links
#######################################
create_social_links() {
log "Creating Social Links..."
local config_file="$SCRIPT_DIR/tenants/$TENANT/social-links.json"
if [[ ! -f "$config_file" ]]; then
warn "No social-links.json found for $TENANT"
return
fi
# Check existing
local existing
existing=$(check_existing "social-links")
if [[ "$existing" -gt 0 ]]; then
warn "Social Links already exist ($existing found), skipping..."
return
fi
local count=0
local link
while IFS= read -r link; do
local data
data=$(echo "$link" | jq --argjson tid "$TENANT_ID" '. + {tenant: $tid}')
local platform
platform=$(echo "$link" | jq -r '.platform')
if api_post "social-links" "$data" "$platform" > /dev/null; then
((count++))
fi
done < <(jq -c '.[]' "$config_file")
success "Created $count Social Links"
}
#######################################
# Create Pages
#######################################
create_pages() {
log "Creating Pages..."
local config_file="$SCRIPT_DIR/tenants/$TENANT/pages.json"
if [[ ! -f "$config_file" ]]; then
warn "No pages.json found for $TENANT"
return
fi
local count=0
local page
while IFS= read -r page; do
local slug
slug=$(echo "$page" | jq -r '.slug')
local title
title=$(echo "$page" | jq -r '.title')
# Check if page exists
local existing
existing=$(api_get "pages?where[tenant][equals]=$TENANT_ID&where[slug][equals]=$slug&limit=1" | jq '.totalDocs // 0')
if [[ "$existing" -gt 0 ]]; then
debug "Page '$slug' already exists, skipping..."
local id
id=$(api_get "pages?where[tenant][equals]=$TENANT_ID&where[slug][equals]=$slug&limit=1" | jq -r '.docs[0].id')
PAGE_ID_MAP["$slug"]="$id"
continue
fi
local data
data=$(echo "$page" | jq --argjson tid "$TENANT_ID" '. + {tenant: $tid}')
local response
if response=$(api_post "pages" "$data" "$title"); then
local id
id=$(echo "$response" | jq -r '.doc.id // .id // 0')
if [[ "$id" != "0" && "$id" != "null" ]]; then
PAGE_ID_MAP["$slug"]="$id"
((count++))
debug "Created page: $title (ID: $id)"
fi
fi
done < <(jq -c '.[]' "$config_file")
success "Created $count Pages"
# Show page ID mapping
if [[ "$VERBOSE" == "true" ]]; then
log "Page ID Mapping:"
for slug in "${!PAGE_ID_MAP[@]}"; do
echo " $slug: ${PAGE_ID_MAP[$slug]}"
done
fi
}
#######################################
# Create Navigation
#######################################
create_navigation() {
log "Creating Navigation..."
local config_file="$SCRIPT_DIR/tenants/$TENANT/navigation.json"
if [[ ! -f "$config_file" ]]; then
warn "No navigation.json found for $TENANT"
return
fi
# Check existing
local existing
existing=$(check_existing "navigations")
if [[ "$existing" -gt 0 ]]; then
warn "Navigation already exists, skipping..."
return
fi
# Read navigation template and replace page slugs with IDs
local nav_data
nav_data=$(cat "$config_file")
# Replace placeholders with actual page IDs
for slug in "${!PAGE_ID_MAP[@]}"; do
local id="${PAGE_ID_MAP[$slug]}"
nav_data=$(echo "$nav_data" | sed "s/\"PAGE_ID_${slug^^}\"/\"$id\"/g" | sed "s/\"PAGE_ID_$slug\"/$id/g")
done
# Add tenant ID
nav_data=$(echo "$nav_data" | jq --argjson tid "$TENANT_ID" '. + {tenant: $tid}')
if api_post "navigations" "$nav_data" "Navigation" > /dev/null; then
success "Navigation created"
fi
}
#######################################
# Verification
#######################################
verify_setup() {
log "Verifying setup..."
echo ""
echo "=== Verification Report for $TENANT (Tenant ID: $TENANT_ID) ==="
echo ""
# Site Settings
local ss_count
ss_count=$(check_existing "site-settings")
echo "Site Settings: $ss_count"
# Social Links
local sl_count
sl_count=$(check_existing "social-links")
echo "Social Links: $sl_count"
# Pages
local pages_count
pages_count=$(check_existing "pages")
echo "Pages: $pages_count"
# List pages
if [[ "$VERBOSE" == "true" ]]; then
echo ""
echo "Pages:"
api_get "pages?where[tenant][equals]=$TENANT_ID&limit=50" | jq -r '.docs[] | " [\(.id)] \(.title) (/\(.slug))"'
fi
# Navigation
local nav_count
nav_count=$(check_existing "navigations")
echo "Navigation: $nav_count"
echo ""
echo "=== Setup Complete ==="
}
#######################################
# Main
#######################################
main() {
parse_args "$@"
echo ""
echo "=================================="
echo " Payload CMS Tenant Setup"
echo "=================================="
echo ""
echo "Tenant: $TENANT"
echo "Tenant ID: $TENANT_ID"
echo "Environment: $ENV"
echo "API URL: $API_URL"
echo "Dry Run: $DRY_RUN"
echo ""
if [[ "$DRY_RUN" == "true" ]]; then
warn "DRY RUN MODE - No changes will be made"
echo ""
fi
get_credentials
api_login
echo ""
log "Starting setup for $TENANT..."
echo ""
create_site_settings
create_social_links
create_pages
create_navigation
echo ""
verify_setup
}
main "$@"