﻿#!/bin/bash
COMPILETIME='Tue Feb 24 00:35:33 UTC 2026';
BasePath=/usr/local/Somnode
function RunBackup(){
    # Check if backup should run today at this current hour
    Output "$(echo `date`)" blue;
    # Output settings for debug purposes
    for key in "${!schedule[@]}"; do echo -n "${key}=${schedule[${key}]} "; done; echo "";
    for key in "${!settings[@]}"; do echo -n "${key}=${settings[${key}]} "; done; echo "";
    CheckRunBackup
    Output "Beginning backup procedure";
    DisplayLine
    # Lowering process priority
    AdjustProcessPriority
    PrepareStaging
    CheckBackupPathExists
    CheckDiskSpace
    BackupDir=${settings[backup_path]};
    StagingDir=${settings[backup_path]}/.staging;
    ##
    ## Backup contentdir
    ##
    CONTENTDIR=`${BasePath}/php/bin/php -r 'require "/usr/local/Somnode/htdocs/database.php"; echo $setting["content_path"];'`
    Output "Content directory is: ${CONTENTDIR}";
    echo ${CONTENTDIR} > ${StagingDir}/contentdir;
    ##
    ## Backup htdocs
    ##
    Output "\nBacking up htdocs" blue;
    DisplayLine
    cd ${BasePath}/htdocs;
    tar \
        --exclude='./temp/*.conf' \
        --exclude='./temp/*.sh' \
        --exclude='./temp/*.txt' \
        --exclude='./temp/cache/*' \
        --exclude='./temp/historics/*' \
        --exclude='./temp/statistics/*' \
        --exclude='./temp/reporting/*' \
        --exclude='./temp/templates_c/*' \
        --exclude='./temp/wowza/*.log' \
        -cf \
        ${StagingDir}/htdocs.tar \
        ./;
    cd ${StagingDir}; # Return to staging directory
    ##
    ## Backup Database
    ##
    if [[ "${schedule[runPartialDatabaseBackup]}" == 1 ]]; then
        Output "\nCreating partial (no historics) database backup" blue;
        DisplayLine
        BackupPartialDatabase
    else
        Output "Skipping partial database backup";
    fi
    if [[ "${schedule[runFullDatabaseBackup]}" == 1 ]]; then
        Output "\nCreating full database backup" blue;
        DisplayLine
        BackupFullDatabase
    else
        Output "Skipping full database backup";
    fi
    # Backup Configurations
    Output "\nBackup configuration files" blue;
    DisplayLine
    BackupConfig
    # Backup Media Content
    if [[ "${schedule[runContentBackup]}" == "1" ]]; then
        Output "\nBacking up media content" blue;
        DisplayLine
        BackupContent
    else
        Output "Skipping Media Content";
    fi
    # Package Daily and move into /backups/daily/xx
    if [[ "${schedule[isDailyBackup]}" == "1" ]]; then
        Output "\nPackaging daily backup" blue;
        DisplayLine
        PackageDaily
    fi
    # Package Weekly and move into /backups/weekly/xx
    if [[ "${schedule[isWeeklyBackup]}" == "1" ]]; then
        Output "\nPackaging weekly backup" blue;
        DisplayLine
        PackageWeekly
    fi
    # Retention Cleanup
    Output "Retention";
    echo "Removing daily backups older than ${settings[backup_daily_retention]} days";
    if [[ "$(ls -A ${BackupDir}/daily/)" ]];then
        find ${BackupDir}/daily/* -maxdepth 0 -mtime +${settings[backup_daily_retention]} -exec rm -f {} \;
    else
        echo "${BackupDir}/daily/ is empty.";
    fi
    echo "Removing weekly backups older than ${settings[backup_weekly_retention]} weeks";
    if [[ "$(ls -A ${BackupDir}/weekly/)" ]];then
        WeeklyRetentionDays=$(awk "BEGIN {printf \"%.0f\n\", ${settings[backup_weekly_retention]} * 7}")
        find ${BackupDir}/weekly/* -maxdepth 0 -mtime +${WeeklyRetentionDays};
        find ${BackupDir}/weekly/* -maxdepth 0 -mtime +${WeeklyRetentionDays} -exec rm -f {} \;
    else
        echo "${BackupDir}/daily/ is empty.";
    fi
    # Staging Cleanup
    echo "Clearing staging directory";
    rm -rf ${StagingDir}/;
    DisplayLine
    Output "Backup completed.\n" green;
}
function AdjustProcessPriority(){
    /usr/bin/ionice -c2 -n${settings[backup_io]} -p $$
    /usr/bin/renice 10 $$
}
function TriggerFailure(){
    echo "BACKUP FAILED.";
    exit;
}
function DisplayLine(){
    echo "-----------------------------------------------------------";
}
function Output(){
    text=$1
    color=$2
    if [[ "${color}" == "" ]]; then
        color="black";
        return;
    fi
    ansiColor='\033[0m'; # Set default
    case $color in
        'red')
            ansiColor='\033[0;31m';
        ;;
        'green')
            ansiColor='\033[0;32m';
        ;;
        'blue')
            ansiColor='\033[01;34m';
        ;;
        'black'|'default')
            ansiColor='\033[0m';
        ;;
    esac
    echo -e "${ansiColor}${text}\033[0m"
}
function CheckRunBackup(){
    if [[ ! "${schedule[isBackupHour]}" == "1" ]] ; then
        echo "Backup is not scheduled to run at this hour.";
        exit;
    fi
    if [[ ! "${schedule[isDailyBackup]}" == "1" ]] && [[ ! "${schedule[isWeeklyBackup]}" == "1" ]]; then
        echo "No daily or weekly backup scheduled today.";
        exit;
    fi
}
function CheckBackupPathExists(){
    if [[ ! -d ${settings[backup_path]} ]]; then
        echo "Creating backup path: ${settings[backup_path]}"
        mkdir -p ${settings[backup_path]};
    fi
    if [[ ! -d ${settings[backup_path]} ]]; then
        echo "Backup path is not valid";
        TriggerFailure
    fi
    if [[ ! -d ${settings[backup_path]}/daily ]]; then mkdir -p ${settings[backup_path]}/daily; fi
    if [[ ! -d ${settings[backup_path]}/weekly ]]; then mkdir -p ${settings[backup_path]}/weekly; fi
    chown Somnode:Somnode -R ${settings[backup_path]};
}
function CheckDiskSpace(){
    BACKUPDIR=${settings[backup_path]}
    htdocsSize=`du --max-depth=1 ${BasePath}/htdocs | tail -1 | awk '{print $1}'`
    databaseSize=`du --max-depth=1 ${BasePath}/mysql/data | tail -1 | awk '{print $1}'`
    MCPDIRSIZE=`expr ${htdocsSize} + ${databaseSize} | awk '{print $1}'`
    MCPDIRSIZEGB=$(awk "BEGIN {printf \"%.2f\n\", ${MCPDIRSIZE} / 1024 / 1024}")
    AVAILABLEDISKSPACE=`df -P $BACKUPDIR | awk 'NR==2 {print $4}'`
    AVAILABLEDISKSPACEGB=$(awk "BEGIN {printf \"%.2f\n\", ${AVAILABLEDISKSPACE} / 1024 / 1024}")
    echo "Somnode is ${MCPDIRSIZEGB}GB";
    CONTENTDIR=`/usr/local/Somnode/php/bin/php -r 'require "/usr/local/Somnode/htdocs/database.php"; echo $setting["content_path"];'`
    includeContent=0;
    if [[ ! -d ${CONTENTDIR} ]]; then CONTENTDIR=/usr/local/Somnode/content/; fi
    if [[ (${schedule[isDailyBackup]} -eq 1 && ${schedule[isDailyContent]} -eq 1) || (${schedule[isWeeklyBackup]} -eq 1 && ${schedule[isWeeklyContent]} -eq 1) ]]; then
        includeContent=1
        CONTENTDIRSIZE=`du --max-depth=1 ${CONTENTDIR} | tail -1 | awk '{print $1}'`
        CONTENTDIRSIZEGB=$(awk "BEGIN {printf \"%.2f\n\", ${CONTENTDIRSIZE} / 1024 / 1024}")
        echo "${CONTENTDIR} size is ${CONTENTDIRSIZEGB}GB";
        # Update MCPDIRSIZE TO INCLUDE CONTENT
        MCPDIRSIZE=$(awk "BEGIN {printf \"%.0f\n\", ${CONTENTDIRSIZE} + ${MCPDIRSIZE} }")
        MCPDIRSIZEGB=$(awk "BEGIN {printf \"%.2f\n\", ${MCPDIRSIZE} / 1024 / 1024}")
      else
        echo "${CONTENTDIR} not scheduled for backup."
    fi
    echo "${BACKUPDIR} has ${AVAILABLEDISKSPACEGB}GB available.";
    if [ `expr ${MCPDIRSIZE} \* 2` -gt ${AVAILABLEDISKSPACE} ]; then
        if [[ ${includeContent} -eq 1 ]]; then
          Output "There is not enough space on this disk to backup the Somnode and contents (${MCPDIRSIZEGB}GB)." red;
        else
          Output "There is not enough space on this disk to backup the Somnode htdocs & database (${MCPDIRSIZEGB}GB)." red;
        fi
        TriggerFailure
    fi
}
function PrepareStaging(){
    if [[ -d ${settings[backup_path]}/.staging ]]; then
        echo "Clearing staging path";
        rm -rf ${settings[backup_path]}/.staging/;
    fi
    mkdir -p ${settings[backup_path]}/.staging/;
}
function BackupPartialDatabase(){
    DBPREFIX=`${BasePath}/php/bin/php -r 'require "/usr/local/Somnode/htdocs/database.php"; echo $db_prefix;'`
    # Create database table structures
    ${BasePath}/mysql/bin/mysqldump \
    --protocol=SOCKET \
    --socket=${BasePath}/mysql/data/mysql.sock \
    --user=root \
    --password= \
    Somnode \
    --no-data \
    --single-transaction \
    --opt \
    --quick \
    > ${StagingDir}/Somnode_partial.sql;
    if [[ $? -gt 0 ]]; then
        Output "Database backup was not successful." red;
        TriggerFailure
    fi
    # Insert data, without historic data
    ${BasePath}/mysql/bin/mysqldump \
        --protocol=SOCKET \
        --socket=${BasePath}/mysql/data/mysql.sock \
        --user=root \
        --password= \
        Somnode \
        --single-transaction \
        --opt \
        --quick \
        --ignore-table=Somnode.${DBPREFIX}historic \
        --ignore-table=Somnode.${DBPREFIX}historic_country \
        --ignore-table=Somnode.${DBPREFIX}historic_media \
        --ignore-table=Somnode.${DBPREFIX}historic_referrer \
        --ignore-table=Somnode.${DBPREFIX}hourly \
        --ignore-table=Somnode.${DBPREFIX}statcache \
        >> ${StagingDir}/Somnode_partial.sql
    if [[ $? -gt 0 ]] || [[ ! -f ${StagingDir}/Somnode_partial.sql ]]; then
        Output "Database backup was not successful." red;
        TriggerFailure
    fi
    Output "Partial database saved to ${StagingDir}/Somnode_partial.sql" green;
}
function BackupFullDatabase(){
    # Create database table structures
    ${BasePath}/mysql/bin/mysqldump \
    --protocol=SOCKET \
    --socket=${BasePath}/mysql/data/mysql.sock \
    --user=root \
    --password= \
    Somnode \
    --single-transaction \
    --opt \
    --quick \
    > ${StagingDir}/Somnode_full.sql;
    if [[ $? -gt 0 ]] || [[ ! -f ${StagingDir}/Somnode_full.sql ]]; then
        Output "Database backup was not successful." red;
        TriggerFailure
    fi
    Output "Full database saved to ${StagingDir}/Somnode_full.sql" green;
}
function BackupConfig(){
    BACKUPDIR=$StagingDir;
    BackupLetsEncrypt=""
    if [ -d ${BasePath}/letsencrypt ]; then
        echo "Backup ${BasePath}/letsencrypt";
        cd ${BasePath}/letsencrypt;
        tar cvf ${BACKUPDIR}/letsencrypt.tar ./;
        BackupLetsEncrypt="letsencrypt.tar";
    fi
    mkdir -p ${BACKUPDIR}/config
    mkdir -p ${BACKUPDIR}/config/nginx/conf.d/;
    if [ -f ${BasePath}/nginx/conf.d/ssl.conf ]; then
        cp -a ${BasePath}/nginx/conf.d/ssl.conf ${BACKUPDIR}/config/nginx/conf.d/ssl.conf;
        cp -a ${BasePath}/nginx/*.pem ${BACKUPDIR}/config/nginx/
        cp -a ${BasePath}/nginx/*.key ${BACKUPDIR}/config/nginx/
        cp -a ${BasePath}/icecast2/icecast.cert ${BACKUPDIR}/config/icecast.cert
    fi
    mkdir -p ${BACKUPDIR}/config/php/php.d;
    cp -a ${BasePath}/php/php.d/custom.ini ${BACKUPDIR}/config/php/php.d/custom.ini;
    cd ${BACKUPDIR}/config/;
    tar cvf ${BACKUPDIR}/config.tar ./;
    rm -rf ${BACKUPDIR}/config/;
    Output "Configurations saved to ${BACKUPDIR}/config.tar" green;
}
function BackupContent(){
    CONTENTDIR=`/usr/local/Somnode/php/bin/php -r 'require "/usr/local/Somnode/htdocs/database.php"; echo $setting["content_path"];'`
    cd ${CONTENTDIR};
    tar --exclude='*.sock' --exclude='*.lock' --exclude='.ffmpeg-*' -cf ${StagingDir}/content.tar ./
    cd ${StagingDir};
    if [ ! -f ${StagingDir}/content.tar ]; then
        Output "ERROR! COULD NOT COMPILE content.tar" red;
        Output "BACKUP FAILED" red;
        TriggerFailure
    fi
    Output "Media Content saved in ${StagingDir}/content.tar" green;
}
function PackageDaily(){
    BACKUPDIR=${settings[backup_path]}
    cd ${StagingDir};
    ##
    ## Package database.tar
    ##
    echo "Packaging database.tar";
    if [[ "${schedule[isDailyHistorics]}" == "1" ]]; then
        tar --transform='flags=r;s|Somnode_full.sql|Somnode.sql|' -cf database.tar Somnode_full.sql
    else
        tar --transform='flags=r;s|Somnode_partial.sql|Somnode.sql|' -cf database.tar Somnode_partial.sql
    fi
    ##
    ## Package all archives
    ##
    echo "Packaging archives";
    tarLetsEncrypt="";
    tarContent="";
    if [[ -f content.tar ]] && [[ "${schedule[isDailyContent]}" == "1" ]]; then
        tarContent="content.tar";
    fi
    if [[ -f letsencrypt.tar ]]; then
        tarLetsEncrypt="letsencrypt.tar";
    fi
    echo "Compiling package ...";
    Filename=${BACKUPDIR}/daily/Somnode_${CurrentDate}.tar
    tar \
        -cvf \
        ${Filename} \
        database.tar \
        htdocs.tar \
        config.tar \
        contentdir \
        ${tarContent} \
        ${tarLetsEncrypt};
    Output "Backup saved at ${Filename}" green;
}
function PackageWeekly(){
    BACKUPDIR=${settings[backup_path]}
    cd ${StagingDir};
    ##
    ## Package database.tar
    ##
    echo "Packaging database.tar";
    if [[ -f database.tar ]]; then rm -f database.tar; fi
    if [[ "${schedule[isWeeklyHistorics]}" == "1" ]]; then
        tar --transform='flags=r;s|Somnode_full.sql|Somnode.sql|' -cf database.tar Somnode_full.sql
    else
        tar --transform='flags=r;s|Somnode_partial.sql|Somnode.sql|' -cf database.tar Somnode_partial.sql
    fi
    ##
    ## Package all archives
    ##
    echo "Packaging archives";
    tarLetsEncrypt="";
    tarContent="";
    if [[ "${schedule[isWeeklyContent]}" == "1" ]]; then
        tarContent="content.tar";
    fi
    if [[ -f letsencrypt.tar ]]; then
        tarLetsEncrypt="letsencrypt.tar";
    fi
    echo "Compiling package ...";
    Filename=${BACKUPDIR}/weekly/Somnode_${CurrentDate}.tar
    tar \
        -cvf \
        ${Filename} \
        database.tar \
        htdocs.tar \
        config.tar \
        contentdir \
        ${tarContent} \
        ${tarLetsEncrypt};
    echo "Backup saved at ${Filename}";
}
#
# backup.sh
# This script is designed to check and run automated backups
#
##
## Developer Inclusions
##
if [[ -f backup_functions.sh ]]; then source ./backup_functions.sh; fi
# Set Variables
CurrentDate=`date +"%F"`;
# Load Configurations
if [[ ! -f /usr/local/Somnode/htdocs/system/cron/backup.php ]]; then echo "backup.php missing, make sure you are using Somnode 2.7 or newer"; exit; fi
declare -A schedule='('`echo $(/usr/local/Somnode/php/bin/php /usr/local/Somnode/htdocs/system/cron/backup.php schedule)`')';
declare -A settings='('`echo $(/usr/local/Somnode/php/bin/php /usr/local/Somnode/htdocs/system/cron/backup.php config)`')';
CheckRunBackup
# Ensure logs directory exists
sudo -u Somnode mkdir -p /usr/local/Somnode/log/backup;
BackupFile=/usr/local/Somnode/log/backup/backup_`date +"%F_%H%M"`.log
RunBackup 2>&1 | tee ${BackupFile}
# Cleanup logs over 60 days old
find /usr/local/Somnode/log/backup/*.log -maxdepth 0 -mtime +60 -exec rm -f {} \;
# Trigger Email
/usr/local/Somnode/php/bin/php /usr/local/Somnode/htdocs/system/cron/backup.php sendMostRecentLogfile

