From patchwork Thu May 7 22:14:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2283 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778192132; bh=b3g3bskZ3sIhIj944TZp2RXzjuyz2Sfju8DwZHXxpjQ=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=BzlbfUtTQWLtcAYEYAldDlq8dCbc2T9il95IZgzZsN12zvmbRubzkOGohNmb5EnZ1 TOeRNj5VPufqju7LAwG+bCy3hDFLq7KYD8HHgNIm3vv3BK3pAC+zO1EVxjMyH102E2 Qlm5wy74x8M2bBA/SbUDIgCkZA1xjBVmDvYeZeBc= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id B8A676A9B7 for ; Thu, 7 May 2026 16:15:32 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id QcJ8ARQyVhyS for ; Thu, 7 May 2026 16:15:32 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778192131; bh=b3g3bskZ3sIhIj944TZp2RXzjuyz2Sfju8DwZHXxpjQ=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=XNhC1EQyjN6bC9c6SCzTSAK+6Yt03l/zudoc+1Nw13f8ZMndQKw7qVgcIgf4YRatp b14OFeCdz9MUsdA/5bcOiND5qQly8NquysMJBJ3Y5wZ8SqX4zdyIhMheVlHYqfdR7a lw6E3EE+vhQveAWsV9W3gY8hAlKoMXPQ9MlOXrdk= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9B0086A9B8 for ; Thu, 7 May 2026 16:15:31 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778192127; bh=cNkjRQGW52YCMcy2AP2u94bu85U5Rr/fCP0Hp0hyVgc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ii1U/a9WJEl34+TwzrjINyT0PY5ZNdTYVtr6LgJB2KztpVJ9UWhUew2IanJ84iMDs f4Vd+j7yFgm3CCSv4jpHE5pEnra3a1wCe9qkqP1usehMkLbQPee2ybEjyqzwMI4v/+ /CfR938s3uWi4xBnlP/o8VGhaIbN9zN79fqV98CQ= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D0E566A9A8; Thu, 7 May 2026 16:15:27 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id ZIrfNeUVoCQN; Thu, 7 May 2026 16:15:27 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778192125; bh=R13ecaeEVkAXSH42QO4D+wb+1j+ZKM6qfTivNWCEprM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sCPe5esQZy7q9+nCMidOwSbdaswJOmNJ1xMlykX0u/N6aM4pegLSLvm7q9Wvx+xRu d3YILgnswfzJDxPJzTpe25T1ov8QSNXi8LHM0GVgyj5P4dhD4TKxDhVFki8wxactUi gK+9JxieYC6S02WPh0H8FEmrQtXt3ih1PDxA5was= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id BA3556A9B4; Thu, 7 May 2026 16:15:25 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Thu, 7 May 2026 16:14:50 -0600 Message-ID: <20260507221507.505998-6-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260507221507.505998-1-sjg@u-boot.org> References: <20260507221507.505998-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: TCFCPFWBOS7RAW33RMUID64CLNM76BZV X-Message-ID-Hash: TCFCPFWBOS7RAW33RMUID64CLNM76BZV X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 05/13] scripts: ubuntu: Add unattended autoinstall mode List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The rewritten ISO already ships /autoinstall.yaml, but the snippet only declares packages and late-commands. Anyone invoking subiquity with the 'autoinstall' kernel argument hits a halt on the missing identity and storage sections, so there is no way to drive an end-to-end install from the test harness. Add -A / --autoinstall. When set, the script: - appends 'autoinstall' to the BLS kernel cmdline so subiquity picks up /autoinstall.yaml on first boot - emits a complete autoinstall.yaml with interactive-sections: [], identity (hostname, username, hashed password), storage layout (direct), refresh-installer disabled, and openssh-server so a test can SSH in afterwards The password is supplied as plaintext via --password and hashed with 'openssl passwd -6' at build time, keeping the script's runtime dependencies unchanged - Python's crypt module is gone in 3.13. --hostname and --username round out the identity block. Guard against --autoinstall being combined with --no-target-bls (the yaml is mandatory in unattended mode) and fail early if openssl is missing. Signed-off-by: Simon Glass --- scripts/ubuntu-iso-to-uboot.py | 94 +++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/scripts/ubuntu-iso-to-uboot.py b/scripts/ubuntu-iso-to-uboot.py index 1c09e226d16..c22bd9423f2 100755 --- a/scripts/ubuntu-iso-to-uboot.py +++ b/scripts/ubuntu-iso-to-uboot.py @@ -65,6 +65,7 @@ import argparse import os import re import shutil +import subprocess import sys import tempfile from pathlib import Path @@ -313,7 +314,26 @@ mksquashfs '{stage}' '{modified}' -noappend -comp xz -no-progress return modified -def autoinstall_yaml() -> str: +def hash_password(password: str) -> str: + """Return a SHA-512 crypt hash for @password via openssl passwd -6. + + Subiquity's identity.password field wants a crypt(3) hash, not a plaintext + password. Shelling out to openssl keeps the script's dependency list + unchanged (Python's crypt module is gone in 3.13). + """ + out = subprocess.run( + ['openssl', 'passwd', '-6', '-stdin'], + input=password, capture_output=True, text=True, check=True, + ) + return out.stdout.strip() + + +def autoinstall_yaml( + unattended: bool = False, + hostname: str = 'ubuntu-uboot', + username: str = 'ubuntu', + password_hash: str = '', +) -> str: """Return an autoinstall snippet that seeds BLS entries on the installed ESP. @@ -329,11 +349,33 @@ def autoinstall_yaml() -> str: The kernel-install path is bypassed because curtin's chroot has /boot/efi as a plain directory rather than the ESP mountpoint, so systemd-boot-efi's 90-loaderentry.install plugin silently exits without writing entries. + + When @unattended is True, also emit identity and storage sections so + subiquity can complete without user input - required for the + test_distro_ubuntu_iso_install CI path. """ - return ( - '#cloud-config\n' - 'autoinstall:\n' - ' version: 1\n' + head = '''\ +#cloud-config +autoinstall: + version: 1 +''' + unattended_block = '' + if unattended: + unattended_block = f'''\ + interactive-sections: [] + refresh-installer: + update: no + identity: + hostname: {hostname} + username: {username} + password: '{password_hash}' + storage: + layout: + name: direct + ssh: + install-server: true +''' + body = ( ' late-commands:\n' # Write BLS entries to the install target's ESP. The kernel # and initrd are copied alongside on the ESP so the entry can @@ -371,6 +413,7 @@ options root=UUID=%s ro console=ttyS0,115200 console=tty0\\n"\ fi' ''' ) + return head + unattended_block + body def main() -> None: @@ -402,6 +445,20 @@ def main() -> None: help='path inside the ISO of the install squashfs to ' 'modify for interactive installs ' '(default: %(default)s; set to empty to skip)') + p.add_argument('-A', '--autoinstall', action='store_true', + help='build for unattended autoinstall: append ' + '"autoinstall" to the BLS cmdline and ship a complete ' + 'autoinstall.yaml so subiquity runs without user input') + p.add_argument('--hostname', default='ubuntu-uboot', + help='hostname for the autoinstalled system ' + '(default: %(default)s)') + p.add_argument('--username', default='ubuntu', + help='username created by autoinstall ' + '(default: %(default)s)') + p.add_argument('--password', default='ubuntu', + help='plaintext password for the autoinstall user; ' + 'hashed with `openssl passwd -6` before being ' + 'written to autoinstall.yaml (default: %(default)s)') p.add_argument('-v', '--verbose', action='store_true', help='show progress markers and subprocess output') args = p.parse_args() @@ -414,7 +471,11 @@ def main() -> None: tout.fatal(f'ISO not found: {args.iso}') if not args.uboot.is_file(): tout.fatal(f'EFI app not found: {args.uboot}') + if args.autoinstall and args.no_target_bls: + tout.fatal('--autoinstall requires target-BLS wiring; drop -N') check_tools() + if args.autoinstall and not shutil.which('openssl'): + tout.fatal('openssl is required when --autoinstall is set') tout.notice(f'=> Reading boot config from {args.iso}') # Extract the volume label and ESP partition GUID from xorriso's report @@ -423,6 +484,19 @@ def main() -> None: cmdline = args.cmdline if cmdline is None: cmdline = parse_grub_cmdline(args.iso, args.kernel) + if args.autoinstall: + # Force subiquity onto the serial console so the CI harness can watch + # progress: drop the live-ISO 'quiet splash' (kernel would otherwise + # silently swallow the reboot line), pin the console to ttyS0, and ask + # systemd-journald to mirror its stream onto the console so subiquity's + # own events land on serial too. + tokens = [t for t in cmdline.split() if t not in ('quiet', 'splash')] + for extra in ('console=ttyS0,115200', + 'systemd.journald.forward_to_console=1', + 'autoinstall'): + if extra not in tokens: + tokens.append(extra) + cmdline = ' '.join(tokens) tout.notice(f' Volume label: {vol_id}') tout.notice(f' ESP GUID: {esp_guid}') tout.notice(f' Cmdline: {cmdline}') @@ -446,7 +520,15 @@ def main() -> None: file_maps = [(entry, '/loader/entry.conf')] if not args.no_target_bls: ai = work / 'autoinstall.yaml' - ai.write_text(autoinstall_yaml()) + if args.autoinstall: + ai.write_text(autoinstall_yaml( + unattended=True, + hostname=args.hostname, + username=args.username, + password_hash=hash_password(args.password), + )) + else: + ai.write_text(autoinstall_yaml()) file_maps.append((ai, '/autoinstall.yaml')) if args.install_squashfs: modified_sqfs = inject_first_boot_unit(