Systemd On Debian/Ubuntu

SystemdOnDebianUbuntu systemd Tutorial On Debian and Ubuntu

Copyright (C) 2023 Exforge exforge@x386.org

# - 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. Intro 
# - systemd is a suite of basic building blocks for a Linux system. It 
# provides a system and service manager that runs as PID 1 and starts the 
# rest of the system (from systemd.io site).
# - systemd is a software suite that provides an array of system components 
# for Linux operating systems (from wikipedia).
# - systemd is an init system for Linux. It replaces SysV init. There are a 
# lot of distros that use it as an init system, like Debian, Ubuntu, RHEL.
# Also there are a lot of distros that don't use systemd, like Slackware,
# Devuan, Alpine, Gentoo.
# - A strong alternative of systemd is OpenRC (you may expect a tutorial for 
# OpenRC).
# 
#-- 0.1. Definitions
# - D-Bus (Desktop Bus) is a message-oriented middleware mechanism that 
# allows communication between multiple processes running concurrently on 
# the same machine (from wikipedia).
# - cgroups: A part built into kernel, that allows setting resource 
# utilization limits for processes. Like; cpu shares, memory usage, block 
# I/O per process. Developed by Google.
#
#-- 0.2. Sources
https://wiki.debian.org/systemd/documentation
https://www.digitalocean.com/community/tutorials/systemd-essentials-working-with-services-units-and-the-journal
https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units
https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs
https://www.redhat.com/sysadmin/cgroups-part-one
https://en.wikipedia.org/wiki/Systemd
https://en.wikipedia.org/wiki/D-Bus
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/chap-managing_services_with_systemd
https://www.howtogeek.com/675569/why-linuxs-systemd-is-still-divisive-after-all-these-years/
https://systemd.io/
Book:ISBN: 979-10-91414-20-3 The Debian Administrator’s Handbook by Raphaël Hertzog and Roland Mas
Book:ISBN 978-1-80181-164-4 Linux Service Management Made Easy with systemd by Donald A. Tevault

1. systemd Units

