The Lazy Admin Blog

Home  /  cPanel • JetBackup  /  Convert JetBackup to cPanel structure

Convert JetBackup to cPanel structure

October 06, 2022 cPanel, JetBackup 10 Comments

JetBackup 5 introduces multi-panel support, meaning that backups can be created in a cPanel server, for example, can be restored on a DirectAdmin server (and vice versa).
To achieve that, the JetBackup team has to create its own unique backup structure (Unlike JetBackup v4 which was based on the cPanel backups structure).

Here is a “Quick & Dirty” bash magic script that will convert a JetBackup 5 structure into cPanel backup structure (“cpmove file”), the generated cPmove file can be restored on any cPanel server (/scripts/restorepkg) regardless of JetBackup (doesn’t have to be installed on the server).

The script can generate a cPanel backup from an already downloaded backup file (usually located at /usr/local/jetapps/usr/jetbackup5/downloads), or you can provide a username, and it will fetch the latest backup automatically (given that there are full active backups for that account).

RAW version: https://thelazyadmin.blog/scripts/jb5_to_cpanel_convertor.txt


Quick Download

wget https://thelazyadmin.blog/scripts/jb5_to_cpanel_convertor.txt -O /root/jb5_to_cpanel_convertor.sh
chmod +x /root/jb5_to_cpanel_convertor.sh
cd /root
./jb5_to_cpanel_convertor.sh

Full Script

#!/bin/bash
# Convert the supplied JetBackup 5 backup file to a cPanel-compatible backup.
# 
# Originally created by TheLazyAdmin, https://thelazyadmin.blog
#
# ** USE AT YOUR OWN RISK ** 
 
# MIT License
# 
# Copyright (c) 2022 TheLazyAdmin
# Copyright (c) 2023,2024 Christopher Handley
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
 
function Error {
        echo ""
        echo "$1"
        exit 1
}
 
function ErrorHelp {
        echo ""
        echo "$1"
        echo "
How to use:
jb5_to_cpanel_convertor.sh JETBACKUP5_BACKUP [DESTINATION_FOLDER]
 
JETBACKUP5_BACKUP  = Source JetBackup file
DESTINATION_FOLDER = Optional destination folder for cPanel backup, 
                     defaults to /home/, if you are root, otherwise to ~/
 
e.g. 
jb5_to_cpanel_convertor.sh /home/download_jb5user_1663238955_28117.tar.gz
"
        exit 1
}
 
function Untar() {
        BackupPath="$1"
        DestPath="$2"
        tar -xf "$BackupPath" -C "$DestPath"
        Err=$?
        [[ $Err -gt 0  ]] && Error "Unable to untar the file '$BackupPath'"
}
 
function Extract() {
        FilePath="$1"
        gunzip $FilePath        # FilePath not quoted so can expand wildcard
        Err=$?
        [[ $Err -gt 0 ]] && Error "Unable to extract files"
}
 
function MoveDir() {
        Src="$1"
        Dst="$2"
        echo "Converting folder '$Src'"
        # Fixed an issue where the Dst directory did not exist, causing problems with MySQL DB/DB user conversion
        mkdir -p "$Dst"
        mv $Src "$Dst"          # Src not quoted so can expand wildcard
        Err=$?
        [[ $Err -gt 0 ]] && Error "An error occurred"
}
 
function Archive() {
        TarName="$1"
        echo "Creating archive '$DestDir/$TarName'"
 
        if [ -f "$DestDir/$TarName" ]; then rm "$DestDir/$TarName"; fi  # Ensure create a new archive from scratch
        cd "$UnzipDest" || Error "Failed to CD to '$UnzipDest'"
        tar -czf "$DestDir/$TarName" "cpmove-$AccountName" >/dev/null 2>&1
        Err=$?
        [[ $Err != 0 ]] && Error "Unable to create tar file"
}
 
