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 The default database is named `psforever` and the credentials are
`psforever:psforever`. To change these, create a configuration file at `psforever:psforever`. To change these, create a configuration file at
`config/psforever.conf`. For configuration options and their defaults, see `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. ALL access to tables, sequences, and functions.
The permissions required can be summarized by the SQL below. 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. 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.github.pureconfig" %% "pureconfig" % "0.13.0",
"com.beachape" %% "enumeratum" % "1.6.1", "com.beachape" %% "enumeratum" % "1.6.1",
"joda-time" % "joda-time" % "2.10.6", "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(decodePacketsPackSettings: _*)
.settings( .settings(
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0", "org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0"
"com.github.scopt" %% "scopt" % "4.0.0-RC2"
) )
) )
.dependsOn(common) .dependsOn(common)

View file

@ -2,7 +2,6 @@ package net.psforever.pslogin
import java.net.InetAddress import java.net.InetAddress
import java.util.Locale import java.util.Locale
import akka.actor.{ActorSystem, Props} import akka.actor.{ActorSystem, Props}
import akka.routing.RandomPool import akka.routing.RandomPool
import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.LoggerContext
@ -25,10 +24,18 @@ import org.apache.commons.io.FileUtils
import services.properties.PropertyOverrideManager import services.properties.PropertyOverrideManager
import org.flywaydb.core.Flyway import org.flywaydb.core.Flyway
import java.nio.file.Paths import java.nio.file.Paths
import scopt.OParser
object PsLogin { object PsLogin {
private val logger = org.log4s.getLogger 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 = { def printBanner(): Unit = {
println(ansi().fgBright(BLUE).a(""" ___ ________""")) println(ansi().fgBright(BLUE).a(""" ___ ________"""))
println(ansi().fgBright(BLUE).a(""" / _ \/ __/ __/__ _______ _ _____ ____""")) println(ansi().fgBright(BLUE).a(""" / _ \/ __/ __/__ _______ _ _____ ____"""))
@ -44,84 +51,30 @@ object PsLogin {
val maxMemory = FileUtils.byteCountToDisplaySize(Runtime.getRuntime.maxMemory()) val maxMemory = FileUtils.byteCountToDisplaySize(Runtime.getRuntime.maxMemory())
s"""|~~~ System Information ~~~ 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
|CPU: Detected $processors available logical processor${if (processors != 1) "s" else ""} .getProperty("os.arch")})
|MEM: ${maxMemory} available to the JVM (tune with -Xmx flag) |CPU: Detected $processors available logical processor${if (processors != 1) "s" else ""}
|JVM: ${System.getProperty("java.vm.name")} (build ${System.getProperty("java.version")}), ${System.getProperty( |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" "java.vendor"
)} - ${System.getProperty("java.vendor.url")} )} - ${System.getProperty("java.vendor.url")}
""".stripMargin """.stripMargin
} }
def main(args: Array[String]): Unit = { def run(args: CliConfig): 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(_) =>
}
val bindAddress: InetAddress = val bindAddress: InetAddress =
args.lift(0) match { args.bind match {
case Some(address) => InetAddress.getByName(address) // address from first argument case Some(address) => InetAddress.getByName(address) // address from first argument
case None => InetAddress.getByName(Config.app.bind) // address from config case None => InetAddress.getByName(Config.app.bind) // address from config
} }
/** Initialize the PSCrypto native library if (Config.app.kamon.enable) {
* logger.info("Starting Kamon")
* PSCrypto provides PlanetSide specific crypto that is required to communicate with it. Kamon.init()
* 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 =>
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 */ /** 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) Default(system)
/** Create pipelines for the login and world servers /** Create pipelines for the login and world servers
@ -144,18 +97,18 @@ object PsLogin {
SessionPipeline("world-session-", Props[WorldSessionActor]) SessionPipeline("world-session-", Props[WorldSessionActor])
) )
val netSim: Option[NetworkSimulatorParameters] = Config.app.developer.netSim.enable match { val netSim: Option[NetworkSimulatorParameters] = if (Config.app.developer.netSim.enable) {
case true => val params = NetworkSimulatorParameters(
val params = NetworkSimulatorParameters( Config.app.developer.netSim.loss,
Config.app.developer.netSim.loss, Config.app.developer.netSim.delay.toMillis,
Config.app.developer.netSim.delay.toMillis, Config.app.developer.netSim.reorderChance,
Config.app.developer.netSim.reorderChance, Config.app.developer.netSim.reorderTime.toMillis
Config.app.developer.netSim.reorderTime.toMillis )
) logger.warn("NetSim is active")
logger.warn("NetSim is active") logger.warn(params.toString)
logger.warn(params.toString) Some(params)
Some(params) } else {
case false => None None
} }
val continents = Zones.zones.values ++ Seq(Zone.Nowhere) val continents = Zones.zones.values ++ Seq(Zone.Nowhere)
@ -203,4 +156,109 @@ object PsLogin {
logger.info("Login server now shutting down...") 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)
}
}
} }