# Dashcam Transfer Automatisch dashcam-opnames (MP4) overzetten van een SD-kaart naar een USB-SSD, met organisatie per datum en camera-positie (front/back). Draait op de **nextcloud** server. ## Architectuur ``` SD-kaart insteken → udev detecteert vendor:product (14cd:1212 of 05e3:0751) → systemd-run start /usr/local/bin/dashcam-transfer-mount %k → skip als de transfer-service al draait (flaky lezer = geen 2e mount) → wacht tot /dev/sdX1 verschijnt (max 60s) → mount apparaat dynamisch op /mnt/sdcard (uid=1000, exfat) → read-only fsck.exfat health check (logt, blokkeert niet) → systemctl start dashcam-transfer.service → /usr/local/bin/dashcam-transfer 1. Check mounts (/mnt/sdcard, /mnt/ultratouch) 2. Scan /mnt/sdcard/Novatek/Movie/ op *.MP4 3. Leesbaarheidscheck bron (voorkomt ruimte wissen bij defecte kaart) 4. Check vrije ruimte — verwijder oudste datum-mappen indien nodig 5. Copy + verify + delete per bestand, met retry 6. Early-abort bij aanhoudende I/O-fouten (SD-kaart defect) 7. Stuur WhatsApp notificatie via wwebjs-api SD-kaart uittrekken → udev REMOVE trigger (zelfde vendor:product) → systemd-run start /usr/local/bin/dashcam-transfer-cleanup (%k) → stale mount? (device-node weg of source = verwijderd device) → stop dashcam-transfer.service → umount /mnt/sdcard (fallback: umount -l) ``` ## Robuustheid Het script is ontworpen tegen de hierboven geziene productie-fout (defecte/flakke SD-kaartlezer die midden in de transfer offline klapt, `I/O error`, `device offline`): - **Echte fout zichtbaar** — kopieerfouten worden als `OSError` met reden gelogd (i.p.v. alleen "exit status 1"); de eerdere `cp`-stderr ging verloren. - **Pre-flight leesbaarheidscheck** — voordat oude datum-mappen op de SSD worden verwijderd, wordt eerst een bestand van de kaart gelezen. Een onleesbare kaart resulteert in een duidelijke WhatsApp-"GESTOPT" en **niets** wordt gewist. - **Retry per bestand** — 3 pogingen met 2s pauze; vangt transiënte USB-glitches. - **Early-abort** — bij 5 opeenvolgende fouten stopt het script en stuurt het een WhatsApp met de laatste fout. Bronbestanden worden nooit verwijderd zonder geverifieerde kopie. - **Geen crash mid-loop** — alle bestandssysteem-calls zijn afgeschermd. - **fsck.exfat** — na mount draait het mount-script een read-only health check; een dirty/defecte kaart wordt daarmee zichtbaar in syslog. - **Geen dubbele runs** — het mount-script skip als de transfer al actief is. - **Stale-mount opruiming** — als de kaartlezer wordt uitgetrokken (of de kaart offline klapt) ruimt een udev REMOVE-trigger de mount op: de service wordt gestopt en `/mnt/sdcard` netjes ontkoppeld (lazy als dat mislukt). Zo blijft er nooit een dood device in de `mount`-output hangen. ## Hardware | Component | Mount | Device | Doel | |---|---|---|---| | USB-SSD (4TB, Seagate Ultra Touch) | `/mnt/ultratouch` | `/dev/disk/by-uuid/3027-8ECA` | Opslag | | SD-kaart (via USB reader) | `/mnt/sdcard` | dynamisch via udev `%k` | Bron | SSD wordt gemount via systemd `.automount` unit (permanent aangesloten). SD-kaart wordt gemount door `dashcam-transfer-mount.sh` na udev trigger, met het kernel-device (b.v. `sde1`) als argument — zo werkt het ongeacht het filesystem UUID. ## Bestandsstructuur ### SD-kaart (bron) ``` /mnt/sdcard/Novatek/Movie/ ├── 20260622050346_0000001F.MP4 ← front camera ├── 20260622050346_0000002B.MP4 ← back camera └── ... ``` Bestandsnaam: `YYYYMMDDHHMMSS_.MP4` - `F` = front, `B` = back - ~381 MB per clip (~5 minuten) ### SSD (doel) ``` /mnt/ultratouch/dashcam/ └── YYYYMMDD/ ├── front/ │ └── ...F.MP4 └── back/ └── ...B.MP4 ``` ## WhatsApp notificatie Na elke transfer stuurt het script een WhatsApp bericht naar de beheerder: ``` ✅ Dashcam transfer voltooid: 164 bestanden (56.8 GB) overgezet, geen fouten ``` De API (`wwebjs-api`) draait als Docker container op nextcloud zelf: | Config | Waarde | |---|---| | API endpoint | `http://localhost:3001/client/sendMessage/EdGpt` | | Sessie | `EdGpt` | | Ontvanger | `31620248011@c.us` (privé chat Eddy) | De container: `docker-compose.yml` in `/home/aiaiaiaiagent/wwebjs-api/`. ## Service & triggers ### Udev rule (`/etc/udev/rules.d/99-dashcam-trigger.rules`) ```udev ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd*[0-9]", ENV{ID_VENDOR_ID}=="14cd", ENV{ID_MODEL_ID}=="1212", RUN+="/usr/bin/systemd-run --no-block --collect /usr/local/bin/dashcam-transfer-mount %k" ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd*[0-9]", ENV{ID_VENDOR_ID}=="05e3", ENV{ID_MODEL_ID}=="0751", RUN+="/usr/bin/systemd-run --no-block --collect /usr/local/bin/dashcam-transfer-mount %k" ACTION=="remove", SUBSYSTEM=="block", KERNEL=="sd*[0-9]", ENV{ID_VENDOR_ID}=="14cd", ENV{ID_MODEL_ID}=="1212", RUN+="/usr/bin/systemd-run --no-block --collect /usr/local/bin/dashcam-transfer-cleanup %k" ACTION=="remove", SUBSYSTEM=="block", KERNEL=="sd*[0-9]", ENV{ID_VENDOR_ID}=="05e3", ENV{ID_MODEL_ID}=="0751", RUN+="/usr/bin/systemd-run --no-block --collect /usr/local/bin/dashcam-transfer-cleanup %k" ``` `%k` geeft het kernel device (b.v. `sde1`) door aan het mount- en cleanup-script, zodat de kaart dynamisch gemount wordt ongeacht het filesystem UUID. Na wijziging: `sudo udevadm control --reload-rules`. ### Mount script (`/usr/local/bin/dashcam-transfer-mount.sh`) Wacht tot het doorgegeven device beschikbaar is, unmount eventuele stale mounts, mount de SD-kaart en start de service. ### Cleanup script (`/usr/local/bin/dashcam-transfer-cleanup.sh`) Draait op udev REMOVE-trigger. Checkt of `/mnt/sdcard` een stale mount is (device-node weg of gemount op een verwijderd device) en ruimt hem dan op: service stoppen, `umount` (fallback `umount -l`). Bij een gezonde mount (ander device) doet het niets. ### Systemd service (`/etc/systemd/system/dashcam-transfer.service`) ```ini [Unit] Description=Dashcam SD card transfer After=mnt-sdcard.mount [Service] Type=oneshot ExecStart=/usr/local/bin/dashcam-transfer User=root ``` ### Logging Alle activiteit wordt gelogd naar `/tmp/dashcam-transfer.log` op nextcloud. ## Handmatig gebruik ```bash # Alle bestanden overzetten /usr/local/bin/dashcam-transfer # Beperkt tot eerste N bestanden (testen) /usr/local/bin/dashcam-transfer --limit 10 ``` ## Ontwikkeling Lokale repo: `~/src/dashcam-transfer/` op Mac Mini. Tests: `python3 -m unittest discover -s tests`, `./tests/test_mount_script.sh` en `./tests/test_cleanup_script.sh`. Deployen naar nextcloud: ```bash scp dashcam-transfer.py aiaiaiaiagent@nextcloud:/tmp/ ssh aiaiaiaiagent@nextcloud 'sudo -A cp /tmp/dashcam-transfer.py /usr/local/bin/dashcam-transfer' scp dashcam-transfer-mount.sh aiaiaiaiagent@nextcloud:/tmp/ ssh aiaiaiaiagent@nextcloud 'sudo -A cp /tmp/dashcam-transfer-mount.sh /usr/local/bin/dashcam-transfer-mount' scp dashcam-transfer-cleanup.sh aiaiaiaiagent@nextcloud:/tmp/ ssh aiaiaiaiagent@nextcloud 'sudo -A cp /tmp/dashcam-transfer-cleanup.sh /usr/local/bin/dashcam-transfer-cleanup' scp udev/99-dashcam-trigger.rules aiaiaiaiagent@nextcloud:/tmp/ ssh aiaiaiaiagent@nextcloud 'sudo -A cp /tmp/99-dashcam-trigger.rules /etc/udev/rules.d/ && sudo -A udevadm control --reload-rules' ``` Controleer na deploy altijd met `sudo sha256sum` dat de hash op de server gelijk is aan de lokale `shasum -a 256`.