Colubrina/cli/index.js

351 lines
10 KiB
JavaScript
Raw Normal View History

2022-07-29 19:04:37 +10:00
const input = require("input")
const fs = require("fs")
const path = require("path")
const { Umzug, SequelizeStorage } = require("umzug")
const { Sequelize } = require("sequelize")
const argon2 = require("argon2")
const axios = require("axios")
const os = require("os")
2022-07-30 17:45:29 +10:00
const { execSync } = require("child_process")
2022-07-29 19:04:37 +10:00
console.log("Troplo/Colubrina CLI")
2022-08-07 00:35:00 +10:00
if (fs.existsSync("../backend/config/config.json")) {
console.log(
"Want to modify either the Colubrina, or database config? Check out the config files in backend/config."
)
}
2022-07-29 20:34:27 +10:00
console.log("Colubrina version", require("../frontend/package.json").version)
2022-07-29 19:04:37 +10:00
async function checkForUpdates() {
2022-07-29 22:53:31 +10:00
if (!process.argv.includes("--skip-update")) {
await axios
.get("https://services.troplo.com/api/v1/state", {
headers: {
2022-07-30 17:45:29 +10:00
"X-Troplo-Project": "colubrina",
"X-Troplo-Project-Version": require("../frontend/package.json")
.version
2022-07-29 22:53:31 +10:00
},
timeout: 1000
})
.then((res) => {
2022-07-30 17:45:29 +10:00
if (res.data.warning) {
console.log(res.data.warning)
}
if (
require("../frontend/package.json").version !== res.data.latestVersion
) {
2022-07-29 22:53:31 +10:00
console.log("A new version of Colubrina is available!")
console.log("Latest version:", res.data.latestVersion)
} else {
console.log("Colubrina is up to date.")
}
})
.catch((e) => {
console.log(e)
console.log(
"Failed to check for updates, ensure you are connected to the internet, and services.troplo.com is whitelisted behind any potential firewalls."
)
})
} else {
console.log("Skipping update check")
}
2022-07-29 19:04:37 +10:00
}
let state = {
db: {
host: "localhost",
port: 3306,
username: "colubrina",
password: null,
database: "colubrina",
2022-07-29 20:34:27 +10:00
storage: "../backend/storage.db",
dialect: "mariadb"
2022-07-29 19:04:37 +10:00
},
dbConfig: {}
}
async function doSetupDB() {
const dialect = await input.select(
"What database dialect do you want to use? (MariaDB tested, recommended)",
["mariadb", "postgres", "sqlite"]
)
const host = await input.text("What is the host?", {
default: state.db.host || "localhost"
})
const port = await input.text("What is the port?", {
default: state.db.port || 3306
})
const username = await input.text("What is the username?", {
default: state.db.username || "colubrina"
})
const password = await input.text("What is the password?", {
default: state.db.password ? "Enter for cached password" : "Please specify"
})
const database = await input.text("What is the database name?", {
default: state.db.database || "colubrina"
})
let storage
if (dialect === "sqlite") {
storage = await input.text(
"What is the path to the storage file (SQLite only)?",
{
default: state.db.storage || "./storage.db"
}
)
}
state.db = {
username: username,
password: password,
database: database,
host: host,
dialect: dialect,
port: port,
logging: false
}
state.dbConfig = {
development: {
username: username,
password: password,
database: database,
host: host,
dialect: dialect,
port: port,
storage: dialect === "sqlite" ? storage : null,
logging: false
},
test: {
username: username,
password: password,
database: database,
host: host,
dialect: dialect,
port: port,
logging: false
},
production: {
username: username,
password: password,
database: database,
host: host,
dialect: dialect,
port: port,
logging: false
}
}
await testDB()
}
async function testDB() {
try {
const sequelize = new Sequelize(state.db)
await sequelize.authenticate()
console.log("Connection to database has been established successfully.")
} catch (error) {
console.error("Unable to connect to the database:", error)
await doSetupDB()
}
}
async function dbSetup() {
await doSetupDB()
fs.writeFileSync(
2022-08-07 00:35:00 +10:00
path.join(__dirname, "../backend/config/database.json"),
JSON.stringify(state.dbConfig, null, 2)
2022-07-29 19:04:37 +10:00
)
2022-08-07 00:35:00 +10:00
console.log("config/database.json overwritten")
2022-07-29 19:04:37 +10:00
}
async function runMigrations() {
console.log("Running migrations")
2022-07-30 17:45:29 +10:00
execSync("cd ../backend && sequelize db:migrate", () => {
2022-07-29 23:34:35 +10:00
console.log("Migrations applied")
2022-07-29 19:04:37 +10:00
})
}
async function createUser() {
const user = {
username: await input.text("Username", {
default: "admin"
}),
password: await argon2.hash(await input.text("Password", {})),
email: await input.text("Email", {
default: "troplo@troplo.com"
}),
2022-07-31 14:56:43 +10:00
admin: await input.confirm("Admin (true/false)", {
default: false
}),
emailVerified: await input.confirm("Email verified (true/false)", {
default: true
})
2022-07-29 19:04:37 +10:00
}
2022-07-29 23:35:41 +10:00
const { User } = require("../backend/models")
2022-07-29 19:04:37 +10:00
await User.create(user)
console.log("User created")
}
async function configureDotEnv() {
2022-08-07 00:35:00 +10:00
if (!fs.existsSync("../backend/config/config.json")) {
fs.writeFileSync("../backend/config/config.json", "{}")
2022-07-29 19:04:37 +10:00
}
2022-08-07 00:35:00 +10:00
let config = require("../backend/config/config.json")
config.hostname = await input.text("Public Domain", {
default: "localhost"
})
config.corsHostname = await input.text("CORS Hostname", {
default: "http://localhost"
})
config.siteName = await input.text("Site Name", {
default: "Colubrina"
})
config.allowRegistrations = await input.text("Allow Registrations", {
default: true
})
config.publicUsers = await input.text("Show instance users publicly?", {
default: true
})
config.emailVerify = await input.text("Enforce email verification?", {
default: false
})
if (config.emailVerify) {
config.emailSMTPHost = await input.text("SMTP Host", {
default: "mail.example.com"
2022-07-29 19:04:37 +10:00
})
2022-08-07 00:35:00 +10:00
config.emailSMTPPort = await input.text("SMTP Port", {
default: 587
2022-07-29 19:04:37 +10:00
})
2022-08-07 00:35:00 +10:00
config.emailSMTPUsername = await input.text("SMTP Username", {
default: "colubrina@example.com"
2022-07-29 19:04:37 +10:00
})
2022-08-07 00:35:00 +10:00
config.emailSMTPPassword = await input.text("SMTP Password", {})
config.emailSMTPFrom = await input.text("SMTP From Address", {
default: "colubrina@example.com"
2022-07-29 19:04:37 +10:00
})
2022-08-07 00:35:00 +10:00
config.emailSMTPSecure = await input.text("SMTP Secure", {
default: true
2022-07-30 17:45:29 +10:00
})
2022-07-31 14:56:43 +10:00
} else {
2022-08-07 00:35:00 +10:00
config.emailSMTPHost = "smtp.myhost.com"
config.emailSMTPPort = 587
config.emailSMTPUsername = "colubrina@example.com"
config.emailSMTPFrom = "colubrina@example.com"
config.emailSMTPPassword = ""
config.emailSMTPSecure = true
2022-07-31 14:56:43 +10:00
}
2022-08-07 00:35:00 +10:00
config.notification = ""
config.notificationType = "info"
config.release = "stable"
config.rules = "Write your instance rules here."
fs.writeFileSync(
"../backend/config/config.json",
JSON.stringify(config, null, 2)
)
2022-07-29 19:04:37 +10:00
}
async function init() {
while (true) {
const option = await input.select(`Please select an option`, [
2022-07-29 23:03:07 +10:00
"First-time setup",
2022-07-29 19:04:37 +10:00
"Create user",
"Run migrations",
2022-08-07 00:35:00 +10:00
"Update/create database config file",
2022-07-29 19:04:37 +10:00
"Check for updates",
2022-07-29 20:34:27 +10:00
"Build frontend for production",
2022-07-29 19:04:37 +10:00
"Exit"
])
2022-07-29 23:03:07 +10:00
if (option === "First-time setup") {
2022-07-29 20:34:27 +10:00
// run yarn install in ../backend
console.log("Running yarn install")
execSync("cd ../backend && yarn install --frozen-lockfile", () => {
console.log("yarn install complete (backend)")
})
2022-07-30 17:45:29 +10:00
execSync("cd ../frontend && yarn install --frozen-lockfile", () => {
console.log("yarn install complete (frontend)")
2022-07-29 20:34:27 +10:00
})
2022-08-07 00:35:00 +10:00
if (
fs.existsSync(path.join(__dirname, "../backend/config/config.json"))
) {
const option = await input.confirm(
"Colubrina config already exists, overwrite?",
{
default: false
}
)
2022-07-29 19:04:37 +10:00
if (option) {
await configureDotEnv()
}
} else {
await configureDotEnv()
}
2022-07-30 17:45:29 +10:00
if (
2022-08-07 00:35:00 +10:00
fs.existsSync(path.join(__dirname, "../backend/config/database.json"))
2022-07-30 17:45:29 +10:00
) {
2022-07-29 19:04:37 +10:00
const option = await input.select(
2022-08-07 00:35:00 +10:00
`config/database.json already exists. Do you want to overwrite it?`,
2022-07-29 19:04:37 +10:00
["Yes", "No"]
)
if (option === "Yes") {
await dbSetup()
}
} else {
await dbSetup()
}
await runMigrations()
2022-07-29 20:34:27 +10:00
const { User, Theme } = require("../backend/models")
2022-07-29 19:04:37 +10:00
try {
await Theme.bulkCreate(
JSON.parse(
fs.readFileSync(path.join(__dirname, "./templates/themes.json"))
)
)
} catch {
console.log("Themes already exist.")
}
try {
await User.create({
username: "Colubrina",
id: 0,
bot: true,
2022-07-29 19:20:19 +10:00
email: "colubrina@troplo.com",
banned: true
2022-07-29 19:04:37 +10:00
})
await User.update(
{
id: 0
},
{
where: {
username: "Colubrina"
}
}
)
2022-07-29 21:42:59 +10:00
} catch {
console.log("System user already exists.")
2022-07-29 19:04:37 +10:00
}
console.log("DB templates applied")
console.log("Admin user creation")
await createUser()
console.log("Colubrina has been setup.")
console.log(
"Colubrina can be started with `yarn serve` or `node .` in the backend directory."
)
console.log(
"The Colubrina frontend can be built with `yarn build` in the root project directory, and is recommended to be served via NGINX, with a proxy_pass to the backend on /api and /socket.io."
)
2022-08-07 00:35:00 +10:00
} else if (option === "Update/create database config file") {
2022-07-29 19:04:37 +10:00
await dbSetup()
2022-08-07 00:35:00 +10:00
console.log("config/database.json overwritten or created")
2022-07-29 19:04:37 +10:00
} else if (option === "Create user") {
await createUser()
} else if (option === "Run migrations") {
await runMigrations()
} else if (option === "Check for updates") {
await checkForUpdates()
2022-07-30 17:45:29 +10:00
} else if (option === "Build frontend for production") {
2022-07-29 20:34:27 +10:00
console.log("Building...")
2022-07-30 17:45:29 +10:00
execSync(
"cd ../frontend && yarn install --frozen-lockfile && yarn build",
() => {
console.log("yarn build complete")
}
)
2022-07-29 19:04:37 +10:00
} else if (option === "Exit") {
process.exit(0)
}
}
}
checkForUpdates().finally(() => {
init()
})