# Units are the resources that systemd knows how to manage and to operate.
#-- 1.1. Unit Locations
# Locations of unit files (in increasing precedence):
# - /lib/systemd/system
# - /run/systemd/system
# - /etc/systemd/system
#
#-- 1.2. Unit Types:
# - .service: Contains information on managing a service or application. 
# Managing includes starting, stopping, automatic starting, dependencies 
# etc.
# - .socket: Describes a socket for systemd's socket based activation. It 
# must have an associated .service file for a service.
# - .device: Describes a device that needs systemd management. Not all 
# devices has a .device file.
# - .mount: Mountpoints needed to be managed by systemd.
# - .automount: Configures a mountpoint to be automatically mounted. Must 
# have a .mount unit.
# - .swap: Describes swap space on the system.
# - .target: Used to provide syncronization with other units. 
# - .path: Defines a path for path based activation. A matching unit is 
# started depending on the path existence or inexistence.
# - .timer: Defines a timer to be managed by systemd. A matching unit is 
# started when the timer is reached.
# - .snapshot: Created with systemctl snapshot command. Saves a state of the 
# system. Does not survive among sessions.
# - .slice: Associated with cgroups (Linux Control Group nodes). Allows 
# resources to be restricted.
# - .scope: Created automatically by systemd from information received from 
# its bus interfaces. Used to manage sets of system processes that are 
# created externally.
#
#-- 1.3. Sample Unit Files
# Contents of /lib/systemd/system/apache2.service
[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=https://httpd.apache.org/docs/2.4/
[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/sbin/apachectl start
ExecStop=/usr/sbin/apachectl graceful-stop
ExecReload=/usr/sbin/apachectl graceful
KillMode=mixed
PrivateTmp=true
Restart=on-abort
[Install]
WantedBy=multi-user.target
#
# Contents of /lib/systemd/system/ssh.service
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
Alias=sshd.service
#
#
# Contents of /lib/systemd/system/ssh.socket
[Unit]
Description=OpenBSD Secure Shell server socket
Before=ssh.service
Conflicts=ssh.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Socket]
ListenStream=22
Accept=yes
[Install]
WantedBy=sockets.target
#
# 
# Contents of /etc/systemd/system/snap-firefox-2356.mount
[Unit]
Description=Mount unit for firefox, revision 2356
After=snapd.mounts-pre.target
Before=snapd.mounts.target
Before=local-fs.target
[Mount]
What=/var/lib/snapd/snaps/firefox_2356.snap
Where=/snap/firefox/2356
Type=squashfs
Options=nodev,ro,x-gdu.hide,x-gvfs-hide
LazyUnmount=yes
[Install]
WantedBy=snapd.mounts.target
WantedBy=multi-user.target
#
#-- 1.4. Unit File Structure
# - Unit files are made of sections. Unit and Install sections can exist in 
# all types of units. Also there are some other sections which can exist in 
# certain unit types. They are Socket, Mount, Automount, Swap, Path, Timer, 
# and Slice.
# - Sections contain directives. Some directives are unit type specific and 
# some are general.
# For a full list of directives see:
https://www.freedesktop.org/software/systemd/man/systemd.directives.html
#
# 1.4.1. Unit Section Directives (Applies to all unit types)
# - Description: A short description of the unit.
# - Documentation: Location of documentation.
# - Requires: The units that this unit depends. All of them must be 
# activated for this unit to be activated.
# - Wants: Similar to Requires, but not strict. Systemd tries to activate 
# the list of units before activating this unit.
# - BindsTo: Similar to Requires, also stops this unit when the listed unit 
# stops.
# - Before: This unit must be started before the listed units. Does not 
# imply dependency.
# - After: The list of units must be started before this unit. Does not 
# imply dependency.
# - Conflicts: This unit can not be run at the same time with the listed 
# unit. If this unit starts, other units stop.
# - Condition: This unit starts if the conditions are met. If conditions are 
# not met, this unit is skipped.
# - Assert: Similar to condition. But if conditions are not met, a failure 
# is caused.
# - ConditionPathExists: Unit starts if this path exists. Start the path 
# with ! to negate.
#
# 1.4.2. Install Section Directives (Applies to all unit types)
# - WantedBy: Similar to Wants directive of the Unit section. Mostly used 
# with targets.
# - RequiredBy: Similar to WantedBy but is more strict. Causes a failure if 
# the dependency is not met.
# - Alias: Allows the unit have an alias name. (Like ssh, sshd).
# - Also: Additional units to install/deinstall when this unit is installed/
# deinstalled,
# - DefaultInstance: Only used with template units. Default instance name of 
# the unit enabled from a template.
#
# 1.4.3. Service Section Directives (Applies to Service unit types)
# - Type: Could be one of the following
# -- simple: Systemd considers that the service starts immediately.
# -- exec: Similar to simple, but systemd waits for binary to execute.
# -- forking: The service forks a child process and exists.
# -- oneshot: The service live for a short time and then exit.
# -- dbus: The service will take a name on the D-Bus bus.
# -- notify: The service will notify when finishes starting up.
# -- idle: This indicates that the service will not be run until all jobs 
# are dispatched.
# - RemainAfterExit: Commonly used with the oneshot type. Indicates that the 
# service isactive even after the process exits.
# - PIDFile: For forking type, file to contain the PID of the child process.
# - BusName: For dbus type, name to acquire as D-Bus name.
# - NotifyAccess: Takes none, main, exec or none values. Controls which 
# service status messages are notified. 
# - Environment: Environment values to set.
# - EnvironmentFile: Read a list of environment values from this file. (-) 
# sign before the file name means, ignore if the file is not found.
# - Killmode: How to stop the service. process means only to kill the main 
# process, mixed means to kill the main process and the others, none means 
# just run ExecStop.
# - ExecStart: Command to start the service. If starts with -, ignore 
# failure.
# - ExecStartPre: Additional commands to run before the main process. 
# - ExecStartPost: Additional commands to run after the main process.
# - ExecReload: Command to reload the service.
# - ExecStop: Command to stop the service.
# - ExecStopPost: Additional commands to run after stopping the process.
# - RestartSec: Amount of time to wait before restart.
# - Restart: Circumstances to automatically restart the service. Could be: 
# always, on-success, on-failure, on-abnormal, on-abort, on-watchdog etc.
# - RestartPreventExitStatus: Prevents the service from automatically 
# restarting if the given exit code is received.
# - RuntimeDirectory: Directory to run the service (under /run).
# - RuntimeDirectoryMode: Permissions of the runtime directory (Default 
# 0755).
# - TimeoutSec: Amount of time to wait before starting or stopping the 
# service before marking as failed (or killing it).
# - TimeoutStartSec: Amount of time to wait before starting service before 
# marking as failed (or killing it).
# - TimeoutStopSec: Amount of time to wait before starting service before 
# marking as failed (or killing it).
#
# 1.4.3. Socket Section Directives (Applies to Socket unit types)
# - ListenStream: Address to listen on for a TCP stream.
# - ListenDatagram: Address to listen on for a UDP stream.
# - ListenSequentialPacket: Address to listen on for a Unix socket stream.
# - ListenFIFO: File system FIFO to listen on.
# - Accept: If yes, a service instance is spawned for each incoming 
# connection, if no, all listening sockets are passed to the started service
# unit.
# - SocketUser: Unix user name for the socket (default root).
# - SocketGroup: Unix group name for the socket (default root).
# - SocketMode: System access permissions for the socket (default 0666).
# - Service: Connected service name (if different than the socket name).
#
# 1.4.4. Mount Section Directives (Applies to Mount unit types)
# - What: Path to be mounted.
# - Where: Path to mount.
# - Type: The filesystem type of the mount.
# - Options: A comma-separated list of the mount options.
# - SloppyOptions: Boolean to determine whether the mount will fail if there 
# is an unrecognized mount option.
# - DirectoryMode: Permission mode for the parent dirs of the mount point.
# - TimeoutSec: Amount of time to wait before the operation is marked as 
# failed.
#
# 1.4.5. Automount Section Directives (Applies to Automount unit types)
# - Where: Path to mount.
# - DirectoryMode: Permission mode for the parent dirs of the mount point.
#
# 1.4.6. Swap Section Directives (Applies to Swap unit types)
# - What: Path of the location of swap (file or device).
# - Priority: Swap priority in integer form.
# - Options: Comma separated list of options for /etc/fstab file.
# - TimeoutSec: Amount of time to wait before the operation is marked as 
# failed.
#
# 1.4.7. Path Section Directives (Applies to Path unit types)
# - PathExists: If this path exists, associated unit will be activated.
# - PathExistsGlob: Similar to PathExists, supports file glob expressions.
# - PathChanged: Activates the associated unit when the file in the path is 
# saved and closed.
# - PathModified: Similar to PathChanged but also activates the unit when 
# the file is changed.
# - DirectoryNotEmpty: Activates the associated unit when the specified 
# directory is not empty.
# - Unit: Connected unit name (if different than the path name).
# - MakeDirectory: If true, the directories to watch are created before 
# watching.
# - DirectoryMode: Permissions to use if MakeDirectory is true (Default 755)
#
# 1.4.8. Timer Section Directives (Applies to Timer unit types)
# - OnActiveSec: Activate the associated unit after this amount of time  
# after the activation time of the timer unit.
# - OnBootSec: Activate the associated unit after this amount of the time 
# after the boot.
# - OnStartupSec: Activate the associated unit after this amount of the time 
# after systemd process started.
# - OnUnitActiveSec: Activate the associated unit after this amount of the 
# time after the associated unit last activated.
# - OnUnitInactiveSec: Activate the associated unit after this amount of the 
# time after the associated unit last marked as inactive.
# - OnCalendar: Absolute time to activate the associated unit.
# - AccuracySec: Level of accuracy of the timer.
# - Unit: Associated unit name (if different than the timer name).
# - Persistent: If true, the time when the service unit was last triggered 
# is stored on disk. When the timer is activated, the service unit is 
# triggered immediately if it would have been triggered at least once during
# the time when the timer was inactive. 

2. Targets

#-- 2.1. Definition and List
# - Similar to SysV init runlevel. Their purpose is to group together other 
# systemd units through a chain of dependencies.
# A fresh install Ubuntu 22.04 server has the following targets:
systemctl list-unit-files --type=target
UNIT FILE                     STATE           VENDOR PRESET
basic.target                  static          -            
blockdev@.target              static          -            
bluetooth.target              static          -            
boot-complete.target          static          -            
cloud-config.target           static          -            
cloud-init.target             enabled-runtime enabled      
cryptsetup-pre.target         static          -            
cryptsetup.target             static          -            
ctrl-alt-del.target           alias           -            
default.target                alias           -            
emergency.target              static          -            
exit.target                   disabled        disabled     
final.target                  static          -            
first-boot-complete.target    static          -            
friendly-recovery.target      static          -            
getty-pre.target              static          -            
getty.target                  static          -            
graphical.target              static          -            
halt.target                   disabled        disabled     
hibernate.target              static          -            
hybrid-sleep.target           static          -            
initrd-fs.target              static          -            
initrd-root-device.target     static          -            
initrd-root-fs.target         static          -            
initrd-switch-root.target     static          -            
initrd-usr-fs.target          static          -            
initrd.target                 static          -            
kexec.target                  disabled        disabled     
local-fs-pre.target           static          -            
local-fs.target               static          -            
multi-user.target             static          -            
network-online.target         static          -            
network-pre.target            static          -            
network.target                static          -            
nss-lookup.target             static          -            
nss-user-lookup.target        static          -            
paths.target                  static          -            
poweroff.target               disabled        disabled     
printer.target                static          -            
reboot.target                 disabled        enabled      
remote-cryptsetup.target      disabled        enabled      
remote-fs-pre.target          static          -            
remote-fs.target              enabled         enabled      
remote-veritysetup.target     disabled        enabled      
rescue-ssh.target             static          -            
rescue.target                 static          -            
rpcbind.target                static          -            
runlevel0.target              alias           -            
runlevel1.target              alias           -            
runlevel2.target              alias           -            
runlevel3.target              alias           -            
runlevel4.target              alias           -            
runlevel5.target              alias           -            
runlevel6.target              alias           -            
shutdown.target               static          -            
sigpwr.target                 static          -            
sleep.target                  static          -            
slices.target                 static          -            
smartcard.target              static          -            
snapd.mounts-pre.target       static          -            
snapd.mounts.target           static          -            
sockets.target                static          -            
sound.target                  static          -            
suspend-then-hibernate.target static          -            
suspend.target                static          -            
swap.target                   static          -            
sysinit.target                static          -            
system-update-pre.target      static          -            
system-update.target          static          -            
time-set.target               static          -            
time-sync.target              static          -            
timers.target                 static          -            
umount.target                 static          -            
usb-gadget.target             static          -            
veritysetup-pre.target        static          -            
veritysetup.target            static          -            
#
# Target configuration files reside in /lib/systemd/system/
#
#-- 2.2. Sample Target Files
# network.target
[Unit]
Description=Network
Documentation=man:systemd.special(7)
Documentation=https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget
After=network-pre.target
RefuseManualStart=yes
#
# multi-user.target
[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes
#
# graphical.target
[Unit]
Description=Graphical Interface
Documentation=man:systemd.special(7)
Requires=multi-user.target
Wants=display-manager.service
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target display-manager.service
AllowIsolate=yes

3. Unit Management: systemctl Command

# Start a service
sudo systemctl start apache2.service
#
# Stop a service
sudo systemctl stop apache2.service
#
# Reload a service
sudo systemctl reload apache2.service
#
# Restart a service
sudo systemctl restart apache2.service
#
# Try to reload if possible, otherwise restart a service
sudo systemctl reload-or-restart apache2.service
#
# Enable a service (starts at boot)
sudo systemctl enable apache2.service
#
# Disable a service (does not start at boot)
sudo systemctl disable apache2.service
#
# Show status of a service
sudo systemctl status apache2.service
#
# Show if service is active
systemctl is-active apache2.service
#
# Show if service is enabled
systemctl is-enabled apache2.service
#
# Show if service is failed
systemctl is-failed apache2.service
#
# Mask a service (completely unstartable)
sudo systemctl mask apache2.service
#
# Unmask a service
sudo systemctl unmask apache2.service
#
# List all active units
sudo systemctl list-units
#
# Including loaded and attempted to load
sudo systemctl list-units --all
#
# Including all installed
sudo systemctl list-unit-files
#
# List services only
systemctl list-units --type=service
#
# See contents of a unit file
systemctl cat apache2.service
#
# See dependencies of a unit
systemctl list-dependencies apache2.service
#
# See dependencies of a unit recursively
systemctl list-dependencies apache2.service --all
#
# See low level details of a unit
systemctl show apache2.service
#
# Append or modify settings in a unit file
sudo systemctl edit apache2.service
#
# Edit entire contents of a unit file
sudo systemctl edit --full apache2.service
#
# Reload systemd
sudo systemctl daemon-reload
#
# Show default target (run level) of a system
systemctl get-default
#
# Set default target
sudo systemctl set-default graphical.target
sudo systemctl set-default multi-user.target
#
# List of available targets
systemctl list-unit-files --type=target
#
# List of units tied to a target
systemctl list-dependencies multi-user.target
#
# Poweroff and reboot
sudo systemctl poweroff
sudo systemctl reboot
#
# Boot into rescue mode
sudo systemctl rescue
#
# Halt (Does not poweroff the machine)
sudo systemctl halt
#
# Control systemd of a remote system
systemctl --host user_name@host_name command

4.Log Management: journal-ctl Command

# See all log entries
journalctl
#
# See all log entries for the current boot
journalctl -b
#
# See only kernel entries
journalctl -k
#
# See only kernel entries for the current boot
journalctl -k -b
#
# See only apache log entries
journalctl -u apache2.service
#
# See only apache log entries for the current boot
journalctl -b -u apache2.service
#
# See logs from previous boot
journalctl -b -1
#
# List boots
journalctl --list-boots
#
# Logs in a time interval
journalctl --since "2023-01-10 17:15:00"
journalctl --since "2023-01-10" --until "2023-01-11 03:00"
journalctl --since yesterday
journalctl --since 09:00 --until "1 hour ago"
journalctl -u apache2.service --since today
#
# Log entries for an executable
journalctl /usr/bin/bash
#
# Log in json format
journalctl -b -u apache2 -o json
journalctl -b -u apache2 -o json-pretty
#
# Most recent 10 entries
journalctl -n
#
# Most recent 20 entries
journalctl -n 20
#
# Actively follow logs (like tail -f). Press Ctrl-C to exit.
journalctl -f
#
# Disk usage
journalctl --disk-usage
#
# Delete old logs up to size
sudo journalctl --vacuum-size=1G
#
# Delete old logs up to time
sudo journalctl --vacuum-time=1years

5. Other systemd Components

# systemd has some other components too. Some of them:
#
# - systemd-boot: UEFI boot manager. Supports basic boot manager 
# configuration. 
# Integrates with systemctl command. Has the command bootctl.
# - systemd-cat: Adds a record to the systemd log with a pipeline.
echo "Test" | systemd-cat -p info
# - systemd-localed: Manages system locale settings. Has the command 
# localectl.
# - systemd-logind: Manages user logins. Keeps track of users and sessions. 
# Handles device access management for users.
# - systemd-machined: Detects and monitors virtual machines and containers. 
# Has machinectl command.
# - systemd-mount: Handles .mount and .automount units.
# - systemd-networkd: Network configuration management. Configuration files 
# are in /lib/systemd/network, /run/systemd/network, and /etc/systemd/network 
# in increasing precedence.
# - systemd-nspawn: May be used to run a command or OS in a light-weight
# namespace container. In many ways it is similar to chroot
# - systemd-resolved: Provides network name resolution. Has resolvectl 
# command.
# - systemd-sysusers: Creates system users and groups.
# - systemd-timesyncd: Provides system time synchronization across the 
# network with a remote NTP server. Has timedatectl command.
# - systemd-tmpfiles: Creates, deletes, and cleans up volatile and temporary 
# files and directories.
# - systemd-udevd: Manages physical devices.

6. Creating a Service

# - We will create a very simple service. Our service will ping an IP 
# address in every 10 minutes. It will create an info log if the ping is OK, 
# otherwise it will create and error log.
# - The application will be a shell script, the script will be put into 
# /usr/local/bin, a unit file will be created, enabled, started and tested.
#
#-- 6.1. Create Shell Script
nano ipcheck.sh
# Fill as below
#!/bin/bash
echo "ipcheck.service: Start. $(date)" | systemd-cat -p info
while true ; do
  ping -q -w 1 192.168.1.1 > /dev/null
  if [ $? = 0 ] ; then
     echo "ipcheck.service: Ping to IP is OK. $(date)" | systemd-cat -p info
  else
     echo "ipcheck.service: Error cannot ping IP. $(date)" | systemd-cat -p err
  fi
  sleep 600
done
#
#-- 6.2. Copy it into /usr/local/bin
# Make it executable
chmod +x ipcheck.sh
# Copy
sudo cp ipcheck.sh /usr/local/bin
#
#-- 6.3. Create Unit File
sudo systemctl edit --force --full ipcheck.service
# The command will open a nano editor
# Fill it as below, save and exit
[Unit]
Description=IPCheck Demo Service
Wants=network.target
After=syslog.target network-online.target
[Service]
ExecStart=/usr/local/bin/ipcheck.sh
Restart=on-failure
RestartSec=20
KillMode=process
[Install]
WantedBy=multi-user.target
#
#-- 6.4. Enable and Start The Service
sudo systemctl enable ipcheck.service
sudo systemctl start ipcheck.service
#
#-- 6.5. Test it
sudo journalctl -n 20




Nginx On Debian/Ubuntu

NginxOnDebianUbuntu: Basic Nginx Configuration On Debian and Ubuntu

Copyright (C) 2023 Exforge exforge@x386.org

# - 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

# - Basic Nginx configuration, installation, SSL, LEMP stack, sample site
# configurations, 
# 
# Server: Debian 12/11 or Ubuntu 22.04/20.04 LTS Server
# srv1, srv2, srv3, srv4 all has the server's IP address.
#
# Sources:
Book:ISBN 978-1-80323-424-3 Mastering Ubuntu Server 4th Ed. by Jay LaCroix
https://nginx.org/en/docs/
https://www.geeksforgeeks.org/how-to-retrieve-data-from-mysql-database-using-php/

1. Installation and Configuration Files

#-- 1.1. Installation
# Update repositories
sudo apt update
# Install nginx
sudo apt install nginx --yes
# A simple website is ready at http://srv1/
#
#-- 1.2. Configuration Files
# Configuration files reside in /etc/nginx.
# See main configuration file:
sudo nano /etc/nginx/nginx.conf
# 
# Available sites are in /etc/nginx/sites-available/
#  They must be enabled, that is linked to /etc/nginx/sites-enabled/ 
# - To enable a site conf named mysite in /etc/nginx/sites-available/ :
sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/mysite
# And to disable it:
sudo rm /etc/nginx/sites-enabled/mysite
# - There is a default configuration which is already enabled
sudo nano /etc/nginx/sites-available/default
#
# - After enabling or disabling a site, we need to reload nginx:
sudo systemctl reload nginx
# 
#-- 1.3. Site enable and disable scripts
# - You may remember Ubuntu (and Debian) Apache installations has a2ensite 
# and a2dissite scripts. We will create very (actually very very) simple 
# nginx scripts like them. 
#
# 1.3.1. Create ~/bin Directory
# - This directory is in the search list of the executable files. You may 
# already have it. If it is so, skip this step.
mkdir ~/bin
# You have to logoff and logon again.
#
# 1.3.2. Scripts
# Create site enable script
nano ~/bin/ngensite
# Fill it as below
#/bin/bash
sudo ln -s /etc/nginx/sites-available/$1 /etc/nginx/sites-enabled/$1
#
# Create site disable script
nano ~/bin/ngdissite
# Fill it as below
#/bin/bash
sudo rm /etc/nginx/sites-enabled/$1
#
# Make scripts executable
chmod +x ~/bin/ngensite
chmod +x ~/bin/ngdissite
#
# Now we can disable or enable a site with these scripts:
ngensite default
ngdissite default
# 
#-- 1.4. Redesign Our Site
# - We will disable default configuration, leave it at sites-available as a 
# template, create a new conf with the name srv1 (my hostname) and enable 
# it.
#
# 1.4.1. Disable default Conf
sudo rm /etc/nginx/sites-enabled/default
# or simply
ngdissite default
#
# 1.4.2. Create the New Conf
sudo nano /etc/nginx/sites-available/srv1
# Fill it as below
server {
   listen 80;
   listen [::]:80;
   root /var/www/html;
   index index.html index.htm index.nginx-debian.html;
   server_name srv1;
   location / {
      try_files $uri $uri/ =404;
   }
   server_tokens off;
}
#
# Explanations:
# - Listen IP version 4 at port 80
# - Listen IP version 6 at port 80
# - Root directory is /var/www/html
# - Index file (default file) is one of the following in order
# - Server name is srv1 (can be more than 1 - after srv1)
# - For the location in root (and subfolders), try the given name as a file,
# then as a folder, if can't find, send 404 error message.
# - Don't display server version at error (and other) messages.
#
# 1.4.3. Enable the New Conf
ngensite srv1
# It is necessary to reload nginx
sudo systemctl reload nginx

2. SSL Configuration

# - We will test SSL configuration with self signed certificates. Later on 
# the tutorial, we are going to test getting certificates with certbot tool 
# too.
#
#-- 2.1. Create a Self Signed Certificate
# Create a place for the certificates
sudo mkdir /etc/nginx/certs
# Create certificates
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/certs/srv1.key -out /etc/nginx/certs/srv1.crt
# You can give default answers to all the questions.
# Key and certificate files are copied to /etc/nginx/certs/
#
#-- 2.1. Create SSL Conf
sudo nano /etc/nginx/sites-available/srv1-ssl
# Fill it as below
server {
   listen 443 ssl;
   listen [::]:443 ssl;
   root /var/www/html;
   index index.html index.htm index.nginx-debian.html;
   server_name srv1;
   ssl_certificate /etc/nginx/certs/srv1.crt;
   ssl_certificate_key /etc/nginx/certs/srv1.key;
   ssl_session_timeout 5m;
   location / {
      try_files $uri $uri/ =404;
   }
   server_tokens off;
}
#
# Enable it
ngensite srv1-ssl
#
#-- 2.2. HTTPS Redirection
# - We have to add a redirection to srv1 conf to automatically redirect 
# http://srv1/ to https://srv1/
sudo nano /etc/nginx/sites-available/srv1
# Add the line below after the listen lines
   return 301 https://$host$request_uri;
#
# Reload nginx
sudo systemctl reload nginx
#
#-- 2.3. SSL Site is Ready
https://srv1/
# - Your firefox will complain as "Warning: Potential Security Risk Ahead", 
# because our certificate is a self signed one. You can click "Advanced" and
# "Accept the Risk and Continue" to reach the SSL site.

3. LEMP Stack

# L: Linux (Debian or Ubuntu in our case)
# E: Nginx (Enginx actually)
# M: Mariadb (or Mysql if you love Or*cle so much)
# P: PHP, Python, or Perl (PHP in our case)
#
# So not a big deal, we'll install Mariadb and PHP and connect them with 
# Nginx.
#
#-- 3.1. Install mariadb, php, and necessary dependancies.
# php-cli   : PHP client package
# php-fpm   : to run php as a cgi, nginx doesn't have a native support for 
# php-mysql : for php to connect to mariadb
sudo apt install --yes mariadb-server php-cli php-fpm php-mysql
#
#-- 3.2. Update srv1-ssl Conf for PHP
sudo nano /etc/nginx/sites-available/srv1-ssl
# Add the following part after the end of the location stanza.
   location ~ \.php$ {
      fastcgi_pass unix:/run/php/php-fpm.sock;
      include fastcgi.conf;
   }
#
# Restart nginx
sudo systemctl restart nginx
# 
#-- 3.3. Test it
# - We'll create a test database, a table in that database, add some rows to 
# the table on Mariadb. We will also create a test PHP file with the PHP 
# code to retrieve the data from the database and display it as HTML. 
#
# 3.3.1. DB Operations
# Connect to Mariadb shell
sudo mariadb
# - Create mysampledb database, connect to it, create a table, fill the 
# table, create a user with the access permission to that database and the 
# table.
# !!! BEGIN Run on Mariadb shell. !!!
CREATE DATABASE mysampledb;
USE mysampledb;
CREATE TABLE Employees (Name char(15), Age int(3), Occupation char(15));
INSERT INTO Employees VALUES ('Joe Smith', '26', 'Ninja');
INSERT INTO Employees VALUES ('John Doe', '33', 'Sleeper');
INSERT INTO Employees VALUES ('Mariadb Server', '14', 'RDBM');
GRANT ALL ON mysampledb.* TO 'appuser'@'localhost' IDENTIFIED BY 'password';
exit
# !!! END Run on Mariadb shell. !!!
# 
# 3.3.2. Create Test PHP
sudo nano /var/www/html/test.php
# Fill it as below
<?php
   $mycon = new mysqli("localhost", "appuser", "password", "mysampledb");
   if ($mycon->connect_errno)
   {
      echo "Connection Error";
      exit();
   }
   $mysql = "SELECT * FROM Employees";
   $result = ($mycon->query($mysql));
   $rows = [];
   if ($result->num_rows > 0)
    {
        $rows = $result->fetch_all(MYSQLI_ASSOC);
    }
?>
<!DOCTYPE html>
<html>
<body>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Age</th>
                <th>Occupation</th>
            </tr>
        </thead>
        <tbody>
            <?php
               if(!empty($rows))
               foreach($rows as $row)
              {
            ?>
            <tr>
                <td><?php echo $row['Name']; ?></td>
                <td><?php echo $row['Age']; ?></td>
                <td><?php echo $row['Occupation']; ?></td>
            </tr>
            <?php } ?>
        </tbody>
    </table>
</body>
</html>
<?php
    mysqli_close($conn);
?>
# Now go to below address to see if it is working
https://srv1/test.php

4. Sample Configs

#-- 4.1. Three Sites in One Conf File
# srv2, srv3, and srv4 all have their directories and served in the same 
# server
server {
   listen 80;
   listen [::]:80;
   root /var/www/srv2;
   index index.html index.htm index.nginx-debian.html;
   server_name srv2;
   location / {
      try_files $uri $uri/ =404;
   }
}
server {
   listen 80;
   listen [::]:80;
   root /var/www/srv3;
   index index.html index.htm index.nginx-debian.html;
   server_name srv3;
   location / {
      try_files $uri $uri/ =404;
   }
}
server {
   listen 80;
   listen [::]:80;
   root /var/www/srv4;
   index index.html index.htm index.nginx-debian.html;
   server_name srv4;
   location / {
      try_files $uri $uri/ =404;
   }
}
#
#-- 4.2. Site Only Allowing 2 IPs to connect
server {
   listen 80;
   listen [::]:80;
   root /var/www/srv2;
   index index.html index.htm index.nginx-debian.html;
   server_name srv2;
   allow 192.168.1.108;
   allow 192.168.1.109;
   deny all;
   location / {
      try_files $uri $uri/ =404;
   }
}
#
#-- 4.3. IP Access Control on One Directory Only
# Site is open to all IPs. Admin folder is restricted to 1 IP.
server {
   listen 80;
   listen [::]:80;
   root /var/www/srv2;
   index index.html index.htm index.nginx-debian.html;
   server_name srv2;
   deny all;
   location / {
      try_files $uri $uri/ =404;
   }
   location /admin {
      allow 192.168.1.108;
      deny all;
   }
}
#
#-- 4.4. Https Redirection with Certbot Access
# - Redirect to Https site except the certbot (Letsencrypt acme challenge) 
# accessing /.well-known/acme-challenge/. So that certbot can renew 
# certificates by connecting to the Http site.
#
server {
   listen 80;
   listen [::]:80;
   index index.html index.htm index.nginx-debian.html;
   server_name srv1;
   location ^~ /.well-known/acme-challenge {
       allow all; 
       root /var/www/html;
   }
    location / {
       return 301 https://$host$request_uri;
    }
   server_tokens off;
}

5. HTTPS With Free Let's Encrypt Certificates

# - This section is performed on a VPS on internet. To get free Let's 
# Encrypt certificates, our hostname must be in a DNS in internet. 
#
#-- 5.0. Specs
# Server   : Debian 12/11 or Ubuntu 22.04/20.04 LTS Server
# Hostname : x11.xyz
# Server is fresh installed.
# 
# - Remember to change all the occurences of x11.xyz and www.x11.xyz with 
# your server names.
#
#-- 5.1. Install Nginx
sudo apt update
sudo apt install nginx --yes
#
#-- 5.2. Create ngensite and ngdissite scripts as in 1.3.
#
#-- 5.3. Disable default Site and Create a New One Named as x11.xyz
ngdissite default
sudo nano /etc/nginx/sites-available/x11.xyz
# Fill it as below
server {
   listen 80;
   listen [::]:80;
   root /var/www/x11.xyz;
   index index.html index.htm index.nginx-debian.html;
   server_name x11.xyz www.x11.xyz;
   location / {
      try_files $uri $uri/ =404;
   }
   server_tokens off;
}
#
# Create /var/www/x11.xyz folder. Put some test HTMLs into it.
#
# Enable the new conf
ngensite x11.xyz
# Reload nginx
sudo systemctl reload nginx
# Your site is ready
# 
#-- 5.4. Install certbot and Get a Free Certificate
# Install certbot
sudo apt install certbot --yes
#
# - Run certbot to get certificates. For authentication method question; 
# select the option 2 (Place files ...), and enter root directory 
# (/var/www/x11.xyz for my server).  Enter an email address and accept TOS.
sudo certbot certonly -d x11.xyz -d www.x11.xyz
# Certificates are installed to /etc/letsencrypt/live/x11.xyz/
# Certificates will auto renew, you can check the process with:
sudo certbot renew --dry-run
#
#-- 5.5. Create a conf for the SSL site
sudo nano /etc/nginx/sites-available/x11.xyz-ssl
server {
   listen 443 ssl;
   listen [::]:443 ssl;
   root /var/www/x11.xyz;
   index index.html index.htm index.nginx-debian.html;
   server_name x11.xyz www.x11.xyz;
   ssl_certificate /etc/letsencrypt/live/x11.xyz/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/x11.xyz/privkey.pem;
   ssl_session_timeout 5m;
   location / {
      try_files $uri $uri/ =404;
   }
   server_tokens off;
}
# Enable it
ngensite x11.xyz-ssl
#
#-- 5.5. Update HTTP conf
# Our http conf must redirect to https site with the exception of certbot
# renew process
sudo nano /etc/nginx/sites-available/x11.xyz
# Change as below
server {
   listen 80;
   listen [::]:80;
   index index.html index.htm index.nginx-debian.html;
   server_name x11.xyz www.x11.xyz;
   location ^~ /.well-known/acme-challenge {
       allow all; 
       root /var/www/x11.xyz;
   }
    location / {
       return 301 https://$host$request_uri;
    }
   server_tokens off;
}
#
# Reload Nginx
sudo systemctl reload nginx
#
# Your HTTPS site is ready:
https://x11.xyz/

6. Nginx and Apache Together

# !!! This section starts with a fresh installed server !!!
# - Nginx is very good at static content, Apache is very good at dynamic 
# content. So we can use them together for the maximum performance.
#-- 6.0. Specs
# - Nginx will run at port 80 and listen to the outside.
# - Apache will run at port 8080 and listen to only inside. That is it can 
# be connected by only the localhost. So that only Nginx will listen to it.
# - PHP and Mariadb will be connected to Apache only.
# - All the HTML (and other static content) will be served by Nginx.
# - All the PHP (and other dynamic content) will be served by Apache.
#
#-- 6.1. Install nginx And Reconfigure the Default Site
# Of course you may disable the default site and configure a new one
# Install nginx
sudo apt update
sudo apt install nginx --yes
# Backup default conf
sudo cp /etc/nginx/sites-available/{default,default.backup}
# Update default conf
sudo nano /etc/nginx/sites-available/default
# Fill as below
server {
   listen 80;
   listen [::]:80;
   server_name .x11.xyz;
   index index.html index.htm index.nginx-debian.html;
   root /var/www/html;
   location ~ \.php {
      proxy_pass http://127.0.0.1:8080;
   }
   location / {
      try_files $uri $uri/ =404;
   }
   server_tokens off;
}
# Reload nginx
sudo systemctl reload nginx
#
#-- 6.2. Install apache2, php, mariadb, and dependencies
sudo apt install --yes apache2 php mariadb-server libapache2-mod-php php-mysql
# Apache doesn't start automatically, because port 80 is busy with nginx.
# Change Apache's default listening port from 80 to 8080.
sudo nano /etc/apache2/ports.conf
# Change the following line from: 
Listen 80
# to as below
Listen 8080
# Update the default conf to listen to 8080 and only from local
sudo nano /etc/apache2/sites-available/000-default.conf
# Change the first line from:
<VirtualHost *:80>
# to as below
<VirtualHost 127.0.0.1:8080>
# Restart Apache
sudo systemctl restart apache2
#
# You can test the combination using steps at 3.3.




Other Linuxes On Debian/Ubuntu

OtherLinuxesOnDebianUbuntu – Other Linuxes for Debian/Ubuntu Admins

Copyright (C) 2023 Exforge exforge@x386.org

# - 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. Information
# - Although my tutorials (and my learning curve) contain Debian and 
# Ubuntu Linux distributions; Time to time, an admin may need to handle
# other Linuxes too. In this tutorial, my aim is to help with other linuxes,
# namely Red Hat, Opensuse, Alpine, and Devuan.
#
# - Main subjects are:
# -- Package Management
# -- Network Configuration
# -- Installing LAMP Stack
# -- Service Management
# - Main Distributions:
# -- Debian 12 and 11
# -- Ubuntu 22.04 and 20.04 LTS
# -- RHEL (Centos, Alma, Rocky) 9.x, 8.x
# -- OpenSuse Leap 15.x
# -- Alpine 3.17
# -- Devuan 4
#
#-- 0.2. Resources:
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9
https://wiki.alpinelinux.org/wiki/Main_Page
https://doc.opensuse.org/
https://wiki.debian.org/
https://www.geeksforgeeks.org/how-to-retrieve-data-from-mysql-database-using-php/

1. Debian 12 & 11

#-- 1.1. Package Management
# Commands require root or sudo.
# 1.1.1. Update Cache
apt update
# 1.1.2. Upgrade Packages
apt upgrade
# 1.1.3. Install a Package
apt install apache2
# 1.1.4. Remove a Package
apt remove apache2
# 1.1.5. Search a Package
apt search apache2
# 1.1.6. Clean Unused Packages
apt autoremove
# 1.1.7. Show Package Info
apt show apache2
# 
#-- 1.2. Network Configuration
# 1.2.1. Get the name of the network adapter
# - The name of the network adapter will be something like enp0s3, but we 
# need the exact name. 
# - The following command lists the network interface name(s). The one in 
# en* format should be the name of your network adapter.
ls /sys/class/net
# In any case you cannot get the name, you can try to following command:
ip a
#
# 1.2.2. IP configuration. 
sudo nano /etc/network/interfaces
# I assume that your network adapter name is enp0s3, otherwise change it.
# Fill the file like below (change to your IP addresses)
auto enp0s3
iface enp0s3 inet static
address 192.168.0.3/24
broadcast 192.168.0.255
network 192.168.0.0
gateway 192.168.0.1
# If you want to use DHCP, fill the file as below
auto enp0s3
iface enp0s3 inet dhcp
# 
# 1.2.3. DNS Addresses
sudo nano /etc/resolv.conf
# Add your DNS addresses as below
nameserver 46.196.235.35
nameserver 178.233.140.110
nameserver 46.197.15.60
#
# 1.2.4. Restart Network Adapter
# Assuming your network adapter name is enp0s3
sudo ifdown enp0s3 && sudo ifup enp0s3
# or
sudo systemctl restart networking.service
# - If you are connecting through SSH, your connection would break up. You 
# need to connect with the new IP again.
#
#-- 1.3. Installing LAMP Stack
# 1.3.1. Install packages
sudo apt update
sudo apt install --yes apache2 mariadb-server php libapache2-mod-php php-mysql
# - This is all we need actually. But if you are like me, that is you have 
# to see it to believe it; you are going to want to test it. So let's test 
# it.
#
# 1.3.2. Test LAMP
# - We'll create a test database on Mariadb, we'll create a table in that 
# database, add some rows to the table. We will also create a test PHP file
# with the PHP code to retrieve the data from the database and display it as 
# HTML. That way we'll be able to test the PHP-Mariadb and PHP-Apache 
# connections.
sudo mariadb
# - Create mysampledb database, connect to it, create a table, fill the 
# table, create a user with the access permission to that database and the 
# table.
# !!! BEGIN Run on Mariadb shell. !!!
CREATE DATABASE mysampledb;
USE mysampledb;
CREATE TABLE Employees (Name char(15), Age int(3), Occupation char(15));
INSERT INTO Employees VALUES ('Joe Smith', '26', 'Ninja');
INSERT INTO Employees VALUES ('John Doe', '33', 'Sleeper');
INSERT INTO Employees VALUES ('Mariadb Server', '14', 'RDBM');
GRANT ALL ON mysampledb.* TO 'appuser'@'localhost' IDENTIFIED BY 'password';
exit
# !!! END Run on Mariadb shell. !!!
# 
# Create test PHP
sudo nano /var/www/html/test.php
# Fill it as below
<?php
   $mycon = new mysqli("localhost", "appuser", "password", "mysampledb");
   if ($mycon->connect_errno)
   {
      echo "Connection Error";
      exit();
   }
   $mysql = "SELECT * FROM Employees";
   $result = ($mycon->query($mysql));
   $rows = [];
   if ($result->num_rows > 0)
    {
        $rows = $result->fetch_all(MYSQLI_ASSOC);
    }
?>
<!DOCTYPE html>
<html>
<body>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Age</th>
                <th>Occupation</th>
            </tr>
        </thead>
        <tbody>
            <?php
               if(!empty($rows))
               foreach($rows as $row)
              {
            ?>
            <tr>
                <td><?php echo $row['Name']; ?></td>
                <td><?php echo $row['Age']; ?></td>
                <td><?php echo $row['Occupation']; ?></td>
            </tr>
            <?php } ?>
        </tbody>
    </table>
</body>
</html>
<?php
    mysqli_close($conn);
?>
#
# - Now, from your workstation's browser, load the page (replace srv with 
# your server's IP: 
http:/srv/test.php
# You can use the same steps for testing LAMP stack on other distros too.
#
#-- 1.4. Service Management
# - Conventionally, when a package with a service is installed on Devuan, it
# is enabled and started by default.
#
# 1.4.1. Status of a Service
systemctl status apache2
# 
# 1.4.2. Start/Stop a Service
sudo systemctl stop apache2
sudo systemctl start apache2
# To force to stop
sudo systemctl kill apache2
#
# 1.4.3. Reload a Service
# Reads configuration file again
sudo systemctl reload apache2
#
# 1.4.4. Restart a Service
# Stops and Starts
sudo systemctl restart apache2
#
# 1.4.5. Enable/Disable a Service
sudo systemctl enable apache2
sudo systemctl disable apache2

2. Ubuntu 22.04 LTS & 20.04 LTS

#-- 2.1. Package Management
# Commands require root or sudo.
# 2.1.1. Update Cache
apt update
# 2.1.2. Upgrade Packages
apt upgrade
# 2.1.3. Install a Package
apt install apache2
# 2.1.4. Remove a Package
apt remove apache2
# 2.1.5. Search a Package
apt search apache2
# 2.1.6. Clean Unused Packages
apt autoremove
# 2.1.7. Show Package Info
apt show apache2
# 
#-- 2.2. Network Configuration
# 2.2.1. Get the name of the network adapter
# - The name of the network adapter will be something like enp0s3, but we 
# need the exact name. 
# - The following command lists the network interface name(s). The one in 
# en* format should be the name of your network adapter.
ls /sys/class/net
# In any case you cannot get the name, you can try to following command:
ip a
#
# 2.2.2. Network configuration. 
# By default Ubuntu uses netplan for network configuration.
sudo nano /etc/netplan/00-installer-config.yaml
# The file name might be different, in that case, use that file.
# I assume that your network adapter name is enp0s3, otherwise change it.
# Fill the file like below (change to your IP addresses)
network:
  ethernets:
    enp0s3:
      addresses:
      - 192.168.1.182/24
      routes:
      - to: default
        via: 192.168.1.1
      nameservers:
        addresses:
        - 8.8.8.8
        - 192.168.1.1
        search:
        - x11.xyz
  version: 2
#
# 2.2.3. Restart Netplan
# Assuming your network adapter name is enp0s3
sudo netplan apply
# - If you are connecting through SSH, your connection would break up. You 
# need to connect with the new IP again.
#
#-- 2.3. Installing LAMP Stack
# 2.3.1. Install packages
sudo apt update
sudo apt install --yes apache2 mariadb-server php libapache2-mod-php php-mysql
#
# 2.3.2. Test LAMP
# You can use the test scenario at 1.3.2 to test RHEL LAMP stack.
#
#-- 2.4. Service Management
# - As Ubuntu being a derivative of Debian, when a package with a service is 
# installed, it is enabled and started by default.
#
# 2.4.1. Status of a Service
systemctl status apache2
# 
# 2.4.2. Start/Stop a Service
sudo systemctl stop apache2
sudo systemctl start apache2
# To force to stop
sudo systemctl kill apache2
#
# 2.4.3. Reload a Service
# Reads configuration file again
sudo systemctl reload apache2
#
# 2.4.4. Restart a Service
# Stops and Starts
sudo systemctl restart apache2
#
# 2.4.5. Enable/Disable a Service
sudo systemctl enable apache2
sudo systemctl disable apache2

3. RHEL (Centos, Alma, Rocky) 9.x, 8.x

# - Centos (upto 8.x version), Alma and Rocky Linux are compatible 
# with Red Hat. That is, they are same other than brandings and names. So 
# if something works in RHEL, it works in Centos, Alma, and Rocky too.
# - RHEL gives free licenses for testing purposes, I have 2 VMs running for
# testing purposes (versions 8.x and 9.x).
#
#-- 3.1. Package Management
# Commands require root or sudo.
# 3.1.1. Update Cache
dnf check-update   
# It is always called when installing or updating packages
# So it is not necessary
# 3.1.2. Upgrade Packages
dnf upgrade
# 3.1.3. Install a Package
dnf install httd
# 3.1.4. Remove a Package
dnf remove httpd
# 3.1.5. Search a Package
dnf search httpd
# 3.1.6. Clean Unused Packages
dnf autoremove
# 3.1.7. Show Package Info
dnf info httpd
# 
#-- 3.2. Network Configuration
# 3.2.1. Get the name of the network adapter
# - The name of the network adapter will be something like enp0s3, but we 
# need the exact name. 
# - The following command lists the network interface name(s). The one in 
# en* format should be the name of your network adapter.
ls /sys/class/net
# In any case you cannot get the name, you can try to following command:
ip a
#
# 3.2.2. IP and DNS configuration. 
# I assume that your network adapter name is enp0s3, otherwise change it.
# Change IP and Gateway, change DNS
sudo nmcli con modify 'enp0s3' ifname enp0s3 ipv4.method manual \
   ipv4.addresses 192.168.0.210/24 gw4 192.168.0.1
sudo nmcli con modify 'enp0s3' ipv4.dns 8.8.8.8
#
# 3.2.3. Restart Network Adapter
sudo nmcli con down 'enp0s3' && sudo nmcli con up 'enp0s3'
#
#-- 3.3. Installing LAMP Stack
# - As you might expect package names are different in RHEL (e.g. httpd 
# instead of apache2).
# 3.3.1. Install packages
sudo dnf -y install httpd mariadb-server php php-mysqlnd
#
# 3.3.2. Enable and Start Apache and Mariadb
sudo systemctl enable --now httpd
sudo systemctl start httpd
sudo systemctl enable --now mariadb
sudo systemctl start mariadb
#
# 3.3.3. Opening Firewall Ports
# - RHEL activates firewall by default. We have to open http and https ports
# permanently.
sudo firewall-cmd --add-service=http --add-service=https
sudo firewall-cmd --add-service=http --add-service=https --permanent
#
# 3.3.4. Test
# You can use the test scenario at 1.3.2 to test RHEL LAMP stack. For RHEL 
# 8, you should run "sudo mysql" instead of "sudo mariadb".
#
#-- 3.4. Service Management
# - RHEL and derivatives does not enable and start services by default.
#
# 3.4.1. Status of a Service
systemctl status httpd
# 
# 3.4.2. Start/Stop a Service
sudo systemctl stop httpd
sudo systemctl start httpd
# To force to stop
sudo systemctl kill httpd
#
# 3.4.3. Reload a Service
# Reads configuration file again
sudo systemctl reload httpd
#
# 3.4.4. Restart a Service
# Stops and Starts
sudo systemctl restart httpd
#
# 3.4.5. Enable/Disable a Service
sudo systemctl enable httpd
sudo systemctl disable httpd

4. OpenSuse Leap 15.x

# - As far as I know, OpenSuse Leap is binary compatible with Suse Linux. So 
# anything works in OpenSuse, must work in Suse too.
#-- 4.1. Package Management
# Commands require root or sudo.
# 4.1.1. Update Cache
zypper refresh
# 4.1.2. Upgrade Packages
zypper update
# 4.1.3. Install a Package
zypper install apache2
# 4.1.4. Remove a Package
zypper remove apache2
# 4.1.5. Search a Package
zypper search apache2
# 4.1.6. Clean Unused Packages
# Not available as much as I know.
# 4.1.7. Show Package Info
zypper info apache2
# 
#-- 4.2. Network Configuration
# - OpenSuse has a configuration utility, you can configure a lot of things 
# including the network. Usage is easy and intuitive:
sudo yast
# But we will also handle it the classical way.
#
# 4.2.1. Get the name of the network adapter
# - The name of the network adapter will be something like eth0 or eth1, but 
# we need the exact name. 
# - The following command lists the network interface name(s). The one in 
# eth* format should be the name of your network adapter.
ls /sys/class/net
# In any case you cannot get the name, you can try to following command:
ip a
#
# 4.2.2. IP and DNS configuration. 
# I assume that your network adapter name is eth0, otherwise change it. 
# Change IP address:
sudo nano /etc/sysconfig/network/ifcfg-eth0
# File contents will be like below
BOOTPROTO='static'
STARTMODE='auto'
IPADDR='192.168.0.248'
NETMASK='255.255.255.0'
# Change Routes:
sudo nano /etc/sysconfig/network/routes
# File contents will be like below
default 192.168.0.1 - -
# Change DNS Addresses:
sudo nano /etc/resolv.conf
# File contents will be like below
nameserver 192.168.0.1
nameserver 8.8.8.8
#
# 4.2.3. Restart Network Adapter
sudo ifdown eth0 && sudo ifup eth0
#
#-- 4.3. Installing LAMP Stack
# 4.3.1. Install packages
sudo zypper -n install apache2 mariadb php8 php8-cli php8-mysql \
    apache2-mod_php8 mariadb
#
# 4.3.2. Enable and Start Apache and Mariadb
sudo systemctl enable apache2
sudo systemctl start apache2
sudo systemctl enable mariadb
sudo systemctl start mariadb
#
# 4.3.3. Test
# - You can use the test scenario at 1.3.2 to test OpenSuse LAMP stack. Just 
# remember, default web site directory is /srv/www/htdocs/ in OpenSuse.
#
#-- 4.4. Service Management
# - OpenSUSE does not enable and start services by default.
#
# 4.4.1. Status of a Service
systemctl status apache2
# 
# 4.4.2. Start/Stop a Service
sudo systemctl stop apache2
sudo systemctl start apache2
# To force to stop
sudo systemctl kill apache2
#
# 4.4.3. Reload a Service
# Reads configuration file again
sudo systemctl reload apache2
#
# 4.4.4. Restart a Service
# Stops and Starts
sudo systemctl restart apache2
#
# 4.4.5. Enable/Disable a Service
sudo systemctl enable apache2
sudo systemctl disable apache2

5. Alpine Linux 3.17 & 3.18

#-- 5.1. Package Management
# Commands require root or sudo.
# 5.1.1. Update Cache
apk update
# 5.1.2. Upgrade Packages
apk upgrade
# 5.1.3. Install a Package
apk add apache2
# 5.1.4. Remove a Package
apt del apache2
# 5.1.5. Search a Package
apk search apache2
# 5.1.6. Clean Unused Packages
# Not available as much as I know.
# 5.1.7. Show Package Info
apk info apache2
# 
#-- 5.2. Network Configuration
# 5.2.1. Get the name of the network adapter
# - The name of the network adapter will be something like eth0, but we need 
# the exact name. 
# - The following command lists the network interface name(s). The one in 
# eth* format should be the name of your network adapter.
ls /sys/class/net
# In any case you cannot get the name, you can try to following command:
ip a
#
# 5.2.2. IP and DNS Configuration
# - I assume that your network adapter name is eth0, otherwise change it. 
# - Change IP address and Gateway:
sudo nano /etc/network/interfaces
# File contents will be like below
auto lo
iface lo inet loopback
#auto eth0
#iface eth0 inet dhcp
auto eth0
iface eth0 inet static
        address 192.168.0.247/24
        gateway 192.168.0.1
        hostname alpine
# Change DNS Addresses:
sudo nano /etc/resolv.conf
# File contents will be like below
nameserver 192.168.0.1
nameserver 8.8.8.8
#
# 5.2.3. Restart Network Adapter
sudo ifdown eth0 && sudo ifup eth0
#
#-- 5.3. Installing LAMP Stack
# 5.3.1. Install Packages
sudo apk add apache2 php php-mysqli php-apache2 mariadb mariadb-client
#
# 5.3.2. Enable Apache
sudo rc-update add apache2 default
sudo rc-service apache2 restart
#
# 5.3.3. Initialize and Enable Mariadb
sudo mysql_install_db --user=mysql --datadir=/var/lib/mysql
sudo rc-update add mariadb default
sudo rc-service mariadb start
#
# 5.3.4. Test
# - You can use the test scenario at 1.3.2 to test Alpine Linux LAMP stack. 
# Just remember, default web site directory is /var/www/localhost/htdocs in 
# Alpine.
#
#-- 5.4. Service Management
# Alpine Linux uses OpenRC as the init system. 
# 5.4.1. Status of a Service
rc-service apache2 status
# 
# 5.4.2. Start/Stop a Service
sudo rc-service apache2 stop
sudo rc-service apache2 start
#
# 5.4.3. Reload a Service
# Reads configuration file again
sudo rc-service apache2 reload
#
# 5.4.4. Restart a Service
# Stops and Starts
sudo rc-service apache2 restart
#
# 5.4.5. Enable/Disable a Service
sudo rc-update add apache2 default
sudo rc-update del apache2 default

6. Devuan 5 & 4

# - Devuan is a derivative of Debian without systemd. Devuan 5 & 4 is based 
# on Debian 12 & 11.
#-- 6.1. Package Management
# Commands require root or sudo.
# 6.1.1. Update Cache
apt update
# 6.1.2. Upgrade Packages
apt upgrade
# 6.1.3. Install a Package
apt install apache2
# 6.1.4. Remove a Package
apt remove apache2
# 6.1.5. Search a Package
apt search apache2
# 6.1.6. Clean Unused Packages
apt autoremove
# 6.1.7. Show Package Info
apt show apache2
# 
#-- 6.2. Network Configuration
# 6.2.1. Get the name of the network adapter
# - The name of the network adapter will be something like enp0s3, but we 
# need the exact name. 
# - The following command lists the network interface name(s). The one in 
# eth* format should be the name of your network adapter.
ls /sys/class/net
# In any case you cannot get the name, you can try to following command:
ip a
#
# 6.2.2. IP configuration. 
sudo nano /etc/network/interfaces
# I assume that your network adapter name is eth0, otherwise change it.
# Fill the file like below (change to your IP addresses)
auto eth0
iface eth0 inet static
address 192.168.0.3/24
broadcast 192.168.0.255
network 192.168.0.0
gateway 192.168.0.1
# If you want to use DHCP, fill the file as below
auto eth0
iface eth0 inet dhcp
# 
# 6.2.3. DNS Addresses
sudo nano /etc/resolv.conf
# Add your DNS addresses as below
nameserver 46.196.235.35
nameserver 178.233.140.110
nameserver 46.197.15.60
#
# 6.2.4. Restart Network Adapter
# Assuming your network adapter name is enp0s3
sudo ifdown eth0 && sudo ifup eth0
# - If you are connecting through SSH, your connection would break up. You 
# need to connect with the new IP again.
#
#-- 6.3. Installing LAMP Stack
# 6.3.1. Install packages
sudo apt update
sudo apt install --yes apache2 mariadb-server php libapache2-mod-php php-mysql
# - This is all we need actually. But if you are like me, that is you have 
# to see it to believe it; you are going to want to test it. So let's test 
# it.
#
# 6.3.2. Test LAMP
# - You can use the test scenario at 1.3.2 to test Alpine Linux LAMP stack. 
# Just remember, default web site directory is /var/www/localhost/htdocs in 
# Alpine.
#
#-- 6.4. Service Management
# - Conventionally, when a package with a service is installed on Debian, it
# is enabled and started by default.
#
# - At Devuan installation, user can choose from 3 init systems:
# sysvinit (default option), runit, or OpenRC. 
# 
# At this tutorial, I assume our Devuan server has sysvinit system.
#
# 6.4.1. Status of a Service
sudo service apache2 status 
# 
# 6.4.2. Start/Stop a Service
sudo service apache2 stop
sudo service apache2 start
#
# 6.4.3. Reload a Service
# Reads configuration file again
sudo service apache2 reload
#
# 6.4.4. Restart a Service
# Stops and Starts
sudo service apache2 restart
#
# 6.4.5. Enable/Disable a Service
sudo update-rc.d apache2 defaults
sudo update-rc.d apache2 remove




Adminer On Debian/Ubuntu

AdminerOnDebianUbuntu: Adminer on Debian and Ubuntu for MariaDB

Copyright (C) 2020 – 2023 Exforge exforge@x386.org

# - 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

# - Adminer is a powerful web based management tool for Mysql, Mariadb and
# more. You have to install it on the server which has the DB installed.
# - On my config I wanted to bind Adminer to a specific site config on the 
# server side and restrict it with only 1 client IP (might be more) to
# reach.
#
# - My Hostname: adminer.x11.xyz
# Lamp is already installed (See LampOnUbuntu Tutorial)
# My Client IP address: 192.168.1.108
# MariaDB Admin User: dbadmin   Password: PaSswOrD1234
# - Server Versions: Debian 12/11 Ubuntu 22.04/20.04 LTS Server

1. Install Adminer

# - It is possible to download Adminer php files and use them, but I prefer
# installing its package, this way all the upgrades will be managed by
# Debian/Ubuntu.
sudo apt update
sudo apt install adminer --yes

2. DB Admin User

# - We are going to need a Database Admin user to log in Adminer and manage 
# the databases.
sudo mariadb
# - Run on Mariadb shell
grant all on *.* to 'dbadmin'@'localhost' identified by 'PaSswOrD1234';

3. Configure Web Site

#-- 3.1. Create a Web Site Config File and Fill it
sudo nano /etc/apache2/sites-available/adminer.x11.xyz.conf
# If you have more than 1 IP to reach Adminer, add them to Require IP line
#   after the first IP.
# If you don't need IP control, remove all the directory stanza (lines 2,3,4)
<VirtualHost *:80>
    <Directory /usr/share/adminer/adminer>
       Require ip 192.168.1.108
    </Directory>   
    Alias /adminer /usr/share/adminer/adminer
    ServerAdmin webmaster@x11.xyz	
    ServerName adminer.x11.xyz
    DocumentRoot /var/www/adminer
    ErrorLog ${APACHE_LOG_DIR}/adminer-error.log
    CustomLog ${APACHE_LOG_DIR}/adminer-access.log combined
</VirtualHost>
#
# - Create a home directory for the site and set permissions
sudo mkdir /var/www/adminer
sudo chown www-data:www-data /var/www/adminer
sudo chmod 770 /var/www/adminer
# - If you want, you can put an index.html file to the home directory, but I 
# prefer leaving the home directory empty and access to Adminer through its
# directory.
#
#-- 3.2. Enable the Site and Reload Apache
sudo a2ensite adminer.x11.xyz.conf
sudo systemctl reload apache2

4. Run it

# - Your web based Database Management tool is ready:
http://adminer.x11.xyz/adminer
# - You need to select MySQL for MariaDB, server must be localhost 
# (default), username: dbadmin, password: (whatever you gave at the Mariadb 
# script, Database: leave empty to reach all the databases.

5. Security

# - You should enable https if you want to put your site on the internet. 
# Actually https should be enabled on local network sites too. Refer to 
# CertbotOnDebianUbuntu tutorial for enabling https with free certificates.




HAProxy On Debian/Ubuntu

HAProxyOnDebianUbuntu: High Availability with HAProxy Load Balancer on Debian and Ubuntu

Copyright (C) 2021 – 2023 Exforge exforge@x386.org

# - 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.1.210  
# srvlb1 : Load Balancer 1  --> 192.168.1.221 
# srvlb2 : Load Balancer 2  --> 192.168.1.222 
# srvaw1 : App/Web Server 1 --> 192.168.1.223 
# srvaw2 : App/Web Server 2 --> 192.168.1.224 
# srvaw3 : App/Web Server 3 --> 192.168.1.225 
# My SMTP Server --> 192.168.1.140 (for keepalived notify messages)
#
# - Tested with Debian 12/11 and Ubuntu 22.04/20.04 LTS Servers
#
# - 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.1.210. 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.1.210) 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.1.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.1.210
	}
}
#
#-- 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.1.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.1.210
	}
}
#
#-- 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-4 --yes
# For Ubuntu 20.04 galera-4 is not compatible, install as below:
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 is 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 logs) 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 both of your LB IPs.
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 192.168.1.221 192.168.1.222
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)
#   The following command makes some fine tunes regarding Mariadb security.
sudo mysql_secure_installation
#   You will be asked some questions.
#     "Enter current password for root (enter for none):"
#     There is no password yet, so press enter.
#   The next 2 questions "Switch to unix_socket authentication [Y/n]" and 
#     "Change the root password? [Y/n]" (for Ubuntu 20.04 there is only 1 
#     question "Set root password? [Y/n]" only) are about securing root 
#     account. In Ubuntu and Debian root account is already protected, so 
#     you can answer n.
#   For the next questions you can select default answers.
# 
#
# 2.4.2. 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.
sudo mariadb
# Run on mariadb shell
GRANT ALL ON *.* TO 'admin'@'192.168.1.221' IDENTIFIED BY 'Password12';
GRANT ALL ON *.* TO 'admin'@'192.168.1.222' 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 27-30)
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.1.223,192.168.1.224,192.168.1.225"
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.1.223, 
# 192.168.1.224 and 192.168.1.225. 
#-- 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.1.223:80 check
        server   srvaw2 192.168.1.224:80 check
        server   srvaw3 192.168.1.225: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.1.223:80 check
