Read the following files and report any discrepancies between the documentation and the actual script headers. The goal is to find places where the README or Manual says something that no longer matches what the script header says (wrong schedule, wrong config vars, wrong behavior description, missing scripts, extra scripts, etc.).

Files to read:
1. /boot/config/plugins/varaverk/Rsync/README-Rsync.md
2. /boot/config/plugins/varaverk/Rsync/Manual-Rsync.md
3. Script headers (top ~120 lines each):
   - /boot/config/plugins/varaverk/Rsync/rsync.sh

Report: list each discrepancy as "FILE: what the doc says vs what the header says". If everything matches, say so. Be specific — quote the conflicting text. Keep the report under 300 words.


- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, suggest the /config command.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- verify: Verify that a code change actually does what it's supposed to by running the app and observing behavior. Use when asked to verify a PR, confirm a fix works, test a change manually, check that a feature works, or validate local changes before pushing.
- code-review: Review the current diff for correctness bugs and reuse/simplification/efficiency cleanups at the given effort level (low/medium: fewer, high-confidence findings; high→max: broader coverage, may include uncertain findings; ultra: deep multi-agent review in the cloud). Pass --comment to post findings as inline PR comments, or --fix to apply the findings to the working tree after the review.
- simplify: Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- schedule: Create, update, list, or run scheduled cloud agents (routines) that execute on a cron schedule. - When the user wants to schedule a recurring cloud agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/routines. Also use when the user wants a one-time scheduled run ("run this once at 3pm", "remind me to check X tomorrow").
- claude-api: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.
TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).
SKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).
- run: Launch and drive this project's app to see a change working. Use when asked to run, start, or screenshot the app, or to confirm a change works in the real app (not just tests). First looks for a project skill that already covers launching the app; otherwise falls back to built-in patterns per project type (CLI, server, TUI, Electron, browser-driven, library).
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a pull request
- security-review: Complete a security review of the pending changes on the current branch

update-config keybindings-help verify code-review simplify fewer-permission-prompts loop schedule claude-api run init review security-review


I'll read the documentation files and script headers to identify any discrepancies.


1	# ━━━━━ RSYNC ━━━━━
2
3	The transfer engine for the two-server ecosystem. `rsync.sh` is the single script
4	called by every orchestrator that moves data between servers — it handles profiles,
5	pre-flight checks, container stops, the actual transfer, and bandwidth logging.
6	Orchestrators decide what to sync and when. `rsync.sh` decides how to do it safely.
7
8	> **Never schedule rsync.sh directly for daily/weekly syncs.** Use the orchestrators
9	> in `Orchestrators/`. rsync.sh is called directly only for manual runs and the
10	> 30-minute Emby dirty sync, which needs its own cron entry.
11
12	---
13
14	## ━━━ THE PROBLEM THAT BUILT THIS ━━━
15
16	**rsync Alone Isn't Safe Enough for Live Databases**
17	Running rsync against a share while SQLite databases are being written produces
18	corrupt snapshots on the remote. The arr databases, Emby library database, and
19	Authelia session store all write continuously. A plain rsync copies them mid-write.
20	The remote gets a file that opens cleanly but has internal inconsistencies.
21	Fix: profiles stop specific containers before syncing and restart them after.
22	The database is quiesced, rsync runs against a static snapshot, containers come back up.
23
24	**Each Share Needs Different Behavior**
25	Media shares (Movies, TV) just spread files — nothing stops, no `--delete`
26	(arr cleanup scripts own deletions, and arr_sync ensures both arrs already
27	track incoming files before they arrive). Arr databases need containers stopped,
28	clean SQLite snapshot, restart. Emby has two modes: weekly full-stop clean mirror
29	and 30-minute dirty sync while Emby stays running (WAL files excluded). Critical-Data
30	stops the auth stack, waits for Authelia to come back after restart delay.
31	Fix: the profile system — one script, behavior defined entirely by the profile key.
32
33	**A Failed Remote Shouldn't Corrupt a Live Sync**
34	If the remote's rootfs is nearly full, an rsync that starts will write partial files
35	then fail mid-transfer, leaving the remote in a worse state than before. If the remote's
36	backing disks are offline, rsync writes to an empty mount point and "succeeds."
37	Fix: pre-flight checks abort before touching anything if remote conditions are wrong.
38
39	---
40
41	## ━━━ WHAT THIS FOLDER DOES ━━━
42
43	One script. One job: move data from this server to the remote safely.
44
45	`rsync.sh` handles the full transfer lifecycle:
46	1. Infer or accept a profile for the given directory
47	2. Run pre-flight checks (connectivity, rootfs, disk temps, remote share exists)
48	3. Stop containers specified by the profile (both sides)
49	4. Run rsync with profile flags, excludes, and bandwidth limit
50	5. Restart containers (with delay if configured)
51	6. Restart remote containers if dirty-sync profile specifies it
52	7. Log the transfer to bandwidth_monitor.sh
53
54	Everything else — deciding which shares to sync, in what order, on what schedule —
55	lives in the orchestrators.
56
57	---
58
59	## ━━━ RELATIONSHIP TO OTHER FOLDERS ━━━
60
61	```
62	Media/
63	  arr_sync.sh ── runs before rsync in daily window ──────────► all arrs agree on library
64	                                                                 ↓
65	Orchestrators/                 ← decides what to sync, when, and in what order
66	  daily_sync_maintenance.sh  ──────────────────────────────────► rsync.sh (per share)
67	  weekly_sync_maintenance.sh ──────────────────────────────────► rsync.sh (emby, critical-data)
68	  critical_sync_maintenance.sh ────────────────────────────────► rsync.sh (partnership shares)
69
70	Cron (direct):
71	  */30 * * * *               ──────────────────────────────────► rsync.sh --profile=emby-fallback
72
73	Monitors/
74	  bandwidth_monitor.sh       ◄─── called by rsync.sh after each sync (--log-transfer)
75
76	Fallback/
77	  fallback.sh                ──── rsync writeback during handback ──► rsync.sh
78	```
79
80	rsync.sh never calls other scripts except `bandwidth_monitor.sh` at the end of a sync.
81	All orchestration logic lives in the callers. arr_sync.sh (Media/) is a peer that runs
82	before rsync in the daily window — it is not called by rsync.sh directly.
83
84	---
85
86	## ━━━ THE PROFILE SYSTEM ━━━
87
88	Profile key = directory basename lowercased. `--profile=name` overrides.
89
90	| Profile | What It Syncs | Containers Stopped | Notes |
91	|---------|--------------|-------------------|-------|
92	| *(none)* | Media shares (Movies, TV, Music…) | None | Bidirectional spread — no `--delete` in DEFAULT_RSYNC_OPTS. Arr cleanup scripts own deletions. |
93	| `arrs_stack` | Arr databases | Sonarr, Radarr, Lidarr, Prowlarr, Bazarr, Pinchflat | Clean SQLite snapshot |
94	| `critical-data` | Auth stack | Mariadb-Authelia, Redis-Authelia, NPM, Lldap | Authelia has restart delay |
95	| `important-data` | NextCloud + Postgres | Postgres-NextCloud | NextCloud has restart delay |
96	| `emby` | Full Emby mirror | Emby (both sides) | Weekly — Sunday 2:30am |
97	| `emby-fallback` | Emby watch state delta | None | Dirty sync — Emby stays running |
98
99	For full profile definitions see `Manual-Rsync.md`.
100
101	---
102
103	## ━━━ SCRIPTS IN THIS FOLDER ━━━
104
105	| Script | Role | When It Runs |
106	|--------|------|-------------|
107	| `rsync.sh` | Core transfer engine — profile resolution, pre-flights, container management, transfer, bandwidth logging | Called by orchestrators; directly for manual and 30-min Emby dirty sync |
108
109	---
110
111	## ━━━ HOW THE SCRIPTS RELATE ━━━
112
113	```
114	Callers (Orchestrators/) ──────────────────────────────────────────────────────
115	  daily_sync_maintenance.sh                                                     │
116	  weekly_sync_maintenance.sh         rsync.sh /path/to/share [--profile=name]  │
117	  critical_sync_maintenance.sh  ─────────────────────────────────────────────► │
118	  fallback.sh (writeback)                                                       │
119	  Direct cron (emby-fallback)                                                   │
120	                                                                               ▼
121	                                            ┌─────────────────────────────────────┐
122	                                            │  1. Infer/accept profile             │
123	                                            │  2. Pre-flight checks                │
124	                                            │     - connectivity                   │
125	                                            │     - rootfs / disk temp / disks     │
126	                                            │     - remote share exists            │
127	                                            │  3. Stop containers (profile)        │
128	                                            │  4. rsync transfer                   │
129	                                            │  5. Restart containers               │
130	                                            │  6. Remote restart (dirty sync)      │
131	                                            │  7. Log to bandwidth_monitor.sh      │
132	                                            └─────────────────────────────────────┘
133	```
134


