How to: Shrink/Reclaim free virtual disk space from Virtual Machines on Proxmox VE (PVE) (Windows/Linux/Debian/Ubuntu/Kali Linux/RHEL/CentOS/Fedora etc.)

Pre-requirements

Following method only works for virtual machines (VM) that are satisfying these pre-requirements:

  • Thin-provisioned backing storage (qcow2 disk, thin-lvm, zfs, …)
  • Virtio-SCSI controller configured on guest.
  • Guest scsi disks with the discard option enabled [1]

Note: While changing provisioning types and Virtio-SCSI driver are not easy with existing virtual machines, but changing VM scsi disk’s discard options is simple, that means, if we appear to have an existing VM that is using thin-provisioned backing storage and Virtio-SCSI but “discard” options is not enabled/checked, we can simply find that VM and check that option, then we are good to follow the reset of this guide.

The Issue

When we are using qcow2 sparse virtual disks, we can reclaim free disk spaces which are not using by the virtual machine. How to trigger the VM/guest operating system to reclaim it for us though?

The Fix

1 Login to Proxmox VE web gui

2 Find the VM we want to reclaim the unused disk space for and click on it

3 Click on Hardware

4 Double click on the virtual hard’s virtual hard drive we want to reclaim unused space for

5 Make sure the “Discard” is checked

Proxmox VE - Discard option
Proxmox VE – Discard option

6 Start the VM

Once the VM is fully booted

6a For Linux/Debian/Ubuntu/Kali Linux/CentOS/RHEL/Fedora etc.

6a.1 We use following command to reclaim the unused disk space from a terminal

sudo fstrim -av

Once it’s done, we should be able to see the reclaimed disk space from Proxmox VE host (Only if there is unused space, if there is no unused space, we will not see any changes from Proxmox VE host’s disk space)

6a.2 We can also enable the automatic fstrim from the VM, so we do not need to do it manually everytime. Use following command to enable this feature

sudo systemctl enable fstrim.timer

6b For Windows

Usually the trim is enabled by default on Windows (Windows 7/2008R2 and up), we should not need to modify anything.

We can check if TRIM is enabled or not by using following command

fsutil behavior query DisableDeleteNotify

The output should be 0, otherwise, we can set it manually

fsutil behavior set DisableDeleteNotify 0

We can also trigger it manually, here is how.

First, we need to shutdown the Windows VM.

Then from the Proxmox VE web gui, find the Windows VM, Navigate to “Hardware”, double click on the virtual hard drive that we want to reclaim unused space from, make sure the “Discard” and “SSD emulation” are both checked, now start the Windows VM

Proxmox VE - Discard and SSD emulation checked
Proxmox VE – Discard and SSD emulation checked

When the Windows booted, we type “defrag” in start menu to search for “Defragment and Optimize Drives” program.

Windows 10 - Defragment and Optimize Drives
Windows 10 – Defragment and Optimize Drives

Click on it to launch it, then select the drive which we want to claim unused space from, click on “Optimize” button.

We now have manually reclaimed unused space from Windows VM

References

[1] “Shrink Qcow2 Disk Files – Proxmox VE”, Pve.proxmox.com, 2019. [Online]. Available: https://pve.proxmox.com/wiki/Shrink_Qcow2_Disk_Files


How to: Upload ISO files to Proxmox VE (PVE)

So that we can install operating systems 😉

1 Login to Proxmox VE web gui

2 Find the storage with “ISO image” listed in “Content”

Proxmox VE Storage View
Proxmox VE Storage View
Proxmox VE Storage View - Summary - Content - ISO Image
Proxmox VE Storage View – Summary – Content – ISO Image

Note: If the “ISO image” is not listed, that means we can not upload ISO to that storage location (Usually we can create an folder on that storage, make sure “ISO image” is selected during Directory creation, then we can upload ISO files to that storage location)

3 Click on “Content”

4 Click on “Upload” button at the top

5 Make sure “ISO image” is selected for “Content”

6 Now we can upload ISO files to Proxmox VE

7 The rest will be easy, click on “Select File…” button, select the ISO file we want to upload, click on “Upload” button to begin uploading, do not close the page until it’s finished

8 Now we can start to use it to install operating system.


How to: Fix “You have to delete all the items in this folder before you can delete the folder” Error from SharePoint/OneDrive

The Issue

When trying to delete a folder from Microsoft 365/Office 365 SharePoint, we get following error (Even if we are using Microsoft 365 Global Administrator)

You have to delete all the items in this folder before you can delete the folder

Microsoft 365, SharePoint, Delete folder error
Microsoft 365, SharePoint, Delete folder error

The Fix

There are two workarounds, 1 one is manually dive into the folders, till we open the last and innermost folder, then delete our way to outermost/top-level folders, 2 another way is fix this error completely but there will be cons as well.

Method 1 – Manually (Old fashioned/Classic way)

This method is easiest and suitable for deleting small amount of folders and files (e.g. 5 folders in hierarchy structure), if you have more than 10 level of folders like Matryoshka doll below or even 10 folder and each of them has 20 folders in hierarchy structure…..

Matryoshka doll
Matryoshka doll

You probably would not want to delete them one by one manually, then let’s look at method 2 below

Method 2 – Disable File Retention

This method is easy to deal with, disable file retention for everything in Microsoft 365 or just for SharePoint (It depends on you Retention rules), the con for this method is that, Warning: If anyone or any users is deleting folders or any files during this period with Retention off, then those folder and files will not be protected by the Retention rules, since it is off. Here is how to do it

