cms.c2sgmbh/scripts/setup-tenants/setup.sh
Martin Porwoll 0976a691ec fix(setup-tenants): improve error handling to prevent silent failures
- Add robust error handling to check_existing function
- Fix create_social_links to handle API errors gracefully
- Fix create_pages to handle API errors and track skipped/failed counts
- Fix create_navigation with proper error handling
- Replace ((count++)) with $((count + 1)) for POSIX compatibility
- Add 2>/dev/null to jq calls to suppress error output
- Return 0 from functions on early exit to prevent set -e issues

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

593 lines
16 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 response
local count
response=$(api_get "$collection?where[tenant][equals]=$TENANT_ID&limit=1")
debug "check_existing response: $response"
# Handle API errors or invalid JSON
if ! count=$(echo "$response" | jq -e '.totalDocs // 0' 2>/dev/null); then
warn "Failed to check existing $collection (API error or invalid response)"
debug "Response was: $response"
echo "0"
return 0
fi
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 0
fi
# Check existing
local existing
existing=$(check_existing "social-links") || existing=0
if [[ "$existing" -gt 0 ]]; then
warn "Social Links already exist ($existing found), skipping..."
return 0
fi
local count=0
local failed=0
local link
local links
links=$(jq -c '.[]' "$config_file" 2>/dev/null) || {
warn "Failed to parse social-links.json"
return 0
}
while IFS= read -r link; do
[[ -z "$link" ]] && continue
local data
data=$(echo "$link" | jq --argjson tid "$TENANT_ID" '. + {tenant: $tid}') || continue
local platform
platform=$(echo "$link" | jq -r '.platform') || platform="unknown"
debug "Creating social link: $platform"
if api_post "social-links" "$data" "$platform" > /dev/null 2>&1; then
count=$((count + 1))
else
failed=$((failed + 1))
warn "Failed to create social link: $platform"
fi
done <<< "$links"
if [[ "$failed" -gt 0 ]]; then
warn "Created $count Social Links ($failed failed)"
else
success "Created $count Social Links"
fi
}
#######################################
# 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 0
fi
local count=0
local skipped=0
local failed=0
local pages
pages=$(jq -c '.[]' "$config_file" 2>/dev/null) || {
warn "Failed to parse pages.json"
return 0
}
local page
while IFS= read -r page; do
[[ -z "$page" ]] && continue
local slug title
slug=$(echo "$page" | jq -r '.slug' 2>/dev/null) || continue
title=$(echo "$page" | jq -r '.title' 2>/dev/null) || title="$slug"
# Check if page exists
local response existing
response=$(api_get "pages?where[tenant][equals]=$TENANT_ID&where[slug][equals]=$slug&limit=1") || response="{}"
existing=$(echo "$response" | jq '.totalDocs // 0' 2>/dev/null) || existing=0
if [[ "$existing" -gt 0 ]]; then
debug "Page '$slug' already exists, skipping..."
local id
id=$(echo "$response" | jq -r '.docs[0].id' 2>/dev/null) || id=""
[[ -n "$id" && "$id" != "null" ]] && PAGE_ID_MAP["$slug"]="$id"
skipped=$((skipped + 1))
continue
fi
local data
data=$(echo "$page" | jq --argjson tid "$TENANT_ID" '. + {tenant: $tid}') || continue
local post_response
if post_response=$(api_post "pages" "$data" "$title" 2>/dev/null); then
local id
id=$(echo "$post_response" | jq -r '.doc.id // .id // 0' 2>/dev/null) || id="0"
if [[ "$id" != "0" && "$id" != "null" ]]; then
PAGE_ID_MAP["$slug"]="$id"
count=$((count + 1))
debug "Created page: $title (ID: $id)"
else
failed=$((failed + 1))
fi
else
failed=$((failed + 1))
warn "Failed to create page: $title"
fi
done <<< "$pages"
if [[ "$failed" -gt 0 ]]; then
warn "Created $count Pages ($skipped skipped, $failed failed)"
else
success "Created $count Pages ($skipped already existed)"
fi
# 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 0
fi
# Check existing
local existing
existing=$(check_existing "navigations") || existing=0
if [[ "$existing" -gt 0 ]]; then
warn "Navigation already exists, skipping..."
return 0
fi
# Read navigation template and replace page slugs with IDs
local nav_data
nav_data=$(cat "$config_file") || {
warn "Failed to read navigation.json"
return 0
}
# 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") || true
done
# Add tenant ID
nav_data=$(echo "$nav_data" | jq --argjson tid "$TENANT_ID" '. + {tenant: $tid}' 2>/dev/null) || {
warn "Failed to add tenant ID to navigation data"
return 0
}
if api_post "navigations" "$nav_data" "Navigation" > /dev/null 2>&1; then
success "Navigation created"
else
warn "Failed to create navigation"
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 "$@"