# - server   srvaw2 192.168.1.224:80 check
# - server   srvaw3 192.168.1.225: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.1.210 from different 
# workstations and
# see it is connecting to 192.168.1.223, 192.168.1.224, and 192.168.1.225.

4. Configure Mariadb Load Balancing

#-- 4.1. Explanations
# - Load Balancing an application is similar to load balancing a web server. 
# All we need is determining the TCP/IP port it is using an making the
# configurations 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.1.223:3306 check
        server   srvaw2 192.168.1.224:3306 check
        server   srvaw3 192.168.1.225: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.1.210
# - 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    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
#
#-- 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
# response 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.1.223:80 check
        server   srv2 192.168.1.224:80 check
        server   srv3 192.168.1.225: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.1.222:80 weight 2 check
        server   srv2 192.168.1.223:80 weight 2 check
        server   srv3 192.168.1.224:80 weight 1 check
# You can temporarily disable a backend server by disabled keyword:
        server   srv3 192.168.1.224: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.1.223:3306 check
        server   srv2 192.168.1.224:3306 check
        server   srv3 192.168.1.225: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.1.232: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 connections 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.1.223:3306 weight 2 check
        server   srv2 192.168.1.224:3306 weight 2 check
        server   srv3 192.168.1.225: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.1.223:80 check
        server   srv2 192.168.1.224:80 check
        server   srv3 192.168.1.225: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.1.222:80 weight 2 check
        server   srv2 192.168.1.223:80 weight 2 check
        server   srv3 192.168.1.224: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.1.223:80 maxconn 50
        server   srv2 192.168.1.224:80 maxconn 50
        server   srv3 192.168.1.225: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 for HTTP 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.1.223:80 check
