#!/bin/sh set -e PLUGIN_PATH="/usr/share/perl5/PVE/Storage/Custom/TrueNASPlugin.pm" STORAGE_CFG="/etc/pve/storage.cfg" BROKER_BIN="/usr/sbin/truenas-plugin-broker" BROKER_UNIT="truenas-plugin-broker.service" have_systemd() { [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1 } enable_broker() { if have_systemd; then systemctl daemon-reload || true systemctl enable "$BROKER_UNIT" >/dev/null 2>&1 || true # Start the broker before restarting PVE services so the plugin # finds /run/truenas-plugin/broker.sock on first call. try-restart # picks up a new broker binary on upgrade without a separate stop. if systemctl is-active --quiet "$BROKER_UNIT"; then systemctl try-restart "$BROKER_UNIT" || true else systemctl start "$BROKER_UNIT" || true fi # Verify the socket appears; the plugin now refuses direct-WS mode # and dies loudly if the socket is missing. Wait briefly for # RuntimeDirectory + first accept() to settle. i=0 while [ $i -lt 20 ] && [ ! -S /run/truenas-plugin/broker.sock ]; do sleep 0.25 i=$((i + 1)) done if [ ! -S /run/truenas-plugin/broker.sock ]; then echo "WARNING: /run/truenas-plugin/broker.sock did not appear after starting $BROKER_UNIT." >&2 echo " The plugin will fail with a broker-missing error until this is resolved." >&2 echo " Check: systemctl status $BROKER_UNIT && journalctl -u $BROKER_UNIT -n 50" >&2 fi fi } restart_proxmox_services() { # Restarting pvedaemon / pveproxy / pvestatd disconnects the operator # from any active PVE web-UI session, which for people who reach the # UI through a jump host or a PAM-mediated password vault is more # painful than for a local admin (issue #60). Give them an escape # hatch: setting TRUENAS_PLUGIN_NO_RESTART=1 before `dpkg -i` skips # the PVE-side restarts and prints exactly the command to run when # they can afford the disconnect. The truenas-plugin-broker restart # in enable_broker() still runs unconditionally -- that's our own # service and does not touch the web UI session. if [ "${TRUENAS_PLUGIN_NO_RESTART:-0}" = "1" ]; then echo "TRUENAS_PLUGIN_NO_RESTART=1 is set; not restarting Proxmox services." echo " When convenient, run manually to pick up the new plugin:" echo " systemctl restart pvedaemon pveproxy pvestatd pve-ha-crm pve-ha-lrm" return 0 fi if have_systemd; then systemctl try-restart pvedaemon pveproxy pvestatd pve-ha-crm pve-ha-lrm || true elif command -v invoke-rc.d >/dev/null 2>&1; then invoke-rc.d pvedaemon restart || true invoke-rc.d pveproxy restart || true invoke-rc.d pve-ha-crm restart || true invoke-rc.d pve-ha-lrm restart || true fi } _do_migrate() { local backup="${STORAGE_CFG}.bak.$(date +%Y%m%d%H%M%S)" cp "$STORAGE_CFG" "$backup" echo " Backup: $backup" perl -i -pe ' BEGIN { our $in_stanza = 0 } if (/^truenasplugin:/) { $in_stanza = 1 } elsif (/^\S/) { $in_stanza = 0 } if ($in_stanza) { s/^(\s+)(api_host)(\s)/${1}tn_api_host${3}/; s/^(\s+)(api_key)(\s)/${1}tn_api_key${3}/; s/^(\s+)(api_scheme)(\s)/${1}tn_api_scheme${3}/; s/^(\s+)(api_transport)(\s)/${1}tn_api_transport${3}/; s/^(\s+)(api_port)(\s)/${1}tn_api_port${3}/; s/^(\s+)(api_insecure)(\s)/${1}tn_api_insecure${3}/; s/^(\s+)(api_retry_max)(\s)/${1}tn_api_retry_max${3}/; s/^(\s+)(api_retry_delay)(\s)/${1}tn_api_retry_delay${3}/; s/^(\s+)(prefer_ipv4)(\s)/${1}tn_prefer_ipv4${3}/; s/^(\s+)(dataset)(\s)/${1}tn_dataset${3}/; s/^(\s+)(zvol_blocksize)(\s)/${1}tn_zvol_blocksize${3}/; s/^(\s+)(compression)(\s)/${1}tn_compression${3}/; s/^(\s+)(transport_mode)(\s)/${1}tn_transport_mode${3}/; s/^(\s+)(target_iqn)(\s)/${1}tn_target_iqn${3}/; s/^(\s+)(discovery_portal)(\s)/${1}tn_discovery_portal${3}/; s/^(\s+)(portals)(\s)/${1}tn_portals${3}/; s/^(\s+)(use_multipath)(\s)/${1}tn_use_multipath${3}/; s/^(\s+)(force_delete_on_inuse)(\s)/${1}tn_force_delete_on_inuse${3}/; s/^(\s+)(logout_on_free)(\s)/${1}tn_logout_on_free${3}/; s/^(\s+)(use_by_path)(\s)/${1}tn_use_by_path${3}/; s/^(\s+)(ipv6_by_path)(\s)/${1}tn_ipv6_by_path${3}/; s/^(\s+)(chap_user)(\s)/${1}tn_chap_user${3}/; s/^(\s+)(chap_password)(\s)/${1}tn_chap_password${3}/; s/^(\s+)(subsystem_nqn)(\s)/${1}tn_subsystem_nqn${3}/; s/^(\s+)(hostnqn)(\s)/${1}tn_hostnqn${3}/; s/^(\s+)(nvme_dhchap_secret)(\s)/${1}tn_nvme_dhchap_secret${3}/; s/^(\s+)(nvme_dhchap_ctrl_secret)(\s)/${1}tn_nvme_dhchap_ctrl_secret${3}/; s/^(\s+)(enable_live_snapshots)(\s)/${1}tn_enable_live_snapshots${3}/; s/^(\s+)(snapshot_volume_chains)(\s)/${1}tn_snapshot_volume_chains${3}/; s/^(\s+)(vmstate_storage)(\s)/${1}tn_vmstate_storage${3}/; s/^(\s+)(enable_bulk_operations)(\s)/${1}tn_enable_bulk_operations${3}/; s/^(\s+)(storage_lock_timeout)(\s)/${1}tn_storage_lock_timeout${3}/; s/^(\s+)(debug)(\s)/${1}tn_debug${3}/; s/^(\s+)(device_ready_retries)(\s)/${1}tn_device_ready_retries${3}/; } ' "$STORAGE_CFG" echo " Migration complete." } migrate_storage_cfg() { local old_version="$1" # Only migrate when upgrading from a pre-2.1.0 version dpkg --compare-versions "$old_version" lt "2.1.0" 2>/dev/null || return 0 [ -f "$STORAGE_CFG" ] || return 0 # Stanza-scoped detection: check for old key names only within truenasplugin: blocks local KEYS="api_host|api_key|api_scheme|api_transport|api_port|api_insecure|api_retry_max|api_retry_delay|prefer_ipv4|dataset|zvol_blocksize|compression|transport_mode|target_iqn|discovery_portal|portals|use_multipath|force_delete_on_inuse|logout_on_free|use_by_path|ipv6_by_path|chap_user|chap_password|subsystem_nqn|hostnqn|nvme_dhchap_secret|nvme_dhchap_ctrl_secret|enable_live_snapshots|snapshot_volume_chains|vmstate_storage|enable_bulk_operations|storage_lock_timeout|debug|device_ready_retries" perl -ne ' if (/^truenasplugin:/) { $in=1 } elsif (/^\S/) { $in=0 } exit(0) if $in && /^\s+('"$KEYS"')\s/; END { exit(1) } ' "$STORAGE_CFG" || return 0 echo "" echo "=== TrueNAS Plugin: storage.cfg migration required ===" echo "Config keys in truenasplugin: stanzas must be renamed (tn_* prefix)." echo "Affected lines:" perl -ne ' if (/^truenasplugin:/) { $in=1; $f=$.." " } elsif (/^\S/) { $in=0 } print " $ARGV:$.: $_" if $in && /^\s+('"$KEYS"')\s/; ' "$STORAGE_CFG" echo "" # Always auto-migrate. Maintainer scripts must not prompt the user # interactively (Debian Policy 6.3, lintian: read-in-maintainer-script). # A timestamped backup is created in _do_migrate before any edit, so # operators who want to opt out can restore from .bak.. echo "Auto-migrating storage.cfg (backup will be created first)." _do_migrate } case "$1" in configure) if [ ! -f "$PLUGIN_PATH" ]; then echo "ERROR: Missing plugin file: $PLUGIN_PATH" >&2 exit 1 fi if [ ! -x "$BROKER_BIN" ]; then echo "ERROR: Missing broker binary: $BROKER_BIN" >&2 exit 1 fi perl -c "$PLUGIN_PATH" >/dev/null perl -c "$BROKER_BIN" >/dev/null if [ -n "$2" ]; then migrate_storage_cfg "$2" fi enable_broker restart_proxmox_services ;; abort-upgrade|abort-remove|abort-deconfigure|triggered) ;; *) ;; esac exit 0