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