Showing 27 changed files with 693 additions and 319 deletions
+48 -0
AGENTS.md
@@ -0,0 +1,48 @@
1
+# Agent Notes
2
+
3
+## Media importer lineage
4
+
5
+AutoNAS derives its unattended camera import behavior from:
6
+
7
+```text
8
+/Users/bogdan/Documents/Workspaces/Bogdan/Media Importer/media-importer.sh
9
+```
10
+
11
+Before changing `scripts/autonas-media-importer.sh`, read the upstream note:
12
+
13
+```text
14
+/Users/bogdan/Documents/Workspaces/Bogdan/Media Importer/AGENTS.md
15
+```
16
+
17
+The Raspberry Pi importer/wrapper direction from the old Media Importer repo is a dead end and must not be used as a reference design unless that direction is explicitly reopened.
18
+
19
+## Unattended safety rules
20
+
21
+AutoNAS runs from udev/systemd and is therefore unattended by default. Changes must preserve the upstream data-loss protections:
22
+
23
+- no overwrite of existing destination files;
24
+- destination conflicts resolved before copy/move;
25
+- planned destination paths reserved during one run;
26
+- move mode implemented as copy, verify, then delete source;
27
+- source deletion only after destination verification succeeds;
28
+- destination temp files cleaned on copy/verify failures;
29
+- `VERIFY_MODE=size` default with `strict` available;
30
+- GoPro timestamp changes require regression testing against real MP4/THM/LRV cases.
31
+
32
+If any of these protections are missing, do not recommend AutoNAS unattended import.
33
+
34
+## Required validation
35
+
36
+For importer changes, compare behavior with upstream `media-importer.sh` and, when possible, run upstream tests:
37
+
38
+```bash
39
+cd "/Users/bogdan/Documents/Workspaces/Bogdan/Media Importer"
40
+./test_runner.sh timestamp-collision
41
+./test_runner.sh verify-mode
42
+./test_runner.sh dest-in-source
43
+./test_runner.sh gopro-sidecar-sync
44
+./test_runner.sh gopro-no-sidecar-reimport
45
+```
46
+
47
+If these cannot be run, mark the AutoNAS importer as partially validated only.
48
+
+31 -0
CHANGELOG.md
@@ -1,5 +1,36 @@
1 1
 # AutoNAS - Change Log
2 2
 
3
+## [v3.2.0] - 2026-05-18
4
+
5
+### Enhanced Media Importer (v2.0)
6
+
7
+- **autonas-media-importer.sh**: Completely refactored with advanced features:
8
+  - Organization patterns support: `ymd` (default), `ym`, `y`, `m`, `d`, `h` for flexible folder structures
9
+  - Filename modes: `full` (timestamp), `auto` (pattern-based), `orig` (preserve original)
10
+  - Date source options: `auto`, `exif`, `filesystem` for flexible date extraction
11
+  - Advanced EXIF parsing with fallback to filesystem timestamps
12
+  - GoPro special handling with THM/LRV sidecar file detection
13
+  - Explicit `--unattended` support for background imports
14
+  - Automatic SD/MMC udev attach support for GoPro cards exposed as `/dev/mmcblkNpM`
15
+  - Per-card import locking to ignore duplicate udev/systemd starts
16
+  - Verified copy-then-delete moves, preventing partial destination files when SD/MMC reads fail
17
+  - GoPro metadata rewrites now happen in `/dev/shm` staging when space allows
18
+  - Imported files now keep filesystem mtime aligned with the chosen capture timestamp
19
+  - GoPro filesystem timestamp selection now uses the MP4 file itself, not THM/LRV sidecars
20
+  - GoPro QuickTime integer dates are written with `QuickTimeUTC=1` so local camera time is stored as UTC metadata correctly
21
+  - QuickTime UTC date conversion for accurate timestamps
22
+  - Conflict resolution with automatic numeric suffix generation
23
+  - Unsortable file collection into dedicated directory
24
+  - Enhanced error handling and detailed logging
25
+  - Backward compatible with existing autonas-core.sh integration
26
+
27
+- **standalone-media-importer.sh**: Updated to v1.0 with full feature parity
28
+  - Complete rewrite with all advanced features for standalone use
29
+  - Dry-run mode for safe previewing of import operations
30
+  - Comprehensive reporting and statistics
31
+
32
+- Both scripts maintain 100% backward compatibility with positional arguments: `script.sh <source_mount> <destination_path> --verbose`
33
+
3 34
 ## [v3.1.2] - 2026-03-07
4 35
 
5 36
 ### Fixed
+18 -19
DEVELOPMENT.md
@@ -31,27 +31,26 @@ autoNAS/
31 31
 ```
32 32
 User Command: autonas <cmd> [options]
33 33
34
-    /usr/local/bin/autonas.sh (Main CLI)
34
+    /usr/local/sbin/autonas (Main CLI)
35 35
36
-    /usr/local/bin/autonas-core.sh (Business Logic Library)
36
+    /usr/local/lib/xdev/autonas/autonas-core.sh (Business Logic Library)
37 37
38
-    /usr/local/lib/autonas/*.sh (Internal System Scripts - Hidden)
38
+    /usr/local/lib/xdev/autonas/*.sh (Internal System Scripts - Hidden)
39 39
 ```
40 40
 
41 41
 ### Script Organization - Professional Deployment
42 42
 ```
43
-/usr/local/bin/
44
-├── autonas -> autonas.sh          # Only user-visible command
45
-├── autonas.sh                     # Main CLI interface
46
-└── autonas-core.sh                # Core business logic library
43
+/usr/local/sbin/
44
+└── autonas                        # Only user-visible command
47 45
 
48
-/usr/local/lib/autonas/            # Internal scripts (hidden from autocomplete)
46
+/usr/local/lib/xdev/autonas/       # Internal scripts (hidden from autocomplete)
47
+├── autonas-core.sh                # Core business logic library
49 48
 ├── autonas-boot-scan.sh           # Boot scanner
50 49
 ├── autonas-disk-handler.sh        # Disk event handler  
51 50
 ├── autonas-media-importer.sh      # Media importer
52 51
 ├── autonas-network-handler.sh     # Network handler
53 52
 ├── autonas-udev-wrapper.sh        # Udev wrapper
54
-└── autonas-uninstall.sh           # System uninstaller
53
+└── uninstall.sh                   # System uninstaller
55 54
 ```
56 55
 
57 56
 ### v3.0 Code Reduction Achievement - Technical Metrics
@@ -76,11 +75,11 @@ DUPLICATION ELIMINATED: 100% through core library pattern
76 75
 ### System Integration Updates - v3.0 Complete
77 76
 ```
78 77
 Configuration Files (All Updated):
79
-✅ 99-autonas-disk.rules       - Updated with /usr/local/lib/autonas/ paths
80
-✅ 98-autonas-interfaces.rules - Updated with /usr/local/lib/autonas/ paths
81
-✅ autonas.service             - Updated with /usr/local/lib/autonas/ paths  
82
-✅ autonas-boot-scan.service   - Updated with /usr/local/lib/autonas/ paths
83
-✅ autonas-attach@.service     - Updated with /usr/local/lib/autonas/ paths
78
+✅ 99-autonas-disk.rules       - Updated with /usr/local/lib/xdev/autonas/ paths
79
+✅ 98-autonas-interfaces.rules - Updated with /usr/local/lib/xdev/autonas/ paths
80
+✅ autonas.service             - Updated with /usr/local/lib/xdev/autonas/ paths  
81
+✅ autonas-boot-scan.service   - Updated with /usr/local/lib/xdev/autonas/ paths
82
+✅ autonas-attach@.service     - Updated with /usr/local/lib/xdev/autonas/ paths
84 83
 ✅ disks.conf                  - Configuration template (unchanged)
85 84
 ```
86 85
 
@@ -107,7 +106,7 @@ autonas --help               # Complete help system
107 106
 #### 2. Core Business Logic Library (`autonas-core.sh`) ⭐ v3.0
108 107
 **Single source of truth** for all AutoNAS functionality:
109 108
 ```bash
110
-source "/usr/local/bin/autonas-core.sh"  # Loaded by all scripts
109
+source "/usr/local/lib/xdev/autonas/autonas-core.sh"  # Loaded by all scripts
111 110
 ```
112 111
 
113 112
 **Key Features:**
@@ -118,7 +117,7 @@ source "/usr/local/bin/autonas-core.sh"  # Loaded by all scripts
118 117
 - **Complete feature parity** with separate scripts
119 118
 
120 119
 #### 3. Internal System Scripts (Hidden from Users) ⭐ v3.0
121
-**Location:** `/usr/local/lib/autonas/*.sh`
120
+**Location:** `/usr/local/lib/xdev/autonas/*.sh`
122 121
 
123 122
 **Professional organization** with scripts hidden from autocomplete:
124 123
 - **autonas-disk-handler.sh**: Disk attach/detach operations
@@ -126,7 +125,7 @@ source "/usr/local/bin/autonas-core.sh"  # Loaded by all scripts
126 125
 - **autonas-udev-wrapper.sh**: udev timeout prevention
127 126
 - **autonas-boot-scan.sh**: Boot-time disk discovery
