diff --git a/README.md b/README.md
index 4dd6799..f17c2c2 100644
--- a/README.md
+++ b/README.md
@@ -6,42 +6,49 @@ The copy of the source tree is maintained by members of
the `_sourcezap` group, and a copy of the source tree
can be installed into `/usr/src/` by root.
-## Examples
+## CLI
-#### CLI: setup-sourcezap
+### CLI: setup-sourcezap
-`setup-sourcezap` should be run after installing
-sourcezap for the first time.
There is no harm in
-running `setup-sourcezap` multiple times:
+`setup-sourcezap` should be run after installing sourcezap for
+the first time.
There is no harm in running `setup-sourcezap`
+multiple times:
# Add the '_sourcezap' user, group and home directory
# This command requires root privileges
root@localhost# setup-sourcezap
-#### CLI: sourcezap
+### CLI: group
- # Clone the HardenedBSD source tree into /home/_sourcezap/src/
- # This command is delegated to the '_sourcezap' user
- user@localhost$ sourcezap clone
+The following commands are delegated to the `_sourcezap` user and
+restricted to members of the `_sourcezap` group. The restrictions
+are enforced by sourcezap and to a lesser extent by
+[doas(1)](https://man.openbsd.org/doas):
- # Pull updates into /home/_sourcezap/src/
- # This command is delegated to the '_sourcezap' user
- user@localhost$ sourcezap pull
+* **sourcezap clone**
+Clone the HardenedBSD ports tree into `/home/_sourcezap/src/`
- # Checkout a branch other than the default: hardened/14-stable/master
- # This command is delegated to the '_sourcezap' user
- user@localhost$ sourcezap checkout hardened/13-stable/master
+* **sourcezap pull**
+Pull updates into `/home/_sourcezap/src/`
- # Install /home/_sourcezap/src/ into /usr/src/
- # This command requires root privileges
- root@localhost# sourcezap install
+* **sourcezap checkout**
+Checkout a branch other than the default: `hardened/14-stable/master`
- # Remove the contents of /usr/src/ and /home/_sourcezap/src/
- # This command requires root privileges
- root@localhost# sourcezap rm
+* **sourcezap sh**
+Run `/bin/sh` within `/home/_sourcezap/src/`
+### CLI: superuser
-#### Environment
+The following commands are restricted to root.
+The restrictions are enforced by sourcezap:
+
+* **sourcezap rm**
+Remove the contents of `/usr/src/` and `/home/_sourcezap/src/`
+
+* **sourcezap install**
+Install `/home/_sourcezap/src/` into `/usr/src/`
+
+## Environment
* __$SOURCEZAP\_CLONEURL__
The URL of a git repository
@@ -53,15 +60,10 @@ running `setup-sourcezap` multiple times:
## Install
-#### Package
-
sourcezap is available
-[from the HardenedBSD ports tree](https://git.HardenedBSD.org/HardenedBSD/ports/-/tree/HardenedBSD/main/hardenedbsd/sourcezap).
-`pkg install sourcezap` should work too but expect slower updates.
-
-#### Git
-
-The most recent version of sourcezap can be installed via git:
+[from the HardenedBSD ports tree](https://git.HardenedBSD.org/HardenedBSD/ports/-/tree/HardenedBSD/main/hardenedbsd/sourcezap).
+`pkg install sourcezap` should work too but expect slower updates. The most
+recent version of sourcezap can be installed via git:
# Clone
user@localhost$ git clone https://git.hardenedbsd.org/0x1eef/sourcezap.git
diff --git a/bin/sourcezap b/bin/sourcezap
index ed1e935..0c105c7 100755
--- a/bin/sourcezap
+++ b/bin/sourcezap
@@ -54,6 +54,10 @@ case $1 in
require_dependency "git doas"
"${libexec}"/commands/sourcezap-checkout "${gitdir}" "${2}"
;;
+ "sh")
+ require_dependency "doas"
+ "${libexec}"/commands/sourcezap-sh "${gitdir}"
+ ;;
"rm")
"${libexec}"/commands/sourcezap-rm "${gitdir}" "${installdir}"
;;
@@ -68,7 +72,8 @@ case $1 in
printf " clone Clone the HardenedBSD source tree\n"
printf " pull Pull source tree updates\n"
printf " checkout Checkout a branch other than the default\n"
- printf " install Install the source tree into /usr/src/\n"
+ printf " sh Run /bin/sh within /home/_sourcezap/src/\n"
printf " rm Remove /usr/src/ and /home/_sourcezap/src/\n"
+ printf " install Install the source tree into /usr/src/\n"
;;
esac
diff --git a/libexec/sourcezap/commands/sourcezap-sh b/libexec/sourcezap/commands/sourcezap-sh
new file mode 100755
index 0000000..0f70d6f
--- /dev/null
+++ b/libexec/sourcezap/commands/sourcezap-sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+set -e
+
+##
+# variables
+localbase=${LOCALBASE:-$(realpath "$(dirname "$0")"/../../..)}
+libexec="${localbase}"/libexec/sourcezap
+user=_sourcezap
+gitdir="${1}"
+
+##
+# functions
+# shellcheck source=/dev/null
+. "${libexec}"/functions/print.sh
+
+##
+# main
+if [ "$(id -u)" = "0" ]; then
+ printerr "you must be a user other than root"
+ exit 1
+fi
+
+if [ ! -e "${gitdir}" ]; then
+ printerr "try 'sourcezap clone' instead"
+ exit 1
+fi
+
+if ! "${libexec}"/utils/issourcezap-member; then
+ printerr "$(id -un) is not a member of _sourcezap"
+fi
+
+cd "${gitdir}"
+doas -n \
+ -u "${user}" \
+ /bin/sh
diff --git a/man/man8/sourcezap.8 b/man/man8/sourcezap.8
index b2e5587..9f3096c 100644
--- a/man/man8/sourcezap.8
+++ b/man/man8/sourcezap.8
@@ -38,17 +38,24 @@ Checkout a branch other than the default: hardened/14-stable/master
.br
This command is delegated to the '_sourcezap' user
.Pp
-.Nm sourcezap install
+.Nm sourcezap sh
.br
-Install /home/_sourcezap/src/ into /usr/src/
+Run /bin/sh within /home/_sourcezap/src/
.br
-This command requires root privileges
+This command is delegated to the '_sourcezap' user and
+limited to members of the '_sourcezap' group
.Pp
.Nm sourcezap rm
.br
Remove the contents of /usr/src/ and /home/_sourcezap/src/
.br
This command requires root privileges
+.Pp
+.Nm sourcezap install
+.br
+Install /home/_sourcezap/src/ into /usr/src/
+.br
+This command requires root privileges
.br
.Sh ENVIRONMENT
.sp
diff --git a/share/sourcezap/RELNOTES b/share/sourcezap/RELNOTES
index 8878c5e..3c9ee8b 100644
--- a/share/sourcezap/RELNOTES
+++ b/share/sourcezap/RELNOTES
@@ -1,3 +1,8 @@
+* vNEXT
+
+** Add libexec/sourcezap/commands/sourcezap-sh
+Runs /bin/sh within /home/_sourcezap/ports as the '_sourcezap' user
+
* v1.0.0
** Add libexec/sourcezap/setup/setup-user