diff --git a/release/scripts/pkgbase-stage.lua b/release/scripts/pkgbase-stage.lua index 94dec96673de..6845e0079b05 100755 --- a/release/scripts/pkgbase-stage.lua +++ b/release/scripts/pkgbase-stage.lua @@ -1,108 +1,115 @@ #!/usr/libexec/flua -- SPDX-License-Identifier: BSD-2-Clause -- -- Copyright(c) 2025 The FreeBSD Foundation. -- -- This software was developed by Isaac Freund -- under sponsorship from the FreeBSD Foundation. -- Run a command using the OS shell and capture the stdout -- Strips exactly one trailing newline if present, does not strip any other whitespace. -- Asserts that the command exits cleanly local function capture(command) local p = io.popen(command) local output = p:read("*a") assert(p:close()) -- Strip exactly one trailing newline from the output, if there is one return output:match("(.-)\n$") or output end -- Returns a list of packages to be included in the given media local function select_packages(pkg, media, all_libcompats) + -- Note: if you update this list, you must also update the list in + -- usr.sbin/bsdinstall/scripts/pkgbase.in. + local kernel_packages = { + -- Most architectures use this + ["FreeBSD-kernel-generic"] = true, + -- PowerPC uses either of these, depending on platform + ["FreeBSD-kernel-generic64"] = true, + ["FreeBSD-kernel-generic64le"] = true, + } + local components = {} local rquery = capture(pkg .. "rquery -U -r FreeBSD-base %n") for package in rquery:gmatch("[^\n]+") do local set = package:match("^FreeBSD%-set%-(.*)$") if set then components[set] = package - -- Kernels other than FreeBSD-kernel-generic are ignored - -- Note that on powerpc64 and powerpc64le the names are - -- slightly different. - elseif package:match("^FreeBSD%-kernel%-generic.*-dbg") then - components["kernel-dbg"] = package - elseif package:match("^FreeBSD%-kernel%-generic.*") then + elseif kernel_packages[package] then components["kernel"] = package + elseif kernel_packages[package:match("(.*)%-dbg$")] then + components["kernel-dbg"] = package elseif package == "pkg" then components["pkg"] = package end end assert(components["kernel"]) assert(components["base"]) assert(components["pkg"]) local selected = {} if media == "disc" then table.insert(selected, components["pkg"]) table.insert(selected, components["base"]) table.insert(selected, components["kernel"]) table.insert(selected, components["kernel-dbg"]) table.insert(selected, components["src"]) table.insert(selected, components["tests"]) for compat in all_libcompats:gmatch("%S+") do table.insert(selected, components["lib" .. compat]) end else assert(media == "dvd") table.insert(selected, components["pkg"]) table.insert(selected, components["base"]) table.insert(selected, components["base-dbg"]) table.insert(selected, components["kernel"]) table.insert(selected, components["kernel-dbg"]) table.insert(selected, components["src"]) table.insert(selected, components["tests"]) for compat in all_libcompats:gmatch("%S+") do table.insert(selected, components["lib" .. compat]) table.insert(selected, components["lib" .. compat .. "-dbg"]) end end return selected end local function main() -- Determines package subset selected local media = assert(arg[1]) assert(media == "disc" or media == "dvd") -- Local repository to fetch from local source = assert(arg[2]) -- Directory to create new repository local target = assert(arg[3]) -- =hitespace separated list of all libcompat names (e.g. "32") local all_libcompats = assert(arg[4]) -- ABI of repository local ABI = assert(arg[5]) assert(os.execute("mkdir -p pkgbase-repo-conf")) local f = assert(io.open("pkgbase-repo-conf/FreeBSD-base.conf", "w")) assert(f:write(string.format([[ FreeBSD-base: { url: "file://%s", enabled: yes } ]], source))) assert(f:close()) local pkg = "pkg -o ASSUME_ALWAYS_YES=yes -o IGNORE_OSVERSION=yes " .. "-o ABI=" .. ABI .. " " .. "-o INSTALL_AS_USER=1 -o PKG_DBDIR=./pkgdb -R ./pkgbase-repo-conf " assert(os.execute(pkg .. "update")) local packages = select_packages(pkg, media, all_libcompats) assert(os.execute(pkg .. "fetch -d -o " .. target .. " " .. table.concat(packages, " "))) assert(os.execute(pkg .. "repo " .. target)) end main() diff --git a/usr.sbin/bsdinstall/scripts/pkgbase.in b/usr.sbin/bsdinstall/scripts/pkgbase.in index c06a3f789791..3ba6a3474e0f 100755 --- a/usr.sbin/bsdinstall/scripts/pkgbase.in +++ b/usr.sbin/bsdinstall/scripts/pkgbase.in @@ -1,316 +1,321 @@ #!/usr/libexec/flua -- SPDX-License-Identifier: BSD-2-Clause -- -- Copyright(c) 2025 The FreeBSD Foundation. -- -- This software was developed by Isaac Freund -- under sponsorship from the FreeBSD Foundation. local sys_wait = require("posix.sys.wait") local unistd = require("posix.unistd") local all_libcompats = "%%_ALL_libcompats%%" -- Run a command using the OS shell and capture the stdout -- Strips exactly one trailing newline if present, does not strip any other whitespace. -- Asserts that the command exits cleanly local function capture(command) local p = io.popen(command) local output = p:read("*a") assert(p:close()) -- Strip exactly one trailing newline from the output, if there is one return output:match("(.-)\n$") or output end local function append_list(list, other) for _, item in ipairs(other) do table.insert(list, item) end end -- Read from the given fd until EOF -- Returns all the data read as a single string local function read_all(fd) local ret = "" repeat local buffer = assert(unistd.read(fd, 1024)) ret = ret .. buffer until buffer == "" return ret end -- Run bsddialog with the given argument list -- Returns the exit code and stderr output of bsddialog local function bsddialog(args) local r, w = assert(unistd.pipe()) local pid = assert(unistd.fork()) if pid == 0 then assert(unistd.close(r)) assert(unistd.dup2(w, 2)) assert(unistd.execp("bsddialog", args)) unistd._exit() end assert(unistd.close(w)) local output = read_all(r) assert(unistd.close(r)) local _, _, exit_code = assert(sys_wait.wait(pid)) return exit_code, output end -- Prompts the user for a yes/no answer to the given question using bsddialog -- Returns true if the user answers yes and false if the user answers no. local function prompt_yn(question) local exit_code = bsddialog({ "--yesno", "--disable-esc", question, 0, 0, -- autosize }) return exit_code == 0 end -- Creates a dialog for component selection mirroring the -- traditional tarball component selection dialog. local function select_components(components, options) local descriptions = { ["kernel-dbg"] = "Debug symbols for the kernel", ["devel"] = "C/C++ compilers and related utilities", ["base"] = "The complete base system (includes devel)", ["src"] = "System source tree", ["tests"] = "Test suite", ["lib32"] = "32-bit compatibility libraries", ["debug"] = "Debug symbols for the selected components", } -- These defaults match what the non-pkgbase installer selects -- by default. local defaults = { ["base"] = "on", ["kernel-dbg"] = "on", } -- Enable compat sets by default. for compat in all_libcompats:gmatch("%S+") do defaults["lib" .. compat] = "on" end -- Sorting the components is necessary to ensure that the ordering is -- consistent in the UI. local sorted_components = {} for component, _ in pairs(components) do -- Decide which sets we want to offer to the user: -- -- "minimal" is not offered since it's always included. -- -- "-dbg" sets are never offered, because those are handled -- via the "debug" component. -- -- "kernels" is never offered because we only want one kernel, -- which is handled separately. -- -- Sets whose name ends in "-jail" are intended for jails, and -- are only offered if no_kernel is set. if not component:match("^minimal") and not component:match("%-dbg$") and not (component == "kernels") and not (not options.no_kernel and component:match("%-jail$")) then table.insert(sorted_components, component) end end table.sort(sorted_components) local checklist_items = {} for _, component in ipairs(sorted_components) do if component ~= "kernel" and not (component == "kernel-dbg" and options.no_kernel) then local description = descriptions[component] or "" local default = defaults[component] or "off" table.insert(checklist_items, component) table.insert(checklist_items, description) table.insert(checklist_items, default) end end local bsddialog_args = { "--backtitle", "FreeBSD Installer", "--title", "Select System Components", "--nocancel", "--disable-esc", "--separate-output", "--checklist", "A minimal set of packages suitable for a multi-user system ".. "is always installed. Select additional packages you wish ".. "to install:", "0", "0", "0", -- autosize } append_list(bsddialog_args, checklist_items) local exit_code, output = bsddialog(bsddialog_args) -- This should only be possible if bsddialog is killed by a signal -- or buggy, we disable the cancel option and esc key. -- If this does happen, there's not much we can do except exit with a -- hopefully useful stack trace. assert(exit_code == 0) -- Always install the minimal set, since it's required for the system -- to work. The base set depends on minimal, but it's fine to install -- both, and this way the user can remove the base set without pkg -- autoremove then trying to remove minimal. local selected = {"minimal"} if not options.no_kernel then table.insert(selected, "kernel") end for component in output:gmatch("[^\n]+") do table.insert(selected, component) end return selected end -- Returns a list of pkgbase packages selected by the user local function select_packages(pkg, options) -- These are the components which aren't generated automatically from -- package sets. local components = { ["kernel"] = {}, ["kernel-dbg"] = {}, ["debug"] = {}, } + -- Note: if you update this list, you must also update the list in + -- release/scripts/pkgbase-stage.lua. + local kernel_packages = { + -- Most architectures use this + ["FreeBSD-kernel-generic"] = true, + -- PowerPC uses either of these, depending on platform + ["FreeBSD-kernel-generic64"] = true, + ["FreeBSD-kernel-generic64le"] = true, + } + local rquery = capture(pkg .. "rquery -U -r FreeBSD-base %n") for package in rquery:gmatch("[^\n]+") do local setname = package:match("^FreeBSD%-set%-(.+)$") if setname then components[setname] = components[setname] or {} table.insert(components[setname], package) - elseif package:match("^FreeBSD%-kernel%-.*") and - package ~= "FreeBSD-kernel-man" - then - -- Kernels other than FreeBSD-kernel-generic are ignored - if package == "FreeBSD-kernel-generic" then - table.insert(components["kernel"], package) - elseif package == "FreeBSD-kernel-generic-dbg" then - table.insert(components["kernel-dbg"], package) - end + elseif kernel_packages[package] then + table.insert(components["kernel"], package) + elseif kernel_packages[package:match("(.*)%-dbg$")] then + table.insert(components["kernel-dbg"], package) end end -- Assert that both a kernel and the "minimal" set are available, since -- those are both required to install a functional system. Don't worry -- if other sets are missing (e.g. base or src), which might happen -- when using custom install media. assert(#components["kernel"] == 1) assert(#components["minimal"] == 1) -- Prompt the user for what to install. local selected = select_components(components, options) -- Determine if the "debug" component was selected. local debug = false for _, component in ipairs(selected) do if component == "debug" then debug = true break end end local packages = {} for _, component in ipairs(selected) do local pkglist = components[component] append_list(packages, pkglist) -- If the debug component was selected, install the -dbg -- package for each set. We have to check if the dbg set -- actually exists, because some sets (src, tests) don't -- have a -dbg subpackage. for _, c in ipairs(pkglist) do local setname = c:match("^FreeBSD%-set%-(.*)$") if debug and setname then local dbgset = setname.."-dbg" if components[dbgset] then append_list(packages, components[dbgset]) end end end end return packages end local function parse_options() local options = {} for _, a in ipairs(arg) do if a == "--no-kernel" then options.no_kernel = true else io.stderr:write("Error: unknown option " .. a .. "\n") os.exit(1) end end return options end -- Fetch and install pkgbase packages to BSDINSTALL_CHROOT. -- Respect BSDINSTALL_PKG_REPOS_DIR if set, otherwise use pkg.freebsd.org. local function pkgbase() local options = parse_options() -- TODO Support fully offline pkgbase installation by taking a new enough -- version of pkg.pkg as input. if not os.execute("pkg -N > /dev/null 2>&1") then print("Bootstrapping pkg on the host system") assert(os.execute("pkg bootstrap -y")) end local chroot = assert(os.getenv("BSDINSTALL_CHROOT")) assert(os.execute("mkdir -p " .. chroot)) -- Always install the default FreeBSD-base.conf file to the chroot, even -- if we don't actually fetch the packages from the repository specified -- there (e.g. because we are performing an offline installation). local chroot_repos_dir = chroot .. "/usr/local/etc/pkg/repos/" assert(os.execute("mkdir -p " .. chroot_repos_dir)) assert(os.execute("cp /usr/share/bsdinstall/FreeBSD-base.conf " .. chroot_repos_dir)) local repos_dir = os.getenv("BSDINSTALL_PKG_REPOS_DIR") if not repos_dir then repos_dir = chroot_repos_dir -- Since pkg always interprets fingerprints paths as relative to -- the --rootdir we must copy the key from the host. assert(os.execute("mkdir -p " .. chroot .. "/usr/share/keys")) assert(os.execute("cp -R /usr/share/keys/pkg " .. chroot .. "/usr/share/keys/")) end -- We must use --repo-conf-dir rather than -o REPOS_DIR here as the latter -- is interpreted relative to the --rootdir. BSDINSTALL_PKG_REPOS_DIR must -- be allowed to point to a path outside the chroot. local pkg = "pkg --rootdir " .. chroot .. " --repo-conf-dir " .. repos_dir .. " -o IGNORE_OSVERSION=yes " while not os.execute(pkg .. "update") do if not prompt_yn("Updating repositories failed, try again?") then os.exit(1) end end local packages = table.concat(select_packages(pkg, options), " ") while not os.execute(pkg .. "install -U -F -y -r FreeBSD-base " .. packages) do if not prompt_yn("Fetching packages failed, try again?") then os.exit(1) end end if not os.execute(pkg .. "install -U -y -r FreeBSD-base " .. packages) then os.exit(1) end end pkgbase()