1	# ━━━━━ RSYNC — Manual ━━━━━
2
3	Config reference, procedures, operational workflows.
4	For overview see README-Rsync.md. For per-script detail see the rsync.sh header.
5
6	This document also serves as the **initial setup guide** for standing up the
7	two-server ecosystem from scratch.
8
9	---
10
11	## ━━━ WHAT YOU'RE BUILDING ━━━
12
13	```
14	HOST1 (unRAID-Gmer4Lfe)                    HOST2 (unRAID-Jayred365)
15	──────────────────────                     ──────────────────────
16	Downloads to:                              Downloads to:
17	  /mnt/user/Movies                           /mnt/user/Anime_Movies
18	  /mnt/user/Tv_Shows                         /mnt/user/Anime_Shows
19	  /mnt/user/Music
20
21	Both servers' arrs track ALL content across both servers.
22	It does not matter who downloaded what or when.
23
24	Daily 3-phase window (1am):
25
26	  Phase 1 — arr_sync.sh (runs first):
27	    All arrs on all nodes reconcile libraries using external IDs
28	    (TMDB, TVDB, MusicBrainz). Union model — any node that tracks
29	    an item, all nodes get it. After this phase both servers' arrs
30	    know about all content regardless of who downloaded it.
31
32	  Phase 2 — rsync (bidirectional, no --delete on media shares):
33	    HOST1 pushes its shares  ──────►  HOST2 receives files
34	    HOST1 receives files     ◄──────  HOST2 pushes its shares
35	    Files arrive already tracked by the remote arr (arr_sync ran first).
36	    No --delete — media files only spread. Arr cleanup handles deletions.
37
38	  Phase 3 — arr cleanup:
39	    sonarr_cleanup / radarr_cleanup / lidarr_cleanup query the live arr
40	    API and remove any files no longer tracked. Emby notified after.
41
42	Note: profiled syncs (arrs_stack, emby, critical-data…) use their own
43	PROFILE_RSYNC_OPTS which include --delete. Only the no-profile media
44	shares use DEFAULT_RSYNC_OPTS (spread only).
45
46	Weekly clean sync (2:30am Sunday, containers stopped):
47	  Emby — full clean mirror       ◄──────►  Emby
48	  Critical-Data                  ──────►   Auth stack (HOST1 → HOST2)
49
50	Every 30 minutes — dirty sync:
51	  Emby watch states              ──────►   HOST2 stays current on playback
52	```
53
54	---
55
56	## ━━━ PREREQUISITES ━━━
57
58	Required on both servers before starting:
59
60	```
61	unRAID 7.x
62	Community Applications plugin     — search "Community Applications" in unRAID plugins
63	User Scripts plugin               — install via Community Applications
64	Tailscale plugin                  — install via Community Applications
65	Terminal access                   — unRAID UI → Tools → Terminal, or SSH
66	```
67
68	Optional but recommended:
69	```
70	Gitea (Docker container on HOST1)  — self-hosted git for the script repository
71	Working Emby installation          — for transcode management and fallback
72	```
73
74	---
75
76	## ━━━ STEP 1 — TAILSCALE ━━━
77
78	Tailscale provides the encrypted mesh network between servers. Scripts resolve the
79	remote server's IP via Tailscale at runtime — no hardcoded IPs, no VPN configuration,
80	no open ports. All server-to-server communication goes through Tailscale.
81
82	### Install on Both Servers
83
84	```
85	1. Open Apps in the unRAID UI
86	2. Search "Tailscale" — install the plugin
87	3. Settings → Tailscale → Connect
88	4. Authenticate with your Tailscale account (browser opens on your machine)
89	5. Verify both servers appear: https://login.tailscale.com/admin/machines
90	```
91
92	### Verify Connectivity
93
94	```bash
95	# From HOST1 — should return HOST2's 100.x.x.x Tailscale IP:
96	tailscale ip -4 unRAID-Jayred365
97
98	# Test actual connectivity:
99	tailscale ping unRAID-Jayred365
100	```
101
102	> **Critical:** The hostnames in `master.conf` (`HOST1` and `HOST2`) must match the
103	> Tailscale machine names **exactly** — case sensitive. All remote IP resolution goes
104	> through `tailscale ip -4 HOSTNAME`. A name mismatch causes every remote operation to
105	> fail at the IP resolution step.
106
107	---
108
109	## ━━━ STEP 2 — ENABLE SSH ━━━
110
111	unRAID 7.x has SSH disabled by default. Enable it on both servers.
112
113	```
114	Settings → Management Access → Secure Shell
115	  SSH: Enabled
116	  SSH port: 22
117	Apply
118	```
119
120	SSH is only exposed on your local network and Tailscale interface. No ports are
121	opened to the public internet.
122
123	---
124
125	## ━━━ STEP 3 — SSH KEYS ━━━
126
127	Two sets of keys needed: rsync automation keys (server-to-server) and a Gitea
128	access key (for script repository pulls).
129
130	### 3a — Rsync Automation Keys (ssh_setup.sh)
131
132	Run on **each server**. `ssh_setup.sh` generates the key, copies it to the remote,
133	and updates `host*.conf` with the key path automatically.
134
135	```bash
136	# On HOST1 — after the repo is cloned:
137	bash /boot/config/plugins/varaverk/Partnership/ssh_setup.sh
138
139	# On HOST2:
140	bash /boot/config/plugins/varaverk/Partnership/ssh_setup.sh
141	```
142
143	Key naming convention: hostname lowercased, `unraid-` prefix stripped.
144	```
145	unRAID-Gmer4Lfe  → /root/.ssh/gmer4lfe_rsync_automation
146	unRAID-Jayred365 → /root/.ssh/jayred365_rsync_automation
147	```
148
149	`host*.conf` is updated automatically with `HOST*_SSH_KEY` pointing to the
150	generated key. Run `--status` to verify:
151
152	```bash
153	bash /boot/config/plugins/varaverk/Partnership/ssh_setup.sh --status
154	```
155
156	> **`ssh_setup.sh` is idempotent** — safe to re-run. Use `--force` to regenerate
157	> a key (e.g., after a security incident) and re-copy it to the remote.
158
159	### 3b — Gitea SSH Key (manual)
160
161	```bash
162	# On BOTH servers:
163	ssh-keygen -t ed25519 -f /root/.ssh/unraid_gitea -C "unraid-gitea" -N ""
164
165	# Print the public key to add to Gitea:
166	cat /root/.ssh/unraid_gitea.pub
167
168	# In Gitea: Settings → SSH / GPG Keys → Add Key → paste above
169	```
170
171	---
172
173	## ━━━ STEP 4 — CLONE THE REPOSITORY ━━━
174
175	```bash
176	# Create target directory:
177	mkdir -p /boot/config/plugins/varaverk
178
179	# Clone:
180	GIT_SSH_COMMAND="ssh -i /root/.ssh/unraid_gitea" \
181	    git clone git@YOUR_GITEA_HOST:Varaverk/varaverk.git \
182	    /boot/config/plugins/varaverk
183
184	# Make scripts executable:
185	find /boot/config/plugins/varaverk -name "*.sh" -exec chmod +x {} \;
186	```
187
188	Expected structure after clone:
189	```
190	master.conf          ← all shared configuration
191	host1.conf    ← HOST1-specific configuration
192	host2.conf    ← HOST2-specific configuration
193	common.sh            ← shared library
194	load_config.sh       ← config loader
195	Orchestrators/
196	Rsync/
197	Fallback/
198	Docker_Essentials/
199	...
200	```
201
202	---
203
204	## ━━━ STEP 5 — CONFIGURE MASTER.CONF ━━━
205
206	```bash
207	nano /boot/config/plugins/varaverk/master.conf
208	```
209
210	### Host Identity
211
212	```bash
213	# These must match Tailscale machine names exactly — case sensitive.
214	HOST1="unRAID-Gmer4Lfe"       # REQUIRED
215	HOST2="unRAID-Jayred365"       # REQUIRED
216
217	# SSH keys — each server uses its own key to authenticate to the other:
218	HOST1_SSH_KEY="/root/.ssh/Gmer4Lfe-rsync-key"
219	HOST2_SSH_KEY="/root/.ssh/Jayred365-rsync-key"
220	```
221
222	### Git Repository
223
224	```bash
225	GITEA_CONTAINER="Gitea"
226	GITEA_REPO_PATH="Varaverk/varaverk.git"
227	TARGET_DIR="/boot/config/plugins/varaverk"
228	GITEA_SSH_KEY="/root/.ssh/unraid_gitea"
229	SSH_PORT=221
230	```
231
232	### Daily Sync Shares
233
234	```bash
235	# host1.conf
236	# Shares this server downloads to — pushed to the other server nightly.
237	# arr_sync.sh runs before rsync in the daily window, so the receiving
238	# server's arrs already track incoming files when they arrive.
239	# Never put the same path in both lists.
240	HOST1_DAILY_SYNC_SHARES=(
241	    "/mnt/user/Movies"
242	    "/mnt/user/Tv_Shows"
243	    "/mnt/user/Music"
244	    "/mnt/user/Kids_Movies"
245	    "/mnt/user/Kids_Tv_Shows"
246	    "/mnt/user/Sports"
247	    "/mnt/user/stand-up_comedy"
248	)
249
250	# host2.conf
251	HOST2_DAILY_SYNC_SHARES=(
252	    "/mnt/user/Anime_Shows"
253	    "/mnt/user/Anime_Movies"
254	)
255	```
256
257	### Weekly Sync Shares
258
259	```bash
260	# master.conf
261	# Synced during the Sunday 2:30am window — containers stopped both sides.
262	WEEKLY_SYNC_SHARES=(
263	    "/mnt/user/Media_Server/Emby"
264	    "/mnt/user/appdata-Fallback/Critical-Data"
265	)
266	```
267
268	---
269
270	## ━━━ STEP 6 — MASTER_HOST*.CONF ━━━
271
272	`detect_hosts()` reads which server is running and aliases `HOST*_` prefixed vars
273	to their unprefixed names. Scripts only ever reference the unprefixed name — they
274	work identically on both servers.
275
276	```bash
277	nano /boot/config/plugins/varaverk/host1.conf    # on HOST1
278	nano /boot/config/plugins/varaverk/host2.conf    # on HOST2
279	```
280
281	Every variable is documented in the conf files. Key values to set:
282	- SSH key paths
283	- DAILY_SYNC_SHARES
284	- EMBY_URL / EMBY_API_KEY
285	- CERT_MONITOR_DOMAINS
286	- SMART_IGNORE_DRIVES
287	- ZFS_REPORT_IGNORE_POOLS
288
289	---
290
291	## ━━━ RSYNC PROFILES ━━━
292
293	Profiles control per-share behavior. Profile key = directory basename lowercased.
294	Override with `--profile=name`.
295
296	### Profile Matching
297
298	```bash
299	# rsync.sh /mnt/user/appdata-Fallback/Arrs_Stack
300	#   basename: Arrs_Stack → lowercased: arrs_stack → matches [arrs_stack] profile
301	#
302	# rsync.sh /mnt/user/Movies
303	#   basename: Movies → no profile match → global defaults (no containers stopped)
304	#
305	# rsync.sh /mnt/user/appdata-Fallback/Critical-Data --profile=critical-data
306	#   explicit override
307	```
308
309	### Current Profile Definitions
310
311	```bash
312	# master.conf
313
314	# ── arrs_stack ───────────────────────────────────────────────────────────────
315	# Arr databases — stopped for clean SQLite snapshot
316	PROFILES["arrs_stack_CRITICAL_CONTAINER_NAMES"]=(
317	    "Sonarr" "Radarr" "Lidarr" "Prowlarr" "Bazarr" "Pinchflat"
318	)
319
320	# ── critical-data ─────────────────────────────────────────────────────────────
321	# Auth stack — stopped for clean database snapshot, Authelia delayed restart
322	PROFILES["critical-data_CRITICAL_CONTAINER_NAMES"]=(
323	    "Mariadb-Authelia" "Redis-Authelia"
324	    "NginxProxyManager" "Lldap-Gmer4Lfe"
325	)
326	PROFILES["critical-data_DELAYED_CONTAINERS"]=(
327	    "Authelia" "Authelia-Secondary"
328	)
329	PROFILES["critical-data_CONTAINER_DELAY"]=30
330
331	# ── important-data ────────────────────────────────────────────────────────────
332	# NextCloud + Postgres — stopped for clean snapshot
333	PROFILES["important-data_CRITICAL_CONTAINER_NAMES"]=(
334	    "Postgres-NextCloud"
335	)
336	PROFILES["important-data_DELAYED_CONTAINERS"]=("NextCloud")
337
338	# ── emby ──────────────────────────────────────────────────────────────────────
339	# Weekly full clean sync — both Emby instances stopped
340	PROFILES["emby_CRITICAL_CONTAINER_NAMES"]=("Emby")
341	PROFILES["emby_EXCLUDE_DIRS"]=(
342	    "transcodes/" "logs/" "crash*" "cache/"
343	)
344
345	# ── emby-fallback ─────────────────────────────────────────────────────────────
346	# Every 30 minutes, Emby STAYS RUNNING — dirty sync of critical state only
347	PROFILES["emby-fallback_CRITICAL_CONTAINER_NAMES"]=()   # nothing stops
348	PROFILES["emby-fallback_EXCLUDE_DIRS"]=(
349	    "*.wal" "*.shm"                            # unsafe mid-write
350	    "transcodes/" "logs/" "crash*" "cache/"
351	)
352	PROFILES["emby-fallback_REMOTE_RESTART_CONTAINERS"]=("Emby")
353	```
354
355	### Two Emby Profiles — Why Both Exist
356
357	**emby-fallback** (every 30 minutes, Emby stays running):
358	- Syncs: users.db, library.db, authentication.db, config/
359	- Skips: \*.wal, \*.shm, transcodes/, logs/, cache/
360	- Why: WAL files are written while Emby runs — copying them would produce a corrupt database on HOST2
361	- Result: HOST2 is always within 30 minutes of HOST1 on watch state and user activity
362
363	**emby** (Sunday 2:30am, both Emby instances stopped):
364	- Syncs: everything except transcodes, logs, cache, crash files
365	- Includes: metadata, plugins, full database state, all config
366	- Why: WAL is checkpointed on clean shutdown — safe to copy everything
367	- Result: HOST2 gets a gold-standard Emby state once per week
368
369	The two profiles work together. emby-fallback keeps HOST2 current for immediate fallback.
370	emby gives HOST2 full fidelity once per week. Neither alone is sufficient.
371
372	---
373
374	## ━━━ PERSONAL ENCRYPTED SHARES ━━━
375
376	Personal shares sync to the remote for offsite backup. ZFS encrypts at the dataset
377	level — the remote receives encrypted blocks and cannot read the content without your
378	passphrase or keyfile.
379
380	### Create an Encrypted ZFS Dataset
381
382	```bash
383	# In the unRAID UI:
384	# Main → click your ZFS pool name → + Dataset
385	#   Name: Gmer4Lfe-Personal
386	#   Encryption: Enabled
387	#   Passphrase: [your passphrase]
388	#   Write your passphrase down — if lost, data is completely unrecoverable
389
390	# Verify encryption is active before syncing:
391	zfs get encryption poolname/Gmer4Lfe-Personal
392	# Should show: encryption  aes-256-gcm
393	```
394
395	### Auto-Unlock on Boot (Optional)
396
397	```bash
398	# Keyfile approach — more convenient, but the keyfile itself is a secret
399	dd if=/dev/urandom bs=32 count=1 | base64 > /root/.zfs-keys/personal.key
400	chmod 600 /root/.zfs-keys/personal.key
401
402	zfs change-key \
403	    -o keylocation=file:///root/.zfs-keys/personal.key \
404	    -o keyformat=raw \
405	    poolname/Gmer4Lfe-Personal
406
407	# Add to array_started.sh or ramdisk_setup.sh:
408	zfs load-key poolname/Gmer4Lfe-Personal
409	zfs mount poolname/Gmer4Lfe-Personal
410
411	# Manual unlock alternative (most secure):
412	zfs load-key poolname/Gmer4Lfe-Personal    # prompts for passphrase
413	zfs mount poolname/Gmer4Lfe-Personal
414	```
415
416	### Add to host1.conf
417
418	```bash
419	HOST1_PERSONAL_SHARES=(
420	    "/mnt/user/Gmer4Lfe-Personal"
421	)
422	```
423
424	---
425
426	## ━━━ STEP 7 — SCHEDULER SETUP ━━━
427
428	All scheduling is managed by the Varaverk plugin — no User Scripts entries needed.
429
430	### Array start (Varaverk disks_mounted event hook)
431
432	The Varaverk plugin fires `array_started.sh` automatically via its `disks_mounted` event
433	hook. It's registered in `schedule.json`:
434
435	```json
436	"Orchestrators/array_started.sh": { "cron": "array_start", "enabled": true }
437	```
438
439	Launches everything in ARRAY_START_SCRIPTS: api_key_renew, inotify_tuning,
440	docker_syslog_filter, php_fpm_max_children, ramdisk_setup, docker_network_connect, fallback.
441	Watchdogs run separately via watchdog_orchestrator.sh (cron — every 15 minutes).
442
443	### Cron Schedule (Varaverk Scheduler)
444
445	```bash
446	*/7   * * * *   transcode_management.sh
447	*/15  * * * *   watchdog_orchestrator.sh
448	*/30  * * * *   critical_sync_maintenance.sh
449	0 */4  * * *    intermediate_sync_maintenance.sh
450	0  1   * * *    daily_sync_maintenance.sh
451	0  7   * * 0    sunday_morning_coffee_report.sh
452	30 2   * * 0    weekly_sync_maintenance.sh
453	0  0  15 * *    monthly_maintenance.sh
454	```
455
456	All entries are configured via the Varaverk scheduler UI or `schedule.json`.
457	Scripts live at `/boot/config/plugins/varaverk/`.
458
459	---
460
461	## ━━━ VERIFICATION ━━━
462
463	Test with `--dry-run` first — all pre-flight checks run, no changes made.
464
465	### Test a Single Profile Sync
466
467	```bash
468	bash /boot/config/plugins/varaverk/Rsync/rsync.sh \
469	    /mnt/user/appdata-Fallback/Arrs_Stack --dry-run --log
470	```
471
472	Expected output (healthy):
473	```
474	━━━ Setup ━━━
475	  Host: HOST1 (unRAID-Gmer4Lfe) → HOST2 (unRAID-Jayred365)
476	  Remote IP: 100.x.x.x
477	  Profile: arrs_stack
478
479	━━━ Pre-flight ━━━
480	  Remote reachable
481	  version parity — both on unRAID X.Y.Z
482	  Remote Docker daemon responding
483	  Remote rootfs: 12% (threshold: 75%)
484	  Remote share exists and not empty
485	  All pre-flight checks passed
486	```
487
488	### Test the Daily Orchestrator
489
490	```bash
491	bash /boot/config/plugins/varaverk/Orchestrators/daily_sync_maintenance.sh --dry-run
492	```
493
494	### Check Configuration Resolution
495
496	```bash
497	bash /boot/config/plugins/varaverk/Rsync/rsync.sh \
498	    /mnt/user/appdata-Fallback/Arrs_Stack --status
499	```
500
501	---
502
503	## ━━━ INITIAL HOST2 SYNC ━━━
504
505	If HOST2 is being set up from scratch with empty shares:
506
507	```bash
508	# Create share directories on HOST2:
509	bash /boot/config/plugins/varaverk/Tools/recreate_shares.sh
510
511	# Initial push from HOST1 — first run may take several hours for large libraries:
512	bash /boot/config/plugins/varaverk/Orchestrators/daily_sync_maintenance.sh --log
513	```
514
515	The scheduled nightly sync will be incremental after the initial push.
516
517	---
518
519	## ━━━ NAMING CONSISTENCY REQUIREMENT ━━━
520
521	The ecosystem uses one codebase on both servers. This only works if containers and
522	shares have identical names on both servers. **This is not configurable — it is a
523	design requirement.**
524
525	```
526	Container names must match exactly on both servers:
527	  "Emby"                ← both HOST1 and HOST2
528	  "NginxProxyManager"   ← both HOST1 and HOST2
529	  "Mariadb-Authelia"    ← both HOST1 and HOST2
530
531	Share paths must match exactly on both servers:
532	  /mnt/user/Movies      ← both HOST1 and HOST2
533	  /mnt/user/Tv_Shows    ← both HOST1 and HOST2
534	```
535
536	If a container has a different name on one server: the script skips it silently.
537	You only notice when the container is not stopped during a sync that requires it.
538
539	If a share has a different path: rsync.sh aborts with "remote share missing."
540	Easier to catch — but still requires renaming the share to fix.
541
542	---
543
544	## ━━━ TROUBLESHOOTING ━━━
545
546	### SSH Connection Refused / Timeout
547
548	```bash
549	# Verify SSH is enabled on the remote:
550	#   Settings → Management Access → Secure Shell → Enabled
551
552	# Verify Tailscale is connected:
553	tailscale ip -4 unRAID-Jayred365
554
555	# Test SSH manually (key path from --status output):
556	ssh -i /root/.ssh/gmer4lfe_rsync_automation \
557	    root@$(tailscale ip -4 unRAID-Jayred365) "hostname"
558	# If password prompted: key not authorised — re-run ssh_setup.sh
559
560	# Re-run setup (idempotent, re-copies key to remote):
561	bash /boot/config/plugins/varaverk/Partnership/ssh_setup.sh
562	```
563
564	### Pre-flight Aborts on Remote Rootfs
565
566	```bash
567	# Check current usage on remote:
568	ssh -i /root/.ssh/Gmer4Lfe-rsync-key root@[HOST2-ip] "df /"
569
570	# Common cause: array not started, drives not mounted
571	```
572
573	### Remote Share Missing
574
575	```bash
576	# Verify the share exists on HOST2:
577	ssh -i /root/.ssh/Gmer4Lfe-rsync-key root@[HOST2-ip] "ls /mnt/user/"
578
579	# If missing: create the share on HOST2, then run initial sync
580	```
581
582	### Containers Not Stopping / Starting
583
584	```bash
585	# Verify container names match Docker exactly — case sensitive:
586	docker ps --format "{{.Names}}"
587
588	# Test remote:
589	ssh -i /root/.ssh/Gmer4Lfe-rsync-key root@[HOST2-ip] "docker ps --format '{{.Names}}'"
590	```
591
592	### Profile Not Matching
593
594	```bash
595	# Profile key = directory basename lowercased
596	# /mnt/user/appdata-Fallback/Arrs_Stack → key: arrs_stack
597
598	# Override explicitly:
599	bash rsync.sh /mnt/user/appdata-Fallback/My_Stuff --profile=arrs_stack
600
601	# Verify what profile resolved:
602	bash rsync.sh /mnt/user/appdata-Fallback/Arrs_Stack --status
603	```
604
605	---
606
607	## ━━━ FULL CONFIGURATION REFERENCE ━━━
608
609	### master.conf
610
611	```bash
612	# Rsync engine
613	RSYNC_ENABLED=true
614	DEFAULT_RSYNC_OPTS="-az --no-perms --no-owner --no-group --inplace"
615	# No --delete in DEFAULT_RSYNC_OPTS — media share rsync is additive only.
616	# arr_sync.sh keeps all arr databases in union — either server can download anything.
617	# arr cleanup handles deletions (orphans only). Profiles use --delete explicitly.
618	BW_LIMIT=0                  # KB/s, 0 = unlimited
619	RETRY_COUNT=3
620	SLEEP=60                    # seconds between retries
621	ROOTFS_WARN_PCT=75          # abort if remote rootfs above this %
622
623	# Shared with Monitors/
624	BANDWIDTH_LOG="/boot/config/bandwidth_history.db"
625	BANDWIDTH_LOG_RETENTION=90
626	BANDWIDTH_WARN_GB=50
627
628	# Profile definitions (see RSYNC PROFILES section above)
629	declare -A PROFILES
630	declare -A PROFILE_BW_LIMIT
631	declare -A PROFILE_RETRY_COUNT
632	declare -A PROFILE_SLEEP
633	declare -A PROFILE_CONTAINER_DELAY
634	declare -A PROFILE_CRITICAL_CONTAINER_NAMES
635	declare -A PROFILE_DELAYED_CONTAINERS
636	declare -A PROFILE_EXCLUDE_DIRS
637	declare -A PROFILE_REMOTE_RESTART_CONTAINERS
638	```
639
640	### host*.conf
641
642	```bash
643	# Daily sync shares — one list per server (mutually exclusive)
644	HOST1_DAILY_SYNC_SHARES=(...)
645	HOST2_DAILY_SYNC_SHARES=(...)
646
647	# Personal encrypted shares
648	HOST1_PERSONAL_SHARES=(...)
649
650	# SSH key for this server to authenticate to the remote
651	# Set automatically by ssh_setup.sh — do not edit manually
652	HOST1_SSH_KEY="/root/.ssh/gmer4lfe_rsync_automation"
653	HOST2_SSH_KEY="/root/.ssh/jayred365_rsync_automation"
654	```
655
656	---
657
658	## ━━━ OUTPUT TIERS ━━━
659
660	Without `--log`, each run shows: section headers (Pre-flight, Transfer, Stop/Start
661	Containers), the transfer identity block (source, remote, profile, identity), a
662	progress indicator while rsync is running, and a summary block with duration,
663	bytes transferred, and status. Warnings and errors are always visible.
664
665	With `--log`, every decision is shown — pre-flight check results, profile resolution,
666	per-container stop/start state, retry details, and bandwidth log confirmation.
667	Use for first-run validation or when investigating unexpected behavior.
668
669	---
670
671	## ━━━ FLAG REFERENCE ━━━
672
673	All flags work on `rsync.sh` and all orchestrators.
674
675	### --dry-run
676
677	Runs all pre-flight checks. Shows what rsync would transfer. No transfer, no container
678	stops, no bandwidth log entry. Safe to run at any time.
679
680	```bash
681	rsync.sh /mnt/user/Movies --dry-run
682	rsync.sh /mnt/user/appdata-Fallback/Arrs_Stack --dry-run --log
683	daily_sync_maintenance.sh --dry-run
684	```
685
686	### --status
687
688	Shows resolved configuration — profile, remote identity, all vars that would be
689	used — then exits. No pre-flight checks, no rsync. Use to verify configuration
690	loaded correctly.
691
692	```bash
693	rsync.sh /mnt/user/appdata-Fallback/Arrs_Stack --status
694	```
695
696	### --log
697
698	Verbose output throughout. Every decision, every container operation, every rsync
699	progress line. Use for first-time runs or when investigating issues.
700
701	### --profile=name
702
703	Override profile selection. Bypasses basename inference. Use when the directory
704	name doesn't match any profile key, or when testing a specific profile.
705
706	```bash
707	rsync.sh /mnt/user/appdata-Fallback/Critical-Data --profile=critical-data
708	rsync.sh /mnt/user/Media_Server/Emby --profile=emby-fallback
709	```
710


