Add command line interface

Comes with a flag to run flyway baseline automatically
This commit is contained in:
Jakob Gillich 2020-07-14 19:07:14 +02:00
parent e0defe8240
commit 752a195178
No known key found for this signature in database
GPG key ID: FD8BF52DB8452C91
3 changed files with 140 additions and 82 deletions

View file

@ -81,7 +81,7 @@ The Login and World servers require PostgreSQL for persistence.
The default database is named `psforever` and the credentials are
`psforever:psforever`. To change these, create a configuration file at
`config/psforever.conf`. For configuration options and their defaults, see
[`pslogin/src/main/resources/application.conf`]. This database user will need
[`application.conf`](/pslogin/src/main/resources/application.conf). This database user will need
ALL access to tables, sequences, and functions.
The permissions required can be summarized by the SQL below.
Loading this in requires access to a graphical tool such as [pgAdmin](https://www.pgadmin.org/download/) (highly recommended) or a PostgreSQL terminal (`psql`) for advanced users.

View file

@ -73,7 +73,8 @@ lazy val commonSettings = Seq(
"com.github.pureconfig" %% "pureconfig" % "0.13.0",
"com.beachape" %% "enumeratum" % "1.6.1",
"joda-time" % "joda-time" % "2.10.6",
"commons-io" % "commons-io" % "2.6"
"commons-io" % "commons-io" % "2.6",
"com.github.scopt" %% "scopt" % "4.0.0-RC2"
)
)
@ -131,8 +132,7 @@ lazy val decodePackets = (project in file("tools/decode-packets"))
.settings(decodePacketsPackSettings: _*)
.settings(
libraryDependencies ++= Seq(
"org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0",
"com.github.scopt" %% "scopt" % "4.0.0-RC2"
"org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0"
)
)
.dependsOn(common)

View file

