From 1501978abd4db433e8c0b46c5687cb774d899f84 Mon Sep 17 00:00:00 2001 From: Dave Cottlehuber Date: Wed, 1 Jul 2026 07:42:22 +0000 Subject: [PATCH 1/2] Check for securelevel, chflags, & jails before proceeding Elevated securelevel and blocked chflags will cause the upgrade to fail, so cowardly refuse to run. An appropriately configured jail is acceptable, so let the user decide if they prefer to run internally vs externally. --- pkgbasify.lua | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pkgbasify.lua b/pkgbasify.lua index 95abcf1..bd374ca 100755 --- a/pkgbasify.lua +++ b/pkgbasify.lua @@ -522,6 +522,44 @@ local function check_no_readonly_var_empty() return capture("zfs get -H -o value readonly /var/empty") == "off" end +local function check_securelevel() + local securelevel = math.tointeger(capture("sysctl -n kern.securelevel")) + if securelevel and securelevel >= 1 then + fatal(string.format([[ +kern.securelevel is %d (>= 1). +This prevents clearing system file flags (e.g. schg) which is required +for pkg to overwrite base system files.]], securelevel)) + end +end + +local function check_chflags() + local chflags_ok = os.execute("touch /tmp/.pkgbasify_chflags_test 2>/dev/null" .. + " && chflags schg /tmp/.pkgbasify_chflags_test 2>/dev/null") + if chflags_ok then + os.execute("chflags noschg /tmp/.pkgbasify_chflags_test 2>/dev/null") + os.execute("rm -f /tmp/.pkgbasify_chflags_test 2>/dev/null") + else + os.execute("rm -f /tmp/.pkgbasify_chflags_test 2>/dev/null") + fatal([[ +chflags operations do not appear to be permitted. +pkg needs chflags to replace base system files with schg flags set. +Conversion cannot proceed without chflags support.]]) + end +end + +local function check_jail_environment() + if capture("sysctl -n security.jail.jailed") ~= "1" then + return true + end + + print("Warning: pkgbasify is running inside a jail.") + print("It is recommended to run pkgbasify from the host system using --jail instead.") + print("Running inside the jail may fail if the jail lacks sufficient privileges.") + print("") + + return prompt_yn("Continue running inside this jail anyway?") +end + local function check_disk_space() -- KiB available on the root filesystem local avail = tonumber(capture( @@ -648,6 +686,12 @@ local function main() The system is already using pkgbase. Pass --force to run pkgbasify anyway, for example to fix a partial conversion.]]) end + check_securelevel() + check_chflags() + if not check_jail_environment() then + print("Canceled") + os.exit(1) + end if not check_disk_space() then print("Canceled") os.exit(1) From 22012a9eff98f3718896fdf82933b0eebf4d95e2 Mon Sep 17 00:00:00 2001 From: Dave Cottlehuber Date: Wed, 1 Jul 2026 07:43:43 +0000 Subject: [PATCH 2/2] Document all the options in the README And explain the choices for running in jails. --- README.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 28616c3..73a4432 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,15 @@ Conversion can likely succeed with less, but pkg is [not yet](https://github.com able to detect and handle insufficient space gracefully. It can be difficult to recover if the system runs out of space during conversion. +pkgbasify runs several pre-flight checks before starting conversion: + +- **Securelevel**: `kern.securelevel` must be less than 1, otherwise system immutable flags (`schg`) cannot be cleared and pkg will be unable to overwrite protected base system files. pkgbasify will refuse to proceed if this check fails. +- **chflags**: The ability to set and clear file flags is tested. This can fail due to elevated securelevel, or in a jail without `allow.chflags`. pkgbasify will refuse to proceed if this check fails. +- **Jail detection**: If running inside a jail, pkgbasify will warn and recommend using `--jail` from the host instead (see [Jail Conversion](#jail-conversion)). The user may choose to proceed. +- **Disk space**: At least 5 GiB free on the root filesystem. +- **Read-only `/var/empty`**: On ZFS, `/var/empty` must not be a read-only filesystem. +- **Symlinks in `/etc`**: Unexpected symlinks are flagged since pkg may overwrite them. + Download the script, give it permission to execute, run it as root: 1. `fetch https://github.com/FreeBSDFoundation/pkgbasify/raw/refs/heads/main/pkgbasify.lua` @@ -39,24 +48,79 @@ In this case, the user should fix whatever issue caused the error and run `./pkg See also [Common Problems and Solutions](#common-problems-and-solutions). +## Options + +``` +-h, --help Print usage message and exit +--version Print the version and exit +--force Attempt conversion even if /usr/bin/uname + is already owned by a package (e.g. to fix + a partial conversion) +--repo-name Name of the pkgbase repository + (default: FreeBSD-base) +--no-create-repo-conf Don't create a repository configuration; + requires the user to configure a pkgbase + repository manually +--rootdir Operate on the given directory rather than / +--jail Operate on the jail with the given jid or + name, matching the version of the jail's + userland (see "Jail Conversion" below) +``` + +## Jail Conversion + +pkgbasify can convert a jail to pkgbase in two ways: + +### From the host (recommended) + +Run pkgbasify on the host system with the `--jail` flag: + +``` +./pkgbasify.lua --jail +``` + +This is the recommended approach because the host has full access to +modify files inside the jail without any privilege restrictions. + +### From inside the jail + +Running pkgbasify directly inside a jail is possible but may fail if +the jail lacks sufficient privileges. pkgbasify will detect that it is +running inside a jail and warn about potential issues. + +The general securelevel and chflags pre-flight checks (described in +[Usage](#usage)) are particularly relevant inside jails: + +- A jail inherits its securelevel from the host if not explicitly set. + If the host has `kern.securelevel >= 1`, the jail will too. +- The jail must be configured with `allow.chflags` (or `enforce_statfs` + set appropriately) for pkg to manipulate file flags. + +If these checks fail, pkgbasify will warn and offer to continue, but +conversion will very likely fail. + +Kernel packages are automatically excluded when converting a jail, as +jails share the host kernel. + ## Behavior On FreeBSD 15, pkgbasify performs the following steps: -1. Select a repository based on the output of [freebsd-version(1)] and create `/usr/local/etc/pkg/repos/FreeBSD.conf`. -2. Select package sets that correspond to the currently installed base system components. +1. Run pre-flight checks (securelevel, chflags, jail detection, disk space, `/var/empty` readonly, `/etc` symlinks). +2. Select a repository based on the output of [freebsd-version(1)] and create `/usr/local/etc/pkg/repos/FreeBSD.conf`. +3. Select package sets that correspond to the currently installed base system components. - For example: if the lib32 component is not already installed, pkgbasify will not install `FreeBSD-set-lib32`. - pkgbasify never installs `FreeBSD-set-src` package even if `/usr/src` is present and non-empty. This prevents unwanted overwriting of potentially modified source files and/or a VCS repository. -3. Prompt the user to create a "pre-pkgbasify" boot environment using [bectl(8)] if possible. -4. Download selected packages -5. Register selected packages in the pkg database without installing any files (`pkg install --register-only`). -6. Install selected packages, overwriting normal files and merging config files (`pkg install --force`). +4. Prompt the user to create a "pre-pkgbasify" boot environment using [bectl(8)] if possible. +5. Download selected packages +6. Register selected packages in the pkg database without installing any files (`pkg install --register-only`). +7. Install selected packages, overwriting normal files and merging config files (`pkg install --force`). - As per normal [pkg(8)] behavior, `.pkgnew` files are created for config files for which merge fails. -7. If [sshd(8)] is running, restart the service. -8. Run [pwd_mkdb(8)] and [cap_mkdb(1)]. -9. Remove `/boot/kernel/linker.hints`. +8. If [sshd(8)] is running, restart the service. +9. Run [pwd_mkdb(8)] and [cap_mkdb(1)]. +10. Remove `/boot/kernel/linker.hints`. [bectl(8)]: https://man.freebsd.org/cgi/man.cgi?query=bectl&sektion=8&manpath=freebsd-release [pkgbase]: https://wiki.freebsd.org/PkgBase