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