1 Login to Microsoft 365 admin center

2 Click on “Security” to open “Office 365 Security & Compliance

3 Navigate to “Information governance” -> “Retention” from left hand side menu bar

4 From right hand side, find the Retention policy which is protecting SharePoint or OneDrive or both, or maybe 1 rule is protecting everything Microsoft 365, again it depends on your settings.

5 Click on that rule, Find the switch underneath “Status”, switch it off.

6 Wait for 10 – 60 minutes

7 Try to delete the folder which contains many sub folders and files, this time it should proceed without any errors.

8 Remember to turn the Retention switch back on!!! (If you still want to use it)


How to: Approve/Reject/Moderate email from new Microsoft 365/Office 365 web Outlook/Online/Outlook/web mail on Mobile devices/phones

The Issue

We used to be able to Moderate/Approve/Reject emails within the web version of Outlook from mobile phones/devices, the new web version of Outlook on mobile phone/device however, removed that feature

The Fix

So how do we approve or reject emails now?

Here is how

No matter you are using Android or iOS devices, most mobile devices’ browsers should support “Desktop Version”

As you guessed, here is how to approve/reject emails from our mobile phone or devices

1 Use the native web browser from our mobile device, or we can use third-party browser by installing the browser app e.g. Mozilla Firefox

2 Login to https://office.com with your Microsoft 265 account

3 Click on “Outlook”

4 Now we will see the mobile web version of Outlook will appear

5 Find “…” or “Aa” etc. or Settings…

6 Find the option to “Switch to Desktop Version” or “Desktop Version” or similar options

7 Now we will be able to Approve/Reject the email


How to: Fix Proxmox VE 6.2.1 Installation error: unknown filesystsms

The Error

error: unknown file systems
error: unknown file systems
error: unknown file systems
error: unknown file systems

Entering rescue mode…

grub rescue>

Usually it appears when we are booting from Proxmox installation USB with a PC/Desktop which is using UEFI and the USB was created via Rufus on Windows.

The Fix

1 Download balenaEtcher (Alternative to Rufus)

2 Use balenaEtcher to load the Proxmox VE iso and make sure only the desired USB flash drive is selected, create the bootable media.

3 Use the Proxmox USB as the same way as we did last time

4 It should load Proxmox installation correctly


How to: Fix Proxmox VE/ZFS Pool extremely slow write performance issue

The Issue

If we just create the ZFS pool from Proxmox gui, then start to use it. (Especially for HDDs)

e.g. We write large datasets continuously.

Sooner or later (Depend on the ZFS pool usage), we will find out that the writes is around 1-10MB/s, which is extremely slow.

(Note: If we test the newly created pool with and without ZIL/SLOG, there probably won’t be much difference or even slower with ZIL/SLOG deice attached, after we have data filled in the ZFS pool, the pool with dedicate ZIL/SLOG device will perform better than the pool without dedicate ZIL/SLOG device)

The Fix

This can happen due to ZFS Intent Log (ZIL)/Separate ZFS Intent Log (SLOG) is getting written to the same ZFS data pool which all our data are stored, which eventually caused “double write” issue.

To fix this issue is easy, best way to fix it properly is to grab a SSD, worst case, if we do not have one temporary, we can even grab a 5400RPM or 7200RPM HDD use it via HBA/SATA/SCSI or even USB 3.0 (USB is not suitable for long term for this purpose, but can work fine as an temporary solution/fix).

Once attached the disk to the Proxmox host, note down the device name e.g. /dev/sde, /dev/sdf etc.

Login to terminal from Proxmox host or via SSH or via Shell from web gui.

Use following command to use an dedicated HDD/SSD for ZIL/SLOG purpose

# For single HDD/SSD
zpool add -f [pool name] log [device name]
# e.g.
zpool add -f rpool log /dev/sdd
 
# For mirrored ZIL/SLOG
zpool add -f [pool name] log mirror [device 1 name] [device 2 name]
# e.g.
zpool add -f rpool log mirror /dev/sdd /dev/sde

Now if we have a look at write performance, it will be increased

Note: Best device for ZIL/SLOG is to have an datacenter grade SSD via HBA, so that we can get best performance

To check the pool status see if the ZIL/SLOG device is added use following commands

zpool status

Bonus

Read/Write buffer/cache for ZFS pool

With ZFS pool

  • SLOG is used to cache synchronous ZIL data (Write) before flushing to disk (Write performance related)
  • Adaptive Replacement Cache (ARC) and Second level adaptive replacement cache (L2ARC) are used to cache reads (Read performance related)

How big should the ZIL/SLOG device/HDD/SSD be?

Usually the ZIL is default to flush every 5 seconds or when it reaches capacity, which means a SLOG that holds 5-10 seconds’ worth of the pool maximum throughput will be find, unless we are doing something extreme/special.

How to Remove/Delete/Replace ZIL/SLOG device/HDD/SSD?

# To remove ZIL/SLOG device
zpool remove [pool name] [device name]
# e.g.
zpool remove rpool /dev/sdd
or
zpool remove rpool sdd
or
# By using real device name
zpool remove rpool ata-xxxx_xxxx_Xxxx_x.....

To replace, simply remove the current HDDs/SSDs then add new disks again

How to find out real device name from Proxmox?

Refer to this guide: How to: Find drive name (real name) for /dev/sdb /dev/sdc from Proxmox (PVE)

How to add read cache disks?

It is very similar to adding ZIL/SLOG drives

zpool add -f [pool name] cache [device name]
# e.g.
zpool add -f rpool /dev/sde
 