function CreateFTPaccount() {
        DirPath="$1"
        ConfigPath="$2"
        HomeDir="$( cat "$ConfigPath/meta/homedir_paths" )"
        User="$( ls "$ConfigPath/cp/")"
 
        for FILE in "$DirPath"/*.acct; do
                [ -f "$FILE" ] || continue
                Username="$(grep -Po '(?<=name: )(\w\D+)' "$FILE")"
                Password="$(grep -Po '(?<=password: )([A-Za-z0-9!@#$%^&*,()\/\\.])+' "$FILE")"
                WebRootPath="$(grep -Po '(?<=path: )([A-Za-z0-9\/_.-]+)' "$FILE")"
                echo "Creating FTP account '$Username'";
                printf "%s:%s:0:0:%s:%s/%s:/bin/ftpsh" "$Username" "$Password" "$User" "$HomeDir" "$WebRootPath" >> "$CPanelDir/proftpdpasswd"
        done
}
 
function CreateMySQLfile() {
        DirPath="$1"
        SQL_FilePath="$2"
 
        for FILE in "$DirPath"/*.user; do
                [ -f "$FILE" ] || continue
                Username="$(grep -Po '(?<=name: )([a-zA-Z0-9!@#$%^&*(\)\_\.-]+)' "$FILE")"
                Database="$(grep -Po '(?<=database `)([_a-zA-Z0-9]+)' "$FILE")"
                User="$(grep -Po '(?<=name: )([a-zA-Z0-9!#$%^&*(\)\_\.]+)' "$FILE")"
                Domain="$(echo "$Username" | grep -Po '(?<=@)(.*)$')"
                Password="$(grep -Po '(?<=password: )([a-zA-Z0-9*]+)' "$FILE")"
                Permissions="$(grep -Po '(?<=:)[A-Z ,]+$' "$FILE")"
 
                echo "Creating DB '$Database' & adding DB user '$User'"
 
                echo "GRANT USAGE ON *.* TO '$User'@'$Domain' IDENTIFIED BY PASSWORD '$Password';" >> "$SQL_FilePath"
                echo "GRANT$Permissions ON \`$Database\`.* TO '$User'@'$Domain';" >> "$SQL_FilePath"
        done
}
 
function CreateEmailAccount() {
        BackupEmailPath="$1"
        DestEmailPath="$2"
 
        echo "Creating email accounts"
 
        for JsonFile in "$BackupEmailPath"/*.conf; do
                [ -f "$JsonFile" ] || continue
                MailUser="$(jq -r '.account' "$JsonFile" | base64 --decode)"
                MailDomain="$(jq -r '.domain' "$JsonFile" | base64 --decode)"
                MailPassword="$(jq -r '.password' "$JsonFile" | base64 --decode)"
                if [ -z "$MailUser" ] || [ -z "$MailDomain" ]; then
                        echo "  Skipping email account in '$(basename "$JsonFile")' (missing account or domain)"
                        continue
                fi
                # Ensure the per-domain mail dir exists before appending credentials,
                # otherwise the redirect below fails silently and the password is lost
                mkdir -p "$DestEmailPath/${MailDomain}"
                echo "${MailUser}:${MailPassword}:::::::" >>"$DestEmailPath/${MailDomain}/shadow"
        done
}
 
function CreateDomains() {
        DirPath="$1"
        ConfigPath="$2"
 
        echo "Creating domains"
 
        # find primary domain
        PrimaryDomain=""
        for JSON_FILE in "$DirPath"/*.conf; do
                Domain="$(jq -r '.domain' "$JSON_FILE" | base64 --decode)"
                Type="$(jq -r '.type' "$JSON_FILE" | base64 --decode)"
                if [ "$Type" = "1" ]; then
                        PrimaryDomain="$Domain"
                fi
        done
        if [ -z "$PrimaryDomain" ]; then Error "Failed to find the primary domain of account '$AccountName'"; fi
        # OR COULD JUST DO: PrimaryDomain="$( cat "$CPanelDir/cp/$AccountName" | grep -Po '(?<=DNS=)([A-Za-z0-9-.]+)')"
 
        # write info about sub-domains of the primary domain
        echo -n "" >"$ConfigPath"/sds
        echo -n "" >"$ConfigPath"/sds2
        for JSON_FILE in "$DirPath"/*.conf; do
                #WebRoot="$(jq -r '.public_dir' "$JSON_FILE" | base64 --decode)"
                Domain="$(jq -r '.domain' "$JSON_FILE" | base64 --decode)"
                Type="$(jq -r '.type' "$JSON_FILE" | base64 --decode)"
                if [[ "$Type" == "3" && "$Domain" == *.$PrimaryDomain ]]; then
                        # (sub-domain)
                        echo "  Adding sub-domain '$Domain'"
                        echo "${Domain/./_}"         >>"$ConfigPath"/sds
                        echo "${Domain/./_}=$Domain" >>"$ConfigPath"/sds2
                fi
        done
 
        # write info about addon & parked domains
        echo -n "" >"$ConfigPath"/addons
        echo -n "" >"$ConfigPath"/pds
        for JSON_FILE in "$DirPath"/*.conf; do
                #WebRoot="$(jq -r '.public_dir' "$JSON_FILE" | base64 --decode)"
                Domain="$(jq -r '.domain' "$JSON_FILE" | base64 --decode)"
                Type="$(jq -r '.type' "$JSON_FILE" | base64 --decode)"
                if [ "$Type" = "1" ]; then
                        # (primary domain) so ignore it
                        :
                elif [ "$Type" = "2" ]; then
                        # (addon domain)
                        echo "  Adding addon domain '$Domain'"
                        echo "$Domain=${Domain/./_}.$PrimaryDomain" >>"$ConfigPath"/addons
                        echo "${Domain/./_}.$PrimaryDomain"         >>"$ConfigPath"/sds
                        echo "${Domain/./_}.$PrimaryDomain=$Domain" >>"$ConfigPath"/sds2
 
                elif [ "$Type" = "3" ]; then
                        # (sub-domain) so ignore for the moment
                        :
 
                elif [ "$Type" = "4" ]; then
                        # (parked/alias domain)
                        echo "$Domain" >>"$ConfigPath"/pds
                else
                        # (unknown domain type)
                        Error "Domain '$Domain' has unknown type '$Type'"
                fi
        done
 
        # write info about sub-domains that are NOT of the primary domain
        for JSON_FILE in "$DirPath"/*.conf; do
                #WebRoot="$(jq -r '.public_dir' "$JSON_FILE" | base64 --decode)"
                Domain="$(jq -r '.domain' "$JSON_FILE" | base64 --decode)"
                Type="$(jq -r '.type' "$JSON_FILE" | base64 --decode)"
                if [[ "$Type" == "3" && ! "$Domain" == *.$PrimaryDomain ]]; then
                        # (sub-domain)
                        echo "  Adding sub-domain '$Domain'"
                        echo "${Domain/./_}"         >>"$ConfigPath"/sds
                        echo "${Domain/./_}=$Domain" >>"$ConfigPath"/sds2
                fi
        done
}
 
function CreateSSLcerts() {
        DirPath="$1"
        ConfigPath="$2"
 
        echo "Creating SSL certificates"
 
        # indicates the account will use WHM’s SSL Storage Manager feature (WHM » Home » SSL/TLS » SSL Storage Manager)
        # And without this, restoring the account will fail to install the SSL certificate, reporting "An error prevented adding a record of type “crt” ... That certificate is already installed as ..."
        touch "$ConfigPath"/has_sslstorage
 
        # Rebuild apache_tls/<domain> so the restore actually installs the cert on each
        # vhost. We derive everything from the cert/key files themselves (via openssl)
        # rather than from ssl.db, because ssl.db only records a 'modulus' for RSA keys --
        # EC certs (and wildcard certs) cannot be matched the old modulus/commonName way.
 
        SSLDir="$ConfigPath/homedir/ssl"
        if ! compgen -G "$SSLDir/certs/*.crt" >/dev/null 2>&1; then
                echo "  No SSL certificates in backup; nothing to install on vhosts"
                return
        fi
        if ! command -v openssl >/dev/null 2>&1; then
                echo "  WARNING: openssl not found; skipping vhost SSL install (SSL store still carried over)"
                return
        fi
 
        mkdir -p "$ConfigPath/apache_tls"
 
        # Index private keys by their public-key fingerprint, so a key can be paired to a
        # cert by public key alone -- works for both RSA and EC.
        declare -A KeyByPub
        for K in "$SSLDir/keys/"*.key; do
                [ -f "$K" ] || continue
                PH="$(openssl pkey -in "$K" -pubout -outform DER 2>/dev/null | openssl dgst -sha256 2>/dev/null | awk '{print $NF}')"
                [ -n "$PH" ] && KeyByPub["$PH"]="$K"
        done
 
        # _ssl_covers CERT_DOMAIN ACCOUNT_DOMAIN -> true if the (possibly *.wildcard)
        # cert domain covers the account domain (wildcard matches exactly one label).
        _ssl_covers() {
                local D="$1" X="$2" Suffix Head
                [ "$D" = "$X" ] && return 0
                if [[ "$D" == \*.* ]]; then
                        Suffix="${D#\*.}"; Head="${X%.$Suffix}"
                        [ "$Head.$Suffix" = "$X" ] && [ "$Head" != "$X" ] && [[ "$Head" != *.* ]] && return 0
                fi
                return 1
        }
 
        for JSON_FILE in "$DirPath"/*.conf; do
                [ -f "$JSON_FILE" ] || continue
                Domain="$(jq -r '.domain' "$JSON_FILE" | base64 --decode)"
                [ -z "$Domain" ] && continue
 
                # Choose the newest cert that covers this domain AND has a matching key.
                BestCert=""; BestKey=""; BestStart=-1
                for C in "$SSLDir/certs/"*.crt; do
                        [ -f "$C" ] || continue
                        CertDomains="$( { openssl x509 -in "$C" -noout -ext subjectAltName 2>/dev/null | grep -oE 'DNS:[^,]+' | sed 's/DNS://; s/ //g';
                                          openssl x509 -in "$C" -noout -subject -nameopt multiline 2>/dev/null | sed -n 's/^ *commonName *= *//p'; } )"
                        Covered=0
                        for D in $CertDomains; do _ssl_covers "$D" "$Domain" && { Covered=1; break; }; done
                        [ "$Covered" -eq 1 ] || continue
                        # require the private key to be present
                        CPH="$(openssl x509 -in "$C" -noout -pubkey 2>/dev/null | openssl pkey -pubin -outform DER 2>/dev/null | openssl dgst -sha256 2>/dev/null | awk '{print $NF}')"
                        MatchKey="${KeyByPub[$CPH]}"
                        [ -n "$MatchKey" ] || continue
                        Start="$(openssl x509 -in "$C" -noout -startdate 2>/dev/null | sed 's/notBefore=//')"
                        StartEpoch="$(date -d "$Start" +%s 2>/dev/null || echo 0)"
                        if [ "$StartEpoch" -ge "$BestStart" ]; then BestStart="$StartEpoch"; BestCert="$C"; BestKey="$MatchKey"; fi
                done
 
                [ -n "$BestCert" ] || continue
                echo "  Installing SSL cert for '$Domain'"
                # cPanel "combined" format: certificate then key. (CA bundle is omitted --
                # self-signed certs have none, and cPanel can fetch a CA's bundle itself.)
                { cat "$BestCert"; echo ""; cat "$BestKey"; echo ""; } >"$ConfigPath/apache_tls/$Domain"
        done
 
        unset -f _ssl_covers
}
 
