def __init__(self, opts): if is_linux_tty(): self.input_filter = KeyCodesFilter() else: self.input_filter = DummyKeycodesFilter() self.help_menu = HelpMenu(self) super().__init__(opts) self.interactive = None self.server_updated = None self.restarting_server = False self.global_overlays = [] try: self.our_tty = os.ttyname(0) except OSError: self.our_tty = "not a tty" self.conn = aiohttp.UnixConnector(self.opts.socket) self.client = make_client_for_conn(API, self.conn, self.resp_hook) self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root, self.client) self.note_data_for_apport("SnapUpdated", str(self.updated)) self.note_data_for_apport("UsingAnswers", str(bool(self.answers)))
def __init__(self, opts, block_log_dir): super().__init__(opts) self.block_log_dir = block_log_dir self.cloud_init_ok = None self._state = ApplicationState.STARTING_UP self.state_event = asyncio.Event() self.interactive = None self.confirming_tty = '' self.fatal_error = None self.running_error_commands = False self.echo_syslog_id = 'subiquity_echo.{}'.format(os.getpid()) self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid()) self.log_syslog_id = 'subiquity_log.{}'.format(os.getpid()) self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root) self.prober = Prober(opts.machine_config, self.debug_flags) self.kernel_cmdline = shlex.split(opts.kernel_cmdline) if opts.snaps_from_examples: connection = FakeSnapdConnection( os.path.join( os.path.dirname(os.path.dirname( os.path.dirname(__file__))), "examples", "snaps"), self.scale_factor) else: connection = SnapdConnection(self.root, self.snapd_socket_path) self.snapd = AsyncSnapd(connection) self.note_data_for_apport("SnapUpdated", str(self.updated)) self.event_listeners = [] self.autoinstall_config = None self.signal.connect_signals([ ('network-proxy-set', lambda: schedule_task(self._proxy_set())), ('network-change', self._network_change), ])
def __init__(self, opts, block_log_dir): if is_linux_tty(): self.input_filter = KeyCodesFilter() else: self.input_filter = DummyKeycodesFilter() self.help_menu = HelpMenu(self) super().__init__(opts) self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid()) self.log_syslog_id = 'subiquity_log.{}'.format(os.getpid()) self.server_updated = None self.restarting_server = False self.prober = Prober(opts.machine_config, self.debug_flags) journald_listen(self.aio_loop, ["subiquity"], self.subiquity_event, seek=True) self.event_listeners = [] self.install_lock_file = Lockfile(self.state_path("installing")) self.global_overlays = [] self.block_log_dir = block_log_dir self.kernel_cmdline = shlex.split(opts.kernel_cmdline) if opts.snaps_from_examples: connection = FakeSnapdConnection( os.path.join(os.path.dirname(os.path.dirname(__file__)), "examples", "snaps"), self.scale_factor) else: connection = SnapdConnection(self.root, self.snapd_socket_path) self.snapd = AsyncSnapd(connection) self.signal.connect_signals([ ('network-proxy-set', lambda: schedule_task(self._proxy_set())), ('network-change', self._network_change), ]) self.conn = aiohttp.UnixConnector(self.opts.socket) self.client = make_client_for_conn(API, self.conn, self.resp_hook) self.autoinstall_config = {} self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root, self.client) self.note_data_for_apport("SnapUpdated", str(self.updated)) self.note_data_for_apport("UsingAnswers", str(bool(self.answers)))
def __init__(self, opts): super().__init__(opts) self.status = ApplicationState.STARTING self.server_proc = None self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root)
class SubiquityServer(Application): project = "subiquity" from subiquity.server import controllers as controllers_mod controllers = [] def make_model(self): return None def __init__(self, opts): super().__init__(opts) self.status = ApplicationState.STARTING self.server_proc = None self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root) def note_file_for_apport(self, key, path): self.error_reporter.note_file_for_apport(key, path) def note_data_for_apport(self, key, value): self.error_reporter.note_data_for_apport(key, value) def make_apport_report(self, kind, thing, *, wait=False, **kw): return self.error_reporter.make_apport_report( kind, thing, wait=wait, **kw) @web.middleware async def middleware(self, request, handler): if self.updated: updated = 'yes' else: updated = 'no' controller = await controller_for_request(request) if isinstance(controller, SubiquityController): if not controller.interactive(): return web.Response( headers={'x-status': 'skip', 'x-updated': updated}) elif self.base_model.needs_confirmation(controller.model_name): return web.Response( headers={'x-status': 'confirm', 'x-updated': updated}) resp = await handler(request) resp.headers['x-updated'] = updated if resp.get('exception'): exc = resp['exception'] log.debug( 'request to {} crashed'.format(request.raw_path), exc_info=exc) report = self.make_apport_report( ErrorReportKind.SERVER_REQUEST_FAIL, "request to {}".format(request.raw_path), exc=exc) resp.headers['x-error-report'] = to_json( ErrorReportRef, report.ref()) return resp async def start_api_server(self): app = web.Application(middlewares=[self.middleware]) bind(app.router, API.meta, MetaController(self)) bind(app.router, API.errors, ErrorController(self)) if self.opts.dry_run: from .dryrun import DryRunController bind(app.router, API.dry_run, DryRunController(self)) runner = web.AppRunner(app) await runner.setup() site = web.UnixSite(runner, self.opts.socket) await site.start() async def start(self): await super().start() await self.start_api_server() def restart(self): cmdline = ['snap', 'run', 'subiquity'] if self.opts.dry_run: cmdline = [ sys.executable, '-m', 'subiquity.cmd.server', ] + sys.argv[1:] os.execvp(cmdline[0], cmdline)
class SubiquityClient(TuiApplication): snapd_socket_path = '/run/snapd.socket' from subiquity.client import controllers as controllers_mod project = "subiquity" def make_model(self): return None def make_ui(self): return SubiquityUI(self, self.help_menu) controllers = [ "Welcome", "Refresh", "Keyboard", "Zdev", "Network", "Proxy", "Mirror", "Refresh", "Filesystem", "Identity", "SSH", "SnapList", "Progress", ] def __init__(self, opts): if is_linux_tty(): self.input_filter = KeyCodesFilter() else: self.input_filter = DummyKeycodesFilter() self.help_menu = HelpMenu(self) super().__init__(opts) self.interactive = None self.server_updated = None self.restarting_server = False self.global_overlays = [] try: self.our_tty = os.ttyname(0) except OSError: self.our_tty = "not a tty" self.conn = aiohttp.UnixConnector(self.opts.socket) self.client = make_client_for_conn(API, self.conn, self.resp_hook) self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root, self.client) self.note_data_for_apport("SnapUpdated", str(self.updated)) self.note_data_for_apport("UsingAnswers", str(bool(self.answers))) async def _restart_server(self): log.debug("_restart_server") try: await self.client.meta.restart.POST() except aiohttp.ServerDisconnectedError: pass self.restart(remove_last_screen=False) def restart(self, remove_last_screen=True, restart_server=False): log.debug(f"restart {remove_last_screen} {restart_server}") if remove_last_screen: self._remove_last_screen() if restart_server: self.restarting_server = True self.ui.block_input = True self.aio_loop.create_task(self._restart_server()) return if self.urwid_loop is not None: self.urwid_loop.stop() cmdline = ['snap', 'run', 'subiquity'] if self.opts.dry_run: cmdline = [ sys.executable, '-m', 'subiquity.cmd.tui', ] + sys.argv[1:] + ['--socket', self.opts.socket] if self.opts.server_pid is not None: cmdline.extend(['--server-pid', self.opts.server_pid]) log.debug("restarting %r", cmdline) os.execvp(cmdline[0], cmdline) def resp_hook(self, response): headers = response.headers if 'x-updated' in headers: if self.server_updated is None: self.server_updated = headers['x-updated'] elif self.server_updated != headers['x-updated']: self.restart(remove_last_screen=False) status = headers.get('x-status') if status == 'skip': raise Skip elif status == 'confirm': raise Confirm if headers.get('x-error-report') is not None: ref = from_json(ErrorReportRef, headers['x-error-report']) raise Abort(ref) try: response.raise_for_status() except aiohttp.ClientError: report = self.error_reporter.make_apport_report( ErrorReportKind.SERVER_REQUEST_FAIL, "request to {}".format(response.url.path)) raise Abort(report.ref()) return response async def noninteractive_confirmation(self): await asyncio.sleep(1) yes = _('yes') no = _('no') answer = no print(_("Confirmation is required to continue.")) print(_("Add 'autoinstall' to your kernel command line to avoid this")) print() prompt = "\n\n{} ({}|{})".format(_("Continue with autoinstall?"), yes, no) while answer != yes: print(prompt) answer = await run_in_thread(input) await self.confirm_install() async def noninteractive_watch_app_state(self, initial_status): app_status = initial_status confirm_task = None while True: app_state = app_status.state if app_state == ApplicationState.NEEDS_CONFIRMATION: if confirm_task is None: confirm_task = self.aio_loop.create_task( self.noninteractive_confirmation()) elif confirm_task is not None: confirm_task.cancel() confirm_task = None try: app_status = await self.client.meta.status.GET(cur=app_state) except aiohttp.ClientError: await asyncio.sleep(1) continue def subiquity_event_noninteractive(self, event): if event['SUBIQUITY_EVENT_TYPE'] == 'start': print('start: ' + event["MESSAGE"]) elif event['SUBIQUITY_EVENT_TYPE'] == 'finish': print('finish: ' + event["MESSAGE"]) context_name = event.get('SUBIQUITY_CONTEXT_NAME', '') if context_name == 'subiquity/Reboot/reboot': self.exit() async def connect(self): print("connecting...", end='', flush=True) while True: try: status = await self.client.meta.status.GET() except aiohttp.ClientError: await asyncio.sleep(1) print(".", end='', flush=True) else: break print("\nconnected") journald_listen(self.aio_loop, [status.echo_syslog_id], lambda e: print(e['MESSAGE'])) if status.state == ApplicationState.STARTING_UP: status = await self.client.meta.status.GET(cur=status.state) await asyncio.sleep(0.5) return status async def start(self): status = await self.connect() self.interactive = status.interactive if self.interactive: if self.opts.ssh: from subiquity.ui.views.help import (ssh_help_texts, get_installer_password) from subiquitycore.ssh import get_ips_standalone texts = ssh_help_texts( get_ips_standalone(), get_installer_password(self.opts.dry_run)) for line in texts: if hasattr(line, 'text'): if line.text.startswith('installer@'): print(' ' * 4 + line.text) else: print(line.text) else: print(line) return await super().start() journald_listen(self.aio_loop, [status.event_syslog_id], self.controllers.Progress.event) journald_listen(self.aio_loop, [status.log_syslog_id], self.controllers.Progress.log_line) if not status.cloud_init_ok: self.add_global_overlay(CloudInitFail(self)) self.error_reporter.load_reports() for report in self.error_reporter.reports: if report.kind == ErrorReportKind.UI and not report.seen: self.show_error_report(report.ref()) break else: if self.opts.run_on_serial: # Thanks to the fact that we are launched with agetty's # --skip-login option, on serial lines we can end up starting # with some strange terminal settings (see the docs for # --skip-login in agetty(8)). For an interactive install this # does not matter as the settings will soon be clobbered but # for a non-interactive one we need to clear things up or the # prompting for confirmation will be confusing. os.system('stty sane') journald_listen(self.aio_loop, [status.event_syslog_id], self.subiquity_event_noninteractive, seek=True) self.aio_loop.create_task( self.noninteractive_watch_app_state(status)) def _exception_handler(self, loop, context): exc = context.get('exception') if self.restarting_server: log.debug('ignoring %s %s during restart', exc, type(exc)) return if isinstance(exc, Abort): if self.interactive: self.show_error_report(exc.error_report_ref) return super()._exception_handler(loop, context) def extra_urwid_loop_args(self): return dict(input_filter=self.input_filter.filter) def run(self): try: super().run() except Exception: print("generating crash report") try: report = self.make_apport_report(ErrorReportKind.UI, "Installer UI", interrupt=False, wait=True) if report is not None: print("report saved to {path}".format(path=report.path)) except Exception: print("report generation failed") traceback.print_exc() if self.interactive: self._remove_last_screen() raise else: traceback.print_exc() signal.pause() finally: if self.opts.server_pid: print('killing server {}'.format(self.opts.server_pid)) pid = int(self.opts.server_pid) os.kill(pid, 2) os.waitpid(pid, 0) async def confirm_install(self): await self.client.meta.confirm.POST(self.our_tty) def add_global_overlay(self, overlay): self.global_overlays.append(overlay) if isinstance(self.ui.body, BaseView): self.ui.body.show_stretchy_overlay(overlay) def remove_global_overlay(self, overlay): self.global_overlays.remove(overlay) if isinstance(self.ui.body, BaseView): self.ui.body.remove_overlay(overlay) def _remove_last_screen(self): last_screen = self.state_path('last-screen') if os.path.exists(last_screen): os.unlink(last_screen) def exit(self): self._remove_last_screen() super().exit() def select_initial_screen(self): last_screen = None if self.updated: state_path = self.state_path('last-screen') if os.path.exists(state_path): with open(state_path) as fp: last_screen = fp.read().strip() index = 0 if last_screen: for i, controller in enumerate(self.controllers.instances): if controller.name == last_screen: index = i self.aio_loop.create_task(self._select_initial_screen(index)) async def _select_initial_screen(self, index): endpoint_names = [] for c in self.controllers.instances[:index]: if c.endpoint_name: endpoint_names.append(c.endpoint_name) if endpoint_names: await self.client.meta.mark_configured.POST(endpoint_names) self.controllers.index = index - 1 self.next_screen() async def move_screen(self, increment, coro): try: await super().move_screen(increment, coro) except Confirm: self.show_confirm_install() def show_confirm_install(self): log.debug("showing InstallConfirmation over %s", self.ui.body) self.add_global_overlay(InstallConfirmation(self)) async def _start_answers_for_view(self, controller, view): # The view returned by make_view_for_controller is not always shown # immediately (if progress is being shown, but has not yet been shown # for a full second) so wait until it is before starting the answers. while self.ui.body is not view: await asyncio.sleep(0.1) coro = controller.run_answers() if inspect.iscoroutine(coro): await coro async def make_view_for_controller(self, new): view = await super().make_view_for_controller(new) if new.answers: self.aio_loop.create_task(self._start_answers_for_view(new, view)) with open(self.state_path('last-screen'), 'w') as fp: fp.write(new.name) return view def show_progress(self): self.ui.set_body(self.controllers.Progress.progress_view) def unhandled_input(self, key): if key == 'f1': if not self.ui.right_icon.current_help: self.ui.right_icon.open_pop_up() elif key in ['ctrl z', 'f2']: self.debug_shell() elif self.opts.dry_run: self.unhandled_input_dry_run(key) else: super().unhandled_input(key) def unhandled_input_dry_run(self, key): if key in ['ctrl e', 'ctrl r']: interrupt = key == 'ctrl e' try: 1 / 0 except ZeroDivisionError: self.make_apport_report(ErrorReportKind.UNKNOWN, "example", interrupt=interrupt) elif key == 'ctrl u': 1 / 0 elif key == 'ctrl b': self.aio_loop.create_task(self.client.dry_run.crash.GET()) else: super().unhandled_input(key) def debug_shell(self, after_hook=None): def _before(): os.system("clear") print(DEBUG_SHELL_INTRO) self.run_command_in_foreground(["bash"], before_hook=_before, after_hook=after_hook, cwd='/') def note_file_for_apport(self, key, path): self.error_reporter.note_file_for_apport(key, path) def note_data_for_apport(self, key, value): self.error_reporter.note_data_for_apport(key, value) def make_apport_report(self, kind, thing, *, interrupt, wait=False, **kw): report = self.error_reporter.make_apport_report(kind, thing, wait=wait, **kw) if report is not None and interrupt: self.show_error_report(report.ref()) return report def show_error_report(self, error_ref): log.debug("show_error_report %r", error_ref.base) if isinstance(self.ui.body, BaseView): w = getattr(self.ui.body._w, 'stretchy', None) if isinstance(w, ErrorReportStretchy): # Don't show an error if already looking at one. return self.add_global_overlay(ErrorReportStretchy(self, error_ref))
class Subiquity(TuiApplication): snapd_socket_path = '/run/snapd.socket' base_schema = { 'type': 'object', 'properties': { 'version': { 'type': 'integer', 'minimum': 1, 'maximum': 1, }, }, 'required': ['version'], 'additionalProperties': True, } from subiquity import controllers as controllers_mod project = "subiquity" def make_model(self): root = '/' if self.opts.dry_run: root = os.path.abspath('.subiquity') return SubiquityModel(root, self.opts.sources) def make_ui(self): return SubiquityUI(self, self.help_menu) controllers = [ "Early", "Reporting", "Error", "Userdata", "Package", "Debconf", "Welcome", "Refresh", "Keyboard", "Zdev", "Network", "Proxy", "Mirror", "Refresh", "Filesystem", "Identity", "SSH", "SnapList", "InstallProgress", "Late", "Reboot", ] def __init__(self, opts, block_log_dir): if is_linux_tty(): self.input_filter = KeyCodesFilter() else: self.input_filter = DummyKeycodesFilter() self.help_menu = HelpMenu(self) super().__init__(opts) self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid()) self.log_syslog_id = 'subiquity_log.{}'.format(os.getpid()) self.server_updated = None self.restarting_server = False self.prober = Prober(opts.machine_config, self.debug_flags) journald_listen(self.aio_loop, ["subiquity"], self.subiquity_event, seek=True) self.event_listeners = [] self.install_lock_file = Lockfile(self.state_path("installing")) self.global_overlays = [] self.block_log_dir = block_log_dir self.kernel_cmdline = shlex.split(opts.kernel_cmdline) if opts.snaps_from_examples: connection = FakeSnapdConnection( os.path.join(os.path.dirname(os.path.dirname(__file__)), "examples", "snaps"), self.scale_factor) else: connection = SnapdConnection(self.root, self.snapd_socket_path) self.snapd = AsyncSnapd(connection) self.signal.connect_signals([ ('network-proxy-set', lambda: schedule_task(self._proxy_set())), ('network-change', self._network_change), ]) self.conn = aiohttp.UnixConnector(self.opts.socket) self.client = make_client_for_conn(API, self.conn, self.resp_hook) self.autoinstall_config = {} self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root, self.client) self.note_data_for_apport("SnapUpdated", str(self.updated)) self.note_data_for_apport("UsingAnswers", str(bool(self.answers))) def subiquity_event(self, event): if event["MESSAGE"] == "starting install": if event["_PID"] == os.getpid(): return if not self.install_lock_file.is_exclusively_locked(): return from subiquity.ui.views.installprogress import ( InstallRunning, ) tty = self.install_lock_file.read_content() install_running = InstallRunning(self.ui.body, self, tty) self.add_global_overlay(install_running) schedule_task(self._hide_install_running(install_running)) async def _hide_install_running(self, install_running): # Wait until the install has completed... async with self.install_lock_file.shared(): # And remove the overlay. self.remove_global_overlay(install_running) async def _restart_server(self): log.debug("_restart_server") try: await self.client.meta.restart.POST() except aiohttp.ServerDisconnectedError: pass self.restart(remove_last_screen=False) def restart(self, remove_last_screen=True, restart_server=False): log.debug(f"restart {remove_last_screen} {restart_server}") if remove_last_screen: self._remove_last_screen() if restart_server: self.restarting_server = True self.ui.block_input = True self.aio_loop.create_task(self._restart_server()) return if self.urwid_loop is not None: self.urwid_loop.stop() cmdline = ['snap', 'run', 'subiquity'] if self.opts.dry_run: cmdline = [ sys.executable, '-m', 'subiquity.cmd.tui', ] + sys.argv[1:] + ['--socket', self.opts.socket] if self.opts.server_pid is not None: cmdline.extend(['--server-pid', self.opts.server_pid]) log.debug("restarting %r", cmdline) os.execvp(cmdline[0], cmdline) def get_primary_tty(self): tty = '/dev/tty1' for work in self.kernel_cmdline: if work.startswith('console='): tty = '/dev/' + work[len('console='):].split(',')[0] return tty async def load_autoinstall_config(self): with open(self.opts.autoinstall) as fp: self.autoinstall_config = yaml.safe_load(fp) primary_tty = self.get_primary_tty() try: our_tty = os.ttyname(0) except OSError: # This is a gross hack for testing in travis. our_tty = "/dev/not a tty" if not self.interactive() and our_tty != primary_tty: while True: print( _("the installer running on {tty} will perform the " "autoinstall").format(tty=primary_tty)) print() print(_("press enter to start a shell")) input() os.system("cd / && bash") self.controllers.Reporting.start() with self.context.child("core_validation", level="INFO"): jsonschema.validate(self.autoinstall_config, self.base_schema) self.controllers.Reporting.setup_autoinstall() self.controllers.Early.setup_autoinstall() self.controllers.Error.setup_autoinstall() if self.controllers.Early.cmds: stamp_file = self.state_path("early-commands") if our_tty != primary_tty: print( _("waiting for installer running on {tty} to run early " "commands").format(tty=primary_tty)) while not os.path.exists(stamp_file): await asyncio.sleep(1) elif not os.path.exists(stamp_file): await self.controllers.Early.run() open(stamp_file, 'w').close() with open(self.opts.autoinstall) as fp: self.autoinstall_config = yaml.safe_load(fp) with self.context.child("core_validation", level="INFO"): jsonschema.validate(self.autoinstall_config, self.base_schema) for controller in self.controllers.instances: controller.setup_autoinstall() if not self.interactive() and self.opts.run_on_serial: # Thanks to the fact that we are launched with agetty's # --skip-login option, on serial lines we can end up starting with # some strange terminal settings (see the docs for --skip-login in # agetty(8)). For an interactive install this does not matter as # the settings will soon be clobbered but for a non-interactive # one we need to clear things up or the prompting for confirmation # in next_screen below will be confusing. os.system('stty sane') def resp_hook(self, response): headers = response.headers if 'x-updated' in headers: if self.server_updated is None: self.server_updated = headers['x-updated'] elif self.server_updated != headers['x-updated']: self.restart(remove_last_screen=False) status = response.headers.get('x-status') if status == 'skip': raise Skip elif status == 'confirm': raise Confirm if headers.get('x-error-report') is not None: ref = from_json(ErrorReportRef, headers['x-error-report']) raise Abort(ref) try: response.raise_for_status() except aiohttp.ClientError: report = self.error_reporter.make_apport_report( ErrorReportKind.SERVER_REQUEST_FAIL, "request to {}".format(response.url.path)) raise Abort(report.ref()) return response def subiquity_event_noninteractive(self, event): if event['SUBIQUITY_EVENT_TYPE'] == 'start': print('start: ' + event["MESSAGE"]) elif event['SUBIQUITY_EVENT_TYPE'] == 'finish': print('finish: ' + event["MESSAGE"]) context_name = event.get('SUBIQUITY_CONTEXT_NAME', '') if context_name == 'subiquity/Reboot/reboot': self.exit() async def connect(self): print("connecting...", end='', flush=True) while True: try: await self.client.meta.status.GET() except aiohttp.ClientError: await asyncio.sleep(1) print(".", end='', flush=True) else: print() break def load_serialized_state(self): for controller in self.controllers.instances: controller.load_state() async def start(self): self.controllers.load_all() self.load_serialized_state() await self.connect() if self.opts.autoinstall is not None: await self.load_autoinstall_config() if not self.interactive() and not self.opts.dry_run: open('/run/casper-no-prompt', 'w').close() interactive = self.interactive() if interactive: journald_listen(self.aio_loop, [self.event_syslog_id], self.controllers.InstallProgress.event) journald_listen(self.aio_loop, [self.log_syslog_id], self.controllers.InstallProgress.log_line) else: journald_listen(self.aio_loop, [self.event_syslog_id], self.subiquity_event_noninteractive, seek=True) await asyncio.sleep(1) await super().start(start_urwid=interactive) if not interactive: self.select_initial_screen() def _exception_handler(self, loop, context): exc = context.get('exception') if self.restarting_server: log.debug('ignoring %s %s during restart', exc, type(exc)) return if isinstance(exc, Abort): self.show_error_report(exc.error_report_ref) return super()._exception_handler(loop, context) def _remove_last_screen(self): last_screen = self.state_path('last-screen') if os.path.exists(last_screen): os.unlink(last_screen) def exit(self): self._remove_last_screen() super().exit() def extra_urwid_loop_args(self): return dict(input_filter=self.input_filter.filter) def run(self): try: super().run() except Exception: print("generating crash report") try: report = self.make_apport_report(ErrorReportKind.UI, "Installer UI", interrupt=False, wait=True) if report is not None: print("report saved to {path}".format(path=report.path)) except Exception: print("report generation failed") traceback.print_exc() Error = getattr(self.controllers, "Error", None) if Error is not None and Error.cmds: new_loop = asyncio.new_event_loop() asyncio.set_event_loop(new_loop) new_loop.run_until_complete(Error.run()) if self.interactive(): self._remove_last_screen() raise else: traceback.print_exc() signal.pause() finally: if self.opts.server_pid: print('killing server {}'.format(self.opts.server_pid)) pid = int(self.opts.server_pid) os.kill(pid, 2) os.waitpid(pid, 0) def add_event_listener(self, listener): self.event_listeners.append(listener) def report_start_event(self, context, description): for listener in self.event_listeners: listener.report_start_event(context, description) self._maybe_push_to_journal('start', context, description) def report_finish_event(self, context, description, status): for listener in self.event_listeners: listener.report_finish_event(context, description, status) self._maybe_push_to_journal('finish', context, description) def _maybe_push_to_journal(self, event_type, context, description): if not context.get('is-install-context') and self.interactive(): controller = context.get('controller') if controller is None or controller.interactive(): return if context.get('request'): return indent = context.full_name().count('/') - 2 if context.get('is-install-context') and self.interactive(): indent -= 1 msg = context.description else: msg = context.full_name() if description: msg += ': ' + description msg = ' ' * indent + msg if context.parent: parent_id = str(context.parent.id) else: parent_id = '' journal.send(msg, PRIORITY=context.level, SYSLOG_IDENTIFIER=self.event_syslog_id, SUBIQUITY_CONTEXT_NAME=context.full_name(), SUBIQUITY_EVENT_TYPE=event_type, SUBIQUITY_CONTEXT_ID=str(context.id), SUBIQUITY_CONTEXT_PARENT_ID=parent_id) async def confirm_install(self): self.base_model.confirm() def interactive(self): if not self.autoinstall_config: return True return bool(self.autoinstall_config.get('interactive-sections')) def add_global_overlay(self, overlay): self.global_overlays.append(overlay) if isinstance(self.ui.body, BaseView): self.ui.body.show_stretchy_overlay(overlay) def remove_global_overlay(self, overlay): self.global_overlays.remove(overlay) if isinstance(self.ui.body, BaseView): self.ui.body.remove_overlay(overlay) def initial_controller_index(self): if not self.updated: return 0 state_path = self.state_path('last-screen') if not os.path.exists(state_path): return 0 with open(state_path) as fp: last_screen = fp.read().strip() controller_index = 0 for i, controller in enumerate(self.controllers.instances): if controller.name == last_screen: controller_index = i return controller_index def select_initial_screen(self): self.error_reporter.load_reports() for report in self.error_reporter.reports: if report.kind == ErrorReportKind.UI and not report.seen: self.show_error_report(report.ref()) break index = self.initial_controller_index() for controller in self.controllers.instances[:index]: controller.configured() self.controllers.index = index - 1 self.next_screen() async def move_screen(self, increment, coro): try: await super().move_screen(increment, coro) except Confirm: if self.interactive(): log.debug("showing InstallConfirmation over %s", self.ui.body) from subiquity.ui.views.installprogress import ( InstallConfirmation, ) self.add_global_overlay(InstallConfirmation(self)) else: yes = _('yes') no = _('no') answer = no if 'autoinstall' in self.kernel_cmdline: answer = yes else: print(_("Confirmation is required to continue.")) print( _("Add 'autoinstall' to your kernel command line to" " avoid this")) print() prompt = "\n\n{} ({}|{})".format( _("Continue with autoinstall?"), yes, no) while answer != yes: print(prompt) answer = input() self.next_screen(self.confirm_install()) async def make_view_for_controller(self, new): if self.base_model.needs_confirmation(new.model_name): raise Confirm if new.interactive(): view = await super().make_view_for_controller(new) if new.answers: self.aio_loop.call_soon(new.run_answers) with open(self.state_path('last-screen'), 'w') as fp: fp.write(new.name) return view else: if self.autoinstall_config and not new.autoinstall_applied: await new.apply_autoinstall_config() new.autoinstall_applied = True new.configured() raise Skip def show_progress(self): self.ui.set_body(self.controllers.InstallProgress.progress_view) def _network_change(self): self.signal.emit_signal('snapd-network-change') async def _proxy_set(self): await run_in_thread(self.snapd.connection.configure_proxy, self.base_model.proxy) self.signal.emit_signal('snapd-network-change') def unhandled_input(self, key): if key == 'f1': if not self.ui.right_icon.current_help: self.ui.right_icon.open_pop_up() elif key in ['ctrl z', 'f2']: self.debug_shell() elif self.opts.dry_run: self.unhandled_input_dry_run(key) else: super().unhandled_input(key) def unhandled_input_dry_run(self, key): if key == 'ctrl g': from systemd import journal async def mock_install(): async with self.install_lock_file.exclusive(): self.install_lock_file.write_content("nowhere") journal.send("starting install", SYSLOG_IDENTIFIER="subiquity") await asyncio.sleep(5) schedule_task(mock_install()) elif key in ['ctrl e', 'ctrl r']: interrupt = key == 'ctrl e' try: 1 / 0 except ZeroDivisionError: self.make_apport_report(ErrorReportKind.UNKNOWN, "example", interrupt=interrupt) elif key == 'ctrl u': 1 / 0 elif key == 'ctrl b': self.aio_loop.create_task(self.client.dry_run.crash.GET()) else: super().unhandled_input(key) def debug_shell(self, after_hook=None): def _before(): os.system("clear") print(DEBUG_SHELL_INTRO) self.run_command_in_foreground(["bash"], before_hook=_before, after_hook=after_hook, cwd='/') def note_file_for_apport(self, key, path): self.error_reporter.note_file_for_apport(key, path) def note_data_for_apport(self, key, value): self.error_reporter.note_data_for_apport(key, value) def make_apport_report(self, kind, thing, *, interrupt, wait=False, **kw): report = self.error_reporter.make_apport_report(kind, thing, wait=wait, **kw) if report is not None and interrupt and self.interactive(): self.show_error_report(report.ref()) return report def show_error_report(self, error_ref): log.debug("show_error_report %r", error_ref.base) if isinstance(self.ui.body, BaseView): w = getattr(self.ui.body._w, 'stretchy', None) if isinstance(w, ErrorReportStretchy): # Don't show an error if already looking at one. return self.add_global_overlay(ErrorReportStretchy(self, error_ref)) def make_autoinstall(self): config = {'version': 1} for controller in self.controllers.instances: controller_conf = controller.make_autoinstall() if controller_conf: config[controller.autoinstall_key] = controller_conf return config
class SubiquityServer(Application): snapd_socket_path = '/run/snapd.socket' base_schema = { 'type': 'object', 'properties': { 'version': { 'type': 'integer', 'minimum': 1, 'maximum': 1, }, }, 'required': ['version'], 'additionalProperties': True, } project = "subiquity" from subiquity.server import controllers as controllers_mod controllers = [ "Early", "Reporting", "Error", "Userdata", "Package", "Debconf", "Locale", "Refresh", "Kernel", "Keyboard", "Zdev", "Network", "Proxy", "Mirror", "Filesystem", "Identity", "SSH", "SnapList", "Install", "Updates", "Late", "Reboot", ] def make_model(self): root = '/' if self.opts.dry_run: root = os.path.abspath('.subiquity') return SubiquityModel(root) def __init__(self, opts, block_log_dir): super().__init__(opts) self.block_log_dir = block_log_dir self.cloud = None self.cloud_init_ok = None self._state = ApplicationState.STARTING_UP self.state_event = asyncio.Event() self.interactive = None self.confirming_tty = '' self.fatal_error = None self.running_error_commands = False self.installer_user_name = None self.installer_user_passwd_kind = PasswordKind.NONE self.installer_user_passwd = None self.echo_syslog_id = 'subiquity_echo.{}'.format(os.getpid()) self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid()) self.log_syslog_id = 'subiquity_log.{}'.format(os.getpid()) self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root) self.prober = Prober(opts.machine_config, self.debug_flags) self.kernel_cmdline = shlex.split(opts.kernel_cmdline) if opts.snaps_from_examples: connection = FakeSnapdConnection( os.path.join( os.path.dirname( os.path.dirname( os.path.dirname(__file__))), "examples", "snaps"), self.scale_factor) else: connection = SnapdConnection(self.root, self.snapd_socket_path) self.snapd = AsyncSnapd(connection) self.note_data_for_apport("SnapUpdated", str(self.updated)) self.event_listeners = [] self.autoinstall_config = None self.hub.subscribe('network-up', self._network_change) self.hub.subscribe('network-proxy-set', self._proxy_set) self.geoip = GeoIP(self) def load_serialized_state(self): for controller in self.controllers.instances: controller.load_state() def add_event_listener(self, listener): self.event_listeners.append(listener) def _maybe_push_to_journal(self, event_type, context, description): if not context.get('is-install-context') and \ self.interactive in [True, None]: controller = context.get('controller') if controller is None or controller.interactive(): return if context.get('request'): return indent = context.full_name().count('/') - 2 if context.get('is-install-context') and self.interactive: indent -= 1 msg = context.description else: msg = context.full_name() if description: msg += ': ' + description msg = ' ' * indent + msg if context.parent: parent_id = str(context.parent.id) else: parent_id = '' journal.send( msg, PRIORITY=context.level, SYSLOG_IDENTIFIER=self.event_syslog_id, SUBIQUITY_CONTEXT_NAME=context.full_name(), SUBIQUITY_EVENT_TYPE=event_type, SUBIQUITY_CONTEXT_ID=str(context.id), SUBIQUITY_CONTEXT_PARENT_ID=parent_id) def report_start_event(self, context, description): for listener in self.event_listeners: listener.report_start_event(context, description) self._maybe_push_to_journal('start', context, description) def report_finish_event(self, context, description, status): for listener in self.event_listeners: listener.report_finish_event(context, description, status) self._maybe_push_to_journal('finish', context, description) @property def state(self): return self._state def update_state(self, state): self._state = state self.state_event.set() self.state_event.clear() def note_file_for_apport(self, key, path): self.error_reporter.note_file_for_apport(key, path) def note_data_for_apport(self, key, value): self.error_reporter.note_data_for_apport(key, value) def make_apport_report(self, kind, thing, *, wait=False, **kw): return self.error_reporter.make_apport_report( kind, thing, wait=wait, **kw) async def _run_error_cmds(self, report): await report._info_task Error = getattr(self.controllers, "Error", None) if Error is not None and Error.cmds: try: await Error.run() except Exception: log.exception("running error-commands failed") if not self.interactive: self.update_state(ApplicationState.ERROR) def _exception_handler(self, loop, context): exc = context.get('exception') if exc is None: super()._exception_handler(loop, context) return report = self.error_reporter.report_for_exc(exc) log.error("top level error", exc_info=exc) if not report: report = self.make_apport_report( ErrorReportKind.UNKNOWN, "unknown error", exc=exc) self.fatal_error = report if self.interactive: self.update_state(ApplicationState.ERROR) if not self.running_error_commands: self.running_error_commands = True self.aio_loop.create_task(self._run_error_cmds(report)) @web.middleware async def middleware(self, request, handler): override_status = None controller = await controller_for_request(request) if isinstance(controller, SubiquityController): if not controller.interactive(): override_status = 'skip' elif self.state == ApplicationState.NEEDS_CONFIRMATION: if self.base_model.needs_configuration(controller.model_name): override_status = 'confirm' if override_status is not None: resp = web.Response(headers={'x-status': override_status}) else: resp = await handler(request) if self.updated: resp.headers['x-updated'] = 'yes' else: resp.headers['x-updated'] = 'no' if resp.get('exception'): exc = resp['exception'] log.debug( 'request to {} crashed'.format(request.raw_path), exc_info=exc) report = self.make_apport_report( ErrorReportKind.SERVER_REQUEST_FAIL, "request to {}".format(request.raw_path), exc=exc) resp.headers['x-error-report'] = to_json( ErrorReportRef, report.ref()) return resp @with_context() async def apply_autoinstall_config(self, context): for controller in self.controllers.instances: if controller.interactive(): log.debug( "apply_autoinstall_config: skipping %s as interactive", controller.name) continue await controller.apply_autoinstall_config() controller.configured() def load_autoinstall_config(self, *, only_early): log.debug("load_autoinstall_config only_early %s", only_early) if self.opts.autoinstall is None: return with open(self.opts.autoinstall) as fp: self.autoinstall_config = yaml.safe_load(fp) if only_early: self.controllers.Reporting.setup_autoinstall() self.controllers.Reporting.start() self.controllers.Error.setup_autoinstall() with self.context.child("core_validation", level="INFO"): jsonschema.validate(self.autoinstall_config, self.base_schema) self.controllers.Early.setup_autoinstall() else: for controller in self.controllers.instances: controller.setup_autoinstall() async def start_api_server(self): app = web.Application(middlewares=[self.middleware]) bind(app.router, API.meta, MetaController(self)) bind(app.router, API.errors, ErrorController(self)) if self.opts.dry_run: from .dryrun import DryRunController bind(app.router, API.dry_run, DryRunController(self)) for controller in self.controllers.instances: controller.add_routes(app) runner = web.AppRunner(app) await runner.setup() site = web.UnixSite(runner, self.opts.socket) await site.start() # It is intended that a non-root client can connect. os.chmod(self.opts.socket, 0o666) async def wait_for_cloudinit(self): if self.opts.dry_run: self.cloud_init_ok = True return ci_start = time.time() status_coro = arun_command(["cloud-init", "status", "--wait"]) try: status_cp = await asyncio.wait_for(status_coro, 600) except asyncio.CancelledError: status_txt = '<timeout>' self.cloud_init_ok = False else: status_txt = status_cp.stdout self.cloud_init_ok = True log.debug("waited %ss for cloud-init", time.time() - ci_start) if "status: done" in status_txt: log.debug("loading cloud config") init = stages.Init() init.read_cfg() init.fetch(existing="trust") self.cloud = init.cloudify() autoinstall_path = '/autoinstall.yaml' if 'autoinstall' in self.cloud.cfg: if not os.path.exists(autoinstall_path): atomic_helper.write_file( autoinstall_path, safeyaml.dumps( self.cloud.cfg['autoinstall']).encode('utf-8'), mode=0o600) if os.path.exists(autoinstall_path): self.opts.autoinstall = autoinstall_path else: log.debug( "cloud-init status: %r, assumed disabled", status_txt) def _user_has_password(self, username): with open('/etc/shadow') as fp: for line in fp: if line.startswith(username + ":$"): return True return False def set_installer_password(self): if self.cloud is None: return passfile = self.state_path("installer-user-passwd") if os.path.exists(passfile): with open(passfile) as fp: contents = fp.read() self.installer_user_passwd_kind = PasswordKind.KNOWN self.installer_user_name, self.installer_user_passwd = \ contents.split(':', 1) return def use_passwd(passwd): self.installer_user_passwd = passwd self.installer_user_passwd_kind = PasswordKind.KNOWN with open(passfile, 'w') as fp: fp.write(self.installer_user_name + ':' + passwd) if self.opts.dry_run: self.installer_user_name = os.environ['USER'] use_passwd(rand_user_password()) return (users, _groups) = ug_util.normalize_users_groups( self.cloud.cfg, self.cloud.distro) (username, _user_config) = ug_util.extract_default(users) self.installer_user_name = username if self._user_has_password(username): # Was the password set to a random password by a version of # cloud-init that records the username in the log? (This is the # case we hit on upgrading the subiquity snap) passwd = get_installer_password_from_cloudinit_log() if passwd: use_passwd(passwd) else: self.installer_user_passwd_kind = PasswordKind.UNKNOWN elif not user_key_fingerprints(username): passwd = rand_user_password() cp = run_command('chpasswd', input=username + ':'+passwd+'\n') if cp.returncode == 0: use_passwd(passwd) else: log.info("setting installer password failed %s", cp) self.installer_user_passwd_kind = PasswordKind.NONE else: self.installer_user_passwd_kind = PasswordKind.NONE async def start(self): self.controllers.load_all() await self.start_api_server() self.update_state(ApplicationState.CLOUD_INIT_WAIT) await self.wait_for_cloudinit() self.set_installer_password() self.load_autoinstall_config(only_early=True) if self.autoinstall_config and self.controllers.Early.cmds: stamp_file = self.state_path("early-commands") if not os.path.exists(stamp_file): self.update_state(ApplicationState.EARLY_COMMANDS) # Just wait a second for any clients to get ready to print # output. await asyncio.sleep(1) await self.controllers.Early.run() open(stamp_file, 'w').close() await asyncio.sleep(1) self.load_autoinstall_config(only_early=False) if self.autoinstall_config: self.interactive = bool( self.autoinstall_config.get('interactive-sections')) else: self.interactive = True if not self.interactive and not self.opts.dry_run: open('/run/casper-no-prompt', 'w').close() self.load_serialized_state() self.update_state(ApplicationState.WAITING) await super().start() await self.apply_autoinstall_config() def _network_change(self): self.hub.broadcast('snapd-network-change') async def _proxy_set(self): await run_in_thread( self.snapd.connection.configure_proxy, self.base_model.proxy) self.hub.broadcast('snapd-network-change') def restart(self): cmdline = ['snap', 'run', 'subiquity.subiquity-server'] if self.opts.dry_run: cmdline = [ sys.executable, '-m', 'subiquity.cmd.server', ] + sys.argv[1:] os.execvp(cmdline[0], cmdline) def make_autoinstall(self): config = {'version': 1} for controller in self.controllers.instances: controller_conf = controller.make_autoinstall() if controller_conf: config[controller.autoinstall_key] = controller_conf return config
class SubiquityServer(Application): snapd_socket_path = '/run/snapd.socket' base_schema = { 'type': 'object', 'properties': { 'version': { 'type': 'integer', 'minimum': 1, 'maximum': 1, }, }, 'required': ['version'], 'additionalProperties': True, } project = "subiquity" from subiquity.server import controllers as controllers_mod controllers = [ "Early", "Reporting", "Error", "Userdata", "Package", "Debconf", "Locale", "Refresh", "Keyboard", "Zdev", "Network", "Proxy", "Mirror", "Filesystem", "Identity", "SSH", "SnapList", "Install", "Late", "Reboot", ] def make_model(self): root = '/' if self.opts.dry_run: root = os.path.abspath('.subiquity') return SubiquityModel(root, self.opts.sources) def __init__(self, opts, block_log_dir, cloud_init_ok): super().__init__(opts) self.block_log_dir = block_log_dir self.cloud_init_ok = cloud_init_ok self._state = ApplicationState.STARTING_UP self.state_event = asyncio.Event() self.interactive = None self.confirming_tty = '' self.fatal_error = None self.running_error_commands = False self.echo_syslog_id = 'subiquity_echo.{}'.format(os.getpid()) self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid()) self.log_syslog_id = 'subiquity_log.{}'.format(os.getpid()) self.error_reporter = ErrorReporter( self.context.child("ErrorReporter"), self.opts.dry_run, self.root) self.prober = Prober(opts.machine_config, self.debug_flags) self.kernel_cmdline = shlex.split(opts.kernel_cmdline) if opts.snaps_from_examples: connection = FakeSnapdConnection( os.path.join( os.path.dirname( os.path.dirname( os.path.dirname(__file__))), "examples", "snaps"), self.scale_factor) else: connection = SnapdConnection(self.root, self.snapd_socket_path) self.snapd = AsyncSnapd(connection) self.note_data_for_apport("SnapUpdated", str(self.updated)) self.event_listeners = [] self.autoinstall_config = None self.signal.connect_signals([ ('network-proxy-set', lambda: schedule_task(self._proxy_set())), ('network-change', self._network_change), ]) def load_serialized_state(self): for controller in self.controllers.instances: controller.load_state() def add_event_listener(self, listener): self.event_listeners.append(listener) def _maybe_push_to_journal(self, event_type, context, description): if not context.get('is-install-context') and self.interactive: controller = context.get('controller') if controller is None or controller.interactive(): return if context.get('request'): return indent = context.full_name().count('/') - 2 if context.get('is-install-context') and self.interactive: indent -= 1 msg = context.description else: msg = context.full_name() if description: msg += ': ' + description msg = ' ' * indent + msg if context.parent: parent_id = str(context.parent.id) else: parent_id = '' journal.send( msg, PRIORITY=context.level, SYSLOG_IDENTIFIER=self.event_syslog_id, SUBIQUITY_CONTEXT_NAME=context.full_name(), SUBIQUITY_EVENT_TYPE=event_type, SUBIQUITY_CONTEXT_ID=str(context.id), SUBIQUITY_CONTEXT_PARENT_ID=parent_id) def report_start_event(self, context, description): for listener in self.event_listeners: listener.report_start_event(context, description) self._maybe_push_to_journal('start', context, description) def report_finish_event(self, context, description, status): for listener in self.event_listeners: listener.report_finish_event(context, description, status) self._maybe_push_to_journal('finish', context, description) @property def state(self): return self._state def update_state(self, state): self._state = state self.state_event.set() self.state_event.clear() def note_file_for_apport(self, key, path): self.error_reporter.note_file_for_apport(key, path) def note_data_for_apport(self, key, value): self.error_reporter.note_data_for_apport(key, value) def make_apport_report(self, kind, thing, *, wait=False, **kw): return self.error_reporter.make_apport_report( kind, thing, wait=wait, **kw) async def _run_error_cmds(self, report): await report._info_task Error = getattr(self.controllers, "Error", None) if Error is not None and Error.cmds: await Error.run() def _exception_handler(self, loop, context): exc = context.get('exception') if exc is None: super()._exception_handler(loop, context) return report = self.error_reporter.report_for_exc(exc) log.error("top level error", exc_info=exc) if not report: report = self.make_apport_report( ErrorReportKind.UNKNOWN, "unknown error", exc=exc) self.fatal_error = report self.update_state(ApplicationState.ERROR) if not self.running_error_commands: self.running_error_commands = True self.aio_loop.create_task(self._run_error_cmds(report)) @web.middleware async def middleware(self, request, handler): override_status = None controller = await controller_for_request(request) if isinstance(controller, SubiquityController): if not controller.interactive(): override_status = 'skip' elif self.state == ApplicationState.NEEDS_CONFIRMATION: if self.base_model.needs_configuration(controller.model_name): override_status = 'confirm' if override_status is not None: resp = web.Response(headers={'x-status': override_status}) else: resp = await handler(request) if self.updated: resp.headers['x-updated'] = 'yes' else: resp.headers['x-updated'] = 'no' if resp.get('exception'): exc = resp['exception'] log.debug( 'request to {} crashed'.format(request.raw_path), exc_info=exc) report = self.make_apport_report( ErrorReportKind.SERVER_REQUEST_FAIL, "request to {}".format(request.raw_path), exc=exc) resp.headers['x-error-report'] = to_json( ErrorReportRef, report.ref()) return resp @with_context() async def apply_autoinstall_config(self, context): for controller in self.controllers.instances: if controller.interactive(): log.debug( "apply_autoinstall_config: skipping %s as interactive", controller.name) continue await controller.apply_autoinstall_config() controller.configured() def load_autoinstall_config(self, only_early): log.debug("load_autoinstall_config only_early %s", only_early) if self.opts.autoinstall is None: return with open(self.opts.autoinstall) as fp: self.autoinstall_config = yaml.safe_load(fp) if only_early: self.controllers.Reporting.setup_autoinstall() self.controllers.Reporting.start() self.controllers.Error.setup_autoinstall() with self.context.child("core_validation", level="INFO"): jsonschema.validate(self.autoinstall_config, self.base_schema) self.controllers.Early.setup_autoinstall() else: for controller in self.controllers.instances: controller.setup_autoinstall() async def start_api_server(self): app = web.Application(middlewares=[self.middleware]) bind(app.router, API.meta, MetaController(self)) bind(app.router, API.errors, ErrorController(self)) if self.opts.dry_run: from .dryrun import DryRunController bind(app.router, API.dry_run, DryRunController(self)) for controller in self.controllers.instances: controller.add_routes(app) runner = web.AppRunner(app) await runner.setup() site = web.UnixSite(runner, self.opts.socket) await site.start() async def start(self): self.controllers.load_all() await self.start_api_server() self.load_autoinstall_config(only_early=True) if self.autoinstall_config and self.controllers.Early.cmds: stamp_file = self.state_path("early-commands") if not os.path.exists(stamp_file): await self.controllers.Early.run() open(stamp_file, 'w').close() await asyncio.sleep(1) self.load_autoinstall_config(only_early=False) if self.autoinstall_config: self.interactive = bool( self.autoinstall_config.get('interactive-sections')) else: self.interactive = True if not self.interactive and not self.opts.dry_run: open('/run/casper-no-prompt', 'w').close() self.load_serialized_state() self.update_state(ApplicationState.WAITING) await super().start() await self.apply_autoinstall_config() def _network_change(self): self.signal.emit_signal('snapd-network-change') async def _proxy_set(self): await run_in_thread( self.snapd.connection.configure_proxy, self.base_model.proxy) self.signal.emit_signal('snapd-network-change') def restart(self): cmdline = ['snap', 'run', 'subiquity.subiquity-server'] if self.opts.dry_run: cmdline = [ sys.executable, '-m', 'subiquity.cmd.server', ] + sys.argv[1:] os.execvp(cmdline[0], cmdline) def make_autoinstall(self): config = {'version': 1} for controller in self.controllers.instances: controller_conf = controller.make_autoinstall() if controller_conf: config[controller.autoinstall_key] = controller_conf return config