diff --git a/bin/console-conf-tui b/bin/console-conf-tui new file mode 100755 index 00000000..4b756451 --- /dev/null +++ b/bin/console-conf-tui @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import argparse +import sys +import logging +import signal +from subiquitycore.log import setup_logger, LOGFILE +from subiquitycore import __version__ as VERSION +from console_conf.core import Controller as ConsoleConf +from subiquitycore.core import CoreControllerError +from subiquitycore.ui.frame import SubiquityUI +from subiquitycore.utils import environment_check + + +def parse_options(argv): + parser = argparse.ArgumentParser( + description='console-conf - Pre-Ownership Configuration for Ubuntu Core', + prog='console-conf') + parser.add_argument('--dry-run', action='store_true', + dest='dry_run', + help='menu-only, do not call installer function') + # XXX Defaults to firstboot mode unless one runs subiquity --install. + parser.add_argument('--install', action='store_false', + dest='firstboot', default=True, + help='run installer in firstboot mode') + parser.add_argument('--serial', action='store_true', + dest='run_on_serial', + help='Run the installer over serial console.') + parser.add_argument('--machine-config', metavar='CONFIG', + dest='machine_config', + help="Don't Probe. Use probe data file") + parser.add_argument('--uefi', action='store_true', + dest='uefi', + help='run in uefi support mode') + return parser.parse_args(argv) + + +def control_c_handler(signum, frame): + sys.exit(1) + + +def main(): + opts = parse_options(sys.argv[1:]) + setup_logger() + logger = logging.getLogger('console-conf') + logger.info("Starting console-conf v{}".format(VERSION)) + logger.info("Arguments passed: {}".format(sys.argv)) + + signal.signal(signal.SIGINT, control_c_handler) + + env_ok = environment_check() + if env_ok is False and not opts.dry_run: + print('Failed environment check. ' + 'Check {} for errors.'.format(LOGFILE)) + return 1 + + ui = SubiquityUI() + + try: + interface = ConsoleConf(ui, opts) + except CoreControllerError as e: + logger.exception('Failed to load ConsoleConf interface') + print(e) + return 1 + + interface.run() + +if __name__ == '__main__': + sys.exit(main()) diff --git a/console_conf/controllers/__init__.py b/console_conf/controllers/__init__.py new file mode 100644 index 00000000..f76f4325 --- /dev/null +++ b/console_conf/controllers/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" console-conf controllers """ + +from .welcome import WelcomeController +from subiquitycore.controllers.network import NetworkController +from subiquitycore.controllers.login import LoginController +from subiquitycore.controllers.identity import IdentityController diff --git a/console_conf/controllers/identity.py b/console_conf/controllers/identity.py new file mode 100644 index 00000000..47c23167 --- /dev/null +++ b/console_conf/controllers/identity.py @@ -0,0 +1,53 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +from subiquitycore.controller import ControllerPolicy +from subiquitycore.models import IdentityModel +from subiquitycore.ui.views import IdentityView, LoginView + +log = logging.getLogger('subiquitycore.controllers.identity') + +class IdentityController(ControllerPolicy): + def __init__(self, common): + super().__init__(common) + self.model = IdentityModel(self.opts) + + def identity(self): + title = "Profile setup" + excerpt = ("Input your username and password to log in to the system.") + footer = "" + self.ui.set_header(title, excerpt) + self.ui.set_footer(footer, 40) + self.ui.set_body(IdentityView(self.model, self.signal, self.opts)) + + + def login(self): + log.debug("Identity login view") + title = ("Configuration Complete") + footer = ("View configured user and device access methods") + self.ui.set_header(title) + self.ui.set_footer(footer) + + net_model = self.controllers['Network'].model + configured_ifaces = net_model.get_configured_interfaces() + login_view = LoginView(self.model, + self.signal, + self.model.user, + configured_ifaces) + + self.ui.set_body(login_view) + + diff --git a/console_conf/controllers/login.py b/console_conf/controllers/login.py new file mode 100644 index 00000000..3cd29ce1 --- /dev/null +++ b/console_conf/controllers/login.py @@ -0,0 +1,32 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from subiquitycore.ui.views import LoginView +from subiquitycore.models import LoginModel +from subiquitycore.controller import ControllerPolicy + + +class LoginController(ControllerPolicy): + def __init__(self, common): + super().__init__(common) + self.model = LoginModel() + + def login(self): + title = "Configuration Complete" + excerpt = "Your device is now configured. Login details below." + self.ui.set_header(title, excerpt) + view = LoginView(self.model, self.signal, self.model.user) + self.ui.set_body(view) diff --git a/console_conf/controllers/network.py b/console_conf/controllers/network.py new file mode 100644 index 00000000..d0cbf6d4 --- /dev/null +++ b/console_conf/controllers/network.py @@ -0,0 +1,94 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +from subiquitycore.controller import ControllerPolicy +from subiquitycore.models import NetworkModel +from subiquitycore.ui.views import (NetworkView, + NetworkSetDefaultRouteView, + NetworkBondInterfacesView, + NetworkConfigureInterfaceView, + NetworkConfigureIPv4InterfaceView) +from subiquitycore.ui.dummy import DummyView + +from subiquitycore.curtin import curtin_write_network_actions +from subiquitycore.curtin import curtin_apply_networking + +log = logging.getLogger("subiquitycore.controller.network") + + +class NetworkController(ControllerPolicy): + def __init__(self, common): + super().__init__(common) + self.model = NetworkModel(self.prober, self.opts) + + def network(self): + title = "Network connections" + excerpt = ("Configure at least the main interface this server will " + "use to talk to other machines, and preferably provide " + "sufficient access for updates.") + footer = ("Additional networking info here") + self.ui.set_header(title, excerpt) + self.ui.set_footer(footer, 20) + self.ui.set_body(NetworkView(self.model, self.signal)) + + def network_finish(self, actions): + try: + curtin_write_network_actions(actions) + except (PermissionError, FileNotFoundError): + log.exception('Failed to obtain write permission') + self.signal.emit_signal('filesystem:error', + 'curtin_write_network_actions') + return None + + curtin_apply_networking(actions, dryrun=self.opts.dry_run) + + # switch to identity view + self.signal.emit_signal('menu:identity:main') + + def set_default_route(self): + self.ui.set_header("Default route") + self.ui.set_body(NetworkSetDefaultRouteView(self.model, + self.signal)) + + def bond_interfaces(self): + self.ui.set_header("Bond interfaces") + self.ui.set_body(NetworkBondInterfacesView(self.model, + self.signal)) + + def network_configure_interface(self, iface): + self.ui.set_header("Network interface {}".format(iface)) + self.ui.set_body(NetworkConfigureInterfaceView(self.model, + self.signal, + iface)) + + def network_configure_ipv4_interface(self, iface): + self.model.prev_signal = ('Back to configure interface menu', + 'network:configure-interface-menu', + 'network_configure_interface') + self.ui.set_header("Network interface {} manual IPv4 " + "configuration".format(iface)) + self.ui.set_body(NetworkConfigureIPv4InterfaceView(self.model, + self.signal, + iface)) + + def network_configure_ipv6_interface(self, iface): + self.model.prev_signal = ('Back to configure interface menu', + 'network:configure-interface-menu', + 'network_configure_interface') + self.ui.set_body(DummyView(self.signal)) + + def install_network_driver(self): + self.ui.set_body(DummyView(self.signal)) diff --git a/console_conf/controllers/welcome.py b/console_conf/controllers/welcome.py new file mode 100644 index 00000000..76bc499a --- /dev/null +++ b/console_conf/controllers/welcome.py @@ -0,0 +1,31 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from console_conf.ui.views import WelcomeView +from subiquitycore.models import WelcomeModel +from subiquitycore.controllers.welcome import WelcomeController as WelcomeControllerBase + + +class WelcomeController(WelcomeControllerBase): + def welcome(self): + title = " Welcome!" + excerpt = "Please choose your preferred language" + footer = ("Use UP, DOWN arrow keys, and ENTER, to " + "select your language.") + self.ui.set_header(title, excerpt) + self.ui.set_footer(footer) + view = WelcomeView(self.model, self.signal) + self.ui.set_body(view) diff --git a/console_conf/core.py b/console_conf/core.py new file mode 100644 index 00000000..f11779fb --- /dev/null +++ b/console_conf/core.py @@ -0,0 +1,94 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +import urwid +from tornado.ioloop import IOLoop +from tornado.util import import_object +from subiquitycore.signals import Signal +from subiquitycore.palette import STYLES, STYLES_MONO +from subiquitycore.prober import Prober, ProberException + +from subiquitycore.core import CoreControllerError, Controller as ControllerBase + +log = logging.getLogger('console_conf.core') + + +class CoreControllerError(Exception): + """ Basecontroller exception """ + pass + + +class Controller(ControllerBase): + def __init__(self, ui, opts): + try: + prober = Prober(opts) + except ProberException as e: + err = "Prober init failed: {}".format(e) + log.exception(err) + raise CoreControllerError(err) + + self.common = { + "ui": ui, + "opts": opts, + "signal": Signal(), + "prober": prober, + "loop": None + } + self.controllers = { + "Welcome": None, + "Network": None, + "Identity": None, + "Login": None, + } + self.common['controllers'] = self.controllers + + def run(self): + if not hasattr(self, 'loop'): + palette = STYLES + additional_opts = { + 'screen': urwid.raw_display.Screen(), + 'unhandled_input': self.header_hotkeys, + 'handle_mouse': False + } + if self.common['opts'].run_on_serial: + palette = STYLES_MONO + else: + additional_opts['screen'].set_terminal_properties(colors=256) + additional_opts['screen'].reset_default_terminal_palette() + + evl = urwid.TornadoEventLoop(IOLoop()) + self.common['loop'] = urwid.MainLoop( + self.common['ui'], palette, event_loop=evl, **additional_opts) + log.debug("Running event loop: {}".format( + self.common['loop'].event_loop)) + + try: + self.set_alarm_in(0.05, self.welcome) + for k in self.controllers.keys(): + log.debug("Importing controller: {}".format(k)) + klass = import_object( + "subiquitycore.controllers.{}Controller".format( + k)) + klass = import_object( + "console_conf.controllers.{}Controller".format( + k)) + self.controllers[k] = klass(self.common) + + self._connect_base_signals() + self.common['loop'].run() + except: + log.exception("Exception in controller.run():") + raise diff --git a/console_conf/ui/__init__.py b/console_conf/ui/__init__.py new file mode 100644 index 00000000..ed2c4c08 --- /dev/null +++ b/console_conf/ui/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" Subiquity UI Components """ diff --git a/console_conf/ui/views/__init__.py b/console_conf/ui/views/__init__.py new file mode 100644 index 00000000..5fbc865c --- /dev/null +++ b/console_conf/ui/views/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" ConsoleConf UI Views """ + +from .welcome import WelcomeView diff --git a/console_conf/ui/views/welcome.py b/console_conf/ui/views/welcome.py new file mode 100644 index 00000000..ac3b0a46 --- /dev/null +++ b/console_conf/ui/views/welcome.py @@ -0,0 +1,36 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" Welcome + +Welcome provides user with language selection + +""" +import logging +from urwid import (ListBox, Pile, BoxAdapter) +from subiquitycore.ui.lists import SimpleList +from subiquitycore.ui.buttons import menu_btn, ok_btn, cancel_btn +from subiquitycore.ui.utils import Padding, Color +from subiquitycore.view import ViewPolicy +from subiquitycore.ui.views.welcome import CoreWelcomeView + +log = logging.getLogger("console_conf.views.welcome") + + +class WelcomeView(CoreWelcomeView): + def confirm(self, result): + self.model.selected_language = result.label + log.debug('calling network') + self.signal.emit_signal('menu:network:main') diff --git a/subiquitycore/ui/views/__init__.py b/subiquitycore/ui/views/__init__.py index afbd5623..5c00563c 100644 --- a/subiquitycore/ui/views/__init__.py +++ b/subiquitycore/ui/views/__init__.py @@ -30,6 +30,6 @@ from .network_configure_ipv4_interface import NetworkConfigureIPv4InterfaceView from .network_bond_interfaces import NetworkBondInterfacesView # NOQA from .installpath import InstallpathView # NOQA from .installprogress import ProgressView # NOQA -from .welcome import WelcomeView # NOQA +from .welcome import CoreWelcomeView as WelcomeView # NOQA from .identity import IdentityView # NOQA from .login import LoginView # NOQA diff --git a/subiquitycore/ui/views/welcome.py b/subiquitycore/ui/views/welcome.py index 40153ef5..1e14ff37 100644 --- a/subiquitycore/ui/views/welcome.py +++ b/subiquitycore/ui/views/welcome.py @@ -28,7 +28,7 @@ from subiquitycore.view import ViewPolicy log = logging.getLogger("subiquitycore.views.welcome") -class WelcomeView(ViewPolicy): +class CoreWelcomeView(ViewPolicy): def __init__(self, model, signal): self.model = model self.signal = signal