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