Austin Schuh | 0bd410a | 2023-11-05 12:38:12 -0800 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | |
| 3 | import contextlib |
| 4 | import datetime |
| 5 | import pathlib |
| 6 | import subprocess |
| 7 | import shlex |
| 8 | import os |
| 9 | import sys |
| 10 | |
| 11 | REQUIRED_DEPS = ["debootstrap"] |
| 12 | |
| 13 | ROOTFS_FOLDER = "/tmp/rootfs" |
| 14 | |
| 15 | |
| 16 | @contextlib.contextmanager |
| 17 | def scoped_bind_mount(partition): |
| 18 | """Bind mounts a folder from the host into the rootfs.""" |
| 19 | result = subprocess.run( |
| 20 | ["sudo", "mount", "--bind", partition, f"{ROOTFS_FOLDER}/{partition}"], |
| 21 | check=True) |
| 22 | |
| 23 | try: |
| 24 | yield partition |
| 25 | finally: |
| 26 | subprocess.run(["sudo", "umount", f"{ROOTFS_FOLDER}/{partition}"], |
| 27 | check=True) |
| 28 | |
| 29 | |
| 30 | def check_required_deps(deps): |
| 31 | """Checks if the provided list of dependencies is installed.""" |
| 32 | missing_deps = [] |
| 33 | for dep in deps: |
| 34 | result = subprocess.run(["dpkg-query", "-W", "-f='${Status}'", dep], |
| 35 | check=True, |
| 36 | stdout=subprocess.PIPE) |
| 37 | |
| 38 | if "install ok installed" not in result.stdout.decode('utf-8'): |
| 39 | missing_deps.append(dep) |
| 40 | |
| 41 | if len(missing_deps) > 0: |
| 42 | print("Missing dependencies, please install:") |
| 43 | print("sudo apt-get install", " ".join(missing_deps)) |
| 44 | return True |
| 45 | |
| 46 | return False |
| 47 | |
| 48 | |
| 49 | def target_unescaped(cmd): |
| 50 | """Runs a command as root with bash -c cmd, ie without escaping.""" |
| 51 | subprocess.run([ |
| 52 | "sudo", "chroot", "--userspec=0:0", f"{ROOTFS_FOLDER}", "/bin/bash", |
| 53 | "-c", cmd |
| 54 | ], |
| 55 | check=True) |
| 56 | |
| 57 | |
| 58 | def target(cmd): |
| 59 | """Runs a command as root with escaping.""" |
| 60 | target_unescaped(shlex.join([shlex.quote(c) for c in cmd])) |
| 61 | |
| 62 | |
| 63 | def copyfile(owner, permissions, file): |
| 64 | """Copies a file from contents/{file} with the provided owner and permissions.""" |
| 65 | print("copyfile", owner, permissions, file) |
| 66 | subprocess.run( |
| 67 | ["sudo", "cp", f"contents/{file}", f"{ROOTFS_FOLDER}/{file}"], |
| 68 | check=True) |
| 69 | subprocess.run(["sudo", "chmod", permissions, f"{ROOTFS_FOLDER}/{file}"], |
| 70 | check=True) |
| 71 | target(["chown", owner, f"/{file}"]) |
| 72 | |
| 73 | |
| 74 | def target_symlink(owner, permissions, link_target, linkname): |
| 75 | full_linkname = f"{ROOTFS_FOLDER}/{linkname}" |
| 76 | print(link_target) |
| 77 | print(full_linkname) |
| 78 | if not os.path.exists(full_linkname): |
| 79 | target(["ln", "-s", link_target, linkname]) |
| 80 | |
| 81 | assert (pathlib.Path(full_linkname).is_symlink()) |
| 82 | |
| 83 | target(["chown", owner, linkname]) |
| 84 | target(["chmod", permissions, linkname]) |
| 85 | |
| 86 | |
| 87 | def target_mkdir(owner_group, permissions, folder): |
| 88 | """Creates a directory recursively with the provided permissions and ownership.""" |
| 89 | print("target_mkdir", owner_group, permissions, folder) |
| 90 | owner, group = owner_group.split('.') |
| 91 | target( |
| 92 | ["install", "-d", "-m", permissions, "-o", owner, "-g", group, folder]) |
| 93 | |
| 94 | |
| 95 | def main(): |
| 96 | if check_required_deps(REQUIRED_DEPS): |
| 97 | return 1 |
| 98 | |
| 99 | new_image = not os.path.exists(ROOTFS_FOLDER) |
| 100 | if new_image: |
| 101 | os.mkdir(ROOTFS_FOLDER) |
| 102 | |
| 103 | if new_image: |
| 104 | subprocess.run([ |
| 105 | "sudo", "debootstrap", "--no-check-gpg", "bookworm", ROOTFS_FOLDER, |
| 106 | "http://deb.debian.org/debian/" |
| 107 | ], |
| 108 | check=True) |
| 109 | |
| 110 | if not os.path.exists( |
| 111 | f"{ROOTFS_FOLDER}/etc/apt/sources.list.d/bullseye-backports.list"): |
| 112 | copyfile("root.root", "644", |
| 113 | "etc/apt/sources.list.d/bullseye-backports.list") |
| 114 | target(["apt-get", "update"]) |
| 115 | |
| 116 | with scoped_bind_mount("/dev") as _: |
| 117 | with scoped_bind_mount("/proc") as _: |
| 118 | target([ |
| 119 | "apt-get", |
| 120 | "-y", |
| 121 | "install", |
| 122 | "libopencv-calib3d406", |
| 123 | "libopencv-contrib406", |
| 124 | "libopencv-core406", |
| 125 | "libopencv-features2d406", |
| 126 | "libopencv-flann406", |
| 127 | "libopencv-highgui406", |
| 128 | "libopencv-imgcodecs406", |
| 129 | "libopencv-imgproc406", |
| 130 | "libopencv-ml406", |
| 131 | "libopencv-objdetect406", |
| 132 | "libopencv-photo406", |
| 133 | "libopencv-shape406", |
| 134 | "libopencv-stitching406", |
| 135 | "libopencv-superres406", |
| 136 | "libopencv-video406", |
| 137 | "libopencv-videoio406", |
| 138 | "libopencv-videostab406", |
| 139 | "libopencv-viz406", |
| 140 | "libv4l-dev", |
| 141 | "libc6-dev", |
| 142 | "libstdc++-12-dev", |
| 143 | "nvidia-cuda-dev", |
| 144 | "nvidia-cuda-toolkit", |
| 145 | ]) |
| 146 | |
| 147 | target_mkdir("root.root", "755", "usr/lib/cuda/bin") |
| 148 | target_symlink("root.root", "555", "../../../bin/fatbinary", |
| 149 | "usr/lib/cuda/bin/x86_64-unknown-linux-gnu-fatbinary") |
| 150 | |
| 151 | target(["apt-get", "clean"]) |
| 152 | |
| 153 | target(["ldconfig"]) |
| 154 | |
| 155 | tarball = datetime.date.today().strftime( |
| 156 | f"{os.getcwd()}/%Y-%m-%d-bookworm-amd64-nvidia-rootfs.tar") |
| 157 | print(tarball) |
| 158 | |
| 159 | subprocess.run([ |
| 160 | "sudo", |
| 161 | "tar", |
| 162 | "--exclude=./usr/share/ca-certificates", |
| 163 | "--exclude=./usr/src", |
| 164 | "--exclude=./usr/lib/mesa-diverted", |
| 165 | "--exclude=./usr/bin/X11", |
| 166 | "--exclude=./usr/lib/systemd/system/system-systemd*cryptsetup.slice", |
| 167 | "--exclude=./dev", |
Austin Schuh | 1fc0d48 | 2023-12-24 14:40:34 -0800 | [diff] [blame^] | 168 | "--exclude=./usr/include/cub", |
| 169 | "--exclude=./usr/include/nv", |
| 170 | "--exclude=./usr/include/thrust", |
| 171 | "--exclude=./usr/include/cuda", |
Austin Schuh | 0bd410a | 2023-11-05 12:38:12 -0800 | [diff] [blame] | 172 | "-cf", |
| 173 | tarball, |
| 174 | ".", |
| 175 | ], |
| 176 | cwd=ROOTFS_FOLDER, |
| 177 | check=True) |
| 178 | |
| 179 | subprocess.run(["sha256sum", tarball], check=True) |
| 180 | |
| 181 | return 0 |
| 182 | |
| 183 | |
| 184 | if __name__ == '__main__': |
| 185 | sys.exit(main()) |