Initial Commit
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# =====================================================================
|
||||
# Maintenance mode: the operator unfreezing a base image to change it.
|
||||
#
|
||||
# THIS IS WHY THE AGENT CAN NEVER UNFREEZE ANYTHING
|
||||
#
|
||||
# The sandbox runs as owuivm. Base images are mode 0444 owned by
|
||||
# owuivmadm, in a directory owuivm does not own and is not in the group
|
||||
# of. So the uid that runs every session physically cannot clear a write
|
||||
# bit, and there is no verb in vmd that asks anyone else to.
|
||||
#
|
||||
# This unit is the only path that can, and it runs as owuivmadm. Starting
|
||||
# it is gated by polkit (50-owuivm.rules) on an ACTIVE LOCAL SESSION of
|
||||
# the operator - not on group membership, which a background process
|
||||
# could inherit. An agent asking for an unfreeze produces a sentence in a
|
||||
# report; a person at the machine is what produces a writable disk.
|
||||
#
|
||||
# FREEZING IS A chmod, WHICH MEANS IT IS VISIBLE
|
||||
#
|
||||
# `ls -l` says whether an image is frozen. This unit does not change it
|
||||
# in either direction: starting a machine and unfreezing its disk are
|
||||
# separate decisions, made by separate buttons, because the most useful
|
||||
# thing the operator can do is boot the image exactly as the agent will
|
||||
# get it - which must not be able to change it.
|
||||
#
|
||||
# A frozen disk therefore boots a throwaway overlay and ExecStopPost
|
||||
# throws it away; an unfrozen one boots itself and keeps what happens.
|
||||
# An image left unfrozen is caught where it matters rather than here:
|
||||
# vmd refuses to open a session on a writable base.
|
||||
# =====================================================================
|
||||
[Unit]
|
||||
Description=owuitools VM maintenance (%i base image)
|
||||
After=owuivm-nft-guard.service
|
||||
Requires=owuivm-nft-guard.service
|
||||
# Refuses to run while a session is live: one VM at a time, and two
|
||||
# processes with the same disk open is how a qcow2 gets corrupted.
|
||||
Conflicts=owuivm.service
|
||||
# Deliberately NO ConditionPathExists on the disk: starting this unit
|
||||
# with no image is how one gets installed. ExecStartPre creates a blank
|
||||
# disk and points the machine at the installation media.
|
||||
ConditionPathExists=/dev/kvm
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=owuivmadm
|
||||
Group=owuivmadm
|
||||
SupplementaryGroups=kvm
|
||||
# 0007 so the console socket qemu creates is group-writable, and the
|
||||
# directory's setgid bit makes that group the OPERATOR's - the person who has
|
||||
# to be able to connect to it.
|
||||
UMask=0007
|
||||
|
||||
Environment=OWUIVM_MODE=maint
|
||||
Environment=OWUIVM_SESSION=00000000000000000000000000000000
|
||||
Environment=OWUIVM_MEM_MB=8192
|
||||
Environment=OWUIVM_CPUS=8
|
||||
Environment=OWUIVM_DISK=/var/lib/owuivm/base/%i.qcow2
|
||||
Environment=OWUIVM_VARS=/run/owuivm/00000000000000000000000000000000/OVMF_VARS.fd
|
||||
|
||||
ExecStartPre=/usr/local/libexec/owuivm-maint-prepare %i open
|
||||
ExecStart=/usr/local/libexec/owuivm-launch
|
||||
# The power button first, then the tidying. Without this, stopping the
|
||||
# unit yanked power on the disk the operator was editing.
|
||||
ExecStop=/usr/local/libexec/owuivm-powerdown ${OWUIVM_SESSION}
|
||||
ExecStopPost=/usr/local/libexec/owuivm-maint-prepare %i close
|
||||
|
||||
# The maintenance VM gets exactly the sandbox's network policy. It is the
|
||||
# operator driving it rather than an agent, but it is the same disk that
|
||||
# every session will later run from: if something is installed here that
|
||||
# only works because this VM could reach the LAN, that breaks later, in a
|
||||
# session, where it is much harder to see.
|
||||
IPAddressDeny=127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16 100.64.0.0/10 192.0.0.0/24 224.0.0.0/4 240.0.0.0/4
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET
|
||||
# No exception needed: the console is a unix socket, so this unit opens
|
||||
# no listening TCP port at all - the same as a session.
|
||||
SocketBindDeny=any
|
||||
|
||||
DevicePolicy=closed
|
||||
DeviceAllow=/dev/kvm rw
|
||||
NoNewPrivileges=yes
|
||||
# THE SANDBOX GETS ITS OWN RESOLVER, AND THIS IS NOT COSMETIC.
|
||||
#
|
||||
# SLIRP hands every guest 10.0.2.3 as its nameserver over DHCP. That is a
|
||||
# forwarder inside the qemu process, and it relays to whatever the HOST's
|
||||
# /etc/resolv.conf says - here 127.0.0.53, systemd-resolved, which
|
||||
# forwards to the LAN's own DNS and knows every internal name in the
|
||||
# house. This unit denies 127.0.0.0/8, correctly, so that relay is dead.
|
||||
#
|
||||
# The result is a guest with working TCP to the internet and no name
|
||||
# resolution whatsoever, which presents as "no internet" and sends
|
||||
# whoever is looking at it to entirely the wrong place. It did exactly
|
||||
# that to a hand-installed image.
|
||||
#
|
||||
# Bind-mounting a resolv.conf of our own fixes it for every guest without
|
||||
# the guest being configured at all, and without the host's resolver ever
|
||||
# becoming reachable from the sandbox.
|
||||
BindReadOnlyPaths=/etc/owuivm/resolv.conf:/etc/resolv.conf
|
||||
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
PrivateTmp=yes
|
||||
# The overlay dataset holds the throwaway disk of a frozen boot. It is the same
|
||||
# dataset a session's overlay lives on, and its 40 GB quota is what
|
||||
# stops a guest that writes forever from filling the root filesystem.
|
||||
ReadWritePaths=/run/owuivm /run/owuivm-maint /var/lib/owuivm/base @POOL_ROOT@/vmrun
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
LockPersonality=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
# NOT optional, and its absence is not a smaller version of having it.
|
||||
# Without this, a filtered syscall raises SIGSYS and kills the process;
|
||||
# with it, the call returns EPERM and the caller decides what to do.
|
||||
# qemu calls seccomp(2) at startup to probe for its own sandbox - which
|
||||
# @system-service does not permit - so a maintenance boot died instantly
|
||||
# with status=31/SYS until this line was added, while a session boot,
|
||||
# which has always had it, simply got EPERM and carried on.
|
||||
SystemCallErrorNumber=EPERM
|
||||
|
||||
# MEASURED: 10G made the machine crawl, and it was not obvious why.
|
||||
#
|
||||
# The guest's RAM is a memfd with share=on (virtiofs requires it), so the
|
||||
# kernel accounts all 8 GiB of it as *shmem* in this cgroup - and with no
|
||||
# swap on this host, shmem cannot be reclaimed at all. That left about
|
||||
# 1.5 GiB for every page of ISO and disk I/O the VM does, so the cgroup
|
||||
# hit its hard limit 6,782 times in one boot (memory.events max), evicting
|
||||
# and re-reading the same installer media over and over.
|
||||
#
|
||||
# The first fix was to raise this to 14G so the page cache had room.
|
||||
# That worked - reclaim went to zero - and it was the wrong fix: it gave
|
||||
# one VM twelve gigabytes of a 58 GB host that is also running Ollama,
|
||||
# and the host started running short instead. Raising a limit to make
|
||||
# room for something that should not have been in there.
|
||||
#
|
||||
# The right fix is one line in owuivm-launch: cache=none on every drive,
|
||||
# so this VM's I/O never enters the page cache at all. What is left in
|
||||
# the cgroup is the guest's RAM and qemu itself, which is what this limit
|
||||
# was always meant to bound. 11G is 8 GiB of guest, about 1 GiB of qemu,
|
||||
# and slack.
|
||||
MemoryMax=@VM_MEMORY_MAX@
|
||||
# 800%, NOT 700%, AND THE DIFFERENCE IS NOT THEORETICAL.
|
||||
#
|
||||
# The machine has 8 vCPUs, so 800% is exactly what it can consume and
|
||||
# 700% means one of its eight can never run. Measured during a Windows
|
||||
# install: qemu sat at 697% of the 700% ceiling, with 1,792 throttle
|
||||
# events and 103 seconds of CPU actually withheld - while the operator
|
||||
# watched a progress bar and wondered whether nested virtualisation was
|
||||
# to blame. It was not; it was this line.
|
||||
#
|
||||
# The session unit keeps 700% on purpose: it runs while the main model
|
||||
# may be answering someone, and it is pinned to eight threads that
|
||||
# Ollama can also use. A maintenance boot is one person doing one thing,
|
||||
# and it should have the whole machine it was given.
|
||||
CPUQuota=@MAINT_CPU_QUOTA@
|
||||
TasksMax=512
|
||||
TimeoutStopSec=120
|
||||
KillMode=mixed
|
||||
Reference in New Issue
Block a user