128 127
 - **autonas-media-importer.sh**: Camera import processing
129
-- **autonas-uninstall.sh**: Complete system removal
128
+- **uninstall.sh**: Complete system removal
130 129
 
131 130
 #### 4. udev Detection System
132 131
 **File:** `config/99-autonas-disk.rules`
@@ -152,8 +151,8 @@ Prevents udev timeout issues through:
152 151
 - **✅ Zero duplication**: Single source of truth for all business logic
153 152
 
154 153
 #### System Integration Status
155
-- **✅ systemd services**: All updated with `/usr/local/lib/autonas/` paths
156
-- **✅ udev rules**: All updated with `/usr/local/lib/autonas/` paths  
154
+- **✅ systemd services**: All updated with `/usr/local/lib/xdev/autonas/` paths
155
+- **✅ udev rules**: All updated with `/usr/local/lib/xdev/autonas/` paths  
157 156
 - **✅ configuration files**: All references updated throughout system
158 157
 - **✅ deploy script**: Enhanced with directory creation and proper installation
159 158
 - **✅ install/uninstall**: Complete support for new architecture
+69 -0
INSTALL.md
@@ -0,0 +1,69 @@
1
+# Instalare
2
+
3
+## Metoda recomandata
4
+
5
+Pentru install/uninstall pe un nod individual se foloseste `setup.sh`.
6
+
7
+### Instalare locala
8
+
9
+```bash
10
+sudo ./setup.sh --local
11
+```
12
+
13
+### Instalare remote
14
+
15
+```bash
16
+sudo ./setup.sh 192.168.2.91
17
+```
18
+
19
+Pentru rollout pe cluster se poate folosi si:
20
+
21
+```bash
22
+./deploy.sh install
23
+./deploy.sh install 192.168.2.91
24
+```
25
+
26
+## Ce instaleaza
27
+
28
+- `/usr/local/sbin/autonas`
29
+- `/usr/local/lib/xdev/autonas/*`
30
+- `/usr/local/lib/xdev/autonas/uninstall.sh`
31
+- `/usr/local/sbin/xdev-autonas-uninstall`
32
+- `/etc/default/xdev-autonas`
33
+- `/etc/systemd/system/autonas.service`
34
+- `/etc/systemd/system/autonas-boot-scan.service`
35
+- `/etc/systemd/system/autonas-attach@.service`
36
+- `/etc/udev/rules.d/99-autonas-disk.rules`
37
+- `/etc/udev/rules.d/98-autonas-interfaces.rules`
38
+- `/usr/local/share/doc/xdev/autonas/*`
39
+
40
+Excepții deliberate:
41
+- configul cluster-shared rămâne în `/etc/pve/autonas`
42
+- mount points și datele operaționale rămân în `/mnt/autonas`
43
+- intrările NFS din `/etc/exports` rămân pentru review manual la uninstall
44
+
45
+## Activare
46
+
47
+Installerul face:
48
+- `systemctl enable --now nfs-kernel-server`
49
+- `systemctl enable --now autonas.service`
50
+- `systemctl enable --now autonas-boot-scan.service`
51
+- `udevadm control --reload-rules`
52
+
53
+Ordering relevant la shutdown:
54
+- `autonas.service` și `autonas-boot-scan.service` sunt instalate cu `Before=remote-fs.target` și `Before=umount.target`
55
+- scopul este să păstreze exporturile AutoNAS locale disponibile până când mount-urile client NFS dependente au avut șansa să se demonteze
56
+- dacă nodul montează propriile exporturi AutoNAS înapoi ca storages Proxmox, installerul generează `/etc/systemd/system/nfs-server.service.d/50-autonas-self-hosted-proxmox.conf` cu `Before=` pe unitățile `mnt-pve-*.mount` relevante, ca providerul NFS să rămână activ până la finalizarea unmount-ului; vezi issue-ul de cluster `ISSUE-2026-002` pentru contextul complet
57
+
58
+## Uninstall
59
+
60
+```bash
61
+sudo ./setup.sh --local --uninstall
62
+sudo ./setup.sh --uninstall 192.168.2.91
63
+```
64
+
65
+Sau direct pe host:
66
+
67
+```bash
68
+sudo /usr/local/lib/xdev/autonas/uninstall.sh
69
+```
+40 -12
README.md
@@ -7,7 +7,7 @@ AutoNAS este un sistem inteligent pentru montarea automată a diskurilor externe
7 7
 - **⚡ Interfață Curată** - Doar comanda `autonas` vizibilă în autocomplete
8 8
 - **🔄 Background Import** - Import-uri în background fără timeout constraints  
9 9
 - **📊 Cod Optimizat** - 47% reducere cod prin eliminarea duplicărilor
10
-- **🗂️ Scripturi Organizate** - Toate scripturile interne ascunse în `/usr/local/lib/autonas/`
10
+- **🗂️ Scripturi Organizate** - Toate scripturile interne ascunse în `/usr/local/lib/xdev/autonas/`
11 11
 
12 12
 ## 🌟 **Interfața Curată AutoNAS**
13 13
 
@@ -37,7 +37,7 @@ autonas --help              # Ajutor complet
37 37
 - **Un singur command vizibil** - doar `autonas` în autocomplete
38 38
 - **Interfață consistentă** pentru toate operațiunile
39 39
 - **Cod optimizat** cu 47% reducere prin eliminarea duplicărilor
40
-- **Scripturi organizate** - toate utilitarele interne în `/usr/local/lib/autonas/`
40
+- **Scripturi organizate** - toate utilitarele interne în `/usr/local/lib/xdev/autonas/`
41 41
 - **Logging unificat** cu tag-uri comune
42 42
 
43 43
 ## 🚀 Instalare în Cluster
@@ -79,6 +79,32 @@ AutoNAS folosește exclusiv deploy remote pentru instalarea și managementul pe
79 79
 - ✅ **Error handling** - management robust al erorilor
80 80
 - ✅ **Logging colorat** - feedback clar pentru fiecare operație
81 81
 
82
+## 📦 Layout standardizat
83
+
84
+AutoNAS este aliniat la layout-ul comun `xdev`, cu două excepții deliberate:
85
+- configul cluster-shared rămâne în `/etc/pve/autonas`
86
+- mount points și datele montate rămân în `/mnt/autonas`
87
+
88
+Layout instalat pe host:
89
+- comandă operator: `/usr/local/sbin/autonas`
90
+- runtime intern: `/usr/local/lib/xdev/autonas`
91
+- uninstall canonic: `/usr/local/lib/xdev/autonas/uninstall.sh`
92
+- wrapper uninstall: `/usr/local/sbin/xdev-autonas-uninstall`
93
+- defaults: `/etc/default/xdev-autonas`
94
+- docs: `/usr/local/share/doc/xdev/autonas`
95
+
96
+Observație operațională:
97
+- `autonas.service` și `autonas-boot-scan.service` rămân intenționat ordonate înainte de `remote-fs.target` și `umount.target`, pentru ca exporturile NFS locale să nu dispară înainte ca mount-urile Proxmox dependente să se demonteze la shutdown
98
+- pentru mount-urile Proxmox self-hosted, installerul generează și un drop-in `nfs-server.service.d/50-autonas-self-hosted-proxmox.conf` care adaugă ordering explicit față de unitățile `mnt-pve-*.mount` identificate din `storage.cfg`; contextul complet este urmărit în issue-ul de cluster `ISSUE-2026-002`
99
+
100
+Metoda standard pentru install/uninstall pe un singur nod:
101
+
102
+```bash
103
+sudo ./setup.sh --local
104
+sudo ./setup.sh 192.168.2.91
105
+sudo ./setup.sh --uninstall 192.168.2.91
106
+```
107
+
82 108
 ### Pregătirea pentru Deploy:
83 109
 1. **Configurează cheile SSH** pe serverele țintă
84 110
 2. **Testează conectivitatea**: `./deploy.sh status`
@@ -219,18 +245,17 @@ mount | grep autonas          # Verifică mount-urile active
219 245
 
220 246
 ### 📁 Organizarea Scripturilor
221 247
 ```
222
-/usr/local/bin/
223
-├── autonas -> autonas.sh          # Singura comandă vizibilă utilizatorului
224
-├── autonas.sh                     # Script principal CLI (1,208 linii)
225
-└── autonas-core.sh                # Biblioteca centrală (1,044 linii)
248
+/usr/local/sbin/
249
+└── autonas                        # Singura comandă vizibilă utilizatorului
226 250
 
227
-/usr/local/lib/autonas/            # Scripturi interne (ascunse din autocomplete)
251
+/usr/local/lib/xdev/autonas/
252
+├── autonas-core.sh                # Biblioteca centrală
228 253
 ├── autonas-boot-scan.sh           # Scanner la bootare
229 254
 ├── autonas-disk-handler.sh        # Handler evenimente disk
230 255
 ├── autonas-media-importer.sh      # Importer automat media
231
-├── autonas-network-handler.sh     # Handler eventi rețea
256
+├── autonas-network-handler.sh     # Handler evenimente rețea
232 257
 ├── autonas-udev-wrapper.sh        # Wrapper pentru udev
233
-└── autonas-uninstall.sh           # Uninstaller sistem
258
+└── uninstall.sh                   # Uninstaller sistem
234 259
 ```
