| author | wenzelm | 
| Sat, 01 Jun 2024 12:31:06 +0200 | |
| changeset 80224 | db92e0b6a11a | 
| parent 79525 | 9bc62f636fc4 | 
| child 80450 | 4355857e13a6 | 
| permissions | -rw-r--r-- | 
| 70967 | 1 | /* Title: Pure/Tools/phabricator.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 79496 | 4 | Support for Phabricator server, notably for Ubuntu 20.04 or 22.04 LTS. | 
| 71068 
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 | ||
| 71330 | 14 | import scala.collection.mutable | 
| 70969 | 15 | import scala.util.matching.Regex | 
| 16 | ||
| 17 | ||
| 75393 | 18 | object Phabricator {
 | 
| 70967 | 19 | /** defaults **/ | 
| 20 | ||
| 79520 | 21 | /* system packages */ | 
| 22 | ||
| 23 | val packages_ubuntu_20_04: List[String] = | |
| 24 | Docker_Build.packages ::: | |
| 25 | List( | |
| 26 | // https://secure.phabricator.com/source/phabricator/browse/master/scripts/install/install_ubuntu.sh 15e6e2adea61 | |
| 27 | "git", "mysql-server", "php", "php-mysql", "php-gd", "php-curl", "php-apcu", "php-cli", | |
| 28 | "php-json", "php-mbstring", | |
| 29 | // more packages | |
| 30 | "php-xml", "php-zip", "python3-pygments", "ssh", "subversion", "python-pygments", | |
| 31 | // mercurial build packages | |
| 32 | "make", "gcc", "python", "python2-dev", "python-docutils", "python-openssl") | |
| 33 | ||
| 34 | val packages_ubuntu_22_04: List[String] = | |
| 35 | Docker_Build.packages ::: | |
| 36 | List( | |
| 37 | // https://secure.phabricator.com/source/phabricator/browse/master/scripts/install/install_ubuntu.sh 15e6e2adea61 | |
| 38 | "git", "mysql-server", "php", "php-mysql", "php-gd", "php-curl", "php-apcu", "php-cli", | |
| 39 | "php-json", "php-mbstring", | |
| 40 | // more packages | |
| 79521 
db2b5c04075d
proper packages for mercurial_setup on Ubuntu 22.04: building from source provides hgweb modules, and also provides a defined version (6.1.1 is also provided by Ubuntu 22.04);
 wenzelm parents: 
79520diff
changeset | 41 | "php-xml", "php-zip", "python3-pygments", "ssh", "subversion", | 
| 
db2b5c04075d
proper packages for mercurial_setup on Ubuntu 22.04: building from source provides hgweb modules, and also provides a defined version (6.1.1 is also provided by Ubuntu 22.04);
 wenzelm parents: 
79520diff
changeset | 42 | // mercurial build packages | 
| 
db2b5c04075d
proper packages for mercurial_setup on Ubuntu 22.04: building from source provides hgweb modules, and also provides a defined version (6.1.1 is also provided by Ubuntu 22.04);
 wenzelm parents: 
79520diff
changeset | 43 | "make", "gcc", "python3", "python3-dev", "python3-docutils") | 
| 79520 | 44 | |
| 45 |   def packages(webserver: Webserver): List[String] = {
 | |
| 46 | val release = Linux.Release() | |
| 47 | val pkgs = | |
| 48 | if (release.is_ubuntu_20_04) packages_ubuntu_20_04 | |
| 49 | else if (release.is_ubuntu_22_04) packages_ubuntu_22_04 | |
| 50 |       else error("Bad Linux version: expected Ubuntu 20.04 or 22.04 LTS")
 | |
| 51 | pkgs ::: webserver.packages() | |
| 52 | } | |
| 53 | ||
| 54 | ||
| 79512 | 55 | /* webservers */ | 
| 56 | ||
| 57 |   sealed abstract class Webserver {
 | |
| 79523 | 58 | override def toString: String = user_name | 
| 59 | def user_name: String | |
| 60 | def system_name: String | |
| 79522 | 61 | def php_name: String = system_name | 
| 79512 | 62 | |
| 63 | def packages(): List[String] | |
| 64 | ||
| 65 | def system_path: Path = Path.basic(system_name) | |
| 66 |     def root_dir: Path = Path.explode("/etc") + system_path
 | |
| 67 |     def sites_dir: Path = root_dir + Path.explode("sites-available")
 | |
| 68 | ||
| 69 | def restart(): Unit = Linux.service_restart(system_name) | |
| 70 | ||
| 79513 | 71 | def php_init(): Unit = | 
| 79515 | 72 | File.write(Linux.php_conf_dir(php_name) + Path.basic(isabelle_phabricator_name(ext = "ini")), | 
| 79513 | 73 | "post_max_size = 32M\n" + | 
| 74 | "opcache.validate_timestamps = 0\n" + | |
| 75 | "memory_limit = 512M\n" + | |
| 76 | "max_execution_time = 120\n") | |
| 79512 | 77 | |
| 78 | def site_name(name: String): String = isabelle_phabricator_name(name = name) | |
| 79 | ||
| 80 | def site_conf(name: String): Path = | |
| 81 | sites_dir + Path.basic(isabelle_phabricator_name(name = name, ext = "conf")) | |
| 82 | ||
| 83 | def site_init(name: String, server_name: String, webroot: String): Unit | |
| 84 | } | |
| 85 | ||
| 86 |   object Apache extends Webserver {
 | |
| 79523 | 87 | override val user_name = "Apache" | 
| 79512 | 88 | override def system_name = "apache2" | 
| 89 |     override def packages(): List[String] = List("apache2", "libapache2-mod-php")
 | |
| 90 | ||
| 91 |     override def site_init(name: String, server_name: String, webroot: String): Unit = {
 | |
| 92 | File.write(site_conf(name), | |
| 93 | """<VirtualHost *:80> | |
| 94 | ServerName """ + server_name + """ | |
| 95 | ServerAdmin webmaster@localhost | |
| 96 | DocumentRoot """ + webroot + """ | |
| 97 | ||
| 98 |     ErrorLog ${APACHE_LOG_DIR}/error.log
 | |
| 99 |     CustomLog ${APACHE_LOG_DIR}/access.log combined
 | |
| 100 | ||
| 101 | RewriteEngine on | |
| 102 | RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA] | |
| 103 | </VirtualHost> | |
| 104 | ||
| 105 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet | |
| 106 | """) | |
| 107 |       Isabelle_System.bash("""
 | |
| 108 | set -e | |
| 109 | a2enmod rewrite | |
| 110 | a2ensite """ + Bash.string(site_name(name))).check | |
| 111 | } | |
| 112 | } | |
| 113 | ||
| 114 |   object Nginx extends Webserver {
 | |
| 79523 | 115 | override val user_name = "Nginx" | 
| 116 | override val system_name = "nginx" | |
| 79513 | 117 | override val php_name = "fpm" | 
| 79512 | 118 |     override def packages(): List[String] = List("nginx", "php-fpm")
 | 
| 119 | ||
| 120 |     override def site_init(name: String, server_name: String, webroot: String): Unit = {
 | |
| 121 | File.write(site_conf(name), | |
| 79517 | 122 | """server {
 | 
| 79518 
ad27859952cb
more robust nginx configuration, notably for "certbot --nginx -d DOMAIN";
 wenzelm parents: 
79517diff
changeset | 123 | listen 80; | 
| 
ad27859952cb
more robust nginx configuration, notably for "certbot --nginx -d DOMAIN";
 wenzelm parents: 
79517diff
changeset | 124 | listen [::]:80; | 
| 
ad27859952cb
more robust nginx configuration, notably for "certbot --nginx -d DOMAIN";
 wenzelm parents: 
79517diff
changeset | 125 | |
| 79512 | 126 | server_name """ + server_name + """; | 
| 127 | root """ + webroot + """; | |
| 128 | ||
| 129 |   location / {
 | |
| 130 | index index.php; | |
| 131 | rewrite ^/(.*)$ /index.php?__path__=/$1 last; | |
| 132 | } | |
| 133 | ||
| 134 |   location ~ \.php$ {
 | |
| 135 | include snippets/fastcgi-php.conf; | |
| 79515 | 136 | fastcgi_pass unix:/var/run/php/php""" + Linux.php_version() + """-fpm.sock; | 
| 79512 | 137 | } | 
| 138 | ||
| 139 |   location /index.php {
 | |
| 140 | fastcgi_index index.php; | |
| 141 | ||
| 142 | #required if PHP was built with --enable-force-cgi-redirect | |
| 143 | fastcgi_param REDIRECT_STATUS 200; | |
| 144 | ||
| 145 | #variables to make the $_SERVER populate in PHP | |
| 146 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| 147 | fastcgi_param QUERY_STRING $query_string; | |
| 148 | fastcgi_param REQUEST_METHOD $request_method; | |
| 149 | fastcgi_param CONTENT_TYPE $content_type; | |
| 150 | fastcgi_param CONTENT_LENGTH $content_length; | |
| 151 | ||
| 152 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; | |
| 153 | ||
| 154 | fastcgi_param GATEWAY_INTERFACE CGI/1.1; | |
| 155 | fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; | |
| 156 | ||
| 157 | fastcgi_param REMOTE_ADDR $remote_addr; | |
| 158 | } | |
| 159 | } | |
| 160 | """) | |
| 161 | Isabelle_System.bash( | |
| 162 | "ln -sf " + File.bash_path(site_conf(name)) + " /etc/nginx/sites-enabled/.").check | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | val all_webservers: List[Webserver] = List(Apache, Nginx) | |
| 167 | ||
| 168 | def get_webserver(name: String): Webserver = | |
| 79523 | 169 | all_webservers.find(w => w.user_name == name) getOrElse | 
| 79512 | 170 |       error("Bad webserver " + quote(name))
 | 
| 171 | ||
| 172 | val default_webserver: Webserver = Apache | |
| 173 | ||
| 174 | ||
| 71049 | 175 | /* global system resources */ | 
| 176 | ||
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 177 | val www_user = "www-data" | 
| 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 178 | |
| 71049 | 179 | val daemon_user = "phabricator" | 
| 180 | ||
| 71601 | 181 |   val sshd_config: Path = Path.explode("/etc/ssh/sshd_config")
 | 
| 71049 | 182 | |
| 183 | ||
| 184 | /* installation parameters */ | |
| 185 | ||
| 70967 | 186 | val default_name = "vcs" | 
| 187 | ||
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 188 | def phabricator_name(name: String = "", ext: String = ""): String = | 
| 77368 | 189 | "phabricator" + if_proper(name, "-" + name) + if_proper(ext, "." + ext) | 
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 190 | |
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 191 | def isabelle_phabricator_name(name: String = "", ext: String = ""): String = | 
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 192 | "isabelle-" + phabricator_name(name = name, ext = ext) | 
| 70967 | 193 | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 194 | def default_root(name: String): Path = | 
| 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 195 |     Path.explode("/var/www") + Path.basic(phabricator_name(name = name))
 | 
| 70967 | 196 | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 197 |   def default_repo(name: String): Path = default_root(name) + Path.basic("repo")
 | 
| 70967 | 198 | |
| 71072 | 199 |   val default_mailers: Path = Path.explode("mailers.json")
 | 
| 71066 | 200 | |
| 76129 | 201 | val default_system_port: Int = 22 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 202 | val alternative_system_port = 222 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 203 | val default_server_port = 2222 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 204 | |
| 79524 | 205 |   def standard_mercurial_source: String = {
 | 
| 79521 
db2b5c04075d
proper packages for mercurial_setup on Ubuntu 22.04: building from source provides hgweb modules, and also provides a defined version (6.1.1 is also provided by Ubuntu 22.04);
 wenzelm parents: 
79520diff
changeset | 206 | val release = Linux.Release() | 
| 
db2b5c04075d
proper packages for mercurial_setup on Ubuntu 22.04: building from source provides hgweb modules, and also provides a defined version (6.1.1 is also provided by Ubuntu 22.04);
 wenzelm parents: 
79520diff
changeset | 207 | if (release.is_ubuntu_20_04) "https://www.mercurial-scm.org/release/mercurial-3.9.2.tar.gz" | 
| 79525 
9bc62f636fc4
clarified Mercurial version: presumably the last version that supports both python2 and python3;
 wenzelm parents: 
79524diff
changeset | 208 | else "https://www.mercurial-scm.org/release/mercurial-6.1.4.tar.gz" | 
| 79521 
db2b5c04075d
proper packages for mercurial_setup on Ubuntu 22.04: building from source provides hgweb modules, and also provides a defined version (6.1.1 is also provided by Ubuntu 22.04);
 wenzelm parents: 
79520diff
changeset | 209 | } | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 210 | |
| 70967 | 211 | |
| 212 | ||
| 213 | /** global configuration **/ | |
| 214 | ||
| 71601 | 215 |   val global_config: Path = Path.explode("/etc/" + isabelle_phabricator_name(ext = "conf"))
 | 
| 70967 | 216 | |
| 71122 | 217 | def global_config_script( | 
| 218 | init: String = "", | |
| 219 | body: String = "", | |
| 75393 | 220 |     exit: String = ""): String = {
 | 
| 71282 | 221 | """#!/bin/bash | 
| 77369 | 222 | """ + if_proper(init, "\n" + init) + """ | 
| 71284 | 223 | {
 | 
| 71122 | 224 |   while { unset REPLY; read -r; test "$?" = 0 -o -n "$REPLY"; }
 | 
| 225 | do | |
| 226 | NAME="$(echo "$REPLY" | cut -d: -f1)" | |
| 227 | ROOT="$(echo "$REPLY" | cut -d: -f2)" | |
| 71284 | 228 |     {
 | 
| 73736 | 229 | """ + Library.indent_lines(6, body) + """ | 
| 71284 | 230 | } < /dev/null | 
| 231 | done | |
| 232 | } < """ + File.bash_path(global_config) + "\n" + | |
| 77369 | 233 | if_proper(exit, "\n" + exit + "\n") | 
| 71122 | 234 | } | 
| 235 | ||
| 75393 | 236 |   sealed case class Config(name: String, root: Path) {
 | 
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 237 | def home: Path = root + Path.explode(phabricator_name()) | 
| 70969 | 238 | |
| 239 | def execute(command: String): Process_Result = | |
| 80224 
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
 wenzelm parents: 
79525diff
changeset | 240 |       Isabelle_System.bash("bin/" + command, cwd = home, redirect = true).check
 | 
| 79512 | 241 | |
| 242 | def webroot: String = home.implode + "/webroot" | |
| 70968 | 243 | } | 
| 70967 | 244 | |
| 75393 | 245 |   def read_config(): List[Config] = {
 | 
| 70967 | 246 |     if (global_config.is_file) {
 | 
| 247 | for (entry <- Library.trim_split_lines(File.read(global_config)) if entry.nonEmpty) | |
| 248 |       yield {
 | |
| 249 |         space_explode(':', entry) match {
 | |
| 250 | case List(name, root) => Config(name, Path.explode(root)) | |
| 251 |           case _ => error("Malformed config file " + global_config + "\nentry " + quote(entry))
 | |
| 252 | } | |
| 253 | } | |
| 254 | } | |
| 255 | else Nil | |
| 256 | } | |
| 257 | ||
| 75393 | 258 |   def write_config(configs: List[Config]): Unit = {
 | 
| 70967 | 259 | File.write(global_config, | 
| 260 |       configs.map(config => config.name + ":" + config.root.implode).mkString("", "\n", "\n"))
 | |
| 261 | } | |
| 262 | ||
| 263 | def get_config(name: String): Config = | |
| 264 | read_config().find(config => config.name == name) getOrElse | |
| 265 |       error("Bad Isabelle/Phabricator installation " + quote(name))
 | |
| 266 | ||
| 267 | ||
| 268 | ||
| 71299 | 269 | /** administrative tools **/ | 
| 71097 | 270 | |
| 271 | /* Isabelle tool wrapper */ | |
| 272 | ||
| 273 | val isabelle_tool1 = | |
| 72763 | 274 |     Isabelle_Tool("phabricator", "invoke command-line tool within Phabricator home directory",
 | 
| 75393 | 275 | Scala_Project.here, | 
| 75394 | 276 |       { args =>
 | 
| 277 | var list = false | |
| 278 | var name = default_name | |
| 71097 | 279 | |
| 75394 | 280 |         val getopts = Getopts("""
 | 
| 71097 | 281 | Usage: isabelle phabricator [OPTIONS] COMMAND [ARGS...] | 
| 282 | ||
| 283 | Options are: | |
| 71101 | 284 | -l list available Phabricator installations | 
| 71097 | 285 | -n NAME Phabricator installation name (default: """ + quote(default_name) + """) | 
| 286 | ||
| 71103 | 287 | Invoke a command-line tool within the home directory of the named | 
| 288 | Phabricator installation. | |
| 71097 | 289 | """, | 
| 71101 | 290 | "l" -> (_ => list = true), | 
| 71097 | 291 | "n:" -> (arg => name = arg)) | 
| 292 | ||
| 75394 | 293 | val more_args = getopts(args) | 
| 294 | if (more_args.isEmpty && !list) getopts.usage() | |
| 71097 | 295 | |
| 75394 | 296 | val progress = new Console_Progress | 
| 71097 | 297 | |
| 75394 | 298 |         if (list) {
 | 
| 299 |           for (config <- read_config()) {
 | |
| 300 |             progress.echo("phabricator " + quote(config.name) + " root " + config.root)
 | |
| 301 | } | |
| 71101 | 302 | } | 
| 75394 | 303 |         else {
 | 
| 304 | val config = get_config(name) | |
| 80224 
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
 wenzelm parents: 
79525diff
changeset | 305 | val result = progress.bash(Bash.strings(more_args), cwd = config.home, echo = true) | 
| 75394 | 306 | if (!result.ok) error(result.print_return_code) | 
| 307 | } | |
| 308 | }) | |
| 71097 | 309 | |
| 310 | ||
| 311 | ||
| 70967 | 312 | /** setup **/ | 
| 313 | ||
| 75393 | 314 |   def user_setup(name: String, description: String, ssh_setup: Boolean = false): Unit = {
 | 
| 71049 | 315 |     if (!Linux.user_exists(name)) {
 | 
| 71054 
b64fc38327ae
prefer system user setup, e.g. avoid occurrence on login screen;
 wenzelm parents: 
71053diff
changeset | 316 | Linux.user_add(name, description = description, system = true, ssh_setup = ssh_setup) | 
| 71049 | 317 | } | 
| 318 |     else if (Linux.user_description(name) != description) {
 | |
| 319 |       error("User " + quote(name) + " already exists --" +
 | |
| 320 | " for Phabricator it should have the description:\n " + quote(description)) | |
| 321 | } | |
| 322 | } | |
| 323 | ||
| 71282 | 324 | def command_setup(name: String, | 
| 325 | init: String = "", | |
| 326 | body: String = "", | |
| 75393 | 327 | exit: String = "" | 
| 328 |   ): Path = {
 | |
| 71270 | 329 |     val command = Path.explode("/usr/local/bin") + Path.basic(name)
 | 
| 71282 | 330 | File.write(command, global_config_script(init = init, body = body, exit = exit)) | 
| 71270 | 331 |     Isabelle_System.chmod("755", command)
 | 
| 332 |     Isabelle_System.chown("root:root", command)
 | |
| 333 | command | |
| 334 | } | |
| 335 | ||
| 75393 | 336 |   def mercurial_setup(mercurial_source: String, progress: Progress = new Progress): Unit = {
 | 
| 71281 | 337 |     progress.echo("\nMercurial installation from source " + quote(mercurial_source) + " ...")
 | 
| 75394 | 338 |     Isabelle_System.with_tmp_dir("mercurial") { tmp_dir =>
 | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 339 | val archive = | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 340 |         if (Url.is_wellformed(mercurial_source)) {
 | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 341 |           val archive = tmp_dir + Path.basic("mercurial.tar.gz")
 | 
| 73566 | 342 | Isabelle_System.download_file(mercurial_source, archive) | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 343 | archive | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 344 | } | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 345 | else Path.explode(mercurial_source) | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 346 | |
| 76540 
83de6e9ae983
clarified signature: prefer Scala functions instead of shell scripts;
 wenzelm parents: 
76529diff
changeset | 347 | Isabelle_System.extract(archive, tmp_dir) | 
| 76529 | 348 | val build_dir = File.get_dir(tmp_dir, title = mercurial_source) | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 349 | |
| 80224 
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
 wenzelm parents: 
79525diff
changeset | 350 |       progress.bash("make all && make install", cwd = build_dir, echo = true).check
 | 
| 75394 | 351 | } | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 352 | } | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 353 | |
| 70967 | 354 | def phabricator_setup( | 
| 71422 
5d5be87330b5
allow to override repository versions at runtime;
 wenzelm parents: 
71421diff
changeset | 355 | options: Options, | 
| 70967 | 356 | name: String = default_name, | 
| 357 | root: String = "", | |
| 358 | repo: String = "", | |
| 79512 | 359 | webserver: Webserver = default_webserver, | 
| 71047 | 360 | package_update: Boolean = false, | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 361 | mercurial_source: String = "", | 
| 75393 | 362 | progress: Progress = new Progress | 
| 363 |   ): Unit = {
 | |
| 70967 | 364 | /* system environment */ | 
| 365 | ||
| 366 | Linux.check_system_root() | |
| 367 | ||
| 71079 | 368 |     progress.echo("System packages ...")
 | 
| 369 | ||
| 71047 | 370 |     if (package_update) {
 | 
| 371 | Linux.package_update(progress = progress) | |
| 372 | Linux.check_reboot_required() | |
| 373 | } | |
| 70967 | 374 | |
| 79512 | 375 | Linux.package_install(packages(webserver), progress = progress) | 
| 70967 | 376 | Linux.check_reboot_required() | 
| 377 | ||
| 378 | ||
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 379 |     if (mercurial_source.nonEmpty) {
 | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 380 |       for { name <- List("mercurial", "mercurial-common") if Linux.package_installed(name) } {
 | 
| 71326 | 381 |         error("Cannot install Mercurial from source:\n" +
 | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 382 | "package package " + quote(name) + " already installed") | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 383 | } | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 384 | mercurial_setup(mercurial_source, progress = progress) | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 385 | } | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 386 | |
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 387 | |
| 71049 | 388 | /* users */ | 
| 389 | ||
| 72520 | 390 | if (name.exists((c: Char) => !(Symbol.is_ascii_letter(c) || Symbol.is_ascii_digit(c))) || | 
| 71269 | 391 |         Set("", "ssh", "phd", "dump", daemon_user).contains(name)) {
 | 
| 71125 | 392 |       error("Bad installation name: " + quote(name))
 | 
| 71049 | 393 | } | 
| 394 | ||
| 395 | user_setup(daemon_user, "Phabricator Daemon User", ssh_setup = true) | |
| 396 | user_setup(name, "Phabricator SSH User") | |
| 397 | ||
| 398 | ||
| 70967 | 399 | /* basic installation */ | 
| 400 | ||
| 71079 | 401 |     progress.echo("\nPhabricator installation ...")
 | 
| 71076 | 402 | |
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 403 | 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 | 404 | val repo_path = if (repo.nonEmpty) Path.explode(repo) else default_repo(name) | 
| 70967 | 405 | |
| 406 | val configs = read_config() | |
| 407 | ||
| 408 |     for (config <- configs if config.name == name) {
 | |
| 409 |       error("Duplicate Phabricator installation " + quote(name) + " in " + config.root)
 | |
| 410 | } | |
| 411 | ||
| 412 |     if (!Isabelle_System.bash("mkdir -p " + File.bash_path(root_path)).ok) {
 | |
| 413 |       error("Failed to create root directory " + root_path)
 | |
| 414 | } | |
| 415 | ||
| 71116 | 416 | Isabelle_System.chown(Bash.string(www_user) + ":" + Bash.string(www_user), root_path) | 
| 417 |     Isabelle_System.chmod("755", root_path)
 | |
| 418 | ||
| 80224 
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
 wenzelm parents: 
79525diff
changeset | 419 | progress.bash(cwd = root_path, echo = true, | 
| 70967 | 420 | script = """ | 
| 421 | set -e | |
| 71126 | 422 | echo "Cloning distribution repositories:" | 
| 71287 
71fd25a7bbe2
more robust setup: avoid blind shot at "the latest" version;
 wenzelm parents: 
71285diff
changeset | 423 | |
| 79487 
47272fac86d8
support Phabricator on Ubuntu 22.04 LTS with PHP 8.1, using community form we.phorge.it version "2023 week 49";
 wenzelm parents: 
79482diff
changeset | 424 | git clone --branch stable https://we.phorge.it/source/arcanist.git | 
| 71422 
5d5be87330b5
allow to override repository versions at runtime;
 wenzelm parents: 
71421diff
changeset | 425 | git -C arcanist reset --hard """ + | 
| 
5d5be87330b5
allow to override repository versions at runtime;
 wenzelm parents: 
71421diff
changeset | 426 |           Bash.string(options.string("phabricator_version_arcanist")) + """
 | 
| 71287 
71fd25a7bbe2
more robust setup: avoid blind shot at "the latest" version;
 wenzelm parents: 
71285diff
changeset | 427 | |
| 79487 
47272fac86d8
support Phabricator on Ubuntu 22.04 LTS with PHP 8.1, using community form we.phorge.it version "2023 week 49";
 wenzelm parents: 
79482diff
changeset | 428 | git clone --branch stable https://we.phorge.it/source/phorge.git phabricator | 
| 71422 
5d5be87330b5
allow to override repository versions at runtime;
 wenzelm parents: 
71421diff
changeset | 429 | git -C phabricator reset --hard """ + | 
| 
5d5be87330b5
allow to override repository versions at runtime;
 wenzelm parents: 
71421diff
changeset | 430 |           Bash.string(options.string("phabricator_version_phabricator")) + """
 | 
| 70967 | 431 | """).check | 
| 432 | ||
| 433 | val config = Config(name, root_path) | |
| 434 | write_config(configs ::: List(config)) | |
| 70968 | 435 | |
| 71051 | 436 |     config.execute("config set pygments.enabled true")
 | 
| 437 | ||
| 70968 | 438 | |
| 71050 | 439 | /* local repository directory */ | 
| 440 | ||
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 441 |     progress.echo("\nRepository hosting setup ...")
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 442 | |
| 71050 | 443 |     if (!Isabelle_System.bash("mkdir -p " + File.bash_path(repo_path)).ok) {
 | 
| 444 |       error("Failed to create local repository directory " + repo_path)
 | |
| 445 | } | |
| 446 | ||
| 71114 | 447 | Isabelle_System.chown( | 
| 448 | "-R " + Bash.string(daemon_user) + ":" + Bash.string(daemon_user), repo_path) | |
| 449 |     Isabelle_System.chmod("755", repo_path)
 | |
| 71050 | 450 | |
| 451 |     config.execute("config set repository.default-local-path " + File.bash_path(repo_path))
 | |
| 452 | ||
| 453 | ||
| 71277 | 454 | val sudoers_file = | 
| 455 |       Path.explode("/etc/sudoers.d") + Path.basic(isabelle_phabricator_name(name = name))
 | |
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 456 | File.write(sudoers_file, | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 457 |       www_user + " ALL=(" + daemon_user + ") SETENV: NOPASSWD: /usr/bin/git, /usr/local/bin/hg, /usr/bin/hg, /usr/bin/ssh, /usr/bin/id\n" +
 | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 458 |       name + " ALL=(" + daemon_user + ") SETENV: NOPASSWD: /usr/bin/git, /usr/bin/git-upload-pack, /usr/bin/git-receive-pack, /usr/local/bin/hg, /usr/bin/hg, /usr/bin/svnserve, /usr/bin/ssh, /usr/bin/id\n")
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 459 | |
| 71115 | 460 |     Isabelle_System.chmod("440", sudoers_file)
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 461 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 462 |     config.execute("config set diffusion.ssh-user " + Bash.string(config.name))
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 463 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 464 | |
| 70969 | 465 | /* MySQL setup */ | 
| 466 | ||
| 71079 | 467 |     progress.echo("\nMySQL setup ...")
 | 
| 70969 | 468 | |
| 71055 
27a998cdc0f4
back to plain name, to have it accepted my mysql;
 wenzelm parents: 
71054diff
changeset | 469 |     File.write(Path.explode("/etc/mysql/mysql.conf.d/" + phabricator_name(ext = "cnf")),
 | 
| 71051 | 470 | """[mysqld] | 
| 471 | max_allowed_packet = 32M | |
| 472 | innodb_buffer_pool_size = 1600M | |
| 473 | local_infile = 0 | |
| 474 | """) | |
| 475 | ||
| 476 |     Linux.service_restart("mysql")
 | |
| 477 | ||
| 478 | ||
| 75393 | 479 |     def mysql_conf(R: Regex, which: String): String = {
 | 
| 71266 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 480 |       val conf = Path.explode("/etc/mysql/debian.cnf")
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 481 |       split_lines(File.read(conf)).collectFirst({ case R(a) => a }) match {
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 482 | case Some(res) => res | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 483 |         case None => error("Cannot determine " + which + " from " + conf)
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 484 | } | 
| 70969 | 485 | } | 
| 486 | ||
| 71266 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 487 |     val mysql_root_user = mysql_conf("""^user\s*=\s*(\S*)\s*$""".r, "superuser name")
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 488 |     val mysql_root_password = mysql_conf("""^password\s*=\s*(\S*)\s*$""".r, "superuser password")
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 489 | |
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 490 |     val mysql_name = phabricator_name(name = name).replace("-", "_")
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 491 | val mysql_user_string = SQL.string(mysql_name) + "@'localhost'" | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 492 | val mysql_password = Linux.generate_password() | 
| 70969 | 493 | |
| 71266 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 494 |     Isabelle_System.bash("mysql --user=" + Bash.string(mysql_root_user) +
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 495 | " --password=" + Bash.string(mysql_root_password) + " --execute=" + | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 496 | Bash.string( | 
| 71274 | 497 | """DROP USER IF EXISTS """ + mysql_user_string + "; " + | 
| 498 | """CREATE USER """ + mysql_user_string + | |
| 71266 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 499 | """ IDENTIFIED BY """ + SQL.string(mysql_password) + """ PASSWORD EXPIRE NEVER; """ + | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 500 |         """GRANT ALL ON `""" + (mysql_name + "_%").replace("_", "\\_") +
 | 
| 72522 
6e27af808c17
more privileges for the sake of mysqldump (avoid workaround --no-tablespaces);
 wenzelm parents: 
72521diff
changeset | 501 | """`.* TO """ + mysql_user_string + ";" + | 
| 
6e27af808c17
more privileges for the sake of mysqldump (avoid workaround --no-tablespaces);
 wenzelm parents: 
72521diff
changeset | 502 | """GRANT PROCESS ON *.* TO """ + mysql_user_string + ";")).check | 
| 70969 | 503 | |
| 71266 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 504 |     config.execute("config set mysql.user " + Bash.string(mysql_name))
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 505 |     config.execute("config set mysql.pass " + Bash.string(mysql_password))
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 506 | |
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 507 |     config.execute("config set phabricator.cache-namespace " + Bash.string(mysql_name))
 | 
| 
8451c86ffa85
proper mysql user setup: avoid superuser powers in production;
 wenzelm parents: 
71265diff
changeset | 508 |     config.execute("config set storage.default-namespace " + Bash.string(mysql_name))
 | 
| 71051 | 509 |     config.execute("config set storage.mysql-engine.max-size 8388608")
 | 
| 510 | ||
| 80224 
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
 wenzelm parents: 
79525diff
changeset | 511 |     progress.bash("bin/storage upgrade --force", cwd = config.home, echo = true).check
 | 
| 70969 | 512 | |
| 513 | ||
| 71269 | 514 | /* database dump */ | 
| 515 | ||
| 516 | val dump_name = isabelle_phabricator_name(name = "dump") | |
| 71282 | 517 | command_setup(dump_name, body = | 
| 71269 | 518 | """mkdir -p "$ROOT/database" && chown root:root "$ROOT/database" && chmod 700 "$ROOT/database" | 
| 519 | [ -e "$ROOT/database/dump.sql.gz" ] && mv -f "$ROOT/database/dump.sql.gz" "$ROOT/database/dump-old.sql.gz" | |
| 71328 | 520 | echo -n "Creating $ROOT/database/dump.sql.gz ..." | 
| 521 | "$ROOT/phabricator/bin/storage" dump --compress --output "$ROOT/database/dump.sql.gz" 2>&1 | fgrep -v '[Warning] Using a password on the command line interface can be insecure' | |
| 522 | echo " $(ls -hs "$ROOT/database/dump.sql.gz" | cut -d" " -f1)" """) | |
| 71269 | 523 | |
| 524 | ||
| 71283 | 525 | /* Phabricator upgrade */ | 
| 526 | ||
| 527 | command_setup(isabelle_phabricator_name(name = "upgrade"), | |
| 528 | init = | |
| 71285 | 529 | """BRANCH="${1:-stable}"
 | 
| 71283 | 530 | if [ "$BRANCH" != "master" -a "$BRANCH" != "stable" ] | 
| 531 | then | |
| 532 | echo "Bad branch: \"$BRANCH\"" | |
| 533 | exit 1 | |
| 534 | fi | |
| 535 | ||
| 536 | systemctl stop isabelle-phabricator-phd | |
| 79516 | 537 | systemctl stop """ + webserver.system_name, | 
| 71283 | 538 | body = | 
| 539 | """echo -e "\nUpgrading phabricator \"$NAME\" root \"$ROOT\" ..." | |
| 71845 | 540 | for REPO in arcanist phabricator | 
| 71283 | 541 | do | 
| 542 | cd "$ROOT/$REPO" | |
| 543 | echo -e "\nUpdating \"$REPO\" ..." | |
| 544 | git checkout "$BRANCH" | |
| 545 | git pull | |
| 546 | done | |
| 547 | echo -e "\nUpgrading storage ..." | |
| 548 | "$ROOT/phabricator/bin/storage" upgrade --force | |
| 549 | """, | |
| 550 | exit = | |
| 79516 | 551 | """systemctl start """ + webserver.system_name + """ | 
| 71283 | 552 | systemctl start isabelle-phabricator-phd""") | 
| 553 | ||
| 554 | ||
| 79512 | 555 | /* webserver setup */ | 
| 71051 | 556 | |
| 79523 | 557 | progress.echo(webserver.user_name + " setup ...") | 
| 71051 | 558 | |
| 79512 | 559 | val sites_dir = webserver.sites_dir | 
| 560 |     if (!sites_dir.is_dir) error("Bad " + webserver + " sites directory " + sites_dir)
 | |
| 70968 | 561 | |
| 79482 | 562 | val server_name = phabricator_name(name = name, ext = "localhost") // alias for "localhost" for testing | 
| 71052 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 563 | val server_url = "http://" + server_name | 
| 
6bf53035baf0
clarified name prefixes: global config always uses "isabelle-phabricator";
 wenzelm parents: 
71051diff
changeset | 564 | |
| 79512 | 565 | webserver.php_init() | 
| 71439 
760e19aa9b09
afford more logging (following defaults on Ubuntu);
 wenzelm parents: 
71422diff
changeset | 566 | |
| 79512 | 567 | webserver.site_init(name, server_name, config.webroot) | 
| 71051 | 568 | |
| 71057 | 569 |     config.execute("config set phabricator.base-uri " + Bash.string(server_url))
 | 
| 570 | ||
| 79512 | 571 | webserver.restart() | 
| 70968 | 572 | |
| 71328 | 573 |     progress.echo("\nFurther manual configuration via " + server_url)
 | 
| 71128 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 574 | |
| 71053 | 575 | |
| 576 | /* PHP daemon */ | |
| 577 | ||
| 71128 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 578 |     progress.echo("\nPHP daemon setup ...")
 | 
| 71053 | 579 | |
| 72376 | 580 |     val phd_log_path = Isabelle_System.make_directory(Path.explode("/var/tmp/phd"))
 | 
| 71273 | 581 | Isabelle_System.chown( | 
| 582 | "-R " + Bash.string(daemon_user) + ":" + Bash.string(daemon_user), phd_log_path) | |
| 583 |     Isabelle_System.chmod("755", phd_log_path)
 | |
| 584 | ||
| 71053 | 585 |     config.execute("config set phd.user " + Bash.string(daemon_user))
 | 
| 71112 | 586 |     config.execute("config set phd.log-directory /var/tmp/phd/" +
 | 
| 587 | isabelle_phabricator_name(name = name) + "/log") | |
| 71053 | 588 | |
| 71124 
7dbadecdc118
just one isabelle-phabricator-phd service, which manages all processes uniformly (NB: "bin/phd stop" affects all installations);
 wenzelm parents: 
71122diff
changeset | 589 | val phd_name = isabelle_phabricator_name(name = "phd") | 
| 71127 | 590 | Linux.service_shutdown(phd_name) | 
| 71282 | 591 | val phd_command = command_setup(phd_name, body = """"$ROOT/phabricator/bin/phd" "$@" """) | 
| 71128 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 592 |     try {
 | 
| 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 593 | Linux.service_install(phd_name, | 
| 71053 | 594 | """[Unit] | 
| 71124 
7dbadecdc118
just one isabelle-phabricator-phd service, which manages all processes uniformly (NB: "bin/phd stop" affects all installations);
 wenzelm parents: 
71122diff
changeset | 595 | Description=PHP daemon manager for Isabelle/Phabricator | 
| 79512 | 596 | After=syslog.target network.target """ + webserver.system_name + """.service mysql.service | 
| 71053 | 597 | |
| 598 | [Service] | |
| 599 | Type=oneshot | |
| 600 | User=""" + daemon_user + """ | |
| 601 | Group=""" + daemon_user + """ | |
| 602 | Environment=PATH=/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin | |
| 71124 
7dbadecdc118
just one isabelle-phabricator-phd service, which manages all processes uniformly (NB: "bin/phd stop" affects all installations);
 wenzelm parents: 
71122diff
changeset | 603 | ExecStart=""" + phd_command.implode + """ start --force | 
| 
7dbadecdc118
just one isabelle-phabricator-phd service, which manages all processes uniformly (NB: "bin/phd stop" affects all installations);
 wenzelm parents: 
71122diff
changeset | 604 | ExecStop=""" + phd_command.implode + """ stop | 
| 71053 | 605 | RemainAfterExit=yes | 
| 606 | ||
| 607 | [Install] | |
| 608 | WantedBy=multi-user.target | |
| 609 | """) | |
| 71128 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 610 | } | 
| 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 611 |     catch {
 | 
| 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 612 | case ERROR(msg) => | 
| 80224 
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
 wenzelm parents: 
79525diff
changeset | 613 |         progress.bash("bin/phd status", cwd = config.home, echo = true).check
 | 
| 71128 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 614 | error(msg) | 
| 
f79006c533b0
clarified errors: PHP daemon can fail under odd circumstances;
 wenzelm parents: 
71127diff
changeset | 615 | } | 
| 70967 | 616 | } | 
| 617 | ||
| 618 | ||
| 619 | /* Isabelle tool wrapper */ | |
| 620 | ||
| 71097 | 621 | val isabelle_tool2 = | 
| 72763 | 622 |     Isabelle_Tool("phabricator_setup", "setup Phabricator server on Ubuntu Linux",
 | 
| 75393 | 623 | Scala_Project.here, | 
| 75394 | 624 |       { args =>
 | 
| 625 | var mercurial_source = "" | |
| 626 | var repo = "" | |
| 627 | var package_update = false | |
| 628 | var name = default_name | |
| 629 | var options = Options.init() | |
| 630 | var root = "" | |
| 79512 | 631 | var webserver = default_webserver | 
| 70967 | 632 | |
| 75394 | 633 |         val getopts = Getopts("""
 | 
| 71078 | 634 | Usage: isabelle phabricator_setup [OPTIONS] | 
| 70967 | 635 | |
| 636 | Options are: | |
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 637 | -M SOURCE install Mercurial from source: local PATH, or URL, or ":" for | 
| 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 638 | """ + standard_mercurial_source + """ | 
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 639 |     -R DIR       repository directory (default: """ + default_repo("NAME") + """)
 | 
| 71047 | 640 | -U full update of system packages before installation | 
| 71078 | 641 | -n NAME Phabricator installation name (default: """ + quote(default_name) + """) | 
| 71422 
5d5be87330b5
allow to override repository versions at runtime;
 wenzelm parents: 
71421diff
changeset | 642 | -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME) | 
| 71068 
510b89906d86
discontinued somewhat pointless Isabelle options: setup implicitly assumes Ubuntu 18.04;
 wenzelm parents: 
71066diff
changeset | 643 |     -r DIR       installation root directory (default: """ + default_root("NAME") + """)
 | 
| 79512 | 644 |     -w NAME      webserver name (""" +
 | 
| 79523 | 645 |           all_webservers.map(w => quote(w.user_name)).mkString (" or ") +
 | 
| 646 | ", default: " + quote(default_webserver.user_name) + """) | |
| 70967 | 647 | |
| 79512 | 648 | Install Phabricator as Linux service, based on webserver + PHP + MySQL. | 
| 70967 | 649 | |
| 71078 | 650 | The installation name (default: """ + quote(default_name) + """) is mapped to a regular | 
| 651 | Unix user; this is relevant for public SSH access. | |
| 70967 | 652 | """, | 
| 71280 
5a2033fc8f3d
avoid odd (harmless) problem with Mercurial 4.5.3 provided by Ubuntu 18.04 on first push: "couldn't write revision branch cache names";
 wenzelm parents: 
71277diff
changeset | 653 | "M:" -> (arg => mercurial_source = (if (arg == ":") standard_mercurial_source else arg)), | 
| 70967 | 654 | "R:" -> (arg => repo = arg), | 
| 71047 | 655 | "U" -> (_ => package_update = true), | 
| 71078 | 656 | "n:" -> (arg => name = arg), | 
| 71422 
5d5be87330b5
allow to override repository versions at runtime;
 wenzelm parents: 
71421diff
changeset | 657 | "o:" -> (arg => options = options + arg), | 
| 79512 | 658 | "r:" -> (arg => root = arg), | 
| 659 | "w:" -> (arg => webserver = get_webserver(arg))) | |
| 70967 | 660 | |
| 75394 | 661 | val more_args = getopts(args) | 
| 662 | if (more_args.nonEmpty) getopts.usage() | |
| 70967 | 663 | |
| 75394 | 664 | val progress = new Console_Progress | 
| 70967 | 665 | |
| 79512 | 666 | phabricator_setup(options, name = name, root = root, repo = repo, webserver = webserver, | 
| 75394 | 667 | package_update = package_update, mercurial_source = mercurial_source, progress = progress) | 
| 668 | }) | |
| 70967 | 669 | |
| 670 | ||
| 671 | ||
| 71066 | 672 | /** setup mail **/ | 
| 70967 | 673 | |
| 71072 | 674 | val mailers_template: String = | 
| 75659 
9bd92ac9328f
more robust Scala 3 indentation, for the sake of IntelliJ IDEA;
 wenzelm parents: 
75394diff
changeset | 675 | """[ | 
| 71072 | 676 |   {
 | 
| 677 | "key": "example.org", | |
| 678 | "type": "smtp", | |
| 679 |     "options": {
 | |
| 680 | "host": "mail.example.org", | |
| 681 | "port": 465, | |
| 682 | "user": "phabricator@example.org", | |
| 683 | "password": "********", | |
| 684 | "protocol": "ssl", | |
| 685 | "message-id": true | |
| 686 | } | |
| 687 | } | |
| 688 | ]""" | |
| 689 | ||
| 71066 | 690 | def phabricator_setup_mail( | 
| 691 | name: String = default_name, | |
| 692 | config_file: Option[Path] = None, | |
| 693 | test_user: String = "", | |
| 75393 | 694 | progress: Progress = new Progress | 
| 695 |   ): Unit = {
 | |
| 70967 | 696 | Linux.check_system_root() | 
| 697 | ||
| 71066 | 698 | val config = get_config(name) | 
| 71073 | 699 | val default_config_file = config.root + default_mailers | 
| 71066 | 700 | |
| 701 | val mail_config = config_file getOrElse default_config_file | |
| 702 | ||
| 75393 | 703 |     def setup_mail: Unit = {
 | 
| 71066 | 704 |       progress.echo("Using mail configuration from " + mail_config)
 | 
| 705 |       config.execute("config set cluster.mailers --stdin < " + File.bash_path(mail_config))
 | |
| 706 | ||
| 707 |       if (test_user.nonEmpty) {
 | |
| 708 |         progress.echo("Sending test mail to " + quote(test_user))
 | |
| 80224 
db92e0b6a11a
clarified signature: prefer symbolic isabelle.Path over physical java.io.File;
 wenzelm parents: 
79525diff
changeset | 709 | progress.bash(cwd = config.home, echo = true, | 
| 71102 | 710 | script = """echo "Test from Phabricator ($(date))" | bin/mail send-test --subject "Test" --to """ + | 
| 71066 | 711 | Bash.string(test_user)).check | 
| 712 | } | |
| 713 | } | |
| 714 | ||
| 715 |     if (config_file.isEmpty) {
 | |
| 71070 | 716 |       if (!default_config_file.is_file) {
 | 
| 717 | File.write(default_config_file, mailers_template) | |
| 71114 | 718 |         Isabelle_System.chmod("600", default_config_file)
 | 
| 71070 | 719 | } | 
| 71066 | 720 |       if (File.read(default_config_file) == mailers_template) {
 | 
| 71131 | 721 |         progress.echo("Please invoke the tool again, after providing details in\n  " +
 | 
| 722 | default_config_file.implode + "\n") | |
| 71066 | 723 | } | 
| 724 | else setup_mail | |
| 725 | } | |
| 726 | else setup_mail | |
| 70967 | 727 | } | 
| 728 | ||
| 729 | ||
| 730 | /* Isabelle tool wrapper */ | |
| 731 | ||
| 71097 | 732 | val isabelle_tool3 = | 
| 72763 | 733 |     Isabelle_Tool("phabricator_setup_mail", "setup mail for one Phabricator installation",
 | 
| 75393 | 734 | Scala_Project.here, | 
| 75394 | 735 |       { args =>
 | 
| 736 | var test_user = "" | |
| 737 | var name = default_name | |
| 738 | var config_file: Option[Path] = None | |
| 71066 | 739 | |
| 75394 | 740 |         val getopts = Getopts("""
 | 
| 71066 | 741 | Usage: isabelle phabricator_setup_mail [OPTIONS] | 
| 742 | ||
| 743 | Options are: | |
| 744 | -T USER send test mail to Phabricator user | |
| 71103 | 745 | -f FILE config file (default: """ + default_mailers + """ within Phabricator root) | 
| 71066 | 746 | -n NAME Phabricator installation name (default: """ + quote(default_name) + """) | 
| 70967 | 747 | |
| 71077 | 748 | Provide mail configuration for existing Phabricator installation. | 
| 71066 | 749 | """, | 
| 750 | "T:" -> (arg => test_user = arg), | |
| 751 | "f:" -> (arg => config_file = Some(Path.explode(arg))), | |
| 752 | "n:" -> (arg => name = arg)) | |
| 70967 | 753 | |
| 75394 | 754 | val more_args = getopts(args) | 
| 755 | if (more_args.nonEmpty) getopts.usage() | |
| 70967 | 756 | |
| 75394 | 757 | val progress = new Console_Progress | 
| 70967 | 758 | |
| 75394 | 759 | phabricator_setup_mail(name = name, config_file = config_file, | 
| 760 | test_user = test_user, progress = progress) | |
| 761 | }) | |
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 762 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 763 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 764 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 765 | /** setup ssh **/ | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 766 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 767 | /* sshd config */ | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 768 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 769 | private val Port = """^\s*Port\s+(\d+)\s*$""".r | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 770 | private val No_Port = """^#\s*Port\b.*$""".r | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 771 | private val Any_Port = """^#?\s*Port\b.*$""".r | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 772 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 773 | def conf_ssh_port(port: Int): String = | 
| 76129 | 774 | if (port == default_system_port) "#Port " + default_system_port else "Port " + port | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 775 | |
| 75393 | 776 |   def read_ssh_port(conf: Path): Int = {
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 777 | val lines = split_lines(File.read(conf)) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 778 | val ports = | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 779 |       lines.flatMap({
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 780 | case Port(Value.Int(p)) => Some(p) | 
| 76129 | 781 | case No_Port() => Some(default_system_port) | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 782 | case _ => None | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 783 | }) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 784 |     ports match {
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 785 | case List(port) => port | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 786 |       case Nil => error("Missing Port specification in " + conf)
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 787 |       case _ => error("Multiple Port specifications in " + conf)
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 788 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 789 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 790 | |
| 75393 | 791 |   def write_ssh_port(conf: Path, port: Int): Boolean = {
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 792 | val old_port = read_ssh_port(conf) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 793 | if (old_port == port) false | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 794 |     else {
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 795 | val lines = split_lines(File.read(conf)) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 796 |       val lines1 = lines.map({ case Any_Port() => conf_ssh_port(port) case line => line })
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 797 | File.write(conf, cat_lines(lines1)) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 798 | true | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 799 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 800 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 801 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 802 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 803 | /* phabricator_setup_ssh */ | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 804 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 805 | def phabricator_setup_ssh( | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 806 | server_port: Int = default_server_port, | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 807 | system_port: Int = default_system_port, | 
| 75393 | 808 | progress: Progress = new Progress | 
| 809 |   ): Unit = {
 | |
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 810 | Linux.check_system_root() | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 811 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 812 | val configs = read_config() | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 813 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 814 |     if (server_port == system_port) {
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 815 |       error("Port for Phabricator sshd coincides with system port: " + system_port)
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 816 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 817 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 818 |     val sshd_conf_system = Path.explode("/etc/ssh/sshd_config")
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 819 | val sshd_conf_server = sshd_conf_system.ext(isabelle_phabricator_name()) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 820 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 821 | val ssh_name = isabelle_phabricator_name(name = "ssh") | 
| 71111 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71109diff
changeset | 822 | Linux.service_shutdown(ssh_name) | 
| 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71109diff
changeset | 823 | |
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 824 | val old_system_port = read_ssh_port(sshd_conf_system) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 825 |     if (old_system_port != system_port) {
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 826 |       progress.echo("Reconfigurig system ssh service")
 | 
| 71111 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71109diff
changeset | 827 |       Linux.service_shutdown("ssh")
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 828 | write_ssh_port(sshd_conf_system, system_port) | 
| 71111 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71109diff
changeset | 829 |       Linux.service_start("ssh")
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 830 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 831 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 832 |     progress.echo("Configuring " + ssh_name + " service")
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 833 | |
| 71282 | 834 | val ssh_command = command_setup(ssh_name, body = | 
| 71122 | 835 | """if [ "$1" = "$NAME" ] | 
| 836 | then | |
| 837 | exec "$ROOT/phabricator/bin/ssh-auth" "$@" | |
| 71270 | 838 | fi""", exit = "exit 1") | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 839 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 840 | File.write(sshd_conf_server, | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 841 | """# OpenBSD Secure Shell server for Isabelle/Phabricator | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 842 | AuthorizedKeysCommand """ + ssh_command.implode + """ | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 843 | AuthorizedKeysCommandUser """ + daemon_user + """ | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 844 | AuthorizedKeysFile none | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 845 | AllowUsers """ + configs.map(_.name).mkString(" ") + """
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 846 | Port """ + server_port + """ | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 847 | Protocol 2 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 848 | PermitRootLogin no | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 849 | AllowAgentForwarding no | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 850 | AllowTcpForwarding no | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 851 | PrintMotd no | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 852 | PrintLastLog no | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 853 | PasswordAuthentication no | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 854 | ChallengeResponseAuthentication no | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 855 | PidFile /var/run/""" + ssh_name + """.pid | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 856 | """) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 857 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 858 | Linux.service_install(ssh_name, | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 859 | """[Unit] | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 860 | Description=OpenBSD Secure Shell server for Isabelle/Phabricator | 
| 79519 | 861 | After=network.target auditd.service isabelle-phabricator-phd.service | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 862 | ConditionPathExists=!/etc/ssh/sshd_not_to_be_run | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 863 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 864 | [Service] | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 865 | EnvironmentFile=-/etc/default/ssh | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 866 | ExecStartPre=/usr/sbin/sshd -f """ + sshd_conf_server.implode + """ -t | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 867 | ExecStart=/usr/sbin/sshd -f """ + sshd_conf_server.implode + """ -D $SSHD_OPTS | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 868 | ExecReload=/usr/sbin/sshd -f """ + sshd_conf_server.implode + """ -t | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 869 | ExecReload=/bin/kill -HUP $MAINPID | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 870 | KillMode=process | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 871 | Restart=on-failure | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 872 | RestartPreventExitStatus=255 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 873 | Type=notify | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 874 | RuntimeDirectory=sshd-phabricator | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 875 | RuntimeDirectoryMode=0755 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 876 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 877 | [Install] | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 878 | WantedBy=multi-user.target | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 879 | Alias=""" + ssh_name + """.service | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 880 | """) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 881 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 882 |     for (config <- configs) {
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 883 |       progress.echo("phabricator " + quote(config.name) + " port " +  server_port)
 | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 884 |       config.execute("config set diffusion.ssh-port " + Bash.string(server_port.toString))
 | 
| 76129 | 885 |       if (server_port == default_system_port) config.execute("config delete diffusion.ssh-port")
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 886 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 887 | } | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 888 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 889 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 890 | /* Isabelle tool wrapper */ | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 891 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 892 | val isabelle_tool4 = | 
| 72763 | 893 |     Isabelle_Tool("phabricator_setup_ssh", "setup ssh service for all Phabricator installations",
 | 
| 75393 | 894 | Scala_Project.here, | 
| 75394 | 895 |       { args =>
 | 
| 896 | var server_port = default_server_port | |
| 897 | var system_port = default_system_port | |
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 898 | |
| 75394 | 899 |         val getopts = Getopts("""
 | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 900 | Usage: isabelle phabricator_setup_ssh [OPTIONS] | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 901 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 902 | Options are: | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 903 | -p PORT sshd port for Phabricator servers (default: """ + default_server_port + """) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 904 | -q PORT sshd port for the operating system (default: """ + default_system_port + """) | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 905 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 906 | Configure ssh service for all Phabricator installations: a separate sshd | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 907 | is run in addition to the one of the operating system, and ports need to | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 908 | be distinct. | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 909 | |
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 910 | A particular Phabricator installation is addressed by using its | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 911 | name as the ssh user; the actual Phabricator user is determined via | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 912 | stored ssh keys. | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 913 | """, | 
| 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 914 | "p:" -> (arg => server_port = Value.Int.parse(arg)), | 
| 71295 
6aadbd650280
eliminated pointless option -T: it merely tests ssh config of root, which is not required later;
 wenzelm parents: 
71292diff
changeset | 915 | "q:" -> (arg => system_port = Value.Int.parse(arg))) | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 916 | |
| 75394 | 917 | val more_args = getopts(args) | 
| 918 | if (more_args.nonEmpty) getopts.usage() | |
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 919 | |
| 75394 | 920 | val progress = new Console_Progress | 
| 71109 
8c1c717a830b
configure SSH hosting via "isabelle phabricator_setup_ssh";
 wenzelm parents: 
71103diff
changeset | 921 | |
| 75394 | 922 | phabricator_setup_ssh( | 
| 923 | server_port = server_port, system_port = system_port, progress = progress) | |
| 924 | }) | |
| 71299 | 925 | |
| 926 | ||
| 927 | ||
| 928 | /** conduit API **/ | |
| 929 | ||
| 75393 | 930 |   object API {
 | 
| 71332 | 931 | /* user information */ | 
| 932 | ||
| 933 | sealed case class User( | |
| 934 | id: Long, | |
| 935 | phid: String, | |
| 936 | name: String, | |
| 937 | real_name: String, | |
| 75393 | 938 | roles: List[String] | 
| 939 |     ) {
 | |
| 71332 | 940 | def is_valid: Boolean = | 
| 941 |         roles.contains("verified") &&
 | |
| 942 |         roles.contains("approved") &&
 | |
| 943 |         roles.contains("activated")
 | |
| 944 |       def is_admin: Boolean = roles.contains("admin")
 | |
| 945 |       def is_regular: Boolean = !(roles.contains("bot") || roles.contains("list"))
 | |
| 946 | } | |
| 947 | ||
| 948 | ||
| 71314 | 949 | /* repository information */ | 
| 950 | ||
| 951 | sealed case class Repository( | |
| 78602 | 952 | vcs: VCS, | 
| 71314 | 953 | id: Long, | 
| 954 | phid: String, | |
| 955 | name: String, | |
| 956 | callsign: String, | |
| 957 | short_name: String, | |
| 958 | importing: Boolean, | |
| 75393 | 959 | ssh_url: String | 
| 960 |     ) {
 | |
| 71314 | 961 | def is_hg: Boolean = vcs == VCS.hg | 
| 962 | } | |
| 963 | ||
| 78602 | 964 |     enum VCS { case hg, git, svn }
 | 
| 965 | ||
| 966 | def read_vcs(s: String): VCS = | |
| 967 |       try { VCS.valueOf(s) }
 | |
| 968 |       catch { case _: IllegalArgumentException => error("Unknown vcs type " + quote(s)) }
 | |
| 71314 | 969 | |
| 970 | def edits(typ: String, value: JSON.T): List[JSON.Object.T] = | |
| 971 |       List(JSON.Object("type" -> typ, "value" -> value))
 | |
| 972 | ||
| 973 | def opt_edits(typ: String, value: Option[JSON.T]): List[JSON.Object.T] = | |
| 974 | value.toList.flatMap(edits(typ, _)) | |
| 975 | ||
| 976 | ||
| 977 | /* result with optional error */ | |
| 978 | ||
| 75393 | 979 |     sealed case class Result(result: JSON.T, error: Option[String]) {
 | 
| 71314 | 980 | def ok: Boolean = error.isEmpty | 
| 981 | def get: JSON.T = if (ok) result else Exn.error(error.get) | |
| 982 | ||
| 983 | def get_value[A](unapply: JSON.T => Option[A]): A = | |
| 984 |         unapply(get) getOrElse Exn.error("Bad JSON result: " + JSON.Format(result))
 | |
| 985 | ||
| 986 | def get_string: String = get_value(JSON.Value.String.unapply) | |
| 987 | } | |
| 988 | ||
| 75393 | 989 |     def make_result(json: JSON.T): Result = {
 | 
| 71314 | 990 | val result = JSON.value(json, "result").getOrElse(JSON.Object.empty) | 
| 991 | val error_info = JSON.string(json, "error_info") | |
| 992 | val error_code = JSON.string(json, "error_code") | |
| 993 | Result(result, error_info orElse error_code) | |
| 994 | } | |
| 995 | ||
| 996 | ||
| 997 | /* context for operations */ | |
| 998 | ||
| 76173 | 999 | def apply(server: String, port: Int = 0): API = new API(server, port) | 
| 71299 | 1000 | } | 
| 1001 | ||
| 76172 | 1002 |   final class API private(server: String, port: Int) {
 | 
| 71299 | 1003 | /* connection */ | 
| 1004 | ||
| 76172 | 1005 | private def port_suffix: String = if (port > 0) ":" + port else "" | 
| 1006 | override def toString: String = server + port_suffix | |
| 1007 | def hg_url: String = "ssh://" + server + port_suffix | |
| 71299 | 1008 | |
| 1009 | ||
| 1010 | /* execute methods */ | |
| 1011 | ||
| 75393 | 1012 |     def execute_raw(method: String, params: JSON.T = JSON.Object.empty): JSON.T = {
 | 
| 75394 | 1013 |       Isabelle_System.with_tmp_file("params", "json") { params_file =>
 | 
| 71300 | 1014 |         File.write(params_file, JSON.Format(JSON.Object("params" -> JSON.Format(params))))
 | 
| 71299 | 1015 | val result = | 
| 1016 | Isabelle_System.bash( | |
| 76172 | 1017 | SSH.client_command(port = port) + " -- " + Bash.string(server) + | 
| 71300 | 1018 | " conduit " + Bash.string(method) + " < " + File.bash_path(params_file)).check | 
| 71299 | 1019 | JSON.parse(result.out, strict = false) | 
| 75394 | 1020 | } | 
| 71299 | 1021 | } | 
| 1022 | ||
| 71300 | 1023 | def execute(method: String, params: JSON.T = JSON.Object.empty): API.Result = | 
| 1024 | API.make_result(execute_raw(method, params = params)) | |
| 71299 | 1025 | |
| 71330 | 1026 | def execute_search[A]( | 
| 75393 | 1027 | method: String, | 
| 1028 | params: JSON.Object.T, | |
| 1029 | unapply: JSON.T => Option[A] | |
| 1030 |     ): List[A] = {
 | |
| 71330 | 1031 | val results = new mutable.ListBuffer[A] | 
| 1032 | var after = "" | |
| 1033 | ||
| 75382 
81673c441ce3
tuned: eliminted do-while for the sake of scala3;
 wenzelm parents: 
74944diff
changeset | 1034 | var cont = true | 
| 
81673c441ce3
tuned: eliminted do-while for the sake of scala3;
 wenzelm parents: 
74944diff
changeset | 1035 |       while (cont) {
 | 
| 71330 | 1036 | val result = | 
| 1037 |           execute(method, params = params ++ JSON.optional("after" -> proper_string(after)))
 | |
| 1038 | results ++= result.get_value(JSON.list(_, "data", unapply)) | |
| 1039 | after = result.get_value(JSON.value(_, "cursor", JSON.string0(_, "after"))) | |
| 75382 
81673c441ce3
tuned: eliminted do-while for the sake of scala3;
 wenzelm parents: 
74944diff
changeset | 1040 | cont = after.nonEmpty | 
| 
81673c441ce3
tuned: eliminted do-while for the sake of scala3;
 wenzelm parents: 
74944diff
changeset | 1041 | } | 
| 71330 | 1042 | |
| 1043 | results.toList | |
| 1044 | } | |
| 1045 | ||
| 71332 | 1046 |     def ping(): String = execute("conduit.ping").get_string
 | 
| 71299 | 1047 | |
| 1048 | ||
| 71332 | 1049 | /* users */ | 
| 71299 | 1050 | |
| 71300 | 1051 |     lazy val user_phid: String = execute("user.whoami").get_value(JSON.string(_, "phid"))
 | 
| 1052 |     lazy val user_name: String = execute("user.whoami").get_value(JSON.string(_, "userName"))
 | |
| 71301 | 1053 | |
| 71332 | 1054 | def get_users( | 
| 1055 | all: Boolean = false, | |
| 1056 | phid: String = "", | |
| 75393 | 1057 | name: String = "" | 
| 1058 |     ): List[API.User] = {
 | |
| 71332 | 1059 | val constraints: JSON.Object.T = | 
| 1060 |         (for { (key, value) <- List("phids" -> phid, "usernames" -> name) if value.nonEmpty }
 | |
| 1061 | yield (key, List(value))).toMap | |
| 1062 | ||
| 1063 |       execute_search("user.search",
 | |
| 1064 |           JSON.Object("queryKey" -> (if (all) "all" else "active"), "constraints" -> constraints),
 | |
| 1065 | data => JSON.value(data, "fields", fields => | |
| 1066 |               for {
 | |
| 1067 | id <- JSON.long(data, "id") | |
| 1068 | phid <- JSON.string(data, "phid") | |
| 1069 | name <- JSON.string(fields, "username") | |
| 1070 | real_name <- JSON.string0(fields, "realName") | |
| 1071 | roles <- JSON.strings(fields, "roles") | |
| 1072 | } yield API.User(id, phid, name, real_name, roles))) | |
| 1073 | } | |
| 1074 | ||
| 1075 | def the_user(phid: String): API.User = | |
| 1076 |       get_users(phid = phid) match {
 | |
| 1077 | case List(user) => user | |
| 1078 |         case _ => error("Bad user PHID " + quote(phid))
 | |
| 1079 | } | |
| 1080 | ||
| 1081 | ||
| 1082 | /* repositories */ | |
| 1083 | ||
| 71306 | 1084 | def get_repositories( | 
| 71331 | 1085 | all: Boolean = false, | 
| 1086 | phid: String = "", | |
| 1087 | callsign: String = "", | |
| 75393 | 1088 | short_name: String = "" | 
| 1089 |     ): List[API.Repository] = {
 | |
| 71306 | 1090 | val constraints: JSON.Object.T = | 
| 1091 |         (for {
 | |
| 1092 |           (key, value) <- List("phids" -> phid, "callsigns" -> callsign, "shortNames" -> short_name)
 | |
| 1093 | if value.nonEmpty | |
| 1094 | } yield (key, List(value))).toMap | |
| 1095 | ||
| 71330 | 1096 |       execute_search("diffusion.repository.search",
 | 
| 71331 | 1097 |           JSON.Object("queryKey" -> (if (all) "all" else "active"), "constraints" -> constraints),
 | 
| 71330 | 1098 | data => JSON.value(data, "fields", fields => | 
| 1099 |               for {
 | |
| 1100 | vcs_name <- JSON.string(fields, "vcs") | |
| 1101 | id <- JSON.long(data, "id") | |
| 1102 | phid <- JSON.string(data, "phid") | |
| 1103 | name <- JSON.string(fields, "name") | |
| 1104 | callsign <- JSON.string0(fields, "callsign") | |
| 1105 | short_name <- JSON.string0(fields, "shortName") | |
| 1106 | importing <- JSON.bool(fields, "isImporting") | |
| 71306 | 1107 | } | 
| 71330 | 1108 |               yield {
 | 
| 78602 | 1109 | val vcs = API.read_vcs(vcs_name) | 
| 71330 | 1110 | val url_path = | 
| 1111 | if (short_name.isEmpty) "/diffusion/" + id else "/source/" + short_name | |
| 1112 | val ssh_url = | |
| 1113 |                   vcs match {
 | |
| 1114 | case API.VCS.hg => hg_url + url_path | |
| 1115 | case API.VCS.git => hg_url + url_path + ".git" | |
| 1116 | case API.VCS.svn => "" | |
| 1117 | } | |
| 1118 | API.Repository(vcs, id, phid, name, callsign, short_name, importing, ssh_url) | |
| 1119 | })) | |
| 71306 | 1120 | } | 
| 71309 | 1121 | |
| 71311 | 1122 | def the_repository(phid: String): API.Repository = | 
| 1123 |       get_repositories(phid = phid) match {
 | |
| 1124 | case List(repo) => repo | |
| 71314 | 1125 |         case _ => error("Bad repository PHID " + quote(phid))
 | 
| 71311 | 1126 | } | 
| 1127 | ||
| 71309 | 1128 | def create_repository( | 
| 1129 | name: String, | |
| 1130 | callsign: String = "", // unique name, UPPERCASE | |
| 1131 | short_name: String = "", // unique name | |
| 1132 | description: String = "", | |
| 1133 | public: Boolean = false, | |
| 78602 | 1134 | vcs: API.VCS = API.VCS.hg | 
| 75393 | 1135 |     ): API.Repository = {
 | 
| 73120 
c3589f2dff31
more informative errors: simplify diagnosis of spurious failures reported by users;
 wenzelm parents: 
72763diff
changeset | 1136 | require(name.nonEmpty, "bad repository name") | 
| 71309 | 1137 | |
| 1138 | val transactions = | |
| 1139 |         API.edits("vcs", vcs.toString) :::
 | |
| 1140 |         API.edits("name", name) :::
 | |
| 1141 |         API.opt_edits("callsign", proper_string(callsign)) :::
 | |
| 1142 |         API.opt_edits("shortName", proper_string(short_name)) :::
 | |
| 1143 |         API.opt_edits("description", proper_string(description)) :::
 | |
| 1144 | (if (public) Nil | |
| 1145 |          else API.edits("view", user_phid) ::: API.edits("policy.push", user_phid)) :::
 | |
| 1146 |         API.edits("status", "active")
 | |
| 1147 | ||
| 71310 | 1148 | val phid = | 
| 71309 | 1149 |         execute("diffusion.repository.edit", params = JSON.Object("transactions" -> transactions))
 | 
| 1150 | .get_value(JSON.value(_, "object", JSON.string(_, "phid"))) | |
| 1151 | ||
| 71310 | 1152 |       execute("diffusion.looksoon", params = JSON.Object("repositories" -> List(phid))).get
 | 
| 71309 | 1153 | |
| 71311 | 1154 | the_repository(phid) | 
| 71309 | 1155 | } | 
| 71299 | 1156 | } | 
| 70967 | 1157 | } |