Skip to content

X386 – Ubuntu & Python

Ubuntu and Python Documentation

  • Home
  • About
  • Contact
  • License
  • Privacy Policy

Category: Ubuntu

Ubuntu Server Documentation

HAProxy On Ubuntu

Posted on 19/04/2021 by exforge

HAProxyOnUbuntu: High Availability with HAProxy Load Balancer on Ubuntu Server 20.04

Copyright (C) 2021 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0. Specs

# 0.0. Abstract
# High Availability Load Balancing with Letsencrypt free certificates HTTPS support.
#
# 0.1. Definitions
# HAProxy is a powerful software for Load Balancing. 
# It can be used Level 4 (TCP) or Level 7 (Http) load balancing. That means you can
#   use it to share load on web sites or directly client server programs.
#
# 0.2. Configurations
# srv   : Load Balancer floating IP --> 192.168.0.150  
# srvlb1: Load Balancer 1  --> 192.168.0.160 Ubuntu 20.04 Server
# srvlb2: Load Balancer 2  --> 192.168.0.161 Ubuntu 20.04 Server
# srvaw1: App/Web Server 1 --> 192.168.0.162 Ubuntu 20.04 Server
# srvaw2: App/Web Server 1 --> 192.168.0.163 Ubuntu 20.04 Server
# srvaw3: App/Web Server 1 --> 192.168.0.164 Ubuntu 20.04 Server
# My SMTP Server --> 192.168.0.140 (for keepalived notify messages)
#
# A keepalived cluster of 2 load balancers will be used. Normally the first server
#   will run, but if an error happens on the first load balancer or if it is powered
#   off, the second load balancer will take the control of balancing. This step is 
#   not absolutely necessary but it eliminates the risk of Single Point of Failure.
#   This way, our infrastructure keeps running if any of the servers go offline.
# 2 Load Balancers will be configured with the floating IP of 192.168.0.150. An email
#   from keepalived@x11.xyz to notify@x11.xyz will be sent if any error occurs or main
#   server changes. 
# Our Application or Web Servers must be configured exactly the same way. That way the
#   users will never know to which server they are connected. For our examples, we'll 
#   install Apache and Mariadb to each App/Web server.
# We'll also install galera cluster to the servers to establish Mariadb clustering. 
#   That way, any change of the database on a server will be replicated to the others.
# First we'll load balance the web server, than we'll load balance the Mariadb database
#   usage. At that time, you'll realize, you can load balance any kind of software.
# The users only see the floating IP (192.168.0.150) of the Load Balancers, they will
#   never see or realize the other servers or their IPs.
#
# 0.3. Sources:
http://www.haproxy.org/
https://www.server-world.info/en/note?os=Ubuntu_20.04&p=haproxy&f=1
https://cbonte.github.io/haproxy-dconv/2.3/configuration.html
https://cbonte.github.io/haproxy-dconv/2.3/management.html
# Book: ISBN: 9781519073846 Load Balancing with HAProxy by Nick Ramirez
 

1. Install and Configure Load Balancers

# 1.1. Install keepalived (srvlb1 and srvlb2)
sudo apt update
sudo apt install keepalived --yes
#
# 1.2. Configure First Load Balancer (srvlb1)
# Create a config file
sudo nano /etc/keepalived/keepalived.conf
# Fill it as below, remember to change to your IPs, also remember to rename your
#   network adapter from enp0s3 to whatever yours is.
#________________________________________
global_defs {
	notification_email {
	notify@x11.xyz
	}
	notification_email_from keepalived@x11.xyz
	smtp_server 192.168.0.140
	smtp_connect_timeout 30
	router_id load_balancer
}
vrrp_instance VI_1 {
	smtp_alert
	interface enp0s3
	virtual_router_id 51
	priority 100
	advert_int 5
	virtual_ipaddress {
	192.168.0.150
	}
}
#________________________________________
#
# 1.3. Configure Second Load Balancer (srvlb2)
# Create a config file
sudo nano /etc/keepalived/keepalived.conf
# Fill it as below, remember to change to your IPs, also remember to rename your
#   network adapter from enp0s3 to whatever yours is.
global_defs {
	notification_email {
	notify@x11.xyz
	}
	notification_email_from keepalived@x11.xyz
	smtp_server 192.168.0.140
	smtp_connect_timeout 30
	router_id load_balancer
}
vrrp_instance VI_1 {
	smtp_alert
	interface enp0s3
	virtual_router_id 51
	priority 90
	advert_int 5
	virtual_ipaddress {
	192.168.0.150
	}
}
#________________________________________
#
# 1.4. Start keepalived on Load Balancers (srvlb1 and srvlb2)
sudo systemctl start keepalived
# You can check the status of keepalived with the following command:
sudo systemctl status -l keepalived
#
# 1.5. Install haproxy on Load Balancers (srvlb1 and srvlb2)
sudo apt install haproxy --yes
# Stop it for now, we'll restart it after configuring
sudo systemctl stop haproxy
 

2. Install and Configure Application/Web Servers

# 2.1. Install Apache, Mariadb and Galera Cluster on App/Web Servers 
#   (srvaw1, srvaw2, and srwaw3)
sudo apt update
sudo apt install apache2 mariadb-server galera-3 --yes
# 
# 2.2. Create Default Web Pages for App/Web Servers
# 2.2.1. Create a Default Web Page for the First Server (srvaw1)
# Delete the original one
sudo rm /var/www/html/index.html
# Create and Fill the New One
sudo nano /var/www/html/index.html
# Normally, they should have all the same html files, but just to test load balancing
#   we'll put a slight information about the server name
#_____________________________________________________
<html>
<title>SrvAW1</title>
<body>
<h1>SrvAW1</h1>
<p>Empty yet.</p>
</body>
</html>
#_____________________________________________________
#
# 2.2.2. Create a Default Web Page for the Second Server (srvaw2)
# Delete the original one
sudo rm /var/www/html/index.html
# Create and Fill the New One
sudo nano /var/www/html/index.html
# Normally, they should have the same html files, but just to test load balancing
#   we'll put a slight information about the server name
#_____________________________________________________
<html>
<title>SrvAW2</title>
<body>
<h1>SrvAW2</h1>
<p>Empty yet.</p>
</body>
</html>
#_____________________________________________________
#
# 2.2.3. Create a Default Web Page for the Third Server (srvaw3)
# Delete the original one
sudo rm /var/www/html/index.html
# Create and Fill the New One
sudo nano /var/www/html/index.html
# Normally, they should have the same html files, but just to test load balancing
#   we'll put a slight information about the server name
#_____________________________________________________
<html>
<title>SrvAW3</title>
<body>
<h1>SrvAW3</h1>
<p>Empty yet.</p>
</body>
</html>
#_____________________________________________________
#
# 2.3. Apache Configuration for Logs (srvaw1, srvaw2, and srvaw3)
# Because the web access if forwarded through the load balancer, our app/web servers
#   sees the IP of the LB (Load Balancer) as the connecting IP. That way, all of the 
#   access logs (and error log) will contain the IP of the LB only. To overcome this
#   situation and log the correct IPs, some configurations are needed.
# 2.3.1. Enable Apache2 remoteip Mod (srvaw1, srvaw2, and srvaw3)
sudo a2enmod remoteip
#
# 2.3.2. Change Apache Log to Contain Real IPs (srvaw1, srvaw2, and srvaw3)
# When the LB forward the request, it adds a X-Forwarded-For header to the request.
#   We'll configure Apache2 to include the contents of that header in the log file.
# Edit Apache config file
sudo nano /etc/apache2/apache2.conf
# Around line 212, add the first 2 lines, and change the second 2 lines as below
#   Remember to use your both of your LB IP numbers.
#__________________________________________________________________
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 192.168.0.160 192.168.0.161
LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
#__________________________________________________________________
#
# 2.3. Restart Apache (srvaw1, srvaw2, and srvaw3)
sudo systemctl restart apache2
#
# 2.4. Configure Mariadb on App/Web Servers (srvaw1, srvaw2, and srvaw3)
# 2.4.1. Secure Mariadb Installations (srvaw1, srvaw2, and srvaw3)
sudo mysql_secure_installation
# All default answers, give a good root password
# 
# 2.4.2. Set Mariadb root User to Native Mode (srvaw1, srvaw2, and srvaw3)
sudo mariadb
# Run on mariadb shell
UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE USER='root';
FLUSH PRIVILEGES;
EXIT;
#
# 2.4.3. Create a Mariadb User to Access from Our Workstation 
#   (srvaw1, srvaw2, and srvaw3)
# Will be used for testing, remember to change to your LB IPs and give your password.
mariadb -u root -p
# Run on mariadb shell
GRANT ALL ON *.* TO 'admin'@'192.168.0.160' IDENTIFIED BY 'Password12';
GRANT ALL ON *.* TO 'admin'@'192.168.0.161' IDENTIFIED BY 'Password12';
FLUSH PRIVILEGES;
EXIT;
#
# 2.5. Configure Galera Cluster on Mariadb (srvaw1, srvaw2, and srvaw3)
# 2.5.1. Temporarily Stop Mariadb Before Configuration (srvaw1, srvaw2, and srvaw3)
sudo systemctl stop mariadb
#
# 2.5.2. Enable Mariadb Binds to Other Computers (srvaw1, srvaw2, and srvaw3)
# This is necessary for the cluster, also will let us join Mariadb from our
#   workstation.
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
# Change the following line (Around line 28)
#______________________________________
bind-address = 127.0.0.1
#______________________________________
# to:
#______________________________________
bind-address = 0.0.0.0
#______________________________________
#
# 2.5.3. Configure Galera Cluster on Mariadb (srvaw1, srvaw2, and srvaw3)
# Create a new conf file and fill it
sudo nano /etc/mysql/mariadb.conf.d/99-cluster.cnf
# Fill as below, remember to use your ip addresses
#______________________________________
[galera]
innodb_autoinc_lock_mode = 2
wsrep_cluster_name    = "x386_cluster"
wsrep_cluster_address = "gcomm://192.168.0.162,192.168.0.163,192.168.0.164"
wsrep_provider = /usr/lib/galera/libgalera_smm.so
wsrep_provider_options = "evs.suspect_timeout=PT10S"
wsrep_on = on 
default_storage_engine = InnoDB 
innodb_doublewrite = 1 
binlog_format = ROW
#______________________________________
#
# 2.5.4. Start Galera Cluster On First App/Web Server (srvaw1)
# !!! You should run this only on one of the servers !!!
sudo galera_new_cluster
# This command should also starts mariadb on this node
#
# 2.5.5. Start Mariadb on Other Nodes too (srvaw2 and srvaw3)
# Run on the other servers:
sudo systemctl start mariadb
 

3. Configure Web Server Load Balancing

# We'll configure HAProxy to load balance 3 web servers (192.168.0.162, 192.168.0.163
#   and 192.168.0.164. 
# 3.1. Configure HAProxy (srvlb1 and srvlb2)
sudo nano /etc/haproxy/haproxy.cfg
# Add to the end of the file
#________________________________________________________
# define frontend for apache
frontend fe_http_80
        # listen to port 80
        bind *:80
        # set the backend
        default_backend    be_http_80
        # send X-Forwarded-For header
        option   forwardfor
# define backend for apache
backend be_http_80
        # use roundrobin algorithm for balancing
        balance  roundrobin
        # define backend servers
        server   srvaw1 192.168.0.162:80 check
        server   srvaw2 192.168.0.163:80 check
        server   srvaw3 192.168.0.164:80 check
#________________________________________________________
#
# 3.2. Restart haproxy (srvlb1 and srvlb2)
sudo systemctl restart haproxy
#
# 3.3. Explanations
# frontend is the incoming connection(s) coming to LB (Load Balancer)
# backend is the forwarding places for these icoming connection(s)
#
#frontend fe_http_80
#   Define a frontend connection and label it as fe_http_80. You can
#     label it whatever you want.
# bind *:80
#   Listen incoming connection from all the IPs of the LB at port 80
# default_backend    be_http_80
#   The backend for this frontend is named as be_http_80
# option             forwardfor
#   Capture the IP of the client at add it with a X-Forwarded-For header. We
#     will use this IP at Apache log.
#backend be_http_80
#   Define the backend named as be_http_80
# balance            roundrobin
#   Roundrobin algorithm is used for load balancing. There are some other algorithms
#     too, and they will be explained at 5. Round robin algorithm means the servers
#     will be selected as one by one. 
# server   srvaw1 192.168.0.162:80 check
# server   srvaw2 192.168.0.163:80 check
# server   srvaw3 192.168.0.164:80 check
#    List of backend servers. srvaw1, srvaw2 and srvaw3 are used as labels. IP and 
#      port will be used as forwarding ip and port. check parameter informs the LB
#      to check the backend server if the ip and port is alive. There are some 
#      other parameters too, and they will be explained at 5.
#
# 3.4. Testing
# You can connect to web site http://192.168.0.150 from different workstations and
#   see it is connecting to 192.168.0.162, 192.168.0.163, and 192.168.0.164.
 

4. Configure Mariadb Load Balancing

# 4.1. Explanations
# Load Balancing an application is similar ro load balancing a web server. All we 
#   need is determining the TCP/IP port it is using an making the configuraitons
#   using that port. We also use mode directive with tcp keyword at backend and frontend
#   sections to instruct HAProxy that it will use tcp (level 4) load balancing.
# Mariadb uses port 3306, so we'll make configurations with that port.
#
# 4.2. Configure HAProxy (srvlb1 and srvlb2)
sudo nano /etc/haproxy/haproxy.cfg
# Add to the end of the file:
#________________________________________________________
# define frontend for mariadb
frontend fe_mariadb_3306
        mode            tcp
        # listen to port 3306
        bind *:3306
        # set the backend
        default_backend    be_mariadb_3306
# define backend for mariadb
backend be_mariadb_3306
        mode            tcp
        # use roundrobin algorithm for balancing
        balance  roundrobin
        # define backend servers
        server   srvaw1 192.168.0.162:3306 check
        server   srvaw2 192.168.0.163:3306 check
        server   srvaw3 192.168.0.164:3306 check
#________________________________________________________
#
# 4.3. Reload haproxy (srvlb1 and srvlb2)
# We can reload the conf, without interrupting web server balancing
sudo systemctl reload haproxy
#
# 4.4. Testing
# You can connect from your workstation using the following command. Remember: 
#   you need to install mariadb-client package to your workstation if you haven't 
#   done so already.
# Use the password given at 2.4.3.
mariadb -u admin -p -h 192.168.0.150
# If you run the following command on mariadb shell, you can tell which server you
#   connected.
SHOW VARIABLES LIKE 'hostname';
 

5. More on HAProxy Configuration Options

# 5.1. Default Configuration File
# Default configuration file is as below:
#________________________________________________________________________
global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
        stats timeout 30s
        user haproxy
        group haproxy
        daemon
        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private
        # See: https://ssl-config.mozilla.org/#server=haproxy&serve...
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDH...
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AE...
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
        log     global
        mode    tcp
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http
#________________________________________________________________________
#
# 5.2. Explanation of Default Config Parameters
# 5.2.1. global Section
# The settings for "global" section is for HAProxy process settings.
# "log" options set up logging for requests and errors. Most of the time we
#   don't need to change them.
# "chroot" option makes HAProxy run under that specific diretory, and prevents it
#   from accessing any other place, thus enables enhanced security.
# "stats" options enables accessing HAProxy from the command line, and sets it
#   timeout value. 
# "user" and "group" options sets the user and group that HAProxy runs as.
# "daemon" option makes HAProxy run as a background process.
# "ca-base" and "crt-base" options defines the TLS (SSL) certificates if we enable
#   SSL. We will use them when we load balance SSL sites.
# The 3 ssl-default-... options are specifications for SSL configuration.
# 
# There are much more parameters, refer to:
https://cbonte.github.io/haproxy-dconv/2.3/configuration.html#3
#
# 5.2.2. defaults Section
# This section is for the default values for which we define load balancing. 
# "log global" option says that our definitions will use global log options.
# "mode http" option states that load balancing operate on level 7 (http), if we
#   set it as "home tcp" it operates at level 4 (tcp), as we did for mariadb load
#   balancing.
# "option httplog" HTTP messages logging is verbosed
# "option dontlognull" don't log requests with no data
# 3 "timeout" options with milisecond values. "connect" for connection to backend
#   servers, "client" for waiting for a client, "server" when a respons is expected
#   from a backend server.
# "errorfile" options defines the error message html files when there is an error
#   at the HAProxy itself. These files can be modified.
#
# 5.3. Other Sections
# The other sections are the options we add to the end of the config file. At 3. and
#   4. we used "backend" and "frontend" sections. 
# 5.3.1. frontend Section
# frontend section defines the part of Load Balancing which is seen by the users. 
#   We can define listening IPs and Ports here, and reference the backend section 
#   to forward the requests.
#
# 5.3.2. backend Section
# In this section, we define the IPs and Ports to forwarded, we can define algorithm,
#    mode and some other values here.
#
# 5.3.3. listen Section
# There is one more possible section, which is "listen". Is it somewhat a  
#   combination of frontend and backend. Below is a very simple example:
#__________________________________________
listen myproxy
     bind *:80
     server srv1 192.168.1.181:80
#___________________________________________
 

6. Load Balancing Algorithms

# Round Robin: Split Traffic Equally
# Weighted Round Robin: Split Traffic by Weight
# Leastconn: Split Connections Equally
# Weighted Leastconn: Split Connections by Weight
# Hash: The same requests always goes to the same servers.
# First Available: Each server sequentially take some number of connections.
# 
# 6.1. Round Robin Algorithm
# For our Apache and Mariadb LB, we used this algorithm. It is a very simple way
#   to split the traffic equally among the servers. All the requests are forwarded
#   to each server sequentially.
# Example frontend and backend sections:
#______________________________________________________
frontend fe_http_80
        bind *:80
        default_backend    be_http_80
backend be_http_80
        balance  roundrobin
        server   srv1 192.168.0.162:80 check
        server   srv2 192.168.0.163:80 check
        server   srv3 192.168.0.164:80 check
#______________________________________________________
#
# 6.2. Weighted Round Robin Algorithm
# Weighted Round Robing is similar to standart Round Robin, just you can set weights
#   to the backends, so that they can have more traffic. It is useful, if some of 
#   your servers have more processing power.
# Example frontend and backend sections, srw1 and srv2 will have 2 times more of 
#   traffic than srv3:
#______________________________________________________
frontend fe_http_80
        bind *:80
        default_backend    be_http_80
backend be_http_80
        balance  roundrobin
        server   srv1 192.168.0.161:80 weight 2 check
        server   srv2 192.168.0.162:80 weight 2 check
        server   srv3 192.168.0.163:80 weight 1 check
#______________________________________________________
# You can temporarily disable a backend server by disabled keyword:
#___________________________________________________________
        server   srv3 192.168.0.163:80 weight 1 disabled
#___________________________________________________________
#
# 6.3. Leastconn Algorithm
# Leastconn algorithm splits the traffic amongst the server regarding the connection
#   numbers. So that, all the servers gets equal number of connections. It is very useful
#   for Load Balancing databases.
# Example frontend and backend sections:
#_______________________________________________________
frontend fe_mariadb_3306
        mode            tcp
        bind *:3306
        default_backend    be_mariadb_3306
backend be_mariadb_3306
        mode            tcp
        balance  leastconn
        server   srv1 192.168.0.162:3306 check
        server   srv2 192.168.0.163:3306 check
        server   srv3 192.168.0.164:3306 check
#_______________________________________________________
# With this algorith, a newly added server may have all the traffic because it
#   has no connection, to avoid it, there is a keyword named as slowstart followed
#   by time :
#________________________________________________________________
        server   srv4 192.168.0.165:3306 check slowstart 60s
#________________________________________________________________
#
# 6.4. Weighted Leastconn Algorithm
# Weighted Leastconn is similar to standart Leastconn algorithm , just you can set 
#   weights to the backends, so that they can have more traffic. It is useful, if some 
#   of your servers have more processing power.
# Example frontend and backend sections, srw1 and srv2 will have 2 times more of 
#   traffic than srv3:
#________________________________________________________________
frontend fe_mariadb_3306
        mode            tcp
        bind *:3306
        default_backend    be_mariadb_3306
backend be_mariadb_3306
        mode            tcp
        balance  leastconn
        server   srv1 192.168.0.162:3306 weight 2 check
        server   srv2 192.168.0.163:3306 weight 2 check
        server   srv3 192.168.0.164:3306 weight 1 check
#________________________________________________________________
#
# 6.4. HASH Uri Algorithm
# This algorithm is very useful especially when load balancing static web servers 
#   with caching. This algorithm always forwards the same requests to the same
#   nodes. This way, cache hits and performance increase.
# Example frontend and backend sections:
#______________________________________________________
frontend fe_http_80
        bind *:80
        default_backend    be_http_80
backend be_http_80
        balance  uri
        server   srv1 192.168.0.162:80 check
        server   srv2 192.168.0.163:80 check
        server   srv3 192.168.0.164:80 check
#______________________________________________________
#
# This algorithm can be used in weighted mode too. This way you can utilize the
#   faster servers better. 
# Example frontend and backend sections, srw1 and srv2 will have 2 times more of 
#   traffic than srv3:
#______________________________________________________
frontend fe_http_80
        bind *:80
        default_backend    be_http_80
backend be_http_80
        balance  uri
        server   srv1 192.168.0.161:80 weight 2 check
        server   srv2 192.168.0.162:80 weight 2 check
        server   srv3 192.168.0.163:80 weight 1 check
#______________________________________________________
#
# 6.5. First Available Algorithm
# This algorithm allows to use servers sequentially, but steps up to next server
#   when specified number of connection is established. That way, it will use
#   srv1 until the first (say) 50 connections, and after it will use srv2 etc. This 
#   algorithm can be useful when you don't want to install a server when it is not
#   necessary.
# Example frontend and backend sections:
#______________________________________________________
frontend fe_http_80
        bind *:80
        default_backend    be_http_80
backend be_http_80
        balance  first
        server   srv1 192.168.0.162:80 maxconn 50
        server   srv2 192.168.0.163:80 maxconn 50
        server   srv3 192.168.0.164:80 maxconn 50
#______________________________________________________
 

7. URL Redirection

# The requested URL can be redirected depending on URL path, URL parameters, HTTP headers, 
#   or HTTP address. This redirections could be very efficient at some circumstances.
# 7.1. URL Path Redirection
# 7.1.0. Scenario
# We have 3 folders at our webserver, folder1, folder2, and folder3. When folder1 is 
#   requested it will be redirected to srvaw1, folder2 to srvaw2, folder3 to srvaw3. 
#   Otherwise the standart load balancing will keep going as it is at Section 3.
# 
# 7.1.1. Configuration  (srvlb1 and srvlb2)
sudo nano /etc/haproxy/haproxy.cfg
# Remove previously added backend and frontend sections and add to the end of the file:
#______________________________________________________________
frontend fe_http_80
	bind *:80
	acl acl_folder1 path_beg -i /folder1
	use_backend be_folder1 if acl_folder1
	acl acl_folder2 path_beg -i /folder2
	use_backend be_folder2 if acl_folder2
	acl acl_folder3 path_beg -i /folder3
	use_backend be_folder3 if acl_folder3
	default_backend    be_http_80
        option   forwardfor
backend be_folder1
        server   srvaw1 192.168.0.162:80 check
backend be_folder2
        server   srvaw2 192.168.0.163:80 check
backend be_folder3
        server   srvaw3 192.168.0.164:80 check
backend be_http_80
        balance  roundrobin
        server   srvaw1 192.168.0.162:80 check
        server   srvaw2 192.168.0.163:80 check
        server   srvaw3 192.168.0.164:80 check
#______________________________________________________________
# 
# Restart HAProxyy
sudo systemctl restart haproxy
# You may prefer reloading haproxy, if it is already active
sudo systemctl reload haproxy
# 
# 7.1.2. Explanations
# ACLs (Access Control Lists) are used to check if a URL path starts with something.
#	acl acl_folder1 path_beg -i /folder1
# acl is a keyword to define an ACL, acl_folder1 is the given name for that acl, path_beg 
#   mean a condition of URL path (part of the URL after the address) starts with something,
#   -i means following string will be considered as case insensitive, finally the /folder1 is 
#   the string we are looking for.
# ACL acl_folder1 is activated when a url path starts with /folder1 like in:
# http://www.x11.xyz/folder1
# For a URL of http://www.x11.xyz/folder1/folder2/folder3, the URL Path is:
#   /folder1/folder2/folder3
#
#	use_backend be_folder1 if acl_folder3
# This command instructs HAProxy to use the server(s) in be_folder1 backend when acl_folder1
#   is activated.
# Similar ACLs and Backends are created for /folder2 and /folder3 too.
#
# There are other possible conditions for URL Path. List of them:
# path  	exact URL path 
# path_beg 	URL path begins with the string
# path_end 	URL path ends with the string
# path_sub 	URL path has the string as a substring
# path_dir 	URL path has the string as a subdirectory
# path_len 	Exact length of the URL path
# path_reg 	Regex match of the URL path
#
# 7.1.3. URL Path ACL Examples
# An acl for info page
acl acl_info path -i /info/info.html
# An acl for jpg and png images
acl acl_image path_end .jpg .png
# An acl for image directories
acl acl_image2 path_dir -i /images
# An acl for URL paths more than 20 chars
acl acl_long path_len gt 20
# An acl for paths including cart
acl acl_cart path_sub -i cart
# Another acl for images
acl acl_image3 path_reg (jpg|jpeg|bmp|gif|png)
#
# 7.2. URL Parameter Redirection
# A URL parameter is a variable and  value pair. A lot of website including duckduckgo
#   and google use it to send a search to the website. Below is an example:
https://www.x386.xyz/?s=x386
# s is the variable which stands for search and x386 is the value to search for.
# HAProxy can capture the parameter (the  variable and the value) and redirects a certain 
#   pair to a certain website. 
#
# 7.2.1. Example
# Assume that we have a variable named block_number and it can have values first, second,  
#   third, and rest. A URL for first block number will be like something below:
http:/x11.xyz/?block_number=First
# We want to redirect first block to a website, second and third to another website and the 
#   rest to another website. A frontend section would be like below:
#__________________________________________________________
frontend fe_blocks
	bind *:80
	acl acl_block1 url_param(block_number) -i -m str first
	use_backend be_block1 if acl_block1
	acl acl_block23 url_param(block_number) -i -m str second third
	use_backend be_block23 if acl_block23
	acl acl_blockrest url_param(block_number) -i -m str rest
	use_backend be_blockrest if acl_blockrest
	default_backend blocks
#__________________________________________________________
# As you might remember -i directive is for case-insensitive string match. -m directive is 
#   used for exact string match.
#
# 7.3. HTTP Header Redirection
# HTTP Headers may contain many information including User-Agent, Host (website root address),
#   Content-Type, Referer (not referrer). For a full list, please refer:
https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
# A User-Agent HTTP Header would be something like below:
# Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0
# A host HTTP Header would be something like below:
Host: www.x11.xyz
#
# A frontend section to redirect requests from mobile devices to a specific address would be:
#__________________________________________________________
frontend be_http
	bind *:80
	acl acl_mobile req.hdr(User-Agent) -i -m reg (android|iphone)
	use_backend be_mobile if acl_mobile
	default_backend be_http
#__________________________________________________________
 

8. Enabling HTTPS at HAProxy

# This section deals with using https with HAProxy. Using TLS (SSL) certificates are easy
#   with HAProxy, but because we want to use LetsEncrytp certificates and certbot tool for
#   frequent (every 2 months) renewals.
# I test everything I write here, actually I write here what I do on servers. To use 
#   LetsEncrypt certificates with certbot, the servers must be connected to the internet. 
#   I can only have 3 test VPS on the internet, 2 of them comes from Oracle Free Tier and 1
#   is my paid test server. Therefore, for this section only, I use 1 HAProxy server and 2
#   web servers (No keepalived). 
#
# 8.0. Configurations (For this section only)
# x11.xyz: Load Balancer  --> 89.47.167.194  Ubuntu 20.04 Server
# u11.xyz: Web Server 1   --> 132.145.77.94  Ubuntu 20.04 Server
# v11.xyz: Web Server 2   --> 132.145.61.164 Ubuntu 20.04 Server
# HAProxy is already installed on x11.xyz, Apache is already installed and running on u11.xyz 
#   and v11.xyz.
# HAProxy configuration must not have any frontend or backend sections.
#
# 8.1. Considerations
# To receive (and then renew) certificates from LetsEncrypt with Certbot; either you should
#   have a web server listening on port 80 on your server, or Certbot spins a temporary web
#   server at port 80 when it is working. 
# It is not so easy, because we bind port 80 at HAProxy configuration. There are some 
#   complicated solutions on the web. I found a solution which is not so painful, also looks
#   safe to implement. 
# We install apache to our Load Balancer, but bind it on loopback adapter (127.0.0.1). 
#   HAProxy is binded to server's other IP addresses. The request of LetsEncrypt's challenge
#   directory is redirected to internal Apache server. That way Apache and HAProxy can survive
#   together, both binding to port 80.
#
# 8.2. Install And Configure Apache on Load Balancer (Run on x11.xyz)
sudo apt update
sudo apt install apache2
# Apache may give error messages and cannot start. Don't worry, it is because HAProxy uses
#   port 80 already.
#
# Configure Apache to bind only on loopback
sudo nano /etc/apache2/ports.conf
# Change as Below
#_____________________________________
Listen 127.0.0.1:80
<IfModule ssl_module>
        Listen 127.0.0.1:443
</IfModule>
<IfModule mod_gnutls.c>
        Listen 127.0.0.1:443
</IfModule>
#_______________________________________
#
# Configure the Default Site Conf to only bind to loopback IP
sudo nano /etc/apache2/sites-available/000-default.conf
# Change as below, remember to change to your domains
#______________________________________________________________
<VirtualHost 127.0.0.1:80>
        ServerAdmin webmaster@localhost
        ServerName x11.xyz
        ServerAlias www.x11.xyz
        DocumentRoot /var/www/html
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
#______________________________________________________________
#
# Create Letsencrypt's challenge directory
sudo mkdir -p /var/www/html/.well-known/acme-challenge
# Restart Apache
sudo systemctl restart apache2
#
# 8.3. Install and Run Certbot
# Install Certbot
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
# Run Certbot to Produce Certificates
# Remember to change to your domain(s), 
sudo certbot certonly --webroot --webroot-path /var/www/html -d x11.xyz,www.x11.xyz
#
# 8.4. Generate Certificate for HAProxy
# Your LetsEncrypt certificates are located at the dir /etc/letsencrypt/live/x11.xyz.
#   Of course yours haves your domain name instead of x11.xyz. You can see its name with
#   the following command:
sudo ls -al /etc/letsencrypt/live
# The directory that you see there, is your domain to replace with x11.xyz at the 
#   following commands.
# HAProxy certificate is generated by adding public key and private key together to a file
# Temporarily become root and generate certificate
sudo -i
cd /etc/letsencrypt/live/x11.xyz
cat fullchain.pem privkey.pem >> haproxy.pem
exit
#
# 8.5. Configure HAProxy (Run on x11.xyz)
# Configure HAProxy for SSL
sudo nano /etc/haproxy/haproxy.cfg
# Add to the end of the file, remember to use your servers' IPs at bind directive. You can
#   see them with "ip a" command.
# 
#__________________________________________________________________
frontend fe_http
        bind 89.47.167.194:80
        bind 10.47.167.194:80
        bind 89.47.167.194:443 ssl crt /etc/letsencrypt/live/x11.xyz/haproxy.pem
        bind 10.47.167.194:443 ssl crt /etc/letsencrypt/live/x11.xyz/haproxy.pem
        acl acl_acme path_beg -i /.well-known/acme-challenge
        use_backend be_acme if acl_acme
        default_backend    be_http
        option   forwardfor
backend be_acme
        server self 127.0.0.1:80 check
backend be_http
        balance  roundrobin
        server   u11 132.145.77.94:80 check
        server   v11 132.145.61.164:80 check
#__________________________________________________________________
# Restart HAProxy
sudo systemctl restart HAProxy
# SSL redirection is running now, but we have some more work to polish it.
#
# 8.6. Check Certbot for Renewal and Add Renewal-Hooks (Run on x11.xyz)
# We are going to wait for 60 days to renew our certificates, but we can simulate it with
#   the following command:
sudo certbot renew --dry-run
# If it works without any errors, most probably it will work forever.
# But there is some more things to consider. Everytime the certificates are renewed, we have
#   to generate certificate for HAProxy and restart HAProxy to use that new certificate. 
# It is easier than you think. We will create a script to do that work, and make it run 
#   everytime our certificates renewed.
#
# Certbot runs all scripts in /etc/letsencrypt/renewal-hooks/deploy/ folder after it 
#   successfully renews a certificate. So we'll add a file there with the necessary 
#   operations
sudo nano /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
# Fill as below, remember to change to your domain
#___________________________________________________________________
cat /etc/letsencrypt/live/x11.xyz/fullchain.pem /etc/letsencrypt/live/x11.xyz/privkey.pem \
  >> /etc/letsencrypt/live/x11.xyz/haproxy.pem