235 260
 
236 261
 ### 🎯 Single Source of Truth
@@ -250,7 +275,7 @@ mount | grep autonas          # Verifică mount-urile active
250 275
 2. **Configurații persistente**: Diskurile configurate se montează automat la conectare
251 276
 3. **Cleanup automat**: Demontarea curăță automat mount points, IP-uri și export-uri NFS
252 277
 4. **Siguranță date**: Dezinstalarea păstrează toate datele și configurațiile
253
-5. **Script organizare**: Doar `autonas` vizibil - toate utilitarele în `/usr/local/lib/autonas/`
278
+5. **Script organizare**: Doar `autonas` vizibil - toate utilitarele în `/usr/local/lib/xdev/autonas/`
254 279
 
255 280
 ## 🎯 Use Cases Rapide
256 281
 
@@ -278,7 +303,9 @@ AutoNAS v3.0 introduce arhitectura revoluționară **Background Import** pentru
278 303
 ### Caracteristici Camera Import
279 304
 - **🎯 Background Services** - Import rulează în servicii systemd separate fără timeout
280 305
 - **Detectare automată** a structurilor de foldere pentru camere (DCIM, PRIVATE, MP_ROOT)
306
+- **Carduri SD/MMC GoPro** - detectare automată pentru carduri expuse ca `/dev/mmcblkNpM`
281 307
 - **Import cu EXIF** - organizare automată pe baza metadatelor temporale cu UTC conversion
308
+- **GoPro MP4** - preferă timestamp-ul filesystem/THM/LRV și sincronizează metadata importată în modul unattended
282 309
 - **Multiple formate suportate**: MP4, MOV, JPG, JPEG, PNG, ARW, DNG, MP4, AVI, MKV
283 310
 - **Organizare automată pe date** din EXIF/QuickTime tags cu conversie UTC → Local Time
284 311
 - **Dry-run mode** pentru testare fără modificarea fișierelor
@@ -288,12 +315,13 @@ AutoNAS v3.0 introduce arhitectura revoluționară **Background Import** pentru
288 315
 
289 316
 ### Format Configurație IMPORT
290 317
 ```
291
-UUID:NAME:IMPORT:IMPORT:MOUNT_POINT:DESTINATION_PATH[:SCRIPT_PATH]
318
+UUID:NAME:IMPORT:IMPORT:MOUNT_POINT:DESTINATION_PATH
292 319
 ```
293 320
 
294 321
 **Exemplu configurație:**
295 322
 ```
296
-1234-5678:camera-garmin:IMPORT:IMPORT:/mnt/autonas/camera-garmin:/mnt/storage/photos:/usr/local/bin/autonas-camera-import.sh
323
+1234-5678:camera-garmin:IMPORT:IMPORT:/mnt/autonas/camera-garmin:/mnt/storage/photos
324
+766C-1A0F:gopro-sd:IMPORT:IMPORT:/mnt/autonas/gopro-sd:/mnt/autonas/ext01/@Camera/gopro
297 325
 ```
298 326
 
299 327
 ### Workflow Import Automat (Background Architecture)
BIN
autoSMART/.DS_Store
Binary file not shown.
+0 -0
autoSMART/examples/complete-deployment.sh
No changes.
+0 -0
autoSMART/scripts/deploy-production.sh
No changes.
+0 -0
autoSMART/scripts/deploy.sh
No changes.
+0 -0
autoSMART/scripts/install.sh
No changes.
+0 -0
autoSMART/scripts/monitor-cluster.sh
No changes.
+0 -0
autoSMART/scripts/smart-collector-daemon.pl
No changes.
+0 -0
autoSMART/scripts/uninstall.sh
No changes.
+10 -10
config/98-autonas-interfaces.rules
@@ -3,25 +3,25 @@
3 3
 
4 4
 
5 5
 # Trigger when network interfaces are added/removed
6
-ACTION=="add", SUBSYSTEM=="net", RUN+="/usr/local/bin/autonas-network-handler.sh interface_up %k"
7
-ACTION=="remove", SUBSYSTEM=="net", RUN+="/usr/local/bin/autonas-network-handler.sh interface_down %k"
6
+ACTION=="add", SUBSYSTEM=="net", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh interface_up %k"
7
+ACTION=="remove", SUBSYSTEM=="net", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh interface_down %k"
8 8
 
9 9
 
10 10
 # Trigger on interface operational state changes (most important for USB/Thunderbolt)
11
-ACTION=="change", SUBSYSTEM=="net", ATTR{operstate}=="up", RUN+="/usr/local/bin/autonas-network-handler.sh interface_change %k up"
12
-ACTION=="change", SUBSYSTEM=="net", ATTR{operstate}=="down", RUN+="/usr/local/bin/autonas-network-handler.sh interface_change %k down"
11
+ACTION=="change", SUBSYSTEM=="net", ATTR{operstate}=="up", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh interface_change %k up"
12
+ACTION=="change", SUBSYSTEM=="net", ATTR{operstate}=="down", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh interface_change %k down"
13 13
 
14 14
 
15 15
 # Additional triggers for carrier state (link up/down)
16
-ACTION=="change", SUBSYSTEM=="net", ATTR{carrier}=="1", RUN+="/usr/local/bin/autonas-network-handler.sh interface_change %k carrier_up"
17
-ACTION=="change", SUBSYSTEM=="net", ATTR{carrier}=="0", RUN+="/usr/local/bin/autonas-network-handler.sh interface_change %k carrier_down"
16
+ACTION=="change", SUBSYSTEM=="net", ATTR{carrier}=="1", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh interface_change %k carrier_up"
17
+ACTION=="change", SUBSYSTEM=="net", ATTR{carrier}=="0", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh interface_change %k carrier_down"
18 18
 
19 19
 
20 20
 # Special handling for USB network interfaces (common with Thunderbolt/USB-C docks)
21
-ACTION=="add", SUBSYSTEM=="net", ENV{ID_BUS}=="usb", RUN+="/usr/local/bin/autonas-network-handler.sh usb_interface_up %k"
22
-ACTION=="remove", SUBSYSTEM=="net", ENV{ID_BUS}=="usb", RUN+="/usr/local/bin/autonas-network-handler.sh usb_interface_down %k"
21
+ACTION=="add", SUBSYSTEM=="net", ENV{ID_BUS}=="usb", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh usb_interface_up %k"
22
+ACTION=="remove", SUBSYSTEM=="net", ENV{ID_BUS}=="usb", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh usb_interface_down %k"
23 23
 
24 24
 
25 25
 # Handle Thunderbolt interfaces specifically
26
-ACTION=="add", SUBSYSTEM=="net", DRIVERS=="thunderbolt", RUN+="/usr/local/bin/autonas-network-handler.sh usb_interface_up %k"
27
-ACTION=="remove", SUBSYSTEM=="net", DRIVERS=="thunderbolt", RUN+="/usr/local/bin/autonas-network-handler.sh usb_interface_down %k"
26
+ACTION=="add", SUBSYSTEM=="net", DRIVERS=="thunderbolt", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh usb_interface_up %k"
27
+ACTION=="remove", SUBSYSTEM=="net", DRIVERS=="thunderbolt", RUN+="/usr/local/lib/xdev/autonas/autonas-network-handler.sh usb_interface_down %k"
+18 -12
config/99-autonas-disk.rules
@@ -4,35 +4,41 @@
4 4
 # Rule for USB storage devices - trigger on add/remove
5 5
 # Detect USB disks (whole disk)
6 6
 ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", RUN+="/bin/systemctl start autonas-attach@%E{ID_FS_UUID}.service"
7
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
7
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
8 8
 
9 9
 # Detect USB partitions (more common case)
10 10
 ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="usb", RUN+="/bin/systemctl start autonas-attach@%E{ID_FS_UUID}.service"
11
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="usb", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
11
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="usb", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
12
+
13
+# Rule for built-in SD/MMC readers, including GoPro SDXC cards exposed as
14
+# /dev/mmcblkNpM. These devices usually do not set ID_BUS=usb, so the USB
15
+# rules above never see them.
16
+ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", KERNEL=="mmcblk*p[0-9]*", RUN+="/usr/local/lib/xdev/autonas/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
17
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", KERNEL=="mmcblk*p[0-9]*", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
12 18
 
13 19
 # Rule for ATA/SATA external storage devices (USB-to-SATA bridges)
14 20
 # Detect ATA disks (whole disk) - these appear as ATA but are actually USB external
15
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/bin/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
16
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
21
+ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/xdev/autonas/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
22
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
17 23
 
18 24
 # Detect ATA partitions (more common case)
19
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/bin/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
20
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
25
+ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/xdev/autonas/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
26
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ATTRS{removable}=="1", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
21 27
 
22 28
 # Special rule for USB-to-SATA bridges that appear as ATA but have USB properties
23 29
 # This safely identifies external USB drives that appear as ATA
24
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/bin/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
25
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
30
+ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/xdev/autonas/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
31
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
26 32
 
27 33
 # Detect USB-to-SATA bridge partitions
