| author | wenzelm | 
| Mon, 11 Nov 2019 11:49:43 +0100 | |
| changeset 71097 | d3ededaa77b3 | 
| parent 71079 | e06852132c1d | 
| child 71098 | da378866f580 | 
| permissions | -rw-r--r-- | 
| 70967 | 1 | /* Title: Pure/Tools/phabricator.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 4 | Support for Phabricator server, notably for Ubuntu 18.04 LTS. | 
| 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 5 | |
| 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 6 | See also: | 
| 70967 | 7 | - https://www.phacility.com/phabricator | 
| 8 | - https://secure.phabricator.com/book/phabricator | |
| 9 | */ | |
| 10 | ||
| 11 | package isabelle | |
| 12 | ||
| 13 | ||
| 70969 | 14 | import scala.util.matching.Regex | 
| 15 | ||
| 16 | ||
| 70967 | 17 | object Phabricator | 
| 18 | {
 | |
| 19 | /** defaults **/ | |
| 20 | ||
| 71049 | 21 | /* required packages */ | 
| 22 | ||
| 23 | val packages: List[String] = | |
| 24 | Build_Docker.packages ::: | |
| 25 | List( | |
| 26 | // https://secure.phabricator.com/source/phabricator/browse/master/scripts/install/install_ubuntu.sh 15e6e2adea61 | |
| 27 | "git", "mysql-server", "apache2", "libapache2-mod-php", "php", "php-mysql", | |
| 28 | "php-gd", "php-curl", "php-apcu", "php-cli", "php-json", "php-mbstring", | |
| 29 | // more packages | |
| 30 | "php-zip", "python-pygments", "ssh") | |
| 31 | ||
| 32 | ||
| 33 | /* global system resources */ | |
| 34 | ||
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 35 | val www_user = "www-data" | 
| 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 36 | |
| 71049 | 37 | val daemon_user = "phabricator" | 
| 38 | ||
| 39 | val ssh_standard = 22 | |
| 40 | val ssh_alternative1 = 222 | |
| 41 | val ssh_alternative2 = 2222 | |
| 42 | ||
| 43 | ||
| 44 | /* installation parameters */ | |
| 45 | ||
| 70967 | 46 | val default_name = "vcs" | 
| 47 | ||
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 48 | def phabricator_name(name: String = "", ext: String = ""): String = | 
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 49 | "phabricator" + (if (name.isEmpty) "" else "-" + name) + (if (ext.isEmpty) "" else "." + ext) | 
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 50 | |
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 51 | def isabelle_phabricator_name(name: String = "", ext: String = ""): String = | 
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 52 | "isabelle-" + phabricator_name(name = name, ext = ext) | 
| 70967 | 53 | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 54 | def default_root(name: String): Path = | 
| 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 55 |     Path.explode("/var/www") + Path.basic(phabricator_name(name = name))
 | 
| 70967 | 56 | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 57 |   def default_repo(name: String): Path = default_root(name) + Path.basic("repo")
 | 
| 70967 | 58 | |
| 71072 | 59 |   val default_mailers: Path = Path.explode("mailers.json")
 | 
| 71066 | 60 | |
| 70967 | 61 | |
| 62 | ||
| 63 | /** global configuration **/ | |
| 64 | ||
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 65 |   val global_config = Path.explode("/etc/" + isabelle_phabricator_name(ext = "conf"))
 | 
| 70967 | 66 | |
| 67 | sealed case class Config(name: String, root: Path) | |
| 70968 | 68 |   {
 | 
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 69 | def home: Path = root + Path.explode(phabricator_name()) | 
| 70969 | 70 | |
| 71 | def execute(command: String): Process_Result = | |
| 71074 | 72 |       Isabelle_System.bash("./bin/" + command, cwd = home.file, redirect = true).check
 | 
| 70968 | 73 | } | 
| 70967 | 74 | |
| 75 | def read_config(): List[Config] = | |
| 76 |   {
 | |
| 77 |     if (global_config.is_file) {
 | |
| 78 | for (entry <- Library.trim_split_lines(File.read(global_config)) if entry.nonEmpty) | |
| 79 |       yield {
 | |
| 80 |         space_explode(':', entry) match {
 | |
| 81 | case List(name, root) => Config(name, Path.explode(root)) | |
| 82 |           case _ => error("Malformed config file " + global_config + "\nentry " + quote(entry))
 | |
| 83 | } | |
| 84 | } | |
| 85 | } | |
| 86 | else Nil | |
| 87 | } | |
| 88 | ||
| 89 | def write_config(configs: List[Config]) | |
| 90 |   {
 | |
| 91 | File.write(global_config, | |
| 92 |       configs.map(config => config.name + ":" + config.root.implode).mkString("", "\n", "\n"))
 | |
| 93 | } | |
| 94 | ||
| 95 | def get_config(name: String): Config = | |
| 96 | read_config().find(config => config.name == name) getOrElse | |
| 97 |       error("Bad Isabelle/Phabricator installation " + quote(name))
 | |
| 98 | ||
| 99 | ||
| 100 | ||
| 71097 | 101 | /** command-line tools **/ | 
| 102 | ||
| 103 | /* Isabelle tool wrapper */ | |
| 104 | ||
| 105 | val isabelle_tool1 = | |
| 106 |     Isabelle_Tool("phabricator", "invoke command-line tool within Phabricator home directory", args =>
 | |
| 107 |     {
 | |
| 108 | var name = default_name | |
| 109 | ||
| 110 | val getopts = | |
| 111 |         Getopts("""
 | |
| 112 | Usage: isabelle phabricator [OPTIONS] COMMAND [ARGS...] | |
| 113 | ||
| 114 | Options are: | |
| 115 | -n NAME Phabricator installation name (default: """ + quote(default_name) + """) | |
| 116 | ||
| 117 | Invoke a command-line tool within the home directory of the named Phabricator | |
| 118 | installation. | |
| 119 | """, | |
| 120 | "n:" -> (arg => name = arg)) | |
| 121 | ||
| 122 | val more_args = getopts(args) | |
| 123 | if (more_args.isEmpty) getopts.usage() | |
| 124 | ||
| 125 | val progress = new Console_Progress | |
| 126 | ||
| 127 | val config = get_config(name) | |
| 128 | ||
| 129 |       if (!progress.bash(Bash.strings(more_args), cwd = config.home.file, echo = true).ok) {
 | |
| 130 |         error("Command failed")
 | |
| 131 | } | |
| 132 | }) | |
| 133 | ||
| 134 | ||
| 135 | ||
| 70967 | 136 | /** setup **/ | 
| 137 | ||
| 71049 | 138 | def user_setup(name: String, description: String, ssh_setup: Boolean = false) | 
| 139 |   {
 | |
| 140 |     if (!Linux.user_exists(name)) {
 | |
| 71054 
b64fc38327ae
prefer system user setup, e.g. avoid occurrence on login screen;
 wenzelm parents: 
71053diff
changeset | 141 | Linux.user_add(name, description = description, system = true, ssh_setup = ssh_setup) | 
| 71049 | 142 | } | 
| 143 |     else if (Linux.user_description(name) != description) {
 | |
| 144 |       error("User " + quote(name) + " already exists --" +
 | |
| 145 | " for Phabricator it should have the description:\n " + quote(description)) | |
| 146 | } | |
| 147 | } | |
| 148 | ||
| 70967 | 149 | def phabricator_setup( | 
| 150 | name: String = default_name, | |
| 151 | root: String = "", | |
| 152 | repo: String = "", | |
| 71047 | 153 | package_update: Boolean = false, | 
| 70967 | 154 | progress: Progress = No_Progress) | 
| 155 |   {
 | |
| 156 | /* system environment */ | |
| 157 | ||
| 158 | Linux.check_system_root() | |
| 159 | ||
| 71079 | 160 |     progress.echo("System packages ...")
 | 
| 161 | ||
| 71047 | 162 |     if (package_update) {
 | 
| 163 | Linux.package_update(progress = progress) | |
| 164 | Linux.check_reboot_required() | |
| 165 | } | |
| 70967 | 166 | |
| 167 | Linux.package_install(packages, progress = progress) | |
| 168 | Linux.check_reboot_required() | |
| 169 | ||
| 170 | ||
| 71049 | 171 | /* users */ | 
| 172 | ||
| 173 |     if (name == daemon_user) {
 | |
| 174 |       error("Clash of installation name with daemon user " + quote(daemon_user))
 | |
| 175 | } | |
| 176 | ||
| 177 | user_setup(daemon_user, "Phabricator Daemon User", ssh_setup = true) | |
| 178 | user_setup(name, "Phabricator SSH User") | |
| 179 | ||
| 180 | ||
| 70967 | 181 | /* basic installation */ | 
| 182 | ||
| 71079 | 183 |     progress.echo("\nPhabricator installation ...")
 | 
| 71076 | 184 | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 185 | val root_path = if (root.nonEmpty) Path.explode(root) else default_root(name) | 
| 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 186 | val repo_path = if (repo.nonEmpty) Path.explode(repo) else default_repo(name) | 
| 70967 | 187 | |
| 188 | val configs = read_config() | |
| 189 | ||
| 190 |     for (config <- configs if config.name == name) {
 | |
| 191 |       error("Duplicate Phabricator installation " + quote(name) + " in " + config.root)
 | |
| 192 | } | |
| 193 | ||
| 194 |     if (!Isabelle_System.bash("mkdir -p " + File.bash_path(root_path)).ok) {
 | |
| 195 |       error("Failed to create root directory " + root_path)
 | |
| 196 | } | |
| 197 | ||
| 198 | progress.bash(cwd = root_path.file, echo = true, | |
| 199 | script = """ | |
| 200 | set -e | |
| 71050 | 201 | chown """ + Bash.string(www_user) + ":" + Bash.string(www_user) + """ . | 
| 70967 | 202 | chmod 755 . | 
| 203 | ||
| 204 | git clone https://github.com/phacility/libphutil.git | |
| 205 | git clone https://github.com/phacility/arcanist.git | |
| 206 | git clone https://github.com/phacility/phabricator.git | |
| 207 | """).check | |
| 208 | ||
| 209 | val config = Config(name, root_path) | |
| 210 | write_config(configs ::: List(config)) | |
| 70968 | 211 | |
| 71051 | 212 |     config.execute("config set pygments.enabled true")
 | 
| 213 | ||
| 70968 | 214 | |
| 71050 | 215 | /* local repository directory */ | 
| 216 | ||
| 217 |     if (!Isabelle_System.bash("mkdir -p " + File.bash_path(repo_path)).ok) {
 | |
| 218 |       error("Failed to create local repository directory " + repo_path)
 | |
| 219 | } | |
| 220 | ||
| 221 | Isabelle_System.bash(cwd = repo_path.file, | |
| 222 | script = """ | |
| 223 | set -e | |
| 224 | chown -R """ + Bash.string(daemon_user) + ":" + Bash.string(daemon_user) + """ . | |
| 225 | chmod 755 . | |
| 226 | """).check | |
| 227 | ||
| 228 |     config.execute("config set repository.default-local-path " + File.bash_path(repo_path))
 | |
| 229 | ||
| 230 | ||
| 70969 | 231 | /* MySQL setup */ | 
| 232 | ||
| 71079 | 233 |     progress.echo("\nMySQL setup ...")
 | 
| 70969 | 234 | |
| 71055 
27a998cdc0f4
back to plain name, to have it accepted my mysql;
 wenzelm parents: 
71054diff
changeset | 235 |     File.write(Path.explode("/etc/mysql/mysql.conf.d/" + phabricator_name(ext = "cnf")),
 | 
| 71051 | 236 | """[mysqld] | 
| 237 | max_allowed_packet = 32M | |
| 238 | innodb_buffer_pool_size = 1600M | |
| 239 | local_infile = 0 | |
| 240 | """) | |
| 241 | ||
| 242 |     Linux.service_restart("mysql")
 | |
| 243 | ||
| 244 | ||
| 70969 | 245 | def mysql_conf(R: Regex): Option[String] = | 
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 246 |       split_lines(File.read(Path.explode("/etc/mysql/debian.cnf"))).collectFirst({ case R(a) => a })
 | 
| 70969 | 247 | |
| 248 |     for (user <- mysql_conf("""^user\s*=\s*(\S*)\s*$""".r)) {
 | |
| 249 |       config.execute("config set mysql.user " + Bash.string(user))
 | |
| 250 | } | |
| 251 | ||
| 252 |     for (pass <- mysql_conf("""^password\s*=\s*(\S*)\s*$""".r)) {
 | |
| 253 |       config.execute("config set mysql.pass " + Bash.string(pass))
 | |
| 254 | } | |
| 255 | ||
| 256 |     config.execute("config set storage.default-namespace " +
 | |
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 257 |       Bash.string(phabricator_name(name = name).replace("-", "_")))
 | 
| 70969 | 258 | |
| 71051 | 259 |     config.execute("config set storage.mysql-engine.max-size 8388608")
 | 
| 260 | ||
| 71075 
70205e023cb4
more messages -- expose potential database problems;
 wenzelm parents: 
71074diff
changeset | 261 |     progress.bash("./bin/storage upgrade --force", cwd = config.home.file, echo = true).check
 | 
| 70969 | 262 | |
| 263 | ||
| 71049 | 264 | /* SSH hosting */ | 
| 265 | ||
| 71079 | 266 |     progress.echo("\nSSH hosting setup ...")
 | 
| 71049 | 267 | |
| 268 | val ssh_port = ssh_alternative2 | |
| 269 | ||
| 270 |     config.execute("config set diffusion.ssh-user " + Bash.string(name))
 | |
| 271 |     config.execute("config set diffusion.ssh-port " + ssh_port)
 | |
| 272 | ||
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 273 |     val sudoers_file = Path.explode("/etc/sudoers.d") + Path.basic(isabelle_phabricator_name())
 | 
| 71049 | 274 | File.write(sudoers_file, | 
| 275 |       www_user + " ALL=(" + daemon_user + ") SETENV: NOPASSWD: /usr/bin/git, /usr/bin/hg, /usr/bin/ssh, /usr/bin/id\n" +
 | |
| 276 |       name + " ALL=(" + daemon_user + ") SETENV: NOPASSWD: /usr/bin/git, /usr/bin/git-upload-pack, /usr/bin/git-receive-pack, /usr/bin/hg, /usr/bin/svnserve, /usr/bin/ssh, /usr/bin/id\n")
 | |
| 277 | ||
| 278 |     Isabelle_System.bash("chmod 0440 " + File.bash_path(sudoers_file)).check
 | |
| 279 | ||
| 280 | ||
| 71051 | 281 | /* PHP setup */ | 
| 282 | ||
| 283 | val php_version = | |
| 284 |       Isabelle_System.bash("""php --run 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;'""")
 | |
| 285 | .check.out | |
| 286 | ||
| 287 | val php_conf = | |
| 288 |       Path.explode("/etc/php") + Path.basic(php_version) +  // educated guess
 | |
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 289 |         Path.explode("apache2/conf.d") +
 | 
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 290 | Path.basic(isabelle_phabricator_name(ext = "ini")) | 
| 71051 | 291 | |
| 292 | File.write(php_conf, | |
| 293 | "post_max_size = 32M\n" + | |
| 294 | "opcache.validate_timestamps = 0\n" + | |
| 295 | "memory_limit = 512M\n") | |
| 296 | ||
| 297 | ||
| 70968 | 298 | /* Apache setup */ | 
| 299 | ||
| 71079 | 300 |     progress.echo("Apache setup ...")
 | 
| 70968 | 301 | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 302 |     val apache_root = Path.explode("/etc/apache2")
 | 
| 70968 | 303 |     val apache_sites = apache_root + Path.explode("sites-available")
 | 
| 304 | ||
| 305 |     if (!apache_sites.is_dir) error("Bad Apache sites directory " + apache_sites)
 | |
| 306 | ||
| 71058 | 307 | val server_name = phabricator_name(name = name, ext = "lvh.me") // alias for "localhost" for testing | 
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 308 | val server_url = "http://" + server_name | 
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 309 | |
| 71058 | 310 | File.write(apache_sites + Path.basic(isabelle_phabricator_name(name = name, ext = "conf")), | 
| 70968 | 311 | """<VirtualHost *:80> | 
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 312 | ServerName """ + server_name + """ | 
| 70968 | 313 | ServerAdmin webmaster@localhost | 
| 70969 | 314 | DocumentRoot """ + config.home.implode + """/webroot | 
| 70968 | 315 | |
| 316 |     ErrorLog ${APACHE_LOG_DIR}/error.log
 | |
| 317 | RewriteEngine on | |
| 318 | RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA] | |
| 319 | </VirtualHost> | |
| 320 | ||
| 321 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet | |
| 322 | """) | |
| 323 | ||
| 71051 | 324 | Isabelle_System.bash( """ | 
| 70968 | 325 | set -e | 
| 326 | a2enmod rewrite | |
| 71058 | 327 | a2ensite """ + Bash.string(isabelle_phabricator_name(name = name))).check | 
| 71051 | 328 | |
| 71057 | 329 |     config.execute("config set phabricator.base-uri " + Bash.string(server_url))
 | 
| 330 | ||
| 71051 | 331 |     Linux.service_restart("apache2")
 | 
| 70968 | 332 | |
| 71053 | 333 | |
| 334 | /* PHP daemon */ | |
| 335 | ||
| 71079 | 336 |     progress.echo("PHP daemon setup ...")
 | 
| 71053 | 337 | |
| 338 |     config.execute("config set phd.user " + Bash.string(daemon_user))
 | |
| 339 | ||
| 71056 
ee3c43eb79ae
proper service name (again): it is specific to each installation;
 wenzelm parents: 
71055diff
changeset | 340 | Linux.service_install(isabelle_phabricator_name(name = name), | 
| 71053 | 341 | """[Unit] | 
| 342 | Description=PHP daemon for Isabelle/Phabricator """ + quote(name) + """ | |
| 343 | After=syslog.target network.target apache2.service mysql.service | |
| 344 | ||
| 345 | [Service] | |
| 346 | Type=oneshot | |
| 347 | User=""" + daemon_user + """ | |
| 348 | Group=""" + daemon_user + """ | |
| 349 | Environment=PATH=/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin | |
| 350 | ExecStart=""" + config.home.implode + """/bin/phd start | |
| 351 | ExecStop=""" + config.home.implode + """/bin/phd stop | |
| 352 | RemainAfterExit=yes | |
| 353 | ||
| 354 | [Install] | |
| 355 | WantedBy=multi-user.target | |
| 356 | """) | |
| 357 | ||
| 358 | ||
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 359 |     progress.echo("\nDONE\nWeb configuration via " + server_url)
 | 
| 70967 | 360 | } | 
| 361 | ||
| 362 | ||
| 363 | /* Isabelle tool wrapper */ | |
| 364 | ||
| 71097 | 365 | val isabelle_tool2 = | 
| 70967 | 366 |     Isabelle_Tool("phabricator_setup", "setup Phabricator server on Ubuntu Linux", args =>
 | 
| 367 |     {
 | |
| 71047 | 368 | var repo = "" | 
| 369 | var package_update = false | |
| 71078 | 370 | var name = default_name | 
| 70967 | 371 | var root = "" | 
| 372 | ||
| 373 | val getopts = | |
| 374 |         Getopts("""
 | |
| 71078 | 375 | Usage: isabelle phabricator_setup [OPTIONS] | 
| 70967 | 376 | |
| 377 | Options are: | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 378 |     -R DIR       repository directory (default: """ + default_repo("NAME") + """)
 | 
| 71047 | 379 | -U full update of system packages before installation | 
| 71078 | 380 | -n NAME Phabricator installation name (default: """ + quote(default_name) + """) | 
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 381 |     -r DIR       installation root directory (default: """ + default_root("NAME") + """)
 | 
| 70967 | 382 | |
| 383 | Install Phabricator as Ubuntu LAMP application (Linux, Apache, MySQL, PHP). | |
| 384 | ||
| 385 | Slogan: "Discuss. Plan. Code. Review. Test. | |
| 386 | Every application your project needs, all in one tool." | |
| 387 | ||
| 71078 | 388 | The installation name (default: """ + quote(default_name) + """) is mapped to a regular | 
| 389 | Unix user; this is relevant for public SSH access. | |
| 70967 | 390 | """, | 
| 391 | "R:" -> (arg => repo = arg), | |
| 71047 | 392 | "U" -> (_ => package_update = true), | 
| 71078 | 393 | "n:" -> (arg => name = arg), | 
| 70967 | 394 | "r:" -> (arg => root = arg)) | 
| 395 | ||
| 396 | val more_args = getopts(args) | |
| 71078 | 397 | if (more_args.nonEmpty) getopts.usage() | 
| 70967 | 398 | |
| 399 | val progress = new Console_Progress | |
| 400 | ||
| 71078 | 401 | phabricator_setup(name = name, root = root, repo = repo, | 
| 71047 | 402 | package_update = package_update, progress = progress) | 
| 70967 | 403 | }) | 
| 404 | ||
| 405 | ||
| 406 | ||
| 71066 | 407 | /** setup mail **/ | 
| 70967 | 408 | |
| 71072 | 409 | val mailers_template: String = | 
| 410 | """[ | |
| 411 |   {
 | |
| 412 | "key": "example.org", | |
| 413 | "type": "smtp", | |
| 414 |     "options": {
 | |
| 415 | "host": "mail.example.org", | |
| 416 | "port": 465, | |
| 417 | "user": "phabricator@example.org", | |
| 418 | "password": "********", | |
| 419 | "protocol": "ssl", | |
| 420 | "message-id": true | |
| 421 | } | |
| 422 | } | |
| 423 | ]""" | |
| 424 | ||
| 71066 | 425 | def phabricator_setup_mail( | 
| 426 | name: String = default_name, | |
| 427 | config_file: Option[Path] = None, | |
| 428 | test_user: String = "", | |
| 429 | progress: Progress = No_Progress) | |
| 70967 | 430 |   {
 | 
| 431 | Linux.check_system_root() | |
| 432 | ||
| 71066 | 433 | val config = get_config(name) | 
| 71073 | 434 | val default_config_file = config.root + default_mailers | 
| 71066 | 435 | |
| 436 | val mail_config = config_file getOrElse default_config_file | |
| 437 | ||
| 438 | def setup_mail | |
| 439 |     {
 | |
| 440 |       progress.echo("Using mail configuration from " + mail_config)
 | |
| 441 |       config.execute("config set cluster.mailers --stdin < " + File.bash_path(mail_config))
 | |
| 442 | ||
| 443 |       if (test_user.nonEmpty) {
 | |
| 444 |         progress.echo("Sending test mail to " + quote(test_user))
 | |
| 445 | progress.bash(cwd = config.home.file, echo = true, | |
| 446 | script = """echo "Test from Phabricator ($(date))" | ./bin/mail send-test --subject "Test" --to """ + | |
| 447 | Bash.string(test_user)).check | |
| 448 | } | |
| 449 | } | |
| 450 | ||
| 451 |     if (config_file.isEmpty) {
 | |
| 71070 | 452 |       if (!default_config_file.is_file) {
 | 
| 453 | File.write(default_config_file, mailers_template) | |
| 454 |         Isabelle_System.bash("chmod 600 " + File.bash_path(default_config_file)).check
 | |
| 455 | } | |
| 71066 | 456 |       if (File.read(default_config_file) == mailers_template) {
 | 
| 457 | progress.echo( | |
| 71077 | 458 | """ | 
| 459 | Please invoke the tool again, after providing details in | |
| 460 | """ + default_config_file.implode + """ | |
| 461 | ||
| 462 | See also section "Mailer: SMTP" in | |
| 463 | https://secure.phabricator.com/book/phabricator/article/configuring_outbound_email | |
| 464 | """) | |
| 71066 | 465 | } | 
| 466 | else setup_mail | |
| 467 | } | |
| 468 | else setup_mail | |
| 70967 | 469 | } | 
| 470 | ||
| 471 | ||
| 472 | /* Isabelle tool wrapper */ | |
| 473 | ||
| 71097 | 474 | val isabelle_tool3 = | 
| 71066 | 475 |     Isabelle_Tool("phabricator_setup_mail",
 | 
| 476 | "setup mail configuration for existing Phabricator server", args => | |
| 70967 | 477 |     {
 | 
| 71066 | 478 | var test_user = "" | 
| 479 | var name = default_name | |
| 480 | var config_file: Option[Path] = None | |
| 481 | ||
| 70967 | 482 | val getopts = | 
| 483 |         Getopts("""
 | |
| 71066 | 484 | Usage: isabelle phabricator_setup_mail [OPTIONS] | 
| 485 | ||
| 486 | Options are: | |
| 487 | -T USER send test mail to Phabricator user | |
| 71073 | 488 | -f FILE config file (default: """ + default_mailers + """ within installation root) | 
| 71066 | 489 | -n NAME Phabricator installation name (default: """ + quote(default_name) + """) | 
| 70967 | 490 | |
| 71077 | 491 | Provide mail configuration for existing Phabricator installation. | 
| 71066 | 492 | """, | 
| 493 | "T:" -> (arg => test_user = arg), | |
| 494 | "f:" -> (arg => config_file = Some(Path.explode(arg))), | |
| 495 | "n:" -> (arg => name = arg)) | |
| 70967 | 496 | |
| 497 | val more_args = getopts(args) | |
| 71066 | 498 | if (more_args.nonEmpty) getopts.usage() | 
| 70967 | 499 | |
| 500 | val progress = new Console_Progress | |
| 501 | ||
| 71066 | 502 | phabricator_setup_mail(name = name, config_file = config_file, | 
| 503 | test_user = test_user, progress = progress) | |
| 70967 | 504 | }) | 
| 505 | } |