diff --git a/docker-compose.yml b/docker-compose.yml index 98d2a28..aa78878 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,9 +77,6 @@ services: PG_DB_SAAS: ${POSTGRES_DB:-cameleer_saas} SAAS_ADMIN_USER: ${SAAS_ADMIN_USER:-admin} SAAS_ADMIN_PASS: ${SAAS_ADMIN_PASS:-admin} - VENDOR_SEED_ENABLED: "${VENDOR_SEED_ENABLED:-false}" - VENDOR_USER: ${VENDOR_USER:-vendor} - VENDOR_PASS: ${VENDOR_PASS:-vendor} healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\" && test -f /data/logto-bootstrap.json"] interval: 10s diff --git a/docker/logto-bootstrap.sh b/docker/logto-bootstrap.sh index 0a83cf5..d636baa 100644 --- a/docker/logto-bootstrap.sh +++ b/docker/logto-bootstrap.sh @@ -28,13 +28,7 @@ API_RESOURCE_NAME="Cameleer SaaS API" SAAS_ADMIN_USER="${SAAS_ADMIN_USER:-admin}" SAAS_ADMIN_PASS="${SAAS_ADMIN_PASS:-admin}" -# Vendor seed (optional — creates saas-vendor role + vendor user) -VENDOR_SEED_ENABLED="${VENDOR_SEED_ENABLED:-false}" -VENDOR_USER="${VENDOR_USER:-vendor}" -VENDOR_PASS="${VENDOR_PASS:-vendor}" -VENDOR_NAME="${VENDOR_NAME:-SaaS Vendor}" - -# No server config — servers are provisioned dynamically by the vendor console +# No server config — servers are provisioned dynamically by the admin console # Redirect URIs (derived from PUBLIC_HOST and PUBLIC_PROTOCOL) HOST="${PUBLIC_HOST:-localhost}" @@ -92,7 +86,7 @@ for i in $(seq 1 60); do sleep 1 done -# No server wait — servers are provisioned dynamically by the vendor console +# No server wait — servers are provisioned dynamically by the admin console # ============================================================ # PHASE 2: Get Management API token @@ -345,8 +339,7 @@ fi # ============================================================ # --- Organization roles: owner, operator, viewer --- -# Note: platform-admin / saas-vendor global role is NOT created here. -# It is injected via docker/vendor-seed.sh on the hosted SaaS environment only. +# Note: saas-vendor global role is created in Phase 12 and assigned to the admin user. log "Creating organization roles..." EXISTING_ORG_ROLES=$(api_get "/api/organization-roles") @@ -491,8 +484,8 @@ fi fi # end: ADMIN_TOKEN check fi # end: M_ADMIN_SECRET check -# No viewer user — tenant users are created by the vendor during tenant provisioning. -# No example organization — tenants are created via the vendor console. +# No viewer user — tenant users are created by the admin during tenant provisioning. +# No example organization — tenants are created via the admin console. # No server OIDC config — each provisioned server gets OIDC from env vars. ORG_ID="" @@ -583,118 +576,44 @@ EOF chmod 644 "$BOOTSTRAP_FILE" # ============================================================ -# Phase 12: Vendor Seed (optional) +# Phase 12: SaaS Admin Role # ============================================================ -if [ "$VENDOR_SEED_ENABLED" = "true" ]; then - log "" - log "=== Phase 12a: Vendor Seed ===" +log "" +log "=== Phase 12: SaaS Admin Role ===" - # Create saas-vendor global role with all API scopes - log "Checking for saas-vendor role..." - EXISTING_ROLES=$(api_get "/api/roles") - VENDOR_ROLE_ID=$(echo "$EXISTING_ROLES" | jq -r '.[] | select(.name == "saas-vendor" and .type == "User") | .id') +# Create saas-vendor global role with all API scopes +log "Checking for saas-vendor role..." +EXISTING_ROLES=$(api_get "/api/roles") +VENDOR_ROLE_ID=$(echo "$EXISTING_ROLES" | jq -r '.[] | select(.name == "saas-vendor" and .type == "User") | .id') - if [ -z "$VENDOR_ROLE_ID" ]; then - ALL_SCOPE_IDS=$(api_get "/api/resources/$API_RESOURCE_ID/scopes" | jq '[.[].id]') - log "Creating saas-vendor role with all scopes..." - VENDOR_ROLE_RESPONSE=$(api_post "/api/roles" "{ - \"name\": \"saas-vendor\", - \"description\": \"SaaS vendor — full platform control across all tenants\", - \"type\": \"User\", - \"scopeIds\": $ALL_SCOPE_IDS - }") - VENDOR_ROLE_ID=$(echo "$VENDOR_ROLE_RESPONSE" | jq -r '.id') - log "Created saas-vendor role: $VENDOR_ROLE_ID" - else - log "saas-vendor role exists: $VENDOR_ROLE_ID" - fi - - # Assign vendor role to admin user - if [ -n "$VENDOR_ROLE_ID" ] && [ "$VENDOR_ROLE_ID" != "null" ] && [ -n "$ADMIN_USER_ID" ]; then - api_post "/api/users/$ADMIN_USER_ID/roles" "{\"roleIds\": [\"$VENDOR_ROLE_ID\"]}" >/dev/null 2>&1 - log "Assigned saas-vendor role to admin user." - fi - - # Create separate vendor user if credentials provided - if [ -n "$VENDOR_USER" ] && [ "$VENDOR_USER" != "$SAAS_ADMIN_USER" ]; then - log "Checking for vendor user '$VENDOR_USER'..." - VENDOR_USER_ID=$(api_get "/api/users?search=$VENDOR_USER" | jq -r ".[] | select(.username == \"$VENDOR_USER\") | .id") - - if [ -z "$VENDOR_USER_ID" ]; then - log "Creating vendor user '$VENDOR_USER'..." - VENDOR_RESPONSE=$(api_post "/api/users" "{ - \"username\": \"$VENDOR_USER\", - \"password\": \"$VENDOR_PASS\", - \"name\": \"$VENDOR_NAME\" - }") - VENDOR_USER_ID=$(echo "$VENDOR_RESPONSE" | jq -r '.id') - log "Created vendor user: $VENDOR_USER_ID" - else - log "Vendor user exists: $VENDOR_USER_ID" - fi - - # Assign saas-vendor role to vendor user - if [ -n "$VENDOR_ROLE_ID" ] && [ "$VENDOR_ROLE_ID" != "null" ]; then - api_post "/api/users/$VENDOR_USER_ID/roles" "{\"roleIds\": [\"$VENDOR_ROLE_ID\"]}" >/dev/null 2>&1 - log "Assigned saas-vendor role to vendor user." - fi - - # Add vendor to all existing organizations with owner role - log "Adding vendor to all organizations..." - ORG_OWNER_ROLE_ID=$(api_get "/api/organization-roles" | jq -r '.[] | select(.name == "owner") | .id') - ORGS=$(api_get "/api/organizations") - ORG_COUNT=$(echo "$ORGS" | jq 'length') - - for i in $(seq 0 $((ORG_COUNT - 1))); do - SEED_ORG_ID=$(echo "$ORGS" | jq -r ".[$i].id") - SEED_ORG_NAME=$(echo "$ORGS" | jq -r ".[$i].name") - api_post "/api/organizations/$SEED_ORG_ID/users" "{\"userIds\": [\"$VENDOR_USER_ID\"]}" >/dev/null 2>&1 - 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/$SEED_ORG_ID/users/$VENDOR_USER_ID/roles" >/dev/null 2>&1 - fi - log " Added to org '$SEED_ORG_NAME' with owner role." - done - - # Grant vendor user Logto console access - if [ -n "$ADMIN_TOKEN" ] && [ "$ADMIN_TOKEN" != "null" ]; then - log "Granting vendor Logto console access..." - VENDOR_CONSOLE_USER_ID=$(admin_api_get "/api/users?search=$VENDOR_USER" | jq -r ".[] | select(.username == \"$VENDOR_USER\") | .id" 2>/dev/null) - if [ -z "$VENDOR_CONSOLE_USER_ID" ] || [ "$VENDOR_CONSOLE_USER_ID" = "null" ]; then - VENDOR_CONSOLE_RESPONSE=$(admin_api_post "/api/users" "{ - \"username\": \"$VENDOR_USER\", - \"password\": \"$VENDOR_PASS\", - \"name\": \"$VENDOR_NAME\" - }") - VENDOR_CONSOLE_USER_ID=$(echo "$VENDOR_CONSOLE_RESPONSE" | jq -r '.id') - log "Created vendor console user: $VENDOR_CONSOLE_USER_ID" - else - log "Vendor console user exists: $VENDOR_CONSOLE_USER_ID" - fi - if [ -n "$VENDOR_CONSOLE_USER_ID" ] && [ "$VENDOR_CONSOLE_USER_ID" != "null" ]; then - ADMIN_USER_ROLE_ID=$(admin_api_get "/api/roles" | jq -r '.[] | select(.name == "user") | .id') - ADMIN_ROLE_ID=$(admin_api_get "/api/roles" | jq -r '.[] | select(.name == "default:admin") | .id') - V_ROLE_IDS="[]" - [ -n "$ADMIN_USER_ROLE_ID" ] && [ "$ADMIN_USER_ROLE_ID" != "null" ] && V_ROLE_IDS=$(echo "$V_ROLE_IDS" | jq ". + [\"$ADMIN_USER_ROLE_ID\"]") - [ -n "$ADMIN_ROLE_ID" ] && [ "$ADMIN_ROLE_ID" != "null" ] && V_ROLE_IDS=$(echo "$V_ROLE_IDS" | jq ". + [\"$ADMIN_ROLE_ID\"]") - [ "$V_ROLE_IDS" != "[]" ] && admin_api_post "/api/users/$VENDOR_CONSOLE_USER_ID/roles" "{\"roleIds\": $V_ROLE_IDS}" >/dev/null 2>&1 - log "Vendor granted Logto console access." - fi - fi - fi - - log "Vendor seed complete." +if [ -z "$VENDOR_ROLE_ID" ]; then + ALL_SCOPE_IDS=$(api_get "/api/resources/$API_RESOURCE_ID/scopes" | jq '[.[].id]') + log "Creating saas-vendor role with all scopes..." + VENDOR_ROLE_RESPONSE=$(api_post "/api/roles" "{ + \"name\": \"saas-vendor\", + \"description\": \"SaaS vendor — full platform control across all tenants\", + \"type\": \"User\", + \"scopeIds\": $ALL_SCOPE_IDS + }") + VENDOR_ROLE_ID=$(echo "$VENDOR_ROLE_RESPONSE" | jq -r '.id') + log "Created saas-vendor role: $VENDOR_ROLE_ID" +else + log "saas-vendor role exists: $VENDOR_ROLE_ID" fi +# Assign vendor role to admin user +if [ -n "$VENDOR_ROLE_ID" ] && [ "$VENDOR_ROLE_ID" != "null" ] && [ -n "$ADMIN_USER_ID" ]; then + api_post "/api/users/$ADMIN_USER_ID/roles" "{\"roleIds\": [\"$VENDOR_ROLE_ID\"]}" >/dev/null 2>&1 + log "Assigned saas-vendor role to admin user." +fi + +log "SaaS admin role configured." + log "" log "=== Bootstrap complete! ===" # dev only — remove credential logging in production log " SPA Client ID: $SPA_ID" -if [ "$VENDOR_SEED_ENABLED" = "true" ]; then - log " Vendor: $VENDOR_USER / $VENDOR_PASS (role: saas-vendor)" -fi log "" -log " No tenants created — use the vendor console to create tenants." +log " No tenants created — use the admin console to create tenants." log "" diff --git a/installer/install.sh b/installer/install.sh index 87ffd8e..b78b6ed 100644 --- a/installer/install.sh +++ b/installer/install.sh @@ -22,8 +22,6 @@ DEFAULT_HTTP_PORT="80" DEFAULT_HTTPS_PORT="443" DEFAULT_LOGTO_CONSOLE_PORT="3002" DEFAULT_LOGTO_CONSOLE_EXPOSED="true" -DEFAULT_VENDOR_ENABLED="false" -DEFAULT_VENDOR_USER="vendor" DEFAULT_COMPOSE_PROJECT="cameleer-saas" DEFAULT_COMPOSE_PROJECT_STANDALONE="cameleer" DEFAULT_DOCKER_SOCKET="/var/run/docker.sock" @@ -42,9 +40,6 @@ _ENV_HTTP_PORT="${HTTP_PORT:-}" _ENV_HTTPS_PORT="${HTTPS_PORT:-}" _ENV_LOGTO_CONSOLE_PORT="${LOGTO_CONSOLE_PORT:-}" _ENV_LOGTO_CONSOLE_EXPOSED="${LOGTO_CONSOLE_EXPOSED:-}" -_ENV_VENDOR_ENABLED="${VENDOR_ENABLED:-}" -_ENV_VENDOR_USER="${VENDOR_USER:-}" -_ENV_VENDOR_PASS="${VENDOR_PASS:-}" _ENV_MONITORING_NETWORK="${MONITORING_NETWORK:-}" _ENV_COMPOSE_PROJECT="${COMPOSE_PROJECT:-}" _ENV_DOCKER_SOCKET="${DOCKER_SOCKET:-}" @@ -66,9 +61,6 @@ HTTP_PORT="" HTTPS_PORT="" LOGTO_CONSOLE_PORT="" LOGTO_CONSOLE_EXPOSED="" -VENDOR_ENABLED="" -VENDOR_USER="" -VENDOR_PASS="" MONITORING_NETWORK="" VERSION="" COMPOSE_PROJECT="" @@ -169,9 +161,6 @@ parse_args() { --https-port) HTTPS_PORT="$2"; shift ;; --logto-console-port) LOGTO_CONSOLE_PORT="$2"; shift ;; --logto-console-exposed) LOGTO_CONSOLE_EXPOSED="$2"; shift ;; - --vendor-enabled) VENDOR_ENABLED="$2"; shift ;; - --vendor-user) VENDOR_USER="$2"; shift ;; - --vendor-password) VENDOR_PASS="$2"; shift ;; --monitoring-network) MONITORING_NETWORK="$2"; shift ;; --version) VERSION="$2"; shift ;; --compose-project) COMPOSE_PROJECT="$2"; shift ;; @@ -219,7 +208,6 @@ show_help() { echo "Expert options:" echo " --postgres-password, --clickhouse-password, --http-port," echo " --https-port, --logto-console-port, --logto-console-exposed," - echo " --vendor-enabled, --vendor-user, --vendor-password," echo " --compose-project, --docker-socket, --node-tls-reject" echo "" echo "Re-run options:" @@ -256,9 +244,6 @@ load_config_file() { https_port) [ -z "$HTTPS_PORT" ] && HTTPS_PORT="$value" ;; logto_console_port) [ -z "$LOGTO_CONSOLE_PORT" ] && LOGTO_CONSOLE_PORT="$value" ;; logto_console_exposed) [ -z "$LOGTO_CONSOLE_EXPOSED" ] && LOGTO_CONSOLE_EXPOSED="$value" ;; - vendor_enabled) [ -z "$VENDOR_ENABLED" ] && VENDOR_ENABLED="$value" ;; - vendor_user) [ -z "$VENDOR_USER" ] && VENDOR_USER="$value" ;; - vendor_password) [ -z "$VENDOR_PASS" ] && VENDOR_PASS="$value" ;; monitoring_network) [ -z "$MONITORING_NETWORK" ] && MONITORING_NETWORK="$value" ;; version) [ -z "$VERSION" ] && VERSION="$value" ;; compose_project) [ -z "$COMPOSE_PROJECT" ] && COMPOSE_PROJECT="$value" ;; @@ -285,9 +270,6 @@ load_env_overrides() { [ -z "$HTTPS_PORT" ] && HTTPS_PORT="$_ENV_HTTPS_PORT" [ -z "$LOGTO_CONSOLE_PORT" ] && LOGTO_CONSOLE_PORT="$_ENV_LOGTO_CONSOLE_PORT" [ -z "$LOGTO_CONSOLE_EXPOSED" ] && LOGTO_CONSOLE_EXPOSED="$_ENV_LOGTO_CONSOLE_EXPOSED" - [ -z "$VENDOR_ENABLED" ] && VENDOR_ENABLED="$_ENV_VENDOR_ENABLED" - [ -z "$VENDOR_USER" ] && VENDOR_USER="$_ENV_VENDOR_USER" - [ -z "$VENDOR_PASS" ] && VENDOR_PASS="$_ENV_VENDOR_PASS" [ -z "$MONITORING_NETWORK" ] && MONITORING_NETWORK="$_ENV_MONITORING_NETWORK" [ -z "$VERSION" ] && VERSION="${CAMELEER_VERSION:-}" [ -z "$COMPOSE_PROJECT" ] && COMPOSE_PROJECT="$_ENV_COMPOSE_PROJECT" @@ -437,7 +419,7 @@ run_simple_prompts() { echo "" echo " Deployment mode:" - echo " [1] Multi-tenant vendor — manage platform, provision tenants on demand" + echo " [1] Multi-tenant SaaS — manage platform, provision tenants on demand" echo " [2] Single-tenant — one server instance, local auth, no identity provider" echo "" local deploy_choice @@ -445,11 +427,9 @@ run_simple_prompts() { case "${deploy_choice:-1}" in 2) DEPLOYMENT_MODE="standalone" - VENDOR_ENABLED="false" ;; *) DEPLOYMENT_MODE="saas" - VENDOR_ENABLED="true" ;; esac } @@ -470,21 +450,6 @@ run_expert_prompts() { prompt_password CLICKHOUSE_PASSWORD "ClickHouse password" "" fi - if [ "$DEPLOYMENT_MODE" = "saas" ]; then - echo "" - if prompt_yesno "Enable vendor account?"; then - VENDOR_ENABLED="true" - prompt VENDOR_USER "Vendor username" "${VENDOR_USER:-$DEFAULT_VENDOR_USER}" - if prompt_yesno "Auto-generate vendor password?" "y"; then - VENDOR_PASS="" - else - prompt_password VENDOR_PASS "Vendor password" "" - fi - else - VENDOR_ENABLED="false" - fi - fi - echo "" echo -e "${BOLD} Networking:${NC}" prompt HTTP_PORT "HTTP port" "${HTTP_PORT:-$DEFAULT_HTTP_PORT}" @@ -523,8 +488,6 @@ merge_config() { : "${HTTPS_PORT:=$DEFAULT_HTTPS_PORT}" : "${LOGTO_CONSOLE_PORT:=$DEFAULT_LOGTO_CONSOLE_PORT}" : "${LOGTO_CONSOLE_EXPOSED:=$DEFAULT_LOGTO_CONSOLE_EXPOSED}" - : "${VENDOR_ENABLED:=$DEFAULT_VENDOR_ENABLED}" - : "${VENDOR_USER:=$DEFAULT_VENDOR_USER}" : "${VERSION:=$CAMELEER_DEFAULT_VERSION}" : "${DOCKER_SOCKET:=$DEFAULT_DOCKER_SOCKET}" @@ -597,10 +560,6 @@ generate_passwords() { CLICKHOUSE_PASSWORD=$(generate_password) log_info "Generated ClickHouse password." fi - if [ "$VENDOR_ENABLED" = "true" ] && [ -z "$VENDOR_PASS" ]; then - VENDOR_PASS=$(generate_password) - log_info "Generated vendor password." - fi } # --- File generation --- @@ -703,11 +662,6 @@ EOF cat >> "$f" << EOF -# Vendor account -VENDOR_SEED_ENABLED=${VENDOR_ENABLED} -VENDOR_USER=${VENDOR_USER} -VENDOR_PASS=${VENDOR_PASS:-} - # Docker DOCKER_SOCKET=${DOCKER_SOCKET} DOCKER_GID=$(stat -c '%g' "${DOCKER_SOCKET}" 2>/dev/null || echo "0") @@ -858,9 +812,6 @@ EOF PG_DB_SAAS: cameleer_saas SAAS_ADMIN_USER: ${SAAS_ADMIN_USER:-admin} SAAS_ADMIN_PASS: ${SAAS_ADMIN_PASS:-admin} - VENDOR_SEED_ENABLED: "${VENDOR_SEED_ENABLED:-false}" - VENDOR_USER: ${VENDOR_USER:-vendor} - VENDOR_PASS: ${VENDOR_PASS:-vendor} healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/oidc/.well-known/openid-configuration', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))\" && test -f /data/logto-bootstrap.json"] interval: 10s @@ -1310,8 +1261,6 @@ http_port=${HTTP_PORT} https_port=${HTTPS_PORT} logto_console_port=${LOGTO_CONSOLE_PORT} logto_console_exposed=${LOGTO_CONSOLE_EXPOSED} -vendor_enabled=${VENDOR_ENABLED} -vendor_user=${VENDOR_USER} monitoring_network=${MONITORING_NETWORK} version=${VERSION} compose_project=${COMPOSE_PROJECT} @@ -1365,17 +1314,6 @@ ClickHouse: default / ${CLICKHOUSE_PASSWORD} EOF - if [ "$VENDOR_ENABLED" = "true" ]; then - cat >> "$f" << EOF -Vendor User: ${VENDOR_USER} -Vendor Password: ${VENDOR_PASS} - -EOF - else - echo "Vendor User: (not enabled)" >> "$f" - echo "" >> "$f" - fi - if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then echo "Logto Console: ${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}" >> "$f" else @@ -1424,9 +1362,9 @@ EOF ## First Steps 1. Open the Platform UI in your browser -2. Log in with the admin credentials from `credentials.txt` -3. Create your first tenant via the Vendor console -4. The platform will provision a dedicated server instance for the tenant +2. Log in as admin with the credentials from `credentials.txt` +3. Create tenants from the admin console +4. The platform will provision a dedicated server instance for each tenant ## Architecture @@ -1475,7 +1413,7 @@ EOF cat >> "$f" << 'EOF' The platform generated a self-signed certificate on first boot. To replace it: -1. Log in as admin and navigate to **Certificates** in the vendor console +1. Log in as admin and navigate to **Certificates** in the admin console 2. Upload your certificate and key via the UI 3. Activate the new certificate (zero-downtime swap) EOF @@ -1693,11 +1631,6 @@ print_credentials() { echo "" if [ "$DEPLOYMENT_MODE" = "saas" ]; then - if [ "$VENDOR_ENABLED" = "true" ]; then - echo -e " Vendor User: ${BOLD}${VENDOR_USER}${NC}" - echo -e " Vendor Password: ${BOLD}${VENDOR_PASS}${NC}" - echo "" - fi if [ "$LOGTO_CONSOLE_EXPOSED" = "true" ]; then echo -e " Logto Console: ${BLUE}${PUBLIC_PROTOCOL}://${PUBLIC_HOST}:${LOGTO_CONSOLE_PORT}${NC}" echo ""