coolify-deploy-logs-cli
Version:
CLI tool for Coolify deployments
335 lines (277 loc) ⢠12.7 kB
JavaScript
/**
* 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;