Compare commits
13 Commits
c312ec3325
...
dokploy-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42e2f1bf0a | ||
|
|
2989613b75 | ||
|
|
285b995afb | ||
|
|
0d9a524267 | ||
|
|
b30da6614a | ||
|
|
0aed57bc2c | ||
|
|
6d24d1f6fc | ||
|
|
189c2d7c58 | ||
|
|
7f58ca9f45 | ||
|
|
66b5d2f89e | ||
|
|
a04222dcf3 | ||
|
|
58aa4d2dbc | ||
|
|
e342c2bdae |
69
Dockerfile
Normal file
69
Dockerfile
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# 1. Use the specific PHP 8.4 Alpine image
|
||||||
|
FROM webdevops/php-nginx:8.4-alpine
|
||||||
|
|
||||||
|
# 2. Environment Configuration
|
||||||
|
ENV WEB_DOCUMENT_ROOT=/code/public
|
||||||
|
ENV APP_ENV=production
|
||||||
|
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||||
|
ENV COMPOSER_PROCESS_TIMEOUT=2000
|
||||||
|
|
||||||
|
# 3. Install System Dependencies & Extensions
|
||||||
|
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
|
sqlite \
|
||||||
|
shadow \
|
||||||
|
&& install-php-extensions pdo_sqlite mongodb redis imap pdo_mysql
|
||||||
|
|
||||||
|
# 4. Copy Custom Configs
|
||||||
|
COPY ./dockerizer/php.ini /opt/docker/etc/php/php.ini
|
||||||
|
COPY ./dockerizer/vhost.conf /opt/docker/etc/nginx/vhost.conf
|
||||||
|
COPY ./dockerizer/supervisor.laravel.conf /opt/docker/etc/supervisor.d/laravel.conf
|
||||||
|
|
||||||
|
# 5. Set Working Directory
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
# CRITICAL FIX: Transfer ownership of /code to the 'application' user
|
||||||
|
# WORKDIR creates folders as 'root' by default, which blocks Composer later.
|
||||||
|
RUN chown application:application /code
|
||||||
|
|
||||||
|
# 6. Install PHP Dependencies (Layered)
|
||||||
|
COPY --chown=application:application composer.json composer.lock ./
|
||||||
|
|
||||||
|
USER application
|
||||||
|
RUN composer install --no-interaction --no-scripts --no-autoloader --prefer-dist --no-dev
|
||||||
|
|
||||||
|
# 7. Install Node Dependencies
|
||||||
|
COPY --chown=application:application package.json package-lock.json* ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# 8. Copy Application Code
|
||||||
|
COPY --chown=application:application . .
|
||||||
|
|
||||||
|
# 9. Final Builds
|
||||||
|
RUN composer dump-autoload --optimize && \
|
||||||
|
composer run-script post-root-package-install && \
|
||||||
|
php artisan package:discover --ansi
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# 10. Runtime Initialization Script
|
||||||
|
# This runs when the container STARTS, not when it builds.
|
||||||
|
USER root
|
||||||
|
|
||||||
|
RUN echo '#!/bin/bash' > /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
echo 'set -e' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
# 1. Fix Permissions \
|
||||||
|
echo 'echo "Fixing storage permissions..."' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
echo 'mkdir -p /code/storage/framework/{views,cache,sessions} /code/bootstrap/cache' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
echo 'touch /code/database/database.sqlite' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
echo 'chown -R application:application /code/storage /code/bootstrap/cache /code/database' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
echo 'chmod -R 775 /code/storage /code/bootstrap/cache /code/database' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
# 2. Run Optimization (As the application user) \
|
||||||
|
echo 'echo "Running Laravel Optimizations..."' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
echo 'su -s /bin/sh application -c "php artisan optimize"' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
# 3. Cache Views (Optional but recommended for production) \
|
||||||
|
echo 'su -s /bin/sh application -c "php artisan view:cache"' >> /opt/docker/provision/entrypoint.d/99-init-laravel.sh && \
|
||||||
|
chmod +x /opt/docker/provision/entrypoint.d/99-init-laravel.sh
|
||||||
37
Project.md
37
Project.md
@@ -11,8 +11,13 @@ Zemailnator is a scalable disposable temporary email service (like Mailinator or
|
|||||||
|
|
||||||
**Core Mechanics:**
|
**Core Mechanics:**
|
||||||
- The platform uses IMAP (`ddeboer/imap` via PHP `ext-imap`) to fetch emails from a master mailbox.
|
- The platform uses IMAP (`ddeboer/imap` via PHP `ext-imap`) to fetch emails from a master mailbox.
|
||||||
- `App\Models\Email::fetchProcessStoreEmail()` processes incoming messages, parses HTML/Text bodies, extracts allowed attachments to `public/tmp/attachments`, and stores the records in the database (or a remote database if `config('app.fetch_from_remote_db')` is true).
|
- `App\Models\Email::fetchProcessStoreEmail()` processes incoming messages, parses HTML/Text bodies, extracts allowed attachments to the `public` Laravel Storage disk, and stores the records in the database.
|
||||||
- External cron/scripts (like `dropmail.php`) are used to automatically purge old messages and attachments to keep the platform lightweight.
|
- Background cleanup and email fetching are handled automatically via Supervisor running Laravel Scheduled Commands (`emails:fetch`, `mailbox:clean`, `attachments:clean`).
|
||||||
|
|
||||||
|
## 1.1 Deployment Architecture (Dokploy)
|
||||||
|
Zemailnator is containerized for zero-downtime deployment on Dokploy:
|
||||||
|
- **Application Container:** Uses `php:8.4-fpm-alpine` configured via a single `Dockerfile` with a bundled Nginx server and Supervisor to manage background queues.
|
||||||
|
- **External Services:** MariaDB and Redis run as fully separate, standalone Dokploy application instances to allow for independent backups and to prevent database restarts during application deployment.
|
||||||
|
|
||||||
## 2. Technology Stack
|
## 2. Technology Stack
|
||||||
- **Framework:** Laravel 12.x
|
- **Framework:** Laravel 12.x
|
||||||
@@ -29,27 +34,27 @@ Zemailnator is a scalable disposable temporary email service (like Mailinator or
|
|||||||
- **Admin Dashboard:** Fully managed via Filament, including logs functionality, failed jobs monitoring, and dynamic database configuration.
|
- **Admin Dashboard:** Fully managed via Filament, including logs functionality, failed jobs monitoring, and dynamic database configuration.
|
||||||
- **Impersonation:** Admins can impersonate users for troubleshooting (`ImpersonationController`).
|
- **Impersonation:** Admins can impersonate users for troubleshooting (`ImpersonationController`).
|
||||||
|
|
||||||
## 4. Known Security Flaws & Vulnerabilities (CRITICAL)
|
## 4. Security & Technical Debt Improvements (Resolved)
|
||||||
When working on this codebase, prioritize addressing or being mindful of the following security issues:
|
The following critical flaws have been successfully addressed:
|
||||||
1. **Hardcoded Credentials in Routes:**
|
1. **Hardcoded Credentials in Routes:**
|
||||||
- Found in `routes/web.php` for endpoints `/0xdash/slink` and `/0xdash/scache`. They use basic HTTP authentication with a hardcoded password (`admin@9608`). This is highly insecure and should be migrated to secure Artisan commands via the Filament Admin panel or proper token-based middleware.
|
- *Resolved:* The highly insecure `/0xdash/slink` and `/0xdash/scache` endpoints with basic HTTP authentication have been permanently removed. Storage linking is now handled securely during deployment via `entrypoint.sh`.
|
||||||
2. **Public Attachment Storage:**
|
2. **Public Attachment Storage (RCE Risk):**
|
||||||
- Attachments are stored directly in `public/tmp/attachments`. While there is an extension filter in `Email::fetchProcessStoreEmail()`, any bypass of this filter (e.g., uploading a `.php` file masquerading as another or zero-byte vulnerabilities) can lead to direct Remote Code Execution (RCE) since the directory is publicly accessible.
|
- *Resolved:* Attachments are no longer stored directly in `public/tmp/attachments`. The `Email` model now strictly leverages Laravel's secure `Storage::disk('public')` abstraction avoiding direct `file_put_contents`.
|
||||||
3. **No Strict Rate Limiting on Generation Endpoints:**
|
3. **Standalone PHP Scripts:**
|
||||||
- Temporary email and bulk email endpoints in `routes/web.php` appear to lack specific rate limiting, making the application vulnerable to Denial of Service (DoS) and excessive spam generation.
|
- *Resolved:* Legacy scripts like `dropmail.php` and `cleanCron.php` now securely dispatch the new native Laravel Console Commands (`mailbox:clean`, etc.).
|
||||||
4. **Standalone PHP Scripts:**
|
4. **Legacy Checkout System:**
|
||||||
- Scripts like `dropmail.php`, `closeTicket.php`, and `cleanCron.php` inside the public/root directory bootstrap Laravel manually. They lack proper authentication and could potentially be executed repeatedly by unauthorized entities, causing database or IMAP server exhaustion. They should be migrated to Laravel Console Commands (`app/Console/Commands`).
|
- *Resolved:* Old Stripe Cashier routes (`checkout/{plan}`) were removed to enforce the newer Unified Payment System.
|
||||||
|
|
||||||
|
*(Note: Ensure rate limiting is strictly applied to Livewire generation endpoints to prevent DoS.)*
|
||||||
|
|
||||||
## 5. Pros & Cons
|
## 5. Pros & Cons
|
||||||
**Pros:**
|
**Pros:**
|
||||||
- **Modern Stack:** Utilizes bleeding-edge tools (Laravel 12, Livewire 3, Tailwind 4) resulting in a highly responsive SPA-like feel.
|
- **Modern Stack:** Utilizes bleeding-edge tools (Laravel 12, PHP 8.4, Livewire 3, Tailwind 4) resulting in a highly responsive SPA-like feel.
|
||||||
- **Robust Admin Panel:** Leveraging Filament provides out-of-the-box CRUD, logging, and mail monitoring.
|
- **Robust Admin Panel:** Leveraging Filament provides out-of-the-box CRUD, logging, and mail monitoring.
|
||||||
- **Flexible Monetization:** Mix of traditional Stripe and unified payments (crypto via OxaPay) allows for diverse revenue streams.
|
- **Production Ready:** Fully containerized setup tailored for Dokploy using MariaDB/Redis standalone instances for maximum scalability and zero-downtime deployments.
|
||||||
|
|
||||||
**Cons:**
|
**Cons:**
|
||||||
- **Technical Debt:** Legacy payment routes (`checkout/{plan}`) exist alongside the unified payment system, leading to code fragmentation.
|
- **Rate Limiting:** Needs comprehensive throttling on Livewire generation endpoints to completely mitigate aggressive abuse.
|
||||||
- **Synchronous IMAP Polling:** Running IMAP fetch directly or via sequential cron is slow. It should be offloaded to robust Laravel Queue workers.
|
|
||||||
- **File Management:** Relying on basic `file_put_contents` for attachments locally limits horizontal scalability (e.g., scaling across multiple Docker pods/servers). S3 or equivalent Object Storage should be used.
|
|
||||||
|
|
||||||
## 6. Development Guide & Setup Best Practices
|
## 6. Development Guide & Setup Best Practices
|
||||||
1. Ensure both `ext-imap` and `ext-curl` are enabled in your `php.ini`.
|
1. Ensure both `ext-imap` and `ext-curl` are enabled in your `php.ini`.
|
||||||
|
|||||||
@@ -35,11 +35,14 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$this->loadConfiguration();
|
$this->loadConfiguration();
|
||||||
$this->loadDomainUsernameData();
|
$this->loadDomainUsernameData();
|
||||||
|
|
||||||
// Only load application data when not in testing environment
|
|
||||||
if (! $this->app->environment('testing')) {
|
if (! $this->app->environment('testing')) {
|
||||||
$this->loadApplicationData();
|
$this->loadApplicationData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->app->environment('production')) {
|
||||||
|
\Illuminate\Support\Facades\URL::forceScheme('https');
|
||||||
|
}
|
||||||
|
|
||||||
Cashier::calculateTaxes();
|
Cashier::calculateTaxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "${APP_PORT:-8000}:80"
|
|
||||||
environment:
|
|
||||||
- APP_ENV=${APP_ENV:-production}
|
|
||||||
- APP_DEBUG=${APP_DEBUG:-false}
|
|
||||||
- APP_URL=${APP_URL}
|
|
||||||
- DB_CONNECTION=${DB_CONNECTION:-mysql}
|
|
||||||
- DB_HOST=${DB_HOST:-mariadb}
|
|
||||||
- DB_PORT=${DB_PORT:-3306}
|
|
||||||
- DB_DATABASE=${DB_DATABASE:-zemail}
|
|
||||||
- DB_USERNAME=${DB_USERNAME:-zemail_user}
|
|
||||||
- DB_PASSWORD=${DB_PASSWORD:-secret}
|
|
||||||
- CACHE_STORE=${CACHE_STORE:-redis}
|
|
||||||
- QUEUE_CONNECTION=${QUEUE_CONNECTION:-redis}
|
|
||||||
- SESSION_DRIVER=${SESSION_DRIVER:-redis}
|
|
||||||
- REDIS_HOST=${REDIS_HOST:-redis}
|
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-null}
|
|
||||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "curl", "-f", "http://localhost/health" ]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
start_period: 30s
|
|
||||||
volumes:
|
|
||||||
- app-storage:/var/www/storage
|
|
||||||
- app-public-tmp:/var/www/public/tmp
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- mariadb
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:10.11
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- MYSQL_DATABASE=${DB_DATABASE:-zemail}
|
|
||||||
- MYSQL_USER=${DB_USERNAME:-zemail_user}
|
|
||||||
- MYSQL_PASSWORD=${DB_PASSWORD:-secret}
|
|
||||||
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-rootsecret}
|
|
||||||
volumes:
|
|
||||||
- mariadb-data:/var/lib/mysql
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
app-storage:
|
|
||||||
app-public-tmp:
|
|
||||||
redis-data:
|
|
||||||
mariadb-data:
|
|
||||||
6
dockerizer/php.ini
Normal file
6
dockerizer/php.ini
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
date.time = UTC
|
||||||
|
display_errors = Off
|
||||||
|
memory_limit = 128M
|
||||||
|
max_execution_time = 60
|
||||||
|
post_max_size = 32M
|
||||||
|
upload_max_filesize = 16M
|
||||||
25
dockerizer/supervisor.laravel.conf
Normal file
25
dockerizer/supervisor.laravel.conf
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[program:laravel-worker]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /code/artisan queue:work --sleep=3 --tries=3 --max-time=3600
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
user=application
|
||||||
|
numprocs=1
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/docker.stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:laravel-scheduler]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /code/artisan schedule:work
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
user=application
|
||||||
|
numprocs=1
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/docker.stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
25
dockerizer/vhost.conf
Normal file
25
dockerizer/vhost.conf
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root "/code/public";
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
client_max_body_size 50m;
|
||||||
|
|
||||||
|
access_log /docker.stdout;
|
||||||
|
error_log /docker.stderr warn;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||||
|
fastcgi_read_timeout 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user