--- a/etc/options Wed Oct 30 15:26:10 2019 -0400
+++ b/etc/options Wed Oct 30 18:30:28 2019 -0400
@@ -343,6 +343,24 @@
option build_log_transaction_size : int = 1 -- "number of log files for each db update"
+section "Phabricator server"
+
+option phabricator_user : string = "phabricator"
+
+option phabricator_www_user : string = "www-data"
+option phabricator_www_root : string = "/var/www"
+
+option phabricator_mysql_config : string = "/etc/mysql/debian.cnf"
+option phabricator_apache_root : string = "/etc/apache2"
+
+option phabricator_smtp_host : string = ""
+option phabricator_smtp_port : int = 465
+option phabricator_smtp_user : string = ""
+option phabricator_smtp_passwd : string = ""
+option phabricator_smtp_protocol : string = "ssl"
+option phabricator_smtp_message_id : bool = true
+
+
section "Isabelle/Scala/ML system channel"
option system_channel_address : string = ""
--- a/src/Pure/System/isabelle_tool.scala Wed Oct 30 15:26:10 2019 -0400
+++ b/src/Pure/System/isabelle_tool.scala Wed Oct 30 18:30:28 2019 -0400
@@ -151,6 +151,8 @@
ML_Process.isabelle_tool,
Mkroot.isabelle_tool,
Options.isabelle_tool,
+ Phabricator.isabelle_tool1,
+ Phabricator.isabelle_tool2,
Present.isabelle_tool,
Profiling_Report.isabelle_tool,
Server.isabelle_tool,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/System/linux.scala Wed Oct 30 18:30:28 2019 -0400
@@ -0,0 +1,66 @@
+/* Title: Pure/System/linux.scala
+ Author: Makarius
+
+Specific support for Linux, notably Ubuntu/Debian.
+*/
+
+package isabelle
+
+
+import scala.util.matching.Regex
+
+
+object Linux
+{
+ /* check system */
+
+ def check_system(): Unit =
+ if (!Platform.is_linux) error("Not a Linux system")
+
+ def check_system_root(): Unit =
+ {
+ check_system()
+ if (Isabelle_System.bash("id -u").check.out != "0") error("Not running as superuser (root)")
+ }
+
+
+ /* release */
+
+ object Release
+ {
+ private val ID = """^Distributor ID:\s*(\S.*)$""".r
+ private val RELEASE = """^Release:\s*(\S.*)$""".r
+ private val DESCRIPTION = """^Description:\s*(\S.*)$""".r
+
+ def apply(): Release =
+ {
+ val lines = Isabelle_System.bash("lsb_release -a").check.out_lines
+ def find(R: Regex): String = lines.collectFirst({ case R(a) => a }).getOrElse("Unknown")
+ new Release(find(ID), find(RELEASE), find(DESCRIPTION))
+ }
+ }
+
+ final class Release private(val id: String, val release: String, val description: String)
+ {
+ override def toString: String = description
+
+ def is_ubuntu: Boolean = id == "Ubuntu"
+ }
+
+
+ /* packages */
+
+ def reboot_required(): Boolean =
+ Path.explode("/var/run/reboot-required").is_file
+
+ def check_reboot_required(): Unit =
+ if (reboot_required()) error("Reboot required")
+
+ def package_update(progress: Progress = No_Progress): Unit =
+ progress.bash(
+ """apt-get update -y && apt-get upgrade -y && apt autoremove -y""",
+ echo = true).check
+
+ def package_install(packages: List[String], progress: Progress = No_Progress): Unit =
+ progress.bash("apt-get install -y -- " + Bash.strings(packages), echo = true).check
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/Tools/phabricator.scala Wed Oct 30 18:30:28 2019 -0400
@@ -0,0 +1,271 @@
+/* Title: Pure/Tools/phabricator.scala
+ Author: Makarius
+
+Support for Phabricator server. See also:
+ - https://www.phacility.com/phabricator
+ - https://secure.phabricator.com/book/phabricator
+*/
+
+package isabelle
+
+
+import scala.util.matching.Regex
+
+
+object Phabricator
+{
+ /** defaults **/
+
+ val default_name = "vcs"
+
+ def default_prefix(name: String): String = "phabricator-" + name
+
+ def default_root(options: Options, name: String): Path =
+ Path.explode(options.string("phabricator_www_root")) + Path.basic(default_prefix(name))
+
+ def default_repo(options: Options, name: String): Path =
+ default_root(options, name) + Path.basic("repo")
+
+ val packages: List[String] =
+ Build_Docker.packages :::
+ List(
+ // https://secure.phabricator.com/source/phabricator/browse/master/scripts/install/install_ubuntu.sh 15e6e2adea61
+ "git", "mysql-server", "apache2", "libapache2-mod-php", "php", "php-mysql",
+ "php-gd", "php-curl", "php-apcu", "php-cli", "php-json", "php-mbstring",
+ // more packages
+ "php-zip", "python-pygments")
+
+
+
+ /** global configuration **/
+
+ val global_config = Path.explode("/etc/isabelle-phabricator.conf")
+
+ sealed case class Config(name: String, root: Path)
+ {
+ def home: Path = root + Path.explode("phabricator")
+
+ def execute(command: String): Process_Result =
+ Isabelle_System.bash("./bin/" + command, cwd = home.file).check
+ }
+
+ def read_config(): List[Config] =
+ {
+ if (global_config.is_file) {
+ for (entry <- Library.trim_split_lines(File.read(global_config)) if entry.nonEmpty)
+ yield {
+ space_explode(':', entry) match {
+ case List(name, root) => Config(name, Path.explode(root))
+ case _ => error("Malformed config file " + global_config + "\nentry " + quote(entry))
+ }
+ }
+ }
+ else Nil
+ }
+
+ def write_config(configs: List[Config])
+ {
+ File.write(global_config,
+ configs.map(config => config.name + ":" + config.root.implode).mkString("", "\n", "\n"))
+ }
+
+ def get_config(name: String): Config =
+ read_config().find(config => config.name == name) getOrElse
+ error("Bad Isabelle/Phabricator installation " + quote(name))
+
+
+
+ /** setup **/
+
+ def phabricator_setup(
+ options: Options,
+ name: String = default_name,
+ prefix: String = "",
+ root: String = "",
+ repo: String = "",
+ progress: Progress = No_Progress)
+ {
+ /* system environment */
+
+ Linux.check_system_root()
+
+ Linux.package_update(progress = progress)
+ Linux.check_reboot_required()
+
+ Linux.package_install(packages, progress = progress)
+ Linux.check_reboot_required()
+
+
+ /* basic installation */
+
+ val prefix_name = proper_string(prefix) getOrElse default_prefix(name)
+ val root_path = if (root.nonEmpty) Path.explode(root) else default_root(options, name)
+ val repo_path = if (repo.nonEmpty) Path.explode(repo) else default_repo(options, name)
+
+ val configs = read_config()
+
+ for (config <- configs if config.name == name) {
+ error("Duplicate Phabricator installation " + quote(name) + " in " + config.root)
+ }
+
+ if (!Isabelle_System.bash("mkdir -p " + File.bash_path(root_path)).ok) {
+ error("Failed to create root directory " + root_path)
+ }
+
+ progress.bash(cwd = root_path.file, echo = true,
+ script = """
+ set -e
+ chown """ + Bash.string(options.string("phabricator_www_user")) + """ .
+ chmod 755 .
+
+ git clone https://github.com/phacility/libphutil.git
+ git clone https://github.com/phacility/arcanist.git
+ git clone https://github.com/phacility/phabricator.git
+ """).check
+
+ val config = Config(name, root_path)
+ write_config(configs ::: List(config))
+
+
+ /* MySQL setup */
+
+ progress.echo("MySQL setup...")
+
+ def mysql_conf(R: Regex): Option[String] =
+ split_lines(File.read(Path.explode(options.string("phabricator_mysql_config")))).
+ collectFirst({ case R(a) => a })
+
+ for (user <- mysql_conf("""^user\s*=\s*(\S*)\s*$""".r)) {
+ config.execute("config set mysql.user " + Bash.string(user))
+ }
+
+ for (pass <- mysql_conf("""^password\s*=\s*(\S*)\s*$""".r)) {
+ config.execute("config set mysql.pass " + Bash.string(pass))
+ }
+
+ config.execute("config set storage.default-namespace " +
+ Bash.string(prefix_name.replace("-", "_")))
+
+ config.execute("storage upgrade --force")
+
+
+ /* Apache setup */
+
+ progress.echo("Apache setup...")
+
+ val apache_root = Path.explode(options.string("phabricator_apache_root"))
+ val apache_sites = apache_root + Path.explode("sites-available")
+
+ if (!apache_sites.is_dir) error("Bad Apache sites directory " + apache_sites)
+
+ File.write(apache_sites + Path.basic(prefix_name + ".conf"),
+"""<VirtualHost *:80>
+ #ServerName: "lvh.me" is an alias for "localhost" for testing
+ ServerName """ + prefix_name + """.lvh.me
+ ServerAdmin webmaster@localhost
+ DocumentRoot """ + config.home.implode + """/webroot
+
+ ErrorLog ${APACHE_LOG_DIR}/error.log
+ RewriteEngine on
+ RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA]
+</VirtualHost>
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
+""")
+
+ Isabelle_System.bash("""
+ set -e
+ a2enmod rewrite
+ a2ensite """ + Bash.string(prefix_name) + """
+ systemctl restart apache2
+""").check
+
+ progress.echo("\nDONE\nWeb configuration via http://" + prefix_name + ".lvh.me")
+ }
+
+
+ /* Isabelle tool wrapper */
+
+ val isabelle_tool1 =
+ Isabelle_Tool("phabricator_setup", "setup Phabricator server on Ubuntu Linux", args =>
+ {
+ var options = Options.init()
+ var prefix = ""
+ var root = ""
+ var repo = ""
+
+ val getopts =
+ Getopts("""
+Usage: isabelle phabricator_setup [OPTIONS] [NAME]
+
+ Options are:
+ -R DIR repository directory (default: """ + default_repo(options, "NAME") + """)
+ -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME)
+ -p PREFIX prefix for derived names (default: """ + default_prefix("NAME") + """)
+ -r DIR installation root directory (default: """ + default_root(options, "NAME") + """)
+
+ Install Phabricator as Ubuntu LAMP application (Linux, Apache, MySQL, PHP).
+
+ Slogan: "Discuss. Plan. Code. Review. Test.
+ Every application your project needs, all in one tool."
+
+ The installation NAME (default: """ + quote(default_name) + """) is mapped to
+ a regular Unix user and used for public SSH access.
+""",
+ "R:" -> (arg => repo = arg),
+ "o:" -> (arg => options = options + arg),
+ "p:" -> (arg => prefix = arg),
+ "r:" -> (arg => root = arg))
+
+ val more_args = getopts(args)
+
+ val name =
+ more_args match {
+ case Nil => default_name
+ case List(name) => name
+ case _ => getopts.usage()
+ }
+
+ val progress = new Console_Progress
+
+ phabricator_setup(options, name, prefix = prefix, root = root, repo = repo,
+ progress = progress)
+ })
+
+
+
+ /** update **/
+
+ def phabricator_update(name: String, progress: Progress = No_Progress)
+ {
+ Linux.check_system_root()
+
+ ???
+ }
+
+
+ /* Isabelle tool wrapper */
+
+ val isabelle_tool2 =
+ Isabelle_Tool("phabricator_update", "update Phabricator server installation", args =>
+ {
+ val getopts =
+ Getopts("""
+Usage: isabelle phabricator_update [NAME]
+
+ Update Phabricator installation, with lookup of NAME (default + """ + quote(default_name) + """)
+ in """ + global_config + "\n")
+
+ val more_args = getopts(args)
+ val name =
+ more_args match {
+ case Nil => default_name
+ case List(name) => name
+ case _ => getopts.usage()
+ }
+
+ val progress = new Console_Progress
+
+ phabricator_update(name, progress = progress)
+ })
+}
--- a/src/Pure/build-jars Wed Oct 30 15:26:10 2019 -0400
+++ b/src/Pure/build-jars Wed Oct 30 18:30:28 2019 -0400
@@ -125,6 +125,7 @@
System/isabelle_process.scala
System/isabelle_system.scala
System/isabelle_tool.scala
+ System/linux.scala
System/numa.scala
System/options.scala
System/platform.scala
@@ -153,6 +154,7 @@
Tools/fontforge.scala
Tools/main.scala
Tools/mkroot.scala
+ Tools/phabricator.scala
Tools/print_operation.scala
Tools/profiling_report.scala
Tools/server.scala