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.
209 lines
10 KiB
Markdown
209 lines
10 KiB
Markdown
# ━━━━━ PLUGIN / UNRAID ━━━━━
|
|
|
|
The Unraid platform layer. Everything in this folder exists to translate one specific
|
|
operating system into the vocabulary Varaverk speaks — and to put a face on it in the Unraid
|
|
WebGUI.
|
|
|
|
Varaverk itself knows nothing about Unraid. No script outside this folder references
|
|
`/etc/rc.d`, `/boot/config/plugins/dynamix`, `emhttp`, the mover, or a `.page` file. They call
|
|
`platform_*()` and read `$STATE_DIR`; this folder decides what those mean on Unraid.
|
|
|
|
```
|
|
adapter.sh 24 platform_*() functions — the entire OS contract
|
|
Varaverk.page WebGUI menu entry (Tasks:95)
|
|
VaraverkSettings.page WebGUI settings entry
|
|
event/ Unraid array lifecycle hooks
|
|
pages/ api/ include/ the three-layer web UI
|
|
System_Essentials/ Unraid-specific system scripts
|
|
Tools/ Unraid-specific operator tools
|
|
Watchdogs/System/ Unraid-specific watchdog (WebGUI health)
|
|
Partnership/ Unraid-specific container deploy/cleanup
|
|
```
|
|
|
|
> Swapping platforms means writing `Plugin/truenas/adapter.sh` implementing the same 24
|
|
> function names. `load_config.sh` sources `Plugin/$PLATFORM/adapter.sh` — nothing else in
|
|
> the codebase changes.
|
|
|
|
---
|
|
|
|
## ━━━ THE PROBLEM THAT BUILT THIS ━━━
|
|
|
|
**Every NAS OS Answers the Same Questions Differently**
|
|
"Is the array mounted?" "Restart this service." "What are the disk temperature thresholds?"
|
|
"Send a notification." Every platform has an answer, and every answer is different — a
|
|
different path, a different init system, a different config format.
|
|
|
|
Scattering those differences through 115 scripts would mean every script carrying an `if
|
|
unraid ... elif truenas` branch, and every new platform touching all of them.
|
|
|
|
**Varaverk Vars Are Not OS Vars**
|
|
Varaverk works in terms of `STATE_DIR`, `SCRIPTS_DIR`, `DOCKER_APPDATA_BASE`,
|
|
`WATCHDOG_CONTAINERS`. Unraid works in terms of `/boot/config/plugins/`, `emhttp`,
|
|
`/mnt/user`, dockerMan XML templates, and `dynamix.cfg`. Something has to map one onto the
|
|
other, in exactly one place, or the two vocabularies leak into each other.
|
|
|
|
**A Headless Ecosystem Is Hard to Trust**
|
|
115 scripts running unattended produce a lot of state nobody can see. Without a UI the only
|
|
way to know what the watchdogs did last night is to read logs. The WebGUI exists so the
|
|
system can be inspected without SSH.
|
|
|
|
---
|
|
|
|
## ━━━ THE ADAPTER — WHERE OS MEETS VARAVERK ━━━
|
|
|
|
`adapter.sh` is the whole contract. 24 functions, sourced by `load_config.sh` after
|
|
`common.sh`, so every script in the ecosystem has them.
|
|
|
|
The most-called ones, by usage across the repo:
|
|
|
|
| Function | Calls | What it hides |
|
|
|----------|-------|---------------|
|
|
| `platform_require_cmd` | 28 | Verifying a platform binary exists and is executable |
|
|
| `platform_restart_service` | 13 | `/etc/rc.d/rc.<name>` init scripts |
|
|
| `platform_is_service_running` | 13 | Unraid's service process conventions |
|
|
| `platform_is_mover_running` | 10 | The Unraid mover — no other OS has one |
|
|
| `platform_get_os_version` | 10 | Where the version string lives |
|
|
| `platform_setup_db_path` | 9 | Where wizard/setup state persists |
|
|
| `platform_storage_path` | 7 | `/mnt/user` as the user-share root |
|
|
|
|
Plus: disk states and temperature thresholds from `dynamix.cfg`, parity/maintenance detection,
|
|
native notifications through the dynamix notify script, container rebuild from dockerMan XML
|
|
templates, and the WebGUI install path.
|
|
|
|
**The contract is strict, and it is what makes the layer work:**
|
|
|
|
- **Every function returns 0/1 and never calls `exit`.** A missing capability is information
|
|
the caller needs, not a decision the adapter makes — a watchdog may want to skip where an
|
|
installer wants to abort.
|
|
- **Value-producing functions write to stdout**, captured with `$()`. Status travels in the
|
|
exit code. Keeping those separate is what lets callers use them in conditionals.
|
|
- **It reports; it does not remediate.** No retries, no escalation, no notification policy.
|
|
Burying those here would make identical calls behave differently per platform.
|
|
|
|
---
|
|
|
|
## ━━━ THE WEB UI — THREE LAYERS ━━━
|
|
|
|
```
|
|
pages/*.php what you look at ── polls ─────► api/
|
|
api/*.php what the page fetches ── requires ──► include/
|
|
include/*.php the actual logic — vv_*() functions
|
|
```
|
|
|
|
**Most pages hold no logic at all.** Eight of the eleven are pure view — markup, CSS and a
|
|
poll loop, with every value arriving as JSON from `api/`. Only `auth.php`, `monitor.php` and
|
|
`scheduler.php` require an `include/` file directly, and then only to server-render their
|
|
initial state; their mutations still go through endpoints.
|
|
|
|
That is why the layer split holds: a page and its endpoint cannot disagree about what a value
|
|
means, because the page does not compute it. Adding a metric means adding it in `include/`
|
|
once, and the endpoint, the cache writer, and the page all pick it up together.
|
|
|
|
| Layer | Files | Role |
|
|
|-------|-------|------|
|
|
| `pages/` | 11 | One per WebGUI tab — monitor, docker, arrs, fallback, watchdog, rsync, partnership, scheduler, auth, settings, setup |
|
|
| `api/` | 50 | JSON endpoints the pages poll, plus action endpoints (run a script, stop a job, toggle a flag) |
|
|
| `include/` | 16 | Shared builders and helpers — `vv_monitor_*`, `vv_arrs_*`, `vv_docker_*`, config read/write, auth |
|
|
|
|
**Caching.** Several endpoints serve from `/tmp/vv_cache` (tmpfs) rather than hitting live
|
|
APIs on every page view, refreshed by `Tools/api_cache_writer.sh`. `?live=1` bypasses the
|
|
cache. A missing or unparseable cache always falls back to a live call, so the cache can never
|
|
be the reason a page fails to load.
|
|
|
|
---
|
|
|
|
## ━━━ WHAT GUARDS THE API LAYER ━━━
|
|
|
|
Every endpoint under `api/` is protected by exactly one thing: **the Unraid WebGUI session.**
|
|
Anything that can reach `/plugins/varaverk/api/*.php` with a valid session can do everything
|
|
this plugin can do — stop the array, power off the host, write `master.conf` and push it to
|
|
every partner, create an lldap user, write and schedule a root-run script.
|
|
|
|
That is the same trust level as the rest of the WebGUI, and it is the intended model. It is
|
|
written down here because two things about it are easy to assume and wrong.
|
|
|
|
**No endpoint validates a CSRF token.** `pages/partnership.php` and `pages/scheduler.php` send
|
|
Unraid's `csrf_token` with their POSTs, which reads like the token is checked somewhere. It is
|
|
not — no file in `api/` looks at it. A request that arrives with a logged-in session cookie is
|
|
honoured whatever caused the browser to send it. Adding validation is a worthwhile hardening
|
|
pass, but it is a real change: every caller has to send the token before any endpoint requires
|
|
it, or the UI breaks silently on whichever page was missed. Do it deliberately, in one pass,
|
|
with the pages open — not opportunistically while touching one endpoint.
|
|
|
|
**`api/webhook.php` is the exception that authenticates nothing at all.** It exists to receive
|
|
Sonarr/Radarr/Lidarr download events, which arrive from a container rather than a browser.
|
|
`master.conf` carries a `WEBHOOK_SECRET` and the standalone Node listener on `WEBHOOK_PORT`
|
|
validates it — this WebGUI-hosted path does not. Injection is not the risk (the path is
|
|
validated and escaped); triggering work is. Closing it means adding a secret check here **and**
|
|
updating the webhook URL in each arr's settings, in that order.
|
|
|
|
Two further endpoints are worth knowing about because they read wider than the rest:
|
|
`api/api_test.php` returns an API key prefix and the live GraphQL schema, and
|
|
`api/import_script.php`'s browse action and `api/manual_sync.php`'s browse actions list
|
|
directory names anywhere on either host. All three are read-only and none return file contents,
|
|
but they are the ones to look at first if the session boundary ever moves.
|
|
|
|
---
|
|
|
|
## ━━━ ARRAY LIFECYCLE HOOKS ━━━
|
|
|
|
`event/` plugs Varaverk into Unraid's own array lifecycle. These are how the ecosystem starts
|
|
and stops with the array rather than with the OS.
|
|
|
|
| Hook | Fires | Runs |
|
|
|------|-------|------|
|
|
| `disks_mounted/array_start_jobs` | Array started | `Orchestrators/array_started.sh` → `ARRAY_START_SCRIPTS` |
|
|
| `disks_mounted/rebuild_cron` | Array started | Regenerates `varaverk.cron` from the scheduler config |
|
|
| `unmounting_disks/array_stop_jobs` | Array stopping | `Orchestrators/array_stopping.sh` → `ARRAY_STOP_SCRIPTS` |
|
|
|
|
Array-start rather than boot is the correct trigger: in flash storage mode `SCRIPTS_DIR` lives
|
|
in appdata, which does not exist until the array mounts.
|
|
|
|
---
|
|
|
|
## ━━━ THE PLATFORM-SPECIFIC SCRIPT MIRRORS ━━━
|
|
|
|
These folders mirror the top-level Varaverk structure, but hold work that **cannot** be
|
|
platform-agnostic. A script lives here rather than in the root folder when it manipulates
|
|
Unraid itself.
|
|
|
|
| Folder | Contents | Why it is platform-specific |
|
|
|--------|----------|----------------------------|
|
|
| `System_Essentials/` | 4 scripts | Mover, User Scripts plugin, PHP-FPM pool, unraid-api registry — all Unraid subsystems |
|
|
| `Tools/` | 5 files | Share `.cfg` recreation, storage-mode migration, API cache writers |
|
|
| `Watchdogs/System/` | `webgui_watchdog.sh` | emhttp / nginx / php-fpm health — there is no generic "WebGUI" |
|
|
| `Partnership/` | `containers.sh` | Container deploy from dockerMan CA XML templates |
|
|
|
|
Each has its own README where the group is large enough to need one; single-script folders are
|
|
documented in the script header.
|
|
|
|
---
|
|
|
|
## ━━━ HOW THIS FOLDER IS INSTALLED ━━━
|
|
|
|
Two paths, deliberately different:
|
|
|
|
**Development** — `Plugin/plugin_setup.sh` symlinks this directory into the WebGUI plugin
|
|
location. Every edit is live immediately. This is the normal working mode.
|
|
|
|
**Release** — `Plugin/build.sh` packages this directory into a Slackware `.txz` and rewrites
|
|
the version and checksum in `varaverk.plg`. That is what Unraid reinstalls from flash on every
|
|
boot.
|
|
|
|
In flash storage mode `git_pull_execute.sh` syncs `Plugin/` back to `/boot/` after each pull,
|
|
so the WebGUI always serves current PHP even though `SCRIPTS_DIR` points at appdata.
|
|
|
|
---
|
|
|
|
## ━━━ ADDING A NEW PLATFORM ━━━
|
|
|
|
1. Create `Plugin/<platform>/adapter.sh` implementing all 24 `platform_*()` functions.
|
|
2. Set `PLATFORM=<platform>` in `varaverk.cfg`.
|
|
|
|
That is the whole contract for the script ecosystem. `load_config.sh` sources
|
|
`Plugin/$PLATFORM/adapter.sh` and every script keeps working unchanged.
|
|
|
|
The WebGUI is separate and optional — it is Unraid-specific by nature (`.page` files, emhttp
|
|
conventions). A new platform can run the entire ecosystem headless with only the adapter, and
|
|
add a UI later in whatever form that platform expects.
|