function CreateDNSZones() {
        DirPath="$1"
        ConfigPath="$2"
 
        echo "Creating DNS zones"
# Fix an issue where "dnszones" directory doesn't exist, preventing copy/move. 
                echo "Ensuring $ConfigPath DNS Zone Directory is made"
                mkdir -p "$ConfigPath/dnszones"
 
        for FILE in "$DirPath"/*.zone; do
                FileName="${FILE##*/}"
                DstFile="$ConfigPath/dnszones/${FileName%.*}.db"
 
                echo "ZoneFile Re-name Step - Creating '$DstFile'"
                mv -v "$FILE" "$DstFile"
                Err=$?
                [[ $Err -gt 0 ]] && Error "An error occurred"
        done
}
 
# Parse arguments
FilePath="$1"
DestDir="$2"
 
# Sanity check
! [[ -f "$FilePath" ]] && ErrorHelp "Invalid file provided"
[[ "$DestDir" == "/" ]] && ErrorHelp "Error :: Don't use root folder as destination"
 
# Default arguments
if [ -z "$DestDir" ]; then
        if [ "$(whoami)" == "root" ]; then
                DestDir=/home
        else
                DestDir=~
        fi
fi
 
# Extract username
#AccountName=$(echo "$FilePath" |  grep -oP '(?<=download_)([^_]+)')
AccountName="$(echo "${FilePath##*/}" | cut -d_ -f2)"
[[ -z "$AccountName" ]] && ErrorHelp "Could not determine the account name from '${FilePath##*/}'. Expected a JetBackup file like download_<user>_<timestamp>_<id>.tar.gz"
 
if [ $(( $(du --block-size=1 "$FilePath" | cut -f1)*10 )) -lt $(df --block-size=1 --output=avail /tmp | tail -n1) ]; then
    # (Free space of /tmp is more than 10 times the compressed archive size) so should be safe to decompress there
    TmpDir=/tmp
 
elif [ $(( $(du --block-size=1 "$FilePath" | cut -f1)*2 )) -gt $(df --block-size=1 --output=avail /tmp | tail -n1) ]; then
    # (Free space of /tmp is less than 2 times the compressed archive size) so definitely NOT enough space
    TmpDir=~
else
    echo "/tmp may not be big enough so checking the decompressed size of the archive..."
    if [ $(( $(time zcat "$FilePath" | wc -c)*2 )) -lt $(df --block-size=1 --output=avail /tmp | tail -n1) ]; then
        # (Free space of /tmp is more than 2 times the UNcompressed archive size) so definitely safe to decompress there
        TmpDir=/tmp
    else
        TmpDir=~
    fi
fi
UnzipDest="$(mktemp --directory --tmpdir=$TmpDir "tmp_jb5_$AccountName.XXXXXXXX")"
BackupPath="$FilePath"
 
echo "Found backup path '$BackupPath'"
echo "Found account '$AccountName'"
 
echo "Creating temporary folder '$UnzipDest'"
mkdir -p "$UnzipDest"   || ErrorHelp "Destination directory error"
! [[ -d "$UnzipDest" ]] && ErrorHelp "Destination directory error"
# Ensure we always clean-up the temporary dir on exit (success, error, or Ctrl-C).
# The finished cPanel archive is written to "$DestDir", outside "$UnzipDest", so this is safe.
trap 'rm -rf "$UnzipDest"' EXIT
 
echo "Untaring '$BackupPath' into '$UnzipDest'"
Untar "$BackupPath" "$UnzipDest"
! [[ -d "$UnzipDest/backup" ]] && Error "JetBackup5 backup directory '$UnzipDest/backup' not found"
 
CPanelDir="$UnzipDest/cpmove-$AccountName"
JB5Backup="$UnzipDest/backup"
 
echo "Converting account '$AccountName'"
echo "Working folder '$CPanelDir'"
 
if ! [[ -d "$JB5Backup/config" ]]; then
        ErrorHelp "The backup does not contain the config directory"
else
        MoveDir "$JB5Backup/config" "$CPanelDir/"
        # Extract any nested tar.gz archives in config/ (contents are flat, belong in cpmove root)
        for TGZ in "$CPanelDir/config/"*.tar.gz; do
                [ -f "$TGZ" ] || continue
                echo "Extracting nested config archive '$(basename "$TGZ")'"
                tar -xzf "$TGZ" -C "$CPanelDir/"
                # tar -xzf "$TGZ" -C "$UnzipDest/"
                Err=$?
                [[ $Err -gt 0 ]] && Error "Unable to extract '$TGZ'"
                rm "$TGZ"
 
        done
        echo "Copying config files to working folder"
        cp -rn $CPanelDir/config/* "$CPanelDir"
        # Remove config/ if it's now empty
        rmdir "$CPanelDir/config" 2>/dev/null
fi
 
if [[ -d "$JB5Backup/homedir" ]]; then
        if ! [[ -d "$CPanelDir/homedir" ]]; then
                MoveDir "$JB5Backup/homedir" "$CPanelDir"
        else
                rsync -ar "$JB5Backup/homedir" "$CPanelDir"
        fi
        # Extract any nested tar.gz archives in homedir/ (contents are flat home dir files)
        for TGZ in "$CPanelDir/homedir/"*.tar.gz; do
                [ -f "$TGZ" ] || continue
                echo "Extracting nested homedir archive '$(basename "$TGZ")'"
                tar -xzf "$TGZ" -C "$CPanelDir/homedir/"
                Err=$?
                [[ $Err -gt 0 ]] && Error "Unable to extract '$TGZ'"
                rm "$TGZ"
        done
fi
 
if [[ -d "$JB5Backup/database" ]] ; then
        echo "Converting databases"
        mkdir -p "$CPanelDir/mysql"
        # JB5 mixes MySQL dumps (gzipped *.sql.gz) and PostgreSQL dumps (pg_dump
        # custom format, misleadingly named *.tar). Detect by magic bytes rather
        # than by file name, since the file names are not trustworthy.
        for DB in "$JB5Backup/database/"*; do
                [ -f "$DB" ] || continue
                Name="${DB##*/}"
                if [ "$(head -c 5 "$DB")" = "PGDMP" ]; then
                        # PostgreSQL custom-format dump -> convert to plain SQL in psql/
                        command -v pg_restore >/dev/null 2>&1 || Error "pg_restore not found; cannot convert PostgreSQL dump '$Name'"
                        mkdir -p "$CPanelDir/psql"
                        DbName="${Name%.tar}"
                        echo "Converting PostgreSQL database '$DbName'"
                        pg_restore -f "$CPanelDir/psql/$DbName.sql" "$DB" || Error "pg_restore failed for PostgreSQL database '$DbName'"
                else
                        # MySQL dump (gzip or already-plain SQL) -> mysql/
                        mv "$DB" "$CPanelDir/mysql/"
                fi
        done
        # Decompress any gzipped MySQL dumps; leave already-plain .sql files untouched
        for GZ in "$CPanelDir/mysql/"*.gz; do
                [ -f "$GZ" ] || continue
                gunzip "$GZ" || Error "Unable to extract database file '$GZ'"
        done
