HEX
Server: LiteSpeed
System: Linux sarajevo.maychu.cloud 5.14.0-503.40.1.el9_5.x86_64 #1 SMP PREEMPT_DYNAMIC Mon May 5 06:06:04 EDT 2025 x86_64
User: inqua407 (1189)
PHP: 8.3.17
Disabled: exec,execl,system,passthru,shell_exec,escapeshellarg,escapeshellcmd,proc_close,ini_alter,proc_open,dl,popen,show_source,posix_getpwuid,getpwuid,posix_geteuid,posix_getegid,posix_getgrgid,open_basedir,safe_mode_include_dir,pcntl_exec,pcntl_fork,proc_get_status,proc_nice,proc_terminate,pclose,virtual,openlog,popen,pclose,virtual,openlog,escapeshellcmd,escapeshellarg,dl,show_source,symlink,mail
Upload Files
File: //var/www/html/install_smartbackup.sh
#!/bin/bash

# ==============================================================================
# SMART BACKUP INSTALLER - AUTOMATED DEPLOYMENT
# Features: Single Job, Auto Config Switch, Telegram Alerts, Weekly Toggle
# Updated: Handle BACKUP_WEEKLY_ENABLE & FORCE CRON EXECUTION
# ==============================================================================

echo ">>> [1/7] Installing Perl Dependencies..."
/usr/local/cpanel/scripts/perlinstaller JSON YAML::Syck LWP::UserAgent
if [ $? -ne 0 ]; then
    echo "ERROR: Failed to install Perl modules."
    exit 1
fi

echo ">>> [2/7] Creating Directories..."
mkdir -p /usr/local/cpanel/whostmgr/docroot/cgi/smartbackup
mkdir -p /scripts

# ==============================================================================
# FILE 1: WHM PLUGIN APPCONFIG
# ==============================================================================
echo ">>> [3/7] Generating Plugin Config..."
cat << 'EOF' > /var/cpanel/apps/smartbackup.conf
# name: Smart Backup & Alert
name=SmartBackupAlert
service=whostmgr
url=/cgi/smartbackup/index.cgi
acls=all
displayname=Smart Backup & Telegram Alert
entryurl=smartbackup/index.cgi
EOF

# ==============================================================================
# FILE 2: USER INTERFACE (index.cgi) - SINGLE JOB
# ==============================================================================
echo ">>> [4/7] Generating UI Script (index.cgi)..."
cat << 'EOF' > /usr/local/cpanel/whostmgr/docroot/cgi/smartbackup/index.cgi
#!/usr/local/cpanel/3rdparty/bin/perl
use strict;
use warnings;
use CGI;
use JSON;
use YAML::Syck; 
use Whostmgr::ACLS ();

my $config_file = '/etc/smartbackup_config.json';
my $cp_dest_dir = -d '/var/cpanel/backups/destinations' ? '/var/cpanel/backups/destinations' : '/var/cpanel/backups';

Whostmgr::ACLS::init_acls();
if (!Whostmgr::ACLS::hasroot()) { print "Content-type: text/html\r\n\r\nAccess Denied"; exit; }

my $q = CGI->new;

if ($q->param('save_config')) {
    my %conf = (
        telegram_token   => $q->param('telegram_token'),
        telegram_chat_id => $q->param('telegram_chat_id'),
        job_dest_id      => $q->param('job_dest_id'),
        job_dates        => $q->param('job_dates'),
        is_active        => $q->param('is_active') ? 1 : 0,
    );
    save_json(\%conf);
    print $q->redirect(-uri => '/cgi/smartbackup/index.cgi?saved=1');
    exit;
}

my $current_conf = load_json();
my $destinations = get_cpanel_destinations();
my $msg = $q->param('saved') ? '<div class="alert success">Configuration Saved Successfully!</div>' : '';

print "Content-type: text/html; charset=utf-8\r\n\r\n";