28
-ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/bin/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
29
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
34
+ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/xdev/autonas/autonas-udev-wrapper.sh attach %E{ID_FS_UUID}"
35
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ENV{ID_BUS}=="ata", ENV{ID_USB_TYPE}=="disk", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
30 36
 
31 37
 # Rule for SCSI external storage devices
32 38
 # Detect removable disks (whole disk) - exclude USB devices (handled above)
33 39
 ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/bin/systemctl start autonas-attach@%E{ID_FS_UUID}.service"
34
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
40
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="disk", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
35 41
 
36 42
 # Detect removable partitions (more common case) - exclude USB devices (handled above)
37 43
 ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/bin/systemctl start autonas-attach@%E{ID_FS_UUID}.service"
38
-ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/usr/local/bin/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
44
+ACTION=="remove", SUBSYSTEM=="block", ENV{ID_FS_UUID}!="", ENV{DEVTYPE}=="partition", ATTRS{removable}=="1", ENV{ID_BUS}!="usb", RUN+="/usr/local/lib/xdev/autonas/autonas-disk-handler.sh detach %E{ID_FS_UUID}"
+2 -2
config/autonas-attach@.service
@@ -5,11 +5,11 @@ After=systemd-udev-settle.service
5 5
 
6 6
 [Service]
7 7
 Type=oneshot
8
-ExecStart=/usr/local/bin/autonas-disk-handler.sh attach-deferred %i
8
+ExecStart=/usr/local/lib/xdev/autonas/autonas-disk-handler.sh attach-deferred %i
9 9
 RemainAfterExit=no
10 10
 TimeoutSec=120
11 11
 StandardOutput=journal
12 12
 StandardError=journal
13 13
 
14 14
 [Install]
15
-WantedBy=multi-user.target
15
+WantedBy=multi-user.target
+1 -1
config/autonas-boot-scan.service
@@ -12,7 +12,7 @@ Before=umount.target
12 12
 
13 13
 [Service]
14 14
 Type=oneshot
15
-ExecStart=/usr/local/bin/autonas-boot-scan.sh
15
+ExecStart=/usr/local/lib/xdev/autonas/autonas-boot-scan.sh
16 16
 RemainAfterExit=yes
17 17
 TimeoutSec=300
18 18
 StandardOutput=journal
+3 -3
config/autonas.service
@@ -1,6 +1,6 @@
1 1
 [Unit]
2 2
 Description=AutoNAS Main Service - Automatic disk mounting and NFS export system
3
-Documentation=file:///usr/local/share/doc/autonas/README.md
3
+Documentation=file:///usr/local/share/doc/xdev/autonas/README.md
4 4
 After=local-fs.target
5 5
 After=systemd-udev-settle.service
6 6
 After=network.target
@@ -16,9 +16,9 @@ RequiredBy=autonas-boot-scan.service
16 16
 Type=oneshot
17 17
 RemainAfterExit=yes
18 18
 ExecStart=/bin/true
19
-ExecReload=/usr/local/bin/autonas-disk-handler.sh reload
19
+ExecReload=/usr/local/lib/xdev/autonas/autonas-disk-handler.sh reload
20 20
 StandardOutput=journal
21 21
 StandardError=journal
22 22
 
23 23
 [Install]
24
-WantedBy=multi-user.target
24
+WantedBy=multi-user.target
+2 -1
config/disks.conf
@@ -7,11 +7,12 @@
7 7
 #
8 8
 # Camera Import (integrated media importer):  
9 9
 # UUID:name:IMPORT:IMPORT:temp_mount_point:destination_path
10
-# Note: Media importer script (/usr/local/bin/autonas-media-importer.sh) is built-in and non-configurable
10
+# Note: Media importer script (/usr/local/lib/xdev/autonas/autonas-media-importer.sh) is built-in and non-configurable
11 11
 #
12 12
 # Examples:
13 13
 # f6ac3d86-5681-4b33-bc64-aa272b333057:ext01:192.168.10.21:thunderbridge:/mnt/autonas/ext01:*(rw,all_squash,insecure,async,no_subtree_check,anonuid=0,anongid=0)
14 14
 # 8765-4321:camera-real:IMPORT:IMPORT:/mnt/autonas/camera-real:/mnt/autonas/ext01/@Camera/import
15
+# 766C-1A0F:gopro-sd:IMPORT:IMPORT:/mnt/autonas/gopro-sd:/mnt/autonas/ext01/@Camera/gopro
15 16
 #
16 17
 # Default NFS Options (AutoNAS standard): *(rw,all_squash,insecure,async,no_subtree_check,anonuid=0,anongid=0)
17 18
 # - rw: Read/write access
