start-ipfs.js
1 import isDev from 'electron-is-dev'; 2 import path from 'path'; 3 import { spawn } from 'child_process'; 4 import fs from 'fs-extra'; 5 import ps from 'node:process'; 6 import proxyServer from './proxy-server.js'; 7 import tcpPortUsed from 'tcp-port-used'; 8 import EnvPaths from 'env-paths'; 9 import { fileURLToPath } from 'url'; 10 const dirname = path.join(path.dirname(fileURLToPath(import.meta.url))); 11 const envPaths = EnvPaths('plebbit', { suffix: false }); 12 13 // use this custom function instead of spawnSync for better logging 14 // also spawnSync might have been causing crash on start on windows 15 const spawnAsync = (...args) => 16 new Promise((resolve, reject) => { 17 const spawedProcess = spawn(...args); 18 spawedProcess.on('exit', (exitCode, signal) => { 19 if (exitCode === 0) resolve(); 20 else reject(Error(`spawnAsync process '${spawedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`)); 21 }); 22 spawedProcess.stderr.on('data', (data) => console.error(data.toString())); 23 spawedProcess.stdin.on('data', (data) => console.log(data.toString())); 24 spawedProcess.stdout.on('data', (data) => console.log(data.toString())); 25 spawedProcess.on('error', (data) => console.error(data.toString())); 26 }); 27 28 const startIpfs = async () => { 29 const ipfsFileName = process.platform == 'win32' ? 'ipfs.exe' : 'ipfs'; 30 let ipfsPath = path.join(process.resourcesPath, 'bin', ipfsFileName); 31 let ipfsDataPath = path.join(envPaths.data, 'ipfs'); 32 33 // test launching the ipfs binary in dev mode 34 // they must be downloaded first using `yarn electron:build` 35 if (isDev) { 36 let binFolderName = 'win'; 37 if (process.platform === 'linux') { 38 binFolderName = 'linux'; 39 } 40 if (process.platform === 'darwin') { 41 binFolderName = 'mac'; 42 } 43 ipfsPath = path.join(dirname, '..', 'bin', binFolderName, ipfsFileName); 44 ipfsDataPath = path.join(dirname, '..', '.plebbit', 'ipfs'); 45 } 46 47 if (!fs.existsSync(ipfsPath)) { 48 throw Error(`ipfs binary '${ipfsPath}' doesn't exist`); 49 } 50 51 console.log({ ipfsPath, ipfsDataPath }); 52 53 fs.ensureDirSync(ipfsDataPath); 54 const env = { IPFS_PATH: ipfsDataPath }; 55 // init ipfs client on first launch 56 try { 57 await spawnAsync(ipfsPath, ['init'], { env, hideWindows: true }); 58 } catch (e) {} 59 60 // make sure repo is migrated 61 try { 62 await spawnAsync(ipfsPath, ['repo', 'migrate'], { env, hideWindows: true }); 63 } catch (e) {} 64 65 // dont use 8080 port because it's too common 66 await spawnAsync(ipfsPath, ['config', '--json', 'Addresses.Gateway', '"/ip4/127.0.0.1/tcp/6473"'], { 67 env, 68 hideWindows: true, 69 }); 70 71 // use different port with proxy for debugging during env 72 let apiAddress = '/ip4/127.0.0.1/tcp/5001'; 73 if (isDev) { 74 apiAddress = apiAddress.replace('5001', '5002'); 75 proxyServer.start({ proxyPort: 5001, targetPort: 5002 }); 76 } 77 await spawnAsync(ipfsPath, ['config', 'Addresses.API', apiAddress], { env, hideWindows: true }); 78 79 await new Promise((resolve, reject) => { 80 const ipfsProcess = spawn(ipfsPath, ['daemon', '--migrate', '--enable-pubsub-experiment', '--enable-namesys-pubsub'], { env, hideWindows: true }); 81 console.log(`ipfs daemon process started with pid ${ipfsProcess.pid}`); 82 let lastError; 83 ipfsProcess.stderr.on('data', (data) => { 84 lastError = data.toString(); 85 console.error(data.toString()); 86 }); 87 ipfsProcess.stdin.on('data', (data) => console.log(data.toString())); 88 ipfsProcess.stdout.on('data', (data) => console.log(data.toString())); 89 ipfsProcess.on('error', (data) => console.error(data.toString())); 90 ipfsProcess.on('exit', () => { 91 console.error(`ipfs process with pid ${ipfsProcess.pid} exited`); 92 reject(Error(lastError)); 93 }); 94 process.on('exit', () => { 95 try { 96 ps.kill(ipfsProcess.pid); 97 } catch (e) { 98 console.log(e); 99 } 100 try { 101 // sometimes ipfs doesnt exit unless we kill pid +1 102 ps.kill(ipfsProcess.pid + 1); 103 } catch (e) { 104 console.log(e); 105 } 106 }); 107 }); 108 }; 109 110 const DefaultExport = {}; 111 112 let pendingStart = false; 113 const start = async () => { 114 if (pendingStart) { 115 return; 116 } 117 pendingStart = true; 118 try { 119 const started = await tcpPortUsed.check(5001, '127.0.0.1'); 120 if (started) { 121 return; 122 } 123 await startIpfs(); 124 } catch (e) { 125 console.log('failed starting ipfs', e); 126 try { 127 // try to run exported onError callback, can be undefined 128 DefaultExport.onError(e)?.catch?.(console.log); 129 } catch (e) {} 130 } 131 pendingStart = false; 132 }; 133 134 // retry starting ipfs every 1 second, 135 // in case it was started by another client that shut down and shut down ipfs with it 136 start(); 137 setInterval(() => { 138 start(); 139 }, 1000); 140 141 DefaultExport.start = start; 142 export default DefaultExport;