systemctl restart haproxy
#___________________________________________________________________
# Make it executable
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/haproxy.sh
#
# 8.7. Explanations
# Everything is fine until now. We have our SSL (TLS actually) certificates, they are 
#   renewed automatically. We can connect to our site using https.
# Actually only the traffic between our browser and the Load Balancer is encrypted, the
#   traffic between Load Balancer and the Web Servers are cleartext. This is called TLS 
#   Termination. It may not be a problem if your web servers are not connected to the 
#   internet. But to be stay safe, we'd better encrypt that traffic too. And this is called
#   TLS re-encryption.
# To establish TLS re-encryption, we'll use self signed certificates on our Web Servers, and
#   instruct our Load Balancer to reach them using https. 
#
# 8.8. Enable HTTPS at Web Servers (Run on u11.xyz and v11.xyz)
# Enable Apache SSL module
sudo a2enmod ssl
# Restart Apache
sudo systemctl restart apache2
# Create a directory for the certificates
sudo mkdir /etc/apache2/certs
# Create a self signed certificate
sudo openssl req -x509 -nodes -days 7300 -newkey rsa:2048 \
-keyout /etc/apache2/certs/x11.xyz.key -out /etc/apache2/certs/x11.xyz.crt
# It will ask some questions, answer them with common sense
#
# Create a conf file for ssl site
sudo nano /etc/apache2/sites-available/000-virtual-ssl.conf
#____________________________________________
<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName x11.xyz
        ServerAlias www.x11.xyz
        ServerAdmin webmaster@x11.xyz
        DocumentRoot /var/www/html
        ErrorLog ${APACHE_LOG_DIR}/x11.xyz-error.log
        CustomLog ${APACHE_LOG_DIR}/x11.xyz-access.log combined
        SSLEngine on
        SSLCertificateFile	/etc/apache2/certs/x11.xyz.crt
        SSLCertificateKeyFile	/etc/apache2/certs/x11.xyz.key
    </VirtualHost>
</IfModule>
#____________________________________________
# Enable new ssl site
sudo a2ensite 000-virtual-ssl.conf
# Reload apache
sudo systemctl reload apache2
# Our web servers are ready for https. Now we need to instruct our Load Balancer to connect
#   them through https.
#
# 8.9. Instruct HAProxy to Access Web Servers Through HTTPS (Run on x11.xyz)
sudo nano /etc/haproxy/haproxy.cfg
# Change Backend Sections as below
#___________________________________________________
backend be_acme
        server self 127.0.0.1:80 check
backend be_http
        balance  roundrobin
        server   u11 132.145.77.94:443 check ssl verify none
        server   v11 132.145.61.164:443 check ssl verify none
#___________________________________________________
# Restart HAProxy
sudo systemctl restart haproxy
#
# 8.10. Auto HTTP to HTTPS Redirection
# Now when someone types https://x11.xyz on the browser, all the traffic between the client
#   and our web servers are encrypted. But if someone types http://x11.xyz, all the traffic
#   goes in plain, old, clear format (unless the browser automatically converts it to 
#   https, like Firefox does). We can force HTTP to HTTPS redirection by modifying frontend
#   section.
sudo nano /etc/haproxy/haproxy.cfg
# Add following line after the bind lines of the frontend section:
#____________________________________________________________
redirect scheme https if !{ ssl_fc }
#____________________________________________________________
# The modified frontend section will look like below:
#________________________________________________________________________________
frontend fe_http
        bind 89.47.167.194:80
        bind 10.47.167.194:80
        bind 89.47.167.194:443 ssl crt /etc/letsencrypt/live/x11.xyz/haproxy.pem
        bind 10.47.167.194:443 ssl crt /etc/letsencrypt/live/x11.xyz/haproxy.pem
        redirect scheme https if !{ ssl_fc }
        acl acl_acme path_beg -i /.well-known/acme-challenge
        use_backend be_acme if acl_acme
        default_backend    be_http
        option   forwardfor
#________________________________________________________________________________
# Restart HAProxyz
sudo systemctl restart haproxy
#
# 8.11. Server Persistance with Cookies
# One final touch and we are good to go. We may want the same computers always connect to the
#   same frontend servers. This is especially necessary when the connection has a session 
#   information. Otherwise, the user must login again everytime the server changed. 
# Server persistance can be established with cookies easily. At the backend session, a cookie
#   directive is added and all servers are assigned to have a unique cookie.
sudo nano /etc/haproxy/haproxy.cfg
# Change backend sections as below:
#_____________________________________________________________
backend be_acme
        server self 127.0.0.1:80 check
backend be_http
        balance  roundrobin
	cookie ACTIVESERVER insert indirect nocache
        server   u11 132.145.77.94:443 check ssl verify none cookie u11
        server   v11 132.145.61.164:443 check ssl verify none cookie v11
#_____________________________________________________________
# Restart HAProxy
sudo systemctl restart haproxy
#
# 8.12. Final Contents of HAProxy Config File
cat /etc/haproxy/haproxy.cfg
#______________________________________________________________________________
global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd lis>
        stats timeout 30s
        user haproxy
        group haproxy
        daemon
        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private
        # See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.>
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128>
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SH>
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http
frontend fe_http
        bind 89.47.167.194:80
        bind 10.47.167.194:80
        bind 89.47.167.194:443 ssl crt /etc/letsencrypt/live/x11.xyz/haproxy.pem
        bind 10.47.167.194:443 ssl crt /etc/letsencrypt/live/x11.xyz/haproxy.pem
        redirect scheme https if !{ ssl_fc }
        acl acl_acme path_beg -i /.well-known/acme-challenge
        use_backend be_acme if acl_acme
        default_backend    be_http
        option   forwardfor
backend be_acme
        server self 127.0.0.1:80 check
backend be_http
        balance  roundrobin
        cookie ACTIVESERVER insert indirect nocache
        server   u11 132.145.77.94:443 check ssl verify none cookie u11
        server   v11 132.145.61.164:443 check ssl verify none cookie v11
#______________________________________________________________________________
 

Posted in Ubuntu

MariaDB Cluster on Ubuntu

Posted on 04/04/2021 - 12/04/2021 by exforge

Mariadb Master-Master Replication with Galera Cluster Tutorial on Ubuntu 20.04

Copyright (C) 2021 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0. Specs

# 0.1. Definitions
# 3 servers will be installed and configured as Mariadb clusters. All the 
#   changes in one server will be updated to others momentarily.
# At least 3 nodes are advised for Mariadb cluster, there is no upper limit.
# 
# !!! At least 2 of the cluster nodes must be online always. If you fall to 1
#   you may have problems. If you shut down all of the nodes, your cluster stops
#   and you need some work to (hopefully) start again. !!!
#
# 0.2. My Configuration
testsrv1 -> Ubuntu 20.04 Server 192.168.0.161
testsrv2 -> Ubuntu 20.04 Server 192.168.0.162
testsrv3 -> Ubuntu 20.04 Server 192.168.0.163
#
# 0.3. Resources
https://www.howtoforge.com/how-to-setup-mariadb-galera-multi-master-synchronous-replication-using-debian-10/
https://www.symmcom.com/docs/how-tos/databases/how-to-recover-mariadb-galera-cluster-after-partial-or-full-crash
https://mariadb.com/docs/multi-node/galera-cluster/understand-mariadb-galera-cluster/
https://mariadb.com/kb/en/galera-cluster-recovery/
 

1. Mariadb Installations (Execute on all servers)

# 1.1. Install Mariadb and Galera Cluster on all servers
sudo apt update
sudo apt install mariadb-server galera-3 --yes
#
# 1.2. Secure Mariadb Installations
sudo mysql_secure_installation
# All default answers, give a good root password
# 
# 1.3. Set Mariadb root User to Native Mode
sudo mariadb
# Run on mariadb shell
UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE USER='root';
FLUSH PRIVILEGES;
EXIT;
# Now you can run mariadb with root user and password
mariadb -u root -p
 

2. Mariadb Configurations (Execute on all servers)

# 2.1. Temporarily Stop Mariadb
sudo systemctl stop mariadb
#
# 2.2. Bind Address Enablement
# Mariadb daemon must listen to the network for the cluster
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
# Change the following line (Around line 28)
#______________________________________
bind-address = 127.0.0.1
#______________________________________
# to:
#______________________________________
bind-address = 0.0.0.0
#______________________________________
#
# 2.3. Cluster Options
# Create a new conf file and fill it
sudo nano /etc/mysql/mariadb.conf.d/99-cluster.cnf
# Fill as below, remember to use your ip addresses
#______________________________________
[galera]
# Mariadb only supports this lock mode
innodb_autoinc_lock_mode = 2
# Name of the cluster, you can change it
wsrep_cluster_name    = "x386_cluster"
# List of cluster nodes
wsrep_cluster_address = "gcomm://192.168.0.161,192.168.0.162,192.168.0.163"
# Galera plugin path
wsrep_provider = /usr/lib/galera/libgalera_smm.so
# If a node does not respond in 10 second, it is assumed to be offline
wsrep_provider_options = "evs.suspect_timeout=PT10S"
# Replication for this node is on
wsrep_on = on 
# Galera cluster supports InnoDB
default_storage_engine = InnoDB 
# Use InnoDB double write buffer
innodb_doublewrite = 1 
# Use ROW format for bin logs
binlog_format = ROW
#______________________________________
 

3. Start Cluster

# 3.1. Start Cluster on One of the Nodes
# !!! You should run this only on one of the servers !!!
sudo galera_new_cluster
# This command should also start mariadb on this node, check it:
systemctl status mariadb
#
# 3.2. Start Mariadb on Other Nodes too
# Run on the other servers:
sudo systemctl start mariadb
# Our Cluster is established
 

4. Test Mariadb Cluster

# We will run commands on the nodes and see the changes on other nodes
# 4.1. Create a Database on the First Node
# !!! Run on the first server !!!
mariadb -u root -p
# Run on mariadb shell
CREATE DATABASE Test;
exit;
#
# 4.2. Create a Table on the Database on the Second Node
# !!! Run on second server !!!
mariadb -u root -p
# Run on mariadb shell
USE Test;
CREATE TABLE People (Name char(15), Age int(3));
exit;
#
# 4.3. Add Records to the Table on the Third Node
# !!! Run on third server !!!
mariadb -u root -p
# Run on mariadb shell
USE Test;
INSERT INTO People VALUES ('Exforge', '52');
INSERT INTO People VALUES ('Kedi', '8');
SELECT * FROM People;
exit;
#
# 4.4. Check First and Second Node for the Records
# !!! Run on first and second server !!!
mariadb -u root -p
# Run on mariadb shell
USE Test;
SELECT * FROM People;
exit;
 

5. Maintenance

# 5.1. Healthcheck
# The following commands runs on Mariadb shell and show information about
#   Mariadb cluster.
# Show the running nodes:
show status like 'wsrep_incoming_addresses' ;
# Show the number of running nodes:
show status like 'wsrep_cluster_size';
# Show the UUID of the cluster
show status like 'wsrep_cluster_state_uuid';
# Show the status of the current node
show status like 'wsrep_local_state_comment';
#
# 5.2. Adding a Node to Mariadb Cluster
# Install Mariadb and Galera Cluster to the new node, that is follow the steps at
#   1. and 2. At step 2.3. at the line starting with wsrep_cluster_address, add the 
#   IP of the new server too. Then start mariadb:
sudo systemctl start mariadb
# The new node is going to start replicating data, it may take some time depending
#   on the volume of the DBs. You can run following command and see the status of
#   the replication:
show status like 'wsrep_local_state_comment';
# When you see the value as "Synced", you can understand that the new node is 
#   replicated.
# You added the ip of the new node to the configuration of the new node only. Before,
#   it is too late, You need to add it to the other cluster member configuraitons too.
#   Otherwise, it would be very difficult to resolve if any cluster error occurs in 
#   the future.
# Run on other cluster members one by one:
sudo nano /etc/mysql/mariadb.conf.d/99-cluster.cnf
# At the line starting with wsrep_cluster_address, add the IP of the new server.
# Restart Mariadb after changing the configuration
sudo systemctl restart mariadb
# 
# 5.3. Removing a Node from Mariadb Cluster
# If you want to remove a node temporarily, it wouldn't be a problem. If you don't 
#   change any configurations on the cluster servers, it would join back to the 
#   cluster.
# If you want to remove a node permanently, a good way would be uninstall mariadb or
#   permanently poweroff the computer. And then, remove its ip from other servers'
#   /etc/mysql/mariadb.conf.d/99-cluster.cnf file and restart mariadb at the other
#   servers one by one.
#
# 5.4. Shutting Down the Cluster
# It is not advised to keep less than 2 nodes online. But if you really need to 
#   shutdown all the cluster (e.g. to physically move to somewhere else), or a 
#   total power failure occurs; you may try to shutdown server one at a time and 
#   when they are ready to start, first you have to turn on the last shutdowned node.
#   If the cluster doesn't go online, refer ro 6.
 

6. Recovery

# Mariadb Galera Cluster would run fine for a long time, as long as you keep at 
#   least 2 nodes alive and running. If you have 3 nodes, you can proceed on  
#   maintenance tasks (backup, upgrade etc) one at a time. 
# But problems are for humans (and computers). There might come one day and
#   cluster doesn't start. And when cluster doesn't start, Mariadb doesn't start
#   either.
# In that case, we need to restart the cluster, but we need to find the safe
#   node to start the cluster.
#
# 6.1. Finding the Safe Node - 1st Try
#   run the following command on every node:
sudo cat /var/lib/mysql/grastate.dat
# It's output will be something like below:
#_______________________________________________________
# GALERA saved state
version: 2.1
uuid:    2d878884-9ae6-11eb-955f-fa6fa258f122
seqno:   -1
safe_to_bootstrap: 0
#_______________________________________________________
# or
#_______________________________________________________
# GALERA saved state
version: 2.1
uuid: 886dd8da-3d07-11e8-a109-8a3c80cebab4
seqno: 31929
safe_to_bootstrap: 1
#_______________________________________________________
#
# If output in any node contains "safe_to_bootstrap: 1" or a positive value of 
#   "seqno: ", that means we can restart the cluster at that node. We have found
#   the safe node, proceed to 6.3.
# Otherwise, we keep trying to find the safe node.
#
# 6.2. Finding the Safe Node - 2nd Try
# !!! Run on all nodes !!!
sudo galera_recovery
# Output will be something like as below:
#___________________________________________________
WSREP: Recovered position 2d878884-9ae6-11eb-955f-fa6fa258f122:8
--wsrep_start_position=2d878884-9ae6-11eb-955f-fa6fa258f122:8
#___________________________________________________
#
# The node with the highest value after ":" will be our candidate to restart the
#   cluster. We have found the safe node.
# We need to set safe node to restart the cluster:
# !!! Run on the Safe Node !!!
sudo nano /var/lib/mysql/grastate.dat
# Change the line starting with "safe_to_bootstrap" as below:
#________________________________
safe_to_bootstrap: 1
#________________________________
#
# 6.3. Restart the Galera Cluster
# Run the following command at the safe node:
sudo galera_new_cluster
# After a while (1-2minutes) Run following command at the other nodes:
sudo systemctl restart mariadb
# The cluster is working again, we are done
# It would be wise to make a healthcheck as in 5.1. and see cluster is working.
 

Posted in Ubuntu

Active Directory On Ubuntu Server – 1

Posted on 28/03/2021 - 29/03/2021 by exforge

ADOnUbuntu1: Simple Active Directory Configuration on Ubuntu with Samba

Copyright (C) 2021 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0.Specs

# 0.1. Definition
# Single Domain Active Directory infrastructure with Ubuntu Servers with
#   2 Domain Controllers and a file server.
# Windows workstations can join to AD.
# No license costs except Windows workstation licences.
# Based on the valuable documents at:
https://www.server-world.info/en/note?os=Ubuntu_18.04&p=samba&f=4
# Relies on Samba software:
https://www.samba.org/
#
# 0.2. Configuration
# Domain Name: X11.XYZ
# Domain Netbios Name: X11
# First DC srv1.x11.xyz (Ubuntu Server 20.04)
# Second DC srv2.x11.xyz (Ubuntu Server 20.04)
# File Server: srvf.x11.xyz (Ubuntu Server 20.04)
# Windows workstations can connect to the domain
# 
# 0.3. Phases
# 0.3.1. Add First DC (Ubuntu 20.04 Server)
# 0.3.2. Add Additional DC (Ubuntu 20.04 Server)
# 0.3.3. AD User Management
# 0.3.4. Add a Linux File Server to the Domain (Ubuntu 20.04 Server)
# 0.3.5. Add a Windows Computer to the Domain
#
# 0.4. Preliminary Tasks
# There are some important matters to consider. Not complying them can cause
#   some problems.
# 0.4.1. Choosing Domain Name and Realm
# Actually Realm is in the domain name format and Domain Name is a single word
#   (actually Netbios Name of your domain).
# If your company's internet domain name is example.com, then you can choose your
#   Realm and Domain Name as following:
# Realm:	EXAMPLE.COM
# Domain Name:	EXAMPLE
# Whenever you use them, they must be UPPERCASE. Don't ask me why, that is 
#   something about Micros*ft's bad design.
#
# 0.4.2. IP Address and Host Name for Domain Controllers and Domain Members
# Domain Controllers and Domain Members should have static IP addresses. There 
#   might be some ways to use DHCP but I don't know how and actually I don't see 
#   any reason to use DHCP for Domain Controllers.
# Hostname's must be in the format of name.example.com (lowercase this time) and due
#   to an imcompatability with Samba and Ubuntu (Actually all Debian based Linuxes)
#   you have to erase the line starting with 127.0.1.1 (not 127.0.0.1) from your
#   /etc/hosts file and add the IP and hostname (short and long formats) in your 
#   /etc/hosts file as in below.
#
#____________________________________________
127.0.0.1	localhost
192.168.0.161	srv1.x11.xyz srv1
#____________________________________________
# 
# 0.4.3. Mixed Environment
# You should have at least 2 DCs (Domain Controllers), you can use 1 Wind*ws and
#   1 Linux to benefit from Micros*ft's AD Management programs. But actually it is 
#   not necessary. I'd advice to install all DCs as Linux and use any Wind*ws 
#   workstation to manage AD. You can install RSAT Management Tools to a Wind*ws
#   computer and use AD Manager programs (including DNS and WINS server) from there.
#
# 0.4.4. Default Values 
# Remember to replace all the occurences of X11, x11, X11.XYZ, and x11.xyz with yours,
#   regarding the cases. 
 

1. Add First Domain Controller

# 1.0. Specs
#   Domain Name:	X11
#   Realm: 		X11.XYZ	
#   Hostname:		srv1.x11.xyz
#   IP:		192.168.0.161
# 1.1. Install required packages
sudo apt update
sudo apt -y install samba krb5-config winbind smbclient 
#  Answers to parameter questions:
## Default Kerberos version 5 realm:
# In Capital Letters
#  X11.XYZ
## Kerberos servers for your realm:
#  srv1.x11.xyz
## Administrative server for your Kerberos realm:
#  srv1.x11.xyz
#
# 1.2. Configure Samba AD DC
# 1.2.1. Backup original samba configuration
sudo mv /etc/samba/smb.conf /etc/samba/smb.conf.original 
# 1.2.2. Run Samba Config
sudo samba-tool domain provision
#  Answers to parameter questions:
## Realm:
#  X11.XYZ
## Domain: 
#  X11
## Server Role (dc, member, standalone) [dc]: 
#  Just press enter
## DNS backend (SAMBA_INTERNAL,...
#  Just press enter
## DNS forwarder IP address...
#  Leave empty or enter 8.8.8.8
## Administrator password:
#  Enter a good password
#
# 1.2.3. Copy kerberos config file, stop and disable unnecessary services
sudo cp /var/lib/samba/private/krb5.conf /etc/
sudo systemctl stop smbd nmbd winbind systemd-resolved
sudo systemctl disable smbd nmbd winbind systemd-resolved
sudo systemctl unmask samba-ad-dc 
#
# 1.2.4. Remove resolv.conf and create a new one
sudo rm /etc/resolv.conf
sudo nano /etc/resolv.conf
# Fill as below
#____________________
domain x11.xyz
nameserver 127.0.0.1
#____________________
#
# 1.2.5. Start DC Services
sudo systemctl start samba-ad-dc
sudo systemctl enable samba-ad-dc 
#
# 1.3. Domain is OK
# 1.3.1. Check domain level
sudo samba-tool domain level show
# 1.3.2. Create a domain user
sudo samba-tool user create exforge
 

2. Add Additional DC

# 2.0. Specs
#   Domain Name:	X11
#   Realm: 		X11.xyz	
#   Hostname:		srv2.x11.xyz
#   IP:		192.168.0.162
#   Org. DC Hostname:	srv1.x11.xyz
#   Org. DC IP:	192.168.0.161
# 2.1. Get domain administrator's kerberos ticket
# 2.1.1. Install kerberos and edit conf
sudo apt -y install krb5-user
# Pass all the questions with enter
sudo nano /etc/krb5.conf 
#   Change the beginning of the file as below
#____________________________________
[libdefaults]
        default_realm = X11.XYZ
        dns_lookup_realm = false
        dns_lookup_kdc = true
#____________________________________
#
# 2.1.2. Stop and disable systemd.resolved
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved 
#
# 2.1.3. Remove resolv.conf and create a new one
sudo rm /etc/resolv.conf
sudo nano /etc/resolv.conf
#   Add following lines
#_____________________________________
domain x11.xyz
nameserver 192.168.0.161
#_____________________________________
#
# 2.1.4. Get Kerberos ticket
# Domain Admin password will be asked (Entered at 1.2.2.)
sudo kinit administrator
sudo klist
#
# 2.2. Add This DC to Existing AD
# 2.2.1. Add necessary packages 
sudo apt -y install samba winbind smbclient 
# 2.2.2. Rename and remove default samba config, create a new one
sudo mv /etc/samba/smb.conf /etc/samba/smb.conf.org 
sudo samba-tool domain join X11.XYZ DC -U "srv1\administrator" --dns-backend=SAMBA_INTERNAL 
# 2.2.3. Close and disable unnecessary services and enable samba
sudo systemctl stop smbd nmbd winbind
sudo systemctl disable smbd nmbd winbind
sudo systemctl unmask samba-ad-dc
sudo systemctl start samba-ad-dc
sudo systemctl enable samba-ad-dc
#
# 2.2.4. Verify Authentication to localhost
sudo smbclient //127.0.0.1/netlogon -U Administrator -c 'ls'
# 2.2.5. Verify replication status with AD
sudo samba-tool drs showrepl
#   Following warning is not important you can ignore it:
#       Warning: No NC replicated for Connection!
 

3. AD User Management

# You can run on any DC
# 3.1. Display domain users list.
sudo samba-tool user list
# 
# 3.2. Add a domain user.
sudo samba-tool user create ubuntu
# 
# 3.3. Delete a domain user.
sudo samba-tool user delete ubuntu
# 
# 3.4. Reset password for a user.
sudo samba-tool user setpassword ubuntu
# 
# 3.5. Set expiry for a user.
sudo samba-tool user setexpiry ubuntu --days=7
# 
# 3.6. Disable/Enable user account.
sudo samba-tool user disable ubuntu
sudo samba-tool user enable ubuntu
# 
# 3.7. Display domain groups list.
sudo samba-tool group list
# 
# 3.8. Display members in a group.
sudo samba-tool group listmembers "Domain Users"
# 
# 3.9. Add a domain group.
sudo samba-tool group add ServerWorld
# 
# 3.10. Delete a domain group.
sudo samba-tool group delete ServerWorld
# 
# 3.11. Add/remove a member from a domain group.
sudo samba-tool group addmembers ServerWorld ubuntu
sudo samba-tool group removemembers ServerWorld ubuntu
 

4. Add Linux File Server to the Domain (Ubuntu 20.04 Server)

# 4.0. Specs
#   Domain Name:	X11
#   Realm: 		X11.XYZ	
#   Hostname:		srvf.x11.xyz
#   IP:		192.168.0.163
#   Org. DC Hostname:	srv1.x11.xyz
#   Org. DC IP:	192.168.0.161
# 4.1. Install necessary packages
sudo apt -y install winbind libpam-winbind libnss-winbind krb5-config samba-dsdb-modules samba-vfs-modules 
#  Answers to parameter questions:
## Default Kerberos version 5 realm:
#  X11.XYZ
## Kerberos servers for your realm:
#  srv1.x11.xyz
## Administrative server for your Kerberos realm:
#  srv1.x11.xyz
#
# 4.2. Configure Winbind
# 4.2.1. Samba config
sudo nano /etc/samba/smb.conf 
#   Change/add following lines under [global] stanza
#___________________________________________
   workgroup = X11
   realm = X11.XYZ
   security = ads
   idmap config * : backend = tdb
   idmap config * : range = 3000-7999
   idmap config X11 : backend = rid
   idmap config X11 : range = 10000-999999
   template homedir = /home/%U
   template shell = /bin/bash
   winbind use default domain = true
   winbind offline logon = false
#___________________________________________
#
# 4.2.2. nsswitch config
sudo nano /etc/nsswitch.conf
#   Change/add following lines
#___________________________________________
passwd:     compat systemd winbind
group:     compat systemd winbind
#___________________________________________
#
# 4.2.3. pam config
sudo nano /etc/pam.d/common-session 
#   Add following line
#___________________________________________
session optional        pam_mkhomedir.so skel=/etc/skel umask=077
#___________________________________________
#
# 4.2.4. Change DNS to AD
#   You have to change your DNS server to first DC and second DC
sudo nano /etc/netplan/01-netcfg.yaml 
# If that file is not there, use the existing file instead
#   Change as below
#____________________________________________
      nameservers:
        addresses: [192.168.0.161,192.168.0.162]
#____________________________________________
#
# 4.2.5. Restart network settings
sudo netplan apply
#
# 4.3. Join AD
# 4.3.1. Add this server to AD 
sudo net ads join -U Administrator
# Restart winbind
sudo systemctl restart winbind
# 4.3.2. Show Domain Users
sudo wbinfo -u
#
# 4.4. Config File Server
# 4.4.0. Specs
#   There will be 4 shares: 
#     /srv/share1: Test1 AD Group full access
#     /srv/share2: Test2 AD Group full access
#     /srv/share3: Domain Users AD Group read only access
#     /srv/share4: Domain Admins AD Group full access
#   I assume that these folders are created on srvf (this server)
#
# 4.4.1. Install Samba
sudo apt -y install samba
# 4.4.2. Configure samba for AD file server
sudo nano /etc/samba/smb.conf
#   Add following lines under [global]  stanza
#___________________________________________________________________
   netbios name = srvf         
   socket options = TCP_NODELAY SO_RCVBUF=16384 SO_SNDBUF=16384         
   idmap uid = 10000-20000         
   winbind enum users = yes         
   winbind gid = 10000-20000         
   os level = 20         
   winbind enum groups = yes         
   socket address = ip of your ads server         
   password server = *         
   preferred master = no         
   winbind separator = +         
   encrypt passwords = yes         
   dns proxy = no         
   wins server = 192.168.0.161         
   wins proxy = no  
#___________________________________________________________________
#   Add following lines at the end of the file
#     !!! Remember to change them according to your shares !!!
#___________________________________________________________________
[share1]         
   comment = Share1         
   path = /srv/share1         
   browseable = yes         
   read only = no         
   inherit acls = yes         
   inherit permissions = yes         
   create mask = 770         
   directory mask = 770         
   valid users = @"X11+Test1"  
   admin users = @"X11+Domain Admins"  
[share2]         
   comment = Share2         
   path = /srv/share2         
   browseable = yes         
   read only = no         
   inherit acls = yes         
   inherit permissions = yes         
   create mask = 770         
   directory mask = 770         
   valid users = @"X11+Test2"  
   admin users = @"X11+Domain Admins"  
[share3]         
   comment = Share3         
   path = /srv/share3         
   browseable = yes         
   read only = yes         
   valid users = @"X11+Domain Users"  
   admin users = @"X11+Domain Admins"  
[share4]         
   comment = Share4         
   path = /srv/share4         
   browseable = yes         
   read only = no         
   inherit acls = yes         
   inherit permissions = yes         
   create mask = 770         
   directory mask = 770         
   valid users = @"X11+Domain Admins"  
   admin users = @"X11+Domain Admins"  
#___________________________________________________________________
# 4.4.3. Restart Samba
sudo systemctl restart smbd
 

5. Add Windows Computers to the Domain

# 5.1. Change Windows computer's DNS setting to first DC
#    and proceed as usual
# 5.2. AD (including the DNS server on DC) could be managed through windows 
#    workstation after installing RSAT management.
# 5.3. You can connect to the file server using \\srvf\share1 (share2,3,4)
#    notation from your workstation.
 

Posted in Ubuntu

ISPmail On Ubuntu

Posted on 22/03/2021 - 25/03/2021 by exforge

ISPmail Tutorial for Ubuntu 20.04 Server

Copyright (C) 2021 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0. Specs

# Fully functional mail server with all open source components. 
#
# Very highly based on ISPmail tutorials of Christoph Haas
https://workaround.org/ispmail
# with some additions from ISP Mail Tutorial (Caramel Edition) of Alexey Abel
https://123qwe.com/
#
# Please consider, all the hard work is done by Christoph Haas, I am just an
#   implementer.
#
# To guarantee everything works, you need to implement the tutorial on a freshly
#   installed Ubuntu 20.04 version. It is tested on freshly installed Ubuntu 20.04 and
#   Debian 10 (Buster) systems. But of course you are free to use it as you wish.
#
#
# Ubuntu 20.04 LTS  	(Debian Buster Originally)
# Postfix		SMTP
# rspamd		spam check
# Dovecot		POP3 & IMAP
# Roundcube		Webmail
# MariaDB		DB Engine
#
# 'mailadmin'@'localhost':
#       MariaDB admin user for mail database with pw: "Password12"
#       This user will be used to administrate database
# 'mailserver'@'127.0.0.1':
#       MariaDB normal user for mail database with pw: "Password34"
#       This user will be used by Postfix and Dovecot
# !!! Do not forget to change the passwords with strong ones !!!
# 
# # Hostname:		mail.x11.xyz
# Domains:		x11.xyz u11.xyz v11.xyz
# There must be an A record for mail.x11.xyz with the IP of your 
#    server. 
# 
# Replace with your server and domains with those names.
# It would be wise to choose your main mail domain and name your server
#   as mail.domain.com
 

1.Preliminary Tasks

# I am sure (almost) everything is possible with sudo, but for this tutorial only
#   I prefer becoming root and processing that way. It is just because the original
# document uses root.
# 1.1. Become root
sudo -i
# 1.2. Update and Upgrade
apt update
apt upgrade
#
# 1.3. Install packages
# MariaDB server
apt install mariadb-server
# Postfix - Select Internet Site and mail.x11.xyz (your hostname)
apt install postfix postfix-mysql
# Apache and PHP
apt install apache2 php
# rspamd
apt install rspamd
# Certbot, for ssl certificates
apt install certbot
# Dovecot
apt install dovecot-mysql dovecot-pop3d dovecot-imapd dovecot-managesieved dovecot-lmtpd
# Adminer, a replacement of phpmyadmin, for managing DB
apt install adminer
# default set of certificates of common certificate authorities.
#   Don't mind if already installed.
apt install ca-certificates
 

2.Configuring Apache Server and Certificates