# Remove cache disk
zpool remove [pool name] [device name]
zpool remove rpool /dev/sde
zpool remove rpool /sde

If the above remove command does not work, try remove the ZIL/SLOG first


How to: Install Apache Guacamole on Debian/Ubuntu the easiest way (Clientless Remote Desktop Gateway)

1 Update the system with following commands

sudo apt update
sudo apt upgrade -y

2 Install Apache Guacamole

2.1 Download the .sh file [1]

wget -O guac-install.sh https://git.io/fxZq5

2.2 Make it executable

chmod +x guac-install.sh

2.2 Run the script as root

  • (Interactive (asks for passwords))
./guac-install.sh
  • Non-Interactive (values provided via cli):
./guac-install.sh --mysqlpwd password --guacpwd password --nomfa --installmysql

OR

./guac-install.sh -r password -gp password -o -i

Once installed, we can access Guacamole by open: http://:8080/guacamole/ The default credentials are guacadmin as both username and password. Please change them or disable guacadmin after install!

(For upgrade, refer to the link in the reference.)

Appendices

The content of the script (as of writing)

#!/bin/bash
# Something isn't working? # tail -f /var/log/messages /var/log/syslog /var/log/tomcat*/*.out /var/log/mysql/*.log
# Check if user is root or sudo
if ! [ $( id -u ) = 0 ]; then
    echo "Please run this script as sudo or root" 1>&2
    exit 1
fi
# Check to see if any old files left over
if [ "$( find . -maxdepth 1 \( -name 'guacamole-*' -o -name 'mysql-connector-java-*' \) )" != "" ]; then
    echo "Possible temp files detected. Please review 'guacamole-*' & 'mysql-connector-java-*'" 1>&2
    exit 1
fi
# Version number of Guacamole to install
# Homepage ~ https://guacamole.apache.org/releases/
GUACVERSION="1.1.0"
# Latest Version of MySQL Connector/J if manual install is required (if libmariadb-java/libmysql-java is not available via apt)
# Homepage ~ https://dev.mysql.com/downloads/connector/j/
MCJVER="8.0.19"
# Colors to use for output
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Log Location
LOG="/tmp/guacamole_${GUACVERSION}_build.log"
# Initialize variable values
installTOTP=""
installDuo=""
installMySQL=""
mysqlHost=""
mysqlPort=""
mysqlRootPwd=""
guacDb=""
guacUser=""
guacPwd=""
PROMPT=""
MYSQL=""
# Get script arguments for non-interactive mode
while [ "$1" != "" ]; do
    case $1 in
        # Install MySQL selection
        -i | --installmysql )
            installMySQL=true
            ;;
        -n | --nomysql )
            installMySQL=false
            ;;
        # MySQL server/root information
        -h | --mysqlhost )
            shift
            mysqlHost="$1"
            ;;
        -p | --mysqlport )
            shift
            mysqlPort="$1"
            ;;
        -r | --mysqlpwd )
            shift
            mysqlRootPwd="$1"
            ;;
        # Guac database/user information
        -db | --guacdb )
            shift
            guacDb="$1"
            ;;
        -gu | --guacuser )
            shift
            guacUser="$1"
            ;;
        -gp | --guacpwd )
            shift
            guacPwd="$1"
            ;;
        # MFA selection
        -t | --totp )
            installTOTP=true
            ;;
        -d | --duo )
            installDuo=true
            ;;
        -o | --nomfa )
            installTOTP=false
            installDuo=false
            ;;
    esac
    shift
done
if [[ -z "${installTOTP}" ]] && [[ "${installDuo}" != true ]]; then
    # Prompt the user if they would like to install TOTP MFA, default of no
    echo -e -n "${CYAN}MFA: Would you like to install TOTP? (y/N): ${NC}"
    read PROMPT
    if [[ ${PROMPT} =~ ^[Yy]$ ]]; then
        installTOTP=true
        installDuo=false
    else
        installTOTP=false
    fi
fi
if [[ -z "${installDuo}" ]] && [[ "${installTOTP}" != true ]]; then
    # Prompt the user if they would like to install Duo MFA, default of no
    echo -e -n "${CYAN}MFA: Would you like to install Duo (configuration values must be set after install in /etc/guacamole/guacamole.properties)? (y/N): ${NC}"
    read PROMPT
    if [[ ${PROMPT} =~ ^[Yy]$ ]]; then
        installDuo=true
        installTOTP=false
    else
        installDuo=false
    fi
fi
# We can't install TOTP and Duo at the same time...
if [[ "${installTOTP}" = true ]] && [ "${installDuo}" = true ]; then
    echo -e "${RED}MFA: The script does not support installing TOTP and Duo at the same time.${NC}" 1>&2
    exit 1
fi
echo
if [[ -z ${installMySQL} ]]; then
    # Prompt the user to see if they would like to install MySQL, default of yes
    echo "MySQL is required for installation, if you're using a remote MySQL Server select 'n'"
    echo -e -n "${CYAN}Would you like to install MySQL? (Y/n): ${NC}"
    read PROMPT
    if [[ ${PROMPT} =~ ^[Nn]$ ]]; then
        installMySQL=false
    else
        installMySQL=true
    fi
fi
if [ "${installMySQL}" = false ]; then
    # We need to get additional values
    [ -z "${mysqlHost}" ] \
      && read -p "Enter MySQL server hostname or IP: " mysqlHost
    [ -z "${mysqlPort}" ] \
      && read -p "Enter MySQL server port [3306]: " mysqlPort
    [ -z "${guacDb}" ] \
      && read -p "Enter Guacamole database name [guacamole_db]: " guacDb
    [ -z "${guacUser}" ] \
      && read -p "Enter Guacamole user [guacamole_user]: " guacUser
