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