--- 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
--- 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
--- 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
--- 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"
--- 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
+ }
}
--- 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")),
"""<VirtualHost *:80>
- #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)
})