fi
# Checking if mysql host given
if [ -z "${mysqlHost}" ]; then
    mysqlHost="localhost"
fi
# Checking if mysql port given
if [ -z "${mysqlPort}" ]; then
    mysqlPort="3306"
fi
# Checking if mysql user given
if [ -z "${guacUser}" ]; then
    guacUser="guacamole_user"
fi
# Checking if database name given
if [ -z "${guacDb}" ]; then
    guacDb="guacamole_db"
fi
if [ -z "${mysqlRootPwd}" ]; then
    # Get MySQL "Root" and "Guacamole User" password
    while true; do
        echo
        read -s -p "Enter ${mysqlHost}'s MySQL root password: " mysqlRootPwd
        echo
        read -s -p "Confirm ${mysqlHost}'s MySQL root password: " PROMPT2
        echo
        [ "${mysqlRootPwd}" = "${PROMPT2}" ] && break
        echo -e "${RED}Passwords don't match. Please try again.${NC}" 1>&2
    done
else
    echo -e "${BLUE}Read MySQL root's password from command line argument${NC}"
fi
echo
if [ -z "${guacPwd}" ]; then
    while true; do
        echo -e "${BLUE}A new MySQL user will be created (${guacUser})${NC}"
        read -s -p "Enter ${mysqlHost}'s MySQL guacamole user password: " guacPwd
        echo
        read -s -p "Confirm ${mysqlHost}'s MySQL guacamole user password: " PROMPT2
        echo
        [ "${guacPwd}" = "${PROMPT2}" ] && break
        echo -e "${RED}Passwords don't match. Please try again.${NC}" 1>&2
        echo
    done
else
    echo -e "${BLUE}Read MySQL ${guacUser}'s password from command line argument${NC}"
fi
echo
if [ "${installMySQL}" = true ]; then
    # Seed MySQL install values
    debconf-set-selections <<< "mysql-server mysql-server/root_password password ${mysqlRootPwd}"
    debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${mysqlRootPwd}"
fi
# Different version of Ubuntu and Debian have different package names...
source /etc/os-release
if [[ "${NAME}" == "Ubuntu" ]]; then
    # Ubuntu > 18.04 does not include universe repo by default
    # Add the "Universe" repo, don't update
    add-apt-repository -yn universe
    # Set package names depending on version
    JPEGTURBO="libjpeg-turbo8-dev"
    if [[ "${VERSION_ID}" == "16.04" ]]; then
        LIBPNG="libpng12-dev"
    else
        LIBPNG="libpng-dev"
    fi
    if [ "${installMySQL}" = true ]; then
        MYSQL="mysql-server mysql-client mysql-common"
    # Checking if (any kind of) mysql-client or compatible command installed. This is useful for existing mariadb server
    elif [ -x "$( command -v mysql )" ]; then
        MYSQL=""
    else
        MYSQL="mysql-client"
    fi
elif [[ "${NAME}" == *"Debian"* ]] || [[ "${NAME}" == *"Raspbian GNU/Linux"* ]] || [[ "${NAME}" == *"Kali GNU/Linux"* ]]; then
    JPEGTURBO="libjpeg62-turbo-dev"
    if [[ "${PRETTY_NAME}" == *"stretch"* ]] || [[ "${PRETTY_NAME}" == *"buster"* ]] || [[ "${PRETTY_NAME}" == *"Kali GNU/Linux Rolling"* ]]; then
        LIBPNG="libpng-dev"
    else
        LIBPNG="libpng12-dev"
    fi
    if [ "${installMySQL}" = true ]; then
        MYSQL="default-mysql-server default-mysql-client mysql-common"
    # Checking if (any kind of) mysql-client or compatible command installed. This is useful for existing mariadb server
    elif [ -x "$( command -v mysql )" ]; then
        MYSQL=""
    else
        MYSQL="default-mysql-client"
    fi
else
    echo "Unsupported distribution - Debian, Kali, Raspbian or Ubuntu only"
    exit 1
fi
# Update apt so we can search apt-cache for newest Tomcat version supported &amp; libmariadb-java/libmysql-java
echo -e "${BLUE}Updating apt...${NC}"
apt-get -qq update
# Check if libmariadb-java/libmysql-java is available
# Debian 10 >= ~ https://packages.debian.org/search?keywords=libmariadb-java
if [[ $( apt-cache show libmariadb-java 2> /dev/null | wc -l ) -gt 0 ]]; then
    # When something higher than 1.1.0 is out ~ https://issues.apache.org/jira/browse/GUACAMOLE-852
    #echo -e "${BLUE}Found libmariadb-java package...${NC}"
    #LIBJAVA="libmariadb-java"
    # For v1.1.0 and lower
    echo -e "${YELLOW}Found libmariadb-java package (known issues). Will download libmysql-java ${MCJVER} and install manually${NC}"
    LIBJAVA=""
# Debian 9 <= ~ https://packages.debian.org/search?keywords=libmysql-java
elif [[ $( apt-cache show libmysql-java 2> /dev/null | wc -l ) -gt 0 ]]; then
    echo -e "${BLUE}Found libmysql-java package...${NC}"
    LIBJAVA="libmysql-java"
else
    echo -e "${YELLOW}lib{mariadb,mysql}-java not available. Will download mysql-connector-java-${MCJVER}.tar.gz and install manually${NC}"
    LIBJAVA=""
