Compare commits

...

5 Commits

10 changed files with 181 additions and 274 deletions

2
.env Normal file
View File

@@ -0,0 +1,2 @@
NEXTJS_WEBHOOK_URL=https://sizin-siteniz.com/api/webhooks/mail-signal
WEBHOOK_SECRET=buraya_guclu_bir_sifre_yazin

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
NEXTJS_WEBHOOK_URL=https://your-domain.com/api/webhooks/mail-signal
WEBHOOK_SECRET=your_strong_secret_here

View File

@@ -1,16 +1,18 @@
FROM node:20-alpine # Hafif bir Node.js imajı kullanalım
FROM node:20-slim
# Install docker cli to be able to run 'docker logs' from within the container # Docker CLI'ı içine kuruyoruz (Çünkü Worker, Dovecot konteynerine komut gönderecek)
# Note: You must mount /var/run/docker.sock when running this container RUN apt-get update && apt-get install -y docker.io && rm -rf /var/lib/apt/lists/*
RUN apk add --no-cache docker-cli
# Çalışma dizini
WORKDIR /app WORKDIR /app
# Paket listelerini kopyala ve bağımlılıkları kur
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install --production
# Tüm kodları ve .env dosyasını kopyala
COPY . . COPY . .
RUN npm run build # Uygulamayı başlat
CMD ["node", "worker.js"]
CMD ["npm", "start"]

View File

@@ -1,14 +1,83 @@
# AyrisTech Worker # AyrisTech Mail Worker (V10.3)
This worker listens to Mailcow Dovecot logs and publishes incoming mail events to Redis. Mailcow Dovecot loglarını ve dosya sistemini izleyerek yeni gelen mailleri Next.js webhook'una ileten servis.
## Features ## Kurulum
- Real-time mail event tracking
- Redis Pub/Sub integration
- Dockerized deployment
## Setup ### 1. Dosyaları Hazırlayın
1. Clone the repository Proje dizinine gidin ve `.env` dosyasını düzenleyin:
2. Install dependencies: `npm install` ```env
3. Configure `.env` file NEXTJS_WEBHOOK_URL=https://sizin-siteniz.com/api/webhooks/mail-signal
4. Run the worker: `npm start` WEBHOOK_SECRET=buraya_guclu_bir_sifre_yazin
```
### 2. Yerel Çalıştırma
Node.js (v18+) yüklü olduğundan emin olun:
```bash
npm install
npm start
```
### 3. Docker ile Çalıştırma
En güvenli ve kolay yöntem Docker konteyneri olarak çalıştırmaktır:
```bash
# Resmi oluşturun
docker build -t ayristech-worker .
# Konteyneri başlatın
docker run -d \
--name ayristech-worker \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/:/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/ \
--env-file .env \
--restart unless-stopped \
ayristech-worker
```
## Önemli Notlar
- **Docker Socket**: Servis `docker exec` komutu kullandığı için `/var/run/docker.sock` mount edilmelidir.
- **Vmail Volümü**: Mailcow'un mail dosyalarını sakladığı volüm `/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/` yoluna mount edilmelidir.
---
# AyrisTech Mail Worker (V10.3) - English
Service that monitors Mailcow Dovecot logs and filesystem to forward new incoming emails to a Next.js webhook.
## Installation
### 1. Prepare Files
Go to the project directory and edit the `.env` file (refer to `.env.example`):
```env
NEXTJS_WEBHOOK_URL=https://your-domain.com/api/webhooks/mail-signal
WEBHOOK_SECRET=your_strong_secret_here
```
### 2. Run Locally
Ensure Node.js (v18+) is installed:
```bash
npm install
npm start
```
### 3. Run with Docker
The most secure and easy method is to run as a Docker container:
```bash
# Build the image
docker build -t ayristech-worker .
# Start the container
docker run -d \
--name ayristech-worker \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/:/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/ \
--env-file .env \
--restart unless-stopped \
ayristech-worker
```
## Important Notes
- **Docker Socket**: The service uses the `docker exec` command, so `/var/run/docker.sock` must be mounted.
- **Vmail Volume**: The volume where Mailcow stores email files must be mounted to the path `/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/`.

View File

@@ -1,35 +0,0 @@
import { Redis } from 'ioredis';
import { spawn } from 'child_process';
import dotenv from 'dotenv';
dotenv.config();
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
const CONTAINER_NAME = process.env.MAILCOW_CONTAINER_NAME || 'mailcowdockerized-dovecot-mailcow-1';
const redis = new Redis(REDIS_URL);
console.log("🚀 AyrisTech Worker Bağlanıyor...");
// Dovecot loglarını dinliyoruz
const logProcess = spawn('docker', ['logs', '-f', CONTAINER_NAME]);
logProcess.stdout.on('data', async (data) => {
const line = data.toString();
// Mailcow'un mail teslimat logunu yakala
if (line.includes('saved mail to INBOX')) {
const match = line.match(/user=<([^>]+)>/);
if (match) {
const email = match[1];
console.log(`📩 Mail Geldi: ${email}`);
// Olayı Redis'e fırlat (Pub/Sub)
// Bu sayede Next.js veya diğer servisler bu haberi alabilir
await redis.publish('NEW_MAIL_EVENT', JSON.stringify({
to: email,
time: new Date().toISOString()
}));
}
}
});

