]> git.ipfire.org Git - people/ms/bricklayer.git/blob - src/python/__init__.py
Add debug shell
[people/ms/bricklayer.git] / src / python / __init__.py
1 ###############################################################################
2 # #
3 # Bricklayer - An Installer for IPFire #
4 # Copyright (C) 2021 IPFire Development Team #
5 # #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 ###############################################################################
20
21 import logging
22 import subprocess
23 import sys
24 import tempfile
25 import traceback
26
27 import pakfire
28
29 from . import bootloaders
30 from . import disk
31 from . import i18n
32 from . import logger
33 from . import packages
34 from . import step
35 from . import timezones
36 from . import tui
37 from . import util
38 from .i18n import _
39 from .errors import *
40
41 # Setup logging
42 log = logging.getLogger("bricklayer")
43
44 class Bricklayer(object):
45 """
46 Bricklayer's base class
47 """
48 def __init__(self, arch, first_install=False, debug=False, unattended=False, disks=[]):
49 self.arch = arch
50 self.first_install = first_install
51 self.debug = debug
52 self.unattended = unattended
53
54 # Enable debug logging
55 if debug:
56 log.setLevel(logging.DEBUG)
57
58 # Settings
59 self.settings = {
60 "language" : i18n.default_language,
61
62 "packages" : [
63 # Install the Base group
64 "@Base",
65 ],
66
67 # Set the default swap size to 1 GiB
68 "swap-size": 1024 ** 3,
69
70 # Default timezone
71 "timezone" : "UTC",
72 }
73
74 # Read OS information
75 self.os = self._read_os_release()
76
77 # Select a bootloaders
78 self.bootloaders = self._select_bootloaders()
79
80 # Hardware
81 self.disks = disk.Disks(self)
82 for path in disks:
83 self.disks.add_disk(path)
84
85 # Initialise the text user interface
86 self.tui = tui.Tui(self)
87
88 # Create root directory
89 self.root = tempfile.mkdtemp(prefix="bricklayer-", suffix="-root")
90
91 # Log when we are ready
92 log.info("Bricklayer initialized")
93
94 # An ordered list of all available steps
95 steps = (
96 step.UnattendedWarning,
97 step.Welcome,
98 timezones.SelectTimezone,
99 disk.SelectDisk,
100 disk.CalculatePartitionLayout,
101 step.RootPassword,
102
103 # Go!
104 disk.CreatePartitionLayout,
105 disk.CreateFilesystems,
106 disk.MountFilesystems,
107 packages.InstallPackages,
108 bootloaders.InstallBootloader,
109
110 # Done!
111 step.ToggleFirstInstallStatus,
112 disk.UmountFilesystems,
113 step.Congratulations,
114 )
115
116 def __call__(self):
117 with self.tui:
118 try:
119 # Walk through all steps
120 for step in self.steps:
121 try:
122 self._run_step(step)
123
124 # End installer if aborted
125 except InstallAbortedError:
126 return 1
127
128 # The user requested to cancel the installation process
129 except UserCanceledError:
130 if self.tui.confirm(
131 _("Cancel Installation?"),
132 _("Are you sure that you want to cancel the installation process?"),
133 ):
134 return 0
135
136 # Catch any failed commands
137 except subprocess.CalledProcessError as e:
138 args = {
139 "command" : " ".join(e.cmd),
140 "output" : e.output.decode(),
141 "returncode" : e.returncode,
142 }
143
144 # Log the error
145 log.error("Command \"%(command)s\" failed with error code "
146 "%(returncode)s:\n%(output)s" % args)
147
148 # Format the error message
149 error = _("Command \"%(command)s\" failed with error code "
150 "%(returncode)s:\n\n%(output)s" % args)
151
152 # Show it
153 self.tui.error(_("An Unexpected Error Occured"), error,
154 buttons=[_("Exit")], width=78)
155
156 # Exit
157 return e.returncode
158
159 # Catch all other exceptions and show an error
160 except:
161 type, value, tb = sys.exc_info()
162
163 # Log the error
164 log.error("An unexpected error occured:", exc_info=True)
165
166 # Format the exception
167 error = _("The installation cannot be continued due to an error:"
168 "\n\n%s") % "".join(traceback.format_exception(type, value, tb))
169
170 # Show an error message
171 self.tui.error(_("An Unexpected Error Occured"),
172 "".join(error), buttons=[_("Exit")], width=78)
173
174 # Exit
175 return 1
176
177 # Cleanup when we leave
178 finally:
179 self.disks.tear_down()
180
181 def _run_step(self, stepcls):
182 """
183 Runs a single step
184 """
185 # Initialize the step
186 step = stepcls(self, tui=self.tui)
187
188 # Skip this step if it isn't enabled in first install mode
189 if self.first_install and not step.first_install:
190 return
191
192 # Skip this step if it isn't enabled
193 if not step.enabled:
194 return
195
196 # Run it
197 return step.run()
198
199 def _read_os_release(self):
200 """
201 Read /etc/os-release
202 """
203 with open("/etc/os-release") as f:
204 return util.config_read(f)
205
206 def command(self, command, error_ok=False, interactive=False):
207 """
208 Runs a command in a shell environment
209 """
210 log.debug("Running command: %s" % " ".join(command))
211
212 args = {}
213
214 if not interactive:
215 args.update({
216 "stdout" : subprocess.PIPE,
217 "stderr" : subprocess.STDOUT,
218 })
219
220 # Execute the command
221 p = subprocess.run(command, **args)
222
223 # Check the return code (raises CalledProcessError on non-zero)
224 if not error_ok:
225 p.check_returncode()
226
227 # There is no output in interactive mode
228 if interactive:
229 return
230
231 # Decode output
232 output = p.stdout.decode()
233
234 # Log output
235 if output:
236 log.debug(output)
237
238 return output
239
240 def setup_pakfire(self):
241 """
242 Calls Pakfire and has it load its configuration
243 """
244 return pakfire.Pakfire(self.root, arch=self.arch,
245 conf="/etc/pakfire/distros/ipfire3.conf")
246
247 def _select_bootloaders(self):
248 """
249 Select all bootloaders for this installation process
250 """
251 _bootloaders = (
252 bootloaders.Grub(self),
253 bootloaders.GrubEFI(self),
254 )
255
256 # Return all supported bootloaders
257 return [bl for bl in _bootloaders if bl.supported]