This commit is contained in:
Ryann 2025-03-21 15:26:49 +00:00
parent f91e50fe6a
commit 21f2636c3e
13 changed files with 172 additions and 79 deletions

View File

@ -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/

View File

@ -1,8 +1,5 @@
# Roundcube Webmail # Roundcube Webmail
server { server {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
listen 80;
server_name webmail.2weekmail.fyi; server_name webmail.2weekmail.fyi;
# Security headers # Security headers
@ -13,7 +10,6 @@ server {
# Proxy settings # Proxy settings
location / { location / {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://localhost:8081; # Roundcube container port proxy_pass http://localhost:8081; # Roundcube container port
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@ -30,13 +26,17 @@ server {
proxy_send_timeout 60s; proxy_send_timeout 60s;
proxy_read_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 # PostfixAdmin
server { server {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
listen 80;
server_name admin.2weekmail.fyi; server_name admin.2weekmail.fyi;
# Security headers # Security headers
@ -47,7 +47,6 @@ server {
# Proxy settings # Proxy settings
location / { location / {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://localhost:8080; # PostfixAdmin container port proxy_pass http://localhost:8080; # PostfixAdmin container port
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
@ -59,13 +58,17 @@ server {
proxy_send_timeout 60s; proxy_send_timeout 60s;
proxy_read_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 # API Service
server { server {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
listen 80;
server_name api.2weekmail.fyi; server_name api.2weekmail.fyi;
# Security headers # Security headers
@ -76,8 +79,7 @@ server {
# Proxy settings # Proxy settings
location / { location / {
limit_req zone=api_limit burst=20 nodelay; proxy_pass http://localhost:3000/api/; # API container port
proxy_pass http://localhost:3000; # API container port
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -93,13 +95,17 @@ server {
proxy_send_timeout 60s; proxy_send_timeout 60s;
proxy_read_timeout 300s; # Longer timeout for API calls 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 # Home page
server { server {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
listen 80;
server_name 2weekmail.fyi; server_name 2weekmail.fyi;
# Security headers # Security headers
@ -110,8 +116,7 @@ server {
# Proxy settings # Proxy settings
location / { location / {
limit_req zone=api_limit burst=20 nodelay; proxy_pass http://localhost:3700; # API container port
proxy_pass http://localhost:3350; # API container port
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -127,4 +132,59 @@ server {
proxy_send_timeout 60s; proxy_send_timeout 60s;
proxy_read_timeout 300s; # Longer timeout for API calls 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
} }

View File

@ -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/

View File

@ -5,6 +5,11 @@ WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
RUN apt-get update && apt-get install -y \
opendkim \
opendkim-tools \
openssl \
&& rm -rf /var/lib/apt/lists/*
COPY . . COPY . .

View File

@ -0,0 +1,21 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.alterTable('mailbox', function (table) {
table.bigInteger('id');
table.index('id');
});
};
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.alterTable('mailbox', function (table) {
table.dropColumn('id');
});
};

View File

@ -0,0 +1,19 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function(knex) {
return knex.schema.alterTable('mailbox', function (table) {
table.uuid('id').alter();
});
};
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.alterTable('mailbox', function (table) {
table.bigInteger('id').alter();
});
};

View File

@ -1,5 +1,6 @@
const BaseModel = require('./BaseModel'); const BaseModel = require('./BaseModel');
const { Model } = require('objection'); const { Model } = require('objection');
const { v4: uuidv4 } = require('uuid');
class Mailbox extends BaseModel { class Mailbox extends BaseModel {
static get tableName() { static get tableName() {
@ -16,6 +17,7 @@ class Mailbox extends BaseModel {
required: ['username', 'password', 'name', 'domain', 'local_part'], required: ['username', 'password', 'name', 'domain', 'local_part'],
properties: { properties: {
id: { type: 'string', format: 'uuid', maxLength: 36, default: uuidv4() },
username: { type: 'string', minLength: 1, maxLength: 255 }, username: { type: 'string', minLength: 1, maxLength: 255 },
password: { type: 'string', minLength: 1, maxLength: 255 }, password: { type: 'string', minLength: 1, maxLength: 255 },
name: { type: 'string', maxLength: 255 }, name: { type: 'string', maxLength: 255 },

View File

@ -5,6 +5,9 @@ const execPromise = util.promisify(exec);
require('dotenv').config(); require('dotenv').config();
const path = require('path'); const path = require('path');
const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js')); 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 // Create Cloudflare API client
const createCloudflareClient = () => { const createCloudflareClient = () => {
@ -37,20 +40,21 @@ const createCloudflareClient = () => {
async function generateDkimKey(domain) { async function generateDkimKey(domain) {
try { try {
// Ensure the domain directory exists in the container // Create directory if it doesn't exist
await execPromise(`docker exec mailserver_opendkim mkdir -p /etc/opendkim/keys/${domain}`); const keyPath = `/etc/opendkim/keys/${domain}`;
await fs.mkdir(keyPath, { recursive: true });
// Generate the DKIM key // Generate the DKIM key using opendkim-genkey directly
await execPromise(`docker exec mailserver_opendkim opendkim-genkey -D /etc/opendkim/keys/${domain} -d ${domain} -s mail`); execSync(`opendkim-genkey -D ${keyPath} -d ${domain} -s mail`);
// Set proper permissions // Set proper permissions (if needed)
await execPromise(`docker exec mailserver_opendkim chown -R opendkim:opendkim /etc/opendkim/keys/${domain}`); execSync(`chown -R opendkim:opendkim ${keyPath}`);
// Read the generated public key // 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 // Extract the DKIM record value
const dkimMatch = stdout.match(/p=([^)]+)/); const dkimMatch = publicKeyContent.match(/p=([^)]+)/);
const dkimValue = dkimMatch ? dkimMatch[1] : null; const dkimValue = dkimMatch ? dkimMatch[1] : null;
if (!dkimValue) { if (!dkimValue) {
@ -181,14 +185,26 @@ async function configureDNS(domain) {
} }
} }
async function checkOpenDkimAvailability() { function checkOpenDKIM() {
try { return new Promise((resolve, reject) => {
await execPromise('docker exec mailserver_opendkim echo "OpenDKIM is available"'); const client = new net.Socket();
return true;
} catch (error) { client.connect(8891, 'opendkim', () => {
console.error('OpenDKIM container is not available:', error.message); client.destroy();
return false; resolve(true);
} });
client.on('error', (err) => {
client.destroy();
reject(err);
});
// Add timeout
setTimeout(() => {
client.destroy();
reject(new Error('Connection timeout'));
}, 5000);
});
} }
async function configure2WeekMailDNS() { async function configure2WeekMailDNS() {
@ -328,7 +344,7 @@ async function configure2WeekMailDNS() {
async function exportAllDomains() { async function exportAllDomains() {
try { try {
// Check if OpenDKIM is available // Check if OpenDKIM is available
const isOpenDkimAvailable = await checkOpenDkimAvailability(); const isOpenDkimAvailable = await checkOpenDKIM();
if (!isOpenDkimAvailable) { if (!isOpenDkimAvailable) {
console.error('Cannot proceed: OpenDKIM container is not available'); console.error('Cannot proceed: OpenDKIM container is not available');
return; return;
@ -354,7 +370,7 @@ async function exportAllDomains() {
if (success) { if (success) {
// Update the domain record to mark it as configured in Cloudflare // 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++; successCount++;
} else { } else {
failCount++; failCount++;

View File

@ -7,8 +7,8 @@ require("dotenv").config();
async function main() { async function main() {
const password = crypto.randomBytes(32).toString("hex"); const password = crypto.randomBytes(32).toString("hex");
const email = "admin@2weekmail.fyi"; const email = process.argv[2];
const username = "admin"; const username = process.argv[3];
const salt = await bcrypt.genSalt(10); const salt = await bcrypt.genSalt(10);
const passwordEncrypted = await bcrypt.hash(password, salt); const passwordEncrypted = await bcrypt.hash(password, salt);

3
api/test.js Normal file
View File

@ -0,0 +1,3 @@
const uuid = require('uuid');
console.log(uuid.v4());

View File

@ -154,6 +154,7 @@ services:
volumes: volumes:
- ./api:/app - ./api:/app
- /app/node_modules - /app/node_modules
- opendkim_data:/etc/opendkim
environment: environment:
- PORT=${PORT} - PORT=${PORT}
- WEB_PORT=${WEB_PORT} - WEB_PORT=${WEB_PORT}
@ -165,7 +166,7 @@ services:
- DB_PASS=${DB_PASS} - DB_PASS=${DB_PASS}
- DB_NAME=${DB_NAME} - DB_NAME=${DB_NAME}
- JWT_SECRET=${JWT_SECRET} - JWT_SECRET=${JWT_SECRET}
- SMTP_HOST=${SMTP_HOST} - SMTP_HOST=mailserver
- SMTP_PORT=${SMTP_PORT} - SMTP_PORT=${SMTP_PORT}
- SMTP_SECURE=${SMTP_SECURE} - SMTP_SECURE=${SMTP_SECURE}
- SMTP_USER=${SMTP_USER} - SMTP_USER=${SMTP_USER}

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "2weekmail",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

0
setup.sh Normal file → Executable file
View File