2021-02-26 12:46:48 +01:00
|
|
|
const Command = require("../structures/Command")
|
2021-03-30 11:50:42 +02:00
|
|
|
const { CommandWords, InitTypes, AnalyticsEvents } = require("../constants")
|
2021-02-26 14:30:24 +01:00
|
|
|
const { lookpath } = require("lookpath")
|
2022-09-27 20:39:17 +02:00
|
|
|
const { resolve } = require("path")
|
2021-03-19 11:29:43 +01:00
|
|
|
const {
|
|
|
|
downloadFile,
|
|
|
|
logErrorToFile,
|
|
|
|
success,
|
|
|
|
info,
|
2022-09-27 20:39:17 +02:00
|
|
|
error,
|
2021-03-19 11:29:43 +01:00
|
|
|
parseEnv,
|
|
|
|
} = require("../utils")
|
2021-02-26 14:30:24 +01:00
|
|
|
const { confirmation } = require("../questions")
|
2021-02-26 16:09:25 +01:00
|
|
|
const fs = require("fs")
|
|
|
|
const compose = require("docker-compose")
|
2021-03-19 11:29:43 +01:00
|
|
|
const makeEnv = require("./makeEnv")
|
2021-03-19 11:02:29 +01:00
|
|
|
const axios = require("axios")
|
2022-09-14 18:35:46 +02:00
|
|
|
const { captureEvent } = require("../events")
|
2022-09-27 20:39:17 +02:00
|
|
|
const yaml = require("yaml")
|
2021-02-26 14:30:24 +01:00
|
|
|
|
2022-02-11 13:32:57 +01:00
|
|
|
const BUDIBASE_SERVICES = ["app-service", "worker-service", "proxy-service"]
|
2021-02-26 16:09:25 +01:00
|
|
|
const ERROR_FILE = "docker-error.log"
|
2021-02-26 14:30:24 +01:00
|
|
|
const FILE_URLS = [
|
|
|
|
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml",
|
|
|
|
]
|
2021-03-19 11:02:29 +01:00
|
|
|
const DO_USER_DATA_URL = "http://169.254.169.254/metadata/v1/user-data"
|
2021-02-26 14:30:24 +01:00
|
|
|
|
2021-02-26 18:08:28 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-02-26 14:30:24 +01:00
|
|
|
async function checkDockerConfigured() {
|
2021-02-26 18:09:20 +01:00
|
|
|
const error =
|
2022-03-23 14:47:27 +01:00
|
|
|
"docker/docker-compose has not been installed, please follow instructions at: https://docs.budibase.com/docs/docker-compose"
|
2021-02-26 14:30:24 +01:00
|
|
|
const docker = await lookpath("docker")
|
|
|
|
const compose = await lookpath("docker-compose")
|
|
|
|
if (!docker || !compose) {
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
}
|
2021-02-24 18:32:45 +01:00
|
|
|
|
2021-02-26 16:09:25 +01:00
|
|
|
function checkInitComplete() {
|
2021-03-19 11:29:43 +01:00
|
|
|
if (!fs.existsSync(makeEnv.filePath)) {
|
2021-02-26 16:09:25 +01:00
|
|
|
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}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-19 11:02:29 +01:00
|
|
|
async function init(type) {
|
|
|
|
const isQuick = type === InitTypes.QUICK || type === InitTypes.DIGITAL_OCEAN
|
2021-02-26 14:30:24 +01:00
|
|
|
await checkDockerConfigured()
|
2021-03-18 19:26:41 +01:00
|
|
|
if (!isQuick) {
|
|
|
|
const shouldContinue = await confirmation(
|
|
|
|
"This will create multiple files in current directory, should continue?"
|
|
|
|
)
|
|
|
|
if (!shouldContinue) {
|
|
|
|
console.log("Stopping.")
|
|
|
|
return
|
|
|
|
}
|
2021-02-26 14:30:24 +01:00
|
|
|
}
|
2022-09-14 18:35:46 +02:00
|
|
|
captureEvent(AnalyticsEvents.SelfHostInit, {
|
|
|
|
type,
|
2021-03-30 11:50:42 +02:00
|
|
|
})
|
2021-02-26 18:08:28 +01:00
|
|
|
await downloadFiles()
|
2021-03-19 11:29:43 +01:00
|
|
|
const config = isQuick ? makeEnv.QUICK_CONFIG : {}
|
2021-03-19 11:02:29 +01:00
|
|
|
if (type === InitTypes.DIGITAL_OCEAN) {
|
|
|
|
try {
|
2021-03-19 11:29:43 +01:00
|
|
|
const output = await axios.get(DO_USER_DATA_URL)
|
|
|
|
const response = parseEnv(output.data)
|
|
|
|
for (let [key, value] of Object.entries(makeEnv.ConfigMap)) {
|
2021-03-19 11:02:29 +01:00
|
|
|
if (response[key]) {
|
|
|
|
config[value] = response[key]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
// don't need to handle error, just don't do anything
|
|
|
|
}
|
|
|
|
}
|
2021-03-19 11:29:43 +01:00
|
|
|
await makeEnv.make(config)
|
2021-02-26 12:46:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async function start() {
|
2021-02-26 16:09:25 +01:00
|
|
|
await checkDockerConfigured()
|
|
|
|
checkInitComplete()
|
2021-05-24 17:20:28 +02:00
|
|
|
console.log(
|
|
|
|
info(
|
|
|
|
"Starting services, this may take a moment - first time this may take a few minutes to download images."
|
|
|
|
)
|
|
|
|
)
|
2021-03-19 11:29:43 +01:00
|
|
|
const port = makeEnv.get("MAIN_PORT")
|
2021-02-26 16:09:25 +01:00
|
|
|
await handleError(async () => {
|
2021-05-24 15:58:54 +02:00
|
|
|
// need to log as it makes it more clear
|
|
|
|
await compose.upAll({ cwd: "./", log: true })
|
2021-02-26 16:09:25 +01:00
|
|
|
})
|
2021-02-26 18:09:20 +01:00
|
|
|
console.log(
|
|
|
|
success(
|
|
|
|
`Services started, please go to http://localhost:${port} for next steps.`
|
|
|
|
)
|
|
|
|
)
|
2021-02-26 16:09:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async function status() {
|
|
|
|
await checkDockerConfigured()
|
|
|
|
checkInitComplete()
|
2021-02-26 18:08:28 +01:00
|
|
|
console.log(info("Budibase status"))
|
2021-02-26 16:09:25 +01:00
|
|
|
await handleError(async () => {
|
|
|
|
const response = await compose.ps()
|
|
|
|
console.log(response.out)
|
|
|
|
})
|
2021-02-26 12:46:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async function stop() {
|
2021-02-26 16:09:25 +01:00
|
|
|
await checkDockerConfigured()
|
|
|
|
checkInitComplete()
|
2021-02-26 18:08:28 +01:00
|
|
|
console.log(info("Stopping services, this may take a moment."))
|
2021-02-26 16:09:25 +01:00
|
|
|
await handleError(async () => {
|
|
|
|
await compose.stop()
|
|
|
|
})
|
2021-02-26 18:08:28 +01:00
|
|
|
console.log(success("Services have been stopped successfully."))
|
2021-02-26 12:46:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async function update() {
|
2021-02-26 16:09:25 +01:00
|
|
|
await checkDockerConfigured()
|
|
|
|
checkInitComplete()
|
2022-02-01 11:02:37 +01:00
|
|
|
if (await confirmation("Do you wish to update you docker-compose.yaml?")) {
|
2021-02-26 18:08:28 +01:00
|
|
|
await downloadFiles()
|
|
|
|
}
|
2021-02-26 16:09:25 +01:00
|
|
|
await handleError(async () => {
|
|
|
|
const status = await compose.ps()
|
|
|
|
const parts = status.out.split("\n")
|
|
|
|
const isUp = parts[2] && parts[2].indexOf("Up") !== -1
|
2021-02-26 18:08:28 +01:00
|
|
|
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."))
|
2021-02-26 18:09:20 +01:00
|
|
|
await compose.pullMany(BUDIBASE_SERVICES, { log: true })
|
2021-02-26 16:09:25 +01:00
|
|
|
if (isUp) {
|
2021-02-26 18:08:28 +01:00
|
|
|
console.log(success("Update complete, restarting services..."))
|
2021-02-26 16:09:25 +01:00
|
|
|
await start()
|
|
|
|
}
|
|
|
|
})
|
2021-02-26 12:46:48 +01:00
|
|
|
}
|
|
|
|
|
2022-09-27 20:39:17 +02:00
|
|
|
async function watchPlugins(pluginPath) {
|
|
|
|
const PLUGIN_PATH = "/plugins"
|
|
|
|
// get absolute path
|
|
|
|
pluginPath = resolve(pluginPath)
|
|
|
|
if (!fs.existsSync(pluginPath)) {
|
|
|
|
console.log(
|
|
|
|
error(
|
|
|
|
`The directory "${pluginPath}" does not exist, please create and then try again.`
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const opts = ["docker-compose.yaml", "docker-compose.yml"]
|
|
|
|
let dockerFilePath = opts.find(name => fs.existsSync(name))
|
|
|
|
if (!dockerFilePath) {
|
|
|
|
console.log(error("Unable to locate docker-compose YAML."))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const dockerYaml = fs.readFileSync(dockerFilePath, "utf8")
|
|
|
|
const parsedYaml = yaml.parse(dockerYaml)
|
|
|
|
let service
|
|
|
|
if (parsedYaml["services"]["app-service"]) {
|
|
|
|
service = parsedYaml["services"]["app-service"]
|
|
|
|
}
|
|
|
|
if (!service) {
|
|
|
|
console.log(
|
|
|
|
error(
|
|
|
|
"Unable to locate service within compose file, is it a valid Budibase configuration?"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// set environment variable
|
|
|
|
service.environment["PLUGINS_DIR"] = PLUGIN_PATH
|
|
|
|
// add volumes to parsed yaml
|
|
|
|
if (!service.volumes) {
|
|
|
|
service.volumes = []
|
|
|
|
}
|
|
|
|
const found = service.volumes.find(vol => vol.includes(PLUGIN_PATH))
|
|
|
|
if (found) {
|
|
|
|
service.volumes.splice(service.volumes.indexOf(found), 1)
|
|
|
|
}
|
|
|
|
service.volumes.push(`${pluginPath}:${PLUGIN_PATH}`)
|
|
|
|
fs.writeFileSync(dockerFilePath, yaml.stringify(parsedYaml))
|
|
|
|
console.log(success("Docker compose configuration has been updated!"))
|
|
|
|
}
|
|
|
|
|
2021-02-26 12:46:48 +01:00
|
|
|
const command = new Command(`${CommandWords.HOSTING}`)
|
2021-02-25 15:42:50 +01:00
|
|
|
.addHelp("Controls self hosting on the Budibase platform.")
|
2021-02-26 12:46:48 +01:00
|
|
|
.addSubOption(
|
2021-03-18 19:26:41 +01:00
|
|
|
"--init [type]",
|
2022-09-27 20:39:17 +02:00
|
|
|
"Configure a self hosted platform in current directory, type can be unspecified, 'quick' or 'single'.",
|
2021-02-26 12:46:48 +01:00
|
|
|
init
|
|
|
|
)
|
|
|
|
.addSubOption(
|
|
|
|
"--start",
|
|
|
|
"Start the configured platform in current directory.",
|
|
|
|
start
|
|
|
|
)
|
2021-02-26 16:09:25 +01:00
|
|
|
.addSubOption(
|
|
|
|
"--status",
|
|
|
|
"Check the status of currently running services.",
|
|
|
|
status
|
|
|
|
)
|
2021-02-26 12:46:48 +01:00
|
|
|
.addSubOption(
|
|
|
|
"--stop",
|
|
|
|
"Stop the configured platform in the current directory.",
|
|
|
|
stop
|
|
|
|
)
|
|
|
|
.addSubOption(
|
|
|
|
"--update",
|
2021-03-01 19:04:30 +01:00
|
|
|
"Update the Budibase images to the latest version.",
|
2021-02-26 12:46:48 +01:00
|
|
|
update
|
|
|
|
)
|
2022-09-27 20:39:17 +02:00
|
|
|
.addSubOption(
|
|
|
|
"--watch-plugin-dir [directory]",
|
|
|
|
"Add plugin directory watching to a Budibase install.",
|
|
|
|
watchPlugins
|
|
|
|
)
|
|
|
|
.addSubOption("--dev", "")
|
2021-02-25 15:42:50 +01:00
|
|
|
|
2021-02-26 12:46:48 +01:00
|
|
|
exports.command = command
|