mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-28 11:56:03 +00:00
docs: Improve commenting (#1349)
This commit is contained in:
@@ -15,12 +15,16 @@ file="/run/shm/dsm.url"
|
||||
address="/run/shm/qemu.ip"
|
||||
gateway="/run/shm/qemu.gw"
|
||||
|
||||
# dsm.url is written only after the guest agent reports both the DSM
|
||||
# address and its configured HTTP port.
|
||||
[ ! -s "$file" ] && echo "DSM has not enabled networking yet..." && exit 0
|
||||
|
||||
location=$(<"$file")
|
||||
|
||||
if ! curl -m 20 -ILfSs "http://$location/" > /dev/null; then
|
||||
|
||||
# In DHCP mode the firewall must allow the container address; with port
|
||||
# forwarding it must allow the internal gateway used to reach the guest.
|
||||
if enabled "$DHCP"; then
|
||||
ip=$(<"$address")
|
||||
echo "Failed to reach DSM at http://$location"
|
||||
|
||||
@@ -6,6 +6,8 @@ DEV_OPTS=""
|
||||
|
||||
configureProcessor() {
|
||||
|
||||
# Expose one thread per core in a single socket; DSM licensing and topology
|
||||
# reporting are more predictable with this fixed layout.
|
||||
CPU_OPTS="-cpu $CPU_FLAGS"
|
||||
CPU_OPTS+=" -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1"
|
||||
|
||||
@@ -29,6 +31,8 @@ configureMonitor() {
|
||||
|
||||
configureMachine() {
|
||||
|
||||
# Disable firmware and chipset features that Virtual DSM does not use and
|
||||
# that can introduce extra devices or timing differences.
|
||||
MAC_OPTS="-machine type=$MACHINE,smm=off,usb=off"
|
||||
MAC_OPTS+=",vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}"
|
||||
|
||||
@@ -47,6 +51,8 @@ configureVirtioDevices() {
|
||||
buildArguments() {
|
||||
|
||||
ARGS="$DEF_OPTS $CPU_OPTS $RAM_OPTS $MAC_OPTS $DISPLAY_OPTS $MON_OPTS $SERIAL_OPTS $NET_OPTS $DISK_OPTS $DEV_OPTS $ARGUMENTS"
|
||||
# Collapse whitespace after optional argument groups are assembled so empty
|
||||
# features cannot leave malformed spacing in the final QEMU command.
|
||||
ARGS=$(echo "$ARGS" | sed 's/\t/ /g' | tr -s ' ')
|
||||
|
||||
return 0
|
||||
|
||||
+26
@@ -25,6 +25,8 @@ DISK_ROTATION=$(strip "$DISK_ROTATION")
|
||||
BOOT="$STORAGE/$BASE.boot.img"
|
||||
SYSTEM="$STORAGE/$BASE.system.img"
|
||||
|
||||
# The boot and system images are installation artifacts, not optional data
|
||||
# disks, and must exist before any user storage is attached.
|
||||
[ ! -s "$BOOT" ] && error "Virtual DSM boot-image does not exist ($BOOT)" && exit 81
|
||||
[ ! -s "$SYSTEM" ] && error "Virtual DSM system-image does not exist ($SYSTEM)" && exit 82
|
||||
|
||||
@@ -127,6 +129,8 @@ allocateRaw() {
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Prefer real allocation, retry with zero-range allocation where supported,
|
||||
# and fall back to a sparse file when the host filesystem rejects both.
|
||||
fallocate -l "$dataSize" "$diskFile" &>/dev/null && return 0
|
||||
fallocate -l -x "$dataSize" "$diskFile" && return 0
|
||||
truncate -s "$dataSize" "$diskFile" || return 1
|
||||
@@ -159,6 +163,8 @@ normalizeSize() {
|
||||
local free dataSize
|
||||
local spare=1073741824
|
||||
|
||||
# Dynamic sizes are resolved once from current free space. max reserves one
|
||||
# GiB for host metadata and container activity; half uses half the space.
|
||||
if [[ "${diskSpace,,}" == "max" || "${diskSpace,,}" == "half" ]]; then
|
||||
|
||||
free=$(df --output=avail -B 1 "$dir" | tail -n 1)
|
||||
@@ -421,6 +427,8 @@ convertDisk() {
|
||||
if [[ "$destinationFmt" == "raw" ]]; then
|
||||
if ! disabled "$ALLOCATE"; then
|
||||
|
||||
# qemu-img may leave converted raw output sparse despite requested
|
||||
# preallocation, so allocate its final length explicitly afterward.
|
||||
# Work around qemu-img bug
|
||||
if ! currentSize=$(stat -c%s "$tmpFile"); then
|
||||
error "Failed to determine converted image size: $tmpFile"
|
||||
@@ -435,6 +443,8 @@ convertDisk() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Publish the converted image before deleting the original so a failed
|
||||
# conversion or rename never destroys the only usable disk.
|
||||
if ! mv "$tmpFile" "$destinationFile"; then
|
||||
error "Failed to move converted $diskDesc image to $destinationFile."
|
||||
exit 79
|
||||
@@ -479,6 +489,8 @@ checkFS () {
|
||||
warn "the filesystem of $base is FUSE, this extra layer will negatively affect performance!"
|
||||
fi
|
||||
|
||||
# Filesystems without O_DIRECT support require threaded I/O and writeback
|
||||
# caching; native AIO with cache=none would fail at runtime.
|
||||
if ! supportsDirect "$fs"; then
|
||||
warn "the filesystem of $base is $fs, which does not support O_DIRECT mode, adjusting settings..."
|
||||
fi
|
||||
@@ -508,6 +520,8 @@ createDevice () {
|
||||
local diskSectors="$9"
|
||||
local bus="${PCI_BUS:-pcie.0}"
|
||||
|
||||
# q35 uses pcie.0, while legacy pc/i440fx machines expose their devices on
|
||||
# pci.0 unless the caller supplied an explicit bus.
|
||||
[[ -z "${PCI_BUS:-}" && ( "${MACHINE,,}" == pc || "${MACHINE,,}" == pc-i440fx* ) ]] && bus="pci.0"
|
||||
|
||||
local options=""
|
||||
@@ -559,6 +573,8 @@ finishDisks () {
|
||||
|
||||
case "${DISK_TYPE,,}" in
|
||||
"blk" | "scsi" | "virtio-blk" | "virtio-scsi" )
|
||||
# VirtIO block and SCSI devices share one dedicated I/O thread, which
|
||||
# must be declared exactly once regardless of disk count.
|
||||
[[ "$DISK_OPTS" != *" -object iothread,id=io2"* ]] && DISK_OPTS+=" -object iothread,id=io2" ;;
|
||||
esac
|
||||
|
||||
@@ -613,6 +629,8 @@ addDisk () {
|
||||
|
||||
previousExt=$(fmt2ext "$previousFmt")
|
||||
|
||||
# Treat a disk in the other supported format as the same logical disk and
|
||||
# convert it automatically instead of creating an empty replacement.
|
||||
if [ -f "$diskBase.$previousExt" ] &&
|
||||
[ -s "$diskBase.$previousExt" ]; then
|
||||
convertDisk "$diskBase.$previousExt" "$previousFmt" "$diskFile" "$diskFmt" "$diskBase" "$diskDesc" "$fs" || exit $?
|
||||
@@ -645,6 +663,8 @@ addDisk () {
|
||||
|
||||
fi
|
||||
|
||||
# Sparse disks can promise more guest capacity than the host can currently
|
||||
# satisfy, so report the future shortfall without blocking startup.
|
||||
if [ -f "$diskFile" ] && disabled "$ALLOCATE"; then
|
||||
|
||||
currentSize=$(getSize "$diskFile") || exit 73
|
||||
@@ -699,6 +719,8 @@ addDevice () {
|
||||
[ -z "$diskDev" ] && return 0
|
||||
[ ! -b "$diskDev" ] && error "Device $diskDev cannot be found! Please add it to the 'devices' section of your compose file." && exit 55
|
||||
|
||||
# DSM may reject whole-disk passthrough when QEMU is given explicit sector
|
||||
# geometry; partitions need it to preserve non-512-byte host geometry.
|
||||
# Only detect and apply sector sizes for partitions, not whole disks.
|
||||
# Whole disk passthrough with explicit sector sizes causes DSM not to recognize the disk.
|
||||
if [[ "$devType" == "part" ]]; then
|
||||
@@ -793,6 +815,8 @@ else
|
||||
DISK_ALLOC="preallocation=falloc"
|
||||
fi
|
||||
|
||||
# Reserve the first two boot indexes and PCI addresses for the managed boot
|
||||
# and system images; user disks begin at index 3.
|
||||
DISK_OPTS+=$(createDevice "$BOOT" "$DISK_TYPE" "1" "0xa" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
|
||||
DISK_OPTS+=$(createDevice "$SYSTEM" "$DISK_TYPE" "2" "0xb" "raw" "$DISK_IO" "$DISK_CACHE" "" "")
|
||||
|
||||
@@ -832,6 +856,8 @@ DISK_DEVICES=( "$DEVICE" "$DEVICE2" "$DEVICE3" "$DEVICE4" )
|
||||
DISK_INDEXES=( "3" "4" "5" "6" )
|
||||
DISK_ADDRESSES=( "0xc" "0xd" "0xe" "0xf" )
|
||||
|
||||
# A passed-through block device takes precedence over the image-file slot
|
||||
# with the same number.
|
||||
for i in "${!DISK_FILES[@]}"; do
|
||||
|
||||
if [ -n "${DISK_DEVICES[i]}" ]; then
|
||||
|
||||
@@ -17,8 +17,12 @@ RENDERNODE=$(strip "$RENDERNODE")
|
||||
|
||||
CPU_VENDOR=$(lscpu | awk '/Vendor ID/{print $3}')
|
||||
|
||||
# The accelerated Intel render-node path is restricted to x86 Intel hosts;
|
||||
# other platforms retain the normal QEMU display backend.
|
||||
if ! enabled "$GPU" || isAmdCpu || [[ "$ARCH" != "amd64" ]]; then
|
||||
|
||||
# A disabled frontend also removes the emulated VGA device to keep the guest
|
||||
# hardware layout headless.
|
||||
[[ "${DISPLAY,,}" == "none" ]] && VGA="none"
|
||||
|
||||
if enabled "$LOSSY" && [[ "${DISPLAY,,}" == vnc=* ]]; then
|
||||
@@ -40,6 +44,8 @@ DISPLAY_OPTS+=" -vga $VGA"
|
||||
[ ! -d /dev/dri ] && mkdir -m 755 /dev/dri
|
||||
|
||||
# Extract the card number from the render node
|
||||
# Linux renderD128 corresponds to card0; derive both device minors because
|
||||
# container device bindings may expose only the render node.
|
||||
CARD_NUMBER=$(echo "$RENDERNODE" | grep -oP '(?<=renderD)\d+')
|
||||
CARD_DEVICE="/dev/dri/card$((CARD_NUMBER - 128))"
|
||||
|
||||
@@ -59,6 +65,8 @@ if [ ! -c "$RENDERNODE" ] || [ ! -r "$RENDERNODE" ] || [ ! -w "$RENDERNODE" ]; t
|
||||
warn "render device '${RENDERNODE}' is unavailable or inaccessible."
|
||||
fi
|
||||
|
||||
# Install acceleration packages lazily so non-GPU deployments keep the base
|
||||
# image small and do not require OpenGL modules.
|
||||
addPackage "xserver-xorg-video-intel" "Intel GPU drivers"
|
||||
addPackage "qemu-system-modules-opengl" "OpenGL module"
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ set -Eeuo pipefail
|
||||
|
||||
: "${URL:=""}" # URL of the PAT file to be downloaded.
|
||||
|
||||
# Persist the exact PAT base name so future starts reopen the matching boot,
|
||||
# system, and cached installation files.
|
||||
if [ -f "$STORAGE/dsm.ver" ]; then
|
||||
BASE=$(<"$STORAGE/dsm.ver")
|
||||
BASE="${BASE//[![:print:]]/}"
|
||||
@@ -16,6 +18,8 @@ FN="boot.pat"
|
||||
DIR=$(find / -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||
[ ! -d "$DIR" ] && DIR=$(find "$STORAGE" -maxdepth 1 -type d -iname "$FN" -print -quit)
|
||||
|
||||
# A boot.pat directory bind represents already extracted boot and system
|
||||
# images and therefore takes precedence over PAT file or URL discovery.
|
||||
if [ -d "$DIR" ]; then
|
||||
BASE="DSM_VirtualDSM" && URL="file://$DIR"
|
||||
if [[ ! -s "$STORAGE/$BASE.boot.img" || ! -s "$STORAGE/$BASE.system.img" ]]; then
|
||||
@@ -29,6 +33,8 @@ FILE=$(find / -maxdepth 1 -type f -iname "$FN" -print -quit)
|
||||
|
||||
URL=$(strip "$URL")
|
||||
|
||||
# Derive a filesystem-safe identity from the URL only when no local boot.pat
|
||||
# source was supplied; preserve an existing system image identity if present.
|
||||
if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||||
BASE=$(basename "$URL" .pat)
|
||||
if [ ! -s "$STORAGE/$BASE.system.img" ]; then
|
||||
@@ -42,6 +48,8 @@ if [ -n "$URL" ] && [ ! -s "$FILE" ] && [ ! -d "$DIR" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# A complete matching image pair is the installation marker; the cached PAT
|
||||
# itself is optional after installation.
|
||||
if [[ -s "$STORAGE/$BASE.boot.img" && -s "$STORAGE/$BASE.system.img" ]]; then
|
||||
return 0 # Previous installation found
|
||||
fi
|
||||
@@ -55,6 +63,8 @@ DL_GLOBAL="https://global.synologydownload.com/download/DSM"
|
||||
[[ "${URL,,}" == *"cndl.synology"* ]] && DL="$DL_CHINA"
|
||||
[[ "${URL,,}" == *"global.synology"* ]] && DL="$DL_GLOBAL"
|
||||
|
||||
# Honor an explicitly selected Synology mirror first, otherwise choose the
|
||||
# China or global endpoint from the detected country.
|
||||
if [ -z "$DL" ]; then
|
||||
[ -z "$COUNTRY" ] && setCountry
|
||||
[ -z "$COUNTRY" ] && info "Warning: could not detect country to select mirror!"
|
||||
@@ -98,6 +108,8 @@ if [[ "${FS,,}" == "fat"* || "${FS,,}" == "vfat"* || "${FS,,}" == "msdos"* ]]; t
|
||||
error "Unable to install on $FS filesystems, please use a different filesystem for /storage." && exit 61
|
||||
fi
|
||||
|
||||
# Extract beside storage on Unix filesystems to avoid container-space limits;
|
||||
# use /tmp for filesystems that cannot safely host the installer workspace.
|
||||
if [[ "${FS,,}" != "exfat"* && "${FS,,}" != "ntfs"* && "${FS,,}" != "unknown"* ]]; then
|
||||
TMP="$STORAGE/tmp"
|
||||
rm -rf "$TMP"
|
||||
@@ -206,6 +218,8 @@ fi
|
||||
|
||||
SIZE=$(stat -c%s "$PAT")
|
||||
|
||||
# Full Virtual DSM PAT files are substantially larger than update packs;
|
||||
# reject undersized inputs before attempting destructive image preparation.
|
||||
if ((SIZE<250000000)); then
|
||||
error "The specified PAT file is probably an update pack as it's too small." && exit 62
|
||||
fi
|
||||
@@ -213,6 +227,8 @@ fi
|
||||
MSG="Extracting installation image..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
# Newer PAT files are normal tar archives; older encrypted/proprietary forms
|
||||
# require the bundled extractor as a compatibility fallback.
|
||||
if { tar tf "$PAT"; } >/dev/null 2>&1; then
|
||||
|
||||
tar xpf "$PAT" -C "$TMP/."
|
||||
@@ -231,6 +247,8 @@ fi
|
||||
MSG="Preparing system partition..."
|
||||
info "Install: $MSG" && html "$MSG"
|
||||
|
||||
# The PAT boot archive becomes the persistent QEMU boot disk after its
|
||||
# companion system partition has been assembled.
|
||||
BOOT=$(find "$TMP" -name "*.bin.zip" -print -quit)
|
||||
[ -z "$BOOT" ] && error "The PAT file contains no boot image." && exit 67
|
||||
[ ! -s "$BOOT" ] && error "The PAT boot image archive is empty." && exit 67
|
||||
@@ -273,6 +291,8 @@ if ! fallocate -l "$SYSTEM_SIZE" "$SYSTEM" &>/dev/null; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Recreate Synology's expected DOS partition layout inside the fixed 10 GiB
|
||||
# system image before populating the ext4 root partition.
|
||||
PART="$TMP/partition.fdisk"
|
||||
|
||||
{
|
||||
@@ -320,6 +340,8 @@ OFFSET="1048576" # 2048 * 512
|
||||
NUMBLOCKS="2097152" # (16777216 * 512) / 4096
|
||||
MSG="Installing system partition..."
|
||||
|
||||
# Build the ext4 filesystem directly from the extracted tree under fakeroot,
|
||||
# preserving archive ownership without mounting a loop device.
|
||||
fakeroot -- bash -c "set -Eeu;\
|
||||
[ -s $HDP.txz ] && tar xpfJ $HDP.txz --absolute-names -C $MOUNT/;\
|
||||
[ -s $IDB.txz ] && tar xpfJ $IDB.txz --absolute-names -C $INDEX_DB/;\
|
||||
@@ -331,6 +353,8 @@ rm -rf "$MOUNT"
|
||||
echo "$BASE" > "$STORAGE/dsm.ver"
|
||||
setOwner "$STORAGE/dsm.ver" || warn "failed to set the owner for \"$STORAGE/dsm.ver\" !"
|
||||
|
||||
# Do not keep a second copy when the source PAT already lives in storage;
|
||||
# downloaded or externally mounted sources are cached for later reuse.
|
||||
if [[ "$URL" == "file://$STORAGE/$BASE.pat" ]]; then
|
||||
rm -f "$PAT"
|
||||
else
|
||||
|
||||
@@ -21,6 +21,8 @@ checkConfiguredMemory() {
|
||||
|
||||
if (( (wanted + RAM_SPARE) > RAM_AVAIL )); then
|
||||
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $avail_mem of free memory available,"
|
||||
# ZFS ARC can release memory under pressure, so current free-memory checks
|
||||
# are advisory rather than a reason to reduce the requested guest RAM.
|
||||
if [[ "${FS,,}" == "zfs" ]]; then
|
||||
info "$msg but since ZFS is active this will be ignored."
|
||||
else
|
||||
@@ -48,6 +50,8 @@ configureHalfMemory() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# half uses half of currently available memory only when that still leaves
|
||||
# the host reserve; otherwise it falls through to the max calculation.
|
||||
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
|
||||
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
|
||||
RAM_SIZE="${wanted}M"
|
||||
@@ -66,6 +70,8 @@ configureMaxMemory() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# max normally leaves multiple reserve units for the container and host,
|
||||
# but scales that margin down on memory-constrained systems.
|
||||
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
|
||||
|
||||
wanted=$(( RAM_AVAIL / 2 ))
|
||||
|
||||
@@ -371,6 +371,8 @@ natGuestIP() {
|
||||
local start="30"
|
||||
fi
|
||||
|
||||
# Scan adjacent 172.30/31 through 172.254 subnets to avoid Docker routes
|
||||
# while retaining the original third octet and guest host number.
|
||||
for (( second=start; second<=254; second++ )); do
|
||||
guest=$(guestIP "172.$second.$third.0" 2)
|
||||
subnet=$(networkCIDR "$guest") || return 1
|
||||
@@ -606,6 +608,8 @@ getHostPorts() {
|
||||
|
||||
getUserPorts() {
|
||||
|
||||
# User-mode networking forwards DSM management and SSH ports by default;
|
||||
# internal container reservations and HOST_PORTS are removed below.
|
||||
local defaults="22/tcp,5000/tcp,5001/tcp"
|
||||
local list="$defaults,${USER_PORTS// /},"
|
||||
|
||||
@@ -2241,6 +2245,8 @@ else
|
||||
if ! configureNAT; then
|
||||
|
||||
closeInterfaces
|
||||
# NAT setup failure is recoverable: tear down partial interfaces and
|
||||
# continue with the default user-mode backend.
|
||||
NETWORK="user"
|
||||
|
||||
if ! enabled "$ROOTLESS" || enabled "$DEBUG"; then
|
||||
@@ -2288,6 +2294,8 @@ else
|
||||
|
||||
fi
|
||||
|
||||
# Suppress the adapter option ROM because firmware network boot is unused and
|
||||
# would otherwise alter boot order and startup timing.
|
||||
NET_OPTS+=" -device $ADAPTER,id=net0,netdev=hostnet0,romfile=,mac=$MAC"
|
||||
|
||||
if [[ "$GUEST_MTU" != "0" && "$GUEST_MTU" != "1500" ]]; then
|
||||
@@ -2298,6 +2306,8 @@ if [[ "$GUEST_MTU" != "0" && "$GUEST_MTU" != "1500" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Publish the container address and detected driver for the healthcheck and
|
||||
# post-boot login-message helper.
|
||||
if ! echo "$UPLINK" > "$QEMU_DIR"/qemu.ip; then
|
||||
error "Failed to write QEMU IP file!"
|
||||
exit 24
|
||||
|
||||
@@ -67,6 +67,8 @@ displayReason() {
|
||||
|
||||
readQemuPid() {
|
||||
|
||||
# Interactive startup uses a wrapper-created PID file before QEMU writes its
|
||||
# own pidfile, so accept either during startup and shutdown races.
|
||||
readPidFile "$1" "$QEMU_START_PID" && return 0
|
||||
readPidFile "$1" "$QEMU_PID"
|
||||
}
|
||||
@@ -184,6 +186,8 @@ startQemu() {
|
||||
|
||||
rm -f -- "$QEMU_START_PID"
|
||||
|
||||
# Launch QEMU in a separate session while recording the real child PID;
|
||||
# setsid's wrapper PID is not suitable for guest shutdown or forced cleanup.
|
||||
(
|
||||
trap '' INT QUIT
|
||||
|
||||
@@ -238,6 +242,8 @@ sendGuestShutdown() {
|
||||
local pid="$1"
|
||||
local response
|
||||
|
||||
# Virtual DSM ignores ACPI powerdown, so graceful shutdown must go through
|
||||
# the qemu-host guest API exposed on the Unix socket.
|
||||
# Don't send the powerdown signal because vDSM ignores ACPI signals
|
||||
# nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
|
||||
|
||||
@@ -265,6 +271,8 @@ sendGuestShutdown() {
|
||||
|
||||
normalizeTimeout() {
|
||||
|
||||
# Divide the remaining timeout into guest wait, SIGTERM grace, and final
|
||||
# cleanup instead of allowing the API call to consume the entire budget.
|
||||
local term_grace=3 # seconds before loop ends to send SIGTERM
|
||||
local cleanup_grace=3 # seconds reserved after the loop for cleanup
|
||||
|
||||
@@ -307,6 +315,8 @@ waitForShutdown() {
|
||||
# Stop waiting if the process has exited
|
||||
isAlive "$pid" || break
|
||||
|
||||
# The process state is authoritative, but disappearance of both pidfiles
|
||||
# also ends the wait when a wrapper exits before process reaping completes.
|
||||
# Workaround for stale/zombie QEMU pid file
|
||||
[ ! -s "$QEMU_START_PID" ] && [ ! -s "$QEMU_PID" ] && break
|
||||
|
||||
@@ -336,6 +346,8 @@ graceful_shutdown() {
|
||||
|
||||
if [ -f "$QEMU_END" ]; then
|
||||
|
||||
# A second Ctrl-C is the explicit user request to skip the remaining
|
||||
# graceful-shutdown wait and proceed to forced cleanup.
|
||||
if (( code == 130 && SHUTDOWN_SIGNAL == code )); then
|
||||
SHUTDOWN_SKIP=1
|
||||
echo && info "Received SIGINT again, forcing shutdown..."
|
||||
|
||||
@@ -37,6 +37,8 @@ exitIfShuttingDown() {
|
||||
|
||||
queryGuest() {
|
||||
|
||||
# Query DSM through the qemu-host sidecar rather than the guest network,
|
||||
# which may not be configured yet.
|
||||
{ json=$(curl --unix-socket "$socket" -m 20 -sk "$url"); local rc=$?; } || :
|
||||
|
||||
exitIfShuttingDown
|
||||
@@ -116,6 +118,8 @@ writeDsmLocation() {
|
||||
|
||||
pollGuestLocation() {
|
||||
|
||||
# Keep polling until the guest reports a usable address, but stop promptly
|
||||
# when container shutdown begins.
|
||||
while [ ! -s "$file" ]; do
|
||||
|
||||
# Check if not shutting down
|
||||
@@ -183,6 +187,8 @@ buildStaticMessage() {
|
||||
ip=$(<"$address")
|
||||
local port="${location##*:}"
|
||||
|
||||
# NAT and user-mode networking are reached through a forwarded host port;
|
||||
# macvlan exposes DSM directly on the container-facing LAN address.
|
||||
if [[ "${nic,,}" != "macvlan" ]]; then
|
||||
msg="port $port"
|
||||
else
|
||||
|
||||
+10
@@ -66,6 +66,8 @@ trimSpaces() {
|
||||
|
||||
removeCpuArgument() {
|
||||
|
||||
# CPU configuration has dedicated variables. Remove raw -cpu arguments so
|
||||
# option ordering cannot silently override the validated model and flags.
|
||||
local args=" ${ARGUMENTS:-} "
|
||||
|
||||
while [[ "$args" =~ [[:space:]]-cpu([[:space:]][^[:space:]]+|=[^[:space:]]+)? ]]; do
|
||||
@@ -94,6 +96,8 @@ configureKvmCpuModel() {
|
||||
|
||||
appendKvmInvtscFeature() {
|
||||
|
||||
# invtsc is safe only when the active accelerator can scale the host TSC;
|
||||
# AMD and Intel expose that capability through different host flags.
|
||||
if hasFlag "svm"; then
|
||||
|
||||
# AMD processor
|
||||
@@ -131,6 +135,8 @@ configureTcgCpuModel() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# TCG uses the broad max model on native x86, but qemu64 is the compatible
|
||||
# cross-architecture fallback.
|
||||
if [[ "$ARCH" == "amd64" ]]; then
|
||||
CPU_MODEL="max"
|
||||
CPU_FEATURES+=",migratable=no"
|
||||
@@ -158,6 +164,8 @@ configureTcg() {
|
||||
|
||||
composeCpuFlags() {
|
||||
|
||||
# Compose one -cpu value in precedence order: model, required features,
|
||||
# then user-provided overrides.
|
||||
CPU_FLAGS="${CPU_MODEL}${CPU_FEATURES:+,$CPU_FEATURES}${CPU_FLAGS:+,$CPU_FLAGS}"
|
||||
|
||||
return 0
|
||||
@@ -170,6 +178,8 @@ configureHostCpuName() {
|
||||
fi
|
||||
|
||||
if [ -n "$HOST_CPU" ]; then
|
||||
# qemu-host expects a comma-separated CPU description with empty family
|
||||
# and suffix fields, not QEMU's -cpu syntax.
|
||||
HOST_CPU="${HOST_CPU%%,*},,"
|
||||
else
|
||||
HOST_CPU="QEMU, Virtual CPU,"
|
||||
|
||||
@@ -22,6 +22,8 @@ writeInfo() {
|
||||
|
||||
local content="$1"
|
||||
|
||||
# Replace the web status atomically so websocket readers never observe a
|
||||
# partially written HTML fragment.
|
||||
if ! printf '%s\n' "$content" > "$info_tmp"; then
|
||||
rm -f -- "$info_tmp"
|
||||
return 1
|
||||
@@ -220,6 +222,8 @@ fi
|
||||
|
||||
trap finishProgress EXIT
|
||||
trap 'exit 0' HUP INT QUIT
|
||||
# SIGTERM requests one final measurement and web update rather than
|
||||
# terminating between progress samples.
|
||||
trap stopProgress TERM
|
||||
|
||||
if [[ "$body" == *"..." ]]; then
|
||||
@@ -232,6 +236,8 @@ while true; do
|
||||
bytes=$(getBytes "$path" "$mode")
|
||||
effective_total="$total"
|
||||
|
||||
# An external downloader may provide authoritative completed and total byte
|
||||
# counters; use them instead of filesystem size when available.
|
||||
if [ -n "$status_file" ] && status=$(getStatus "$status_file"); then
|
||||
read -r status_bytes status_total <<< "$status"
|
||||
bytes="$status_bytes"
|
||||
@@ -268,6 +274,8 @@ while true; do
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Floor the percentage rather than rounding so displayed completion
|
||||
# never gets ahead of bytes actually written.
|
||||
# Truncate to one decimal so progress is never reported early.
|
||||
progress=$((bytes * 1000 / effective_total))
|
||||
(( progress > 1000 )) && progress=1000
|
||||
|
||||
@@ -43,6 +43,8 @@ detectRootless() {
|
||||
|
||||
local uid_map
|
||||
|
||||
# A full identity UID map indicates a rootful container; any remapping is
|
||||
# treated as rootless even though the process itself runs as UID 0.
|
||||
uid_map=$(awk '{$1=$1; print}' /proc/self/uid_map 2>/dev/null || true)
|
||||
|
||||
if [[ "$uid_map" == "0 0 4294967295" ]]; then
|
||||
@@ -66,6 +68,8 @@ checkPrivileged() {
|
||||
last_cap=$(cat /proc/sys/kernel/cap_last_cap)
|
||||
|
||||
# Calculate the maximum capability value
|
||||
# Compare the bounding set with every capability supported by this kernel;
|
||||
# checking only a few known capabilities would misclassify newer kernels.
|
||||
local max_cap=$(((1 << (last_cap + 1)) - 1))
|
||||
|
||||
if [ "$cap_bnd" -eq "$max_cap" ]; then
|
||||
@@ -96,6 +100,8 @@ checkStorage() {
|
||||
|
||||
# Check system
|
||||
|
||||
# Runtime sockets, pidfiles, and status files live in shared memory so they
|
||||
# are fast, ephemeral, and visible to helper processes.
|
||||
QEMU_DIR="/run/shm"
|
||||
|
||||
if [ ! -d "/dev/shm" ]; then
|
||||
@@ -141,6 +147,8 @@ checkFilesystem() {
|
||||
finiteMemoryLimit() {
|
||||
|
||||
local limit="$1"
|
||||
# cgroup v1 commonly reports an enormous sentinel instead of an unlimited
|
||||
# marker; compare as decimal text to avoid shell integer overflow.
|
||||
local sentinel="4611686018427387904"
|
||||
local i
|
||||
|
||||
@@ -179,6 +187,8 @@ getMemoryInfo() {
|
||||
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
|
||||
fi
|
||||
|
||||
# Use the tighter of host availability and the container's remaining cgroup
|
||||
# allowance so RAM sizing works in both limited and unlimited containers.
|
||||
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
|
||||
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
|
||||
|
||||
@@ -204,6 +214,8 @@ normalizeRamSize() {
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
||||
|
||||
# Preserve the historical shorthand: small bare numbers mean GiB, while
|
||||
# values of 130 or more are interpreted as MiB.
|
||||
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
|
||||
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
||||
fi
|
||||
@@ -231,6 +243,8 @@ checkKvm() {
|
||||
if disabled "$KVM"; then
|
||||
warn "KVM acceleration is disabled, this will cause the machine to run about 10 times slower!"
|
||||
else
|
||||
# KVM accelerates only matching host and guest instruction sets; cross-
|
||||
# architecture execution must fall back to software emulation.
|
||||
if [[ "${ARCH,,}" != "$TARGET" ]]; then
|
||||
KVM="N"
|
||||
warn "your CPU architecture is ${ARCH^^} and cannot provide KVM acceleration for ${PLATFORM^^} instructions, so the machine will run about 10 times slower."
|
||||
@@ -344,6 +358,8 @@ echo
|
||||
|
||||
checkKvm
|
||||
|
||||
# Runtime state is intentionally discarded at each container start; persistent
|
||||
# machine and disk identity lives under STORAGE instead.
|
||||
# Cleanup files
|
||||
rm -f "$QEMU_DIR"/dsm.url
|
||||
rm -f "$QEMU_DIR"/{qemu.*,*.{pid,sock,pipe}}
|
||||
|
||||
@@ -42,6 +42,8 @@ validateHostMac() {
|
||||
|
||||
buildHostArguments() {
|
||||
|
||||
# qemu-host is a sidecar that bridges DSM's proprietary serial agent to
|
||||
# Unix sockets used by shutdown and post-boot discovery helpers.
|
||||
HOST_ARGS=()
|
||||
HOST_ARGS+=("-cpu=$CPU_CORES")
|
||||
HOST_ARGS+=("-cpu_arch=$HOST_CPU")
|
||||
@@ -60,6 +62,8 @@ startHostBinary() {
|
||||
|
||||
local pid
|
||||
|
||||
# Remove stale sockets and pid state before starting the sidecar; a Unix
|
||||
# socket path cannot be rebound while an old filesystem entry remains.
|
||||
rm -f -- "$HOST_PID" "$HOST_API_SOCKET" "$HOST_AGENT_SOCKET" || return 1
|
||||
|
||||
if enabled "$HOST_DEBUG"; then
|
||||
@@ -85,6 +89,8 @@ waitForSocket() {
|
||||
local timeout=5 pid
|
||||
local deadline=$((SECONDS + timeout))
|
||||
|
||||
# Do not start QEMU until both sidecar sockets are ready; otherwise the
|
||||
# VirtIO serial channel or API client may race initial creation.
|
||||
while [ ! -S "$socket" ]; do
|
||||
|
||||
if ! readPidFile pid "$HOST_PID" || ! isAlive "$pid"; then
|
||||
@@ -105,6 +111,8 @@ waitForSocket() {
|
||||
|
||||
configureSerialPorts() {
|
||||
|
||||
# Managed interactive mode separates the console and QEMU monitor into
|
||||
# reconnecting sockets; other runs keep the simple combined stdio monitor.
|
||||
if enabled "${SHUTDOWN:-Y}" && interactive; then
|
||||
|
||||
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
|
||||
|
||||
@@ -35,6 +35,8 @@ configureWebPorts() {
|
||||
|
||||
configureIpv6Listen() {
|
||||
|
||||
# Use one dual-stack listener when IPv6 is active, avoiding separate IPv4
|
||||
# and IPv6 sockets that can conflict on the same port.
|
||||
if [ -f /proc/net/if_inet6 ] && [[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" != "1" ]]; then
|
||||
|
||||
if ! sed -i \
|
||||
@@ -67,6 +69,8 @@ stopWebServer() {
|
||||
if readPidFile pid "$WEB_PID"; then
|
||||
pKill "$pid" 2
|
||||
|
||||
# Escalate only after the normal termination grace period; stale nginx
|
||||
# processes would otherwise keep the configured web port occupied.
|
||||
if isAlive "$pid"; then
|
||||
kill -9 -- "$pid" 2>/dev/null || :
|
||||
fi
|
||||
@@ -117,6 +121,8 @@ startWebsocketServer() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Keep the sidecar alive briefly before accepting startup as successful,
|
||||
# surfacing immediate bind or script failures with its captured log.
|
||||
local i
|
||||
for (( i = 1; i <= 5; i++ )); do
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ refresh() {
|
||||
[[ "$msg" == "$lastmsg" ]] && return 0
|
||||
|
||||
lastmsg="$msg"
|
||||
# websocketd clients interpret s: as a status update and c: as a command;
|
||||
# suppress unchanged status to avoid redundant browser work.
|
||||
echo "s: $msg"
|
||||
|
||||
return 0
|
||||
@@ -37,6 +39,8 @@ inotifywait \
|
||||
case "${event,,}" in
|
||||
"delete"* )
|
||||
echo "c: vnc" ;;
|
||||
# moved_to covers the atomic replacement used by html()/writeAtomic(),
|
||||
# while close_write handles direct writers.
|
||||
"close_write"* | "moved_to"* )
|
||||
refresh ;;
|
||||
esac
|
||||
|
||||
@@ -17,6 +17,8 @@ readPidFile() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Reject empty, zero, or nonnumeric pidfiles so cleanup can never signal an
|
||||
# unintended process group.
|
||||
if [[ ! "$_pid" =~ ^[1-9][0-9]*$ ]]; then
|
||||
_pid=""
|
||||
return 1
|
||||
@@ -49,6 +51,8 @@ isAmdCpu() {
|
||||
|
||||
interactive() {
|
||||
|
||||
# A TTY on stdin is insufficient when /dev/tty is unavailable; require both
|
||||
# before enabling interactive console handling.
|
||||
[ -t 0 ] && : 2>/dev/null </dev/tty >/dev/tty
|
||||
|
||||
}
|
||||
@@ -252,6 +256,8 @@ setOwner() {
|
||||
|
||||
[ ! -f "$file" ] && return 1
|
||||
|
||||
# Match generated files to the owner of their bind-mounted parent directory
|
||||
# instead of assuming a fixed container or host UID.
|
||||
dir=$(dirname -- "$file")
|
||||
uid=$(stat -c '%u' "$dir") || return 1
|
||||
gid=$(stat -c '%g' "$dir") || return 1
|
||||
@@ -316,6 +322,8 @@ writeAtomic() {
|
||||
|
||||
local path="$1"
|
||||
local content="$2"
|
||||
# Use a per-process temporary file and rename so readers see either the old
|
||||
# complete value or the new complete value.
|
||||
local tmp="${path}.${BASHPID}.tmp"
|
||||
|
||||
if ! printf '%s\n' "$content" > "$tmp"; then
|
||||
@@ -380,6 +388,8 @@ restoreState() {
|
||||
local prefix="${4:-$PROCESS}"
|
||||
local value
|
||||
|
||||
# Persistent state fills only unset variables unless force is requested,
|
||||
# preserving explicit environment overrides.
|
||||
if ! enabled "$force"; then
|
||||
[ -z "${!var:-}" ] || return 0
|
||||
fi
|
||||
@@ -439,6 +449,8 @@ html() {
|
||||
HTML="${HTML/\[4\]/$footer}"
|
||||
HTML="${HTML/\[5\]/$FOOTER2}"
|
||||
|
||||
# Publish both the full page and websocket fragment atomically because nginx
|
||||
# and websocketd may read them concurrently.
|
||||
writeAtomic "$PAGE" "$HTML" || return 1
|
||||
writeAtomic "$INFO" "$body" || return 1
|
||||
|
||||
@@ -512,6 +524,8 @@ setCountry() {
|
||||
[[ "${TZ,,}" == "asia/shanghai" ]] && COUNTRY="CN"
|
||||
[[ "${TZ,,}" == "asia/chongqing" ]] && COUNTRY="CN"
|
||||
|
||||
# Country detection is best-effort and tries independent services in order;
|
||||
# failure leaves mirror selection at its global default.
|
||||
[ -z "$COUNTRY" ] && getCountry "https://api.ipapi.is" ".location.country_code"
|
||||
[ -z "$COUNTRY" ] && getCountry "https://ifconfig.co/json" ".country_iso"
|
||||
[ -z "$COUNTRY" ] && getCountry "https://api.ip2location.io" ".country_code"
|
||||
@@ -536,6 +550,8 @@ addPackage() {
|
||||
|
||||
[ -z "$COUNTRY" ] && setCountry
|
||||
|
||||
# Use a mainland mirror only for on-demand package installation, avoiding
|
||||
# slow or inaccessible Debian endpoints in that region.
|
||||
if [[ "${COUNTRY^^}" == "CN" ]]; then
|
||||
sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debian.sources
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user