Files
Gmer4Lfe 7ae510ed28 Point the WebGUI symlink at the actual install, and report this host's identity rather than HOST1's
The .plg hardcoded the flash path and runs every boot, so an appdata node served a stale copy that never receives pulls — fixes appeared to do nothing, indefinitely.
2026-08-17 11:52:33 -04:00

308 lines
15 KiB
XML

<?xml version='1.0' standalone='yes'?>
<!DOCTYPE PLUGIN [
<!ENTITY name "varaverk">
<!ENTITY author "gmer4lfe">
<!ENTITY version "2026.05.31">
<!ENTITY sha256 "d588470d6cc7f284601cb56039d5dfea6fb5bb1ee2c800657438a8edacafbd01">
<!ENTITY launch "varaverk/monitor">
<!ENTITY github "https://github.com/FailedProxy/Varaverk">
<!ENTITY branch "main">
<!ENTITY cfgdir "/boot/config/plugins/varaverk">
<!ENTITY plugdir "/usr/local/emhttp/plugins/varaverk">
<!ENTITY pkg "varaverk-&version;-noarch-1.txz">
]>
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;"
support="https://github.com/FailedProxy/Varaverk/issues"
icon="/plugins/varaverk/icons/varaverk.png">
<CHANGES>
###2026.05.31
- Install via raw .plg URL (Gitea or GitHub) — same install script either way, Gitea → Tailscale peer → GITEA_DOMAIN → GitHub fallback chain
- Web files served via symlink to Plugin/unraid/ on flash, recreated every boot — survives reboots, no array dependency
- Scripts git-cloned to flash on first install only; updates handled in-UI (git pull) or the daily orchestrator, not by re-cloning on every boot
- build.sh packages a .txz for a possible future Community Apps release — not yet wired into install; current distribution is git-clone + symlink only
###2026.05.30
- First-run setup wizard: auto-detects hostname, creates master.conf + host conf from templates
- Scheduler setup mode: after wizard, master.conf and host conf open sequentially with forced save flow
- Partnership tab: Onboard button highlighted on arrival from wizard; disabled until partner is configured
- HOST2 install paths: state-file pull, master.conf push detection, conf-only flow
- Onboard Step 9: master.conf automatically pushed to all listed hosts on onboard completion
- Graceful pre-onboard state: neutral banners instead of error warnings before SSH is configured
- GitHub link in tab bar and Settings page; Community Apps support URL
###2026.05.28
- Initial release: Monitor, Scheduler, Docker, Watchdog, Partnership, Fallback, Arrs tabs
- Mutual container fallback with tiered escalation and strike-confirmed handback
- Partnership lifecycle: onboard, offboard, transfer
- Rsync profile system with per-share container stops and writeback
- Watchdog: Tier 1 (explicit) + Tier 2 (global scan) container monitoring
- master.conf push-on-save to all configured partners via SSH
</CHANGES>
<!--
── 1. Web files symlink (runs on every boot) ──────────────────────────────────
Instead of extracting a .txz, we symlink the installed plugin web dir directly
to the workspace on flash. Changes to Plugin/unraid/ are live instantly — no
package build, no sync step. /boot is always mounted before this runs.
────────────────────────────────────────────────────────────────────────────────
-->
<FILE Run="/bin/bash">
<INLINE>
<![CDATA[
#!/bin/bash
WEB_DIR="/usr/local/emhttp/plugins/varaverk"
# SRC follows the install, it is not assumed. This was hardcoded to the flash path while this
# block runs on EVERY boot, so an appdata-mode node had its WebGUI pointed back at the flash copy
# every time it started — a copy that stops receiving pulls the moment SCRIPTS_DIR moves. The
# result is a host where git pull reports success, the files on disk are current, and the pages
# served are whatever the flash copy last had. Fixes appear to do nothing, indefinitely.
#
# varaverk.cfg stays on flash regardless of layout, which is what makes it readable this early.
SRC=""
if [[ -f /boot/config/plugins/varaverk/varaverk.cfg ]]; then
_sd=$( . /boot/config/plugins/varaverk/varaverk.cfg >/dev/null 2>&1 || true
printf '%s' "${SCRIPTS_DIR:-}" )
[[ -n "$_sd" && -d "$_sd/Plugin/unraid" ]] && SRC="$_sd/Plugin/unraid"
fi
[[ -z "$SRC" ]] && SRC="/boot/config/plugins/varaverk/Plugin/unraid"
[[ -L "$WEB_DIR" ]] && rm -f "$WEB_DIR"
[[ -d "$WEB_DIR" ]] && rm -rf "$WEB_DIR"
ln -sf "$SRC" "$WEB_DIR"
# The link is made either way — it costs nothing and becomes valid the moment the scripts land,
# which matters because this block runs on every boot while the clone runs once. What is not
# acceptable is reporting success when the target is not there: the symlink dangles, the Varaverk
# tab never appears, and "web dir symlinked" is the only thing anyone sees.
if [[ -d "$SRC" ]]; then
echo "[Varaverk] web dir symlinked → $SRC"
else
echo "[Varaverk] WARNING: $SRC does not exist — the scripts are not installed."
echo "[Varaverk] The Varaverk tab will not appear until the repo is cloned."
echo "[Varaverk] See /boot/config/plugins/varaverk/install.log for why the clone failed."
fi
]]>
</INLINE>
</FILE>
<!--
── 2. Scripts bootstrap (first install only) ──────────────────────────────────
Clones the repo directly into the plugin config dir on flash (/boot/config/plugins/varaverk).
No array dependency — scripts live on flash (64GB NVMe) and are available at boot.
Uses git init+fetch+reset so the clone works into the non-empty cfgdir (varaverk.cfg,
varaverk-*.txz etc. are already there). Never auto-pulls — updates via the UI git pull.
Clone source priority:
1. Gitea (local container, then Tailscale peer probe) — reads settings from varaverk.cfg
2. Gitea (GITEA_DOMAIN) — public domain fallback if local/Tailscale lookup fails
3. GitHub (public) — HTTPS fallback if Gitea is unreachable by any path
────────────────────────────────────────────────────────────────────────────────
-->
<FILE Run="/bin/bash" Method="install">
<INLINE>
<![CDATA[
#!/bin/bash
PLUGIN="varaverk"
CFG_DIR="/boot/config/plugins/$PLUGIN"
CFG_FILE="$CFG_DIR/varaverk.cfg"
GITHUB="https://github.com/FailedProxy/Varaverk"
BRANCH="main"
LOG="$CFG_DIR/install.log"
mkdir -p "$CFG_DIR"
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG"; }
# Scripts live in the plugin dir on flash — no array needed.
SCRIPTS_DIR="$CFG_DIR"
CONF_DIR="$SCRIPTS_DIR/Configurations"
DEPLOY_DIR="$SCRIPTS_DIR/Deployment"
# ── Boot device check ─────────────────────────────────────────────────────────
# Warn if /boot is on a USB/removable device. Varaverk is designed for internal
# NVMe/SSD boot — git repo + state files + data writes on USB will wear it out
# fast and may run out of space. Install proceeds but user is warned.
_boot_dev=$(df /boot --output=source 2>/dev/null | tail -1)
_boot_base=$(lsblk -no pkname "$_boot_dev" 2>/dev/null || basename "${_boot_dev%[0-9p]*}")
_removable=$(cat "/sys/block/${_boot_base}/removable" 2>/dev/null || echo "0")
if [[ "$_removable" == "1" ]]; then
log "WARNING: /boot is on a removable/USB device ($_boot_dev)"
log "WARNING: Varaverk is designed for internal NVMe/SSD boot."
log "WARNING: Running from USB risks drive wear and space exhaustion."
log "WARNING: Strongly recommend migrating boot to an internal NVMe/SSD drive."
fi
unset _boot_dev _boot_base _removable
# Seed varaverk.cfg with defaults (SCRIPTS_DIR + Gitea settings) if not present.
# Requires internal NVMe/SSD boot — scripts live on flash, available before array mounts.
if [[ ! -f "$CFG_FILE" ]]; then
cat > "$CFG_FILE" <<'CFGEOF'
SCRIPTS_DIR="/boot/config/plugins/varaverk"
GITEA_CONTAINER="Gitea"
GITEA_REPO_PATH="FailedProxy/Varaverk.git"
GITEA_SSH_KEY="/root/.ssh/unraid_gitea"
GITEA_DOMAIN=""
SSH_PORT="221"
CFGEOF
log "seeded varaverk.cfg"
fi
# Read Gitea settings from varaverk.cfg (allows override without editing .plg).
_read_cfg() { grep -oP "(?<=^${1}=\")[^\"]*" "$CFG_FILE" 2>/dev/null || echo "${2}"; }
GITEA_CONTAINER=$(_read_cfg GITEA_CONTAINER "Gitea")
GITEA_REPO_PATH=$(_read_cfg GITEA_REPO_PATH "FailedProxy/Varaverk.git")
GITEA_SSH_KEY=$(_read_cfg GITEA_SSH_KEY "/root/.ssh/unraid_gitea")
GITEA_DOMAIN=$(_read_cfg GITEA_DOMAIN "")
SSH_PORT=$(_read_cfg SSH_PORT "221")
# Clone on first install only; never auto-pull (updates via the UI git pull).
if [[ ! -d "$SCRIPTS_DIR/.git" ]]; then
log "initialising repo in $SCRIPTS_DIR ($BRANCH)..."
# Locate Gitea: local container → Tailscale → GITEA_DOMAIN → GitHub.
GITEA_IP=""
if command -v docker >/dev/null 2>&1 && \
docker ps --format "{{.Names}}" 2>/dev/null | grep -q "^${GITEA_CONTAINER}$"; then
GITEA_IP=$(hostname -I | awk '{print $1}')
log "Gitea running locally — using $GITEA_IP"
elif command -v tailscale >/dev/null 2>&1; then
# Try each known peer until we find one hosting Gitea
while IFS= read -r peer_ip; do
# Match Gitea's actual banner, not the word "gitea" anywhere in the output. The
# identity path is /root/.ssh/unraid_gitea, so a plain "gitea" match hits ssh's own
# "Warning: Identity file /root/.ssh/unraid_gitea not accessible" — which means a
# missing key made EVERY peer look like a Gitea host. The loop takes the first match
# and breaks, so that picked an arbitrary peer, failed the fetch against it, and fell
# through to the GitHub fallback with an error naming the wrong machine.
#
# These phrases are sentences Gitea emits; none of them can occur in a file path.
if ssh -i "$GITEA_SSH_KEY" -p "$SSH_PORT" \
-o ConnectTimeout=3 -o StrictHostKeyChecking=no \
-o BatchMode=yes "git@${peer_ip}" 2>&1 \
| grep -qiE "does not provide shell access|successfully authenticated|welcome to gitea"; then
GITEA_IP="$peer_ip"
log "Gitea found on Tailscale peer $GITEA_IP"
break
fi
done < <(tailscale status 2>/dev/null | \
awk '{print $1}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')
fi
if [[ -z "$GITEA_IP" && -n "$GITEA_DOMAIN" ]]; then
log "local/Tailscale lookup failed — falling back to domain: $GITEA_DOMAIN"
GITEA_IP="$GITEA_DOMAIN"
fi
# init-in-place — git clone would fail because the dir already has files.
git -C "$SCRIPTS_DIR" init >> "$LOG" 2>&1
CLONED=false
if [[ -n "$GITEA_IP" && -f "$GITEA_SSH_KEY" ]]; then
GITEA_URL="ssh://git@${GITEA_IP}:${SSH_PORT}/${GITEA_REPO_PATH}"
log "trying Gitea: $GITEA_URL"
git -C "$SCRIPTS_DIR" remote add origin "$GITEA_URL" >> "$LOG" 2>&1
if GIT_SSH_COMMAND="ssh -i $GITEA_SSH_KEY -p $SSH_PORT -o StrictHostKeyChecking=no" \
GIT_TERMINAL_PROMPT=0 \
git -C "$SCRIPTS_DIR" fetch --depth=1 origin "$BRANCH" >> "$LOG" 2>&1; then
git -C "$SCRIPTS_DIR" reset --hard FETCH_HEAD >> "$LOG" 2>&1
git -C "$SCRIPTS_DIR" branch -M "$BRANCH" >> "$LOG" 2>&1
git -C "$SCRIPTS_DIR" branch --set-upstream-to=origin/"$BRANCH" "$BRANCH" >> "$LOG" 2>&1
log "scripts installed from Gitea ($GITEA_IP)"
CLONED=true
else
log "Gitea fetch failed — falling back to GitHub"
git -C "$SCRIPTS_DIR" remote remove origin >> "$LOG" 2>&1 || true
fi
fi
if [[ "$CLONED" == false ]]; then
log "trying GitHub: $GITHUB"
git -C "$SCRIPTS_DIR" remote add origin "$GITHUB" >> "$LOG" 2>&1
if GIT_TERMINAL_PROMPT=0 \
git -C "$SCRIPTS_DIR" fetch --depth=1 origin "$BRANCH" >> "$LOG" 2>&1; then
git -C "$SCRIPTS_DIR" reset --hard FETCH_HEAD >> "$LOG" 2>&1
git -C "$SCRIPTS_DIR" branch -M "$BRANCH" >> "$LOG" 2>&1
git -C "$SCRIPTS_DIR" branch --set-upstream-to=origin/"$BRANCH" "$BRANCH" >> "$LOG" 2>&1
log "scripts installed from GitHub"
CLONED=true
else
log "WARNING: both Gitea and GitHub failed — scripts not installed, retry when network is up"
exit 0
fi
fi
else
log "repo present — leaving scripts untouched (update from the UI)"
fi
# Seed master.conf from template if absent.
#
# "If absent" is load-bearing, not defensive. Phase 1 of partnership onboarding pushes the
# owner's master.conf to this host BEFORE the plugin is installed — it lands in exactly this
# directory, which the push creates. That conf already names HOST1 and HOST2, which is how the
# setup wizard knows this machine's identity without asking for it. Overwriting it with the
# template here would replace a populated conf with one whose HOSTn slots are all empty and
# throw the wizard back to the questions the push exists to answer.
#
# The clone above cannot destroy it either: every conf is gitignored, so it is untracked and
# `git reset --hard` leaves it alone.
mkdir -p "$CONF_DIR"
if [[ ! -f "$CONF_DIR/master.conf" && -f "$DEPLOY_DIR/master.conf.template" ]]; then
cp "$DEPLOY_DIR/master.conf.template" "$CONF_DIR/master.conf"
log "seeded master.conf from template"
fi
log "install step complete"
]]>
</INLINE>
</FILE>
<!--
── 3. Remove ───────────────────────────────────────────────────────────────────
Stops background scripts, removes cron, the installed package, and flash config.
Scripts/conf in appdata are left intact (delete manually for a full wipe).
────────────────────────────────────────────────────────────────────────────────
-->
<FILE Run="/bin/bash" Method="remove">
<INLINE>
<![CDATA[
#!/bin/bash
PLUGIN="varaverk"
CFG_DIR="/boot/config/plugins/$PLUGIN"
CFG_FILE="$CFG_DIR/varaverk.cfg"
CRON_FILE="$CFG_DIR/varaverk.cron"
WEB_DIR="/usr/local/emhttp/plugins/$PLUGIN"
log() { echo "[Varaverk remove] $*"; }
[[ -f "$CFG_FILE" ]] && _sd=$(grep -oP '(?<=SCRIPTS_DIR=")[^"]+' "$CFG_FILE" 2>/dev/null)
SCRIPTS_DIR="${_sd:-$CFG_DIR}"
# Stop continuous background scripts.
if [[ -f "$SCRIPTS_DIR/Fallback/fallback.sh" ]]; then
bash "$SCRIPTS_DIR/Fallback/fallback.sh" --stop 2>/dev/null && log "fallback.sh stopped" || true
fi
pkill -f "run_job.sh" 2>/dev/null || true
pkill -f "watchdog_orchestrator.sh" 2>/dev/null || true
# Remove cron entries.
if [[ -f "$CRON_FILE" ]]; then
rm -f "$CRON_FILE"
/usr/local/sbin/update_cron 2>/dev/null || true
log "cron removed"
fi
rm -f /etc/cron.d/varaverk
# Remove the installed package (and its RAM files).
removepkg "$PLUGIN" 2>/dev/null || true
[[ -L "$WEB_DIR" ]] && rm -f "$WEB_DIR"
[[ -d "$WEB_DIR" ]] && rm -rf "$WEB_DIR"
log "web files removed"
# Remove flash config (incl. cached .txz).
rm -rf "$CFG_DIR"
log "flash config removed"
log "done — scripts/conf in $SCRIPTS_DIR preserved (delete manually for full wipe)"
]]>
</INLINE>
</FILE>
</PLUGIN>