backend be_folder2
        server   srvaw2 192.168.1.224:80 check
backend be_folder3
        server   srvaw3 192.168.1.225:80 check
backend be_http_80
        balance  roundrobin
        server   srvaw1 192.168.1.223:80 check
        server   srvaw2 192.168.1.224:80 check
        server   srvaw3 192.168.1.225: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.org/?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 we want to use LetsEncrypt 
# 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. So we need VPS servers. 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  --> 178.128.174.77  178.128.174.77
# u11.xyz: Web Server 1   --> 178.128.34.126  178.128.34.126
# v11.xyz: Web Server 2   --> 178.128.165.126  178.128.165.126
#
# - Tested with Debian 12/11 and Ubuntu 22.04/20.04 LTS Servers
#
# - 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 --yes
# - 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. Configure HAProxy to Redirect to Apache (Run on x11.xyz)
# Edit HAProxy configuration file
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 178.128.174.77:80
        acl acl_acme path_beg -i /.well-known/acme-challenge
        use_backend be_acme if acl_acme
backend be_acme
        server self 127.0.0.1:80 check
# Restart HAProxy
sudo systemctl restart haproxy
#
#-- 8.4. Install and Run Certbot (Run on x11.xyz)
# Install Certbot
sudo apt update
sudo apt install --yes  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.5. Generate Certificate for HAProxy (Run on x11.xyz)
# - Your LetsEncrypt certificates are located at the dir 
# /etc/letsencrypt/live/x11.xyz.
# Of course yours have 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.6. Configure HAProxy (Run on x11.xyz)
# - At 8.3. we made a configuration for redirecting to Apache. This time we 
# are configuring HAProxy website redirection with SSL.
sudo nano /etc/haproxy/haproxy.cfg
# - Remove the lines we added at 8.3
# - Add to the end of the file, remember to use your servers' IPs at bind 
# directive. 
# You can see them with "ip a" command. Remove the lines we added at 8.3. 
# 
frontend fe_http
        bind 178.128.174.77:80
        bind 178.128.174.77: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 178.128.34.126:80 check
        server   v11 178.128.165.126:80 check
