# HG changeset patch # User wenzelm # Date 1572990966 -3600 # Node ID 9b531e611d66a88dce07a0046d2c834d0fe92c18 # Parent 9858f391ed2d28dd52c115991e63e249664b3d94# Parent 6ca9e83776137e80f5f14ca745e66c43d5dbf1ba merged diff -r 9858f391ed2d -r 9b531e611d66 Admin/Phabricator/README --- a/Admin/Phabricator/README Tue Nov 05 21:07:15 2019 +0100 +++ b/Admin/Phabricator/README Tue Nov 05 22:56:06 2019 +0100 @@ -54,11 +54,11 @@ Port 222 /etc/passwd: - phab-daemon:x:118:126::/home/phab-daemon:/bin/bash + phabricator:x:118:126::/home/phabricator:/bin/bash vcs:x:119:125::/home/vcs:/bin/bash /etc/group: - phab-daemon:x:126: + phabricator:x:126: vcs:x:125: $ cp ssh/ssh-hook /usr/local/bin/. @@ -66,24 +66,24 @@ $ cp ssh/sshd-phabricator.service /lib/systemd/system/. $ cp ssh/sudoers.d/phabricator /etc/sudoers.d/. - $ ./bin/config set phd.user phab-daemon + $ ./bin/config set phd.user phabricator $ ./bin/config set diffusion.ssh-user vcs $ ./bin/config set diffusion.ssh-port 22 + $ systemctl enable sshd-phabricator $ systemctl start sshd-phabricator - $ systemctl enable sshd-phabricator Test on local machine: $ echo "{}" | ssh vcs@phabricator.sketis.net conduit conduit.ping - Repository Local Path: mkdir -p /var/www/phabricator/repo - chown phab-daemon:phab-daemon /var/www/phabricator/repo + chown phabricator:phabricator /var/www/phabricator/repo - PHP Daemon: $ cp phd/phd-phabricator.service /lib/systemd/system/. + $ systemctl enable phd-phabricator $ systemctl start phd-phabricator - $ systemctl enable phd-phabricator - Update: https://secure.phabricator.com/book/phabricator/article/upgrading diff -r 9858f391ed2d -r 9b531e611d66 Admin/Phabricator/phd/phd-phabricator.service --- a/Admin/Phabricator/phd/phd-phabricator.service Tue Nov 05 21:07:15 2019 +0100 +++ b/Admin/Phabricator/phd/phd-phabricator.service Tue Nov 05 22:56:06 2019 +0100 @@ -4,8 +4,8 @@ [Service] Type=oneshot -User=phab-daemon -Group=phab-daemon +User=phabricator +Group=phabricator Environment=PATH=/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin ExecStart=/var/www/phabricator/phabricator/bin/phd start ExecStop=/var/www/phabricator/phabricator/bin/phd stop diff -r 9858f391ed2d -r 9b531e611d66 Admin/Phabricator/ssh/sudoers.d/phabricator --- a/Admin/Phabricator/ssh/sudoers.d/phabricator Tue Nov 05 21:07:15 2019 +0100 +++ b/Admin/Phabricator/ssh/sudoers.d/phabricator Tue Nov 05 22:56:06 2019 +0100 @@ -1,2 +1,2 @@ -www-data ALL=(phab-daemon) SETENV: NOPASSWD: /usr/bin/git, /usr/bin/hg, /usr/bin/ssh, /usr/bin/id -vcs ALL=(phab-daemon) 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 +www-data ALL=(phabricator) SETENV: NOPASSWD: /usr/bin/git, /usr/bin/hg, /usr/bin/ssh, /usr/bin/id +vcs ALL=(phabricator) 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 diff -r 9858f391ed2d -r 9b531e611d66 etc/options --- a/etc/options Tue Nov 05 21:07:15 2019 +0100 +++ b/etc/options Tue Nov 05 22:56:06 2019 +0100 @@ -345,8 +345,6 @@ section "Phabricator server" -option phabricator_user : string = "phabricator" - option phabricator_www_user : string = "www-data" option phabricator_www_root : string = "/var/www" diff -r 9858f391ed2d -r 9b531e611d66 src/Pure/System/linux.scala --- a/src/Pure/System/linux.scala Tue Nov 05 21:07:15 2019 +0100 +++ b/src/Pure/System/linux.scala Tue Nov 05 22:56:06 2019 +0100 @@ -63,4 +63,73 @@ def package_install(packages: List[String], progress: Progress = No_Progress): Unit = progress.bash("apt-get install -y -- " + Bash.strings(packages), echo = true).check + + + /* users */ + + def user_exists(name: String): Boolean = + Isabelle_System.bash("id " + Bash.string(name)).ok + + def user_entry(name: String, field: Int): String = + { + val result = Isabelle_System.bash("getent passwd " + Bash.string(name)).check + val fields = space_explode(':', result.out) + + if (1 <= field && field <= fields.length) fields(field - 1) + else error("No passwd field " + field + " for user " + quote(name)) + } + + def user_description(name: String): String = user_entry(name, 5).takeWhile(_ != ',') + + def user_home(name: String): String = user_entry(name, 6) + + def user_add(name: String, + description: String = "", + system: Boolean = false, + ssh_setup: Boolean = false) + { + require(!description.contains(',')) + + if (user_exists(name)) error("User already exists: " + quote(name)) + + Isabelle_System.bash( + "adduser --quiet --disabled-password --gecos " + Bash.string(description) + + (if (system) " --system --group --shell /bin/bash " else "") + + " " + Bash.string(name)).check + + if (ssh_setup) { + val id_rsa = user_home(name) + "/.ssh/id_rsa" + Isabelle_System.bash(""" +if [ ! -f """ + Bash.string(id_rsa) + """ ] +then + yes '\n' | sudo -i -u """ + Bash.string(name) + + """ ssh-keygen -q -f """ + Bash.string(id_rsa) + """ +fi + """).check + } + } + + + /* system services */ + + def service_start(name: String): Unit = + Isabelle_System.bash("systemctl start " + Bash.string(name)).check + + def service_stop(name: String): Unit = + Isabelle_System.bash("systemctl stop " + Bash.string(name)).check + + def service_restart(name: String): Unit = + Isabelle_System.bash("systemctl restart " + Bash.string(name)).check + + def service_install(name: String, spec: String) + { + val service_file = Path.explode("/lib/systemd/system") + Path.basic(name).ext("service") + File.write(service_file, spec) + + Isabelle_System.bash(""" + set -e + chmod 0644 """ + File.bash_path(service_file) + """ + systemctl enable """ + Bash.string(name) + """ + systemctl start """ + Bash.string(name)).check + } } diff -r 9858f391ed2d -r 9b531e611d66 src/Pure/Tools/phabricator.scala --- a/src/Pure/Tools/phabricator.scala Tue Nov 05 21:07:15 2019 +0100 +++ b/src/Pure/Tools/phabricator.scala Tue Nov 05 22:56:06 2019 +0100 @@ -16,15 +16,7 @@ { /** defaults **/ - val default_name = "vcs" - - def default_prefix(name: String): String = "phabricator-" + name - - def default_root(options: Options, name: String): Path = - Path.explode(options.string("phabricator_www_root")) + Path.basic(default_prefix(name)) - - def default_repo(options: Options, name: String): Path = - default_root(options, name) + Path.basic("repo") + /* required packages */ val packages: List[String] = Build_Docker.packages ::: @@ -33,17 +25,44 @@ "git", "mysql-server", "apache2", "libapache2-mod-php", "php", "php-mysql", "php-gd", "php-curl", "php-apcu", "php-cli", "php-json", "php-mbstring", // more packages - "php-zip", "python-pygments") + "php-zip", "python-pygments", "ssh") + + + /* global system resources */ + + val daemon_user = "phabricator" + + val ssh_standard = 22 + val ssh_alternative1 = 222 + val ssh_alternative2 = 2222 + + + /* installation parameters */ + + val default_name = "vcs" + + def phabricator_name(name: String = "", ext: String = ""): String = + "phabricator" + (if (name.isEmpty) "" else "-" + name) + (if (ext.isEmpty) "" else "." + ext) + + def isabelle_phabricator_name(name: String = "", ext: String = ""): String = + "isabelle-" + phabricator_name(name = name, ext = ext) + + def default_root(options: Options, name: String): Path = + Path.explode(options.string("phabricator_www_root")) + + Path.basic(phabricator_name(name = name)) + + def default_repo(options: Options, name: String): Path = + default_root(options, name) + Path.basic("repo") /** global configuration **/ - val global_config = Path.explode("/etc/isabelle-phabricator.conf") + val global_config = Path.explode("/etc/" + isabelle_phabricator_name(ext = "conf")) sealed case class Config(name: String, root: Path) { - def home: Path = root + Path.explode("phabricator") + def home: Path = root + Path.explode(phabricator_name()) def execute(command: String): Process_Result = Isabelle_System.bash("./bin/" + command, cwd = home.file).check @@ -77,28 +96,52 @@ /** setup **/ + def user_setup(name: String, description: String, ssh_setup: Boolean = false) + { + if (!Linux.user_exists(name)) { + Linux.user_add(name, description = description, system = true, ssh_setup = ssh_setup) + } + else if (Linux.user_description(name) != description) { + error("User " + quote(name) + " already exists --" + + " for Phabricator it should have the description:\n " + quote(description)) + } + } + def phabricator_setup( options: Options, name: String = default_name, - prefix: String = "", root: String = "", repo: String = "", + package_update: Boolean = false, progress: Progress = No_Progress) { /* system environment */ Linux.check_system_root() - Linux.package_update(progress = progress) - Linux.check_reboot_required() + if (package_update) { + Linux.package_update(progress = progress) + Linux.check_reboot_required() + } Linux.package_install(packages, progress = progress) Linux.check_reboot_required() + /* users */ + + if (name == daemon_user) { + error("Clash of installation name with daemon user " + quote(daemon_user)) + } + + user_setup(daemon_user, "Phabricator Daemon User", ssh_setup = true) + user_setup(name, "Phabricator SSH User") + + val www_user = options.string("phabricator_www_user") + + /* basic installation */ - val prefix_name = proper_string(prefix) getOrElse default_prefix(name) val root_path = if (root.nonEmpty) Path.explode(root) else default_root(options, name) val repo_path = if (repo.nonEmpty) Path.explode(repo) else default_repo(options, name) @@ -115,7 +158,7 @@ progress.bash(cwd = root_path.file, echo = true, script = """ set -e - chown """ + Bash.string(options.string("phabricator_www_user")) + """ . + chown """ + Bash.string(www_user) + ":" + Bash.string(www_user) + """ . chmod 755 . git clone https://github.com/phacility/libphutil.git @@ -126,11 +169,39 @@ val config = Config(name, root_path) write_config(configs ::: List(config)) + config.execute("config set pygments.enabled true") + + + /* local repository directory */ + + if (!Isabelle_System.bash("mkdir -p " + File.bash_path(repo_path)).ok) { + error("Failed to create local repository directory " + repo_path) + } + + Isabelle_System.bash(cwd = repo_path.file, + script = """ + set -e + chown -R """ + Bash.string(daemon_user) + ":" + Bash.string(daemon_user) + """ . + chmod 755 . + """).check + + config.execute("config set repository.default-local-path " + File.bash_path(repo_path)) + /* MySQL setup */ progress.echo("MySQL setup...") + File.write(Path.explode("/etc/mysql/mysql.conf.d/" + phabricator_name(ext = "cnf")), +"""[mysqld] +max_allowed_packet = 32M +innodb_buffer_pool_size = 1600M +local_infile = 0 +""") + + Linux.service_restart("mysql") + + def mysql_conf(R: Regex): Option[String] = split_lines(File.read(Path.explode(options.string("phabricator_mysql_config")))). collectFirst({ case R(a) => a }) @@ -144,11 +215,47 @@ } config.execute("config set storage.default-namespace " + - Bash.string(prefix_name.replace("-", "_"))) + Bash.string(phabricator_name(name = name).replace("-", "_"))) + + config.execute("config set storage.mysql-engine.max-size 8388608") config.execute("storage upgrade --force") + /* SSH hosting */ + + progress.echo("SSH hosting setup...") + + val ssh_port = ssh_alternative2 + + config.execute("config set diffusion.ssh-user " + Bash.string(name)) + config.execute("config set diffusion.ssh-port " + ssh_port) + + val sudoers_file = Path.explode("/etc/sudoers.d") + Path.basic(isabelle_phabricator_name()) + File.write(sudoers_file, + www_user + " ALL=(" + daemon_user + ") SETENV: NOPASSWD: /usr/bin/git, /usr/bin/hg, /usr/bin/ssh, /usr/bin/id\n" + + 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") + + Isabelle_System.bash("chmod 0440 " + File.bash_path(sudoers_file)).check + + + /* PHP setup */ + + val php_version = + Isabelle_System.bash("""php --run 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;'""") + .check.out + + val php_conf = + Path.explode("/etc/php") + Path.basic(php_version) + // educated guess + Path.explode("apache2/conf.d") + + Path.basic(isabelle_phabricator_name(ext = "ini")) + + File.write(php_conf, + "post_max_size = 32M\n" + + "opcache.validate_timestamps = 0\n" + + "memory_limit = 512M\n") + + /* Apache setup */ progress.echo("Apache setup...") @@ -158,10 +265,12 @@ if (!apache_sites.is_dir) error("Bad Apache sites directory " + apache_sites) - File.write(apache_sites + Path.basic(prefix_name + ".conf"), + val server_name = phabricator_name(name = name, ext = "lvh.me") // alias for "localhost" for testing + val server_url = "http://" + server_name + + File.write(apache_sites + Path.basic(isabelle_phabricator_name(name = name, ext = "conf")), """ - #ServerName: "lvh.me" is an alias for "localhost" for testing - ServerName """ + prefix_name + """.lvh.me + ServerName """ + server_name + """ ServerAdmin webmaster@localhost DocumentRoot """ + config.home.implode + """/webroot @@ -173,14 +282,42 @@ # vim: syntax=apache ts=4 sw=4 sts=4 sr noet """) - Isabelle_System.bash(""" + Isabelle_System.bash( """ set -e a2enmod rewrite - a2ensite """ + Bash.string(prefix_name) + """ - systemctl restart apache2 -""").check + a2ensite """ + Bash.string(isabelle_phabricator_name(name = name))).check + + config.execute("config set phabricator.base-uri " + Bash.string(server_url)) + + Linux.service_restart("apache2") + + + /* PHP daemon */ + + progress.echo("PHP daemon setup...") + + config.execute("config set phd.user " + Bash.string(daemon_user)) - progress.echo("\nDONE\nWeb configuration via http://" + prefix_name + ".lvh.me") + Linux.service_install(isabelle_phabricator_name(name = name), +"""[Unit] +Description=PHP daemon for Isabelle/Phabricator """ + quote(name) + """ +After=syslog.target network.target apache2.service mysql.service + +[Service] +Type=oneshot +User=""" + daemon_user + """ +Group=""" + daemon_user + """ +Environment=PATH=/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin +ExecStart=""" + config.home.implode + """/bin/phd start +ExecStop=""" + config.home.implode + """/bin/phd stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +""") + + + progress.echo("\nDONE\nWeb configuration via " + server_url) } @@ -189,10 +326,10 @@ val isabelle_tool1 = Isabelle_Tool("phabricator_setup", "setup Phabricator server on Ubuntu Linux", args => { + var repo = "" + var package_update = false var options = Options.init() - var prefix = "" var root = "" - var repo = "" val getopts = Getopts(""" @@ -200,8 +337,8 @@ Options are: -R DIR repository directory (default: """ + default_repo(options, "NAME") + """) + -U full update of system packages before installation -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME) - -p PREFIX prefix for derived names (default: """ + default_prefix("NAME") + """) -r DIR installation root directory (default: """ + default_root(options, "NAME") + """) Install Phabricator as Ubuntu LAMP application (Linux, Apache, MySQL, PHP). @@ -213,8 +350,8 @@ a regular Unix user and used for public SSH access. """, "R:" -> (arg => repo = arg), + "U" -> (_ => package_update = true), "o:" -> (arg => options = options + arg), - "p:" -> (arg => prefix = arg), "r:" -> (arg => root = arg)) val more_args = getopts(args) @@ -228,8 +365,8 @@ val progress = new Console_Progress - phabricator_setup(options, name, prefix = prefix, root = root, repo = repo, - progress = progress) + phabricator_setup(options, name, root = root, repo = repo, + package_update = package_update, progress = progress) })