Showing 13 changed files with 101 additions and 1488 deletions
+96 -0
AGENTS.md
@@ -0,0 +1,96 @@
1
+# Agent README
2
+
3
+## Scope
4
+
5
+Acest fisier este pentru agentii si toolurile care folosesc acest repository ca baza pentru importuri media automate, inclusiv rulari unattended din udev, systemd, cron, pipeline-uri NAS sau wrapper-e generate.
6
+
7
+Scriptul principal validat este:
8
+
9
+```text
10
+media-importer.sh
11
+```
12
+
13
+Prototipurile Raspberry Pi / unattended wrapper au fost eliminate din repository deoarece erau bazate pe o versiune invechita si netestata a importerului. Nu le recreati si nu le folositi ca reference design fara o decizie explicita de reluare a acelei directii.
14
+
15
+## Context critic: incidente de pierdere de date
16
+
17
+Au existat pierderi reale de date in mediul nostru cu versiuni anterioare ale importerului. Incidentul documentat in `INCIDENTS.md` a fost declansat de fisiere GoPro chapter care au ajuns sa fie mapate la acelasi nume destinatie. Scriptul vechi a continuat dupa conflict, copia/mutarea a suprascris destinatia existenta, verificarea a validat fisierul suprascris, iar sursele au fost sterse.
18
+
19
+Orice agent care copiaza, deriva, rescrie sau integreaza acest script intr-un tool unattended trebuie sa verifice explicit ca nu reintroduce bugurile care au dus la acea pierdere de date.
20
+
21
+## Reguli obligatorii pentru import unattended
22
+
23
+- Nu folositi variante vechi, copii locale sau scripturi derivate fara comparatie cu protectiile curente din `media-importer.sh`.
24
+- Nu folositi `cp`, `mv`, `rsync` sau alte unelte lasate sa rezolve singure conflictele de destinatie. Conflictele trebuie rezolvate in cod inainte de operatie.
25
+- Nu suprascrieti niciodata o destinatie existenta.
26
+- Nu stergeti sursa pana cand destinatia nu exista si verificarea a trecut.
27
+- In move mode, fluxul sigur este `copy -> verify -> delete source`; nu inlocuiti acest flux cu `mv` direct in scripturi unattended.
28
+- Destinatiile planificate in aceeasi rulare trebuie rezervate in memorie, ca doua surse sa nu poata ajunge la acelasi path chiar daca fisierul destinatie nu exista inca.
29
+- Pentru conflicte unattended, comportamentul acceptat este suffix numeric unic (`_1`, `_2`, ...), skip explicit sau abort. Nu este acceptat overwrite silentios.
30
+- Daca destinatia este in interiorul sursei, destinatia trebuie exclusa din scanare, iar orice fisier aflat deja in destinatie trebuie refuzat inainte de procesare.
31
+- Pentru camere GoPro/Garmin/Varia sau fisiere QuickTime, nu modificati logica de data/timp fara teste de regresie; timestampurile identice sunt un caz normal, nu o eroare de intrare.
32
+
33
+## Checklist pentru copii sau scripturi derivate
34
+
35
+Inainte ca o copie sau derivare sa fie folosita unattended, verificati ca include echivalentul acestor protectii:
36
+
37
+- `safe_cp` si `safe_mv` refuza destinatii existente.
38
+- Copierea se face intr-un fisier temporar, se verifica temporarul, apoi se muta in destinatia finala doar daca destinatia finala nu exista.
39
+- `VERIFY_MODE=size` este default; `strict` este disponibil pentru comparatie byte-to-byte.
40
+- Stergerea sursei are loc doar dupa verificarea destinatiei.
41
+- Conflictele de destinatie sunt rezolvate prin `resolve_destination_conflict` / `ensure_unique_destination_path` sau logica echivalenta.
42
+- Path-urile destinatie deja planificate sunt rezervate, nu doar cele existente pe disk.
43
+- Testul `timestamp-collision` trece.
44
+- Dry-run-ul pentru camere noi arata destinatii distincte pentru fiecare fisier.
45
+
46
+## Teste minime inainte de automatizare
47
+
48
+Pentru orice schimbare care atinge importul, copierea, mutarea, verificarea, stergerea sursei, generarea numelor sau extragerea datelor, rulati cel putin:
49
+
50
+```bash
51
+./test_runner.sh timestamp-collision
52
+./test_runner.sh verify-mode
53
+./test_runner.sh dest-in-source
54
+```
55
+
56
+Pentru schimbari legate de GoPro sau metadata sidecar, rulati si:
57
+
58
+```bash
59
+./test_runner.sh gopro-sidecar-sync
60
+./test_runner.sh gopro-no-sidecar-reimport
61
+```
62
+
63
+Daca aceste teste nu pot fi rulate, agentul trebuie sa marcheze integrarea ca nevalidata si sa nu recomande unattended move mode.
64
+
65
+## Recomandari operationale
66
+
67
+Pentru camere sau destinatii noi, prima rulare trebuie sa fie dry-run:
68
+
69
+```bash
70
+./media-importer.sh -s "/path/to/camera" -d "/path/to/destination" --dry-run -v
71
+```
72
+
73
+Verificati in output ca:
74
+
75
+- fiecare fisier sursa are o destinatie distincta;
76
+- nu exista mesaje de overwrite;
77
+- conflictele sunt suffixate, sarite explicit sau duc la abort;
78
+- pentru GoPro, sursa datei este cea asteptata, inclusiv sidecar `THM`/`LRV` cand exista.
79
+
80
+Pentru date cu valoare mare sau cand sursa poate fi pastrata, preferati temporar:
81
+
82
+```bash
83
+./media-importer.sh -s "/path/to/camera" -d "/path/to/destination" --verify-mode strict --keep-originals -v
84
+```
85
+
86
+## Regula de oprire
87
+
88
+Daca un agent gaseste o copie a scriptului care:
89
+
90
+- foloseste `mv` direct in move mode;
91
+- permite overwrite;
92
+- sterge sursa fara verificare;
93
+- nu rezerva destinatii planificate;
94
+- sau nu poate demonstra ca trece testul `timestamp-collision`;
95
+
96
+atunci agentul trebuie sa opreasca integrarea unattended si sa raporteze riscul ca posibil bug de pierdere de date.
+5 -5
README.md
@@ -5,10 +5,9 @@ This repository contains the development and testing resources for the `media-im
5 5
 ## Project Structure
6 6
 
7 7
 - `media-importer.sh`: The primary script under development and testing.
8
+- `AGENTS.md`: Safety rules for agents or unattended automation that copy, derive, or run the importer.
8 9
 - `INCIDENTS.md`: Postmortems for data-loss or near-data-loss incidents and the regression rules derived from them.
9
-- `RPI/`: Raspberry Pi integration prototype. This area is currently frozen because Raspberry Pi 3B+ hardware with 1 GB RAM has not provided reliable validation capacity and may also be affected by hardware defects.
10
-- `samples/code/autonas-media-importer.sh`: The original script that serves as inspiration for this project.
11
-- `samples/`: Contains code samples, example files, and other resources used for development and testing. **Note:** Scripts and functions are not imported from this directory; it is strictly for reference and testing purposes.
10
+- `sample/`: Contains local media fixtures used for development and testing. **Note:** Scripts and functions are not imported from sample directories.
12 11
 
13 12
 ## Version Control
14 13
 
@@ -23,8 +22,9 @@ samples/
23 22
 ## Contribution Guidelines
24 23
 
25 24
 - All development should focus on `media-importer.sh`.
26
-- Do not import or source scripts/functions from the `samples/` directory.
27
-- Use resources in `samples/` only for testing and development reference.
25
+- Do not import, source, or derive importer behavior from sample directories.
26
+- Use sample media only for testing and development reference.
27
+- Do not use removed Raspberry Pi / unattended wrapper prototypes as a reference design; they were based on an outdated, unvalidated importer copy.
28 28
 - Destination conflicts must never be delegated to copy/move tools. In unattended runs the importer appends numeric suffixes (`_1`, `_2`, ...); in interactive runs it asks the user and supports applying the choice to all similar conflicts.
29 29
 
30 30
 ## License
+0 -15
RPI/CHANGELOG.md
@@ -1,15 +0,0 @@
1
-# Changelog
2
-
3
-## 0.2.0 - 2026-03-12
4
-
5
-- Switched configuration to UUID-based camera profiles for reliable attach detection.
6
-- Added udev + wrapper + systemd attach pipeline modeled after proven AutoNAS behavior.
7
-- Added disk handler to run imports on device detection events.
8
-- Reused AutoNAS media importer script unchanged for proven Varia-compatible import behavior.
9
-
10
-## 0.1.0 - 2026-03-12
11
-
12
-- Created standalone Raspberry Pi camera media importer project.
13
-- Added interactive menu-based wizard for profile management.
14
-- Added media import engine with EXIF date extraction and QuickTime UTC conversion.
15
-- Added setup script for dependency and file installation.
+0 -46
RPI/INSTALL.md
@@ -1,46 +0,0 @@
1
-# Install Guide
2
-
3
-## Status
4
-
5
-RPI section is frozen.
6
-
7
-Nu se recomanda folosirea acestui ghid ca baza pentru testare curenta pe Raspberry Pi 3B+ cu 1 GB RAM. Platforma nu a oferit suficiente resurse pentru validare fiabila si exista posibilitatea unui defect hardware.
8
-
9
-## 1. Copy project to Raspberry Pi
10
-
11
-Clone or copy this folder to the Raspberry Pi.
12
-
13
-## 2. Run installer
14
-
15
-```bash
16
-cd rpi-camera-media-importer
17
-sudo ./setup.sh
18
-```
19
-
20
-## 3. Configure camera profiles
21
-
22
-```bash
23
-rpi-camera-importer wizard
24
-```
25
-
26
-In wizard, configure each camera by `ID_FS_UUID` and destination path.
27
-
28
-## 4. Verify detection pipeline (udev + systemd)
29
-
30
-Connect camera and verify attach unit logs:
31
-
32
-```bash
33
-journalctl -u rpi-camera-importer-attach@<UUID>.service -f
34
-```
35
-
36
-## 5. Test import
37
-
38
-```bash
39
-rpi-camera-importer import --all --dry-run --verbose
40
-```
41
-
42
-## 6. Run real import
43
-
44
-```bash
45
-rpi-camera-importer import --all
46
-```
+0 -103
RPI/README.md
@@ -1,103 +0,0 @@
1
-# Raspberry Pi Camera Media Importer
2
-
3
-## Status
4
-
5
-Aceasta sectiune este frozen.
6
-
7
-Motiv: Raspberry Pi 3B+ cu 1 GB RAM nu ofera suficiente resurse pentru testare si validare fiabila, iar comportamentul observat poate indica si un defect hardware. Pana la confirmarea unei platforme mai stabile, componenta RPI ramane documentata, dar nu mai este considerata activ dezvoltata sau validata.
8
-
9
-Proiect standalone pentru Raspberry Pi care importa media de pe camere (foto/video) prin detectie automata la conectare dispozitiv.
10
-
11
-Acest proiect este independent de cluster si nu are integrare cu codul de deploy existent.
12
-
13
-## Cerinte cheie implementate
14
-
15
-- Compatibilitate cu camere Garmin Varia.
16
-- Importerul media existent, testat in AutoNAS, este reutilizat fara modificari in acest proiect.
17
-- Trigger la detectie dispozitiv, pe mecanismul testat AutoNAS:
18
-  - udev rule
19
-  - wrapper (pentru device-uri ata bridge)
20
-  - systemd attach template unit
21
-  - disk handler care ruleaza import pe UUID
22
-
23
-## Functionalitati
24
-
25
-- Wizard menu-based pentru configurare profile camera
26
-- Profile multiple bazate pe UUID (`ID_FS_UUID`)
27
-- Import automat la conectarea camerei
28
-- Import manual pentru un profil, un UUID sau toate profilele
29
-- Organizare media pe data + conversie QuickTime UTC
30
-- Curatare fisiere `.glv` (util pentru Varia/Garmin)
31
-
32
-## Configurare profil
33
-
34
-Fisier configurare:
35
-
36
-`/etc/rpi-camera-importer/cameras.conf`
37
-
38
-Format linie:
39
-
40
-`name|uuid|destination_path`
41
-
42
-Exemplu:
43
-
44
-`varia_rct715|A1B2-C3D4|/srv/media/varia`
45
-
46
-## Instalare
47
-
48
-```bash
49
-sudo ./setup.sh
50
-```
51
-
52
-## Utilizare
53
-
54
-Wizard:
55
-
56
-```bash
57
-rpi-camera-importer wizard
58
-```
59
-
60
-Listare profile:
61
-
62
-```bash
63
-rpi-camera-importer list
64
-```
65
-
66
-Detectie UUID-uri conectate:
67
-
68
-```bash
69
-rpi-camera-importer discover
70
-```
71
-
72
-Import manual dupa nume profil:
73
-
74
-```bash
75
-rpi-camera-importer import --profile varia_rct715
76
-```
77
-
78
-Import manual dupa UUID:
79
-
80
-```bash
81
-rpi-camera-importer import --uuid A1B2-C3D4
82
-```
83
-
84
-Import toate profilele active:
85
-
86
-```bash
87
-rpi-camera-importer import --all
88
-```
89
-
90
-Dry-run:
91
-
92
-```bash
93
-rpi-camera-importer import --all --dry-run --verbose
94
-```
95
-
96
-## Fisiere runtime instalate
97
-
98
-- `/usr/local/bin/rpi-camera-importer`
99
-- `/usr/local/lib/rpi-camera-importer/autonas-media-importer.sh`
100
-- `/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh`
101
-- `/usr/local/lib/rpi-camera-importer/rpi-camera-udev-wrapper.sh`
102
-- `/etc/udev/rules.d/99-rpi-camera-importer.rules`
103
-- `/etc/systemd/system/rpi-camera-importer-attach@.service`
+0 -26
RPI/config/99-rpi-camera-importer.rules
@@ -1,26 +0,0 @@
1
-# Raspberry Pi Camera Importer udev rules
2
-# Attach flow mirrors the proven AutoNAS trigger model (systemd attach unit + wrapper for ata bridges).
3
-
4
-# USB block devices (only removable, to exclude Pi's own rootfs)
5
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", ATTRS{removable}=="1", RUN+="/bin/systemctl start --no-block rpi-camera-importer-attach@%E{ID_FS_UUID}.service"
6
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", ATTRS{removable}=="1", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
7
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="usb", ATTRS{removable}=="1", RUN+="/bin/systemctl start --no-block rpi-camera-importer-attach@%E{ID_FS_UUID}.service"
8
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="usb", ATTRS{removable}=="1", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
9
-
10
-# ATA removable devices (via wrapper, as in AutoNAS)
11
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-udev-wrapper.sh attach %E{ID_FS_UUID}"
12
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
13
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-udev-wrapper.sh attach %E{ID_FS_UUID}"
14
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
15
-
16
-# USB-SATA bridges reported as ATA + ID_USB_TYPE=disk
17
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-udev-wrapper.sh attach %E{ID_FS_UUID}"
18
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
19
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-udev-wrapper.sh attach %E{ID_FS_UUID}"
20
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
21
-
22
-# Other removable storage
23
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/bin/systemctl start --no-block rpi-camera-importer-attach@%E{ID_FS_UUID}.service"
24
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
25
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/bin/systemctl start --no-block rpi-camera-importer-attach@%E{ID_FS_UUID}.service"
26
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh detach %E{ID_FS_UUID}"
+0 -4
RPI/config/cameras.conf
@@ -1,4 +0,0 @@
1
-# Camera profiles for rpi-camera-importer
2
-# Format: name|uuid|destination_path
3
-# Example:
4
-# varia_rct715|A1B2-C3D4|/srv/media/varia
+0 -14
RPI/config/rpi-camera-importer-attach@.service
@@ -1,14 +0,0 @@
1
-[Unit]
2
-Description=Raspberry Pi Camera Importer Attach %i
3
-After=systemd-udev-settle.service
4
-
5
-[Service]
6
-Type=oneshot
7
-ExecStart=/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh attach-deferred %i
8
-RemainAfterExit=no
9
-TimeoutSec=120
10
-StandardOutput=journal
11
-StandardError=journal
12
-
13
-[Install]
14
-WantedBy=multi-user.target
+0 -438
RPI/scripts/autonas-media-importer.sh
@@ -1,438 +0,0 @@
1
-#!/bin/bash
2
-
3
-# AutoNAS Media Importer
4
-# Advanced media import engine that processes, organizes and imports media files from cameras
5
-# Usage: autonas-media-importer.sh <source_mount> <destination_path>
6
-
7
-# Global configuration
8
-LOG_TAG="autonas-import"
9
-
10
-# Function to log messages
11
-log_message() {
12
-    local message="$1"
13
-    local priority="${2:-info}"  # Default priority is info
14
-    
15
-    # Log to syslog with facility local0 and specified priority
16
-    logger -p "local0.$priority" -t "$LOG_TAG" "$message"
17
-    
18
-    # Also echo to stdout/stderr for interactive use
19
-    if [ -t 1 ]; then
20
-        echo "$(date '+%Y-%m-%d %H:%M:%S') - $message"
21
-    fi
22
-}
23
-
24
-# Usage function
25
-usage() {
26
-    echo "Usage: $0 <source_mount> <destination_path> [options]"
27
-    echo ""
28
-    echo "Arguments:"
29
-    echo "  source_mount     - Mount point of the camera (e.g., /mnt/autonas/camera)"
30
-    echo "  destination_path - Destination directory for imported files"
31
-    echo ""
32
-    echo "Options:"
33
-    echo "  --dry-run        - Show what would be done without actually doing it"
34
-    echo "  --keep-originals - Keep original files on camera after import"
35
-    echo "  --verbose        - Enable verbose output"
36
-    echo "  --limit N        - Process only N files (useful for testing)"
37
-    echo "  --help           - Show this help"
38
-    echo ""
39
-    echo "Examples:"
40
-    echo "  $0 /mnt/autonas/camera /mnt/autonas/photos/imported"
41
-    echo "  $0 /mnt/autonas/camera /mnt/autonas/photos/imported --dry-run --verbose"
42
-}
43
-
44
-# Parse command line arguments
45
-SOURCE_MOUNT=""
46
-DESTINATION=""
47
-DRY_RUN=0
48
-KEEP_ORIGINALS=0
49
-VERBOSE=0
50
-FILE_LIMIT=0
51
-
52
-while [[ $# -gt 0 ]]; do
53
-    case $1 in
54
-        --dry-run)
55
-            DRY_RUN=1
56
-            shift
57
-            ;;
58
-        --keep-originals)
59
-            KEEP_ORIGINALS=1
60
-            shift
61
-            ;;
62
-        --verbose)
63
-            VERBOSE=1
64
-            shift
65
-            ;;
66
-        --limit)
67
-            FILE_LIMIT="$2"
68
-            if ! [[ "$FILE_LIMIT" =~ ^[0-9]+$ ]]; then
69
-                echo "Error: --limit requires a number"
70
-                usage
71
-                exit 1
72
-            fi
73
-            shift 2
74
-            ;;
75
-        --help)
76
-            usage
77
-            exit 0
78
-            ;;
79
-        -*)
80
-            echo "Unknown option: $1"
81
-            usage
82
-            exit 1
83
-            ;;
84
-        *)
85
-            if [[ -z "$SOURCE_MOUNT" ]]; then
86
-                SOURCE_MOUNT="$1"
87
-            elif [[ -z "$DESTINATION" ]]; then
88
-                DESTINATION="$1"
89
-            else
90
-                echo "Too many arguments"
91
-                usage
92
-                exit 1
93
-            fi
94
-            shift
95
-            ;;
96
-    esac
97
-done
98
-
99
-# Validate arguments
100
-if [[ -z "$SOURCE_MOUNT" || -z "$DESTINATION" ]]; then
101
-    echo "Error: Both source_mount and destination_path are required"
102
-    usage
103
-    exit 1
104
-fi
105
-
106
-# Check if source exists and is mounted
107
-if [[ ! -d "$SOURCE_MOUNT" ]]; then
108
-    log_message "Error: Source mount point does not exist: $SOURCE_MOUNT" "err"
109
-    exit 1
110
-fi
111
-
112
-# Check if source is actually mounted
113
-if ! mountpoint -q "$SOURCE_MOUNT" 2>/dev/null; then
114
-    log_message "Warning: Source path is not a mount point: $SOURCE_MOUNT" "warning"
115
-fi
116
-
117
-# Create destination directory if it doesn't exist
118
-if [[ $DRY_RUN -eq 0 ]]; then
119
-    mkdir -p "$DESTINATION"
120
-    if [[ ! -d "$DESTINATION" ]]; then
121
-        log_message "Error: Cannot create destination directory: $DESTINATION" "err"
122
-        exit 1
123
-    fi
124
-fi
125
-
126
-# Check for required tools
127
-if ! command -v exiftool &> /dev/null; then
128
-    log_message "Error: exiftool is required but not installed" "err"
129
-    exit 1
130
-fi
131
-
132
-# Function to process a single file
133
-process_file() {
134
-    local file="$1"
135
-    local relative_path="${file#$SOURCE_MOUNT/}"
136
-    
137
-    # Check if source mount is still available
138
-    if ! mountpoint -q "$SOURCE_MOUNT" 2>/dev/null; then
139
-        log_message "Error: Source mount point is no longer available: $SOURCE_MOUNT" "err"
140
-        return 1
141
-    fi
142
-    
143
-    # Check if file still exists
144
-    if [[ ! -f "$file" ]]; then
145
-        log_message "Error: File no longer exists: $relative_path" "err"
146
-        log_message "Camera appears to be disconnected, stopping import" "warning"
147
-        exit 1
148
-    fi
149
-    
150
-    if [[ $VERBOSE -eq 1 ]]; then
151
-        log_message "Processing: $relative_path" "info"
152
-    fi
153
-    
154
-    # Check which group CreateDate comes from to determine correct handling
155
-    create_date_info=$(exiftool -G1 -s -CreateDate "$file" 2>/dev/null | grep CreateDate | head -1)
156
-    
157
-    # Check if exiftool failed (possible if device disconnected)
158
-    local exiftool_exit_code=$?
159
-    if [[ $exiftool_exit_code -ne 0 ]] && [[ $exiftool_exit_code -ne 1 ]]; then
160
-        log_message "Error: Cannot read file (device may be disconnected): $relative_path" "err"
161
-        log_message "Camera appears to be disconnected, stopping import" "warning"
162
-        exit 1
163
-    fi
164
-    
165
-    if [[ -z "$create_date_info" ]]; then
166
-        log_message "Warning: No CreateDate found in $relative_path, using file modification time" "warning"
167
-        # Fallback to file modification time
168
-        local file_date=$(date -r "$file" "+%Y-%m-%d %H:%M:%S" 2>/dev/null)
169
-        if [[ -n "$file_date" ]]; then
170
-            create_date_value="$file_date"
171
-            create_date_group="FileSystem"
172
-        else
173
-            log_message "Error: Cannot determine date for $relative_path" "err"
174
-            return 1
175
-        fi
176
-    else
177
-        create_date_group=$(echo "$create_date_info" | cut -d']' -f1 | cut -d'[' -f2)
178
-        create_date_value=$(echo "$create_date_info" | cut -d':' -f2- | xargs)
179
-        # Convert EXIF date format (YYYY:MM:DD HH:MM:SS) to standard format (YYYY-MM-DD HH:MM:SS)
180
-        create_date_value=$(echo "$create_date_value" | sed 's/^\([0-9]\{4\}\):\([0-9]\{2\}\):\([0-9]\{2\}\)/\1-\2-\3/')
181
-    fi
182
-    
183
-    if [[ $VERBOSE -eq 1 ]]; then
184
-        echo -n "  Date: [$create_date_value] from $create_date_group "
185
-    fi
186
-    
187
-    # Extract file extension
188
-    local filename=$(basename "$file")
189
-    local extension="${filename##*.}"
190
-    
191
-    # For QuickTime files, the CreateDate is in UTC and needs conversion to local time
192
-    if [[ "$create_date_group" == "QuickTime" ]]; then
193
-        # Convert UTC time to local time
194
-        local utc_timestamp=$(date -d "$create_date_value UTC" "+%s" 2>/dev/null)
195
-        if [[ -n "$utc_timestamp" ]]; then
196
-            create_date_value=$(date -d "@$utc_timestamp" "+%Y-%m-%d %H:%M:%S" 2>/dev/null)
197
-            if [[ $VERBOSE -eq 1 ]]; then
198
-                echo -n "(converted from UTC) "
199
-            fi
200
-        fi
201
-    fi
202
-    
203
-    # Create output directory structure
204
-    local date_dir=$(date -d "$create_date_value" "+%Y-%m-%d" 2>/dev/null)
205
-    if [[ -z "$date_dir" ]]; then
206
-        log_message "Error: Invalid date format for $relative_path: $create_date_value" "err"
207
-        return 1
208
-    fi
209
-    
210
-    local output_dir="$DESTINATION/$date_dir"
211
-    
212
-    if [[ $DRY_RUN -eq 0 ]]; then
213
-        mkdir -p "$output_dir"
214
-    fi
215
-    
216
-    # Generate output filename with timestamp
217
-    local timestamp=$(date -d "$create_date_value" "+%Y-%m-%d_%H-%M-%S" 2>/dev/null)
218
-    local output_filename="${timestamp}.${extension,,}"  # Convert extension to lowercase
219
-    local output_path="$output_dir/$output_filename"
220
-    
221
-    # Handle filename conflicts
222
-    local counter=1
223
-    local base_output_path="$output_path"
224
-    while [[ -f "$output_path" ]] && [[ $DRY_RUN -eq 0 ]]; do
225
-        local name_without_ext="${timestamp}_${counter}"
226
-        output_path="$output_dir/${name_without_ext}.${extension,,}"
227
-        counter=$((counter + 1))
228
-    done
229
-    
230
-    if [[ $DRY_RUN -eq 1 ]]; then
231
-        if [[ $KEEP_ORIGINALS -eq 1 ]]; then
232
-            echo "Would copy: $relative_path -> ${output_path#$DESTINATION/}"
233
-        else
234
-            echo "Would move: $relative_path -> ${output_path#$DESTINATION/}"
235
-        fi
236
-    else
237
-        # Perform the actual file operation
238
-        if [[ $KEEP_ORIGINALS -eq 1 ]]; then
239
-            if cp "$file" "$output_path"; then
240
-                if [[ $VERBOSE -eq 1 ]]; then
241
-                    echo "✓ Copied"
242
-                fi
243
-                log_message "Copied: $relative_path -> ${output_path#$DESTINATION/}" "info"
244
-                return 0
245
-            else
246
-                if [[ $VERBOSE -eq 1 ]]; then
247
-                    echo "✗ Copy failed"
248
-                fi
249
-                log_message "Error: Failed to copy $relative_path" "err"
250
-                return 1
251
-            fi
252
-        else
253
-            if mv "$file" "$output_path"; then
254
-                if [[ $VERBOSE -eq 1 ]]; then
255
-                    echo "✓ Moved"
256
-                fi
257
-                log_message "Moved: $relative_path -> ${output_path#$DESTINATION/}" "info"
258
-                return 0
259
-            else
260
-                if [[ $VERBOSE -eq 1 ]]; then
261
-                    echo "✗ Move failed"
262
-                fi
263
-                log_message "Error: Failed to move $relative_path" "err"
264
-                return 1
265
-            fi
266
-        fi
267
-    fi
268
-}
269
-
270
-# Function to find camera directories
271
-find_camera_directories() {
272
-    local search_patterns=("DCIM" "PRIVATE" "MP_ROOT" "AVCHD" "Photos" "Videos")
273
-    local found_dirs=()
274
-    
275
-    # Test if the mount point is accessible with a timeout
276
-    if ! timeout 3 ls "$SOURCE_MOUNT" >/dev/null 2>&1; then
277
-        log_message "Error: Mount point is not accessible (device likely disconnected): $SOURCE_MOUNT" "err"
278
-        exit 1
279
-    fi
280
-    
281
-    for pattern in "${search_patterns[@]}"; do
282
-        while IFS= read -r -d '' dir; do
283
-            found_dirs+=("$dir")
284
-        done < <(timeout 5 find "$SOURCE_MOUNT" -maxdepth 3 -type d -iname "$pattern" -print0 2>/dev/null)
285
-    done
286
-    
287
-    # If no camera directories found, search for common media file extensions
288
-    if [[ ${#found_dirs[@]} -eq 0 ]]; then
289
-        log_message "No camera directories found, searching for media files..." "info"
290
-        local media_extensions=("*.jpg" "*.jpeg" "*.png" "*.tiff" "*.cr2" "*.nef" "*.arw" "*.mp4" "*.mov" "*.avi" "*.mts" "*.m2ts")
291
-        
292
-        for ext in "${media_extensions[@]}"; do
293
-            while IFS= read -r -d '' file; do
294
-                local dir=$(dirname "$file")
295
-                if [[ ! " ${found_dirs[@]} " =~ " ${dir} " ]]; then
296
-                    found_dirs+=("$dir")
297
-                fi
298
-            done < <(timeout 5 find "$SOURCE_MOUNT" -maxdepth 3 -type f -iname "$ext" -print0 2>/dev/null)
299
-        done
300
-    fi
301
-    
302
-    printf '%s\n' "${found_dirs[@]}" | sort -u
303
-}
304
-
305
-# Main execution
306
-log_message "Starting camera import from $SOURCE_MOUNT to $DESTINATION" "info"
307
-
308
-if [[ $DRY_RUN -eq 1 ]]; then
309
-    log_message "DRY RUN MODE - No files will be actually moved/copied" "info"
310
-fi
311
-
312
-if [[ $KEEP_ORIGINALS -eq 1 ]]; then
313
-    log_message "KEEP ORIGINALS MODE - Files will be copied instead of moved" "info"
314
-fi
315
-
316
-# Find camera directories
317
-log_message "Scanning for camera directories..." "info"
318
-camera_dirs=$(find_camera_directories)
319
-
320
-if [[ -z "$camera_dirs" ]]; then
321
-    log_message "No camera directories or media files found in $SOURCE_MOUNT" "warning"
322
-    exit 0
323
-fi
324
-
325
-echo "Found camera directories:"
326
-echo "$camera_dirs" | while IFS= read -r dir; do
327
-    echo "  $dir"
328
-done
329
-
330
-# Process files
331
-total_files=0
332
-processed_files=0
333
-error_files=0
334
-
335
-# Delete GLV files (Garmin video preview files) - they're usually not needed
336
-log_message "Cleaning up GLV preview files..." "info"
337
-glv_count=0
338
-while IFS= read -r dir; do
339
-    if [[ $DRY_RUN -eq 1 ]]; then
340
-        glv_files=$(find "$dir" -type f -iname "*.glv" 2>/dev/null | wc -l)
341
-        if [[ $glv_files -gt 0 ]]; then
342
-            echo "Would delete $glv_files GLV files from $dir"
343
-            glv_count=$((glv_count + glv_files))
344
-        fi
345
-    else
346
-        while IFS= read -r -d '' glv_file; do
347
-            if rm "$glv_file" 2>/dev/null; then
348
-                glv_count=$((glv_count + 1))
349
-            fi
350
-        done < <(find "$dir" -type f -iname "*.glv" -print0 2>/dev/null)
351
-    fi
352
-done <<< "$camera_dirs"
353
-
354
-if [[ $glv_count -gt 0 ]]; then
355
-    if [[ $DRY_RUN -eq 1 ]]; then
356
-        log_message "Would delete $glv_count GLV preview files" "info"
357
-    else
358
-        log_message "Deleted $glv_count GLV preview files" "info"
359
-    fi
360
-fi
361
-
362
-# Process media files
363
-log_message "Processing media files..." "info"
364
-media_extensions=("*.jpg" "*.jpeg" "*.png" "*.tiff" "*.tif" "*.cr2" "*.nef" "*.arw" "*.dng" "*.mp4" "*.mov" "*.avi" "*.mts" "*.m2ts" "*.mkv" "*.wmv")
365
-
366
-# In dry-run mode, limit output to avoid overwhelming logs
367
-max_files_to_show=20
368
-files_shown=0
369
-files_processed_count=0
370
-
371
-while IFS= read -r dir; do
372
-    # Check if mount point is still available before processing each directory
373
-    if ! mountpoint -q "$SOURCE_MOUNT" 2>/dev/null; then
374
-        log_message "Camera disconnected, stopping import process" "warning"
375
-        break
376
-    fi
377
-    
378
-    for ext in "${media_extensions[@]}"; do
379
-        while IFS= read -r -d '' file; do
380
-            total_files=$((total_files + 1))
381
-            files_processed_count=$((files_processed_count + 1))
382
-            
383
-            # Check if camera is still connected before processing each file
384
-            if ! mountpoint -q "$SOURCE_MOUNT" 2>/dev/null; then
385
-                log_message "Camera disconnected during processing, stopping import" "warning"
386
-                exit 1
387
-            fi
388
-            
389
-            # Check file limit
390
-            if [[ $FILE_LIMIT -gt 0 && $files_processed_count -gt $FILE_LIMIT ]]; then
391
-                echo "Reached file limit of $FILE_LIMIT files, stopping processing..."
392
-                break 3
393
-            fi
394
-            
395
-            # In dry-run mode, limit verbose output
396
-            if [[ $DRY_RUN -eq 1 && $files_shown -ge $max_files_to_show ]]; then
397
-                if [[ $files_shown -eq $max_files_to_show ]]; then
398
-                    echo "... (limiting output in dry-run mode, processing continues)"
399
-                    files_shown=$((files_shown + 1))
400
-                fi
401
-                # Still process but don't show details
402
-                processed_files=$((processed_files + 1))
403
-            else
404
-                if [[ $DRY_RUN -eq 1 ]]; then
405
-                    files_shown=$((files_shown + 1))
406
-                fi
407
-                
408
-                if process_file "$file"; then
409
-                    processed_files=$((processed_files + 1))
410
-                else
411
-                    error_files=$((error_files + 1))
412
-                fi
413
-            fi
414
-        done < <(find "$dir" -type f -iname "$ext" -print0 2>/dev/null)
415
-    done
416
-done <<< "$camera_dirs"
417
-
418
-# Summary
419
-log_message "Import completed: $processed_files/$total_files files processed successfully" "info"
420
-if [[ $error_files -gt 0 ]]; then
421
-    log_message "Import had errors: $error_files files failed to process" "warning"
422
-fi
423
-
424
-echo ""
425
-echo "=== Import Summary ==="
426
-echo "Total files found: $total_files"
427
-echo "Successfully processed: $processed_files"
428
-echo "Errors: $error_files"
429
-if [[ $glv_count -gt 0 ]]; then
430
-    echo "GLV files cleaned up: $glv_count"
431
-fi
432
-
433
-# Exit with error code if there were errors
434
-if [[ $error_files -gt 0 ]]; then
435
-    exit 1
436
-else
437
-    exit 0
438
-fi
+0 -59
RPI/scripts/rpi-camera-disk-handler.sh
@@ -1,59 +0,0 @@
1
-#!/usr/bin/env bash
2
-
3
-set -euo pipefail
4
-
5
-LOG_TAG="rpi-camera-disk-handler"
6
-APP_BIN="/usr/local/bin/rpi-camera-importer"
7
-
8
-log_message() {
9
-  local message="$1"
10
-  local priority="${2:-info}"
11
-  logger -p "local0.${priority}" -t "$LOG_TAG" "$message"
12
-}
13
-
14
-usage() {
15
-  echo "Usage: $0 {attach-deferred|detach} <UUID>"
16
-}
17
-
18
-action="${1:-}"
19
-uuid="${2:-}"
20
-attach_dry_run="${RPI_CAMERA_IMPORTER_ATTACH_DRY_RUN:-0}"
21
-
22
-if [[ -z "$action" || -z "$uuid" ]]; then
23
-  log_message "Invalid arguments: action='$action' uuid='$uuid'" "err"
24
-  usage
25
-  exit 1
26
-fi
27
-
28
-case "$action" in
29
-  attach-deferred)
30
-    log_message "Attach deferred triggered for UUID=$uuid"
31
-    if [[ ! -x "$APP_BIN" ]]; then
32
-      log_message "CLI not found: $APP_BIN" "err"
33
-      exit 1
34
-    fi
35
-
36
-    # Run import only for configured UUID; unconfigured devices exit cleanly.
37
-    import_args=(import --uuid "$uuid")
38
-    if [[ "$attach_dry_run" == "1" ]]; then
39
-      import_args+=(--dry-run --verbose)
40
-      log_message "Attach handler running in dry-run mode (RPI_CAMERA_IMPORTER_ATTACH_DRY_RUN=1)"
41
-    fi
42
-
43
-    if ! "$APP_BIN" "${import_args[@]}"; then
44
-      log_message "Import flow failed for UUID=$uuid" "err"
45
-      exit 1
46
-    fi
47
-    ;;
48
-  detach)
49
-    # No detach-side action required; importer handles mount/unmount around imports.
50
-    log_message "Detach event for UUID=$uuid (no-op)"
51
-    ;;
52
-  *)
53
-    log_message "Unsupported action: $action" "err"
54
-    usage
55
-    exit 1
56
-    ;;
57
-esac
58
-
59
-exit 0
+0 -676
RPI/scripts/rpi-camera-importer.sh
@@ -1,676 +0,0 @@
1
-#!/usr/bin/env bash
2
-
3
-set -euo pipefail
4
-
5
-VERSION="0.2.0"
6
-APP_NAME="rpi-camera-importer"
7
-PROJECT_DIR="/usr/local/lib/rpi-camera-importer"
8
-MEDIA_IMPORTER_SCRIPT="${PROJECT_DIR}/autonas-media-importer.sh"
9
-CONFIG_DIR="${RPI_CAMERA_IMPORTER_CONFIG_DIR:-/etc/rpi-camera-importer}"
10
-CONFIG_FILE="${CONFIG_DIR}/cameras.conf"
11
-MOUNT_BASE="${RPI_CAMERA_IMPORTER_MOUNT_BASE:-/mnt/rpi-camera-importer}"
12
-
13
-DRY_RUN=0
14
-VERBOSE=0
15
-
16
-log_message() {
17
-  local level="$1"
18
-  local message="$2"
19
-  local ts
20
-  ts="$(date '+%Y-%m-%d %H:%M:%S')"
21
-  echo "[$ts] [$level] $message"
22
-}
23
-
24
-usage() {
25
-  cat <<EOF
26
-${APP_NAME} v${VERSION}
27
-
28
-Usage:
29
-  ${APP_NAME} wizard
30
-  ${APP_NAME} list
31
-  ${APP_NAME} discover
32
-  ${APP_NAME} verify
33
-  ${APP_NAME} update --profile <name> --destination <path>
34
-  ${APP_NAME} import --profile <name> [--dry-run] [--verbose]
35
-  ${APP_NAME} import --uuid <uuid> [--dry-run] [--verbose]
36
-  ${APP_NAME} import --all [--dry-run] [--verbose]
37
-
38
-Config format:
39
-  name|uuid|destination_path
40
-
41
-Notes:
42
-  - Import flow is device-trigger compatible (udev + systemd)
43
-  - Media importer uses the proven AutoNAS importer script unchanged
44
-EOF
45
-}
46
-
47
-require_root() {
48
-  if [[ "${EUID}" -ne 0 ]]; then
49
-    log_message "ERROR" "Run as root"
50
-    exit 1
51
-  fi
52
-}
53
-
54
-require_dependency() {
55
-  local bin="$1"
56
-  if ! command -v "$bin" >/dev/null 2>&1; then
57
-    log_message "ERROR" "Missing dependency: $bin"
58
-    exit 1
59
-  fi
60
-}
61
-
62
-suggest_package_for_bin() {
63
-  local bin="$1"
64
-  case "$bin" in
65
-    exiftool) echo "libimage-exiftool-perl" ;;
66
-    blkid|mount|umount|mountpoint) echo "util-linux" ;;
67
-    find) echo "findutils" ;;
68
-    awk) echo "mawk" ;;
69
-    sed|grep) echo "sed grep" ;;
70
-    logger) echo "bsdutils" ;;
71
-    *) echo "" ;;
72
-  esac
73
-}
74
-
75
-ensure_runtime_dependencies() {
76
-  local missing_bins=()
77
-  local packages=()
78
-  local dep pkg
79
-
80
-  for dep in "$@"; do
81
-    if ! command -v "$dep" >/dev/null 2>&1; then
82
-      missing_bins+=("$dep")
83
-      pkg="$(suggest_package_for_bin "$dep")"
84
-      if [[ -n "$pkg" ]]; then
85
-        for p in $pkg; do
86
-          packages+=("$p")
87
-        done
88
-      fi
89
-    fi
90
-  done
91
-
92
-  if [[ ${#missing_bins[@]} -eq 0 ]]; then
93
-    return 0
94
-  fi
95
-
96
-  log_message "ERROR" "Missing runtime dependencies: ${missing_bins[*]}"
97
-
98
-  if [[ ${#packages[@]} -gt 0 ]]; then
99
-    local unique_packages
100
-    unique_packages="$(printf '%s\n' "${packages[@]}" | awk '!seen[$0]++' | xargs)"
101
-    log_message "ERROR" "Suggested install command: apt update && apt install -y ${unique_packages}"
102
-
103
-    if [[ -t 0 && -t 1 ]] && command -v apt >/dev/null 2>&1; then
104
-      local answer
105
-      read -r -p "Install missing dependencies now? [y/N]: " answer
106
-      if [[ "${answer,,}" == "y" || "${answer,,}" == "yes" ]]; then
107
-        apt update
108
-        apt install -y ${unique_packages}
109
-      fi
110
-    fi
111
-  fi
112
-
113
-  for dep in "$@"; do
114
-    if ! command -v "$dep" >/dev/null 2>&1; then
115
-      log_message "ERROR" "Dependency still missing after check: $dep"
116
-      return 1
117
-    fi
118
-  done
119
-
120
-  return 0
121
-}
122
-
123
-ensure_config_file() {
124
-  mkdir -p "$CONFIG_DIR"
125
-  if [[ ! -f "$CONFIG_FILE" ]]; then
126
-    cat > "$CONFIG_FILE" <<'EOF'
127
-# Camera profiles
128
-# Format: name|uuid|destination_path
129
-# Example: varia_rct715|A1B2-C3D4|/srv/media/varia
130
-EOF
131
-  fi
132
-
133
-  # Normalize legacy entries to the current 3-field profile schema.
134
-  : > "${CONFIG_FILE}.tmp"
135
-  local line
136
-  while IFS= read -r line; do
137
-    if [[ -z "$line" || "$line" == \#* ]]; then
138
-      echo "$line" >> "${CONFIG_FILE}.tmp"
139
-      continue
140
-    fi
141
-
142
-    IFS='|' read -r name uuid destination _ <<< "$line"
143
-    if [[ -n "$name" && -n "$uuid" && -n "$destination" ]]; then
144
-      echo "${name}|${uuid}|${destination}" >> "${CONFIG_FILE}.tmp"
145
-    fi
146
-  done < "$CONFIG_FILE"
147
-  mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
148
-}
149
-
150
-sanitize_profile_name() {
151
-  local v="$1"
152
-  echo "$v" | tr -cd '[:alnum:]_-' | xargs
153
-}
154
-
155
-profile_exists_by_name() {
156
-  local profile="$1"
157
-  grep -E "^${profile//./\\.}\|" "$CONFIG_FILE" >/dev/null 2>&1
158
-}
159
-
160
-profile_exists_by_uuid() {
161
-  local uuid="$1"
162
-  grep -E "^[^|]+\|${uuid//./\\.}\|" "$CONFIG_FILE" >/dev/null 2>&1
163
-}
164
-
165
-find_profile_line() {
166
-  local mode="$1"
167
-  local target="$2"
168
-  local line
169
-
170
-  while IFS= read -r line; do
171
-    [[ -z "$line" || "$line" == \#* ]] && continue
172
-    IFS='|' read -r name uuid destination _ <<< "$line"
173
-
174
-    case "$mode" in
175
-      name)
176
-        if [[ "$name" == "$target" ]]; then
177
-          echo "$line"
178
-          return 0
179
-        fi
180
-        ;;
181
-      uuid)
182
-        if [[ "$uuid" == "$target" ]]; then
183
-          echo "$line"
184
-          return 0
185
-        fi
186
-        ;;
187
-      *)
188
-        return 1
189
-        ;;
190
-    esac
191
-  done < "$CONFIG_FILE"
192
-
193
-  return 1
194
-}
195
-
196
-list_profiles() {
197
-  ensure_config_file
198
-  echo ""
199
-  echo "Configured camera profiles"
200
-  echo "--------------------------------------------------------------------------"
201
-  printf "%-20s %-16s %-30s\n" "Name" "UUID" "Destination"
202
-  echo "--------------------------------------------------------------------------"
203
-
204
-  local line
205
-  while IFS= read -r line; do
206
-    [[ -z "$line" || "$line" == \#* ]] && continue
207
-    IFS='|' read -r name uuid destination _ <<< "$line"
208
-    printf "%-20s %-16s %-30s\n" "$name" "$uuid" "$destination"
209
-  done < "$CONFIG_FILE"
210
-
211
-  echo "--------------------------------------------------------------------------"
212
-}
213
-
214
-discover_devices() {
215
-  ensure_runtime_dependencies "blkid" || return 1
216
-  echo ""
217
-  echo "Detected removable/storage block devices"
218
-  echo "---------------------------------------------------------------"
219
-  blkid -o export 2>/dev/null | awk '
220
-    /^DEVNAME=/ {dev=$0; sub(/^DEVNAME=/, "", dev)}
221
-    /^UUID=/ {uuid=$0; sub(/^UUID=/, "", uuid)}
222
-    /^TYPE=/ {type=$0; sub(/^TYPE=/, "", type)}
223
-    /^$/ {
224
-      if (dev != "" && uuid != "") {
225
-        printf "%s | UUID=%s | FS=%s\n", dev, uuid, type
226
-      }
227
-      dev=""; uuid=""; type=""
228
-    }
229
-    END {
230
-      if (dev != "" && uuid != "") {
231
-        printf "%s | UUID=%s | FS=%s\n", dev, uuid, type
232
-      }
233
-    }
234
-  '
235
-  echo "---------------------------------------------------------------"
236
-}
237
-
238
-add_profile_interactive() {
239
-  ensure_config_file
240
-
241
-  echo ""
242
-  echo "Tip: conecteaza camera si ruleaza optional 'discover' intr-un alt terminal."
243
-
244
-  local name uuid destination
245
-  read -r -p "Profile name (ex: varia_rct715): " name
246
-  name="$(sanitize_profile_name "$name")"
247
-  if [[ -z "$name" ]]; then
248
-    echo "Invalid profile name"
249
-    return
250
-  fi
251
-
252
-  if profile_exists_by_name "$name"; then
253
-    echo "Profile already exists"
254
-    return
255
-  fi
256
-
257
-  read -r -p "Camera UUID (ID_FS_UUID): " uuid
258
-  if [[ -z "$uuid" ]]; then
259
-    echo "UUID is required"
260
-    return
261
-  fi
262
-
263
-  if profile_exists_by_uuid "$uuid"; then
264
-    echo "UUID already configured"
265
-    return
266
-  fi
267
-
268
-  read -r -p "Destination path (ex: /srv/media/varia): " destination
269
-  if [[ -z "$destination" ]]; then
270
-    echo "Destination is required"
271
-    return
272
-  fi
273
-
274
-  echo "${name}|${uuid}|${destination}" >> "$CONFIG_FILE"
275
-  echo "Profile '$name' added"
276
-}
277
-
278
-remove_profile_interactive() {
279
-  ensure_config_file
280
-  local name
281
-  read -r -p "Profile name to remove: " name
282
-  name="$(sanitize_profile_name "$name")"
283
-
284
-  if ! profile_exists_by_name "$name"; then
285
-    echo "Profile not found"
286
-    return
287
-  fi
288
-
289
-  grep -v -E "^${name//./\\.}\|" "$CONFIG_FILE" > "${CONFIG_FILE}.tmp"
290
-  mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
291
-  echo "Profile '$name' removed"
292
-}
293
-
294
-update_profile_values() {
295
-  local target_name="$1"
296
-  local new_destination="$2"
297
-
298
-  if ! profile_exists_by_name "$target_name"; then
299
-    log_message "ERROR" "Profile '$target_name' not found"
300
-    return 1
301
-  fi
302
-
303
-  : > "${CONFIG_FILE}.tmp"
304
-  local line
305
-  local changed=0
306
-  while IFS= read -r line; do
307
-    if [[ -z "$line" || "$line" == \#* ]]; then
308
-      echo "$line" >> "${CONFIG_FILE}.tmp"
309
-      continue
310
-    fi
311
-
312
-    IFS='|' read -r name uuid destination _ <<< "$line"
313
-    if [[ "$name" == "$target_name" ]]; then
314
-      [[ -n "$new_destination" ]] && destination="$new_destination"
315
-      changed=1
316
-    fi
317
-
318
-    echo "${name}|${uuid}|${destination}" >> "${CONFIG_FILE}.tmp"
319
-  done < "$CONFIG_FILE"
320
-
321
-  mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE"
322
-
323
-  if [[ $changed -eq 1 ]]; then
324
-    log_message "INFO" "Profile '$target_name' updated"
325
-  fi
326
-}
327
-
328
-update_profile_interactive() {
329
-  ensure_config_file
330
-
331
-  local name
332
-  read -r -p "Profile name to update: " name
333
-  name="$(sanitize_profile_name "$name")"
334
-
335
-  local line
336
-  if ! line="$(find_profile_line "name" "$name")"; then
337
-    echo "Profile not found"
338
-    return 1
339
-  fi
340
-
341
-  local current_name current_uuid current_destination
342
-  IFS='|' read -r current_name current_uuid current_destination _ <<< "$line"
343
-
344
-  echo "Current UUID: $current_uuid"
345
-  echo "Current destination: $current_destination"
346
-
347
-  local new_destination
348
-  read -r -p "New destination path (leave blank to keep current): " new_destination
349
-
350
-  update_profile_values "$name" "$new_destination"
351
-}
352
-
353
-run_import_for_entry() {
354
-  local name="$1"
355
-  local uuid="$2"
356
-  local destination="$3"
357
-
358
-  local mount_point="${MOUNT_BASE}/${name}"
359
-  local device_path="/dev/disk/by-uuid/${uuid}"
360
-
361
-  if [[ ! -x "$MEDIA_IMPORTER_SCRIPT" ]]; then
362
-    log_message "ERROR" "Media importer missing: $MEDIA_IMPORTER_SCRIPT"
363
-    return 1
364
-  fi
365
-
366
-  if [[ ! -e "$device_path" ]]; then
367
-    log_message "WARN" "Device for UUID $uuid not present, skipping profile '$name'"
368
-    return 0
369
-  fi
370
-
371
-  mkdir -p "$mount_point" "$destination"
372
-
373
-  local mounted_here=0
374
-  if ! mountpoint -q "$mount_point" 2>/dev/null; then
375
-    if [[ $DRY_RUN -eq 1 ]]; then
376
-      log_message "INFO" "[dry-run] would mount $device_path at $mount_point"
377
-    else
378
-      mount "$device_path" "$mount_point"
379
-      mounted_here=1
380
-    fi
381
-  fi
382
-
383
-  local args=("$mount_point" "$destination")
384
-  if [[ $DRY_RUN -eq 1 ]]; then
385
-    args+=("--dry-run")
386
-  fi
387
-  if [[ $VERBOSE -eq 1 ]]; then
388
-    args+=("--verbose")
389
-  fi
390
-
391
-  log_message "INFO" "Import start for '$name' (uuid=$uuid)"
392
-  if [[ $DRY_RUN -eq 1 ]]; then
393
-    "$MEDIA_IMPORTER_SCRIPT" "${args[@]}" || true
394
-  else
395
-    "$MEDIA_IMPORTER_SCRIPT" "${args[@]}"
396
-  fi
397
-  local importer_rc=$?
398
-
399
-  if [[ $mounted_here -eq 1 ]]; then
400
-    sync || true
401
-    umount "$mount_point" || log_message "WARN" "Could not unmount $mount_point"
402
-  fi
403
-
404
-  if [[ $importer_rc -ne 0 ]]; then
405
-    log_message "ERROR" "Import failed for '$name'"
406
-    return 1
407
-  fi
408
-
409
-  log_message "INFO" "Import finished for '$name'"
410
-  return 0
411
-}
412
-
413
-import_profile_by_name() {
414
-  ensure_config_file
415
-  local target="$1"
416
-  local line
417
-  if ! line="$(find_profile_line "name" "$target")"; then
418
-    log_message "ERROR" "Profile '$target' not found"
419
-    return 1
420
-  fi
421
-
422
-  local name uuid destination
423
-  IFS='|' read -r name uuid destination _ <<< "$line"
424
-  run_import_for_entry "$name" "$uuid" "$destination"
425
-}
426
-
427
-import_profile_by_uuid() {
428
-  ensure_config_file
429
-  local target_uuid="$1"
430
-
431
-  local line
432
-  if ! line="$(find_profile_line "uuid" "$target_uuid")"; then
433
-    log_message "INFO" "UUID '$target_uuid' is not configured"
434
-    return 0
435
-  fi
436
-
437
-  local name uuid destination
438
-  IFS='|' read -r name uuid destination _ <<< "$line"
439
-  run_import_for_entry "$name" "$uuid" "$destination"
440
-}
441
-
442
-verify_pipeline() {
443
-  ensure_config_file
444
-
445
-  local rc=0
446
-  local deps=(mount umount mountpoint exiftool blkid find awk sed grep logger)
447
-  local dep
448
-
449
-  log_message "INFO" "Verifying dependencies"
450
-  for dep in "${deps[@]}"; do
451
-    if command -v "$dep" >/dev/null 2>&1; then
452
-      log_message "INFO" "OK dependency: $dep"
453
-    else
454
-      log_message "ERROR" "Missing dependency: $dep"
455
-      rc=1
456
-    fi
457
-  done
458
-
459
-  local paths=(
460
-    "$MEDIA_IMPORTER_SCRIPT"
461
-    "/usr/local/lib/rpi-camera-importer/rpi-camera-disk-handler.sh"
462
-    "/usr/local/lib/rpi-camera-importer/rpi-camera-udev-wrapper.sh"
463
-    "/etc/udev/rules.d/99-rpi-camera-importer.rules"
464
-    "/etc/systemd/system/rpi-camera-importer-attach@.service"
465
-  )
466
-  local p
467
-
468
-  log_message "INFO" "Verifying runtime files"
469
-  for p in "${paths[@]}"; do
470
-    if [[ -e "$p" ]]; then
471
-      log_message "INFO" "OK file: $p"
472
-    else
473
-      log_message "ERROR" "Missing file: $p"
474
-      rc=1
475
-    fi
476
-  done
477
-
478
-  if command -v systemctl >/dev/null 2>&1; then
479
-    if systemctl daemon-reload >/dev/null 2>&1; then
480
-      log_message "INFO" "OK systemd daemon-reload"
481
-    else
482
-      log_message "WARN" "systemd daemon-reload returned non-zero"
483
-    fi
484
-  fi
485
-
486
-  log_message "INFO" "Configured profiles overview"
487
-  list_profiles
488
-
489
-  log_message "INFO" "Connected block devices overview"
490
-  discover_devices
491
-
492
-  if [[ $rc -eq 0 ]]; then
493
-    log_message "INFO" "Mechanism verification completed successfully"
494
-  else
495
-    log_message "ERROR" "Mechanism verification failed"
496
-  fi
497
-
498
-  return $rc
499
-}
500
-
501
-import_all_profiles() {
502
-  ensure_config_file
503
-  local line any=0
504
-
505
-  while IFS= read -r line; do
506
-    [[ -z "$line" || "$line" == \#* ]] && continue
507
-    IFS='|' read -r name uuid destination _ <<< "$line"
508
-
509
-    any=1
510
-    run_import_for_entry "$name" "$uuid" "$destination"
511
-  done < "$CONFIG_FILE"
512
-
513
-  if [[ $any -eq 0 ]]; then
514
-    log_message "WARN" "No profiles found"
515
-  fi
516
-}
517
-
518
-wizard() {
519
-  ensure_config_file
520
-
521
-  while true; do
522
-    echo ""
523
-    echo "=== Raspberry Pi Camera Importer Wizard ==="
524
-    echo "1) List profiles"
525
-    echo "2) Discover connected devices (UUID)"
526
-    echo "3) Add profile"
527
-    echo "4) Remove profile"
528
-    echo "5) Update profile destination"
529
-    echo "6) Run import for one profile"
530
-    echo "7) Run import for all profiles"
531
-    echo "0) Exit"
532
-    echo ""
533
-
534
-    local choice
535
-    read -r -p "Choose option: " choice
536
-
537
-    case "$choice" in
538
-      1) list_profiles ;;
539
-      2) discover_devices ;;
540
-      3) add_profile_interactive ;;
541
-      4) remove_profile_interactive ;;
542
-      5) update_profile_interactive ;;
543
-      6)
544
-        local p
545
-        read -r -p "Profile name: " p
546
-        import_profile_by_name "$(sanitize_profile_name "$p")"
547
-        ;;
548
-      7) import_all_profiles ;;
549
-      0) break ;;
550
-      *) echo "Invalid option" ;;
551
-    esac
552
-  done
553
-}
554
-
555
-main() {
556
-  local command="${1:-}"
557
-  if [[ -z "$command" ]]; then
558
-    usage
559
-    exit 1
560
-  fi
561
-  shift || true
562
-
563
-  case "$command" in
564
-    --help|-h)
565
-      usage
566
-      ;;
567
-    list)
568
-      require_root
569
-      list_profiles
570
-      ;;
571
-    discover)
572
-      require_root
573
-      ensure_runtime_dependencies "blkid" || exit 1
574
-      discover_devices
575
-      ;;
576
-    verify)
577
-      require_root
578
-      verify_pipeline
579
-      ;;
580
-    update)
581
-      require_root
582
-      ensure_config_file
583
-
584
-      local profile=""
585
-      local destination=""
586
-
587
-      while [[ $# -gt 0 ]]; do
588
-        case "$1" in
589
-          --profile)
590
-            profile="$2"
591
-            shift 2
592
-            ;;
593
-          --destination)
594
-            destination="$2"
595
-            shift 2
596
-            ;;
597
-          *)
598
-            log_message "ERROR" "Unknown option: $1"
599
-            exit 1
600
-            ;;
601
-        esac
602
-      done
603
-
604
-      if [[ -z "$profile" ]]; then
605
-        log_message "ERROR" "Use --profile <name>"
606
-        exit 1
607
-      fi
608
-
609
-      if [[ -z "$destination" ]]; then
610
-        log_message "ERROR" "Use --destination <path>"
611
-        exit 1
612
-      fi
613
-
614
-      update_profile_values "$profile" "$destination"
615
-      ;;
616
-    wizard)
617
-      require_root
618
-      ensure_runtime_dependencies "mount" "umount" "mountpoint" "exiftool" "blkid" || exit 1
619
-      wizard
620
-      ;;
621
-    import)
622
-      require_root
623
-      ensure_runtime_dependencies "mount" "umount" "mountpoint" "exiftool" || exit 1
624
-      local profile=""
625
-      local uuid=""
626
-      local import_all=0
627
-
628
-      while [[ $# -gt 0 ]]; do
629
-        case "$1" in
630
-          --profile)
631
-            profile="$2"
632
-            shift 2
633
-            ;;
634
-          --uuid)
635
-            uuid="$2"
636
-            shift 2
637
-            ;;
638
-          --all)
639
-            import_all=1
640
-            shift
641
-            ;;
642
-          --dry-run)
643
-            DRY_RUN=1
644
-            shift
645
-            ;;
646
-          --verbose)
647
-            VERBOSE=1
648
-            shift
649
-            ;;
650
-          *)
651
-            log_message "ERROR" "Unknown option: $1"
652
-            exit 1
653
-            ;;
654
-        esac
655
-      done
656
-
657
-      if [[ $import_all -eq 1 ]]; then
658
-        import_all_profiles
659
-      elif [[ -n "$profile" ]]; then
660
-        import_profile_by_name "$profile"
661
-      elif [[ -n "$uuid" ]]; then
662
-        import_profile_by_uuid "$uuid"
663
-      else
664
-        log_message "ERROR" "Use --profile <name>, --uuid <uuid> or --all"
665
-        exit 1
666
-      fi
667
-      ;;
668
-    *)
669
-      log_message "ERROR" "Unknown command: $command"
670
-      usage
671
-      exit 1
672
-      ;;
673
-  esac
674
-}
675
-
676
-main "$@"
+0 -39
RPI/scripts/rpi-camera-udev-wrapper.sh
@@ -1,39 +0,0 @@
1
-#!/usr/bin/env bash
2
-
3
-set -euo pipefail
4
-
5
-LOG_TAG="rpi-camera-wrapper"
6
-export PATH="/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin"
7
-export HOME="/root"
8
-export USER="root"
9
-export LOGNAME="root"
10
-export SHELL="/bin/bash"
11
-umask 022
12
-
13
-action="${1:-}"
14
-uuid="${2:-}"
15
-
16
-if [[ -z "$action" || -z "$uuid" ]]; then
17
-  logger -p local0.err -t "$LOG_TAG" "Invalid arguments: action='$action' uuid='$uuid'"
18
-  exit 1
19
-fi
20
-
21
-logger -p local0.info -t "$LOG_TAG" "Called with action=$action uuid=$uuid"
22
-
23
-(
24
-  sleep 3
25
-  if [[ "$(id -u)" != "0" ]]; then
26
-    logger -p local0.err -t "$LOG_TAG" "Not running as root"
27
-    exit 1
28
-  fi
29
-
30
-  if [[ "$action" == "attach" ]]; then
31
-    systemctl start "rpi-camera-importer-attach@${uuid}.service"
32
-    logger -p local0.info -t "$LOG_TAG" "Started attach service for UUID=$uuid"
33
-  else
34
-    logger -p local0.warning -t "$LOG_TAG" "Unsupported wrapper action: $action"
35
-  fi
36
-) &
37
-
38
-logger -p local0.info -t "$LOG_TAG" "Background job started for $action $uuid"
39
-exit 0
+0 -63
RPI/setup.sh
@@ -1,63 +0,0 @@
1
-#!/usr/bin/env bash
2
-
3
-set -euo pipefail
4
-
5
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
-APP_NAME="rpi-camera-importer"
7
-BIN_PATH="/usr/local/bin/${APP_NAME}"
8
-RUNTIME_DIR="/usr/local/lib/${APP_NAME}"
9
-CONFIG_DIR="/etc/rpi-camera-importer"
10
-CONFIG_FILE="${CONFIG_DIR}/cameras.conf"
11
-UDEV_RULE="/etc/udev/rules.d/99-rpi-camera-importer.rules"
12
-ATTACH_UNIT="/etc/systemd/system/rpi-camera-importer-attach@.service"
13
-
14
-require_root() {
15
-  if [[ "${EUID}" -ne 0 ]]; then
16
-    echo "This script must run as root"
17
-    exit 1
18
-  fi
19
-}
20
-
21
-install_dependencies() {
22
-  echo "Dependency installation at deploy is disabled."
23
-  echo "Dependencies are checked at runtime by rpi-camera-importer."
24
-}
25
-
26
-install_files() {
27
-  install -d /usr/local/bin
28
-  install -d "$RUNTIME_DIR"
29
-  install -d "$CONFIG_DIR"
30
-
31
-  install -m 0755 "${SCRIPT_DIR}/scripts/rpi-camera-importer.sh" "$BIN_PATH"
32
-  install -m 0755 "${SCRIPT_DIR}/scripts/rpi-camera-disk-handler.sh" "${RUNTIME_DIR}/rpi-camera-disk-handler.sh"
33
-  install -m 0755 "${SCRIPT_DIR}/scripts/rpi-camera-udev-wrapper.sh" "${RUNTIME_DIR}/rpi-camera-udev-wrapper.sh"
34
-  install -m 0755 "${SCRIPT_DIR}/scripts/autonas-media-importer.sh" "${RUNTIME_DIR}/autonas-media-importer.sh"
35
-
36
-  if [[ ! -f "$CONFIG_FILE" ]]; then
37
-    install -m 0644 "${SCRIPT_DIR}/config/cameras.conf" "$CONFIG_FILE"
38
-  else
39
-    echo "Config already exists, preserving: $CONFIG_FILE"
40
-  fi
41
-
42
-  install -m 0644 "${SCRIPT_DIR}/config/99-rpi-camera-importer.rules" "$UDEV_RULE"
43
-  install -m 0644 "${SCRIPT_DIR}/config/rpi-camera-importer-attach@.service" "$ATTACH_UNIT"
44
-}
45
-
46
-activate() {
47
-  systemctl daemon-reload
48
-  udevadm control --reload-rules
49
-  udevadm trigger --subsystem-match=block --action=add
50
-}
51
-
52
-main() {
53
-  require_root
54
-  install_dependencies
55
-  install_files
56
-  activate
57
-
58
-  echo "Installed ${APP_NAME}"
59
-  echo "Wizard: ${APP_NAME} wizard"
60
-  echo "Logs: journalctl -u rpi-camera-importer-attach@<UUID>.service -f"
61
-}
62
-
63
-main "$@"