# Restart HAProxy
sudo systemctl restart haproxy
# SSL redirection is running now, but we have some more work to polish it.
#
#-- 8.7. 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.8. 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.9. 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.10. 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 178.128.34.126:443 check ssl verify none
        server   v11 178.128.165.126:443 check ssl verify none
# Restart HAProxy
sudo systemctl restart haproxy
#
#-- 8.11. 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 178.128.174.77:80
        bind 178.128.174.77: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 HAProxy
sudo systemctl restart haproxy
#
#-- 8.12. 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 178.128.34.126:443 check ssl verify none cookie u11
        server   v11 178.128.165.126:443 check ssl verify none cookie v11
# Restart HAProxy
sudo systemctl restart haproxy
#
#-- 8.13. 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 178.128.174.77:80
        bind 178.128.174.77: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 178.128.34.126:443 check ssl verify none cookie u11
        server   v11 178.128.165.126:443 check ssl verify none cookie v11




MariaDB Cluster on Debian/Ubuntu

MariadbClusterOnDebianUbuntu: Mariadb Main-Main Replication with Galera Cluster Tutorial on Debian and Ubuntu

Copyright (C) 2021 – 2023 Exforge exforge@x386.org

# - 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
# srv1 -> 192.168.1.214 Debian 12/11 Ubuntu 22.04/20.04 LTS Server
# srv2 -> 192.168.1.215 Debian 12/11 Ubuntu 22.04/20.04 LTS Server
# srv3 -> 192.168.1.216 Debian 12/11 Ubuntu 22.04/20.04 LTS Server
#
# - All the nodes must have the same version of Mariadb. That is, they must
# have the same Linux distros.
#
#-- 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 (Run on all servers)

