On Ubuntu systems with Ubuntu Pro / ESM Apps enabled, Ubuntu's ESM repository pins its own packages to Pin-Priority: 510. Ubuntu 24.04 ships Incus 6.0 in universe/ESM, so APT prefers the older 6.0 incus-client over Zabbly's 7.0 incus-client (default priority 500). When installing incus-base, this causes unmet dependencies and breaks the installer with held broken packages. - write /etc/apt/preferences.d/zabbly-incus with Pin-Priority: 600 in 10-incus.sh - purge the preference file in uninstall.sh --purge
405 lines
18 KiB
Bash
405 lines
18 KiB
Bash
#!/usr/bin/env bash
|
|
# 10-incus.sh — install Incus (Zabbly LTS repo), storage pool, bridge.
|
|
# Targets Debian 12/13 and Ubuntu 22.04/24.04/26.04 via the Zabbly package
|
|
# repository (the Incus team's official third-party repo, maintained by the
|
|
# project lead). Version follows the lts-7.0 channel.
|
|
#
|
|
# vpsmgr is containers-only, so it installs the container-only incus-base
|
|
# package (never the full `incus` meta package with its VM stack).
|
|
set -uo pipefail
|
|
|
|
log(){ echo "[10] $*"; }
|
|
die(){ echo "[10] error: $*" >&2; exit 1; }
|
|
|
|
# --- snap lxd conflict ---
|
|
# Ubuntu images commonly ship lxd as a snap, even when it is never used or
|
|
# initialized. The snap's daemon (snap.lxd.daemon) binds 8443 and owns
|
|
# /var/lib/lxd + lxdbr0, which collide with Incus's own socket, bridge and
|
|
# storage namespace. Since vpsmgr manages Incus (never LXD), remove the snap
|
|
# before installing Incus — but only when it is clearly unused: an initialized
|
|
# lxd with containers/config is the user's data, and aborting is safer than
|
|
# deleting it. Idempotent (skipped when the snap is already gone).
|
|
if snap list lxd >/dev/null 2>&1; then
|
|
log "snap lxd is installed (Ubuntu images ship it by default) — checking whether it is used"
|
|
IN_USE=0
|
|
if systemctl is-active --quiet snap.lxd.daemon 2>/dev/null; then
|
|
log " lxd daemon is RUNNING"
|
|
IN_USE=1
|
|
fi
|
|
# Containers/config are the strongest "in use" signals. The data dir depends
|
|
# on the package: deb lxd uses /var/lib/lxd, the snap uses
|
|
# /var/snap/lxd/common/lxd (both may exist; check whichever is present).
|
|
for D in /var/lib/lxd /var/snap/lxd/common/lxd; do
|
|
[[ -d "$D/containers" ]] && compgen -G "$D/containers/*" >/dev/null 2>&1 && {
|
|
log " lxd has containers under $D/containers"
|
|
IN_USE=1
|
|
}
|
|
# An initialized database is a strong signal even without running daemon.
|
|
if [[ -f "$D/lxd.db" ]] && [[ $(stat -c%s "$D/lxd.db" 2>/dev/null || echo 0) -gt 4096 ]]; then
|
|
log " lxd has an initialized database ($D/lxd.db)"
|
|
IN_USE=1
|
|
fi
|
|
done
|
|
if [[ "$IN_USE" -eq 1 ]]; then
|
|
die "snap lxd appears to be IN USE (daemon running / containers / initialized db).
|
|
vpsmgr manages Incus, not LXD — running both would collide on ports, bridges and
|
|
storage. Remove lxd manually (after backing up anything you need):
|
|
snap stop lxd
|
|
snap remove --purge lxd
|
|
then re-run the installer."
|
|
fi
|
|
log "snap lxd is not in use — removing it to free space and avoid conflicts"
|
|
snap stop lxd 2>/dev/null || true
|
|
if snap remove --purge lxd; then
|
|
log "snap lxd removed"
|
|
else
|
|
die "could not remove snap lxd (snap remove --purge lxd failed) — remove it manually and re-run"
|
|
fi
|
|
fi
|
|
|
|
# --- ensure the Zabbly repo is present, then install Incus ---
|
|
ZABBLY_SOURCE="/etc/apt/sources.list.d/zabbly-incus-lts-7.0.sources"
|
|
ZABBLY_PREF="/etc/apt/preferences.d/zabbly-incus"
|
|
|
|
# Prefer Zabbly packages over distro-provided packages. On Ubuntu hosts
|
|
# with Ubuntu Pro / ESM enabled, ESM-apps packages carry Pin-Priority: 510,
|
|
# which makes APT prefer Ubuntu's older incus-client (6.0) over Zabbly's (7.0)
|
|
# and break incus-base's strict dependency.
|
|
if [[ ! -f "$ZABBLY_PREF" ]]; then
|
|
install -d -m 0755 /etc/apt/preferences.d
|
|
cat > "$ZABBLY_PREF" <<EOF
|
|
Package: *
|
|
Pin: origin pkgs.zabbly.com
|
|
Pin-Priority: 600
|
|
EOF
|
|
fi
|
|
|
|
if [[ -f "$ZABBLY_SOURCE" ]]; then
|
|
log "zabbly incus lts-7.0 repo already configured"
|
|
else
|
|
log "adding Zabbly Incus LTS 7.0 repository..."
|
|
install -d -m 0755 /etc/apt/keyrings
|
|
curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc \
|
|
|| die "could not download the Zabbly signing key"
|
|
# Verify the published fingerprint (4EFC 5906 96CB 15B8 7C73 A3AD 82CC 8797 C838 DCFD).
|
|
FP=$(gpg --show-keys --with-colons /etc/apt/keyrings/zabbly.asc 2>/dev/null | awk -F: '/^fpr:/{print $10; exit}')
|
|
if [[ "$FP" != "4EFC590696CB15B87C73A3AD82CC8797C838DCFD" ]]; then
|
|
die "zabbly key fingerprint mismatch ($FP) — refusing to install from an unverified repo"
|
|
fi
|
|
cat > "$ZABBLY_SOURCE" <<EOF
|
|
Enabled: yes
|
|
Types: deb
|
|
URIs: https://pkgs.zabbly.com/incus/lts-7.0
|
|
Suites: $(. /etc/os-release && echo ${VERSION_CODENAME})
|
|
Components: main
|
|
Architectures: $(dpkg --print-architecture)
|
|
Signed-By: /etc/apt/keyrings/zabbly.asc
|
|
EOF
|
|
apt-get update -qq || die "apt-get update failed (check the Zabbly repo)"
|
|
fi
|
|
|
|
# vpsmgr only runs containers, so new installs use the container-only
|
|
# incus-base build (it pulls in incus-client for the CLI). The full `incus`
|
|
# package adds the entire VM stack (QEMU/SPICE/Ceph/ffmpeg/VA-API — roughly
|
|
# 240 MiB extra download, 500 MiB+ on disk) that this panel never uses.
|
|
# Hosts that already carry the full `incus` package keep it: swapping
|
|
# packages under a running daemon risks the existing setup, and VM support
|
|
# does not interfere with containers. Existing incus-base installs are
|
|
# adopted as-is.
|
|
if dpkg -s incus-base >/dev/null 2>&1 || dpkg -s incus >/dev/null 2>&1; then
|
|
if dpkg -s incus >/dev/null 2>&1; then
|
|
log "incus already installed (full package incl. VM support) — keeping it"
|
|
else
|
|
log "incus-base already installed (container-only) — keeping it"
|
|
fi
|
|
else
|
|
log "installing incus-base (container-only, Zabbly lts-7.0)..."
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq incus-base || die "apt install incus-base failed"
|
|
fi
|
|
log "incus version: $(incus version 2>/dev/null | head -1)"
|
|
|
|
# Make the incus-admin group available to the vps service user (created by
|
|
# 40-panel.sh / `vps install`). The panel talks to Incus over its Unix socket,
|
|
# which is group-readable by incus-admin — membership grants full management
|
|
# of Incus without running as root.
|
|
if id -u vps >/dev/null 2>&1; then
|
|
usermod -aG incus-admin vps >/dev/null 2>&1 || true
|
|
log "added user 'vps' to group incus-admin (panel can manage Incus via the API)"
|
|
fi
|
|
|
|
# Wait for the daemon socket to be up (systemd socket activation).
|
|
for i in $(seq 1 60); do
|
|
[[ -S /var/lib/incus/unix.socket ]] && break
|
|
sleep 1
|
|
done
|
|
[[ -S /var/lib/incus/unix.socket ]] || die "incus daemon socket not ready"
|
|
|
|
# --- storage pool ---
|
|
# zfs (default), btrfs or dir, chosen by install.sh / VPSMGR_STORAGE. zfs and
|
|
# btrfs give Incus quotas, snapshots and clone-on-create — the whole disk
|
|
# model. Both can be backed by a sparse loop file anywhere, and btrfs can also
|
|
# live as a native subvolume on a btrfs host filesystem. The dir backend has
|
|
# none of those features and is only offered for throwaway test boxes; it is
|
|
# NEVER a fallback (a failed pool is a hard error, not a silent downgrade).
|
|
# New installations use a sparse loop-file pool on / (zfs, or btrfs when the
|
|
# host is not itself btrfs). Incus does NOT bundle the ZFS userspace tools, so
|
|
# zfsutils-linux must be installed (00-check.sh ensures it); btrfs-progs is
|
|
# ensured the same way for btrfs.
|
|
STORAGE="${VPSMGR_STORAGE:-zfs}"
|
|
case "$STORAGE" in
|
|
zfs|btrfs|dir) ;;
|
|
*) die "VPSMGR_STORAGE must be zfs, btrfs or dir (got '$STORAGE')" ;;
|
|
esac
|
|
if [[ "$STORAGE" == "zfs" ]]; then
|
|
command -v zpool >/dev/null 2>&1 || die "zfsutils-linux is not installed (zpool missing) — 00-check.sh should have installed it; run it or: apt-get install -y zfsutils-linux"
|
|
fi
|
|
if [[ "$STORAGE" == "btrfs" ]]; then
|
|
command -v btrfs >/dev/null 2>&1 || die "btrfs-progs is not installed (btrfs missing) — 00-check.sh should have installed it; run it or: apt-get install -y btrfs-progs"
|
|
fi
|
|
POOL=vpsmgr
|
|
POOL_EXISTS=0
|
|
if incus storage show "$POOL" >/dev/null 2>&1; then
|
|
POOL_EXISTS=1
|
|
DRIVER=$(incus storage show "$POOL" | awk -F': ' '/driver:/{print $2}')
|
|
if { [[ "$STORAGE" == "zfs" || "$STORAGE" == "btrfs" ]] && [[ "$DRIVER" != "$STORAGE" ]]; }; then
|
|
die "existing storage pool '$POOL' uses driver '$DRIVER' — ${STORAGE} mode requires ${STORAGE} (dir has no quotas)"
|
|
fi
|
|
log "storage pool '$POOL' exists ($DRIVER)"
|
|
fi
|
|
|
|
# Decide how to configure the pool in preseed.
|
|
# New installations always use a sparse loop-file pool on / (zfs, or btrfs
|
|
# when the host is not itself btrfs), PLUS a native btrfs subvolume when a
|
|
# btrfs host chooses the btrfs backend. The installer deliberately never scans,
|
|
# selects, formats, or modifies secondary block devices: virtual CD/ISO
|
|
# devices and provider-specific disk layouts are left completely untouched.
|
|
# Existing vpsmgr pools are adopted unchanged.
|
|
DRIVER=$STORAGE
|
|
POOL_SIZE_MB=""
|
|
LOOP_BACKED=0
|
|
BTRFS_SOURCE=""
|
|
if [[ $POOL_EXISTS -eq 1 ]]; then
|
|
# Adopt existing pool.
|
|
DRIVER=$(incus storage show "$POOL" | awk -F': ' '/driver:/{print $2}')
|
|
SRC_LINE=" source: \"$POOL\""
|
|
SIZE_LINE=""
|
|
elif [[ "$STORAGE" == "zfs" ]]; then
|
|
LOOP_BACKED=1
|
|
elif [[ "$STORAGE" == "btrfs" ]]; then
|
|
# On a btrfs host, prefer a native subvolume directly on the filesystem:
|
|
# reflink clones, no loop-device overhead, no double space accounting, and
|
|
# the whole root's free space is available to the pool. Incus only accepts a
|
|
# btrfs source path of `<dataDir>/storage-pools/<pool>`, which it creates
|
|
# ITSELF as the subvolume — so the source is NOT created here and must not
|
|
# already exist (a pre-existing path makes Incus refuse with "already
|
|
# exists"). Any other host is a loop file, exactly like zfs.
|
|
BTRFS_MNT=$(findmnt -no FSTYPE /)
|
|
if [[ "$BTRFS_MNT" == "btrfs" ]]; then
|
|
BTRFS_SOURCE=/var/lib/incus/storage-pools/$POOL
|
|
SRC_LINE=" source: \"$BTRFS_SOURCE\""
|
|
SIZE_LINE=""
|
|
log "btrfs native subvolume '$POOL' on the btrfs host filesystem (source: $BTRFS_SOURCE, created by Incus)"
|
|
else
|
|
LOOP_BACKED=1
|
|
fi
|
|
else
|
|
log "dir pool '$POOL' (no quotas, snapshots or clone-on-create)"
|
|
SRC_LINE=" {}"
|
|
SIZE_LINE=""
|
|
fi
|
|
|
|
# Shared loop-file sizing for zfs and (btrfs on a non-btrfs host). The pool is
|
|
# sparse and grows on demand inside /. Before sizing it, reclaim caches and
|
|
# build artifacts when free space is tight so the pool, Incus image store, and
|
|
# host remain usable.
|
|
if [[ $LOOP_BACKED -eq 1 ]]; then
|
|
FREE_KB=$(df -k --output=avail / | tail -1 | tr -d ' ')
|
|
if (( FREE_KB < 25 * 1024 * 1024 )); then
|
|
log "free space on / is $(( FREE_KB / 1024 / 1024 )) GiB (< 25 GiB) — reclaiming caches before sizing the pool..."
|
|
# Cleanest wins first; each is idempotent and never touches user data.
|
|
DEBIAN_FRONTEND=noninteractive apt-get clean 2>/dev/null || true
|
|
rm -rf /var/lib/apt/lists/* 2>/dev/null || true
|
|
DEBIAN_FRONTEND=noninteractive apt-get autoremove -y -qq 2>/dev/null || true
|
|
journalctl --vacuum-time=3d >/dev/null 2>&1 || true
|
|
rm -rf /tmp/* /var/tmp/* 2>/dev/null || true
|
|
# Stale kernels: purge ALL linux-image-* except the RUNNING kernel — but
|
|
# if a NEWER kernel than the running one is already installed (upgraded,
|
|
# not yet rebooted), keep that newest one too: deleting it would make the
|
|
# next reboot BOOT AN OLDER KERNEL than the one currently running. The
|
|
# meta package linux-image-<arch> (no version, e.g. linux-image-amd64)
|
|
# is excluded — it holds no image payload but keeps apt pulling future
|
|
# kernels; purging it would silently stop kernel upgrades.
|
|
RUNNING_KERNEL=$(uname -r)
|
|
mapfile -t ALL_KERNELS < <(dpkg -l 'linux-image-*' 2>/dev/null | awk '/^ii/{print $2}' | grep -v -- "-dbg" | grep -E -- "-[0-9]+" | sort -V)
|
|
KEEP=("linux-image-$RUNNING_KERNEL")
|
|
NEWEST=${ALL_KERNELS[-1]}
|
|
if [[ -n "$NEWEST" && "$NEWEST" != "linux-image-$RUNNING_KERNEL" ]]; then
|
|
KEEP+=("$NEWEST")
|
|
log " keeping $NEWEST (newer than running kernel — reboot pending)"
|
|
fi
|
|
OLD_KERNELS=()
|
|
for k in "${ALL_KERNELS[@]}"; do
|
|
keep=0
|
|
for kk in "${KEEP[@]}"; do [[ "$k" == "$kk" ]] && keep=1; done
|
|
(( keep )) || OLD_KERNELS+=("$k")
|
|
done
|
|
if (( ${#OLD_KERNELS[@]} > 0 )); then
|
|
log " purging ${#OLD_KERNELS[@]} old kernel(s): ${OLD_KERNELS[*]}"
|
|
DEBIAN_FRONTEND=noninteractive apt-get purge -y -qq "${OLD_KERNELS[@]}" >/dev/null 2>&1 || true
|
|
fi
|
|
rm -rf /root/.cache/pip /root/.cache/go-build /root/go/pkg/mod/cache/download 2>/dev/null || true
|
|
FREE_KB=$(df -k --output=avail / | tail -1 | tr -d ' ')
|
|
log "after reclaim: $(( FREE_KB / 1024 / 1024 )) GiB free on /"
|
|
fi
|
|
# Small disks keep 80% so the host and Incus image store retain headroom;
|
|
# larger free roots can afford 90%. The loop file is sparse, so creation
|
|
# itself consumes no space; actual container data grows it on demand.
|
|
PCT=80
|
|
if (( FREE_KB > 20 * 1024 * 1024 )); then
|
|
PCT=90
|
|
fi
|
|
POOL_SIZE_MB=$(( FREE_KB * PCT / 100 / 1024 ))
|
|
log "loop-file ${STORAGE} pool '$POOL' on / (~${POOL_SIZE_MB} MiB = ${PCT}% of free)"
|
|
SRC_LINE=""
|
|
SIZE_LINE=" size: \"${POOL_SIZE_MB}MiB\""
|
|
fi
|
|
|
|
# ZFS volumes created from here on use refquota: the disk limit counts all the
|
|
# blocks a container references (a hard cap), instead of the default `quota`
|
|
# that only charges a CoW clone's own delta and leaves inherited snapshot data
|
|
# unaccounted. Only new volumes are affected — existing containers keep their
|
|
# accounting. No-op for non-ZFS drivers.
|
|
REFQUOTA_LINE=""
|
|
if [[ "$DRIVER" == "zfs" ]]; then
|
|
REFQUOTA_LINE=" volume.zfs.use_refquota: \"true\""
|
|
fi
|
|
|
|
# --- incus admin init (preseed) ---
|
|
# IPv6 pass-through: when VPSMGR_IPV6_SUBNET is set, incusbr0 carries the global
|
|
# prefix (no NAT) and containers SLAAC global addresses from it. The bridge
|
|
# ADDRESS is deliberately not set here: the Go setup (SetupIPv6Bridge, run at
|
|
# `vps install`) picks a free address inside the prefix and skips any the host
|
|
# already uses — hardcoding prefix::1 at init time would claim the most common
|
|
# upstream-router / host address before that clash check ever runs.
|
|
V6_IP="none"
|
|
V6_NAT="false"
|
|
if [[ -n "${VPSMGR_IPV6_SUBNET:-}" ]]; then
|
|
log "IPv6 pass-through enabled: incusbr0 address chosen by \`vps install\` (clash-avoiding)"
|
|
fi
|
|
# Container IPv4 subnet (10.<n>.0.0/24): the bridge gateway is .1. Defaults to
|
|
# 10.115.0.1/24; 00-ip-ask.sh exports the chosen subnet before this step.
|
|
V4_GW="$(echo "${VPSMGR_IPV4_SUBNET:-10.115.0.0/24}" | cut -d. -f1-3).1/24"
|
|
if [[ $POOL_EXISTS -eq 0 ]] || ! incus network show incusbr0 >/dev/null 2>&1; then
|
|
PRESEED="$(mktemp /tmp/vpsmgr-preseed.XXXXXX.yaml)"
|
|
trap 'rm -f "$PRESEED"' EXIT
|
|
cat > "$PRESEED" <<EOF
|
|
config: {}
|
|
networks:
|
|
- config:
|
|
ipv4.address: $V4_GW
|
|
ipv4.nat: "true"
|
|
ipv6.address: $V6_IP
|
|
ipv6.nat: "$V6_NAT"
|
|
ipv6.dhcp.stateful: "true"
|
|
ipv6.routing: "true"
|
|
# Do not serve instance-name DNS: Incus's dnsmasq would publish
|
|
# <username>.lxd records for every container, so any tenant could nmap the
|
|
# subnet and read everyone's username. dns.mode=none keeps DHCP + upstream
|
|
# forwarding but drops the instance-name records.
|
|
dns.mode: none
|
|
description: ""
|
|
name: incusbr0
|
|
type: bridge
|
|
storage_pools:
|
|
- config:
|
|
$SRC_LINE
|
|
$SIZE_LINE
|
|
$REFQUOTA_LINE
|
|
description: ""
|
|
name: $POOL
|
|
driver: $DRIVER
|
|
profiles:
|
|
- config: {}
|
|
description: Default Incus profile
|
|
devices:
|
|
eth0:
|
|
nictype: bridged
|
|
parent: incusbr0
|
|
type: nic
|
|
root:
|
|
path: /
|
|
pool: $POOL
|
|
type: disk
|
|
name: default
|
|
EOF
|
|
log "running incus admin init --preseed (driver=$DRIVER, subnet $V4_GW, ipv6=${VPSMGR_IPV6_SUBNET:-none})"
|
|
if ! incus admin init --preseed < "$PRESEED"; then
|
|
log "preseed failed — creating missing pieces"
|
|
if [[ -n "${VPSMGR_IPV6_SUBNET:-}" ]]; then
|
|
incus network show incusbr0 >/dev/null 2>&1 || incus network create incusbr0 ipv4.address=$V4_GW ipv4.nat=true ipv6.address=none ipv6.nat=false ipv6.dhcp.stateful=true ipv6.routing=true dns.mode=none
|
|
else
|
|
incus network show incusbr0 >/dev/null 2>&1 || incus network create incusbr0 ipv4.address=$V4_GW ipv4.nat=true ipv6.address=none dns.mode=none
|
|
fi
|
|
if ! incus storage show "$POOL" >/dev/null 2>&1; then
|
|
case "$STORAGE" in
|
|
zfs)
|
|
[[ -n "$POOL_SIZE_MB" ]] || die "no loop-file size decided for the ZFS pool"
|
|
incus storage create "$POOL" zfs size="${POOL_SIZE_MB}MiB" volume.zfs.use_refquota=true || die "zfs pool creation (loop file) failed"
|
|
;;
|
|
btrfs)
|
|
if [[ -n "$BTRFS_SOURCE" ]]; then
|
|
incus storage create "$POOL" btrfs source="$BTRFS_SOURCE" || die "btrfs native-subvolume pool creation failed"
|
|
else
|
|
[[ -n "$POOL_SIZE_MB" ]] || die "no loop-file size decided for the btrfs pool"
|
|
incus storage create "$POOL" btrfs size="${POOL_SIZE_MB}MiB" || die "btrfs pool creation (loop file) failed"
|
|
fi
|
|
;;
|
|
dir)
|
|
incus storage create "$POOL" dir || die "dir pool creation failed"
|
|
;;
|
|
esac
|
|
fi
|
|
fi
|
|
else
|
|
log "Incus already initialized (pool+network present)"
|
|
fi
|
|
|
|
# Ensure the default profile carries the root disk + eth0 devices. This runs on
|
|
# EVERY install (not only preseed) because Incus's preseed may leave the default
|
|
# profile untouched ("unless you explicitly express that in the YAML") and an
|
|
# adopted/partially-initialized daemon can have an empty profile.
|
|
incus profile show default >/dev/null 2>&1 || incus profile create default
|
|
incus profile device list default | grep -q "root" || \
|
|
incus profile device add default root disk path=/ pool="$POOL" || true
|
|
incus profile device set default root pool "$POOL" 2>/dev/null || true
|
|
incus profile device list default | grep -q "eth0" || \
|
|
incus profile device add default eth0 nic nictype=bridged parent=incusbr0 || true
|
|
incus profile device set default eth0 parent incusbr0 2>/dev/null || true
|
|
|
|
# Always re-assert dns.mode=none (fresh install and upgrade alike): this is the
|
|
# only thing that stops Incus from publishing <username>.lxd DNS/PTR records
|
|
# that let any tenant enumerate every other user's username with a subnet scan.
|
|
incus network set incusbr0 dns.mode=none 2>/dev/null || true
|
|
|
|
# ZFS hard-quota default: re-assert on EVERY run (fresh install and upgrade
|
|
# alike) so a pool created before this setting still gives NEW containers a
|
|
# refquota hard limit. Existing volumes are left as they are.
|
|
if [[ "$(incus storage show "$POOL" 2>/dev/null | awk -F': ' '/driver:/{print $2}')" == "zfs" ]]; then
|
|
incus storage set "$POOL" volume.zfs.use_refquota=true 2>/dev/null || true
|
|
fi
|
|
|
|
DRIVER_NOW=$(incus storage show "$POOL" | awk -F': ' '/driver:/{print $2}')
|
|
log "storage backend: $DRIVER_NOW"
|
|
case "$STORAGE" in
|
|
zfs|btrfs)
|
|
if [[ "$DRIVER_NOW" != "$STORAGE" ]]; then
|
|
die "storage pool '$POOL' is not ${STORAGE^^} ('$DRIVER_NOW') — ${STORAGE} mode requires ${STORAGE} (quotas, snapshots, clones)"
|
|
fi
|
|
;;
|
|
dir)
|
|
log " warn: dir backend — disk quotas, snapshots and clone-on-create are NOT enforced"
|
|
;;
|
|
esac
|
|
|
|
echo "[10] incus ready"
|