1	#!/bin/bash
2	# ==============================================================================================
3	# ============================= Rsync Core Script ==============================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Core rsync engine for the two-server ecosystem. Called per share or per
9	# appdata profile by orchestrators (daily_sync_maintenance, weekly_sync_maintenance,
10	# critical_sync_maintenance) and directly for manual or scheduled dirty syncs.
11	#
12	# Profile is inferred from the directory basename (lowercased). Override with
13	# --profile=name for explicit selection. If no profile matches, global defaults
14	# from master.conf apply and no containers are stopped.
15	#
16	# After each sync, logs transfer data to bandwidth_monitor.sh for the weekly
17	# bandwidth report. Silent on success — only failures produce visible output.
18	#
19	# ==============================================================================================
20	# OPERATIONAL MODEL
21	# ==============================================================================================
22	#
23	# Profiles define per-share behavior:
24	#   PROFILE_CRITICAL_CONTAINER_NAMES  — containers stopped on both servers before sync
25	#   PROFILE_DELAYED_CONTAINERS        — containers with a delay before restart after sync
26	#   PROFILE_CONTAINER_DELAY           — seconds before delayed containers start
27	#   PROFILE_RSYNC_OPTS                — rsync flags (does not inherit DEFAULT_RSYNC_OPTS)
28	#   PROFILE_BW_LIMIT                  — bandwidth limit in KB/s
29	#   PROFILE_RETRY_COUNT               — retry attempts on failure
30	#   PROFILE_SLEEP                     — seconds between retry attempts
31	#   PROFILE_EXCLUDE_DIRS              — paths excluded from transfer
32	#   PROFILE_REMOTE_RESTART_CONTAINERS — containers restarted on remote after dirty sync
33	#                                       Was running → restart. Was stopped → leave stopped.
34	#
35	# Two-tier rsync enable/disable:
36	#   Tier 1: RSYNC_ENABLED=false → all rsync stops immediately (checked by this script)
37	#   Tier 2: per-orchestrator flag (DAILY_RSYNC_ENABLED etc.) → checked by caller
38	#
39	# Bandwidth logging: after each sync, logs profile/duration/status/bytes to
40	# bandwidth_monitor.sh --log-transfer. Bytes captured from rsync --stats via awk
41	# using version-stable field names.
42	#
43	# ==============================================================================================
44	# OPERATIONAL SAFEGUARDS
45	# ==============================================================================================
46	#
47	# Global Rsync Gate
48	#   check_rsync_enabled() — RSYNC_ENABLED=false exits cleanly before any operation.
49	#
50	# Partnership Blocklist
51	#   Refuses to sync if REMOTE_SERVER_NAME appears in the partnership blocklist.
52	#   Written at offboard — prevents stale access after a partnership ends.
53	#
54	# Version Parity
55	#   check_os_version_parity — refuses sync if servers on incompatible unRAID versions.
56	#
57	# Remote Health Pre-flights
58	#   check_connectivity() — Tailscale IP reachable before any SSH
59	#   check_remote_rootfs() — aborts if remote rootfs exceeds ROOTFS_WARN_PCT
60	#   check_remote_share() — aborts if target directory missing or empty on remote
61	#   check_remote_disks() — verifies all backing disks online on remote
62	#
63	# Drive Temperature Check
64	#   check_local_disk_temps() — runs before any transfer. Exit 1 = skip this profile,
65	#   exit 2 = abort all remaining syncs (CRITICAL temperature).
66	#
67	# Remote Docker Daemon Check
68	#   check_remote_docker_daemon — verified before any container stop/start operations.
69	#   If daemon unresponsive: container operations skipped, rsync proceeds without stopping.
70	#
71	# Per-Profile Concurrency Lock
72	#   acquire_rsync_lock() — per-profile lock prevents parallel runs of the same profile.
73	#   Global concurrent limit prevents too many simultaneous rsync processes.
74	#
75	# Notification Validated
76	#   platform_require_cmd confirms the notify script is present before use.
77	#
78	# ==============================================================================================
79	# CONFIGURATION
80	# ==============================================================================================
81	#
82	# master.conf
83	#
84	#   RSYNC_ENABLED
85	#       Global on/off toggle for all rsync operations. (default: true)
86	#
87	#   DEFAULT_RSYNC_OPTS
88	#       Base rsync flags for unproiled shares. Note: --delete is intentionally absent —
89	#       media shares spread files only, arr cleanup scripts own deletions. Profile-specific
90	#       opts set --delete explicitly where needed.
91	#
92	#   BW_LIMIT
93	#       Default bandwidth cap in KB/s when no PROFILE_BW_LIMIT is set. (default: 0 = unlimited)
94	#
95	#   RETRY_COUNT
96	#       Default retry attempts on rsync failure. (default: 3)
97	#
98	#   SLEEP
99	#       Default seconds between retry attempts. (default: 60)
100	#
101	#   ROOTFS_WARN_PCT
102	#       Abort threshold for remote rootfs percentage full. (default: 75)
103	#
104	#   PROFILES["profile_KEY"]
105	#       Profile definitions — one entry per PROFILE_* key per profile name.
106	#       See OPERATIONAL MODEL above for all supported keys.
107	#
108	#   BANDWIDTH_LOG / BANDWIDTH_WARN_GB
109	#       Shared with bandwidth_monitor.sh — set once, used by both.
110	#
111	# ==============================================================================================
112	# RUNTIME MODES
113	# ==============================================================================================
114	#
115	# rsync.sh /path/to/share
116	#     Sync the given path to the remote. Profile inferred from directory basename.
117	#
118	# rsync.sh /path/to/share --profile=name
119	#     Sync with explicit profile override — bypasses basename inference.
120	#