#-- 1.1. Install Mariadb and Galera Cluster on all servers
sudo apt update
sudo apt install mariadb-server galera-4 --yes
#
#-- 1.2. Secure Mariadb Installations
#   The following command makes some fine tunes regarding Mariadb security.
sudo mysql_secure_installation
#   You will be asked some questions.
#     "Enter current password for root (enter for none):"
#     There is no password yet, so press enter.
#   The next 2 questions "Switch to unix_socket authentication [Y/n]" and 
#     "Change the root password? [Y/n]" (for Ubuntu 20.04 there is only 1 
#     question "Set root password? [Y/n]" only) are about securing root 
#     account. In Ubuntu and Debian root account is already protected, so 
#     you can answer n.
#   For the next questions you can select default answers.
#

2. Mariadb Configurations (Run 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 27-30)
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.1.214,192.168.1.215,192.168.1.216"
# 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 !!!
sudo mariadb
# Run on mariadb shell
CREATE DATABASE Test;
exit;
#
#-- 4.2. Create a Table on the Database on the Second Node
# !!! Run on second server !!!
sudo mariadb
# 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 !!!
sudo mariadb
# 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 !!!
sudo mariadb
# 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 shutdown node.
# - If the cluster doesn't go online, refer to 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. If more than 1 node has
# the same highest value, just choose one.
# 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.
#
# - If this step doesn't work either. We have just one more thing to do. Go 
# to the next step.
# 
#-- 6.4. Last Chance
# !! On the safe node !!
# Disable mariadb and reboot
sudo systemctl disable mariadb
sudo reboot
#
# Edit cluster config at the safe node:
sudo nano /etc/mysql/mariadb.conf.d/99-cluster.cnf
# - Change the wsrep_cluster_address parameter to contain only the safe node 
# e.g. if the 3rd node is the safe node as below
wsrep_cluster_address = "gcomm://192.168.1.216"
# Enable mariadb 
sudo systemctl enable mariadb
# Start Galera Cluster
sudo galera_new_cluster
#
# !! On the other nodes !!
# Restart mariadb 
sudo systemctl restart mariadb
# - If they cannot restart, it is because it cannot be stopped Do the 
# disable, reboot, enable trick on them too
sudo systemctl disable mariadb
sudo reboot
# After reboot enable and start mariadb
sudo systemctl enable mariadb
sudo systemctl start mariadb
# - If they start, most probably your cluster is working again Otherwise, 
# you need a professional support.
# 
# !! On the safe node
# Revert the first node to the original configuration
sudo nano /etc/mysql/mariadb.conf.d/99-cluster.cnf
# Change the wsrep_cluster_address parameter to original
wsrep_cluster_address = "gcomm://192.168.1.214,192.168.1.215,192.168.1.216"
# Restart mariadb
sudo systemctl restart mariadb




Active Directory On Debian/Ubuntu

ADOnDebianUbuntu: Simple Active Directory Configuration on Debian and Ubuntu

Copyright (C) 2021 – 2023 Exforge exforge@x386.org

# - 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 Debian or 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
#
#-- 0.2. Configuration
# Domain Name: X11.XYZ
# Domain Netbios Name: X11
# First DC:    
#    srv1.x11.xyz 
#    192.168.1.224 
#    Debian 12/11 or Ubuntu 22.04/20.04 LTS Server
# Second DC:    
#    srv2.x11.xyz 
#    192.168.1.225 
#    Debian 12/11 or Ubuntu 22.04/20.04 LTS Server
# File Server: 
#    srvf.x11.xyz 
#    192.168.1.226 
#    Debian 12/11 or Ubuntu 22.04/20.04 LTS Server
# Windows workstations can connect to the domain
# 
# - On my tests I used distros uniformly. That is all servers were Debian 
# 11, Debian 12, Ubuntu 20.04, or Ubuntu 22.04. I believe the system would 
# work with nonuniform distros too, but I haven't tested it.
#
#-- 0.3. Phases
# 0.3.1. Add First DC (srv1.x11.xyz)
# 0.3.2. Add Additional DC (srv2.x11.xyz)
# 0.3.3. AD User Management
# 0.3.4. Add a Linux File Server to the Domain (srvf.x11.xyz)
# 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 must be in the format of name.example.com (lowercase this time) 
# and due to an imcompatability with Samba and Debian/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.1.224	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 installing all DCs as Linux 
# and use any Wind*ws workstation to manage AD. You can install RSAT
# Management Tools to a Wind*ws workstation 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.1.224
#
#-- 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 named exforge
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.1.225
# Org. DC Hostname: srv1.x11.xyz
# Org. DC IP:       192.168.1.224
#
#--2.1. Get domain administrator's kerberos ticket
# 2.1.1. Install kerberos and edit conf
sudo apt update
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
# This step is not necessary for Debian 12
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.1.224
#
# 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 expiration 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 22.04 Server)

#-- 4.0. Specs
# Domain Name:      X11
# Realm:            X11.XYZ	
# Hostname:         srvf.x11.xyz
# IP:               192.168.1.226
# Org. DC Hostname: srv1.x11.xyz
# Org. DC IP:       192.168.1.224
#
#-- 4.1. Install necessary packages
sudo apt update
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
#
# For Ubuntu servers:
#
# !!! Ubuntu DNS Configuration BEGIN !!!
sudo nano /etc/netplan/00-installer-config.yaml 
# If that file is not there, use the existing file instead
#   Change as below
      nameservers:
        addresses: [192.168.1.224,192.168.1.225]
#
# Restart network settings
sudo netplan apply
# !!! Ubuntu DNS Configuration END !!!
#
# For Debian servers
#
# !!! Debian DNS Configuration BEGIN !!!
sudo nano /etc/resolv.conf
# Fill as below
nameserver 192.168.1.224
nameserver 192.168.1.225
#
# !!! Debian DNS Configuration END !!!
#
#-- 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.1.224         
   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

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




ISPmail On Debian/Ubuntu

ISPMailOnDebianUbuntu: ISPmail Tutorial for Debian and Ubuntu

Copyright (C) 2021 – 2023 Exforge exforge@x386.org

# - 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/
#
# - Christoph Haas has not released the Debian 12 version yet. This document
# will be updated after his release.
#
# - 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 server. It is tested on freshly installed Debian 12/11
# and Ubuntu 22.04/20.04 systems. But of course you are free to use it as
# you wish.
#
# Distro Versions:      Debian 12/11  Ubuntu 22.04/20.04 LTS
#
# 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 
# like 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 -y install mariadb-server
# Postfix - Select Internet Site and mail.x11.xyz (your hostname)
apt -y install postfix postfix-mysql
# Apache and PHP
apt -y install apache2 php
# rspamd
apt -y install rspamd
# Certbot, for ssl certificates
apt -y install certbot
# Dovecot
apt -y install dovecot-mysql dovecot-pop3d dovecot-imapd \
   dovecot-managesieved dovecot-lmtpd
# Adminer, a replacement of phpmyadmin, for managing database
apt -y install adminer
# default set of certificates of common certificate authorities.
#   Don't mind if already installed.
apt -y 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 renews; 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 reload 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 linux shell)
mariadb
# Create the database
CREATE DATABASE mailserver;
# Create Database Users (Remember to change the passwords)
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 definitions 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 the 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
# Create a config file and fill it
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-65, 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 one 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 the 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 
# mixed 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 -y 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
# !!! For Debian 11, Debian 12 and Ubuntu 22.04 LTS !!!
# Change DocumentRoot line as below:
 DocumentRoot /var/lib/roundcube/public_html
#
# !!! For Ubuntu 20.04 LTS !!!
# 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
# 8.3.1. For Debian 11, Ubuntu 20.04 and Ubuntu 22.04
nano /etc/roundcube/config.inc.php
# !!! Remember to change to your domains !!!
# Around line 36 change as below
$config['default_host'] = 'tls://mail.x11.xyz';
# Around lines 48-50   change as below
$config['smtp_server'] = 'tls://mail.x11.xyz';
$config['smtp_port'] = 587;
# Around lines 76-79 change as below
$config['plugins'] = array(
     'managesieve',
     'password',
 );
#
# 8.3.2. For Debian 12
nano /etc/roundcube/config.inc.php
# !!! Remember to change to your domains !!!
# Around line 27 change as below
$config['imap_host'] = ['tls://mail.x11.xyz'];
#
# Around line 31 change as below
$config['smtp_host'] = 'tls://mail.x11.xyz:587';
#
# Around lines 56-59 change as below
$config['plugins'] = [
     'managesieve',
     'password',
];
#
#-- 8.4. Configure Password Plugin
# This plugin will let the user change their 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 address. 

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-19 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 line below 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-86 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-94, 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
#
# Restart Dovecot
systemctl restart dovecot
# 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, ownerships
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.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 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.xyz 
# Save the outputs of all the commands somewhere.
# Create keypair (-s option is for the selector)
rspamadm dkim_keygen -d x11.xyz -s 20230830 -k /var/lib/rspamd/dkim/x11.xyz.20230830.key
rspamadm dkim_keygen -d u11.xyz -s 20230830 -k /var/lib/rspamd/dkim/u11.xyz.20230830.key
rspamadm dkim_keygen -d v11.xyz -s 20230830 -k /var/lib/rspamd/dkim/v11.xyz.20230830.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 20230830._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 20230830
u11.xyz 20230830
v11.xyz 20230830
# 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 -y install git
# Download ISPispmailadmin
git clone  -b master 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 20: 'db_user' --> 'ispmailadmin'
# Line 21: 'db_pass' --> 'Password56'  (Your password)
# Line 29: uncomment line, that is remove // and the space (Enable single admin)
# Line 33: "admin_user' --> 'yourchoiceofadminusername' (Choose a name)
# Line 34: "admin_user' --> 'Password78' (Choose a good password)
# Line 40: uncomment line, that is remove // and the space (Enable BCRYPT Hashes)
# Line 48: uncomment line, that is remove // and the space (Enable Quotas)
#
#-- 14.4. Config Webserver to Include ISPmailserver
nano /etc/apache2/sites-available/mail.x11.xyz-https.conf
# Just after the Alias /adminer 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 your email password as password.
# 
#-- 15.2. Through Mail Client
# - Some people (myself 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://ispmailinstall.x386.org/
https://github.com/enatsek/ISPMailInstall
# Now let's see how to install it.
#
#-- 16.1. Install git
apt update
apt -y 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




KVM on Debian/Ubuntu 2 (KVM Networking)

KVMOnDebianUbuntu: KVM Tutorial On Debian and Ubuntu Server (KVM Networking)

Copyright (C) 2021 – 2023 Exforge exforge@x386.org

# - 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 Debian and Ubuntu Server. 
# Please refer to 1st KVM tutorial before reading this one.
# This tutorial specializes on KVM Networking.
#
#-- 0.1. Infrastructure
# - Server (Host): Debian (12/11) or Ubuntu (22.04/20.04) Server
#   IP: 192.168.1.161 
#   Name: elma
#   NIC1: enp3s0f0
#   NIC2: enx00e04c534458
# - Network1: 192.168.1.0/24 which is supplied by my internet modem (1st 
# interface)
# - Network2: 10.1.1.0/24 with an external switch (2nd interface)
# - Workstation: Debian 12 or Ubuntu 22.04 LTS Desktop
#
#-- 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.
# - Before creating a bridge on the KVM, we have to create it on the server. 
# As Debian and Ubuntu have different ways of network configuration, we will
# do it for both of them.
#
# !!! Creating the Bridge on Ubuntu BEGIN !!!
# - Edit netplan file to define the bridge. (If your netplan file is 
# named as something else, change it below to that.
# 
sudo nano /etc/netplan/01-netcfg.yaml
# - Remove its content , fill it as below, beware of changing enp3s0f0 and
# enx00e04c534458 to your interfaces' names.
# - Also remember to change IP addresses as in your networks too.
network:
  ethernets:
    enp3s0f0:
      dhcp4: false
      dhcp6: false
    enx00e04c534458:
      dhcp4: false
      dhcp6: false
  bridges:
    br0:
      interfaces: [ enp3s0f0 ]
      addresses: [192.168.1.161/24]
      routes:
      - to: default
        via: 192.168.1.1
      mtu: 1500
      nameservers:
        addresses: 
        - 8.8.8.8
        - 8.8.4.4
      parameters:
        stp: true
        forward-delay: 4
      dhcp4: false
      dhcp6: false
    br1:
      interfaces: [ enx00e04c534458 ]
      addresses: [10.1.1.1/24]
      routes:
      - to: 10.1.1.0/24
        via: 10.1.1.1
      mtu: 1500
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
      dhcp4: false
      dhcp6: false
  version: 2