+9 -5
deploy.sh
@@ -176,8 +176,8 @@ check_status() {
176 176
     ssh -o LogLevel=ERROR "$SSH_USER@$target" "
177 177
         if [ -f '$REMOTE_DIR/scripts/install.sh' ]; then
178 178
             bash $REMOTE_DIR/scripts/install.sh status
179
-        elif [ -f '/usr/local/bin/autonas' ]; then
180
-            /usr/local/bin/autonas --version 2>/dev/null || echo 'AutoNAS instalat dar versiune necunoscută'
179
+        elif [ -f '/usr/local/sbin/autonas' ]; then
180
+            /usr/local/sbin/autonas --version 2>/dev/null || echo 'AutoNAS instalat dar versiune necunoscută'
181 181
         else
182 182
             echo 'AutoNAS nu este instalat'
183 183
         fi
@@ -200,7 +200,9 @@ force_cleanup() {
200 200
     
201 201
     # Încearcă să folosească uninstaller-ul existent
202 202
     ssh -o LogLevel=ERROR "$SSH_USER@$target" "
203
-        if [ -f '/usr/local/bin/autonas-uninstall.sh' ]; then
203
+        if [ -f '/usr/local/lib/xdev/autonas/uninstall.sh' ]; then
204
+            bash /usr/local/lib/xdev/autonas/uninstall.sh --force
205
+        elif [ -f '/usr/local/bin/autonas-uninstall.sh' ]; then
204 206
             bash /usr/local/bin/autonas-uninstall.sh --force
205 207
         elif [ -f '/usr/local/lib/autonas/autonas-uninstall.sh' ]; then
206 208
             bash /usr/local/lib/autonas/autonas-uninstall.sh --force
@@ -219,7 +221,10 @@ uninstall_autonas() {
219 221
     log_info "Dezinstalarea AutoNAS pe $target..."
220 222
 
221 223
     if ssh -o LogLevel=ERROR "$SSH_USER@$target" "
222
-        if [ -f '/usr/local/bin/autonas-uninstall.sh' ]; then
224
+        if [ -f '/usr/local/lib/xdev/autonas/uninstall.sh' ]; then
225
+            bash /usr/local/lib/xdev/autonas/uninstall.sh
226
+            echo 'AutoNAS dezinstalat cu succes!'
227
+        elif [ -f '/usr/local/bin/autonas-uninstall.sh' ]; then
223 228
             bash /usr/local/bin/autonas-uninstall.sh
224 229
             rm -f '/usr/local/bin/autonas-uninstall.sh'
225 230
             echo 'AutoNAS dezinstalat cu succes!'
@@ -325,7 +330,6 @@ execute_command() {
325 330
             "install")
326 331
                 copy_files "$target"
327 332
                 install_autonas "$target"
328
-                start_services "$target"
329 333
                 cleanup "$target"
330 334
                 ;;
331 335
             "install-deps")
+15 -1
scripts/autonas-boot-scan.sh
@@ -179,8 +179,22 @@ cleanup_boot_orphans() {
179 179
                     skip_next=false
180 180
                 fi
181 181
             elif [ "$skip_next" = true ]; then
182
-                # Skip the export line for orphaned entry
182
+                # Skip the export line for orphaned entry, or drop marked
183
+                # exports whose path does not exist yet. This avoids keeping
184
+                # stale subpath exports like /mnt/autonas/<disk>/@pve across
185
+                # reboot before the backing disk is mounted again.
183 186
                 skip_next=false
187
+
188
+                local export_path
189
+                export_path=$(awk '{print $1}' <<< "$line")
190
+
191
+                if [ -z "$export_path" ] || [ ! -e "$export_path" ]; then
192
+                    log_message "Removing AutoNAS export with missing path: $export_path" "info"
193
+                    ((cleaned_exports++))
194
+                    continue
195
+                fi
196
+
197
+                echo "$line" >> "$temp_file"
184 198
                 continue
185 199
             else
186 200
                 echo "$line" >> "$temp_file"
+36 -6
scripts/autonas-core.sh
@@ -4,9 +4,19 @@
4 4
 # Shared business logic for all AutoNAS components
5 5
 # This library contains all core functions to eliminate duplication
6 6
 
7
+# Standard install paths
8
+AUTONAS_ORG_ID="${AUTONAS_ORG_ID:-xdev}"
9
+AUTONAS_PROJECT_ID="${AUTONAS_PROJECT_ID:-autonas}"
10
+AUTONAS_RUNTIME_DIR="${AUTONAS_RUNTIME_DIR:-/usr/local/lib/${AUTONAS_ORG_ID}/${AUTONAS_PROJECT_ID}}"
11
+AUTONAS_COMMAND="${AUTONAS_COMMAND:-/usr/local/sbin/autonas}"
12
+AUTONAS_DEFAULTS_FILE="${AUTONAS_DEFAULTS_FILE:-/etc/default/${AUTONAS_ORG_ID}-${AUTONAS_PROJECT_ID}}"
13
+AUTONAS_DOC_DIR="${AUTONAS_DOC_DIR:-/usr/local/share/doc/${AUTONAS_ORG_ID}/${AUTONAS_PROJECT_ID}}"
14
+AUTONAS_MEDIA_IMPORTER="${AUTONAS_MEDIA_IMPORTER:-${AUTONAS_RUNTIME_DIR}/autonas-media-importer.sh}"
15
+AUTONAS_DISK_HANDLER="${AUTONAS_DISK_HANDLER:-${AUTONAS_RUNTIME_DIR}/autonas-disk-handler.sh}"
16
+
7 17
 # Load default configuration
8
-if [ -f "/etc/default/autonas" ]; then
9
-    source "/etc/default/autonas"
18
+if [ -f "${AUTONAS_DEFAULTS_FILE}" ]; then
19
+    source "${AUTONAS_DEFAULTS_FILE}"
10 20
 fi
11 21
 
12 22
 # Set defaults if not loaded from config
@@ -812,7 +822,7 @@ handle_camera_import() {
812 822
     fi
813 823
     
814 824
     # Always use the integrated media importer (non-configurable)
815
-    script_path="/usr/local/bin/autonas-media-importer.sh"
825
+    script_path="${AUTONAS_MEDIA_IMPORTER}"
816 826
     
817 827
     # Check if import script exists
818 828
     if [[ ! -x "$script_path" ]]; then
@@ -822,6 +832,11 @@ handle_camera_import() {
822 832
     
823 833
     log_message "Starting camera import process for UUID: $uuid"
824 834
     log_message "Import destination: $destination_path"
835
+
836
+    if systemctl is-active --quiet "autonas-import-$uuid.service" 2>/dev/null; then
837
+        log_message "Import already running for UUID: $uuid; skipping duplicate start" "warning"
838
+        return 0
839
+    fi
825 840
     
826 841
     # Clean up any previous transient unit before starting a new one
827 842
     systemctl stop autonas-import-$uuid.service 2>/dev/null || true
@@ -831,7 +846,7 @@ handle_camera_import() {
831 846
 
832 847
     # Run import in background using systemd-run for better process management
833 848
     systemd-run --no-block --unit="autonas-import-$uuid" \
834
-                /usr/local/bin/autonas-disk-handler.sh import "$uuid" "$mount_point" "$destination_path" "$script_path" || {
849
+                "${AUTONAS_DISK_HANDLER}" import "$uuid" "$mount_point" "$destination_path" "$script_path" || {
835 850
         log_message "Error: Failed to start background import process" "err"
836 851
         return 1
837 852
     }
@@ -845,10 +860,25 @@ run_background_import() {
845 860
     local uuid="$1"
846 861
     local mount_point="$2"
847 862
     local destination="$3"
848
-    local script_path="${4:-/usr/local/bin/autonas-media-importer.sh}"
863
+    local script_path="${4:-${AUTONAS_MEDIA_IMPORTER}}"
849 864
     
850 865
     log_message "Running background import for UUID: $uuid" "info" "autonas-import"
851 866
     log_message "Source: $mount_point, Destination: $destination" "info" "autonas-import"
867
+
868
+    local lock_dir="/run/lock/autonas"
869
+    local lock_file="$lock_dir/import-$uuid.lock"
870
+    local import_lock_fd
871
+
872
+    mkdir -p "$lock_dir"
873
+    exec {import_lock_fd}>"$lock_file" || {
874
+        log_message "Error: Cannot open import lock for UUID: $uuid" "err" "autonas-import"
875
+        return 1
876
+    }
877
+
878
+    if ! flock -n "$import_lock_fd"; then
879
+        log_message "Import already running for UUID: $uuid; duplicate background run skipped" "warning" "autonas-import"
880
+        return 0
881
+    fi
852 882
     
853 883
     # Change to a safe directory
854 884
     cd /tmp || cd /
@@ -864,7 +894,7 @@ run_background_import() {
864 894
     
865 895
     # Run the import script
866 896
     log_message "Executing import script: $script_path" "info" "autonas-import"
867
-    if "$script_path" "$mount_point" "$destination" --verbose; then
897
+    if "$script_path" "$mount_point" "$destination" --unattended --verbose; then
868 898
         log_message "Import completed successfully for UUID: $uuid" "info" "autonas-import"
869 899
         
870 900
         # Cleanup: unmount and remove mount point
+9 -0
scripts/autonas-disk-handler.sh
@@ -30,6 +30,15 @@ case "$1" in
30 30
             exit 1
31 31
         fi
32 32
         log_message "AutoNAS deferred attach operation started for UUID: $2" "info"
33
+        # Ignore unconfigured disks discovered by udev without marking the
34
+        # systemd unit as failed. Boot disks and random removable media should
35
+        # not create failed autonas-attach@... units just because AutoNAS does
36
+        # not manage them.
37
+        if ! resolve_config_identifier "$2" >/dev/null; then
38
+            log_message "No configuration found for identifier: $2 - deferred attach skipped" "info"
39
+            log_message "This is normal for disks not managed by AutoNAS" "info"
40
+            exit 0
41
+        fi
33 42
         handle_attach "$2"
34 43
         ;;
35 44
     "detach")
+236 -17
scripts/autonas-media-importer.sh
@@ -24,6 +24,7 @@ FILE_LIMIT=0
24 24
 CONFLICT_APPLY_ALL=""
25 25
 RESOLVED_DESTINATION_PATH=""
26 26
 RESERVED_DESTINATION_PATHS=()
27
+WORK_BASE="${AUTONAS_IMPORT_WORKDIR:-/dev/shm/autonas-media-importer}"
27 28
 
28 29
 TOTAL_FILES=0
29 30
 PROCESSED_FILES=0
@@ -90,6 +91,7 @@ Options:
90 91
     -F, --filename-mode  auto|full|orig (default: full)
91 92
     --date-source        auto|exif|filesystem (default: auto)
92 93
     --sync-metadata      Write date into destination metadata
94
+    --unattended         Never prompt; resolve destination conflicts with numeric suffixes
93 95
     --collect-unsortable Put undated files into DEST/unsortable
94 96
     --keep-empty-dirs    Keep empty directories after processing
95 97
     --dry-run            Show actions without changing files
@@ -133,24 +135,69 @@ should_prefer_gopro_filesystem_date() {
133 135
     is_gopro_media_file "$1"
134 136
 }
135 137
 
136
-filesystem_date_reference() {
138
+date_to_exiftool_format() {
139
+    local date_str="$1"
140
+
141
+    if [[ "$date_str" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})[[:space:]]+([0-9]{2}):([0-9]{2}):([0-9]{2})$ ]]; then
142
+        echo "${BASH_REMATCH[1]}:${BASH_REMATCH[2]}:${BASH_REMATCH[3]} ${BASH_REMATCH[4]}:${BASH_REMATCH[5]}:${BASH_REMATCH[6]}"
143
+        return 0
144
+    fi
145
+
146
+    return 1
147
+}
148
+
149
+sync_destination_metadata_to_date() {
137 150
     local file="$1"
138
-    local dir base stem ext sidecar_ext sidecar
139
-    dir=$(dirname "$file")
140
-    base=$(basename "$file")
141
-    stem="${base%.*}"
142
-    ext="${base##*.}"
143
-
144
-    if [[ "$ext" =~ ^([Mm][Pp]4)$ ]]; then
145
-        for sidecar_ext in THM thm LRV lrv; do
146
-            sidecar="$dir/$stem.$sidecar_ext"
147
-            if [[ -f "$sidecar" ]]; then
148
-                echo "$sidecar"
149
-                return 0
150
-            fi
151
-        done
151
+    local date_str="$2"
152
+    local exif_date
153
+
154
+    exif_date=$(date_to_exiftool_format "$date_str") || return 1
155
+
156
+    # GoPro filesystem timestamps are local camera time. QuickTime integer
157
+    # timestamps should be stored as UTC, so let ExifTool convert from local
158
+    # time on write instead of storing the local clock value as if it were UTC.
159
+    exiftool -api QuickTimeUTC=1 -overwrite_original \
160
+        "-CreateDate=$exif_date" \
161
+        "-DateTimeOriginal=$exif_date" \
162
+        "-DateTime=$exif_date" \
163
+        "-ModifyDate=$exif_date" \
164
+        "-MediaCreateDate=$exif_date" \
165
+        "-TrackCreateDate=$exif_date" \
166
+        "-QuickTime:CreateDate=$exif_date" \
167
+        "-QuickTime:ModifyDate=$exif_date" \
168
+        "$file" >/dev/null 2>&1
169
+}
170
+
171
+set_filesystem_mtime_to_date() {
172
+    local file="$1"
173
+    local date_str="$2"
174
+
175
+    if [[ "$OSTYPE" == "darwin"* ]]; then
176
+        local touch_date
177
+        touch_date=$(date -j -f "%Y-%m-%d %H:%M:%S" "$date_str" "+%Y%m%d%H%M.%S" 2>/dev/null) || return 1
178
+        touch -t "$touch_date" "$file"
179
+    else
180
+        touch -d "$date_str" "$file"
181
+    fi
182
+}
183
+
184
+should_sync_imported_metadata() {
185
+    local original_filename="$1"
186
+    local date_source="$2"
187
+
188
+    if [[ $SYNC_METADATA -eq 1 ]]; then
189
+        return 0
152 190
     fi
153 191
 
192
+    [[ "$date_source" == Filesystem* ]] && is_gopro_media_file "$original_filename"
193
+}
194
+
195
+filesystem_date_reference() {
196
+    local file="$1"
197
+
198
+    # GoPro MP4 files carry the capture time reliably in the file's own
199
+    # filesystem timestamp. THM/LRV sidecars can drift to chapter/end times on
200
+    # some cards, so they must not override the MP4 timestamp.
154 201
     echo "$file"
155 202
 }
156 203
 
@@ -398,6 +445,166 @@ safe_mv() {
398 445
     mv "$src" "$dst" 2> >(grep -v "set flags (was:" >&2)
399 446
 }
400 447
 
448
+safe_cp() {
449
+    local src="$1" dst="$2"
450
+    cp -p "$src" "$dst" 2> >(grep -v "set flags (was:" >&2)
451
+}
452
+
453
+verify_copied_file() {
454
+    local src="$1"
455
+    local dst="$2"
456
+
457
+    if [[ ! -f "$dst" ]]; then
458
+        log_message "Verified copy missing at destination: $dst" "err"
459
+        return 1
460
+    fi
461
+
462
+    if [[ "$VERIFY_MODE" == "none" ]]; then
463
+        return 0
464
+    fi
465
+
466
+    local src_size dst_size
467
+    src_size=$(get_file_size "$src")
468
+    dst_size=$(get_file_size "$dst")
469
+
470
+    if [[ "$src_size" != "$dst_size" ]]; then
471
+        log_message "Size mismatch after copy: $src ($src_size) != $dst ($dst_size)" "err"
472
+        return 1
473
+    fi
474
+
475
+    if [[ "$VERIFY_MODE" == "strict" ]] && ! cmp -s "$src" "$dst"; then
476
+        log_message "Content mismatch after copy: $src -> $dst" "err"
477
+        return 1
478
+    fi
479
+
480
+    return 0
481
+}
482
+
483
+copy_with_verification() {
484
+    local src="$1"
485
+    local dst="$2"
486
+
487
+    if [[ -e "$dst" ]]; then
488
+        log_message "Refusing to overwrite existing destination: $dst" "err"
489
+        return 1
490
+    fi
491
+
492
+    local dst_dir tmp
493
+    dst_dir=$(dirname "$dst")
494
+    tmp=$(mktemp "$dst_dir/.media-importer.$(basename "$dst").tmp.XXXXXX") || return 1
495
+    rm -f "$tmp"
496
+
497
+    if ! safe_cp "$src" "$tmp"; then
498
+        rm -f "$tmp"
499
+        return 1
500
+    fi
501
+
502
+    if ! verify_copied_file "$src" "$tmp"; then
503
+        rm -f "$tmp"
504
+        return 1
505
+    fi
506
+
507
+    if [[ -e "$dst" ]]; then
508
+        log_message "Destination appeared during copy, refusing to overwrite: $dst" "err"
509
+        rm -f "$tmp"
510
+        return 1
511
+    fi
512
+
513
+    if ! safe_mv "$tmp" "$dst"; then
514
+        rm -f "$tmp"
515
+        return 1
516
+    fi
517
+}
518
+
519
+available_bytes_for_path() {
520
+    local path="$1"
521
+
522
+    df -PB1 "$path" 2>/dev/null | awk 'NR==2 {print $4}'
523
+}
524
+
525
+staging_directory_for_file() {
526
+    local file_size="$1"
527
+    local needs_metadata_sync="$2"
528
+    local dest_dir="$3"
529
+    local required_bytes available_bytes
530
+
531
+    required_bytes=$((file_size + 67108864))
532
+    if [[ "$needs_metadata_sync" -eq 1 ]]; then
533
+        required_bytes=$((file_size * 2 + 67108864))
534
+    fi
535
+
536
+    if mkdir -p "$WORK_BASE" 2>/dev/null; then
537
+        available_bytes=$(available_bytes_for_path "$WORK_BASE")
538
+        if [[ "$available_bytes" =~ ^[0-9]+$ && "$available_bytes" -gt "$required_bytes" ]]; then
539
+            echo "$WORK_BASE"
540
+            return 0
541
+        fi
542
+        log_message "Not enough space in $WORK_BASE for staging; falling back to destination staging" "warning"
543
+    fi
544
+
545
+    echo "$dest_dir"
546
+}
547
+
548
+copy_with_staging() {
549
+    local src="$1"
550
+    local dst="$2"
551
+    local date_str="$3"
552
+    local sync_metadata="$4"
553
+
554
+    if [[ -e "$dst" ]]; then
555
+        log_message "Refusing to overwrite existing destination: $dst" "err"
556
+        return 1
557
+    fi
558
+
559
+    local dst_dir src_size stage_dir stage_tmp
560
+    dst_dir=$(dirname "$dst")
561
+    src_size=$(get_file_size "$src")
562
+    stage_dir=$(staging_directory_for_file "$src_size" "$sync_metadata" "$dst_dir")
563
+    mkdir -p "$stage_dir" || return 1
564
+
565
+    stage_tmp=$(mktemp "$stage_dir/.media-importer.$(basename "$dst").stage.XXXXXX") || return 1
566
+    rm -f "$stage_tmp"
567
+
568
+    if ! safe_cp "$src" "$stage_tmp"; then
569
+        rm -f "$stage_tmp"
570
+        return 1
571
+    fi
572
+
573
+    if ! verify_copied_file "$src" "$stage_tmp"; then
574
+        rm -f "$stage_tmp"
575
+        return 1
576
+    fi
577
+
578
+    if [[ "$sync_metadata" -eq 1 ]]; then
579
+        if ! sync_destination_metadata_to_date "$stage_tmp" "$date_str"; then
580
+            log_message "Failed to sync metadata date for staged file: $stage_tmp" "warning"
581
+            rm -f "$stage_tmp"
582
+            return 1
583
+        fi
584
+    fi
585
+
586
+    set_filesystem_mtime_to_date "$stage_tmp" "$date_str" || log_message "Failed to set filesystem timestamp for staged file: $stage_tmp" "warning"
587
+
588
+    if ! copy_with_verification "$stage_tmp" "$dst"; then
589
+        rm -f "$stage_tmp"
590
+        return 1
591
+    fi
592
+
593
+    set_filesystem_mtime_to_date "$dst" "$date_str" || log_message "Failed to set filesystem timestamp for $dst" "warning"
594
+    rm -f "$stage_tmp"
595
+}
596
+
597
+verified_move_file() {
598
+    local src="$1"
599
+    local dst="$2"
600
+
601
+    if ! copy_with_verification "$src" "$dst"; then
602
+        return 1
603
+    fi
604
+
605
+    rm -f "$src"
606
+}
607
+
401 608
 # Process a single file
402 609
 process_file() {
403 610
     local file="$1"
@@ -469,6 +676,9 @@ process_file() {
469 676
         else
470 677
             echo "Would move: $relative_path -> ${dest_path#$DESTINATION/}"
471 678
         fi
679
+        if should_sync_imported_metadata "$original_basename" "$date_source"; then
680
+            echo "Would sync metadata date: ${dest_path#$DESTINATION/} -> $date_str"
681
+        fi
472 682
         PROCESSED_FILES=$((PROCESSED_FILES + 1))
473 683
         PROCESSED_SIZE=$((PROCESSED_SIZE + file_size))
474 684
         return 0
@@ -476,8 +686,13 @@ process_file() {
476 686
 
477 687
     mkdir -p "$dest_dir" || { log_message "Could not create directory: $dest_dir" "err"; ERROR_FILES=$((ERROR_FILES + 1)); return 1; }
478 688
 
689
+    local sync_metadata_after_copy=0
690
+    if should_sync_imported_metadata "$original_basename" "$date_source"; then
691
+        sync_metadata_after_copy=1
692
+    fi
693
+
479 694
     if [[ $KEEP_ORIGINALS -eq 1 ]]; then
480
-        if cp "$file" "$dest_path"; then
695
+        if copy_with_staging "$file" "$dest_path" "$date_str" "$sync_metadata_after_copy"; then
481 696
             log_message "Copied: $relative_path" "info"
482 697
             [[ $VERBOSE -eq 1 ]] && echo "✓ Copied"
483 698
             PROCESSED_FILES=$((PROCESSED_FILES + 1))
@@ -489,7 +704,7 @@ process_file() {
489 704
             return 1
490 705
         fi
491 706
     else
492
-        if safe_mv "$file" "$dest_path"; then
707
+        if copy_with_staging "$file" "$dest_path" "$date_str" "$sync_metadata_after_copy" && rm -f "$file"; then
493 708
             log_message "Moved: $relative_path" "info"
494 709
             [[ $VERBOSE -eq 1 ]] && echo "✓ Moved"
495 710
             PROCESSED_FILES=$((PROCESSED_FILES + 1))
@@ -528,6 +743,10 @@ while [[ $# -gt 0 ]]; do
528 743
             SYNC_METADATA=1
529 744
             shift
530 745
             ;;
746
+        --unattended)
747
+            UNATTENDED=1
748
+            shift
749
+            ;;
531 750
         --collect-unsortable)
532 751
             COLLECT_UNSORTABLE=1
533 752
             shift
+2 -2
scripts/autonas-udev-wrapper.sh
@@ -7,7 +7,7 @@
7 7
 LOG_TAG="autonas-wrapper"
8 8
 
9 9
 # Set comprehensive environment for udev context
10
-export PATH="/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin"
10
+export PATH="/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin"
11 11
 export HOME="/root"
12 12
 export USER="root"
13 13
 export LOGNAME="root"
@@ -62,4 +62,4 @@ fi
62 62
 logger -p local0.info -t "$LOG_TAG" "Started background job for $ACTION $UUID (PID: $!)"
63 63
 
64 64
 # Exit immediately so udev doesn't wait
65
-exit 0
65
+exit 0
+3 -228
scripts/uninstall.sh 1000644 → 1000755
@@ -1,231 +1,6 @@
1 1
 #!/bin/bash
2 2
 
3
-# AutoNAS Uninstall Script
4
-# This script is installed on each cluster node for version-specific removal
5
-# Executed by deploy.sh or can be run directly on node: /usr/local/lib/autonas/autonas-uninstall.sh
3
+set -euo pipefail
6 4
 
7
-# Global configuration
8
-LOG_TAG="autonas-uninstall"
9
-
10
-set -e  # Exit on any error
11
-
12
-# Check for force flag for silent cleanup
13
-FORCE_MODE=false
14
-if [ "$1" = "--force" ]; then
15
-    FORCE_MODE=true
16
-fi
17
-
18
-# Helper function for conditional logging
19
-log_message() {
20
-    if [ "$FORCE_MODE" = false ]; then
21
-        echo "$1"
22
-    fi
23
-}
24
-
25
-CONFIG_DIR="/etc/pve/autonas"
26
-SCRIPT_NAME="autonas-disk-handler.sh"
27
-WRAPPER_NAME="autonas-udev-wrapper.sh"
28
-CONFIG_SCRIPT="autonas-config.sh"
29
-BOOT_SCAN_SCRIPT="autonas-boot-scan.sh"
30
-CAMERA_IMPORT_SCRIPT="autonas-media-importer.sh"
31
-INTERFACE_HANDLER_SCRIPT="autonas-interface-handler.sh"
32
-UNIFIED_SCRIPT="autonas.sh"
33
-UDEV_RULES="99-autonas-disk.rules"
34
-SYSTEMD_SERVICE="autonas-attach@.service"
35
-MAIN_SERVICE="autonas.service"
36
-BOOT_SCAN_SERVICE="autonas-boot-scan.service"
37
-
38
-if [ "$FORCE_MODE" = false ]; then
39
-    echo "=== AutoNAS Uninstall Script ==="
40
-    echo ""
41
-fi
42
-
43
-# Check if running as root
44
-if [ "$EUID" -ne 0 ]; then
45
-    if [ "$FORCE_MODE" = false ]; then
46
-        echo "❌ This script must be run as root (use sudo)"
47
-    fi
48
-    exit 1
49
-fi
50
-
51
-log_message "🗑️  Uninstalling AutoNAS..."
52
-log_message ""
53
-
54
-# Remove scripts
55
-log_message "Removing AutoNAS scripts..."
56
-if [ -f "/usr/local/bin/$SCRIPT_NAME" ]; then
57
-    rm "/usr/local/bin/$SCRIPT_NAME"
58
-    log_message "✓ Manager script removed"
59
-fi
60
-
61
-if [ -f "/usr/local/bin/$WRAPPER_NAME" ]; then
62
-    rm "/usr/local/bin/$WRAPPER_NAME"
63
-    log_message "✓ Wrapper script removed"
64
-fi
65
-
66
-if [ -f "/usr/local/bin/$CONFIG_SCRIPT" ]; then
67
-    rm "/usr/local/bin/$CONFIG_SCRIPT"
68
-    log_message "✓ Configuration script removed"
69
-fi
70
-
71
-if [ -f "/usr/local/bin/$BOOT_SCAN_SCRIPT" ]; then
72
-    rm "/usr/local/bin/$BOOT_SCAN_SCRIPT"
73
-    log_message "✓ Boot scan script removed"
74
-fi
75
-
76
-if [ -f "/usr/local/bin/$CAMERA_IMPORT_SCRIPT" ]; then
77
-    rm "/usr/local/bin/$CAMERA_IMPORT_SCRIPT"
78
-    log_message "✓ Camera import script removed"
79
-fi
80
-
81
-if [ -f "/usr/local/bin/$INTERFACE_HANDLER_SCRIPT" ]; then
82
-    rm "/usr/local/bin/$INTERFACE_HANDLER_SCRIPT"
83
-    log_message "✓ Interface handler script removed"
84
-fi
85
-
86
-if [ -f "/usr/local/bin/$UNIFIED_SCRIPT" ]; then
87
-    rm "/usr/local/bin/$UNIFIED_SCRIPT"
88
-    log_message "✓ Unified AutoNAS script removed"
89
-fi
90
-
91
-# Remove symlink
92
-if [ -L "/usr/local/bin/autonas" ]; then
93
-    rm "/usr/local/bin/autonas"
94
-    log_message "✓ AutoNAS symlink removed"
95
-fi
96
-
97
-# Remove internal scripts directory
98
-if [ -d "/usr/local/lib/autonas" ]; then
99
-    rm -rf "/usr/local/lib/autonas"
100
-    log_message "✓ AutoNAS internal scripts directory removed"
101
-fi
102
-
103
-# Aggressive cleanup for orphaned files from previous versions
104
-log_message "Cleaning up orphaned files from previous versions..."
105
-for orphan_file in "autonas-interface-debug.sh" "autonas-test-interface-stability.sh"; do
106
-    if [ -f "/usr/local/bin/$orphan_file" ]; then
107
-        rm "/usr/local/bin/$orphan_file"
108
-        log_message "✓ Orphaned file removed: $orphan_file"
109
-    fi
110
-done
111
-
112
-# Remove udev rules
113
-log_message "Removing udev rules..."
114
-if [ -f "/etc/udev/rules.d/$UDEV_RULES" ]; then
115
-    rm "/etc/udev/rules.d/$UDEV_RULES"
116
-    udevadm control --reload-rules
117
-    log_message "✓ Udev rules removed"
118
-fi
119
-
120
-# Remove systemd service
121
-log_message "Removing systemd service..."
122
-if [ -f "/etc/systemd/system/$SYSTEMD_SERVICE" ]; then
123
-    systemctl stop autonas-attach@*.service 2>/dev/null || true
124
-    systemctl disable autonas-attach@*.service 2>/dev/null || true
125
-    rm "/etc/systemd/system/$SYSTEMD_SERVICE"
126
-    systemctl daemon-reload
127
-    log_message "✓ Systemd service removed"
128
-fi
129
-
130
-# Remove main service
131
-log_message "Removing main AutoNAS service..."
132
-if [ -f "/etc/systemd/system/$MAIN_SERVICE" ]; then
133
-    systemctl stop autonas.service 2>/dev/null || true
134
-    systemctl disable autonas.service 2>/dev/null || true
135
-    rm "/etc/systemd/system/$MAIN_SERVICE"
136
-    systemctl daemon-reload
137
-    log_message "✓ Main AutoNAS service removed"
138
-fi
139
-
140
-# Remove boot scan service
141
-log_message "Removing boot scan service..."
142
-if [ -f "/etc/systemd/system/$BOOT_SCAN_SERVICE" ]; then
143
-    systemctl stop autonas-boot-scan.service 2>/dev/null || true
144
-    systemctl disable autonas-boot-scan.service 2>/dev/null || true
145
-    rm "/etc/systemd/system/$BOOT_SCAN_SERVICE"
146
-    systemctl daemon-reload
147
-    log_message "✓ Boot scan service removed"
148
-fi
149
-
150
-# Handle configuration and data preservation
151
-log_message ""
152
-log_message "📁 Handling user data..."
153
-
154
-# Check if configuration exists and has user data
155
-if [ -f "$CONFIG_DIR/disks.conf" ]; then
156
-    if grep -v '^#' "$CONFIG_DIR/disks.conf" | grep -v '^$' | grep -q ':'; then
157
-        log_message "⚠️  User configurations detected in $CONFIG_DIR/disks.conf"
158
-        log_message "   Configuration file preserved for manual cleanup"
159
-        log_message "   Remove manually with: sudo rm -rf $CONFIG_DIR"
160
-    else
161
-        log_message "ℹ️  Configuration file contains only template - removing"
162
-        rm "$CONFIG_DIR/disks.conf"
163
-        rmdir "$CONFIG_DIR" 2>/dev/null || true
164
-    fi
165
-else
166
-    log_message "ℹ️  No configuration file found"
167
-    rmdir "$CONFIG_DIR" 2>/dev/null || true
168
-fi
169
-
170
-# Check for mounted AutoNAS disks
171
-log_message ""
172
-log_message "💾 Checking for mounted AutoNAS disks..."
173
-mounted_autonas=$(mount | grep "/mnt/autonas/" | wc -l)
174
-if [ $mounted_autonas -gt 0 ]; then
175
-    log_message "⚠️  Found $mounted_autonas mounted AutoNAS disk(s):"
176
-    if [ "$FORCE_MODE" = false ]; then
177
-        mount | grep "/mnt/autonas/" | while read line; do
178
-            echo "   $line"
179
-        done
180
-    fi
181
-    log_message ""
182
-    log_message "   These disks are still mounted and in use."
183
-    log_message "   Mount points preserved: /mnt/autonas/"
184
-    log_message "   Unmount manually if needed: sudo umount /mnt/autonas/*"
185
-else
186
-    log_message "ℹ️  No AutoNAS disks currently mounted"
187
-    if [ -d "/mnt/autonas" ]; then
188
-        # Only remove if empty
189
-        if [ -z "$(ls -A /mnt/autonas)" ]; then
190
-            rmdir "/mnt/autonas"
191
-            log_message "✓ Empty AutoNAS mount directory removed"
192
-        else
193
-            log_message "⚠️  AutoNAS mount directory contains files - preserved"
194
-        fi
195
-    fi
196
-fi
197
-
198
-# Check NFS exports
199
-log_message ""
200
-log_message "🌐 Checking NFS exports..."
201
-if [ -f "/etc/exports" ] && grep -q "/mnt/autonas/" "/etc/exports" 2>/dev/null; then
202
-    log_message "⚠️  AutoNAS NFS exports found in /etc/exports"
203
-    log_message "   Please manually remove AutoNAS entries from /etc/exports"
204
-    log_message "   And reload NFS exports: sudo exportfs -ra"
205
-else
206
-    log_message "ℹ️  No AutoNAS NFS exports found"
207
-fi
208
-
209
-log_message ""
210
-log_message "🎉 AutoNAS core components removed successfully!"
211
-
212
-if [ "$FORCE_MODE" = false ]; then
213
-    echo ""
214
-    echo "📋 Summary:"
215
-    echo "✓ Scripts removed from /usr/local/bin/"
216
-    echo "✓ Udev rules removed"
217
-    echo "✓ Systemd services removed"
218
-    echo ""
219
-    echo "⚠️  Preserved (manual cleanup required if desired):"
220
-    echo "• User configurations: $CONFIG_DIR/ (if contains user data)"
221
-    echo "• Mount points: /mnt/autonas/ (if contains data)"
222
-    echo "• NFS exports: /etc/exports (manual edit required)"
223
-    echo ""
224
-    echo "📝 To completely remove all AutoNAS data:"
225
-    echo "sudo rm -rf $CONFIG_DIR"
226
-    echo "sudo rm -rf /mnt/autonas"
227
-    echo "sudo nano /etc/exports  # Remove AutoNAS entries manually"
228
-    echo "sudo exportfs -ra"
229
-fi
230
-echo ""
231
-echo "🔄 To reinstall AutoNAS later, run: sudo bash scripts/install.sh"
5
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+exec bash "${SCRIPT_DIR}/autonas-uninstall.sh" "$@"
+141 -0
setup.sh
@@ -0,0 +1,141 @@
1
+#!/bin/bash
2
+
3
+set -euo pipefail
4
+
5
+PROJECT_ID="autonas"
6
+ORG_ID="xdev"
7
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+MODE="install"
9
+REMOTE_NODE=""
10
+REMOTE_USER="root"
11
+LOCAL_MODE=0
12
+
13
+show_help() {
14
+    cat <<EOF
15
+${PROJECT_ID} setup wrapper
16
+
17
+Usage: $0 [OPTIONS] [<target_node>]
18
+
19
+Options:
20
+  -h, --help           Show this help message
21
+  -l, --local          Run on localhost
22
+  -u, --uninstall      Uninstall instead of install
23
+  --user <user>        Remote SSH user (default: root)
24
+EOF
25
+}
26
+
27
+run_local_install() {
28
+    bash "${SCRIPT_DIR}/scripts/install.sh" install
29
+}
30
+
31
+run_local_uninstall() {
32
+    local canonical="/usr/local/lib/${ORG_ID}/${PROJECT_ID}/uninstall.sh"
33
+    if [[ -x "${canonical}" ]]; then
34
+        "${canonical}"
35
+    else
36
+        bash "${SCRIPT_DIR}/scripts/autonas-uninstall.sh"
37
+    fi
38
+}
39
+
40
+copy_remote_tree() {
41
+    local target="$1"
42
+    local remote_tmp="$2"
43
+
44
+    ssh "${target}" "rm -rf '${remote_tmp}' && mkdir -p '${remote_tmp}'"
45
+    scp -q -r \
46
+        "${SCRIPT_DIR}/config" \
47
+        "${SCRIPT_DIR}/scripts" \
48
+        "${SCRIPT_DIR}/README.md" \
49
+        "${SCRIPT_DIR}/DEVELOPMENT.md" \
50
+        "${SCRIPT_DIR}/CHANGELOG.md" \
51
+        "${SCRIPT_DIR}/INSTALL.md" \
52
+        "${target}:${remote_tmp}/"
53
+}
54
+
55
+run_remote_install() {
56
+    local target="$1"
57
+    local remote_tmp="/tmp/${PROJECT_ID}.$$"
58
+    local remote_prefix=""
59
+
60
+    [[ "${REMOTE_USER}" != "root" ]] && remote_prefix="sudo "
61
+
62
+    copy_remote_tree "${target}" "${remote_tmp}"
63
+    ssh "${target}" "${remote_prefix}bash '${remote_tmp}/scripts/install.sh' install"
64
+    ssh "${target}" "rm -rf '${remote_tmp}'"
65
+}
66
+
67
+run_remote_uninstall() {
68
+    local target="$1"
69
+    local remote_tmp="/tmp/${PROJECT_ID}-uninstall.$$"
70
+    local canonical="/usr/local/lib/${ORG_ID}/${PROJECT_ID}/uninstall.sh"
71
+
72
+    ssh "${target}" "rm -rf '${remote_tmp}' && mkdir -p '${remote_tmp}/scripts'"
73
+    scp -q "${SCRIPT_DIR}/scripts/autonas-uninstall.sh" "${target}:${remote_tmp}/scripts/"
74
+
75
+    if [[ "${REMOTE_USER}" == "root" ]]; then
76
+        ssh "${target}" "if [ -x '${canonical}' ]; then '${canonical}'; else bash '${remote_tmp}/scripts/autonas-uninstall.sh'; fi"
77
+    else
78
+        ssh "${target}" "sudo bash -lc \"if [ -x '${canonical}' ]; then '${canonical}'; else bash '${remote_tmp}/scripts/autonas-uninstall.sh'; fi\""
79
+    fi
80
+
81
+    ssh "${target}" "rm -rf '${remote_tmp}'"
82
+}
83
+
84
+while [[ $# -gt 0 ]]; do
85
+    case "$1" in
86
+        -h|--help)
87
+            show_help
88
+            exit 0
89
+            ;;
90
+        -l|--local)
91
+            LOCAL_MODE=1
92
+            shift
93
+            ;;
94
+        -u|--uninstall)
95
+            MODE="uninstall"
96
+            shift
97
+            ;;
98
+        --user)
99
+            REMOTE_USER="$2"
100
+            shift 2
101
+            ;;
102
+        -*)
103
+            echo "ERROR: unknown option: $1" >&2
104
+            show_help
105
+            exit 1
106
+            ;;
107
+        *)
108
+            REMOTE_NODE="$1"
109
+            shift
110
+            ;;
111
+    esac
112
+done
113
+
114
+if [[ -z "${REMOTE_NODE}" && ${LOCAL_MODE} -eq 0 ]]; then
115
+    LOCAL_MODE=1
116
+fi
117
+
118
+echo "================================"
119
+echo "${PROJECT_ID} - ${MODE}"
120
+echo "================================"
121
+
122
+if [[ ${LOCAL_MODE} -eq 1 ]]; then
123
+    if [[ "${MODE}" == "install" ]]; then
124
+        run_local_install
125
+    else
126
+        run_local_uninstall
127
+    fi
128
+    exit 0
129
+fi
130
+
131
+TARGET="${REMOTE_USER}@${REMOTE_NODE}"
132
+if ! ping -c 1 "${REMOTE_NODE}" >/dev/null 2>&1; then
133
+    echo "ERROR: cannot reach ${REMOTE_NODE}" >&2
134
+    exit 1
135
+fi
136
+
137
+if [[ "${MODE}" == "install" ]]; then
138
+    run_remote_install "${TARGET}"
139
+else
140
+    run_remote_uninstall "${TARGET}"
141
+fi