diff --git a/index.js b/index.js index ace79d8..c56d5ed 100755 --- a/index.js +++ b/index.js @@ -721,7 +721,324 @@ async function versionBump (options) { } } -// Command to handle login +// Command to handle registration +async function registerCommand(options) { + const host = options.host || 'https://api.fleetbase.io'; + // Ensure host has protocol, add https:// if missing + const apiHost = host.startsWith('http://') || host.startsWith('https://') ? host : `https://${host}`; + const registrationApi = `${apiHost}/~registry/v1/developer-account/register`; + + try { + // Collect registration information + const answers = await prompt([ + { + type: 'input', + name: 'username', + message: 'Username:', + initial: options.username, + skip: !!options.username, + validate: (value) => { + if (!value || value.length < 3) { + return 'Username must be at least 3 characters'; + } + if (!/^[a-zA-Z0-9_-]+$/.test(value)) { + return 'Username can only contain letters, numbers, hyphens, and underscores'; + } + return true; + } + }, + { + type: 'input', + name: 'email', + message: 'Email:', + initial: options.email, + skip: !!options.email, + validate: (value) => { + if (!value || !value.includes('@')) { + return 'Please enter a valid email address'; + } + return true; + } + }, + { + type: 'password', + name: 'password', + message: 'Password:', + skip: !!options.password, + validate: (value) => { + if (!value || value.length < 8) { + return 'Password must be at least 8 characters'; + } + return true; + } + }, + { + type: 'input', + name: 'name', + message: 'Full Name (optional):', + initial: options.name + } + ]); + + const registrationData = { + username: options.username || answers.username, + email: options.email || answers.email, + password: options.password || answers.password, + name: options.name || answers.name || undefined + }; + + console.log('\nRegistering account...'); + console.log(`API Endpoint: ${registrationApi}`); + console.log(`Request Data:`, JSON.stringify(registrationData, null, 2)); + + // Make API call to register + const response = await axios.post(registrationApi, registrationData); + + if (response.data.status === 'success') { + console.log('\nāœ“ Account created successfully!'); + console.log('āœ“ Please check your email to verify your account.'); + const loginCmd = options.host ? `flb login -u ${registrationData.username} --host ${options.host}` : `flb login -u ${registrationData.username}`; + console.log(`\nāœ“ Once verified, you can login with: ${loginCmd}`); + } else { + console.error('Registration failed:', response.data.message || 'Unknown error'); + process.exit(1); + } + } catch (error) { + console.error('\n[DEBUG] Error caught:', error.message); + if (error.code) console.error('[DEBUG] Error code:', error.code); + if (error.request && !error.response) { + console.error('[DEBUG] No response received from server'); + console.error('[DEBUG] Request was made to:', registrationApi); + } + if (error.response && error.response.data) { + const errorData = error.response.data; + if (errorData.errors) { + console.error('\nRegistration failed with the following errors:'); + Object.keys(errorData.errors).forEach(field => { + const fieldErrors = errorData.errors[field]; + // Handle both array and string error formats + if (Array.isArray(fieldErrors)) { + fieldErrors.forEach(message => { + console.error(` - ${field}: ${message}`); + }); + } else { + console.error(` - ${field}: ${fieldErrors}`); + } + }); + } else { + console.error('Registration failed:', errorData.message || 'Unknown error'); + } + } else { + console.error('Registration failed:', error.message); + } + process.exit(1); + } +} + +// Command to install Fleetbase via Docker +async function installFleetbaseCommand(options) { + const crypto = require('crypto'); + const os = require('os'); + + console.log('\nšŸš€ Fleetbase Installation\n'); + + try { + // Collect installation parameters + const answers = await prompt([ + { + type: 'input', + name: 'host', + message: 'Enter host or IP address to bind to:', + initial: options.host || 'localhost', + validate: (value) => { + if (!value) { + return 'Host is required'; + } + return true; + } + }, + { + type: 'select', + name: 'environment', + message: 'Choose environment:', + initial: options.environment === 'production' ? 1 : 0, + choices: [ + { title: 'Development', value: 'development' }, + { title: 'Production', value: 'production' } + ] + }, + { + type: 'input', + name: 'directory', + message: 'Installation directory:', + initial: options.directory || process.cwd(), + validate: (value) => { + if (!value) { + return 'Directory is required'; + } + return true; + } + } + ]); + + const host = options.host || answers.host; + const environment = options.environment || answers.environment; + const directory = options.directory || answers.directory; + + // Determine configuration based on environment + const useHttps = environment === 'production'; + const appDebug = environment === 'development'; + const scSecure = environment === 'production'; + const schemeApi = useHttps ? 'https' : 'http'; + const schemeConsole = useHttps ? 'https' : 'http'; + + console.log(`\nšŸ“‹ Configuration:`); + console.log(` Host: ${host}`); + console.log(` Environment: ${environment}`); + console.log(` Directory: ${directory}`); + console.log(` HTTPS: ${useHttps}`); + + // Check if directory exists and has Fleetbase files + const dockerComposePath = path.join(directory, 'docker-compose.yml'); + const needsClone = !await fs.pathExists(dockerComposePath); + + if (needsClone) { + console.log('\nā³ Fleetbase repository not found, cloning...'); + + // Ensure parent directory exists + await fs.ensureDir(directory); + + // Clone the repository + const { execSync } = require('child_process'); + try { + execSync('git clone https://github.com/fleetbase/fleetbase.git .', { + cwd: directory, + stdio: 'inherit' + }); + console.log('āœ” Repository cloned successfully'); + } catch (error) { + console.error('\nāœ– Failed to clone repository:', error.message); + console.log('\nā„¹ļø You can manually clone with:'); + console.log(' git clone https://github.com/fleetbase/fleetbase.git'); + process.exit(1); + } + } + + // Generate APP_KEY + console.log('\nā³ Generating APP_KEY...'); + const appKey = 'base64:' + crypto.randomBytes(32).toString('base64'); + console.log('āœ” APP_KEY generated'); + + // Create docker-compose.override.yml + console.log('ā³ Creating docker-compose.override.yml...'); + const overrideContent = `services: + application: + environment: + APP_KEY: "${appKey}" + CONSOLE_HOST: "${schemeConsole}://${host}:4200" + ENVIRONMENT: "${environment}" + APP_DEBUG: "${appDebug}" +`; + const overridePath = path.join(directory, 'docker-compose.override.yml'); + await fs.writeFile(overridePath, overrideContent); + console.log('āœ” docker-compose.override.yml created'); + + // Create console/fleetbase.config.json (for development runtime config) + console.log('ā³ Creating console/fleetbase.config.json...'); + const configDir = path.join(directory, 'console'); + await fs.ensureDir(configDir); + const configContent = { + API_HOST: `${schemeApi}://${host}:8000`, + SOCKETCLUSTER_HOST: host, + SOCKETCLUSTER_PORT: '38000', + SOCKETCLUSTER_SECURE: scSecure + }; + const configPath = path.join(configDir, 'fleetbase.config.json'); + await fs.writeJson(configPath, configContent, { spaces: 2 }); + console.log('āœ” console/fleetbase.config.json created'); + + // Update console environment files (.env.development and .env.production) + console.log('ā³ Updating console environment files...'); + const environmentsDir = path.join(configDir, 'environments'); + + // Update .env.development + const envDevelopmentContent = `API_HOST=http://${host}:8000 +API_NAMESPACE=int/v1 +SOCKETCLUSTER_PATH=/socketcluster/ +SOCKETCLUSTER_HOST=${host} +SOCKETCLUSTER_SECURE=false +SOCKETCLUSTER_PORT=38000 +OSRM_HOST=https://router.project-osrm.org +`; + const envDevelopmentPath = path.join(environmentsDir, '.env.development'); + await fs.writeFile(envDevelopmentPath, envDevelopmentContent); + + // Update .env.production + const envProductionContent = `API_HOST=https://${host}:8000 +API_NAMESPACE=int/v1 +API_SECURE=true +SOCKETCLUSTER_PATH=/socketcluster/ +SOCKETCLUSTER_HOST=${host} +SOCKETCLUSTER_SECURE=true +SOCKETCLUSTER_PORT=38000 +OSRM_HOST=https://router.project-osrm.org +`; + const envProductionPath = path.join(environmentsDir, '.env.production'); + await fs.writeFile(envProductionPath, envProductionContent); + + console.log('āœ” Console environment files updated'); + + // Start Docker containers + console.log('\nā³ Starting Fleetbase containers...'); + console.log(' This may take a few minutes on first run...\n'); + + exec('docker compose up -d', { cwd: directory, maxBuffer: maxBuffer }, async (error, stdout, stderr) => { + if (error) { + console.error(`\nāœ– Error starting containers: ${error.message}`); + if (stderr) console.error(stderr); + process.exit(1); + } + + console.log(stdout); + console.log('āœ” Containers started'); + + // Wait for database + console.log('\nā³ Waiting for database to be ready...'); + await new Promise(resolve => setTimeout(resolve, 15000)); // Wait 15 seconds + console.log('āœ” Database should be ready'); + + // Run deploy script + console.log('\nā³ Running deployment script...'); + exec('docker compose exec -T application bash -c "./deploy.sh"', { cwd: directory, maxBuffer: maxBuffer }, (deployError, deployStdout, deployStderr) => { + if (deployError) { + console.error(`\nāœ– Error during deployment: ${deployError.message}`); + if (deployStderr) console.error(deployStderr); + console.log('\nā„¹ļø You may need to run the deployment manually:'); + console.log(' docker compose exec application bash -c "./deploy.sh"'); + } else { + console.log(deployStdout); + console.log('āœ” Deployment complete'); + } + + // Restart containers to ensure all changes are applied + exec('docker compose up -d', { cwd: directory }, () => { + console.log('\nšŸ Fleetbase is up!'); + console.log(` API → ${schemeApi}://${host}:8000`); + console.log(` Console → ${schemeConsole}://${host}:4200\n`); + console.log('ā„¹ļø Default credentials:'); + console.log(' Email: admin@fleetbase.io'); + console.log(' Password: password\n'); + }); + }); + }); + } catch (error) { + console.error('\nāœ– Installation failed:', error.message); + process.exit(1); + } +} + + + function loginCommand (options) { const npmLogin = require('npm-cli-login'); const username = options.username; @@ -875,6 +1192,24 @@ program .option('--pre-release [identifier]', 'Add pre-release identifier') .action(versionBump); +program + .command('register') + .description('Register a new Registry Developer Account') + .option('-u, --username ', 'Username for the registry') + .option('-e, --email ', 'Email address') + .option('-p, --password ', 'Password') + .option('-n, --name ', 'Your full name (optional)') + .option('-h, --host ', 'API host with protocol (default: https://api.fleetbase.io)') + .action(registerCommand); + +program + .command('install-fleetbase') + .description('Install Fleetbase using Docker') + .option('--host ', 'Host or IP address to bind to (default: localhost)') + .option('--environment ', 'Environment: development or production (default: development)') + .option('--directory ', 'Installation directory (default: current directory)') + .action(installFleetbaseCommand); + program .command('login') .description('Log in to the Fleetbase registry') diff --git a/package.json b/package.json index 84efe6e..963204a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fleetbase/cli", - "version": "0.0.3", + "version": "0.0.4", "description": "CLI tool for managing Fleetbase Extensions", "repository": "https://github.com/fleetbase/fleetbase", "license": "AGPL-3.0-or-later",