fi
# tomcat9 is the latest version
# tomcat8.0 is end of life, but tomcat8.5 is current
# fallback is tomcat7
if [[ $( apt-cache show tomcat9 2> /dev/null | egrep "Version: 9" | wc -l ) -gt 0 ]]; then
    echo -e "${BLUE}Found tomcat9 package...${NC}"
    TOMCAT="tomcat9"
elif [[ $( apt-cache show tomcat8 2> /dev/null | egrep "Version: 8.[5-9]" | wc -l ) -gt 0 ]]; then
    echo -e "${BLUE}Found tomcat8.5+ package...${NC}"
    TOMCAT="tomcat8"
elif [[ $( apt-cache show tomcat7 2> /dev/null | egrep "Version: 7" | wc -l ) -gt 0 ]]; then
    echo -e "${BLUE}Found tomcat7 package...${NC}"
    TOMCAT="tomcat7"
else
    echo -e "${RED}Failed. Can't find Tomcat package${NC}" 1>&amp;2
    exit 1
fi
# Uncomment to manually force a Tomcat version
#TOMCAT=""
# Install features
echo -e "${BLUE}Installing packages. This might take a few minutes...${NC}"
# Don't prompt during install
export DEBIAN_FRONTEND=noninteractive
# Required packages
apt-get -y install build-essential libcairo2-dev ${JPEGTURBO} ${LIBPNG} libossp-uuid-dev libavcodec-dev libavutil-dev \
libswscale-dev freerdp2-dev libpango1.0-dev libssh2-1-dev libtelnet-dev libvncserver-dev libpulse-dev libssl-dev \
libvorbis-dev libwebp-dev libwebsockets-dev \
freerdp2-x11 libtool-bin ghostscript dpkg-dev \
wget crudini \
${MYSQL} ${LIBJAVA} ${TOMCAT} &amp;>> ${LOG}
# If apt fails to run completely the rest of this isn't going to work...
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed. See ${LOG}${NC}" 1>&amp;2
    exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
echo
# Set SERVER to be the preferred download server from the Apache CDN
SERVER="http://apache.org/dyn/closer.cgi?action=download&amp;filename=guacamole/${GUACVERSION}"
echo -e "${BLUE}Downloading files...${NC}"
# Download Guacamole Server
wget -q --show-progress -O guacamole-server-${GUACVERSION}.tar.gz ${SERVER}/source/guacamole-server-${GUACVERSION}.tar.gz
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed to download guacamole-server-${GUACVERSION}.tar.gz" 1>&amp;2
    echo -e "${SERVER}/source/guacamole-server-${GUACVERSION}.tar.gz${NC}"
    exit 1
else
    # Extract Guacamole Files
    tar -xzf guacamole-server-${GUACVERSION}.tar.gz
fi
echo -e "${GREEN}Downloaded guacamole-server-${GUACVERSION}.tar.gz${NC}"
# Download Guacamole Client
wget -q --show-progress -O guacamole-${GUACVERSION}.war ${SERVER}/binary/guacamole-${GUACVERSION}.war
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed to download guacamole-${GUACVERSION}.war" 1>&amp;2
    echo -e "${SERVER}/binary/guacamole-${GUACVERSION}.war${NC}"
    exit 1
fi
echo -e "${GREEN}Downloaded guacamole-${GUACVERSION}.war${NC}"
# Download Guacamole authentication extensions (Database)
wget -q --show-progress -O guacamole-auth-jdbc-${GUACVERSION}.tar.gz ${SERVER}/binary/guacamole-auth-jdbc-${GUACVERSION}.tar.gz
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed to download guacamole-auth-jdbc-${GUACVERSION}.tar.gz" 1>&amp;2
    echo -e "${SERVER}/binary/guacamole-auth-jdbc-${GUACVERSION}.tar.gz"
    exit 1
else
    tar -xzf guacamole-auth-jdbc-${GUACVERSION}.tar.gz
fi
echo -e "${GREEN}Downloaded guacamole-auth-jdbc-${GUACVERSION}.tar.gz${NC}"
# Download Guacamole authentication extensions
# TOTP
if [ "${installTOTP}" = true ]; then
    wget -q --show-progress -O guacamole-auth-totp-${GUACVERSION}.tar.gz ${SERVER}/binary/guacamole-auth-totp-${GUACVERSION}.tar.gz
    if [ $? -ne 0 ]; then
        echo -e "${RED}Failed to download guacamole-auth-totp-${GUACVERSION}.tar.gz" 1>&amp;2
        echo -e "${SERVER}/binary/guacamole-auth-totp-${GUACVERSION}.tar.gz"
        exit 1
    else
        tar -xzf guacamole-auth-totp-${GUACVERSION}.tar.gz
    fi
    echo -e "${GREEN}Downloaded guacamole-auth-totp-${GUACVERSION}.tar.gz${NC}"
fi
# Duo
if [ "${installDuo}" = true ]; then
    wget -q --show-progress -O guacamole-auth-duo-${GUACVERSION}.tar.gz ${SERVER}/binary/guacamole-auth-duo-${GUACVERSION}.tar.gz
    if [ $? -ne 0 ]; then
        echo -e "${RED}Failed to download guacamole-auth-duo-${GUACVERSION}.tar.gz" 1>&amp;2
        echo -e "${SERVER}/binary/guacamole-auth-duo-${GUACVERSION}.tar.gz"
        exit 1
    else
        tar -xzf guacamole-auth-duo-${GUACVERSION}.tar.gz
    fi
    echo -e "${GREEN}Downloaded guacamole-auth-duo-${GUACVERSION}.tar.gz${NC}"
