VCM sync dashboard. Two features:
- Media Sync — copy course media from a NAS source folder into a local
media/vcm-sdestination, with selective course/instruction/discourse filtering and mirror/overwrite/update modes. - Tablet Sync — push a local
media/vcm-sfolder onto a connected Android tablet viaadb-sync, plus device setup (orientation, DND, screen timeout) and wallpaper customization.
./start.shThat's it — on a fresh machine or a stale clone, it self-bootstraps:
- Verifies Node ≥ 18, npm, and warns if
adb/adb-syncare missing (needed for Tablet Sync only). - Installs root + client
node_modulesif missing (with one retry on failure). - Makes
clone-tab/clone.shexecutable and createsserver/data/for sync history. - Checks ports 3001 and 5173. If in use, lists the offending process and prompts to kill it.
- Boots Express + Vite concurrently.
Flags:
./start.sh --clean # wipe node_modules and reinstall (use if deps got weird)
./start.sh --no-deps # skip dependency checks (fastest restart)
./start.sh --helpServer runs on http://localhost:3001, client on http://localhost:5173. Ctrl+C stops both — the server aborts any in-flight copy + tablet-sync subprocess before exiting.
server/
├── index.js bootstrap (express + socket.io)
├── config.js ports, paths, sync modes, ignored files
├── state.js shared singletons (abort controller, last dest)
├── routes/
│ ├── config.js GET /api/config
│ ├── structure.js GET /api/structure
│ ├── copy.js POST /api/copy, /api/stop
│ └── tablet-sync.js POST /api/tablet-sync/start|stop
├── services/
│ ├── task-builder.js selections → copy task list
│ ├── mirror-cleanup.js pre-copy destination pruning
│ └── copy-orchestrator.js scan + cleanup + copy + emit progress
└── lib/
├── path-validator.js whitelist allowed source/dest roots
├── find-source-root.js picks first existing CANDIDATE_PATHS
├── copier.js recursive copy + stream + smart-skip
└── tablet-sync.js spawns clone-tab/clone.sh
client/
└── src/
├── App.jsx tabs (Media Sync / Tablet Sync)
├── feature/
│ ├── SyncDashboard.jsx
│ ├── TabletSync.jsx
│ └── CopyProgress.jsx
├── hooks/
│ ├── useSyncState.js hook for the dashboard
│ └── useConfig.js fetches /api/config (once, cached)
├── lib/
│ ├── languageCodes.js ENG → English, plus auto-map helper
│ └── utils.js cn() — tailwind class merger
└── components/ui/ Button, Badge, Card
clone-tab/clone.sh adb settings + headless wallpaper + adb-sync
All filesystem paths live in server/config.js:
SOURCE_PATHS— candidate NAS roots to scanDESTINATION_PATHS— candidate local dest roots (dropdown in UI)TABLET_SOURCE_PATHS— sources for the tablet-sync tabALLOWED_SOURCE_ROOTS/ALLOWED_DESTINATION_ROOTS— security whitelist; any path outside these is rejected before any FS operation
The client fetches this list from /api/config so dropdowns stay in sync with the server config without duplication.
| Route | Method | Purpose |
|---|---|---|
/api/config |
GET | Static config (paths, modes) |
/api/structure?sourcePath=&force= |
GET | Scan source, return course tree (cached 10 min per sourcePath) |
/api/copy |
POST | Start copy. Body: { selections, destination, sourcePath, syncMode } |
/api/stop |
POST | Abort the current copy |
/api/tablet-sync/start |
POST | Run clone-tab/clone.sh. Body: { sourcePath, centerName, skipWallpaper } |
/api/tablet-sync/stop |
POST | SIGTERM the running clone.sh and its children |
Socket.io events emitted server → client: log, progress, complete, stopped, tablet-log, tablet-complete.
- mirror (default) — pre-cleans dest to match selection, then copies. Files unique to dest (and not in source) are deleted.
- overwrite — wipes dest entirely, then copies. Confirmation prompt in UI.
- update — copies on top of dest, no cleanup. Smart-skip if size matches and source is not newer.
clone-tab/clone.sh does (in order): adb device check → date/time/orientation/timeout/DND/dev-options/lockscreen settings → wallpaper → adb-sync of media folder.
Wallpaper: uses headless cmd wallpaper set-from-file (Android 7+). Falls back to stdin-pipe, then the Gallery picker if both refuse.
To override the default wallpaper text, pass CENTER_NAME as env var (the server does this from the UI input) — the script invokes scripts/create_wallpaper.js to overlay text on pagoda.jpg.
scripts/gen_all_updates.sh is a separate one-off SQL generator for the data2-<centre>.db SQLite DB used by the VCM Android app. It scans a media tree for valid instruction/discourse folders and calls gen-updates-v2 for each.
./scripts/gen_all_updates.sh --media-root /path/to/media --out /tmp/a.sql
./scripts/gen_all_updates.sh --dry-run # preview which folders would be scannedRequires gen-updates-v2 on PATH.