budibase/packages/cli/src/hosting/index.js

192 lines
5.1 KiB
JavaScript

const Command = require("../structures/Command")
const { CommandWords, InitTypes, AnalyticsEvents } = require("../constants")
const { lookpath } = require("lookpath")
const {
downloadFile,
logErrorToFile,
success,
info,
parseEnv,
} = require("../utils")
const { confirmation } = require("../questions")
const fs = require("fs")
const compose = require("docker-compose")
const makeEnv = require("./makeEnv")
const axios = require("axios")
const AnalyticsClient = require("../analytics/Client")
const BUDIBASE_SERVICES = ["app-service", "worker-service", "proxy-service"]
const ERROR_FILE = "docker-error.log"
const FILE_URLS = [
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml",
]
const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"
const client = new AnalyticsClient()
async function downloadFiles() {
const promises = []
for (let url of FILE_URLS) {
const fileName = url.split("/").slice(-1)[0]
promises.push(downloadFile(url, `./${fileName}`))
}
await Promise.all(promises)
}
async function checkDockerConfigured() {
const error =
"docker/docker-compose has not been installed, please follow instructions at: https://docs.budibase.com/docs/docker-compose"
const docker = await lookpath("docker")
const compose = await lookpath("docker-compose")
if (!docker || !compose) {
throw error
}
}
function checkInitComplete() {
if (!fs.existsSync(makeEnv.filePath)) {
throw "Please run the hosting --init command before any other hosting command."
}
}
async function handleError(func) {
try {
await func()
} catch (err) {
if (err && err.err) {
logErrorToFile(ERROR_FILE, err.err)
}
throw `Failed to start - logs written to file: ${ERROR_FILE}`
}
}
async function init(type) {
const isQuick = type === InitTypes.QUICK || type === InitTypes.DIGITAL_OCEAN
await checkDockerConfigured()
if (!isQuick) {
const shouldContinue = await confirmation(
"This will create multiple files in current directory, should continue?"
)
if (!shouldContinue) {
console.log("Stopping.")
return
}
}
client.capture({
distinctId: "cli",
event: AnalyticsEvents.SelfHostInit,
properties: {
type,
},
})
await downloadFiles()
const config = isQuick ? makeEnv.QUICK_CONFIG : {}
if (type === InitTypes.DIGITAL_OCEAN) {
try {
const output = await axios.get(DO_USER_DATA_URL)
const response = parseEnv(output.data)
for (let [key, value] of Object.entries(makeEnv.ConfigMap)) {
if (response[key]) {
config[value] = response[key]
}
}
} catch (err) {
// don't need to handle error, just don't do anything
}
}
await makeEnv.make(config)
}
async function start() {
await checkDockerConfigured()
checkInitComplete()
console.log(
info(
"Starting services, this may take a moment - first time this may take a few minutes to download images."
)
)
const port = makeEnv.get("MAIN_PORT")
await handleError(async () => {
// need to log as it makes it more clear
await compose.upAll({ cwd: "./", log: true })
})
console.log(
success(
`Services started, please go to http://localhost:${port} for next steps.`
)
)
}
async function status() {
await checkDockerConfigured()
checkInitComplete()
console.log(info("Budibase status"))
await handleError(async () => {
const response = await compose.ps()
console.log(response.out)
})
}
async function stop() {
await checkDockerConfigured()
checkInitComplete()
console.log(info("Stopping services, this may take a moment."))
await handleError(async () => {
await compose.stop()
})
console.log(success("Services have been stopped successfully."))
}
async function update() {
await checkDockerConfigured()
checkInitComplete()
if (await confirmation("Do you wish to update you docker-compose.yaml?")) {
await downloadFiles()
}
await handleError(async () => {
const status = await compose.ps()
const parts = status.out.split("\n")
const isUp = parts[2] && parts[2].indexOf("Up") !== -1
if (isUp) {
console.log(info("Stopping services, this may take a moment."))
await compose.stop()
}
console.log(info("Beginning update, this may take a few minutes."))
await compose.pullMany(BUDIBASE_SERVICES, { log: true })
if (isUp) {
console.log(success("Update complete, restarting services..."))
await start()
}
})
}
const command = new Command(`${CommandWords.HOSTING}`)
.addHelp("Controls self hosting on the Budibase platform.")
.addSubOption(
"--init [type]",
"Configure a self hosted platform in current directory, type can be unspecified or 'quick'.",
init
)
.addSubOption(
"--start",
"Start the configured platform in current directory.",
start
)
.addSubOption(
"--status",
"Check the status of currently running services.",
status
)
.addSubOption(
"--stop",
"Stop the configured platform in the current directory.",
stop
)
.addSubOption(
"--update",
"Update the Budibase images to the latest version.",
update
)
exports.command = command