forked from I2P_Developers/i2p.i2p
Major update for the OSX Launcher code. Now it will bundle
installer resources with the fat jar (we add an exclusion list later) which the base directory will be built(or updated if lacking files) upon startup of the I2P router. This is done by the OSXDeployment class which is an extension for the DeployProfile class written for Mac OS X. Since the app bundle itself should be R/O, we use ~/Library/I2P as base path, and continue using ~/Library/Application Support/i2p as config path. The BB code will have other paths.
This commit is contained in:
@ -57,6 +57,8 @@ launchers/macosx/project/target
|
||||
launchers/browserbundle/project/target
|
||||
launchers/target
|
||||
launchers/project/target
|
||||
launchers/common/target
|
||||
launchers/output
|
||||
|
||||
# Reporting
|
||||
sloccount.sc
|
||||
|
@ -1,3 +1,9 @@
|
||||
2018-05-01 meeh
|
||||
* launchers:
|
||||
- Added deployment profile for Mac OS X launcher.
|
||||
- Sourced out common code to a common library SBT project.
|
||||
- Decided ~/Library/I2P should be base path on Mac OS X when using regular bundle (Non-BB).
|
||||
|
||||
2018-04-29 zzz
|
||||
* Console: Fix NPE on /configsidebar (ticket #2220)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package net.i2p
|
||||
|
||||
import java.io.{File, InputStream}
|
||||
|
||||
import net.i2p.launchers.DeployProfile
|
||||
import net.i2p.router.Router
|
||||
import org.json4s._
|
||||
import org.json4s.native.JsonMethods._
|
||||
|
@ -20,6 +20,11 @@ lazy val commonSettings = Seq(
|
||||
packageDescription := "Blabla"
|
||||
)
|
||||
|
||||
lazy val common = (project in file("common"))
|
||||
.settings(
|
||||
commonSettings,
|
||||
name := "LauncherCommon"
|
||||
)
|
||||
|
||||
lazy val browserbundle = (project in file("browserbundle"))
|
||||
.settings(
|
||||
@ -27,7 +32,7 @@ lazy val browserbundle = (project in file("browserbundle"))
|
||||
name := "RouterLaunchApp",
|
||||
assemblyJarName in assembly := s"${name.value}-${version.value}.jar",
|
||||
mainClass in assembly := Some("net.i2p.RouterLauncherApp")
|
||||
)
|
||||
).dependsOn(common)
|
||||
|
||||
lazy val macosx = (project in file("macosx"))
|
||||
.settings(
|
||||
@ -35,11 +40,11 @@ lazy val macosx = (project in file("macosx"))
|
||||
name := "MacI2PLauncher",
|
||||
assemblyJarName in assembly := s"${name.value}-${version.value}.jar",
|
||||
mainClass in assembly := Some("net.i2p.MacOSXRouterLauncherApp")
|
||||
)
|
||||
).dependsOn(common)
|
||||
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.aggregate(browserbundle, macosx)
|
||||
.aggregate(common, browserbundle, macosx)
|
||||
|
||||
scalacOptions in Compile := Seq("-deprecated")
|
||||
|
||||
|
0
launchers/common/build.sbt
Normal file
0
launchers/common/build.sbt
Normal file
@ -0,0 +1,45 @@
|
||||
package net.i2p.launchers
|
||||
|
||||
import java.io.{File, InputStream}
|
||||
|
||||
/**
|
||||
* This represent a file or a directory. A filesystem resource.
|
||||
* It's used to handle a correct deployment of base files, even
|
||||
* installer resources got kind of a flat list. (i.e geoip files
|
||||
* is placed under a dir geoip)
|
||||
*
|
||||
* With this class we can now have a list containing both directories
|
||||
* and files in a type safe list.
|
||||
*
|
||||
* @param path
|
||||
* @param content
|
||||
* @param files
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class FDObject(path: String, content: Option[InputStream], files: Option[List[File]] = None, subDirectories: Boolean = false) {
|
||||
def isFile = files.isEmpty
|
||||
|
||||
def getPath = path
|
||||
|
||||
def filesIsDirectories = subDirectories
|
||||
|
||||
def getContent: Either[InputStream, List[File]] = {
|
||||
if (files.isEmpty) return Left(content.get)
|
||||
Right(files.get.map { f => new File(DeployProfile.pathJoin(path, f.getName)) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FDObjFile(file: String) extends FDObject(new File(file).getPath, Some(getClass.getResourceAsStream("/".concat(file))) )
|
||||
class FDObjDir(name: String, files: List[String],subDirectories:Boolean=false) extends FDObject(name, None, Some(files.map { s => new File(s) }),subDirectories)
|
||||
|
||||
/**
|
||||
*
|
||||
* This is a case class, it's a Scala thing. Think of it as a type.
|
||||
* If you're familiar with C, it's like a "typedef" even iirc Scala
|
||||
* has a own function for that as well.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -1,20 +1,10 @@
|
||||
package net.i2p
|
||||
package net.i2p.launchers
|
||||
|
||||
import java.io.{File, InputStream}
|
||||
/**
|
||||
*
|
||||
* The purpose of this class is to copy files from the i2p "default config" directory
|
||||
* and to a "current config" directory relative to the browser bundle.
|
||||
*
|
||||
* @author Meeh
|
||||
* @version 0.0.1
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class DeployProfile(confDir: String, baseDir: String) {
|
||||
import java.nio.file.{Paths, Files}
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
||||
object DeployProfile {
|
||||
|
||||
/**
|
||||
* This joins two paths in a cross platform way. Meaning it takes care of either we use
|
||||
* \\ or / as directory separator. It returns the resulting path in a string.
|
||||
@ -25,6 +15,21 @@ class DeployProfile(confDir: String, baseDir: String) {
|
||||
* @return String
|
||||
*/
|
||||
def pathJoin(parent:String,child:String): String = new File(new File(parent), child).getPath
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* The purpose of this class is to copy files from the i2p "default config" directory
|
||||
* and to a "current config" directory relative to the browser bundle - but this class is
|
||||
* also used by the OSX launcher since it shares common properties like that the bundle has
|
||||
* to be read only.
|
||||
*
|
||||
* @author Meeh
|
||||
* @version 0.0.1
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class DeployProfile(val confDir: String, val baseDir: String) {
|
||||
import java.nio.file.{Files, Paths}
|
||||
|
||||
/**
|
||||
* This function copies resources from the fatjar to the config directory of i2p.
|
||||
@ -35,7 +40,21 @@ class DeployProfile(confDir: String, baseDir: String) {
|
||||
*/
|
||||
def copyFileResToDisk(fStr: String) = Files.copy(
|
||||
getClass.getResource("/".concat(fStr)).getContent.asInstanceOf[InputStream],
|
||||
Paths.get(pathJoin(confDir, fStr)).normalize()
|
||||
Paths.get(DeployProfile.pathJoin(confDir, fStr)).normalize()
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* This function copies resources from the fatjar to the config directory of i2p.
|
||||
*
|
||||
* @since 0.9.35
|
||||
* @param path
|
||||
* @param is
|
||||
* @return Unit
|
||||
*/
|
||||
def copyBaseFileResToDisk(path: String, is: InputStream) = Files.copy(
|
||||
is,
|
||||
Paths.get(DeployProfile.pathJoin(baseDir, path)).normalize()
|
||||
)
|
||||
|
||||
/**
|
@ -0,0 +1,18 @@
|
||||
package net.i2p.launchers
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Defaults object for Mac OS X
|
||||
*
|
||||
*
|
||||
* @author Meeh
|
||||
* @since 0.9.35
|
||||
*/
|
||||
object OSXDefaults {
|
||||
|
||||
def getOSXConfigDirectory = new File(DeployProfile.pathJoin(System.getProperty("user.home"), "Library/Application Support/i2p"))
|
||||
|
||||
def getOSXBaseDirectory = new File(DeployProfile.pathJoin(System.getProperty("user.home"),"Library/I2P"))
|
||||
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
package net.i2p.launchers
|
||||
|
||||
import java.io.{File, IOException}
|
||||
import java.util.zip.ZipFile
|
||||
import collection.JavaConverters._
|
||||
|
||||
/**
|
||||
*
|
||||
* OSXDeployment
|
||||
*
|
||||
* This class can be a bit new for java developers. In Scala, when inherit other classes,
|
||||
* you would need to define their arguments if the super class only has constructors taking arguments.
|
||||
*
|
||||
* This child class don't take arguments, but rather get them from the signleton OSXDefaults.
|
||||
*
|
||||
* This class should be able to copy recursive resources into correct position for a normal deployment.
|
||||
*
|
||||
*
|
||||
* This class might look like black magic for some. But what it does is to deploy a structure like for example:
|
||||
* tree ~/Library/I2P
|
||||
* /Users/mikalv/Library/I2P
|
||||
* ├── blocklist.txt
|
||||
* ├── certificates
|
||||
* │ ├── family
|
||||
* │ │ ├── gostcoin.crt
|
||||
* │ │ ├── i2p-dev.crt
|
||||
* │ │ ├── i2pd-dev.crt
|
||||
* │ │ └── volatile.crt
|
||||
* │ ├── i2ptunnel
|
||||
* │ ├── news
|
||||
* │ │ ├── ampernand_at_gmail.com.crt
|
||||
* │ │ ├── echelon_at_mail.i2p.crt
|
||||
* │ │ ├── str4d_at_mail.i2p.crt
|
||||
* │ │ └── zzz_at_mail.i2p.crt
|
||||
* │ ├── plugin
|
||||
* │ │ ├── cacapo_at_mail.i2p.crt
|
||||
* │ │ ├── str4d_at_mail.i2p.crt
|
||||
* │ │ └── zzz-plugin_at_mail.i2p.crt
|
||||
* │ ├── reseed
|
||||
* │ │ ├── atomike_at_mail.i2p.crt
|
||||
* │ │ ├── backup_at_mail.i2p.crt
|
||||
* │ │ ├── bugme_at_mail.i2p.crt
|
||||
* │ │ ├── creativecowpat_at_mail.i2p.crt
|
||||
* │ │ ├── echelon_at_mail.i2p.crt
|
||||
* │ │ ├── hottuna_at_mail.i2p.crt
|
||||
* │ │ ├── igor_at_novg.net.crt
|
||||
* │ │ ├── lazygravy_at_mail.i2p.crt
|
||||
* │ │ ├── meeh_at_mail.i2p.crt
|
||||
* │ │ └── zmx_at_mail.i2p.crt
|
||||
* │ ├── revocations
|
||||
* │ ├── router
|
||||
* │ │ ├── echelon_at_mail.i2p.crt
|
||||
* │ │ ├── str4d_at_mail.i2p.crt
|
||||
* │ │ └── zzz_at_mail.i2p.crt
|
||||
* │ └── ssl
|
||||
* │ ├── echelon.reseed2017.crt
|
||||
* │ ├── i2p.mooo.com.crt
|
||||
* │ ├── i2pseed.creativecowpat.net.crt
|
||||
* │ ├── isrgrootx1.crt
|
||||
* │ └── reseed.onion.im.crt
|
||||
* ├── clients.config
|
||||
* ├── geoip
|
||||
* │ ├── continents.txt
|
||||
* │ ├── countries.txt
|
||||
* │ ├── geoip.txt
|
||||
* │ └── geoipv6.dat.gz
|
||||
* ├── hosts.txt
|
||||
* └── i2ptunnel.config
|
||||
*
|
||||
* @author Meeh
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class OSXDeployment extends
|
||||
DeployProfile(
|
||||
OSXDefaults.getOSXConfigDirectory.getAbsolutePath,
|
||||
OSXDefaults.getOSXBaseDirectory.getAbsolutePath
|
||||
) {
|
||||
|
||||
/**
|
||||
* This function will find the executing jar. "myself"
|
||||
* @return
|
||||
*/
|
||||
def executingJarFile = getClass().getProtectionDomain().getCodeSource().getLocation()
|
||||
|
||||
|
||||
/**
|
||||
* This list is a micro DSL for how files should
|
||||
* be deployed to the filesystem in the base
|
||||
* directory.
|
||||
*/
|
||||
val staticFilesFromResources = List(
|
||||
new FDObjFile("blocklist.txt"),
|
||||
new FDObjFile("clients.config"),
|
||||
new FDObjFile("hosts.txt"),
|
||||
new FDObjDir("geoip", files = List(
|
||||
"continents.txt",
|
||||
"countries.txt",
|
||||
"geoip.txt",
|
||||
"geoipv6.dat.gz")),
|
||||
new FDObjFile("i2ptunnel.config"),
|
||||
new FDObjDir("certificates", List(
|
||||
"family",
|
||||
"i2ptunnel",
|
||||
"news",
|
||||
"plugin",
|
||||
"reseed",
|
||||
"revocations",
|
||||
"router",
|
||||
"ssl"
|
||||
),subDirectories=true)
|
||||
)
|
||||
|
||||
/**
|
||||
* This function copies an directory of files from the jar
|
||||
* to the base directory defined in the launcher.
|
||||
* @param dir
|
||||
* @return
|
||||
*/
|
||||
def copyDirFromRes(dir: File) = {
|
||||
// A small hack
|
||||
val zipFile = new ZipFile(executingJarFile.getFile)
|
||||
zipFile.entries().asScala.toList.filter(_.toString.startsWith(dir.getPath)).filter(!_.isDirectory).map { entry =>
|
||||
copyBaseFileResToDisk(entry.getName, getClass.getResourceAsStream("/".concat(entry.getName)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will depending on directory or not copy either the file
|
||||
* or create the directory and copy directory content if any.
|
||||
*
|
||||
* @param file
|
||||
* @param isDir
|
||||
* @return
|
||||
*/
|
||||
def createFileOrDirectory(file: File, isDir: Boolean = false) = {
|
||||
if (file != null) {
|
||||
println(s"createFileOrDirectory(${file},${isDir})")
|
||||
try {
|
||||
if (!new File(DeployProfile.pathJoin(baseDir,file.getPath)).exists()) {
|
||||
if (isDir) {
|
||||
// Handle dir
|
||||
new File(DeployProfile.pathJoin(baseDir,file.getPath)).mkdirs()
|
||||
copyDirFromRes(file)
|
||||
} else {
|
||||
// Handle file
|
||||
copyBaseFileResToDisk(file.getPath, getClass.getResourceAsStream("/".concat(file.getName)))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex:IOException => println(s"Error! Exception ${ex}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!new File(baseDir).exists()) {
|
||||
new File(baseDir).mkdirs()
|
||||
}
|
||||
|
||||
/**
|
||||
* a map function work as a loop with some built in security
|
||||
* for "null" objects.
|
||||
* What happens here is "for each staticFilesFromResources" do =>
|
||||
*
|
||||
* Then, based upon if it's a file or a directory, different actions take place.
|
||||
*
|
||||
* the match case is controlling the flow based upon which type of object it is.
|
||||
*/
|
||||
staticFilesFromResources.map { fd => fd.getContent match {
|
||||
case Left(is) => {
|
||||
// Write file
|
||||
if (!new File(DeployProfile.pathJoin(baseDir, fd.getPath)).exists()) {
|
||||
println(s"copyBaseFileResToDisk(${fd.getPath})")
|
||||
try {
|
||||
copyBaseFileResToDisk(fd.getPath, is)
|
||||
} catch {
|
||||
case ex:IOException => println(s"Error! Exception ${ex}")
|
||||
}
|
||||
}
|
||||
}
|
||||
case Right(dir) => {
|
||||
// Ensure directory
|
||||
println(s"Directory(${fd.getPath})")
|
||||
if (!new File(DeployProfile.pathJoin(baseDir,fd.getPath)).exists()) {
|
||||
new File(DeployProfile.pathJoin(baseDir,fd.getPath)).mkdirs()
|
||||
}
|
||||
dir.map { f => createFileOrDirectory(f,fd.filesIsDirectories) }
|
||||
}
|
||||
} }
|
||||
|
||||
}
|
@ -9,6 +9,16 @@ lazy val i2pVersion = "0.9.34"
|
||||
lazy val buildAppBundleTask = taskKey[Unit](s"Build an Mac OS X bundle for I2P ${i2pVersion}.")
|
||||
lazy val bundleBuildPath = file("./output")
|
||||
|
||||
lazy val staticFiles = List(
|
||||
"blocklist.txt",
|
||||
"clients.config",
|
||||
"continents.txt",
|
||||
"countries.txt",
|
||||
"hosts.txt",
|
||||
"geoip.txt",
|
||||
"router.config",
|
||||
"webapps.config"
|
||||
)
|
||||
|
||||
// Pointing the resources directory to the "installer" directory
|
||||
resourceDirectory in Compile := baseDirectory.value / ".." / ".." / "installer" / "resources"
|
||||
@ -33,6 +43,19 @@ buildAppBundleTask := {
|
||||
)
|
||||
paths.map { case (s,p) => p.mkdirs() }
|
||||
val dirsToCopy = List("certificates","locale","man")
|
||||
|
||||
/**
|
||||
*
|
||||
* First of, if "map" is unknown for you - shame on you :p
|
||||
*
|
||||
* It's a loop basically where it loops through a list/array
|
||||
* with the current indexed item as subject.
|
||||
*
|
||||
* The code bellow takes the different lists and
|
||||
* copy all the directories or files from the i2p.i2p build dir,
|
||||
* and into the bundle so the launcher will know where to find i2p.
|
||||
*
|
||||
*/
|
||||
dirsToCopy.map { d => IO.copyDirectory( new File(resDir, d), new File(paths.get("i2pbaseBunldePath").get, d) ) }
|
||||
warsForCopy.map { w => IO.copyFile( new File(i2pBuildDir, w), new File(paths.get("webappsBunldePath").get, w) ) }
|
||||
warsForCopy.map { j => IO.copyFile( new File(i2pBuildDir, j), new File(paths.get("i2pJarsBunldePath").get, j) ) }
|
||||
@ -53,6 +76,6 @@ libraryDependencies ++= Seq(
|
||||
)
|
||||
|
||||
|
||||
assemblyOption in assembly := (assemblyOption in assembly).value.copy(prependShellScript = Some(defaultShellScript))
|
||||
//assemblyOption in assembly := (assemblyOption in assembly).value.copy(prependShellScript = Some(defaultShellScript))
|
||||
|
||||
assemblyJarName in assembly := s"${name.value}-${version.value}"
|
||||
|
2
launchers/macosx/project/plugins.sbt
Normal file
2
launchers/macosx/project/plugins.sbt
Normal file
@ -0,0 +1,2 @@
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.4")
|
@ -1,6 +1,7 @@
|
||||
package net.i2p
|
||||
|
||||
import net.i2p.router.Router
|
||||
import net.i2p.launchers.OSXDeployment
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@ -35,5 +36,7 @@ object MacOSXRouterLauncherApp extends App {
|
||||
|
||||
val i2pBaseBundleDir = new File(new File("."), "../Resources/i2pbase")
|
||||
|
||||
new OSXDeployment()
|
||||
|
||||
Router.main(args)
|
||||
}
|
||||
|
Reference in New Issue
Block a user