#!/usr/bin/env bash
# scripts/provision-enterprise.sh
# Usage: scripts/provision-enterprise.sh <slug> <org-name> <subdomain>
# Example: scripts/provision-enterprise.sh dentocorrect "DentoCorrect" dc.odontox.io
set -euo pipefail
if [ "${1:-}" = "" ] || [ "${2:-}" = "" ] || [ "${3:-}" = "" ]; then
echo "Usage: scripts/provision-enterprise.sh <slug> <org-name> <subdomain>"
echo "Example: scripts/provision-enterprise.sh dentocorrect \"DentoCorrect\" dc.odontox.io"
exit 1
fi
SLUG="$1"
ORG_NAME="$2"
SUBDOMAIN="$3"
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
INSTANCE_DIR="$ROOT_DIR/deploy/instances/$SLUG"
WORKER_NAME="odonto-${SLUG}"
DB_NAME="odontox_${SLUG}"
ORG_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
FIRST_ADMIN_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
mkdir -p "$INSTANCE_DIR"
# ── instance.json ──────────────────────────────────────────────────────────────
cat > "$INSTANCE_DIR/instance.json" <<EOF
{
"slug": "$SLUG",
"orgName": "$ORG_NAME",
"subdomain": "$SUBDOMAIN",
"workerName": "$WORKER_NAME",
"databaseName": "$DB_NAME",
"orgId": "$ORG_ID",
"createdAt": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
}
EOF
# ── .env.template ──────────────────────────────────────────────────────────────
cat > "$INSTANCE_DIR/.env.template" <<EOF
APP_URL=https://$SUBDOMAIN
API_URL=https://api.$SUBDOMAIN
PORTAL_URL=https://portal.$SUBDOMAIN
DATABASE_URL=postgres://USER:PASSWORD@HOST/$DB_NAME
OPENAI_API_KEY=
ZEPTO_API_KEY=
[email protected]
EOF
# ── wrangler.instance.toml ─────────────────────────────────────────────────────
cat > "$INSTANCE_DIR/wrangler.instance.toml" <<EOF
name = "$WORKER_NAME"
main = "src/worker.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
[vars]
APP_URL = "https://$SUBDOMAIN"
PORTAL_URL = "https://portal.$SUBDOMAIN"
RUNTIME = "cloudflare"
INSTANCE_SLUG = "$SLUG"
EOF
# ── seed.sql ───────────────────────────────────────────────────────────────────
cat > "$INSTANCE_DIR/seed.sql" <<EOF
-- Run against the isolated DB after migrations.
-- Provisions the organization and a placeholder org_admin user.
-- Replace PASSWORD_HASH with a real bcrypt hash before running.
INSERT INTO app.organizations (id, slug, name, plan_tier, billing_contact_email)
VALUES ('$ORG_ID', '$SLUG', '$ORG_NAME', 'enterprise', 'billing@$SLUG.local')
ON CONFLICT (slug) DO NOTHING;
-- Replace email and password_hash before running.
INSERT INTO app.users (id, email, password_hash, first_name, last_name, role, status, is_active, is_onboarded, account_type)
VALUES ('$FIRST_ADMIN_ID', 'admin@$SLUG.local', 'REPLACE_WITH_BCRYPT_HASH', 'Head', 'Office', 'org_admin', 'active', true, true, 'login')
ON CONFLICT DO NOTHING;
INSERT INTO app.user_organization_assignments (id, user_id, organization_id, role)
VALUES ('$(uuidgen | tr '[:upper:]' '[:lower:]')', '$FIRST_ADMIN_ID', '$ORG_ID', 'org_admin')
ON CONFLICT DO NOTHING;
EOF
# ── README.md ──────────────────────────────────────────────────────────────────
cat > "$INSTANCE_DIR/README.md" <<EOF
# $ORG_NAME Enterprise Instance
Generated by \`scripts/provision-enterprise.sh\` on $(date -u +"%Y-%m-%d").
## Checklist
- [ ] Create Neon database: \`$DB_NAME\`
- [ ] Fill \`.env.template\` values, add secrets to CF Worker \`$WORKER_NAME\`
- [ ] Run migrations: \`DATABASE_URL=<url> npx drizzle-kit push\`
- [ ] Create CF Pages project: \`odontox-$SLUG\`
- [ ] Deploy Worker: \`wrangler deploy --config deploy/instances/$SLUG/wrangler.instance.toml\`
- [ ] Add CF Worker route for \`$SUBDOMAIN/*\` → \`$WORKER_NAME\`
- [ ] Edit \`seed.sql\`: set real email + bcrypt password hash for org_admin
- [ ] Run \`seed.sql\` against the isolated DB
- [ ] Verify login at https://$SUBDOMAIN
- [ ] (Optional) Add-on: CNAME \`app.dentocorrect.pk\` → \`$SUBDOMAIN\` then add CF custom domain
EOF
echo ""
echo "✓ Enterprise instance scaffold created at: $INSTANCE_DIR"
echo ""
echo "Next: fill .env.template, then follow the checklist in $INSTANCE_DIR/README.md"