# 2.1. Create a home for mail.x11.xyz and change owner
mkdir /var/www/mail.x11.xyz
chown www-data:www-data /var/www/mail.x11.xyz
#
# 2.2. Remove apache default confs if you don't already use it
rm /etc/apache2/sites-enabled/*
#
# 2.3. Create conf file for mail.x11.xyz
nano /etc/apache2/sites-available/mail.x11.xyz-http.conf 
#____________________________________________
<VirtualHost *:80>
  ServerName mail.x11.xyz
  DocumentRoot /var/www/mail.x11.xyz
</VirtualHost>
#____________________________________________
#
# 2.4. Enable our new conf
a2ensite mail.x11.xyz-http.conf
#
# 2.5. Reload apache to make the config active
systemctl reload apache2
#
# 2.6. Create a test file to check web server
echo "Just a test" > /var/www/mail.x11.xyz/test
#
# 2.7. For a test, on your browser, go to the following adres
http://mail.x11.xyz/test
# If you see Just a test, then everything is fine up to now
# Now we need to get our ssl certificates
#
# 2.8. Get SSL Certificates form letsencrypt.org using certbot
certbot certonly --webroot --webroot-path /var/www/mail.x11.xyz -d mail.x11.xyz
#   You will be asked to give your email, don't hesitate giving it. You must 
#      agree their Terms of Service with A. Then you'll be asked to share your
#      email with EFF (Organization that owns LetsEncrytp, I advise you to say Y
#
#   If everything goes well there will be 4 files on 
#       /etc/letsencrypt/archive/mail.x11.xyz
#   cert.pem : the certificate file
#   chain.pem : the chaining or intermediate certificate
#   fullchain.pem : cert.pem and chain.pem combined
#   privkey.pem : Your site's private key. Keep it secret (I mean it)
#   There might be some numbers at the names of the file. Do not mind them.
# Certificates will expire in 3 months but certbot will renew them automatically
#
# 2.9. Adding https to our site
#   Create a conf for https site and enable it
nano /etc/apache2/sites-available/mail.x11.xyz-https.conf
#_________________________________________________________________________
<VirtualHost *:443>
 ServerName mail.x11.xyz
 DocumentRoot /var/www/mail.x11.xyz
 SSLEngine on
 SSLCertificateFile /etc/letsencrypt/live/mail.x11.xyz/fullchain.pem
 SSLCertificateKeyFile /etc/letsencrypt/live/mail.x11.xyz/privkey.pem
</VirtualHost>
#_________________________________________________________________________     
#
#   Enable ssl module and the new conf, restart apache2
a2enmod ssl
a2ensite mail.x11.xyz-https
systemctl restart apache2
#
# 2.10. Redirect http to https
#   We want http://mail.x11.xyz to redirect https://mail.x11.xyz
#      unless letsencrypt checking /well-known/acme-challenge directory
#   Add redirect lines to just before the end of the http conf (before
#     </VirtualHost>
nano /etc/apache2/sites-available/mail.x11.xyz-http.conf
#_______________________________________________________
  RewriteEngine On
  RewriteCond %{REQUEST_URI} !.well-known/acme-challenge
  RewriteRule ^(.*)$ https://%{SERVER_NAME}$1 [R=301,L]
#_______________________________________________________
#
#   Enable rewrite mode and restart apache
a2enmod rewrite
systemctl restart apache2
#
# 2.11. Automatic Ceritificate Renewal
# Certbot automatically renews the certificates, if any error happens
#    we will be warned by an email. But there is a small issue; when the 
#    certificate renewed postfix, dovecot and apache services on our server
#    must be restarted. Therefore we will add an post-hook to certbot to do it.
# Create a file on the place certbot looks for running
nano /etc/letsencrypt/renewal-hooks/deploy/reloadall.sh
#___________________________________________________________________________
#!/bin/bash
systemctl reload apache2
systemctl reload postfix
systemctl reload dovecot
#___________________________________________________________________________
#Make it executable
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reloadall.sh
 

3. Preparing The Database

# 3.1. Setting up Adminer
#   Adminer will be our Mariadb Management tool
#   It will be added to our web site. Can be added just after <VirtualHost>
nano /etc/apache2/sites-available/mail.x11.xyz-https.conf
#___________________________________________
 Alias /adminer /usr/share/adminer/adminer
#___________________________________________
# Reload apache
systemctl restart apache2
# You can see its login screen at:
https://mail.x11.xyz/adminer
# But it is not ready yet
#
# 3.2. Create DB and DB Users
#   Enter Mariadb shell  (you can use exit to return to unix shell)
mariadb
#   Create the database
CREATE DATABASE mailserver;
#   Create Database Users
grant all on mailserver.* to 'mailadmin'@'localhost' identified by 'Password12';
grant select on mailserver.* to 'mailserver'@'127.0.0.1' identified by 'Password34';
# Now you can login to adminer with mailadmin user
#
# 3.3. Creating DB Tables
# There will be 3 tables in our DB:
#   virtual_domains: Domains we host
#   virtual_users: mail users for domains
#   virtual_aliases: alias definition for users
# 3 create table commands must be run on Mariadb shell:
#   First connect to our DB
USE mailserver;
#   virtual_domains:
CREATE TABLE IF NOT EXISTS `virtual_domains` (
 `id` int(11) NOT NULL auto_increment,
 `name` varchar(50) NOT NULL,
 PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#   virtual_users:
CREATE TABLE IF NOT EXISTS `virtual_users` (
 `id` int(11) NOT NULL auto_increment,
 `domain_id` int(11) NOT NULL,
 `email` varchar(100) NOT NULL,
 `password` varchar(150) NOT NULL,
 `quota` bigint(11) NOT NULL DEFAULT 0,
 PRIMARY KEY (`id`),
 UNIQUE KEY `email` (`email`),
 FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 #   virtual_aliases:
 CREATE TABLE IF NOT EXISTS `virtual_aliases` (
 `id` int(11) NOT NULL auto_increment,
 `domain_id` int(11) 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;
 

4. Postfix – MariaDB Integration

# 4.1. Connect Domains
#   Create a config file and fill it, remember changing password
nano /etc/postfix/mysql-virtual-mailbox-domains.cf
#_______________________________________________________
user = mailserver
password = Password34
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'
#_______________________________________________________
#   Add this mapping to postfix
postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
#
# 4.2. Connect Virtual Mailboxes
#   Create a config file and fill it, remember changing password
nano /etc/postfix/mysql-virtual-mailbox-maps.cf
#_______________________________________________________
user = mailserver
password = Password34
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_users WHERE email='%s'
#_______________________________________________________
#   Add this mapping to postfix
postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
#
# 4.3. Connect Virtual Aliases
#   Create a config file and fill it
nano /etc/postfix/mysql-virtual-alias-maps.cf
#_______________________________________________________
user = mailserver
password = Password34
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'
#_______________________________________________________
#   Add this mapping to postfix
postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf
#
# 4.4. Catch-all Aliases
nano /etc/postfix/mysql-email2email.cf
#_______________________________________________________
user = mailserver
password = Password34
hosts = 127.0.0.1
dbname = mailserver
query = SELECT email FROM virtual_users WHERE email='%s'
#_______________________________________________________
#   Add this mapping to postfix
postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf,mysql:/etc/postfix/mysql-email2email.cf
#
# 4.5. Fix Permissions on Files
chgrp postfix /etc/postfix/mysql-*.cf
chmod u=rw,g=r,o= /etc/postfix/mysql-*.cf
 

5. Dovecot Setup

# Dovecot will:
#   Get emails from Postfix
#   Execute user filters to move emails to different folders
#   Allow users to fetch mail using POP3 or IMAP
# 5.1. Create a user and a group named vmail, make /var/vmail its home
groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /var/vmail -m
#   If in any case /var/vmail already exists, change its owner
chown -R vmail:vmail /var/vmail
#
# 5.2. Authentication Configuration
nano /etc/dovecot/conf.d/10-auth.conf
# Around line 100, change as below
auth_mechanisms = plain login 
# At the end of the file
#    Comment line: !include auth-system.conf.ext
#    Uncomment line: #!include auth-sql.conf.ext
# Final state will be like following:
#_______________________________________________________
#!include auth-system.conf.ext
!include auth-sql.conf.ext
#!include auth-ldap.conf.ext
#!include auth-passwdfile.conf.ext
#!include auth-checkpassword.conf.ext
#!include auth-vpopmail.conf.ext
#!include auth-static.conf.ext
#_______________________________________________________
#
# 5.3. Mail Configuration
nano /etc/dovecot/conf.d/10-mail.conf
# Around line 30, change the line as below:
#______________________________________
mail_location = maildir:~/Maildir
#______________________________________
# Around line 218, uncomment and change as below
#______________________________________
mail_plugins = quota
#______________________________________
#
# 5.4. Master Configuration
nano /etc/dovecot/conf.d/10-master.conf
# Around lines 106-109, uncomment and change like follows:
#_____________________________________________________
# Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
#_____________________________________________________
#
# 5.5. SSL Configuration
nano /etc/dovecot/conf.d/10-ssl.conf
# Around line 6 change as below:
#___________________
ssl = required
#___________________
# Around line 12-13 change as following: 
#    (remember to change to your domain)
#__________________________________________________________________
ssl_cert = </etc/letsencrypt/live/mail.x11.xyz/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.x11.xyz/privkey.pem
#__________________________________________________________________
#
# 5.6. SQL Configuration
nano /etc/dovecot/dovecot-sql.conf.ext
# Add to the end of the file: (Remember to enter your own password)
driver = mysql
connect = host=127.0.0.1 dbname=mailserver user=mailserver password=Password34
user_query = SELECT email as user, \
  concat('*:bytes=', quota) AS quota_rule, \
  '/var/vmail/%d/%n' AS home, \
  5000 AS uid, 5000 AS gid \
  FROM virtual_users WHERE email='%u'
password_query = SELECT password FROM virtual_users WHERE email='%u'
iterate_query = SELECT email AS user FROM virtual_users
#
# 5.7. Secure SQL config file
#  This file has some sensitive information, we have to secure it
chown root:root /etc/dovecot/dovecot-sql.conf.ext
chmod go= /etc/dovecot/dovecot-sql.conf.ext
#
# 5.8. Restart Dovecot
systemctl restart dovecot
 

6. Postfix – Dovecot Connection

# 6.1. Connect Dovecot to Postfix using LMTP
nano /etc/dovecot/conf.d/10-master.conf
# Around lines 54-57, change like below
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    group = postfix
    mode = 0600
    user = postfix
  }
}
#
# 6.2. Enable LMTP at Postfix too
postconf virtual_transport=lmtp:unix:private/dovecot-lmtp
#
# 6.3. Enable Server Side Mail Rules (Sieves)
nano /etc/dovecot/conf.d/20-lmtp.conf
# Around the end uncomment and change as below:
#______________________________________
mail_plugins = $mail_plugins sieve
#______________________________________
#
# 6.4. Restart Dovecot and Postfix
systemctl restart dovecot postfix
 

7. Mail Quotas

# Dovecot needs to keep track of user quotas and Postfix needs to reject 
#   email if the user's mailbox exceed her quota.
# 7.1. Setting Dovecot Quota Policy
nano /etc/dovecot/conf.d/90-quota.conf
# Select 1 amongst several plugin {…} sections, and make it look like below:
#______________________________________________________
plugin {
  quota = maildir:User quota
  quota_status_success = DUNNO
  quota_status_nouser = DUNNO
  quota_status_overquota = "452 4.2.2 Mailbox is full and cannot receive any more emails"
}
#______________________________________________________
# Add a new section (preferably at the end) like below:
#______________________________________________________
service quota-status {
  executable = /usr/lib/dovecot/quota-status -p postfix
  unix_listener /var/spool/postfix/private/quota-status {
    user = postfix
  }
}
#______________________________________________________
# Restart Dovecot
systemctl restart dovecot
#
# 7.2. Postfix must know when quota exceeds
#   Add to postfix conf by running the below command
postconf "smtpd_recipient_restrictions = \
     reject_unauth_destination \
     check_policy_service unix:private/quota-status"
#
# 7.3. Automatic Quota Warning emails
nano /etc/dovecot/conf.d/90-quota.conf
#   Add following sections
#_______________________________________________________________
plugin {
   quota_warning = storage=95%% quota-warning 95 %u
   quota_warning2 = storage=80%% quota-warning 80 %u
   quota_warning3 = -storage=100%% quota-warning below %u
}
service quota-warning {
   executable = script /usr/local/bin/quota-warning.sh
   unix_listener quota-warning {
     group = dovecot
     mode = 0660
   }
}
#_______________________________________________________________
#   /usr/local/bin/quota-warning.sh is referred here, we have to create it
#
# 7.4. Configure Sending quota warning emails
#   Create a shell file and fill it, remember changing from email
nano /usr/local/bin/quota-warning.sh
#___________________________________________________________________________
#!/bin/sh
PERCENT=$1
USER=$2
cat << EOF | /usr/lib/dovecot/dovecot-lda -d $USER -o "plugin/quota=maildir:User quota:noenforcing"
From: postmaster@x11.org
Subject: Quota warning - $PERCENT% reached
Your mailbox can only store a limited amount of emails.
Currently it is $PERCENT% full. If you reach 100% then
new emails cannot be stored. Thanks for your understanding.
EOF
#___________________________________________________________________________
#   Make the shell file executable
chmod +x /usr/local/bin/quota-warning.sh
#
# 7.5. Recalculate quota
#   If you manually remove from a user's Maildir, quota calculations are mixep
#     up. To force Dovecot to recalculate users quota run:
doveadm quota recalc -u user@example.org
# 7.6. Restart Dovecot and Postfix
systemctl restart dovecot postfix
 

8. Roundcube as Webmail

# 8.1. Install Roundcube
apt install roundcube roundcube-plugins roundcube-plugins-extra roundcube-mysql
# Answer yes to first question and give an empty password. If it asks for database,
#   select mysql.
#
# 8.2. Configure Apache for Roundcube
nano /etc/apache2/sites-available/mail.x11.xyz-https.conf
#  Change DocumentRoot line as below:
#_________________________________________
 DocumentRoot /var/lib/roundcube
#_________________________________________
# Add following line just after the <VirtualHost> line
#_________________________________________
 Include /etc/roundcube/apache.conf
#_________________________________________
# Restart Apache
systemctl restart apache2
#
# 8.3. Configure Roundcube
nano /etc/roundcube/config.inc.php
#  Around line 36 change as below
#____________________________________________________-
$config['default_host'] = 'tls://mail.x11.xyz';
#____________________________________________________-
#  Around lines 48 & 51  change as below
#____________________________________________________-
$config['smtp_server'] = 'tls://mail.x11.xyz';
$config['smtp_port'] = 587;
#____________________________________________________-
#  Around lines 76-77 change as below
#____________________________________________________-
$config['plugins'] = array(
     'managesieve',
     'password',
 );
#____________________________________________________-
#
# 8.4. Configure Password Plugin
#   This plugin will let the user change her password
nano /etc/roundcube/plugins/password/config.inc.php
#  Clear the $config=array(); line fill it as below just before ?>
#    At the line 1 before the end, remember to enter your mailadmin password
#__________________________________________________________
$config['password_driver'] = 'sql';
$config['password_minimum_length'] = 12;
$config['password_force_save'] = true;
$config['password_algorithm'] = 'dovecot';
$config['password_dovecotpw'] = '/usr/bin/doveadm pw -s BLF-CRYPT';
$config['password_dovecotpw_method'] = 'BLF-CRYPT';
$config['password_dovecotpw_with_method'] = true;
$config['password_db_dsn'] = 'mysql://mailadmin:Password12@localhost/mailserver';
$config['password_query'] = "UPDATE virtual_users SET password=%D WHERE email=%u";
#__________________________________________________________
#
# 8.5. Configure Sieve (Mail Rules) Plugin
#  Mail rules will be handled by Roundcube, so config is easy
nano /etc/roundcube/plugins/managesieve/config.inc.php
#  Clear the $config=array(); line fill it as below just before ?>
#_________________________________________
$config['managesieve_host'] = 'localhost';
#_________________________________________
#
# 8.6. Restart Dovecot
systemctl restart dovecot
# Just a quick reminder: When you login to webmail, you have to use your full email
 

9. Configure Postfix to Send Mail

# 9.1. Configure Postfix to use Dovecot authentication
postconf smtpd_sasl_type=dovecot
postconf smtpd_sasl_path=private/auth
postconf smtpd_sasl_auth_enable=yes
#
# 9.2. Enable encryption (Remember to change to your domain)
postconf smtpd_tls_security_level=may
postconf smtpd_tls_auth_only=yes
postconf smtpd_tls_cert_file=/etc/letsencrypt/live/mail.x11.xyz/fullchain.pem
postconf smtpd_tls_key_file=/etc/letsencrypt/live/mail.x11.xyz/privkey.pem
postconf smtp_tls_security_level=may
#
# 9.3. Enable Postfix for port 587 smtp for end users 
nano /etc/postfix/master.cf
# Around lines 17-28 change as below (remove comments)
#______________________________________________________________________
submission inet n       -       y       -       -       smtpd
 -o syslog_name=postfix/submission
 -o smtpd_tls_security_level=encrypt
 -o smtpd_sasl_auth_enable=yes
 -o smtpd_tls_auth_only=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
#______________________________________________________________________
#
# 9.4. Protect against forged sender addresses
postconf smtpd_sender_login_maps=mysql:/etc/postfix/mysql-email2email.cf
#
# 9.5. Restart Postfix
systemctl restart postfix
 

10. Handling Spam with rspamd

# 10.1. Configuring postfix to use rspamd
postconf smtpd_milters=inet:127.0.0.1:11332
postconf non_smtpd_milters=inet:127.0.0.1:11332
postconf milter_mail_macros="i {mail_addr} {client_addr} {client_name} {auth_authen}"
#
# 10.2. Adding rspamd headers to emails
# Create a new conf file
nano /etc/rspamd/override.d/milter_headers.conf
# And put the below line in it
#____________________________
extended_spam_headers = true;
#____________________________
# Restart rspamd
systemctl restart rspamd
#
# 10.3. Sending spam mails to Junk folder
nano /etc/dovecot/conf.d/90-sieve.conf
# Around line 83 add a new line with indent
#_____________________________________
  sieve_after = /etc/dovecot/sieve-after
#_____________________________________
# Make the directory to put the rule
mkdir /etc/dovecot/sieve-after
# Add a new file and fill it like below
nano /etc/dovecot/sieve-after/spam-to-folder.sieve
#_________________________________________
require ["fileinto","mailbox"];
if header :contains "X-Spam" "Yes" {
 fileinto :create "Junk";
 stop;
}
#_________________________________________
# Compile the file to make Dovecot use it
sievec /etc/dovecot/sieve-after/spam-to-folder.sieve
# Restart dovecot and rspamd
systemctl restart dovecot rspamd
# 
# 10.4. Spam Detection Training
# To use autolearning feature of rspamd
#  create a new conf file
nano /etc/rspamd/override.d/classifier-bayes.conf
#  put the next line in it
#___________________________
autolearn = true;
#___________________________
# 
# To enable per user training
#  create a new conf file
nano /etc/rspamd/local.d/classifier-bayes.conf 
#  put the next line in it
#___________________________
users_enabled = true;
#___________________________
#
# 10.5 Spam learning from user actions
# When users move an email to junk folder, rspamd will learn from it
# Edit conf file to enable IMAPSieve
nano /etc/dovecot/conf.d/20-imap.conf
# Find the line with mail_plugins around line 93, uncomment and change it as below
#_____________________________________________________
mail_plugins = $mail_plugins imap_sieve
#_____________________________________________________
#
# Edit Dovecot Sieve conf
nano /etc/dovecot/conf.d/90-sieve.conf
# Put the following lines just before the end of the file, remember indenting
#_____________________________________________________
  # From elsewhere to Junk folder
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY
  imapsieve_mailbox1_before = file:/etc/dovecot/sieve/learn-spam.sieve
  # From Junk folder to elsewhere
  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/etc/dovecot/sieve/learn-ham.sieve
  sieve_pipe_bin_dir = /etc/dovecot/sieve
  sieve_global_extensions = +vnd.dovecot.pipe
  sieve_plugins = sieve_imapsieve sieve_extprograms
#_____________________________________________________
#
# Now we need to create Sieve scripts for Dovecot
# Create a new directory for our files
mkdir /etc/dovecot/sieve
# Create learn spam sieve file
nano /etc/dovecot/sieve/learn-spam.sieve
# Fill it as below
#_____________________________________________________
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
pipe :copy "rspamd-learn-spam.sh";
#_____________________________________________________
# Create learn ham (nospam) sieve file
nano /etc/dovecot/sieve/learn-ham.sieve
# Fill it as below
#_____________________________________________________
require ["vnd.dovecot.pipe", "copy", "imapsieve", "variables"];
if string "${mailbox}" "Trash" {
  stop;
}
pipe :copy "rspamd-learn-ham.sh";
#_____________________________________________________
#
# Compile both scripts, compiled files will have svbin extension
sievec /etc/dovecot/sieve/learn-spam.sieve
sievec /etc/dovecot/sieve/learn-ham.sieve
# Fix permissions for these files
chmod u=rw,go= /etc/dovecot/sieve/learn-{spam,ham}.{sieve,svbin}
chown vmail.vmail /etc/dovecot/sieve/learn-{spam,ham}.{sieve,svbin}
# Create shell scripts for spam and ham learning
# Create spam learning script
nano /etc/dovecot/sieve/rspamd-learn-spam.sh
# And fill it as below
#_____________________________________________________
#!/bin/sh
exec /usr/bin/rspamc learn_spam
#_____________________________________________________
# Create ham learning script
nano /etc/dovecot/sieve/rspamd-learn-ham.sh
# And fill it as below
#_____________________________________________________
#!/bin/sh
exec /usr/bin/rspamc learn_ham
#_____________________________________________________
# Fix their permissions
chmod u=rwx,go= /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh
chown vmail.vmail /etc/dovecot/sieve/rspamd-learn-{spam,ham}.sh
#
# 10.6. Configure Dovecot to autodelete Junk and Trash folders after 30 days
nano /etc/dovecot/conf.d/15-mailboxes.conf 
# Add just 1 line before the end
#___________________________
  mailbox Junk {
    special_use = \Junk
    auto = subscribe
    autoexpunge = 30d
  }
  mailbox Trash {
    special_use = \Trash
    auto = subscribe
    autoexpunge = 30d
  }
#___________________________
# Restart dovecot and rspamd
systemctl restart dovecot rspamd
#
# 10.7. Rspamd Web Interface
# rspamd comes with a web interface, it can be reached from localhost only
#  from port 11334.
# By using http proxy modules of Apache, we can reach it from outside
# Enable Apache modules HTTP proxy and rewrite
a2enmod proxy_http
a2enmod rewrite
# Add to our website conf file for enabling rpamd web interface
nano /etc/apache2/sites-available/mail.x11.xyz-https.conf 
# Add somewhere between VirtualHost tags
#_______________________________________________________________
 <Location /rspamd>
   Require all granted   
 </Location>
 RewriteEngine On
 RewriteRule ^/rspamd$ /rspamd/ [R,L]
 RewriteRule ^/rspamd/(.*) http://localhost:11334/$1 [P,L]
#_______________________________________________________________
# Restart apache2
systemctl restart apache2
# Now we can reach it from the following address:
https://mail.x11.xyz/rspamd
# But the interface is password protected, we need to give a password to it
#  and we'll save the password in a hashed format
#
# Determine a password
# Create the password hash with the following command:
rspamadm pw
# Write your password at Enter passphrase: prompt
# The command's output will be your hashed password
# Now we need to put this hashed password in a config file
# Create the config file
nano /etc/rspamd/local.d/worker-controller.inc
# Fill it as in below, put the hashed pw between double quotes
#_______________________________________________________________
password = "$2$icoahes75e7g9wxapnrbmqnpuzjoq7z…"
#_______________________________________________________________
# Restart rspamd and apache2
systemctl restart rspamd apache2
#
# 10.8. Privacy Warning
# Rspamd uses so called fuzzy feeds for spam detection, that is it contacts its 
#   server with your data.
# Rspamd is not completely free, if 1 of the 2 following conditions are  met, 
#   you need a license
# 1. Using commercial data
# 2. More than 500.000 spam scans a day
 

Break Time

# I skipped the sections about malware protection. I believe it is not necessary
#   for my installations. If you'd like to enable it, please refer to the 
#   original document: 
https://workaround.org/ispmail/buster/blocking-malware/
 

11. Adding DNS Records

# Assuming your host already has a DNS A Record; for every domain 
#   to be hosted, you need to create MX records pointing to
#   your hostname.
#
# In my case, my hostname is mail.x11.xyz, x11.xyz, u11.xyz and
#   v11.xyz will be hosted. So for all domains there must be an MX
#   record pointing to mail.x11.xyz with a small priority number (say 10)
# We also need spf and dmarc DNS entries to ensure no other mail servers can 
#   send emails using our domain names.
#
# In my configuration, mail.x11.xyz is the mail server and it hosts 3 domains:
#   x11.xyz, u11.xyz and v11.xyz.
# My DNS servers will have following records
#
# DNS Server for x11.xyz
# A Record --> 195.181.240.41 mail.x11.xyz
# MX Record --> 10 mail.x11e.xyz
# TXT Record --> @ v=spf1 mx -all
# TXT Record --> _dmarc v=DMARC1; aspf=s; adkim=s; pct=100; p=reject; rua=mailto:postmaster@x11.xyz
#
# DNS Server for u11.xyz
# MX Record --> 10 mail.x11.xyz
# TXT Record --> @ v=spf1 mx -all
# TXT Record --> _dmarc v=DMARC1; aspf=s; adkim=s; pct=100; p=reject; rua=mailto:postmaster@x11.xyz
#
# DNS Server for v11.xyz
# MX Record --> 10 mail.x11.xyz
# TXT Record --> @ v=spf1 mx -all
# TXT Record --> _dmarc v=DMARC1; aspf=s; adkim=s; pct=100; p=reject; rua=mailto:postmaster@x11.xyz
 

12. Preventing Spoofing with DKIM

# With DKIM, we can sign our emails with a certificate and the other mail
#   servers can make sure that the emails coming from us are legitimate.
# We need to create keypairs (private and public) for all the domains we
#   host, put private keys to rspamd and public keys to DNS records. 
# When we create a keypair, we use a selector (or a sequence number) to
#   add a date record to it. In this case, when we create another keypair
#   later, we can distinguish them and older mails wouldn't be invalidated.
#
# 12.1. Create a home for our DKIM keypairs and fix permissions
mkdir /var/lib/rspamd/dkim
chown _rspamd:_rspamd /var/lib/rspamd/dkim
#
# 12.2. Enable DKIM maps in rspamd
# Create a new rspamd conf file for dkim maps
nano /etc/rspamd/local.d/dkim_signing.conf
# Put the below lines in it
#______________________________________________________
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
#______________________________________________________
# We will put our maps in this /etc/rspamd/dkim_selectors.map file later
#
# 12.3. Create keypairs for all the domains we host
# This step must be followed for all the domains we host. I'll do this
#   for x11.xyz, u11.xyz and v11.tk 
# Save the outputs of all commands somewhere.
# Create keypair (-s option is for the selector)
rspamadm dkim_keygen -d x11.xyz -s 20210320 -k /var/lib/rspamd/dkim/x11.xyz.20210320.key
rspamadm dkim_keygen -d u11.xyz -s 20210320 -k /var/lib/rspamd/dkim/u11.xyz.20210320.key
rspamadm dkim_keygen -d v11.xyz -s 20210320 -k /var/lib/rspamd/dkim/v11.xyz.20210320.key
#
# The output will be used for DNS records. If you have your own DNS server, you can put
#   it as it is to the DNS server. But if you use cloudfare or a similar
#   service like me, you can use their web interface to insert your record
#   considering below comments:
# Create a new TXT record with a hostname 20210320._domainkey and put the value
#   in the record as in the example below. That is starting from v=DKIM1; 
#   removing all double quotes and paranthesis.
# v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBA....
#
# 12.4. Enable DKIM maps
# Create and edit dkim map file we defined at 13.2.
nano /etc/rspamd/dkim_selectors.map
# Fill it as below, remember to change to your domains and selectors.
#  If you have more than 2 domains, add them too
x11.xyz 20210320
u11.xyz 20210320
v11.xyz 20210320
# Fix file permissions
chown _rspamd /var/lib/rspamd/dkim/*
chmod u=r,go= /var/lib/rspamd/dkim/*
#
# Reload rspamd
systemctl reload rspamd
 

13. User, Alias, and Domain Management – Through Database

# There are 2 ways to manage your ISP Mail; 
#    The Hard Way: Through the Database; or
#    The Easy Way: Using a web interface for it
# We will start with the hard way
#    Remember we installed Mariadb and created a database on it named mailserver.
# I'll use Mariadb shell, but you may prefer to use Adminer interface we 
#    installed at 3.1.
#
# All commands run in mariadb shell, unless stated otherwise (bash shell)
# 13.1. Connect to Mariadb
# Start Mariadb (bash shell)
mariadb
# Select our database
use mailserver
#
# 13.2. Create Domains
# My primary domain: x11.xyz
INSERT INTO virtual_domains (name) VALUES ('x11.xyz');
# My second domain: u11.xyz
INSERT INTO virtual_domains (name) VALUES ('u11.xyz');
# My third domain: v11.xyz
INSERT INTO virtual_domains (name) VALUES ('v11.xyz');
# Another domain to test deleting it
INSERT INTO virtual_domains (name) VALUES ('example.org');
#
# 13.3. Delete Domains
# When you delete a domain, all users and aliases of this domain are
#   deleted too. But mailboxes stay on disk at /var/vmail/...
#   You have to delete them too.
# Delete our test domain
DELETE FROM virtual_domains where name='example.org';
#
# 13.4. Create a Mail User
# You can create a mail user after creating its mail domain.
# To create a mail user, you have to give it a password, and encrypt that
#   password, using dovecot's password tool.
# For user exforge@x11.xyz
# Encrytp user's password (bash shell)
dovecot pw -s BLF-CRYPT
# Enter the given password twice, output will be the encrypted password
# Create user exforge@x11.xyz
INSERT INTO virtual_users (domain_id, email, password) 
VALUES ((SELECT id FROM virtual_domains WHERE name='x11.xyz'), 'exforge@x11.xyz','{BLF-CRYPT}$2y$05$.We…');
#
# For user johndoe@karasite.xyz
# Encrytp user's password (bash shell)
dovecot pw -s BLF-CRYPT
# Enter the given password twice, output will be the encrypted password
# Create user johndoe@x11.xyz
INSERT INTO virtual_users (domain_id, email, password) 
VALUES ((SELECT id FROM virtual_domains WHERE name='x11.xyz'), 'johndoe@x11.xyz','{BLF-CRYPT}$2y$05$.We…');
#
# For user exforge@u11.xyz
# Encrytp user's password (bash shell)
dovecot pw -s BLF-CRYPT 
# Enter the given password twice, output will be the encrypted password
# Create user exforge@u11.xyz
INSERT INTO virtual_users (domain_id, email, password) 
VALUES ((SELECT id FROM virtual_domains WHERE name='u11.xyz'), 'exforge@u11.xyz','{BLF-CRYPT}$2y$05$.We…');
#
# 13.5. Change (Reset) a User's Password
# Again we need to encrypt the new password
# Encrytp password (bash shell)
dovecot pw -s BLF-CRYPT 
# Change password of johndoe@x11.xyz
UPDATE virtual_users SET password='{BLF-CRYPT}$2y$05$.We…' 
WHERE email='johndoe@x11.xyz';
#
# 13.6. Delete a Mail User
# Delete johndoe@x11.xyz, remember mail files will stay in /var/vmail/..
DELETE FROM virtual_users WHERE email='johndoe@x11.xyz';
#
# 13.7. Create a Mail Forwarding (Alias)
# Create aliases postmaster@x11.xyz, webmaster@x11.xyz, 
#   abuse@x11.xyz, and test@x11.xyz. Forward them to exforge@x11.xyz
#
# postmaster@x11.xyz
INSERT INTO virtual_aliases (domain_id, source, destination) VALUES 
( (SELECT id FROM virtual_domains WHERE name='x11.xyz'),
 'postmaster@x11.xyz', 'exforge@x11.xyz');
# webmaster@x11.xyz
INSERT INTO virtual_aliases (domain_id, source, destination) VALUES 
( (SELECT id FROM virtual_domains WHERE name='x11.xyz'),
 'webmaster@x11.xyz', 'exforge@x11.xyz');
# abuse@x11.xyz
INSERT INTO virtual_aliases (domain_id, source, destination) VALUES 
( (SELECT id FROM virtual_domains WHERE name='x11.xyz'),
 'abuse@x11.xyz', 'exforge@x11.xyz');
# test@x11.xyz
INSERT INTO virtual_aliases (domain_id, source, destination) VALUES 
( (SELECT id FROM virtual_domains WHERE name='x11.xyz'),
 'test@x11.xyz', 'exforge@x11.xyz');
#
# Create an alias of postmaster@u11.xyz and forward it to exforge@x11.xyz
INSERT INTO virtual_aliases (domain_id, source, destination) VALUES 
( (SELECT id FROM virtual_domains WHERE name='u11.xyz'),
 'postmaster@u11.xyz', 'exforge@x11.xyz');
#
# Create an alias of abuse@u11.xyz and forward it to my gmail
INSERT INTO virtual_aliases (domain_id, source, destination) VALUES 
( (SELECT id FROM virtual_domains WHERE name='u11.xyz'),
 'abuse@u11.xyz', 'exforge12@gmail.com');
#
# 13.8. Delete a Mail Forwarding
# Delete abuse@u11.xyz forwarding
DELETE FROM virtual_aliases WHERE source='abuse@u11.xyz';
# Delete test@x11.xyz forwarding
DELETE FROM virtual_aliases WHERE source='test@x11.xyz';
 

14. User, Alias, and Domain Management – Through Web Interface

# There are some software written to manage ISPmail.
# The one I choose is; Tomas Kelemen's fork of Ole Jungclaussen's ISPmailadmin.
# Ole Jungclaussen's ISPmailadmin site:
https://ima.jungclaussen.com/
# Tomas Kelemen's fork:
https://gitlab.com/ToKe79/ispmailadmin.git
# 
# 14.1. Database Configuration
# We need to add a new user (ispmailadmin) to the database.
# Enter mariadb
mariadb
# Connect to the database (on mariadb shell)
use mailserver;
# Add ispmailadmin user, remember to change the password (on mariadb shell)
GRANT SELECT, UPDATE, INSERT, DELETE on mailserver.* 
TO 'ispmailadmin'@'127.0.0.1' IDENTIFIED BY 'Password56';
# Activate the user's rights (on mariadb shell)
FLUSH PRIVILEGES;
# Exit to Linux shell (on mariadb shell)
exit
#
# 14.2. Install ISPmailadmin
# Create a home for it
mkdir /var/www/ispmailadmin
# Install git, we are going to need it to download ISPmailadmin
apt update
apt install git
# Download ISPispmailadmin
git clone  -b buster https://gitlab.com/ToKe79/ispmailadmin.git /var/www/ispmailadmin
#
# 14.3. Configure ISPmailadmin
# We will make an Admin only config, that means only admin will be able to
#   use ISP Mail Admin
cd /var/www/ispmailadmin/cfg
cp config.sample.inc.php config.inc.php
nano config.inc.php
# Change the file as described below:
#__________________________________________________________________________________
# Line 16: 'mysql_user' --> 'ispmailadmin'
# Line 17: 'mysql_pass' --> 'Password56'  (Your password)
# Line 24: uncomment line, that is remove // and the space (Enable BCRYPT Hashes)
# Line 29: uncomment line, that is remove // and the space (Enable Quotas)
# Line 35: uncomment line, that is remove // and the space (Enable single admin)
# Line 41: "admin_user' --> 'yourchoiceofadminusername' (Choose a name)
# Line 42: "admin_user' --> 'Password78' (Choose a good password)
#__________________________________________________________________________________
#
# 14.4. Config Webserver to Include ISPmailserver
nano /etc/apache2/sites-available/mail.x11.xyz-https.conf
# Just after the Alias /adminer line (4th line) add following line
#_____________________________________________
 Alias /admin /var/www/ispmailadmin
#_____________________________________________
#
# 14.5. Last Security Settings
# Secure /var/www and set appropriate ownerships
chown -R www-data:www-data /var/www/
chmod -R 770 /var/www/
# Reload apache
systemctl reload apache2
#
# Now we can Connect to ISPMailAdmin
https://mail.x11.xyz/admin/
# You can config domains, users and aliases through the web interface.
 

15. Accessing Your Mail

# 15.1. Through Web Interface
# Obviously you can access your mail through webmail for x11.xyz, u11.xyz, and v11.xyz
#   at the following address:
https://mail.x11.xyz
# You should use your email address in exforge@x11.xyz or exforge@u11.xyz format for 
#   username and you email password as password.
# 
# 15.2. Through Mail Client
# Some people (take me as an example) prefers using a mail client (like Thunderbird) 
#   instead of a web mail interface. 
# Also you may prefer using Thunderbird on your main computer and use webmail when you
#   are away. 
# For exforge@x11.xyz, exforge@u11.xyz, and exforge@v11.xyz accounts, configuration 
#   screens are given below.
https://imgur.com/a/uGt7EdV
#
# 15.3. Mail Client Autoconfig
# When you try to "Add mail account" on Thunderbird, a pop-up displays with the header
#   "Set Up Your Existing Email Address". After entering your name, email address and 
#   password, it tries to configure automatically. Sometimes it can, sometimes it can't.
#   There is no magic there; if a domain has a mail autoconfig file, its mail accounts
#   can be automatically configured.
# Because we have 3 domains, we need 3 autoconfig files. Where to host the autoconfig
#   files and the content of the autoconfig files are explained by examples below.
#
# For x11.xyz
# File must be hosted at the below address (http is ok too) :
https://x11.xyz/.well-known/autoconfig/mail/config-v1.1.xml
# Contents of the file:
#_______________________________________________________
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
  <emailProvider id="x11.xyz">
    <domain>x11.xyz</domain>
    <displayName>X11.xyz Mail Service</displayName>
    <displayShortName>X11</displayShortName>
    <incomingServer type="imap">
      <hostname>mail.x11.xyz</hostname>
      <port>143</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </incomingServer>
    <incomingServer type="pop3">
      <hostname>mail.x11.xyz</hostname>
      <port>110</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </incomingServer>
    <outgoingServer type="smtp">
      <hostname>mail.x11.xyz</hostname>
      <port>587</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </outgoingServer>
  </emailProvider>
</clientConfig>
#_______________________________________________________
#
# For u11.xyz
# File must be hosted at the below address (http is ok too) :
https://u11.xyz/.well-known/autoconfig/mail/config-v1.1.xml
# Contents of the file:
#_______________________________________________________
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
  <emailProvider id="u11.xyz">
    <domain>u11.xyz</domain>
    <displayName>U11.xyz Mail Service</displayName>
    <displayShortName>U11</displayShortName>
    <incomingServer type="imap">
      <hostname>mail.x11.xyz</hostname>
      <port>143</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </incomingServer>
    <incomingServer type="pop3">
      <hostname>mail.x11.xyz</hostname>
      <port>110</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </incomingServer>
    <outgoingServer type="smtp">
      <hostname>mail.x11.xyz</hostname>
      <port>587</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </outgoingServer>
  </emailProvider>
</clientConfig>
#_______________________________________________________
#
# For v11.xyz
# File must be hosted at the below address (http is ok too) :
https://v11.xyz/.well-known/autoconfig/mail/config-v1.1.xml
# Contents of the file:
#_______________________________________________________
<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
  <emailProvider id="x11.xyz">
    <domain>v11.xyz</domain>
    <displayName>V11.xyz Mail Service</displayName>
    <displayShortName>V11</displayShortName>
    <incomingServer type="imap">
      <hostname>mail.x11.xyz</hostname>
      <port>143</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </incomingServer>
    <incomingServer type="pop3">
      <hostname>mail.x11.xyz</hostname>
      <port>110</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </incomingServer>
    <outgoingServer type="smtp">
      <hostname>mail.x11.xyz</hostname>
      <port>587</port>
      <socketType>STARTTLS</socketType>
      <authentication>password-cleartext</authentication>
      <username>%EMAILADDRESS%</username>
    </outgoingServer>
  </emailProvider>
</clientConfig>
#_______________________________________________________
 

16. ISPMailInstall

# The best is saved for the last. 
# There is a Python3 program that implements all of the tasks in this document. That 
#   is you can download it and run it to have a working mail server with as many
#   domains as you want. 
# The program carries out all the necessary tasks (including SSL certificates) except
#   DNS configuration and hosting mail server autoconfig files. Description for them
#   will be given to you by the program as text files. 
# The program has a web site and github page as below:
https://karasite.xyz
https://github.com/enatsek/ISPMailInstall
# Now let's see how to install it.
#
# 16.1. Install git
apt update
apt install git
#
# 16.2. Download ISPMailInstall
git clone https://github.com/enatsek/ISPMailInstall.git /tmp/ispmailinstall
# The program is copied to /tmp/ispmailinstall
#
# 16.3. Set Program Configurations
# Go to program directory
cd /tmp/ispmailinstall
# There is a file ispmail.conf as a configuration file there. It is very well
#   documented. You can set parameters as hostname, domains to hosts, and necessary 
#   passwords. It can generate passwords too. 
# Set all the parameters as you wish. 
nano ispmail.conf
#
# 16.4. Run the Program
python3 ./ispmail.py
# Check log files if everything is OK. Use created text files to set DNS records and
#   mail autocofigurations as you wish, and everything is ready.
# ispmail.log --> All commands and their output
# ispmail.error.log --> Errors (if any)
# *.*.dns.config --> DNS configurations for the domains
# *.* config-v1.1.xml --> Mail autoconfig files for the domains
# 
# 16.5. Access
# Webmail:
https://mail.x11.xyz
#
# Database Administration
https://mail.x11.xyz/adminer
#
# ISP Mail Administration
https://mail.x11.xyz/admin
 

Posted in Ubuntu

KVM on Ubuntu Server 2 (KVM Networking)

Posted on 26/01/2021 - 25/03/2021 by exforge

KVM Tutorial On Ubuntu Server (KVM Networking)

Copyright (C) 2021 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0. Specs

# 0.0. Definition
# KVM virtualization tutorial 2 on Ubuntu 20.04 LTS Server. 
# Please refer to 1st KVM tutorial before reading this one.
# This tutorial specializes on KVM Networking.
#
#
# 0.1. Infrastructure
# Server: (srv) Ubuntu 20.04 LTS Server on a 4 core CPU, 8 GB RAM no GUI 
#   IP: 192.168.0.240/24 and 10.1.1.1/24
# Workstation: (lahana) Ubuntu 20.04 LTS IP: 192.168.0.243
# Network1: 192.168.0.0/24 which is supplied by my internet modem (1st interface)
# Network2: 10.1.1.0/24 with an external switch (2nd interface)
#
# 0.2. Resources
# ISBN: 978-1-78829-467-6 KVM Virtualization Cookbook by Konstantin Ivanov
# ISBN 978-1-83882-871-4 Mastering KVM Virtualization 2nd Ed. by Vedran Dakic, 
#   Humble Devassy Chirammal, Prasad Mukhedkar, Anil Vettathu
 

1. KVM Networks – Configuration Commands

# Although it is possible to produce endless variations, there are 3 basic network types
#   in KVM: Bridged, NAT, and Isolated.
# Before exploring KVM networking more, let's revise the commands for it.
# 
# 1.1. Active Networks
# List KVM Networks:
virsh net-list
# We've already configured a bridged network on tutorial 1, so my server gives the 
#   following output:
#______________________________________________________
 Name           State    Autostart   Persistent
-------------------------------------------------
 host-bridge    active   yes         yes
#______________________________________________________
# 
# To display information about a network, the following command can be used:
virsh net-info NETWORKNAME
virsh net-info host-bridge
# Output on my server:
#______________________________________________________
Name:           host-bridge
UUID:           a67dfcef-86e9-4e4c-832f-bc14443da475
Active:         yes
Persistent:     yes
Autostart:      yes
Bridge:         br0
#______________________________________________________
# 
# We can display the information for the network as an XML file too:
virsh net-dumpxml NETWORKNAME
virsh net-dumpxml host-bridge
# Output on my server:
#______________________________________________________
<network>
  <name>host-bridge</name>
  <uuid>a67dfcef-86e9-4e4c-832f-bc14443da475</uuid>
  <forward mode='bridge'/>
  <bridge name='br0'/>
</network>
#______________________________________________________
#
# 1.2. Adding a Network
# To add a network, we must prepare the configuration in an XML file, and use the name
#   of the file as a parameter.
virsh net-define XMLFILE
# As an example, I'm going to create another bridge on my 2nd interface and add that bridge
#   to the KVM as another network.
# First create the bridge in the netplan file (If your netplan file is named as something
#   else, change it to that.
sudo nano /etc/netplan/01-netcfg.yaml
#____________________________________________________
network:
  ethernets:
    enp3s0f0:
      dhcp4: false
      dhcp6: false
    enx00e04c534458:
      dhcp4: false
      dhcp6: false
  bridges:
    br0:
      interfaces: [ enp3s0f0 ]
      addresses: [192.168.0.240/24]
      gateway4: 192.168.0.1
      mtu: 1500
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
      parameters:
        stp: true
        forward-delay: 4
      dhcp4: no
      dhcp6: no
    br1:
      interfaces: [ enx00e04c534458 ]
      addresses: [10.1.1.1/24]
      gateway4: 10.1.1.1
      mtu: 1500
      nameservers:
        addresses: [8.8.8.8,4.4.4.4]
      dhcp4: no
      dhcp6: no
  version: 2
  renderer: networkd
#_____________________________________________________
#
# enp3s0f0 is my 1st interface, its name is a bit funny, but my 2nd interface really has
#   a weird name as enx00e04c534458. I guess that is because it is a USB network adapter. 
#   Anyway, don't forget to change the names as your adapters'.
# Now, it is time to create the XML file for the second bridge (namely br1).
sudo nano host-bridge2.xml
#_____________________________________________________
<network>
  <name>host-bridge2</name>
  <uuid>c723a80b-d496-460e-9235-9eced7b218cf</uuid>
  <forward mode='bridge'/>
  <bridge name='br1'/>
</network>
#_____________________________________________________
#
# Now we can define it
virsh net-define host-bridge2.xml
# Start it:
virsh net-start host-bridge2
virsh net-start NETWORKNAME
# Make it autostart (Starts when the server starts)
virsh net-autostart host-bridge2
virsh net-autostart NETWORKNAME
#
# Now we have 2 bridges. If we want a VM in 192.168.0.0/24 network we use br0, if we want it
#   in 10.1.1.0/24 then we use br1.
#
# 1.3. Stopping and Removing KVM Networks
# Stop a KVM Network
virsh net-destroy NETWORKNAME
virsh net-destroy host-bridge2
# Disable autostart property of a KVM Network
virsh net-autostart NETWORKNAME --disable
virsh net-autostart host-bridge2 --disable
# Remove a KVM Network
virsh net-undefine NETWORKNAME
virsh net-undefine host-bridge2
 

2. KVM Networks – Network Types

# When preparing XML files for creating KVM networks, we use UUID and MAC values. These
# UUID and MAC must be unique for each network. Remember to replace them with unique values.
# Random UUID Generator
uuidgen
# Random MAC Address Generator
https://www.browserling.com/tools/random-mac
#
# 2.1. Bridged Networks
# I believe you already have an idea of bridged networks. It is like the host is sharing its
#   interface and network with the VM. VM is in the same network as the host. If there is a 
#   DHCP Server on the network the host resides, the VM can use it to get an IP.
# If you are going to use a server which directly serves information or a service to the 
#   users, most probably you'll use a Bridged Network.
# To use a bridged network, first you need to create the bridge in the netplan file, and 
#   then prepare an XML file and add the network to the KVM with "virsh net-define" 
#   command, as we did in 1.2.
# A sample Bridged Network XML File:
#_____________________________________________________
<network>
  <name>host-bridge2</name>
  <uuid>c723a80b-d496-460e-9235-9eced7b218cf</uuid>
  <forward mode='bridge'/>
  <bridge name='br1'/>
</network>
#_____________________________________________________
# Considerations:
#   Replace host-bridge2 with your chosen network name.
#   Replace c723a80b-d496-460e-9235-9eced7b218cf with your generated uuid.
#   Replace br1 with your bridge name in netplan file.
#
# 2.2. NAT Network
# A NAT (Network Address Translation) Network is similar (actually the same) to your home
#   network behind your internet router. Your host's interface stands like your internet 
#   router and VMs are like your home devices. When VMs want to access to the network, they
#   use host's IP address, but the other devices on the network cannot access to your VMs.
# This type of network is useful when you don't want anyone to access your VMs, but you 
#   want your VMs to access everywhere. 
# An example of Routed Network XML File:
#_____________________________________________________
<network>
  <name>nat</name>
  <uuid>d589efd6-7d61-4f92-976b-bde62956cca7</uuid>
  <forward mode='nat'>
    <nat>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='brnat' stp='on' delay='0'/>
  <mac address='4a:c3:6a:72:c2:30'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.101' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>
#_____________________________________________________
# Considerations:
#   Replace nat with your chosen network name.
#   Replace d589efd6-7d61-4f92-976b-bde62956cca7 with your generated uuid.
#   Replace brnat with your chosen bridge name.
#   Replace 52:54:00:6e:a9:d8 with your generated MAC address.
#   Our nat bridge will have 192.168.122.1/24 IP and a DHCP server will announce addresses
#     between 192.168.122.101 and 192.168.10.254. Change these values as you like.
#
# 2.3. Isolated Network
# An Isolated Network, as the name implies, is isolated. Noone can go out, noone can come in.
#   The VMs in the isolated network cannot reach outside, and the devices outside cannot 
#   reach the VMs in the isolated network.
# Although it is very useful for testing purposes, there might be some situations that 
#   isolated network could be very useful. Consider you have a web server and a database 
#   server. The DB server can only be accessed by the web server and the web server will be 
#   accessed by everyone. You can put the DB server in an isolated network and define 2 
#   interfaces for the web server as 1 in a bridged network and the other one in the 
#   isolated network. That way, noone other than the web server can access the DB server.
# An example of Isolated Network XML File:
#_____________________________________________________
<network>
  <name>isolated</name>
  <uuid>a67bbbaf-81e9-4e4c-832f-bc14443da475</uuid>
  <bridge name='brisolated' stp='on' delay='0'/>
  <mac address='4a:c3:6a:72:c2:26'/>
  <domain name='myisolateddomain'/>
  <ip address='192.168.20.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.20.101' end='192.168.20.200'/>
    </dhcp>
  </ip>
</network>
#_____________________________________________________
# Considerations:
#   Replace isolated with your chosen network name.
#   Replace a67bbbaf-81e9-4e4c-832f-bc14443da475 with your generated uuid.
#   Replace brisolated with your chosen bridge name.
#   Replace 55:33:00:dd:dd:ee with your generated MAC address.
#   Our nat bridge will have 192.168.20.1/24 IP and a DHCP server will announce addresses
#     between 192.168.20.101 and 192.168.20.200. Change these values as you like.
 

3. Case Study A: Bridged and Isolated Networks Together

# 3.1. Specs:
#   Our host has a bridged network on 192.168.0.0/24 (Network 1)
#   Our host has an isolated network on 192.168.20.0/24 (Network 2)
#   Our VM1 has 2 interfaces, 1 in Network1 and 1 in Network2
#   Our VM2 has 1 interface in Network2
#   After the installations, VM2 will be accessed by VM1 only, but VM1 will be accessed
#     by all the devices on the network.
# 
# 3.2. Create the Networks
# We already have Network1, lets create Network2
# Prepare XML File
nano isolated.xml
#_____________________________________________________
<network>
  <name>isolated</name>
  <uuid>a67bbbaf-81e9-4e4c-832f-bc14443da475</uuid>
  <bridge name='brisolated' stp='on' delay='0'/>
  <mac address='4a:c3:6a:72:c2:26'/>
  <domain name='myisolateddomain'/>
  <ip address='192.168.20.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.20.101' end='192.168.20.200'/>
    </dhcp>
  </ip>
</network>
#_____________________________________________________
#
# Create the network
virsh net-define isolated.xml
# Start it:
virsh net-start isolated
# Make it autostart 
virsh net-autostart isolated
#
# 3.3. Create VM1 and VM2
# VM1
sudo virt-install --name vm1 \
    --connect qemu:///system  --virt-type kvm \
    --memory 1024 --vcpus 1 \
    --disk /srv/kvm/vm1.qcow2,format=qcow2,size=10 \
    --cdrom /srv/isos/ubuntu20.04.iso  \
    --network bridge=br0 \
    --network bridge=brisolated \
    --graphics vnc,port=5901,listen=0.0.0.0 \
    --os-type linux --os-variant ubuntu20.04 \
    --noautoconsole
# VM2
sudo virt-install --name vm2 \
    --connect qemu:///system  --virt-type kvm \
    --memory 1024 --vcpus 1 \
    --disk /srv/kvm/vm2.qcow2,format=qcow2,size=10 \
    --cdrom /srv/isos/ubuntu20.04.iso  \
    --network bridge=brisolated \
    --graphics vnc,port=5902,listen=0.0.0.0 \
    --os-type linux --os-variant ubuntu20.04 \
    --noautoconsole
# Now you can connect VM1 and VM2 from your workstation and install them. 
# 
# 3.4. Considerations for Isolated Networks
# If a VM is in an isolated network, and if it has no connections to other networks, it 
#   cannot connect to the internet. That means, VM1 can connect to the internet and VM2 
#   cannot connect to the internet. 
# Actually, when we put it in an isolated network, we accepted that it won't connect to 
#   other networks. But we need internet to install or update application.
# I have a not so bad solution for this situation. Install squid proxy to the host, make it 
#   listen to Isolated Network IP of host (192.168.20.1), allow all IPs to access it. 
#   Configure your VMs to use "apt" command through a proxy.
# I won't go in the details of installing and configuring squid proxy here, there are tons 
#   of materials on the internet about it. 
# Configure your VM to use apt commands through a proxy:
# !!! Run on your VM !!!
sudo nano /etc/apt/apt.conf
# Add following line:
#_____________________________________________________
Acquire::http::proxy "http://10.1.1.1:3128";
#_____________________________________________________
#
# If you use username/password for the proxy, use the following format:
#_____________________________________________________
Acquire::http::proxy "http://user:pass@proxyserver:port";
#_____________________________________________________
 

4. Case Study B: Separating Host and VM Access with 2 NICs

# I don't know if it would be a best practice but definitely it will be a good practice to
#   separate host's and VMs' network. That means, we will connect our host to our network 
#   with 2 interfaces; 1 interface will be used for accessing the host and the other will be
#   used to access VMs.
#
# 4.1. Specs:
#   Both interfaces of host are connected to my internet router.
#   Our host has a bridged network on 192.168.0.0/24 (192.168.0.240-NIC 1)
#   Our host has a standart network on 192.168.0.0/24 (192.168.0.241-NIC 2) 
#   Our VM will have 1 interface on bridged network. 
#   The first nic will be used by VMs and the second nic will be used to access the host.
#
# 4.2. Host Network Configuration
# Before the KVM network configuration, we need to configure the server's network. 
# Edit netplan file (change if your netplan file has a different name):
sudo nano /etc/netplan/01-netcfg.yaml
#_______________________________________________________
network:
  ethernets:
    enp3s0f0:
      dhcp4: false
      dhcp6: false
    enx00e04c534458:
      addresses: [192.168.0.241/24]
      gateway4: 192.168.0.1
      nameservers:
        addresses: [8.8.8.8,4.4.4.4]
  bridges:
    br0:
      interfaces: [ enp3s0f0 ]
      addresses: [192.168.0.240/24]
      gateway4: 192.168.0.1
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
  version: 2
  renderer: networkd
#_______________________________________________________
#
# Apply the configuration, (You'd better restart the host)
sudo netplan apply
#
# 4.3. KVM Network Configuration
# We already configured our br0 bridge on KVM. But in case you didn't do it, or removed it.
# If you already have it, skip this step.
nano host-bridge.xml
#___________________________________________
<network>
  <name>host-bridge</name>
  <forward mode="bridge"/>
  <bridge name="br0"/>
</network>
#___________________________________________
# Define the KVM Network
virsh net-define host-bridge.xml
# Start and make it autostarted:
virsh net-start host-bridge
virsh net-autostart host-bridge
#
# 4.4. Create a VM on the Bridged Network
# Now if we create a VM on br0 bridge, it will use the first interface of the host, and 
#   we will keep using the second interface at 192.168.0.241
sudo virt-install --name vm3 \
    --connect qemu:///system  --virt-type kvm \
    --memory 1024 --vcpus 1 \
    --disk /srv/kvm/vm3.qcow2,format=qcow2,size=10 \
    --cdrom /srv/isos/ubuntu20.04.iso  \
    --network bridge=br0 \
    --graphics vnc,port=5901,listen=0.0.0.0 \
    --os-type linux --os-variant ubuntu20.04 \
    --noautoconsole
 

5. Case Study C: NAT KVM Network

# We will create a VM, in a NAT network.
# 5.1. Specs:
# Server: 
#   Interface 1 is in bridged mode (as in 4.)
#   Interface 2 is in standard mode (as in 4.)
#   A NAT KVM network will be added through interface 2
# VM (Named vmn):
#   An interface will be connected to the nat network
#
# 5.2. Host Network Configuration
# There is no change needed if you applied 4.2. Otherwise do it now.
#
# 5.3. KVM NAT Network Configuration
# Prepare XML File
nano nat.xml
#_______________________________________________________________
<network>
  <name>nat</name>
  <uuid>d589efd6-7d61-4f92-976b-bde62956cca7</uuid>
  <forward mode='nat'>
    <nat>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='brnat' stp='on' delay='0'/>
  <mac address='4a:c3:6a:72:c2:30'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.101' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>
#_______________________________________________________________
# Remember to generate a new uuid and a new MAC address
# Remember to rename enx00e04c534458 to your second interface name
#
# Define the KVM Network
virsh net-define nat.xml
# Start and make it autostarted:
virsh net-start nat
virsh net-autostart nat
#
# 5.4. Create the VM
sudo virt-install --name vmn \
    --connect qemu:///system  --virt-type kvm \
    --memory 1024 --vcpus 1 \
    --disk /srv/kvm/vmn.qcow2,format=qcow2,size=10 \
    --cdrom /srv/isos/ubuntu20.04.iso  \
    --network bridge=brnat \
    --graphics vnc,port=5902,listen=0.0.0.0 \
    --os-type linux --os-variant ubuntu20.04 \
    --noautoconsole
# Your VM will be able to connect to your network, but the devices on your
#   network will not be able to connect to it.
 

6. Adding and Removing Networks From a VM

# I assume that we have bridged and isolated networks ready on our host.
#
# 6.1. Specs
# VM Name: vm
# Initial Network: host-bridge (bridge br0)
# Network to be added: isolated
# Network to be removed: host-bridge (br0)
#
# We will create a VM with br0, then we will add it to the isolated network, and then
#   we will remove the br0 network.
#
# 6.2. Create a VM with Bridged Network
sudo virt-install --name vm \
    --connect qemu:///system  --virt-type kvm \
    --memory 1024 --vcpus 1 \
    --disk /srv/kvm/vm.qcow2,format=qcow2,size=10 \
    --cdrom /srv/isos/ubuntu20.04.iso  \
    --network bridge=br0 \
    --graphics vnc,port=5902,listen=0.0.0.0 \
    --os-type linux --os-variant ubuntu20.04 \
    --noautoconsole
#
# 6.3. Add an Interface to the VM at the Isolated Network
virsh attach-interface vm bridge brisolated --target ens1 --config
# Add an interface to the VM named vm, network type is bridge and bridge name is brisolated,
#   interface name on the VM (--target) will be ens1 and it will be active after shutdown
#   and start again.
# Restart the VM, either by logging into it, or using the following commands at the host:
virsh destroy vm
virsh start vm
# "virsh reboot" does not work, it restarts the VM but the interface does not become active.
#
# 6.4. Configure the New Network at the VM
# The new interface ens1 will become active at the VM but it won't start, because it is not
#  configured. We need to configure it using the netplan file.
sudo nano /etc/netplan/00-installer-config.yaml
# Change it as below
#______________________
network:
  ethernets:
    enp1s0:
      dhcp4: true
    ens1:
      dhcp4: true
  version: 2
#______________________
# 
# Activate it
sudo netplan apply
# Now the VM will have an IP from 192.168.20.0/24 (isolated) network for the 2nd interface.
#
# 6.5. Remove the Bridged Network from the VM
# We want to remove the Bridged Network from the VM, we will accomplish it by removing its
#   first interface (enp1s0). 
# To do it, we need the MAC Address of the interface. Run the following command on the VM:
# !!! Run on the VM !!!
ip link show
# It will display something like below:
#____________________________________________________________________________
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:fe:4a:51 brd ff:ff:ff:ff:ff:ff
3: ens1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:70:55:cf brd ff:ff:ff:ff:ff:ff
#____________________________________________________________________________
# My first interface is enp1s0, most probably yours will be the same, or something like it.
#   At the line under the one starting with "2: enp1s0...", the part after link/ether is
#   the MAC Address. Mine is: 52:54:00:fe:4a:51
#
# Now we return back to our host
virsh detach-interface ubuntu2 bridge --mac 52:54:00:fe:4a:51 --config
# When you shutdown and start your VM, the interface will be gone.
 

Posted in Ubuntu

KVM On Ubuntu Server 1 (Beginner)

Posted on 06/01/2021 - 25/03/2021 by exforge

KVM Tutorial On Ubuntu Server (Beginner)

Copyright (C) 2021 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0. Specs

# 0.0. Definition
# KVM virtualization tutorial 1 on Ubuntu 20.04 LTS Server. 
# Our aim is to install and configure a host computer for virtual machines. 
# This tutorial aims to bring you (and me) to a moderate level of Virtualization
#   Administration.
#
# 0.1. How It Works
# KVM (Kernel-based Virtual Machine) is a loadable kernel module which supply
#   virtualization with APIs.
# QEMU is a virtualizer which uses KVM API. QEMU supports other virtualization 
#   solutions too.
# Libvirt is a library for managing virtualization hosts. virsh command comes 
#   from Libvirt.
# Libguestfs is a collection of tools for accessing and managing VM images.
# virt-manager is a GUI for managing VMs. I use it on my workstation for simple
#   tasks.
#
# 0.2. Infrastructure
# Server: Ubuntu 20.04 LTS Server on a 4 core CPU, 8 GB RAM no GUI IP: 192.168.0.240
# Workstation: Ubuntu 20.04 LTS
# Network: 192.168.0.0/24 which is supplied by my internet modem
#
# 0.3. (Very) Basic Terminology
# Domain: Virtual Machine (VM)
# Image:  A file in which a VM (or a disk of VM) is stored. 
# Host:   A server which runs virtualization software
# Guest:  A VM running on a host
# Snapshot: A saved state of an image. You can revert to that stage later.
#
# 0.4. Resources
https://ostechnix.com/install-and-configure-kvm-in-ubuntu-20-04-headless-server/
https://www.qemu.org/docs/master/interop/qemu-img.html
https://www.libvirt.org/manpages/virsh.html
https://docs.fedoraproject.org/en-US/Fedora/18/html/Virtualization_Administration_Guide/index.html
https://libguestfs.org/
https://fabianlee.org/2020/02/23/kvm-testing-cloud-init-locally-using-kvm-for-an-ubuntu-cloud-image/
https://cloudinit.readthedocs.io/en/latest/topics/examples.html
ISBN: 979-10-91414-20-3 The Debian Administrator's Handbook by Raphaël Hertzog and Roland Mas
ISBN: 978-1-78829-467-6 KVM Virtualization Cookbook by Konstantin Ivanov
 

1. Installation and Configuration

# 1.1. Installation
# Install necessary packages
sudo apt update
sudo apt-get install libvirt-clients libvirt-daemon-system qemu-kvm \
     virtinst virt-manager virt-viewer bridge-utils
#
# 1.2. Bridge Configuration
# By default kvm creates a virtual bridge named virbr0. This bridge allows the VMs
#   to communicate between each other and the host. But we prefer that the VMs join to our 
#   network by getting IP address from our DHCP server. That is we will create a public filter.
# First we need to disable netfilter, which is enabled on bridges by default.
sudo nano /etc/sysctl.d/bridge.conf
# File is empty, add following lines
#_________________________________________
net.bridge.bridge-nf-call-ip6tables=0
net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-arptables=0
#_________________________________________
sudo nano /etc/udev/rules.d/99-bridge.rules
# File is empty, add following line
#_________________________________________
ACTION=="add", SUBSYSTEM=="module", KERNEL=="br_netfilter", RUN+="/sbin/sysctl -p /etc/sysctl.d/bridge.conf"
#_________________________________________
# A reboot is necessary
sudo reboot
#
# Now we need to remove the bridge created by KVM
#   With "ip link" command we see all the networks. KVM networks are named as
#   virbr0 and virbr0-nic.
# Delete and undefine KVM networks.
sudo virsh net-destroy default
sudo virsh net-undefine default
# If in any case any error happens, you can try two following commands.
sudo ip link delete virbr0 type bridge
sudo ip link delete virbr0-nic
# Now if you run "ip link" again, you will see virbr0 and virbr-nic are removed.
# When you run "ip link" take a note of your interface name(s), it must be something
#   like enp0s0. If you have more than 1 interface there will be more than 1 name.
# Backup your network configuration file
# If that file does not exist, there must be another file there with yaml extension. Proceed on that
#   file.
sudo cp /etc/netplan/00-installer-config.yaml{,.backup}
# Edit your network config file
sudo nano /etc/netplan/00-installer-config.yaml
# Remove its content , fill it as below, beware of changing enp3s0f0 to your 
#   interface name. If you have more than 1 interfaces, add them too. 
#   Also you will add an IP address and default gateway from your local network. 
#   Mine are 192.168.0.240 and 192.168.0.1
#___________________________________________________
network:
  ethernets:
    enp3s0f0:
      dhcp4: false
      dhcp6: false
  bridges:
    br0:
      interfaces: [ enp3s0f0 ]
      addresses: [192.168.0.240/24]
      gateway4: 192.168.0.1
      mtu: 1500
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
      parameters:
        stp: true
        forward-delay: 4
      dhcp4: no
      dhcp6: no
  version: 2
#___________________________________________________
# Apply the changes. If you connect through ssh, you connection may break.
#   In this case, close the terminal and reconnect.
sudo netplan apply
# If you run "ip link" now, you can see our bridge br0
#
# Add our Bridge to KVM
nano host-bridge.xml
#___________________________________
<network>
  <name>host-bridge</name>
  <forward mode="bridge"/>
  <bridge name="br0"/>
</network>
#___________________________________
virsh net-define host-bridge.xml
virsh net-start host-bridge
virsh net-autostart host-bridge
#
# 1.3. Configure Directories
# Set places for disk images and installation isos
# /srv/kvm for VM disk images
# /srv/isos for installation iso images
sudo mkdir /srv/kvm /srv/isos
sudo virsh pool-create-as srv-kvm dir --target /srv/kvm
# At this point, you may want to copy some installation isos to server's 
#   /srv/isos dir
 

2. VM Creation

# 2.1. Create the 1st VM
# Now it is time to create our first vm
# It will be Ubuntu Server 20.04 LTS with 1 GB RAM and 10 GB HDD
# I already copied Ubuntu server iso ubuntu-20.04.1-live-server-amd64.iso to /srv/isos
# Install a VM named testkvm, through QEMU with KVM virtualization, with 1024MiB memory 
#   and 1 vcpu,
#   prepare a qcow2 format disk of 10GiB, connect a CDROM drive to it with the specified
#   image, use the server's network bridge br0, allow VNC connections to the VM through 
#   the server, optimize it as Linux and Ubuntu 20.04 servers and don't try to attach a
#   console from server.
sudo virt-install --name testkvm \
    --connect qemu:///system  --virt-type kvm \
    --memory 1024 --vcpus 1 \
    --disk /srv/kvm/testkvm.qcow2,format=qcow2,size=10 \
    --cdrom /srv/isos/ubuntu-20.04.1-live-server-amd64.iso  \
    --network bridge=br0 \
    --graphics vnc,port=5901,listen=0.0.0.0 \
    --os-type linux --os-variant ubuntu20.04 \
    --noautoconsole
# 2.2. os-variant List
# There are lots of OS Variant selections. You can find yours with the following 
#   command. It helps hypervisor to optimize the system for the guest OS. It can be
#   skipped.
osinfo-query os
#
# 2.3. Connecting to the VM
# A graphical desktop is needed to connect to the VM. You can install virt-viewer 
#   package on your Ubuntu workstation and connect to the VM.
###----------- Run on your workstation BEGIN ----------------###
sudo apt update
sudo apt install virt-viewer
virt-viewer --connect qemu+ssh://exforge@srv/system testkvm
###----------- Run on your workstation END ------------------###
# Remember to replace exforge with your user name on the server and srv with your
#   server's hostname
 

3. Remote Graphical Management

# Our server has no graphical interface (like the most servers). If you really want
#   a graphical management, you can install virt-manager on your workstation and 
#   manage your VMs from there. 
###----------- Run on your workstation BEGIN ----------------###
sudo apt update
sudo apt install virt-manager
virt-manager
###----------- Run on your workstation END ------------------###
# The application is added to Applications Menu with the name "Virtual Machine Manager"
 

4. Installing VMs from Ready Images

# Starting a new VM and installing OS into is a good but time consuming way. Another way
#   would be preparing an installed image and start it as a new VM. Most server distros
#   supply cloud images. By adding them some necessary configurations (user and network
#   definitions), you can use them as ready images.
#
# 4.0. Installing cloud-image-utils
sudo apt update
sudo apt install cloud-image-utils
#
# 4.1. Acquiring Cloud Images
# A search for "ubuntu cloud image" in duckduck2 gives the following address:
https://cloud-images.ubuntu.com/
# Following focal and current, download kvm image focal-server-cloudimg-amd64.img 
# Put it to server's /srv/isos folder.
#
# 4.2. Creating a New Image From the Original Image
# We will create a new image from the image we downloaded. The original image has a max
#   size of 2.25 GiB, it will be increased to 20 GiB and the new image format will be
#   qcow2, the preferred format for KVM.
sudo qemu-img create -b /srv/isos/focal-server-cloudimg-amd64.img -F qcow2 \
    -f qcow2 /srv/kvm/ubuntusrv-cloudimg.qcow2 20G
#
# 4.3. Cloud-init Configuration
# The next step is to crate a cloud-init config file. This file contains instructions for
#   the cloud image. There is a wide range of instructions like; creating a user, creating
#   and filling files, adding apt repositories, running initial commands, installing
#   packages, reboot and poweroff after finishing, disk and configuration. See below url
#   for details:
https://cloudinit.readthedocs.io/en/latest/topics/examples.html
# Our cloud-init file will configure the following:
# Create a user named exforge with sudo privileges, assign its password, add it to exforge
#   group as primary, also add it to users group, create its home directory as 
#   /home/exforge, and set its shell to bash.
# To add our user's password, we need to have the hash of it.
sudo apt install whois
mkpasswd --method=SHA-512 --rounds=4096
# Enter the user's assigned password here, it will display the hash, copy the hash, we will
#   use it later.
#
# Create a place for our cloud-init files. /srv/init would be fine.
sudo mkdir /srv/init
# Create our cloud-init file
sudo nano /srv/init/ubuntu-cloud-init.cfg
#_________________________________________________________________
#cloud-config
hostname: ubuntu20
fqdn: ubuntu20.x386.xyz
manage_etc_hosts: true
groups: exforge
users:
   - name: exforge
     sudo: ALL=(ALL) ALL
     primary_group: exforge
     groups: users
     home: /home/exforge
     shell: /bin/bash
     lock_passwd: false
     passwd: $6$rounds=4096$0BSLhp4jtwR1$vm3QE1m70VuTO2GJ8j5GvVKPqmq3>
packages: qemu-guest-agent
#_________________________________________________________________
# Do not forget to change passwd value with your copied hash.
#
# 4.4. Cloud-init Network Configuration
# If a network configuration other than DHCP is needed, a network configuration file is
#   necessary.
# Remember to change IP addresses as needed by your VM
sudo nano /srv/init/ubuntu-network-init.cfg
#______________________________________________________________
#cloud-config
version: 2
ethernets:
  enp1s0:
     dhcp4: false
     addresses: [ 192.168.0.221/24 ]
     gateway4: 192.168.0.1
     nameservers:
       addresses: [ 192.168.0.1,8.8.8.8 ]
#______________________________________________________________
#
# 4.5. Creating Cloud Seed Image
# Now we will create an image file with our ubuntu-cloud-init.cfg and 
#   ubuntu-network-init.cfg inside.
sudo cloud-localds --network-config /srv/init/ubuntu-network-init.cfg /srv/kvm/ubuntu20-seed.qcow2 \
   /srv/init/ubuntu-cloud-init.cfg
#
# 4.6. Start Our Image as a New VM
virt-install --name ubuntu20 \
  --connect qemu:///system \
  --virt-type kvm --memory 2048 --vcpus 2 \
  --boot hd,menu=on \
  --disk path=/srv/kvm/ubuntu20-seed.qcow2,device=cdrom \
  --disk path=/srv/kvm/ubuntusrv-cloudimg.qcow2,device=disk \
  --graphics vnc,port=5901,listen=0.0.0.0 \
  --os-type linux --os-variant ubuntu20.04 \
  --network bridge=br0 \
  --noautoconsole
# It might take a few minutes for cloud-init to finish. You can connect to your VM from 
#   your workstation.
virt-viewer --connect qemu+ssh://exforge@srv/system ubuntu20
#
# 4.7. Clean-up Tasks for Cloud-init
# On your VM run:
sudo touch /etc/cloud/cloud-init.disabled
# If the file /etc/cloud/cloud-init.disabled exists, cloud-init does not run.
#
# 4.8. The whole process except 4.7. can be automated by a python script. 
# Download latest focal current cloud image (or use an already downloaded one) 4.1.
# A system call to run a command to create a new image 4.2.
#    Image size (and name) can be a parameter
# Create password hash and init files 4.3. and 4.4. 
#    User name can be a parameter
#    Password can be obtained at run time
#    Network properties (IP, GW etc) can be parameters
# A system call to run a command to create seed image 4.5.
# A system call to run a command to start the new image 4.6.
#   Memory size, vcpu count can be parameters.
 

5. virsh: Shell Based VM Management

# virt-manager can only help with the basic management tasks. If you want to dive deep,
#   you need the old-style shell.
# There are countless options to do with virsh command. I can only list a handfull
#   of most useful ones (IMHO) here.
# For a complete list of virsh command usage, see the following web page:
https://www.libvirt.org/manpages/virsh.html
# In all examples, NAME is the name of your VM.
#
# 5.1. Info about host
virsh nodeinfo
#
# 5.2. List VMs and their states
# Running VMs
virsh list
# All VMs
virsh list --all
#
# 5.3. Start, shutdown, reboot, force shutdown, remove a VM
virsh start NAME
virsh shutdown NAME
virsh reboot NAME
virsh destroy NAME
virsh undefine NAME
virsh undefine NAME --remove-all-storage
virsh reboot ubuntu20
#
# 5.4. Pause and resume a VM
virsh suspend NAME
virsh resume NAME
#
# 5.5. Autostart a VM (starts when the host starts)
virsh autostart NAME
virsh autostart --disable NAME   # Disable autostart
#
# 5.6. Information about a VM
virsh dominfo NAME
virsh domid NAME
virsh domuuid NAME
virsh domstate NAME
# Display VNC connection settings of VM
virsh domdisplay NAME
#
# 5.7. VM Memory Management
# A VM has 2 memory parameters: Max Memory and Used Memory. 
# Used memory is the amount of mem allocated to the VM
# Max memory is the max amount of mem to be allocated to the VM
# To see current memory allocation:
virsh domstate NAME
# Change Max memory (Activated after shutdown and start)
virsh setmaxmem NAME 2G --config
#   size could be something like 2G 1536M etc
# Used memory can be changed when the VM is running (decreasing is not advised)
# Change memory for this session only (reverts after shutdown and start):
virsh setmem NAME 1536M
virsh setmem NAME 1536M --live
virsh setmem NAME 1536M --current
# Change memory after the next shutdown and start
virsh setmem NAME 1536M --config
# Activate immediately and keep the changes after the next shutdown and start
virsh setmem NAME 1536M --live --config
virsh setmem ubuntu20 1536M --live --config
# !!!!! Beware of Shutdown and Start. Reboots do not count !!!!!
# 
# 5.8. VM vCPU Management
# Just like memory, VMs have 2 virtual CPU parameters. Maximum and Current.
#   Current is the number of vcpus that VM uses actively (on-line).
#   Maximum is the max number of vcpus to be allocated to the VM.
# Also, there are 2 states. Config and Live.
#   Config is the permanent state, it will be active after shutdown and start.
#   Live is the running VM's state, it may not be active after shutdown and start.
# A cartesian product gives us 4 values:
#   maximum config : Max number of vcpus, valid after shutdown and start.
#   maximum live   : Max number of vcpus, valid now (while running).
#   current config : Active number of vcpus, valid after shutdown and start.
#   current live   : Active number of vcpus, valid now (while running).
# To see these values for your VM:
virsh vcpucount NAME
# I keep saying shutdown and start instead of restart or reboot, because kvm, qemu or
#   whatever it is, acts differently when you reboot or shutdown and then start the VM. 
#   So when I say shutdown and start, I mean shutdown first, wait a while (from 0.001
#   miliseconds to as long as you want) and then start the VM.
#
# There is no way (AFAIK) to change maximum live value, you can change maximum config as:
virsh setvcpus NAME NUMBER --maximum --config
virsh setvcpus ubuntu20 3 --maximum --config
# To change current vcpu count for the current state (all options are valid)
virsh setvcpus NAME NUMBER
virsh setvcpus NAME NUMBER --current
virsh setvcpus NAME NUMBER --live
virsh setvcpus ubuntu20 3 
virsh setvcpus ubuntu20 3 --current
virsh setvcpus ubuntu20 3 --live
# To change current vcpu count for the config state
virsh setvcpus NAME NUMBER --config
virsh setvcpus ubuntu20 3 --config
# To do it both together
virsh setvcpus NAME NUMBER --config --live
virsh setvcpus ubuntu20 3 --config --live
#
# You can both increase and decrease the vcpu count. But beware that decreasing vcpu count #   of a running VM could be dangerous.
#
# When you increase the current live vcpu count, the increased vcpus becomes offline. That
#   means you cannot use them right away. At least that is what happened to me. You can see
#   online and offline vcpu information of your VM with the following command (run it on
#   your VM):
lscpu | head
# To activate an offline cpu, first you have to know its number. cpu numbering starts from
#   0, so if you had 2 vcpus and increased them by 1, the number for the 3rd vcpu will be
#   2. You need to edit the following file and change the 0 inside to 1:
sudo nano /sys/devices/system/cpu/cpu2/online
# The number 2 after cpu means the cpu with number 2 i.e. 3rd cpu. When you change the
#   file, magically that vcpu will become online. For more vcpus, you have to change that
#   file for each vcpu you added.
#
# 5.9. Snapshots
# When you take a snapshot, current disk and memory state is saved.
# Take a live snapshot
virsh snapshot-create-as VMNAME --name SNAPSHOTNAME --description DESCRIPTION
virsh snapshot-create-as ubuntu20 --name ss1-ubuntu20 --description "Firsh Snapshot of Ubuntu20"
# The snapshot becomes the current one and everything after is built onto this 
#   snapshot. If you want to revert to that snapshot:
virsh snapshot-revert VMNAME --current
# If you want to revert to a specific snapshot:
virsh snapshot-revert VMNAME --snapshotname SNAPSHOTNAME
# To see which snapshot is current:
virsh snapshot-current VMNAME --name
# To delete the current snapshot
virsh snapshot-delete VMNAME --current
# To delete a specific snapshot
virsh snapshot-delete VMNAME --snapshotname SNAPSHOTNAME
# To list all snapshots of a VM
virsh snapshot-list VMNAME
#
# 5.10. Attach Another Disk to a VM
# Suppose that, for our ubuntu20 VM, we need another disk of 20GB size. Because, we need to 
#   keep some data on another disk. 
# We need to create a new image and attach it to the VM.
# Create a 20GB image in qcow2 format:
sudo qemu-img create -f qcow2 /srv/kvm/ubuntu20-disk2.qcow2 20G
# Now our image is ready to be attached to our VM. Before attaching it to the VM, we have 
#   to decide its name on the VM.
# VM disks are named as vda, vdb, vdc ... so on. We have to give it a name just follows the
#   last disk. Because my ubuntu20 VM has only one disk, name for the second one will be
#   vdb. To see your disks on your VM, type the following command (On your VM):
lsblk -o name -d | grep vd
# Most probably you will have only vda, in that case you can use the name vdb. Otherwise
#   use a name just after the last disk name.
# Add the new image as a second disk to my ubuntu20 VM:
virsh attach-disk ubuntu20 /srv/kvm/ubuntu20-disk2.qcow2 vdb --persistent
# The disk is added persistently, that is it is added alive and it will be there after
#   shutdown and and start. If you want to add the disk for the session only, you can
#   change --persistent to --live. Also, if you want to add the disk after shutdown and
#   start you can change --persistent to --config.
# Needless to say that, you are going to have to mount the new disk before using it.
#
# In any case, if you want to detach the added disk, solution is easy:
virsh detach-disk ubuntu20 vdb --persistent
# As in virsh attach-disk, you can change --persistent option to --live or --config.
 

6. All virsh Commands

# The following output is taken from the following command: 
virsh --help
# You can get detailed help for each subcommand with: virsh SUBCOMMAND --help, like:
virsh dominfo --help
#
# Domain Management (help keyword 'domain')
#    attach-device                  attach device from an XML file
#    attach-disk                    attach disk device
#    attach-interface               attach network interface
#    autostart                      autostart a domain
#    blkdeviotune                   Set or query a block device I/O tuning parameters.
#    blkiotune                      Get or set blkio parameters
#    blockcommit                    Start a block commit operation.
#    blockcopy                      Start a block copy operation.
#    blockjob                       Manage active block operations
#    blockpull                      Populate a disk from its backing image.
#    blockresize                    Resize block device of domain.
#    change-media                   Change media of CD or floppy drive
#    console                        connect to the guest console
#    cpu-stats                      show domain cpu statistics
#    create                         create a domain from an XML file
#    define                         define (but don't start) a domain from an XML file
#    desc                           show or set domain's description or title
#    destroy                        destroy (stop) a domain
#    detach-device                  detach device from an XML file
#    detach-device-alias            detach device from an alias
#    detach-disk                    detach disk device
#    detach-interface               detach network interface
#    domdisplay                     domain display connection URI
#    domfsfreeze                    Freeze domain's mounted filesystems.
#    domfsthaw                      Thaw domain's mounted filesystems.
#    domfsinfo                      Get information of domain's mounted filesystems.
#    domfstrim                      Invoke fstrim on domain's mounted filesystems.
#    domhostname                    print the domain's hostname
#    domid                          convert a domain name or UUID to domain id
#    domif-setlink                  set link state of a virtual interface
#    domiftune                      get/set parameters of a virtual interface
#    domjobabort                    abort active domain job
#    domjobinfo                     domain job information
#    domname                        convert a domain id or UUID to domain name
#    domrename                      rename a domain
#    dompmsuspend                   suspend a domain gracefully using power management functions
#    dompmwakeup                    wakeup a domain from pmsuspended state
#    domuuid                        convert a domain name or id to domain UUID
#    domxml-from-native             Convert native config to domain XML
#    domxml-to-native               Convert domain XML to native config
#    dump                           dump the core of a domain to a file for analysis
#    dumpxml                        domain information in XML
#    edit                           edit XML configuration for a domain
#    event                          Domain Events
#    inject-nmi                     Inject NMI to the guest
#    iothreadinfo                   view domain IOThreads
#    iothreadpin                    control domain IOThread affinity
#    iothreadadd                    add an IOThread to the guest domain
#    iothreadset                    modifies an existing IOThread of the guest domain
#    iothreaddel                    delete an IOThread from the guest domain
#    send-key                       Send keycodes to the guest
#    send-process-signal            Send signals to processes
#    lxc-enter-namespace            LXC Guest Enter Namespace
#    managedsave                    managed save of a domain state
#    managedsave-remove             Remove managed save of a domain
#    managedsave-edit               edit XML for a domain's managed save state file
#    managedsave-dumpxml            Domain information of managed save state file in XML
#    managedsave-define             redefine the XML for a domain's managed save state file
#    memtune                        Get or set memory parameters
#    perf                           Get or set perf event
#    metadata                       show or set domain's custom XML metadata
#    migrate                        migrate domain to another host
#    migrate-setmaxdowntime         set maximum tolerable downtime
#    migrate-getmaxdowntime         get maximum tolerable downtime
#    migrate-compcache              get/set compression cache size
#    migrate-setspeed               Set the maximum migration bandwidth
#    migrate-getspeed               Get the maximum migration bandwidth
#    migrate-postcopy               Switch running migration from pre-copy to post-copy
#    numatune                       Get or set numa parameters
#    qemu-attach                    QEMU Attach
#    qemu-monitor-command           QEMU Monitor Command
#    qemu-monitor-event             QEMU Monitor Events
#    qemu-agent-command             QEMU Guest Agent Command
#    guest-agent-timeout            Set the guest agent timeout
#    reboot                         reboot a domain
#    reset                          reset a domain
#    restore                        restore a domain from a saved state in a file
#    resume                         resume a domain
#    save                           save a domain state to a file
#    save-image-define              redefine the XML for a domain's saved state file
#    save-image-dumpxml             saved state domain information in XML
#    save-image-edit                edit XML for a domain's saved state file
#    schedinfo                      show/set scheduler parameters
#    screenshot                     take a screenshot of a current domain console and store it into a file
#    set-lifecycle-action           change lifecycle actions
#    set-user-password              set the user password inside the domain
#    setmaxmem                      change maximum memory limit
#    setmem                         change memory allocation
#    setvcpus                       change number of virtual CPUs
#    shutdown                       gracefully shutdown a domain
#    start                          start a (previously defined) inactive domain
#    suspend                        suspend a domain
#    ttyconsole                     tty console
#    undefine                       undefine a domain
#    update-device                  update device from an XML file
#    vcpucount                      domain vcpu counts
#    vcpuinfo                       detailed domain vcpu information
#    vcpupin                        control or query domain vcpu affinity
#    emulatorpin                    control or query domain emulator affinity
#    vncdisplay                     vnc display
#    guestvcpus                     query or modify state of vcpu in the guest (via agent)
#    setvcpu                        attach/detach vcpu or groups of threads
#    domblkthreshold                set the threshold for block-threshold event for a given block device or it's backing chain element
#    guestinfo                      query information about the guest (via agent)
#
# Domain Monitoring (help keyword 'monitor')
#    domblkerror                    Show errors on block devices
#    domblkinfo                     domain block device size information
#    domblklist                     list all domain blocks
#    domblkstat                     get device block stats for a domain
#    domcontrol                     domain control interface state
#    domif-getlink                  get link state of a virtual interface
#    domifaddr                      Get network interfaces' addresses for a running domain
#    domiflist                      list all domain virtual interfaces
#    domifstat                      get network interface stats for a domain
#    dominfo                        domain information
#    dommemstat                     get memory statistics for a domain
#    domstate                       domain state
#    domstats                       get statistics about one or multiple domains
#    domtime                        domain time
#    list                           list domains
#
# Host and Hypervisor (help keyword 'host')
#    allocpages                     Manipulate pages pool size
#    capabilities                   capabilities
#    cpu-baseline                   compute baseline CPU
#    cpu-compare                    compare host CPU with a CPU described by an XML file
#    cpu-models                     CPU models
#    domcapabilities                domain capabilities
#    freecell                       NUMA free memory
#    freepages                      NUMA free pages
#    hostname                       print the hypervisor hostname
#    hypervisor-cpu-baseline        compute baseline CPU usable by a specific hypervisor
#    hypervisor-cpu-compare         compare a CPU with the CPU created by a hypervisor on the host
#    maxvcpus                       connection vcpu maximum
#    node-memory-tune               Get or set node memory parameters
#    nodecpumap                     node cpu map
#    nodecpustats                   Prints cpu stats of the node.
#    nodeinfo                       node information
#    nodememstats                   Prints memory stats of the node.
#    nodesuspend                    suspend the host node for a given time duration
#    sysinfo                        print the hypervisor sysinfo
#    uri                            print the hypervisor canonical URI
#    version                        show version
#
# Checkpoint (help keyword 'checkpoint')
#    checkpoint-create              Create a checkpoint from XML
#    checkpoint-create-as           Create a checkpoint from a set of args
#    checkpoint-delete              Delete a domain checkpoint
#    checkpoint-dumpxml             Dump XML for a domain checkpoint
#    checkpoint-edit                edit XML for a checkpoint
#    checkpoint-info                checkpoint information
#    checkpoint-list                List checkpoints for a domain
#    checkpoint-parent              Get the name of the parent of a checkpoint
#
# Interface (help keyword 'interface')
#    iface-begin                    create a snapshot of current interfaces settings, which can be later committed (iface-commit) or restored (iface-rollback)
#    iface-bridge                   create a bridge device and attach an existing network device to it
#    iface-commit                   commit changes made since iface-begin and free restore point
#    iface-define                   define an inactive persistent physical host interface or modify an existing persistent one from an XML file
#    iface-destroy                  destroy a physical host interface (disable it / "if-down")
#    iface-dumpxml                  interface information in XML
#    iface-edit                     edit XML configuration for a physical host interface
#    iface-list                     list physical host interfaces
#    iface-mac                      convert an interface name to interface MAC address
#    iface-name                     convert an interface MAC address to interface name
#    iface-rollback                 rollback to previous saved configuration created via iface-begin
#    iface-start                    start a physical host interface (enable it / "if-up")
#    iface-unbridge                 undefine a bridge device after detaching its slave device
#    iface-undefine                 undefine a physical host interface (remove it from configuration)
#
# Network Filter (help keyword 'filter')
#    nwfilter-define                define or update a network filter from an XML file
#    nwfilter-dumpxml               network filter information in XML
#    nwfilter-edit                  edit XML configuration for a network filter
#    nwfilter-list                  list network filters
#    nwfilter-undefine              undefine a network filter
#    nwfilter-binding-create        create a network filter binding from an XML file
#    nwfilter-binding-delete        delete a network filter binding
#    nwfilter-binding-dumpxml       network filter information in XML
#    nwfilter-binding-list          list network filter bindings
#
# Networking (help keyword 'network')
#    net-autostart                  autostart a network
#    net-create                     create a network from an XML file
#    net-define                     define an inactive persistent virtual network or modify an existing persistent one from an XML file
#    net-destroy                    destroy (stop) a network
#    net-dhcp-leases                print lease info for a given network
#    net-dumpxml                    network information in XML
#    net-edit                       edit XML configuration for a network
#    net-event                      Network Events
#    net-info                       network information
#    net-list                       list networks
#    net-name                       convert a network UUID to network name
#    net-start                      start a (previously defined) inactive network
#    net-undefine                   undefine a persistent network
#    net-update                     update parts of an existing network's configuration
#    net-uuid                       convert a network name to network UUID
#    net-port-list                  list network ports
#    net-port-create                create a network port from an XML file
#    net-port-dumpxml               network port information in XML
#    net-port-delete                delete the specified network port
#
# Node Device (help keyword 'nodedev')
#    nodedev-create                 create a device defined by an XML file on the node
#    nodedev-destroy                destroy (stop) a device on the node
#    nodedev-detach                 detach node device from its device driver
#    nodedev-dumpxml                node device details in XML
#    nodedev-list                   enumerate devices on this host
#    nodedev-reattach               reattach node device to its device driver
#    nodedev-reset                  reset node device
#    nodedev-event                  Node Device Events
#
# Secret (help keyword 'secret')
#    secret-define                  define or modify a secret from an XML file
#    secret-dumpxml                 secret attributes in XML
#    secret-event                   Secret Events
#    secret-get-value               Output a secret value
#    secret-list                    list secrets
#    secret-set-value               set a secret value
#    secret-undefine                undefine a secret
#
# Snapshot (help keyword 'snapshot')
#    snapshot-create                Create a snapshot from XML
#    snapshot-create-as             Create a snapshot from a set of args
#    snapshot-current               Get or set the current snapshot
#    snapshot-delete                Delete a domain snapshot
#    snapshot-dumpxml               Dump XML for a domain snapshot
#    snapshot-edit                  edit XML for a snapshot
#    snapshot-info                  snapshot information
#    snapshot-list                  List snapshots for a domain
#    snapshot-parent                Get the name of the parent of a snapshot
#    snapshot-revert                Revert a domain to a snapshot
#
# Backup (help keyword 'backup')
#    backup-begin                   Start a disk backup of a live domain
#    backup-dumpxml                 Dump XML for an ongoing domain block backup job
#
# Storage Pool (help keyword 'pool')
#    find-storage-pool-sources-as   find potential storage pool sources
#    find-storage-pool-sources      discover potential storage pool sources
#    pool-autostart                 autostart a pool
#    pool-build                     build a pool
#    pool-create-as                 create a pool from a set of args
#    pool-create                    create a pool from an XML file
#    pool-define-as                 define a pool from a set of args
#    pool-define                    define an inactive persistent storage pool or modify an existing persistent one from an XML file
#    pool-delete                    delete a pool
#    pool-destroy                   destroy (stop) a pool
#    pool-dumpxml                   pool information in XML
#    pool-edit                      edit XML configuration for a storage pool
#    pool-info                      storage pool information
#    pool-list                      list pools
#    pool-name                      convert a pool UUID to pool name
#    pool-refresh                   refresh a pool
#    pool-start                     start a (previously defined) inactive pool
#    pool-undefine                  undefine an inactive pool
#    pool-uuid                      convert a pool name to pool UUID
#    pool-event                     Storage Pool Events
#    pool-capabilities              storage pool capabilities
#
# Storage Volume (help keyword 'volume')
#    vol-clone                      clone a volume.
#    vol-create-as                  create a volume from a set of args
#    vol-create                     create a vol from an XML file
#    vol-create-from                create a vol, using another volume as input
#    vol-delete                     delete a vol
#    vol-download                   download volume contents to a file
#    vol-dumpxml                    vol information in XML
#    vol-info                       storage vol information
#    vol-key                        returns the volume key for a given volume name or path
#    vol-list                       list vols
#    vol-name                       returns the volume name for a given volume key or path
#    vol-path                       returns the volume path for a given volume name or key
#    vol-pool                       returns the storage pool for a given volume key or path
#    vol-resize                     resize a vol
#    vol-upload                     upload file contents to a volume
#    vol-wipe                       wipe a vol
 

7. qemu-img: Shell Based Image Management

# qemu-img allows us to manipulate images. The command is expected to work offline. That
#   means, before you start using qemu-img, you have to shut down the VM associated with
#   it. 
# !!! Do not use qemu-img with an image of running VM !!!
# A full documentation can be found at the below site:
https://www.qemu.org/docs/master/interop/qemu-img.html
#
# 7.1. Get Basic Info About an Image
qemu-img info FILENAME
# FILENAME is the name of the file which is the image for the VM.
# For my ubuntu20 VM's image info:
qemu-img info ubuntusrv-cloudimg.qcow2
#
# 7.2. Creating an Image
qemu-img create -f FORMAT FILENAME SIZE
# Remember, at 5.10. we created an empty disk image to add as another disk to a VM:
sudo qemu-img create -f qcow2 /srv/kvm/ubuntu20-disk2.qcow2 20G
#
# An image also can be created by backing from another image. In that way, we will have 
#   another image from an image, differentiating its format and size:
sudo qemu-img create -b BACKINGFILENAME -F BACKINGFILEFORMAT \
    -f OUTPUTFILEFORMAT OUTPUTFILENAME SIZE
# Remember, at 4.2. we created a new cloud image from the cloud image we doownloaded:
sudo qemu-img create -b /srv/isos/focal-server-cloudimg-amd64.img -F qcow2 \
    -f qcow2 /srv/kvm/ubuntusrv-cloudimg.qcow2 20G
# 
# 7.3. Changing the Format of an Image
# There are a lot of formats for images. For us, the 2 most important ones are raw and qcow2. 
#   raw   : As the name implies. 
#   qcow2 : Feature rich, allows snapshots, compression and encrytion.
#   qcow  : Older version of qcow2.
#   dmg   : Mac format.
#   nbd   : Network block device, used to access remote storages
#   vdi   : Virtualbox format
#   vmdk  : VMW*re format
#   vhdx  : Micros*ft HyperV format
qemu-img convert -f SOURCEFORMAT -O DESTINATIONFORMAT SOURCEFILE DESTFILE
# I have Virtualbox installed on my workstation (Ubuntu 20.04 LTS). There is a Windows 10 
#   installed on it for testing purposes. I'll copy its image (obviously in vdi format) 
#   to my server to /srv/kvm directory, convert it to qcow2 and run it on my server using
#   KVM. 
# !!! On my workstation BEGIN !!!
# Copy Windows 10 image to the server
scp windows10.vdi exforge@srv:/tmp
# !!! On my workstation END !!!
# On my server
sudo qemu-img convert -f vdi -O qcow2 /tmp/windows10.vdi /srv/kvm/windows10.qcow2
# If we want to display the progress percentage while converting the image, add -p option.
sudo qemu-img convert -p -f vdi -O qcow2 /tmp/windows10.vdi /srv/kvm/windows10.qcow2
# Now we can add it as a KVM image
virt-install --name windows10 \
  --connect qemu:///system \
  --virt-type kvm --memory 2048 --vcpus 2 \
  --boot hd,menu=on \
  --disk path=/srv/kvm/windows10.qcow2,device=disk \
  --graphics vnc,port=5901,listen=0.0.0.0 \
  --os-type windows --os-variant win10 \
  --network bridge=br0 \
  --noautoconsole
#   
# 7.4. Resize a Disk Image.
# If you need extra disk space for your VM, you can increase the size of the image file.
sudo qemu-img resize FILENAME +SIZE
# Resize an image, FILENAME is the name of the file which is the image for the VM.
# SIZE could be something like +10G. Image size will be increased by (not to) this amount. 
# it is possible to shrink with -
# You must use the parameter --shrink to shrink the image
# You must use partitioning tools in the VM to resize the disk to shrinked size before
#   shrinking.
# To increase the size of my ubuntu20 VM's image by 5GB:
sudo qemu-img resize /srv/kvm/ubuntusrv-cloudimg.qcow2 +5G
#
# 7.5. Check an Image For Errors
qemu-img check FILENAME
# In any case if you suspect the integrity of the image file
 

8. Export and Import of VMs

# If you want to move your VM to another host, or in a way you want to backup and restore 
#   your VM; there might be a lot of ways to do it. I'm going to demonstrate a very simple
#   method which requires shutting down the VM (you can try while it is running, but with
#   no success guaranteed).
#
# 8.1. Export
# First of all, let's prepare a place for our backup files, /tmp/kvmbackup would be fine.
mkdir /tmp/kvmbackup
# We need the definition file of our VM and the image file it is using. "virsh dumpxml" 
#   command creates the definition file in xml format, we can save it with the VM's name.
virsh dumpxml ubuntu20 > /tmp/kvmbackup/ubuntu20.xml
# This file contains all the necessary information about our VM.
#
# If our VM was installed from the scratch as in 2.1. there will be only 1 image file. But
#   if it was installed from a cloud image as we did in 4. or if another disk was added as
#   in 5.10; there would be more than 1 images. We need to copy all the images. 
#
# Images used by the VM is listed in the xml file. Let's find them:
grep "source file" /tmp/kvmbackup/ubuntu20.xml
# For my ubuntu20 VM, output is listed below:
#_____________________________________________________________________
      <source file='/srv/kvm/ubuntu20-seed.qcow2' index='2'/>
      <source file='/srv/kvm/ubuntusrv-cloudimg.qcow2' index='1'/>
        <source file='/srv/isos/focal-server-cloudimg-amd64.img'/>
#_____________________________________________________________________
# That means I need to prepare 3 files: /srv/kvm/ubuntu20-seed.qcow2, 
#   /srv/kvm/ubuntusrv-cloudimg.qcow2 #   and /srv/isos/focal-server-cloudimg-amd64.img.
#   Lets copy them to our backup locations.
cp /srv/kvm/ubuntu20-seed.qcow2 /srv/kvm/ubuntusrv-cloudimg.qcow2 \
   /srv/isos/focal-server-cloudimg-amd64.img /tmp/kvmbackup
# Beware: You can copy the files while the VM is running, but it is advised to shutdown (or 
#   at least suspend your VM) before copying. Continue at your own risk.
#
# Let's package them
tar -cf /tmp/ubuntu20.tar -C /tmp/kvmbackup .
# Now we have /tmp/ubuntu20.tar, it has all the necessary data to import our VM anywhere.
# You have to copy this file to another server, before importing there.
#
# 8.2. Import
# Assuming we have another virtualization server and we have copied ubuntu20.tar there, we 
#   are going to import it and make it operational.
# Beware: Before importing your VM to another server, you have to remove it on the original 
#   server, #   otherwise you would have 2 guests with the same IP and that may cause
#   unexpected results.
# ubuntu20.tar is copied to the server's /tmp directory as /tmp/ubuntu20.tar
# Create a place for our import files
mkdir /tmp/import
# Extract tar file there
tar -xf /tmp/ubuntu20.tar -C /tmp/import
# Now we need to move our image files to their directories as in the original server. If 
#   you have a different directory structure on your new server, and you want to copy files
#   to different directories you have to edit the xml file and change directories there.
sudo cp /tmp/import/ubuntu20-seed.qcow2 /tmp/import/ubuntusrv-cloudimg.qcow2 /srv/kvm
sudo cp /tmp/import/focal-server-cloudimg-amd64.img /srv/isos
# 
# It is time to define our server. Remember the xml file? We will use it to define our
#   ubuntu20 server.
virsh define /tmp/import/ubuntu20.xml
# Now we can start it
virsh start ubuntu20
 

9. libguestfs: VM Disk Management

# A set of commands for managing VM disks. Full documentation:
https://libguestfs.org/
# Normally, as a system admin, you won't need to reach to VM's disks. But there may happen 
#   a need once in a while. 
# I think you already understand that when you have a VPS on a cloud server, the 
#   administrators of that cloud environment can reach your VPS' data. 
# There are many tools, I'm going to try to explain only mounting commands. 
#
# 9.1. Installation
sudo apt update
sudo apt-get install libguestfs-tools
#
# 9.2. Mounting VM's Disks
# Works online (While the VM is running) Mount my VMs disk on my host's /mnt directory:
sudo guestmount -d ubuntu20 -i --ro /mnt
# /mnt directory holds all the files of my VM. If you remove --ro, you can mount it with 
#  write permissions. But be very careful.
# Unmount it:
sudo guestunmount /mnt
# I prefer mounting with readonly permissions just to be safe.
# Details for guestmount and guestunmount commands:
guestmount --help
guestunmount --help
#
# 9.3. All Commands
# guestfish(1) — interactive shell
# guestmount(1) — mount guest filesystem in host
# guestunmount(1) — unmount guest filesystem
# virt-alignment-scan(1) — check alignment of virtual machine partitions
# virt-builder(1) — quick image builder
# virt-builder-repository(1) — create virt-builder repositories
# virt-cat(1) — display a file
# virt-copy-in(1) — copy files and directories into a VM
# virt-copy-out(1) — copy files and directories out of a VM
# virt-customize(1) — customize virtual machines
# virt-df(1) — free space
# virt-dib(1) — safe diskimage-builder
# virt-diff(1) — differences
# virt-edit(1) — edit a file
# virt-filesystems(1) — display information about filesystems, devices, LVM
# virt-format(1) — erase and make blank disks
# virt-get-kernel(1) — get kernel from disk
# virt-inspector(1) — inspect VM images
# virt-list-filesystems(1) — list filesystems
# virt-list-partitions(1) — list partitions
# virt-log(1) — display log files
# virt-ls(1) — list files
# virt-make-fs(1) — make a filesystem
# virt-p2v(1) — convert physical machine to run on KVM
# virt-p2v-make-disk(1) — make P2V ISO
# virt-p2v-make-kickstart(1) — make P2V kickstart
# virt-rescue(1) — rescue shell
# virt-resize(1) — resize virtual machines
# virt-sparsify(1) — make virtual machines sparse (thin-provisioned)
# virt-sysprep(1) — unconfigure a virtual machine before cloning
# virt-tail(1) — follow log file
# virt-tar(1) — archive and upload files
# virt-tar-in(1) — archive and upload files
# virt-tar-out(1) — archive and download files
 

Posted in Ubuntu

Ansible On Ubuntu

Posted on 28/11/2020 - 07/05/2021 by exforge

AnsibleOnUbuntu – Ansible Tutorial for Ubuntu Linux

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0. Specs

# 0.0. Servers managed from a workstation
#   This tutorial aims to bring you to a moderate level using Ansible. (Almost) All 
#      examples are tested and verified as working. There might be slight mistakes and
#      you can think of them as small challenges. 
#   This tutorial is about using Ansible on Ubuntu (and Debian) servers, but I believe
#      you can apply most of the examples to other servers like Centos.
#   I am not an expert of Ansible. Actually I prepared this tutorial while I was 
#     learning it. 
#
# 0.1. Workstation: lahana -> Ubuntu 20.04 LTS Desktop
# 0.2. Servers:
#        Local Virtual Servers:
#          test201, test202, test203 -> Ubuntu 20.04 LTS Server
#          test2010                  -> Ubuntu 20.10 Server
#          debian10                  -> Debian 10 Server
#        
# 0.3. Resources:
# Book: 978-1-4842-1660-6 Ansible From Beginner to Pro by Michael Heap
# Book: 978-1-78899-756-0 Mastering Ubuntu Server Second Edition by Jay LaCroix
https://docs.ansible.com/ansible/
https://www.howtoforge.com/ansible-guide-ad-hoc-command/
https://www.golinuxcloud.com/ansible-tutorial/
 

1. Installation and Main Configuration

# 1.1. Install ansible on workstation
sudo apt update
sudo apt install ansible
#
# 1.2. Create ansible user on all the servers and on the workstation
# !!!Run on workstation and on all servers!!!
#  Create user ansible and give it a password 
sudo useradd -d /home/ansible -m ansible -s /bin/bash
sudo passwd ansible
#  add it to the sudo group
sudo usermod -aG sudo ansible
#  make sure it is added
getent group sudo
#
# 1.3. Copy workstation's ansible user's ssh key to servers
# !!!Run only on workstation!!!
#   Change to ansible user
sudo su ansible
# Create SSH keys, leave passfield empty
ssh-keygen
# Copy ansible users SSH key to the servers
ssh-copy-id -i ~/.ssh/id_rsa.pub test201
ssh-copy-id -i ~/.ssh/id_rsa.pub test202
ssh-copy-id -i ~/.ssh/id_rsa.pub test203
ssh-copy-id -i ~/.ssh/id_rsa.pub test2010
ssh-copy-id -i ~/.ssh/id_rsa.pub debian10
#  Now we can ssh to servers with ansible user without password
#
# 1.4. On all servers, configure ansible user to sudo without password
# !!!Run on all servers!!!
#    create /etc/sudoers.d/ansible file
sudo nano /etc/sudoers.d/ansible
#  put the following line on it
#__________________
ansible ALL=(ALL) NOPASSWD: ALL
#__________________
#  make the file owned by root
sudo chown root:root /etc/sudoers.d/ansible
#   change the permissions of file 
sudo chmod 440 /etc/sudoers.d/ansible
# All the preliminary work is completed
# From now on all commands will be run on workstation
 

2. Configuration

# 2.1. Configuration File
# Ansible looks for the configuration file in the following order:
#  - File specified by the ANSIBLE_CONFIG environment variable
#  - ./ansible.cfg (ansible.cfg in the current directory)
#  - ~/.ansible.cfg (.ansible.cfg in your home directory)
#  - /etc/ansible/ansible.cfg
# My choice is to use the 3. option
# First change to user ansible (If you haven't done already)
sudo su ansible
nano /home/ansible/.ansible.cfg
#_________________________
[defaults]
inventory = .hosts
remote_user = ansible
roles_path = /home/ansible/ansible/playbooks
forks = 5
#_________________________
# We stated as:
#   our hosts file will be /home/ansible/.hosts
#   the remote user to use on servers is ansible
#   look to /etc/ansible/roles for extra roles
#   maximum 5 parallel tasks between the workstation and servers
# There are numerous thing to be configured, you may check them
#   at file /etc/ansible/ansible.cfg
#
# 2.2. Making a home for Ansible files
# I prefer placing all ansible files on /home/ansible/ansible
mkdir /home/ansible/ansible
# And a subdirectory for playbook (explained later)
mkdir /home/ansible/ansible/playbooks
#
# 2.3. Inventory File
# Create a clean inventory file
touch /home/ansible/.hosts
# Change ownership and permissions for ansible user
sudo chown ansible /home/ansible/.hosts
sudo chmod 600 /home/ansible/.hosts
# Populate the file with server IPs or names
nano /home/ansible/.hosts
#________________________
[ubuntu20]
test201.x386.xyz
test202.x386.xyz
test203.x386.xyz
test2010.x386.xyz
#
[debian]
debian10.x386.xyz
#
[internet]
hostens
hostens2
#
[local]
ubuntu
#________________________
# As in our sample, you can group hosts
#
# 2.4. A simple test
# Ping all our servers
ansible all -m ping
#   with full verbose
ansible all -m ping -vvvv
 

3. More on Inventory

# 3.1. it is possible to use a different inventory for each ansible or ansible-playbook
#   command:
ansible all –i /path/to/inventory –m ping
#
# 3.2. to use a different ssh port
host1.example.com:50822
#
# 3.3. Using ranges in host file names
host[1:3].example.com
host[a:d][a:z].example.com
#
# 3.4. Using options for user name, ssh port, ssh private key
alpha.example.com ansible_user=bob ansible_port=50022
bravo.example.com ansible_user=mary ansible_ssh_private_key_file=/path/to/mary.key
frontend.example.com ansible_port=50022
yellow.example.com ansible_host=192.168.33.10
#
# 3.5. If you want to use more than 1 inventory file you can put all your inventories 
#   in a directory and specify the directory as the inventory file. Below is a simple 
#   example.
sudo su ansible
mkdir /home/ansible/ansible/inventory
nano /home/ansible/ansible/inventory/inventory1
#__________________________________
test201
#__________________________________
nano /home/ansible/ansible/inventory/inventory2
#__________________________________
test203
#__________________________________
ansible all -i /home/ansible/ansible/inventory -m ping
#
# 3.6. If you want to use a dynamic host file, you can use a program which outputs the 
#   inventory. #   Then you can give your program as inventory file. Below is a very 
#   simple example.
sudo su ansible
nano /home/ansible/ansible/inventory.py
#______________________________________
#!/usr/bin/env python3
print("test201")
print("test202")
#______________________________________
chmod +x /home/ansible/ansible/inventory.py
ansible all -i /home/ansible/ansible/inventory.py -m ping
#
# 3.7. Needless to say; you can combine dynamic and static inventories, by combining 
#   methods in 3.5. and 3.6.
#
# 3.8. Groups of Groups
# You can create master groups to include other groups. Master groups require
#   children keyword.
# In my inventory, if I want to combine all local servers (virtual or not) I would
#   modify my inventory file as follows:
#________________________
[ubuntu20]
test201.x386.xyz
test202.x386.xyz
test203.x386.xyz
test2010.x386.xyz
#
[debian]
debian10.x386.xyz
#
[internet]
hostens
hostens2
#
[local]
ubuntu
#
[localservers:children]
ubuntu20
debian
local
#________________________
#
# 3.9. Inventory Variables
# You can define variables in inventory file. They might be host or group based.
#   For group based variables; var keyword is used. 
# Below, using my inventory file, I created a variable named role for a group and 
#   a host. 
[ubuntu20]
test201.x386.xyz
test202.x386.xyz
test203.x386.xyz
test2010.x386.xyz
#
[debian]
debian10.x386.xyz
#
[internet]
hostens role=webserver
hostens2
#
[local]
ubuntu
#
[ubuntu20:vars]
role="dbserver"
#________________________
# That way, using ansible, you can install apache to servers with webserver role and
#   install mariadb to servers with role dbserver.
 

4. Ansible Ad Hoc Commands

# You can run Ansible commands in 2 ways, 1 is direct (adhoc), 2 through playbooks. 
#   In the next section we will work on our first playbook. 
# Ad hoc commands might be suitable for one time tasks. For recurring tasks it would 
#   be wise to use playbooks.
# 4.1. Ping host(s)
# Actually we ran our 1st command at 2.4. Ping all hosts in default inventory.
ansible all -m ping
# We can specify another inventory
ansible all -i /home/ansible/ansible/inventory.py
ansible all -i /home/ansible/ansible/inventory -m ping
#
# 4.2. Run a shell command on hosts
ansible all -m shell -a "ls -al"
# -m can be ommitted
ansible test201 -a "ls -al"
# List of open ports on the servers
ansible all -m shell -a 'netstat -plntu' --become
#
# 4.3. File and Directory Operations
# Copy a file to servers
ansible all -m copy -a "src=/etc/apache2/apache2.conf dest=/tmp/apache2.conf"
# Create a directory on server
ansible all -m file -a "dest=/tmp/test mode=777 owner=ansible group=ansible state=directory"
# Delete a file or directory on server
ansible all -m file -a "dest=/home/ansible/test state=absent"
# Copy a file from server
ansible ubuntu20 -m fetch -a "src=/var/www/html/index.html dest=/home/ansible/backup flat=yes"
#
# 4.4. Reboot Servers
# Reboot all servers
ansible all -a "/sbin/reboot"
# Reboot all servers in 10 parallel forks (default is 5)
ansible all -a "/sbin/reboot" -f 10
# Reboot all servers with sudo privilege
ansible all -a "/sbin/reboot" --become
# Reboot all servers with sudo privilege, manually enter sudo password
ansible all -a "/sbin/reboot" --become --ask-become-pass
#
# 4.5. User Management
# Add a user
ansible all -m ansible.builtin.user -a "name=foo"
# Remove a user
ansible all -m ansible.builtin.user -a "name=foo state=absent"
#
# 4.6. Package Management (apt)
# Add a ppa repository
ansible ubuntu20 -m apt_repository -a "repo=ppa:ondrej/php state=present" --become 
# Remove a ppa repository
ansible ubuntu20 -m apt_repository -a "repo=ppa:ondrej/php state=absent" --become
# Update cache (apt update)
ansible ubuntu20 -m apt -a "update_cache=yes" --become
# update cache and upgrade all modules (apt update && apt upgrade)
ansible ubuntu20 -m apt -a "upgrade=dist update_cache=yes" --become
# Install apache (don't do anything if it is already installed)
ansible ubuntu20 -m apt -a "name=apache2 state=present" --become 
#  or
ansible ubuntu20 -m apt -a "name=apache2 state=installed" --become 
# Install apache, if it is already installed, update it
ansible ubuntu20 -m apt -a "name=apache2 state=latest" --become
# Remove apache
ansible ubuntu20 -m apt -a "name=apache2 state=absent" --become
# Remove apache and remove all configuration about it
ansible ubuntu20 -m apt -a "name=apache2 state=absent purge=yes" --become
# Remove apache, remove all configuration about it, and also remove all unused packages
ansible ubuntu20 -m apt -a "name=apache2 state=absent purge=yes autoremove=yes" --become
#
# 4.7. Service Management
# Start and Enable Apache service
ansible ubuntu20 -m service -a "name=apache2 state=started enabled=yes" --become
# Stop Apache service
ansible ubuntu20 -m service -a "name=apache2 state=stopped" --become
# Restart Apache service
ansible ubuntu20 -m service -a "name=apache2 state=restarted" --become
 

5. A Simple Playbook to Install Apache

# Playbooks are files in YAML format. They contains commands
#   to run by Ansible.
# Our playbook will install apache, and prepare a sample 
#   homepage containing the host name
# 5.1. Create directories
sudo su ansible
mkdir /home/ansible/ansible/playbooks/apache
mkdir /home/ansible/ansible/playbooks/apache/templates
cd /home/ansible/ansible/playbooks/apache
#
# 5.2. Create ansible file and index.html template
nano /home/ansible/ansible/playbooks/apache/apache.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Create webserver with apache
  become: True
  hosts: ubuntu20
  tasks:
  - name: install apache
    apt: name=apache2 update_cache=yes
  - name: copy index.html
    template: src=templates/index.html.j2 dest=/var/www/html/index.html
      mode=0644
  - name: restart apache
    service: name=apache2 state=restarted
#_____________________________________________________
nano /home/ansible/playbooks/apache/templates/index.html.j2
#_____________________________________________________
<html>
<head>
<title>Welcome to ansible on {{ ansible_hostname }}</title>
</head>
<body>
<h1>Apache, configured by Ansible on {{ inventory_hostname }}</h1>
<p>If you can see this, Ansible successfully installed Apache.</p>
</body>
</html>
#_____________________________________________________
#
# 5.3. Explanations
# /home/ansible/ansible/playbooks/apache/apache.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Create webserver with apache
# Name of the playbook, displayed when the playbook runs
  become: True
  # Use sudo
  hosts: ubuntu20
  # Host or host group to run on
  tasks:
  # Tasks to do in this playbook
  - name: install apache
  # Name of task, displayed when the playbook runs, install apache
    apt: name=apache2 update_cache=yes
    # Install apache2, first update the cache
    # Equivalent to:
    #   apt update
    #   apt install apache2
  - name: copy index.html
  # Name of task, displayed when the playbook runs, copy customized index.html
  #   from the template
    template: src=templates/index.html.j2 dest=/var/www/html/index.html
    # variables in index.html.j2 are updated and copied to server
      mode=0644
      # File mode will be 0644
  - name: restart apache
  # Name of task, displayed when the playbook runs, restart apache
    service: name=apache2 state=restarted
  # Restart apache, systemctl restart apache2
#_____________________________________________________
# Variables in /home/ansible/playbooks/apache/templates/index.html.j2
# {{ ansible_hostname }} : hostname as ansible gathers
# {{ inventory_hostname }} : hostname as in inventory file
#
# 5.4. Run the playbook
ansible-playbook apache.yml
# or just
./apache.yml
 

6. A More Complex Playbook to Install LAMP

# 6.0. Necessary Steps
# Cache Update (sudo apt update)
# Install Apache (sudo apt install apache2)
# Install Mariadb (sudo apt install mariadb-server)
# Install PHP (sudo apt install php libapache2-mod-php php-mysql)
#
# 6.1. Create Directories
sudo su ansible
mkdir /home/ansible/ansible/playbooks/lamp
cd /home/ansible/ansible/playbooks/lamp
#
# 6.2. Create ansible playbook
nano /home/ansible/ansible/playbooks/lamp/lamp.yml
#_______________________________________________________________________
#!/usr/bin/env ansible-playbook
- name: Install LAMP; Apache, MariaDB, PHP
  become: True
  hosts: test201
  tasks:
  - name: Update apt cache if not updated in 1 hour
    apt:
      update_cache: yes
      cache_valid_time: 3600
  - name: Install apache
    apt:
      name: apache2
      state: present
  - name: Install MariaDB
    apt:
      name: mariadb-server
      state: present
  - name: Install PHP and dependencies
    apt: 
      name: "{{ item }}"
      state: present
    loop:
      - php
      - libapache2-mod-php
      - php-mysql
#_______________________________________________________________________
 

7. (IMHO) Important Ansible Modules

# Well, actually all of the Ansible modules are important. I just selected some 
#   of them considering my very humble opinion.
# To use an example, you have to put it in a playbook or in a role and apply 
#   necessary indentation. Ansible is like Python, indentation is very important.
# Below is a sample, using an example in a playbook
#_______________________________________________________________________
#!/usr/bin/env ansible-playbook
- name: Tutorial tasks
  become: True
  hosts: test201
  tasks:
  - name: Start apache if not started
    service:
      name: apache2
      state: started
#_______________________________________________________________________
 

7.0. apk Module: Manages Alpine apk packages.

# Examples:
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apk:
    name: apache2
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apk:
    name: apache2
    state: present
#______________________________________________________________
- name: Install apache, upgrade to latest if already installed
  apk:
    name: apache2
    state: latest
#______________________________________________________________
- name: Update repositories and install apache
  apk:
    name: apache2
    update_cache: yes
#______________________________________________________________
- name: Remove apache
  apk:
    name: apache2
    state: absent
#______________________________________________________________
- name: Install more than 1 packages
  apk:
    name: apache2, php
#______________________________________________________________
- name: Update cache and update apache to latest
  apk:
    name: apache2
    state: latest
    update_cache: yes
#______________________________________________________________
- name: Update all packages to their latest version
  apk:
    upgrade: yes
#______________________________________________________________
- name: Update cache (apt-get update)
  apk:
    update_cache: yes
#______________________________________________________________
 

7.1. apt Module: Manages Debian/Ubuntu apt packages.

# Examples:
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apt:
    name: apache2
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apt:
    name: apache2
    state: present
#______________________________________________________________
- name: Install apache, upgrade to latest if already installed
  apt:
    name: apache2
    state: latest
#______________________________________________________________
- name: Update repositories and install apache
  apt:
    name: apache2
    update_cache: yes
#______________________________________________________________
- name: Remove apache
  apt:
    name: apache2
    state: absent
#______________________________________________________________
- name: Install more than 1 packages
  apt:
    pkg:
    - apache2
    - php
#______________________________________________________________
- name: Update cache and update apache to latest
  apt:
    name: apache2
    state: latest
    update_cache: yes
#______________________________________________________________
- name: Install latest php, ignore "install-recommends"
  apt:
    name: php
    state: latest
    install_recommends: no
#______________________________________________________________
- name: Update all packages to their latest version
  apt:
    name: "*"
    state: latest
#______________________________________________________________
- name: Upgrade the OS (apt-get dist-upgrade)
  apt:
    upgrade: dist
#______________________________________________________________
- name: Update cache (apt-get update)
  apt:
    update_cache: yes
#______________________________________________________________
- name: Update cache if the last update is more than 1 hour
  apt:
    update_cache: yes
    cache_valid_time: 3600
#______________________________________________________________
- name: Remove unused packages
  apt:
    autoclean: yes
#______________________________________________________________
- name: Remove unused dependencies
  apt:
    autoremove: yes
#______________________________________________________________
 

7.2. blockinfile Module: Insert/update/remove a text block between marked lines

# Examples:
#______________________________________________________________
- name: Add or update a block to a html file
  blockinfile:
    path: /var/www/html/index.html
    marker: "<!-- {mark} MANAGED by ANSIBLE BLOCK -->"
    # The block will be wrapper by this marker
    # {mark} is replaced as BEGIN at the beginning
    #   and END at the end.
    insertafter: "<body>"
    # The block with the markers will be inserted after the last
    #   match of this this text. Regexps can be used. If there is no
    #   match or value is EOF, block is added at the end of file.
    # Similarly insertbefore can be used.
    block: |
      <h1>Web server: {{ ansible_hostname }}</h1>
      <p>Update time: {{ ansible_date_time.date }}
      {{ ansible_date_time.time }} </p>
#______________________________________________________________
- name: Remove previously added block
  blockinfile:
    path: /var/www/html/index.html
    marker: "<!-- {mark} MANAGED by ANSIBLE BLOCK -->"
    block: ""
#______________________________________________________________
- name: Add mappings to /etc/hosts file, make a backup of file
  blockinfile:
    path: /etc/hosts
    backup: yes
    block: |
      {{ item.ip }} {{ item.hostname }}
    marker: "<!-- {mark} {{ item.hostname }} MANAGED by ANSIBLE BLOCK -->"
  loop:
    - { hostname: test201, ip: 192.168.0.201 }
    - { hostname: test202, ip: 192.168.0.202 }
    - { hostname: test203, ip: 192.168.0.203 }
#______________________________________________________________
 

7.3. command Module: Execute commands

# Examples:
#______________________________________________________________
- name: Run a command on server and take its output to a variable
  command: free
  register: freevals
#______________________________________________________________
- name: Run a command if a path does not exist
  command: /usr/sbin/reboot now creates=/etc/flag
#______________________________________________________________
- name: Run a command if a path does not exist
  command:
    cmd: /usr/sbin/reboot now
    creates: /etc/flag
#______________________________________________________________
- name: Run a command if a path does not exist
  command:
    argv:
      - /usr/sbin/reboot
      - now
    creates: /etc/flag
#______________________________________________________________
- name Become user dbadmin, change to home dir and run mariad backup
  command: mysqldump 
  become: yes
  become_user: dbadmin
  args:
    chdir: /home/dbadmin
#______________________________________________________________
 

7.4. copy Module: Copy files to remote servers

# Examples
#______________________________________________________________
- name: Copy a file with specified owner and permissions, backup the file
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: '0644'
    backup: yes
#______________________________________________________________
- name: Copy a file with specified owner and permissions
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: u=rw,g=r,o=r
#______________________________________________________________
- name: Copy a file with specified owner and permissions
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: u+rw,g-wx,o-rwx
#______________________________________________________________
- name: Copy a file with specified owner and permissions, backup the file
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: '0644'
    backup: yes
#______________________________________________________________
- name: Copy a file on the server to another location
  copy:
    src: /etc/apache2/apache.conf
    dest: /etc/apache2/apache.conf.backup
    remote_src: yes
#______________________________________________________________
- name: Copy an inline text to a file
  copy:
    content: "This file is empty"
    dest: /etc/test
#______________________________________________________________
 

7.5. debug Module: Print debug messages

# Examples:
#______________________________________________________________
- name: Display all variables/facts known for a host
  debug:
    var: hostvars[inventory_hostname]
#______________________________________________________________
- name: Display a message
  debug:
    msg: Working fine so far
#______________________________________________________________
- name: Print return information from a previous task part 1
  shell: /usr/bin/date
  register: result 
- name: Print return information from a previous task part 2
  debug:
    var: result.stdout_lines
#______________________________________________________________
- name: Print multi lines of information from variables part1
  shell: whoami
  register: var1
- name: Print multi lines of information from variables part2
  shell: who -b
  register: var2
- name: Print multi lines of information from variables part3
  debug:
    msg:
    - "Information gathered so far:"
    - "1. User name is {{ var1.stdout_lines }}"
    - "2. System is on since {{ var2.stdout_lines }}"
#______________________________________________________________
 

7.6. expect Module: Executes a command and responds to prompts

# Examples:
#______________________________________________________________
- name: Login to mariadb asking the root password and run a command from a file
  expect:
    command: /bin/bash -c "mariadb -u root -p < /tmp/test.sql"
    responses:
      (.*)password: "password12"
  register: DBUsers
  no_log: true
  # hide your password from log
- name: Display Result
  debug:
    var: DBUsers.stdout_lines
#______________________________________________________________
- name: Generic question with multiple different responses
  expect:
    command: command
    # Assuming a command asking 3 questions
    responses:
      Question:
        - Answer 1
        - Answer 2
        - Answer 3
#______________________________________________________________
 

7.7. fail Module: Fail with a message

# Examples:
#______________________________________________________________
- name: Stop execution if hostname is something special
  fail:
    msg: Cannot continue with hostname Test201
  when: inventory_hostname == "Test201"
#______________________________________________________________
 

7.8. fetch Module: Fetch files from server to the workstation

# Examples:
#______________________________________________________________
- name: Fetch server file, preserve directory information
  fetch:
    src: /etc/apache2/apache.conf
    dest: /tmp/conf
    # File will be copied to /tmp/conf/hostname/etc/apache2/apache.conf
#______________________________________________________________
- name: Fetch server file, directly to the specified directory
  fetch:
    src: /etc/apache2/apache.conf
    dest: /tmp/conf/apache.conf
    # File will be copied to /tmp/conf/apache.conf
    # Consecutive files will be overwritten
    flat: yes
#______________________________________________________________
- name: Fetch server file, directly to the specified directory
#   directly to the specified directory for every server
  fetch:
    src: /etc/apache2/apache.conf
    dest: /tmp/conf/{{ inventory_hostname }}/apache.conf
    flat: yes
#______________________________________________________________
 

7.9. file Module: File and directory management

# Examples:
#______________________________________________________________
- name: Change ownership and permission of a file
  file:
    path: /etc/test.conf
    owner: ansible
    group: ansible
    mode: '0644'
#______________________________________________________________
- name: Create a symbolic link of a file, change ownership of the original file
  file:
    src: /etc/test.conf
    dest: /home/ansible/test.conf
    owner: ansible
    group: ansible
    state: link
#______________________________________________________________
- name: Create a hard link
  file:
    src: /tmp/test.conf
    dest: /home/ansible/test.conf
    state: hard
#______________________________________________________________
- name: Touch a file and set permissions
  file:
    path: /etc/test.conf
    state: touch
    mode: u=rw,g=r,o=r
#______________________________________________________________
- name: Touch a file, but preserve its times. 
#  so there is no change if it was touched before
  file:
    path: /etc/foo.conf
    state: touch
    modification_time: preserve
    access_time: preserve
#______________________________________________________________
- name: Create a directory, do nothing if it already exists
  file:
    path: /tmp/test
    state: directory
    mode: '0755'
#______________________________________________________________
- name: Update modification and access time of a file to now
  file:
    path: /tmp/test.conf
    state: file
    modification_time: now
    access_time: now
#______________________________________________________________
- name: Change ownership of a directory recursively 
  file:
    path: /var/www
    state: directory
    recurse: yes
    owner: www-data
    group: www-data
#______________________________________________________________
- name: Delete a file
  file:
    path: /etc/test.conf
    state: absent
#______________________________________________________________
- name: Remove a directory recursively
  file:
    path: /etc/test
    state: absent
#______________________________________________________________
 

7.10. geturl Module: Download files

# Examples:
#______________________________________________________________
- name: Download a file (wordpress)
  get_url:
    url: https://wordpress.org/latest.tar.gz
    dest: /tmp/wordpress.tar.gz
    mode: '0440'
#______________________________________________________________
- name: Download file with md5 checksum
  get_url:
    url: https://wordpress.org/latest.tar.gz
    dest: /tmp/wordpress/wordpress.tar.gz
    checksum: md5:b7a9eb3560e5e17df79b7131a9fafdd7
#______________________________________________________________
 

7.11. group Module: Linux group management

# Examples:
#______________________________________________________________
- name: Create a group named admins
  group:
    name: admins
    state: present
#______________________________________________________________
- name: Create a group named admins gid 1250
  group:
    name: admins
    state: present
    gid: 1250
#______________________________________________________________
- name: Delete admins group
  group:
    name: admins
    state: absent
#______________________________________________________________
 

7.12. lineinfile Module: Manage lines in text files

# Uses a back referenced rexexp, and puts, updates or deletes a 
#   line in a file
# Examples:
#______________________________________________________________
- name: Change or add the name of an host in /etc/hosts
  lineinfile:
    path: /etc/hosts
    regexp: '^192\.168\.0\.201'
    line: 192.168.0.201 test201.x386.xyz
#______________________________________________________________
- name: Remove previously added line in /etc/hosts
  lineinfile:
    path: /etc/hosts
    regexp: '^192\.168\.0\.201'
    state: absent
#______________________________________________________________
- name: Create a file if it does not exist and add a line
  lineinfile:
    path: /tmp/test
    line: 192.168.0.201 test201.x386.xyz
    create: yes
#______________________________________________________________
 

7.13. pause Module: Pause execution

# Examples:
#______________________________________________________________
- name: Pause for 5 minutes
  pause:
    minutes: 5
#______________________________________________________________
- name: Pause for 30 seconds
  pause:
    seconds: 30
#______________________________________________________________
- name: Pause until prompted
  pause:
#______________________________________________________________
- name: Pause until prompted with message
  pause:
    prompt : "Press enter to continue"
#______________________________________________________________
- name: Pause to get password
  pause:
    prompt: "Enter password"
    echo: no
  register: password
#______________________________________________________________
 

7.14. reboot Module: Reboot server

# Examples:
#______________________________________________________________
 name: Reboot and connect again
  reboot:
#______________________________________________________________
- name: Reboot and wait up to 1 hour for connecting again
  reboot:
    reboot_timeout: 3600
#______________________________________________________________
- name: Display a message to users, wait 5 minutes and reboot
  reboot:
    pre_reboot_delay: 300
    msg: "Rebooting in 5 minutes, please save your work and exit"
#______________________________________________________________
 

7.15. replace Module: Replace a string in a file using a back ref regexp

# Examples:
#______________________________________________________________
- name: Replace all .org names with .com names in /etc/hosts
  replace:
    path: /etc/hosts
    regexp: '(.*)\.org(\s+)'
    # Starts with anything, then comes .org and one or more whitespace
    replace: '\1.com\2'
    # \1 = (.*)   \2 = (\s+)
#______________________________________________________________
- name: Do the same, but start after and expression and end other another
  replace:
    path: /etc/hosts
    after: 'Start Here'
    before: 'End Here'
    regexp: '(.*)\.org(\s+)'
    # Starts with anything, then comes .org and one or more whitespace
    replace: '\1.com\2'
    # \1 = (.*)   \2 = (\s+)
#______________________________________________________________
- name: Comment every line containing TEST, backup the original file
  replace:
    path: /tmp/test.sh
    regexp: '^(.*)TEST(.*)$'
    replace: '#\1TEST\2'
    backup: yes
#______________________________________________________________
 

7.16. script Module: Transfer and run a script from workstation to server

# A script on the worktation is copied to the server(s) and run there
# Examples:
#______________________________________________________________
- name: Run a script 
  script: /home/ansible/ansible/backup.sh all
#______________________________________________________________
- name: Run a script
  script:
    cmd: /home/ansible/ansible/backup.sh all
#______________________________________________________________
- name: Run a script only if a file does not exist on the server
  script: /home/ansible/ansible/backup.sh all
  args:
    creates: /tmp/backup.txt
#______________________________________________________________
- name: Run a script only if a file exists on the server
  script: /home/ansible/ansible/backup.sh all
  args:
    removes: /tmp/backup.txt
#______________________________________________________________
- name: Run a script using bash
  script: /home/ansible/ansible/backup.sh
  args:
    executable: /bin/bash
#______________________________________________________________
- name: Run a python script
  script: /home/ansible/ansible/backup.py
  args:
    executable: python3
#______________________________________________________________
 

7.17. service Module: Manage services

# Examples:
#______________________________________________________________
- name: Start apache if not started
  service:
    name: apache2
    state: started
#______________________________________________________________
- name: Stop apache if started
  service:
    name: apache2
    state: stopped
#______________________________________________________________
- name: Restart apache
  service:
    name: apache2
    state: restarted
#______________________________________________________________
- name: Reload apache
  service:
    name: apache2
    state: reloaded
#______________________________________________________________
- name: Enable apache service, do not touch the state
  service:
    name: apache2
        enabled: yes
#______________________________________________________________
 

7.18. shell Module: Execute shell commands on servers

# Different from command module, redirection and pipes are safe
# Examples:
#______________________________________________________________
- name: Execute command on remote shell; stdout to a file
  shell: backup.sh >> backup.log
#______________________________________________________________
- name: Execute command on remote shell; stdout to a file
  shell: 
    cmd: backup.sh >> backup.log
#______________________________________________________________
- name: Change to a directory before executing a command
  shell: backup.sh >> backup.log
  args:
    chdir: /tmp/
#______________________________________________________________
- name: command only if a file does not exist
  shell: backup.sh >> backup.log
  args:
    creates: backup.log
#______________________________________________________________
- name: Change to a directory before executing a command, disable warning
  shell: backup.sh >> backup.log
  args:
    chdir: /tmp/
    warn: no
#______________________________________________________________
 

7.19. tempfile Module: Create a temporary file or directory

# Examples:
#______________________________________________________________
- name: Create a temporary directory with suffix tempdir
  tempfile:
    state: directory
    suffix: tempdir
#______________________________________________________________
- name: Create a temporary file with suffix and save its name to a variable
  tempfile:
    state: file
    suffix: temp
  register: tempfilename
#______________________________________________________________
- name: Use the variable created above to remove the file
  file:
    path: "{{ tempfilename.path }}"
    state: absent
  when: tempfilename.path is defined
#______________________________________________________________
 

7.20. template Module: Copy a file to servers using a template

# Unlike file module, you can use variables in template files
#   like in 5.2.
# Examples:
#______________________________________________________________
- name: Create an html file from a template
  template:
    src: /home/ansible/ansible/playbooks/apache/templates/index.html.j2
    dest: /var/www/html/index.html
    owner: www-data
    group: www-data
    mode: '0660'
#______________________________________________________________
- name: Create an html file from a template
  template:
    src: /home/ansible/ansible/playbooks/apache/templates/index.html.j2
    dest: /var/www/html/index.html
    owner: www-data
    group: www-data
    mode: u=rw,g=r
#______________________________________________________________
 

7.21. unarchive Module: Unpack an archive

# The archive might be on the workstation or on the server
# Examples:
#______________________________________________________________
- name: Extract a tar.xz file
  unarchive:
    src: /home/ansible/test.tar.xz
    dest: /tmp
#______________________________________________________________
- name: Unarchive a file on the server
  unarchive:
    src: /home/ansible/test.zip
    dest: /tmp
    remote_src: yes
#______________________________________________________________
- name: Download and unpack wordpress
  unarchive:
    src: https://wordpress.org/latest.zip
    dest: /var/www/html
    remote_src: yes
#______________________________________________________________
 

7.22. user Module: User management

# Examples:
#______________________________________________________________
- name: Add user exforge with a primary group with the same name
  user:
    name: exforge
    comment: main user
    group: exforge
#______________________________________________________________
- name: Add user exforge with a primary group with the same name,
#   with a specific uid
  user:
    name: exforge
    comment: main user
    uid: 1111
    group: exforge
#______________________________________________________________
- name: Add user exforge with bash shell, append the user to www-data and postfix group
  user:
    name: exforge
    shell: /bin/bash
    groups: www-data,postfix
    append: yes
#______________________________________________________________
- name: Add vmail user with a specific home dir
  user:
    name: vmail
    comment: Postfix Mail User
    uid: 2222
    group: vmail
    home: /var/mail
#______________________________________________________________
- name: Remove user exforge
  user:
    name: exforge
    state: absent
#______________________________________________________________
- name: Remove user exforge, remove directories too
  user:
    name: exforge
    state: absent
    remove: yes
#______________________________________________________________
 

8. Roles

# 8.0. Introduction
# Playbooks can be splitted into roles. That way, we can create reusable code.
# The lamp example at 7. will be rewritten using 4 roles:
#   Cache Update
#   Install Apache
#   Install MariaDB
#   Install PHP and dependencies
# 8.1. Role Structure
# Roles are created with ansible-galaxy init command. Naming convetion for roles is
#   as identifier.role. I use exforge as my identifier, you can use anything you 
#   want. For a role to install apache, my rolename would be exforge.apache
ansible-galaxy init exforge.apache
# A directory with role.name is created under the current directory with
#   the following structure:
#
# README.md  (file)
# defaults  (directory)
#    defaults/main.yml  (file)
# files  (directory)
# handlers  (directory)
#    handlers/main.yml (file)
# meta  (directory)
#    meta/main.yml (file)
# tasks  (directory)
#    tasks/main.yml (file)
# templates  (directory)
# tests  (directory)
#    tests/inventory  (directory)
#    tests/test.yml (file)
# vars  (directory)
#    vars/main.yml  (file)
#
# All of the directories and files are optional.
# README.md file is used for documentation. Expected to contain the purpose
#   of the role and any other important information.
# defaults/main.yml is used as a configuration file to define default variables in
#   the role. Variables in vars/main.yml overrides variables defined here.
# files directory is used to place static files. Files used in roles without any
#   manipulation can be stored here.
# handlers/main.yml is used to define handlers (like starting, stopping or 
#   restarting services). 
# meta/main.yml is used to contain metadata for the role. Metadata can be used
#   if you want to publish your role to Ansible Galaxy.
# tasks/main.yml is the main file of the role. Expected to contain role actions. 
#   Actions here will be executed when your role runs.
# templates directory is used to place template (dynamic) files. The files here can 
#   contain variables to interpolate them before using on target systems.
# test directory is used to create test playbooks to consume the role. Mostly used to
#   test roleswith a system like Jenkins or Travis.
# vars/main.yml is similar to defaults/main.yml with an exception. Variables defined 
#   here overrides the variables defined at fact gathering section. Variables defined
#   here also overrides the variables defined in defaults/main.yml.
#
# 8.2. Preparing LAMP Roles
# We will have 4 roles for LAMP installation. Namely; aptcache, apache, mariadb and
#   php.
# Create a directory for the roles and init the roles:
mkdir -p /home/ansible/ansible/playbooks/roles
cd /home/ansible/ansible/playbooks/roles
ansible-galaxy init exforge.aptcache
ansible-galaxy init exforge.apache
ansible-galaxy init exforge.mariadb
ansible-galaxy init exforge.php
#
# 8.3. Create the new playbook with the roles
nano /home/ansible/ansible/playbooks/lamp.yml
#_______________________________________
#!/usr/bin/env ansible-playbook
---
- hosts: test201
  become: true
  roles:
  - exforge.aptcache 
  - exforge.apache 
  - exforge.mariadb
  - exforge.php 
#_______________________________________
#
# Make it executable
chmod +x /home/ansible/ansible/playbooks/lamp.yml
#
# 8.4. aptcache role
nano /home/ansible/ansible/playbooks/roles/exforge.aptcache/tasks/main.yml
#_______________________________________
---
# tasks file for exforge.aptcache
- name: Update apt cache if not updated in 1 hour
  apt:
    update_cache: yes
    cache_valid_time: 3600
#_______________________________________
#
# 8.5. apache role
nano /home/ansible/ansible/playbooks/roles/exforge.apache/tasks/main.yml
#_______________________________________
---
# tasks file for exforge.apache
- name: Install apache
  apt:
    name: apache2
    state: present
#_______________________________________
#
# 8.6. mariadb role
nano /home/ansible/ansible/playbooks/roles/exforge.mariadb/tasks/main.yml
#_______________________________________
---
# tasks file for exforge.mariadb
- name: Install MariaDB
  apt:
    name: mariadb-server
    state: present
#_______________________________________
#
# 8.7. php role
nano /home/ansible/ansible/playbooks/roles/exforge.php/tasks/main.yml
#_______________________________________
- name: Install PHP and dependencies
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - php
    - libapache2-mod-php
    - php-mysql
#_______________________________________
#
# 8.8. Running the new playbook
cd /home/ansible/ansible/playbooks
ansible-playbook lamp.yml
 

9.Ansible Facts and Magic Variables

# 9.1. Ansible Facts
# 9.1.1. Getting Facts
# Get all facts for test201 server
ansible test201 -m setup
# The output will be long, something like:
#_____________________________________________________________________
test201 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.0.111"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::a00:27ff:fe10:9"
        ],
        "ansible_apparmor": {
            "status": "enabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "12/01/2006",
        "ansible_bios_version": "VirtualBox",
        "ansible_cmdline": {
            "BOOT_IMAGE": "/boot/vmlinuz-5.4.0-48-generic",
            "maybe-ubiquity": true,
            "ro": true,
            "root": "UUID=8842fb18-ffef-4b0d-8c10-419865ae27a2"
        },
        "ansible_date_time": {
            "date": "2020-10-16",
            "day": "16",
            "epoch": "1602869883",
            "hour": "17",
            "iso8601": "2020-10-16T17:38:03Z",
            "iso8601_basic": "20201016T173803661252",
            "iso8601_basic_short": "20201016T173803",
            "iso8601_micro": "2020-10-16T17:38:03.661346Z",
            "minute": "38",
            "month": "10",
            "second": "03",
            "time": "17:38:03",
            "tz": "UTC",
            "tz_offset": "+0000",
            "weekday": "Friday",
            "weekday_number": "5",
            "weeknumber": "41",
            "year": "2020"
        },
        "ansible_system_capabilities_enforced": "True",
        "ansible_system_vendor": "innotek GmbH",
        "ansible_uptime_seconds": 244,
        "ansible_user_dir": "/home/ansible",
        "ansible_user_gecos": "",
        "ansible_user_gid": 1001,
        "ansible_user_id": "ansible",
        "ansible_user_shell": "/bin/bash",
        "ansible_user_uid": 1001,
        "ansible_userspace_architecture": "x86_64",
        "ansible_userspace_bits": "64",
        "ansible_virtualization_role": "guest",
        "ansible_virtualization_type": "virtualbox",
        "discovered_interpreter_python": "/usr/bin/python3",
        "gather_subset": [
            "all"
        ],
        "module_setup": true
    },
    "changed": false
#_________________________________________________________________________
#
# 9.1.2. Accessing Ansible Facts
# You can use any value from the facts as a variable. Some examples:
# Model of first disk
{{ ansible_facts['devices']['xvda']['model'] }}
# System hostname
{{ ansible_facts['nodename'] }}
# Using another system's fact
{{ hostvars['asdf.example.com']['ansible_facts']['os_family'] }}
# 10.1.3. Important Ansible Facts
# Date and time
ansible_date_time.date -->  "2020-11-11"
ansible_date_time.time -->  "08:44:06"
# OS
ansible_os_family      -->  "Debian" for Debian and Ubuntu, "RedHat" for CentOS
ansible_distribution   -->  "Ubuntu" for Ubuntu, "Debian" for Debian, "CentOS" for CentOS
ansible_hostname       -->  "test201"
ansible_distribution_version --> "20.04"
#
# 9.2. Ansible Magic Variables
inventory_hostname: Hostname as in inventory
inventory_hostname_short: Hostname as in inventory short format
ansible_play_hosts: list of all hosts still active in the current play.
ansible_play_batch: list of hostnames that are in scope for the current ‘batch’ of the play.
ansible_playbook_python: Path to the python executable used to invoke the Ansible command line tool.
inventory_dir: Pathname of the directory holding Ansible’s inventory 
inventory_file: Pathname and the filename pointing to the Ansible’s inventory host file.
playbook_dir: Playbook base directory.
role_path: current role’s pathname and only works inside a role.
ansible_check_mode: Boolean, set to True if you run Ansible with --check.
 

10. Distinguishing Linux Distribution

# Debian and Ubuntu name name Apache Server as apache2 and use apt package manager. 
#   Alpine also names Apache Server as apache2 and uses apk package manager. RHEL 
#   (and Alma) names it as httpd and use dnf package manager. 
# Our example role will distinguish the distribution and call appropriate tasks.
#
# 10.1. A Playbook to install Apache on Ubuntu (and Debian) and Centos (and Redhat)
nano /home/ansible/ansible/playbooks/apache.yml
#______________________________________________________
#!/usr/bin/env ansible-playbook
- name: Install Apache on Ubuntu (Debian), RHEL (Alma) and Alpine
  become: True
  hosts: all
  tasks:
  - name: install apache if Ubuntu or Debian
    apt: 
      name: apache2 
      update_cache: yes
    when: ansible_os_family == "Debian"
  - name: install apache if Redhat or Alma
    dnf: 
      name: httpd
    when: (ansible_os_family == "RedHat") or (ansible_os_family == "AlmaLinux")
  - name: install apache if Alpine
    apk: 
      name: apache2 
      update_cache: yes
    when: ansible_os_family == "Alpine"
#______________________________________________________
#
# 10.2. Other OS Families
# Some of the other possible ansible_os_family options are:
#   "Debian" for Linux Mint, Neon, KDE Neon, Raspbian
#   "RedHat" for Centos, Fedora, Scientific, CloudLinux, PSBM, OracleLinux, Amazon
#   "AlmaLinux" for Alma
#   "Suse" for Suse, OpenSuSe, SLES, SLED
#   "Gentoo" for Gentoo
#   "Archlinux" for ArchLinux, Manjaro
#   "Mandrake" for Mandrake, Mandriva
#   "Solaris"  for Solaris, Nexenta, OnmiOS, OpenIndiana, SmartOS
#   "Slackware" for Slackware
#   "Darwin" for MacOSX
#
# 10.3. A Role to install Apache on Ubuntu (Debian), Alma (Redhat), and Alpine
cd /home/ansible/ansible/playbooks/roles
ansible-galaxy init exforge.apacheDRA
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/main.yml
#_______________________________________________________________
---
# tasks file for exforge.apacheDRA
- include_tasks: debian.yml
  when: ansible_os_family == "Debian"
- include_tasks: redhat.yml
  when: (ansible_os_family == "RedHat") or (ansible_os_family == "AlmaLinux")
- include_tasks: alpine.yml
  when: ansible_os_family == "Alpine"
#_______________________________________________________________
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/debian.yml
#_______________________________________________________________
- name: install apache if Ubuntu or Debian
  apt: 
    name: apache2 
    state: present
    update_cache: yes
#_______________________________________________________________
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/redhat.yml
#_______________________________________________________________
- name: install apache if RedHat or Alma
  dnf: 
    name: httpd 
    state: present
#_______________________________________________________________
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/alpine.yml
#_______________________________________________________________
- name: install apache if Alpine
  apk: 
    name: apache2 
    state: present
    update_cache: yes
#_______________________________________________________________
# Now we can create a playbook to consume this role
nano  /home/ansible/ansible/playbooks/apacheDRA.yml
#_______________________________________________________________
#!/usr/bin/env ansible-playbook
---
- hosts: test
  become: true
  roles:
  - exforge.apacheDRA
#_______________________________________________________________
# Run the playbook
cd /home/ansible/ansible/playbooks
ansible-playbook apacheDRA.yml
#
# 10.1.4. Exercise
# Redesign apacheDR role, using package module.
 

11. Role Variables

# You can set role variables when you consume a role in a playbook. The variables
#   are defined in the roles and can be set values at the playbook.
# Our example will install apache (if it is not installed), create a configuration
#   with the given site name and create a default page with the given parameters.
# After creating the role, default variables will be defined in defaults/main.yml dir,
#   apache conf file and html templates will be created at templates/ dir, and role 
#   tasks will be coded at tasks/main.yml. After all, we will create a playbook, 
#   set all variables there and run the role.
#
# 11.1. Create the role apachesite
cd /home/ansible/ansible/playbooks/roles
ansible-galaxy init exforge.apachesite
# 
# 11.2. Define Variables
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/defaults/main.yml
#____________________________________________________
---
# defaults file for exforge.apachesite
server_name: www.example.com
server_alias: example.com
html_title: Welcome to {{ ansible_hostname }}
html_header: Welcome to {{ ansible_hostname }}
html_text: This page is created by Ansible
#____________________________________________________
#
# 11.3. Create Apache conf file and index.html file templates
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/apache.conf.j2
#____________________________________________________
<VirtualHost *:80>
  ServerAdmin webmaster@{{ server_name }}
  ServerName {{ server_name }}
  ServerAlias {{ server_alias }}
  DocumentRoot /var/www/{{ server_name }}
  ErrorLog ${APACHE_LOG_DIR}/{{ server_name }}-error.log
  CustomLog ${APACHE_LOG_DIR}/{{ server_name }}-access.log combined
</VirtualHost>
#____________________________________________________
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/index.html.j2
#____________________________________________________
<html>
<head>
<title>{{ html_title }}</title>
</head>
<body>
<h1>{{ html_header }}</h1>
<p>{{ html_text }}</p>
</body>
</html>
#____________________________________________________
#
# 11.4. Create Tasks
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/tasks/main.yml
#____________________________________________________
---
# tasks file for exforge.apachesite
- name: Stop execution if OS is not in Debian family
  fail:
    msg: Only works on Debian and her children (Ubuntu, Mint, ..)
  when: ansible_os_family != "Debian"
- name: Install apache2 if not already installed
  apt: 
    name: apache2 
    state: present
    update_cache: yes
- name: Create apache conf file from the template
# File is named as servername.conf and will be put in /etc/apache2/sites-available
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/apache.conf.j2
    dest: /etc/apache2/sites-available/{{ server_name }}.conf
    mode: "0644"
    owner: root
    group: root
- name: Enable new conf
# It will be enabled if we create a link to this conf file in /etc/apache2/sites-enabled
  file:
    src: /etc/apache2/sites-available/{{ server_name }}.conf
    dest: /etc/apache2/sites-enabled/{{ server_name }}.conf
    remote_src: yes
    owner: root
    group: root
    state: link
- name: Create home directory for the site
# Home directory will be /var/www/server_name
  file:
    path: /var/www/{{ server_name }}
    state: directory
    mode: "0770"
    owner: www-data
    group: www-data
- name: Copy index.html to site's home directory
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/index.html.j2
    dest: /var/www/{{ server_name }}/index.html
    mode: "0644"
    owner: www-data
    group: www-data
- name: Reload apache2
  service:
    name: apache2
    state: reloaded
#____________________________________________________
#
# 11.5. Create a playbook and consume the role
nano /home/ansible/ansible/playbooks/apachesite.yml
#____________________________________________________
#!/usr/bin/env ansible-playbook
---
- hosts: test201
  become: true
  vars:
    server_name: test201.x386.xyz
    server_alias: test201
    html_title: test201.x386.xyz Homepage
    html_header: This is the homepage of test201.x386.xyz
    html_text: This is a sample page created by Ansible    
  roles:
  - exforge.apachesite
#_______________________________________________________________
# Run the playbook
cd /home/ansible/ansible/playbooks
ansible-playbook apachesite.yml
#____________________________________________________
 

12. Variable Filters

# There are a number of filters available for the variables used in templates, 
#   playbooks and roles.
#
# 12.1. Syntax Filters
# Allows case manipulation: lowercase, uppercase, capital case, title case
# my_message: We are the world
# {{ my_message | lower }}  --> we are the world
# {{ my_message | upper }}  --> WE ARE THE WORLD
# {{ my_message | capitalize }}  --> We are the world
# {{ my_message | title }}  --> We Are The World
#
# 12.2. Default Filter
# Using an undefined variable causes an error, to avoid that situation default filter
#   can be used.
# {{ my_message | default('No message') }}
#
# 12.3. List Filters
# List definition is similar to Python:
#   num_list: [1,2,3,4,5,6,7,8,9,0]
# Some of the list filters are: max, min and random
# {{ num_list | max }}  --> 9
# {{ num_list | min }}  --> 0
# {{ num_list | random }}  --> a random one
#
# 12.4. Pathname Filters
# First, let's define a variable containing a path
#   path: "/etc/apache2/apache2.conf"
# Two of the most important filters are; dirname and basename
# {{ path | dirname }}   --> /etc/apache2
# {{ path | basename }}  --> apache2.conf
#
# 12.5. Date and Time Filters
# {{ '%d-%m-%Y' | strftime }}  --> Current date
# {{ '%H:%M:%S' | strftime }}  --> Current time
# {{ '%d-%m-%Y %H:%M:%S' | strftime }}  --> Current date and time
#
# 12.6. Math Filters
# {{ num | log }}     --> log of num on base e
# {{ num | log(10) }} --> log of num on base 10
# {{ num | pow(2) }}  --> square of num
# {{ num | root }}    --> square root of num
# {{ num | root(3) }} --> third root of num
# {{ num | abs }}     --> absolute of num
# {{ num | round }}   --> round of num
#
# 12.7. Encryption Filters
# {{ my_message | hash('sha1') }}  --> sha1 hash of variable
# {{ my_message | hash('md5') }}   --> md5 hash of variable
# {{ my_message | checksum }}     --> checksum of variable
#
# 12.8. An Example Playbook to Cover all the Filters Here
nano /home/ansible/ansible/playbooks/filters.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Demonstration of Filters
  become: True
  hosts: test201
  vars:
    my_message: "We are the world"
    num_list: [1,2,3,4,5,6,7,8,9,0]
    path: "/etc/apache2/apache2.conf"
    num: 85
    num2: -8
    num3: 2.6
  tasks:
  - name: All the filters
    debug:
      msg:
        - "My original message: {{ my_message }}"
        - "My message in lowercase: {{ my_message | lower }}"
        - "My message in upper: {{ my_message | upper }}"
        - "My message in sentence case: {{ my_message | capitalize }}"
        - "My message in title case: {{ my_message | title }}"
        - "Sha1 hash of my message: {{ my_message | hash('sha1') }}"
        - "Md5 hash of my message: {{ my_message | hash('md5') }}" 
        - "checksum of my message: {{ my_message | checksum }}"
        - "---"
        - "Default value: {{ my_message2 | default('No message') }}"
        - "---"
        - "My list: {{ num_list }}"
        - "Maximum of list: {{ num_list | max }}"
        - "Minimum of list: {{ num_list | min }}"
        - "A random item of list: {{ num_list | random }}"
        - "---"
        - "Path: {{path}}"
        - "Directory of path: {{ path | dirname }}"
        - "Filename of path: {{ path | basename }}"
        - "---"
        - "Current date: {{ '%d-%m-%Y' | strftime }}"
        - "Current time: {{ '%H:%M:%S' | strftime }}"
        - "Current date and time: {{ '%d-%m-%Y %H:%M:%S' | strftime }}"
        - "---"
        - "e base log of {{ num }}: {{ num | log }}"
        - "10 base log of {{ num }}: {{ num | log(10) }}"
        - "Square of {{ num }}: {{ num | pow(2) }}"
        - "4th power of {{ num }}: {{ num | pow(4) }}"
        - "Square root of {{ num }}: {{ num | root }}"
        - "3rd root of {{ num }}: {{ num | root(3) }}"
        - "Absolute of {{ num2 }}: {{ num2 | abs }}"
        - "Round of {{ num3 }}: {{ num3 | round }}"
#_____________________________________________________        
 

13. Handlers

# If you want a task to run when something is changed, you can use handlers. For
#   example, a task tries to change a conf file for apache, and you need to reload
#   or restart apache if the file is changed. That is when you use handlers.
# You might remember, there is a folder for handlers for the roles. That is you are
#   expected to put your handlers there. 
#
# 13.1. A Simple Example
# Our example playbook will install apache and reload it if it is installed. 
nano /home/ansible/ansible/playbooks/simple_handler.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Simple handler example
  become: true
  hosts: test201
  tasks:
  - name: Install Apache
    apt:
      name: apache2
      state: present
    notify: restart_apache
  handlers:
  - name: restart_apache
    service:
      name: apache2
      state: restarted
#
# 13.2. Handlers in Roles
# Let's change the role in apachesite in 11. so that it includes handlers.
# First change tasks in tasks folder:
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/tasks/main.yml
#____________________________________________________
---
# tasks file for exforge.apachesite
- name: Stop execution if OS is not in Debian family
  fail:
    msg: Only works on Debian and her children (Ubuntu, Mint, ..)
  when: ansible_os_family != "Debian"
- name: Install apache2 if not already installed
  apt: 
    name: apache2 
    state: present
    update_cache: yes
- name: Create apache conf file from the template
# File is named as servername.conf and will be put in /etc/apache2/sites-available
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/apache.conf.j2
    dest: /etc/apache2/sites-available/{{ server_name }}.conf
    mode: "0644"
    owner: root
    group: root
  notify: reload_apache
- name: Enable new conf
# It will be enabled if we create a link to this conf file in /etc/apache2/sites-enabled
  file:
    src: /etc/apache2/sites-available/{{ server_name }}.conf
    dest: /etc/apache2/sites-enabled/{{ server_name }}.conf
    remote_src: yes
    owner: root
    group: root
    state: link
  notify: reload_apache
- name: Create home directory for the site
# Home directory will be /var/www/server_name
  file:
    path: /var/www/{{ server_name }}
    state: directory
    mode: "0770"
    owner: www-data
    group: www-data
  notify: reload_apache
- name: Copy index.html to site's home directory
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/index.html.j2
    dest: /var/www/{{ server_name }}/index.html
    mode: "0644"
    owner: www-data
    group: www-data
  notify: reload_apache
#____________________________________________________
# Handlers run after the play is finished, so if a handler called twice (or more), it will
#   run only once.
#
# Add handlers
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/handlers/main.yml
#____________________________________________________
---
# handlers file for exforge.apachesite
- name: reload_apache
  service:
    name: apache2
    state: reloaded
#____________________________________________________
#
# Now you can run the role with handlers by calling the playbook we wrote at 11:
# Run the playbook
cd /home/ansible/ansible/playbooks
ansible-playbook apachesite.yml
 

14. Error Recovery (Block and rescue)

# Ansible has an exception handling (error recovery) mechanism similar to Python's
#   try-except-finally block.
# 14.1. Block-Rescue-Always usage
# A very simple example playbook would be:
nano /home/ansible/ansible/playbooks/blocktest.yml
#_______________________________________________
#!/usr/bin/env ansible-playbook
- name: Demonstration block-rescue-always
  become: True
  hosts: test201
  vars:
    message1: "1. Message"
    message2: "2. Message"
  tasks:
  - block:
    - name: Task 1
      debug:
        msg: "{{ message1 }}"
    - name: Task 2
      debug:
        msg: "{{ message2 }}"
    - name: Task 3  (Error expected, variable is not defined)
      debug:
        msg: "{{ message3 }}"
    - name: Task 4  (Never expected to run)
      debug:
        msg: "{{ message4 }}"
    rescue:
      - name: Rescue Task
        debug:
          msg: "Some of the messages could not be displayed"
    always:
      - name: Always Task
        debug:
          msg: "Job finished"
#_______________________________________________
#
# 14.2. Explanations
# Tasks in the block (Tasks 1, 2, 3 and 4 in our example) run sequentially. 
# If an error occurs in any task (Task 3 in our example), execution stops and 
#   the control goes to rescue task. Then the tasks in rescue block (Rescue Task in our
#   example) run. Then the tasks in always block (Always task in our example) run.
# If there is no error in tasks, rescue block is skipped and the tasks in always block
#   (Always task in our example) run.
#
# Error recovery is a very important subject in all kinds of programming. I believe you
#   should use it as much as possible to prevent an unexpected termination of programs
#   (playbooks for ansible).
 

15. Skipped Content

# I skipped the following subjects just because I think I won't use them. I believe
#   most of you won't use them ever. 
#
# Ansible vault
# Ansible pull
# Ansible collections
# Testing (with a test tool)
# Writing your own modules
# Using Ansible on Windows servers
# And may be some more that I am not able to know now :)
 

Posted in Ubuntu

User and Group Administration on Ubuntu

Posted on 13/07/2020 - 29/03/2021 by exforge

UserAndGroupAdminOnUbuntu: User and Group Administration on Ubuntu 20.04

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# Aimed for Ubuntu 20.04 Servers, but works fine on Ubuntu 18.04 Server and
#   on for all Ubuntu Desktops (Ubuntu, Kubuntu, Xubuntu, Lubuntu etc) too
#
# Based on the book Mastering Ubuntu Server 2nd Ed. by Jay LaCroix
#   This book has introduced me to Ubuntu Server and I have to thank him for this
#   excellent book. 
https://www.packtpub.com/networking-and-servers/mastering-ubuntu-server-second-edition
#
 

1. User Add and Delete

# 1.1. Add a new user jdoe and create home folder
sudo useradd -d /home/jdoe -m jdoe
#
# 1.2. Change user's password
sudo passwd jdoe
#
# 1.3. Delete a user
sudo userdel jdoe
#   remove home directory too
sudo userdel -r jdoe
 

2. Where the user information stored

# 2.1. User account information is stored in two special text files
cat /etc/passwd
sudo cat /etc/shadow
#
# 2.2. /etc/passwd file
#__________________________________
exforge:x:1000:1000:Exforge,,,:/home/exforge:/bin/bash
username:pw:UID:GID:Name,Surname,XX:homefolder:shell
#__________________________________
#
# 2.3. /etc/shadow file
# Passwords are stored as hashed in shadow file
#__________________________________
#  exforge:$6$z09H4l.6$h....A/tDL0:18221:0:99999:7:::
#  username:pwHash:DatesSinceLastPwChange:MinDaysToChangePw:
#   MaxDaysToChangePw:DaysBeforeUserWarnedToChangePw:
#   DaysToPwExpire:DaysToUserDisable
#__________________________________
#
# 2.4. User pw information extracted from /etc/shadow
sudo passwd -S username
#
# 2.5. Default contents for home folders:
#   Contents of /etc/skel folder is distributed to
#      created user's home directory
 

3. root user

# 3.1. root account is locked by default in Ubuntu
#   to give a pw to (and unlock) root
sudo passwd
#
# 3.2. switch to root account without unlocking it
sudo -i
#
# 3.3. switch to another user (if you know pw)
su - username
#
# 3.4. switch to another user (if you don't know pw)
sudo su - username
 

4. Batch user add

# 4.1. Create a text file for users
touch users.txt
# 4.2. Change the permissions of the file
chmod 600 users.txt
# 4.3. Add users information to the file
nano users.txt
#___________________________________
user1:password:::User1:/home/user1:/bin/bash
user2:password:::User2:/home/user2:/bin/bash
user3:password:::User3:/home/user3:/bin/bash
#___________________________________
#username:passwd:uid:gid:full name:home_dir:shell
#
# 4.4. Process file to add users
sudo newusers users.txt
#   You can check users from /etc/passwd
# 4.5. It is a good idea to change passwords of the users
sudo passwd user1
 

5. Group Management

# 5.1. List of groups
groups
#   or
cat /etc/group  
# it is similar to /etc/password
#
# 5.2. Add a new group
sudo groupadd admins
#
# 5.3. Delete a group
sudo groupdel admins
#
# 5.4. List members of a group
getent group groupname
#
# 5.5. Add a user to a group
# -a append new group to groups of user
# -G as a secondary group
sudo usermod -aG admins myuser
sudo usermod -a -G admins myuser
#    or
sudo gpasswd -a <username> <group>
#
# 5.6. Change users primary group
sudo usermod -g admins myuser
#
# 5.7. Remove user from a group
sudo gpasswd -d <username> <grouptoremove>
 

6. User manipulation

# 6.1. Change username
#   first change home directory
sudo usermod -d /home/jsmith jdoe -m
#   then change username
sudo usermod -l jsmith jdoe
#
# 6.2. Lock a user
sudo passwd -l <username>
# 6.3. unlock
sudo passwd -u <username>
#
# 6.4. Password expiration info
sudo chage -l <username>
# 6.5. Password requirements configuration
sudo apt install libpam-cracklib
sudo nano /etc/pam.d/common-password
 

7. sudo Group

# Members of sudo group can use sudo command
# 7.1. Configuration of sudo group members
sudo visudo
#__________________________________
%sudo	ALL=(ALL:ALL) ALL
#__________________________________
# sudo group members
#  can use sudo from any terminal
#  can use sudo to impersonate any user
#  can use sudo to impersonate any group
#  can use sudo for any command
#__________________________________
charlie  ubuntu-server=(dscully:admins) /usr/bin/apt
#__________________________________
#  user charlie, 
#   can only use sudo on ubuntu_server
#   can only impersonate dscully user
#   can only impersonate admins group
#   can only run /usr/bin/apt
#  For a user to sudo without passwd
#_____________________
ansible ALL=(ALL) NOPASSWD: ALL
#_____________________
#
# 7.2. List granted sudo privileges
sudo -l
 

Posted in Ubuntu

Keepalived On Ubuntu

Posted on 06/07/2020 - 30/03/2021 by exforge

KeepalivedOnUbuntu: keepalived Clustering on Ubuntu 20.04

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# 2 Ubuntu 20.04 Servers with IPs 192.168.0.161 and 192.168.0.162
# Floating IP Address: 192.168.0.160
# Floating IP address will point to first server; if first server goes off
#   for any reason, then floating IP address will point to second server. 
#   It will return to the first server again when it goes back online.
# This looks like clustering only at the network level.
# All the necessary services on the first server must be installed on second
#   server too. (Web server, DB server, mail server etc)
# The servers must be in the same network.
#
# Based on the book Mastering Ubuntu Server 2nd Ed. by Jay LaCroix
#   This book has introduced me to Ubuntu Server and I have to thank him for this
#   excellent book:
https://www.packtpub.com/networking-and-servers/mastering-ubuntu-server-second-edition
#
# You may establish a full cluster of LAMP stack by:
#   1. Installing Apache on both servers
#   2. Configuring the apache and sites equally on the both server
#   3. Installing MariaDB on both server
#   4. Establishing Master-Master Replication for Mariadb on 1st and 2nd servers
#
 

1. Install keepalived

# install on both the first and the second servers
sudo apt install keepalived
#  after installation, tries to start but cannot because there is no config
 

2. First Server Config

# Config file location is /etc/keepalived directory, initially empty
# Create primary server conf file
sudo nano /etc/keepalived/keepalived.conf
#  primary config file contents
#________________________________________
global_defs {
	notification_email {
	notify@x11.xyz
	}
	notification_email_from keepalived@x11.xyz
	smtp_server 192.168.0.150
	smtp_connect_timeout 30
	router_id mycompany_web_prod
}
vrrp_instance VI_1 {
	smtp_alert
	interface enp0s3
	virtual_router_id 51
	priority 100
	advert_int 5
	virtual_ipaddress {
	192.168.0.160
	}
}
#________________________________________
# explanations
#   global_defs
#       notification_email
#           email address to notify of cluster changes
#           replace notify@x11.xyz with your email
#	notification_email_from keepalived@x11.xyz
#           from address on the email, change as you wish
#       smtp_server
#           smtp server to send the mail through
#       smtp_connect_timeout
#           30 seconds would be enough
#       router_id
#           Any value to distinguish
#   vrrp_instance
#	interface xxxxx
#		network interface to run keepalived
#	virtual_router_id xx
#		keepalived clusterid (0-255)
#		must be same on all cluster members
#	priority xx
#		must be different on each cluster member
#		highest will be master member of the cluster
#	virtual_ip_address
#		floating ip address of the cluster
#
 

3. Second Server Config

# Almost the same as step 2. Just give a smaller number (say 90) for priority.
# If you want to add more servers, give them numbers less then 90
 

4. Start keepalived

# run on both servers
sudo systemctl start keepalived
#   You can check the status of you cluster
sudo systemctl status -l keepalived
 

Posted in Ubuntu

Apache Fine Tunes on Ubuntu

Posted on 13/06/2020 - 16/04/2021 by exforge

Apache Fine Tunes on Ubuntu 20.04

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# Based on Alexey Abel's ISP Mail Tutorial Caramel Edition
https://123qwe.com/
 

1. Special DNS Records:

# 1.1. Certificate Authorities
# 1.1.1. Letsencrypt may issue normal certificates 
@ CAA 0 issue "letsencrypt.org"
# 1.1.2. Wildcard domains are not allowed by anyone
@ CAA 0 issuewild ";"
# 1.1.3. Violations can be reported to postmaster@karasite.com
CAA 0 iodef "mailto:postmaster@x11.xyz"
#
# 1.2. SPF Policy for Email Server
# 1.2.1. All my emails come from the servers with MX record 
#   in my domain. Reject all others.
# Add as a txt record for x11.xyz (replace with your domain)
@ TXT "v=spf1 mx -all"
#
# 1.3. DMARC Policy for Email Server
# Dmarc Version 1, reject non complied mail, report to postmaster, strict dkim
#   and spf policy, filter 100% of the messages.
# Add as a txt record for _dmarc.x11.xyz (replace with your domain)
_dmarc "v=DMARC1; p=reject; rua=mailto:postmaster@x11.xyz; adkim=s; aspf=s; pct=100;"
 

2. Apache More Secure Configuration

https://ssl-config.mozilla.org/ 
#   is a good place to start
# 2.1. More Secure HTTPS redirect
#   Contents of the conf file
#      rewrite mod must be enabled
<VirtualHost *:80>
    ServerName www.x11.xyz
    # Force redirect to HTTPS
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
    ErrorLog ${APACHE_LOG_DIR}/www.x11.xyz-error.log
</VirtualHost>
#
# 2.2. More Secure HTTPS conf
#   Contents of the conf file
#       headers and ssl mods must be enabled
<VirtualHost *:443>
    ServerName my.server.com
    DocumentRoot /var/www/www.x11.xyz
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/www.x11.xyz/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/www.x11.xyz/privkey.pem
    # enable HTTP/2, if available
    Protocols h2 http/1.1
    # HSTS (mod_headers is required) (63072000 seconds = 2 years)
    Header always set Strict-Transport-Security "max-age=63072000"
    ErrorLog  ${APACHE_LOG_DIR}/my.server.com.port443-error.log
</VirtualHost>
#
# 2.3. More Secure TLS Configuration
#   A more secure Apache conf may be created to apply security
#     to all hosted sites
sudo nano /etc/apache2/conf-available/ssl-stricter-options.conf
#_________________________________________________________________
# Generated by: https://ssl-config.mozilla.org/
# modern configuration, tweak to your needs
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
SSLHonorCipherOrder     off
SSLSessionTickets       off
SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
#_________________________________________________________________
#
# Enable new conf
sudo a2enconf ssl-stricter-options
#
# 2.4. Enable all mentioned mods
sudo a2enmod rewrite ssl headers
 

3. Disabling All Apache Access Logs

#   You may consider respecting your user's privacy by not keeping any
#     access logs on Apache Servers. I'd like to follow Alexey Abel's
#     approach.
#   We start by  not configuring any access or custom logs on apache confs, 
#     but Apache itself catches all access logs at orher-vhosts-access-log.conf
#     So we have to disable it.
sudo a2disconf other-vhosts-access-log
 

4. Certbot Post Hooks

# For HTTPS we use letsencrypt's certificates, Certbot automates the renewal 
#   process of the certificates. It is a good thing, because letsencrypt 
#   certificates last only 2 months. 
# I use the certificates mostly for Apache, Postfix and Dovecot. When Certbot
#   renews a certificate, Apache, Postfix or Dovecot don't know about it. They 
#   might keep using the old certificates. Therefore it is a good idea to
#   restart these services when a certificate is renewed.
# Christoph Haas and Alexey Abel use 2 different methods for that purpose.
#   Both is documented here, choose whichever you want.
#
# 4.1. Christoph's Method:
# Add a line to certbot ini file
sudo nano /etc/letsencrypt/cli.ini
#___________________________________________________________________________
post-hook = systemctl restart postfix ; systemctl restart dovecot ; systemctl restart apache2
#___________________________________________________________________________
#
# 4.2. Alexey's Method:
# Certbot runs all scripts in the  /etc/letsencrypt/renewal-hooks/deploy
#   directory after a successfull renewal. We'll put there a scipt.
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reloadall.sh
#__________________________________________
#!/bin/bash
systemctl reload apache2
systemctl reload postfix
systemctl reload dovecot
#__________________________________________
# Make the script executable
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reloadall.sh
 

Posted in Ubuntu

DNS On Ubuntu

Posted on 29/05/2020 - 24/03/2021 by exforge

DNSOnUbuntu: Installation and Configuration of DNS Servers on Ubuntu 20.04

Copyright (C) Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# Based on the book Mastering Ubuntu Server 2nd Ed. by Jay LaCroix
#   This book hes introduced me to Ubuntu Server and I have to thank him for this
#   excellent book. 
https://www.packtpub.com/networking-and-servers/mastering-ubuntu-server-second-edition
#
# There will be 2 DNS Servers, 1 Master and 1 Slave. 
# For every domain on the internet, 2 DNS Servers are needed. So if you want to
#    run your own DNS Server, you need 2 servers.
# If you need DNS Servers for your internal network, it might be a good idea to
#    install a spare one.
# I, myself prefer using www.cloudfare.com for DNS services of my domain. But you may
#   prefer running on your servers.
#
# My Specs: (Change the values to your ones)
#  Domain Name:   		x11.xyz	    	(Change it to your domain name)
#  Master DNS Server:		ns1.x11.xyz		192.168.0.161
#  Slave DNS Server:		ns2.x11.xyz		192.168.0.162
#  Some sample servers to add as DNS records:
#  filesrv.x11.xyz:		192.168.0.171
#  mail.x11.xyz:		192.168.0.172
#  mailsrv as a canonical name for mail
#  mail as a mail server for the domain
#  Google DNS servers 8.8.8.8 8.8.4.4 are used as forwarder DNSs
 

1. Master DNS Server

# 1.1. Install bind9 (DNS Server)
sudo apt install bind9
#
# 1.2. Edit main config file
sudo nano /etc/bind/named.conf.options
# Change as below
#_________________________________________________________
options {
	directory "/var/cache/bind";
		allow-transfer { localhost; 192.168.0.162; };
		forwarders {
		8.8.8.8;
		8.8.4.4;
	};
	dnssec-validation auto;
	auth-nxdomain no;
	listen-on-v6 { any; };
};
#___________________________
#
# 1.3. Add a local zone (that is our zone x11.xyz)
sudo nano /etc/bind/named.conf.local
#_________________________________
zone "x11.xyz" IN {
   type master;
   file "/etc/bind/net.x11.xyz";
};
#_________________________________
# 
# 1.4. Fill the local zone file we just defined
sudo nano /etc/bind/net.x11.xyz
#__________________________________________
$TTL 1D
@ IN SOA x11.xyz hostmaster.x11.xyz (
2021032401 ; serial
8H ; refresh
4H ; retry
4W ; expire
1D ) ; minimum
IN A 192.168.0.1
;
@               IN NS     ns1
@               IN MX 10  mail.x11.xyz
ns1             IN A      192.168.0.161
ns2             IN A      192.168.0.162
filesrv         IN A      192.168.0.171
mail            IN A      192.168.0.172
mailsrv         IN CNAME  mail.xyz.x11
#__________________________________________
 

2. Slave DNS Server

# 2.1. Install bind9 (DNS Server)
sudo apt install bind9
#
# 2.2. Edit main config file
sudo nano /etc/bind/named.conf.options
#________________________________
options {
	directory "/var/cache/bind";
		forwarders {
		8.8.8.8;
		8.8.4.4;
	};
	dnssec-validation auto;
	auth-nxdomain no;
	listen-on-v6 { any; };
};
#__________________________________
#
# 2.3. Add the local zone (this zone will be replicated from the master DNS)
sudo nano /etc/bind/named.conf.local
#____________________________
zone "x11.xyz" IN {
	type slave;
	masters { 192.168.0.161; };
	file "/var/lib/bind/net.x11.xyz";
};
#____________________________
# This time local zone file is placed at /var/lib
#   and it will be populated automatically
#
# 2.4. Restart DNS on both master and slave
sudo systemctl restart bind9
 

3. Your DNS Servers must be running now

# !!! Remember !!!
# 3.1. When you change the zone file on your master server, remember to increase
#    the number given before serial. 
# 3.2. You can change DNS server settings of your computers (including DNS Servers) to the
#    new DNS servers.
 

Posted in Ubuntu

PHPMyAdmin On Ubuntu

Posted on 27/05/2020 - 01/04/2021 by exforge

PHPMyAdmin on Ubuntu 20.04 for MariaDB

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# PHPMyAdmin is a powerful web based management tool for Mysql and Mariadb.
# You have to install it on the server which has the DB installed.
# It is installed as an Apache conf and after installing, you can reach
#   PHPMyAdmin with /phpmyadmin directory through all the apache sites on the 
#   server
# On my config I wanted to bind PHPMyAdmin to only one site config on the server 
#   side and restrict it with only 1 client IP (might be more) to reach.
#
# My Hostname: test.x11.xyz
# Lamp is already installed (See LampOnUbuntu Tutorial)
# My Client IP address: 195.174.209.24
 

1. Revert mariadb root to unix_socket

# On LAMP installation, we changed the MariaDB root user plugin to mysql
#   native.
# Unfortunately PHPMyAdmin gives an error on installation with this setting.
#   So we'll revert it back to unix socket momentarily
mariadb -u root -p
#   On MariaDB shell:
UPDATE mysql.user SET plugin = 'unix_socket' WHERE USER='root';
FLUSH PRIVILEGES;
exit;
 

2. Install and configure phpmyadmin

sudo apt install phpmyadmin 
# First Screen: Make sure apache2 is selected, there must be an * on its box
#    (press space to select, tab to go OK, press enter) 
# Second screen: Select Yes and give a good password for phpmyadmin user
 

3. Revert mariadb root to native again

#  We will restore Maria root user plugin to mysql native. As it was before.
sudo mariadb
#   On MariaDB shell:
UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE USER='root';
FLUSH PRIVILEGES;
exit;
 

4. Disable phpmyadmin apache mod.

# We'll configure it later in a more secure way
sudo a2disconf phpmyadmin
sudo systemctl reload apache2
 

5. Create test.x11.xyz.conf

# 5.1. Make a home for the new site
sudo mkdir /var/www/test.x11.xyz
#
# 5.2. Edit phpmyadmin to allow only our IPs
#   Make a backup first (always)
sudo cp /etc/phpmyadmin/apache.conf /etc/phpmyadmin/apache.conf.original
sudo nano /etc/phpmyadmin/apache.conf
# Just after the line
#   <Directory /usr/share/phpmyadmin>
# Add your IP as the following format, remember indenting
#    You can add more than 1 IP if required
    Require ip 195.174.209.24
# The beginning of the file will be like below:
#______________________________________________________________
Alias /phpmyadmin /usr/share/phpmyadmin
<Directory /usr/share/phpmyadmin>
    Require ip 195.174.209.24
    Options SymLinksIfOwnerMatch
    DirectoryIndex index.php
    # limit libapache2-mod-php to files and directories necessary by pma
    <IfModule mod_php7.c>
        php_admin_value upload_tmp_dir /var/lib/phpmyadmin/tmp
        php_admin_value open_basedir /usr/share/phpmyadmin/:/etc/phpmyadmin/:/v>
    </IfModule>
</Directory>
#______________________________________________________________
#
# 5.2. Create a site config to reach PHPMyAdmin
# If you want to use your own conf file, just add the line starting with
#  Include
#      as in the following conf
sudo nano /etc/apache2/sites-available/test.x11.xyz.conf
#_________________________________________________________________________
<VirtualHost *:80>
    Include /etc/apache2/conf-available/phpmyadmin.conf
    ServerAdmin webmaster@x11.xyz	
    ServerName test.x11.xyz
    DocumentRoot /var/www/test
    ErrorLog ${APACHE_LOG_DIR}/test-error.log
    CustomLog ${APACHE_LOG_DIR}/test-access.log combined
</VirtualHost>
#_________________________________________________________________________
#
# 5.3. Enable new site
sudo a2ensite test.x11.xyz.conf
#
# 5.4. Reload Apache
sudo systemctl reload apache2
#
# 5.5. Enable https for new site
# Refer to CertbotOnUbuntu tutorial
#
# 5.6. To use PHPMyAdmin on SSL site, you need to add a line to the SSL config
#   file created by Certbot, which must be test.karasite.com-le-ssl.conf
sudo nano /etc/apache2/sites-available/test.karasite.com-le-ssl.conf
#   After line <VirtualHost *:443> add the following line: (remember indenting)
	Include /etc/apache2/conf-available/phpmyadmin.conf
 

6. PHPMyAdmin is ready

https://test.x11.xyz/phpmyadmin
# You can use username: phpmyadmin and the password you gave at 2.
# For a full administrative operation on the database you can use root user
#   of maridab or create a new user for web administration.
 

7. Troubleshooting

# Taken from: DevAnswers:
https://devanswers.co/problem-php-7-2-phpmyadmin-warning-in-librariessql-count/
# 7.1. Problem with phpMyAdmin and PHP 7.2: 
#   “Warning in ./libraries/sql.lib.php#613 count(): 
#        Parameter must be an array or an object that implements Countable”
#   This bug is an incompatability issue between PHP7.2 and PHPMyAdmin version in Ubuntu 18.04
#   The easiest solution is to correct the line on sql.lib.php ourselves.
# 7.1.1. Backup sql.lib.php
sudo cp /usr/share/phpmyadmin/libraries/sql.lib.php /usr/share/phpmyadmin/libraries/sql.lib.php.original
# 7.1.2. Modify sql.lib.php
sudo nano /usr/share/phpmyadmin/libraries/sql.lib.php
#   Find: (count($analyzed_sql_results['select_expr'] == 1)
#   Change to:  ((count($analyzed_sql_results['select_expr']) == 1)
#
# 7.2. Problem with phpMyAdmin and PHP 7.2: (at import and export tabs)
#   "Warning in ./libraries/plugin_interface.lib.php#551
#        count(): Parameter must be an array or an object that implements Countable
#   Again, this bug is an incompatability issue between PHP7.2 and PHPMyAdmin version in Ubuntu 18.04
#   The easiest solution is to correct the line on plugin_interface.lib.php ourselves.
# 7.2.1. Backup plugin_interface.lib.php
sudo cp /usr/share/phpmyadmin/libraries/plugin_interface.lib.php /usr/share/phpmyadmin/libraries/plugin_interface.lib.php.original
# 7.2.2. Modify plugin_interface.lib.php
sudo nano /usr/share/phpmyadmin/libraries/plugin_interface.lib.php
#   Find: if (! is_null($options) && count($options) > 0) {
#   If you cannot find it Find: if ($options != null && count($options) > 0) {
#   Change to: if (! is_null($options) && count((array)$options) > 0) {
 

Posted in Ubuntu

WordPress on Ubuntu

Posted on 24/05/2020 - 29/03/2021 by exforge

WordPressOnUbuntu: Install WordPress On Ubuntu 20.04

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# Based on Brian Boucheron's Tutorial on
https://www.digitalocean.com/community/tutorials/how-to-install-wordpress-with-lamp-on-ubuntu-18-04
# My Server Name: www.x11.xyz
# WordPress Mariadb Database Name: wordpress
# WordPress Mariadb Database User: wordpressuser
# Password of wordpressuser: pAsswOrd1234
# WordPress website location: /var/www/wordpress
 

0. Install LAMP Stack (See LampOnUbuntu)

#   Refer to LampOnUbuntu Tutorial
 

1. Configure a Website (If you haven’t done already)

# 1.1. Make room for our website
sudo mkdir /var/www/wordpress
# 1.1. Create a configuration file for our web server
sudo nano /etc/apache2/sites-available/x11.xyz.conf
#____________________________________________________________________
<VirtualHost *:80>
        ServerAdmin postmaster@x11.xyz
        ServerName www.x11.xyz
        ServerAlias x11.xyz
        DocumentRoot /var/www/wordpress
        ErrorLog ${APACHE_LOG_DIR}/x11.xyz-error.log
        CustomLog ${APACHE_LOG_DIR}/x11.xyz-access.log combined
</VirtualHost>
#____________________________________________________________________
# 1.2. Enable website
sudo a2ensite x11.xyz.conf
# 1.3. Reload apache
sudo systemctl reload apache2
 

2. Create a MariaDB DB User for WordPress

# 2.1. Login to MariaDB
mariadb -u root -p
# 2.2. Create a database named as wordpress, create a user named as wordpressuser
#      and give the user necessarry permissions for the database.
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
CREATE USER 'wordpressuser'@'localhost' IDENTIFIED BY 'pAsswOrd1234';
GRANT ALL ON wordpress.* TO 'wordpressuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
 

3. Install Additional PHP Extensions

sudo apt update
sudo apt install php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip
#   Restart apache
sudo systemctl restart apache2
 

4. Adjust Apache for WordPress

# 4.1. Enable wordpress' .htaccess files
#      by editing our site config, adding folders to override
sudo nano /etc/apache2/sites-available/x11.xyz.conf
#   Add following lines to just after DocumentRoot line
#____________________________________
        <Directory /var/www/wordpress/>
            AllowOverride All
        </Directory>
#____________________________________
# 4.2. Enable Apache Rewrite module 
sudo a2enmod rewrite
# 4.3. (Optional) Check Status of Apache Configs
sudo apache2ctl configtest
# 4.4. Restart Apache
sudo systemctl restart apache2
 

5. Download WordPress

#    WordPress can be installed by apt install, but 
#      we prefer to download the latest version from the original source
# 5.1. Go to temp directory, download wordpress, extract it
cd /tmp
curl -O https://wordpress.org/latest.tar.gz
tar xzvf latest.tar.gz
# 5.2. Before copying to its home we need to make some additions
#   Create a dummy .htaccess file
touch /tmp/wordpress/.htaccess
#   Copy sample config file to real config file
cp /tmp/wordpress/wp-config-sample.php /tmp/wordpress/wp-config.php
# 5.3. Copy wordpress files to their location
sudo cp -a /tmp/wordpress/. /var/www/wordpress
 

6. Configure WordPress Directory

# 6.1. Set ownerships and permissions
#     Owned by www-data:www-data
sudo chown -R www-data:www-data /var/www/wordpress
#     All files 640, all dirs 750
sudo find /var/www/wordpress/ -type d -exec chmod 750 {} \;
sudo find /var/www/wordpress/ -type f -exec chmod 640 {} \;
# 6.2. Setup wordpress config file
# 6.2.1. Generate secure phrases
#    WP requires some secure phrases for security, we will create them now
curl -s https://api.wordpress.org/secret-key/1.1/salt/
#    The output of this program will be something like following:
#    Copy them into a text file
#______________________________________________________________________________________________
define('AUTH_KEY',         'O`tLoX^0[pT24ty<YOByEP#}wBtd|7M!9^-az.W_v{`;+!*PX_9/A#^#}SL@I_wD');
define('SECURE_AUTH_KEY',  '-U]7Eu_Bbh!tA/5lk3.eDRzGrY<%i,:cn*yBOiE^*zZHK&RTbHmv]^+[[1v49=bq');
define('LOGGED_IN_KEY',    'VHYd-]>SDIsT_^-;>_0DBV:2}>u^wI;]T>IqXr}++h1sRjQM%U^I0ijVwAi? (yB');
define('NONCE_KEY',        'lui4^EuI3U-m8m!IUI%>;+)r[dJW`w2pl@g4JU==(,ipCi|EC)+vo,&2rAR Dm+-');
define('AUTH_SALT',        'W,>S!kG,KCPZ/`Y7;(hpL,1-M2lanZz(3)kdds-{;t9D(X&Qy:+0^H&3jE%WS:L4');
define('SECURE_AUTH_SALT', 'f*q%x{M6#GQ|L{U|!UoI~`8(71};e}Xm;4#e^J/b&DC<DO=Xv6$caAC<2q4gs}^0');
define('LOGGED_IN_SALT',   '=HN;=E:zl1-X:5w:MTw3LHV^?VP})Z}&T*P!zvAG|R=S>6;~Xz|rh@S#MrSH2FA)');
define('NONCE_SALT',       '`d)>*Ae)9g<Aaa1eQ*9HlqY-|__kE5,Nte2UAMJO3ro=9T#y=,|-/^D(&+XQ:,la');
#______________________________________________________________________________________________
# 6.2.2. Add phrases to WP config file
sudo nano /var/www/wordpress/wp-config.php
#    Browse to the section with the following lines, replace them with the
#    text you copied. (Around line 49)
#___________________________________________________________     
define( 'AUTH_KEY',         'put your unique phrase here' );
define( 'SECURE_AUTH_KEY',  'put your unique phrase here' );
define( 'LOGGED_IN_KEY',    'put your unique phrase here' );
define( 'NONCE_KEY',        'put your unique phrase here' );
define( 'AUTH_SALT',        'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT',   'put your unique phrase here' );
define( 'NONCE_SALT',       'put your unique phrase here' );
#___________________________________________________________
#
#   While in the same file, browse up to the section with the following lines:
#______________________________________________________________
/** The name of the database for WordPress */
define( 'DB_NAME', 'database_name_here' );
/** MySQL database username */
define( 'DB_USER', 'username_here' );
/** MySQL database password */
define( 'DB_PASSWORD', 'password_here' );
/** MySQL hostname */
define( 'DB_HOST', 'localhost' );
/** Database Charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );
/** The Database Collate type. Don't change this if in doubt. */
define( 'DB_COLLATE', '' );
#______________________________________________________________
#
#   Change as specified:
# database_name_here : wordpress
# username_here : wordpressuser
# password : pAsswOrd1234
#
#  Add following line:
#_____________________________
define('FS_METHOD', 'direct');
#_____________________________
 