Now let me read the rest of the rsync.sh header to see the complete documentation:


120	#
121	# rsync.sh /path/to/share --dry-run
122	#     Run all pre-flight checks and show what rsync would transfer. No transfer,
123	#     no container stops.
124	#
125	# rsync.sh /path/to/share --status
126	#     Show resolved profile, remote identity, and configuration. Then exit.
127	#
128	# rsync.sh /path/to/share --log
129	#     Verbose output throughout — every decision logged.
130	#
131	# ==============================================================================================
132
133	SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
134
135	source "$SCRIPT_DIR/../load_config.sh"
136
137	# ── Separate positional directory arg from flags ───────────────────────────────────────────────
138	DIRECTORY=""
139	PROFILE_OVERRIDE=""
140	RAW_ARGS=()
141
142	for ARG in "$@"; do
143	    case "$ARG" in
144	        --profile=*) PROFILE_OVERRIDE="${ARG#--profile=}" ;;
145	        --*|*=*)     RAW_ARGS+=("$ARG") ;;
146	        *)           [[ -z "$DIRECTORY" ]] && DIRECTORY="$ARG" || RAW_ARGS+=("$ARG") ;;
147	    esac
148	done
149
150	parse_args "${RAW_ARGS[@]}"
151
152	[[ -z "$DIRECTORY" ]] && {
153	    error "No directory specified"
154	    error "Usage: rsync.sh <dir> [--dry-run] [--log] [--profile=name]"
155	    exit 1
156	}
157
158	# ==============================================================================================
159	# ━━━ Setup ━━━
160	# ==============================================================================================
161	if [[ "$EUID" -ne 0 ]]; then
162	    error "Must be run as root"
163	    exit 1
164	fi
165
166	acquire_lock
167
168	if ! command -v docker &>/dev/null; then
169	    error "Docker command not found"
170	    exit 1
171	fi
172
173
174	detect_hosts
175
176	# Tier 1 global gate — Tier 2 (per-orchestrator) checked by caller
177	if ! check_rsync_enabled; then
178	    warn "RSYNC_ENABLED=false — exiting cleanly"
179	    exit 0
180	fi
181
182	# Blocklist gate — refuse to sync with a partner blocked after offboard
183	BLOCKLIST_FILE="${PARTNERSHIP_BLOCKLIST_FILE:-${STATE_DIR}/partnership_blocklist.db}"
184	if [[ -f "$BLOCKLIST_FILE" ]] && grep -q "^${REMOTE_SERVER_NAME}|" "$BLOCKLIST_FILE" 2>/dev/null; then
185	    error "Rsync blocked — $REMOTE_SERVER_NAME is on the partnership blocklist"
186	    error "Re-onboard the partnership to restore access: partnership_manager.sh --onboard"
187	    exit 1
188	fi
189
190	resolve_remote_ip
191
192	# ── Profile inference ─────────────────────────────────────────────────────────────────────────
193	if [[ -n "$PROFILE_OVERRIDE" ]]; then
194	    PROFILE_NAME="$PROFILE_OVERRIDE"
195	    log "Profile override: $PROFILE_NAME"
196	else
197	    PROFILE_NAME=$(basename "$DIRECTORY" | tr '[:upper:]' '[:lower:]')
198	    log "Profile inferred: $PROFILE_NAME"
199	fi
200
201	# Acquire per-profile lock and check global concurrent limit
202	acquire_rsync_lock "$PROFILE_NAME"
203
204	# Tee all output to a live log file for the Varaverk UI
205	VV_LIVE_LOG="/tmp/unraid_locks/rsync_${PROFILE_NAME}.log"
206	VV_LAST_LOG="/tmp/unraid_locks/rsync_${PROFILE_NAME}.last.log"
207	: > "$VV_LIVE_LOG"
208	exec 1> >(tee -a "$VV_LIVE_LOG") 2>&1
209
210	# ── Load profile settings ─────────────────────────────────────────────────────────────────────
211	BW_LIMIT=${PROFILE_BW_LIMIT[$PROFILE_NAME]:-$BW_LIMIT}
212	RETRY_COUNT=${PROFILE_RETRY_COUNT[$PROFILE_NAME]:-$RETRY_COUNT}
213	SLEEP=${PROFILE_SLEEP[$PROFILE_NAME]:-$SLEEP}
214	CONTAINER_DELAY=${PROFILE_CONTAINER_DELAY[$PROFILE_NAME]:-$CONTAINER_DELAY}
215
216	read -r -a CRITICAL_CONTAINER_NAMES       <<< "${PROFILE_CRITICAL_CONTAINER_NAMES[$PROFILE_NAME]:-}"
217	read -r -a DELAYED_CONTAINERS             <<< "${PROFILE_DELAYED_CONTAINERS[$PROFILE_NAME]:-}"
218	read -r -a EXCLUDE_DIRS                   <<< "${PROFILE_EXCLUDE_DIRS[$PROFILE_NAME]:-}"
219	read -r -a REMOTE_RESTART_CONTAINERS      <<< "${PROFILE_REMOTE_RESTART_CONTAINERS[$PROFILE_NAME]:-}"
220
221	# Local containers use same names as remote (mirrored naming scheme)
222	LOCAL_CRITICAL_CONTAINER_NAMES=("${CRITICAL_CONTAINER_NAMES[@]}")
223
224	log "$ICON_GEAR Config: profile=${PROFILE_NAME} bw-limit=${BW_LIMIT}KB/s retry=${RETRY_COUNT} sleep=${SLEEP}s container-delay=${CONTAINER_DELAY}s"
225	log "$ICON_GEAR Containers: critical=${CRITICAL_CONTAINER_NAMES[*]:-none} delayed=${DELAYED_CONTAINERS[*]:-none} remote-restart=${REMOTE_RESTART_CONTAINERS[*]:-none}"
226
227	[[ "$SHOW_STATUS" == true ]] && show_status && exit 0
228	[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
229
230	# ==============================================================================================
231	# ━━━ Pre-flight Checks ━━━
232	# ==============================================================================================
233	echo ""
234	echo "━━━ $ICON_SHIELD Pre-flight ━━━"
235
236	# Disk temp — before touching remote or moving data
237	# Exit 1 = skip this profile | Exit 2 = abort all remaining profiles
238	check_local_disk_temps
239	TEMP_RESULT=$?


Perfect. Now I have all the information I need. Let me analyze the discrepancies between the documentation and the script header.

Based on my reading of the files, here are the discrepancies I found:

---

## Discrepancies Report

**README-Rsync.md (line 71) vs rsync.sh header (line 71):**
README states: "Cron (direct): `*/30 * * * *` ──────────────────────────────────► `rsync.sh --profile=emby-fallback`"
However, the script header and Manual do not specify this schedule. The script itself shows no knowledge of being automatically scheduled every 30 minutes — that's purely an orchestrator responsibility. This is misleading as rsync.sh doesn't self-schedule; the cron entry would need to be external.

**Manual-Rsync.md (line 448) vs actual cron schedule context:**
Manual lists: "`*/30 * * * *   critical_sync_maintenance.sh`"
But the README line 71 shows emby-fallback is the 30-minute sync. This is a documentation inconsistency — the */30 entry should be for emby-fallback dirty sync, not critical_sync_maintenance. (Manual later shows line 448 lists critical as */30, but elsewhere describes emby-fallback as "every 30 minutes.")

**No discrepancy found regarding:**
- Profiles and their container definitions — all match
- Configuration variables — all match
- Script behavior description — matches accurately
- Pre-flight checks — all described correctly
- Bandwidth logging — matches rsync.sh implementation

The main issue is the 30-minute schedule attribution is ambiguous between critical_sync_maintenance.sh (line 448 of Manual) and the emby-fallback profile (implied by README line 71 and Manual line 50).