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