7. WordPress is ready, open in your browser

http://www.x11.xyz
# Of course it would be a good idea to add SSL to your site, refer to
#   CertbotOnUbuntu tutorial.
 

Posted in Ubuntu

Simple Mail Server on Ubuntu

Posted on 24/05/2020 - 29/03/2021 by exforge

SimpleMailServerOnUbuntu: Mail Server with SMTP and IMAP on Ubuntu 20.04

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# Based on the valuable documents at:
https://www.server-world.info/en/note?os=Ubuntu_18.04&p=mail&f=1
# SMTP: Postfix
# IMAP: Dovecot
# No virtual domains, only Linux users will have email accounts
# Tried to be as simple as possible
#
# Hostname: mail.x11.xyz
# Mail Domain: x11.xyz
#
# An MX record must be created at the DNS Server with the value of mail.x11.xyz
 

1. Install and Configure Postfix

# 1.1. Install Postfix and SASL (Simple Auth. & Security Layer)
sudo apt -y install postfix sasl2-bin 
#   Select No configuration at the install question, we will configure manually
#
# 1.2. Create a standart config file for postfix
sudo cp /usr/share/postfix/main.cf.dist /etc/postfix/main.cf
#
# 1.3. Edit postfix config file as described
sudo nano /etc/postfix/main.cf 
#
# around line 78: uncomment
mail_owner = postfix
#
# around line 94: uncomment and specify hostname
myhostname = mail.x11.xyz
#
# around line 102: uncomment and specify domainname
mydomain = x11.xyz
#
# around line 123: uncomment
myorigin = $mydomain
#
# around line 137: uncomment
inet_interfaces = all
#
# around line 185: uncomment
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
#
# around line 228: uncomment
local_recipient_maps = unix:passwd.byname $alias_maps
#
# around line 270: uncomment
mynetworks_style = subnet
#
# around line 287: add your local network
mynetworks = 127.0.0.0/8, 10.0.0.0/24
#
# around line 407: uncomment
alias_maps = hash:/etc/aliases
#
# around line 418: uncomment
alias_database = hash:/etc/aliases
#
# around line 440: uncomment
home_mailbox = Maildir/
#
# around line 576: comment out and add
#smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
smtpd_banner = $myhostname ESMTP
#
# around line 650: add
sendmail_path = /usr/sbin/postfix
#
# around line 655: add
newaliases_path = /usr/bin/newaliases
#
# around line 660: add
mailq_path = /usr/bin/mailq
#
# around line 666: add
setgid_group = postdrop
#
# around line 670: comment out
#html_directory =
#
# around line 674: comment out
#manpage_directory =
#
# around line 679: comment out
#sample_directory =
#
# around line 683: comment out
#readme_directory =
#
# add to the end: 
# limit an email size 10M
message_size_limit = 10485760
# limit mailbox 1G
mailbox_size_limit = 1073741824
# SMTP-Auth setting
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
smtpd_recipient_restrictions = permit_mynetworks, permit_auth_destination, permit_sasl_authenticated, reject
#
# 1.4. Activate Aliases (Will be explained at 5.2.) 
sudo newaliases
# 1.5. Restart Postfix
sudo systemctl restart postfix 
 

