<< Return to documentation overview
- Installation guide for the development stack
- Table of Contents
- 1. Preparation
- 2. Setup your data disk
- 3. Setup Acme.sh for issuing Let's Encrypt certificates
- 4. Setting up your webserver
- 5. Setup Portainer
- 6. Setup Sonatype Nexus
- 7. Setup Jenkins
- 8. Setup Jenkins pipelines
- 9. Setup simple mail server for backend services
- Configure SSH keys for you VM to enable remote access
- Download and install Putty (on Windows) SSH client
Connect to your Ubuntu VM and update the system
sudo su
apt update
apt upgrade -y
do-release-upgrade
- Keep local versions of configs
- Remove old packages
- Reboot
We need to install some packages in order to be able to configure the development stack. A rundown of the packages is listed below:
git
: Commandline tool for git repository management- (Optional)
mc
: Midnight commander - a visual interface for browsing through directory structures (with many useful features) wget
: Commandline tool for downloading filesdocker
: Service for Docker container deployment and managementdocker-compose
: Commandline tool for working with Docker container stacksnginx
: Web server and HTTP/HTTPS/WS/WSS proxyphp-fpm
: PHP file interpretter for integration with Nginx- (Optional)
htop
: Utility to monitor system resources zip
&unzip
: Utilities for file compression and decompression
apt install git mc wget htop zip unzip docker docker-compose nginx php-fpm
- Postifx configuration: Internet site
Your VM should come with a separate data disk. This disk isn't formatted or mounted by default.
To find out if you have an additional disk run lsblk
:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
loop0 7:0 0 55.3M 1 loop /snap/core18/1885
loop1 7:1 0 70.6M 1 loop /snap/lxd/16922
loop2 7:2 0 30.3M 1 loop /snap/snapd/9279
vda 252:0 0 10G 0 disk
├─vda1 252:1 0 9.9G 0 part /
├─vda14 252:14 0 4M 0 part
└─vda15 252:15 0 106M 0 part /boot/efi
vdb 252:16 0 75G 0 disk
From the output above you can clearly see, that the VM has two disks, one named vda
and the other named vdb
. The first disk contains your Ubuntu installation and the second one is not formatted.
To set-up the second disk we first need to create a new partition table cfdisk /dev/vdb
:
- Select partition scheme as
GPT
- Select the Empty space and press New partition button
- Create a primary partition with the maximum size
- As partition type select
Linux
- Press quit and save changes
The partition is now set-up but we still need to format it: mkfs.ext4 /dev/vdb1
.
To auto-mount the partition create a new directory in the root of your filesystem mkdir /data
and open fstab vim /etc/fstab
:
# Data disk
/dev/vdb1 /data ext4 defaults 0 0
Save the file and now you can mount the new disk to your VM with mount -a
.
Acme.sh script is used to generate SSL certificates for our web server and mail server.
curl https://get.acme.sh | sh
cd /root/.acme.sh/
mkdir /etc/nginx/certs
Now you can issue a certificate for your domain:
./acme.sh --issue -d team01-20.studenti.fiit.stuba.sk \
-w /var/www/html/ \
--cert-file /etc/nginx/certs/team01-20.studenti.fiit.stuba.sk/cert.cer \
--key-file /etc/nginx/certs/team01-20.studenti.fiit.stuba.sk/key.key \
--fullchain-file /etc/nginx/certs/team01-20.studenti.fiit.stuba.sk/fullchain.cer
This command will issue a certificate with the use of our NginX server. By default all domains pointing to the VM will use the website in the webroot /var/www/html/
. We tell the acme.sh
script where to store the domain verification files, then where to output the certificate files themselves.
- Create an SSL certificate with Let's Encrypt (use step above for all domains)
- Create a new directory in /etc/nginx/ for custom configuration files:
mkdir /etc/nginx/conf
The folder structure will look like this:
/etc/nginx
./certs | SSL certificates for domains
./{domain1} | SSL certificates for domain1
fullchain.cer | SSL fullchain certificate
key.key | SSL certificate key
./{domain2} | SSL certificates for domain2
fullchain.cer | SSL fullchain certificate
key.key | SSL certificate key
...
./conf | custom configuration directory
default.conf | default virtual host configuration
logging.conf | logging configuration
php.conf | PHP configuration
redirect_https.conf | redirect rule HTTP to HTTPS
redirect_www.conf | redirect rule non-www to www
ssl_{domain1}.conf | SSL config for domain
ssl_{domain2}.conf | SSL config for domain
...
./conf.d | NginX additional global configuration
./modules-available | NginX available modules
./modules-enabled | NginX enabled modules
./sites-available | NginX available sites configs
./{domain1} | domain config folder
proxy.subdomain.conf | subdomain config
subdomain.conf | subdomain config
www.conf | subdomain config
...
./{domain2} | domain config folder
www.conf | subdomain config
{domain1}.conf | main domain name configuration
{domain2}.conf | main domain name configuration
./sites-enabled | NginX enabled sites configs
{domain1}.conf -> /etc/nginx/sites-available/{domain1}.conf | linked domain config
{domain2}.conf -> /etc/nginx/sites-available/{domain2}.conf | linked domain config
./snippets | NginX snippets
fastcgi.conf
fastcgi_params
koi-utf
koi-win
mime.types
nginx.conf | main NginX configuration
proxy_params
scgi_params
uwsgi_params
win-utf
Create a new configuration file for request size vim /etc/nginx/conf.d/request_size.conf
:
client_max_body_size 1G;
vim /etc/nginx/conf/default.conf
index index.php index.html; location ~ /\.ht { deny all; }
vim /etc/nginx/conf/logging.conf
access_log /data/logs/nginx_access.log; error_log /data/logs/nginx_error.log warn;
vim /etc/nginx/conf/php.conf
location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+?\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param HTTPS on; #Avoid sending the security headers twice fastcgi_param modHeadersAvailable true; fastcgi_param front_controller_active true; fastcgi_pass unix:/run/php/php7.4-fpm.sock; fastcgi_intercept_errors on; fastcgi_request_buffering off; }
vim /etc/nginx/conf/redirect_https.conf
return 301 https://$host$request_uri;
vim /etc/nginx/conf/redirect_www.conf
return 301 https://www.$host$request_uri;
vim /etc/nginx/conf/ssl_team01-20.studenti.fiit.stuba.sk.conf
ssl_certificate /etc/nginx/certs/team01-20.studenti.fiit.stuba.sk/fullchain.cer; ssl_certificate_key /etc/nginx/certs/team01-20.studenti.fiit.stuba.sk/key.key; ssl_stapling on;
vim /etc/nginx/conf/ssl_asicde.org.conf
ssl_certificate /etc/nginx/certs/asicde.org/fullchain.cer; ssl_certificate_key /etc/nginx/certs/asicde.org/key.key; ssl_stapling on;
In order to server a web site to the user, you need to define a virtual host configuration that tells the NginX server how to handle incomming connections from your domain name.
mkdir /etc/nginx/sites-available/team01-20.studenti.fiit.stuba.sk
vim /etc/nginx/sites-available/team01-20.studenti.fiit.stuba.sk.conf
# Virtual Host: team01-20.studenti.fiit.stuba.sk # redirect to https server { listen 80; server_name team01-20.studenti.fiit.stuba.sk; include /etc/nginx/conf/redirect_https.conf; include /etc/nginx/conf/logging.conf; } include /etc/nginx/sites-available/team01-20.studenti.fiit.stuba.sk/*.conf;
vim /etc/nginx/sites-available/team01-20.studenti.fiit.stuba.sk/www.conf
# Virtual Host: team01-20.studenti.fiit.stuba.sk server { listen 443 ssl http2; server_name team01-20.studenti.fiit.stuba.sk; # where files are located for this subdomain root /data/www/team01-20.studenti.fiit.stuba.sk/www/; include /etc/nginx/conf/default.conf; include /etc/nginx/conf/ssl_team01-20.studenti.fiit.stuba.sk.conf; include /etc/nginx/conf/php.conf; include /etc/nginx/conf/logging.conf; }
mkdir /etc/nginx/sites-available/asicde.org
vim /etc/nginx/sites-available/asicde.org.conf
# Virtual Host: asicde.org # redirect to www server { listen 80; server_name asicde.org; include /etc/nginx/conf/redirect_www.conf; include /etc/nginx/conf/logging.conf; } server { listen 443 ssl http2; server_name asicde.org; include /etc/nginx/conf/ssl_asicde.org.conf; include /etc/nginx/conf/redirect_www.conf; include /etc/nginx/conf/logging.conf; } # redirect to https server { listen 80; server_name *.asicde.org; include /etc/nginx/conf/redirect_https.conf; include /etc/nginx/conf/logging.conf; } include /etc/nginx/sites-available/asicde.org/*.conf;
vim /etc/nginx/sites-available/asicde.org/proxy.dockerhub.conf
# Virtual Host: hub.asicde.org server { listen 443 ssl http2; server_name hub.asicde.org; chunked_transfer_encoding on; # a default website location / { root /data/www/default/; } location /v2/ { proxy_pass http://localhost:8082/v2/; add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto 'https'; } include /etc/nginx/conf/ssl_asicde.org.conf; include /etc/nginx/conf/logging.conf; }
To enable a domain name, you need to link the configuration to enabled sites and reload NginX:
ln -s /etc/nginx/sites-available/team01-20.studenti.fiit.stuba.sk.conf /etc/nginx/sites-enabled/team01-20.studenti.fiit.stuba.sk.conf
ln -s /etc/nginx/sites-available/asicde.org.conf /etc/nginx/sites-enabled/asicde.org.conf
mkdir /data/logs
chown www-data:www-data /data/logs
chown -R root:www-data /etc/nginx/
systemctl enable --now php7.4-fpm
systemctl reload nginx
- Note: Subdomains are included automatically - see configuration for a custom domain
Portainer is a tool for management of Docker containers and other components. It provides a web interface which is easy to navigate and user-friendly.
First we need to stop Docker and change the storage location for Docker volumes. Stop Docker with systemctl stop docker
.
Now create a new directory called docker
in the /data
directory and add the entry below into /etc/fstab
:
# Docker data bind mount
/data/docker /var/lib/docker none bind 0 0
Refresh mounts with mount -a
and start docker again systemctl start docker
.
docker volume create portainer_data
docker run -d -p 9000:9000 \
--name=portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data portainer/portainer-ce
Create a configuration for new subdomain in the NginX sites-available directory:
vim /etc/nginx/sites-available/asicde.org/proxy.portainer.conf
# Virtual Host: admin.asicde.org server { listen 443 ssl http2; server_name admin.asicde.org; location / { proxy_pass http://localhost:9000/; } include /etc/nginx/conf/ssl_asicde.org.conf; include /etc/nginx/conf/logging.conf; }
On first start, you will be asked to enter administrator user credentials and then select to use a local Docker server.
Nexus is a portal for multiple package repositories including Maven repository, Docker registry and NPM repository. We will use this service to store our Maven artefacts, Docker images and cache downloaded packages in order to save some internet bandwidth.
To install Nexus, run the following command:
docker volume create --name nexus_data
docker run -d -p 8081:8081 \
-p 8082:8082 \
--name nexus \
--restart unless-stopped \
-v nexus_data:/nexus-data sonatype/nexus3
Create a configuration for new subdomain in the NginX sites-available directory:
vim /etc/nginx/sites-available/asicde.org/proxy.nexus.conf
# Virtual Host: nexus.asicde.org server { listen 443 ssl http2; server_name nexus.asicde.org; location / { proxy_pass http://localhost:8081/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto "https"; } include /etc/nginx/conf/ssl_asicde.org.conf; include /etc/nginx/conf/logging.conf; }
During your first installation a password will be created for you which you can get with docker exec -it nexus cat /nexus-data/admin.password
.
You will be asked to change your password and then enable anonymous access.
Setup repositories:
- NEW: docker (hosted)
- Name:
asicde-docker
- Create HTTP connector on
8082
- Allow anonymous docker pull
- Name:
- NEW: npm (proxy)
- Name:
asicde-npm
- Remote storage:
https://registry.npmjs.org
- Use stored certificates
- View certificate -> Add certificate to truststore
- Name:
Setup reader user permission:
- Go to Security -> Roles
- Create role -> Nexus role
- Reader
- Role ID:
reader
- Role name:
reader
- Add all
-read
and-browse
permissions
- Role ID:
- Jenkins
- Role ID:
jenkins
- Role name:
jenkins
- Add permission
nx-repository-admin-*-*-add
- Add permission
nx-repository-admin-*-*-browse
- Add permission
nx-repository-admin-*-*-edit
- Add permission
nx-repository-admin-*-*-read
- Role ID:
Allow anonymous user to download files from repository:
- Security -> Users -> Anonymous user
- Add role
reader
Create new user for Jenkins to use:
- Security -> Users -> Create user
- ID:
jenkins
- First name:
Jenkins
- Last name:
Jenkins
- Email:
[email protected]
- Status:
Active
- Role:
jenkins
Create a configuration for new subdomain in the NginX sites-available directory:
vim /etc/nginx/sites-available/asicde.org/proxy.dockerhub.conf
# Virtual Host: hub.asicde.org server { listen 443 ssl http2; server_name hub.asicde.org; chunked_transfer_encoding on; location / { root /var/www/html/; } location /v2/ { proxy_pass http://localhost:8082/v2/; add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto "https"; } include /etc/nginx/conf/ssl_asicde.org.conf; include /etc/nginx/conf/logging.conf; }
We will be running Jenkins in a Docker container. For this, we will need to create a custom container based on the original Jenkins image to be able to inject some other libraries and executables that are needed for building of Maven, NPM and Docker projects.
For this, let's create a workspace directory where we will store our configuration files mkdir /opt/jenkins-docker
. In here we will create a Dockerfile for the app:
# Get LTS version of Jenkins Docker image
FROM jenkins/jenkins:lts
# Switch to root user
USER root
# Update apt repositories
RUN apt -y update
# Download Docker and docker-compose
RUN cd /opt/; wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.13.tgz; tar xzvf docker-19.03.13.tgz; rm docker-19.03.13.tgz
RUN cd /opt/docker/; wget "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -O ./docker-compose; chmod +x ./docker-compose
ENV PATH="/opt/docker:${PATH}"
# Install JDK 13
COPY ./jdk-13.0.2_linux-x64_bin.deb /tmp/jdk-13.0.2_linux-x64_bin.deb
RUN apt -y install /tmp/jdk-13.0.2_linux-x64_bin.deb; rm -f /tmp/jdk-13.0.2_linux-x64_bin.deb
RUN update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-13.0.2/bin/java 2
RUN update-alternatives --set java /usr/lib/jvm/jdk-13.0.2/bin/java
ENV JAVA_HOME /usr/lib/jvm/jdk-13.0.2/
# Download Maven
RUN cd /opt/; wget https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz; tar xzvf apache-maven-3.6.3-bin.tar.gz; rm apache-maven-3.6.3-bin.tar.gz
ENV PATH="/opt/apache-maven-3.6.3/bin:${PATH}"
USER jenkins
You also need to download JDK 13 Debian installation package and place it in the same folder (/opt/jenkins-docker/jdk-13.0.2_linux-x64_bin.deb
).
Then build the image docker build -t jenkins-docker .
.
Now we can create a Docker container that will run the Jenkins build service:
docker run -d -v /var/run/docker.sock:/var/run/docker.sock \
-v jenkins_home:/var/jenkins_home \
-p 8080:8080 \
--name jenkins \
--restart unless-stopped jenkins-docker
Create a configuration for new subdomain in the NginX sites-available directory:
vim /etc/nginx/sites-available/asicde.org/proxy.jenkins.conf
# Virtual Host: jenkins.asicde.org server { listen 443 ssl http2; server_name jenkins.asicde.org; location / { proxy_pass http://localhost:8080; proxy_set_header Host $http_host; proxy_redirect http://localhost:8080 $scheme://jenkins.asicde.org; proxy_read_timeout 90; proxy_set_header X-Forwarded-Host $host:$server_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; } include /etc/nginx/conf/ssl_asicde.org.conf; include /etc/nginx/conf/logging.conf; }
On first start a temporary password is generated for the Jenkins instance. You can get the password with the following command:
docker exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Open a web browser, go to the Jenkins proxy URL and enter the password.
- Select option to automatically install suggested plugins
- Once the installation is finished, go to Manage Jenkins -> Manage Plugins
- Install the following additional plugins:
- Docker
- Docker Pipeline
- File Operations
- Maven Integration
- Nexus Artifact Uploader
- NodeJS
- Pipeline NPM Integration
- Pipeline Utility Steps
- (Optional) Slack Notification
- Add JDK
- Name:
jdk
- Uncheck install automatically
- JAVA_HOME:
/usr/lib/jvm/jdk-13.0.2
- Add Maven
- Name:
maven
- Uncheck install automatically
- MAVEN_HOME:
/opt/apache-maven-3.6.3/
- Add NodeJS
- Name:
nodejs
- Install automatically
- Global NPM packages to install:
@angular/[email protected]
- Add Docker
- Name:
docker
- Installation root:
/opt/docker
Create a new m2.settings.xml file containing a basic Maven repository configuration:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
<servers>
<server>
<id>{CREDENTIALS_ID}</id>
<username>{USER}</username>
<password>{PASSWORD}</password>
</server>
</servers>
<mirrors>
<mirror>
<id>{CREDENTIALS_ID}</id>
<name>{SERVER_NAME}</name>
<url>{SERVER_URL}</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
<profiles>
<profile>
<id>{PROFILE_ID}</id>
<repositories>
<repository>
<id>{CREDENTIALS_ID}</id>
<url>{SERVER_URL}</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>{CREDENTIALS_ID}</id>
<url>{SERVER_URL}</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>{PROFILE_ID}</activeProfile>
</activeProfiles>
</settings>
Fill the information based on the previous step for Configuring Nexus.
- {CREDENTIALS_ID}: name for your credentials, e.g.
credentials
- {USER}: Nexus server username, e.g.
jenkins
- {PASSWORD}: Nexus server password, e.g.
supersecret
- {SERVER_NAME}: name for your server, e.g.
nexus
- {SERVER_URL}: Nexus Maven repository URL, e.g.
https://nexus.asicde.org/repository/maven-group/
- {PROFILE_ID}: name for profile, e.g.
asicde
Then copy this file into the Jenkins container:
docker exec -it jenkins mkdir -p /var/jenkins_home/.m2/
docker cp ./m2.settings.xml jenkins:/var/jenkins_home/.m2/settings.xml
Create .npmrc file containing configuration for default NPM repository:
registry={REPO_URL}
_auth={CREDENTIALS}
Fill the information based on the previous step for Configuring Nexus.
- {REPO_URL}: Nexus NPM repository URL, e.g.
https://nexus.asicde.org/repository/asicde-npm/
- {CREDENTIALS}: credentials for Nexus (
username:password
encoded with base64), e.g.bmljZTp0cnkK
Then copy this file into the Jenkins container:
docker cp ./.npmrc jenkins:/var/jenkins_home/.npmrc
Go to Manage Jenkins -> Manage credentials -> Jenkins -> Global credentials
- Type: SSH username with private key
- ID:
github
- Username:
git
- Private key -> Enter directly
- Type: Username with password
- ID:
nexus
- Username:
jenkins
- Password:
supersecret
- Type: Secret text
- ID:
slack
- Secret:
somesecrettoken
Each part of the project contains a Jenkinsfile (if the project module is buildable). You can use this pipeline as a reference to create your own. To add this pipeline simply open Jenkins, click on New item -> Type in name -> Select Pipeline project type -> Create. Then you can insert the pipeline code into the build item.
The backend requires a way of sending emails to users. For example, when a new user is invited into any organization, they will receive the invitation email to confirm the invitation and create their user account.
postfix
: SMTP mail serverpostfix-mysql
: Postfix MySQL plugindovecot-core
: Dovecot mail serverdovecot-imapd
: Dovecot IMAP plugindovecot-lmtpd
: Dovecot LMTP plugindovecot-mysql
: Dovecot MySQL pluginmariadb-server
: MySQL server
apt install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql mariadb-server
Create a database for server mail:
CREATE DATABASE mail;
Create a new user for server mail:
GRANT SELECT ON mail.* TO 'mail'@'localhost' IDENTIFIED BY 'mailpassword';
FLUSH PRIVILEGES;
Create tables for domains, mailboxes and mail aliases:
# user database 'mail'
USE mail;
# create table for domains
CREATE TABLE `virtual_domains` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# create table for mailboxes
CREATE TABLE `virtual_users` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`password` VARCHAR(106) NOT NULL,
`email` VARCHAR(120) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# create table for mail aliases
CREATE TABLE `virtual_aliases` (
`id` INT NOT NULL AUTO_INCREMENT,
`domain_id` INT NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Add your domains:
INSERT INTO `mail`.`virtual_domains` (`id` ,`name`) VALUES ('1', 'team01-20.studenti.fiit.stuba.sk'), ('2', 'asicde.org');
Add a mailbox for team mail and one for the backend:
INSERT INTO `mail`.`virtual_users` (`id`, `domain_id`, `password` , `email`) VALUES
('1', '1', ENCRYPT('firstpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), '[email protected]'),
('2', '2', ENCRYPT('secondpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), '[email protected]');
Before we continue, let's back up the old configuration files:
mv /etc/postfix/main.cf /etc/postfix/main.cf.bak
cp /etc/postfix/master.cf /etc/postfix/master.cf.bak
We are also going to create a separate user that will be the owner of all mail on the server:
groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /var/mail/
Create a new configuration file for the basic configuration main.cf
:
mydomain = [YOUR DOMAIN]
myhostname = $mydomain
myorigin = $mydomain
mydestination = localhost
relay_domains = *
append_dot_mydomain = no
inet_protocols = ipv4
inet_interfaces = all
smtpd_banner = $myhostname
biff = no
mailbox_command = /usr/lib/dovecot/deliver -f "$SENDER" -a "$RECIPIENT"
default_privs = nobody
mail_owner = postfix
setgid_group = vmail
smtp_tls_security_level = may
smtp_tls_loglevel = 1
smtpd_tls_security_level = may
smtpd_use_tls = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_tls_chain_files = [SSL BUNDLE 1], [SSL BUNDLE 2], ...
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_unknown_reverse_client_hostname
# 100M
message_size_limit = 104857600
# 1G
mailbox_size_limit = 1073741824
virtual_mailbox_limit = 1073741824
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
virtual_mailbox_domains = mysql:/etc/postfix/mysql/virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql/virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql/virtual-alias-maps.cf,
mysql:/etc/postfix/mysql/virtual-email2email.cf
virtual_transport = lmtp:unix:private/dovecot-lmtp
Do not forget to replace the information in this configuration file with your domain name and SSL certificate. The SSL cetificate bundles consist of private key followed by the certificate and any intermediate issuer certificates.
Create MySQL query files:
mkdir -p /etc/postfix/mysql
touch /etc/postfix/mysql/virtual-alias-maps.cf
touch /etc/postfix/mysql/virtual-email2email.cf
touch /etc/postfix/mysql/virtual-mailbox-domains.cf
touch /etc/postfix/mysql/virtual-mailbox-maps.cf
At the top of each of these files, place your MySQL connection details:
user = [DB USER]
password = [DB PASSWORD]
hosts = [DB HOST]
dbname = [DB DATABASE]
Then add each query:
virtual-alias-maps.cf
:query = SELECT destination FROM virtual_aliases WHERE source='%s'
virtual-email2email.cf
:query = SELECT email FROM virtual_users WHERE email='%s'
virtual-mailbox-domains.cf
:query = SELECT destination FROM virtual_aliases WHERE source='%s'
virtual-mailbox-maps.cf
:query = SELECT 1 FROM virtual_users WHERE email='%s'
Change the master.cf
configuration to look like this:
smtp inet n - y - - smtpd
#smtp inet n - y - 1 postscreen
#smtpd pass - - y - - smtpd
#dnsblog unix - - y - 0 dnsblog
#tlsproxy unix - - y - 0 tlsproxy
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=$mua_client_restrictions
-o smtpd_helo_restrictions=$mua_helo_restrictions
-o smtpd_sender_restrictions=$mua_sender_restrictions
-o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=$mua_client_restrictions
-o smtpd_helo_restrictions=$mua_helo_restrictions
-o smtpd_sender_restrictions=$mua_sender_restrictions
-o smtpd_recipient_restrictions=
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
...
Before we continue, let's back up the old configuration files:
cp /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.bak
cp /etc/dovecot/conf.d/10-logging.conf /etc/dovecot/conf.d/10-logging.conf.bak
cp /etc/dovecot/conf.d/10-mail.conf /etc/dovecot/conf.d/10-mail.conf.bak
cp /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf.bak
cp /etc/dovecot/conf.d/10-master.conf /etc/dovecot/conf.d/10-master.conf.bak
cp /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf.bak
cp /etc/dovecot/conf.d/auth-sql.conf.ext /etc/dovecot/conf.d/auth-sql.conf.ext.bak
Enable IMAP and LMTP protocols in the Dovecot configuration /etc/dovecot/dovecot.conf
:
# Enable installed protocols
#!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap lmtp
In the mail configuration /etc/dovecot/conf.d/10-mail.conf
, set the location and mail group:
...
mail_location = maildir:/var/mail/vhosts/%d/%n
...
mail_privileged_group = vmail
...
Enable authentication mechanisms in /etc/dovecot/conf.d/10-auth.conf
:
...
auth_mechanisms = plain login
...
!include auth-sql.conf.ext
...
Configure how the users are authenticated in /etc/dovecot/conf.d/auth-sql.conf.ext
:
...
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
...
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
...
Create the SQL connection configuration file /etc/dovecot/dovecot-sql.conf.ext
:
driver = mysql
connect = host=[DB HOST] dbname=[DB DATABASE] user=[DB USER] password=[DB PASSWORD]
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
Edit the master configuration file /etc/dovecot/conf.d/10-master.conf
:
...
service imap-login {
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
...
}
...
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
...
}
...
service auth {
...
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
unix_listener auth-userdb {
mode = 0600
user = vmail
}
...
user = dovecot
}
...
service auth-worker {
...
user = vmail
}
...
Enable and configure SSL support /etc/dovecot/conf.d/10-ssl.conf
:
...
# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
ssl = yes
...
ssl_cert = <[SSL CERTIFICATE PATH]
ssl_key = <[SSL CERTIFICATE KEY PATH]
# team01-20.studenti.fiit.stuba.sk
local_name team01-20.studenti.fiit.stuba.sk {
ssl_cert = <[SSL CERTIFICATE PATH]
ssl_key = <[SSL CERTIFICATE KEY PATH]
}
...
# SSL protocols to use
ssl_protocols = !SSLv2 !SSLv3
# SSL ciphers to use
ssl_cipher_list = ALL:!LOW:!SSLv2:!SSLv3:!EXP:!aNULL
At last, update directory permissions:
chmod -R o-rwx /etc/postfix
chown -R vmail:dovecot /etc/dovecot
chmod -R o-rwx /etc/dovecot
chown -R vmail:vmail /var/mail/
chown postfix:vmail /usr/sbin/postqueue
chown postfix:vmail /usr/sbin/postdrop
chown -R postfix:vmail /var/spool/postfix/public
chown -R postfix:vmail /var/spool/postfix/maildrop
chmod g+s /usr/sbin/postqueue
chmod g+s /usr/sbin/postdrop
Enable and restart all of the services:
systemctl enable --now mariadb
systemctl enable postfix
systemctl enable dovecot
systemctl restart postfix
systemctl restart dovecot