merged
authorimmler
Wed, 30 Oct 2019 18:30:28 -0400
changeset 70972 196b41b9b9c8
parent 70971 82057e7b9ea0 (current diff)
parent 70970 80dfc9a2f9c8 (diff)
child 70973 a7a52ba0717d
child 70974 3ee90f831805
merged
--- 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