| author | wenzelm | 
| Fri, 24 Sep 2021 22:23:26 +0200 | |
| changeset 74362 | 0135a0c77b64 | 
| parent 73534 | e7fb17bca374 | 
| child 75393 | 87ebf5a50283 | 
| permissions | -rw-r--r-- | 
| 70965 | 1 | /* Title: Pure/System/linux.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 4 | Specific support for Linux, notably Ubuntu/Debian. | |
| 5 | */ | |
| 6 | ||
| 7 | package isabelle | |
| 8 | ||
| 9 | ||
| 10 | import scala.util.matching.Regex | |
| 11 | ||
| 12 | ||
| 13 | object Linux | |
| 14 | {
 | |
| 15 | /* check system */ | |
| 16 | ||
| 17 | def check_system(): Unit = | |
| 18 |     if (!Platform.is_linux) error("Not a Linux system")
 | |
| 19 | ||
| 20 | def check_system_root(): Unit = | |
| 21 |   {
 | |
| 22 | check_system() | |
| 23 |     if (Isabelle_System.bash("id -u").check.out != "0") error("Not running as superuser (root)")
 | |
| 24 | } | |
| 25 | ||
| 26 | ||
| 27 | /* release */ | |
| 28 | ||
| 29 | object Release | |
| 30 |   {
 | |
| 31 | private val ID = """^Distributor ID:\s*(\S.*)$""".r | |
| 32 | private val RELEASE = """^Release:\s*(\S.*)$""".r | |
| 33 | private val DESCRIPTION = """^Description:\s*(\S.*)$""".r | |
| 34 | ||
| 35 | def apply(): Release = | |
| 36 |     {
 | |
| 37 |       val lines = Isabelle_System.bash("lsb_release -a").check.out_lines
 | |
| 38 |       def find(R: Regex): String = lines.collectFirst({ case R(a) => a }).getOrElse("Unknown")
 | |
| 39 | new Release(find(ID), find(RELEASE), find(DESCRIPTION)) | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | final class Release private(val id: String, val release: String, val description: String) | |
| 44 |   {
 | |
| 45 | override def toString: String = description | |
| 46 | ||
| 47 | def is_ubuntu: Boolean = id == "Ubuntu" | |
| 72521 | 48 | def is_ubuntu_20_04: Boolean = is_ubuntu && release == "20.04" | 
| 70965 | 49 | } | 
| 70966 | 50 | |
| 51 | ||
| 52 | /* packages */ | |
| 53 | ||
| 54 | def reboot_required(): Boolean = | |
| 55 |     Path.explode("/var/run/reboot-required").is_file
 | |
| 56 | ||
| 57 | def check_reboot_required(): Unit = | |
| 58 |     if (reboot_required()) error("Reboot required")
 | |
| 59 | ||
| 71726 
a5fda30edae2
clarified signature: more uniform treatment of stopped/interrupted state;
 wenzelm parents: 
71327diff
changeset | 60 | def package_update(progress: Progress = new Progress): Unit = | 
| 70966 | 61 | progress.bash( | 
| 62 | """apt-get update -y && apt-get upgrade -y && apt autoremove -y""", | |
| 63 | echo = true).check | |
| 64 | ||
| 71726 
a5fda30edae2
clarified signature: more uniform treatment of stopped/interrupted state;
 wenzelm parents: 
71327diff
changeset | 65 | def package_install(packages: List[String], progress: Progress = new Progress): Unit = | 
| 70966 | 66 |     progress.bash("apt-get install -y -- " + Bash.strings(packages), echo = true).check
 | 
| 71046 | 67 | |
| 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: 
71268diff
changeset | 68 | def package_installed(name: String): Boolean = | 
| 71327 | 69 |   {
 | 
| 70 |     val result = Isabelle_System.bash("dpkg-query -s " + Bash.string(name))
 | |
| 71 | val pattern = """^Status:.*installed.*$""".r.pattern | |
| 72 | result.ok && result.out_lines.exists(line => pattern.matcher(line).matches) | |
| 73 | } | |
| 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: 
71268diff
changeset | 74 | |
| 71046 | 75 | |
| 76 | /* users */ | |
| 77 | ||
| 78 | def user_exists(name: String): Boolean = | |
| 79 |     Isabelle_System.bash("id " + Bash.string(name)).ok
 | |
| 80 | ||
| 81 | def user_entry(name: String, field: Int): String = | |
| 82 |   {
 | |
| 83 |     val result = Isabelle_System.bash("getent passwd " + Bash.string(name)).check
 | |
| 84 |     val fields = space_explode(':', result.out)
 | |
| 85 | ||
| 86 | if (1 <= field && field <= fields.length) fields(field - 1) | |
| 87 |     else error("No passwd field " + field + " for user " + quote(name))
 | |
| 88 | } | |
| 89 | ||
| 90 | def user_description(name: String): String = user_entry(name, 5).takeWhile(_ != ',') | |
| 91 | ||
| 92 | def user_home(name: String): String = user_entry(name, 6) | |
| 93 | ||
| 71054 
b64fc38327ae
prefer system user setup, e.g. avoid occurrence on login screen;
 wenzelm parents: 
71051diff
changeset | 94 | def user_add(name: String, | 
| 
b64fc38327ae
prefer system user setup, e.g. avoid occurrence on login screen;
 wenzelm parents: 
71051diff
changeset | 95 | description: String = "", | 
| 
b64fc38327ae
prefer system user setup, e.g. avoid occurrence on login screen;
 wenzelm parents: 
71051diff
changeset | 96 | system: Boolean = false, | 
| 73340 | 97 | ssh_setup: Boolean = false): Unit = | 
| 71046 | 98 |   {
 | 
| 73120 
c3589f2dff31
more informative errors: simplify diagnosis of spurious failures reported by users;
 wenzelm parents: 
72521diff
changeset | 99 |     require(!description.contains(','), "malformed description")
 | 
| 71046 | 100 | |
| 101 |     if (user_exists(name)) error("User already exists: " + quote(name))
 | |
| 102 | ||
| 103 | Isabelle_System.bash( | |
| 104 | "adduser --quiet --disabled-password --gecos " + Bash.string(description) + | |
| 71054 
b64fc38327ae
prefer system user setup, e.g. avoid occurrence on login screen;
 wenzelm parents: 
71051diff
changeset | 105 | (if (system) " --system --group --shell /bin/bash " else "") + | 
| 71046 | 106 | " " + Bash.string(name)).check | 
| 107 | ||
| 108 |     if (ssh_setup) {
 | |
| 109 | val id_rsa = user_home(name) + "/.ssh/id_rsa" | |
| 110 |       Isabelle_System.bash("""
 | |
| 111 | if [ ! -f """ + Bash.string(id_rsa) + """ ] | |
| 112 | then | |
| 113 | yes '\n' | sudo -i -u """ + Bash.string(name) + | |
| 114 | """ ssh-keygen -q -f """ + Bash.string(id_rsa) + """ | |
| 115 | fi | |
| 116 | """).check | |
| 117 | } | |
| 118 | } | |
| 71048 | 119 | |
| 120 | ||
| 121 | /* system services */ | |
| 122 | ||
| 71107 | 123 | def service_operation(op: String, name: String): Unit = | 
| 124 |     Isabelle_System.bash("systemctl " + Bash.string(op) + " " + Bash.string(name)).check
 | |
| 71048 | 125 | |
| 73340 | 126 |   def service_enable(name: String): Unit = service_operation("enable", name)
 | 
| 127 |   def service_disable(name: String): Unit = service_operation("disable", name)
 | |
| 128 |   def service_start(name: String): Unit = service_operation("start", name)
 | |
| 129 |   def service_stop(name: String): Unit = service_operation("stop", name)
 | |
| 130 |   def service_restart(name: String): Unit = service_operation("restart", name)
 | |
| 71051 | 131 | |
| 73340 | 132 | def service_shutdown(name: String): Unit = | 
| 71111 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71108diff
changeset | 133 |     try { service_stop(name) }
 | 
| 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71108diff
changeset | 134 |     catch { case ERROR(_) => }
 | 
| 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71108diff
changeset | 135 | |
| 73340 | 136 | def service_install(name: String, spec: String): Unit = | 
| 71048 | 137 |   {
 | 
| 71111 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71108diff
changeset | 138 | service_shutdown(name) | 
| 
cd166c3904dd
more robust: system ssh service is required for Phabricator ssh service;
 wenzelm parents: 
71108diff
changeset | 139 | |
| 71048 | 140 |     val service_file = Path.explode("/lib/systemd/system") + Path.basic(name).ext("service")
 | 
| 141 | File.write(service_file, spec) | |
| 71115 | 142 |     Isabelle_System.chmod("644", service_file)
 | 
| 71048 | 143 | |
| 71107 | 144 | service_enable(name) | 
| 71108 | 145 | service_restart(name) | 
| 71048 | 146 | } | 
| 71265 | 147 | |
| 148 | ||
| 149 | /* passwords */ | |
| 150 | ||
| 151 | def generate_password(length: Int = 10): String = | |
| 152 |   {
 | |
| 73120 
c3589f2dff31
more informative errors: simplify diagnosis of spurious failures reported by users;
 wenzelm parents: 
72521diff
changeset | 153 | require(length >= 6, "password too short") | 
| 71265 | 154 |     Isabelle_System.bash("pwgen " + length + " 1").check.out
 | 
| 155 | } | |
| 70965 | 156 | } |