xref: /freebsd/usr.sbin/bsdinstall/scripts/pkgbase.in (revision 0bfa8a45962644947e17b5262c5d0efa36d3f4d9)
1ee9cfd72SIsaac Freund#!/usr/libexec/flua
2ee9cfd72SIsaac Freund
3ee9cfd72SIsaac Freund-- SPDX-License-Identifier: BSD-2-Clause
4ee9cfd72SIsaac Freund--
5ee9cfd72SIsaac Freund-- Copyright(c) 2025 The FreeBSD Foundation.
6ee9cfd72SIsaac Freund--
7ee9cfd72SIsaac Freund-- This software was developed by Isaac Freund <ifreund@freebsdfoundation.org>
8ee9cfd72SIsaac Freund-- under sponsorship from the FreeBSD Foundation.
9ee9cfd72SIsaac Freund
103c3dd629SIsaac Freundlocal sys_wait = require("posix.sys.wait")
113c3dd629SIsaac Freundlocal unistd = require("posix.unistd")
123c3dd629SIsaac Freund
13ee9cfd72SIsaac Freundlocal all_libcompats <const> = "%%_ALL_libcompats%%"
14ee9cfd72SIsaac Freund
15ee9cfd72SIsaac Freund-- Run a command using the OS shell and capture the stdout
16ee9cfd72SIsaac Freund-- Strips exactly one trailing newline if present, does not strip any other whitespace.
17ee9cfd72SIsaac Freund-- Asserts that the command exits cleanly
18ee9cfd72SIsaac Freundlocal function capture(command)
19ee9cfd72SIsaac Freund	local p = io.popen(command)
20ee9cfd72SIsaac Freund	local output = p:read("*a")
21ee9cfd72SIsaac Freund	assert(p:close())
22ee9cfd72SIsaac Freund	-- Strip exactly one trailing newline from the output, if there is one
23ee9cfd72SIsaac Freund	return output:match("(.-)\n$") or output
24ee9cfd72SIsaac Freundend
25ee9cfd72SIsaac Freund
26ee9cfd72SIsaac Freundlocal function append_list(list, other)
27ee9cfd72SIsaac Freund	for _, item in ipairs(other) do
28ee9cfd72SIsaac Freund		table.insert(list, item)
29ee9cfd72SIsaac Freund	end
30ee9cfd72SIsaac Freundend
31ee9cfd72SIsaac Freund
323c3dd629SIsaac Freund-- Read from the given fd until EOF
333c3dd629SIsaac Freund-- Returns all the data read as a single string
343c3dd629SIsaac Freundlocal function read_all(fd)
353c3dd629SIsaac Freund	local ret = ""
363c3dd629SIsaac Freund	repeat
373c3dd629SIsaac Freund		local buffer = assert(unistd.read(fd, 1024))
383c3dd629SIsaac Freund		ret = ret .. buffer
393c3dd629SIsaac Freund	until buffer == ""
403c3dd629SIsaac Freund	return ret
413c3dd629SIsaac Freundend
423c3dd629SIsaac Freund
433c3dd629SIsaac Freund-- Run bsddialog with the given argument list
443c3dd629SIsaac Freund-- Returns the exit code and stderr output of bsddialog
453c3dd629SIsaac Freundlocal function bsddialog(args)
463c3dd629SIsaac Freund	local r, w = assert(unistd.pipe())
473c3dd629SIsaac Freund
483c3dd629SIsaac Freund	local pid = assert(unistd.fork())
493c3dd629SIsaac Freund	if pid == 0 then
503c3dd629SIsaac Freund		assert(unistd.close(r))
513c3dd629SIsaac Freund		assert(unistd.dup2(w, 2))
523c3dd629SIsaac Freund		assert(unistd.execp("bsddialog", args))
533c3dd629SIsaac Freund		unistd._exit()
543c3dd629SIsaac Freund	end
553c3dd629SIsaac Freund	assert(unistd.close(w))
563c3dd629SIsaac Freund
573c3dd629SIsaac Freund	local output = read_all(r)
583c3dd629SIsaac Freund	assert(unistd.close(r))
593c3dd629SIsaac Freund
603c3dd629SIsaac Freund	local _, _, exit_code = assert(sys_wait.wait(pid))
613c3dd629SIsaac Freund	return exit_code, output
623c3dd629SIsaac Freundend
633c3dd629SIsaac Freund
64*0bfa8a45SIsaac Freund-- Prompts the user for a yes/no answer to the given question using bsddialog
65*0bfa8a45SIsaac Freund-- Returns true if the user answers yes and false if the user answers no.
66*0bfa8a45SIsaac Freundlocal function prompt_yn(question)
67*0bfa8a45SIsaac Freund	local exit_code = bsddialog({
68*0bfa8a45SIsaac Freund		"--yesno",
69*0bfa8a45SIsaac Freund		"--disable-esc",
70*0bfa8a45SIsaac Freund		question,
71*0bfa8a45SIsaac Freund		0, 0, -- autosize
72*0bfa8a45SIsaac Freund	})
73*0bfa8a45SIsaac Freund	return exit_code == 0
74*0bfa8a45SIsaac Freundend
75*0bfa8a45SIsaac Freund
763c3dd629SIsaac Freund-- Creates a dialog for component selection mirroring the
773c3dd629SIsaac Freund-- traditional tarball component selection dialog.
783c3dd629SIsaac Freundlocal function select_components(components, options)
793c3dd629SIsaac Freund	local descriptions = {
803c3dd629SIsaac Freund		kernel_dbg = "Kernel debug info",
813c3dd629SIsaac Freund		base_dbg = "Base system debug info",
823c3dd629SIsaac Freund		src = "System source tree",
833c3dd629SIsaac Freund		tests = "Test suite",
843c3dd629SIsaac Freund		lib32 = "32-bit compatibility libraries",
853c3dd629SIsaac Freund		lib32_dbg = "32-bit compatibility libraries debug info",
863c3dd629SIsaac Freund	}
873c3dd629SIsaac Freund	local defaults = {
883c3dd629SIsaac Freund		kernel_dbg = "on",
893c3dd629SIsaac Freund		base_dbg = "off",
903c3dd629SIsaac Freund		src = "off",
913c3dd629SIsaac Freund		tests = "off",
923c3dd629SIsaac Freund		lib32 = "on",
933c3dd629SIsaac Freund		lib32_dbg = "off",
943c3dd629SIsaac Freund	}
953c3dd629SIsaac Freund
963c3dd629SIsaac Freund	-- Sorting the components is necessary to ensure that the ordering is
973c3dd629SIsaac Freund	-- consistent in the UI.
983c3dd629SIsaac Freund	local sorted_components = {}
993c3dd629SIsaac Freund	for component, _ in pairs(components) do
1003c3dd629SIsaac Freund		table.insert(sorted_components, component)
1013c3dd629SIsaac Freund	end
1023c3dd629SIsaac Freund	table.sort(sorted_components)
1033c3dd629SIsaac Freund
1043c3dd629SIsaac Freund	local checklist_items = {}
1053c3dd629SIsaac Freund	for _, component in ipairs(sorted_components) do
1063c3dd629SIsaac Freund		if component ~= "base" and component ~= "kernel" and
1073c3dd629SIsaac Freund		    not (component == "kernel_dbg" and options.no_kernel) and
1083c3dd629SIsaac Freund		    #components[component] > 0 then
1093c3dd629SIsaac Freund			local description = descriptions[component] or "''"
1103c3dd629SIsaac Freund			local default = defaults[component]  or "off"
1113c3dd629SIsaac Freund			table.insert(checklist_items, component)
1123c3dd629SIsaac Freund			table.insert(checklist_items, description)
1133c3dd629SIsaac Freund			table.insert(checklist_items, default)
1143c3dd629SIsaac Freund		end
1153c3dd629SIsaac Freund	end
1163c3dd629SIsaac Freund
1173c3dd629SIsaac Freund	local bsddialog_args = {
1183c3dd629SIsaac Freund		"--backtitle", "FreeBSD Installer",
1193c3dd629SIsaac Freund		"--title", "Select System Components",
1203c3dd629SIsaac Freund		"--nocancel",
1213c3dd629SIsaac Freund		"--disable-esc",
1223c3dd629SIsaac Freund		"--separate-output",
1233c3dd629SIsaac Freund		"--checklist", "Choose optional system components to install:",
1243c3dd629SIsaac Freund		"0", "0", "0", -- autosize
1253c3dd629SIsaac Freund	}
1263c3dd629SIsaac Freund	append_list(bsddialog_args, checklist_items)
1273c3dd629SIsaac Freund
1283c3dd629SIsaac Freund	local exit_code, output = bsddialog(bsddialog_args)
1293c3dd629SIsaac Freund	-- This should only be possible if bsddialog is killed by a signal
1303c3dd629SIsaac Freund	-- or buggy, we disable the cancel option and esc key.
1313c3dd629SIsaac Freund	-- If this does happen, there's not much we can do except exit with a
1323c3dd629SIsaac Freund	-- hopefully useful stack trace.
1333c3dd629SIsaac Freund	assert(exit_code == 0)
1343c3dd629SIsaac Freund
1353c3dd629SIsaac Freund	local selected = {"base"}
1363c3dd629SIsaac Freund	if not options.no_kernel then
1373c3dd629SIsaac Freund		table.insert(selected, "kernel")
1383c3dd629SIsaac Freund	end
1393c3dd629SIsaac Freund	for component in output:gmatch("[^\n]+") do
1403c3dd629SIsaac Freund		table.insert(selected, component)
1413c3dd629SIsaac Freund	end
1423c3dd629SIsaac Freund
1433c3dd629SIsaac Freund	return selected
1443c3dd629SIsaac Freundend
1453c3dd629SIsaac Freund
1463c3dd629SIsaac Freund-- Returns a list of pkgbase packages selected by the user
147ee9cfd72SIsaac Freundlocal function select_packages(pkg, options)
148ee9cfd72SIsaac Freund	local components = {
1493c3dd629SIsaac Freund		kernel = {},
1503c3dd629SIsaac Freund		kernel_dbg = {},
1513c3dd629SIsaac Freund		base = {},
1523c3dd629SIsaac Freund		base_dbg = {},
1533c3dd629SIsaac Freund		src = {},
1543c3dd629SIsaac Freund		tests = {},
155ee9cfd72SIsaac Freund	}
156ee9cfd72SIsaac Freund
157ee9cfd72SIsaac Freund	for compat in all_libcompats:gmatch("%S+") do
158ee9cfd72SIsaac Freund		components["lib" .. compat] = {}
1593c3dd629SIsaac Freund		components["lib" .. compat .. "_dbg"] = {}
160ee9cfd72SIsaac Freund	end
161ee9cfd72SIsaac Freund
162ee9cfd72SIsaac Freund	local rquery = capture(pkg .. "rquery -U -r FreeBSD-base %n")
163ee9cfd72SIsaac Freund	for package in rquery:gmatch("[^\n]+") do
164ee9cfd72SIsaac Freund		if package == "FreeBSD-src" or package:match("^FreeBSD%-src%-.*") then
165ee9cfd72SIsaac Freund			table.insert(components["src"], package)
166ee9cfd72SIsaac Freund		elseif package == "FreeBSD-tests" or package:match("^FreeBSD%-tests%-.*") then
167ee9cfd72SIsaac Freund			table.insert(components["tests"], package)
168ee9cfd72SIsaac Freund		elseif package:match("^FreeBSD%-kernel%-.*") then
169ee9cfd72SIsaac Freund			-- Kernels other than FreeBSD-kernel-generic are ignored
170ee9cfd72SIsaac Freund			if package == "FreeBSD-kernel-generic" then
171ee9cfd72SIsaac Freund				table.insert(components["kernel"], package)
172ee9cfd72SIsaac Freund			elseif package == "FreeBSD-kernel-generic-dbg" then
1733c3dd629SIsaac Freund				table.insert(components["kernel_dbg"], package)
174ee9cfd72SIsaac Freund			end
175ee9cfd72SIsaac Freund		elseif package:match(".*%-dbg$") then
1763c3dd629SIsaac Freund			table.insert(components["base_dbg"], package)
177ee9cfd72SIsaac Freund		else
178ee9cfd72SIsaac Freund			local found = false
179ee9cfd72SIsaac Freund			for compat in all_libcompats:gmatch("%S+") do
180ee9cfd72SIsaac Freund				if package:match(".*%-dbg%-lib" .. compat .. "$") then
1813c3dd629SIsaac Freund					table.insert(components["lib" .. compat .. "_dbg"], package)
182ee9cfd72SIsaac Freund					found = true
183ee9cfd72SIsaac Freund					break
184ee9cfd72SIsaac Freund				elseif package:match(".*%-lib" .. compat .. "$") then
185ee9cfd72SIsaac Freund					table.insert(components["lib" .. compat], package)
186ee9cfd72SIsaac Freund					found = true
187ee9cfd72SIsaac Freund					break
188ee9cfd72SIsaac Freund				end
189ee9cfd72SIsaac Freund			end
190ee9cfd72SIsaac Freund			if not found then
191ee9cfd72SIsaac Freund				table.insert(components["base"], package)
192ee9cfd72SIsaac Freund			end
193ee9cfd72SIsaac Freund		end
194ee9cfd72SIsaac Freund	end
195ee9cfd72SIsaac Freund	-- Don't assert the existence of dbg, tests, and src packages here. If using
196ee9cfd72SIsaac Freund	-- a custom local repository with BSDINSTALL_PKG_REPOS_DIR we shouldn't
197ee9cfd72SIsaac Freund	-- require it to have all packages.
198ee9cfd72SIsaac Freund	assert(#components["kernel"] == 1)
199ee9cfd72SIsaac Freund	assert(#components["base"] > 0)
200ee9cfd72SIsaac Freund
201ee9cfd72SIsaac Freund	local selected = {}
2023c3dd629SIsaac Freund	for _, component in ipairs(select_components(components, options)) do
2033c3dd629SIsaac Freund		append_list(selected, components[component])
204ee9cfd72SIsaac Freund	end
205ee9cfd72SIsaac Freund
206ee9cfd72SIsaac Freund	return selected
207ee9cfd72SIsaac Freundend
208ee9cfd72SIsaac Freund
209ee9cfd72SIsaac Freundlocal function parse_options()
210ee9cfd72SIsaac Freund	local options = {}
211ee9cfd72SIsaac Freund	for _, a in ipairs(arg) do
212ee9cfd72SIsaac Freund		if a == "--no-kernel" then
213ee9cfd72SIsaac Freund			options.no_kernel = true
214ee9cfd72SIsaac Freund		else
215ee9cfd72SIsaac Freund			io.stderr:write("Error: unknown option " .. a .. "\n")
216ee9cfd72SIsaac Freund			os.exit(1)
217ee9cfd72SIsaac Freund		end
218ee9cfd72SIsaac Freund	end
219ee9cfd72SIsaac Freund	return options
220ee9cfd72SIsaac Freundend
221ee9cfd72SIsaac Freund
222ee9cfd72SIsaac Freund-- Fetch and install pkgbase packages to BSDINSTALL_CHROOT.
223ee9cfd72SIsaac Freund-- Respect BSDINSTALL_PKG_REPOS_DIR if set, otherwise use pkg.freebsd.org.
224ee9cfd72SIsaac Freundlocal function pkgbase()
225ee9cfd72SIsaac Freund	local options = parse_options()
226ee9cfd72SIsaac Freund
227ee9cfd72SIsaac Freund	-- TODO Support fully offline pkgbase installation by taking a new enough
228ee9cfd72SIsaac Freund	-- version of pkg.pkg as input.
229ee9cfd72SIsaac Freund	if not os.execute("pkg -N > /dev/null 2>&1") then
230ee9cfd72SIsaac Freund		print("Bootstrapping pkg on the host system")
231ee9cfd72SIsaac Freund		assert(os.execute("pkg bootstrap -y"))
232ee9cfd72SIsaac Freund	end
233ee9cfd72SIsaac Freund
234ee9cfd72SIsaac Freund	local chroot = assert(os.getenv("BSDINSTALL_CHROOT"))
235ee9cfd72SIsaac Freund	assert(os.execute("mkdir -p " .. chroot))
236ee9cfd72SIsaac Freund
237ee9cfd72SIsaac Freund	local repos_dir = os.getenv("BSDINSTALL_PKG_REPOS_DIR")
238ee9cfd72SIsaac Freund	if not repos_dir then
239ee9cfd72SIsaac Freund		repos_dir = chroot .. "/usr/local/etc/pkg/repos/"
240ee9cfd72SIsaac Freund		assert(os.execute("mkdir -p " .. repos_dir))
241ee9cfd72SIsaac Freund		assert(os.execute("cp /usr/share/bsdinstall/FreeBSD-base.conf " .. repos_dir))
242ee9cfd72SIsaac Freund
243ee9cfd72SIsaac Freund		-- Since pkg always interprets fingerprints paths as relative to
244ee9cfd72SIsaac Freund		-- the --rootdir we must copy the key from the host.
245ee9cfd72SIsaac Freund		assert(os.execute("mkdir -p " .. chroot .. "/usr/share/keys"))
246ee9cfd72SIsaac Freund		assert(os.execute("cp -R /usr/share/keys/pkg " .. chroot .. "/usr/share/keys/"))
247ee9cfd72SIsaac Freund	end
248ee9cfd72SIsaac Freund
249ee9cfd72SIsaac Freund	-- We must use --repo-conf-dir rather than -o REPOS_DIR here as the latter
250ee9cfd72SIsaac Freund	-- is interpreted relative to the --rootdir. BSDINSTALL_PKG_REPOS_DIR must
251ee9cfd72SIsaac Freund	-- be allowed to point to a path outside the chroot.
252ee9cfd72SIsaac Freund	local pkg = "pkg --rootdir " .. chroot ..
253ee9cfd72SIsaac Freund		" --repo-conf-dir " .. repos_dir .. " -o IGNORE_OSVERSION=yes "
254ee9cfd72SIsaac Freund
255ee9cfd72SIsaac Freund	while not os.execute(pkg .. "update") do
256ee9cfd72SIsaac Freund		if not prompt_yn("Updating repositories failed, try again?") then
257ee9cfd72SIsaac Freund			os.exit(1)
258ee9cfd72SIsaac Freund		end
259ee9cfd72SIsaac Freund	end
260ee9cfd72SIsaac Freund
261ee9cfd72SIsaac Freund	local packages = table.concat(select_packages(pkg, options), " ")
262ee9cfd72SIsaac Freund
263ee9cfd72SIsaac Freund	while not os.execute(pkg .. "install -U -F -y -r FreeBSD-base " .. packages) do
264ee9cfd72SIsaac Freund		if not prompt_yn("Fetching packages failed, try again?") then
265ee9cfd72SIsaac Freund			os.exit(1)
266ee9cfd72SIsaac Freund		end
267ee9cfd72SIsaac Freund	end
268ee9cfd72SIsaac Freund
269ee9cfd72SIsaac Freund	if not os.execute(pkg .. "install -U -y -r FreeBSD-base " .. packages) then
270ee9cfd72SIsaac Freund		os.exit(1)
271ee9cfd72SIsaac Freund	end
272ee9cfd72SIsaac Freundend
273ee9cfd72SIsaac Freund
274ee9cfd72SIsaac Freundpkgbase()
275