Add script to build Bookworm rootfs with libargus support
This is the minimal amount needed to build a bookworm rootfs which boots
and is able to take pictures with the imx296 sensor
Note: the paths are pretty hard-coded right now, they need to be
improved.
Flashing is done using ./doflash_frc971.sh
Change-Id: Ibf480d3f5b0aed553ba71431f8a94a8e5290a98f
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/frc971/orin/build_rootfs.py b/frc971/orin/build_rootfs.py
new file mode 100755
index 0000000..f82bed7
--- /dev/null
+++ b/frc971/orin/build_rootfs.py
@@ -0,0 +1,412 @@
+#!/usr/bin/python3
+
+import contextlib
+import pathlib
+import collections
+import subprocess
+import shlex
+import os
+
+IMAGE = "arm64_bookworm_debian_yocto.img"
+YOCTO = "/home/austin/local/jetpack/robot-yocto/build"
+
+REQUIRED_DEPS = ["debootstrap", "u-boot-tools"]
+
+
+@contextlib.contextmanager
+def scoped_loopback(image):
+ """Mounts an image as a loop back device."""
+ result = subprocess.run(["sudo", "losetup", "--show", "-f", image],
+ check=True,
+ stdout=subprocess.PIPE)
+ device = result.stdout.decode('utf-8').strip()
+ print("Mounted", image, "to", repr(device))
+ try:
+ yield device
+ finally:
+ subprocess.run(["sudo", "losetup", "-d", device], check=True)
+
+
+@contextlib.contextmanager
+def scoped_mount(image):
+ """Mounts an image as a partition."""
+ partition = f"{image}.partition"
+ try:
+ os.mkdir(partition)
+ except FileExistsError:
+ pass
+
+ result = subprocess.run(["sudo", "mount", "-o", "loop", image, partition],
+ check=True)
+
+ try:
+ yield partition
+ finally:
+ subprocess.run(
+ ["sudo", "rm", f"{partition}/usr/bin/qemu-aarch64-static"])
+ subprocess.run(["sudo", "umount", partition], check=True)
+
+
+def check_required_deps(deps):
+ """Checks if the provided list of dependencies is installed."""
+ missing_deps = []
+ for dep in deps:
+ result = subprocess.run(["dpkg-query", "-W", "-f='${Status}'", dep],
+ check=True,
+ stdout=subprocess.PIPE)
+
+ if "install ok installed" not in result.stdout.decode('utf-8'):
+ missing_deps.append(dep)
+
+ if len(missing_deps) > 0:
+ print("Missing dependencies, please install:")
+ print("sudo apt-get install", " ".join(missing_deps))
+
+
+def make_image(image):
+ """Makes an image and creates an xfs filesystem on it."""
+ result = subprocess.run([
+ "dd", "if=/dev/zero", f"of={image}", "bs=1", "count=0",
+ "seek=8589934592"
+ ],
+ check=True)
+
+ with scoped_loopback(image) as loopback:
+ subprocess.run([
+ "sudo", "mkfs.xfs", "-d", "su=128k", "-d", "sw=1", "-L", "rootfs",
+ loopback
+ ],
+ check=True)
+
+
+def target_unescaped(cmd):
+ """Runs a command as root with bash -c cmd, ie without escaping."""
+ subprocess.run([
+ "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
+ "qemu-aarch64-static", "/bin/bash", "-c", cmd
+ ],
+ check=True)
+
+
+def target(cmd):
+ """Runs a command as root with escaping."""
+ target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
+
+
+def pi_target_unescaped(cmd):
+ """Runs a command as pi with bash -c cmd, ie without escaping."""
+ subprocess.run([
+ "sudo", "chroot", "--userspec=pi:pi", "--groups=pi", f"{PARTITION}",
+ "qemu-aarch64-static", "/bin/bash", "-c", cmd
+ ],
+ check=True)
+
+
+def pi_target(cmd):
+ """Runs a command as pi with escaping."""
+ pi_target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
+
+
+def copyfile(owner, permissions, file):
+ """Copies a file from contents/{file} with the provided owner and permissions."""
+ print("copyfile", owner, permissions, file)
+ subprocess.run(["sudo", "cp", f"contents/{file}", f"{PARTITION}/{file}"],
+ check=True)
+ subprocess.run(["sudo", "chmod", permissions, f"{PARTITION}/{file}"],
+ check=True)
+ target(["chown", owner, f"/{file}"])
+
+
+def target_mkdir(owner_group, permissions, folder):
+ """Creates a directory recursively with the provided permissions and ownership."""
+ print("target_mkdir", owner_group, permissions, folder)
+ owner, group = owner_group.split('.')
+ target(
+ ["install", "-d", "-m", permissions, "-o", owner, "-g", group, folder])
+
+
+def list_packages():
+ """Lists all installed packages.
+
+ Returns:
+ A dictionary with keys as packages, and values as versions.
+ """
+ result = subprocess.run([
+ "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
+ "qemu-aarch64-static", "/bin/bash", "-c",
+ "dpkg-query -W -f='${Package} ${Version}\n'"
+ ],
+ check=True,
+ stdout=subprocess.PIPE)
+
+ device = result.stdout.decode('utf-8').strip()
+
+ r = {}
+ for line in result.stdout.decode('utf-8').strip().split('\n'):
+ package, version = line.split(' ')
+ r[package] = version
+
+ return r
+
+
+def list_yocto_packages():
+ """Lists all packages in the Yocto folder.
+
+ Returns:
+ list of Package classes.
+ """
+ Package = collections.namedtuple(
+ 'Package', ['path', 'name', 'version', 'architecture'])
+ result = []
+ pathlist = pathlib.Path(f"{YOCTO}/tmp/deploy/deb").glob('**/*.deb')
+ for path in pathlist:
+ # Strip off the path, .deb, and split on _ to parse the package info.
+ s = os.path.basename(str(path))[:-4].split('_')
+ result.append(Package(str(path), s[0], s[1], s[2]))
+
+ return result
+
+
+def install_packages(new_packages, existing_packages):
+ """Installs the provided yocto packages, if they are new."""
+ # To install the yocto packages, first copy them into a folder in /tmp, then install them, then clean the folder up.
+ target(["mkdir", "-p", "/tmp/yocto_packages"])
+ try:
+ to_install = []
+ for package in new_packages:
+ if package.name in existing_packages and existing_packages[
+ package.name] == package.version:
+ print('Skipping', package)
+ continue
+
+ subprocess.run([
+ "sudo", "cp", package.path,
+ f"{PARTITION}/tmp/yocto_packages/{os.path.basename(package.path)}"
+ ],
+ check=True)
+ to_install.append(package)
+
+ if len(to_install) > 0:
+ target(["dpkg", "-i"] + [
+ f"/tmp/yocto_packages/{os.path.basename(package.path)}"
+ for package in to_install
+ ])
+
+ finally:
+ target(["rm", "-rf", "/tmp/yocto_packages"])
+
+
+def install_virtual_packages(virtual_packages):
+ """Builds and installs the provided virtual packages."""
+ try:
+ target(["mkdir", "-p", "/tmp/yocto_packages"])
+ for virtual_package in virtual_packages:
+ subprocess.run(
+ ["dpkg-deb", "--build", f"virtual_packages/{virtual_package}"],
+ check=True)
+ subprocess.run([
+ "sudo", "cp", f"virtual_packages/{virtual_package}.deb",
+ f"{PARTITION}/tmp/yocto_packages/{virtual_package}.deb"
+ ],
+ check=True)
+
+ target(["dpkg", "-i"] + [
+ f"/tmp/yocto_packages/{package}.deb"
+ for package in virtual_packages
+ ])
+
+ finally:
+ target(["rm", "-rf", "/tmp/yocto_packages"])
+
+
+def main():
+ check_required_deps(REQUIRED_DEPS)
+
+ new_image = not os.path.exists(IMAGE)
+ if new_image:
+ make_image(IMAGE)
+
+ with scoped_mount(IMAGE) as partition:
+ if new_image:
+ subprocess.run([
+ "sudo", "debootstrap", "--arch=arm64", "--no-check-gpg",
+ "--foreign", "bookworm", partition,
+ "http://deb.debian.org/debian/"
+ ],
+ check=True)
+
+ subprocess.run([
+ "sudo", "cp", "/usr/bin/qemu-aarch64-static",
+ f"{partition}/usr/bin/"
+ ],
+ check=True)
+
+ global PARTITION
+ PARTITION = partition
+
+ if new_image:
+ target(["/debootstrap/debootstrap", "--second-stage"])
+
+ target([
+ "useradd", "-m", "-p",
+ '$y$j9T$85lzhdky63CTj.two7Zj20$pVY53UR0VebErMlm8peyrEjmxeiRw/rfXfx..9.xet1',
+ '-s', '/bin/bash', 'pi'
+ ])
+ target(["addgroup", "debug"])
+ target(["addgroup", "crypto"])
+ target(["addgroup", "trusty"])
+
+ if not os.path.exists(
+ f"{partition}/etc/apt/sources.list.d/bullseye-backports.list"):
+ copyfile("root.root", "644",
+ "etc/apt/sources.list.d/bullseye-backports.list")
+ target(["apt-get", "update"])
+
+ target([
+ "apt-get", "-y", "install", "gnupg", "wget", "systemd",
+ "systemd-resolved", "locales"
+ ])
+
+ target(["localedef", "-i", "en_US", "-f", "UTF-8", "en_US.UTF-8"])
+
+ target_mkdir("root.root", "755", "run/systemd")
+ target_mkdir("systemd-resolve.systemd-resolve", "755",
+ "run/systemd/resolve")
+ copyfile("systemd-resolve.systemd-resolve", "644",
+ "run/systemd/resolve/stub-resolv.conf")
+ target(["systemctl", "enable", "systemd-resolved"])
+
+ target([
+ "apt-get", "-y", "install", "bpfcc-tools", "sudo",
+ "openssh-server", "python3", "bash-completion", "git", "v4l-utils",
+ "cpufrequtils", "pmount", "rsync", "vim-nox", "chrony",
+ "libopencv-calib3d406", "libopencv-contrib406",
+ "libopencv-core406", "libopencv-features2d406",
+ "libopencv-flann406", "libopencv-highgui406",
+ "libopencv-imgcodecs406", "libopencv-imgproc406",
+ "libopencv-ml406", "libopencv-objdetect406", "libopencv-photo406",
+ "libopencv-shape406", "libopencv-stitching406",
+ "libopencv-superres406", "libopencv-video406",
+ "libopencv-videoio406", "libopencv-videostab406",
+ "libopencv-viz406", "libnice10", "pmount", "libnice-dev", "feh",
+ "libgstreamer1.0-0", "libgstreamer-plugins-base1.0-0",
+ "libgstreamer-plugins-bad1.0-0", "gstreamer1.0-plugins-base",
+ "gstreamer1.0-plugins-good", "gstreamer1.0-plugins-bad",
+ "gstreamer1.0-plugins-ugly", "gstreamer1.0-nice", "usbutils",
+ "locales", "trace-cmd", "clinfo", "jq", "strace", "sysstat",
+ "lm-sensors", "can-utils", "xfsprogs", "gstreamer1.0-tools",
+ "bridge-utils", "net-tools", "apt-file", "parted", "xxd"
+ ])
+ target(["apt-get", "clean"])
+
+ target(["usermod", "-a", "-G", "sudo", "pi"])
+ target(["usermod", "-a", "-G", "video", "pi"])
+ target(["usermod", "-a", "-G", "systemd-journal", "pi"])
+ target(["usermod", "-a", "-G", "dialout", "pi"])
+
+ virtual_packages = [
+ 'libglib-2.0-0', 'libglvnd', 'libgtk-3-0', 'libxcb-glx', 'wayland'
+ ]
+
+ install_virtual_packages(virtual_packages)
+
+ yocto_package_names = [
+ 'tegra-argus-daemon', 'tegra-firmware', 'tegra-firmware-tegra234',
+ 'tegra-firmware-vic', 'tegra-firmware-xusb',
+ 'tegra-libraries-argus-daemon-base', 'tegra-libraries-camera',
+ 'tegra-libraries-core', 'tegra-libraries-cuda',
+ 'tegra-libraries-eglcore', 'tegra-libraries-glescore',
+ 'tegra-libraries-glxcore', 'tegra-libraries-multimedia',
+ 'tegra-libraries-multimedia-utils',
+ 'tegra-libraries-multimedia-v4l', 'tegra-libraries-nvsci',
+ 'tegra-libraries-vulkan', 'tegra-nvphs', 'tegra-nvphs-base',
+ 'libnvidia-egl-wayland1'
+ ]
+ yocto_packages = list_yocto_packages()
+ packages = list_packages()
+
+ install_packages([
+ package for package in yocto_packages
+ if package.name in yocto_package_names
+ ], packages)
+
+ # Now, install the kernel and modules after all the normal packages are in.
+ yocto_packages_to_install = [
+ package for package in yocto_packages
+ if (package.name.startswith('kernel-module-') or package.name.
+ startswith('kernel-5.10') or package.name == 'kernel-modules')
+ ]
+
+ packages_to_remove = []
+
+ # Remove kernel-module-* packages + kernel- package.
+ for key in packages:
+ if key.startswith('kernel-module') or key.startswith(
+ 'kernel-5.10'):
+ already_installed = False
+ for index, yocto_package in enumerate(
+ yocto_packages_to_install):
+ if key == yocto_package.name and packages[
+ key] == yocto_package.version:
+ print('Found already installed package', key,
+ yocto_package)
+ already_installed = True
+ del yocto_packages_to_install[index]
+ break
+ if not already_installed:
+ packages_to_remove.append(key)
+
+ print("Removing", packages_to_remove)
+ if len(packages_to_remove) > 0:
+ target(['dpkg', '--purge'] + packages_to_remove)
+ print("Installing",
+ [package.name for package in yocto_packages_to_install])
+
+ install_packages(yocto_packages_to_install, packages)
+
+ target(["systemctl", "enable", "nvargus-daemon.service"])
+
+ copyfile("root.root", "644", "etc/sysctl.d/sctp.conf")
+ copyfile("root.root", "644", "etc/systemd/logind.conf")
+ copyfile("root.root", "555",
+ "etc/bash_completion.d/aos_dump_autocomplete")
+ copyfile("root.root", "644", "etc/security/limits.d/rt.conf")
+ copyfile("root.root", "644", "etc/systemd/system/usb-mount@.service")
+ copyfile("root.root", "644", "etc/chrony/chrony.conf")
+ target_mkdir("root.root", "700", "root/bin")
+ target_mkdir("pi.pi", "755", "home/pi/.ssh")
+ copyfile("pi.pi", "600", "home/pi/.ssh/authorized_keys")
+ target_mkdir("root.root", "700", "root/bin")
+ copyfile("root.root", "644", "etc/systemd/system/grow-rootfs.service")
+ copyfile("root.root", "500", "root/bin/change_hostname.sh")
+ copyfile("root.root", "700", "root/trace.sh")
+ copyfile("root.root", "440", "etc/sudoers")
+ copyfile("root.root", "644", "etc/fstab")
+ copyfile("root.root", "644",
+ "var/nvidia/nvcam/settings/camera_overrides.isp")
+
+ target_mkdir("root.root", "755", "etc/systemd/network")
+ copyfile("root.root", "644", "etc/systemd/network/eth0.network")
+ copyfile("root.root", "644", "etc/systemd/network/80-can.network")
+ target(["/root/bin/change_hostname.sh", "pi-971-1"])
+
+ target(["systemctl", "enable", "systemd-networkd"])
+ target(["systemctl", "enable", "grow-rootfs"])
+ target(["apt-file", "update"])
+
+ target(["ldconfig"])
+
+ if not os.path.exists(f"{partition}/home/pi/.dotfiles"):
+ pi_target_unescaped(
+ "cd /home/pi/ && git clone --separate-git-dir=/home/pi/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles && rsync --recursive --verbose --exclude .git tmpdotfiles/ /home/pi/ && rm -r tmpdotfiles && git --git-dir=/home/pi/.dotfiles/ --work-tree=/home/pi/ config --local status.showUntrackedFiles no"
+ )
+ pi_target(["vim", "-c", "\":qa!\""])
+
+ target_unescaped(
+ "cd /root/ && git clone --separate-git-dir=/root/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles && rsync --recursive --verbose --exclude .git tmpdotfiles/ /root/ && rm -r tmpdotfiles && git --git-dir=/root/.dotfiles/ --work-tree=/root/ config --local status.showUntrackedFiles no"
+ )
+ target(["vim", "-c", "\":qa!\""])
+
+
+if __name__ == '__main__':
+ main()