2// Initialize root tenant and admin user without psql (uses node-postgres and bcrypt)
4// node init_root_tenant.js --email admin@example.com [--tenant "Root MSP"] [--subdomain root] [--name "System Administrator"] [--password "..."]
5// DATABASE_URL is read from devops/digitalocean/db_credentials.txt or env
7const fs = require('fs');
8const path = require('path');
9const { Pool } = require('pg');
10const bcrypt = require('bcrypt');
11const crypto = require('crypto');
13const args = process.argv.slice(2);
14function getArg(flag, def) {
15 const idx = args.indexOf(flag);
16 if (idx !== -1 && args[idx + 1]) return args[idx + 1];
20const email = getArg('--email');
22 console.error('Usage: node init_root_tenant.js --email admin@example.com [--tenant "Root MSP"] [--subdomain root] [--name "System Administrator"] [--password "..."]');
26const tenantName = getArg('--tenant', 'Root MSP');
27const subdomain = getArg('--subdomain', 'root');
28const adminName = getArg('--name', 'System Administrator');
29let adminPassword = getArg('--password');
32 adminPassword = crypto.randomBytes(12).toString('base64').replace(/[^a-zA-Z0-9]/g, '').slice(0, 16);
35const ROOT = path.resolve(__dirname, '../..');
36const CREDS_FILE = path.join(ROOT, 'devops', 'digitalocean', 'db_credentials.txt');
38function loadDatabaseUrlFromCreds() {
39 if (!fs.existsSync(CREDS_FILE)) return null;
40 const content = fs.readFileSync(CREDS_FILE, 'utf8');
41 const match = content.match(/^DATABASE_URL=(.*)$/m);
42 return match ? match[1].trim() : null;
46 let databaseUrl = loadDatabaseUrlFromCreds() || process.env.DATABASE_URL;
48 console.error('[ERR] No DATABASE_URL found in creds/env.');
52 // Strip sslmode=require; set SSL in client options
53 databaseUrl = databaseUrl.replace(/([?&])sslmode=require(&|$)/, (m, p1, p2) => (p2 === '&' ? p1 : ''));
54 const pool = new Pool({ connectionString: databaseUrl, ssl: { rejectUnauthorized: false } });
57 await pool.query('BEGIN');
60 const tenantRes = await pool.query(
61 `INSERT INTO tenants (name, subdomain, is_msp, status)
62 VALUES ($1, $2, TRUE, 'active')
63 ON CONFLICT (subdomain) DO UPDATE
64 SET name = EXCLUDED.name,
69 [tenantName, subdomain]
71 const tenantId = tenantRes.rows[0].tenant_id;
74 const hash = await bcrypt.hash(adminPassword, 10);
76 // Ensure users table has required columns; fallback between name/full_name
77 // Prefer columns: (tenant_id, email, password_hash, full_name, role, is_active)
78 // Fallback to (tenant_id, email, password_hash, name, role)
80 // Detect columns quickly
81 const colsRes = await pool.query(`SELECT column_name FROM information_schema.columns WHERE table_name='users'`);
82 const cols = colsRes.rows.map(r => r.column_name);
83 const hasFullName = cols.includes('full_name');
84 const hasIsActive = cols.includes('is_active');
88 `INSERT INTO users (tenant_id, email, password_hash, full_name, role${hasIsActive ? ', is_active' : ''})
89 VALUES ($1, $2, $3, $4, 'admin'${hasIsActive ? ', TRUE' : ''})
90 ON CONFLICT (email) DO UPDATE
91 SET password_hash = EXCLUDED.password_hash,
92 full_name = EXCLUDED.full_name,
94 ${hasIsActive ? 'is_active = TRUE,' : ''}
96 [tenantId, email, hash, adminName]
100 `INSERT INTO users (tenant_id, email, password_hash, name, role)
101 VALUES ($1, $2, $3, $4, 'admin')
102 ON CONFLICT (email) DO UPDATE
103 SET password_hash = EXCLUDED.password_hash,
104 name = EXCLUDED.name,
106 [tenantId, email, hash, adminName]
110 await pool.query('COMMIT');
112 // Save credentials to secure file
113 const outFile = path.join(ROOT, 'devops', 'digitalocean', 'admin_credentials.txt');
114 const contents = `# Root Admin Credentials\n# Created: ${new Date().toISOString()}\n\nTENANT_NAME=${tenantName}\nSUBDOMAIN=${subdomain}\nADMIN_EMAIL=${email}\nADMIN_NAME=${adminName}\nADMIN_PASSWORD=${adminPassword}\n\nLogin URL: https://demo.everydaytech.au/login\n`;
115 fs.writeFileSync(outFile, contents, { mode: 0o600 });
117 console.log('==========================================');
118 console.log('✅ Root Tenant & Admin Initialized');
119 console.log('==========================================');
120 console.log(`Tenant: ${tenantName} (subdomain: ${subdomain})`);
121 console.log(`Admin: ${adminName} <${email}>`);
122 console.log('Credentials saved to devops/digitalocean/admin_credentials.txt (chmod 600)');
124 await pool.query('ROLLBACK');
125 console.error('[ERR] Initialization failed:', err.message);