UNPKG

bgipfs

Version:
187 lines (186 loc) 8 kB
import { Flags } from '@oclif/core'; import { execa } from 'execa'; import { promises as fs } from 'node:fs'; import { BaseCommand } from '../../../base-command.js'; import { EnvManager } from '../../../lib/env-manager.js'; import { dnsSchema } from '../../../lib/env-schema.js'; import { checkRunningContainers } from '../../../lib/system.js'; export default class Start extends BaseCommand { static description = 'Start IPFS cluster'; static examples = ['bgipfs cluster start', 'bgipfs cluster start --mode dns']; static flags = { mode: Flags.string({ char: 'm', default: 'ip', description: 'Cluster mode: ip (default) or dns (with Cloudflare proxy)', options: ['ip', 'dns'], }), }; async run() { const { flags } = await this.parse(Start); try { this.logInfo(`Starting IPFS cluster in ${flags.mode.toUpperCase()} mode...`); // Update IPFS config in DNS mode if (flags.mode === 'dns') { try { const env = (await new EnvManager().readEnv({ schema: dnsSchema })); await this.updateIpfsConfig(env.GATEWAY_DOMAIN); } catch (error) { this.logError(`Failed to process IPFS config: ${error.message}`); return; } } // Build compose file list const composeFiles = ['docker-compose.yml']; if (flags.mode === 'dns') { this.logInfo('Using DNS mode config'); composeFiles.push('docker-compose.dns.yml'); } this.logInfo(`Using compose files: ${composeFiles.join(', ')}`); // Check required files const requiredFiles = [ ...composeFiles, '.env', 'service.json', 'identity.json', 'auth/admin-htpasswd', 'auth/user-htpasswd', 'ipfs.config.json', ]; // Check all required files this.logInfo('Checking required files...'); for (const file of requiredFiles) { try { // eslint-disable-next-line no-await-in-loop await fs.access(file); this.logSuccess(`Found ${file}`); } catch { this.logError(`Missing required file: ${file}`); return; } } // Check for running containers const running = await checkRunningContainers(); if (running.length > 0) { this.logInfo('The following containers are running:\n' + running.join('\n')); this.logError('You must stop the cluster with `bgipfs stop` before starting it again'); return; } // Build compose command this.logInfo('Building Docker Compose configuration...'); const composeCommand = ['compose', ...composeFiles.flatMap((f) => ['-f', f])]; // Start containers this.logInfo('Starting Docker containers...'); await execa('docker', [...composeCommand, 'up', '-d']); this.logSuccess('Containers started'); // Wait for services to be healthy this.logInfo('Waiting for services to initialize (this may take a few moments)...'); // eslint-disable-next-line no-promise-executor-return await new Promise((resolve) => setTimeout(resolve, 10_000)); // Check services in parallel const services = ['traefik', 'ipfs', 'cluster']; this.logInfo('Checking service health...'); const results = await Promise.all(services.map(async (service) => { this.logInfo(`Checking ${service}...`); const isRunning = await this.checkContainerIsRunning(service); // Get logs for all services regardless of status const { stdout } = await execa('docker', [...composeCommand, 'logs', '--tail', '5', service]); return { logs: stdout, running: isRunning, service, }; })); // Report results let allRunning = true; for (const result of results) { if (result.running) { this.logSuccess(`${result.service} is running`); } else { allRunning = false; this.logError(`${result.service} failed to start properly`); } this.log(`=== ${result.service} recent logs ===\n${result.logs}\n`); } if (allRunning) { this.logSuccess('IPFS cluster started successfully'); this.logInfo('You can now access:'); const config = flags.mode === 'ip' ? undefined : (await new EnvManager().readEnv({ partial: false, schema: dnsSchema, })); const urls = this.getEndpointUrls(flags.mode, config); this.log(`- IPFS Gateway: ${urls.gateway}`); this.log(`- Upload Endpoint: ${urls.upload}`); if (flags.mode === 'dns') { this.logInfo('\nEnsure Cloudflare is configured with:'); this.log('1. DNS records pointing to your server IP'); this.log('2. Proxy status enabled (orange cloud)'); this.log('3. SSL/TLS set to Flexible or Full'); } } else { throw new Error('Some services failed to start properly'); } } catch (error) { this.logError(`Failed to start cluster: ${error.message}`); } } async checkContainerIsRunning(service) { try { const { stdout } = await execa('docker', ['compose', 'ps', service, '--format', 'json']); const status = JSON.parse(stdout); return status.State === 'running'; } catch { return false; } } getEndpointUrls(mode, config) { const isSecure = mode !== 'ip'; const protocol = isSecure ? 'https://' : 'http://'; if (mode !== 'ip' && config) { return { gateway: `${protocol}${config.GATEWAY_DOMAIN}`, upload: `${protocol}${config.UPLOAD_DOMAIN}`, }; } return { gateway: 'http://localhost:8080', upload: 'http://localhost:5555', }; } async updateIpfsConfig(gatewayDomain) { const configPath = 'ipfs.config.json'; const config = JSON.parse(await fs.readFile(configPath, 'utf8')); const publicGateways = config.Gateway.PublicGateways || {}; // Check for existing gateways const gatewayDomains = Object.keys(publicGateways); if (gatewayDomains.length > 0) { this.logInfo('Found existing gateway configurations:'); for (const domain of gatewayDomains) { this.logInfo(`- ${domain}`); } } // Add our gateway if not present if (!publicGateways[gatewayDomain]) { this.logWarning('Adding DNS gateway configuration...'); this.logInfo(`Setting gateway domain: ${gatewayDomain}`); config.Gateway.PublicGateways = { ...publicGateways, [gatewayDomain]: { Paths: ['/ipfs', '/ipns'], UseSubdomains: true, }, }; await fs.writeFile(configPath, JSON.stringify(config, null, 2)); this.logSuccess('IPFS config updated'); } } }