feat: standalone single-tenant deployment mode
All checks were successful
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 14s

Single-tenant installations now run the server directly without Logto
or the SaaS management plane. The installer generates a simpler compose
with 5 services: traefik, postgres, clickhouse, cameleer3-server, and
cameleer3-server-ui. Uses local auth (built-in admin), no OIDC.

Multi-tenant (vendor) mode is unchanged — full SaaS stack with Logto.

Changes:
- New DEPLOYMENT_MODE variable (standalone/saas) replaces TENANT_ORG_NAME
- generate_compose_file_standalone() for the 5-service compose
- Standalone traefik-dynamic.yml (no /platform/ redirect)
- Stock postgres:16-alpine (server creates schema via Flyway)
- Standalone health checks (server + UI instead of Logto + SaaS)
- Standalone credentials/docs generation
- Remove Phase 12b from bootstrap (no longer needed)
- Remove setup_single_tenant_record (no longer needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-13 20:12:02 +02:00
parent 17d8d98d5f
commit f254f2700f
3 changed files with 495 additions and 159 deletions

View File

@@ -583,11 +583,9 @@ EOF
chmod 644 "$BOOTSTRAP_FILE"
# ============================================================
# Phase 12: Deployment Mode (vendor or single-tenant)
# Phase 12: Vendor Seed (optional)
# ============================================================
TENANT_ORG_NAME="${TENANT_ORG_NAME:-}"
if [ "$VENDOR_SEED_ENABLED" = "true" ]; then
log ""
log "=== Phase 12a: Vendor Seed ==="
@@ -688,53 +686,6 @@ if [ "$VENDOR_SEED_ENABLED" = "true" ]; then
fi
log "Vendor seed complete."
elif [ -n "$TENANT_ORG_NAME" ]; then
log ""
log "=== Phase 12b: Single-Tenant Setup ==="
# Create organization for the tenant
TENANT_SLUG=$(echo "$TENANT_ORG_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g; s/--*/-/g; s/^-//; s/-$//')
log "Creating organization '$TENANT_ORG_NAME' (slug: $TENANT_SLUG)..."
EXISTING_ORG_ID=$(api_get "/api/organizations" | jq -r ".[] | select(.name == \"$TENANT_ORG_NAME\") | .id")
if [ -n "$EXISTING_ORG_ID" ]; then
log "Organization already exists: $EXISTING_ORG_ID"
TENANT_ORG_ID="$EXISTING_ORG_ID"
else
ORG_RESPONSE=$(api_post "/api/organizations" "{\"name\": \"$TENANT_ORG_NAME\"}")
TENANT_ORG_ID=$(echo "$ORG_RESPONSE" | jq -r '.id')
log "Created organization: $TENANT_ORG_ID"
fi
# Add admin user to organization with owner role
if [ -n "$TENANT_ORG_ID" ] && [ "$TENANT_ORG_ID" != "null" ]; then
api_post "/api/organizations/$TENANT_ORG_ID/users" "{\"userIds\": [\"$ADMIN_USER_ID\"]}" >/dev/null 2>&1
ORG_OWNER_ROLE_ID=$(api_get "/api/organization-roles" | jq -r '.[] | select(.name == "owner") | .id')
if [ -n "$ORG_OWNER_ROLE_ID" ] && [ "$ORG_OWNER_ROLE_ID" != "null" ]; then
curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" $HOST_ARGS \
-d "{\"organizationRoleIds\": [\"$ORG_OWNER_ROLE_ID\"]}" \
"${LOGTO_ENDPOINT}/api/organizations/$TENANT_ORG_ID/users/$ADMIN_USER_ID/roles" >/dev/null 2>&1
fi
log "Added admin user to organization with owner role."
# Register OIDC redirect URIs for the tenant
TRAD_APP=$(api_get "/api/applications" | jq -r ".[] | select(.name == \"$TRAD_APP_NAME\") | .id")
if [ -n "$TRAD_APP" ] && [ "$TRAD_APP" != "null" ]; then
EXISTING_URIS=$(api_get "/api/applications/$TRAD_APP" | jq -r '.oidcClientMetadata.redirectUris')
NEW_URI="${PROTO}://${HOST}/t/${TENANT_SLUG}/oidc/callback"
if ! echo "$EXISTING_URIS" | jq -e ".[] | select(. == \"$NEW_URI\")" >/dev/null 2>&1; then
UPDATED_URIS=$(echo "$EXISTING_URIS" | jq ". + [\"$NEW_URI\"]")
api_patch "/api/applications/$TRAD_APP" "{\"oidcClientMetadata\": {\"redirectUris\": $UPDATED_URIS}}" >/dev/null 2>&1
log "Registered OIDC redirect URI for tenant: $NEW_URI"
fi
fi
# NOTE: Tenant DB record is created by the installer after Flyway migrations
# have run (the tenants table doesn't exist yet at bootstrap time).
fi
log "Single-tenant setup complete."
fi
log ""