2. Install and Configure Dovecot

# 2.1. Install Dovecot core, pop3 and imap deamons
sudo apt -y install dovecot-core dovecot-pop3d dovecot-imapd 
#
# 2.2. Dovecot main config
sudo nano /etc/dovecot/dovecot.conf
# around line 30: uncomment
listen = *, ::
#
# 2.3. Dovecot auth config
sudo nano /etc/dovecot/conf.d/10-auth.conf
# around line 10: uncomment and change ( allow plain text auth )
disable_plaintext_auth = no
#
# around line 100: add
auth_mechanisms = plain login
#
# 2.4. Dovecot mail config
sudo nano /etc/dovecot/conf.d/10-mail.conf
# around line 30: change to Maildir
mail_location = maildir:~/Maildir
#
# 2.5. Dovecot master config
sudo nano /etc/dovecot/conf.d/10-master.conf
# around line 107-109: uncomment and add
# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
    mode = 0666
    user = postfix
    group = postfix
}
#
# 2.6. Restart Dovecot
sudo systemctl restart dovecot 
 

Break Time

# At this point we have a very basic mail config, all our linux users at 
#   mail.x11.xyz have mail addresses. They can access smtp at port 25 and imap at
#   port 143. But unfortunately there is no encryption. 
# At the next step we will add SSL encrytpion and that will change our smtp port to 587.
#
# The easiest way to get SSL certificate for our mail server is:
#   Install apache2
#   Create a site with servername mail.x11.xyz
#   Get ssl certificate with Certbot
#   (See tutorial CertbotOnUbuntu)
# At the next section I assume you have certificates  as
#    /etc/letsencrypt/live/mail.x11.xyz/fullchain.pem
#    /etc/letsencrypt/live/mail.x11.xyz/privkey.pem
 

