first commit

This commit is contained in:
Fabio 2026-03-26 16:52:41 +01:00
commit 13e4c9d5d2
7 changed files with 449 additions and 0 deletions

1
.env Normal file
View file

@ -0,0 +1 @@
BASE_URL=https://prova.patachina.it

21
Dockerfile Normal file
View file

@ -0,0 +1,21 @@
FROM node:22-slim
WORKDIR /app
RUN apt-get update && apt-get install -y inotify-tools jq
COPY watcher_logic.mjs /app/watcher_logic.mjs
COPY start.sh /app/start.sh
RUN chmod +x /app/start.sh
# Installiamo dotenv
RUN npm install dotenv
ENV NODE_ENV=production
CMD ["/app/start.sh"]

11
README.md Normal file
View file

@ -0,0 +1,11 @@
# Watcher per galleria
fare il setup su docker-compose.yml
lanciare con
```
sudo docker compose up --build
```
è settata per la porta 4002 e in nginx ws-prova.patachina.it

19
docker-compose.yml Normal file
View file

@ -0,0 +1,19 @@
version: "3.9"
services:
watcher:
build: .
container_name: watcher_multi
env_file:
- .env
volumes:
- /home/nvme/dockerdata/prove/s16j/public/photos:/app/photos
- /home/nvme/dockerdata/prove/s16j/api_v1:/app/config
restart: always
networks:
- fabio
networks:
fabio:
external: true

37
start.sh Executable file
View file

@ -0,0 +1,37 @@
#!/bin/sh
echo "== Avvio watcher multipli =="
CONFIG_FILE="/app/config/users.json"
PHOTOS_DIR="/app/photos"
if [ ! -f "$CONFIG_FILE" ]; then
echo "ERRORE: $CONFIG_FILE non trovato"
exit 1
fi
USERS=$(jq -r '.users[].name' "$CONFIG_FILE")
for USER in $USERS; do
# 🔥 Regola speciale per Admin
if [ "$USER" = "Admin" ]; then
WATCH_DIR="$PHOTOS_DIR/Common/original"
else
WATCH_DIR="$PHOTOS_DIR/$USER/original"
fi
if [ ! -d "$WATCH_DIR" ]; then
echo "Cartella non trovata per $USER: $WATCH_DIR"
continue
fi
echo "Avvio watcher per $USER$WATCH_DIR"
sh -c "inotifywait -m -r -e close_write,delete,move --format '%w %e %f' \
\"$WATCH_DIR\" | node /app/watcher_logic.mjs \"$USER\"" &
done
echo "Tutti i watcher avviati."
tail -f /dev/null

181
watcher_logic.mjs Normal file
View file

