There is no nginx access log on this host and the CSRF prepend exits with an
empty body, so a request that never arrived and one that arrived and failed
were indistinguishable — the page just sat at "starting…". The job-file write
was also suppressed with @, which would produce exactly that hang: a token
returned for a job that can never report.
Token and poll rather than SSE, so the api layer keeps one response
convention and reuses the pattern manual_sync already proved. History is
capped at three turns because the model is only fully offloaded at 16384
context and unbounded history would cross that silently. The tab exists
only while AI_ENABLED is true, rejected server-side and not merely hidden.
Unraid already enforces CSRF on every POST via auto_prepend, but its
injector is jQuery-only — the plugin's native fetch() calls carried no
token and were being terminated before the endpoint ran, silently,
because csrf_terminate exits with an empty body that r.json() swallows.
Writing down what each endpoint actually guarantees made the places it
didn't obvious — shell arguments reaching a crontab or a bash -c
unescaped, master.conf written without tmp+rename, and conf edits that
could be saved without ever being parsed.
- WEBGUI_PHP_WAIT was referenced by webgui_watchdog.sh but never defined
in master.conf, always silently falling back to a hardcoded default
- arrs.php/confform.php still pointed at Media/ for arr cleanup/discovery
scripts moved to Arrs_Stack/ in b4bc926 — broke the Arrs page's stats
and the per-script settings editor for those scripts
- docker_folders.php read directly from the optional folder.view3 plugin's
file instead of Varaverk's own docker_folders.json (the primary store
since the Docker tab got its own config) — left the Monitor page's
Docker Folders widget empty on any host without folder.view3 installed
- vv_wd_remote_data() read remote watchdog state files from hardcoded
/tmp or /boot/config paths instead of the remote's actual STATE_DIR
(which resolves dynamically and can differ under flash mode) — remote
node's Watchdog panel was always empty; same wrong path also used for
two local reads (system_watchdog_oom.db, watchdog_appdata_growth.db)
- rsync.php referenced a {HOST}_MONTHLY_SYNC_SHARES conf var that never
existed (monthly_maintenance.sh has no rsync section) — nulled out to
match the existing pattern used for the fallback window
- vv_arr_node_names() did a pointless identity array_map
- vv_dk_webui() had its own duplicate local-IP resolution instead of
using vv_local_ip(), despite config.php's comment claiming that exact
duplication was already consolidated
Custom Scripts (the Scheduler page's inline editor) used to save into the
git-tracked Custom/ folder, so anything saved there would end up on GitHub.
They now live in /boot/config/plugins/user.scripts/Varaverk/Scripts, same
folder family as Unraid's own User Scripts plugin. Import Script lets you
browse the whole server and move an existing script in instead of only
creating new ones inline — always a move, never a copy, so no stray
duplicate is left where it came from.
findmnt returns 'flash/boot' for a ZFS dataset, not a /dev/* path.
lsblk -no pkname then fails, leaving transport as 'unknown'. Resolve
ZFS pools to a backing device via zpool before checking transport.
All state/data file paths in scripts and PHP now resolve via STATE_DIR / DATA_DIR /
PERSISTENT_CONF_CACHE instead of hardcoded /boot/config/ or /tmp/ paths, so the
ecosystem works in both internal and appdata storage modes.
PHP layer (watchdog.php, partnership.php, fallback.php, monitor.php, snapshot.php,
config.php): all state reads switched to STATE_DIR constant; remote state reads use
the new vv_remote_state_cmd() helper which resolves the remote's SCRIPTS_DIR via
their varaverk.cfg before building the path.
conf_sync.sh: fixed SCRIPTS_ROOT → SCRIPTS_DIR bug on MY_CONF path; added
_remote_scripts_dir() to resolve partner's SCRIPTS_DIR before SCP pull.
fallback.php page: added controls card (PARTNERSHIP_ENABLED, FALLBACK_ENABLED,
FALLBACK_RSYNC_ENABLED toggles), status grid, and settings card.
README and Manual updated for System_Essentials, Watchdogs, Fallback, Rsync,
Media, Monitors, Orchestrators, Partnership: added new scripts (conf_sync,
conf_cache_save/restore, conf_cache_watchdog, play_state_sync, start_webhook_listener,
upgrade_webhook_handler), corrected all stale /boot/config/ state file paths to
$STATE_DIR/$DATA_DIR, noted webgui/php_fpm/mover/user_scripts scripts moved to
Plugin/unraid/System_Essentials, fixed start_webhook_listener.sh header (Node.js,
not PHP -S).
Web files now served via symlink to the git repo so git pull changes survive
reboots without rebuilding the txz. Also includes: docker pull/rebuild/restart
with live log streaming, arr_profile_enforcer for Sonarr/Radarr quality
profiles, monitor page cache fix (background writer now in cron), and
ARR_KIDS/SONARR/RADARR profile name vars in master.conf.
host.conf.template: add AUTH STACK section (NPM/lldap/Authelia) so
conf_upgrade.sh stops stripping those keys from host*.conf at 1am.
setup wizard: storage mode selector auto-detects USB vs NVMe at first
run; user can override; triggers storage_migrate.sh when mode differs
from current SCRIPTS_DIR location.
claude_startup.sh: reads HOST*_STORAGE_MODE_INTERNAL to pick internal
or appdata path rather than always requiring array to be mounted.
- Replace python3/PyYAML Authelia ACL parser with pure PHP (no deps available on Unraid)
- Cert tab now pulls live from NPM API instead of cert_monitor.sh — auto-discovers all managed certs sorted by urgency
- Watchdog page: add missing GB constant and _fmtBytes/_relTime functions that were causing silent render failure
- Rsync settings card: pin to far-right 3 columns (grid-column:6/-1), toggle grid narrowed to 2 columns
- Add CLAUDE.md project context file on /boot for session persistence across reboots
- claude_startup.sh: symlink CLAUDE.md into /root on array start
- Auth stack: fold cert monitor into Auth Stack page as fourth tab (Certs);
remove standalone cert page and top-level tab
- cert_monitor.sh: write JSON status cache to State_Files/cert_status.json
after each run; expose per-domain days/expiry via _CERT_DAYS/_CERT_EXPIRY globals
- api/cert.php: new — serves cached cert status; falls back to configured
domains as UNKN when no cache exists; POST action=run triggers live check
- arrs db fallbacks: vv_arr_cleanup_stats/discovery_stats/recovery_stats now
read from data/*.db files when log JSON files don't yet exist
- config.php vv_conf_vars(): unescape bash \$ → $ so passwords with dollar
signs read correctly from conf files
- host1.conf: fill in HOST1_NPM_USER/PASS and HOST1_LLDAP_USER/PASS
- Partnership adapter pattern: Unraid-specific container logic extracted to
Plugin/unraid/Partnership/; platform-agnostic structure stays in Partnership/
- First-run wizard: uniform multi-step flow for all hosts; HOST2 pull moved
to checklist; auto SSH keygen and API key creation on save
- api/checklist.php: live setup checklist with pull_master action
- Fullscreen toggle: hide Unraid header/menu; state persists via localStorage
- ARRAY_START_SCRIPTS: add api_cache_writer.sh so monitor/arrs cache is populated
immediately on array start (/tmp is tmpfs — cleared on reboot, so first-boot
load was hitting live API for up to 60 seconds until the cron fired)
- api/monitor.php + arrs.php: raise cache TTL from 90s to 300s — stale-but-instant
beats a 6-second live API wait if the writer is momentarily behind
- include/unraid_api.php: replace last inline uptime formatter with vv_format_uptime()
Consolidations (config.php gains 5 shared utilities):
- vv_format_uptime() replaces 4 inline uptime-formatting blocks
- vv_parse_conf_scalar() replaces vv_arr_scalar/vv_wd_scalar/vv_fb_scalar/vv_media_conf_scalar
- vv_known_hosts() replaces vv_arr_known_hosts/vv_fb_known_hosts + inline parser in watchdog
- vv_parse_kv_db() replaces inline key=value parsing in snapshot and monitor
- vv_local_ip() replaces duplicate in docker_folders.php and inline in docker.php
All module-level function names kept as thin aliases so call sites unchanged.
Critical bug fixes:
- api/system.php: added require_once config.php and POST-only guard (no auth on shutdown)
- api/movescript.php + reorderarray.php: use vv_write_conf_raw (atomic) + vv_push_master_conf
- api/snapshot.php: share /tmp/vv_cpu_stat.json with vv_cpu_per_core() instead of own state file
Correctness:
- vv_cpu_per_core() and vv_network_stats(): atomic tmp+rename for state files (concurrent poll safety)
- ext_ip curl cache moved from /tmp/vv_ext_ip.cache to vv_cache_read/write (canonical cache dir)
- monitor_remote.php + board.php + snapshot.php: all use vv_cache_read/write instead of ad-hoc /tmp files
HTTP method guards added to write-only APIs that were missing them:
- api/scheduler.php, conf_toggle.php, flag_toggle.php
Split slow cached monitor endpoint from the live stats. monitor_fast.php
reads /proc/stat, /proc/meminfo, ZFS arcstats, and /proc/net/dev directly
— no cache wrapper, 87ms response. Docker/vm/swap pulled from last full
cache so mem card stays complete. Full monitor poll stays at 2s for
everything else (GPU, containers, storage, etc).
unRAID API rejects underscores and hyphens in key names — only letters, numbers,
and spaces allowed. Varaverk_HOST1 / Varaverk_Gmer4Lfe both fail; now uses
"Varaverk <hostname>" (space). Also adds monitor_remote.php, conf_populate.sh,
and conf_sync.sh from dev branch that were missing from production.
New span-1 card shows global RSYNC_ENABLED gate, per-window badges (C/D/I/W),
active profile names + elapsed time from lock files, and last-sync timestamp
per orchestrator window from script log files.
GPU shrunk from span 2 to span 1 to make room. Row 3 is now:
Rsync(1) | GPU(1) | Transcode(2) | Streams(4)
Adds vv_disk_io_rates() — snapshots /proc/diskstats each poll and
computes per-device MB/s. Exposed as disk_io in the monitor API.
JS:
- Per-disk rows: ↓read ↑write inline next to disk name (green/amber, hidden when idle)
- Pool rows: aggregate pool I/O next to pool name
- Array header: total array I/O in the subtitle line
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move SCRIPTS_DIR/DATA_DIR/STATE_DIR from appdata to /boot/config/plugins/varaverk
- All state files now in STATE_DIR (no more /tmp or /boot/config root writes)
- Bootstrap: Gitea-first clone with GitHub fallback, no array dependency
- varaverk.cfg seeded with Gitea connection settings
- .gitignore: add State_Files/, varaverk.cfg, varaverk-*.txz
- Partnership/transcode/fallback scripts use STATE_DIR variables
- PHP config.php: DATA_DIR/STATE_DIR constants, VV_SETUP_STATE_FILE dynamic
- deploy.sh PROD_ROOT updated to plugin flash dir
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Varaverk.page: also shows wizard when local host.conf is missing
(handles master.conf pushed by HOST1 before HOST2 installs plugin).
pages/setup.php: three wizard flows
- Standard: blank master.conf, fill hostnames, redirect to scheduler
- Host2/state file: state file detected, pull master.conf from HOST1 via SSH
- Conf-only: master.conf already filled (was pushed), just create local host.conf
api/setup.php:
- save action: writes master.conf + host.conf, creates varaverk_setup.db state file,
redirects to ?tab=scheduler&vv_setup=master.conf
- pull action: resolves HOST1 Tailscale IP, queries HOST1 SCRIPTS_DIR, SCPs
master.conf, creates local host.conf, redirects to ?tab=scheduler&vv_setup=hostN.conf
include/config.php: vv_setup_state_read/write/push helpers.
vv_push_setup_state() pushes varaverk_setup.db to /boot/config/ on all known
remotes — no plugin-readiness probe needed (flash is always accessible).
api/rawconf.php: calls vv_push_setup_state() alongside master.conf push.
pages/scheduler.php: setup mode via ?vv_setup=<conf> URL param.
Auto-opens the specified conf file on page load (DOMContentLoaded).
vvSaveRawConf: in setup mode, skips confirm dialog and forces sequence:
master.conf save → auto-open hostN.conf
hostN.conf save → redirect to Monitor (setup complete)
Plugin/varaverk.plg: URL updated to github.com/FailedProxy/Varaverk
Configurations/host.conf.template:
Full host conf structure with HOSTN/hostn placeholders.
All personal values blank, all sections documented.
Covers identity, rsync, docker, fallback, media, monitors,
transcodes, arrs, system watchdog, resource manager.
Varaverk.page: checks if HOST1 is blank before rendering tabs.
If blank → shows setup wizard, returns early (tabs never render).
pages/setup.php: first-run wizard UI.
Auto-populates hostname from hostname -s.
Role selection: primary (HOST1) or partner (HOST2+).
Partner slot selector for HOST3+.
api/setup.php: handles wizard form POST.
Writes HOST1/HOST2 (and HOST3+) into master.conf preserving all
other content. Creates host*.conf from template with HOSTN/hostn
replaced and SSH key path pre-filled from hostname convention.
Never overwrites an existing host*.conf.
vv_push_master_conf() in config.php: finds all remote HOST* entries,
resolves Tailscale IPs, SCPs master.conf to each.
rawconf.php calls it after a successful write when file=master.conf.
Scheduler save button shows sync status: "✓ Saved · synced to HOST2"
or "✓ Saved · push failed: HOST2" on error.
Also fix path bug in partnership_transfer.sh — was SCPing to
appdata root instead of Configurations/.
- Tools and Custom: args input always visible (translucent at rest, full on hover)
- Orch children: args input hidden when orch is on, shown when orch is off
(same toggle as the cron field — orch off = standalone mode)
- vvRunJob/vvDryRun: pick up .vv-script-args value, POST as extra_args
- run.php / dryrun.php: accept extra_args, validate against shell metacharacters,
append to run_job.sh invocation (flows through SCRIPT_ARGS to the script)
- CSS: .vv-script-args — 30% opacity at rest, 100% on hover/focus
- New Docker tab (between Scheduler and Watchdog)
- Reads /boot/config/plugins/folder.view3/docker.json — fully compatible with folder.view3 plugin
- Writes changes to both docker.json AND HOST*_DOCKER_FOLDER_MAP in host*.conf simultaneously
- Edit mode: rename folders (inline input), delete folders, move containers via popover picker, create new folders
- Drift banner: highlights containers where conf desired state doesn't match json actual state
- Sync conf→JSON: apply conf desired state to json (fixes drift after onboard)
- Sync JSON→conf: capture manual json edits back into conf
- Ungrouped section shows all containers not assigned to any folder
- Onboard scripts can read HOST*_DOCKER_FOLDER_MAP to auto-place new containers