diff --git a/debian/changelog b/debian/changelog index 22b3b2cf..36c76579 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +livecd-rootfs (24.04.75) UNRELEASED; urgency=medium + + * Add 'ubuntu-core-installer' project. + + -- Michael Hudson-Doyle Thu, 25 Jul 2024 17:26:08 +1200 + livecd-rootfs (24.04.74) noble; urgency=medium * riscv64: preinstalled server image for Microchip PIC64GX Curiosity Kit diff --git a/live-build/auto/config b/live-build/auto/config index e30fd6b4..4933d975 100755 --- a/live-build/auto/config +++ b/live-build/auto/config @@ -382,7 +382,7 @@ if [ -z "${IMAGEFORMAT:-}" ]; then ;; esac ;; - ubuntu-server:live|ubuntu-mini-iso:) + ubuntu-server:live|ubuntu-mini-iso:|ubuntu-core-installer:) IMAGEFORMAT=plain ;; esac @@ -422,7 +422,7 @@ case $IMAGEFORMAT in ;; plain) case $PROJECT:${SUBPROJECT:-} in - ubuntu-server:live) + ubuntu-server:live|ubuntu-core-installer:) touch config/universe-enabled ;; ubuntu-mini-iso:) @@ -1047,6 +1047,22 @@ case $PROJECT in PREINSTALL_POOL_SEEDS='server-ship' ;; + ubuntu-core-installer) + # The ubuntu-core-installer image is an installer that installs ubuntu + # core. The environment the installer runs in is similar to the server + # installer but it has a source catalog entry that points to the model + # created in ubuntu-core-installer/hooks/05-prepare-image.binary, which + # subiquity knows how to install. + OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal" + PASSES_TO_LAYERS=true + add_task base server-minimal server + add_task base.live server-live + add_package base.live linux-image-generic + + /usr/share/livecd-rootfs/checkout-translations-branch \ + https://git.launchpad.net/subiquity po config/catalog-translations + ;; + ubuntu-mini-iso) OPTS="${OPTS:+$OPTS }--bootstrap-flavour=minimal" @@ -1242,7 +1258,7 @@ case "$ARCH${SUBARCH:++$SUBARCH}" in esac case $PROJECT:${SUBPROJECT:-} in - ubuntu-server:*|ubuntu-base:*|ubuntu-oci:*) + ubuntu-server:*|ubuntu-base:*|ubuntu-oci:*|ubuntu-core-installer:*) OPTS="${OPTS:+$OPTS }--linux-packages=none --initramfs=none" KERNEL_FLAVOURS=none BINARY_REMOVE_LINUX=false @@ -1439,7 +1455,7 @@ case $PROJECT:${SUBPROJECT:-} in ubuntu-cpc:*|ubuntu-server:live|ubuntu:desktop-preinstalled| \ ubuntu-wsl:*|ubuntu-mini-iso:*|ubuntu:|ubuntu-oem:*| \ ubuntustudio-dvd:*|edubuntu:*|ubuntu-budgie:*|ubuntucinnamon:*|xubuntu:*| \ - ubuntukylin:*|ubuntu-mate:*|ubuntu-core-desktop:*) + ubuntukylin:*|ubuntu-mate:*|ubuntu-core-desktop:*|ubuntu-core-installer:*) # Ensure that most things e.g. includes.chroot are copied as is for entry in /usr/share/livecd-rootfs/live-build/${PROJECT}/*; do case $entry in diff --git a/live-build/ubuntu-core-installer/hooks/01-setup_modules.chroot b/live-build/ubuntu-core-installer/hooks/01-setup_modules.chroot new file mode 100755 index 00000000..87af73c2 --- /dev/null +++ b/live-build/ubuntu-core-installer/hooks/01-setup_modules.chroot @@ -0,0 +1,5 @@ +#!/bin/sh -x + +set -e + +mkdir -p /lib/modules diff --git a/live-build/ubuntu-core-installer/hooks/02-installer-bits.chroot b/live-build/ubuntu-core-installer/hooks/02-installer-bits.chroot new file mode 100755 index 00000000..41aae676 --- /dev/null +++ b/live-build/ubuntu-core-installer/hooks/02-installer-bits.chroot @@ -0,0 +1,17 @@ +#!/bin/bash -ex +# vi: ts=4 noexpandtab + +if [ "${PASS}" != "base.live" ]; then + exit 0 +fi + +# Make sure NoCloud is last +values=$(echo get cloud-init/datasources | debconf-communicate | sed 's/^0 //;s/NoCloud, //;s/None/NoCloud, None/') +printf "%s\t%s\t%s\t%s\n" \ + cloud-init cloud-init/datasources multiselect "$values" | debconf-set-selections +dpkg-reconfigure --frontend=noninteractive cloud-init + +if [ `dpkg --print-architecture` = s390x ]; then + # because z/VM x3270 is just ttyS0 + cp -r /usr/lib/systemd/system/serial-getty@sclp_line0.service.d /usr/lib/systemd/system/serial-getty@ttyS0.service.d +fi diff --git a/live-build/ubuntu-core-installer/hooks/03-kernel-metapkg.chroot_early b/live-build/ubuntu-core-installer/hooks/03-kernel-metapkg.chroot_early new file mode 100755 index 00000000..f6ad5c7e --- /dev/null +++ b/live-build/ubuntu-core-installer/hooks/03-kernel-metapkg.chroot_early @@ -0,0 +1,19 @@ +#!/bin/bash -ex +# vi: ts=4 noexpandtab + +case $PASS in + base.live) + ;; + *) + exit 0 + ;; +esac + + +cat < /etc/initramfs-tools/conf.d/casperize.conf +export CASPER_GENERATE_UUID=1 +EOF + +cat < /etc/initramfs-tools/conf.d/default-layer.conf +LAYERFS_PATH=${PASS}.squashfs +EOF diff --git a/live-build/ubuntu-core-installer/hooks/04-kernel-bits.binary b/live-build/ubuntu-core-installer/hooks/04-kernel-bits.binary new file mode 100755 index 00000000..6c0ca200 --- /dev/null +++ b/live-build/ubuntu-core-installer/hooks/04-kernel-bits.binary @@ -0,0 +1,17 @@ +#!/bin/bash -eux +# vi: ts=4 noexpandtab + +case $PASS in + base.live) + ;; + *) + exit 0 + ;; +esac + +PROJECT=$PROJECT${SUBARCH:+-$SUBARCH} + +# Fish out generated kernel image and initrd +mv chroot/boot/initrd.img-* ${PWD}/livecd.${PROJECT}.initrd-generic +mv chroot/boot/vmlinu?-* ${PWD}/livecd.${PROJECT}.kernel-generic +chmod a+r ${PWD}/livecd.${PROJECT}.initrd-generic ${PWD}/livecd.${PROJECT}.kernel-generic diff --git a/live-build/ubuntu-core-installer/hooks/05-prepare-image.binary b/live-build/ubuntu-core-installer/hooks/05-prepare-image.binary new file mode 100644 index 00000000..2e1f3146 --- /dev/null +++ b/live-build/ubuntu-core-installer/hooks/05-prepare-image.binary @@ -0,0 +1,38 @@ +#!/bin/bash + +set -eux + +case ${PASS:-} in + base.live) + ;; + *) + exit 0 + ;; +esac + +. config/binary +. config/functions + +env SNAPPY_STORE_NO_CDN=1 snap known --remote model series=16 brand-id=canonical model=ubuntu-core-24-amd64 > config/ubuntu-core-24-amd64.model + +env SNAPPY_STORE_NO_CDN=1 snap prepare-image \ + config/ubuntu-core-24-amd64.model --snap console-conf chroot +mv chroot/system-seed/systems/* chroot/system-seed/systems/ubuntu-core-24-amd64 +rsync -av chroot/system-seed/{systems,snaps} chroot/var/lib/snapd/seed +rm -rf chroot/system-seed + +cat <<-EOF > config/edge.catalog-in.yaml +name: "Ubuntu Core 24" +description: >- + Ubuntu Core. +id: ubuntu-core +type: null +variant: core +locale_support: none +snapd_system_label: ubuntu-core-24-amd64 +EOF +PROJECT_FULL=$PROJECT${SUBARCH:+-$SUBARCH} +usc_opts="--output livecd.${PROJECT_FULL}.install-sources.yaml \ + --template config/edge.catalog-in.yaml \ + --size 0" +/usr/share/livecd-rootfs/update-source-catalog $usc_opts diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/cloud/cloud.cfg b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/cloud/cloud.cfg new file mode 100644 index 00000000..e9571afd --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/cloud/cloud.cfg @@ -0,0 +1,117 @@ +# The top level settings are used as module +# and system configuration. + +# A set of users which may be applied and/or used by various modules +# when a 'default' entry is found it will reference the 'default_user' +# from the distro configuration specified below +users: + - default + +# If this is set, 'root' will not be able to ssh in and they +# will get a message to login instead as the default $user +disable_root: true + +# This will cause the set+update hostname module to not operate (if true) +preserve_hostname: true + +ssh_pwauth: yes +chpasswd: + expire: false + +# This is the initial network config. +# It can be overwritten by cloud-init or subiquity. +network: + version: 2 + ethernets: + zz-all-en: + match: + name: "en*" + dhcp4: true + zz-all-eth: + match: + name: "eth*" + dhcp4: true + +# We used to have a custom final_message here. Just use the default instead. + +# Example datasource config +# datasource: +# Ec2: +# metadata_urls: [ 'blah.com' ] +# timeout: 5 # (defaults to 50 seconds) +# max_wait: 10 # (defaults to 120 seconds) + +# The modules that run in the 'init' stage +cloud_init_modules: + - bootcmd + - write-files + - ca-certs + - rsyslog + - users-groups + - ssh + +# The modules that run in the 'config' stage +cloud_config_modules: +# Emit the cloud config ready event +# this can be used by upstart jobs for 'start on cloud-config'. + - ssh-import-id + - set-passwords + - timezone + - disable-ec2-metadata + - runcmd + +# The modules that run in the 'final' stage +cloud_final_modules: + - scripts-per-once + - scripts-user + - ssh-authkey-fingerprints + - keys-to-console + - phone-home + - final-message + +# System and/or distro specific settings +# (not accessible to handlers/transforms) +system_info: + # This will affect which distro class gets used + distro: ubuntu + # Default user name + that default users groups (if added/used) + default_user: + name: installer + lock_passwd: false + gecos: Ubuntu + groups: [adm, audio, cdrom, dialout, dip, floppy, lxd, netdev, plugdev, sudo, video] + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + shell: /usr/bin/subiquity-shell + # Automatically discover the best ntp_client + ntp_client: auto + # Other config here will be given to the distro class and/or path classes + paths: + cloud_dir: /var/lib/cloud/ + templates_dir: /etc/cloud/templates/ + upstart_dir: /etc/init/ + package_mirrors: + - arches: [i386, amd64] + failsafe: + primary: http://archive.ubuntu.com/ubuntu + security: http://security.ubuntu.com/ubuntu + search: + primary: + - http://%(ec2_region)s.ec2.archive.ubuntu.com/ubuntu/ + - http://%(availability_zone)s.clouds.archive.ubuntu.com/ubuntu/ + - http://%(region)s.clouds.archive.ubuntu.com/ubuntu/ + security: [] + - arches: [arm64, armel, armhf] + failsafe: + primary: http://ports.ubuntu.com/ubuntu-ports + security: http://ports.ubuntu.com/ubuntu-ports + search: + primary: + - http://%(ec2_region)s.ec2.ports.ubuntu.com/ubuntu-ports/ + - http://%(availability_zone)s.clouds.ports.ubuntu.com/ubuntu-ports/ + - http://%(region)s.clouds.ports.ubuntu.com/ubuntu-ports/ + security: [] + - arches: [default] + failsafe: + primary: http://ports.ubuntu.com/ubuntu-ports + security: http://ports.ubuntu.com/ubuntu-ports + ssh_svcname: ssh diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/cloud/cloud.cfg.d/06_quiet.cfg b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/cloud/cloud.cfg.d/06_quiet.cfg new file mode 100644 index 00000000..a614e723 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/cloud/cloud.cfg.d/06_quiet.cfg @@ -0,0 +1,4 @@ +output: {all: '>> /var/log/cloud-init-output.log'} +no_ssh_fingerprints: true +ssh: + emit_keys_to_console: false diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/journald.conf.d/no-rate-limit.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/journald.conf.d/no-rate-limit.conf new file mode 100644 index 00000000..fbaab956 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/journald.conf.d/no-rate-limit.conf @@ -0,0 +1,2 @@ +[Journal] +RateLimitIntervalSec=0 diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/system/systemd-journald.service.d/no-compact.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/system/systemd-journald.service.d/no-compact.conf new file mode 100644 index 00000000..a0701e52 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/system/systemd-journald.service.d/no-compact.conf @@ -0,0 +1,4 @@ +# systemd in 23.04+ uses a newer "compact" format by default which is not +# understood by the systemd libraries from jammy used in the subiquity snap. +[Service] +Environment="SYSTEMD_JOURNAL_COMPACT=0" diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/system/systemd-journald.service.d/no-hardening.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/system/systemd-journald.service.d/no-hardening.conf new file mode 100644 index 00000000..c4262d26 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/etc/systemd/system/systemd-journald.service.d/no-hardening.conf @@ -0,0 +1,4 @@ +# systemd in 22.04+ uses "hash table hardening" by default which is not +# understood by the systemd libraries from focal used in the subiquity snap. +[Service] +Environment="SYSTEMD_JOURNAL_KEYED_HASH=0" diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/bin/subiquity-shell b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/bin/subiquity-shell new file mode 100755 index 00000000..d739d86f --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/bin/subiquity-shell @@ -0,0 +1,3 @@ +#!/bin/sh +exec sudo snap run subiquity + diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/getty@.service.d/autologin.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/getty@.service.d/autologin.conf new file mode 100644 index 00000000..0d32e7f6 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/getty@.service.d/autologin.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=-/sbin/agetty --noclear -n --autologin ubuntu-core-installer %I $TERM diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/getty@tty1.service b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/getty@tty1.service new file mode 120000 index 00000000..dc1dc0cd --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/getty@tty1.service @@ -0,0 +1 @@ +/dev/null \ No newline at end of file diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/local-fs.target.wants/media-filesystem.mount b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/local-fs.target.wants/media-filesystem.mount new file mode 120000 index 00000000..0a663081 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/local-fs.target.wants/media-filesystem.mount @@ -0,0 +1 @@ +../media-filesystem.mount \ No newline at end of file diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/serial-getty@.service.d/subiquity-serial.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/serial-getty@.service.d/subiquity-serial.conf new file mode 100644 index 00000000..077184fa --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/serial-getty@.service.d/subiquity-serial.conf @@ -0,0 +1,8 @@ +[Unit] +Description=Subiquity, the installer for Ubuntu Server %I +StartLimitInterval=0 + +[Service] +Environment=SNAP_REEXEC=0 +ExecStart= +ExecStart=/usr/bin/snap run subiquity.subiquity-service %I diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/serial-getty@sclp_line0.service.d/subiquity-serial.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/serial-getty@sclp_line0.service.d/subiquity-serial.conf new file mode 100644 index 00000000..56900a35 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/serial-getty@sclp_line0.service.d/subiquity-serial.conf @@ -0,0 +1,4 @@ +[Service] +StandardOutput=tty +ExecStart= +ExecStart=/usr/bin/snap run subiquity --ssh diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/snap.subiquity.subiquity-service.service.d/subiquity.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/snap.subiquity.subiquity-service.service.d/subiquity.conf new file mode 100644 index 00000000..6500a675 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/snap.subiquity.subiquity-service.service.d/subiquity.conf @@ -0,0 +1,13 @@ +[Unit] +IgnoreOnIsolate=yes + +[Service] +Environment=SNAP_REEXEC=0 +UtmpIdentifier=tty1 +TTYPath=/dev/tty1 +TTYReset=yes +TTYVHangup=yes +TTYVTDisallocate=yes +KillMode=process +IgnoreSIGPIPE=no +SendSIGHUP=yes diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/snapd.service.d/no-reexec.conf b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/snapd.service.d/no-reexec.conf new file mode 100644 index 00000000..14a6b477 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/system/snapd.service.d/no-reexec.conf @@ -0,0 +1,2 @@ +[Service] +Environment=SNAP_REEXEC=0 diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/systemd-networkd-wait-online b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/systemd-networkd-wait-online new file mode 120000 index 00000000..63b10de4 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base.live/usr/lib/systemd/systemd-networkd-wait-online @@ -0,0 +1 @@ +/bin/true \ No newline at end of file diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/var/lib/cloud/seed/nocloud/meta-data b/live-build/ubuntu-core-installer/includes.chroot.base.live/var/lib/cloud/seed/nocloud/meta-data new file mode 100644 index 00000000..e69de29b diff --git a/live-build/ubuntu-core-installer/includes.chroot.base.live/var/lib/cloud/seed/nocloud/user-data b/live-build/ubuntu-core-installer/includes.chroot.base.live/var/lib/cloud/seed/nocloud/user-data new file mode 100644 index 00000000..e69de29b diff --git a/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/multi-user.target.wants/systemd-networkd.service b/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/multi-user.target.wants/systemd-networkd.service new file mode 120000 index 00000000..3c55b243 --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/multi-user.target.wants/systemd-networkd.service @@ -0,0 +1 @@ +/lib/systemd/system/systemd-networkd.service \ No newline at end of file diff --git a/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service b/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service new file mode 120000 index 00000000..3b627c5c --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service @@ -0,0 +1 @@ +/lib/systemd/system/systemd-networkd-wait-online.service \ No newline at end of file diff --git a/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/sockets.target.wants/systemd-networkd.socket b/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/sockets.target.wants/systemd-networkd.socket new file mode 120000 index 00000000..bcfcdbac --- /dev/null +++ b/live-build/ubuntu-core-installer/includes.chroot.base/etc/systemd/system/sockets.target.wants/systemd-networkd.socket @@ -0,0 +1 @@ +/lib/systemd/system/systemd-networkd.socket \ No newline at end of file diff --git a/update-source-catalog b/update-source-catalog index 6de025de..9abf3c5e 100755 --- a/update-source-catalog +++ b/update-source-catalog @@ -10,9 +10,9 @@ import yaml parser = argparse.ArgumentParser() parser.add_argument('--output', required=True) parser.add_argument('--size', required=True) -parser.add_argument('--squashfs', required=True) -parser.add_argument('--translations', required=True) -parser.add_argument('--template', required=True) +parser.add_argument('--squashfs', default='') +parser.add_argument('--translations') +parser.add_argument('--template') parser.add_argument('--langs', default=None) opts = parser.parse_args(sys.argv[1:]) @@ -58,19 +58,20 @@ else: template['name'] = {'en': en_name} template['description'] = {'en': en_description} - for mo in glob.glob(os.path.join(opts.translations, '*.mo')): - with open(mo, 'rb') as fp: - t = gettext.GNUTranslations(fp=fp) - t_name = t.gettext(en_name) - if t_name != en_name: - lang = os.path.splitext(os.path.basename(mo))[0] - template['name'][lang] = t_name - t_description = t.gettext(en_description) - if t_description != en_description: - lang = os.path.splitext(os.path.basedescription(mo))[0] - template['description'][lang] = t_description - if opts.langs is not None: - template['preinstalled_langs'] = opts.langs.split(',') + if opts.translations: + for mo in glob.glob(os.path.join(opts.translations, '*.mo')): + with open(mo, 'rb') as fp: + t = gettext.GNUTranslations(fp=fp) + t_name = t.gettext(en_name) + if t_name != en_name: + lang = os.path.splitext(os.path.basename(mo))[0] + template['name'][lang] = t_name + t_description = t.gettext(en_description) + if t_description != en_description: + lang = os.path.splitext(os.path.basedescription(mo))[0] + template['description'][lang] = t_description + if opts.langs is not None: + template['preinstalled_langs'] = opts.langs.split(',') output.append(template)