fi
# Deal with missing MySQL Connector/J
if [[ -z $LIBJAVA ]]; then
    # Download MySQL Connector/J
    wget -q --show-progress -O mysql-connector-java-${MCJVER}.tar.gz https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-${MCJVER}.tar.gz
    if [ $? -ne 0 ]; then
        echo -e "${RED}Failed to download mysql-connector-java-${MCJVER}.tar.gz" 1>&amp;2
        echo -e "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-${MCJVER}.tar.gz${NC}"
        exit 1
    else
        tar -xzf mysql-connector-java-${MCJVER}.tar.gz
    fi
    echo -e "${GREEN}Downloaded mysql-connector-java-${MCJVER}.tar.gz${NC}"
else
    echo -e "${YELLOW}Skipping manually installing MySQL Connector/J${NC}"
fi
echo -e "${GREEN}Downloading complete.${NC}"
echo
# Make directories
rm -rf /etc/guacamole/lib/
rm -rf /etc/guacamole/extensions/
mkdir -p /etc/guacamole/lib/
mkdir -p /etc/guacamole/extensions/
# Install guacd (Guacamole-server)
cd guacamole-server-${GUACVERSION}/
echo -e "${BLUE}Building Guacamole-Server with GCC $( gcc --version | head -n1 | grep -oP '\)\K.*' | awk '{print $1}' ) ${NC}"
echo -e "${BLUE}Configuring Guacamole-Server. This might take a minute...${NC}"
./configure --with-init-dir=/etc/init.d  &amp;>> ${LOG}
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed. See ${LOG}${NC}" 1>&amp;2
    exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
echo -e "${BLUE}Running Make on Guacamole-Server. This might take a few minutes...${NC}"
make &amp;>> ${LOG}
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed. See ${LOG}${NC}" 1>&amp;2
    exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
echo -e "${BLUE}Running Make Install on Guacamole-Server...${NC}"
make install &amp;>> ${LOG}
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed. See ${LOG}${NC}" 1>&amp;2
    exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
ldconfig
echo
# Move files to correct locations (guacamole-client &amp; Guacamole authentication extensions)
cd ..
mv -f guacamole-${GUACVERSION}.war /etc/guacamole/guacamole.war
mv -f guacamole-auth-jdbc-${GUACVERSION}/mysql/guacamole-auth-jdbc-mysql-${GUACVERSION}.jar /etc/guacamole/extensions/
# Create Symbolic Link for Tomcat
ln -sf /etc/guacamole/guacamole.war /var/lib/${TOMCAT}/webapps/
# Deal with MySQL Connector/J
if [[ -z $LIBJAVA ]]; then
    echo -e "${BLUE}Moving mysql-connector-java-${MCJVER}.jar (/etc/guacamole/lib/mysql-connector-java.jar)...${NC}"
    mv -f mysql-connector-java-${MCJVER}/mysql-connector-java-${MCJVER}.jar /etc/guacamole/lib/mysql-connector-java.jar
elif [ -e /usr/share/java/mariadb-java-client.jar ]; then
    echo -e "${BLUE}Linking mariadb-java-client.jar  (/etc/guacamole/lib/mariadb-java-client.jar)...${NC}"
    ln -sf /usr/share/java/mariadb-java-client.jar /etc/guacamole/lib/mariadb-java-client.jar
elif [ -e /usr/share/java/mysql-connector-java.jar ]; then
    echo -e "${BLUE}Linking mysql-connector-java.jar  (/etc/guacamole/lib/mysql-connector-java.jar)...${NC}"
    ln -sf /usr/share/java/mysql-connector-java.jar /etc/guacamole/lib/mysql-connector-java.jar
else
    echo -e "${RED}Can't find *.jar file${NC}" 1>&amp;2
    exit 1
fi
echo
# Move TOTP Files
if [ "${installTOTP}" = true ]; then
    echo -e "${BLUE}Moving guacamole-auth-totp-${GUACVERSION}.jar (/etc/guacamole/extensions/)...${NC}"
    mv -f guacamole-auth-totp-${GUACVERSION}/guacamole-auth-totp-${GUACVERSION}.jar /etc/guacamole/extensions/
    echo
fi
# Move Duo Files
if [ "${installDuo}" = true ]; then
    echo -e "${BLUE}Moving guacamole-auth-duo-${GUACVERSION}.jar (/etc/guacamole/extensions/)...${NC}"
    mv -f guacamole-auth-duo-${GUACVERSION}/guacamole-auth-duo-${GUACVERSION}.jar /etc/guacamole/extensions/
    echo
fi
# Configure guacamole.properties
rm -f /etc/guacamole/guacamole.properties
touch /etc/guacamole/guacamole.properties
echo "mysql-hostname: ${mysqlHost}" >> /etc/guacamole/guacamole.properties
echo "mysql-port: ${mysqlPort}" >> /etc/guacamole/guacamole.properties
echo "mysql-database: ${guacDb}" >> /etc/guacamole/guacamole.properties
echo "mysql-username: ${guacUser}" >> /etc/guacamole/guacamole.properties
echo "mysql-password: ${guacPwd}" >> /etc/guacamole/guacamole.properties
# Output Duo configuration settings but comment them out for now
if [ "${installDuo}" = true ]; then
    echo "# duo-api-hostname: " >> /etc/guacamole/guacamole.properties
    echo "# duo-integration-key: " >> /etc/guacamole/guacamole.properties
    echo "# duo-secret-key: " >> /etc/guacamole/guacamole.properties
    echo "# duo-application-key: " >> /etc/guacamole/guacamole.properties
    echo -e "${YELLOW}Duo is installed, it will need to be configured via guacamole.properties${NC}"