fi
 
[[ -d "$JB5Backup/database_user" ]] && CreateMySQLfile "$JB5Backup/database_user" "$CPanelDir/mysql.sql"
 
if [[ -d "$JB5Backup/email" ]]; then
        MoveDir "$JB5Backup/email" "$CPanelDir/homedir/mail"
        [[ -d "$JB5Backup/jetbackup.configs/email" ]] && CreateEmailAccount "$JB5Backup/jetbackup.configs/email" "$CPanelDir/homedir/etc"
fi
 
[[ -d "$JB5Backup/ftp" ]] && CreateFTPaccount "$JB5Backup/ftp" "$CPanelDir"
 
if [[ -d "$JB5Backup/jetbackup.configs/domain" ]]; then
        CreateDomains "$JB5Backup/jetbackup.configs/domain" "$CPanelDir"
        CreateSSLcerts "$JB5Backup/jetbackup.configs/domain" "$CPanelDir"
fi
 
if [[ -d "$JB5Backup/domain" ]]; then
        CreateDNSZones "$JB5Backup/domain" "$CPanelDir"
fi
 
echo "Creating final cPanel backup archive...";
Archive "cpmove-$AccountName.tar.gz"
echo "Converting Done!"
echo "Temporary working folder '$UnzipDest' will be removed automatically."
echo -e "Your cPanel backup:\n$DestDir/cpmove-$AccountName.tar.gz"
Tags: cPanel, JetBackup
Previous Article
Next Article

Related Posts

  • Enable TLS 1.1/1.0 on cPanel servers

    Enable TLS 1.1/1.0 on cPanel servers

    30th September 2022
  • Building your own private cloud with XenServer

    20th April 2021
  • JetBackup :: Restore on steroids !

    JetBackup :: Restore on steroids !

    21st August 2020

10 Comments

  1. Javier Bastet Reply
    16th April 2023 at 13:57

    It works perfect

  2. Anonymous Reply
    22nd May 2023 at 18:48

    Great work, thanks!

  3. T Reply
    3rd November 2023 at 21:49

    hi, i have a question,
    how we can select a backup from date?
    i mean an account have many backups like daily/weekly/monthly
    and how we can say which one we want to create?

  4. TheLazyAdmin Reply
    4th November 2023 at 12:06

    The convert script works on an already downloaded backup, which means you already selected the backup you want to convert.

  5. Chris Reply
    20th November 2023 at 12:29

    What license is this code released under? e.g. Public Domain or MIT License?
    https://opensource.guide/legal/#which-open-source-license-is-appropriate-for-my-project

    Your code was a great starting point, but I’ve made a lot of fixes that I’d like to release, as I found issues/limitations with most parts of the cPanel backup it created.

    • TheLazyAdmin Reply
      20th November 2023 at 12:53

      Hi Chris,

      Thank you for sharing! I believe the MIT License would be the most appropriate here.
      Please share a link to your revision, I am interested to see the changes!

      Thanks

  6. Chris Reply
    29th November 2023 at 15:30

    Sorry for the delay, I had to get approval for releasing my revised script. I’ve released it on GitHub, in the hope of encouraging other people to improve it further:
    https://github.com/cshandley-uk/bash_jb5-to-cpanel-convertor

    Let me know if you’d like any changes to the README.

    Most notably it fixes the following issues I ran into with the generated cPanel backups:
    * It failed to restore addon, alias & sub domains.
    * It failed to restore mailboxes.
    * It failed to restore SSL certificates (at least on the hosting I was using).
    * It didn’t restore DNS zones.

    There are Git commits for each of those:
    https://github.com/cshandley-uk/bash_jb5-to-cpanel-convertor/commit/3e2affc63bb1dea28d8df43cddbaff4b9c75d541
    https://github.com/cshandley-uk/bash_jb5-to-cpanel-convertor/commit/505db88a354f25a3ccd4f572e12ad9db9f29de16
    https://github.com/cshandley-uk/bash_jb5-to-cpanel-convertor/commit/eb40cb4efd38c0805d20756286a4dd48d73c4684
    https://github.com/cshandley-uk/bash_jb5-to-cpanel-convertor/commit/37de8b9e7d0950d0b9ab51721bb374b193f20334

    Other significant changes I made to the script:
    * It needs the “jq” command to have been installed.
    * I removed the –fetch option which caused the local server to generate a new JetBackup 5 backup, as this is only useful if you have root access to the source server – in which case you could just re-enable cPanel’s built-in backup functionality anyway. The –fetch code was just extra complexity that I didn’t need when trying to understand & improve the script, and I would have been unable to test it anyway.
    * I made it allocate a proper /tmp folder for storing temporary files (e.g. unpacked archive), which is always deleted – rather than leaving a randomly named folder in the destination that the user must manually delete. Currently it assumes root access when creating the /tmp folder, but this could be easily fixed.

    I also made a lot of cosmetic changes to make it easier for me to understand & edit the script.

    • TheLazyAdmin Reply
      29th November 2023 at 15:39

      Thanks for sharing Chris! Looks really good and the commits links is very useful 🙂

  7. aaop Reply
    3rd December 2025 at 07:53

    can it be run on local, for downloaded jetbackup file download.XXX.XXX.tar.gz

    • TheLazyAdmin Reply
      11th December 2025 at 09:36

      The script itself downloads the backup then refactor it to the cPanel format, so in theory – yes, you will however need to change the script to skip the download part