3. Add SSL/TLS to Postfix and Dovecot

# 3.1. Postfix main config
sudo nano /etc/postfix/main.cf 
#   Add to the end
smtpd_use_tls = yes
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.x11.xyz/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.x11.xyz/privkey.pem
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
#
# 3.2. Postfix master config
sudo nano /etc/postfix/master.cf
#  Around line 17-21: uncomment like follows
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
#  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
#  Around line 29-31: uncomment like follows
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
#
# 3.3. Dovecot config
sudo nano /etc/dovecot/conf.d/10-ssl.conf
# Around line 12,13: Specify certificates (change as below)
#   Remember to change domain names:  
ssl_cert = </etc/letsencrypt/live/mail.x11.xyz/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.x11.xyz/privkey.pem
#
# 3.4. Restart Postfix and Dovecot
sudo systemctl restart postfix dovecot 
 

4. Client Mail Settings

# For Linux User exforge at mail.x11.xyz
# Replace all occurences of:
#  exforge --> your user name
#  mail.x11.xyz --> your server name
#  x11.xyz --> your domain
#
# Thunderbird Config:
#  Your name: Exforge
#  Email address: exforge@x11.xyz
#  Password: (Your Linux Password)
#  Incoming: IMAP  mail.x11.xyz  	143  STARTTLS  Normal password
#  Outgoing: SMTP  mail.x11.xyz  	465  SSL/TLS   Normal password
#  Username: Incoming: exforge    	Outgoing: exforge
#     
#  See my config screenshot at:
https://imgur.com/a/RVIAy3o
 

