1	# ━━━━━ TOOLS — Manual ━━━━━
2
3	Configuration reference, usage procedures, and field guides for every script
4	in `Tools/`. Run any script with `--status` first — it shows current state before
5	making any changes.
6
7	---
8
9	## ━━━ CONTENTS ━━━
10
11	- [emby_to_lidarr_sync.sh](#emby_to_lidarr_syncsh)
12	- [emby_to_sonarr_sync.sh](#emby_to_sonarr_syncsh)
13	- [emby_to_radarr_sync.sh](#emby_to_radarr_syncsh)
14	- [fallback_state_reset.sh](#fallback_state_resetsh) *(not yet built — manual workaround)*
15	- [watchdog_skip_list_manager.sh](#watchdog_skip_list_managersh)
16	- [bulk_permissions_repair.sh](#bulk_permissions_repairsh)
17	- [container_data_export.sh](#container_data_exportsh)
18	- [emby_database_repair.sh](#emby_database_repairsh)
19	- [zfs_pool_scrub.sh](#zfs_pool_scrubsh)
20	- [recreate_shares.sh](#recreate_sharessh)

# ━━━━━ TOOLS — Manual ━━━━━

Configuration reference, usage procedures, and field guides for every script
in `Tools/`. Run any script with `--status` first — it shows current state before
making any changes.

---

## ━━━ CONTENTS ━━━

- [emby_to_lidarr_sync.sh](#emby_to_lidarr_syncsh)
- [emby_to_sonarr_sync.sh](#emby_to_sonarr_syncsh)
- [emby_to_radarr_sync.sh](#emby_to_radarr_syncsh)
- [fallback_state_reset.sh](#fallback_state_resetsh) *(not yet built — manual workaround)*
- [watchdog_skip_list_manager.sh](#watchdog_skip_list_managersh)
- [bulk_permissions_repair.sh](#bulk_permissions_repairsh)
- [container_data_export.sh](#container_data_exportsh)
- [emby_database_repair.sh](#emby_database_repairsh)
- [zfs_pool_scrub.sh](#zfs_pool_scrubsh)
- [recreate_shares.sh](#recreate_sharessh)


lets use the updated headers and check the read mes and manuals to see if corrections are needed


Monitor media server sync and backup health


20	- [recreate_shares.sh](#recreate_sharessh)
21	- [continuous_scripts_status.sh](#continuous_scripts_statussh)
22	- [claude_startup.sh](#claude_startupsh)
23	- [ramdisk_stop.sh](#ramdisk_stopsh)
24	- [Adding a New Tool](#adding-a-new-tool)
25
26	---
27
28	## Output Tiers
29

- [recreate_shares.sh](#recreate_sharessh)
- [continuous_scripts_status.sh](#continuous_scripts_statussh)
- [claude_startup.sh](#claude_startupsh)
- [ramdisk_stop.sh](#ramdisk_stopsh)
- [Adding a New Tool](#adding-a-new-tool)

---

## Output Tiers


Edit

- [fallback_state_reset.sh](#fallback_state_resetsh) *(not yet built — manual workaround)*

- [fallback_state_reset.sh](#fallback_state_resetsh)
- [docker_prune_images.sh](#docker_prune_imagessh)


The file /boot/config/plugins/varaverk/Tools/Manual-Tools.md has been updated successfully. (file state is current in your context — no need to Read it back)

- [fallback_state_reset.sh](#fallback_state_resetsh)
- [docker_prune_images.sh](#docker_prune_imagessh)

-- [fallback_state_reset.sh](#fallback_state_resetsh) *(not yet built — manual workaround)*


Now rewrite the fallback_state_reset section:


## fallback_state_reset.sh

> **Not yet built.** Use the manual workaround below.

Planned: reset the fallback state file to NORMAL and clear all tier flags. State file
only — does NOT start or stop any containers.

### When to Use

```
After fallback_test.sh didn't complete cleanly
  → state left in FALLBACK but containers are actually back to normal

After a failed handback
  → state shows FALLBACK but remote is back up and containers are split

After killing fallback.sh directly (not gracefully via SIGTERM)
  → state is unknown, cycle was interrupted mid-operation

After a dev/debug session
  → state left in a non-NORMAL state from testing
```

### Manual Workaround

```bash
# Verify before resetting:

# Right containers on right server?
continuous_scripts_status.sh        # shows fallback current state

# DDNS pointing correctly?
nslookup Gmer4Lfe.com               # confirm it resolves to the right IP

# fallback.sh not running?
pgrep -f "fallback.sh"              # empty output = not running

## fallback_state_reset.sh

Resets the fallback state file to NORMAL and clears all tier flags. State file only —
does NOT start or stop any containers. After reset, fallback.sh resumes from NORMAL on
its next cycle.

**Only run after verifying the stack is actually in a normal state** — right containers
on the right server, DDNS correct, no active fallback in progress. Resetting state during
a real fallback causes fallback.sh to stop covering the remote until the next detection cycle.

### When to Use

```
After fallback_test.sh didn't complete cleanly
  → state left in FALLBACK but containers are actually back to normal

After a failed handback
  → state shows FALLBACK but remote is back up and containers are split

After killing fallback.sh directly (not gracefully via SIGTERM)
  → state is unknown, cycle was interrupted mid-operation

After a dev/debug session
  → state left in a non-NORMAL state from testing
```

### Usage

```bash
fallback_state_reset.sh             # show current state, prompt for YES before resetting
fallback_state_reset.sh --status    # show current state file contents only
fallback_state_reset.sh --dry-run   # show what the new state file would contain, no write
fallback_state_reset.sh --force     # reset without confirmation prompt (for scripted use)
```

### Verify Before Resetting

```bash
# Right containers on right server?
continuous_scripts_status.sh        # shows fallback current state

# DDNS pointing correctly?
nslookup Gmer4Lfe.com               # confirm it resolves to the right IP

# fallback.sh not running?
pgrep -f "fallback.sh"              # empty output = not running


## fallback_state_reset.sh

> **Not yet built.** Use the manual workaround below.

Planned: reset the fallback state file to NORMAL and clear all tier flags. State file
only — does NOT start or stop any containers.

### When to Use

```
After fallback_test.sh didn't complete cleanly
  → state left in FALLBACK but containers are actually back to normal

After a failed handback
  → state shows FALLBACK but remote is back up and containers are split

After killing fallback.sh directly (not gracefully via SIGTERM)
  → state is unknown, cycle was interrupted mid-operation

After a dev/debug session
  → state left in a non-NORMAL state from testing
```

### Manual Workaround

```bash
# Verify before resetting:

# Right containers on right server?
continuous_scripts_status.sh        # shows fallback current state

# DDNS pointing correctly?
nslookup Gmer4Lfe.com               # confirm it resolves to the right IP

# fallback.sh not running?
pgrep -f "fallback.sh"              # empty output = not running

## fallback_state_reset.sh

Resets the fallback state file to NORMAL and clears all tier flags. State file only —
does NOT start or stop any containers. After reset, fallback.sh resumes from NORMAL on
its next cycle.

**Only run after verifying the stack is actually in a normal state** — right containers
on the right server, DDNS correct, no active fallback in progress. Resetting state during
a real fallback causes fallback.sh to stop covering the remote until the next detection cycle.

### When to Use

```
After fallback_test.sh didn't complete cleanly
  → state left in FALLBACK but containers are actually back to normal

After a failed handback
  → state shows FALLBACK but remote is back up and containers are split

After killing fallback.sh directly (not gracefully via SIGTERM)
  → state is unknown, cycle was interrupted mid-operation

After a dev/debug session
  → state left in a non-NORMAL state from testing
```

### Usage

```bash
fallback_state_reset.sh             # show current state, prompt for YES before resetting
fallback_state_reset.sh --status    # show current state file contents only
fallback_state_reset.sh --dry-run   # show what the new state file would contain, no write
fallback_state_reset.sh --force     # reset without confirmation prompt (for scripted use)
```

### Verify Before Resetting

```bash
# Right containers on right server?
continuous_scripts_status.sh        # shows fallback current state

# DDNS pointing correctly?
nslookup Gmer4Lfe.com               # confirm it resolves to the right IP

# fallback.sh not running?
pgrep -f "fallback.sh"              # empty output = not running

-> **Not yet built.** Use the manual workaround below.

+Resets the fallback state file to NORMAL and clears all tier flags. State file only —

+does NOT start or stop any containers. After reset, fallback.sh resumes from NORMAL on

-Planned: reset the fallback state file to NORMAL and clear all tier flags. State file

-only — does NOT start or stop any containers.

+**Only run after verifying the stack is actually in a normal state** — right containers

+on the right server, DDNS correct, no active fallback in progress. Resetting state during

+a real fallback causes fallback.sh to stop covering the remote until the next detection cycle.

+fallback_state_reset.sh             # show current state, prompt for YES before resetting

+fallback_state_reset.sh --status    # show current state file contents only

+fallback_state_reset.sh --dry-run   # show what the new state file would contain, no write

+fallback_state_reset.sh --force     # reset without confirmation prompt (for scripted use)


Now fix the manual workaround text that follows and the claude_startup section:


188
189	# fallback.sh not running?
190	pgrep -f "fallback.sh"              # empty output = not running
191
192	# Both servers Tailscale connected?
193	tailscale status                    # both hosts should show active
194
195	# Check current state file:
196	cat /boot/config/fallback_state.db
197
198	# Reset to NORMAL (only after confirming containers and DDNS are correct):
199	echo "state=NORMAL" > /boot/config/fallback_state.db
200	```
201
202	Resetting during an actual fallback causes fallback.sh to think everything is normal
203	and stop covering the remote — services go offline until the next detection cycle.
204
205	### What the State File Contains
206
207	```bash
208	state=NORMAL
209	fallback_start=0
210	handback_strikes=0
211	tier2_started=false
212	tier3_started=false
213	tier4_started=false
214	```
215
216	---
217

# fallback.sh not running?
pgrep -f "fallback.sh"              # empty output = not running

# Both servers Tailscale connected?
tailscale status                    # both hosts should show active

# Check current state file:
cat /boot/config/fallback_state.db

# Reset to NORMAL (only after confirming containers and DDNS are correct):
echo "state=NORMAL" > /boot/config/fallback_state.db
```

Resetting during an actual fallback causes fallback.sh to think everything is normal
and stop covering the remote — services go offline until the next detection cycle.

### What the State File Contains

```bash
state=NORMAL
fallback_start=0
handback_strikes=0
tier2_started=false
tier3_started=false
tier4_started=false
```

---


# Both servers Tailscale connected?
tailscale status                    # both hosts should show active

# Check current state file:
cat /boot/config/fallback_state.db

# Reset to NORMAL (only after confirming containers and DDNS are correct):
echo "state=NORMAL" > /boot/config/fallback_state.db
```

Resetting during an actual fallback causes fallback.sh to think everything is normal
and stop covering the remote — services go offline until the next detection cycle.

### What the State File Contains

```bash
state=NORMAL
fallback_start=0
handback_strikes=0
tier2_started=false
tier3_started=false
tier4_started=false
```

# Both servers Tailscale connected?
tailscale status                    # both hosts should show active
```

### What the State File Contains

```bash
state=NORMAL
fallback_start=0
handback_strikes=0
tier2_started=false
tier3_started=false
tier4_started=false
```


# Both servers Tailscale connected?
tailscale status                    # both hosts should show active

# Check current state file:
cat /boot/config/fallback_state.db

# Reset to NORMAL (only after confirming containers and DDNS are correct):
echo "state=NORMAL" > /boot/config/fallback_state.db
```

Resetting during an actual fallback causes fallback.sh to think everything is normal
and stop covering the remote — services go offline until the next detection cycle.

### What the State File Contains

```bash
state=NORMAL
fallback_start=0
handback_strikes=0
tier2_started=false
tier3_started=false
tier4_started=false
```

# Both servers Tailscale connected?
tailscale status                    # both hosts should show active
```

### What the State File Contains

```bash
state=NORMAL
fallback_start=0
handback_strikes=0
tier2_started=false
tier3_started=false
tier4_started=false
```

-# Reset to NORMAL (only after confirming containers and DDNS are correct):

-echo "state=NORMAL" > /boot/config/fallback_state.db

-Resetting during an actual fallback causes fallback.sh to think everything is normal

-and stop covering the remote — services go offline until the next detection cycle.


Now fix the claude_startup section and add docker_prune_images:


### Calling from array_started.sh

To auto-restore Claude data on every boot without launching an interactive session:

```bash
# In /boot/config/go or array_started.sh:
/path/to/Tools/claude_startup.sh --setup
```

### Usage

```bash
claude_startup.sh           # set up persistent symlinks and launch Claude
claude_startup.sh --setup   # set up symlinks only — no launch (for array_started.sh)
```

### Calling from array_started.sh

`array_started.sh` calls `claude_startup.sh` directly (no flags). This sets up the
symlinks only — no interactive session is launched. That is the default behavior.

### Usage

```bash
claude_startup.sh           # set up persistent symlinks only (default — used by array_started.sh)
claude_startup.sh --launch  # set up symlinks and launch Claude interactively
```


### Calling from array_started.sh

To auto-restore Claude data on every boot without launching an interactive session:

```bash
# In /boot/config/go or array_started.sh:
/path/to/Tools/claude_startup.sh --setup
```

### Usage

```bash
claude_startup.sh           # set up persistent symlinks and launch Claude
claude_startup.sh --setup   # set up symlinks only — no launch (for array_started.sh)
```

### Calling from array_started.sh

`array_started.sh` calls `claude_startup.sh` directly (no flags). This sets up the
symlinks only — no interactive session is launched. That is the default behavior.

### Usage

```bash
claude_startup.sh           # set up persistent symlinks only (default — used by array_started.sh)
claude_startup.sh --launch  # set up symlinks and launch Claude interactively
```

-To auto-restore Claude data on every boot without launching an interactive session:

+`array_started.sh` calls `claude_startup.sh` directly (no flags). This sets up the

+symlinks only — no interactive session is launched. That is the default behavior.

-# In /boot/config/go or array_started.sh:

-claude_startup.sh           # set up persistent symlinks and launch Claude

-claude_startup.sh --setup   # set up symlinks only — no launch (for array_started.sh)

+claude_startup.sh           # set up persistent symlinks only (default — used by array_started.sh)

+claude_startup.sh --launch  # set up symlinks and launch Claude interactively


Now add docker_prune_images.sh section to Manual-Tools. First find where to insert:


Bash

grep -n "Adding a New Tool\|## ramdisk_stop\|## watchdog_skip" /boot/config/plugins/varaverk/Tools/Manual-Tools.md | head -5

Find insertion point for docker_prune_images section


25:- [Adding a New Tool](#adding-a-new-tool)
209:## watchdog_skip_list_manager.sh
631:## ramdisk_stop.sh
698:## Adding a New Tool


694	```
695
696	---
697
698	## Adding a New Tool
699
700	Write the tool when you solve a problem manually with bash commands. You'll face it again.
701	The cost of writing the tool is 30 minutes. The cost of reconstructing the commands at 2am
702	is much higher.
703

```

---

## Adding a New Tool

Write the tool when you solve a problem manually with bash commands. You'll face it again.
The cost of writing the tool is 30 minutes. The cost of reconstructing the commands at 2am
is much higher.


Edit

## docker_prune_images.sh

Removes orphaned Docker images that accumulate after container updates. Two modes:

**Default (dangling only)** — removes untagged images (no name, no container reference).
Safe — running containers are never affected. Use routinely after update cycles.

**`--all` (full orphan cleanup)** — first removes stopped/exited containers, then removes
all images not used by any running container. Use when you've removed apps and want to
recover the disk space. CAUTION: also removes intentionally stopped containers.

### Usage

```bash
docker_prune_images.sh           # remove dangling (untagged) images only
docker_prune_images.sh --all     # remove stopped containers, then all unused images
docker_prune_images.sh --dry-run # show what would be removed without making changes
docker_prune_images.sh --status  # show dangling images and stopped containers
```

---

## Adding a New Tool


## docker_prune_images.sh

Removes orphaned Docker images that accumulate after container updates. Two modes:

**Default (dangling only)** — removes untagged images (no name, no container reference).
Safe — running containers are never affected. Use routinely after update cycles.

**`--all` (full orphan cleanup)** — first removes stopped/exited containers, then removes
all images not used by any running container. Use when you've removed apps and want to
recover the disk space. CAUTION: also removes intentionally stopped containers.

### Usage

```bash
docker_prune_images.sh           # remove dangling (untagged) images only
docker_prune_images.sh --all     # remove stopped containers, then all unused images
docker_prune_images.sh --dry-run # show what would be removed without making changes
docker_prune_images.sh --status  # show dangling images and stopped containers
```

---

## Adding a New Tool

+Removes orphaned Docker images that accumulate after container updates. Two modes:

+**Default (dangling only)** — removes untagged images (no name, no container reference).

+Safe — running containers are never affected. Use routinely after update cycles.

+**`--all` (full orphan cleanup)** — first removes stopped/exited containers, then removes

+all images not used by any running container. Use when you've removed apps and want to

+recover the disk space. CAUTION: also removes intentionally stopped containers.

+docker_prune_images.sh           # remove dangling (untagged) images only

+docker_prune_images.sh --all     # remove stopped containers, then all unused images

+docker_prune_images.sh --dry-run # show what would be removed without making changes

+docker_prune_images.sh --status  # show dangling images and stopped containers

Write the tool when you solve a problem manually with bash commands. You'll face it again.


Now update README-Tools.md to add docker_prune_images:


| `claude_startup.sh` | Claude Code session setup after reboot — symlinks persistent storage | After each unRAID reboot, or called by array_started.sh |

| `claude_startup.sh` | Claude Code session setup after reboot — symlinks persistent storage | After each unRAID reboot, or called by array_started.sh |
| `docker_prune_images.sh` | Remove dangling or unused Docker images accumulated after updates | After update cycles, or when disk space is low |


The file /boot/config/plugins/varaverk/Tools/README-Tools.md has been updated successfully. (file state is current in your context — no need to Read it back)

| `claude_startup.sh` | Claude Code session setup after reboot — symlinks persistent storage | After each unRAID reboot, or called by array_started.sh |
| `docker_prune_images.sh` | Remove dangling or unused Docker images accumulated after updates | After update cycles, or when disk space is low |

# ━━━━━ TOOLS ━━━━━

**Situational utilities — run when something needs fixing, not on a schedule.**
Recovery, repair, migration, and inspection tools for situations that arise outside
the scheduled maintenance model. These scripts sit ready for the moment you need them.

> **None of these scripts run on a schedule.** A script belongs here when it solves
> a specific operational situation — something you run in response to a problem, before
> a risky operation, or during a one-time setup task. Having a dedicated folder keeps
> the other folders clean and makes it obvious what runs routinely vs. situationally.

---

## ━━━ THE PROBLEM THAT BUILT THIS ━━━

**Fallback State Stuck After Testing**
Run a fallback test, something exits uncleanly, state file shows `FALLBACK`.
`fallback.sh` resumes and reads FALLBACK — starts containers it shouldn't, makes
decisions based on a state that doesn't reflect reality. Manual recovery means
knowing the exact file format and every field to reset. At 2am after a failed test.
Fix: `fallback_state_reset.sh` — shows current state, prompts for confirmation,
resets cleanly to NORMAL.

**Container Stuck on Watchdog Skip List After Fixing the Problem**
Authelia hit the restart loop limit — went on the skip list. Problem fixed. But
the watchdog still isn't monitoring it because the skip list persists on `/boot/config`
across reboots. Where's the file? What format? How do you clear restart history?
Fix: `watchdog_skip_list_manager.sh` — shows the skip list and which containers are
running vs. stopped, clears specific containers with confirmation.

**Emby Crashing With No Clear Cause After a Power Cut**
Server lost power with Emby running. Emby comes back, runs for 20 minutes, crashes.
Logs show database errors. Which database? library.db? users.db? Each has different
recovery implications — deleting the wrong one resets all user watch history.
Fix: `emby_database_repair.sh` — stops Emby, runs `PRAGMA integrity_check` on every
database, reports per-database with specific guidance on what to do about each one.

**Files Owned by Root After an Admin Copy**
`scp` a file into a media share. File arrives as `root:root`. Radarr fails to import —
permission denied. The daily permissions script won't run for another 20 hours. Running
`media_shares_permissions.sh` on the whole library takes 30 minutes just to fix one dir.
Fix: `bulk_permissions_repair.sh` — takes specific paths, applies correct ownership and
permissions in seconds.

**No Way to Back Up a Container Before a Risky Update**
Major version update, changelog says "database migration — no rollback." You want a
point-in-time backup. But `cp -r` while the container is running produces an
inconsistent backup, and tar without stopping the container is equally unreliable.
Fix: `container_data_export.sh` — stops the container cleanly, archives appdata to a
timestamped `.tar.gz`, verifies archive integrity, restarts the container.

**Fresh HOST2 Has Shares Configured But Directories Missing**
Fresh install on HOST2. Restored `/boot/config/shares/` from backup. Array starts.
Shares show in the UI. But the actual `/mnt/diskN/sharename` directories don't exist —
unRAID created the share definitions but not the directories. rsync.sh aborts.
Fix: `recreate_shares.sh` — reads every `.cfg` file, creates directories on each
included disk, places `.recovery` markers so the first rsync won't delete anything.

---

## ━━━ WHAT THIS FOLDER DOES ━━━

One role: hold scripts for situations the scheduled maintenance model can't handle.

Every script here was written because a specific situation arose that required bash
commands to resolve — and that situation is guaranteed to arise again. When you encounter
something new, write the tool. Store it here. Find it at 2am next time.

**Recovery Tools** — Restore known-good state after a failure
`fallback_state_reset.sh`, `watchdog_skip_list_manager.sh`

**Diagnostic Tools** — Inspect and verify before acting
`emby_database_repair.sh`, `continuous_scripts_status.sh`

**Repair Tools** — Fix a specific known problem
`bulk_permissions_repair.sh`, `zfs_pool_scrub.sh`

**Lifecycle Tools** — Backup, setup, and migration support
`container_data_export.sh`, `recreate_shares.sh`, `claude_startup.sh`, `ramdisk_stop.sh`

**Library Sync Bootstrap** — Close the gap between Emby and arr libraries
`emby_to_lidarr_sync.sh`, `emby_to_sonarr_sync.sh`, `emby_to_radarr_sync.sh`

---

## ━━━ RELATIONSHIP TO OTHER FOLDERS ━━━

```
System_Essentials/  ← regular system maintenance    — scheduled
Docker_Essentials/  ← regular container management  — scheduled
Monitors/           ← regular health reporting       — scheduled
Orchestrators/      ← regular maintenance windows   — scheduled
Fallback/           ← automated fallback/handback   — event-driven
Tools/              ← situational utilities          — run when needed
```

Some tools interact with state written by other folders:

```
Fallback/
  fallback.sh ──────── writes FALLBACK_STATE_FILE ──► fallback_state_reset.sh reads/writes it

Docker_Essentials/
  docker_watchdog.sh ── writes skip list + history ──► watchdog_skip_list_manager.sh manages them

Docker_Essentials/ + System_Essentials/ + Fallback/
  All continuous scripts ──────────────────────────► continuous_scripts_status.sh reads their state
```

Tools never call scripts in other folders. Other folders never call Tools scripts.
The relationship is one-way: Tools act on state that other scripts have written.

---

## ━━━ SCRIPTS IN THIS FOLDER ━━━

| Script | What It Fixes | When to Run |
|--------|--------------|-------------|
| `fallback_state_reset.sh` | State file stuck in FALLBACK after test or failed handback | After fallback testing or manual intervention |
| `watchdog_skip_list_manager.sh` | Container stuck on watchdog skip list after fixing root cause | After fixing a container that hit the restart loop limit |
| `bulk_permissions_repair.sh` | Files owned by wrong user after admin copy or bad container config | When arr operations fail due to permissions |
| `container_data_export.sh` | Need a clean backup before a risky container update or migration | Before major updates, appdata migrations, or container removals |
| `emby_database_repair.sh` | Emby crashing with database errors after power loss or crash | When Emby logs show corruption or repeated crashes |
| `zfs_pool_scrub.sh` | Verify ZFS pool integrity — catch silent corruption before it spreads | Monthly, or after any disk or power event |
| `recreate_shares.sh` | Share directories missing after fresh install or disk rebuild | After fresh unRAID install or disk replacement on HOST2 |
| `continuous_scripts_status.sh` | Need a live view of watchdog and fallback state | Any time — manual dashboard, no schedule |
| `claude_startup.sh` | Claude Code session setup after reboot — symlinks persistent storage | After each unRAID reboot, or called by array_started.sh |
| `ramdisk_stop.sh` | Safely stop the transcode ramdisk — redirect symlink to SSD, unmount, update state | Before re-running ramdisk_setup.sh with new size or thresholds |
| `emby_to_lidarr_sync.sh` | Add all Emby album artists not yet tracked in Lidarr | After Lidarr setup, database wipe, or when you suspect gaps |
| `emby_to_sonarr_sync.sh` | Add all Emby TV series not yet tracked in Sonarr | After Sonarr setup, database wipe, or when you suspect gaps |
| `emby_to_radarr_sync.sh` | Add all Emby movies not yet tracked in Radarr | After Radarr setup, database wipe, or when you suspect gaps |

---

## ━━━ HOW THE SCRIPTS RELATE ━━━

All Tools scripts are independent — none call each other, none are called by other Tools.

```
Situation arises
      │
      ▼
┌──────────────────────────────────────────────────────────────────────┐
│  Tools/                          Run directly when needed             │
│                                                                       │
│  fallback_state_reset.sh     ◄── after fallback test / failed handback│
│  watchdog_skip_list_manager  ◄── after fixing a crash-looping container│
│  bulk_permissions_repair     ◄── wrong ownership after copy or rsync  │
│  container_data_export       ◄── before a risky update or migration   │
│  emby_database_repair        ◄── Emby logs show corruption            │
│  zfs_pool_scrub              ◄── monthly integrity check / post-event │
│  recreate_shares             ◄── fresh HOST2 setup or disk rebuild    │
│  continuous_scripts_status   ◄── manual status check at any time     │
│  claude_startup              ◄── after each unRAID reboot             │
│  ramdisk_stop                ◄── before ramdisk resize / remount      │
│                                                                       │
│  emby_to_lidarr_sync         ◄── Lidarr setup / database wipe / gap  │
│  emby_to_sonarr_sync         ◄── Sonarr setup / database wipe / gap  │
│  emby_to_radarr_sync         ◄── Radarr setup / database wipe / gap  │
└──────────────────────────────────────────────────────────────────────┘
      │
      ▼
  State files in other folders (Fallback/, Docker_Essentials/) may be read or written.
  No other scripts call into Tools/.
```

+| `docker_prune_images.sh` | Remove dangling or unused Docker images accumulated after updates | After update cycles, or when disk space is low |


**Media Manual — add play_state_sync --full:**


633	---
634
635	### arrs_failed_stalled_recovery.sh
636
637	`arrs_failed_stalled_recovery.sh`
638	Check all configured arrs for failed imports and stalled downloads. Blocklist + remove +
639	re-search for each problem item.
640
641	`arrs_failed_stalled_recovery.sh --dry-run`
642	Show what would be actioned per arr without making any changes.
643
644	`arrs_failed_stalled_recovery.sh --status`
645	Show configuration, arr reachability, and last recovery stats.
646
647	`arrs_failed_stalled_recovery.sh --log`
648	Verbose output per item per arr.
649
650	---
651
652	### arr_sync.sh
653
654	`arr_sync.sh`
655	Sync all arr types across all configured nodes.
656
657	`arr_sync.sh --dry-run`

---

### arrs_failed_stalled_recovery.sh

`arrs_failed_stalled_recovery.sh`
Check all configured arrs for failed imports and stalled downloads. Blocklist + remove +
re-search for each problem item.

`arrs_failed_stalled_recovery.sh --dry-run`
Show what would be actioned per arr without making any changes.

`arrs_failed_stalled_recovery.sh --status`
Show configuration, arr reachability, and last recovery stats.

`arrs_failed_stalled_recovery.sh --log`
Verbose output per item per arr.

---

### arr_sync.sh

`arr_sync.sh`
Sync all arr types across all configured nodes.

`arr_sync.sh --dry-run`


### play_state_sync.sh

`play_state_sync.sh`
Sync played/unplayed state and resume positions from local Emby to the remote Emby.
Only items played within PLAY_SYNC_DAYS are synced.

`play_state_sync.sh --full`
Ignore PLAY_SYNC_DAYS — sync all played items regardless of age. May be slow on large
libraries. Use after a new Emby install or database restore to rebuild full play history.

`play_state_sync.sh --dry-run`
Show what would be synced without writing any state.

`play_state_sync.sh --status`
Show configured servers, reachability, and user counts.

`play_state_sync.sh --log`
Verbose output — show each item comparison.

---

### arrs_failed_stalled_recovery.sh


The file /boot/config/plugins/varaverk/Media/Manual-Media.md has been updated successfully. (file state is current in your context — no need to Read it back)

### play_state_sync.sh

`play_state_sync.sh`
Sync played/unplayed state and resume positions from local Emby to the remote Emby.
Only items played within PLAY_SYNC_DAYS are synced.

`play_state_sync.sh --full`
Ignore PLAY_SYNC_DAYS — sync all played items regardless of age. May be slow on large
libraries. Use after a new Emby install or database restore to rebuild full play history.

`play_state_sync.sh --dry-run`
Show what would be synced without writing any state.

`play_state_sync.sh --status`
Show configured servers, reachability, and user counts.

`play_state_sync.sh --log`
Verbose output — show each item comparison.

---

### arrs_failed_stalled_recovery.sh

+Sync played/unplayed state and resume positions from local Emby to the remote Emby.

+Only items played within PLAY_SYNC_DAYS are synced.

+Ignore PLAY_SYNC_DAYS — sync all played items regardless of age. May be slow on large

+libraries. Use after a new Emby install or database restore to rebuild full play history.

+Show what would be synced without writing any state.

+Show configured servers, reachability, and user counts.

+Verbose output — show each item comparison.


**Orchestrators README — fix arrs_failed schedule:**


170	| Script | What It Orchestrates | Schedule |
171	|--------|---------------------|----------|
172	| `array_started.sh` | All array startup scripts in order | `array_start` event (Varaverk plugin hook) |
173	| `watchdog_orchestrator.sh` | resource → docker → system → api_renew → stability watchdogs | Every 15 minutes |
174	| `transcode_management.sh` | Cleanup then manager — order critical | Every 7 minutes |
175	| `arrs_failed_stalled_recovery.sh` | Failed import + stalled download recovery | Every 6 hours |
176	| `daily_sync_maintenance.sh` | git pull → sync → media maintenance → restarts | 1am daily |
177	| `weekly_sync_maintenance.sh` | Stop → update → clean sync → start → weekly restarts | 2:30am Sunday |
178	| `monthly_maintenance.sh` | Uptime-triggered heavy tasks — ZFS scrub, SMART tests | Daily check, fires when uptime ≥ 30d |
179	| `intermediate_sync_maintenance.sh` | arr library sync, artwork, failed recovery | Every 4 hours |
180
181	---
182
183	## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
184	## 🚀  array_started.sh

| Script | What It Orchestrates | Schedule |
|--------|---------------------|----------|
| `array_started.sh` | All array startup scripts in order | `array_start` event (Varaverk plugin hook) |
| `watchdog_orchestrator.sh` | resource → docker → system → api_renew → stability watchdogs | Every 15 minutes |
| `transcode_management.sh` | Cleanup then manager — order critical | Every 7 minutes |
| `arrs_failed_stalled_recovery.sh` | Failed import + stalled download recovery | Every 6 hours |
| `daily_sync_maintenance.sh` | git pull → sync → media maintenance → restarts | 1am daily |
| `weekly_sync_maintenance.sh` | Stop → update → clean sync → start → weekly restarts | 2:30am Sunday |
| `monthly_maintenance.sh` | Uptime-triggered heavy tasks — ZFS scrub, SMART tests | Daily check, fires when uptime ≥ 30d |
| `intermediate_sync_maintenance.sh` | arr library sync, artwork, failed recovery | Every 4 hours |

---

## ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## 🚀  array_started.sh


| `arrs_failed_stalled_recovery.sh` | Failed import + stalled download recovery | Every 6 hours |
| `daily_sync_maintenance.sh` | git pull → sync → media maintenance → restarts | 1am daily |
| `weekly_sync_maintenance.sh` | Stop → update → clean sync → start → weekly restarts | 2:30am Sunday |
| `monthly_maintenance.sh` | Uptime-triggered heavy tasks — ZFS scrub, SMART tests | Daily check, fires when uptime ≥ 30d |
| `intermediate_sync_maintenance.sh` | arr library sync, artwork, failed recovery | Every 4 hours |

| `daily_sync_maintenance.sh` | git pull → sync → media maintenance → restarts | 1am daily |
| `weekly_sync_maintenance.sh` | Stop → update → clean sync → start → weekly restarts | 2:30am Sunday |
| `monthly_maintenance.sh` | Uptime-triggered heavy tasks — ZFS scrub, SMART tests | Daily check, fires when uptime ≥ 30d |
| `intermediate_sync_maintenance.sh` | arr sync + arrs_failed_stalled_recovery + optional rsync | Every 4 hours |


The file /boot/config/plugins/varaverk/Orchestrators/README-Orchestrators.md has been updated successfully. (file state is current in your context — no need to Read it back)

| `arrs_failed_stalled_recovery.sh` | Failed import + stalled download recovery | Every 6 hours |
| `daily_sync_maintenance.sh` | git pull → sync → media maintenance → restarts | 1am daily |
| `weekly_sync_maintenance.sh` | Stop → update → clean sync → start → weekly restarts | 2:30am Sunday |
| `monthly_maintenance.sh` | Uptime-triggered heavy tasks — ZFS scrub, SMART tests | Daily check, fires when uptime ≥ 30d |
| `intermediate_sync_maintenance.sh` | arr library sync, artwork, failed recovery | Every 4 hours |

| `daily_sync_maintenance.sh` | git pull → sync → media maintenance → restarts | 1am daily |
| `weekly_sync_maintenance.sh` | Stop → update → clean sync → start → weekly restarts | 2:30am Sunday |
| `monthly_maintenance.sh` | Uptime-triggered heavy tasks — ZFS scrub, SMART tests | Daily check, fires when uptime ≥ 30d |
| `intermediate_sync_maintenance.sh` | arr sync + arrs_failed_stalled_recovery + optional rsync | Every 4 hours |

| `array_started.sh` | All array startup scripts in order | `array_start` event (Varaverk plugin hook) |

| `watchdog_orchestrator.sh` | resource → docker → system → api_renew → stability watchdogs | Every 15 minutes |

| `transcode_management.sh` | Cleanup then manager — order critical | Every 7 minutes |

-| `arrs_failed_stalled_recovery.sh` | Failed import + stalled download recovery | Every 6 hours |

-| `intermediate_sync_maintenance.sh` | arr library sync, artwork, failed recovery | Every 4 hours |

+| `intermediate_sync_maintenance.sh` | arr sync + arrs_failed_stalled_recovery + optional rsync | Every 4 hours |


You've hit your session limit · resets 4:10am (America/New_York)


continue


Bash

grep -n "every 6\|Every 6\|arrs_failed" /boot/config/plugins/varaverk/Orchestrators/README-Orchestrators.md | head -15

Find remaining arrs_failed / every 6 hours references in Orchestrators README


74	## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
75
76	| Script | Role | When It Runs |
77	|--------|------|--------------|
78	| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |
79
80	---
81
82
83	## ━━━ THE PLATFORM ADAPTER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| Script | Role | When It Runs |
|--------|------|--------------|
| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |

---

## ━━━ THE PLATFORM ADAPTER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


74:The fix: `arrs_failed_stalled_recovery.sh` runs every 6 hours. It finds all
178:| `intermediate_sync_maintenance.sh` | arr sync + arrs_failed_stalled_recovery + optional rsync | Every 4 hours |
384:## 🔄  arrs_failed_stalled_recovery.sh
392:# Scheduled: 0 */6 * * *   (every 6 hours)
494:arrs_failed_stalled_recovery.sh              # normal run
495:arrs_failed_stalled_recovery.sh --dry-run    # show what would be actioned, no changes
496:arrs_failed_stalled_recovery.sh --log        # verbose — show each queue item evaluated
497:arrs_failed_stalled_recovery.sh --status     # show arr config and API connectivity


70	until someone manually opens Lidarr, identifies the problem, blocklists the release,
71	and triggers a new search. This takes minutes to do — but nobody does it at 3am
72	when it usually happens.
73
74	The fix: `arrs_failed_stalled_recovery.sh` runs every 6 hours. It finds all
75	`importFailed`, `importPending`, `error`, and `stalled` items, blocklists them, removes
76	them from the queue, and triggers a new search — automatically. By morning the failed
77	import has already been replaced by a working one. No manual intervention required.
78
79	---
80
81	### 🔴  Array Start Scripts Running in Wrong Order or Not at All
82
83	Startup scripts configured individually ran in an unpredictable order. The ramdisk
84	setup might run after Emby starts. The syslog filter might run after containers have

until someone manually opens Lidarr, identifies the problem, blocklists the release,
and triggers a new search. This takes minutes to do — but nobody does it at 3am
when it usually happens.

The fix: `arrs_failed_stalled_recovery.sh` runs every 6 hours. It finds all
`importFailed`, `importPending`, `error`, and `stalled` items, blocklists them, removes
them from the queue, and triggers a new search — automatically. By morning the failed
import has already been replaced by a working one. No manual intervention required.

---

### 🔴  Array Start Scripts Running in Wrong Order or Not at All

Startup scripts configured individually ran in an unpredictable order. The ramdisk
setup might run after Emby starts. The syslog filter might run after containers have


Edit

The fix: `arrs_failed_stalled_recovery.sh` runs every 6 hours. It finds all

The fix: `arrs_failed_stalled_recovery.sh` runs every 4 hours (via `intermediate_sync_maintenance.sh`). It finds all


and triggers a new search. This takes minutes to do — but nobody does it at 3am

-The fix: `arrs_failed_stalled_recovery.sh` runs every 6 hours. It finds all

+The fix: `arrs_failed_stalled_recovery.sh` runs every 4 hours (via `intermediate_sync_maintenance.sh`). It finds all

`importFailed`, `importPending`, `error`, and `stalled` items, blocklists them, removes

them from the queue, and triggers a new search — automatically. By morning the failed

import has already been replaced by a working one. No manual intervention required.


Now the embedded arrs_failed docs block inside the Orchestrators README has `# Scheduled: 0 */6 * * *` at line 392 — that cron expression represents every 6 hours, but it actually runs via intermediate_sync every 4 hours. Fix that and add build.sh to Plugin README:


388	and Lidarr. Blocklists the bad release, removes it from the queue, and triggers a
389	new search — hands-free recovery while you sleep.
390
391	```bash
392	# Scheduled: 0 */6 * * *   (every 6 hours)
393	```
394
395	---
396
397	### ── Four Problem Types ───────────────────────────────────────────────────────
398
399	```bash

and Lidarr. Blocklists the bad release, removes it from the queue, and triggers a
new search — hands-free recovery while you sleep.

```bash
# Scheduled: 0 */6 * * *   (every 6 hours)
```

---

### ── Four Problem Types ───────────────────────────────────────────────────────

```bash


# Scheduled: 0 */6 * * *   (every 6 hours)

# Called by: intermediate_sync_maintenance.sh (every 4 hours)


new search — hands-free recovery while you sleep.

-# Scheduled: 0 */6 * * *   (every 6 hours)

+# Called by: intermediate_sync_maintenance.sh (every 4 hours)


| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |

| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |
| `build.sh` | Packages the plugin as a `.txz` for release | Before publishing a new plugin version |


The file /boot/config/plugins/varaverk/Plugin/README-Plugin.md has been updated successfully. (file state is current in your context — no need to Read it back)

| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |
| `build.sh` | Packages the plugin as a `.txz` for release | Before publishing a new plugin version |

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 🔌  PLUGIN
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

**The Varaverk Unraid plugin — a web UI that wraps the entire script ecosystem.**
Scheduler, Monitor, Docker management, Partnership sync, Fallback state, and Arrs —
all surfaced inside the Unraid web interface as a first-class plugin.

> **Why this folder exists:** The scripts need a control surface. Managing a 50+ container
> homelab ecosystem from terminal windows is friction. The plugin turns configuration files
> into editable forms, cron schedules into a visual scheduler, and runtime log output into
> a live dashboard — without duplicating any of the logic that already lives in common.sh
> and the conf files.

---

## ━━━ THE PROBLEM THAT BUILT THIS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The script ecosystem works well from the command line, but day-to-day operation is not
the command line. Checking whether the nightly sync ran, adjusting a container's watchdog
limit, confirming the partnership fallback is active — all of that requires SSH sessions,
knowing which log files to look at, and remembering which conf variable controls what.

The plugin solves the visibility problem: one URL on any browser, on any device on the
Tailscale network, shows everything running and lets you act on it. No extra tooling,
no separate monitoring stack, no third-party dashboards.

---

## ━━━ WHAT THIS FOLDER CONTAINS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

```
Plugin/
├── dev_install.sh             # One-time developer setup: symlinks plugin into web server
├── Icons/                     # Source icon assets (1024px master files)
└── unraid/                    # The Unraid platform adapter + plugin application
    ├── adapter.sh             # Platform adapter — provides platform_*() API to all scripts
    ├── Varaverk.page          # Main plugin entry point (Tasks menu)
    ├── VaraverkSettings.page  # Unraid Settings → Other Settings entry
    ├── api/                   # PHP API endpoints (called by JS via fetch)
    ├── css/                   # Plugin stylesheet
    ├── event/                 # Unraid event hooks (boot-time cron setup, array lifecycle)
    ├── icons/                 # Plugin icons served by emhttp
    ├── images/                # Plugin images
    ├── include/               # PHP business logic shared across pages
    ├── js/                    # Frontend JavaScript
    ├── pages/                 # Per-tab page includes (monitor, scheduler, docker, ...)
    └── run_job.sh             # Script runner invoked by the Scheduler

# Future platform adapters follow the same structure:
# Plugin/truenas/adapter.sh   — TrueNAS adapter (future)
# Plugin/ubuntu/adapter.sh    — Ubuntu/Debian adapter (future)
```

---

## ━━━ RELATIONSHIP TO THE REST OF THE REPO ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

**Plugin is a wrapper, never a reimplementation.** Every setting the plugin reads or writes
lives in `Configurations/master.conf` or `Configurations/host*.conf` — the same files the
shell scripts read. The plugin has no separate data store. If a conf file changes outside
the plugin (by hand, by SSH), the plugin reflects it on next load.

The one exception is `varaverk.cfg` on flash (`/boot/config/plugins/varaverk/varaverk.cfg`),
which holds a single bootstrap value: `SCRIPTS_DIR`. This is the path the plugin uses to
find the Configurations directory and all scripts. Everything else flows from there.

The plugin also taps `common.sh` indirectly — `include/config.php` mirrors
`resolve_tailscale_ip()` and `detect_host()` exactly, using the same logic as common.sh
so behaviour stays consistent without a shell dependency.

---

## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| Script | Role | When It Runs |
|--------|------|--------------|
| `dev_install.sh` | Symlinks `Plugin/unraid/` into Unraid's web server | Once, manually, after cloning or moving the repo |

---

## ━━━ THE PLATFORM ADAPTER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

`Plugin/unraid/adapter.sh` is the Unraid platform adapter. It is sourced automatically
by `load_config.sh` whenever `PLATFORM=unraid` is detected (via `/etc/unraid-version`).

Every bash script in the ecosystem calls `platform_*()` functions instead of OS-specific
commands directly. The adapter translates those calls into Unraid-specific implementations.

```
platform_storage_healthy          # is the array up and shfs mounted?
platform_is_maintenance_running   # parity check or sync in progress?
platform_is_service_running       # is a named service process alive?
platform_restart_service          # restart via rc.d (Unraid) or systemctl (future)
platform_stop_service             # stop a named service
platform_is_mover_running         # Unraid mover active?
platform_get_mover_pid            # PID of the mover process
platform_stop_user_scripts        # kill Unraid user.scripts background jobs
platform_send_os_notification     # dynamix notify (Unraid) or equivalent
platform_get_disk_states          # reads disks.ini (Unraid) or equivalent
platform_get_temp_thresholds      # reads dynamix.cfg (Unraid) or equivalent
platform_is_service_enabled       # docker.cfg / domain.cfg enabled check
platform_require_cmd              # verify a platform command exists
```

**Adding a new platform:** Create `Plugin/<platform>/adapter.sh` implementing the same
function names. `load_config.sh` detects the OS at runtime and sources the correct adapter.
No other files need changing.

---

## ━━━ UNRAID INTEGRATION POINTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| File | Where it appears in Unraid |
|------|---------------------------|
| `Varaverk.page` | Tasks menu item |
| `VaraverkSettings.page` | Settings → Other Settings tile |
| `event/disks_mounted/rebuild_cron` | Fires on every boot — copies `.plg`, rebuilds cron |
| `event/disks_mounted/array_start_jobs` | Fires when array starts |
| `event/disks_unmounting/array_stop_jobs` | Fires when array stops |
| `/boot/config/plugins/varaverk.plg` | Registers the plugin with Unraid's plugin system (lives on flash, not in repo) |

+| `build.sh` | Packages the plugin as a `.txz` for release | Before publishing a new plugin version |


Now Partnership docs — read the current script tables first:


Bash

grep -n "onboard_cancel\|partnership_transfer\|Script.*Role\|Script.*What\|SCRIPTS IN" /boot/config/plugins/varaverk/Partnership/README-Partnership.md | head -15

Find scripts table in Partnership README


137:## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
139:| Script | Role | When It Runs |


grep -n "onboard_cancel\|partnership_transfer\|Script.*Role\|SCRIPTS IN\|## ━" /boot/config/plugins/varaverk/Partnership/Manual-Partnership.md | head -20

Find scripts table and sections in Partnership Manual


10:## ━━━ THE RELATIONSHIP MODEL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
39:## ━━━ SETUP PREREQUISITES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
103:## ━━━ FULL CONFIGURATION REFERENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
225:## ━━━ PROCEDURES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
374:## ━━━ OUTPUT TIERS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
397:## ━━━ FLAG REFERENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
444:## ━━━ TROUBLESHOOTING ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
568:## ━━━ STATE FILES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
590:## ━━━ ROLE-BASED ACCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
608:## ━━━ DESIGN NOTES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


137	## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
138
139	| Script | Role | When It Runs |
140	|--------|------|--------------|
141	| `partnership_onboard.sh` | One-time setup — SSH keys, stack deploy, arr bootstrap | Manually, once per server per partnership |
142	| `partnership_offboard.sh` | Clean separation — both paths, both roles | Via `partnership_manager.sh --offboard`; or directly |
143	| `partnership_manager.sh` | Dispatcher + monitor — onboard WebUIs, health check, transfer, status | `--check` every 30min; all other modes manually |
144	| `ssh_setup.sh` | SSH key generation, remote install, auth validation | Called by onboard; manually for re-keying or validation |
145
146	---
147
148	## ━━━ HOW THE SCRIPTS RELATE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
149
150	```
151	INITIAL SETUP (run once)
152	─────────────────────────────────────────────────────────────────────────────
153	HOST2 (mirror) runs:
154	  partnership_onboard.sh
155	    └─ ssh_setup.sh                   generates keypair, copies to owner
156

## ━━━ SCRIPTS IN THIS FOLDER ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

| Script | Role | When It Runs |
|--------|------|--------------|
| `partnership_onboard.sh` | One-time setup — SSH keys, stack deploy, arr bootstrap | Manually, once per server per partnership |
| `partnership_offboard.sh` | Clean separation — both paths, both roles | Via `partnership_manager.sh --offboard`; or directly |
| `partnership_manager.sh` | Dispatcher + monitor — onboard WebUIs, health check, transfer, status | `--check` every 30min; all other modes manually |
| `ssh_setup.sh` | SSH key generation, remote install, auth validation | Called by onboard; manually for re-keying or validation |

---

## ━━━ HOW THE SCRIPTS RELATE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

```
INITIAL SETUP (run once)
─────────────────────────────────────────────────────────────────────────────
HOST2 (mirror) runs:
  partnership_onboard.sh
    └─ ssh_setup.sh                   generates keypair, copies to owner


397	## ━━━ FLAG REFERENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
398
399	### partnership_onboard.sh
400
401	| Flag | Effect |
402	|------|--------|
403	| `--dry-run` | Preview all steps without making changes |
404	| `--log` | Verbose per-step output |
405	| `--skip-ssh` | Skip SSH key setup (Step 1) — key already in place |
406	| `--skip-auth-stack` | Skip auth stop + deploy (Steps 3-4) — already done |
407	| `--skip-arr-stack` | Skip arr stop + deploy (Steps 5-6) — not needed or already done |
408	| `--skip-arr-sync` | Skip arr library bootstrap (Step 8) — arrs not live yet |
409
410	### partnership_offboard.sh
411
412	Called automatically by `partnership_manager.sh --offboard`. Can also be run directly.
413
414	| Flag | Effect |
415	|------|--------|
416	| `--dry-run` | Preview all steps without making changes |
417	| `--log` | Verbose per-step output |
418	| `--reason=<string>` | Tag the offboard reason in state file and blocklist (default: `manual`) |
419
420	### partnership_manager.sh
421
422	| Flag | Effect |
423	|------|--------|
424	| `--dry-run` | Show the complete sequence without executing |
425	| `--log` | Verbose per-operation output |
426	| `--onboard` | Establish mirror relationship (owner only) |
427	| `--offboard` | Clean separation (either server) — 10s countdown |
428	| `--transfer --confirm=...` | Flip ownership (owner only) |
429	| `--check --remote-seen\|--remote-unseen` | 15-min health check (called by orchestrator) |
430	| `--status` | Show state files, blocklist, SSH key status |
431	| `--unblock <hostname>` | Remove hostname from blocklist |
432
433	### ssh_setup.sh
434
435	| Flag | Effect |
436	|------|--------|
437	| `--force` | Regenerate key even if it exists, re-copy to remote |
438	| `--validate` | Test SSH auth, track strikes, notify at limit |
439	| `--status` | Show key path, fingerprint, remote connectivity |
440	| `--dry-run` | Preview without creating or copying |
441
442	---
443
444	## ━━━ TROUBLESHOOTING ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
445
446	### Auth Stack Deploy Fails on Onboard
447
448	```bash
449	# Check that XML files exist on HOST1:
450	ls /boot/config/plugins/dockerMan/templates-user/my-Authelia.xml
451
452	# Check that mirror is reachable via Tailscale:
453	tailscale ping unRAID-Jayred365
454
455	# Check that SSH key is working:
456	Partnership/ssh_setup.sh --validate

## ━━━ FLAG REFERENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

### partnership_onboard.sh

| Flag | Effect |
|------|--------|
| `--dry-run` | Preview all steps without making changes |
| `--log` | Verbose per-step output |
| `--skip-ssh` | Skip SSH key setup (Step 1) — key already in place |
| `--skip-auth-stack` | Skip auth stop + deploy (Steps 3-4) — already done |
| `--skip-arr-stack` | Skip arr stop + deploy (Steps 5-6) — not needed or already done |
| `--skip-arr-sync` | Skip arr library bootstrap (Step 8) — arrs not live yet |

### partnership_offboard.sh

Called automatically by `partnership_manager.sh --offboard`. Can also be run directly.

| Flag | Effect |
|------|--------|
| `--dry-run` | Preview all steps without making changes |
| `--log` | Verbose per-step output |
| `--reason=<string>` | Tag the offboard reason in state file and blocklist (default: `manual`) |

### partnership_manager.sh

| Flag | Effect |
|------|--------|
| `--dry-run` | Show the complete sequence without executing |
| `--log` | Verbose per-operation output |
| `--onboard` | Establish mirror relationship (owner only) |
| `--offboard` | Clean separation (either server) — 10s countdown |
| `--transfer --confirm=...` | Flip ownership (owner only) |
| `--check --remote-seen\|--remote-unseen` | 15-min health check (called by orchestrator) |
| `--status` | Show state files, blocklist, SSH key status |
| `--unblock <hostname>` | Remove hostname from blocklist |

### ssh_setup.sh

| Flag | Effect |
|------|--------|
| `--force` | Regenerate key even if it exists, re-copy to remote |
| `--validate` | Test SSH auth, track strikes, notify at limit |
| `--status` | Show key path, fingerprint, remote connectivity |
| `--dry-run` | Preview without creating or copying |

---

## ━━━ TROUBLESHOOTING ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

### Auth Stack Deploy Fails on Onboard

```bash
# Check that XML files exist on HOST1:
ls /boot/config/plugins/dockerMan/templates-user/my-Authelia.xml

# Check that mirror is reachable via Tailscale:
tailscale ping unRAID-Jayred365

# Check that SSH key is working:
Partnership/ssh_setup.sh --validate


1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= Delete Keys ====================================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Removes SSH keys between HOST1 and HOST2 in the specified direction.
9	# Safe to run at any phase. Clears related setup.db flags.
10	#
11	# ==============================================================================================
12	# RUNTIME MODES
13	# ==============================================================================================
14	#
15	# Partnership/onboard_cancel.sh --direction=h1   (default)
16	#     HOST1 → HOST2: remove HOST1's public key from HOST2's authorized_keys,
17	#     delete local HOST1 key pair, clear HOST2 phase flags from setup.db.
18	#
19	# Partnership/onboard_cancel.sh --direction=h2
20	#     HOST2 → HOST1: remove HOST2's public key from HOST1's authorized_keys.
21	#     Identifies the key by HOST2's hostname in the key comment.
22	#
23	# Partnership/onboard_cancel.sh --direction=both
24	#     Both directions.
25	#
26	# Partnership/onboard_cancel.sh --dry-run
27	#     Preview without making changes.
28	#
29	# ==============================================================================================
30
31	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
32	SCRIPTS_ROOT="$SCRIPT_DIR/.."
33	SSH_TIMEOUT=15
34	DIRECTION="h1"
35	FILTERED_ARGS=()
36
37	for arg in "$@"; do
38	    case "$arg" in
39	        --direction=*) DIRECTION="${arg#--direction=}" ;;
40	        *)             FILTERED_ARGS+=("$arg") ;;
41	    esac
42	done
43
44	source "$SCRIPTS_ROOT/load_config.sh"
45	parse_args "${FILTERED_ARGS[@]}"
46
47	[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }
48
49	acquire_lock
50
51	detect_hosts
52
53	OWNER_ID="${PARTNERSHIP_OWNER_HOST:-HOST1}"
54	MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" )
55	MIRROR="${!MIRROR_ID}"
56	SSH_KEY_PUB="${SSH_KEY}.pub"
57	STATE_FILE="$(platform_setup_db_path)"
58	AUTH_KEYS="/root/.ssh/authorized_keys"
59	MIRROR_SHORT="${MIRROR%%.*}"
60
61	START=$(date +%s)
62
63	echo ""
64	echo "━━━ Delete Keys — direction:${DIRECTION} — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
65	[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"

#!/bin/bash
# ==============================================================================================
# ============================= Delete Keys ====================================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Removes SSH keys between HOST1 and HOST2 in the specified direction.
# Safe to run at any phase. Clears related setup.db flags.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# Partnership/onboard_cancel.sh --direction=h1   (default)
#     HOST1 → HOST2: remove HOST1's public key from HOST2's authorized_keys,
#     delete local HOST1 key pair, clear HOST2 phase flags from setup.db.
#
# Partnership/onboard_cancel.sh --direction=h2
#     HOST2 → HOST1: remove HOST2's public key from HOST1's authorized_keys.
#     Identifies the key by HOST2's hostname in the key comment.
#
# Partnership/onboard_cancel.sh --direction=both
#     Both directions.
#
# Partnership/onboard_cancel.sh --dry-run
#     Preview without making changes.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPTS_ROOT="$SCRIPT_DIR/.."
SSH_TIMEOUT=15
DIRECTION="h1"
FILTERED_ARGS=()

for arg in "$@"; do
    case "$arg" in
        --direction=*) DIRECTION="${arg#--direction=}" ;;
        *)             FILTERED_ARGS+=("$arg") ;;
    esac
done

source "$SCRIPTS_ROOT/load_config.sh"
parse_args "${FILTERED_ARGS[@]}"

[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }

acquire_lock

detect_hosts

OWNER_ID="${PARTNERSHIP_OWNER_HOST:-HOST1}"
MIRROR_ID=$( [[ "$OWNER_ID" == "HOST1" ]] && echo "HOST2" || echo "HOST1" )
MIRROR="${!MIRROR_ID}"
SSH_KEY_PUB="${SSH_KEY}.pub"
STATE_FILE="$(platform_setup_db_path)"
AUTH_KEYS="/root/.ssh/authorized_keys"
MIRROR_SHORT="${MIRROR%%.*}"

START=$(date +%s)

echo ""
echo "━━━ Delete Keys — direction:${DIRECTION} — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"


1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= Partnership Transfer ============================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Transfers ownership from the current owner to the current mirror. After
9	# transfer the roles are swapped: what was the mirror becomes the new owner,
10	# and what was the owner becomes the new mirror.
11	#
12	# No containers are moved — only config and WebUI targets are updated. Both
13	# servers remain in the partnership; the sync direction reverses on the next
14	# fallback.sh / critical_sync_maintenance.sh cycle.
15	#
16	# ==============================================================================================
17	# OPERATIONAL MODEL
18	# ==============================================================================================
19	#
20	# OWNER ONLY — mirror cannot run this script.
21	#
22	#   Step 1:  Health Verification  — both servers pass N consecutive health checks
23	#   Step 2:  Pre-transfer Sync    — final sync in current direction (owner → mirror)
24	#   Step 3:  Reconfigure WebUIs   — new owner WebUIs → localhost
25	#                                   new mirror WebUIs → new owner IP
26	#   Step 4:  Flip Ownership       — update PARTNERSHIP_OWNER_HOST in master.conf
27	#                                   on both servers
28	#   Step 5:  Write State          — ACTIVE written locally and pushed to new mirror
29	#
30	# ==============================================================================================
31	# OPERATIONAL SAFEGUARDS
32	# ==============================================================================================
33	#
34	# Owner-Only Enforcement
35	#   The script reads MY_ID from detect_hosts() and exits immediately if it is
36	#   not the current PARTNERSHIP_OWNER_HOST. The mirror cannot run a transfer.
37	#
38	# Explicit Confirmation String
39	#   Requires the exact passphrase from PARTNERSHIP_TRANSFER_CONFIRM via
40	#   --confirm=<value>. Without a matching string the transfer is cancelled
41	#   before any steps execute. Prevents accidental ownership changes.
42	#
43	# Active Partnership Guard
44	#   Reads the local state file and exits if the current state is INACTIVE.
45	#   A transfer without an active partnership has no defined outcome.
46	#
47	# Dual Health Verification
48	#   Both servers must pass PARTNERSHIP_TRANSFER_STRIKES consecutive health
49	#   checks before proceeding. A single failure resets the strike counter.
50	#   After PARTNERSHIP_TRANSFER_MAX_ATTEMPTS total attempts the transfer aborts.
51	#
52	# Pre-transfer Final Sync
53	#   A full sync in the current direction (owner → mirror) runs immediately
54	#   before roles flip. Ensures the mirror is current before it becomes the owner.
55	#
56	# ==============================================================================================
57	# CONFIGURATION
58	# ==============================================================================================
59	#
60	# master.conf
61	#
62	#   PARTNERSHIP_OWNER_HOST
63	#       Current owner host ID (e.g. "HOST1"). Updated on both servers after transfer.
64	#
65	#   PARTNERSHIP_TRANSFER_CONFIRM

#!/bin/bash
# ==============================================================================================
# ============================= Partnership Transfer ============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Transfers ownership from the current owner to the current mirror. After
# transfer the roles are swapped: what was the mirror becomes the new owner,
# and what was the owner becomes the new mirror.
#
# No containers are moved — only config and WebUI targets are updated. Both
# servers remain in the partnership; the sync direction reverses on the next
# fallback.sh / critical_sync_maintenance.sh cycle.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# OWNER ONLY — mirror cannot run this script.
#
#   Step 1:  Health Verification  — both servers pass N consecutive health checks
#   Step 2:  Pre-transfer Sync    — final sync in current direction (owner → mirror)
#   Step 3:  Reconfigure WebUIs   — new owner WebUIs → localhost
#                                   new mirror WebUIs → new owner IP
#   Step 4:  Flip Ownership       — update PARTNERSHIP_OWNER_HOST in master.conf
#                                   on both servers
#   Step 5:  Write State          — ACTIVE written locally and pushed to new mirror
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Owner-Only Enforcement
#   The script reads MY_ID from detect_hosts() and exits immediately if it is
#   not the current PARTNERSHIP_OWNER_HOST. The mirror cannot run a transfer.
#
# Explicit Confirmation String
#   Requires the exact passphrase from PARTNERSHIP_TRANSFER_CONFIRM via
#   --confirm=<value>. Without a matching string the transfer is cancelled
#   before any steps execute. Prevents accidental ownership changes.
#
# Active Partnership Guard
#   Reads the local state file and exits if the current state is INACTIVE.
#   A transfer without an active partnership has no defined outcome.
#
# Dual Health Verification
#   Both servers must pass PARTNERSHIP_TRANSFER_STRIKES consecutive health
#   checks before proceeding. A single failure resets the strike counter.
#   After PARTNERSHIP_TRANSFER_MAX_ATTEMPTS total attempts the transfer aborts.
#
# Pre-transfer Final Sync
#   A full sync in the current direction (owner → mirror) runs immediately
#   before roles flip. Ensures the mirror is current before it becomes the owner.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   PARTNERSHIP_OWNER_HOST
#       Current owner host ID (e.g. "HOST1"). Updated on both servers after transfer.
#
#   PARTNERSHIP_TRANSFER_CONFIRM


Now I have everything to add both scripts to both docs:


Edit

| `partnership_onboard.sh` | One-time setup — SSH keys, stack deploy, arr bootstrap | Manually, once per server per partnership |
| `partnership_offboard.sh` | Clean separation — both paths, both roles | Via `partnership_manager.sh --offboard`; or directly |
| `partnership_manager.sh` | Dispatcher + monitor — onboard WebUIs, health check, transfer, status | `--check` every 30min; all other modes manually |
| `ssh_setup.sh` | SSH key generation, remote install, auth validation | Called by onboard; manually for re-keying or validation |

| `partnership_onboard.sh` | One-time setup — SSH keys, stack deploy, arr bootstrap | Manually, once per server per partnership |
| `partnership_offboard.sh` | Clean separation — both paths, both roles | Via `partnership_manager.sh --offboard`; or directly |
| `partnership_manager.sh` | Dispatcher + monitor — onboard WebUIs, health check, transfer, status | `--check` every 30min; all other modes manually |
| `partnership_transfer.sh` | Transfer ownership from current owner to current mirror | Via `partnership_manager.sh --transfer`; owner only |
| `ssh_setup.sh` | SSH key generation, remote install, auth validation | Called by onboard; manually for re-keying or validation |
| `onboard_cancel.sh` | Remove SSH keys in one or both directions, clear setup flags | During cancelled or failed onboard; manual cleanup |


The file /boot/config/plugins/varaverk/Partnership/README-Partnership.md has been updated successfully. (file state is current in your context — no need to Read it back)

| `partnership_onboard.sh` | One-time setup — SSH keys, stack deploy, arr bootstrap | Manually, once per server per partnership |
| `partnership_offboard.sh` | Clean separation — both paths, both roles | Via `partnership_manager.sh --offboard`; or directly |
| `partnership_manager.sh` | Dispatcher + monitor — onboard WebUIs, health check, transfer, status | `--check` every 30min; all other modes manually |
| `ssh_setup.sh` | SSH key generation, remote install, auth validation | Called by onboard; manually for re-keying or validation |

| `partnership_onboard.sh` | One-time setup — SSH keys, stack deploy, arr bootstrap | Manually, once per server per partnership |
| `partnership_offboard.sh` | Clean separation — both paths, both roles | Via `partnership_manager.sh --offboard`; or directly |
| `partnership_manager.sh` | Dispatcher + monitor — onboard WebUIs, health check, transfer, status | `--check` every 30min; all other modes manually |
| `partnership_transfer.sh` | Transfer ownership from current owner to current mirror | Via `partnership_manager.sh --transfer`; owner only |
| `ssh_setup.sh` | SSH key generation, remote install, auth validation | Called by onboard; manually for re-keying or validation |
| `onboard_cancel.sh` | Remove SSH keys in one or both directions, clear setup flags | During cancelled or failed onboard; manual cleanup |

+| `partnership_transfer.sh` | Transfer ownership from current owner to current mirror | Via `partnership_manager.sh --transfer`; owner only |

+| `onboard_cancel.sh` | Remove SSH keys in one or both directions, clear setup flags | During cancelled or failed onboard; manual cleanup |


Now add the flag reference entries for both scripts in Manual-Partnership:


### ssh_setup.sh

| Flag | Effect |
|------|--------|
| `--force` | Regenerate key even if it exists, re-copy to remote |
| `--validate` | Test SSH auth, track strikes, notify at limit |
| `--status` | Show key path, fingerprint, remote connectivity |
| `--dry-run` | Preview without creating or copying |

### partnership_transfer.sh

Owner-only. Transfers ownership to the current mirror — swaps roles without moving
containers. Run via `partnership_manager.sh --transfer --confirm=<phrase>`.

| Flag | Effect |
|------|--------|
| `--confirm=<phrase>` | Required confirmation string (from `PARTNERSHIP_TRANSFER_CONFIRM` in master.conf) |
| `--dry-run` | Preview all steps without executing |
| `--log` | Verbose per-step output |

### onboard_cancel.sh

Removes SSH keys between hosts in the specified direction. Safe to run at any onboard
phase — clears the corresponding setup.db flags.

| Flag | Effect |
|------|--------|
| `--direction=h1` | Remove HOST1 → HOST2 key (default) |
| `--direction=h2` | Remove HOST2 → HOST1 key |
| `--direction=both` | Both directions |
| `--dry-run` | Preview without making changes |

### ssh_setup.sh

| Flag | Effect |
|------|--------|
| `--force` | Regenerate key even if it exists, re-copy to remote |
| `--validate` | Test SSH auth, track strikes, notify at limit |
| `--status` | Show key path, fingerprint, remote connectivity |
| `--dry-run` | Preview without creating or copying |


The file /boot/config/plugins/varaverk/Partnership/Manual-Partnership.md has been updated successfully. (file state is current in your context — no need to Read it back)

### partnership_transfer.sh

Owner-only. Transfers ownership to the current mirror — swaps roles without moving
containers. Run via `partnership_manager.sh --transfer --confirm=<phrase>`.

| Flag | Effect |
|------|--------|
| `--confirm=<phrase>` | Required confirmation string (from `PARTNERSHIP_TRANSFER_CONFIRM` in master.conf) |
| `--dry-run` | Preview all steps without executing |
| `--log` | Verbose per-step output |

### onboard_cancel.sh

Removes SSH keys between hosts in the specified direction. Safe to run at any onboard
phase — clears the corresponding setup.db flags.

| Flag | Effect |
|------|--------|
| `--direction=h1` | Remove HOST1 → HOST2 key (default) |
| `--direction=h2` | Remove HOST2 → HOST1 key |
| `--direction=both` | Both directions |
| `--dry-run` | Preview without making changes |

### ssh_setup.sh

| Flag | Effect |
|------|--------|
| `--force` | Regenerate key even if it exists, re-copy to remote |
| `--validate` | Test SSH auth, track strikes, notify at limit |
| `--status` | Show key path, fingerprint, remote connectivity |
| `--dry-run` | Preview without creating or copying |

| `--status` | Show state files, blocklist, SSH key status |

| `--unblock <hostname>` | Remove hostname from blocklist |

+Owner-only. Transfers ownership to the current mirror — swaps roles without moving

+containers. Run via `partnership_manager.sh --transfer --confirm=<phrase>`.

+| `--confirm=<phrase>` | Required confirmation string (from `PARTNERSHIP_TRANSFER_CONFIRM` in master.conf) |

+| `--dry-run` | Preview all steps without executing |

+Removes SSH keys between hosts in the specified direction. Safe to run at any onboard

+phase — clears the corresponding setup.db flags.

+| `--direction=h1` | Remove HOST1 → HOST2 key (default) |

+| `--direction=h2` | Remove HOST2 → HOST1 key |

+| `--direction=both` | Both directions |

+| `--dry-run` | Preview without making changes |


Now commit everything:


Bash

Show all modified files


 Media/Manual-Media.md                 | 21 +++++++++++
 Orchestrators/README-Orchestrators.md |  7 ++--
 Partnership/Manual-Partnership.md     | 23 ++++++++++++
 Partnership/README-Partnership.md     |  2 ++
 Plugin/README-Plugin.md               |  1 +
 Tools/Manual-Tools.md                 | 66 +++++++++++++++++++++++------------
 Tools/README-Tools.md                 |  1 +
 Transcodes/README-Transcoding.md      |  4 +--
 Watchdogs/Manual-Watchdogs.md         |  2 +-
 Watchdogs/stability_watchdog.sh       |  4 +--
 10 files changed, 99 insertions(+), 32 deletions(-)


git add Media/Manual-Media.md Orchestrators/README-Orchestrators.md Partnership/Manual-Partnership.md Partnership/README-Partnership.md Plugin/README-Plugin.md Tools/Manual-Tools.md Tools/README-Tools.md Transcodes/README-Transcoding.md Watchdogs/Manual-Watchdogs.md Watchdogs/stability_watchdog.sh && git commit -m "$(cat <<'EOF'
Sync READMEs and manuals to current script headers

Fix discrepancies found by auditing docs against headers:
- stability_watchdog.sh: wrong variable name (STRIKES → STRIKE_LIMIT) and wrong default (2hr → 12hr) in header
- Watchdogs manual: REBOOT_WINDOW_HRS example value was 2, should be 12
- Transcodes README: transcode_cleanup/manager table said "Every 3 min", should be "Every 7 minutes"
- Tools manual: fallback_state_reset.sh still marked "not yet built" — rewrite section to reflect current script
- Tools manual: claude_startup --setup flag doesn't exist; modes were inverted — fix and add --launch
- Tools README + manual: add docker_prune_images.sh (existed but undocumented)
- Tools manual: add play_state_sync.sh --full to flag reference
- Orchestrators README: arrs_failed_stalled_recovery runs via intermediate_sync (every 4hr), not standalone every 6hr
- Plugin README: add build.sh to scripts table
- Partnership README + manual: add partnership_transfer.sh and onboard_cancel.sh to script tables and flag reference
EOF
)"

Commit all documentation corrections


[main ba3eed3] Sync READMEs and manuals to current script headers
 10 files changed, 99 insertions(+), 32 deletions(-)


Push to origin


remote: . Processing 1 references
remote: Processed 1 references in total
To ssh://192.168.50.2:221/FailedProxy/Varaverk.git
   420e13a..ba3eed3  main -> main


Pushed. Here's what was fixed across 10 files:

**Script header:**
- `stability_watchdog.sh`: wrong variable name (`SYS_WATCHDOG_STRIKES` → `SYS_WATCHDOG_STRIKE_LIMIT`) and wrong default (2hr → 12hr) for reboot window

**Factual errors in docs:**
- `Watchdogs/Manual`: `SYS_WATCHDOG_REBOOT_WINDOW_HRS` example showed `=2` but master.conf default is `12`
- `Transcodes/README`: table said "Every 3 min" for both transcode scripts — everything else (including script headers) says "Every 7 minutes"
- `Orchestrators/README`: `arrs_failed_stalled_recovery.sh` listed as standalone "every 6 hours" — it runs via `intermediate_sync_maintenance.sh` every 4 hours; removed standalone row, updated intermediate_sync description

**Stale/missing content:**
- `Tools/Manual`: `fallback_state_reset.sh` still documented as "not yet built" — rewrote the section to reflect the actual script (with `--force`, `--dry-run`, `--status`, `--log`)
- `Tools/Manual`: `claude_startup.sh` had modes completely inverted and referenced a `--setup` flag that doesn't exist — fixed to match actual `--launch` flag
- `Tools/README + Manual`: `docker_prune_images.sh` existed and was functional but had no documentation
- `Media/Manual`: `play_state_sync.sh --full` flag was in the header but missing from the FLAG REFERENCE section
- `Plugin/README`: `build.sh` present but not in the scripts table
- `Partnership/README + Manual`: `partnership_transfer.sh` and `onboard_cancel.sh` both exist but were absent from the scripts table and had no flag reference entries