Leave a Reply

Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Search Our Blog

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
Filter by Categories
Apache
C++
CentOS
CloudLinux
cPanel
Emails
ESXI
iSCSI
JetBackup
Linux
Litespeed
MySQL
NGINX
Oracle
Reduxio
Security
SSL
Uncategorized
VMware
Wordpress
XEN

Tags

apache aspx backup bash C++ CentOS cloudlinux cPanel CXS Emails freetds google htaccess IMAP InnoDB iscsi JetBackup Libmodsecurity litespeed modsec modsecurity mssql MySQL netapp nginx odbc Oracle php php.ini phpselector rsync ssh ssmtp systemd Telnet threads VMFS WHM Wordpress xenserver

Popular Posts

  • Convert JetBackup to cPanel structure 6th October 2022
  • How To Install & Configure a Galera Cluster with MariaDB on Centos 7 6th February 2018
  • Allow a cPanel server to run a VHOST from multiple IP addresses 3rd April 2018
  • rsync without prompting for password 10th October 2022

Recent Posts

  • Creating a simple Telnet client in C++ 29th June 2025
  • Understanding Why More Threads Can Sometimes Slow Down Performance 9th October 2024
  • Set up a new systemd service 18th May 2024
  • Bash Arrays 7th November 2023

Recent Comments

  • TheLazyAdmin on Convert JetBackup to cPanel structure
  • aaop on Convert JetBackup to cPanel structure
  • Sven on rsync without prompting for password
  • TheLazyAdmin on rsync without prompting for password
  • Sven on rsync without prompting for password
Privacy Policy • Contact