mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
12 changed files with 2054 additions and 0 deletions
@ -0,0 +1,12 @@ |
|||
{ |
|||
"globals": { |
|||
"emit": true, |
|||
"key": true |
|||
}, |
|||
"env": { |
|||
"node": true |
|||
}, |
|||
"extends": ["eslint:recommended"], |
|||
"rules": { |
|||
} |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
node_modules/ |
|||
docker-compose.yaml |
|||
envoy.yaml |
|||
hosting.properties |
|||
build/ |
|||
docker-error.log |
|||
@ -0,0 +1,25 @@ |
|||
{ |
|||
"name": "cli", |
|||
"version": "0.7.8", |
|||
"description": "Budibase CLI, for developers, self hosting and migrations.", |
|||
"main": "src/index.js", |
|||
"bin": "src/index.js", |
|||
"author": "Budibase", |
|||
"license": "AGPL-3.0-or-later", |
|||
"scripts": { |
|||
"build": "pkg . --out-path build" |
|||
}, |
|||
"dependencies": { |
|||
"axios": "^0.21.1", |
|||
"chalk": "^4.1.0", |
|||
"commander": "^7.1.0", |
|||
"docker-compose": "^0.23.6", |
|||
"inquirer": "^8.0.0", |
|||
"lookpath": "^1.1.0", |
|||
"pkg": "^4.4.9", |
|||
"randomstring": "^1.1.5" |
|||
}, |
|||
"devDependencies": { |
|||
"eslint": "^7.20.0" |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
exports.CommandWords = { |
|||
HOSTING: "hosting", |
|||
HELP: "help", |
|||
} |
|||
@ -0,0 +1,156 @@ |
|||
const Command = require("../structures/Command") |
|||
const { CommandWords } = require("../constants") |
|||
const { lookpath } = require("lookpath") |
|||
const { downloadFile, logErrorToFile, success, info } = require("../utils") |
|||
const { confirmation } = require("../questions") |
|||
const fs = require("fs") |
|||
const compose = require("docker-compose") |
|||
const envFile = require("./makeEnv") |
|||
|
|||
const BUDIBASE_SERVICES = ["app-service", "worker-service"] |
|||
const ERROR_FILE = "docker-error.log" |
|||
const FILE_URLS = [ |
|||
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml", |
|||
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/envoy.yaml", |
|||
] |
|||
|
|||
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/self-hosting/hosting-methods/docker-compose#installing-docker" |
|||
const docker = await lookpath("docker") |
|||
const compose = await lookpath("docker-compose") |
|||
if (!docker || !compose) { |
|||
throw error |
|||
} |
|||
} |
|||
|
|||
function checkInitComplete() { |
|||
if (!fs.existsSync(envFile.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() { |
|||
await checkDockerConfigured() |
|||
const shouldContinue = await confirmation( |
|||
"This will create multiple files in current directory, should continue?" |
|||
) |
|||
if (!shouldContinue) { |
|||
console.log("Stopping.") |
|||
return |
|||
} |
|||
await downloadFiles() |
|||
await envFile.make() |
|||
} |
|||
|
|||
async function start() { |
|||
await checkDockerConfigured() |
|||
checkInitComplete() |
|||
console.log(info("Starting services, this may take a moment.")) |
|||
const port = envFile.get("MAIN_PORT") |
|||
await handleError(async () => { |
|||
await compose.upAll({ cwd: "./", log: false }) |
|||
}) |
|||
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 and envoy.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", |
|||
"Configure a self hosted platform in current directory.", |
|||
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 |
|||
@ -0,0 +1,59 @@ |
|||
const { string, number } = require("../questions") |
|||
const { success } = require("../utils") |
|||
const fs = require("fs") |
|||
const path = require("path") |
|||
const randomString = require("randomstring") |
|||
|
|||
const FILE_PATH = path.resolve("./.env") |
|||
|
|||
function getContents(port, hostingKey) { |
|||
return ` |
|||
# Use the main port in the builder for your self hosting URL, e.g. localhost:10000 |
|||
MAIN_PORT=${port} |
|||
|
|||
# Use this password when configuring your self hosting settings |
|||
HOSTING_KEY=${hostingKey} |
|||
|
|||
# This section contains all secrets pertaining to the system |
|||
JWT_SECRET=${randomString.generate()} |
|||
MINIO_ACCESS_KEY=${randomString.generate()} |
|||
MINIO_SECRET_KEY=${randomString.generate()} |
|||
COUCH_DB_PASSWORD=${randomString.generate()} |
|||
COUCH_DB_USER=${randomString.generate()} |
|||
|
|||
# This section contains variables that do not need to be altered under normal circumstances |
|||
APP_PORT=4002 |
|||
WORKER_PORT=4003 |
|||
MINIO_PORT=4004 |
|||
COUCH_DB_PORT=4005 |
|||
BUDIBASE_ENVIRONMENT=PRODUCTION` |
|||
} |
|||
|
|||
module.exports.filePath = FILE_PATH |
|||
|
|||
module.exports.make = async () => { |
|||
const hostingKey = await string( |
|||
"Please input the password you'd like to use as your hosting key: " |
|||
) |
|||
const hostingPort = await number( |
|||
"Please enter the port on which you want your installation to run: ", |
|||
10000 |
|||
) |
|||
const fileContents = getContents(hostingPort, hostingKey) |
|||
fs.writeFileSync(FILE_PATH, fileContents) |
|||
console.log( |
|||
success( |
|||
"Configuration has been written successfully - please check .env file for more details." |
|||
) |
|||
) |
|||
} |
|||
|
|||
module.exports.get = property => { |
|||
const props = fs.readFileSync(FILE_PATH, "utf8").split(property) |
|||
if (props[0].charAt(0) === "=") { |
|||
property = props[0] |
|||
} else { |
|||
property = props[1] |
|||
} |
|||
return property.split("=")[1].split("\n")[0] |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
const { getCommands } = require("./options") |
|||
const { Command } = require("commander") |
|||
const { getHelpDescription } = require("./utils") |
|||
|
|||
// add hosting config
|
|||
async function init() { |
|||
const program = new Command() |
|||
.addHelpCommand("help", getHelpDescription("Help with Budibase commands.")) |
|||
.helpOption(false) |
|||
program.helpOption() |
|||
// add commands
|
|||
for (let command of getCommands()) { |
|||
command.configure(program) |
|||
} |
|||
// this will stop the program if no command found
|
|||
await program.parseAsync(process.argv) |
|||
} |
|||
|
|||
init().catch(err => { |
|||
console.error(`Unexpected error - `, err) |
|||
}) |
|||
@ -0,0 +1,5 @@ |
|||
const hosting = require("./hosting") |
|||
|
|||
exports.getCommands = () => { |
|||
return [hosting.command] |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
const inquirer = require("inquirer") |
|||
|
|||
exports.confirmation = async question => { |
|||
const config = { |
|||
type: "confirm", |
|||
message: question, |
|||
default: true, |
|||
name: "confirmation", |
|||
} |
|||
return (await inquirer.prompt(config)).confirmation |
|||
} |
|||
|
|||
exports.string = async (question, defaultString = null) => { |
|||
const config = { |
|||
type: "input", |
|||
name: "string", |
|||
message: question, |
|||
} |
|||
if (defaultString) { |
|||
config.default = defaultString |
|||
} |
|||
return (await inquirer.prompt(config)).string |
|||
} |
|||
|
|||
exports.number = async (question, defaultNumber) => { |
|||
const config = { |
|||
type: "input", |
|||
name: "number", |
|||
message: question, |
|||
validate: value => { |
|||
let valid = !isNaN(parseFloat(value)) |
|||
return valid || "Please enter a number" |
|||
}, |
|||
filter: Number, |
|||
} |
|||
if (defaultNumber) { |
|||
config.default = defaultNumber |
|||
} |
|||
return (await inquirer.prompt(config)).number |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
const { getSubHelpDescription, getHelpDescription, error } = require("../utils") |
|||
|
|||
class Command { |
|||
constructor(command, func = null) { |
|||
// if there are options, need to just get the command name
|
|||
this.command = command |
|||
this.opts = [] |
|||
this.func = func |
|||
} |
|||
|
|||
addHelp(help) { |
|||
this.help = help |
|||
return this |
|||
} |
|||
|
|||
addSubOption(command, help, func) { |
|||
this.opts.push({ command, help, func }) |
|||
return this |
|||
} |
|||
|
|||
configure(program) { |
|||
const thisCmd = this |
|||
let command = program.command(thisCmd.command) |
|||
if (this.help) { |
|||
command = command.description(getHelpDescription(thisCmd.help)) |
|||
} |
|||
for (let opt of thisCmd.opts) { |
|||
command = command.option( |
|||
`${opt.command}`, |
|||
getSubHelpDescription(opt.help) |
|||
) |
|||
} |
|||
command.helpOption( |
|||
"--help", |
|||
getSubHelpDescription(`Get help with ${this.command} options`) |
|||
) |
|||
command.action(async options => { |
|||
try { |
|||
let executed = false |
|||
if (thisCmd.func) { |
|||
await thisCmd.func(options) |
|||
executed = true |
|||
} |
|||
for (let opt of thisCmd.opts) { |
|||
if (options[opt.command.replace("--", "")]) { |
|||
await opt.func(options) |
|||
executed = true |
|||
} |
|||
} |
|||
if (!executed) { |
|||
console.log(error(`Unknown ${this.command} option.`)) |
|||
command.help() |
|||
} |
|||
} catch (err) { |
|||
console.log(error(err)) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
module.exports = Command |
|||
@ -0,0 +1,46 @@ |
|||
const chalk = require("chalk") |
|||
const fs = require("fs") |
|||
const axios = require("axios") |
|||
const path = require("path") |
|||
|
|||
exports.downloadFile = async (url, filePath) => { |
|||
filePath = path.resolve(filePath) |
|||
const writer = fs.createWriteStream(filePath) |
|||
|
|||
const response = await axios({ |
|||
url, |
|||
method: "GET", |
|||
responseType: "stream", |
|||
}) |
|||
|
|||
response.data.pipe(writer) |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
writer.on("finish", resolve) |
|||
writer.on("error", reject) |
|||
}) |
|||
} |
|||
|
|||
exports.getHelpDescription = string => { |
|||
return chalk.cyan(string) |
|||
} |
|||
|
|||
exports.getSubHelpDescription = string => { |
|||
return chalk.green(string) |
|||
} |
|||
|
|||
exports.error = error => { |
|||
return chalk.red(`Error - ${error}`) |
|||
} |
|||
|
|||
exports.success = success => { |
|||
return chalk.green(success) |
|||
} |
|||
|
|||
exports.info = info => { |
|||
return chalk.cyan(info) |
|||
} |
|||
|
|||
exports.logErrorToFile = (file, error) => { |
|||
fs.writeFileSync(path.resolve(`./${file}`), `Budiase Error\n${error}`) |
|||
} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue