From 47539f1bef35049efc0c9c8b53bb332096f24a73 Mon Sep 17 00:00:00 2001 From: Mark Hargreaves Date: Thu, 1 Jul 2021 18:16:34 +0300 Subject: [PATCH] pmb/netboot: new feature (MR 2064) pmbootstrap netboot command exposes the generated vendor-codename.img rootfs through nbd interface so that device can mount it and boot postmarketOS without having any storage medium at all. Co-authored-by: Luca Weiss --- pmb/chroot/zap.py | 5 ++- pmb/config/__init__.py | 1 + pmb/helpers/frontend.py | 8 ++++- pmb/netboot/__init__.py | 68 +++++++++++++++++++++++++++++++++++++++++ pmb/parse/arguments.py | 16 ++++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 pmb/netboot/__init__.py diff --git a/pmb/chroot/zap.py b/pmb/chroot/zap.py index a60075a7..fa63038f 100644 --- a/pmb/chroot/zap.py +++ b/pmb/chroot/zap.py @@ -15,7 +15,7 @@ import pmb.parse.apkindex def zap(args, confirm=True, dry=False, pkgs_local=False, http=False, pkgs_local_mismatch=False, pkgs_online_mismatch=False, distfiles=False, - rust=False): + rust=False, netboot=False): """ Shutdown everything inside the chroots (e.g. distccd, adb), umount everything and then safely remove folders from the work-directory. @@ -29,6 +29,7 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False, downloaded from mirrors (e.g. from Alpine) :param distfiles: Clear the downloaded files cache :param rust: Remove rust related caches + :param netboot: Remove images for netboot NOTE: This function gets called in pmb/config/init.py, with only args.work and args.device set! @@ -65,6 +66,8 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False, patterns += ["cache_distfiles"] if rust: patterns += ["cache_rust"] + if netboot: + patterns += ["images_netboot"] # Delete everything matching the patterns for pattern in patterns: diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 29219183..2e70c18d 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -210,6 +210,7 @@ chroot_mount_bind = { "$WORK/cache_rust": "/mnt/pmbootstrap-rust", "$WORK/config_abuild": "/mnt/pmbootstrap-abuild-config", "$WORK/config_apk_keys": "/etc/apk/keys", + "$WORK/images_netboot": "/mnt/pmbootstrap-netboot", "$WORK/packages/$CHANNEL": "/mnt/pmbootstrap-packages", } diff --git a/pmb/helpers/frontend.py b/pmb/helpers/frontend.py index ab24c483..45cb9086 100644 --- a/pmb/helpers/frontend.py +++ b/pmb/helpers/frontend.py @@ -29,6 +29,7 @@ import pmb.helpers.aportupgrade import pmb.helpers.status import pmb.install import pmb.install.blockdevice +import pmb.netboot import pmb.parse import pmb.qemu @@ -140,6 +141,11 @@ def sideload(args): args.packages) +def netboot(args): + if args.action_netboot == "serve": + pmb.netboot.start_nbd_server(args) + + def chroot(args): # Suffix suffix = _parse_suffix(args) @@ -543,7 +549,7 @@ def zap(args): distfiles=args.distfiles, pkgs_local=args.pkgs_local, pkgs_local_mismatch=args.pkgs_local_mismatch, pkgs_online_mismatch=args.pkgs_online_mismatch, - rust=args.rust) + rust=args.rust, netboot=args.netboot) # Don't write the "Done" message pmb.helpers.logging.disable() diff --git a/pmb/netboot/__init__.py b/pmb/netboot/__init__.py new file mode 100644 index 00000000..fc35c9b8 --- /dev/null +++ b/pmb/netboot/__init__.py @@ -0,0 +1,68 @@ +# Copyright 2022 Mark Hargreaves, Luca Weiss +# SPDX-License-Identifier: GPL-3.0-or-later +import logging +import os +import socket +import time + +import pmb.chroot.root +import pmb.helpers.run + + +def start_nbd_server(args, ip="172.16.42.2", port=9999): + """ + Start nbd server in chroot_native with pmOS rootfs. + :param ip: IP address to serve nbd server for + :param port: port of nbd server + """ + + pmb.chroot.apk.install(args, ['nbd']) + + chroot = f"{args.work}/chroot_native" + + rootfs_path = f"/mnt/pmbootstrap-netboot/{args.device}.img" + if not os.path.exists(chroot + rootfs_path) or args.replace: + rootfs_path2 = f"/home/pmos/rootfs/{args.device}.img" + if not os.path.exists(chroot + rootfs_path2): + raise RuntimeError("The rootfs has not been generated yet, please " + "run 'pmbootstrap install' first.") + if args.replace and not \ + pmb.helpers.cli.confirm(args, f"Are you sure you want to " + f"replace the rootfs for " + f"{args.device}?"): + return + pmb.chroot.root(args, ["cp", rootfs_path2, rootfs_path]) + logging.info(f"NOTE: Copied device image to {args.work}" + f"/images_netboot/. The image will persist \"pmbootstrap " + f"zap\" for your convenience. Use \"pmbootstrap netboot " + f"serve --help\" for more options.") + + logging.info(f"Running nbd server for {args.device} on {ip} port {port}.") + + while True: + logging.info("Waiting for postmarketOS device to appear...") + + # Try to bind to the IP ourselves before handing it to nbd-servere + # This is purely to improve the UX as nbd-server just quits when it + # cannot bind to an IP address. + test_socket = socket.socket() + while True: + try: + test_socket.bind((ip, 9998)) + except OSError as e: + if e.errno != 99: # Cannot assign requested address + raise e + # Wait a bit before retrying + time.sleep(0.5) + continue + test_socket.close() + break + + logging.info("Found postmarketOS device, serving image...") + pmb.chroot.root( + args, ["nbd-server", f"{ip}@{port}", rootfs_path, "-d"], + check=False, disable_timeout=True) + logging.info("nbd-server quit. Connection lost?") + # On a reboot nbd-server will quit, but the IP address sticks around + # for a bit longer, so wait. + time.sleep(5) diff --git a/pmb/parse/arguments.py b/pmb/parse/arguments.py index 7f1a7145..2fd50d78 100644 --- a/pmb/parse/arguments.py +++ b/pmb/parse/arguments.py @@ -508,6 +508,19 @@ def arguments_status(subparser): return ret +def arguments_netboot(subparser): + ret = subparser.add_parser("netboot", + help="launch nbd server with pmOS rootfs") + sub = ret.add_subparsers(dest="action_netboot") + sub.required = True + + start = sub.add_parser("serve", help="start nbd server") + start.add_argument("--replace", action="store_true", + help="replace stored netboot image") + + return ret + + def package_completer(prefix, action, parser=None, parsed_args=None): args = parsed_args pmb.config.merge_with_args(args) @@ -646,6 +659,7 @@ def arguments(): arguments_kconfig(sub) arguments_export(sub) arguments_sideload(sub) + arguments_netboot(sub) arguments_flasher(sub) arguments_initfs(sub) arguments_qemu(sub) @@ -682,6 +696,8 @@ def arguments(): dest="pkgs_local_mismatch", help="also delete locally compiled packages without" " existing aport of same version") + zap.add_argument("-n", "--netboot", action="store_true", + help="also delete stored images for netboot") zap.add_argument("-o", "--pkgs-online-mismatch", action="store_true", dest="pkgs_online_mismatch", help="also delete outdated packages from online mirrors"