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