logo

live-bootstrap

Mirror of <https://github.com/fosslinux/live-bootstrap>

rootfs.py (12785B)


  1. #!/usr/bin/env python3
  2. """
  3. A helper application used to start bootstrapping process.
  4. It has a few modes of operation, you can create initramfs with
  5. binary seeds and sources that you can boot into or alternatively
  6. you can run bootstap inside chroot.
  7. """
  8. # SPDX-License-Identifier: GPL-3.0-or-later
  9. # SPDX-FileCopyrightText: 2022 Dor Askayo <dor.askayo@gmail.com>
  10. # SPDX-FileCopyrightText: 2021 Andrius Štikonas <andrius@stikonas.eu>
  11. # SPDX-FileCopyrightText: 2021 Bastian Bittorf <bb@npl.de>
  12. # SPDX-FileCopyrightText: 2021 Melg Eight <public.melg8@gmail.com>
  13. # SPDX-FileCopyrightText: 2021-23 fosslinux <fosslinux@aussies.space>
  14. # SPDX-FileCopyrightText: 2023-24 Gábor Stefanik <netrolller.3d@gmail.com>
  15. import argparse
  16. import os
  17. from lib.utils import run, run_as_root
  18. from lib.target import Target
  19. from lib.generator import Generator, stage0_arch_map
  20. def create_configuration_file(args):
  21. """
  22. Creates bootstrap.cfg file which would contain options used to
  23. customize bootstrap.
  24. """
  25. config_path = os.path.join('steps', 'bootstrap.cfg')
  26. with open(config_path, "w", encoding="utf_8") as config:
  27. config.write(f"ARCH={args.arch}\n")
  28. config.write(f"ARCH_DIR={stage0_arch_map.get(args.arch, args.arch)}\n")
  29. config.write(f"FORCE_TIMESTAMPS={args.force_timestamps}\n")
  30. config.write(f"CHROOT={args.chroot or args.bwrap}\n")
  31. config.write(f"UPDATE_CHECKSUMS={args.update_checksums}\n")
  32. config.write(f"JOBS={args.cores}\n")
  33. config.write(f"SWAP_SIZE={args.swap}\n")
  34. config.write(f"FINAL_JOBS={args.cores}\n")
  35. config.write(f"INTERNAL_CI={args.internal_ci or False}\n")
  36. config.write(f"INTERACTIVE={args.interactive}\n")
  37. config.write(f"BARE_METAL={args.bare_metal or (args.qemu and args.interactive)}\n")
  38. if (args.bare_metal or args.qemu) and not args.kernel:
  39. if args.repo or args.external_sources:
  40. config.write("DISK=sdb1\n")
  41. else:
  42. config.write("DISK=sda\n")
  43. config.write("KERNEL_BOOTSTRAP=True\n")
  44. else:
  45. config.write("DISK=sda1\n")
  46. config.write("KERNEL_BOOTSTRAP=False\n")
  47. config.write(f"BUILD_KERNELS={args.update_checksums or args.build_kernels}\n")
  48. config.write(f"CONFIGURATOR={args.configurator}\n")
  49. # pylint: disable=too-many-statements,too-many-branches
  50. def main():
  51. """
  52. A few command line arguments to customize bootstrap.
  53. This function also creates object which prepares directory
  54. structure with bootstrap seeds and all sources.
  55. """
  56. parser = argparse.ArgumentParser()
  57. parser.add_argument("-a", "--arch", help="Bootstrap architecture",
  58. default="x86")
  59. parser.add_argument("-c", "--chroot", help="Run inside chroot",
  60. action="store_true")
  61. parser.add_argument("-bw", "--bwrap", help="Run inside a bwrap sandbox",
  62. action="store_true")
  63. parser.add_argument("-t", "--target", help="Target directory",
  64. default="target")
  65. parser.add_argument("--tmpfs", help="Use a tmpfs on target",
  66. action="store_true")
  67. parser.add_argument("--tmpfs-size", help="Size of the tmpfs",
  68. default="8G")
  69. parser.add_argument("--cores", help="Cores to use for building",
  70. default=2)
  71. parser.add_argument("--force-timestamps",
  72. help="Force all files timestamps to be 0 unix time",
  73. action="store_true")
  74. parser.add_argument("--update-checksums",
  75. help="Update checksum files",
  76. action="store_true")
  77. parser.add_argument("--external-sources",
  78. help="Download sources externally from live-bootstrap",
  79. action="store_true")
  80. parser.add_argument("--build-kernels",
  81. help="Also build kernels in chroot and bwrap builds",
  82. action="store_true")
  83. parser.add_argument("--no-create-config",
  84. help="Do not automatically create config file",
  85. action="store_true")
  86. parser.add_argument("-i", "--interactive",
  87. help="Use interactive prompts to resolve issues during bootstrap",
  88. action="store_true")
  89. parser.add_argument("--configurator",
  90. help="Run the interactive configurator",
  91. action="store_true")
  92. parser.add_argument("-r", "--repo",
  93. help="Path to prebuilt binary packages", nargs=None)
  94. parser.add_argument("--early-preseed",
  95. help="Skip early stages of live-bootstrap", nargs=None)
  96. parser.add_argument("--internal-ci", help="INTERNAL for github CI")
  97. parser.add_argument("-s", "--swap", help="Swap space to allocate in Linux",
  98. default=0)
  99. # QEMU arguments
  100. parser.add_argument("-q", "--qemu", help="Use QEMU",
  101. action="store_true")
  102. parser.add_argument("-qc", "--qemu-cmd", help="QEMU command to run",
  103. default="qemu-system-x86_64")
  104. parser.add_argument("-qr", "--qemu-ram", help="Memory (in megabytes) allocated to QEMU VM",
  105. default=4096)
  106. parser.add_argument("-qs", "--target-size", help="Size of the target image (for QEMU only)",
  107. default="16G")
  108. parser.add_argument("-qk", "--kernel", help="Custom early kernel to use")
  109. parser.add_argument("-b", "--bare-metal", help="Build images for bare metal",
  110. action="store_true")
  111. args = parser.parse_args()
  112. # Mode validation
  113. def check_types():
  114. count = 0
  115. if args.qemu:
  116. count += 1
  117. if args.chroot:
  118. count += 1
  119. if args.bwrap:
  120. count += 1
  121. if args.bare_metal:
  122. count += 1
  123. return count
  124. if check_types() > 1:
  125. raise ValueError("No more than one of qemu, chroot, bwrap, bare metal"
  126. "may be used.")
  127. if check_types() == 0:
  128. raise ValueError("One of qemu, chroot, bwrap, or bare metal must be selected.")
  129. # Arch validation
  130. if args.arch != "x86":
  131. print("Only x86 is supported at the moment, other arches are for development only.")
  132. # Tmpfs validation
  133. if args.bwrap and args.tmpfs:
  134. raise ValueError("tmpfs cannot be used with bwrap.")
  135. # Cores validation
  136. if int(args.cores) < 1:
  137. raise ValueError("Must use one or more cores.")
  138. # Target image size validation
  139. if args.qemu:
  140. if int(str(args.target_size).rstrip('gGmM')) < 1:
  141. raise ValueError("Please specify a positive target size for qemu.")
  142. args.target_size = (int(str(args.target_size).rstrip('gGmM')) *
  143. (1024 if str(args.target_size).lower().endswith('g') else 1))
  144. else:
  145. args.target_size = 0
  146. # Swap file size validation
  147. if args.qemu or args.bare_metal:
  148. args.swap = (int(str(args.swap).rstrip('gGmM')) *
  149. (1024 if str(args.swap).lower().endswith('g') else 1))
  150. else:
  151. args.swap = 0
  152. # Set constant umask
  153. os.umask(0o022)
  154. # bootstrap.cfg
  155. try:
  156. os.remove(os.path.join('steps', 'bootstrap.cfg'))
  157. except FileNotFoundError:
  158. pass
  159. if not args.no_create_config:
  160. create_configuration_file(args)
  161. else:
  162. with open(os.path.join('steps', 'bootstrap.cfg'), 'a', encoding='UTF-8'):
  163. pass
  164. # target
  165. target = Target(path=args.target)
  166. if args.tmpfs:
  167. target.tmpfs(size=args.tmpfs_size)
  168. generator = Generator(arch=args.arch,
  169. external_sources=args.external_sources,
  170. repo_path=args.repo,
  171. early_preseed=args.early_preseed)
  172. bootstrap(args, generator, target, args.target_size)
  173. def bootstrap(args, generator, target, size):
  174. """Kick off bootstrap process."""
  175. print(f"Bootstrapping {args.arch}", flush=True)
  176. if args.chroot:
  177. find_chroot = """
  178. import shutil
  179. print(shutil.which('chroot'))
  180. """
  181. chroot_binary = run_as_root('python3', '-c', find_chroot,
  182. capture_output=True).stdout.decode().strip()
  183. generator.prepare(target, using_kernel=False)
  184. arch = stage0_arch_map.get(args.arch, args.arch)
  185. init = os.path.join(os.sep, 'bootstrap-seeds', 'POSIX', arch, 'kaem-optional-seed')
  186. run_as_root('env', '-i', 'PATH=/bin', chroot_binary, generator.target_dir, init)
  187. elif args.bwrap:
  188. init = '/init'
  189. if not args.internal_ci or args.internal_ci == "pass1":
  190. generator.prepare(target, using_kernel=False)
  191. arch = stage0_arch_map.get(args.arch, args.arch)
  192. init = os.path.join(os.sep, 'bootstrap-seeds', 'POSIX', arch, 'kaem-optional-seed')
  193. else:
  194. generator.reuse(target)
  195. run('env', '-i', 'bwrap', '--unshare-user',
  196. '--uid', '0',
  197. '--gid', '0',
  198. '--unshare-net' if args.external_sources else None,
  199. '--setenv', 'PATH', '/usr/bin',
  200. '--bind', generator.target_dir, '/',
  201. '--dir', '/dev',
  202. '--dev-bind', '/dev/null', '/dev/null',
  203. '--dev-bind', '/dev/zero', '/dev/zero',
  204. '--dev-bind', '/dev/random', '/dev/random',
  205. '--dev-bind', '/dev/urandom', '/dev/urandom',
  206. '--dev-bind', '/dev/ptmx', '/dev/ptmx',
  207. '--dev-bind', '/dev/tty', '/dev/tty',
  208. '--tmpfs', '/dev/shm',
  209. '--proc', '/proc',
  210. '--bind', '/sys', '/sys',
  211. '--tmpfs', '/tmp',
  212. init)
  213. elif args.bare_metal:
  214. if args.kernel:
  215. generator.prepare(target, using_kernel=True, target_size=size)
  216. path = os.path.join(args.target, os.path.relpath(generator.target_dir, args.target))
  217. print("Please:")
  218. print(f" 1. Take {path}/initramfs and your kernel, boot using this.")
  219. print(f" 2. Take {path}/disk.img and put this on a writable storage medium.")
  220. else:
  221. generator.prepare(target, kernel_bootstrap=True, target_size=size)
  222. path = os.path.join(args.target, os.path.relpath(generator.target_dir, args.target))
  223. print("Please:")
  224. print(f" 1. Take {path}.img and write it to a boot drive and then boot it.")
  225. else:
  226. if args.kernel:
  227. generator.prepare(target, using_kernel=True, target_size=size)
  228. arg_list = [
  229. '-enable-kvm',
  230. '-m', str(args.qemu_ram) + 'M',
  231. '-smp', str(args.cores),
  232. '-drive', 'file=' + target.get_disk("disk") + ',format=raw'
  233. ]
  234. if target.get_disk("external") is not None:
  235. arg_list += [
  236. '-drive', 'file=' + target.get_disk("external") + ',format=raw',
  237. ]
  238. arg_list += [
  239. '-nic', 'user,ipv6=off,model=e1000',
  240. '-kernel', args.kernel,
  241. '-append',
  242. ]
  243. if args.interactive:
  244. arg_list += ['consoleblank=0 earlyprintk=vga root=/dev/sda1 '
  245. 'rootfstype=ext3 init=/init rw']
  246. else:
  247. arg_list += ['console=ttyS0 earlycon=uart8250,io,0x3f8,115200n8 '
  248. 'root=/dev/sda1 rootfstype=ext3 init=/init rw']
  249. else:
  250. generator.prepare(target, kernel_bootstrap=True, target_size=size)
  251. arg_list = [
  252. '-enable-kvm',
  253. '-m', str(args.qemu_ram) + 'M',
  254. '-smp', str(args.cores),
  255. '-drive', 'file=' + generator.target_dir + '.img' + ',format=raw'
  256. ]
  257. if target.get_disk("external") is not None:
  258. arg_list += [
  259. '-drive', 'file=' + target.get_disk("external") + ',format=raw',
  260. ]
  261. arg_list += [
  262. '-machine', 'kernel-irqchip=split',
  263. '-nic', 'user,ipv6=off,model=e1000'
  264. ]
  265. if not args.interactive:
  266. arg_list += ['-no-reboot', '-nographic']
  267. run(args.qemu_cmd, *arg_list)
  268. if __name__ == "__main__":
  269. main()