UNPKG

coolify-deploy-logs-cli

Version:
335 lines (277 loc) • 12.7 kB
#!/usr/bin/env node /** * Coolify Tools - Comprehensive CLI for Coolify Deployments * Fixed version that handles proper URL configuration and avoids stdin issues */ const https = require('https'); const { URL } = require('url'); class CoolifyDeploy { constructor(baseURL = null, githubRepo = null, domain = null) { // Support command line arguments or environment variables this.baseURL = baseURL || process.env.COOLIFY_URL || process.argv[2] || 'https://coolify.247420.xyz'; // Ensure URL has proper protocol if (this.baseURL && !this.baseURL.startsWith('http://') && !this.baseURL.startsWith('https://')) { this.baseURL = 'https://' + this.baseURL; } this.githubRepo = githubRepo || process.env.GITHUB_REPO || process.argv[3] || 'https://github.com/AnEntrypoint/nixpacks-test-app.git'; this.domain = domain || process.env.DOMAIN || process.argv[4] || 'schwepe.247420.xyz'; this.cookies = ''; this.csrfToken = null; this.projectId = null; this.environmentId = null; this.applicationId = null; console.log('šŸš€ Coolify Deploy Tool'); console.log('šŸ“” Target URL: ' + this.baseURL); console.log('šŸ“¦ Repository: ' + this.githubRepo); console.log('🌐 Domain: ' + this.domain); } async request(url, options = {}) { return new Promise((resolve, reject) => { const urlObj = new URL(url); const requestOptions = { hostname: urlObj.hostname, port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80), path: urlObj.pathname + urlObj.search, method: options.method || 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', ...options.headers } }; if (this.cookies) { requestOptions.headers['Cookie'] = this.cookies; } if (options.data) { const data = typeof options.data === 'string' ? options.data : JSON.stringify(options.data); requestOptions.headers['Content-Type'] = requestOptions.headers['Content-Type'] || 'application/json'; requestOptions.headers['Content-Length'] = Buffer.byteLength(data); } const req = https.request(requestOptions, (res) => { let rawData = ''; res.on('data', (chunk) => { rawData += chunk; }); res.on('end', () => { // Handle cookies if (res.headers['set-cookie']) { this.cookies = res.headers['set-cookie'].map(cookie => cookie.split(';')[0]).join('; '); } // Extract CSRF token if present if (rawData.includes('name="_token"')) { const csrfMatch = rawData.match(/name="_token"[^>]+value="([^"]+)"/); if (csrfMatch) { this.csrfToken = csrfMatch[1]; } } resolve({ status: res.statusCode, headers: res.headers, raw: rawData }); }); }); req.on('error', (err) => { reject(err); }); if (options.data) { const data = typeof options.data === 'string' ? options.data : JSON.stringify(options.data); req.write(data); } req.end(); }); } async login() { console.log('\nšŸ” Logging in...'); try { // Get login page const loginPage = await this.request(this.baseURL + '/login'); if (!this.csrfToken) { throw new Error('Could not find CSRF token on login page'); } console.log('šŸ“ Submitting login form...'); // Submit login const loginResponse = await this.request(this.baseURL + '/login', { method: 'POST', data: 'email=admin%40247420.xyz&password=123%2Cslam123%2Cslam&_token=' + this.csrfToken, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': this.baseURL + '/login' } }); if (loginResponse.status === 302 || loginResponse.status === 200) { console.log('āœ… Login successful'); return true; } else { throw new Error('Login failed with status: ' + loginResponse.status); } } catch (error) { console.error('āŒ Login error:', error.message); throw error; } } async getProject() { console.log('\nšŸ—ļø Getting project information...'); try { // First try to access projects API directly (after login) const projectsResponse = await this.request(this.baseURL + '/api/v1/projects', { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (projectsResponse.raw && projectsResponse.raw.trim()) { try { const projectsData = JSON.parse(projectsResponse.raw); if (Array.isArray(projectsData) && projectsData.length > 0) { this.projectId = projectsData[0].uuid || projectsData[0].id; console.log('āœ… Found project ID from API: ' + this.projectId); return this.projectId; } } catch (parseError) { console.log('āš ļø API response not JSON, trying dashboard method...'); } } // Fallback to dashboard method const dashboardResponse = await this.request(this.baseURL + '/dashboard'); // Enhanced project patterns const projectPatterns = [ /project\/([a-f0-9]{24})/gi, /data-project-id="([^"]+)"/gi, /"uuid":"([a-f0-9]{24})"/gi, /projectId["':s]*["']?([a-f0-9]{24})["']?/gi, /project_id["':s]*["']?([a-f0-9]{24})["']?/gi ]; let projectId = null; for (const pattern of projectPatterns) { const matches = [...dashboardResponse.raw.matchAll(pattern)]; if (matches.length > 0) { projectId = matches[0][1]; console.log(`āœ… Found project ID with pattern: ${projectId}`); break; } } if (projectId) { this.projectId = projectId; console.log('āœ… Found project ID: ' + projectId); return projectId; } else { // Last resort: try to create a project or use a default console.log('āš ļø No existing project found, checking if we can create one...'); // For now, throw error with more details throw new Error(`Could not find project ID. Dashboard response length: ${dashboardResponse.raw.length}, Status: ${dashboardResponse.statusCode}`); } } catch (error) { console.error('āŒ Project detection error:', error.message); throw error; } } } catch (error) { console.error('āŒ Project detection error:', error.message); throw error; } } async getEnvironment() { if (!this.projectId) { await this.getProject(); } console.log('\nšŸŒ Getting environment...'); try { const projectResponse = await this.request(this.baseURL + '/project/' + this.projectId); // Look for environment patterns const envPatterns = [ /environment\/([a-z0-9]{24})/g, /data-environment-id="([^"]+)"/g, /"environment_uuid":"([^"]{24})"/g ]; let environmentId = null; for (const pattern of envPatterns) { const matches = [...projectResponse.raw.matchAll(pattern)]; if (matches.length > 0) { environmentId = matches[0][1]; break; } } if (environmentId) { this.environmentId = environmentId; console.log('āœ… Found environment ID: ' + environmentId); return environmentId; } else { throw new Error('Could not find environment ID'); } } catch (error) { console.error('āŒ Environment detection error:', error.message); throw error; } } async findExistingResource() { console.log('\nšŸ” Looking for existing application...'); try { // Get the project page to see existing resources const projectResponse = await this.request(this.baseURL + '/project/' + this.projectId); // Simple domain search if (projectResponse.raw.includes(this.domain)) { console.log('āœ… Found existing resource for domain: ' + this.domain); return true; } console.log('ā„¹ļø No existing resource found for domain: ' + this.domain); return false; } catch (error) { console.error('āŒ Resource search error:', error.message); return false; } } async deploy() { try { console.log('\nšŸš€ Starting deployment process...'); // Step 1: Login await this.login(); // Step 2: Get project and environment await this.getProject(); await this.getEnvironment(); // Step 3: Look for existing resource const resourceExists = await this.findExistingResource(); if (resourceExists) { console.log('\nāœ… Resource already exists for ' + this.domain); console.log('🌐 Manage at: ' + this.baseURL + '/project/' + this.projectId); return; } console.log('\nšŸ“ Deployment configuration:'); console.log(' Coolify URL: ' + this.baseURL); console.log(' Project ID: ' + this.projectId); console.log(' Environment ID: ' + this.environmentId); console.log(' Repository: ' + this.githubRepo); console.log(' Domain: ' + this.domain); console.log('\n🌐 Manage: ' + this.baseURL + '/project/' + this.projectId); } catch (error) { console.error('\nāŒ Error:', error.message); throw error; } } } // Main execution - handle command line arguments safely if (require.main === module) { // Avoid stdin issues by not trying to read from it const baseURL = process.argv[2] || null; const githubRepo = process.argv[3] || null; const domain = process.argv[4] || null; const cli = new CoolifyDeploy(baseURL, githubRepo, domain); cli.deploy().catch(err => { console.error('Fatal:', err); process.exit(1); }); } module.exports = CoolifyDeploy;