No description
  • Python 74.1%
  • Shell 25.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-11 20:44:43 +02:00
docs/superpowers fix: corrigeer WHATSAPP_API URL naar localhost:3001 (nextcloud) 2026-06-23 11:04:39 +02:00
tests feat: harden dashcam transfer tegen flakke SD-lezer + stale-mount cleanup 2026-08-11 20:37:48 +02:00
udev feat: harden dashcam transfer tegen flakke SD-lezer + stale-mount cleanup 2026-08-11 20:37:48 +02:00
.gitignore feat: harden dashcam transfer tegen flakke SD-lezer + stale-mount cleanup 2026-08-11 20:37:48 +02:00
dashcam-transfer-cleanup.sh feat: harden dashcam transfer tegen flakke SD-lezer + stale-mount cleanup 2026-08-11 20:37:48 +02:00
dashcam-transfer-mount.sh feat: harden dashcam transfer tegen flakke SD-lezer + stale-mount cleanup 2026-08-11 20:37:48 +02:00
dashcam-transfer.py feat: harden dashcam transfer tegen flakke SD-lezer + stale-mount cleanup 2026-08-11 20:37:48 +02:00
README.md docs: vermeld vibecoded oorsprong bovenaan README 2026-08-11 20:44:43 +02:00
restructure.py Uniformeer naar front/back i.p.v. front/rear (B = back, F = front) 2026-05-29 19:34:12 +02:00

Dashcam Transfer

Dit project is vibecoded — gebouwd, gedebugd en gehard in een AI-agent sessie.

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)

De triggers worden gedefinieerd in udev/99-dashcam-trigger.rules. Udev zelf voert de scripts niet uit maar start ze via systemd-run — in udev-context ontbreken mount/umount-rechten op Fedora met SELinux in enforcing mode.

Robuustheid

Het script is ontworpen tegen een 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_<sequentie><kamera>.MP4

  • F = front, B = back
  • ~381 MB per clip (~5 minuten)

SSD (doel)

/mnt/ultratouch/dashcam/
└── YYYYMMDD/
    ├── front/
    │   └── ...F.MP4
    └── back/
        └── ...B.MP4

Operations-handleiding

WhatsApp berichten

Na elke transfer stuurt het script een notificatie naar de beheerder. Er zijn drie basissituaties:

Situatie Bericht Actie
Geslaagd Succesmelding met aantal bestanden en totaal in GB Geen actie nodig
Voltooid met fouten Waarschuwing met aantal bestanden, GB en aantal fouten De betreffende bestanden blijven op de kaart staan en worden de volgende keer opnieuw geprobeerd. Blijft dit terugkomen → kaart/lezer controleren
Gestopt (abort) Afbreekmelding met reden en aantal al overgezette bestanden Zie onderstaande oorzaken

Bij een abort zijn er drie vaste redenen:

  • SD-kaart onleesbaar — de lezer of kaart gaf een I/O-fout, of de kaart werd niet netjes afgemeld (dirty). Geen enkel bronbestand is verwijderd. Controleer de kaart met fsck.exfat en probeer een andere lezer/poort.
  • Te veel opeenvolgende fouten — 5 fouten achter elkaar tijdens het kopiëren. Ook hier zijn geen bronbestanden verwijderd; de melding bevat de laatste fout.
  • Onvoldoende vrije ruimte — de SSD had ook na het automatisch verwijderen van oudste datum-mappen te weinig ruimte.

Let op: een abort betekent niet dat data verloren is. Het script verwijdert een bronbestand alleen nadat een kopie is geverifieerd (zelfde byte-grootte).

Logging

Alle transfer-activiteit wordt geschreven naar /tmp/dashcam-transfer.log op nextcloud, in het formaat [YYYY-MM-DD HH:MM:SS] LEVEL bericht. De mount- en cleanup-scripts loggen via logger naar syslog met tag dashcam-transfer.

Handige greppatronen voor diagnose:

# Laatste transfer-run
tail -50 /tmp/dashcam-transfer.log

# Mount/cleanup triggers + fsck-uitvoer (syslog)
journalctl -t dashcam-transfer --since today
grep dashcam-transfer /var/log/messages

# I/O- en USB-fouten van de lezer
dmesg | grep -Ei "usb|sd[a-z]|dided|device offline|I/O error"

Troubleshooting

Symptoom Waarschijnlijke oorzaak Actie
GESTOPT: SD-kaart onleesbaar Lezer/kaart defect of niet netjes afgemeld fsck.exfat -n draaien, lezer/poort wisselen, kaart opnieuw insteken
device offline / DID_ERROR in dmesg Flakke USB-verbinding, kaart klapt weg Lezer vervangen, USB-poort wisselen; het script herstelt automatisch bij een nieuwe udev-trigger
Geen MP4 bestanden gevonden in log Kaart leeg (transfer geslaagd) of verkeerde mapstructuur Niks mis; controleer op de SSD of bestanden er zijn
Transfer start niet na insteken Service draait al (is-active gate) of device verscheen niet binnen 60s Logs checken; als het device nooit komt, lezer opnieuw insteken
Stale mount in mount-output REMOVE-trigger miste (lezer trok in één keer beide devices weg) Cleanup-script handmatig draaien (zie herstel)
WhatsApp notificatie mislukt in log wwebjs-api container down docker compose in /home/aiaiaiaiagent/wwebjs-api/ controleren
Abort: onvoldoende vrije ruimte SSD vol, opschonen hielp niet Oude datum-mappen handmatig opschonen of SSD uitbreiden

