From b60db8a0d0382a11ffb9b321c0a0db75c969acd8 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Thu, 23 Apr 2026 04:37:56 +0000 Subject: [PATCH] Initial infrastructure: NPM + Mailcow on Proxmox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VM 200: Nginx Proxy Manager (10.48.200.80) - VM 201: Mailcow email server (10.48.200.82) - Cloud-init automation for both VMs - FortiGate VIP/policy documentation - DNS records for web.orbishosting.com - NPM proxy host setup guide - Mailcow post-install checklist - Cert sync script (NPM → Mailcow) External IP: 97.176.15.26 --- README.md | 64 +++++++- docs/dns-records.md | 68 ++++++++ docs/fortigate-changes.md | 194 +++++++++++++++++++++++ mailcow/post-install.md | 126 +++++++++++++++ mailcow/sync-certs.sh | 46 ++++++ nginx-proxy-manager/npm-proxy-setup.md | 119 ++++++++++++++ proxmox/01-complete-npm-vm.sh | 63 ++++++++ proxmox/02-create-mailcow-vm.sh | 83 ++++++++++ proxmox/snippets/mailcow-cloud-init.yaml | 80 ++++++++++ proxmox/snippets/npm-cloud-init.yaml | 76 +++++++++ 10 files changed, 917 insertions(+), 2 deletions(-) create mode 100644 docs/dns-records.md create mode 100644 docs/fortigate-changes.md create mode 100644 mailcow/post-install.md create mode 100644 mailcow/sync-certs.sh create mode 100644 nginx-proxy-manager/npm-proxy-setup.md create mode 100644 proxmox/01-complete-npm-vm.sh create mode 100644 proxmox/02-create-mailcow-vm.sh create mode 100644 proxmox/snippets/mailcow-cloud-init.yaml create mode 100644 proxmox/snippets/npm-cloud-init.yaml diff --git a/README.md b/README.md index a4b3d2d..6dd63d5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ -# ProxMailcow -Proxmox and Mailcow Configuration +# ProxMailcow — Proxmox + Nginx Proxy Manager + Mailcow + +Full mail server stack for **web.orbishosting.com** hosted on Proxmox at `10.48.200.90`. + +## Infrastructure Overview + +| Service | VM ID | Hostname | LAN IP | Role | +|---------|-------|----------|--------|------| +| Nginx Proxy Manager | 200 | npm.web.orbishosting.com | 10.48.200.80 | Reverse proxy, SSL termination | +| Mailcow | 201 | mail.web.orbishosting.com | 10.48.200.82 | Full mail server | + +**External IP:** `97.176.15.26` +**Domain:** `web.orbishosting.com` +**Gateway:** `10.48.200.1` (FortiGate) +**FusionPBX** (do not disturb): `orbisne.fortiddns.com` — existing SIP rules left untouched + +--- + +## Quick Start Order + +``` +1. Configure DNS records → docs/dns-records.md +2. Run Proxmox VM scripts → proxmox/ +3. Wait for VMs to boot (~5 min) +4. Configure NPM proxy hosts → nginx-proxy-manager/ +5. Configure FortiGate → docs/fortigate-changes.md (REVIEW BEFORE APPLYING) +6. Set up Mailcow domains/users → mailcow/ +``` + +--- + +## Default Credentials (Change Immediately!) + +| Service | URL | Username | Password | +|---------|-----|----------|----------| +| NPM Admin | http://10.48.200.80:81 | admin@example.com | changeme | +| Mailcow Admin | https://mail.web.orbishosting.com | admin | moohoo | +| NPM VM SSH | 10.48.200.80 | ubuntu | mailstack2024! | +| Mailcow VM SSH | 10.48.200.82 | ubuntu | mailstack2024! | + +--- + +## Architecture + +``` +Internet + │ + ▼ +FortiGate (97.176.15.26 / 10.48.200.1) + │ + ├── Port 80, 443 ──────────────► NPM VM (10.48.200.80) + │ │ + │ └── Proxies ──► Mailcow web UI (10.48.200.82:8080) + │ + ├── Port 25, 465, 587 ─────────► Mailcow VM (10.48.200.82) + └── Port 143, 993, 110, 995 ──► Mailcow VM (10.48.200.82) +``` + +SSL flow: +- NPM obtains Let's Encrypt cert for `mail.web.orbishosting.com` (web UI) +- Mailcow's internal ACME obtains its own cert via HTTP-01 challenge forwarded through NPM +- Mailcow uses its cert for all mail protocols (SMTP/IMAP/POP3) diff --git a/docs/dns-records.md b/docs/dns-records.md new file mode 100644 index 0000000..347adfe --- /dev/null +++ b/docs/dns-records.md @@ -0,0 +1,68 @@ +# DNS Records — web.orbishosting.com + +Configure these at your DNS registrar/provider. External IP: **97.176.15.26** + +## Required Records + +### A Records +| Name | Type | Value | TTL | +|------|------|-------|-----| +| `web.orbishosting.com` | A | `97.176.15.26` | 3600 | +| `mail.web.orbishosting.com` | A | `97.176.15.26` | 3600 | +| `npm.web.orbishosting.com` | A | `97.176.15.26` | 3600 | + +### MX Record +| Name | Type | Priority | Value | TTL | +|------|------|----------|-------|-----| +| `web.orbishosting.com` | MX | `10` | `mail.web.orbishosting.com` | 3600 | + +### SPF (TXT) +| Name | Type | Value | +|------|------|-------| +| `web.orbishosting.com` | TXT | `v=spf1 mx a ip4:97.176.15.26 ~all` | + +### DMARC (TXT) +| Name | Type | Value | +|------|------|-------| +| `_dmarc.web.orbishosting.com` | TXT | `v=DMARC1; p=quarantine; rua=mailto:postmaster@web.orbishosting.com; ruf=mailto:postmaster@web.orbishosting.com; fo=1` | + +### Autodiscover / Autoconfig (for mail clients) +| Name | Type | Value | +|------|------|-------| +| `autodiscover.web.orbishosting.com` | CNAME | `mail.web.orbishosting.com` | +| `autoconfig.web.orbishosting.com` | CNAME | `mail.web.orbishosting.com` | + +### DKIM (add AFTER Mailcow is running) +1. Log into Mailcow admin: https://mail.web.orbishosting.com +2. Go to **Configuration → Domains → web.orbishosting.com → DKIM** +3. Copy the TXT record value shown +4. Add to DNS: + +| Name | Type | Value | +|------|------|-------| +| `dkim._domainkey.web.orbishosting.com` | TXT | *(copy from Mailcow admin)* | + +### PTR Record (Reverse DNS) +Contact your ISP and request a PTR record: +- IP: `97.176.15.26` +- Points to: `mail.web.orbishosting.com` + +This is critical for email deliverability. Without it, many servers will reject your mail. + +--- + +## Verification Commands (run after DNS propagates) + +```bash +# Check A record +dig mail.web.orbishosting.com A + +# Check MX record +dig web.orbishosting.com MX + +# Check SPF +dig web.orbishosting.com TXT + +# Test mail score +# Visit: https://www.mail-tester.com and send a test email +``` diff --git a/docs/fortigate-changes.md b/docs/fortigate-changes.md new file mode 100644 index 0000000..6996ee1 --- /dev/null +++ b/docs/fortigate-changes.md @@ -0,0 +1,194 @@ +# FortiGate Configuration Changes + +FortiGate URL: https://10.48.200.1:9443 +Login: admin / (your password) + +**REVIEW BEFORE APPLYING. FusionPBX rules are NOT touched.** + +--- + +## Step 1 — Create Virtual IPs (VIPs) + +Go to: **Policy & Objects → Virtual IPs → Create New → Virtual IP** + +### VIP 1: NPM (Web Traffic) +| Field | Value | +|-------|-------| +| Name | `VIP-NPM-HTTP` | +| Interface | WAN interface (the one with 97.176.15.26) | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.80` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `80` | +| Mapped Port | `80` | + +### VIP 2: NPM (HTTPS) +| Field | Value | +|-------|-------| +| Name | `VIP-NPM-HTTPS` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.80` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `443` | +| Mapped Port | `443` | + +### VIP 3: Mailcow SMTP (Port 25) +| Field | Value | +|-------|-------| +| Name | `VIP-MAIL-SMTP` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.82` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `25` | +| Mapped Port | `25` | + +### VIP 4: Mailcow SMTPS (Port 465) +| Field | Value | +|-------|-------| +| Name | `VIP-MAIL-SMTPS` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.82` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `465` | +| Mapped Port | `465` | + +### VIP 5: Mailcow Submission (Port 587) +| Field | Value | +|-------|-------| +| Name | `VIP-MAIL-SUBMISSION` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.82` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `587` | +| Mapped Port | `587` | + +### VIP 6: Mailcow IMAP (Port 143) +| Field | Value | +|-------|-------| +| Name | `VIP-MAIL-IMAP` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.82` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `143` | +| Mapped Port | `143` | + +### VIP 7: Mailcow IMAPS (Port 993) +| Field | Value | +|-------|-------| +| Name | `VIP-MAIL-IMAPS` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.82` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `993` | +| Mapped Port | `993` | + +### VIP 8: Mailcow POP3 (Port 110) — Optional +| Field | Value | +|-------|-------| +| Name | `VIP-MAIL-POP3` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.82` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `110` | +| Mapped Port | `110` | + +### VIP 9: Mailcow POP3S (Port 995) — Optional +| Field | Value | +|-------|-------| +| Name | `VIP-MAIL-POP3S` | +| Interface | WAN interface | +| External IP | `97.176.15.26` | +| Mapped IP | `10.48.200.82` | +| Port Forwarding | Enabled | +| Protocol | TCP | +| External Port | `995` | +| Mapped Port | `995` | + +--- + +## Step 2 — Create Firewall Policies + +Go to: **Policy & Objects → Firewall Policy → Create New** + +### Policy 1: Allow Web Traffic to NPM +| Field | Value | +|-------|-------| +| Name | `WAN-to-NPM-Web` | +| Incoming Interface | WAN | +| Outgoing Interface | LAN (internal) | +| Source | `all` | +| Destination | `VIP-NPM-HTTP`, `VIP-NPM-HTTPS` | +| Schedule | `always` | +| Service | `HTTP`, `HTTPS` | +| Action | `ACCEPT` | +| NAT | Enabled | +| Log | Enabled (recommended) | + +### Policy 2: Allow Mail Traffic to Mailcow +| Field | Value | +|-------|-------| +| Name | `WAN-to-Mailcow-Mail` | +| Incoming Interface | WAN | +| Outgoing Interface | LAN (internal) | +| Source | `all` | +| Destination | `VIP-MAIL-SMTP`, `VIP-MAIL-SMTPS`, `VIP-MAIL-SUBMISSION`, `VIP-MAIL-IMAP`, `VIP-MAIL-IMAPS` | +| Schedule | `always` | +| Service | Custom (ports 25, 465, 587, 143, 993) | +| Action | `ACCEPT` | +| NAT | Enabled | +| Log | Enabled | + +> **NOTE:** If you also want POP3, add `VIP-MAIL-POP3` and `VIP-MAIL-POP3S` to Policy 2. + +--- + +## Step 3 — Verify FusionPBX Rules Are Intact + +After applying the above, confirm your existing SIP/RTP rules still exist: +- Port `5060` UDP/TCP → FusionPBX host +- Port `5061` UDP/TCP → FusionPBX host +- Ports `10000-20000` UDP → FusionPBX host (RTP) + +Do NOT remove or modify these. + +--- + +## Step 4 — (Optional) Block SMTP relay abuse + +Add a firewall policy to prevent internal hosts from sending SMTP directly (forces use of Mailcow): +- Outgoing port 25 from LAN → blocked (except from 10.48.200.82) + +--- + +## Verification + +After applying: +```bash +# Test from external network or use mxtoolbox.com: +# https://mxtoolbox.com/SuperTool.aspx + +# Test SMTP +telnet 97.176.15.26 25 + +# Test IMAP +telnet 97.176.15.26 143 + +# Test web +curl -I http://97.176.15.26 +curl -Ik https://97.176.15.26 +``` diff --git a/mailcow/post-install.md b/mailcow/post-install.md new file mode 100644 index 0000000..cf983e4 --- /dev/null +++ b/mailcow/post-install.md @@ -0,0 +1,126 @@ +# Mailcow Post-Installation Steps + +Access Mailcow admin: **https://mail.web.orbishosting.com** +Default login: `admin` / `moohoo` + +--- + +## Step 1: Change Admin Password + +**Configuration → Access → Administrator accounts → admin → Edit** + +--- + +## Step 2: Add Your Domain + +**Configuration → Mail Setup → Domains → Add domain** + +| Field | Value | +|-------|-------| +| Domain | `web.orbishosting.com` | +| Description | Orbis Hosting Mail | +| Max. aliases | 400 | +| Max. mailboxes | 10 (adjust as needed) | +| Max. quota | 10240 MB | +| Default mailbox quota | 1024 MB | +| Active | On | + +--- + +## Step 3: Create Mailboxes + +**Configuration → Mail Setup → Mailboxes → Add mailbox** + +Suggested mailboxes to create: +- `admin@web.orbishosting.com` +- `postmaster@web.orbishosting.com` (required for RFC compliance) +- `abuse@web.orbishosting.com` (required for RFC compliance) +- Your personal email address + +--- + +## Step 4: Get DKIM Key + +**Configuration → Domains → web.orbishosting.com → DKIM** + +Click **Generate** if no key exists, then copy the TXT record and add it to your DNS: +``` +dkim._domainkey.web.orbishosting.com TXT "v=DKIM1; k=rsa; p=YOURKEY..." +``` + +--- + +## Step 5: Verify Mail Configuration + +**Configuration → Diagnostics** — Run all checks and ensure green on: +- DNS MX record +- SPF record +- DMARC record +- Reverse DNS (PTR) +- DKIM + +--- + +## Step 6: Configure Mail Clients + +Use these settings for Outlook, Thunderbird, Apple Mail, etc.: + +### Incoming Mail (IMAP) +| Setting | Value | +|---------|-------| +| Server | `mail.web.orbishosting.com` | +| Port | `993` | +| Security | SSL/TLS | +| Username | full email address (e.g., user@web.orbishosting.com) | + +### Outgoing Mail (SMTP) +| Setting | Value | +|---------|-------| +| Server | `mail.web.orbishosting.com` | +| Port | `587` | +| Security | STARTTLS | +| Authentication | Normal password | +| Username | full email address | + +--- + +## Step 7: Test Email Deliverability + +1. Send a test email from your new mailbox to an external address (Gmail, Outlook) +2. Check spam score: https://www.mail-tester.com +3. Check blacklists: https://mxtoolbox.com/blacklists.aspx + +**Target score:** 10/10 on mail-tester.com + +Common issues if score is low: +- Missing PTR record → contact ISP +- Missing DKIM → check Step 4 +- Missing DMARC → check dns-records.md +- On a residential ISP blacklist → consider a mail relay (SendGrid, Mailgun) for outbound + +--- + +## Mailcow Useful Commands + +```bash +ssh ubuntu@10.48.200.82 +cd /opt/mailcow-dockerized + +# View all container status +sudo docker compose ps + +# View logs for specific container +sudo docker compose logs -f postfix-mailcow +sudo docker compose logs -f dovecot-mailcow +sudo docker compose logs -f acme-mailcow + +# Restart all Mailcow containers +sudo docker compose restart + +# Update Mailcow +sudo docker compose pull +sudo docker compose up -d + +# Update Mailcow (official method) +sudo ./update.sh +``` diff --git a/mailcow/sync-certs.sh b/mailcow/sync-certs.sh new file mode 100644 index 0000000..0bb6f06 --- /dev/null +++ b/mailcow/sync-certs.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Sync SSL certificates from NPM to Mailcow +# Run on the NPM VM (10.48.200.80) via cron after cert renewal +# +# Cron entry (on NPM VM): 0 3 * * * /opt/sync-certs.sh +# +# Prerequisites: +# 1. SSH key from NPM VM to Mailcow VM is set up (no password needed) +# Run on NPM VM: ssh-keygen -t ed25519 -f ~/.ssh/mailcow_sync +# Run on Mailcow VM: echo "" >> ~/.ssh/authorized_keys +# +# 2. DOMAIN below matches the cert folder in NPM's letsencrypt directory + +set -euo pipefail + +DOMAIN="mail.web.orbishosting.com" +MAILCOW_HOST="10.48.200.82" +MAILCOW_USER="ubuntu" +MAILCOW_SSH_KEY="/root/.ssh/mailcow_sync" + +NPM_CERT_DIR="/opt/npm/letsencrypt/live/${DOMAIN}" +MAILCOW_CERT_DIR="/opt/mailcow-dockerized/data/assets/ssl" + +# Check if cert exists +if [ ! -f "${NPM_CERT_DIR}/fullchain.pem" ]; then + echo "ERROR: Certificate not found at ${NPM_CERT_DIR}" + echo "Make sure the NPM proxy host for ${DOMAIN} has an active SSL cert." + exit 1 +fi + +echo "Syncing certs for ${DOMAIN} to Mailcow at ${MAILCOW_HOST}..." + +# Copy certs to Mailcow +scp -i "${MAILCOW_SSH_KEY}" \ + "${NPM_CERT_DIR}/fullchain.pem" \ + "${MAILCOW_USER}@${MAILCOW_HOST}:${MAILCOW_CERT_DIR}/cert.pem" + +scp -i "${MAILCOW_SSH_KEY}" \ + "${NPM_CERT_DIR}/privkey.pem" \ + "${MAILCOW_USER}@${MAILCOW_HOST}:${MAILCOW_CERT_DIR}/key.pem" + +# Reload Mailcow services that use the cert +ssh -i "${MAILCOW_SSH_KEY}" "${MAILCOW_USER}@${MAILCOW_HOST}" \ + "cd /opt/mailcow-dockerized && sudo docker compose restart postfix-mailcow dovecot-mailcow nginx-mailcow" + +echo "Done. Certs synced and Mailcow services restarted." diff --git a/nginx-proxy-manager/npm-proxy-setup.md b/nginx-proxy-manager/npm-proxy-setup.md new file mode 100644 index 0000000..f356869 --- /dev/null +++ b/nginx-proxy-manager/npm-proxy-setup.md @@ -0,0 +1,119 @@ +# Nginx Proxy Manager — Proxy Host Configuration + +After NPM is running, configure it via the web UI at **http://10.48.200.80:81** + +Default login: `admin@example.com` / `changeme` — **change immediately** + +--- + +## Proxy Host 1: Mailcow Web UI (HTTPS) + +Go to: **Hosts → Proxy Hosts → Add Proxy Host** + +### Details Tab +| Field | Value | +|-------|-------| +| Domain Names | `mail.web.orbishosting.com` | +| Scheme | `http` | +| Forward Hostname / IP | `10.48.200.82` | +| Forward Port | `8080` | +| Cache Assets | Off | +| Block Common Exploits | On | +| Websockets Support | **On** (required for Mailcow) | + +### SSL Tab +| Field | Value | +|-------|-------| +| SSL Certificate | Request a new SSL Certificate | +| Force SSL | On | +| HTTP/2 Support | On | +| HSTS Enabled | On | +| Let's Encrypt Email | postmaster@web.orbishosting.com | +| I Agree to ToS | Checked | + +Click **Save** — NPM will automatically get a Let's Encrypt certificate. + +--- + +## Proxy Host 2: NPM Admin UI (Optional — for remote management) + +If you want to access the NPM admin panel via your domain: + +### Details Tab +| Field | Value | +|-------|-------| +| Domain Names | `npm.web.orbishosting.com` | +| Scheme | `http` | +| Forward Hostname / IP | `127.0.0.1` | +| Forward Port | `81` | +| Block Common Exploits | On | + +### SSL Tab +| Field | Value | +|-------|-------| +| SSL Certificate | Request a new SSL Certificate | +| Force SSL | On | +| Let's Encrypt Email | postmaster@web.orbishosting.com | + +--- + +## Proxy Host 3: Redirect www → root domain (Optional) + +### Details Tab +| Field | Value | +|-------|-------| +| Domain Names | `www.web.orbishosting.com` | +| Scheme | `https` | +| Forward Hostname / IP | `web.orbishosting.com` | +| Forward Port | `443` | + +--- + +## Enable Mailcow's Own ACME (for mail protocol SSL) + +After NPM proxy is working for `mail.web.orbishosting.com`, SSH into the Mailcow VM and enable its own Let's Encrypt: + +```bash +ssh ubuntu@10.48.200.82 + +# Edit mailcow.conf +sudo nano /opt/mailcow-dockerized/mailcow.conf + +# Ensure these settings: +# SKIP_LETS_ENCRYPT=n ← Mailcow will get its own cert via HTTP challenge +# HTTP_PORT=8080 ← NPM forwards port 80 → this port +# HTTPS_PORT=8443 +# HTTPS_BIND=127.0.0.1 + +# Restart acme container +cd /opt/mailcow-dockerized +sudo docker compose restart acme-mailcow + +# Watch the ACME container get the cert +sudo docker compose logs -f acme-mailcow +``` + +The ACME challenge goes: +`Let's Encrypt → port 80 → FortiGate → NPM → port 8080 on Mailcow → acme responds` + +Once Mailcow's ACME succeeds, it will automatically use that cert for Postfix/Dovecot. + +--- + +## Verify Everything Works + +```bash +# From outside your network or use mxtoolbox.com: + +# Test HTTPS web UI +curl -I https://mail.web.orbishosting.com + +# Test SMTP banner +telnet 97.176.15.26 25 + +# Test IMAP +openssl s_client -connect 97.176.15.26:993 + +# Test SMTP with TLS +openssl s_client -starttls smtp -connect 97.176.15.26:587 +``` diff --git a/proxmox/01-complete-npm-vm.sh b/proxmox/01-complete-npm-vm.sh new file mode 100644 index 0000000..8b491f6 --- /dev/null +++ b/proxmox/01-complete-npm-vm.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Complete NPM VM 200 setup +# The cloud image disk was already imported as unused0 during initial VM creation. +# This script attaches the disk, configures cloud-init, resizes, and starts the VM. +# +# Run on Proxmox host: bash proxmox/01-complete-npm-vm.sh + +set -euo pipefail + +VMID=200 +VMNAME="NginxProxyManager" +IP="10.48.200.80" +GW="10.48.200.1" +DNS="8.8.8.8,1.1.1.1" +DISK_SIZE="20G" +SNIPPETS_DIR="/var/lib/vz/snippets" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "=== Configuring VM ${VMID}: ${VMNAME} ===" + +# Attach the imported disk as scsi0 +echo "[1/7] Attaching disk..." +qm set ${VMID} --scsi0 local-lvm:vm-${VMID}-disk-0,discard=on,ssd=1 + +# Add cloud-init CD-ROM drive +echo "[2/7] Adding cloud-init drive..." +qm set ${VMID} --ide2 local-lvm:cloudinit + +# Set boot order +echo "[3/7] Setting boot order..." +qm set ${VMID} --boot order=scsi0 + +# Upload cloud-init snippet +echo "[4/7] Uploading cloud-init snippet..." +mkdir -p ${SNIPPETS_DIR} +cp "${SCRIPT_DIR}/snippets/npm-cloud-init.yaml" "${SNIPPETS_DIR}/npm-cloud-init.yaml" + +# Configure cloud-init parameters +echo "[5/7] Configuring cloud-init..." +qm set ${VMID} \ + --ciuser ubuntu \ + --cipassword 'mailstack2024!' \ + --ipconfig0 ip=${IP}/24,gw=${GW} \ + --nameserver "${DNS}" \ + --searchdomain web.orbishosting.com \ + --cicustom "vendor=local:snippets/npm-cloud-init.yaml" + +# Resize disk +echo "[6/7] Resizing disk to ${DISK_SIZE}..." +qm resize ${VMID} scsi0 ${DISK_SIZE} + +# Start VM +echo "[7/7] Starting VM ${VMID}..." +qm start ${VMID} + +echo "" +echo "=== VM ${VMID} started ===" +echo "IP: ${IP}" +echo "SSH: ssh ubuntu@${IP} (password: mailstack2024!)" +echo "NPM Admin UI: http://${IP}:81" +echo "" +echo "Cloud-init will run for ~3-5 minutes to install Docker and start NPM." +echo "Monitor with: ssh ubuntu@${IP} 'sudo tail -f /var/log/cloud-init-output.log'" diff --git a/proxmox/02-create-mailcow-vm.sh b/proxmox/02-create-mailcow-vm.sh new file mode 100644 index 0000000..b4cd59d --- /dev/null +++ b/proxmox/02-create-mailcow-vm.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# Create and configure Mailcow VM 201 +# Run on Proxmox host: bash proxmox/02-create-mailcow-vm.sh + +set -euo pipefail + +VMID=201 +VMNAME="Mailcow" +IP="10.48.200.82" +GW="10.48.200.1" +DNS="8.8.8.8,1.1.1.1" +DISK_SIZE="80G" +RAM=8192 +CORES=4 +CLOUD_IMAGE="/var/lib/vz/template/iso/noble-server-cloudimg-amd64.img" +SNIPPETS_DIR="/var/lib/vz/snippets" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "=== Creating VM ${VMID}: ${VMNAME} ===" + +# Create VM +echo "[1/8] Creating VM..." +qm create ${VMID} \ + --name "${VMNAME}" \ + --memory ${RAM} \ + --cores ${CORES} \ + --sockets 1 \ + --cpu x86-64-v2-AES \ + --net0 virtio,bridge=vmbr0 \ + --ostype l26 \ + --scsihw virtio-scsi-pci \ + --boot order=scsi0 \ + --serial0 socket \ + --vga serial0 \ + --agent enabled=1 \ + --onboot 1 \ + --description "Mailcow Email Server - mail.web.orbishosting.com" + +# Import cloud image +echo "[2/8] Importing cloud image (this takes ~2 minutes)..." +qm importdisk ${VMID} "${CLOUD_IMAGE}" local-lvm --format raw + +# Attach disk +echo "[3/8] Attaching disk..." +qm set ${VMID} --scsi0 local-lvm:vm-${VMID}-disk-0,discard=on,ssd=1 + +# Add cloud-init CD-ROM +echo "[4/8] Adding cloud-init drive..." +qm set ${VMID} --ide2 local-lvm:cloudinit + +# Upload cloud-init snippet +echo "[5/8] Uploading cloud-init snippet..." +mkdir -p ${SNIPPETS_DIR} +cp "${SCRIPT_DIR}/snippets/mailcow-cloud-init.yaml" "${SNIPPETS_DIR}/mailcow-cloud-init.yaml" + +# Configure cloud-init +echo "[6/8] Configuring cloud-init..." +qm set ${VMID} \ + --ciuser ubuntu \ + --cipassword 'mailstack2024!' \ + --ipconfig0 ip=${IP}/24,gw=${GW} \ + --nameserver "${DNS}" \ + --searchdomain web.orbishosting.com \ + --cicustom "vendor=local:snippets/mailcow-cloud-init.yaml" + +# Resize disk +echo "[7/8] Resizing disk to ${DISK_SIZE}..." +qm resize ${VMID} scsi0 ${DISK_SIZE} + +# Start VM +echo "[8/8] Starting VM ${VMID}..." +qm start ${VMID} + +echo "" +echo "=== VM ${VMID} started ===" +echo "IP: ${IP}" +echo "SSH: ssh ubuntu@${IP} (password: mailstack2024!)" +echo "Mailcow Web: http://${IP}:8080 (internal only until NPM proxy is set up)" +echo "" +echo "Mailcow cloud-init takes ~10-15 minutes (Docker pull + image downloads)." +echo "Monitor with: ssh ubuntu@${IP} 'sudo tail -f /var/log/cloud-init-output.log'" +echo "" +echo "NEXT: Configure NPM proxy hosts — see nginx-proxy-manager/ directory." diff --git a/proxmox/snippets/mailcow-cloud-init.yaml b/proxmox/snippets/mailcow-cloud-init.yaml new file mode 100644 index 0000000..0d4827b --- /dev/null +++ b/proxmox/snippets/mailcow-cloud-init.yaml @@ -0,0 +1,80 @@ +#cloud-config +# Mailcow VM - Ubuntu 24.04 +# VM 201 | IP: 10.48.200.82 +# Hostname: mail.web.orbishosting.com + +package_update: true +package_upgrade: true + +packages: + - curl + - ca-certificates + - git + - htop + - net-tools + - nftables + - qemu-guest-agent + +runcmd: + # Enable and start qemu-guest-agent + - systemctl enable qemu-guest-agent + - systemctl start qemu-guest-agent + + # Set hostname (Mailcow requires this to match MAILCOW_HOSTNAME) + - hostnamectl set-hostname mail.web.orbishosting.com + - echo "127.0.0.1 mail.web.orbishosting.com mail" >> /etc/hosts + + # Install Docker + - curl -fsSL https://get.docker.com | sh + - systemctl enable docker + - usermod -aG docker ubuntu + + # Install Mailcow + - git clone https://github.com/mailcow/mailcow-dockerized /opt/mailcow-dockerized + + # Generate Mailcow config non-interactively + - | + cd /opt/mailcow-dockerized + MAILCOW_HOSTNAME=mail.web.orbishosting.com \ + MAILCOW_TZ=America/New_York \ + ./generate_config.sh + + # Configure Mailcow to run behind NPM reverse proxy + # HTTP on 8080 (proxied by NPM), HTTPS bound only to localhost + - | + sed -i 's/^HTTP_PORT=.*/HTTP_PORT=8080/' /opt/mailcow-dockerized/mailcow.conf + sed -i 's/^HTTP_BIND=.*/HTTP_BIND=0.0.0.0/' /opt/mailcow-dockerized/mailcow.conf + sed -i 's/^HTTPS_PORT=.*/HTTPS_PORT=8443/' /opt/mailcow-dockerized/mailcow.conf + sed -i 's/^HTTPS_BIND=.*/HTTPS_BIND=127.0.0.1/' /opt/mailcow-dockerized/mailcow.conf + + # Pull images and start Mailcow + - cd /opt/mailcow-dockerized && docker compose pull + - cd /opt/mailcow-dockerized && docker compose up -d + + # Add Mailcow startup service + - | + cat > /etc/systemd/system/mailcow.service << 'SERVICE' + [Unit] + Description=Mailcow Email Server + After=docker.service + Requires=docker.service + + [Service] + Type=oneshot + RemainAfterExit=yes + WorkingDirectory=/opt/mailcow-dockerized + ExecStart=/usr/bin/docker compose up -d + ExecStop=/usr/bin/docker compose down + TimeoutStartSec=0 + + [Install] + WantedBy=multi-user.target + SERVICE + + - systemctl enable mailcow.service + +final_message: | + Mailcow VM is ready. + Web UI accessible internally at: http://10.48.200.82:8080 + Default admin: admin / moohoo + CHANGE THE PASSWORD IMMEDIATELY after DNS and NPM proxy are configured. diff --git a/proxmox/snippets/npm-cloud-init.yaml b/proxmox/snippets/npm-cloud-init.yaml new file mode 100644 index 0000000..a98ab64 --- /dev/null +++ b/proxmox/snippets/npm-cloud-init.yaml @@ -0,0 +1,76 @@ +#cloud-config +# Nginx Proxy Manager VM - Ubuntu 24.04 +# VM 200 | IP: 10.48.200.80 + +package_update: true +package_upgrade: true + +packages: + - curl + - ca-certificates + - git + - htop + - net-tools + - qemu-guest-agent + +runcmd: + # Enable and start qemu-guest-agent + - systemctl enable qemu-guest-agent + - systemctl start qemu-guest-agent + + # Install Docker + - curl -fsSL https://get.docker.com | sh + - systemctl enable docker + - usermod -aG docker ubuntu + + # Create NPM directory + - mkdir -p /opt/npm/data /opt/npm/letsencrypt + + # Write docker-compose.yml + - | + cat > /opt/npm/docker-compose.yml << 'COMPOSE' + version: '3.8' + services: + npm: + image: jc21/nginx-proxy-manager:latest + restart: unless-stopped + ports: + - "80:80" + - "443:443" + - "81:81" + volumes: + - ./data:/data + - ./letsencrypt:/etc/letsencrypt + environment: + DISABLE_IPV6: "true" + COMPOSE + + # Start NPM + - cd /opt/npm && docker compose up -d + + # Add compose startup on boot + - | + cat > /etc/systemd/system/npm.service << 'SERVICE' + [Unit] + Description=Nginx Proxy Manager + After=docker.service + Requires=docker.service + + [Service] + Type=oneshot + RemainAfterExit=yes + WorkingDirectory=/opt/npm + ExecStart=/usr/bin/docker compose up -d + ExecStop=/usr/bin/docker compose down + + [Install] + WantedBy=multi-user.target + SERVICE + + - systemctl enable npm.service + +final_message: | + NPM VM is ready. + Admin UI: http://10.48.200.80:81 + Default login: admin@example.com / changeme + CHANGE THE PASSWORD IMMEDIATELY.