fi
# Restart Tomcat
echo -e "${BLUE}Restarting Tomcat service &amp; enable at boot...${NC}"
service ${TOMCAT} restart
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed${NC}" 1>&amp;2
    exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
# Start at boot
systemctl enable ${TOMCAT}
echo
# Set MySQL password
export MYSQL_PWD=${mysqlRootPwd}
if [ "${installMySQL}" = true ]; then
    # Restart MySQL service
    echo -e "${BLUE}Restarting MySQL service &amp; enable at boot...${NC}"
    service mysql restart
    if [ $? -ne 0 ]; then
        echo -e "${RED}Failed${NC}" 1>&amp;2
        exit 1
    else
        echo -e "${GREEN}OK${NC}"
    fi
    # Start at boot
    systemctl enable mysql
    echo
    # Default locations of MySQL config file
    for x in /etc/mysql/mariadb.conf.d/50-server.cnf \
             /etc/mysql/mysql.conf.d/mysqld.cnf \
             /etc/mysql/my.cnf \
             ; do
        # Check the path exists
        if [ -e "${x}" ]; then
            # Does it have the necessary section
            if grep -q '^\[mysqld\]$' "${x}"; then
                mysqlconfig="${x}"
                # no point keep checking!
                break
            fi
        fi
    done
    if [ -z "${mysqlconfig}" ]; then
        echo -e "${YELLOW}Couldn't detect MySQL config file - you may need to manually enter timezone settings${NC}"
    else
        # Is there already a value?
        if grep -q "^default_time_zone[[:space:]]?=" "${mysqlconfig}"; then
            echo -e "${YELLOW}Timezone already defined in ${mysqlconfig}${NC}"
        else
            timezone="$( cat /etc/timezone )"
            if [ -z "${timezone}" ]; then
                echo -e "${YELLOW}Couldn't find timezone, using UTC${NC}"
                timezone="UTC"
            fi
            echo -e "${YELLOW}Setting timezone as ${timezone}${NC}"
            # Fix for https://issues.apache.org/jira/browse/GUACAMOLE-760
            mysql_tzinfo_to_sql /usr/share/zoneinfo 2>/dev/null | mysql -u root -D mysql -h ${mysqlHost} -P ${mysqlPort}
            crudini --set ${mysqlconfig} mysqld default_time_zone "${timezone}"
            # Restart to apply
            service mysql restart
            echo
        fi
    fi
fi
# Create ${guacDb} and grant ${guacUser} permissions to it
# SQL code
guacUserHost="localhost"
if [[ "${mysqlHost}" != "localhost" ]]; then
    guacUserHost="%"
    echo -e "${YELLOW}MySQL Guacamole user is set to accept login from any host, please change this for security reasons if possible.${NC}"
fi
# Check for ${guacDb} already being there
echo -e "${BLUE}Checking MySQL for existing database (${guacDb})${NC}"
SQLCODE="
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='${guacDb}';"
# Execute SQL code
MYSQL_RESULT=$( echo ${SQLCODE} | mysql -u root -D information_schema -h ${mysqlHost} -P ${mysqlPort} )
if [[ $MYSQL_RESULT != "" ]]; then
    echo -e "${RED}It appears there is already a MySQL database (${guacDb}) on ${mysqlHost}${NC}" 1>&amp;2
    echo -e "${RED}Try:    mysql -e 'DROP DATABASE ${guacDb}'${NC}" 1>&amp;2
    #exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
# Check for ${guacUser} already being there
echo -e "${BLUE}Checking MySQL for existing user (${guacUser})${NC}"
SQLCODE="
SELECT COUNT(*) FROM mysql.user WHERE user = '${guacUser}';"
# Execute SQL code
MYSQL_RESULT=$( echo ${SQLCODE} | mysql -u root -D mysql -h ${mysqlHost} -P ${mysqlPort} | grep '0' )
if [[ $MYSQL_RESULT == "" ]]; then
    echo -e "${RED}It appears there is already a MySQL user (${guacUser}) on ${mysqlHost}${NC}" 1>&amp;2
    echo -e "${RED}Try:    mysql -e \"DROP USER '${guacUser}'@'${guacUserHost}'; FLUSH PRIVILEGES;\"${NC}" 1>&amp;2
    #exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
