diff --git a/subiquity/server/apt.py b/subiquity/server/apt.py new file mode 100644 index 00000000..98517c2e --- /dev/null +++ b/subiquity/server/apt.py @@ -0,0 +1,109 @@ +# Copyright 2021 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 os +import tempfile + +from curtin.util import write_file + +from subiquitycore.lsb_release import lsb_release + +from subiquity.server.curtin import run_curtin_command + + +async def mount(runner, device, mountpoint, options=None, type=None): + opts = [] + if options is not None: + opts.extend(['-o', options]) + if type is not None: + opts.extend(['-t', type]) + await runner.run(['mount'] + opts + [device, mountpoint]) + + +async def unmount(runner, mountpoint): + await runner.run(['umount', mountpoint]) + + +async def setup_overlay(runner, dir): + tdir = tempfile.mkdtemp() + w = f'{tdir}/work' + u = f'{tdir}/upper' + for d in w, u: + os.mkdir(d) + await mount( + runner, 'overlay', dir, type='overlay', + options=f'lowerdir={dir},upperdir={u},workdir={w}') + + +async def configure_apt(app, context, config_location): + # Configure apt so that installs from the pool on the cdrom are + # preferred during installation but not in the installed system. + # + # This has a few steps. + # + # 1. As the remaining steps mean that any changes to apt configuration + # are do not persist into the installed system, we get curtin to + # configure apt a bit earlier than it would by default. + # + # 2. Bind-mount the cdrom into the installed system as /cdrom. + # + # 3. Set up an overlay over /target/etc/apt. This means that any + # changes we make will not persist into the installed system and we + # do not have to worry about cleaning up after ourselves. + # + # 4. Configure apt in /target to look at the pool on the cdrom. This + # has two subcases: + # + # a. if we expect the network to be working, this basically means + # prepending + # "deb file:///run/cdrom $(lsb_release -sc) main restricted" + # to the sources.list file. + # + # b. if the network is not expected to be working, we replace the + # sources.list with a file just referencing the cdrom. + # + # 5. If the network is not expected to be working, we also set up an + # overlay over /target/var/lib/apt/lists (if the network is working, + # we'll run "apt update" after the /target/etc/apt overlay has been + # cleared). + + def tpath(*args): + return os.path.join(app.base_model.target, *args) + + await run_curtin_command( + app, context, 'apt-config', '-t', tpath(), config=config_location) + + await setup_overlay(app.command_runner, tpath('etc/apt')) + + os.mkdir(tpath('cdrom')) + await mount(app.command_runner, '/cdrom', tpath('cdrom'), options='bind') + + if app.base_model.network.has_network: + os.rename( + tpath('etc/apt/sources.list'), + tpath('etc/apt/sources.list.d/original.list')) + else: + os.unlink(tpath('etc/apt/apt.conf.d/90curtin-aptproxy')) + await setup_overlay(app.command_runner, tpath('var/lib/apt/lists')) + + codename = lsb_release()['codename'] + + write_file( + tpath('etc/apt/sources.list'), + f'deb [check-date=no] file:///cdrom {codename} main restricted\n', + ) + + await run_curtin_command( + app, context, "in-target", "-t", tpath(), "--", "apt-get", "update") diff --git a/subiquity/server/controllers/install.py b/subiquity/server/controllers/install.py index f237955c..68073177 100644 --- a/subiquity/server/controllers/install.py +++ b/subiquity/server/controllers/install.py @@ -18,7 +18,6 @@ import logging import os import re import shutil -import tempfile from curtin.commands.install import ( ERROR_TARFILE, @@ -32,7 +31,6 @@ from subiquitycore.async_helpers import ( run_in_thread, ) from subiquitycore.context import with_context -from subiquitycore.lsb_release import lsb_release from subiquity.common.apidef import API from subiquity.common.errorreport import ErrorReportKind @@ -42,13 +40,14 @@ from subiquity.common.types import ( from subiquity.journald import ( journald_listen, ) +from subiquity.server.apt import configure_apt +from subiquity.server.controller import ( + SubiquityController, + ) from subiquity.server.curtin import ( run_curtin_command, start_curtin_command, ) -from subiquity.server.controller import ( - SubiquityController, - ) log = logging.getLogger("subiquity.server.controllers.install") @@ -265,86 +264,7 @@ class InstallController(SubiquityController): self.unattended_upgrades_cmd.proc.terminate() async def configure_apt_POST(self, context): - # Configure apt so that installs from the pool on the cdrom are - # preferred during installation but not in the installed system. - # - # This has a few steps. - # - # 1. As the remaining steps mean that any changes to apt configuration - # are do not persist into the installed system, we get curtin to - # configure apt a bit earlier than it would by default. - # - # 2. Bind-mount the cdrom into the installed system as /cdrom. - # - # 3. Set up an overlay over /target/etc/apt. This means that any - # changes we make will not persist into the installed system and we - # do not have to worry about cleaning up after ourselves. - # - # 4. Configure apt in /target to look at the pool on the cdrom. This - # has two subcases: - # - # a. if we expect the network to be working, this basically means - # prepending - # "deb file:///run/cdrom $(lsb_release -sc) main restricted" - # to the sources.list file. - # - # b. if the network is not expected to be working, we replace the - # sources.list with a file just referencing the cdrom. - # - # 5. If the network is not expected to be working, we also set up an - # overlay over /target/var/lib/apt/lists (if the network is working, - # we'll run "apt update" after the /target/etc/apt overlay has been - # cleared). - - async def mount(device, mountpoint, options=None, type=None): - opts = [] - if options is not None: - opts.extend(['-o', options]) - if type is not None: - opts.extend(['-t', type]) - await self.app.command_runner.run( - ['mount'] + opts + [device, mountpoint]) - - async def unmount(mountpoint): - await self.app.command_runner.run(['umount', mountpoint]) - - async def setup_overlay(dir): - tdir = tempfile.mkdtemp() - w = f'{tdir}/work' - u = f'{tdir}/upper' - for d in w, u: - os.mkdir(d) - await mount( - 'overlay', dir, type='overlay', - options=f'lowerdir={dir},upperdir={u},workdir={w}') - - await run_curtin_command( - self.app, context, 'apt-config', '-t', self.tpath(), - config=self.config_location) - - await setup_overlay(self.tpath('etc/apt')) - - os.mkdir(self.tpath('cdrom')) - await mount('/cdrom', self.tpath('cdrom'), options='bind') - - if self.model.network.has_network: - os.rename( - self.tpath('etc/apt/sources.list'), - self.tpath('etc/apt/sources.list.d/original.list')) - else: - os.unlink(self.tpath('etc/apt/apt.conf.d/90curtin-aptproxy')) - await setup_overlay(self.tpath('var/lib/apt/lists')) - - codename = lsb_release()['codename'] - - write_file( - self.tpath('etc/apt/sources.list'), - f'deb [check-date=no] file:///cdrom {codename} main restricted\n', - ) - - await run_curtin_command( - self.app, context, "in-target", "-t", self.tpath(), - "--", "apt-get", "update") + await configure_apt(self.app, context, self.config_location) uu_apt_conf = b"""\