]> ###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 /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 ]]> /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" ]]> /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)" ]]>