Initial Commit
This commit is contained in:
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# The only thing vmd is allowed to run as root.
|
||||
#
|
||||
# vmd runs as owuivmd and needs four privileged things: start and stop
|
||||
# the VM unit, create a project dataset with a quota, and start and stop
|
||||
# virtiofsd. Granting it `systemctl` or `zfs` outright would be granting
|
||||
# it every unit and every dataset on this host, so it gets this instead:
|
||||
# a fixed verb list, one optional argument, and a pattern that argument
|
||||
# must match before it is used for anything.
|
||||
#
|
||||
# The sudoers entry names this path and nothing else. Everything here is
|
||||
# an array-form command - there is no `eval`, no unquoted expansion and
|
||||
# no shell metacharacter anywhere a project name can reach.
|
||||
#
|
||||
# THE PATTERN IS DUPLICATED ON PURPOSE
|
||||
#
|
||||
# It also lives in vm/vmd/project.py, and tests/test_vm_tasks.py asserts
|
||||
# the two are identical. Importing it would mean root code reading a
|
||||
# module out of a tree that vmd's own user can write to, which is exactly
|
||||
# backwards. Two copies and a test that they agree is the cheaper risk.
|
||||
# =====================================================================
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_RE='^[a-z0-9][a-z0-9-]{0,29}[a-z0-9]$'
|
||||
PROJECTS_DATASET="Pool/vmprojects"
|
||||
# The pool root is filled in by install.sh. This runs under sudo, where
|
||||
# the environment is not preserved, so it cannot read a setting the way
|
||||
# vmd does - the value has to be baked in when it is installed.
|
||||
PROJECTS_ROOT="@POOL_ROOT@/vmprojects"
|
||||
QUOTA="10G"
|
||||
RESERVED=" base iso state run git gitea owuivm "
|
||||
|
||||
die() { printf 'owuivm-ctl: %s\n' "$*" >&2; exit 64; }
|
||||
|
||||
check_name() {
|
||||
local n="${1:-}"
|
||||
[[ "$n" =~ $PROJECT_RE ]] || die "'$n' is not a valid project name"
|
||||
[[ "$RESERVED" == *" $n "* ]] && die "'$n' is reserved"
|
||||
return 0
|
||||
}
|
||||
|
||||
verb="${1:-}"; shift || true
|
||||
|
||||
case "$verb" in
|
||||
vm-start) exec /usr/bin/systemctl start owuivm.service ;;
|
||||
vm-stop) exec /usr/bin/systemctl stop owuivm.service ;;
|
||||
vm-status) exec /usr/bin/systemctl is-active owuivm.service ;;
|
||||
# `reset-failed` on its own, so a unit that died once cannot wedge
|
||||
# every later session behind "start request repeated too quickly".
|
||||
vm-reset) exec /usr/bin/systemctl reset-failed owuivm.service ;;
|
||||
|
||||
project-ensure)
|
||||
name="${1:-}"; check_name "$name"
|
||||
if ! /usr/sbin/zfs list -H -o name -- "$PROJECTS_DATASET/$name" >/dev/null 2>&1; then
|
||||
# Via systemd-run, not directly. `zfs create` MOUNTS the new
|
||||
# dataset, and mount() is not in @system-service - which is the
|
||||
# syscall filter vmd runs under and which every child of vmd
|
||||
# inherits, sudo included. Run directly it dies with SIGSYS, and
|
||||
# "Bad system call" from zfs is a genuinely confusing way to learn
|
||||
# that. systemd-run spawns from PID 1 instead, so this lands in its
|
||||
# own unit outside vmd's cgroup, and vmd's own filter stays tight
|
||||
# rather than being widened to @mount for one call.
|
||||
/usr/bin/systemd-run --quiet --wait --collect \
|
||||
--unit="owuivm-zfs-create" --property=Type=oneshot \
|
||||
-- /usr/sbin/zfs create -o "quota=$QUOTA" -- "$PROJECTS_DATASET/$name"
|
||||
fi
|
||||
# setgid so everything the guest writes stays in a group vmd and
|
||||
# virtiofsd can both still read afterwards.
|
||||
chown owuivmd:owuivm -- "$PROJECTS_ROOT/$name"
|
||||
chmod 2770 -- "$PROJECTS_ROOT/$name"
|
||||
printf '%s\n' "$PROJECTS_ROOT/$name"
|
||||
;;
|
||||
|
||||
project-usage)
|
||||
name="${1:-}"; check_name "$name"
|
||||
exec /usr/sbin/zfs get -Hp -o value used,quota -- "$PROJECTS_DATASET/$name"
|
||||
;;
|
||||
|
||||
vfs-start)
|
||||
name="${1:-}"; check_name "$name"
|
||||
session="${2:-}"
|
||||
[[ "$session" =~ ^[0-9a-f]{32}$ ]] || die "session id is not 32 hex characters"
|
||||
# Type=exec, NOT notify: virtiofsd never sends sd_notify(READY=1), so
|
||||
# systemd waits the full start timeout and then kills a daemon that
|
||||
# was working perfectly - the log says "Waiting for vhost-user socket
|
||||
# connection..." right up until it is terminated. Readiness here is
|
||||
# the socket file appearing, which vmd waits for itself.
|
||||
exec /usr/bin/systemd-run \
|
||||
--unit="owuivm-virtiofsd" --collect --property=Type=exec \
|
||||
--property=Restart=no --property=NoNewPrivileges=yes \
|
||||
--property=ProtectSystem=strict \
|
||||
--property="ReadWritePaths=/run/owuivm @POOL_ROOT@/vmprojects/$name" \
|
||||
--property=PrivateNetwork=yes \
|
||||
-- /usr/libexec/virtiofsd \
|
||||
--socket-path="/run/owuivm/$session/vfs.sock" \
|
||||
--socket-group=owuivm \
|
||||
--shared-dir="$PROJECTS_ROOT/$name" \
|
||||
--sandbox=namespace \
|
||||
--modcaps=-mknod:-setfcap
|
||||
# No --xattr: it is a bare flag in virtiofsd 1.10 (there is no
|
||||
# --xattr=false), and extended attributes are off unless it is given.
|
||||
# Leaving them off is what is wanted - an xattr is another thing the
|
||||
# guest can set on a file that then lives on the host's array.
|
||||
;;
|
||||
|
||||
vfs-stop)
|
||||
/usr/bin/systemctl stop owuivm-virtiofsd.service >/dev/null 2>&1 || true
|
||||
/usr/bin/systemctl reset-failed owuivm-virtiofsd.service >/dev/null 2>&1 || true
|
||||
;;
|
||||
|
||||
*) die "unknown verb '${verb}'" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user