test/lib/functions.sh

606 lines
24 KiB
Bash

# Federated Computer functions
# Define all services
SERVICES=("pdnsmysql" "pdns" "pdnsadmin" "traefik" "postgresql" "ldap" "mail" "collabora" "nextcloud" "matrix" "element" "listmonk" "vaultwarden" "panel" "wireguard" "jitsi" "baserow" "gitea" "caddy")
fail() {
echo -ne "FAILED\n\n$1\n\n"
kill -9 $SPINPID &> /dev/null
# [ -d "apps/dns" ] && rm -rf apps/dns
# docker network rm fstack &> /dev/null
exit 2;
}
failcheck() {
echo -ne "\n\nFAILED - $1\n\n"
exit 2;
}
cleanup() {
kill -9 $SPINPID &> /dev/null
exit 2;
}
spin() {
spinner="/|\\-/|\\-"
while :
do
for i in `seq 0 7`
do
echo -n "${spinner:$i:1}"
echo -en "\010"
sleep 1
done
done
}
add_cron() {
cat > /etc/logrotate.d/federated <<EOF
/federated/logs/*.log {
rotate 12
monthly
compress
missingok
notifempty
}
EOF
(crontab -l 2>/dev/null; echo "30 23 * * * date >> /federated/logs/backup.log && /federated/bin/backuptool -b all >> /federated/logs/backup.log 2>&1") | sort -u | crontab -
(crontab -l 2>/dev/null; echo "0 2 * * * date >> /federated/logs/upgrade.log && /federated/bin/upgrade >> /federated/logs/upgrade.log 2>&1") | sort -u | crontab -
(crontab -l 2>/dev/null; echo "0 3 * * * date >> /federated/logs/dumpcerts.log && /federated/bin/dumpcerts >> /federated/logs/dumpcerts.log 2>&1") | sort -u | crontab -
}
install_federated() {
[ -d "/federated" ] && fail "Directory /federated already exists. Already installed?"
API_TOKEN="92d97f5aa371d420ebce7bc9a008ea8c6ec5d334"
git clone https://derek:$API_TOKEN@code.federated.company/federatedcomputer/Core /federated
}
upgrade_federated() {
echo -ne "\n* Updating federated install.."
[ ! -d "/federated" ] && fail "Directory /federated doesn't exist."
echo -ne "\n* Grabbing the latest version from Gitea.."
API_TOKEN="92d97f5aa371d420ebce7bc9a008ea8c6ec5d334"
cd /federated && git pull https://derek:$API_TOKEN@code.federated.company/federatedcomputer/Core &> /dev/null
[ $? -ne 0 ] && fail "Git pull not working on update of federated."
echo -ne "\n* Checking installed app versions with the latest.."
[ ! -f "/federated/lib/latest-versions" ] && fail "File /federated/lib/latest-version doesn't exist."
for i in `cat /federated/lib/latest-versions`; do
SERVICE=(${i//=/ });
APP="${SERVICE[0]}"
VERSION="${SERVICE[1]}"
echo -ne "\n** Checking $APP.."
[ ! -f "/federated/apps/$APP/.env" ] && fail "File /federated/apps/$APP/.env doesn't exist."
APP_VERSION_RAW=`grep IMAGE_VERSION /federated/apps/$APP/.env | awk -F= '{ print $2 }'`
APP_VERSION="${APP_VERSION_RAW//\"}"
if [ "$APP_VERSION" = "$VERSION" ]; then
echo -ne "\n $APP is already at the latest version."
else
NC_COMMAND=`grep start_service /federated/lib/$APP.sh | awk -F\" '{ print $4 }'`
echo -ne "\n Upgrading $APP to $VERSION.."
echo -ne "\n Shutting Down $APP.."
cd /federated/apps/$APP && docker-compose -f docker-compose.yml -p $APP down
if [ "$APP" = "jitsi" ]; then
sed -i "s/#JITSI_IMAGE_VERSION=.*/JITSI_IMAGE_VERSION=$VERSION/g" /federated/apps/$APP/.env
sed -i "s/JITSI_IMAGE_VERSION=.*/JITSI_IMAGE_VERSION=$VERSION/g" /federated/apps/$APP/.env
else
sed -i "s#VERSION=.*#VERSION=$VERSION#g" /federated/apps/$APP/.env
fi
echo -ne "\n Starting Up $APP.."
start_service_upgrade "$APP" "$NC_COMMAND"
echo -ne "\n Done Updating $APP to $VERSION."
fi
done
echo -ne "\n\n"
}
create_password() {
# eval $1_var=$1
# echo "$postgres_var"
SECRET=`tr -cd '[:alnum:]' < /dev/urandom | fold -w32 | head -n1`
echo "$SECRET";
}
start_service_convert() {
SERVICE="$1"
COMMAND="$2"
# Start /federated/apps/SERVICE with output to /dev/null
echo -ne "\n* Starting /federated/apps/$SERVICE service.."
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE up -d &> /dev/null
# Keep trying service port to make sure it's up before
# we proceed
RETRY="40"
while [ $RETRY -gt 0 ]; do
bash -c "$COMMAND" &> /dev/null
if [ $? -eq 0 ]; then
break
else
if [ "$RETRY" == 1 ]; then
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE down &> /dev/null
fail "There was a problem starting service /federated/apps/$SERVICE\nCheck the output of 'docker logs $SERVICE' or turn on\ndebug with -d"
fi
((RETRY--))
sleep 7
fi
done
}
start_service_upgrade() {
SERVICE="$1"
COMMAND="$2"
# Start /federated/apps/SERVICE with output to /dev/null
echo -ne "\n* Starting /federated/apps/$SERVICE service.."
if [ $DEBUG ]; then
# Start /federated/apps/SERVICE with output to console for debug
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE up
[ $? -eq 0 ] && echo -ne "done.\n" || fail "There was a problem starting service /federated/apps/$SERVICE"
else
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE up -d &> /dev/null
# Keep trying service port to make sure it's up before
# we proceed
RETRY="30"
while [ $RETRY -gt 0 ]; do
bash -c "$COMMAND" &> /dev/null
if [ $? -eq 0 ]; then
break
else
if [ "$RETRY" == 1 ]; then
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE down &> /dev/null
fail "There was a problem starting service /federated/apps/$SERVICE\nCheck the output of 'docker logs $SERVICE' or turn on\ndebug with -d"
fi
((RETRY--))
sleep 7
fi
done
fi
}
start_service_withalert() {
SERVICE="$1"
COMMAND="$2"
# Start /federated/apps/SERVICE with output to /dev/null
# echo -ne "\n* Starting /federated/apps/$SERVICE service.."
# spin &
# SPINPID=$!
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE up -d &> /dev/null
# Keep trying service port to make sure it's up before
# we proceed
RETRY="4"
while [ $RETRY -gt 0 ]; do
bash -c "eval $COMMAND" &> /dev/null
if [ $? -eq 0 ]; then
break
else
if [ "$RETRY" == 1 ]; then
EXTERNALIP=`dig @resolver4.opendns.com myip.opendns.com +short 2> /dev/null`
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE down &> /dev/null
echo "Generated by /federated/bin/start" > /federated/apps/mail/data/root/certs/mailfile
docker exec mail bash -c "mail -r admin@$DOMAIN -a \"Content-type: text/html\" -s \"$SERVICE failed to start on $EXTERNALIP\" $ALERTS_EMAIL < /root/certs/mailfile"
fail "There was a problem starting service /federated/apps/$SERVICE\nCheck the output of 'docker logs $SERVICE' or turn on\ndebug with -d"
fi
((RETRY--))
sleep 7
fi
done
}
start_service_upgrade() {
SERVICE="$1"
# Keep trying service port to make sure it's up before
# we proceed
RETRY="30"
while [ $RETRY -gt 0 ]; do
bash -c "$COMMAND" &> /dev/null
if [ $? -eq 0 ]; then
break
else
if [ "$RETRY" == 1 ]; then
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE down &> /dev/null
kill -9 $SPINPID &> /dev/null
fail "There was a problem starting service /federated/apps/$SERVICE\nCheck the output of 'docker logs $SERVICE' or turn on\ndebug with -d"
fi
((RETRY--))
sleep 7
fi
done
}
start_service() {
SERVICE="$1"
COMMAND="$2"
# Start /federated/apps/SERVICE with output to /dev/null
echo -ne "\n* Starting /federated/apps/$SERVICE service.."
spin &
SPINPID=$!
if [ $DEBUG ]; then
# Start /federated/apps/SERVICE with output to console for debug
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE up
[ $? -eq 0 ] && echo -ne "done.\n" || fail "There was a problem starting service /federated/apps/$SERVICE"
else
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE up -d &> /dev/null
# Keep trying service port to make sure it's up before
# we proceed
RETRY="30"
while [ $RETRY -gt 0 ]; do
bash -c "$COMMAND" &> /dev/null
if [ $? -eq 0 ]; then
break
else
if [ "$RETRY" == 1 ]; then
docker-compose -f /federated/apps/$SERVICE/docker-compose.yml -p $SERVICE down &> /dev/null
kill -9 $SPINPID &> /dev/null
fail "There was a problem starting service /federated/apps/$SERVICE\nCheck the output of 'docker logs $SERVICE' or turn on\ndebug with -d"
fi
((RETRY--))
sleep 7
fi
done
fi
}
print_details() {
cat > /federated/apps/mail/data/root/certs/mailfile <<EOF
<html>
<img src="https://www.federated.computer/images/logo.png" alt="" /><br>
Dear Federated Customer,
<p>
Please read this note in its entirety since it has some important information for you to get the best use
out of your Federated Core. If you ever get stuck for whatever reason, please reach out to us for support:
<p>
Website: https://support.federated.computer<br>
Phone: 970-722-8715<br>
Email: support@federated.computer<br>
<p>
We are here to help you, the Customer, get the most out of your Federated Core.
<p>
All documentation for your Federated Core can be found at https://documentation.federated.computer.
There you'll find tips for how to use your Core and the various open source applications bundled with
your Core.
<p>
<h4>Temporary Domain Information</h4>
This is your temporary domain: <b>$DOMAIN</b>
<p>
Please do not change the admin password (see below) until you switch to your own domain name (example: mustache.com).
We use the admin account and password to re-configure all the software for your new/permanent domain. If you have a
question about this, send us an email to support@federated.computer.
<p>
When you are ready to switch over to your own domain.com send us an e-mail at support@federated.computer
and let us know you are done migrating your data, have pointed DNS correctly, and ready for us to switch.
We also need to know the domain.com you will be using.
<p>
For setting DNS records you will need to set your glue records for your own domain.com to be:
<p>
ns1.domain.com - $EXTERNALIP<br>
ns2.domain.com - $EXTERNALIP<br>
<p>
<p>
</p>
And your authoritative:
<p>
ns1.domain.com<br>
ns2.domain.com<br>
<p>
<p>
The information in this document is for the Admin User only. See the documentation pages for
information pertinent to users.
<p>
Here are some quick pointers...
<p>
<h4>Admin User</h4>
<p>
There is an "admin" or administrative use that has access to everything on your Federated Core.
Here are the credentials for that user.
<p>
Username = "admin@$DOMAIN"<br>
Password = "$ADMINPASS"
<p>
<h4>Panel: <a href="https://panel.$DOMAIN">https://panel.$DOMAIN</a></h4>
Your control panel for users for most Federated Core apps and services is here: https://panel.$DOMAIN
<p>
You will log in with the admin user and, then change the password (and ideally, save this
password in a safe place for later reference).
<p>
You can also use Panel to create new user accounts for your team. You are able to give certain users
administrative privileges, but please be cautious.
<p>
Additional documentation about the Panel service can be found at
https://documentation.federated.computer/docs/core_applications/panel.
<p>
Username: admin<br>
Password: as given above
<p>
<h4>Email</h4>
<p>
Your email server has the following settings you can use to configure mobile and desktop email clients.
Note: Nextcloud (next section) has a built-in web email client.
<p>
SMTP: Server is "mail.$DOMAIN" and the port is "465".<br>
IMAP: Server is "mail.$DOMAIN" and the port is "993".
<p>
The user and password for the email service is the full email address and password as assigned in Panel
(see above). Example: rupert@federated.computer, 56tasty23.
<p>
<h4>Nextcloud: <a href="https://nextcloud.$DOMAIN">https://nextcloud.$DOMAIN</a></h4>
<p>
The Nextcloud suite (replacing Google Workplace) provides apps and services covering mail, calendar,
contacts, notes, tasks, files, word processing, spreadsheets, slides, project management (deck),
bookmarks, forms, team talk, pictures, and an activity monitor. The user and password for the Nextcloud
service is the full email address and password as assigned in Panel (see above). Example:
rupert@federated.computer, 56tasty23.
<p>
There are a number of additional clients for mobile and desktop that work with Nextcloud. Please see
https://documentation.federated.computer/docs/core_applications/nextcloud for more information and
other documentation.
<p>
Username: admin@$DOMAIN<br>
Password: As given above
<p>
<h4>Jitsi: <a href="https://jitsi.$DOMAIN">https://jitsi.$DOMAIN</a></h4>
<p>
Jitsi (your Zoom replacement) is a wonderful video conferencing solution. We have tuned Jitsi so that
your Core can handle video conferences up to 8 people. NOTE, at this time the user and password for
the Jitsi service is the user name ALONE and password as assigned in Panel (see above).
Example: rupert, 56tasty23.
<p>
There are a number of mobile and desktop clients available to use with the Jitsi service. Please see
https://documentation.federated.computer/docs/core_applications/jitsi for more information and other
documentation.
<p>
User administration is done in Panel.
<p>
<h4>Element: <a href="https://element.$DOMAIN">https://element.$DOMAIN</a></h4>
<p>
Element (a Slack replacement) provides you the ability to chat and communicate in real time with those
on your team and outside of the team.
<p>
There are a number of mobile and desktop clients available to use with the Element service. Please see
https://documentation.federated.computer/docs/core_applications/element for more information and
other documentation.
<p>
There are three services that do not use panel for user creation and management. This is for various
reasons either based on the constraints for the current application, or so that you can share the system
easily with users outside your team without giving access to other parts of your system.
<p>
User administration is done in Panel.
<p>
<h4>Listmonk: <a href="https://listmonk.$DOMAIN">https://listmonk.$DOMAIN</a></h4>
<p>
Listmonk is a replacement for Mailchimp. You can set up and manage the email lists used to send out
marketing messages. Listmonk is a single-user system and the user name is "listmonk@$DOMAIN"
and the password is $LISTMONKPASS. For more information, please see the documentation
https://documentation.federated.computer/docs/core_applications/listmonk.
<p>
There is only one user for Listmonk:
<p>
Listmonk Admin User: listmonk<br>
Listmonk Admin Password: $LISTMONKPASS
<p>
<h4>Baserow: <a href="https://baserow.$DOMAIN">https://baserow.$DOMAIN</a></h4>
<p>
Baserow (an Airtable replacement) allows you to create customized databases without writing any code.
It's an amazing product. The user authentication for Baserow is separate from Federated Core. To set up
the service, log in as your "admin@$DOMAIN" user with the correct password. You invite other users
to Baserow within the service. For more information, please see the documentation
https://documentation.federated.computer/docs/core_applications/baserow.
<p>
User administration is done in Baserow, but you set up users using the admin user given above.
<p>
<h4>Vaultwarden: <a href="https://vaultwarden.$DOMAIN">https://vaultwarden.$DOMAIN</a></h4>
<p>
Vaultwarden is a password manager. The user authentication for Vaultwarden is separate from Federated
Core. To set up the service, create an account as your "admin@$DOMAIN" user with the
password for the "admin user". You invite other users to Vaultwarden within the service itself. For more
information, please see the documentation
https://documentation.federated.computer/docs/core_applications/vaultwarden.
<p>
User administration is done in Vaultwarden, but you set up users using the admin user created above.
<p>
<h4>Gitea: <a href="https://gitea.$DOMAIN">https://gitea.$DOMAIN</a></h4>
<p>
Gitea is a code repository and management system similar to Github. The user authentication is seperate
from Federated Core. To set up the service, log in as your "gitea@$DOMAIN" user with the
admin password. You invite other users to Gitea within the service itself. For more information, please
see the documentation https://documentation.federated.computer/docs/core_applications/gitea.
<p>
User administration is done in Gitea.
<p>
Gitea Admin User: gitea<br>
Gitea Admin Password: as given above
<p>
<h4>VPN</h4>
<p>
Your core comes with a very powerful VPN solution built using Wireguard. The following information is
needed by your users so that they can use the VPN. Please treat this information as a very important
secret. There are two important parts of the VPN:
<p>
Here is your VPN configuration:<br>
<br>
<code>
EOF
sed "s/$/<br>/" /federated/apps/wireguard/data/config/peer1/peer1.conf >> /federated/apps/mail/data/root/certs/mailfile
cat >> /federated/apps/mail/data/root/certs/mailfile <<EOF
</code>
<br>
Additional information about the VPN service can be found at https://documentation.federated.computer/docs/core_applications/vpn.
<p>
<h4>PowerDNS: <a href="https://powerdns.$DOMAIN">https://powerdns.$DOMAIN</a></h4>
<p>
PowerDNS is a very powerful tool for managing the DNS of your domain. Your Federated Core has a number of sub-
domains pre-configured (examples: mail.domain.com, jitsi.domain.com, etc.) But if you need another
sub-domain to connect your domain to a service outside your Federated Core, use PowerDNS.
<p>
Additional information about the PowerDNS service can be found at
https://documentation.federated.computer/docs/core_applications/powerdns.
<p>
Only the admin user has access to this service.
<p>
Username: admin<br>
Password: as given above<br>
<p>
<h4>Thanks for your support!</h4>
<p>
Thank you for your support of Federated Computer. We really appreciate it and hope you have a very successful
time with Federated Core.
<p>
Again, if we can be of any assistance, please don't hesitate to get in touch. Don't hesitate.
<p>
Support: https://support.federated.computer<br>
Phone: (970) 722-8715<br>
Email: support@federated.computer<br>
<p>
It's <b>your</b> computer. Let's make it work for you!
EOF
# Send out e-mail from mail container with details
docker exec mail bash -c "mail -r admin@$DOMAIN -a \"Content-type: text/html\" -s \"Welcome to Federated\" admin@$DOMAIN < /root/certs/mailfile"
docker exec mail bash -c "mail -r admin@$DOMAIN -a \"Content-type: text/html\" -s \"Welcome to Federated\" $EMAIL < /root/certs/mailfile"
cat /federated/apps/mail/data/root/certs/mailfile
rm /federated/apps/mail/data/root/certs/mailfile
}
check_docker() {
OSRELEASE=`lsb_release -a 2>/dev/null | grep ID | awk -F: '{ print $2 }' | xargs`
echo -ne "\n* Updating OS with the latest patches.."
spin &
SPINPID=$!
# Update OS with latest patches
sudo apt-get update -y &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run sudo apt-get update"
NEEDRESTART_MODE=a apt-get upgrade -y &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run sudo apt-get upgrade"
kill -9 $SPINPID &> /dev/null
echo -ne "done."
# Install docker if not found
if ! command -v docker &> /dev/null; then
echo -ne "\n* Couldn't find docker, installing.."
spin &
SPINPID=$!
# Install Docker on Ubuntu
if [ $OSRELEASE == "Ubuntu" ]; then
# Update list of packages
sudo apt-get update -y &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run sudo apt-get update"
# Install packages which let apt use packages over HTTPS
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run sudo apt install for https packages"
# Add GPG key for the official Docker repository to this system
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run curl to add Docker GPG key"
# Add the docker repository to our APT sources list
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu jammy stable" -y &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run sudo add-apt-repository"
# Install docker packages
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-compose -y &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run sudo apt install docker packages"
# Install extra packages
sudo apt-get install duplicity python3-b2sdk uuid apache2-utils -y &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't run sudo apt install extra packages"
# Install Traefik certs dumper
curl -sfL https://raw.githubusercontent.com/ldez/traefik-certs-dumper/master/godownloader.sh | bash -s -- -b $(go env GOPATH 2>/dev/null)/bin v2.8.1 &> /dev/null
[ $? -ne 0 ] && failcheck "Couldn't install traefik certs dumper"
fi
kill -9 $SPINPID &> /dev/null
echo -ne "done."
fi
if ! command -v docker-compose &> /dev/null; then
echo -ne "\n* Couldn't find docker-compose, installing.."
spin &
SPINPID=$!
# Install Docker compose on Ubuntu
if [ $OSRELEASE == "Ubuntu" ]; then
sudo apt-get install docker-compose -y &> /dev/null
fi
kill -9 $SPINPID &> /dev/null
echo -ne "done."
fi
}
check_ports() {
EXTERNALIP=`dig @resolver4.opendns.com myip.opendns.com +short 2> /dev/null`
[ $? -ne 0 ] && failcheck "Couldn't run dig, dns is not working"
# Check if ss command exists
if command -v ss &> /dev/null; then
# Check every port we need if it's in use (only if we have never run before)
if [ $(ls /federated/apps | wc -l) -eq "0" ]; then
for i in 25 53 80 143 389 587 993 8000; do
SS=`ss -tulwn | grep LISTEN | awk '{ print $5 }' | awk -F: '{ print $NF }' | grep "^$i$" | head -1`
# If port 53 (dns) in use by system-resolvd (Ubuntu) then auto fix
if [ "$SS" == 53 ]; then
if [ $OSRELEASE == "Ubuntu" ]; then
if [ `pgrep -x systemd-resolve` ]; then
echo -ne "\n* Port 53 in use by systemd-resolved, fixing.."
spin &
SPINPID=$!
# Install resolvconf to fix
sudo apt install resolvconf -y &> /dev/null
[ $? -eq 0 ] && echo -ne "." || failcheck "Failed running sudo apt install resolvconf"
# Shut down systemd-resolved
systemctl stop systemd-resolved &> /dev/null
[ $? -ne 0 ] && failcheck "Failed running systemctl stop systemd-resolved"
systemctl disable systemd-resolved &> /dev/null
[ $? -ne 0 ] && failcheck "Failed running systemctl stop systemd-resolved"
# Put nameserver entries so will exist on reboot
rm /etc/resolv.conf
echo "nameserver 1.1.1.1" >> /etc/resolv.conf
echo "nameserver 1.0.0.1" >> /etc/resolv.conf
kill -9 $SPINPID &> /dev/null
echo -ne "done."
else
echo -ne "\nFAILED - Port 53 (dns) is already in use\n\n" && exit 2
fi
fi
elif [ "$SS" == "$i" ]; then
failcheck "FAILED - Port $i is already in use"
fi
done
fi
fi
}
check_os() {
VERSIONID=`grep "VERSION_ID=" /etc/os-release | awk -F\" '{ print $2 }'`
if [ "$VERSIONID" != "22.04" ]; then
echo -ne "\nFederated requires a minimum of 4G of RAM and 25G of storage\n \
running Ubuntu 22.04 LTS. Your system is not supported. Please contact\n \
Federated @ support@federated.computer for assistance or choose our\n \
cloud offerings at https://cloud.federated.computer.\n\n"
exit 2;
fi
}
check_memory() {
MEMTOTAL=`awk '/MemTotal/ { printf "%.3d \n", $2/1024 }' /proc/meminfo`
if [ "$MEMTOTAL" -lt "3700" ]; then
echo -ne "\nFederated requires a minimum of 4G of RAM and 25G of storage\n \
running Ubuntu 22.04 LTS. Your system is not supported. Please contact\n \
Federated @ support@federated.computer for assistance or choose our\n \
cloud offerings at https://cloud.federated.computer.\n\n"
exit 2;
fi
}