Set Up a New Server
Table of Contents
- Sample Home Network:
- Check your Network
- Update Package Repository
- New User and Group to Use
- Firewall
- Set your host and domain names
- Rsyslog - Send Syslog Entries to Remote Syslog Host
- Time Control
- E-Mail - Client for Sending Local Mail
- Monit - Monitor System and Restart Processes
- Munin - Resource History Monitor
- Fail2ban - Automatic Firewall Blocking
- Backup - Save Your Files Daily
- Rsync - Remote File Synchronization
- Logwatch - Daily Alert of Logging Activity
- Logcheck - mails anomalies in the system logfiles to the admin
- Sysstat - Gather System Usage Statistics
- S.M.A.R.T. Disk Monitoring
- Login Notices to Users - motd/issue/issue.net
- Login notification
- Continue
Use this as a guide to enable proper monitoring and maintenace of any new server on the network.
Sample Home Network:
graph TD; Modem<-->Router; Router<-->MainSwitch; Router<-->WiFi; Router-->GuestWiFi; GuestWiFi-->Camera; MainSwitch<-->ServerSwitch; MainSwitch<-->Desktop; MainSwitch<-->Laptop; ServerSwitch-->MailServer; ServerSwitch-->WebServer; ServerSwitch-->NetworkAccessStorage;
Sometimes the Modem, Router and Main Switch are one unit, or there is no modem.
-----> Single Arrow is a limited access network (VLAN)
<----> Double Arrows is an Open Network
The key point here is that the servers are isolated on a seperate switch for performance and security reasons, using a VLAN (Virtual Local Area Network) local to the Server Switch. Server VLAN network packets between each other never leave the Server Switch. Each server has another IP address not on the VLAN for public access.
A guest WiFi service does not have access to the Main Switch because it is on it's own VLAN, so local resources are protected from that experimental 12 year old guest.
If your camera accesses a cloud service (most do) link it to the Guest WiFi, for security purposes. Any other untrusted device should also be on the Guest Wifi, like Robot Vacuum Cleaners, Car Chargers, Car, TV streaming box, VOIP (Telephone VoiceOverIP), Garage Door Opener, Door Locks, etc...
Check your Network
- See Network Interfaces
$ ifconfig
eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.0.8 netmask 255.255.255.0 broadcast 192.168.0.255
inet6 fe80::0000:1111:3333:2222 prefixlen 64 scopeid 0x20<link>
ether 2a:53:9b:00:f9:21 txqueuelen 1000 (Ethernet)
RX packets 342047883 bytes 378663045018 (378.6 GB)
RX errors 0 dropped 54131 overruns 0 frame 0
TX packets 221663343 bytes 165067861773 (165.0 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 16 memory 0xc0b00000-c0b20000
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 32619960 bytes 99080107335 (99.0 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 32619960 bytes 99080107335 (99.0 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 36:27:10:52:32:96 brd ff:ff:ff:ff:ff:ff
altname enp0s3
inet 192.168.0.5/24 brd 10.0.2.255 scope global dynamic noprefixroute ens3
valid_lft 66645sec preferred_lft 66645sec
inet6 fec0::000:ff:1111:1111/64 scope site dynamic noprefixroute
valid_lft 86390sec preferred_lft 14390sec
inet6 fe80::000:ff:1111:1111/64 scope link noprefixroute
valid_lft forever preferred_lft forever
- System Log is
/var/log/syslog
/var/log/messages
- Bring Network Up:
$ sudo cat /etc/netplan/01-network-manager-all.yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0:
dhcp4: true
$ sudo netplan try
$ sudo netplan -d apply
$ sudo systemctl restart system-networkd
Reference: https://netplan.io/
$ sudo nmcli connection up ens3
vi /etc/sysconfig/network-scripts/ifcfg-ens3
~
ONBOOT=yes
~
:wq
Update Package Repository
Debian
Always refresh the package repository before getting started.
$ sudo apt-get update
You may need to add extra repositories, just check the sources.
The four main repositories are:
- Main - Canonical-supported free and open-source software.
- Universe - Community-maintained free and open-source software.
- Restricted - Proprietary drivers for devices.
- Multiverse - Software restricted by copyright or legal issues.
- Backports[1] - Software from older releases that should still work.
- Backports for old stuff you really really need.
$ sudo add-apt-repository universe
$ sudo grep '^deb ' /etc/apt/sources.list
...
deb http://us.archive.ubuntu.com/ubuntu/ jammy universe
...
$ sudo add-apt-repository --remove universe
RedHat
Always refresh the package repository before getting started.
$ sudo dnf update
- Now you can disable subscription-manager if you do not have a RedHat subscription.
Change: From -> enabled=1 To -> enabled=0
$ sudo vi /etc/yum/pluginconf.d/subscription-manager.conf
$ sudo yum clean all
0 files removed
- You may need to add extra repositories, just check the sources.
The EPEL repository provides additional high-quality packages for RHEL-based distributions. EPEL is a selection of packages from Fedora, but only packages that are not in RHEL or its layered products to avoid conflicts.
The folks at Fedora have very nicely put up an automatic build and repo system and they are calling it COPR (Cool Other Package Repositories).
Reference:
$ sudo dnf install epel-release 'dnf-command(copr)'
New User and Group to Use
Be sure to match the same user numbers across systems, because when you share files using NFS, the numbers need to match.
$ sudo adduser don --uid 1001
Adding user `don' ...
Adding new group `don' (1001) ...
Adding new user `don' (1001) with group `don' ...
Creating home directory `/home/don' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for don
Enter the new value, or press ENTER for the default
   Full Name []: Don
   Room Number []:
   Work Phone []:
   Home Phone []:
   Other []:
Is the information correct? [Y/n] y
Adding new user `don' to extra groups ...
Adding user `don' to group `dialout' ...
Adding user `don' to group `i2c' ...
Adding user `don' to group `spi' ...
Adding user `don' to group `cdrom' ...
Adding user `don' to group `floppy' ...
Adding user `don' to group `audio' ...
Adding user `don' to group `video' ...
Adding user `don' to group `plugdev' ...
Adding user `don' to group `users' ...
If this user is an administrator;
Debian
$ sudo usermod -aG sudo rootbk
Check your user for group '27(sudo)'.
$ id rootbk
uid=1002(rootbk) gid=1002(rootbk)
groups=1002(rootbk),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),44(video),46(plugdev),100(users),114(i2c),993(spi)
RedHat
$ sudo usermod -aG wheel rootbk
Check your user for group '10(wheel)'.
# id don
uid=1002(rootbk) gid=1002(rootbk) groups=1002(rootbk),10(wheel)
Also set the root password in case the system will not boot
$ sudo passwd root
[sudo] password for don:
New password:
Retype new password:
passwd: password updated successfully
Firewall
Every machine should have a firewall enabled, especially before connecting to the internet.
Now adays there is a choice between Uncomplicated Firewall (ufw) and firewalld. Choose wisely or be hacked.
Debian
$ sudo apt-get install ufw
$ sudo ufw allow 22/tcp
$ sudo ufw enable
$ sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 22/tcp (v6) ALLOW IN Anywhere (v6)
$ sudo ufw delete 2
$ sudo ufw logging low
$ sudo ufw logging on
NOTE: Logging values are: low|medium|high. Only network blocks are logged on low.
Reference: https://launchpad.net/ufw
RedHat
$ sudo dnf install firewalld
$ sudo firewall-cmd --add-service=ssh
success
$ sudo firewall-cmd --list-services
cockpit dhcpv6-client ssh
$ sudo firewall-cmd --remove-service=cockpit
success
$ sudo firewall-cmd --remove-service=dhcpv6-client
success
$ sudo firewall-cmd --list-services
ssh
$ sudo firewall-cmd --runtime-to-permanent
success
Reference: https://firewalld.org/
Block bad actors, whole Autonomous System [1] groups at a time
Using IP addresses from Logwatch. Logcheck, and Logwatcher, feed them into the firewall.sh script.
Run the git clone
and install the dependencies based on your operating system.
File: ~/firewall.sh
#!/bin/bash
##############################################################################
#
# File: firewall.sh
#
# Purpose: Block IP address or CIDR range using OS firewall
#
# Dependencies:
#
# git clone https://github.com/nitefood/asn
#
# * For more detail get an ~/.asn/iqs_token
# from https://github.com/nitefood/asn#ip-reputation-api-token
#
# * **Debian 10 / Ubuntu 20.04 (or newer):**
#
# ```
# apt -y install curl whois bind9-host mtr-tiny jq ipcalc grepcidr nmap ncat aha
# ```
# * Enable ufw and allow the ports you need
#
# # ufw allow 22
# # ufw enable
#
# * Delete rules not needed:
# # ufw status numbered
# # ufw delete <number>
#
# * **CentOS / RHEL / Rocky Linux 8:**
#
# # Install repos:
# $ sudo dnf repolist
# repo id repo name
# appstream CentOS Stream 9 - AppStream
# baseos CentOS Stream 9 - BaseOS
# epel Extra Packages for Enterprise Linux 9 - x86_64
# epel-next Extra Packages for Enterprise Linux 9 - Next - x86_64
# extras-common CentOS Stream 9 - Extras packages
#
# $ ls /etc/yum.repos.d
# centos-addons.repo centos.repo epel-next.repo epel-next-testing.repo epel.repo epel-testing.repo redhat.repo
#
# dnf install bind-utils jq whois curl nmap ipcalc grepcidr aha
#
# If you have a list of IP addresses to block (text file, each IP on a separate line),
# you can easily import that to your block list:
#
# # firewall-cmd --permanent --ipset=networkblock --add-entries-from-file=/path/to/blocklist.txt
# # firewall-cmd --reload
#
# To view ipsets:
# # firewall-cmd --permanent --get-ipsets
# networkblock
# # firewall-cmd --permanent --info-ipset=networkblock
# networkblock
# type: hash:net
# options: maxelem=1000000 family=inet hashsize=4096
# entries: 46.148.40.0/24
#
# # firewall-cmd --add-service=smtp
# success
# # firewall-cmd --add-service=smtps
# success
# # firewall-cmd --list-services
# cockpit dhcpv6-client smtp smtps ssh
# # firewall-cmd --remove-service=cockpit
# success
# # firewall-cmd --remove-service=dhcpv6-client
# success
# # firewall-cmd --list-services
# smtp smtps ssh
# # firewall-cmd --runtime-to-permanent
# success
#
# # firewall-cmd --list-all
# public (active)
# target: default
# icmp-block-inversion: no
# interfaces: ens3
# sources:
# services: smtp smtps ssh
# ports:
# protocols:
# forward: no
# masquerade: no
# forward-ports:
# source-ports:
# icmp-blocks:
# rich rules:
#
# https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-setting_and_controlling_ip_sets_using_firewalld
#
# * TO UNDO A MISTAKEN BLOCK:
# # firewall-cmd --permanent --ipset=networkblock --remove-entry=x.x.x.x/y
# # firewall-cmd --reload
#
# * To drop ipset
# # firewall-cmd --permanent --delete-ipset=networkblock
# # firewall-cmd --reload
#
# Author Date Description
# ---------- -------- --------------------------------------------------------
# D. Cohoon Jan-2023 Created
# D. Cohoon Feb-2023 Add RedHat firewalld
##############################################################################
DIR=/root
LOG=${DIR}/firewall.log
WHO=/tmp/whois.txt
CIDR=/tmp/whois.cidr
IP="${1}"
OS=$(/usr/bin/hostnamectl|/usr/bin/grep 'Operating System'|/usr/bin/cut -d: -f2|/usr/bin/awk '{print $1}')
#.............................................................................
function set_ipset() {
FOUND_IPSET=0
while read SET
do
if [ ! -z ${SET} ] && [ ${SET} == "networkblock" ]; then
FOUND_IPSET=1
fi
done <<< $(sudo /usr/bin/firewall-cmd --permanent --get-ipsets)
#
if [ $FOUND_IPSET -eq 0 ]; then
# Create networkblock ipset
sudo /usr/bin/firewall-cmd --permanent --new-ipset=networkblock --type=hash:net \
--option=maxelem=1000000 --option=family=inet --option=hashsize=4096
# Add new ipset to drop zone
sudo /usr/bin/firewall-cmd --permanent --zone=drop --add-source=ipset:networkblock
# reload
sudo /usr/bin/firewall-cmd --reload
fi
}
#
#.............................................................................
function run_asn() {
${DIR}/asn/asn -n ${IP} > ${WHO}
/usr/bin/cat ${WHO}
RANGE=$(/usr/bin/cat ${WHO} | /usr/bin/grep 'NET' | /usr/bin/grep '/' | /usr/bin/awk -Fm '{print $6}' | /usr/bin/cut -d" " -f1)
/usr/bin/echo "CDR: ${RANGE}"
/usr/bin/echo "${RANGE}" > ${CIDR}
}
#.............................................................................
#
if [ ${1} ]; then
run_asn
else
/usr/bin/echo "Usage: ${0} <IP Address>"
exit 1
fi
#.............................................................................
#
/usr/bin/grep -v deaggregate ${CIDR} > ${CIDR}.block
while read -r IP
do
/usr/bin/echo "$(/usr/bin/date) - OS: ${OS}" | /usr/bin/tee -a ${LOG}
/usr/bin/echo "Blocking: ${IP}" | /usr/bin/tee -a ${LOG}
case ${OS} in
AlmaLinux|CentOS)
/usr/bin/echo "Firewalld"
set_ipset
sudo /usr/bin/firewall-cmd --permanent --ipset=networkblock --add-entry=${IP}
sudo /usr/bin/firewall-cmd --reload
;;
Ubuntu|Debian)
/usr/bin/echo "ufw"
sudo /usr/sbin/ufw prepend deny from ${IP} to any 2>&1 |tee -a $LOG
;;
esac
done < ${CIDR}.block
Set your host and domain names
File: /etc/hosts
127.0.0.1 localhost
192.168.1.5 www.example.com example.com www
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
File: /etc/hostname
don.example.com
Rsyslog - Send Syslog Entries to Remote Syslog Host
It is a good idea to send log messages to another host in case the system crashes. You will be able to see that last gasping breath of the dying server. Also in the event of a compromised system hackers usually zero out the local syslog to cover their tracks. Now you still have any trace of the hackers on the central rsyslog host. It makes things simpler for detailed log analysis with combined logs on one system too.
Local System
Replicate log entries: Add the following to cause log entries to be in /var/log/syslog locally and be sent to a remote syslog host. If you do not have a Remote Syslog Host, skip this.
File: /etc/rsyslog.conf
Add these lines on local system.
~
# Remote logging - Aug 2020 Don
# Provides UDP forwarding
*.* @192.168.1.5:514 #this is the logging host
~
Alert: Create the following to send syslog alerts to email if the severity is high (3 or below).
File: /etc/rsyslog.d/alert.conf
Create the file if it does not exist and replace with these lines.
module(load="ommail")
template (name="mailBody" type="string" string="Alert for %hostname%:\n\nTimestamp: %timereported%\nSeverity: %syslogseverity-text%\nProgram: %programname%\nMessage: %msg%")
template (name="mailSubject" type="string" string="[%hostname%] Syslog alert for %programname%")
if $syslogseverity <= 3 and not ($msg contains 'brcmfmac') then {
action(type="ommail" server="192.168.1.3" port="25"
mailfrom="rsyslog@localhost"
mailto="don@example.com"
subject.template="mailSubject"
template="mailBody"
action.execonlyonceeveryinterval="3600")
}
Remote Syslog Host
Allow remote hosts to log here: Open firewall port 514/udp on remote syslog host.
$ sudo ufw allow 514/udp
File: /etc/rsyslog.conf
Add these lines to remote syslog host.
~
# provides UDP syslog reception
module(load="imudp")
input(type="imudp" port="514")
~
# Process remote logs into seperate directories, then stop. Do not duplicate into syslog
$template RemoteLogs,"/var/log/%HOSTNAME%/%PROGRAMNAME%.log"
*.* ?RemoteLogs
& stop
Restart rsyslog
$ sudo systemctl restart rsyslog
Time Control
All servers should be set up to synchronize their time over the network using Network Time Protocol (NTP). This is critical in validating security certificates. For offline systems, consider using a Real Time Clock (RTC) attached to something like BeagleBone.
timezone
Change to match your timezone.
File: /etc/timezone
$ cat /etc/timezone
America/New_York
Set timezone with timedatactl, and verify.
$ sudo timedatectl set-timezone America/New_York
$ timedatectl
Local time: Sun 2022-10-09 18:27:11 EDT
Universal time: Sun 2022-10-09 22:27:11 UTC
RTC time: n/a
Time zone: America/New_York (EDT, -0400)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
RedHat
RedHat uses chronyd service
File: /etc/chrony.conf
server pool.ntp.org iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
keyfile /etc/chrony.keys
leapsectz right/UTC
logdir /var/log/chrony
Restart to pick up new config
$ sudo systemctl restart chronyd
$ sudo systemctl status chronyd
● chronyd.service - NTP client/server
Loaded: loaded (/usr/lib/systemd/system/chronyd.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2023-02-17 15:47:28 EST; 20h ago
Docs: man:chronyd(8)
man:chrony.conf(5)
Process: 798 ExecStartPost=/usr/libexec/chrony-helper update-daemon (code=exited, status=0/SUCCESS)
Process: 789 ExecStart=/usr/sbin/chronyd $OPTIONS (code=exited, status=0/SUCCESS)
Main PID: 796 (chronyd)
Tasks: 1 (limit: 11366)
Memory: 2.3M
CGroup: /system.slice/chronyd.service
└─796 /usr/sbin/chronyd
Test
$ chronyc sources -v
.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current best, '+' = combined, '-' = not combined,
| / 'x' = may be in error, '~' = too variable, '?' = unusable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^? your-ip-name-d> 0 8 0 - +0ns[ +0ns] +/- 0ns
...
$ timedatectl
Local time: Wed 2023-07-12 09:03:57 EDT
Universal time: Wed 2023-07-12 13:03:57 UTC
RTC time: Wed 2023-07-12 13:03:57
Time zone: America/New_York (EDT, -0400)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
...
$ systemctl is-active chronyd.service
active
...
$ chronyc tracking
Reference ID : 404F64C5 (ntpool1.258.ntp.org)
Stratum : 3
Ref time (UTC) : Wed Jul 12 12:47:34 2023
System time : 0.000000002 seconds slow of NTP time
Last offset : +1.114915133 seconds
RMS offset : 1.114915133 seconds
Frequency : 32.362 ppm slow
Residual freq : +22.860 ppm
Skew : 3.901 ppm
Root delay : 0.046558209 seconds
Root dispersion : 0.050582517 seconds
Update interval : 0.0 seconds
Leap status : Normal
Debian
Debian uses systemd-timesyncd service.
$ sudo systemctl status systemd-timesyncd
● systemd-timesyncd.service - Network Time Synchronization
Loaded: loaded (/lib/systemd/system/systemd-timesyncd.service; enabled; vendor preset: enabled)
Drop-In: /lib/systemd/system/systemd-timesyncd.service.d
└─disable-with-time-daemon.conf
Active: active (running) since Sun 2022-07-24 12:06:36 EDT; 2 weeks 3 days ago
Docs: man:systemd-timesyncd.service(8)
Main PID: 559 (systemd-timesyn)
Status: "Synchronized to time server for the first time 192.155.94.72:123 (2.debian.pool.ntp.org)."
Tasks: 2 (limit: 951)
Memory: 1.0M
CGroup: /system.slice/systemd-timesyncd.service
└─559 /lib/systemd/systemd-timesyncd
Reference:
- Debian: https://www.debian.org/doc/manuals/debian-handbook/sect.config-misc.en.html#sect.time-synchronization
- Ubuntu: https://ubuntu.com/server/docs/network-ntp
E-Mail - Client for Sending Local Mail
Identify Mail Client Host and Domain
Edit the following files:
- /etc/hostname (add fully qualified host & domain; i.e.: www.example.com)
- /etc/mailname (add domain; i.e.: example.com)
Mail Transport Agent (MTA) packages
Install an SMTP daemon to transfer mail to the E-Mail server.
- Debian
Install postfix
$ sudo apt-get install postfix
Reconfigure postfix, if it does not pop up, and select sattelite system.
$ sudo dpkg-reconfigure postfix
The following assumes your host is named app and your email server is smtp.<domain>
File: /etc/postfix/main.cf
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 3.6 on
# fresh installs.
compatibility_level = 3.6
# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level=may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = app
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = app.example.com, $myhostname, app, localhost.localdomain, localhost
relayhost = smtp.example.com
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = loopback-only
inet_protocols = all
Check postfix systemd service.
$ sudo systemctl status postfix
● postfix.service - Postfix Mail Transport Agent
Loaded: loaded (/lib/systemd/system/postfix.service; enabled; preset: enabled)
Active: active (exited) since Sat 2023-07-22 09:32:00 EDT; 5h 26min ago
Docs: man:postfix(1)
Process: 1178 ExecStart=/bin/true (code=exited, status=0/SUCCESS)
Main PID: 1178 (code=exited, status=0/SUCCESS)
CPU: 1ms
Jul 22 09:32:00 app.example.com systemd[1]: Starting postfix.service - Postfix Mail Transport Agent...
Jul 22 09:32:00 app.example.com systemd[1]: Finished postfix.service - Postfix Mail Transport Agent.
- RedHat
Install postfix
$ sudo dnf install postfix
The following assumes your host is named app and your email server is smtp.<domain>
File: /etc/postfix/main.cf
smtpd_banner = $myhostname ESMTP $mail_name (Linux)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2
# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = app.example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = app.example.com, localhost.example.com, localhost
relayhost = smtp.example.com
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 192.168.1.0/32
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = loopback-only
inet_protocols = all
Check the status of postfix
$ sudo systemctl status postfix
[sudo] password for don:
● postfix.service - Postfix Mail Transport Agent
Loaded: loaded (/usr/lib/systemd/system/postfix.service; enabled; preset: disabled)
Active: active (running) since Wed 2023-06-07 17:43:43 EDT; 22h ago
Main PID: 2182 (master)
Tasks: 3 (limit: 99462)
Memory: 8.6M
CPU: 1.975s
CGroup: /system.slice/postfix.service
├─ 2182 /usr/libexec/postfix/master -w
├─ 2184 qmgr -l -t unix -u
└─15581 pickup -l -t unix -u
MAC OS send mail to local server, not system configured in mail app
Use the IP address of the local mail server, or you can edit /etc/hosts and use that name. Postfix does not run as a daemon, but is run by the SMTP process, probably fired of by a listener for port 25.
File: /etc/postfix/main.cf
% sudo vi /etc/postfix/main.cf
~
myhostname = square.example.com
~
mydomain = example.com
~
relayhost = [192.168.1.3]
Test mail
First install the command line mail interface(s). I use mail and mutt.
- Debian
$ sudo apt-get install mailutils mutt
- RedHat
$ sudo dnf install s-nail mutt
% mail -s "Hello internal mail" don@example.com </dev/null
Null message body; hope that's ok
Shows up as: don@square.example.com
...
mutt -s "Hello internal mail from mutt" don@example.com
Shows up as: don@square.local
Mutt change from address
File: ~/.muttrc
set from="Square <don@square.example.com>"
set hostname="square.example.com"
Shows up as: don@square.example.com
- Update root destination in aliases
File: /etc/aliases
~
# Person who should get root's mail
#root: marc
root: bob@example.com
~
Update aliases into database format
[don@ash ~]$ sudo newaliases
- Create mail script to set variables
File: ~/mail.sh
#!/bin/bash
#######################################################################
#
# File: mail.sh
#
# Usage: mail.sh <File Name to Mail> <Subject>
# Change the REPLYTO, FROM, and MAILTO variables
# and choose RedHat or Debian
#
# Who When Why
# --------- ----------- -----------------------------------------------
# D. Cohoon Feb-2023 VPS host name cannot be changed, so set headers
#######################################################################
function usage () {
/usr/bin/echo "Usage: ${0} <File Name to Mail> <Subject>"
exit 1
}
#------------------
if [ $# -lt 2 ]; then
usage
fi
#
if [ ! -z ${1} ] && [ ! -f ${1} ]; then
usage
fi
#
#------------------
HOSTNAME=$(hostname -s)
DOMAINNAME=$(hostname -d)
FILE=${1} # First arg
shift 1
SUBJECT="${HOSTNAME}.${DOMAINNAME}:${@}" # Remainder of args
#
#------------------
export REPLYTO=root@app.example.com
FROM=root@app.example.com
#FROM="${HOSTNAME}@${DOMAINNAME}"
MAILTO=bob@example.com
#
#------------------
# Debian: install mailutils
#/usr/bin/cat ${FILE} | /usr/bin/mail -aFROM:${FROM} -s "${SUBJECT}" ${MAILTO}
# RedHat: install s-nail
#/usr/bin/cat ${FILE} | /usr/bin/mail --from-address=${FROM} -s "${SUBJECT}" ${MAILTO}
Monit - Monitor System and Restart Processes
Monit is a small Open Source utility for managing and monitoring Unix systems. Monit conducts automatic maintenance and repair and can execute meaningful causal actions in error situations.
Reference: https://mmonit.com/monit/
Installation
$ sudo apt-get install monit
$ sudo dnf install monit
Configuration
Change the mailserver to yours, and add some general monitoring.
File: /etc/monit/monitrc:
# Mail server
set mailserver www.example.com port 25 # primary mailserver
# Don 28-Dec 2021 - general monitoring
check system $HOST
if loadavg (1min) per core > 2 for 5 cycles then alert
if loadavg (5min) per core > 1.5 for 10 cycles then alert
if cpu usage > 95% for 5 cycles then alert
if memory usage > 90% then alert
if swap usage > 50% then alert
check device root with path /
if space usage > 90% then alert
if inode usage > 90% then alert
if changed fsflags then alert
if service time > 250 milliseconds for 5 cycles then alert
if read rate > 500 operations/s for 5 cycles then alert
if write rate > 200 operations/s for 5 cycles then alert
check network eth0 with interface eth0
if failed link then alert
if changed link then alert
if saturation > 90% for 2 cycles then alert
if download > 10 MB/s for 5 cycles then alert
if total uploaded > 1 GB in last hour then alert
check host REACHABILITY with address 1.1.1.1
if failed ping with timeout 10 seconds then alert
Process
Monitor and restart the ssh process (and others that you may need using this as a guide).
File: /etc/monit/conf.d/sshd
check process sshd with pidfile /var/run/sshd.pid
alert root@example.com with mail-format {
from: monit@example.com
subject: monit alert: $SERVICE $EVENT $DATE
message: $DESCRIPTION
}
start program "/etc/init.d/ssh start"
stop program "/etc/init.d/ssh stop"
Munin - Resource History Monitor
Munin is a networked resource monitoring tool (started in 2002) that can help analyze resource trends and what just happened to kill our performance? problems. It is designed to be very plug and play.
A default installation provides a lot of graphs with almost no work. Requires Apache or nginx for graphs.
Reference: http://guide.munin-monitoring.org/en/latest/tutorial/alert.html
Installation
On all nodes
package libdb-pg-perl is required for postgresql
# sudo apt-get install munin libdbd-pg-perl
package perl-DBD-Pg is required for postgresql
# sudo dnf install munin perl-DBD-Pg
On Munin-Master node, add the following list of hosts to monitor:
File: /etc/munin.munin.conf
~
# Local Host
[app.example.com]
address 127.0.0.1
use_node_name yes
# E-Mail host
[www.example.com]
address 192.168.1.3
use_node_name yes
~
On Munin-Node node, allow master into IPv6 port:
$ sudo ufw allow 4949
$ sudo ufw status | grep 4949
4949 ALLOW Anywhere
4949 (v6) ALLOW Anywhere (v6)
$ sudo firewall-cmd --permanent --zone=public --add-port=4949/tcp
$ sudo firewall-cmd --reload
$ sudo firewall-cmd --permanent --list-ports
4949/tcp
On Munin-Node node, add the Munin-Master IP address to the following:
File: /etc/munin/munin-node.conf
~
# A list of addresses that are allowed to connect. This must be a
# regular expression, since Net::Server does not understand CIDR-style
# network notation unless the perl module Net::CIDR is installed. You
# may repeat the allow line as many times as you'd like
allow ^127\.0\.0\.1$
allow ^::1$
allow ^192\.168\.1\.3$
allow ^fe80::abcd:1234:0000:abcd$
~
Check your munin-node functions from command line using the network cat utility
Debian -> $ sudo apt-get install ncat
:
RedHat -> $ sudo dnf install ncat
:
Try comands:
- list
- nodes
- config
- fetch
- version
- quit quit
$ ncat 127.0.0.1 4949
# munin node at app.example.com
list
acpi apache_accesses apache_processes apache_volume cpu df df_inode entropy forks fw_packets http_loadtime if_em1 if_eno1 if_err_em1 if_err_eno1 if_err_tun0 if_err_wlp58s0 if_tun0 if_wlp58s0 interrupts irqstats load lpstat memory munin_stats netstat ntp_198.23.200.19 ntp_208.94.243.142 ntp_216.218.254.202 ntp_91.189.94.4 ntp_96.126.100.203 ntp_kernel_err ntp_kernel_pll_freq ntp_kernel_pll_off ntp_offset ntp_states open_files open_inodes postfix_mailqueue postfix_mailvolume postgres_autovacuum postgres_bgwriter postgres_cache_ALL postgres_cache_twotree postgres_checkpoints postgres_connections_ALL postgres_connections_db postgres_connections_twotree postgres_locks_ALL postgres_locks_twotree postgres_querylength_ALL postgres_querylength_twotree postgres_scans_twotree postgres_size_ALL postgres_size_twotree postgres_transactions_ALL postgres_transactions_twotree postgres_tuples_twotree postgres_users postgres_xlog proc_pri processes swap threads uptime users vmstat
.
fetch df
_dev_nvme0n1p2.value 34.4721606114914
_dev_shm.value 0.000540811610733636
_run.value 0.183629672785198
_run_lock.value 0.15625
_run_qemu.value 0
_dev_sda1.value 39.2182617590549
_dev_nvme0n1p1.value 1.02513530868728
_dev_sdb1.value 29.1143742265263
.
fetch cpu
user.value 3392679
nice.value 310628
system.value 792413
idle.value 49254331
iowait.value 202415
irq.value 0
softirq.value 56281
steal.value 0
guest.value 0
.
Apache monitoring requires the mod_status to be enabled and add your IP address range to the status.conf.
Enable apache module mod_status:
$ sudo a2enmod status
Check the IP addresses in the apache status configuration. Change Require ip <address>
to allow other IP addresses to connect to the munin monitor.
File: /etc/apache2/mods-enabled/status.conf
~
<Location /server-status>
SetHandler server-status
Require local
Require ip 192.168.1.0/24
#Require ip 192.0.2.0/24
</Location>
~
Check apache plugin:
$ sudo munin-run apache_volume
volume80.value 500736
Check postgresql plugin:
$ sudo munin-run postgres_connections_miniflux
active.value 0
idle.value 1
idletransaction.value 0
unknown.value 0
waiting.value 0
Check the munin-node daemon status:
$ sudo systemctl status munin-node
Check the munin-master daemon status:
$ sudo systemctl status munin
The utility munin-node-configure is used by the Munin installation procedure to check which plugins are suitable for your node and create the links automatically. It can be called every time when a system configuration changes (services, hardware, etc) on the node and it will adjust the collection of plugins accordingly. '-shell' will display new configuration plugin links 'ln -s ...' for you.
For instance, below a new network interface (if) was discovered since the last configuration of munin. To enable the new monitoring simply execute the 'ln -s ...' commands to create soft links, so interface veth2e40fe9 will be monitored.
$ sudo munin-node-configure -shell
ln -s '/usr/share/munin/plugins/if_' '/etc/munin/plugins/if_veth2e40fe9'
ln -s '/usr/share/munin/plugins/if_err_' '/etc/munin/plugins/if_err_veth2e40fe9'
To have munin-node-configure display 'rm ...' commands for plugins with software that may no longer be installed, use the option ‘–remove-also’.
$ sudo munin-node-configure -shell -remove-also
ln -s '/usr/share/munin/plugins/if_' '/etc/munin/plugins/if_veth2e40fe9'
rm -f '/etc/munin/plugins/if_veth0049d71'
ln -s '/usr/share/munin/plugins/if_err_' '/etc/munin/plugins/if_err_veth2e40fe9'
rm -f '/etc/munin/plugins/if_err_veth0049d71'
Enabled monitors can be found in the same location
$ ls -l /etc/munin/plugins | grep apache
lrwxrwxrwx 1 root root 40 Jun 30 2018 apache_accesses -> /usr/share/munin/plugins/apache_accesses
lrwxrwxrwx 1 root root 41 Jun 30 2018 apache_processes -> /usr/share/munin/plugins/apache_processes
lrwxrwxrwx 1 root root 38 Jun 30 2018 apache_volume -> /usr/share/munin/plugins/apache_volume
Testing new plugins has an autoconf option to munin-run. Errors will be displayed, and a debug '-d' option is also available.
$ sudo munin-run postgres_connections_miniflux autoconf
yes
$ sudo munin-run -d postgres_connections_miniflux autoconf
# Running 'munin-run' via 'systemd-run' with systemd properties based on 'munin-node.service'.
# Command invocation: systemd-run --collect --pipe --quiet --wait --property EnvironmentFile=/tmp/YRfAa1dq9U --property UMask=0022 --property LimitCPU=infinity --property LimitFSIZE=infinity --property LimitDATA=infinity --property LimitSTACK=infinity --property LimitCORE=infinity --property LimitRSS=infinity --property LimitNOFILE=524288 --property LimitAS=infinity --property LimitNPROC=14150 --property LimitMEMLOCK=65536 --property LimitLOCKS=infinity --property LimitSIGPENDING=14150 --property LimitMSGQUEUE=819200 --property LimitNICE=0 --property LimitRTPRIO=0 --property LimitRTTIME=infinity --property SecureBits=0 --property 'CapabilityBoundingSet=cap_chown cap_dac_override cap_dac_read_search cap_fowner cap_fsetid cap_kill cap_setgid cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config cap_mknod cap_lease cap_audit_write cap_audit_control cap_setfcap cap_mac_override cap_mac_admin cap_syslog cap_wake_alarm cap_block_suspend cap_audit_read cap_perfmon cap_bpf cap_checkpoint_restore' --property AmbientCapabilities= --property DynamicUser=no --property MountFlags= --property PrivateTmp=yes --property PrivateDevices=no --property ProtectClock=no --property ProtectKernelTunables=no --property ProtectKernelModules=no --property ProtectKernelLogs=no --property ProtectControlGroups=no --property PrivateNetwork=no --property PrivateUsers=no --property PrivateMounts=no --property ProtectHome=yes --property ProtectSystem=full --property NoNewPrivileges=no --property LockPersonality=no --property MemoryDenyWriteExecute=no --property RestrictRealtime=no --property RestrictSUIDSGID=no --property RestrictNamespaces=no --property ProtectProc=default --property ProtectHostname=no -- /usr/sbin/munin-run --ignore-systemd-properties -d postgres_connections_miniflux autoconf
# Processing plugin configuration from /etc/munin/plugin-conf.d/README
# Processing plugin configuration from /etc/munin/plugin-conf.d/dhcpd3
# Processing plugin configuration from /etc/munin/plugin-conf.d/munin-node
# Processing plugin configuration from /etc/munin/plugin-conf.d/spamstats
# Setting /rgid/ruid/ to /130/117/
# Setting /egid/euid/ to /130 130/117/
# Setting up environment
# Environment PGPORT = 5432
# Environment PGUSER = postgres
# About to run '/etc/munin/plugins/postgres_connections_miniflux autoconf'
yes
Alert via e-mail:
Reference: https://guide.munin-monitoring.org/en/latest/tutorial/alert.html#alerts-send-by-local-system-tools
Change your email here
File: /etc/munin/munin.conf
~
contact.email.command mail -s "Munin-notification for ${var:group} :: ${var:host}" your@email.address.here
~
Adjust disk full thresholds:
Adjust this in master /etc/munin.conf section for node
File: /etc/munin.conf
[beaglebone]
address 192.168.1.7
use_node_name yes
diskstats_latency.mmcblk0.avgrdwait.warning 0:10
diskstats_latency.mmcblk0.avgrdwait.critical -5:5
diskstats_latency.mmcblk0.avgwdwait.warning 0:10
diskstats_latency.mmcblk0.avgwdwait.critical -5:5
diskstats_latency.mmcblk0.avgwait.warning 0:10
diskstats_latency.mmcblk0.avgwait.critical -5:5
Graph Example
Redhat Alternative
Cockpit
$ sudo systemctl start Cockpit
To log in to Cockpit, open your web browser to localhost:9090 and enter your Linux username and password.
Reference: https://www.redhat.com/sysadmin/intro-cockpit
Fail2ban - Automatic Firewall Blocking
Daemon to ban hosts that cause multiple authentication errors by monitoring system logs.
Reference: https://github.com/fail2ban/fail2ban
Install
$ sudo apt-get install fail2ban
$ sudo dnf install fail2ban
Configure
Create a jail.local file to override the defaults. Update your email and IP addresses to suit your environment. Also add or disable applications you do not run. See the reference above for example of how to do that.
action = %(action_)s
This defines the action to execute when a limit is reached. By default it will only block the user.
To receive an email at each ban, set it to:
action = %(action_mw)
To receive the logs with the mail, set it to:
action = %(action_mwl)
File: /etc/fail2ban/jail.local
[DEFAULT]
# email
destemail = don@example.com
sender = root@example.com
# ban & send an e-mail with whois report and relevant log lines
# to the destemail.
action = %(action_mwl)s
# whitelist
ignoreip = 127.0.0.1 192.168.1.0/24 8.8.8.8 1.1.1.1
Secure sshd
File: /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
port = 22
filter = sshd
action = iptables-multiport[name=sshd, port="ssh"]
logpath = /var/log/auth.log
maxretry = 3
bantime = 1d
[sshd]
enabled = true
port = 22
filter = sshd
action = iptables-multiport[name=sshd, port="ssh"]
logpath = /var/log/secure
maxretry = 3
bantime = 1d
More filters show which daemons are available to be enabled here: /etc/fail2ban/filter.d/
Backup - Save Your Files Daily
To find the proper name of your USB stick, check the current mounts:
$ sudo lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 1.8T 0 disk
└─sda1 8:1 0 1.8T 0 part
sdb 8:16 1 0B 0 disk
sdc 8:32 1 0B 0 disk
nvme0n1 259:0 0 238.5G 0 disk
├─nvme0n1p1 259:1 0 300M 0 part /boot/efi
├─nvme0n1p2 259:2 0 119.2G 0 part /
├─nvme0n1p3 259:3 0 16.9G 0 part [SWAP]
Plug it is, then check dmesg -x
immediately after plugging it in. Look for :
[201373.210797] sdd: sdd1
[201373.211917] sd 2:0:0:0: [sdd] Attached SCSI removable disk
Then run blkid again. You can see the new entry, sdd.
$ sudo lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 1.8T 0 disk
└─sda1 8:1 0 1.8T 0 part
sdb 8:16 1 0B 0 disk
sdc 8:32 1 0B 0 disk
sdd 8:48 1 28.9G 0 disk <----- New USB Stick
nvme0n1 259:0 0 238.5G 0 disk
├─nvme0n1p1 259:1 0 300M 0 part /boot/efi
├─nvme0n1p2 259:2 0 119.2G 0 part /
├─nvme0n1p3 259:3 0 16.9G 0 part [SWAP]
Automatic backup to USB disk
Format new USB stick.
Here are the commands to fdisk:
- m - menu
- p - print existing partitions
- d - delete partition
- n - create new partition (in this case only a primary partition is required)
- w - write partition
- q - quit
$ sudo fdisk /dev/sdd
Check the USB stick label and filesystem (this example has no filesystem)
$ sudo blkid /dev/sdd1
/dev/sdd1: PARTUUID="66bc7da7-1234-abcd-1234-ea4bfe7e00a7"
So create an ext4 filesystem on the new partition(1)
$ sudo mkfs.ext4 /dev/sdd1
mke2fs 1.46.2 (28-Feb-2021)
/dev/sdc1 contains a vfat file system
Proceed anyway? (y,N) y
Creating filesystem with 7566075 4k blocks and 1892352 inodes
Filesystem UUID: 8e33672c-1283-49de-98b8-6fd841372db6
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
Check the new filesystem, now we see it is TYPE="ext4"
$ sudo blkid /dev/sdd1
/dev/sdc1: UUID="8e33672c-1234-49de-abcd-6fd841372db6" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="66bc7da7-c9c3-4342-8073-ea4bfe7e00a7"
Label the USB stick as 'backup' so autobackup can find and mount it, then verify that LABEL="backup"
$ sudo e2label /dev/sdd1 backup
$ sudo blkid /dev/sdd1
/dev/sdd1: LABEL="backup" UUID="8e33672c-1234-49de-abcd-6fd841372db6" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="66bc7da7-c9c3-4342-8073-ea4bfe7e00a7"
Copy the autobackup software onto this server
$ git clone https://github.com/bablokb/autobackup-service
Install the autobackup software
$ cd autobackup-service/
$ sudo ./tools/install
RedHat Changes
Lines: 21, 30, 31
File: ./tools/install
17 check_packages() {
18 local p
19 for p in "$@"; do
20 echo -en "Checking $p ... " >&2
21 rpm -qa "$p" 2>/dev/null | grep -q "Status.*ok" || return 0
22 echo "ok" >&2
23 done
24 return 1
25 }
26
27 install_packages() {
28 if [ -n "$PACKAGES" ] && check_packages $PACKAGES; then
29 echo -e "[INFO] installing additional packages" 2>&1
30 dnf update
31 dnf -y --no-upgrade install $PACKAGES
32 fi
33 }
Edit the autobackup configuration file, assigning LABEL=backup, and other items below:
File: /etc/autobackup.conf
~
# => File: /etc/autobackup.conf <=
# label of backup partition
LABEL=backup
# write messages to syslog
SYSLOG=1
# wait for device to appear (in seconds)
WAIT_FOR_DEVICE=2
# run a backup on every mount (i.e. multiple daily backups)
force_daily=0
# backup-levels - this must match your entries in /etc/rsnapshot.conf,
i.e.
# you must have a corresponding 'retain' or 'interval' entry.
# The autobackup-script will skip empty levels
daily="day"
weekly="week"
monthly="month"
yearly=""
"/etc/autobackup.conf" line 29 of 29 --100%--
Edit the rsnapshot configuration file, be sure to use TABS in the BACKUP POINTS / SCRIPTS section.
File: /etc/rsnapshot.conf
~
# => File /etc/rsnapshot.conf <=
###########################
# SNAPSHOT ROOT DIRECTORY #
###########################
# All snapshots will be stored under this root directory.
#
#snapshot_root /var/cache/rsnapshot/
snapshot_root /tmp/autobackup/.autobackup/
~
~
#########################################
# BACKUP LEVELS / INTERVALS #
# Must be unique and in ascending order #
# e.g. alpha, beta, gamma, etc. #
#########################################
retain day 7
retain week 4
retain month 3
#retain year 3
###############################
### BACKUP POINTS / SCRIPTS ###
###############################
# LOCALHOST
# backup /etc/ ./
# backup /var/backups/ ./
# backup /usr/local/ ./
# backup /home ./
# NOTE: Use tabs!
# LOCALHOST$
backup^I/etc/^I./$
backup^I/var/backups/^I./$
backup^I/usr/local/^I./$
backup^I/home^I^I./$
~
> "/etc/rsnapshot.conf"
Copy autobackup script from install to your home directory
cp autobackup-service/files/usr/local/sbin/autobackup $HOME/autobackup-service/autobackup.sh
Comment out lines 58 through 61 from "<" to ">" below, to allow running in cron.
autobackup-service normally runs automatically when a USB stick with the proper label is inserted into the machine. Comment out the
if
statement to allow it to run by cron.
File: $HOME/autobackup-service/autobackup.sh
58,61c60,64
< if [ "${DEVICE:5:3}" != "$udev_arg" ]; then
< msg "info: partition with label $LABEL is not on newly plugged device $udev_arg"
< exit 0
< fi
---
> # Don -> do not check, as we are screduling through cron
> # if [ "${DEVICE:5:3}" != "$udev_arg" ]; then
> # msg "info: partition with label $LABEL is not on newly plugged device $udev_arg"
> # exit 0
> # fi
Schedule in /etc/cron.d (change your home directory):
File: /etc/cron.d/autobackup-daily
# This is a cron file for autobackup/rsnapshot.
# 0 */4 * * * root /usr/bin/rsnapshot alpha
# 30 3 * * * root /usr/bin/rsnapshot beta
# 0 3 * * 1 root /usr/bin/rsnapshot gamma
# 30 2 1 * * root /usr/bin/rsnapshot delta
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO="don@example.com"
# m h dom mon dow user command
55 12 * * * root /home/don/autobackup-service/autobackup.sh
Log entries will be in the syslog
$ sudo grep autobackup.sh /var/log/syslog
Jan 23 12:51:11 box autobackup.sh: info: LABEL = backup
Jan 23 12:51:11 box autobackup.sh: info: WAIT_FOR_DEVICE = 2
Jan 23 12:51:11 box autobackup.sh: info: force_daily = 0
Jan 23 12:51:11 box autobackup.sh: info: yearly =
Jan 23 12:51:11 box autobackup.sh: info: monthly = month
Jan 23 12:51:11 box autobackup.sh: info: weekly = week
Jan 23 12:51:11 box autobackup.sh: info: daily = day
Jan 23 12:51:13 box autobackup.sh: info: checking:
Jan 23 12:51:13 box autobackup.sh: info: mount-directory: /tmp/autobackup
Jan 23 12:51:13 box autobackup.sh: info: current year: 2021
Jan 23 12:51:13 box autobackup.sh: info: current month: 01
Jan 23 12:51:13 box autobackup.sh: info: current week: 03
Jan 23 12:51:13 box autobackup.sh: info: current day: 023
Jan 23 12:51:13 box autobackup.sh: info: starting backup for interval: month (last backup: 0)
Jan 23 12:51:13 box autobackup.sh: info: starting backup for interval: week (last backup: 0)
Jan 23 12:51:13 box autobackup.sh: info: starting backup for interval: day (last backup: 0)
Jan 23 12:51:15 box autobackup.sh: info: umounting /dev/sda1
Automatic Backup of PostgreSQL Database
Place a script in /etc/cron.daily and it will be run once a day, using the root account.
cron.daily
To check the times look here:
$ grep run-parts /etc/crontab
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
# cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron
# See anacron(8) and anacrontab(5) for details.
SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22
#period in days delay in minutes job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly
So our daily runs start at 6:25am every day.
Backing up a PostrgreSQL database can be done while everything is up and running with the following script. Backups are stored in /data/backups.
File: /etc/cron.daily/backup_nextcloud
#!/bin/bash
LOGFILE=/var/log/backup_db.log
ID=$(id -un)
if [ ${ID} != "root" ]; then
echo "Must run as root, try sudo"
exit 1
fi
#
echo $(date) ${0} >> $LOGFILE
umask 027
export DATA=/data/backups
if cd ${DATA}; then
# Postgres
#/usr/bin/pg_dump -c nextcloud > $DATA/nextcloud.db.$(date +%j) </dev/null
sudo -u postgres /usr/bin/pg_dump -c nextcloud > $DATA/nextcloud.db </dev/null
date >> $LOGFILE
sync
sync
sync
sync
savelog -c 7 nextcloud.db >>$LOGFILE 2>&1
fi
Rsync - Remote File Synchronization
Rsync is a good way to keep a daily backup as it only copies changed files to the destination. Make sure you use a seperate disk and preferrably seperate system, as rsync works great over the network.
The PostgreSQL backup above should be sent off to another system using this method. Another rsync script should be called by PostgreSQL backup to do the database network backup. Just copy this one, change the directories, and call it at the end of the database backup.
Schedule
This cron entry will run at 8:40am every day by user root.
File: /etc/cron.d/rsync
# This is a cron file for rsync to NAS
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO="don@example.com"
# m h dom mon dow user command
40 8 * * * root /mnt/raid1/rsync.sh noask
Script
This script will backup the local directory, /mnt/raid1/data, to a remote system (IP Address 192.168.1.2). The files on the remote system will be at /mnt/vol09/backups. The first run will copy everything, all next runs will only copy changed files. Any files deleted on the source will also be deleted on the destination.
To schedule in cron the parameter 'noask' is used, as shown above. Otherwise there is a prompt for y/n.
The last run's history is in log file /mnt/raid1/rsync.log.
File: /mnt/raid1/rsync.sh
#!/bin/bash
DIR=/mnt/raid1
LOG=${DIR}/rsync.log
cd ${DIR}
date >${LOG}
ASK=${1}
if [ -z ${ASK} ]; then
echo "Asking"
fi
#
if [ -z ${ASK} ]; then
echo -n "Copy data? y/n: "
read askme
if [[ $askme =~ ^[Yy]$ ]]; then
rsync -avzz --ignore-errors --progress --delete ${DIR}/data root@192.168.1.2:/mnt/vol09/backups/ |tee -a ${LOG}
else
echo "Sync of data skipped"
echo ". . ."
fi
else
rsync -avzz --ignore-errors --progress --delete ${DIR}/data root@192.168.1.2:/mnt/vol09/backups/ |tee -a ${LOG}
fi
#
date >>${LOG}
Logwatch - Daily Alert of Logging Activity
Logwatch is a customizable, pluggable log-monitoring system. It will go through your logs for a given period of time and make a report in the areas that you wish with the detail that you wish. Logwatch is being used for Linux and many types of UNIX.
Installation
Debian:
$ sudo apt-get install logwatch
Redhat:
$ sudo dnf install logwatch
Schedule
File: /etc/cron.daily/00logwatch
#!/bin/bash
#Check if removed-but-not-purged
test -x /usr/share/logwatch/scripts/logwatch.pl || exit 0
#execute
#/usr/sbin/logwatch --output mail
/usr/sbin/logwatch --mailto don@example.com
#Note: It's possible to force the recipient in above command
#Just pass --mailto address@a.com instead of --output mail
Add services
You can add iptables summary on the daily report. It shows which IP addresses have been blocked by UFW.
$ sudo cp /usr/share/logwatch/default.conf/services/iptables.conf /etc/logwatch/conf/services/
You may need to add syslog on Ubuntu servers
File: /etc/logwatch/conf/services/iptables.conf
~
# Which logfile group...
LogFile = syslog
~
Logcheck - mails anomalies in the system logfiles to the admin
The logcheck program helps spot problems and security violations in your logfiles automatically and will send the results to you periodically in an e-mail. By default logcheck runs as an hourly cronjob just off the hour and after every reboot.
Installation
$ sudo apt-get install logcheck
$ sudo dnf install epel-release 'dnf-command(copr)'
$ sudo dnf copr enable brianjmurrell/epel-8
$ sudo dnf install logcheck
$ sudo setfacl -R -m u:logcheck:rx /var/log/secure*
$ sudo setfacl -R -m u:logcheck:rx /var/log/messages*
$ sudo dnf copr disable brianjmurrell/epel-8
Schedule
Normally the package installation will schedule a cron job for you. Check it here:
File: /etc/cron.d/logcheck
# Cron job runs at 2 minutes past every hour
# /etc/cron.d/logcheck: crontab entries for the logcheck package
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
@reboot logcheck if [ -x /usr/sbin/logcheck ]; then nice -n10 /usr/sbin/logcheck -R; fi
2 * * * * logcheck if [ -x /usr/sbin/logcheck ]; then nice -n10 /usr/sbin/logcheck; fi
# EOF
Change email destination
Change SENDMAILTO variable to point to your email.
File: /etc/logcheck/logcheck.conf
~
# Controls the address mail goes to:
# *NOTE* the script does not set a default value for this variable!
# Should be set to an offsite "emailaddress@some.domain.tld"
#SENDMAILTO="logcheck"
SENDMAILTO="don@example.com"
~
Sysstat - Gather System Usage Statistics
The sysstat[1] package contains various utilities, common to many commercial Unixes, to monitor system performance and usage activity:
- iostat reports CPU statistics and input/output statistics for block devices and partitions.
- mpstat reports individual or combined processor related statistics.
- pidstat reports statistics for Linux tasks (processes) : I/O, CPU, memory, etc.
- tapestat reports statistics for tape drives connected to the system.
- cifsiostat reports CIFS statistics.
Sysstat also contains tools you can schedule via cron or systemd to collect and historize performance and activity data:
- sar collects, reports and saves system activity information (see below a list of metrics collected by sar).
- sadc is the system activity data collector, used as a backend for sar.
- sa1 collects and stores binary data in the system activity daily data file. It is a front end to sadc designed to be run from cron or systemd.
- sa2 writes a summarized daily activity report. It is a front end to sar designed to be run from cron or systemd.
- sadf displays data collected by sar in multiple formats (CSV, XML, JSON, etc.) and can be used for data exchange with other programs. This command can also be used to draw graphs for the various activities collected by sar using SVG (Scalable Vector Graphics) format.
Default sampling interval is 10 minutes but this can be changed of course (it can be as small as 1 second).
Redhat Cockpit uses pmlogger.service [2] from systemd. Install from Cockpit's Overview, Metrics and history.
RedHat pmstat [3]
$ pmstat
@ Mon Jun 12 10:02:01 2023
loadavg memory swap io system cpu
1 min swpd free buff cache pi po bi bo in cs us sy id
0.00 116224 231636 3284 13116m 0 0 0 17 348 387 0 0 100
0.00 116224 233328 3284 13116m 0 0 0 0 339 383 0 0 100
0.00 116224 228704 3284 13116m 0 0 0 0 333 358 0 0 100
0.00 116224 227192 3284 13116m 0 0 0 6 493 548 0 0 99
^C
$ pmstat -a /var/log/pcp/pmlogger/bob.example.com/20230610.0.xz -t 2hour -A 1hour -z
Note: timezone set to local timezone of host "bob.example.com" from archive
@ Sat Jun 10 01:00:00 2023
loadavg memory swap io system cpu
1 min swpd free buff cache pi po bi bo in cs us sy id
0.08 2048 7646m 6440 6591m 0 0 0 3 198 237 0 0 100
0.08 2048 7650m 6440 6596m 0 0 0 3 202 237 0 0 100
0.06 2048 7643m 6440 6600m 0 0 0 3 204 236 0 0 100
0.00 2048 7597m 6440 6624m 0 0 2 27 219 261 0 0 100
0.09 2048 7609m 6440 6629m 0 0 0 3 215 259 0 0 100
0.03 2048 7593m 6440 6633m 0 0 0 3 220 261 0 0 100
0.00 2048 7585m 6440 6638m 0 0 0 4 223 263 0 0 100
0.01 0 14402m 6740 495508 ? ? ? ? ? ? ? ? ?
0.00 0 14268m 6740 630344 ? ? ? ? ? ? ? ? ?
0.15 0 14272m 6740 634764 0 0 0 2 162 151 0 0 100
0.13 0 14266m 6740 639188 0 0 0 2 164 152 0 0 100
pmFetchGroup: End of PCP archive log
Reference: 1 https://github.com/sysstat/sysstat 2 https://cockpit-project.org/guide/latest/feature-pcp.html 3 https://pcp.readthedocs.io/en/latest/UAG/MonitoringSystemPerformance.html#the-pmstat-command
Installation
$ sudo apt-get install sysstat
$ sudo dnf install sysstat
Configuration
It should configure itself, but just in case:
$ sudo dpkg-reconfigure sysstat
Replacing config file /etc/default/sysstat with new version
$ vi /etc/sysconfig/sysstat
$ sudo systemctl enable --now sysstat
The history files are kept here:
$ ls /var/log/sysstat/
sa07
$ ls /var/log/sa/
sa07
The timer is in the systemd configuration file. OnCalendar defines the interval. In this case data is collected every ten minutes. WantedBy defines that the timer should be active when the sysstat.service is running.
Use
systemctl edit sysstat-collect.timer
[1] to edit this file. It will automatically create an override file in the right place [2] and enable it for you, and preserve the change over release updates.
File: /usr/lib/systemd/system/sysstat-collect.timer
# /lib/systemd/system/sysstat-collect.timer
# (C) 2014 Tomasz Torcz <tomek@pipebreaker.pl>
#
# sysstat-12.5.2 systemd unit file:
# Activates activity collector every 10 minutes
[Unit]
Description=Run system activity accounting tool every 10 minutes
[Timer]
OnCalendar=*:00/10
[Install]
WantedBy=sysstat.service
- https://www.catalyst2.com/knowledgebase/server-management/how-to-install-configure-sysstat/
- Systemd edit override example changing the interval from 10 minutes to 5:
# ls -lrt /etc/systemd/system/sysstat-collect.timer.d/
total 4
-rw-r--r--. 1 root root 27 Feb 19 09:38 override.conf
# more /etc/systemd/system/sysstat-collect.timer.d/override.conf
[Timer]
OnCalendar=*:00/05
Past Statistics Report
Report on system statistics over the last few days.
File: sar.sh
#!/bin/bash
#################################
# Files are here:
# ls -l /var/log/sysstat/
# -rw-r--r-- 1 root root 49064 Feb 9 16:35 sa09
#
# Report on some other day:
# sar -u 2 3 -f /var/log/sysstat/sa15
#
# Output to file:
# sar -u 2 3 -o /tmp/logfile
#################################
echo "Disk"
sar -d 2 3
echo "Network"
sar -n DEV 2 3
echo "CPU"
sar -u 2 3
sar -P ALL -u 2 3
echo "Memory"
sar -r 2 3
echo "Paging"
sar -B 2 3
echo "Swap"
sar -S 2 3
echo "Load"
sar -q 2 3
Sample Runs
Memory
$ sar -r
Linux 5.10.120-ti-arm64-r64 (app.example.com) 11/07/2022 _aarch64_ (2 CPU)
02:09:36 PM kbmemfree kbavail kbmemused %memused kbbuffers kbcached kbcommit %commit kbactive kbinact kbdirty
02:10:01 PM 872556 2467364 1037140 27.37 162024 1529680 3898804 66.24 999112 1645384 536
Average: 872556 2467364 1037140 27.37 162024 1529680 3898804 66.24 999112 1645384 536
IO
$ sar -b
Linux 5.10.120-ti-arm64-r64 (app.example.com) 11/07/2022 _aarch64_ (2 CPU)
02:09:36 PM tps rtps wtps dtps bread/s bwrtn/s bdscd/s
02:10:01 PM 6.63 0.16 6.47 0.00 2.25 312.46 0.00
Average: 6.63 0.16 6.47 0.00 2.25 312.46 0.00
Network
$ sar -n DEV
Linux 5.10.120-ti-arm64-r64 (app.example.com) 11/07/2022 _aarch64_ (2 CPU)
02:09:36 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
02:10:01 PM lo 3.94 3.94 1.66 1.66 0.00 0.00 0.00 0.00
02:10:01 PM eth0 4.10 5.27 0.37 1.72 0.00 0.00 0.00 0.00
02:10:01 PM usb0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
02:10:01 PM usb1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
02:10:01 PM docker0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: lo 3.94 3.94 1.66 1.66 0.00 0.00 0.00 0.00
Average: eth0 4.10 5.27 0.37 1.72 0.00 0.00 0.00 0.00
Average: usb0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: usb1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Average: docker0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Load and Run Queue
$ sar -q
Linux 5.10.120-ti-arm64-r64 (app.example.com) 11/07/2022 _aarch64_ (2 CPU)
02:09:36 PM runq-sz plist-sz ldavg-1 ldavg-5 ldavg-15 blocked
02:10:01 PM 3 531 0.74 0.61 0.51 0
Average: 3 531 0.74 0.61 0.51 0
S.M.A.R.T. Disk Monitoring
Monitor and notify disk health using smartmontools, and email any notifications.
Install software:
$ sudo apt-get install smartmontools
$ sudo dnf install smartmontools
Configure
Add Long monitoring test for Sunday (/dev/sda through /dev/sdX) and comment out DEVICESCAN:
File: /etc/smartd.conf
File: /etc/smartmontools/smartd.conf
~
# Don - 5-Nov-2021
# -a Default: equivalent to -H -f -t -l error -l selftest -C 197 -U 198
# -d TYPE Set the device type: ata, scsi, marvell, removable, 3ware,N, hpt,L/M/N
# -n MODE No check. MODE is one of: never, sleep, standby, idle
# -s REGE Start self-test when type/date matches regular expression (see man page)
# T/MM/DD/d/HH
# ^ ^ ^ ^ ^
# | | | | + 24 Hour
# | | | +-- Day of week, 1(monday) through 7(sunday)
# | | +----- Day of month, 1 ~ 31
# | +-------- Month of year, 01 (January) to 12 (December)
# +---------- T is the type of test that should be run, options are:
#
# L for long self-test
# S for short self-test
# C for conveyance test
# O for an Offline immediate Test
#
# -W D,I,C Monitor Temperature D)ifference, I)nformal limit, C)ritical limit
# -m ADD Send warning email to ADD for -H, -l error, -l selftest, and -f
#/dev/nvme0 -a -n never -W 2,30,40 -m don@example.com
# Start long tests on Sunday 9am and short
# self-tests every night at 2am and send errors to me
#/dev/sda -a -n never -s (L/../../7/09|S/../.././02) -W 2,30,40 -m don@example.com -M test
/dev/sda -a -n never -s (L/../../7/09|S/../.././02) -W 2,42,50 -m don@example.com -M diminishing
#/dev/sdb -a -n never -s (L/../../7/09|S/../.././02) -W 2,30,40 -m don@example.com
# Don - 5-Nov-2021
~
#DEVICESCAN -d removable -n standby -m root -M exec /usr/share/smartmontools/smartd-runner
~
Restart
Restart smartd daemon to pick up configuration changes
$ sudo systemctl restart smartd
Monitoring script for testing and reporting.
Change the DEV below and see if your disks support SMART monitoring.
#!/bin/bash
DEV="/dev/sda"
# Info
sudo smartctl -i "${DEV}"
# Show
sudo smartctl -P show "${DEV}"
# turn smart on/off
#sudo smartctl -s on "${DEV}"
# Errors?
sudo smartctl -l error "${DEV}"
# Health Check
sudo smartctl -Hc "${DEV}"
# Selftest Log
sudo smartctl -l selftest "${DEV}"
# Attributes
# Problems if...
# Reallocated_Sector_Ct > 0
# Current_Pending_Sector > 0
sudo smartctl -A "${DEV}"
#
#.... T E S T S ....
# -> short ... couple of minutes
# sudo smartctl -t short /dev/sda
# -> long ... one hour
# sudo smartctl -t long /dev/sda
# -> Look at test results
# sudo smartctl -a /dev/sda
#
#.... R E P O R T ....
sudo smartctl --attributes --log=selftest "${DEV}"
#
# - Get the temprature
sudo hddtemp /dev/sda
smartd database
The history of each smartd monitored disk is located here:
$ ls /var/lib/smartmontools/
drivedb smartd.Samsung_SSD_980_1TB-S64ANS0T408956M.nvme.state~ smartd.Samsung_SSD_980_1TB-S64ANS0T418940T.nvme.state~
smartd.Samsung_SSD_980_1TB-S64ANS0T408956M.nvme.state smartd.Samsung_SSD_980_1TB-S64ANS0T418940T.nvme.state
If you replace the disks and get error reports, you can remove the files and new ones will be created
$ sudo rm /var/lib/smartmontools/smartd.Samsung_SSD_980_1TB-S64ANS0T4*
Run smartctl for each disk:
sudo smartctl -i /dev/sda
sudo smartctl -i /dev/sdb
...
Then check the history data files. New ones should show up.
$ ls /var/lib/smartmontools/
attrlog.CT1000BX500SSD1-2251E695AE97.ata.csv smartd.CT1000BX500SSD1-2251E695AE97.ata.state smartd.CT1000BX500SSD1-2251E695AE9E.ata.state~
attrlog.CT1000BX500SSD1-2251E695AE9E.ata.csv smartd.CT1000BX500SSD1-2251E695AE97.ata.state~ smartd.Samsung_SSD_980_1TB-S64ANS0T408956M.nvme.state
drivedb smartd.CT1000BX500SSD1-2251E695AE9E.ata.state smartd.Samsung_SSD_980_1TB-S64ANS0T418940T.nvme.state
Login Notices to Users - motd/issue/issue.net
You can customize the Message of the Day (motd), and infomation displayed when loggin in to the system.
Installation
$ sudo apt-get install cowsay fortune
$ sudo dnf install cowsay fortune-mod
Configuration
Update the message (/etc/motd) every hour.
File: /etc/cron.hourly/motd
#!/bin/bash
/usr/games/cowsay $(/usr/games/fortune) > /etc/motd
#!/bin/bash
/bin/cowsay $(/bin/fortune) > /etc/motd
Verify
File: /etc/motd
_________________________________________
/ Your mind is the part of you that says, \
| "Why'n'tcha eat that piece of cake?" |
| ... and then, twenty minutes later, |
| says, "Y'know, if I were you, I |
| wouldn't have done that!" -- Steven and |
\ Ondrea Levine /
-----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
File: /etc/issue
Look out!
File: /etc/issue.net
Looke out!
Example login
% ssh don@example
Looke out!
_________________________________________
/ Individuality My words are easy to \
| understand And my actions are easy to |
| perform Yet no other can understand or |
| perform them. My words have meaning; my |
| actions have reason; Yet these cannot |
| be known and I cannot be known. We are |
| each unique, and therefore valuable; |
| Though the sage wears coarse clothes, |
| his heart is jade. -- Lao Tse, "Tao Te |
\ Ching" /
-----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
Last login: Sun Aug 21 10:13:57 2022 from 192.168.0.5
Login notification
Add the following lines to the end of the system bashrc for notification whenever any user logs into the system (with the bash shell).
File: /etc/bash.bashrc
File: /etc/bashrc
~
# Email logins - Don November 2020
echo $(who am i) ' just logged on ' $(hostname) ' ' $(date) $(who) | mail -s "Login on" don@example.com
Continue
Now that you have set up your new server, consider giving it an internet name with DNS.
Proceed in the order presented, some things are depending on prior setups.
Book Last Updated: 29-March-2024
Set up a New Server - Linux in the House - https://linux-in-the-house.org