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 ( )
} )