@ -2,7 +2,6 @@ package net.psforever.pslogin
import java.net.InetAddress
import java.util.Locale
import akka.actor.{ActorSystem, Props}
import akka.routing.RandomPool
import ch.qos.logback.classic.LoggerContext
@ -25,10 +24,18 @@ import org.apache.commons.io.FileUtils
import services.properties.PropertyOverrideManager
import org.flywaydb.core.Flyway
import java.nio.file.Paths
import scopt.OParser
object PsLogin {
private val logger = org.log4s.getLogger
case class CliConfig(
command: String = "run",
noAutoMigrate: Boolean = false,
baselineOnMigrate: Boolean = false,
bind: Option[String] = None
)
def printBanner(): Unit = {
println(ansi().fgBright(BLUE).a(""" ___ ________"""))
println(ansi().fgBright(BLUE).a(""" / _ \/ __/ __/__ _______ _ _____ ____"""))
@ -44,84 +51,30 @@ object PsLogin {
val maxMemory = FileUtils.byteCountToDisplaySize(Runtime.getRuntime.maxMemory())
s"""|~~~ System Information ~~~
|SYS: ${System.getProperty("os.name")} (v. ${System.getProperty("os.version")}, ${System.getProperty("os.arch")})
|SYS: ${System.getProperty("os.name")} (v. ${System.getProperty("os.version")}, ${System
.getProperty("os.arch")})
|CPU: Detected $processors available logical processor${if (processors != 1) "s" else ""}
|MEM: ${maxMemory} available to the JVM (tune with -Xmx flag)
|MEM: $maxMemory available to the JVM (tune with -Xmx flag)
|JVM: ${System.getProperty("java.vm.name")} (build ${System.getProperty("java.version")}), ${System.getProperty(
"java.vendor"
)} - ${System.getProperty("java.vendor.url")}
""".stripMargin
}
def main(args: Array[String]): Unit = {
Locale.setDefault(Locale.US); // to have floats with dots, not comma
printBanner()
println(systemInformation)
val loggerConfigPath = Paths.get(Config.directory, "logback.xml").toAbsolutePath().toString()
val loggerContext = slf4j.LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
val configurator = new JoranConfigurator()
configurator.setContext(loggerContext)
loggerContext.reset()
configurator.doConfigure(loggerConfigPath)
Config.result match {
case Left(failures) =>
logger.error("Loading config failed")
failures.toList.foreach { failure =>
logger.error(failure.toString)
}
sys.exit(1)
case Right(_) =>
}
def run(args: CliConfig): Unit = {
val bindAddress: InetAddress =
args.lift(0) match {
args.bind match {
case Some(address) => InetAddress.getByName(address) // address from first argument
case None => InetAddress.getByName(Config.app.bind) // address from config
}
/** Initialize the PSCrypto native library
*
* PSCrypto provides PlanetSide specific crypto that is required to communicate with it.
* It has to be distributed as a native library because there is no Scala version of the required
* cryptographic primitives (MD5MAC). See https://github.com/psforever/PSCrypto for more information.
*/
try {
CryptoInterface.initialize()
} catch {
case e: UnsatisfiedLinkError =>
logger.error("Unable to initialize " + CryptoInterface.libName)
logger.error(e)(
"This means that your PSCrypto version is out of date. Get the latest version from the README" +
" https://github.com/psforever/PSF-LoginServer#downloading-pscrypto"
)
sys.exit(1)
case e: IllegalArgumentException =>
logger.error("Unable to initialize " + CryptoInterface.libName)
logger.error(e)(
"This means that your PSCrypto version is out of date. Get the latest version from the README" +
" https://github.com/psforever/PSF-LoginServer#downloading-pscrypto"
)
sys.exit(1)
}
val flyway = Flyway
.configure()
.dataSource(Config.app.database.toJdbc, Config.app.database.username, Config.app.database.password)
.load();
flyway.migrate();
Config.app.kamon.enable match {
case true =>
if (Config.app.kamon.enable) {
logger.info("Starting Kamon")
Kamon.init()
case _ => ;
}
/** Start up the main actor system. This "system" is the home for all actors running on this server */
implicit val system = ActorSystem("PsLogin")
implicit val system: ActorSystem = ActorSystem("PsLogin")
Default(system)
/** Create pipelines for the login and world servers
@ -144,8 +97,7 @@ object PsLogin {
SessionPipeline("world-session-", Props[WorldSessionActor])
)
val netSim: Option[NetworkSimulatorParameters] = Config.app.developer.netSim.enable match {
case true =>
val netSim: Option[NetworkSimulatorParameters] = if (Config.app.developer.netSim.enable) {
val params = NetworkSimulatorParameters(
Config.app.developer.netSim.loss,
Config.app.developer.netSim.delay.toMillis,
@ -155,7 +107,8 @@ object PsLogin {
logger.warn("NetSim is active")
logger.warn(params.toString)
Some(params)
case false => None
} else {
None
}
val continents = Zones.zones.values ++ Seq(Zone.Nowhere)
@ -203,4 +156,109 @@ object PsLogin {
logger.info("Login server now shutting down...")
}
}
def flyway(args: CliConfig): Flyway = {
Flyway
.configure()
.dataSource(Config.app.database.toJdbc, Config.app.database.username, Config.app.database.password)
.baselineOnMigrate(args.baselineOnMigrate)
.load()
}
def migrate(args: CliConfig): Unit = {
flyway(args).migrate()
}
def main(args: Array[String]): Unit = {
Locale.setDefault(Locale.US); // to have floats with dots, not comma
printBanner()
println(systemInformation)
val loggerConfigPath = Paths.get(Config.directory, "logback.xml").toAbsolutePath.toString
val loggerContext = slf4j.LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
val configurator = new JoranConfigurator()
configurator.setContext(loggerContext)
loggerContext.reset()
configurator.doConfigure(loggerConfigPath)
Config.result match {
case Left(failures) =>
logger.error("Loading config failed")
failures.toList.foreach { failure =>
logger.error(failure.toString)
}
sys.exit(1)
case Right(_) =>
}
/** Initialize the PSCrypto native library
*
* PSCrypto provides PlanetSide specific crypto that is required to communicate with it.
* It has to be distributed as a native library because there is no Scala version of the required
* cryptographic primitives (MD5MAC). See https://github.com/psforever/PSCrypto for more information.
*/
try {
CryptoInterface.initialize()
} catch {
case e: UnsatisfiedLinkError =>
logger.error("Unable to initialize " + CryptoInterface.libName)
logger.error(e)(
"This means that your PSCrypto version is out of date. Get the latest version from the README" +
" https://github.com/psforever/PSF-LoginServer#downloading-pscrypto"
)
sys.exit(1)
case e: IllegalArgumentException =>
logger.error("Unable to initialize " + CryptoInterface.libName)
logger.error(e)(
"This means that your PSCrypto version is out of date. Get the latest version from the README" +
" https://github.com/psforever/PSF-LoginServer#downloading-pscrypto"
)
sys.exit(1)
}
val builder = OParser.builder[CliConfig]
val parser = {
import builder._
OParser.sequence(
programName("ps-login"),
opt[Unit]("no-auto-migrate")
.action((_, c) => c.copy(noAutoMigrate = true))
.text("Do not auto migrate database."),
opt[Unit]("baseline-on-migrate")
.action((_, c) => c.copy(baselineOnMigrate = true))
.text("Automatically baseline existing databases."),
cmd("run")
.action((_, c) => c.copy(command = "run"))
.text("Run server.")
.children(
opt[String]("bind")
.action((x, c) => c.copy(bind = Some(x)))
.text("Bind address")
),
cmd("migrate")
.action((_, c) => c.copy(command = "migrate"))
.text("Apply database migrations.")
)
}
OParser.parse(parser, args, CliConfig()) match {
case Some(config) =>
config.command match {
case "run" =>
if (config.noAutoMigrate) {
flyway(config).validate()
} else {
migrate(config)
}
run(config)
case "migrate" =>
migrate(config)
}
case _ =>
sys.exit(1)
}
}
}