#
# Activate the new configuration
sudo netplan apply
# !!! Creating the Bridge on Ubuntu END !!!
#
# !!! Creating the Bridge on Debian BEGIN !!!
# Edit your network config file
sudo nano /etc/network/interfaces
# - Remove its content , fill it as below, beware of changing enp3s0f0 and
# enx00e04c534458 to your interfaces' names.
# - Also remember to change IP addresses as in your networks too.
auto lo
iface lo inet loopback
# The primary network interface
auto enp3s0f0
#make sure we don't get addresses on our raw device
iface enp3s0f0 inet manual
#set up bridge and give it a static ip
auto br0
iface br0 inet static
        address 192.168.1.161
        netmask 255.255.255.0
        network 192.168.1.0
        broadcast 192.168.1.255
        gateway 192.168.1.1
        bridge_ports enp3s0f0
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 0
        dns-nameservers 8.8.8.8
auto br1
iface br1 inet static
        address 10.1.1.1
        netmask 255.255.255.0
        network 10.1.1.0
        broadcast 10.1.1.255
        bridge_ports enx00e04c534458
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 0
# Apply the changes. If you connect through ssh, you connection may break.
#   In this case, close the terminal and reconnect.
sudo systemctl restart networking.service
# !!! Creating the Bridge on Debian END !!!
#
# - 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 an 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 values 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 
# host machine's network configuration, 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 to (actually the 
# same as) 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. Only the devices in 
# the isolated network can reach to each other.
# - Although it is very useful for testing purposes, there might be some 
# situations that isolated network could be very useful in the production. 
# 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.1.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/ubuntu-22.04.2-live-server-amd64.iso  \
    --network bridge=br0 \
    --network bridge=brisolated \
    --graphics vnc,port=5901,listen=0.0.0.0 \
    --os-variant ubuntu22.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/ubuntu-22.04.2-live-server-amd64.iso  \
    --network bridge=brisolated \
    --graphics vnc,port=5902,listen=0.0.0.0 \
    --os-variant ubuntu22.04 \
    --noautoconsole
# - On Debian 11, --os-variant ubuntu22.04 gives an error. In that case, 
# change it as --os-variant ubuntu20.04.
# 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://192.168.20.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.1.0/24 (192.168.1.161-NIC 1)
# - Our host has a standart network on 192.168.1.0/24 (192.168.1.162-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. 
# - Again, Debian and Ubuntu have different steps:
#
# !!! Ubuntu Network Configuration BEGIN !!!
#
# 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.1.162/24]
      routes:
      - to: 192.168.1.0/24
        via: 192.168.1.1
      nameservers:
        addresses: 
        - 8.8.8.8
        - 8.8.4.4
  bridges:
    br0:
      interfaces: [ enp3s0f0 ]
      addresses: [192.168.1.161/24]
      routes:
      - to: default
        via: 192.168.1.1
      mtu: 1500
      nameservers:
        addresses: 
        - 8.8.8.8
        - 8.8.4.4
      dhcp4: false
      dhcp6: false
  version: 2
#
# Apply the configuration, (You'd better restart the host)
sudo netplan apply
#
# !!! Ubuntu Network Configuration END !!!
#
# !!! Debian Network Configuration BEGIN !!!
#
# Edit your network config file
sudo nano /etc/network/interfaces
auto lo
iface lo inet loopback
auto enp3s0f0
iface enp3s0f0 inet manual
auto br0
iface br0 inet static
        address 192.168.1.161
        netmask 255.255.255.0
        network 192.168.1.0
        broadcast 192.168.1.255
        gateway 192.168.1.1
        bridge_ports enp3s0f0
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 0
        dns-nameservers 8.8.8.8
auto enx00e04c534458
iface enx00e04c534458 inet static
        address 192.168.1.162
        netmask 255.255.255.0
        network 192.168.1.0
        gateway 192.168.1.1
# Apply the changes. If you connect through ssh, your connection may break.
#   In this case, close the terminal and reconnect.
sudo systemctl restart networking.service
#
# !!! Debian Network Configuration END !!!
#
#-- 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.202
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/ubuntu-22.04.2-live-server-amd64.iso  \
    --network bridge=br0 \
    --graphics vnc,port=5901,listen=0.0.0.0 \
    --os-variant ubuntu22.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.
# 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
#
# 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/ubuntu-22.04.2-live-server-amd64.iso  \
    --network bridge=brnat \
    --graphics vnc,port=5902,listen=0.0.0.0 \
    --os-variant ubuntu22.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: nat (bridge brnat)
# Network to be removed: host-bridge (br0)
#
# - We will create a VM with br0, then we will add it to the nat network, 
# and then we will remove the br0 network.
#
#-- 6.2. Create a VM with Bridged Network
sudo virt-install --name vmtest \
    --connect qemu:///system  --virt-type kvm \
    --memory 1024 --vcpus 1 \
    --disk /srv/kvm/vm.qcow2,format=qcow2,size=10 \
    --cdrom /srv/isos/ubuntu-22.04.2-live-server-amd64.iso  \
    --network bridge=br0 \
    --graphics vnc,port=5902,listen=0.0.0.0 \
    --os-variant ubuntu22.04 \
    --noautoconsole
#
#-- 6.3. Add an Interface to the VM at the Isolated Network
virsh attach-interface vmtest bridge brnat --target ens1 --config
# - Add an interface to the VM named vm, network type is bridge and bridge 
# name is brnat, interface name on the VM (--target) will be ens1 and it 
# will be active after shutdown and start again.
# - Alternatively, you may add the network when the VM is off.
# - Restart the VM, either by logging into it, or using the following 
# commands at the host :
virsh destroy vmtest
virsh start vmtest
# - "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.
# !!! Run on the VM !!!
# - Our VM is Ubuntu, if it were Debian, you should have configured the 
# network through /etc/network/interfaces 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.122.0/24 (nat) 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:83:3c:a0 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:83:3c:a0
#
# Now we return back to our host
virsh detach-interface vmtest bridge --mac 52:54:00:83:3c:a0 --config
# When you shutdown and start your VM, the interface will be gone.




KVM On Debian/Ubuntu 1 (Beginner)

KVMOnDebianUbuntu1: KVM Tutorial On Debian and Ubuntu Server (Beginner)

Copyright (C) 2021 – 2023 Exforge exforge@x386.org

# - 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 Debian and Ubuntu 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 (Host): Debian (12/11) or Ubuntu (22.04/20.04) Server
#   IP: 192.168.1.161 
#   Name: elma
#   NIC: enp3s0f0
# - Workstation: Debian 12 or Ubuntu 22.04 LTS Desktop
# - Network: 192.168.1.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/tools/qemu-img.html
https://www.libvirt.org/manpages/virsh.html
https://docs.fedoraproject.org/en-US/Fedora/18/html/Virtualization_Administration_Guide/index.html (not working now)
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/reference/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 install libvirt-clients libvirt-daemon-system qemu-kvm \
     virtinst virt-manager virt-viewer bridge-utils --yes
# Add your user to libvirt group:
# (Change exforge to your user name)
sudo usermod -aG libvirt exforge
#
#-- 1.2. Bridge Configuration
# - Bridge Configuration differs slighty for Debian and Ubuntu. So it is 
# better to handle them in different sections.
#
# 1.2.1. Ubuntu 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 network.
# 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 network is named as 
# virbr0.
# Delete and undefine KVM networks
virsh net-destroy default
virsh net-undefine default
#
# If in any case an error happens, you can try the following commands:
sudo ip link delete virbr0 type bridge
# Now if you run "ip link" again, you will see that virbr0 is 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.
#
# 1.2.1 Ubuntu Bridge Configuration
# - Backup your network configuration file
# - If that file does not exist, there must be another file there with yaml 
# extension. Proceed with 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 should add an IP address and default gateway from your local 
# network. 
# Mine are 192.168.1.161 and 192.168.1.1
network:
  ethernets:
    enp3s0f0:
      dhcp4: false
      dhcp6: false
  bridges:
    br0:
      interfaces: [enp3s0f0]
      addresses:
      - 192.168.1.161/24
      routes:
      - to: default
        via: 192.168.1.1
      mtu: 1500
      nameservers:
        addresses:
        - 8.8.8.8
        - 8.8.4.4
      parameters:
        stp: true
        forward-delay: 4
      dhcp4: false
      dhcp6: false
  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
#
# 1.2.2. Debian 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 want the
# VMs join to our network by getting IP address from our network.
# - 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 it is time to create a Bridge configuration for the KVM
#
# - Backup your network configuration file
sudo cp /etc/network/interfaces{,.backup}
#
# Edit your network config file
sudo nano /etc/network/interfaces
# - 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 should add an IP address and default gateway from your local 
# network. 
# Mine are 192.168.1.161 and 192.168.1.1
auto lo
iface lo inet loopback
# The primary network interface
auto enp3s0f0
#make sure we don't get addresses on our raw device
iface enp3s0f0 inet manual
#set up bridge and give it a static ip
auto br0
iface br0 inet static
        address 192.168.1.161
        netmask 255.255.255.0
        network 192.168.1.0
        broadcast 192.168.1.255
        gateway 192.168.1.1
        bridge_ports enp3s0f0
        bridge_stp off
        bridge_fd 0
        bridge_maxwait 0
        dns-nameservers 8.8.8.8
# Apply the changes. If you connect through ssh, your connection may break.
#   In this case, close the terminal and reconnect.
sudo systemctl restart networking.service
#
# 1.3. Add Our Bridge to KVM
# - Add our Bridge to KVM. We have to create an XML file for the bridge 
# definition:
nano host-bridge.xml
<network>
  <name>host-bridge</name>
  <forward mode="bridge"/>
  <bridge name="br0"/>
</network>
#
# Define the bridge, start it and make it autostart.
virsh net-define host-bridge.xml
virsh net-start host-bridge
virsh net-autostart host-bridge
#
#-- 1.4. 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 22.04 LTS with 1 GB RAM and 10 GB HDD
# - I already copied Ubuntu server iso ubuntu-22.04.2-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 Ubuntu 22.04 server 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-22.04.2-live-server-amd64.iso  \
    --network bridge=br0 \
    --graphics vnc,port=5901,listen=0.0.0.0 \
    --os-variant ubuntu22.04 \
    --noautoconsole
#
# - Debian 11 does not recognize ubuntu22.04 os variant, so you can change 
# it as --os-variant ubuntu20.04
#-- 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.
sudo apt install libosinfo-bin --yes
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 Debian or Ubuntu workstation and connect to 
# the VM.
# !!!-------- Run on your workstation BEGIN -------------!!!#
sudo apt update
sudo apt install virt-viewer --yes
virt-viewer --connect qemu+ssh://exforge@elma/system testkvm
# !!!-------- Run on your workstation END ---------------!!!#
#
# - Remember to replace exforge with your user name on the server and elma
# 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 --yes
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 it 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 --yes
#
#-- 4.1. Acquiring Cloud Images
# - A search for "ubuntu cloud image" in duckduck2 gives the following 
# address:
https://cloud-images.ubuntu.com/
# Following jammy and current, download kvm image jammy-server-cloudimg-amd64.img 
# and put it to server's /srv/isos folder.
#
# - Similarly a search for "debian cloud images" in duckduck2 gives the
# following address:
https://cloud.debian.org/images/cloud/
# Following bookworm and latest, download debian-12-generic-amd64.qcow2 
# and put it so the server's /srv/isos folder.
#
#-- 4.2. Creating a New Image From the Original Image
# - We will create new images from the images we downloaded. Image sizes 
# will be increased to 20 GiB and the Ubuntu image will be converted to
# qcow2, the preferred format for KVM.
# Ubuntu image:
sudo qemu-img create -b /srv/isos/jammy-server-cloudimg-amd64.img -F qcow2 \
    -f qcow2 /srv/kvm/ubuntusrv-cloudimg.qcow2 20G
# Debian image:
sudo qemu-img create -b /srv/isos/debian-12-generic-amd64.qcow2 -F qcow2 \
    -f qcow2 /srv/kvm/debiansrv-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/reference/examples.html
#
# - Our cloud-init file will configure the following:
#   Set hostname and Fully Qualified Domain Name, 
#   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 --yes
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 ubuntu server's cloud-init file
sudo nano /srv/init/ubuntu-cloud-init.cfg
#cloud-config
hostname: ubuntu22
fqdn: ubuntu22.x386.org
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$u5D5VbBZD7NcG/8Y$sc8WH5R9YU/xx1QqjQnMzNbQJOptj33DQGJqyHHju5EzJkvGF913gPVhjw.CsL8QLX5G79C0312tAuGIhWEhf1
packages: qemu-guest-agent
#
# Create our debian server's cloud-init file
sudo nano /srv/init/debian-cloud-init.cfg
#cloud-config
hostname: debian12
fqdn: debian12.x386.org
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$u5D5VbBZD7NcG/8Y$sc8WH5R9YU/xx1QqjQnMzNbQJOptj33DQGJqyHHju5EzJkvGF913gPVhjw.CsL8QLX5G79C0312tAuGIhWEhf1
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.1.243/24 ]
     gateway4: 192.168.1.1
     nameservers:
       addresses: [ 192.168.1.1,8.8.8.8 ]
