feat: backport remote BTC node support

This commit is contained in:
Sérgio Salgado 2023-02-01 15:16:35 +00:00
parent 24473de6d5
commit 1b6ba5e6dc
15 changed files with 272 additions and 55 deletions

1
.gitignore vendored
View file

@ -32,6 +32,7 @@ scratch/
seeds/
mnemonics/
certs/
blockchains/
test/stress/machines
test/stress/config.json
lamassu.json

View file

@ -1,3 +1,5 @@
NODE_ENV=
## Database variables
# Used to describe which database to use. Possible values include: DEV, RELEASE, STRESS_TEST
@ -51,6 +53,22 @@ HOSTNAME=
LOG_LEVEL=
LIGHTNING_NETWORK_DAEMON=
# Crypto nodes related variables
## Location info (can be local or remote)
BTC_NODE_LOCATION=
BTC_WALLET_LOCATION=
## Node connection info (remote node only)
BTC_NODE_HOST=
BTC_NODE_PORT=
## Node connection info (remote wallet only)
BTC_NODE_RPC_HOST=
BTC_NODE_RPC_PORT=
BTC_NODE_USER=
BTC_NODE_PASSWORD=
## Deprecated or in deprecation
HTTP=

View file

@ -113,6 +113,12 @@ Click on ``+ Add Machine`` in the sidebar. Type in a name for your machine and c
Now continue with lamassu-machine instructions from the ``INSTALL.md`` file in [lamassu-machine repository](https://github.com/lamassu/lamassu-machine).
### Run a local coin node (BTC supported)
Run `node bin/lamassu-coins` in the project root and select `Bitcoin`. This process will require the existence of certain environment variables that the setup will warn about.
Once that is done, the node needs to be run in a terminal with the following command `<YOUR_BLOCKCHAIN_DIR_ENV_VAR>/bin/bitcoind -datadir=<YOUR_BLOCKCHAIN_DIR_ENV_VAR>/bitcoin`
## Subsequent runs

View file

@ -1,54 +1,61 @@
const path = require('path')
const _ = require('lodash/fp')
const { utils: coinUtils } = require('@lamassu/coins')
const common = require('./common')
const { isDevMode, isRemoteNode } = require('../environment-helper')
module.exports = { setup, updateCore }
const coinRec = coinUtils.getCryptoCurrency('BTC')
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
const tmpDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'tmp') : '/tmp'
const usrBinDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'bin') : '/usr/local/bin'
function setup (dataDir) {
common.firewall([coinRec.defaultPort])
!isDevMode() && common.firewall([coinRec.defaultPort])
const config = buildConfig()
common.writeFile(path.resolve(dataDir, coinRec.configFile), config)
const cmd = `/usr/local/bin/${coinRec.daemon} -datadir=${dataDir}`
common.writeSupervisorConfig(coinRec, cmd)
const cmd = `${usrBinDir}/${coinRec.daemon} -datadir=${dataDir}`
!isDevMode() && common.writeSupervisorConfig(coinRec, cmd)
}
function updateCore (coinRec, isCurrentlyRunning) {
common.logger.info('Updating Bitcoin Core. This may take a minute...')
common.es(`sudo supervisorctl stop bitcoin`)
!isDevMode() && common.es(`sudo supervisorctl stop bitcoin`)
common.es(`curl -#o /tmp/bitcoin.tar.gz ${coinRec.url}`)
common.es(`tar -xzf /tmp/bitcoin.tar.gz -C /tmp/`)
common.logger.info('Updating wallet...')
common.es(`cp /tmp/${coinRec.dir}/* /usr/local/bin/`)
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm /tmp/bitcoin.tar.gz`)
common.es(`cp ${tmpDir}/${coinRec.dir}/* ${usrBinDir}/`)
common.es(`rm -r ${tmpDir}/${coinRec.dir.replace('/bin', '')}`)
common.es(`rm ${tmpDir}/bitcoin.tar.gz`)
if (common.es(`grep "addresstype=p2sh-segwit" /mnt/blockchains/bitcoin/bitcoin.conf || true`)) {
if (common.es(`grep "addresstype=p2sh-segwit" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`)) {
common.logger.info(`Enabling bech32 receiving addresses in config file..`)
common.es(`sed -i 's/addresstype=p2sh-segwit/addresstype=bech32/g' /mnt/blockchains/bitcoin/bitcoin.conf`)
common.es(`sed -i 's/addresstype=p2sh-segwit/addresstype=bech32/g' ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
} else {
common.logger.info(`bech32 receiving addresses already defined, skipping...`)
}
if (common.es(`grep "changetype=" /mnt/blockchains/bitcoin/bitcoin.conf || true`)) {
if (common.es(`grep "changetype=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`)) {
common.logger.info(`changetype already defined, skipping...`)
} else {
common.logger.info(`Enabling bech32 change addresses in config file..`)
common.es(`echo "\nchangetype=bech32" >> /mnt/blockchains/bitcoin/bitcoin.conf`)
common.es(`echo "\nchangetype=bech32" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
}
if (common.es(`grep "listenonion=" /mnt/blockchains/bitcoin/bitcoin.conf || true`)) {
if (common.es(`grep "listenonion=" ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf || true`)) {
common.logger.info(`listenonion already defined, skipping...`)
} else {
common.logger.info(`Setting 'listenonion=0' in config file...`)
common.es(`echo "\nlistenonion=0" >> /mnt/blockchains/bitcoin/bitcoin.conf`)
common.es(`echo "\nlistenonion=0" >> ${BLOCKCHAIN_DIR}/bitcoin/bitcoin.conf`)
}
if (isCurrentlyRunning) {
if (isCurrentlyRunning && !isDevMode()) {
common.logger.info('Starting wallet...')
common.es(`sudo supervisorctl start bitcoin`)
}
@ -59,6 +66,7 @@ function updateCore (coinRec, isCurrentlyRunning) {
function buildConfig () {
return `rpcuser=lamassuserver
rpcpassword=${common.randomPass()}
${isDevMode() ? `regtest=1` : ``}
dbcache=500
server=1
connections=40
@ -68,8 +76,16 @@ daemon=0
addresstype=bech32
changetype=bech32
walletrbf=1
bind=0.0.0.0:8332
rpcport=8333
listenonion=0
fallbackfee=0.00005
rpcworkqueue=2000
${isDevMode()
? `[regtest]
rpcport=18333
bind=0.0.0.0:18332
${isRemoteNode(coinRec) ? `connect=${process.env.BTC_NODE_HOST}:${process.env.BTC_NODE_PORT}` : ``}`
: `rpcport=8333
bind=0.0.0.0:8332
${isRemoteNode(coinRec) ? `connect=${process.env.BTC_NODE_HOST}:${process.env.BTC_NODE_PORT}` : ``}`}
`
}

View file

@ -3,11 +3,16 @@ const os = require('os')
const path = require('path')
const cp = require('child_process')
const fs = require('fs')
const makeDir = require('make-dir')
const _ = require('lodash/fp')
const logger = require('console-log-level')({level: 'info'})
const { isDevMode } = require('../environment-helper')
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
module.exports = {
es,
writeSupervisorConfig,
@ -106,6 +111,11 @@ function writeSupervisorConfig (coinRec, cmd, walletCmd = '') {
}
function isInstalledSoftware (coinRec) {
if (isDevMode()) {
return fs.existsSync(`${BLOCKCHAIN_DIR}/${coinRec.code}/${coinRec.configFile}`)
&& fs.existsSync(`${BLOCKCHAIN_DIR}/bin/${coinRec.daemon}`)
}
const nodeInstalled = fs.existsSync(`/etc/supervisor/conf.d/${coinRec.code}.conf`)
const walletInstalled = _.isNil(coinRec.wallet)
? true
@ -127,13 +137,19 @@ function fetchAndInstall (coinRec) {
es(`wget -q ${url}`)
es(`tar -xf ${downloadFile}`)
const usrBinDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'bin') : '/usr/local/bin'
if (isDevMode()) {
makeDir.sync(usrBinDir)
}
if (_.isEmpty(binaries.files)) {
es(`sudo cp ${binDir}/* /usr/local/bin`)
es(`sudo cp ${binDir}/* ${usrBinDir}`)
return
}
_.forEach(([source, target]) => {
es(`sudo cp ${binDir}/${source} /usr/local/bin/${target}`)
es(`sudo cp ${binDir}/${source} ${usrBinDir}/${target}`)
}, binaries.files)
}

View file

@ -10,6 +10,7 @@ const _ = require('lodash/fp')
const { utils: coinUtils } = require('@lamassu/coins')
const settingsLoader = require('../new-settings-loader')
const wallet = require('../wallet')
const { isDevMode, isRemoteNode, isRemoteWallet } = require('../environment-helper')
const common = require('./common')
const doVolume = require('./do-volume')
@ -30,7 +31,10 @@ const PLUGINS = {
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
module.exports = {run}
module.exports = {
isEnvironmentValid,
run
}
function installedVolumeFilePath (crypto) {
return path.resolve(coinUtils.cryptoDir(crypto, BLOCKCHAIN_DIR), '.installed')
@ -52,43 +56,94 @@ function processCryptos (codes) {
logger.info('Thanks! Installing: %s. Will take a while...', _.join(', ', codes))
const goodVolume = doVolume.prepareVolume()
if (!goodVolume) {
logger.error('There was an error preparing the disk volume. Exiting.')
process.exit(1)
}
const selectedCryptos = _.map(code => _.find(['code', code], cryptos), codes)
_.forEach(setupCrypto, selectedCryptos)
common.es('sudo supervisorctl reread')
common.es('sudo supervisorctl update')
const blockchainDir = BLOCKCHAIN_DIR
const backupDir = path.resolve(os.homedir(), 'backups')
const rsyncCmd = `( \
(crontab -l 2>/dev/null || echo -n "") | grep -v "@daily rsync ".*"wallet.dat"; \
echo "@daily rsync -r --prune-empty-dirs --include='*/' \
--include='wallet.dat' \
--exclude='*' ${blockchainDir} ${backupDir} > /dev/null" \
) | crontab -`
common.es(rsyncCmd)
if (isDevMode()) {
_.forEach(setupCrypto, selectedCryptos)
} else {
const goodVolume = doVolume.prepareVolume()
_.forEach(c => {
updateCrypto(c)
common.es(`sudo supervisorctl start ${c.code}`)
}, selectedCryptos)
if (!goodVolume) {
logger.error('There was an error preparing the disk volume. Exiting.')
process.exit(1)
}
_.forEach(setupCrypto, selectedCryptos)
common.es('sudo supervisorctl reread')
common.es('sudo supervisorctl update')
const blockchainDir = BLOCKCHAIN_DIR
const backupDir = path.resolve(os.homedir(), 'backups')
const rsyncCmd = `( \
(crontab -l 2>/dev/null || echo -n "") | grep -v "@daily rsync ".*"wallet.dat"; \
echo "@daily rsync -r --prune-empty-dirs --include='*/' \
--include='wallet.dat' \
--exclude='*' ${blockchainDir} ${backupDir} > /dev/null" \
) | crontab -`
common.es(rsyncCmd)
_.forEach(c => {
updateCrypto(c)
common.es(`sudo supervisorctl start ${c.code}`)
}, selectedCryptos)
}
logger.info('Installation complete.')
}
function isEnvironmentValid (crypto) {
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_LOCATION`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_NODE_LOCATION is not set!`)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_WALLET_LOCATION`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_WALLET_LOCATION is not set!`)
if (isRemoteWallet(crypto) && !isRemoteNode(crypto))
throw new Error(`Invalid environment setup for ${crypto.display}: It's not possible to use a remote wallet without using a remote node!`)
if (isRemoteNode(crypto) && !isRemoteWallet(crypto)) {
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_HOST`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_NODE_HOST is not set!`)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_PORT`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_NODE_PORT is not set!`)
if (_.isEmpty(process.env.BLOCKCHAIN_DIR))
throw new Error(`The environment variable for BLOCKCHAIN_DIR is not set!`)
}
if (isRemoteWallet(crypto)) {
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_RPC_HOST`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_NODE_RPC_HOST is not set!`)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_RPC_PORT`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_NODE_RPC_PORT is not set!`)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_USER`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_NODE_USER is not set!`)
if (_.isEmpty(process.env[`${crypto.cryptoCode}_NODE_PASSWORD`]))
throw new Error(`The environment variable for ${crypto.cryptoCode}_NODE_PASSWORD is not set!`)
}
return true
}
function setupCrypto (crypto) {
logger.info(`Installing ${crypto.display}...`)
if (!isEnvironmentValid(crypto)) throw new Error(`Environment error for ${crypto.display}`)
if (isRemoteWallet(crypto)) {
logger.info(`Environment variable ${crypto.cryptoCode}_WALLET_LOCATION is set as 'remote', so there's no need to install a node in the system. Exiting...`)
return
}
const cryptoDir = coinUtils.cryptoDir(crypto, BLOCKCHAIN_DIR)
makeDir.sync(cryptoDir)
const cryptoPlugin = plugin(crypto)
const oldDir = process.cwd()
const tmpDir = '/tmp/blockchain-install'
const tmpDir = isDevMode() ? path.resolve(BLOCKCHAIN_DIR, 'tmp', 'blockchain-install') : '/tmp/blockchain-install'
makeDir.sync(tmpDir)
process.chdir(tmpDir)
@ -97,7 +152,10 @@ function setupCrypto (crypto) {
cryptoPlugin.setup(cryptoDir)
common.writeFile(installedVolumeFilePath(crypto), '')
if (!isDevMode()) {
common.writeFile(installedVolumeFilePath(crypto), '')
}
process.chdir(oldDir)
}
@ -121,6 +179,8 @@ function plugin (crypto) {
function getBlockchainSyncStatus (cryptoList) {
return settingsLoader.loadLatest()
.then(settings => {
if (isDevMode()) return new Array(_.size(cryptoList)).fill('ready')
const blockchainStatuses = _.reduce((acc, value) => {
const processStatus = common.es(`sudo supervisorctl status ${value.code} | awk '{ print $2 }'`).trim()
return acc.then(a => {
@ -140,7 +200,9 @@ function getBlockchainSyncStatus (cryptoList) {
}
function isInstalled (crypto) {
return isInstalledSoftware(crypto) && isInstalledVolume(crypto)
return isDevMode()
? isInstalledSoftware(crypto)
: isInstalledSoftware(crypto) && isInstalledVolume(crypto)
}
function isDisabled (crypto) {

View file

@ -1,3 +1,21 @@
const path = require('path')
const isDevMode = () => process.env.NODE_ENV === 'development'
const isProdMode = () => process.env.NODE_ENV === 'production'
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
function isRemoteNode (crypto) {
return process.env[`${crypto.cryptoCode}_NODE_LOCATION`] === 'remote'
}
function isRemoteWallet (crypto) {
return process.env[`${crypto.cryptoCode}_WALLET_LOCATION`] === 'remote'
}
module.exports = {
isDevMode,
isProdMode,
isRemoteNode,
isRemoteWallet
}

View file

@ -7,6 +7,8 @@ const request = require('request-promise')
const { utils: coinUtils } = require('@lamassu/coins')
const logger = require('../../logger')
const { isRemoteNode, isRemoteWallet } = require('../../environment-helper')
const { isEnvironmentValid } = require('../../blockchain/install')
const BLOCKCHAIN_DIR = process.env.BLOCKCHAIN_DIR
@ -28,7 +30,7 @@ function fetch (account = {}, method, params) {
if (_.isNil(account.port)) throw new Error('port attribute required for jsonRpc')
const url = _.defaultTo(`http://localhost:${account.port}`, account.url)
const url = _.defaultTo(`http://${account.host}:${account.port}`, account.url)
return axios({
method: 'post',
@ -109,15 +111,30 @@ function parseConf (confPath) {
function rpcConfig (cryptoRec) {
try {
if (isRemoteWallet(cryptoRec) && isEnvironmentValid(cryptoRec)) {
return {
username: process.env[`${cryptoRec.cryptoCode}_NODE_USER`],
password: process.env[`${cryptoRec.cryptoCode}_NODE_PASSWORD`],
host: process.env[`${cryptoRec.cryptoCode}_NODE_RPC_HOST`],
port: process.env[`${cryptoRec.cryptoCode}_NODE_RPC_PORT`]
}
}
const configPath = coinUtils.configPath(cryptoRec, BLOCKCHAIN_DIR)
const config = parseConf(configPath)
return {
username: config.rpcuser,
password: config.rpcpassword,
host: 'localhost',
port: config.rpcport || cryptoRec.defaultPort
}
} catch (err) {
logger.error('Wallet is currently not installed!')
if (!isEnvironmentValid(cryptoRec)) {
logger.error('Environment is not correctly setup for remote wallet usage!')
} else {
logger.error('Wallet is currently not installed!')
}
return {
port: cryptoRec.defaultPort
}

View file

@ -5,6 +5,7 @@ const BN = require('../../../bn')
const E = require('../../../error')
const logger = require('../../../logger')
const { utils: coinUtils } = require('@lamassu/coins')
const { isDevMode } = require('../../../environment-helper')
const cryptoRec = coinUtils.getCryptoCurrency('BTC')
const unitScale = cryptoRec.unitScale
@ -36,15 +37,15 @@ function checkCryptoCode (cryptoCode) {
function accountBalance (cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => fetch('getwalletinfo'))
.then(({ balance }) => new BN(balance).shiftedBy(unitScale).decimalPlaces(0))
.then(() => fetch('getbalances'))
.then(({ mine }) => new BN(mine.trusted).shiftedBy(unitScale).decimalPlaces(0))
.catch(errorHandle)
}
function accountUnconfirmedBalance (cryptoCode) {
return checkCryptoCode(cryptoCode)
.then(() => fetch('getwalletinfo'))
.then(({ unconfirmed_balance: balance }) => new BN(balance).shiftedBy(unitScale).decimalPlaces(0))
.then(() => fetch('getbalances'))
.then(({ mine }) => new BN(mine.untrusted_pending).plus(mine.immature).shiftedBy(unitScale).decimalPlaces(0))
.catch(errorHandle)
}
@ -62,7 +63,7 @@ function estimateFee () {
function calculateFeeDiscount (feeMultiplier) {
// 0 makes bitcoind do automatic fee selection
const AUTOMATIC_FEE = 0
const AUTOMATIC_FEE = isDevMode() ? 0.01 : 0
if (!feeMultiplier || feeMultiplier.eq(1)) return AUTOMATIC_FEE
return estimateFee()
.then(estimatedFee => {

View file

@ -3,7 +3,6 @@ const BN = require('../../../bn')
const E = require('../../../error')
const _ = require('lodash/fp')
const ENV = process.env.NODE_ENV === undefined || process.env.NODE_ENV === 'development' ? 'development' : 'production'
const SUPPORTED_COINS = ['BTC']
const axios = require('axios').create({

View file

@ -0,0 +1,19 @@
const fs = require('fs')
const path = require('path')
const migrateEnv = require('../tools/migrate-env')
exports.up = function (next) {
try {
// NODE_ENV defaults to undefined on some environments, best to check the existence of the production environment file
migrateEnv([
['NODE_ENV', fs.existsSync(path.resolve('/etc', 'lamassu', '.env')) ? 'production' : 'development']
])
} finally {
next()
}
}
exports.down = function (next) {
next()
}

View file

@ -6,6 +6,8 @@ const setEnvVariable = require('./set-env-var')
fs.copyFileSync(path.resolve(__dirname, '../.sample.env'), path.resolve(__dirname, '../.env'))
setEnvVariable('NODE_ENV', 'development')
setEnvVariable('LAMASSU_DB', 'DEV')
setEnvVariable('POSTGRES_USER', 'postgres')
setEnvVariable('POSTGRES_PASSWORD', 'postgres123')
@ -20,6 +22,7 @@ setEnvVariable('KEY_PATH', `${process.env.PWD}/certs/Lamassu_OP.key`)
setEnvVariable('MNEMONIC_PATH', `${process.env.HOME}/.lamassu/mnemonics/mnemonic.txt`)
setEnvVariable('MIGRATE_STATE_PATH', `${process.env.HOME}/.lamassu/.migrate`)
setEnvVariable('BLOCKCHAIN_DIR', `${process.env.PWD}/blockchains`)
setEnvVariable('OFAC_DATA_DIR', `${process.env.HOME}/.lamassu/ofac`)
setEnvVariable('ID_PHOTO_CARD_DIR', `${process.env.HOME}/.lamassu/idphotocard`)
setEnvVariable('FRONT_CAMERA_DIR', `${process.env.HOME}/.lamassu/frontcamera`)
@ -28,5 +31,8 @@ setEnvVariable('OPERATOR_DATA_DIR', `${process.env.HOME}/.lamassu/operatordata`)
setEnvVariable('OFAC_SOURCES_NAMES', 'sdn_advanced,cons_advanced')
setEnvVariable('OFAC_SOURCES_URLS', 'https://www.treasury.gov/ofac/downloads/sanctions/1.0/sdn_advanced.xml,https://www.treasury.gov/ofac/downloads/sanctions/1.0/cons_advanced.xml')
setEnvVariable('BTC_NODE_LOCATION', 'remote')
setEnvVariable('BTC_WALLET_LOCATION', 'local')
setEnvVariable('HOSTNAME', 'localhost')
setEnvVariable('LOG_LEVEL', 'debug')

View file

@ -14,6 +14,8 @@ if (!_.isEqual(_.intersection(_.keys(argv), requiredParams), requiredParams)) {
fs.copyFileSync(path.resolve(__dirname, '../.sample.env'), path.resolve('/etc', 'lamassu', '.env'))
setEnvVariable('NODE_ENV', 'production')
setEnvVariable('POSTGRES_USER', 'lamassu_pg')
setEnvVariable('POSTGRES_PASSWORD', `${argv['db-password']}`)
setEnvVariable('POSTGRES_HOST', 'localhost')
@ -40,5 +42,8 @@ setEnvVariable('COIN_ATM_RADAR_URL', `https://coinatmradar.info/api/lamassu/`)
setEnvVariable('OFAC_SOURCES_NAMES', 'sdn_advanced,cons_advanced')
setEnvVariable('OFAC_SOURCES_URLS', 'https://www.treasury.gov/ofac/downloads/sanctions/1.0/sdn_advanced.xml,https://www.treasury.gov/ofac/downloads/sanctions/1.0/cons_advanced.xml')
setEnvVariable('BTC_NODE_LOCATION', 'local')
setEnvVariable('BTC_WALLET_LOCATION', 'local')
setEnvVariable('HOSTNAME', `${argv.hostname}`)
setEnvVariable('LOG_LEVEL', 'info')

32
tools/migrate-env.js Normal file
View file

@ -0,0 +1,32 @@
const fs = require('fs')
const path = require('path')
const _ = require('lodash/fp')
const setEnvVariable = require('./set-env-var')
const ENV_PATH = process.env.NODE_ENV === 'production' ? path.resolve('/etc', 'lamassu', '.env') : path.resolve(__dirname, '../.env')
const BACKUP_TIMESTAMP = Date.now()
const BACKUP_PATH = `${ENV_PATH}-${BACKUP_TIMESTAMP}`
const migrateEnv = newVars => {
try {
fs.copyFileSync(ENV_PATH, BACKUP_PATH)
_.forEach(it => {
setEnvVariable(it[0], it[1], { ENV_PATH })
}, newVars)
fs.unlinkSync(BACKUP_PATH)
console.log('Environment migration successful')
} catch (e) {
// Rollback the migration and restore the backup file
if (fs.existsSync(BACKUP_PATH)) {
console.log('Rolling back the environment migration...')
fs.copyFileSync(BACKUP_PATH, ENV_PATH)
fs.unlinkSync(BACKUP_PATH)
console.log('Rollback finished')
}
console.log('Environment migration failed')
throw e
}
}
module.exports = migrateEnv

View file

@ -1,9 +1,10 @@
const fs = require('fs')
const os = require('os')
const path = require('path')
const _ = require('lodash/fp')
const setEnvVariable = (key, value) => {
const ENV_PATH = path.resolve(__dirname, '../.env')
const setEnvVariable = (key, value, opts) => {
const ENV_PATH = !_.isNil(opts.ENV_PATH) ? opts.ENV_PATH : process.env.NODE_ENV === 'production' ? path.resolve('/etc', 'lamassu', '.env') : path.resolve(__dirname, '../.env')
const ENV_VARIABLES = fs.readFileSync(ENV_PATH, 'utf-8').split(os.EOL)
const target = ENV_VARIABLES.indexOf(ENV_VARIABLES.find(line => line.match(new RegExp(`^${key}=`))))