14
docker-compose.yml Normal file
View File

@@ -0,0 +1,14 @@
version: '3.8'
services:
ayristech-worker:
build: .
container_name: ayristech-worker
restart: always
volumes:
# Mail dosyalarını okumak için vmail volume'unu bağlıyoruz
- /var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data:/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data:ro
# Docker içinde docker komutu çalıştırabilmek için socket'i bağlıyoruz
- /var/run/docker.sock:/var/run/docker.sock
env_file:
- .env

161
package-lock.json generated
View File

@@ -1,161 +0,0 @@
{
"name": "ayristech-worker",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ayristech-worker",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"ioredis": "^5.10.1"
},
"devDependencies": {
"@types/node": "^25.7.0",
"typescript": "^6.0.3"
}
},
"node_modules/@ioredis/commands": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
"integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.7.0.tgz",
"integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.21.0"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/ioredis": {
"version": "5.10.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
"integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "1.5.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz",
"integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==",
"dev": true,
"license": "MIT"
}
}
}

View File

@@ -1,25 +1,16 @@
{ {
"name": "ayristech-worker", "name": "ayristech-worker",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "AyrisTech Mailcow Worker",
"type": "module", "main": "worker.js",
"main": "dist/apps/worker/main.js",
"scripts": { "scripts": {
"build": "tsc", "start": "node worker.js",
"start": "node dist/apps/worker/main.js", "dev": "node --watch worker.js"
"dev": "tsx apps/worker/main.ts",
"test": "echo \"Error: no test specified\" && exit 1"
}, },
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": { "dependencies": {
"dotenv": "^16.4.7", "axios": "^1.16.1",
"chokidar": "^5.0.0",
"dotenv": "^17.4.2",
"ioredis": "^5.10.1" "ioredis": "^5.10.1"
},
"devDependencies": {
"@types/node": "^25.7.0",
"tsx": "^4.19.2",
"typescript": "^6.0.3"
} }
} }

View File

@@ -1,42 +0,0 @@
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
"rootDir": "./",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "nodenext",
"target": "esnext",
"lib": ["esnext"],
"types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
}
}

65
worker.js Normal file
View File

@@ -0,0 +1,65 @@
require('dotenv').config();
const chokidar = require('chokidar');
const axios = require('axios');
const { exec } = require('child_process');
const NEXTJS_WEBHOOK_URL = process.env.NEXTJS_WEBHOOK_URL;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const vmailPath = '/var/lib/docker/volumes/mailcowdockerized_vmail-vol-1/_data/';
console.log("🚀 AyrisTech Güvenli Dekoder (V10.3) Başlatıldı...");
const watcher = chokidar.watch(vmailPath, {
ignored: /(^|[\/\\])\../,
persistent: true,
ignoreInitial: true,
depth: 5
});
watcher.on('add', (filePath) => {
if (filePath.includes('/new/')) {
const parts = filePath.split('/');
const fullEmail = `${parts[8]}@${parts[7]}`;
console.log(`📩 Yeni şifreli mail: ${fullEmail}. Çözülüyor...`);
// Komutu daha garantici bir hale getirdik
const cmd = `docker exec mailcowdockerized-dovecot-mailcow-1 doveadm fetch -u "${fullEmail}" "hdr.from hdr.subject body.snippet" ALL`;
setTimeout(() => {
exec(cmd, async (error, stdout, stderr) => {
if (error || stderr) {
console.error("❌ Doveadm Hatası:", error ? error.message : stderr);
return;
}
if (!stdout.trim()) {
console.log("⚠️ Mail içeriği henüz hazır değil (Stdout boş).");
return;
}
let mailData = { to: fullEmail, from: 'Bilinmiyor', subject: 'Konu Yok', snippet: '' };
// Satır satır parse etme
const lines = stdout.split('\n');
lines.forEach(line => {
const l = line.trim();
if (l.toLowerCase().startsWith('hdr.from:')) mailData.from = l.split(':').slice(1).join(':').trim();
if (l.toLowerCase().startsWith('hdr.subject:')) mailData.subject = l.split(':').slice(1).join(':').trim();
if (l.toLowerCase().startsWith('body.snippet:')) mailData.snippet = l.split(':').slice(1).join(':').trim();
});
console.log(`✨ İçerik Çözüldü: ${mailData.subject}`);
try {
await axios.post(NEXTJS_WEBHOOK_URL, mailData, {
headers: { 'x-ayristech-secret': WEBHOOK_SECRET }
});
console.log(`✅ Webhook Next.js'e başarıyla ulaştı.`);
} catch (err) {
console.error("❌ Webhook Hatası:", err.message);
}
});
}, 2500); // Mailin DB'ye tam işlenmesi için süreyi biraz artırdık
}
});