#
sudo nano /srv/init/debian-network-init.cfg
#cloud-config
version: 2
ethernets:
  enp1s0:
     dhcp4: false
     addresses: [ 192.168.1.244/24 ]
     gateway4: 192.168.1.1
     nameservers:
       addresses: [ 192.168.1.1,8.8.8.8 ]
#
#-- 4.5. Creating Cloud Seed Images
# - Now we will create image files cloud-init and network-init inside.
# For Ubuntu server:
sudo cloud-localds --network-config /srv/init/ubuntu-network-init.cfg /srv/kvm/ubuntu22-seed.qcow2 \
   /srv/init/ubuntu-cloud-init.cfg
# For Debian server:
sudo cloud-localds --network-config /srv/init/debian-network-init.cfg /srv/kvm/debian12-seed.qcow2 \
   /srv/init/debian-cloud-init.cfg
#
#-- 4.6. Start Our Images as a New VMs
# Ubuntu Server
virt-install --name ubuntu22 \
  --connect qemu:///system \
  --virt-type kvm --memory 2048 --vcpus 2 \
  --boot hd,menu=on \
  --disk path=/srv/kvm/ubuntu22-seed.qcow2,device=cdrom \
  --disk path=/srv/kvm/ubuntusrv-cloudimg.qcow2,device=disk \
  --graphics vnc,port=5902,listen=0.0.0.0 \
  --os-variant ubuntu22.04 \
  --network bridge=br0 \
  --noautoconsole \
  --install no_install=yes
# Debian Server
virt-install --name debian12 \
  --connect qemu:///system \
  --virt-type kvm --memory 2048 --vcpus 2 \
  --boot hd,menu=on \
  --disk path=/srv/kvm/debian12-seed.qcow2,device=cdrom \
  --disk path=/srv/kvm/debiansrv-cloudimg.qcow2,device=disk \
  --graphics vnc,port=5903,listen=0.0.0.0 \
  --os-variant debian12 \
  --network bridge=br0 \
  --noautoconsole \
  --install no_install=yes
#
# - Ironically, on Debian 12, --os-variant debian12 gives an error. In that 
# case, try the following command:
virt-install --name debian12 \
  --connect qemu:///system \
  --virt-type kvm --memory 2048 --vcpus 2 \
  --boot hd,menu=on \
  --disk path=/srv/kvm/debian12-seed.qcow2,device=cdrom \
  --disk path=/srv/kvm/debiansrv-cloudimg.qcow2,device=disk \
  --graphics vnc,port=5903,listen=0.0.0.0 \
  --os-variant debian11 \
  --network bridge=br0 \
  --noautoconsole \
  --install no_install=yes
#
# - 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@elma/system ubuntu22
virt-viewer --connect qemu+ssh://exforge@elma/system debian12
#
#-- 4.7. Clean-up Tasks for Cloud-init
# On your VMs run:
sudo touch /etc/cloud/cloud-init.disabled
# If the file /etc/cloud/cloud-init.disabled exists, cloud-init does not run 
# again.
#
#-- 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.0. Environment Variable Set
# - For Debian, in order to run virsh command without sudo, we need to
# set an environment variable.
export LIBVIRT_DEFAULT_URI='qemu:///system'
# - Instead of setting everytime, we can add it to the .bashrc file:
nano ~/.bashrc
# Add to the end
export LIBVIRT_DEFAULT_URI='qemu:///system'
#
#-- 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 ubuntu22
#
#-- 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 dominfo 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 2536M
virsh setmem NAME 2536M --live
virsh setmem NAME 2536M --current
# Change memory after the next shutdown and start
virsh setmem NAME 2536M --config
# Activate immediately and keep the changes after the next shutdown and 
# start
virsh setmem NAME 1536M --live --config
virsh setmem ubuntu22 2536M --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 can 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 ubuntu22 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 ubuntu22 3 
virsh setvcpus ubuntu22 3 --current
virsh setvcpus ubuntu22 3 --live
# To change current vcpu count for the config state
virsh setvcpus NAME NUMBER --config
virsh setvcpus ubuntu22 3 --config
# To do it both together
virsh setvcpus NAME NUMBER --config --live
virsh setvcpus ubuntu22 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 ubuntu22 --name ss1-ubuntu22 --description "Firsh Snapshot of Ubuntu22"
# - 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 ubuntu22 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/ubuntu22-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 ubuntu22 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 only have 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 ubuntu22 VM:
virsh attach-disk ubuntu22 /srv/kvm/ubuntu22-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 ubuntu22 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/tools/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 ubuntu22 VM's image info:
qemu-img info /srv/kvm/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/ubuntu22-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 
# downloaded:
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 22.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@elma:/tmp
# !!! On my workstation END !!!
#
# On my server
# Convert image to qcow2
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=5904,listen=0.0.0.0 \
  --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 ubuntu22 > /tmp/kvmbackup/ubuntu22.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/ubuntu22.xml
# For my ubuntu22 VM, output is listed below:
      <source file='/srv/kvm/ubuntu22-seed.qcow2'/>
      <source file='/srv/kvm/ubuntusrv-cloudimg.qcow2'/>
# - That means I need to prepare 2 files: /srv/kvm/ubuntu22-seed.qcow2 and
# /srv/kvm/ubuntusrv-cloudimg.qcow2 .
# Lets copy them to our backup locations.
cp /srv/kvm/ubuntu22-seed.qcow2 /srv/kvm/ubuntusrv-cloudimg.qcow2 \
   /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/ubuntu22.tar -C /tmp/kvmbackup .
# Now we have /tmp/ubuntu22.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.
#
# ubuntu22.tar is copied to the server's /tmp directory as /tmp/ubuntu22.tar
# Create a place for our import files
mkdir /tmp/import
# Extract tar file there
tar -xf /tmp/ubuntu22.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/ubuntu22-seed.qcow2 /tmp/import/ubuntusrv-cloudimg.qcow2 /srv/kvm
# 
# - It is time to define our server. Remember the xml file? We will use it 
# to define our ubuntu22 server.
virsh define /tmp/import/ubuntu22.xml
# Now we can start it
virsh start ubuntu22

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 --yes 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 ubuntu22 -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




Ansible On Debian/Ubuntu

AnsibleOnDebianUbuntu – Ansible Tutorial for Debian and Ubuntu Linux

Copyright (C) 2020 – 2023 Exforge exforge@x386.org

# - 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 Debian and Ubuntu servers, but I 
# believe you can apply most of the examples to other distributions.
#
# - I am not an expert of Ansible. Actually I prepared this tutorial while I 
# was learning it. 
#
#-- 0.1. Workstation: 
# wrk -> Debian 12 or Ubuntu 22.04 LTS Desktop
#
# - Hint: You can use server editions, because Ansible does not need any
# graphical UI.
#
#-- 0.2. Servers:
# - Local Virtual Servers:
# debian12    -> Debian 12 Server
# debian11    -> Debian 11 Server
# ubuntu22    -> Ubuntu 22.04 LTS Server
# ubuntu20    -> Ubuntu 20.04 LTS 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
# !!!Run on workstation!!!
sudo apt update
sudo apt install ansible --yes
#
#-- 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 user's SSH key to the servers
ssh-copy-id -i ~/.ssh/id_rsa.pub debian12
ssh-copy-id -i ~/.ssh/id_rsa.pub debian11
ssh-copy-id -i ~/.ssh/id_rsa.pub ubuntu22
ssh-copy-id -i ~/.ssh/id_rsa.pub ubuntu20
# 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 in 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 /home/ansible/ansible/playbooks 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
[debian]
debian12
debian11
[ubuntu]
ubuntu22
ubuntu20
# 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. Command based inventory
# - 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. Using more than 1 inventory
# - 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
ubuntu22
ubuntu20
nano /home/ansible/ansible/inventory/inventory2
debian12
debian10
ansible all -i /home/ansible/ansible/inventory -m ping
#
#-- 3.6. Dynamic Inventory
# - If you want to use a dynamic host file, you can use a program which 
# outputs the inventory in Json format. 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('{"ubuntu": {"hosts" : ["ubuntu22", "ubuntu20"]}}')
chmod +x /home/ansible/ansible/inventory.py
ansible all -i /home/ansible/ansible/inventory.py -m ping
#
# - Needless to say; you can combine dynamic and static inventories, by 
# combining methods in 3.5. and 3.6.
#
# 3.7. 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 ubuntu servers I would modify my 
# inventory file as follows:
[debian]
debian12
debian11
[ubuntu]
ubuntu22
ubuntu20
[ubuntuanddebian:children]
ubuntu
debian
#
# 3.8. 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. 
[debian]
debian12
debian11
[ubuntu]
ubuntu22
ubuntu20
[ubuntu: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 better 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 -m ping
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 ubuntu22 -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=/tmp/testfile dest=/tmp/testfile"
# 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=/tmp/testfile state=absent"
# Copy a file from server
ansible ubuntu22 -m fetch -a "src=/var/log/dmesg dest=/home/ansible/backup flat=yes" --become
#
#-- 4.4. Reboot Servers
# Reboot all servers  (Does not reboot because of the permissions)
ansible all -a "/sbin/reboot"
# Reboot all servers with sudo privilege
ansible all -a "/sbin/reboot" --become
# Reboot all servers in 10 parallel forks (default is 5)
ansible all -a "/sbin/reboot" -f 10 --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 debian12 -m ansible.builtin.user -a "name=foo" --become
# Remove a user
ansible debian12 -m ansible.builtin.user -a "name=foo state=absent" --become
#
#-- 4.6. Package Management (apt)
# Update cache (apt update)
ansible debian12 -m apt -a "update_cache=yes" --become
# update cache and upgrade all modules (apt update && apt upgrade)
ansible debian12 -m apt -a "upgrade=dist update_cache=yes" --become
# Install apache (don't do anything if it is already installed)
ansible debian12 -m apt -a "name=apache2 state=present" --become 
# Install apache, if it is already installed, update it
ansible debian12 -m apt -a "name=apache2 state=latest" --become
# Remove apache
ansible debian12 -m apt -a "name=apache2 state=absent" --become
# Remove apache and remove all configuration about it
ansible debian12 -m apt -a "name=apache2 state=absent purge=yes" --become
# Remove apache, remove all configuration about it, and also remove all unused packages
ansible debian12 -m apt -a "name=apache2 state=absent purge=yes autoremove=yes" --become
#
#-- 4.7. Service Management
# Start and Enable Apache service
ansible debian12 -m service -a "name=apache2 state=started enabled=yes" --become
# Stop Apache service
ansible debian12 -m service -a "name=apache2 state=stopped" --become
# Restart Apache service
ansible debian12 -m service -a "name=apache2 state=restarted" --become

5. A Simple Playbook to Install Apache

# - Playbooks are files in YAML format. They contain 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: debian12
  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/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: debian12
# 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: debian12
  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
#
# 6.3. Run the playbook
ansible-playbook lamp.yml

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: debian12
  tasks:
  - name: Start apache if not started
    service:
      name: apache2
      state: started

7.0. apk Module: Manages Alpine Linux 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
    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 wrapped 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: debian12, ip: 192.168.0.231 }
      - { hostname: srv2, ip: 192.168.0.232 }
      - { hostname: srv3, ip: 192.168.0.233 }

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

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/main.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/main.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/main.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/main.cf
      owner: root
      group: root
      mode: '0644'
      backup: yes
#
  - name: Copy a file on the server to another location
    copy:
      src: /etc/apache2/apache2.conf
      dest: /etc/apache2/apache2.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 debian12
    when: inventory_hostname == "debian12"

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

# Examples:
  - name: Fetch server file, preserve directory information
    fetch:
      src: /etc/apache2/apache2.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/apache2.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/apache2.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: /tmp/test.conf
      owner: ansible
      group: ansible
      mode: '0644'
#
  - name: Create a symbolic link of a file, change ownership of the original file
    file:
      src: /tmp/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: /tmp/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: /tmp/test.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: /tmp/test.conf
      state: absent
#
  - name: Remove a directory recursively
    file:
      path: /tmp/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.tar.gz
      checksum: md5:4bdc05b00725cc0fb72991d3290e4b8d

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 debian12.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 debian12.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 before 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
#
  - name: Run a script
    script:
      cmd: /home/ansible/ansible/backup.sh
#
  - name: Run a script only if a file does not exist on the server
    script: /home/ansible/ansible/backup.sh
    args:
      creates: /tmp/backup.txt
#
  - name: Run a script only if a file exists on the server
    script: /home/ansible/ansible/backup.sh
    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.tar.gz
      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
# group must exist
    user:
      name: exforge
      comment: main user
      group: exforge
#
  - name: Add user exforge with a primary group with the same name,
#   with a specific uid. group must exist.
    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 convention 
# 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 roles with 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.
# - Note: The new version of Ansible, does not create templates and files 
# directories, but they still exist in the documentation. I am not sure if 
# it is a bug or something else. In any way, I create this directories when 
# I need them.
#
#-- 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: debian12
  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 debian12 -m setup
# The output will be long, something like:
debian12 | 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'] }}
#
# 9.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 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 Alma (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: all
  become: true
  roles:
  - exforge.apacheDRA
# Run the playbook
cd /home/ansible/ansible/playbooks
ansible-playbook apacheDRA.yml
#
# 10.4. Exercise
# Redesign apacheDRA 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
mkdir /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates
# 
#-- 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
    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: debian12
  become: true
  vars:
    server_name: debian12.x386.xyz
    server_alias: debian12
    html_title: debian12.x386.xyz Homepage
    html_header: This is the homepage of debian12.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: debian12
  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 where you are expected to put your handlers. 
#
#-- 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: debian12
  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
    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 is 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: debian12
  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 are no errors 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 :)