Files
Varaverk/Plugin/unraid/adapter.sh
T
Gmer4Lfe 27bfc21cb0 Platform-agnostic refactor: eliminate OS-specific hardcodes from core scripts
All bash scripts are now platform-neutral. Unraid-specific paths, commands,
and service checks moved to Plugin/unraid/adapter.sh. Core scripts call
platform_*() functions exclusively — no direct OS paths in runtime logic.

New adapter functions: platform_storage_path, platform_webui_install_path,
platform_scripts_dir_probe_cmd, platform_setup_db_path, platform_storage_healthy,
platform_is_service_enabled, platform_get_temp_thresholds, platform_disk_states_path,
platform_rebuild_container, platform_push_conf, platform_push_setup_state,
platform_get_templates_dir, platform_send_os_notification.

Partnership services stack (Emby/Jellyfin/Seerr/SeerrFin) added as third
onboarding stack alongside auth and arr stacks.
2026-06-14 00:59:19 -04:00

324 lines
24 KiB
Bash
Executable File

#!/bin/bash
# ==============================================================================================
# ================================= Unraid Platform Adapter ====================================
# ==============================================================================================
# Sourced by load_config.sh when PLATFORM=unraid.
# Provides the platform_*() API — bash scripts call these instead of OS-specific commands.
#
# ── API CONTRACT ──────────────────────────────────────────────────────────────────────────────
# Every function returns 0 on success / 1 on failure unless noted.
# Functions that produce output write to stdout; callers capture with $().
# No function calls exit — callers decide what failure means for their flow.
#
# ── ADDING A PLATFORM ─────────────────────────────────────────────────────────────────────────
# Create Plugin/truenas/adapter.sh (or ubuntu/adapter.sh) implementing the same function names.
# load_config.sh sources Plugin/$PLATFORM/adapter.sh — no other changes needed.
#
# ── FUNCTIONS ─────────────────────────────────────────────────────────────────────────────────
# platform_require_cmd — verify a platform command exists and is executable
# platform_storage_healthy — array mounted and shfs active on /mnt/user
# platform_disk_states_path — path to the platform disk state file
# platform_get_disk_states — raw disk state content from platform
# platform_get_temp_thresholds — disk warn/crit °C from platform config
# platform_is_maintenance_running — parity check/sync in progress
# platform_is_service_enabled — docker or libvirt enabled in boot config
# platform_restart_service — restart a named service via rc.d
# platform_stop_service — stop a named service via rc.d
# platform_is_service_running — check if a named service process is alive
# platform_is_mover_running — unRAID mover process check
# platform_stop_user_scripts — kill all user.scripts background processes
# platform_send_os_notification — native unRAID notify (dynamix)
# platform_storage_path — root path for user shares/storage (e.g. /mnt/user)
# platform_webui_install_path — where the platform serves the WebGUI plugin files from
# platform_scripts_dir_probe_cmd — shell command to run on a remote to discover its SCRIPTS_DIR
# platform_get_templates_dir — path to Unraid CA docker templates-user directory
# platform_setup_db_path — path to the persistent Varaverk setup/wizard state database
# platform_rebuild_container — rebuild a container from its stored XML template
# platform_push_conf — push master.conf to all listed hosts via WebGUI PHP
# platform_push_setup_state — push wizard setup state to WebGUI PHP
# ==============================================================================================
# ──────────────────────────────────────────────────────────────────────────────────────────────
# Internal: map a logical service name → its rc.d script path
# ──────────────────────────────────────────────────────────────────────────────────────────────
_platform_rc_script() {
case "$1" in
docker) echo "/etc/rc.d/rc.docker" ;;
sshd) echo "/etc/rc.d/rc.sshd" ;;
libvirt) echo "/etc/rc.d/rc.libvirt" ;;
rsyslog) echo "/etc/rc.d/rc.rsyslogd" ;;
nginx) echo "/etc/rc.d/rc.nginx" ;;
php-fpm) echo "/etc/rc.d/rc.php-fpm" ;;
emhttp) echo "/etc/rc.d/rc.emhttp" ;;
*) echo "/etc/rc.d/rc.$1" ;;
esac
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_require_cmd <path> [test_arg] [expected_output] [label]
# Replaces platform_require_cmd. Returns 0 if the command exists and (optionally) its output
# matches expected_output. Returns 1 and prints a warning if not.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_require_cmd() {
local cmd="$1"
local test_arg="${2:-}"
local expected="${3:-}"
local label="${4:-$cmd}"
if [[ ! -x "$cmd" ]]; then
return 1
fi
if [[ -n "$test_arg" && -n "$expected" ]]; then
"$cmd" $test_arg 2>&1 | grep -q "$expected" || return 1
fi
return 0
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_storage_healthy
# Returns 0 if /mnt/user is mounted as shfs (array is up and healthy).
# Returns 1 if the mount is absent or is not shfs (array stopped / degraded).
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_storage_healthy() {
df --output=fstype /mnt/user 2>/dev/null | grep -q shfs
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_get_disk_states
# Writes raw /var/local/emhttp/disks.ini to stdout.
# Returns 1 if the file is absent (array not started or emhttp not running).
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_disk_states_path() {
echo "/var/local/emhttp/disks.ini"
}
platform_get_disk_states() {
local disks_ini
disks_ini=$(platform_disk_states_path)
[[ -f "$disks_ini" ]] || return 1
cat "$disks_ini"
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_get_temp_thresholds
# Writes four space-separated values to stdout: HDD_HOT HDD_MAX SSD_HOT SSD_MAX (°C integers).
# Falls back to 45/55/60/70 if dynamix.cfg is absent or keys are missing.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_get_temp_thresholds() {
local cfg="/boot/config/plugins/dynamix/dynamix.cfg"
local hdd_hot hdd_max ssd_hot ssd_max
hdd_hot=$(grep -m1 '^hot=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
hdd_max=$(grep -m1 '^max=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
ssd_hot=$(grep -m1 '^hotssd=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
ssd_max=$(grep -m1 '^maxssd=' "$cfg" 2>/dev/null | cut -d= -f2 | tr -d '"')
echo "${hdd_hot:-45} ${hdd_max:-55} ${ssd_hot:-60} ${ssd_max:-70}"
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_is_maintenance_running
# Returns 0 if a parity check or sync is currently in progress.
# Checks var.ini (Unraid 7.3+) then falls back to parity-date.txt (older).
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_is_maintenance_running() {
local resync
resync=$(awk -F'"' '/^mdResync=/{print $2}' /var/local/emhttp/var.ini 2>/dev/null)
if [[ -n "$resync" && "$resync" != "0" ]]; then
return 0
fi
grep -q "progress" /var/local/emhttp/parity-date.txt 2>/dev/null
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_is_service_enabled <service>
# service: docker | libvirt
# Returns 0 if the service is enabled in the Unraid boot config.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_is_service_enabled() {
case "$1" in
docker) grep -q '^DOCKER_ENABLED=yes' /boot/config/docker.cfg 2>/dev/null ;;
libvirt) grep -q '^DOMAIN_ENABLE=yes' /boot/config/domain.cfg 2>/dev/null ;;
*) return 1 ;;
esac
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_restart_service <service>
# Restarts the named service via its rc.d script.
# Returns 1 if the rc.d script does not exist or is not executable.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_restart_service() {
local rc
rc=$(_platform_rc_script "$1")
[[ -x "$rc" ]] || return 1
"$rc" restart >/dev/null 2>&1
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_stop_service <service>
# Stops the named service via its rc.d script.
# Returns 1 if the rc.d script does not exist or is not executable.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_stop_service() {
local rc
rc=$(_platform_rc_script "$1")
[[ -x "$rc" ]] || return 1
"$rc" stop >/dev/null 2>&1
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_is_service_running <service>
# Returns 0 if the service's main process is alive.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_is_service_running() {
case "$1" in
docker) pgrep -x dockerd >/dev/null 2>&1 ;;
emhttp) pgrep -x emhttpd >/dev/null 2>&1 ;;
nginx) pgrep -x nginx >/dev/null 2>&1 ;;
php-fpm) pgrep -x php-fpm >/dev/null 2>&1 ;;
sshd) pgrep -x sshd >/dev/null 2>&1 ;;
rsyslog) pgrep -x rsyslogd >/dev/null 2>&1 ;;
libvirt) pgrep -x libvirtd >/dev/null 2>&1 ;;
*) pgrep -x "$1" >/dev/null 2>&1 ;;
esac
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_is_mover_running
# Returns 0 if the Unraid mover is currently active.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_is_mover_running() {
pgrep -f "emhttp.*Mover" >/dev/null 2>&1
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_get_mover_pid
# Writes the mover's PID to stdout. Returns 1 if the mover is not running.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_get_mover_pid() {
local pid
pid=$(pgrep -f "emhttp.*Mover" | head -1)
[[ -n "$pid" ]] || return 1
echo "$pid"
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_stop_user_scripts
# Kills all Unraid user.scripts background processes.
# Returns 0 whether or not any processes were found (pkill exits 1 on no match).
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_stop_user_scripts() {
pkill -f "/tmp/user.scripts" 2>/dev/null || true
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_send_os_notification <message> [subject] [severity]
# Sends a native Unraid notification via the dynamix notify script.
# severity: normal | warning | alert (default: normal)
# Returns 1 if the notify script is absent.
# ──────────────────────────────────────────────────────────────────────────────────────────────
# ── Platform path defaults ─────────────────────────────────────────────────────────────────────
# Set here so master.conf and core scripts never contain OS-specific path literals.
# host*.conf may override any of these after the adapter is sourced.
DOCKER_APPDATA_BASE="${DOCKER_APPDATA_BASE:-/mnt/user/appdata}"
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_storage_path
# Writes the root path where user shares and storage are accessible to stdout.
# Used by array health checks and disk verification.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_storage_path() {
echo "/mnt/user"
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_webui_install_path
# Writes the path where the platform serves WebGUI plugin files from.
# On Unraid the PHP WebGUI is served from /boot/, separate from SCRIPTS_DIR.
# Returns 1 (empty) if the platform serves directly from SCRIPTS_DIR (no sync needed).
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_webui_install_path() {
echo "/boot/config/plugins/varaverk"
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_scripts_dir_probe_cmd
# Writes a shell command suitable for running on a remote via SSH to discover
# that remote's SCRIPTS_DIR. Each platform exposes its install path differently.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_scripts_dir_probe_cmd() {
echo "grep -m1 'SCRIPTS_DIR' /boot/config/plugins/varaverk/varaverk.cfg 2>/dev/null | cut -d= -f2 | tr -d '\"'"
}
platform_send_os_notification() {
local message="$1"
local subject="${2:-Varaverk}"
local severity="${3:-normal}"
local notify_script="/usr/local/emhttp/plugins/dynamix/scripts/notify"
[[ -x "$notify_script" ]] || return 1
"$notify_script" -e "Varaverk" -s "$subject" -d "$message" -i "$severity" 2>/dev/null
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_get_templates_dir
# Writes the path to the Unraid CA docker templates-user directory to stdout.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_get_templates_dir() {
echo "/boot/config/plugins/dockerMan/templates-user"
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_setup_db_path
# Writes the path to the persistent Varaverk setup/wizard state database.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_setup_db_path() {
echo "/boot/config/varaverk_setup.db"
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_rebuild_container <container_name>
# Rebuilds a container from its stored Unraid CA XML template (stops old, recreates on new
# image digest, prunes old image). Returns 1 if the rebuild script is absent or fails.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_rebuild_container() {
local container="$1"
local rebuild_script="/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container"
[[ -x "$rebuild_script" ]] || return 1
"$rebuild_script" "$container" >/dev/null 2>&1
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_push_conf
# Pushes master.conf to all listed hosts via the WebGUI PHP API.
# Writes per-host push results to stdout. Returns 1 if any host failed.
# Returns 0 silently if php is unavailable (caller should warn manually).
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_push_conf() {
command -v php &>/dev/null || return 0
local rc=0
php -r "
require_once '/usr/local/emhttp/plugins/varaverk/include/config.php';
\$results = vv_push_master_conf();
if (empty(\$results)) { echo 'no remote hosts'; exit(0); }
\$failed = 0;
foreach (\$results as \$r) {
echo \$r['host'] . ': ' . (\$r['ok'] ? 'pushed' : 'FAILED — ' . \$r['error']) . PHP_EOL;
if (!\$r['ok']) \$failed++;
}
exit(\$failed > 0 ? 1 : 0);
" 2>/dev/null
rc=$?
return $rc
}
# ──────────────────────────────────────────────────────────────────────────────────────────────
# platform_push_setup_state
# Pushes the Varaverk wizard setup state to all partners via the WebGUI PHP API.
# No-op if php is unavailable. Always returns 0.
# ──────────────────────────────────────────────────────────────────────────────────────────────
platform_push_setup_state() {
command -v php &>/dev/null || return 0
php -r "require_once '/usr/local/emhttp/plugins/varaverk/include/config.php'; vv_push_setup_state();" 2>/dev/null
return 0
}