ELNOR REPO READER TEXT MIRROR Original path: Current Specs/DOC5/DOC5A_WILL_ACCOUNT_BUILD.md Source repo: /Users/OpenClaw1/Elnor/Elnor Specs Git branch: main Git commit: dbaa25962edc11ab30e8d4ca1715f9ae5bf77331 Generated: 2026-06-09T01:23:58.539Z --- # DOC5A — File Sync Infrastructure (will account) **Date:** 2026-02-25 **Status:** Implementation guide — for Codex / Claude Code on the `will` user account. **Source:** DOC5 File Sync & Security Architecture v1.11.7 (§1–§6, §8–§10) **Companion:** DOC5B — Elnor Skill Build (openclaw1 account) --- ## What This Document Covers Everything that runs under or is owned by the `will` macOS user account: - Folder structure creation (both accounts — `will` has admin rights) - Permissions setup (cross-account ownership and chmod) - Sync scripts (rsync mirror pull + outbox push) - launchd plists (scheduled sync jobs) - Cleanup/purge scripts and their plists - The web dashboard (Express server, auth, API, frontend) - config.json and include files **What this does NOT cover:** The OpenClaw file-sync skill that Elnor uses. That's in DOC5B, built from the `openclaw1` account. --- ## Prerequisites - Logged into the `will` macOS user account - `will` has admin privileges (can `sudo`, can create folders under `/Users/openclaw1/`) - Node.js installed (for the dashboard) - OneDrive configured and syncing on the `will` account: - Firm: `/Users/will/Library/CloudStorage/OneDrive-SharedLibraries-schallfirm.com/` - Personal: `/Users/will/Library/CloudStorage/OneDrive-Personal/` - The `openclaw1` user account exists --- ## Phase 1 — Folder Structure & Permissions ### Step 1: Create All Folders Run as `will` (with sudo for openclaw1-owned paths): ```bash #!/bin/bash # setup-folders.sh — Run once from will account # Creates the complete folder structure for the file sync system set -e OC_HOME="/Users/openclaw1" echo "Creating folder structure..." # Mirror (will-owned, openclaw1 reads) sudo mkdir -p "$OC_HOME/onedrive-mirror/firm" sudo mkdir -p "$OC_HOME/onedrive-mirror/personal" # Outbox (openclaw1-owned, both read/write) sudo mkdir -p "$OC_HOME/onedrive-outbox/firm" sudo mkdir -p "$OC_HOME/onedrive-outbox/personal" # Working (openclaw1-owned, private scratch) sudo mkdir -p "$OC_HOME/working" # Snapshots (openclaw1-owned) sudo mkdir -p "$OC_HOME/snapshots/firm" sudo mkdir -p "$OC_HOME/snapshots/personal" # Mirror versions (will-owned) sudo mkdir -p "$OC_HOME/onedrive-mirror-versions" # Sync config (will-owned except requests.txt) sudo mkdir -p "$OC_HOME/sync-config/scripts" sudo mkdir -p "$OC_HOME/sync-config/logs" echo "Folders created." ``` ### Step 2: Set Ownership & Permissions ```bash #!/bin/bash # setup-permissions.sh — Run once from will account # Sets ownership and permissions per §2.2 of DOC5 set -e OC_HOME="/Users/openclaw1" WILL_USER="will" OC_USER="openclaw1" echo "Setting ownership..." # will-owned (Elnor reads only) sudo chown -R "$WILL_USER" "$OC_HOME/onedrive-mirror" sudo chown -R "$WILL_USER" "$OC_HOME/onedrive-mirror-versions" sudo chown -R "$WILL_USER" "$OC_HOME/sync-config" # openclaw1-owned (Elnor reads and writes) sudo chown -R "$OC_USER" "$OC_HOME/onedrive-outbox" sudo chown -R "$OC_USER" "$OC_HOME/working" sudo chown -R "$OC_USER" "$OC_HOME/snapshots" echo "Setting permissions..." # Mirror: openclaw1 can read, not write sudo chmod -R 755 "$OC_HOME/onedrive-mirror" sudo chmod -R 755 "$OC_HOME/onedrive-mirror-versions" # Outbox: both can read/write sudo chmod -R 777 "$OC_HOME/onedrive-outbox" # Working: openclaw1 only sudo chmod -R 700 "$OC_HOME/working" sudo chown -R "$OC_USER" "$OC_HOME/working" # Snapshots: openclaw1 writes, will can read via dashboard sudo chmod -R 755 "$OC_HOME/snapshots" # Sync config: will owns, openclaw1 reads sudo chmod -R 755 "$OC_HOME/sync-config" sudo chmod -R 700 "$OC_HOME/sync-config/scripts" # scripts: will only # Logs: will writes, openclaw1 reads sudo chmod -R 755 "$OC_HOME/sync-config/logs" # requests.txt: openclaw1 writes, will reads sudo touch "$OC_HOME/sync-config/requests.txt" sudo chown "$OC_USER" "$OC_HOME/sync-config/requests.txt" sudo chmod 644 "$OC_HOME/sync-config/requests.txt" echo "Permissions set." ``` ### Step 3: Verify Permissions After running both scripts, verify: ```bash # From will account: ls -la /Users/openclaw1/onedrive-mirror/ # owner: will ls -la /Users/openclaw1/onedrive-outbox/ # owner: openclaw1 ls -la /Users/openclaw1/sync-config/ # owner: will ls -la /Users/openclaw1/sync-config/scripts/ # owner: will, 700 ls -la /Users/openclaw1/sync-config/requests.txt # owner: openclaw1 # Test from openclaw1 (switch user or ssh): # Should succeed: # cat /Users/openclaw1/onedrive-mirror/firm/somefile.docx # touch /Users/openclaw1/onedrive-outbox/firm/test.txt # echo "test" >> /Users/openclaw1/sync-config/requests.txt # Should fail: # touch /Users/openclaw1/onedrive-mirror/firm/test.txt → Permission denied # touch /Users/openclaw1/sync-config/firm-folders.txt → Permission denied # cat /Users/openclaw1/sync-config/scripts/firm-mirror.sh → Permission denied ``` --- ## Phase 2 — Sync Scripts ### Step 4: config.json Create at `/Users/openclaw1/sync-config/config.json`: ```json { "accounts": { "firm": { "label": "Firm OneDrive", "onedrive_path": "/Users/will/Library/CloudStorage/OneDrive-SharedLibraries-schallfirm.com/", "mirror_path": "/Users/openclaw1/onedrive-mirror/firm/", "outbox_path": "/Users/openclaw1/onedrive-outbox/firm/", "mirror_interval_seconds": 180, "outbox_interval_seconds": 180, "mirror_active": true, "outbox_active": true, "auto_push": true }, "personal": { "label": "Personal OneDrive", "onedrive_path": "/Users/will/Library/CloudStorage/OneDrive-Personal/", "mirror_path": "/Users/openclaw1/onedrive-mirror/personal/", "outbox_path": "/Users/openclaw1/onedrive-outbox/personal/", "mirror_interval_seconds": 900, "outbox_interval_seconds": 900, "mirror_active": true, "outbox_active": true, "auto_push": true } }, "paths": { "working": "/Users/openclaw1/working/", "snapshots": "/Users/openclaw1/snapshots/", "mirror_versions": "/Users/openclaw1/onedrive-mirror-versions/" }, "naming_rule_default": "never_overwrite", "naming_overrides": {}, "retention": { "outbox_days": 7, "mirror_versions_days": 30, "snapshots_days": 60 }, "dashboard": { "port": 8417 } } ``` ### Step 5: Include Files Create `/Users/openclaw1/sync-config/firm-folders.txt`: ``` # Folders synced from firm OneDrive to Elnor's mirror # Managed by the dashboard — do not edit manually # First match wins. + includes, - excludes. - * ``` Create `/Users/openclaw1/sync-config/personal-folders.txt`: ``` # Folders synced from personal OneDrive to Elnor's mirror # Managed by the dashboard — do not edit manually - * ``` Both start empty (exclude all). Folders are added via the dashboard. ### Step 6: Mirror Pull Scripts Create `/Users/openclaw1/sync-config/scripts/firm-mirror.sh`: ```bash #!/bin/bash # Firm OneDrive → Mirror pull # Runs as 'will' user via launchd # SAFETY: rsync --delete only affects DESTINATION (the mirror), never SOURCE (OneDrive) CONFIG_DIR="/Users/openclaw1/sync-config" LOG="$CONFIG_DIR/logs/firm-mirror.log" INCLUDE_FILE="$CONFIG_DIR/firm-folders.txt" SOURCE="/Users/will/Library/CloudStorage/OneDrive-SharedLibraries-schallfirm.com/" DEST="/Users/openclaw1/onedrive-mirror/firm/" VERSIONS="/Users/openclaw1/onedrive-mirror-versions/$(date +%Y%m%d)" # Check if paused ACTIVE=$(python3 -c " import json config = json.load(open('$CONFIG_DIR/config.json')) print(config['accounts']['firm']['mirror_active']) ") if [ "$ACTIVE" = "False" ]; then echo "$(date '+%Y-%m-%d %H:%M:%S'): Firm mirror PAUSED — skipping" >> "$LOG" exit 0 fi mkdir -p "$DEST" "$VERSIONS" echo "$(date '+%Y-%m-%d %H:%M:%S'): Starting firm mirror sync" >> "$LOG" rsync -av --delete \ --backup --backup-dir="$VERSIONS" \ --include-from="$INCLUDE_FILE" \ "$SOURCE" \ "$DEST" \ >> "$LOG" 2>&1 EXIT_CODE=$? echo "$(date '+%Y-%m-%d %H:%M:%S'): Firm mirror sync complete (exit: $EXIT_CODE)" >> "$LOG" ``` Create `/Users/openclaw1/sync-config/scripts/personal-mirror.sh` — identical structure, swap `firm` → `personal` and use the Personal OneDrive path. ### Step 7: Outbox Push Scripts Create `/Users/openclaw1/sync-config/scripts/firm-outbox.sh`: ```bash #!/bin/bash # Outbox → Firm OneDrive push # Runs as 'will' user via launchd # SAFETY: No --delete flag. Can only add/update files, never delete from OneDrive. # SAFETY: Naming rules enforced before copy. # SAFETY: Path traversal blocked. CONFIG_DIR="/Users/openclaw1/sync-config" CONFIG_FILE="$CONFIG_DIR/config.json" LOG="$CONFIG_DIR/logs/firm-outbox.log" SOURCE="/Users/openclaw1/onedrive-outbox/firm/" DEST="/Users/will/Library/CloudStorage/OneDrive-SharedLibraries-schallfirm.com/" # Check if paused ACTIVE=$(python3 -c " import json config = json.load(open('$CONFIG_FILE')) print(config['accounts']['firm']['outbox_active']) ") if [ "$ACTIVE" = "False" ]; then echo "$(date '+%Y-%m-%d %H:%M:%S'): Firm outbox push PAUSED — skipping" >> "$LOG" exit 0 fi echo "$(date '+%Y-%m-%d %H:%M:%S'): Starting firm outbox push" >> "$LOG" # Read global naming rule NAMING_RULE=$(python3 -c " import json config = json.load(open('$CONFIG_FILE')) print(config.get('naming_rule_default', 'never_overwrite')) ") # Process each file in the outbox (skip .pushed markers) find "$SOURCE" -type f -not -name "*.pushed" | while read -r OUTBOX_FILE; do REL_PATH="${OUTBOX_FILE#$SOURCE}" # Path traversal check if [[ "$REL_PATH" == *".."* ]]; then echo "$(date '+%Y-%m-%d %H:%M:%S'): BLOCKED $REL_PATH — path traversal detected" >> "$LOG" continue fi # Skip already-pushed files if [ -f "${OUTBOX_FILE}.pushed" ]; then continue fi DEST_FILE="$DEST$REL_PATH" DEST_DIR=$(dirname "$DEST_FILE") # Check for folder-specific naming override FOLDER_RULE=$(python3 -c " import json, sys config = json.load(open('$CONFIG_FILE')) overrides = config.get('naming_overrides', {}) rel = '$REL_PATH' for folder, rule in overrides.items(): if rel.startswith(folder): print(rule) sys.exit(0) print('$NAMING_RULE') ") # Apply naming rule case "$FOLDER_RULE" in "elnor_decides") ;; "never_overwrite") if [ -f "$DEST_FILE" ]; then echo "$(date '+%Y-%m-%d %H:%M:%S'): BLOCKED $REL_PATH — file exists (never_overwrite)" >> "$LOG" continue fi ;; "auto_version") if [ -f "$DEST_FILE" ]; then BASENAME="${DEST_FILE%.*}" EXT="${DEST_FILE##*.}" VERSION=2 while [ -f "${BASENAME}_v${VERSION}.${EXT}" ]; do VERSION=$((VERSION + 1)) done DEST_FILE="${BASENAME}_v${VERSION}.${EXT}" echo "$(date '+%Y-%m-%d %H:%M:%S'): Auto-versioned $REL_PATH → $(basename "$DEST_FILE")" >> "$LOG" fi ;; "timestamp") BASENAME="${DEST_FILE%.*}" EXT="${DEST_FILE##*.}" TS=$(date +%Y%m%d-%H%M%S) DEST_FILE="${BASENAME}_${TS}.${EXT}" ;; esac mkdir -p "$DEST_DIR" cp -p "$OUTBOX_FILE" "$DEST_FILE" if [ $? -eq 0 ]; then echo "$(date '+%Y-%m-%d %H:%M:%S'): PUSHED $REL_PATH → $(basename "$DEST_FILE")" >> "$LOG" touch "${OUTBOX_FILE}.pushed" else echo "$(date '+%Y-%m-%d %H:%M:%S'): FAILED $REL_PATH" >> "$LOG" fi done echo "$(date '+%Y-%m-%d %H:%M:%S'): Firm outbox push complete" >> "$LOG" ``` Create `personal-outbox.sh` — same structure, swap `firm` → `personal`, use Personal OneDrive path. ### Step 8: Make Scripts Executable (will-only) ```bash sudo chmod 700 /Users/openclaw1/sync-config/scripts/*.sh sudo chown will /Users/openclaw1/sync-config/scripts/*.sh ``` --- ## Phase 3 — launchd Jobs ### Step 9: Sync Plists All plists go in `~/Library/LaunchAgents/` on the `will` account (i.e., `/Users/will/Library/LaunchAgents/`). **com.elnor.sync-mirror-firm.plist:** ```xml Label com.elnor.sync-mirror-firm ProgramArguments /bin/bash /Users/openclaw1/sync-config/scripts/firm-mirror.sh StartInterval 180 RunAtLoad StandardErrorPath /Users/openclaw1/sync-config/logs/firm-mirror-stderr.log ``` **Create four plists total:** | Plist filename | Script | Default interval | |---|---|---| | `com.elnor.sync-mirror-firm.plist` | `firm-mirror.sh` | 180s (3 min) | | `com.elnor.sync-mirror-personal.plist` | `personal-mirror.sh` | 900s (15 min) | | `com.elnor.sync-outbox-firm.plist` | `firm-outbox.sh` | 180s (3 min) | | `com.elnor.sync-outbox-personal.plist` | `personal-outbox.sh` | 900s (15 min) | ### Step 10: Cleanup Plists **Purge scripts** (in `sync-config/scripts/`): `purge-versions.sh`: ```bash #!/bin/bash VERSIONS_DIR="/Users/openclaw1/onedrive-mirror-versions" RETENTION_DAYS=$(python3 -c " import json config = json.load(open('/Users/openclaw1/sync-config/config.json')) print(config['retention']['mirror_versions_days']) ") find "$VERSIONS_DIR" -maxdepth 1 -type d -mtime +${RETENTION_DAYS} -exec rm -rf {} \; ``` `purge-snapshots.sh`: ```bash #!/bin/bash SNAPSHOTS_DIR="/Users/openclaw1/snapshots" RETENTION_DAYS=$(python3 -c " import json config = json.load(open('/Users/openclaw1/sync-config/config.json')) print(config['retention']['snapshots_days']) ") find "$SNAPSHOTS_DIR" -type f -mtime +${RETENTION_DAYS} -delete ``` `purge-outbox.sh`: ```bash #!/bin/bash OUTBOX_DIR="/Users/openclaw1/onedrive-outbox" RETENTION_DAYS=$(python3 -c " import json config = json.load(open('/Users/openclaw1/sync-config/config.json')) print(config['retention']['outbox_days']) ") find "$OUTBOX_DIR" -name "*.pushed" -mtime +${RETENTION_DAYS} | while read -r MARKER; do ORIGINAL="${MARKER%.pushed}" rm -f "$MARKER" "$ORIGINAL" done ``` **One plist for all cleanup** (`com.elnor.sync-purge.plist`): ```xml Label com.elnor.sync-purge ProgramArguments /bin/bash -c /Users/openclaw1/sync-config/scripts/purge-versions.sh; /Users/openclaw1/sync-config/scripts/purge-snapshots.sh; /Users/openclaw1/sync-config/scripts/purge-outbox.sh StartCalendarInterval Hour 3 Minute 0 RunAtLoad ``` ### Step 11: Load All Jobs ```bash launchctl load ~/Library/LaunchAgents/com.elnor.sync-mirror-firm.plist launchctl load ~/Library/LaunchAgents/com.elnor.sync-mirror-personal.plist launchctl load ~/Library/LaunchAgents/com.elnor.sync-outbox-firm.plist launchctl load ~/Library/LaunchAgents/com.elnor.sync-outbox-personal.plist launchctl load ~/Library/LaunchAgents/com.elnor.sync-purge.plist # Verify: launchctl list | grep elnor ``` --- ## Phase 4 — Web Dashboard ### Step 12: Scaffold Create the Express app at `~/.elnor-sync-dashboard/` on the `will` account. ``` ~/.elnor-sync-dashboard/ ├── package.json ├── server.js ← main Express server ├── auth.json ← bcrypt password hash (created on first run) ├── routes/ │ ├── auth.js │ ├── status.js │ ├── folders.js │ ├── sync.js │ ├── outbox.js │ ├── requests.js │ ├── snapshots.js │ └── settings.js └── public/ ├── index.html ← single-page frontend ├── style.css └── app.js ``` **Technology:** - Node.js + Express - bcryptjs for password hashing - express-session for session cookies - No database — reads/writes JSON config files and runs shell commands ### Step 13: Authentication - Password set on first run (prompt or setup endpoint) - Stored as bcrypt hash in `auth.json` - Session-based: login once, cookie lasts 24h - 5 failed attempts → 15 min lockout - Bind to `127.0.0.1` only (never `0.0.0.0`) - Every endpoint requires authentication — no exceptions ### Step 14: API Endpoints Full endpoint list from DOC5 §6.3: | Method | Endpoint | Purpose | |---|---|---| | POST | `/api/auth/login` | Login | | POST | `/api/auth/logout` | Logout | | GET | `/api/status` | Overall sync status | | GET | `/api/accounts` | OneDrive account info | | POST | `/api/accounts/detect` | Re-detect OneDrive folders | | GET | `/api/folders/{account}` | Synced folders | | POST | `/api/folders/{account}/add` | Add folder to sync | | DELETE | `/api/folders/{account}/{folder}` | Remove folder from sync | | GET | `/api/folders/{account}/available` | Browse OneDrive tree | | POST | `/api/sync/{account}/mirror/pause` | Pause mirror | | POST | `/api/sync/{account}/mirror/resume` | Resume mirror | | POST | `/api/sync/{account}/mirror/now` | Trigger immediate mirror | | POST | `/api/sync/{account}/outbox/pause` | Pause outbox push | | POST | `/api/sync/{account}/outbox/resume` | Resume outbox push | | POST | `/api/sync/{account}/outbox/now` | Trigger immediate push | | POST | `/api/sync/emergency-stop` | Stop all jobs | | GET | `/api/sync/{account}/timing` | Get interval | | PUT | `/api/sync/{account}/timing` | Set interval (updates plist, reloads job) | | GET | `/api/outbox/{account}` | List outbox files | | POST | `/api/outbox/{account}/push/{file}` | Push specific file | | POST | `/api/outbox/{account}/rename/{file}` | Rename outbox file | | DELETE | `/api/outbox/{account}/{file}` | Delete from outbox | | POST | `/api/outbox/{account}/overwrite/{file}` | Force-push blocked file | | GET | `/api/requests` | Elnor's folder requests | | POST | `/api/requests/{id}/approve` | Approve request (adds folder to include list) | | POST | `/api/requests/{id}/deny` | Deny request | | GET | `/api/snapshots/{account}` | List snapshots | | POST | `/api/snapshots/{account}/restore/{file}` | Copy snapshot to outbox | | DELETE | `/api/snapshots/{account}/{file}` | Delete snapshot | | GET | `/api/versions/{account}` | Mirror version history | | GET | `/api/logs/{job}` | Recent log lines | | GET | `/api/settings` | All settings | | PUT | `/api/settings` | Update settings | **Key implementation details for sync control endpoints:** Pause/resume: Update `mirror_active` or `outbox_active` in config.json. The sync scripts already check this flag at the start and skip if paused. Timing changes: Update `mirror_interval_seconds` / `outbox_interval_seconds` in config.json, then: ```bash launchctl unload ~/Library/LaunchAgents/com.elnor.sync-mirror-firm.plist # Update in the plist file launchctl load ~/Library/LaunchAgents/com.elnor.sync-mirror-firm.plist ``` Emergency stop: Unload all four sync plists. Trigger now: Run the script directly via `execSync`. ### Step 15: Frontend Single-page HTML/CSS/JS matching the wireframes in DOC5 §6.2. Four views: 1. **Main page** — Sync status for both accounts, synced folders, outbox summary, Elnor's requests 2. **Outbox view** — Pending, blocked, recently pushed files with actions 3. **Snapshots view** — Browse and restore pre-edit backups 4. **Settings page** — Paths, naming rules, timing, retention, password See DOC5 §6.2 for complete wireframe layouts. ### Step 16: Dashboard launchd Plist `com.elnor.sync-dashboard.plist`: ```xml Label com.elnor.sync-dashboard ProgramArguments /usr/local/bin/node /Users/will/.elnor-sync-dashboard/server.js RunAtLoad KeepAlive StandardErrorPath /Users/will/.elnor-sync-dashboard/dashboard-error.log StandardOutPath /Users/will/.elnor-sync-dashboard/dashboard.log ``` Load: `launchctl load ~/Library/LaunchAgents/com.elnor.sync-dashboard.plist` --- ## Phase 5 — Testing ### Infrastructure Tests (after Phase 1–3) | # | Test | How | Expected | |---|---|---|---| | 1 | Folders exist | `ls -la /Users/openclaw1/onedrive-mirror/` etc. | All folders present | | 2 | Permissions correct | See Step 3 verification commands | Pass all checks | | 3 | Mirror pull works | Add a folder to firm-folders.txt, run `bash firm-mirror.sh` | Files appear in mirror | | 4 | Mirror versioning | Edit a file in OneDrive, re-run mirror | Old version saved to versions dir | | 5 | Outbox push works | Put a test file in outbox, run `bash firm-outbox.sh` | File appears in OneDrive | | 6 | never_overwrite blocks | Put a file with existing name in outbox, push | Blocked, logged | | 7 | auto_version works | Set naming rule, push conflicting file | File saved as `_v2` | | 8 | Path traversal blocked | Put file with `..` in path, push | Blocked, logged | | 9 | launchd jobs running | `launchctl list \| grep elnor` | All 5 jobs listed | | 10 | Purge works | Create old test files, run purge script | Old files deleted | ### Dashboard Tests (after Phase 4) | # | Test | How | Expected | |---|---|---|---| | 11 | Server starts | `node server.js`, visit localhost:8417 | Login page | | 12 | Auth works | Login with password | Session created | | 13 | Unauth rejected | Hit API without cookie | 401 | | 14 | Status API | GET /api/status | Correct sync state | | 15 | Add folder | Use folder picker, add a folder | Appears in include list, mirrors on next sync | | 16 | Remove folder | Remove folder via dashboard | Removed from include list | | 17 | Pause/resume | Pause mirror, verify skip, resume | Script skips when paused | | 18 | Outbox management | View, push, rename, delete via dashboard | All operations work | | 19 | Approve request | Write a request to requests.txt, approve in dashboard | Folder added | | 20 | Settings persist | Change settings, restart dashboard | Settings retained | --- ## OneDrive Files-On-Demand Note If OneDrive is set to "Files On-Demand," cloud-only stub files won't sync correctly. Best solution: right-click each synced folder in OneDrive → "Always Keep on This Device." Do this once per folder. Fallback (add to mirror scripts before rsync): ```bash # Force-download cloud-only stubs find "$SOURCE" -name "*.docx" -o -name "*.pdf" -o -name "*.xlsx" | while read -r F; do if xattr -p com.microsoft.OneDrive.DownloadState "$F" 2>/dev/null | grep -q "stub"; then cat "$F" > /dev/null 2>&1 fi done sleep 5 ``` --- ## Files Created by This Build | File | Location | Owner | |---|---|---| | `setup-folders.sh` | One-time run script | will | | `setup-permissions.sh` | One-time run script | will | | `config.json` | `/Users/openclaw1/sync-config/` | will | | `firm-folders.txt` | `/Users/openclaw1/sync-config/` | will | | `personal-folders.txt` | `/Users/openclaw1/sync-config/` | will | | `requests.txt` | `/Users/openclaw1/sync-config/` | openclaw1 | | `firm-mirror.sh` | `/Users/openclaw1/sync-config/scripts/` | will | | `personal-mirror.sh` | `/Users/openclaw1/sync-config/scripts/` | will | | `firm-outbox.sh` | `/Users/openclaw1/sync-config/scripts/` | will | | `personal-outbox.sh` | `/Users/openclaw1/sync-config/scripts/` | will | | `purge-versions.sh` | `/Users/openclaw1/sync-config/scripts/` | will | | `purge-snapshots.sh` | `/Users/openclaw1/sync-config/scripts/` | will | | `purge-outbox.sh` | `/Users/openclaw1/sync-config/scripts/` | will | | 4× sync plists | `/Users/will/Library/LaunchAgents/` | will | | 1× purge plist | `/Users/will/Library/LaunchAgents/` | will | | 1× dashboard plist | `/Users/will/Library/LaunchAgents/` | will | | Dashboard app | `/Users/will/.elnor-sync-dashboard/` | will | --- ## What Happens Next After this build is complete and tested, switch to the `openclaw1` account and run DOC5B to build the file-sync skill that lets Elnor interact with this infrastructure. --- **End of DOC5A.**