print <<HTML;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Smart Backup Manager</title>
    <link rel="stylesheet" href="/cPanel_magic_revision_0/goog-goog-goog/combined_std.css">
    <style>
        body { background: #fff; font-family: sans-serif; padding: 20px; }
        .container { max-width: 650px; margin: 0 auto; }
        .card { border: 1px solid #ddd; padding: 20px; border-radius: 5px; background: #f9f9f9; }
        .form-group { margin-bottom: 15px; }
        label { font-weight: bold; display: block; margin-bottom: 5px; }
        input[type="text"], select { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
        .btn-save { background: #28a745; color: white; padding: 10px 20px; border: none; cursor: pointer; width: 100%; font-weight: bold; }
        .alert { padding: 10px; background: #d4edda; color: #155724; margin-bottom: 15px; border-radius: 4px; }
    </style>
</head>
<body>
<div class="container">
    <h3 style="text-align: center;">Smart Backup Automation</h3>
    $msg
    <div class="card">
        <form method="post" action="index.cgi">
            <input type="hidden" name="save_config" value="1">
            <h4>1. Telegram Settings</h4>
            <div class="form-group"><label>Bot Token</label><input type="text" name="telegram_token" value="$current_conf->{telegram_token}"></div>
            <div class="form-group"><label>Chat ID</label><input type="text" name="telegram_chat_id" value="$current_conf->{telegram_chat_id}"></div>
            
            <hr>
            <h4>2. Remote Backup Schedule</h4>
            <div class="form-group">
                <label>Remote Destination:</label>
                <select name="job_dest_id"><option value="">-- Select Server --</option>@{[ build_options($destinations, $current_conf->{job_dest_id}) ]}</select>
            </div>
            <div class="form-group">
                <label>Run Dates (comma separated):</label>
                <input type="text" name="job_dates" value="$current_conf->{job_dates}" placeholder="e.g.: 15, 30">
            </div>

            <div class="form-group" style="margin-top:20px">
                 <label><input type="checkbox" name="is_active" @{[ $current_conf->{is_active} ? 'checked' : '' ]}> Enable Automation</label>
            </div>
            <button type="submit" class="btn-save">SAVE CONFIG</button>
        </form>
    </div>
</div>
</body>
</html>
HTML

sub get_cpanel_destinations {
    opendir(my $dh, $cp_dest_dir) || return {};
    my @files = grep { /(\.yaml|\.backup_destination)$/i } readdir($dh);
    closedir $dh;
    my %dests;
    foreach my $file (@files) {
        my $path = "$cp_dest_dir/$file";
        my $data = eval { YAML::Syck::LoadFile($path) };
        next if !$data;
        my $id = $data->{id};
        if (!$id) { $id = $file; $id =~ s/(\.yaml|\.backup_destination)$//i; }
        $dests{$id} = $data->{name} || $id;
    }
    return \%dests;
}

sub build_options {
    my ($dests, $selected) = @_;
    my $html = '';
    foreach my $id (sort keys %$dests) {
        my $sel = ($selected && $selected eq $id) ? 'selected' : '';
        $html .= qq{<option value="$id" $sel>$dests->{$id}</option>};
    }
    return $html;
}

sub load_json {
    if (-e $config_file) { open my $fh, '<', $config_file or return {}; local $/; return decode_json(<$fh>); }
    return { is_active => 1 };
}
sub save_json {
    my ($data) = @_; open my $fh, '>', $config_file; print $fh encode_json($data); close $fh;
}
EOF

# ==============================================================================
# FILE 3: CRON SCRIPT (The Opener) - Config Switcher & Force Cron
# ==============================================================================
echo ">>> [5/7] Generating Cron Script (smart_backup_config.pl)..."
cat << 'EOF' > /scripts/smart_backup_config.pl
#!/usr/local/cpanel/3rdparty/bin/perl
use strict;
use warnings;
use Cpanel::Config::LoadCpConf ();
use YAML::Syck;
use POSIX qw(strftime);
use JSON;
use LWP::UserAgent;
use Sys::Hostname;

my $ui_config_file = '/etc/smartbackup_config.json';
my $cp_conf_file   = '/var/cpanel/backups/config';
my $cp_backup_lock = '/var/cpanel/backups/lock'; 
my $dest_dir       = -d '/var/cpanel/backups/destinations' ? '/var/cpanel/backups/destinations' : '/var/cpanel/backups';

if (-e $cp_backup_lock) { exit 0; } # Safety Lock
if (! -e $ui_config_file) { exit 0; }
my $conf = load_json_config($ui_config_file);
exit 0 unless $conf->{is_active};

my $today_date = int(strftime "%d", localtime); 
my $is_remote_day = 0;
my %dests_status;

if ($conf->{job_dest_id}) { 
    $dests_status{ $conf->{job_dest_id} } = 0; # Default Disable
    if (check_date_match($today_date, $conf->{job_dates})) {
        $dests_status{ $conf->{job_dest_id} } = 1; # Enable
        $is_remote_day = 1;
    }
}

apply_destinations_status(\%dests_status);

if ($is_remote_day) {
    # MODE REMOTE: Compressed + No Local Retain + No Weekly + FORCE CRON
    update_cpanel_config('compressed', 0, 'no'); 
    
    # Inject --force into root crontab for /usr/local/cpanel/bin/backup
    modify_system_cron(1);
    
    send_telegram($conf, "šŸ”” <b>SmartBackup Notification</b>\nšŸ“… Date Match: <b>$today_date</b>\nšŸ”„ Mode: <b>REMOTE (Compressed / No Local)</b>\n🚫 Weekly Backup: <b>Disabled</b>\n⚔ System Cron: <b>Added --force</b>\nāœ… Destination Enabled.");
} else {
    # MODE LOCAL: Incremental + Keep Local + Yes Weekly
    update_cpanel_config('incremental', 1, 'yes');
    # Ensure --force is removed if it stuck
    modify_system_cron(0);
}

sub send_telegram {
    my ($conf, $msg) = @_;
    return unless ($conf->{telegram_token} && $conf->{telegram_chat_id});
    $msg .= "\nšŸ–„ Server: " . hostname;
    my $ua = LWP::UserAgent->new; $ua->timeout(10);
    $ua->post("https://api.telegram.org/bot$conf->{telegram_token}/sendMessage", { chat_id => $conf->{telegram_chat_id}, text => $msg, parse_mode => 'HTML' });
}

sub check_date_match {
    my ($today, $date_str) = @_; return 0 unless $date_str;
    my @dates = split /[\s,]+/, $date_str;
    foreach my $d (@dates) { return 1 if ($d == $today); }
    return 0;
}

sub apply_destinations_status {
    my ($target_map) = @_;
    opendir(my $dh, $dest_dir) || die $!;
    my @files = grep { /(\.yaml|\.backup_destination)$/i } readdir($dh);
    closedir $dh;
    foreach my $file (@files) {
        my $path = "$dest_dir/$file";
        my $data = eval { YAML::Syck::LoadFile($path) };
        next if !$data;
        my $id = $data->{id};
        if (!$id) { $id = $file; $id =~ s/(\.yaml|\.backup_destination)$//i; }
        if (exists $target_map->{$id}) {
            my $should = $target_map->{$id};
            my $is_dis = $data->{disabled} // 0;
            if ($should && $is_dis != 0) { $data->{disabled} = 0; YAML::Syck::DumpFile($path, $data); }
            elsif (!$should && $is_dis == 0) { $data->{disabled} = 1; YAML::Syck::DumpFile($path, $data); }
        }
    }
}

sub update_cpanel_config {
    my ($type_val, $keep_local_val, $weekly_val) = @_;
    local $/; open(my $fh, '<', $cp_conf_file) or return; my $c = <$fh>; close($fh);
    my $ch = 0;
    
    # Update BACKUPTYPE
    if ($c =~ s/BACKUPTYPE:\s*\w+/BACKUPTYPE: $type_val/) { $ch = 1; }
    
    # Update KEEPLOCAL
    if ($c =~ s/KEEPLOCAL:\s*\d/KEEPLOCAL: $keep_local_val/) { $ch = 1; }
    
    # Update BACKUP_WEEKLY_ENABLE
    if ($c =~ s/BACKUP_WEEKLY_ENABLE:\s*['"]?\w+['"]?/BACKUP_WEEKLY_ENABLE: '$weekly_val'/) { $ch = 1; }
    
    if ($ch) { open(my $fw, '>', $cp_conf_file); print $fw $c; close($fw); }
}

sub modify_system_cron {
    my ($add_force) = @_;
    my @lines = `crontab -l 2>/dev/null`;
    return unless @lines;
    
    my $changed = 0;
    my $output = "";
    
    foreach my $line (@lines) {
        # Skip comment lines
        if ($line =~ /^\s*#/ ) { $output .= $line; next; }
        
        # Look for cPanel backup line
        if ($line =~ /\/usr\/local\/cpanel\/bin\/backup/) {
            if ($add_force && $line !~ /--force/) {
                chomp($line);
                $line .= " --force\n";
                $changed = 1;
            } elsif (!$add_force && $line =~ /--force/) {
                $line =~ s/\s+--force//g;
                $changed = 1;
            }
        }
        $output .= $line;
    }
    
    if ($changed) {
        open(my $fh, "| crontab -") or return;
        print $fh $output;
        close($fh);
    }
}

sub load_json_config {
    my ($file) = @_; local $/; open(my $fh, '<', $file) or return {}; return decode_json(<$fh>);
}
EOF

# ==============================================================================
# FILE 4: PRE-HOOK (The Announcer)
# ==============================================================================
echo ">>> [6/7] Generating Hooks..."
cat << 'EOF' > /scripts/smart_backup_pre_hook.pl
#!/usr/local/cpanel/3rdparty/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use JSON;
use Sys::Hostname;

my $arg = $ARGV[0] // '';
if ($arg eq '--describe') {
    print encode_json({ 'category' => 'System', 'event' => 'Backup', 'stage' => 'pre', 'hook' => '/scripts/smart_backup_pre_hook.pl', 'exectype' => 'script' });
    exit;
}

my $config_file = '/etc/smartbackup_config.json';
if (-e $config_file) {
    local $/; open(my $fh, '<', $config_file); my $conf = decode_json(<$fh>); close($fh);
    if ($conf->{is_active} && $conf->{telegram_token}) {
        my $ua = LWP::UserAgent->new;
        my $msg = "šŸš€ <b>Backup Process STARTED</b>\nšŸ–„ Server: " . hostname . "\nā³ System is now backing up accounts...";
        $ua->post("https://api.telegram.org/bot$conf->{telegram_token}/sendMessage", { chat_id => $conf->{telegram_chat_id}, text => $msg, parse_mode => 'HTML' });
    }
}
EOF

# ==============================================================================
# FILE 5: POST-HOOK (The Closer & Rollback & Un-Force)
# ==============================================================================
cat << 'EOF' > /scripts/smart_backup_post_hook.pl
#!/usr/local/cpanel/3rdparty/bin/perl
use strict;
use warnings;
use LWP::UserAgent;
use JSON;
use Sys::Hostname;
use YAML::Syck;

my $arg = $ARGV[0] // '';
if ($arg eq '--describe') {
    print encode_json({ 'category' => 'System', 'event' => 'Backup', 'stage' => 'post', 'hook' => '/scripts/smart_backup_post_hook.pl', 'exectype' => 'script' });
    exit;
}

my $ui_config_file = '/etc/smartbackup_config.json';
my $cp_conf_file   = '/var/cpanel/backups/config';
my $dest_dir       = -d '/var/cpanel/backups/destinations' ? '/var/cpanel/backups/destinations' : '/var/cpanel/backups';

disable_managed_destination();

# Rollback: Incremental, KeepLocal 1, Weekly Yes
rollback_cpanel_defaults('incremental', 1, 'yes');

# Rollback Cron: REMOVE --force
modify_system_cron(0);

send_finish_report();

sub disable_managed_destination {
    return unless -e $ui_config_file;
    my $conf = load_json_config($ui_config_file);
    return unless $conf->{is_active} && $conf->{job_dest_id};
    opendir(my $dh, $dest_dir) || return;
    my @files = grep { /(\.yaml|\.backup_destination)$/i } readdir($dh);
    closedir $dh;
    foreach my $file (@files) {
        my $path = "$dest_dir/$file"; my $data = eval { YAML::Syck::LoadFile($path) }; next if !$data;
        my $id = $data->{id}; if (!$id) { $id = $file; $id =~ s/(\.yaml|\.backup_destination)$//i; }
        if ($id eq $conf->{job_dest_id}) {
            if (!defined $data->{disabled} || $data->{disabled} == 0) { $data->{disabled} = 1; YAML::Syck::DumpFile($path, $data); }
        }
    }
}

sub rollback_cpanel_defaults {
    my ($type_val, $keep_local_val, $weekly_val) = @_;
    local $/; open(my $fh, '<', $cp_conf_file) or return; my $c = <$fh>; close($fh);
    my $ch = 0;
    
    if ($c =~ s/BACKUPTYPE:\s*\w+/BACKUPTYPE: $type_val/) { $ch = 1; }
    if ($c =~ s/KEEPLOCAL:\s*\d/KEEPLOCAL: $keep_local_val/) { $ch = 1; }
    if ($c =~ s/BACKUP_WEEKLY_ENABLE:\s*['"]?\w+['"]?/BACKUP_WEEKLY_ENABLE: '$weekly_val'/) { $ch = 1; }
    
    if ($ch) { open(my $fw, '>', $cp_conf_file); print $fw $c; close($fw); }
}

sub modify_system_cron {
    my ($add_force) = @_;
    my @lines = `crontab -l 2>/dev/null`;
    return unless @lines;
    
    my $changed = 0;
    my $output = "";
    
    foreach my $line (@lines) {
        if ($line =~ /^\s*#/ ) { $output .= $line; next; }
        
        # Look for cPanel backup line
        if ($line =~ /\/usr\/local\/cpanel\/bin\/backup/) {
            if ($add_force && $line !~ /--force/) {
                chomp($line);
                $line .= " --force\n";
                $changed = 1;
            } elsif (!$add_force && $line =~ /--force/) {
                $line =~ s/\s+--force//g;
                $changed = 1;
            }
        }
        $output .= $line;
    }
    
    if ($changed) {
        open(my $fh, "| crontab -") or return;
        print $fh $output;
        close($fh);
    }
}

sub send_finish_report {
    return unless -e $ui_config_file;
    my $conf = load_json_config($ui_config_file);
    return unless ($conf->{telegram_token} && $conf->{telegram_chat_id});
    my $log_dir = '/usr/local/cpanel/logs/cpbackup';
    my $latest_log = `ls -t $log_dir/*.log 2>/dev/null | head -1`; chomp($latest_log);
    my $icon = "āœ…"; my $txt = "Backup & Upload Completed";
    if ($latest_log && -e $latest_log) {
        my $errs = `grep -E "Error|Failed" $latest_log | wc -l`; chomp($errs);
        if ($errs > 0) { $icon = "āŒ"; $txt = "Completed with errors"; }
    }
    my $msg = "<b>$icon Backup FINISHED</b>\nšŸ–„ " . hostname . "\n----------------\nšŸ“Š $txt\nā†©ļø Config: Rolled back to Local/Incremental\nāœ… Weekly Backup: Re-enabled\nšŸ”„ Cron: <b>Removed --force</b>\nšŸ”’ Remote Destination Disabled.";
    my $ua = LWP::UserAgent->new; $ua->post("https://api.telegram.org/bot$conf->{telegram_token}/sendMessage", { chat_id => $conf->{telegram_chat_id}, text => $msg, parse_mode => 'HTML' });
}

sub load_json_config { my ($f)=@_; local $/; open(my $fh,'<',$f) or return {}; return decode_json(<$fh>); }
EOF

# ==============================================================================
# SET PERMISSIONS & REGISTER
# ==============================================================================
echo ">>> [7/7] Finalizing Setup..."

# Set Perms
chmod 755 /usr/local/cpanel/whostmgr/docroot/cgi/smartbackup/index.cgi
chmod 755 /scripts/smart_backup_config.pl
chmod 755 /scripts/smart_backup_pre_hook.pl
chmod 755 /scripts/smart_backup_post_hook.pl

# Register Plugin
/usr/local/cpanel/bin/register_appconfig /var/cpanel/apps/smartbackup.conf

# Clean & Register Hooks
/usr/local/cpanel/bin/manage_hooks delete script /scripts/smart_backup_pre_hook.pl --category System --event Backup --stage pre >/dev/null 2>&1
/usr/local/cpanel/bin/manage_hooks delete script /scripts/smart_backup_post_hook.pl --category System --event Backup --stage post >/dev/null 2>&1

/usr/local/cpanel/bin/manage_hooks add script /scripts/smart_backup_pre_hook.pl --category System --event Backup --stage pre
/usr/local/cpanel/bin/manage_hooks add script /scripts/smart_backup_post_hook.pl --category System --event Backup --stage post

# Setup Cron Job (12:15 AM = 00:15)
CRON_CMD="15 9 * * * /usr/local/cpanel/3rdparty/bin/perl /scripts/smart_backup_config.pl >> /var/log/smart_backup.log 2>&1"
(crontab -l 2>/dev/null | grep -v "/scripts/smart_backup_config.pl"; echo "$CRON_CMD") | crontab -

echo "=========================================================="
echo " INSTALLATION COMPLETED SUCCESSFULLY! "
echo "=========================================================="
echo "1. Plugin UI: WHM -> Plugins -> Smart Backup & Telegram Alert"
echo "2. Cron Job: Set to run at 00:15 Daily."
echo "3. Added logic to INJECT --force into system cron on remote days."
echo "4. Added logic to REMOVE --force from system cron after backup."
echo "=========================================================="