Herstelprocedures

# Handmatige transfer (zonder udev-trigger)
/usr/local/bin/dashcam-transfer

# Alleen de eerste N bestanden (testen)
/usr/local/bin/dashcam-transfer --limit 10

# Read-only health check van de kaart (niet-mounted apparaat nodig)
fsck.exfat -n /dev/sde1

# Stale mount opruimen (als de udev-trigger het niet deed)
/usr/local/bin/dashcam-transfer-cleanup
# of handmatig:
umount -l /mnt/sdcard

# Transfer-service handmatig starten
systemctl start dashcam-transfer

Configuratiereferentie

dashcam-transfer.py

Constante Waarde Doel
SD_CARD /mnt/sdcard Mountpoint SD-kaart
SOURCE_DIR /mnt/sdcard/Novatek/Movie Locatie MP4-bestanden op de kaart
SSD /mnt/ultratouch Mountpoint USB-SSD
TARGET_BASE /mnt/ultratouch/dashcam Doelmap voor overgezette opnames
LOG_FILE /tmp/dashcam-transfer.log Logbestand
LOCK_FILE /tmp/dashcam-transfer.lock Lockbestand tegen dubbele runs
CAMERA_MAP F→front, B→back Vertaling van naam-suffix naar submap
FILE_PATTERN ^\d{8}\d{6}_\d+[FB]\.MP4$ Herkent geldige dashcam-bestandsnamen
MAX_RETRIES 3 Kopieerpogingen per bestand
RETRY_DELAY 2 Pauze tussen pogingen (seconden)
MAX_CONSECUTIVE_ERRORS 5 Aantal opeenvolgende fouten vóór abort
WHATSAPP_API http://localhost:3001/client/sendMessage/EdGpt wwebjs-api endpoint (localhost = nextcloud zelf)
WHATSAPP_CHAT 31620248011@c.us Ontvanger van notificaties

dashcam-transfer-mount.sh

Variabele Waarde Doel
MAX_WAIT 60 Maximaal wachten (s) op het device na trigger
DASHCAM_MAX_WAIT Omgevingsvariabele om bovenstaande te overschrijven

Systeemconfiguratie

Onderdeel Locatie
udev-rules /etc/udev/rules.d/99-dashcam-trigger.rules
Mount-script /usr/local/bin/dashcam-transfer-mount
Cleanup-script /usr/local/bin/dashcam-transfer-cleanup
Transfer-service /etc/systemd/system/dashcam-transfer.service
SD-kaart UUID 00D3-7715
SSD UUID 3027-8ECA

WhatsApp notificatie

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/. De notificatie zelf is een JSON-payload naar het endpoint; een mislukte notificatie wordt alleen gelogd en breekt de transfer niet af.

Repo-structuur

dashcam-transfer/
├── dashcam-transfer.py             Hoofdscript: transfer + WhatsApp-notificatie
├── dashcam-transfer-mount.sh       Udev add-trigger: wachten, mount, fsck, service start
├── dashcam-transfer-cleanup.sh     Udev remove-trigger: stale mount opruimen
├── udev/
│   └── 99-dashcam-trigger.rules    Bron van de geïnstalleerde udev-rules
├── tests/
│   ├── test_dashcam_transfer.py    20 unittest-testen (Python)
│   ├── test_mount_script.sh        7 functionele checks (bash)
│   └── test_cleanup_script.sh      12 functionele checks (bash)
└── README.md                       Deze documentatie

Service & triggers

Udev rule (/etc/udev/rules.d/99-dashcam-trigger.rules)

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 device beschikbaar is, unmount eventuele stale mounts, mount de SD-kaart en start de service. Sluit vroegtijdig af als de service al actief is (een flakke lezer mag nooit een draaiende transfer onderbreken).

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)

[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; de trigger-scripts loggen naar syslog met tag dashcam-transfer.

Testdocumentatie

Overzicht

Suite Commando Aantal Dekking
Python unit python3 -m unittest discover -s tests 20 Naam-parsing, directory-scans, kopie/retry, bestandsvergelijking, ruimte-opschoning, WhatsApp-berichten
Mount-script ./tests/test_mount_script.sh 7 Skip bij actieve service, mount + service start, fsck-uitvoering, logging
Cleanup-script ./tests/test_cleanup_script.sh 12 Stale-detectie (device weg / naam-match), service stop, umount + lazy-fallback, no-op bij gezonde mount

De Python-testen draaien tegen een gefakete omgeving (temp-mappen, gemonteerde modules) en raken geen echte bestanden of de API aan. De bash-testen plaatsen een set fake-commando's (findmnt, systemctl, umount, logger) op de PATH en verificeren met markerbestanden of het script het juiste gedrag vertoont. Een nieuw scenario toevoegen betekent: een nieuwe fake-commando/marker-regel in de test en een check-assertie voor het verwachte gedrag.

Handmatig gebruik

# 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:

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.