blob: bf1968e8366ce4e1c33ae2741b4e037a8de25e7b [file] [log] [blame]
Austin Schuh51014832023-10-20 17:44:45 -07001#!/usr/bin/python3
2
3import contextlib
4import pathlib
5import collections
6import subprocess
7import shlex
Austin Schuh86d980e2023-10-20 22:44:47 -07008import datetime
Austin Schuh51014832023-10-20 17:44:45 -07009import os
Austin Schuh86d980e2023-10-20 22:44:47 -070010import shutil
Austin Schuh51014832023-10-20 17:44:45 -070011
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070012# Name of debian image to be created/modified
Austin Schuh51014832023-10-20 17:44:45 -070013IMAGE = "arm64_bookworm_debian_yocto.img"
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070014
15# Path to yocto build for the orin (using meta-frc971)
Austin Schuh51014832023-10-20 17:44:45 -070016YOCTO = "/home/austin/local/jetpack/robot-yocto/build"
17
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070018REQUIRED_DEPS = ["debootstrap", "u-boot-tools", "xfsprogs"]
Austin Schuh51014832023-10-20 17:44:45 -070019
20
21@contextlib.contextmanager
22def scoped_loopback(image):
23 """Mounts an image as a loop back device."""
24 result = subprocess.run(["sudo", "losetup", "--show", "-f", image],
25 check=True,
26 stdout=subprocess.PIPE)
27 device = result.stdout.decode('utf-8').strip()
28 print("Mounted", image, "to", repr(device))
29 try:
30 yield device
31 finally:
32 subprocess.run(["sudo", "losetup", "-d", device], check=True)
33
34
35@contextlib.contextmanager
36def scoped_mount(image):
37 """Mounts an image as a partition."""
38 partition = f"{image}.partition"
39 try:
40 os.mkdir(partition)
41 except FileExistsError:
42 pass
43
44 result = subprocess.run(["sudo", "mount", "-o", "loop", image, partition],
45 check=True)
46
47 try:
48 yield partition
49 finally:
50 subprocess.run(
51 ["sudo", "rm", f"{partition}/usr/bin/qemu-aarch64-static"])
52 subprocess.run(["sudo", "umount", partition], check=True)
53
54
55def check_required_deps(deps):
56 """Checks if the provided list of dependencies is installed."""
57 missing_deps = []
58 for dep in deps:
59 result = subprocess.run(["dpkg-query", "-W", "-f='${Status}'", dep],
60 check=True,
61 stdout=subprocess.PIPE)
62
63 if "install ok installed" not in result.stdout.decode('utf-8'):
64 missing_deps.append(dep)
65
66 if len(missing_deps) > 0:
67 print("Missing dependencies, please install:")
68 print("sudo apt-get install", " ".join(missing_deps))
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070069 exit()
Austin Schuh51014832023-10-20 17:44:45 -070070
71
72def make_image(image):
73 """Makes an image and creates an xfs filesystem on it."""
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -070074 print("Creating image ", f"{image}")
Austin Schuh51014832023-10-20 17:44:45 -070075 result = subprocess.run([
76 "dd", "if=/dev/zero", f"of={image}", "bs=1", "count=0",
77 "seek=8589934592"
78 ],
79 check=True)
80
81 with scoped_loopback(image) as loopback:
82 subprocess.run([
83 "sudo", "mkfs.xfs", "-d", "su=128k", "-d", "sw=1", "-L", "rootfs",
84 loopback
85 ],
86 check=True)
87
88
89def target_unescaped(cmd):
90 """Runs a command as root with bash -c cmd, ie without escaping."""
91 subprocess.run([
92 "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
93 "qemu-aarch64-static", "/bin/bash", "-c", cmd
94 ],
95 check=True)
96
97
98def target(cmd):
99 """Runs a command as root with escaping."""
100 target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
101
102
103def pi_target_unescaped(cmd):
104 """Runs a command as pi with bash -c cmd, ie without escaping."""
105 subprocess.run([
106 "sudo", "chroot", "--userspec=pi:pi", "--groups=pi", f"{PARTITION}",
107 "qemu-aarch64-static", "/bin/bash", "-c", cmd
108 ],
109 check=True)
110
111
112def pi_target(cmd):
113 """Runs a command as pi with escaping."""
114 pi_target_unescaped(shlex.join([shlex.quote(c) for c in cmd]))
115
116
117def copyfile(owner, permissions, file):
118 """Copies a file from contents/{file} with the provided owner and permissions."""
119 print("copyfile", owner, permissions, file)
120 subprocess.run(["sudo", "cp", f"contents/{file}", f"{PARTITION}/{file}"],
121 check=True)
122 subprocess.run(["sudo", "chmod", permissions, f"{PARTITION}/{file}"],
123 check=True)
124 target(["chown", owner, f"/{file}"])
125
126
127def target_mkdir(owner_group, permissions, folder):
128 """Creates a directory recursively with the provided permissions and ownership."""
129 print("target_mkdir", owner_group, permissions, folder)
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700130 owner, group = owner_group.split(':')
Austin Schuh51014832023-10-20 17:44:45 -0700131 target(
132 ["install", "-d", "-m", permissions, "-o", owner, "-g", group, folder])
133
134
135def list_packages():
136 """Lists all installed packages.
137
138 Returns:
139 A dictionary with keys as packages, and values as versions.
140 """
141 result = subprocess.run([
142 "sudo", "chroot", "--userspec=0:0", f"{PARTITION}",
143 "qemu-aarch64-static", "/bin/bash", "-c",
144 "dpkg-query -W -f='${Package} ${Version}\n'"
145 ],
146 check=True,
147 stdout=subprocess.PIPE)
148
149 device = result.stdout.decode('utf-8').strip()
150
151 r = {}
152 for line in result.stdout.decode('utf-8').strip().split('\n'):
153 package, version = line.split(' ')
154 r[package] = version
155
156 return r
157
158
159def list_yocto_packages():
160 """Lists all packages in the Yocto folder.
161
162 Returns:
163 list of Package classes.
164 """
165 Package = collections.namedtuple(
166 'Package', ['path', 'name', 'version', 'architecture'])
167 result = []
168 pathlist = pathlib.Path(f"{YOCTO}/tmp/deploy/deb").glob('**/*.deb')
169 for path in pathlist:
170 # Strip off the path, .deb, and split on _ to parse the package info.
171 s = os.path.basename(str(path))[:-4].split('_')
172 result.append(Package(str(path), s[0], s[1], s[2]))
173
174 return result
175
176
177def install_packages(new_packages, existing_packages):
178 """Installs the provided yocto packages, if they are new."""
179 # To install the yocto packages, first copy them into a folder in /tmp, then install them, then clean the folder up.
180 target(["mkdir", "-p", "/tmp/yocto_packages"])
181 try:
182 to_install = []
183 for package in new_packages:
184 if package.name in existing_packages and existing_packages[
185 package.name] == package.version:
186 print('Skipping', package)
187 continue
188
189 subprocess.run([
190 "sudo", "cp", package.path,
191 f"{PARTITION}/tmp/yocto_packages/{os.path.basename(package.path)}"
192 ],
193 check=True)
194 to_install.append(package)
195
196 if len(to_install) > 0:
197 target(["dpkg", "-i"] + [
198 f"/tmp/yocto_packages/{os.path.basename(package.path)}"
199 for package in to_install
200 ])
201
202 finally:
203 target(["rm", "-rf", "/tmp/yocto_packages"])
204
205
206def install_virtual_packages(virtual_packages):
207 """Builds and installs the provided virtual packages."""
208 try:
209 target(["mkdir", "-p", "/tmp/yocto_packages"])
210 for virtual_package in virtual_packages:
211 subprocess.run(
212 ["dpkg-deb", "--build", f"virtual_packages/{virtual_package}"],
213 check=True)
214 subprocess.run([
215 "sudo", "cp", f"virtual_packages/{virtual_package}.deb",
216 f"{PARTITION}/tmp/yocto_packages/{virtual_package}.deb"
217 ],
218 check=True)
219
220 target(["dpkg", "-i"] + [
221 f"/tmp/yocto_packages/{package}.deb"
222 for package in virtual_packages
223 ])
224
225 finally:
226 target(["rm", "-rf", "/tmp/yocto_packages"])
227
228
229def main():
230 check_required_deps(REQUIRED_DEPS)
231
232 new_image = not os.path.exists(IMAGE)
233 if new_image:
234 make_image(IMAGE)
235
236 with scoped_mount(IMAGE) as partition:
237 if new_image:
238 subprocess.run([
239 "sudo", "debootstrap", "--arch=arm64", "--no-check-gpg",
240 "--foreign", "bookworm", partition,
241 "http://deb.debian.org/debian/"
242 ],
243 check=True)
244
245 subprocess.run([
246 "sudo", "cp", "/usr/bin/qemu-aarch64-static",
247 f"{partition}/usr/bin/"
248 ],
249 check=True)
250
251 global PARTITION
252 PARTITION = partition
253
254 if new_image:
255 target(["/debootstrap/debootstrap", "--second-stage"])
256
257 target([
258 "useradd", "-m", "-p",
259 '$y$j9T$85lzhdky63CTj.two7Zj20$pVY53UR0VebErMlm8peyrEjmxeiRw/rfXfx..9.xet1',
260 '-s', '/bin/bash', 'pi'
261 ])
262 target(["addgroup", "debug"])
263 target(["addgroup", "crypto"])
264 target(["addgroup", "trusty"])
265
266 if not os.path.exists(
267 f"{partition}/etc/apt/sources.list.d/bullseye-backports.list"):
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700268 copyfile("root:root", "644",
Austin Schuh51014832023-10-20 17:44:45 -0700269 "etc/apt/sources.list.d/bullseye-backports.list")
270 target(["apt-get", "update"])
271
272 target([
273 "apt-get", "-y", "install", "gnupg", "wget", "systemd",
274 "systemd-resolved", "locales"
275 ])
276
277 target(["localedef", "-i", "en_US", "-f", "UTF-8", "en_US.UTF-8"])
278
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700279 target_mkdir("root:root", "755", "run/systemd")
280 target_mkdir("systemd-resolve:systemd-resolve", "755",
Austin Schuh51014832023-10-20 17:44:45 -0700281 "run/systemd/resolve")
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700282 copyfile("systemd-resolve:systemd-resolve", "644",
Austin Schuh51014832023-10-20 17:44:45 -0700283 "run/systemd/resolve/stub-resolv.conf")
284 target(["systemctl", "enable", "systemd-resolved"])
285
286 target([
287 "apt-get", "-y", "install", "bpfcc-tools", "sudo",
288 "openssh-server", "python3", "bash-completion", "git", "v4l-utils",
289 "cpufrequtils", "pmount", "rsync", "vim-nox", "chrony",
290 "libopencv-calib3d406", "libopencv-contrib406",
291 "libopencv-core406", "libopencv-features2d406",
292 "libopencv-flann406", "libopencv-highgui406",
293 "libopencv-imgcodecs406", "libopencv-imgproc406",
294 "libopencv-ml406", "libopencv-objdetect406", "libopencv-photo406",
295 "libopencv-shape406", "libopencv-stitching406",
296 "libopencv-superres406", "libopencv-video406",
297 "libopencv-videoio406", "libopencv-videostab406",
298 "libopencv-viz406", "libnice10", "pmount", "libnice-dev", "feh",
299 "libgstreamer1.0-0", "libgstreamer-plugins-base1.0-0",
300 "libgstreamer-plugins-bad1.0-0", "gstreamer1.0-plugins-base",
301 "gstreamer1.0-plugins-good", "gstreamer1.0-plugins-bad",
302 "gstreamer1.0-plugins-ugly", "gstreamer1.0-nice", "usbutils",
303 "locales", "trace-cmd", "clinfo", "jq", "strace", "sysstat",
304 "lm-sensors", "can-utils", "xfsprogs", "gstreamer1.0-tools",
Austin Schuh86d980e2023-10-20 22:44:47 -0700305 "bridge-utils", "net-tools", "apt-file", "parted", "xxd",
306 "libv4l-dev"
Austin Schuh51014832023-10-20 17:44:45 -0700307 ])
308 target(["apt-get", "clean"])
309
310 target(["usermod", "-a", "-G", "sudo", "pi"])
311 target(["usermod", "-a", "-G", "video", "pi"])
312 target(["usermod", "-a", "-G", "systemd-journal", "pi"])
313 target(["usermod", "-a", "-G", "dialout", "pi"])
314
315 virtual_packages = [
316 'libglib-2.0-0', 'libglvnd', 'libgtk-3-0', 'libxcb-glx', 'wayland'
317 ]
318
319 install_virtual_packages(virtual_packages)
320
321 yocto_package_names = [
Austin Schuh86d980e2023-10-20 22:44:47 -0700322 'tegra-argus-daemon',
323 'tegra-firmware',
324 'tegra-firmware-tegra234',
325 'tegra-firmware-vic',
326 'tegra-firmware-xusb',
327 'tegra-libraries-argus-daemon-base',
328 'tegra-libraries-camera',
329 'tegra-libraries-core',
330 'tegra-libraries-cuda',
331 'tegra-libraries-eglcore',
332 'tegra-libraries-glescore',
333 'tegra-libraries-glxcore',
334 'tegra-libraries-multimedia',
Austin Schuh51014832023-10-20 17:44:45 -0700335 'tegra-libraries-multimedia-utils',
Austin Schuh86d980e2023-10-20 22:44:47 -0700336 'tegra-libraries-multimedia-v4l',
337 'tegra-libraries-nvsci',
338 'tegra-libraries-vulkan',
339 'tegra-nvphs',
340 'tegra-nvphs-base',
341 'libnvidia-egl-wayland1',
342 'tegra-mmapi',
343 'tegra-mmapi-dev',
344 'cuda-cudart-11-8',
345 'cuda-cudart-11-8-dev',
346 'cuda-cudart-11-8-stubs',
347 'libcurand-11-8',
348 'libcurand-11-8-dev',
349 'libcurand-11-8-stubs',
350 'cuda-nvcc-11-8',
351 'tegra-cmake-overrides',
352 'cuda-target-environment',
353 'libnpp-11-8',
354 'libnpp-11-8-stubs',
355 'libnpp-11-8-dev',
356 'cuda-cccl-11-8',
357 'cuda-nvcc-11-8',
358 'cuda-nvcc-headers-11-8',
Austin Schuh51014832023-10-20 17:44:45 -0700359 ]
360 yocto_packages = list_yocto_packages()
361 packages = list_packages()
362
363 install_packages([
364 package for package in yocto_packages
365 if package.name in yocto_package_names
366 ], packages)
367
368 # Now, install the kernel and modules after all the normal packages are in.
369 yocto_packages_to_install = [
370 package for package in yocto_packages
371 if (package.name.startswith('kernel-module-') or package.name.
372 startswith('kernel-5.10') or package.name == 'kernel-modules')
373 ]
374
375 packages_to_remove = []
376
377 # Remove kernel-module-* packages + kernel- package.
378 for key in packages:
379 if key.startswith('kernel-module') or key.startswith(
380 'kernel-5.10'):
381 already_installed = False
382 for index, yocto_package in enumerate(
383 yocto_packages_to_install):
384 if key == yocto_package.name and packages[
385 key] == yocto_package.version:
386 print('Found already installed package', key,
387 yocto_package)
388 already_installed = True
389 del yocto_packages_to_install[index]
390 break
391 if not already_installed:
392 packages_to_remove.append(key)
393
394 print("Removing", packages_to_remove)
395 if len(packages_to_remove) > 0:
396 target(['dpkg', '--purge'] + packages_to_remove)
397 print("Installing",
398 [package.name for package in yocto_packages_to_install])
399
400 install_packages(yocto_packages_to_install, packages)
401
402 target(["systemctl", "enable", "nvargus-daemon.service"])
403
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700404 copyfile("root:root", "644", "etc/sysctl.d/sctp.conf")
405 copyfile("root:root", "644", "etc/systemd/logind.conf")
406 copyfile("root:root", "555",
Austin Schuh51014832023-10-20 17:44:45 -0700407 "etc/bash_completion.d/aos_dump_autocomplete")
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700408 copyfile("root:root", "644", "etc/security/limits.d/rt.conf")
409 copyfile("root:root", "644", "etc/systemd/system/usb-mount@.service")
410 copyfile("root:root", "644", "etc/chrony/chrony.conf")
411 target_mkdir("root:root", "700", "root/bin")
412 target_mkdir("pi:pi", "755", "home/pi/.ssh")
413 copyfile("pi:pi", "600", "home/pi/.ssh/authorized_keys")
414 target_mkdir("root:root", "700", "root/bin")
415 copyfile("root:root", "644", "etc/systemd/system/grow-rootfs.service")
416 copyfile("root:root", "500", "root/bin/change_hostname.sh")
417 copyfile("root:root", "700", "root/trace.sh")
418 copyfile("root:root", "440", "etc/sudoers")
419 copyfile("root:root", "644", "etc/fstab")
420 copyfile("root:root", "644",
Austin Schuh51014832023-10-20 17:44:45 -0700421 "var/nvidia/nvcam/settings/camera_overrides.isp")
422
Jim Ostrowski12ef0ff2023-10-22 23:20:20 -0700423 target_mkdir("root:root", "755", "etc/systemd/network")
424 copyfile("root:root", "644", "etc/systemd/network/eth0.network")
425 copyfile("root:root", "644", "etc/systemd/network/80-can.network")
Austin Schuh51014832023-10-20 17:44:45 -0700426 target(["/root/bin/change_hostname.sh", "pi-971-1"])
427
428 target(["systemctl", "enable", "systemd-networkd"])
429 target(["systemctl", "enable", "grow-rootfs"])
430 target(["apt-file", "update"])
431
432 target(["ldconfig"])
433
434 if not os.path.exists(f"{partition}/home/pi/.dotfiles"):
435 pi_target_unescaped(
436 "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"
437 )
438 pi_target(["vim", "-c", "\":qa!\""])
439
440 target_unescaped(
441 "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"
442 )
443 target(["vim", "-c", "\":qa!\""])
444
Austin Schuh86d980e2023-10-20 22:44:47 -0700445 tarball = datetime.date.today().strftime(
446 f"{os.getcwd()}/%Y-%m-%d-bookworm-arm64-nvidia-rootfs.tar")
447 print(tarball)
448
449 subprocess.run([
450 "sudo",
451 "tar",
452 "--exclude=./usr/share/ca-certificates",
453 "--exclude=./home",
454 "--exclude=./root",
455 "--exclude=./usr/src",
456 "--exclude=./usr/lib/mesa-diverted",
457 "--exclude=./usr/bin/X11",
458 "--exclude=./usr/lib/systemd/system/system-systemd*cryptsetup.slice",
459 "--exclude=./dev",
460 "--exclude=./usr/local/cuda-11.8/bin/fatbinary",
461 "--exclude=./usr/local/cuda-11.8/bin/ptxas",
462 "-cf",
463 tarball,
464 ".",
465 ],
466 cwd=partition,
467 check=True)
468
469 # Pack ptxas and fatbinary into the spots that clang expect them to make compiling easy.
470 nvidia_cuda_toolkit_path = 'nvidia-cuda-toolkit'
471 if not os.path.exists(nvidia_cuda_toolkit_path):
472 os.mkdir(nvidia_cuda_toolkit_path)
473
474 subprocess.run(['apt-get', 'download', 'nvidia-cuda-toolkit'],
475 cwd=nvidia_cuda_toolkit_path,
476 check=True)
477
478 subprocess.run(
479 ['dpkg', '-x',
480 os.listdir(nvidia_cuda_toolkit_path)[0], '.'],
481 cwd=nvidia_cuda_toolkit_path,
482 check=True)
483
484 subprocess.run([
485 "sudo", "tar",
486 '--transform=s|usr/bin/ptxas|usr/local/cuda-11.8/bin/ptxas|',
487 '--transform=s|usr/bin/fatbinary|usr/local/cuda-11.8/bin/aarch64-unknown-linux-gnu-fatbinary|',
488 "--append", "-f", tarball, "usr/bin/fatbinary", "usr/bin/ptxas"
489 ],
490 cwd=nvidia_cuda_toolkit_path,
491 check=True)
492
493 subprocess.run(["sha256sum", tarball], check=True)
494
Austin Schuh51014832023-10-20 17:44:45 -0700495
496if __name__ == '__main__':
497 main()