diff --git a/.env-example b/.env-example deleted file mode 100644 index 45ace98..0000000 --- a/.env-example +++ /dev/null @@ -1,20 +0,0 @@ -PORT=3000 -WEB_PORT=3700 -IP=0.0.0.0 -ROOT_PATH=/root/newmail/api -NODE_ENV=production -DB_HOST=localhost -DB_USER=postfix -DB_PASS=CHANGEME -DB_NAME=CHANGEME -JWT_SECRET=CHANGEME -SMTP_HOST=postfix -SMTP_PORT=587 -SMTP_SECURE=false -SMTP_USER=admin@localhost -SMTP_PASS=CHANGEME -CF_API_TOKEN=CHANGEME -CF_EMAIL=CHANGEME -SERVER_IP=CHANGEME -MAILSERVER_CONTAINER=mailserver_postfix -CONFIG_PATH=/tmp/docker-mailserver/config/ \ No newline at end of file diff --git a/2weekmail.fyi b/2weekmail.fyi index ad805b4..80a75a1 100644 --- a/2weekmail.fyi +++ b/2weekmail.fyi @@ -1,8 +1,5 @@ # Roundcube Webmail server { - limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; - - listen 80; server_name webmail.2weekmail.fyi; # Security headers @@ -13,7 +10,6 @@ server { # Proxy settings location / { - limit_req zone=api_limit burst=20 nodelay; proxy_pass http://localhost:8081; # Roundcube container port proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -30,13 +26,17 @@ server { proxy_send_timeout 60s; proxy_read_timeout 60s; } + + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/2weekmail.fyi/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/2weekmail.fyi/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + } # PostfixAdmin server { - limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; - - listen 80; server_name admin.2weekmail.fyi; # Security headers @@ -47,7 +47,6 @@ server { # Proxy settings location / { - limit_req zone=api_limit burst=20 nodelay; proxy_pass http://localhost:8080; # PostfixAdmin container port proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -59,13 +58,17 @@ server { proxy_send_timeout 60s; proxy_read_timeout 60s; } + + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/2weekmail.fyi/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/2weekmail.fyi/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + } # API Service server { - limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; - - listen 80; server_name api.2weekmail.fyi; # Security headers @@ -76,8 +79,7 @@ server { # Proxy settings location / { - limit_req zone=api_limit burst=20 nodelay; - proxy_pass http://localhost:3000; # API container port + proxy_pass http://localhost:3000/api/; # API container port proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -93,13 +95,17 @@ server { proxy_send_timeout 60s; proxy_read_timeout 300s; # Longer timeout for API calls } + + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/2weekmail.fyi/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/2weekmail.fyi/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + } # Home page server { - limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; - - listen 80; server_name 2weekmail.fyi; # Security headers @@ -110,8 +116,7 @@ server { # Proxy settings location / { - limit_req zone=api_limit burst=20 nodelay; - proxy_pass http://localhost:3350; # API container port + proxy_pass http://localhost:3700; # API container port proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -127,4 +132,59 @@ server { proxy_send_timeout 60s; proxy_read_timeout 300s; # Longer timeout for API calls } + + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/2weekmail.fyi/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/2weekmail.fyi/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + +} +server { + if ($host = 2weekmail.fyi) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + listen 80; + server_name 2weekmail.fyi; + return 404; # managed by Certbot + + +} +server { + if ($host = webmail.2weekmail.fyi) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + listen 80; + server_name webmail.2weekmail.fyi; + return 404; # managed by Certbot + + +} +server { + if ($host = admin.2weekmail.fyi) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + listen 80; + server_name admin.2weekmail.fyi; + return 404; # managed by Certbot + + +} +server { + if ($host = api.2weekmail.fyi) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + listen 80; + server_name api.2weekmail.fyi; + return 404; # managed by Certbot + + } \ No newline at end of file diff --git a/api/.env-example b/api/.env-example deleted file mode 100644 index 45ace98..0000000 --- a/api/.env-example +++ /dev/null @@ -1,20 +0,0 @@ -PORT=3000 -WEB_PORT=3700 -IP=0.0.0.0 -ROOT_PATH=/root/newmail/api -NODE_ENV=production -DB_HOST=localhost -DB_USER=postfix -DB_PASS=CHANGEME -DB_NAME=CHANGEME -JWT_SECRET=CHANGEME -SMTP_HOST=postfix -SMTP_PORT=587 -SMTP_SECURE=false -SMTP_USER=admin@localhost -SMTP_PASS=CHANGEME -CF_API_TOKEN=CHANGEME -CF_EMAIL=CHANGEME -SERVER_IP=CHANGEME -MAILSERVER_CONTAINER=mailserver_postfix -CONFIG_PATH=/tmp/docker-mailserver/config/ \ No newline at end of file diff --git a/api/Dockerfile b/api/Dockerfile index 3bdc43f..bbe66aa 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -5,6 +5,11 @@ WORKDIR /app COPY package*.json ./ RUN npm install +RUN apt-get update && apt-get install -y \ + opendkim \ + opendkim-tools \ + openssl \ + && rm -rf /var/lib/apt/lists/* COPY . . diff --git a/api/db/migrations/20250320064614_add_id_to_mailboxes.js b/api/db/migrations/20250320064614_add_id_to_mailboxes.js new file mode 100644 index 0000000..5ef8b7b --- /dev/null +++ b/api/db/migrations/20250320064614_add_id_to_mailboxes.js @@ -0,0 +1,21 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.alterTable('mailbox', function (table) { + table.bigInteger('id'); + + table.index('id'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.alterTable('mailbox', function (table) { + table.dropColumn('id'); + }); +}; diff --git a/api/db/migrations/20250321150021_change_id_to_uuid_mailbox.js b/api/db/migrations/20250321150021_change_id_to_uuid_mailbox.js new file mode 100644 index 0000000..d19f6fd --- /dev/null +++ b/api/db/migrations/20250321150021_change_id_to_uuid_mailbox.js @@ -0,0 +1,19 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema.alterTable('mailbox', function (table) { + table.uuid('id').alter(); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema.alterTable('mailbox', function (table) { + table.bigInteger('id').alter(); + }); +}; diff --git a/api/db/models/Mailbox.js b/api/db/models/Mailbox.js index 9f86ef7..f09c0ab 100644 --- a/api/db/models/Mailbox.js +++ b/api/db/models/Mailbox.js @@ -1,5 +1,6 @@ const BaseModel = require('./BaseModel'); const { Model } = require('objection'); +const { v4: uuidv4 } = require('uuid'); class Mailbox extends BaseModel { static get tableName() { @@ -16,6 +17,7 @@ class Mailbox extends BaseModel { required: ['username', 'password', 'name', 'domain', 'local_part'], properties: { + id: { type: 'string', format: 'uuid', maxLength: 36, default: uuidv4() }, username: { type: 'string', minLength: 1, maxLength: 255 }, password: { type: 'string', minLength: 1, maxLength: 255 }, name: { type: 'string', maxLength: 255 }, diff --git a/api/scripts/export_2_cloudflare.js b/api/scripts/export_2_cloudflare.js index 1d1dd62..cd05e72 100644 --- a/api/scripts/export_2_cloudflare.js +++ b/api/scripts/export_2_cloudflare.js @@ -5,6 +5,9 @@ const execPromise = util.promisify(exec); require('dotenv').config(); const path = require('path'); const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js')); +const net = require('net'); +const fs = require('fs').promises; +const { execSync } = require('child_process'); // Create Cloudflare API client const createCloudflareClient = () => { @@ -37,20 +40,21 @@ const createCloudflareClient = () => { async function generateDkimKey(domain) { try { - // Ensure the domain directory exists in the container - await execPromise(`docker exec mailserver_opendkim mkdir -p /etc/opendkim/keys/${domain}`); + // Create directory if it doesn't exist + const keyPath = `/etc/opendkim/keys/${domain}`; + await fs.mkdir(keyPath, { recursive: true }); - // Generate the DKIM key - await execPromise(`docker exec mailserver_opendkim opendkim-genkey -D /etc/opendkim/keys/${domain} -d ${domain} -s mail`); + // Generate the DKIM key using opendkim-genkey directly + execSync(`opendkim-genkey -D ${keyPath} -d ${domain} -s mail`); - // Set proper permissions - await execPromise(`docker exec mailserver_opendkim chown -R opendkim:opendkim /etc/opendkim/keys/${domain}`); + // Set proper permissions (if needed) + execSync(`chown -R opendkim:opendkim ${keyPath}`); // Read the generated public key - const { stdout } = await execPromise(`docker exec mailserver_opendkim cat /etc/opendkim/keys/${domain}/mail.txt`); + const publicKeyContent = await fs.readFile(`${keyPath}/mail.txt`, 'utf8'); // Extract the DKIM record value - const dkimMatch = stdout.match(/p=([^)]+)/); + const dkimMatch = publicKeyContent.match(/p=([^)]+)/); const dkimValue = dkimMatch ? dkimMatch[1] : null; if (!dkimValue) { @@ -181,14 +185,26 @@ async function configureDNS(domain) { } } -async function checkOpenDkimAvailability() { - try { - await execPromise('docker exec mailserver_opendkim echo "OpenDKIM is available"'); - return true; - } catch (error) { - console.error('OpenDKIM container is not available:', error.message); - return false; - } +function checkOpenDKIM() { + return new Promise((resolve, reject) => { + const client = new net.Socket(); + + client.connect(8891, 'opendkim', () => { + client.destroy(); + resolve(true); + }); + + client.on('error', (err) => { + client.destroy(); + reject(err); + }); + + // Add timeout + setTimeout(() => { + client.destroy(); + reject(new Error('Connection timeout')); + }, 5000); + }); } async function configure2WeekMailDNS() { @@ -328,7 +344,7 @@ async function configure2WeekMailDNS() { async function exportAllDomains() { try { // Check if OpenDKIM is available - const isOpenDkimAvailable = await checkOpenDkimAvailability(); + const isOpenDkimAvailable = await checkOpenDKIM(); if (!isOpenDkimAvailable) { console.error('Cannot proceed: OpenDKIM container is not available'); return; @@ -354,7 +370,7 @@ async function exportAllDomains() { if (success) { // Update the domain record to mark it as configured in Cloudflare - await models.Domain.query().findById(domain.id).patch({ in_cloudflare: 1 }); + await models.Domain.query().where('domain', domain.domain).patch({ in_cloudflare: 1 }); successCount++; } else { failCount++; diff --git a/api/scripts/make_user.js b/api/scripts/make_user.js index 968c2e6..86f623d 100644 --- a/api/scripts/make_user.js +++ b/api/scripts/make_user.js @@ -7,8 +7,8 @@ require("dotenv").config(); async function main() { const password = crypto.randomBytes(32).toString("hex"); - const email = "admin@2weekmail.fyi"; - const username = "admin"; + const email = process.argv[2]; + const username = process.argv[3]; const salt = await bcrypt.genSalt(10); const passwordEncrypted = await bcrypt.hash(password, salt); diff --git a/api/test.js b/api/test.js new file mode 100644 index 0000000..bd2bae8 --- /dev/null +++ b/api/test.js @@ -0,0 +1,3 @@ +const uuid = require('uuid'); + +console.log(uuid.v4()); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 41205ab..f5e64c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -154,6 +154,7 @@ services: volumes: - ./api:/app - /app/node_modules + - opendkim_data:/etc/opendkim environment: - PORT=${PORT} - WEB_PORT=${WEB_PORT} @@ -165,7 +166,7 @@ services: - DB_PASS=${DB_PASS} - DB_NAME=${DB_NAME} - JWT_SECRET=${JWT_SECRET} - - SMTP_HOST=${SMTP_HOST} + - SMTP_HOST=mailserver - SMTP_PORT=${SMTP_PORT} - SMTP_SECURE=${SMTP_SECURE} - SMTP_USER=${SMTP_USER} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d70b611 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "2weekmail", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755