feat: Prepare Zemailnator for Dokploy deployment

- Add highly optimized Dockerfile with Nginx and PHP-FPM 8.4
- Add docker-compose.yml configured with Redis and MariaDB 10.11
- Implement entrypoint.sh and supervisord.conf for background workers
- Refactor legacy IMAP scripts into scheduled Artisan Commands
- Secure app by removing old routes with hardcoded basic auth credentials
- Configure email attachments to use Laravel Storage instead of insecure public/tmp
This commit is contained in:
idevakk
2026-02-28 23:17:39 +05:30
parent bf5b797cd8
commit c312ec3325
78 changed files with 750 additions and 360 deletions

View File

@@ -2,18 +2,17 @@
namespace App\Models;
use DateTimeImmutable;
use Illuminate\Support\Facades\Date;
use App\ColorPicker;
use Carbon\CarbonImmutable;
use DateTime;
use DateTimeImmutable;
use Ddeboer\Imap\ConnectionInterface;
use Ddeboer\Imap\Search\Date\Since;
use Ddeboer\Imap\Server;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
@@ -78,7 +77,7 @@ class Email extends Model
$sender = $message->getFrom();
$date = $message->getDate();
if (!$date instanceof DateTimeImmutable) {
if (! $date instanceof DateTimeImmutable) {
$date = new DateTime;
if ($message->getHeaders()->get('udate')) {
$date->setTimestamp($message->getHeaders()->get('udate'));
@@ -100,11 +99,11 @@ class Email extends Model
$obj = [];
$to = $message->getHeaders()->get('To') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('To')) : [];
$to = $message->getHeaders()->get('To') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('To')) : [];
$cc = $message->getHeaders()->get('Cc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Cc')) : [];
$cc = $message->getHeaders()->get('Cc') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Cc')) : [];
$bcc = $message->getHeaders()->get('Bcc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Bcc')) : [];
$bcc = $message->getHeaders()->get('Bcc') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Bcc')) : [];
$messageTime = $message->getDate();
$utcTime = CarbonImmutable::instance($messageTime)->setTimezone('UTC')->toDateTimeString();
@@ -127,28 +126,27 @@ class Email extends Model
if ($message->hasAttachments()) {
$attachments = $message->getAttachments();
$directory = './tmp/attachments/'.$obj['id'].'/';
$directoryPath = 'attachments/'.$obj['id'];
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
foreach ($attachments as $attachment) {
$filenameArray = explode('.', (string) $attachment->getFilename());
$extension = $filenameArray[count($filenameArray) - 1];
if (in_array($extension, $allowed)) {
if (! file_exists($directory.$attachment->getFilename())) {
$filePath = $directoryPath.'/'.$attachment->getFilename();
if (! \Illuminate\Support\Facades\Storage::disk('public')->exists($filePath)) {
try {
file_put_contents(
$directory.$attachment->getFilename(),
\Illuminate\Support\Facades\Storage::disk('public')->put(
$filePath,
$attachment->getDecodedContent()
);
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
if ($attachment->getFilename() !== 'undefined') {
$url = config('app.settings.app_base_url').str_replace('./', '/', $directory.$attachment->getFilename());
$url = config('app.settings.app_base_url').'/storage/'.$filePath;
$structure = $attachment->getStructure();
if (isset($structure->id) && str_contains($obj['content'], trim($structure->id, '<>'))) {
$obj['content'] = str_replace('cid:'.trim($structure->id, '<>'), $url, $obj['content']);
@@ -159,9 +157,7 @@ class Email extends Model
];
}
}
}
}
$response['data'][] = $obj;
@@ -354,11 +350,9 @@ class Email extends Model
public static function deleteBulkAttachments(): void
{
$dir = public_path('/tmp/attachments');
try {
if (File::exists($dir)) {
File::cleanDirectory($dir);
if (\Illuminate\Support\Facades\Storage::disk('public')->exists('attachments')) {
\Illuminate\Support\Facades\Storage::disk('public')->deleteDirectory('attachments');
}
} catch (Exception $e) {
Log::error($e->getMessage());
@@ -431,6 +425,7 @@ class Email extends Model
$currentTime = Date::now('UTC');
$lastRecordTime = Date::parse($latestRecord->timestamp);
return $lastRecordTime->diffInMinutes($currentTime) < 5;
}

View File

@@ -2,9 +2,9 @@
namespace App\Models;
use Illuminate\Support\Facades\Date;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Date;
class Log extends Model
{

View File

@@ -2,10 +2,10 @@
namespace App\Models;
use DateTimeImmutable;
use App\ColorPicker;
use Carbon\Carbon;
use DateTime;
use DateTimeImmutable;
use Ddeboer\Imap\Search\Email\Cc;
use Ddeboer\Imap\Search\Email\To;
use Ddeboer\Imap\SearchExpression;
@@ -41,7 +41,7 @@ class Message extends Model
$message->attachments = $request->get('attachment-info');
$message->save();
$directory = './attachments/'.$message->id;
if (!is_dir($directory)) {
if (! is_dir($directory)) {
mkdir($directory, 0777, true);
}
$attachment_ids = json_decode((string) $request->get('attachment-info'));
@@ -146,7 +146,7 @@ class Message extends Model
$blocked = false;
$sender = $message->getFrom();
$date = $message->getDate();
if (!$date instanceof DateTimeImmutable) {
if (! $date instanceof DateTimeImmutable) {
$date = new DateTime;
if ($message->getHeaders()->get('udate')) {
$date->setTimestamp($message->getHeaders()->get('udate'));
@@ -193,7 +193,7 @@ class Message extends Model
if ($message->hasAttachments() && ! $blocked) {
$attachments = $message->getAttachments();
$directory = './tmp/attachments/'.$obj['id'].'/';
if (!is_dir($directory)) {
if (! is_dir($directory)) {
mkdir($directory, 0777, true);
}
foreach ($attachments as $attachment) {

View File

@@ -2,11 +2,11 @@
namespace App\Models;
use Illuminate\Support\Facades\Date;
use App\ColorPicker;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Validator;
class PremiumEmail extends Model

View File

@@ -2,9 +2,9 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Ddeboer\Imap\ConnectionInterface;
use Ddeboer\Imap\Server;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cookie;
@@ -14,6 +14,7 @@ class ZEmail extends Model
{
use HasFactory;
use HasFactory;
public static function connectMailBox($imap = null): ConnectionInterface
{
if ($imap === null) {
@@ -38,6 +39,7 @@ class ZEmail extends Model
if (Email::mailToDBStatus()) {
return Email::parseEmail($email, $deleted);
}
return Message::fetchMessages($email, $type, $deleted);
}
@@ -57,6 +59,7 @@ class ZEmail extends Model
if (Cookie::has('email')) {
return Cookie::get('email');
}
return $generate ? ZEmail::generateRandomEmail() : null;
}
@@ -65,6 +68,7 @@ class ZEmail extends Model
if (Cookie::has('emails')) {
return unserialize(Cookie::get('emails'));
}
return [];
}