5. Account Management

# 5.1. All Linux users already have mail accounts with their login name and passwords.
#   To add a new mail user, you need to add a user to your server
sudo useradd -d /home/exforge -m exforge
#   And you need to give her a password too
sudo passwd exforge
#
# 5.2. If you want to use aliases, say postmaster and abuse for user exforge.
#   First create Linux users
sudo useradd -d /home/postmaster -m postmaster
sudo useradd -d /home/abuse -m abuse
sudo passwd postmaster
sudo passwd abuse
#   Add them to /etc/aliases file
sudo nano /etc/aliases
#_______________________________________
# See man 5 aliases for format
postmaster:    exforge
abuse:     exforge
#_______________________________________
#
#   Activate aliases
sudo newaliases
# 
# 5.3. Restart Postfix and Dovecot
sudo systemctl restart postfix dovecot
 

Posted in Ubuntu

MariaDB On Ubuntu

Posted on 24/05/2020 - 29/03/2021 by exforge

MariadbOnUbuntu: MariaDB Tutorial on Ubuntu 20.04

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

Specs

# Mariadb 2 Installation, configuration, simple user and DB management
#   on Ubuntu 20.04 Server
# Based on the book Mastering Ubuntu Server 2nd Ed. by Jay LaCroix
#   This book hes introduced me to Ubuntu Server and I have to thank him for this
#   excellent book:
https://www.packtpub.com/networking-and-servers/mastering-ubuntu-server-second-edition
# Almost (if not all) everything on this tutorial can be applied to Mysql.
# Mariadb is a fork or Mysql, and I prefer using it, besides a lot of other reasons, 
#   I just don't like Oracle
# !!! Do not ever install Mariadb and Mysql on the same server !!! 
 