@ -0,0 +1,181 @@
import readline from "readline";
import fs from "fs";
import dotenv from "dotenv";
dotenv.config();
const user = process.argv[2];
// ===============================
// BASE_URL DAL .env
// ===============================
const BASE_URL = process.env.BASE_URL;
if (!BASE_URL) {
console.error("ERRORE: BASE_URL non definita in .env");
process.exit(1);
}
// ===============================
// CREDENZIALI ADMIN
// ===============================
const adminSecret = JSON.parse(fs.readFileSync("/app/config/admin_secret.json", "utf8"));
let adminToken = null;
let isRefreshing = false;
// ===============================
// LOGIN ADMIN (solo Admin lo usa)
// ===============================
async function loginAdmin() {
while (true) {
try {
const res = await fetch(`${BASE_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: adminSecret.email,
password: adminSecret.password
})
});
if (!res.ok) {
const now = new Date().toISOString();
console.error(`[${now}] [Admin] Login fallito (${res.status}). Riprovo tra 20 secondi...`);
await new Promise(r => setTimeout(r, 20000));
continue;
}
const data = await res.json();
adminToken = data.token;
const now = new Date().toISOString();
console.log(`[${now}] Watcher autenticato come Admin`);
return;
} catch (err) {
const now = new Date().toISOString();
console.error(`[${now}] [Admin] Connessione al server fallita. Riprovo tra 20 secondi...`);
await new Promise(r => setTimeout(r, 20000));
}
}
}
// ===============================
// GARANTISCE TOKEN VALIDO (solo Admin)
// ===============================
async function ensureAdminToken() {
if (adminToken) return;
if (isRefreshing) {
return new Promise(resolve => {
const interval = setInterval(() => {
if (!isRefreshing) {
clearInterval(interval);
resolve();
}
}, 100);
});
}
isRefreshing = true;
await loginAdmin();
isRefreshing = false;
}
// ===============================
// CHIAMATA A /auto_scan
// ===============================
async function callAutoScan(type, file, path, user) {
// Admin ottiene il token
// Gli altri utenti aspettano che Admin lo abbia
await ensureAdminToken();
const headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + adminToken // 🔥 tutti usano il token di Admin
};
path = path.replace(/^\/app/, "");
const res = await fetch(`${BASE_URL}/api_v1/auto_scan`, {
method: "POST",
headers,
body: JSON.stringify({ type, file, path, user })
});
// Se il token scade, Admin lo rinnova
if (res.status === 401) {
console.log(`[${new Date().toISOString()}] Token scaduto, rinnovo…`);
adminToken = null;
return callAutoScan(type, file, path, user);
}
return res.ok;
}
// ===============================
// ESTENSIONI MEDIA
// ===============================
const photoExt = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".heic", ".heif"];
const videoExt = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
function isMediaFile(filename) {
const lower = filename.toLowerCase();
return photoExt.some(ext => lower.endsWith(ext)) ||
videoExt.some(ext => lower.endsWith(ext));
}
console.log(`Node attivo per utente: ${user}`);
const rl = readline.createInterface({
input: process.stdin,
crlfDelay: Infinity
});
// ===============================
// LOGICA EVENTI
// ===============================
function startWatcher() {
rl.on("line", line => {
const parts = line.trim().split(/\s+/);
const path = parts[0];
const action = parts[1];
const file = parts[2];
if (!file) return;
const isDir = action.includes("ISDIR");
if (!isDir && !isMediaFile(file)) {
console.log(`Ignorato (non media): ${file}`);
return;
}
let type = null;
if (action === "MOVED_TO,ISDIR") type = "ADD_DIR";
else if (action === "MOVED_FROM,ISDIR") type = "DEL_DIR";
else if (/^CLOSE_WRITE(,CLOSE)?$/.test(action)) type = "ADD";
else if (action === "MOVED_TO") type = "ADD";
else if (action === "MOVED_FROM") type = "DEL";
else if (action === "DELETE") type = "DEL";
else return;
console.log(`${type} ${file} ${path} ${user}`);
callAutoScan(type, file, path, user);
});
}
// ===============================
// AVVIO: SOLO ADMIN FA LOGIN
// ===============================
if (user === "Admin") {
loginAdmin().then(() => {
startWatcher();
});
} else {
console.log(`Watcher per ${user} avviato senza login`);
startWatcher();
}

179
watcher_logic.mjs.ok Normal file
View file

@ -0,0 +1,179 @@
import readline from "readline";
import fs from "fs";
import dotenv from "dotenv";
dotenv.config();
const user = process.argv[2];
// ===============================
// BASE_URL DAL .env
// ===============================
const BASE_URL = process.env.BASE_URL;
if (!BASE_URL) {
console.error("ERRORE: BASE_URL non definita in .env");
process.exit(1);
}
// ===============================
// CREDENZIALI ADMIN
// ===============================
const adminSecret = JSON.parse(fs.readFileSync("/app/config/admin_secret.json", "utf8"));
let adminToken = null;
let isRefreshing = false;
// ===============================
// LOGIN ADMIN (solo Admin lo usa)
// ===============================
async function loginAdmin() {
while (true) {
try {
const res = await fetch(`${BASE_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: adminSecret.email,
password: adminSecret.password
})
});
if (!res.ok) {
const now = new Date().toISOString();
console.error(`[${now}] [Admin] Login fallito (${res.status}). Riprovo tra 20 secondi...`);
await new Promise(r => setTimeout(r, 20000));
continue;
}
const data = await res.json();
adminToken = data.token;
const now = new Date().toISOString();
console.log(`[${now}] Watcher autenticato come Admin`);
return;
} catch (err) {
const now = new Date().toISOString();
console.error(`[${now}] [Admin] Connessione al server fallita. Riprovo tra 20 secondi...`);
await new Promise(r => setTimeout(r, 20000));
}
}
}
// ===============================
// GARANTISCE TOKEN VALIDO (solo Admin)
// ===============================
async function ensureAdminToken() {
if (adminToken) return;
if (isRefreshing) {
return new Promise(resolve => {
const interval = setInterval(() => {
if (!isRefreshing) {
clearInterval(interval);
resolve();
}
}, 100);
});
}
isRefreshing = true;
await loginAdmin();
isRefreshing = false;
}
// ===============================
// CHIAMATA A /auto_scan
// ===============================
async function callAutoScan(type, file, path, user) {
// Admin ottiene il token
// Gli altri utenti aspettano che Admin lo abbia
await ensureAdminToken();
const headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + adminToken // 🔥 tutti usano il token di Admin
};
const res = await fetch(`${BASE_URL}/api_v1/auto_scan`, {
method: "POST",
headers,
body: JSON.stringify({ type, file, path, user })
});
// Se il token scade, Admin lo rinnova
if (res.status === 401) {
console.log(`[${new Date().toISOString()}] Token scaduto, rinnovo…`);
adminToken = null;
return callAutoScan(type, file, path, user);
}
return res.ok;
}
// ===============================
// ESTENSIONI MEDIA
// ===============================
const photoExt = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".heic", ".heif"];
const videoExt = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
function isMediaFile(filename) {
const lower = filename.toLowerCase();
return photoExt.some(ext => lower.endsWith(ext)) ||
videoExt.some(ext => lower.endsWith(ext));
}
console.log(`Node attivo per utente: ${user}`);
const rl = readline.createInterface({
input: process.stdin,
crlfDelay: Infinity
});
// ===============================
// LOGICA EVENTI
// ===============================
function startWatcher() {
rl.on("line", line => {
const parts = line.trim().split(/\s+/);
const path = parts[0];
const action = parts[1];
const file = parts[2];
if (!file) return;
const isDir = action.includes("ISDIR");
if (!isDir && !isMediaFile(file)) {
console.log(`Ignorato (non media): ${file}`);
return;
}
let type = null;
if (action === "MOVED_TO,ISDIR") type = "ADD_DIR";
else if (action === "MOVED_FROM,ISDIR") type = "DEL_DIR";
else if (/^CLOSE_WRITE(,CLOSE)?$/.test(action)) type = "ADD";
else if (action === "MOVED_TO") type = "ADD";
else if (action === "MOVED_FROM") type = "DEL";
else if (action === "DELETE") type = "DEL";
else return;
console.log(`${type} ${file} ${path} ${user}`);
callAutoScan(type, file, path, user);
});
}
// ===============================
// AVVIO: SOLO ADMIN FA LOGIN
// ===============================
if (user === "Admin") {
loginAdmin().then(() => {
startWatcher();
});
} else {
console.log(`Watcher per ${user} avviato senza login`);
startWatcher();
}