# Create database &amp; user, then set permissions
SQLCODE="
DROP DATABASE IF EXISTS ${guacDb};
CREATE DATABASE IF NOT EXISTS ${guacDb};
CREATE USER IF NOT EXISTS '${guacUser}'@'${guacUserHost}' IDENTIFIED BY \"${guacPwd}\";
GRANT SELECT,INSERT,UPDATE,DELETE ON ${guacDb}.* TO '${guacUser}'@'${guacUserHost}';
FLUSH PRIVILEGES;"
# Execute SQL code
echo ${SQLCODE} | mysql -u root -D mysql -h ${mysqlHost} -P ${mysqlPort}
# Add Guacamole schema to newly created database
echo -e "${BLUE}Adding database tables...${NC}"
cat guacamole-auth-jdbc-${GUACVERSION}/mysql/schema/*.sql | mysql -u root -D ${guacDb} -h ${mysqlHost} -P ${mysqlPort}
if [ $? -ne 0 ]; then
    echo -e "${RED}Failed${NC}" 1>&amp;2
    exit 1
else
    echo -e "${GREEN}OK${NC}"
fi
echo
# Ensure guacd is started
echo -e "${BLUE}Starting guacd service &amp; enable at boot...${NC}"
service guacd stop 2>/dev/null
service guacd start
systemctl enable guacd
echo
# Deal with ufw and/or iptables
# Check if ufw is a valid command
if [ -x "$( command -v ufw )" ]; then
    # Check if ufw is active (active|inactive)
    if [[ $(ufw status | grep inactive | wc -l) -eq 0 ]]; then
        # Check if 8080 is not already allowed
        if [[ $(ufw status | grep "8080/tcp" | grep "ALLOW" | grep "Anywhere" | wc -l) -eq 0 ]]; then
            # ufw is running, but 8080 is not allowed, add it
            ufw allow 8080/tcp comment 'allow tomcat'
        fi
    fi
fi    
# It's possible that someone is just running pure iptables...
# Check if iptables is a valid running service
systemctl is-active --quiet iptables
if [ $? -eq 0 ]; then
    # Check if 8080 is not already allowed
    # FYI: This same command matches the rule added with ufw (-A ufw-user-input -p tcp -m tcp --dport 22 -j ACCEPT)
    if [[ $(iptables --list-rules | grep -- "-p tcp" | grep -- "--dport 22" | grep -- "-j ACCEPT" | wc -l) -eq 0 ]]; then
        # ALlow it
        iptables -A INPUT -p tcp --dport 8080 --jump ACCEPT
    fi
fi
# I think there is another service called firewalld that some people could be running instead
# Unless someone opens an issue about it or submits a pull request, I'm going to ignore it for now
# Cleanup
echo -e "${BLUE}Cleanup install files...${NC}"
rm -rf guacamole-*
rm -rf mysql-connector-java-*
unset MYSQL_PWD
echo
# Done
echo -e "${BLUE}Installation Complete\n- Visit: http://localhost:8080/guacamole/\n- Default login (username/password): guacadmin/guacadmin\n***Be sure to change the password***.${NC}"
if [ "${installDuo}" = true ]; then
    echo -e "${YELLOW}\nDon't forget to configure Duo in guacamole.properties. You will not be able to login otherwise.\nhttps://guacamole.apache.org/doc/${GUACVERSION}/gug/duo-auth.html${NC}"
fi

References

[1 ]”MysticRyuujin/guac-install”, GitHub, 2020. [Online]. Available: https://github.com/MysticRyuujin/guac-install


How to: Fix “CT is locked (rollback)” on Proxmox VE (PVE) (How to: Unlock Container/CT on Proxmox VE)

The Error

CT is locked (rollback)

The Fix

1 Login to Proxmox VE terminal or via SSH or Proxmox web gui

2 Execute following command

pct unlock <containerID>

e.g.

pct unlock 100

3 Now we should be able do what we want to previously without error


pct command help

USAGE: pct <COMMAND> [ARGS] [OPTIONS]
       pct clone <vmid> <newid> [OPTIONS]
       pct create <vmid> <ostemplate> [OPTIONS]
       pct destroy <vmid> [OPTIONS]
       pct list
       pct migrate <vmid> <target> [OPTIONS]
       pct move_volume <vmid> <volume> <storage> [OPTIONS]
       pct pending <vmid>
       pct resize <vmid> <disk> <size> [OPTIONS]
       pct restore <vmid> <ostemplate> [OPTIONS]
       pct template <vmid>
       pct config <vmid> [OPTIONS]
       pct set <vmid> [OPTIONS]
       pct delsnapshot <vmid> <snapname> [OPTIONS]
       pct listsnapshot <vmid>
       pct rollback <vmid> <snapname>
       pct snapshot <vmid> <snapname> [OPTIONS]
       pct reboot <vmid> [OPTIONS]
       pct resume <vmid>
       pct shutdown <vmid> [OPTIONS]
       pct start <vmid> [OPTIONS]
       pct stop <vmid> [OPTIONS]
       pct suspend <vmid>
       pct console <vmid> [OPTIONS]
       pct cpusets
       pct df <vmid>
       pct enter <vmid>
       pct exec <vmid> [<extra-args>]
       pct fsck <vmid> [OPTIONS]
       pct fstrim <vmid>
       pct mount <vmid>
       pct pull <vmid> <path> <destination> [OPTIONS]
       pct push <vmid> <file> <destination> [OPTIONS]
       pct rescan  [OPTIONS]
       pct status <vmid> [OPTIONS]
       pct unlock <vmid>
       pct unmount <vmid>
       pct help [<extra-args>] [OPTIONS]

How to: Find which virtual machine (VM) occupying/using largest amount of storage/disk space from Proxmox VE (PVE)

1 Login to Proxmox VE web gui

2 Navigate to Datacenter -> Storage, note down the disk we want to check

3 Launch “Shell” for the node we want to check, or via SSH or directly from Proxmox host

4 Install “ncdu” utility

apt install -y ncdu

4 Use following command to check (Here we use “/rpool/images” as example)

ncdu /rpool/images

Note 1: Use Arrow keys to navigate, Left = Upper level folder, Right = Open the highlighted folder, Use “q” key to exit the program

Note 2: Change the directory to suit your situation

Example output

Output from ncdu for Proxmox VE
Output from ncdu for Proxmox VE

If we are not sure which folder, we can simply just use “ncdu /” to scan everything on the host.

ncdu /