1. Installation and Securing

# 1.1. Install MariaDB
sudo apt install mariadb-server
# 1.2. Check if installation is OK
systemctl status mariadb
#
# 1.3. Secure MariaDB, 
#   select Default for all questions, give a good password
sudo mysql_secure_installation
#
# 1.4. Enter Mariadb shell
# EXIT; to exit
sudo mariadb
 

2. Configuration Files

# 2.1. Config files are in /etc/mysql
#_______________________________
debian.cnf
debian-start
mariadb.cnf
my.cnf
my.cnf.fallback
#  and these directories
conf.d
mariadb.conf.d
#____________________________
#
# 2.2. Sample /etc/mysql/debian.cnf file
#____________________________
[client]
host = localhost
user= root
password =
socket= /var/run/mysqld/mysqld.sock
[mysql_upgrade]
host = localhost
user = root
password =
socket = /var/run/mysqld/mysqld.sock
basedir = /usr
#____________________________
 

3. MariaDB Shell

# 3.1. Mariadb shell can be run in two ways:
sudo mariadb
#  or
mariadb -u root -p
# 3.2. To use the latter, we first enter with sudo mariadb and enter following 
#   commands on Mariadb shell
UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE USER='root';
FLUSH PRIVILEGES;
EXIT;
# Now we can use it
mariadb -u root -p
 

4. Basic User Management

# !!! All commands must be run on Mariadb shell !!!
# 4.1. For administrating the db, it is best to create an admin user
#     on mariadb shell. admin can only login from localhost
CREATE USER 'admin'@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
# 4.2. To let admin login from anywhere use:
CREATE USER 'admin'@'%' IDENTIFIED BY 'password';
FLUSH PRIVILEGES;
#
# 4.3. Give admin full access DB server. Can do anything but grant
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost';
#
# 4.4. Following command makes a full admin, with grant permissions
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
#
# 4.5. Create a readonly user for DB
GRANT SELECT ON *.* TO 'readonlyuser'@'localhost' IDENTIFIED BY 'password';
#
# 4.6. List database users
SELECT HOST, USER, PASSWORD FROM mysql.user;
#
# 4.7. Grant a user readonly access for one database
GRANT SELECT ON mysampledb.* TO 'appuser'@'localhost' IDENTIFIED BY 'password';
#
# 4.8. Grant a user full access for a database
GRANT ALL ON mysampledb.* TO 'appuser'@'localhost' IDENTIFIED BY 'password';
#
# 4.9. Show the grants for a particular user:
SHOW GRANTS FOR 'appuser'@'localhost';
#
# 4.10. Remove a user
DELETE FROM mysql.user WHERE user='myuser' AND host='localhost';
 

5. Database Manipulation

# !!! All commands must be run on Mariadb shell !!!
# 5.1. Create a database
CREATE DATABASE mysampledb;
#
# 5.2. List databases
SHOW DATABASES;
#
# 5.3. Enter the workspace of  a database
USE mysampledb;
#
# 5.4. Create a table
CREATE TABLE Employees (Name char(15), Age int(3), Occupation char(15));
#
# 5.5. List columns of a table
SHOW COLUMNS IN Employees;
#
# 5.6. Insert a row into a table
INSERT INTO Employees VALUES ('Joe Smith', '26', 'Ninja');
#
# 5.7. List contents of a table
SELECT * FROM Employees;
#
# 5.8. Remove an entry from a database
DELETE FROM Employees WHERE Name = 'Joe Smith';
#
# 5.9. Drop a table
DROP TABLE Employees;
#
# 5.10. Drop an entire database:
DROP DATABASE mysampledb;
 

6. Backup and Restore

# 6.1. Backup a database
mysqldump -u root -p --databases mysampledb > mysampledb.sql
#
# 6.2. Restore it
mariadb -u root -p < mysampledb.sql
 

7. Master-Slave Replication Configuration

# 7.1. Specs and Preliminary Tasks
#   Master Server: 192.168.0.161
#   Slave Server:  192.168.0.162
#   Replication User: 'replicate'@'192.168.0.162'
#   Rep. User Password: Pass1234 
#   Database instance to replicate: mysampledb
#
#   Install mariadb on both servers, 
#   Apply steps 1 and 3 on both servers
#   Apply step 5.1 to 5.7 on master server
#
#   !!! Please Remember: !!! 
#      Replication doesn't mean that you don't have to backup.
#      If you delete something accidentally, it is automatically deleted at slave too
#      So if you are running a production server, backup (at least) daily and weekly
#
# 7.2. Master Server Configuration
# 7.2.1. Configure master for bin log
sudo nano /etc/mysql/conf.d/mysql.cnf
# Change as below:
#__________________
[mysql]
[mysqld]
log-bin
binlog-do-db=mysampledb
server-id=1
#__________________
# 7.2.2. Change bind address to outside
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
# Change following line (Around line 28)
#_______________________
bind-address = 127.0.0.1
#_______________________
#  to
#_______________________
bind-address = 0.0.0.0
#_______________________
# 7.2.3. Create replication user
# Run following command on master mariadb shell
GRANT REPLICATION SLAVE ON *.* to 'replicate'@'192.168.0.162' identified by 'Pass1234';
#
# 7.2.4. Restart master mariadb server
sudo systemctl restart mariadb
#
# 7.2.5. Lock Master server for initial full replication
#   Run on Master server Mariadb shell
FLUSH TABLES WITH READ LOCK;
#
# 7.2.6. Backup the database at master server
mysqldump -u root -p --databases mysampledb > mysampledb.sql
# Move backup file to the slave server for restoring later
#
# 7.3. Slave Server Config
# 7.3.1. Restore database backed up at master
mariadb -u root -p < mysampledb.sql
#
# 7.3.2. Update slave server conf file
sudo nano /etc/mysql/conf.d/mysql.cnf
#__________________
[mysql]
[mysqld]
server-id=2
#__________________
#  For more than 1 slaves, give different server-id numbers
#
# 7.3.3. Restart slave mariadb
sudo systemctl restart mariadb
#
# 7.3.4. Run the commands on slave mariadb shell
CHANGE MASTER TO MASTER_HOST="192.168.0.161", MASTER_USER='replicate', MASTER_PASSWORD='Pass1234';
# Check to see if slave running (mariadb shell)
SHOW SLAVE STATUS;
# If Slave_IO_State is empty run (mariadb shell)
START SLAVE;
#
# 7.4. Unlock Master Mariadb
#   Run on master mariadb shell
UNLOCK TABLES;
# 7.5. All set. You can try manipulating the DB on master, changes will be applied on slave
#   in a few seconds.
 

Posted in Ubuntu

Posts navigation

Older posts
Proudly powered by WordPress | Theme: micro, developed by DevriX.