update dix
This commit is contained in:
parent
e921a49d5b
commit
11691a5876
11 changed files with 1108 additions and 54 deletions
|
|
@ -340,6 +340,7 @@ my $d = Frontier::Daemon::OGP::Forking->new(
|
||||||
fastdl_get_info => \&fastdl_get_info,
|
fastdl_get_info => \&fastdl_get_info,
|
||||||
fastdl_create_config => \&fastdl_create_config,
|
fastdl_create_config => \&fastdl_create_config,
|
||||||
agent_restart => \&agent_restart,
|
agent_restart => \&agent_restart,
|
||||||
|
component_update => \&component_update,
|
||||||
scheduler_add_task => \&scheduler_add_task,
|
scheduler_add_task => \&scheduler_add_task,
|
||||||
scheduler_del_task => \&scheduler_del_task,
|
scheduler_del_task => \&scheduler_del_task,
|
||||||
scheduler_list_tasks => \&scheduler_list_tasks,
|
scheduler_list_tasks => \&scheduler_list_tasks,
|
||||||
|
|
@ -3498,6 +3499,162 @@ sub fastdl_create_config
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub component_update_shell_quote
|
||||||
|
{
|
||||||
|
my ($value) = @_;
|
||||||
|
$value = '' unless defined $value;
|
||||||
|
$value =~ s/'/'"'"'/g;
|
||||||
|
return "'" . $value . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub component_update_response
|
||||||
|
{
|
||||||
|
my (%data) = @_;
|
||||||
|
my %encoded = ();
|
||||||
|
foreach my $key (keys %data)
|
||||||
|
{
|
||||||
|
$encoded{$key} = encode_base64(defined $data{$key} ? $data{$key} : '', '');
|
||||||
|
}
|
||||||
|
return \%encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub component_update_parse_payload
|
||||||
|
{
|
||||||
|
my ($payload) = @_;
|
||||||
|
my %data = ();
|
||||||
|
foreach my $line (split(/\r?\n/, $payload || ''))
|
||||||
|
{
|
||||||
|
next if $line !~ /^([A-Za-z0-9_]+)=(.*)$/;
|
||||||
|
$data{$1} = decode_base64($2);
|
||||||
|
}
|
||||||
|
return %data;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub component_update
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'bad_encryption', message => 'Bad Encryption Key')
|
||||||
|
unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
||||||
|
my ($payload) = decrypt_params(@_);
|
||||||
|
my %cfg = component_update_parse_payload($payload);
|
||||||
|
|
||||||
|
my $component = $cfg{component} || 'windows_agent';
|
||||||
|
if ($component ne 'linux_agent' && $component ne 'windows_agent')
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_component', message => 'Invalid component.');
|
||||||
|
}
|
||||||
|
my $repo_url = $cfg{repo_url} || '';
|
||||||
|
my $branch = $cfg{branch} || '';
|
||||||
|
my $source_path = $cfg{source_path} || '';
|
||||||
|
my $install_path = $cfg{install_path} || AGENT_RUN_DIR;
|
||||||
|
my $git_path = $cfg{git_path} || 'git';
|
||||||
|
my $backup_path = $cfg{backup_path} || Path::Class::Dir->new(AGENT_RUN_DIR, 'component_backups')->stringify;
|
||||||
|
my $post_update_command = $cfg{post_update_command} || '';
|
||||||
|
|
||||||
|
if ($repo_url !~ m{^(https?://|ssh://|git@|/)} || $repo_url =~ /[\r\n\0]/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_repo', message => 'Invalid repository URL or path.');
|
||||||
|
}
|
||||||
|
if ($branch !~ /^[A-Za-z0-9._\/-]{1,128}$/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_branch', message => 'Invalid branch.');
|
||||||
|
}
|
||||||
|
if ($source_path eq '' || $source_path =~ /\.\./ || $source_path =~ /[\r\n\0]/ || $source_path !~ /^[A-Za-z0-9._\/-]+$/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_source_path', message => 'Invalid source path.');
|
||||||
|
}
|
||||||
|
if ($install_path !~ m{^/} || $install_path =~ /\.\./ || $install_path =~ /[\r\n\0]/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_install_path', message => 'Invalid install path.');
|
||||||
|
}
|
||||||
|
if ($post_update_command =~ /[\r\n\0]/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_post_update', message => 'Post-update command must be a single line.');
|
||||||
|
}
|
||||||
|
|
||||||
|
my $ts = time();
|
||||||
|
my $script_path = Path::Class::File->new(AGENT_RUN_DIR, "gsp_component_update_$ts.sh")->stringify;
|
||||||
|
my $log_path = Path::Class::File->new(AGENT_RUN_DIR, 'gsp_component_update.log')->stringify;
|
||||||
|
my $screenrc = SCREENRC_FILE;
|
||||||
|
my $repo_q = component_update_shell_quote($repo_url);
|
||||||
|
my $branch_q = component_update_shell_quote($branch);
|
||||||
|
my $source_q = component_update_shell_quote($source_path);
|
||||||
|
my $install_q = component_update_shell_quote($install_path);
|
||||||
|
my $git_q = component_update_shell_quote($git_path);
|
||||||
|
my $backup_q = component_update_shell_quote($backup_path);
|
||||||
|
my $post_q = component_update_shell_quote($post_update_command);
|
||||||
|
my $log_q = component_update_shell_quote($log_path);
|
||||||
|
my $screenrc_q = component_update_shell_quote($screenrc);
|
||||||
|
|
||||||
|
my $script = <<"EOS";
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -u
|
||||||
|
LOG=$log_q
|
||||||
|
log(){ printf '[%s] %s\\n' "\$(date '+%Y-%m-%d %H:%M:%S')" "\$*" >> "\$LOG"; }
|
||||||
|
fail(){ log "FAILED: \$*"; exit 1; }
|
||||||
|
REPO=$repo_q
|
||||||
|
BRANCH=$branch_q
|
||||||
|
SOURCE_REL=$source_q
|
||||||
|
DEST=$install_q
|
||||||
|
GIT_BIN=$git_q
|
||||||
|
BACKUP_BASE=$backup_q
|
||||||
|
POST_UPDATE=$post_q
|
||||||
|
TMP="\${TMPDIR:-/tmp}/gsp_agent_update_\$(date +%s)_\$\$"
|
||||||
|
mkdir -p "\$TMP" "\$BACKUP_BASE" || fail "Cannot create staging or backup directories"
|
||||||
|
log "queued component=$component repo=\$REPO branch=\$BRANCH source=\$SOURCE_REL dest=\$DEST"
|
||||||
|
if ! command -v "\$GIT_BIN" >/dev/null 2>&1 && [ ! -x "\$GIT_BIN" ]; then
|
||||||
|
fail "Git executable not found: \$GIT_BIN"
|
||||||
|
fi
|
||||||
|
"\$GIT_BIN" clone --depth 1 --branch "\$BRANCH" "\$REPO" "\$TMP/repo" >> "\$LOG" 2>&1 || fail "git clone failed"
|
||||||
|
SRC="\$TMP/repo/\$SOURCE_REL"
|
||||||
|
[ -d "\$SRC" ] || fail "Source component folder missing: \$SRC"
|
||||||
|
case "\$DEST" in /*) ;; *) fail "Destination must be absolute: \$DEST" ;; esac
|
||||||
|
mkdir -p "\$DEST" || fail "Cannot create destination: \$DEST"
|
||||||
|
ARCHIVE="\$BACKUP_BASE/${component}_\$(date +%Y%m%d_%H%M%S).tar.gz"
|
||||||
|
tar --exclude='./Cfg' --exclude='./ServerFiles' --exclude='./Schedule' --exclude='./logs' --exclude='./screenlogs' --exclude='./steamcmd' --exclude='./startups' --exclude='./tmp' --exclude='./shared' --exclude='./component_backups' --exclude='./*.pid' -czf "\$ARCHIVE" -C "\$DEST" . >> "\$LOG" 2>&1 || fail "Backup failed"
|
||||||
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
|
rsync -a --exclude='Cfg/' --exclude='ServerFiles/' --exclude='Schedule/' --exclude='logs/' --exclude='screenlogs/' --exclude='steamcmd/' --exclude='startups/' --exclude='tmp/' --exclude='shared/' --exclude='component_backups/' --exclude='*.pid' "\$SRC"/ "\$DEST"/ >> "\$LOG" 2>&1 || fail "rsync copy failed"
|
||||||
|
else
|
||||||
|
(cd "\$SRC" && tar --exclude='./Cfg' --exclude='./ServerFiles' --exclude='./Schedule' --exclude='./logs' --exclude='./screenlogs' --exclude='./steamcmd' --exclude='./startups' --exclude='./tmp' --exclude='./shared' --exclude='./component_backups' --exclude='./*.pid' -cf - .) | (cd "\$DEST" && tar -xf -) >> "\$LOG" 2>&1 || fail "tar copy failed"
|
||||||
|
fi
|
||||||
|
if [ -f "\$DEST/ogp_agent.pl" ]; then
|
||||||
|
perl -c "\$DEST/ogp_agent.pl" >> "\$LOG" 2>&1 || fail "Updated ogp_agent.pl failed syntax validation"
|
||||||
|
fi
|
||||||
|
if [ -n "\$POST_UPDATE" ]; then
|
||||||
|
(cd "\$DEST" && bash -lc "\$POST_UPDATE") >> "\$LOG" 2>&1 || fail "post-update command failed"
|
||||||
|
fi
|
||||||
|
log "copy complete archive=\$ARCHIVE"
|
||||||
|
sleep 2
|
||||||
|
cd "\$DEST" || exit 0
|
||||||
|
if [ -f ogp_agent_run.pid ]; then kill "\$(cat ogp_agent_run.pid)" >/dev/null 2>&1 || true; fi
|
||||||
|
if [ -f ogp_agent.pid ]; then kill "\$(cat ogp_agent.pid)" >/dev/null 2>&1 || true; fi
|
||||||
|
sleep 2
|
||||||
|
if [ -f ogp_agent_run ]; then
|
||||||
|
screen -d -m -t "ogp_agent" -c $screenrc_q -S ogp_agent bash ogp_agent_run -pidfile ogp_agent_run.pid >> "\$LOG" 2>&1 || true
|
||||||
|
else
|
||||||
|
screen -d -m -t "ogp_agent" -c $screenrc_q -S ogp_agent perl ogp_agent.pl >> "\$LOG" 2>&1 || true
|
||||||
|
fi
|
||||||
|
log "restart attempted"
|
||||||
|
rm -rf "\$TMP"
|
||||||
|
exit 0
|
||||||
|
EOS
|
||||||
|
|
||||||
|
my $fh;
|
||||||
|
if (!open($fh, '>', $script_path))
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'write_failed', message => "Cannot write updater script: $!");
|
||||||
|
}
|
||||||
|
print $fh $script;
|
||||||
|
close($fh);
|
||||||
|
chmod 0700, $script_path;
|
||||||
|
system('screen -d -m -t "component_update" -c "' . SCREENRC_FILE . '" -S component_update bash ' . component_update_shell_quote($script_path));
|
||||||
|
if ($? != 0)
|
||||||
|
{
|
||||||
|
system('bash ' . component_update_shell_quote($script_path) . ' >/dev/null 2>&1 &');
|
||||||
|
}
|
||||||
|
logger "Component update queued for $component from $repo_url branch $branch.";
|
||||||
|
return component_update_response(success => 1, status => 'queued', message => 'Component update queued on agent.', log_path => $log_path, script_path => $script_path);
|
||||||
|
}
|
||||||
|
|
||||||
sub agent_restart
|
sub agent_restart
|
||||||
{
|
{
|
||||||
return "Bad Encryption Key" unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
return "Bad Encryption Key" unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,7 @@ my $d = Frontier::Daemon::OGP::Forking->new(
|
||||||
fastdl_get_info => \&fastdl_get_info,
|
fastdl_get_info => \&fastdl_get_info,
|
||||||
fastdl_create_config => \&fastdl_create_config,
|
fastdl_create_config => \&fastdl_create_config,
|
||||||
agent_restart => \&agent_restart,
|
agent_restart => \&agent_restart,
|
||||||
|
component_update => \&component_update,
|
||||||
scheduler_add_task => \&scheduler_add_task,
|
scheduler_add_task => \&scheduler_add_task,
|
||||||
scheduler_del_task => \&scheduler_del_task,
|
scheduler_del_task => \&scheduler_del_task,
|
||||||
scheduler_list_tasks => \&scheduler_list_tasks,
|
scheduler_list_tasks => \&scheduler_list_tasks,
|
||||||
|
|
@ -4673,6 +4674,165 @@ sub fastdl_create_config
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub component_update_shell_quote
|
||||||
|
{
|
||||||
|
my ($value) = @_;
|
||||||
|
$value = '' unless defined $value;
|
||||||
|
$value =~ s/'/'"'"'/g;
|
||||||
|
return "'" . $value . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub component_update_response
|
||||||
|
{
|
||||||
|
my (%data) = @_;
|
||||||
|
my %encoded = ();
|
||||||
|
foreach my $key (keys %data)
|
||||||
|
{
|
||||||
|
$encoded{$key} = encode_base64(defined $data{$key} ? $data{$key} : '', '');
|
||||||
|
}
|
||||||
|
return \%encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub component_update_parse_payload
|
||||||
|
{
|
||||||
|
my ($payload) = @_;
|
||||||
|
my %data = ();
|
||||||
|
foreach my $line (split(/\r?\n/, $payload || ''))
|
||||||
|
{
|
||||||
|
next if $line !~ /^([A-Za-z0-9_]+)=(.*)$/;
|
||||||
|
$data{$1} = decode_base64($2);
|
||||||
|
}
|
||||||
|
return %data;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub component_update
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'bad_encryption', message => 'Bad Encryption Key')
|
||||||
|
unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
||||||
|
my ($payload) = decrypt_params(@_);
|
||||||
|
my %cfg = component_update_parse_payload($payload);
|
||||||
|
|
||||||
|
my $component = $cfg{component} || 'linux_agent';
|
||||||
|
if ($component ne 'linux_agent' && $component ne 'windows_agent')
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_component', message => 'Invalid component.');
|
||||||
|
}
|
||||||
|
my $repo_url = $cfg{repo_url} || '';
|
||||||
|
my $branch = $cfg{branch} || '';
|
||||||
|
my $source_path = $cfg{source_path} || '';
|
||||||
|
my $install_path = $cfg{install_path} || AGENT_RUN_DIR;
|
||||||
|
my $git_path = $cfg{git_path} || 'git';
|
||||||
|
my $backup_path = $cfg{backup_path} || Path::Class::Dir->new(AGENT_RUN_DIR, 'component_backups')->stringify;
|
||||||
|
my $post_update_command = $cfg{post_update_command} || '';
|
||||||
|
|
||||||
|
if ($repo_url !~ m{^(https?://|ssh://|git@|/)} || $repo_url =~ /[\r\n\0]/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_repo', message => 'Invalid repository URL or path.');
|
||||||
|
}
|
||||||
|
if ($branch !~ /^[A-Za-z0-9._\/-]{1,128}$/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_branch', message => 'Invalid branch.');
|
||||||
|
}
|
||||||
|
if ($source_path eq '' || $source_path =~ /\.\./ || $source_path =~ /[\r\n\0]/ || $source_path !~ /^[A-Za-z0-9._\/-]+$/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_source_path', message => 'Invalid source path.');
|
||||||
|
}
|
||||||
|
if ($install_path !~ m{^/} || $install_path =~ /\.\./ || $install_path =~ /[\r\n\0]/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_install_path', message => 'Invalid install path.');
|
||||||
|
}
|
||||||
|
if ($post_update_command =~ /[\r\n\0]/)
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'invalid_post_update', message => 'Post-update command must be a single line.');
|
||||||
|
}
|
||||||
|
|
||||||
|
my $ts = time();
|
||||||
|
my $script_path = Path::Class::File->new(AGENT_RUN_DIR, "gsp_component_update_$ts.sh")->stringify;
|
||||||
|
my $log_path = Path::Class::File->new(AGENT_RUN_DIR, 'gsp_component_update.log')->stringify;
|
||||||
|
my $screenrc = SCREENRC_FILE;
|
||||||
|
my $repo_q = component_update_shell_quote($repo_url);
|
||||||
|
my $branch_q = component_update_shell_quote($branch);
|
||||||
|
my $source_q = component_update_shell_quote($source_path);
|
||||||
|
my $install_q = component_update_shell_quote($install_path);
|
||||||
|
my $git_q = component_update_shell_quote($git_path);
|
||||||
|
my $backup_q = component_update_shell_quote($backup_path);
|
||||||
|
my $post_q = component_update_shell_quote($post_update_command);
|
||||||
|
my $log_q = component_update_shell_quote($log_path);
|
||||||
|
my $screenrc_q = component_update_shell_quote($screenrc);
|
||||||
|
|
||||||
|
my $script = <<"EOS";
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -u
|
||||||
|
LOG=$log_q
|
||||||
|
log(){ printf '[%s] %s\\n' "\$(date '+%Y-%m-%d %H:%M:%S')" "\$*" >> "\$LOG"; }
|
||||||
|
fail(){ log "FAILED: \$*"; exit 1; }
|
||||||
|
REPO=$repo_q
|
||||||
|
BRANCH=$branch_q
|
||||||
|
SOURCE_REL=$source_q
|
||||||
|
DEST=$install_q
|
||||||
|
GIT_BIN=$git_q
|
||||||
|
BACKUP_BASE=$backup_q
|
||||||
|
POST_UPDATE=$post_q
|
||||||
|
TMP="\${TMPDIR:-/tmp}/gsp_agent_update_\$(date +%s)_\$\$"
|
||||||
|
mkdir -p "\$TMP" "\$BACKUP_BASE" || fail "Cannot create staging or backup directories"
|
||||||
|
log "queued component=$component repo=\$REPO branch=\$BRANCH source=\$SOURCE_REL dest=\$DEST"
|
||||||
|
if ! command -v "\$GIT_BIN" >/dev/null 2>&1 && [ ! -x "\$GIT_BIN" ]; then
|
||||||
|
fail "Git executable not found: \$GIT_BIN"
|
||||||
|
fi
|
||||||
|
"\$GIT_BIN" clone --depth 1 --branch "\$BRANCH" "\$REPO" "\$TMP/repo" >> "\$LOG" 2>&1 || fail "git clone failed"
|
||||||
|
SRC="\$TMP/repo/\$SOURCE_REL"
|
||||||
|
[ -d "\$SRC" ] || fail "Source component folder missing: \$SRC"
|
||||||
|
case "\$DEST" in /*) ;; *) fail "Destination must be absolute: \$DEST" ;; esac
|
||||||
|
mkdir -p "\$DEST" || fail "Cannot create destination: \$DEST"
|
||||||
|
ARCHIVE="\$BACKUP_BASE/${component}_\$(date +%Y%m%d_%H%M%S).tar.gz"
|
||||||
|
tar --exclude='./Cfg' --exclude='./ServerFiles' --exclude='./Schedule' --exclude='./logs' --exclude='./screenlogs' --exclude='./steamcmd' --exclude='./startups' --exclude='./tmp' --exclude='./shared' --exclude='./component_backups' --exclude='./*.pid' -czf "\$ARCHIVE" -C "\$DEST" . >> "\$LOG" 2>&1 || fail "Backup failed"
|
||||||
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
|
rsync -a --exclude='Cfg/' --exclude='ServerFiles/' --exclude='Schedule/' --exclude='logs/' --exclude='screenlogs/' --exclude='steamcmd/' --exclude='startups/' --exclude='tmp/' --exclude='shared/' --exclude='component_backups/' --exclude='*.pid' "\$SRC"/ "\$DEST"/ >> "\$LOG" 2>&1 || fail "rsync copy failed"
|
||||||
|
else
|
||||||
|
(cd "\$SRC" && tar --exclude='./Cfg' --exclude='./ServerFiles' --exclude='./Schedule' --exclude='./logs' --exclude='./screenlogs' --exclude='./steamcmd' --exclude='./startups' --exclude='./tmp' --exclude='./shared' --exclude='./component_backups' --exclude='./*.pid' -cf - .) | (cd "\$DEST" && tar -xf -) >> "\$LOG" 2>&1 || fail "tar copy failed"
|
||||||
|
fi
|
||||||
|
if [ -f "\$DEST/ogp_agent.pl" ]; then
|
||||||
|
perl -c "\$DEST/ogp_agent.pl" >> "\$LOG" 2>&1 || fail "Updated ogp_agent.pl failed syntax validation"
|
||||||
|
fi
|
||||||
|
if [ -n "\$POST_UPDATE" ]; then
|
||||||
|
(cd "\$DEST" && bash -lc "\$POST_UPDATE") >> "\$LOG" 2>&1 || fail "post-update command failed"
|
||||||
|
fi
|
||||||
|
log "copy complete archive=\$ARCHIVE"
|
||||||
|
sleep 2
|
||||||
|
if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^ogp_agent\\.service'; then
|
||||||
|
systemctl restart ogp_agent.service >> "\$LOG" 2>&1 && exit 0
|
||||||
|
fi
|
||||||
|
cd "\$DEST" || exit 0
|
||||||
|
if [ -f ogp_agent_run.pid ]; then kill "\$(cat ogp_agent_run.pid)" >/dev/null 2>&1 || true; fi
|
||||||
|
if [ -f ogp_agent.pid ]; then kill "\$(cat ogp_agent.pid)" >/dev/null 2>&1 || true; fi
|
||||||
|
sleep 2
|
||||||
|
if [ -f ogp_agent_run ]; then
|
||||||
|
screen -d -m -t "ogp_agent" -c $screenrc_q -S ogp_agent bash ogp_agent_run -pidfile ogp_agent_run.pid >> "\$LOG" 2>&1 || true
|
||||||
|
else
|
||||||
|
screen -d -m -t "ogp_agent" -c $screenrc_q -S ogp_agent perl ogp_agent.pl >> "\$LOG" 2>&1 || true
|
||||||
|
fi
|
||||||
|
log "restart attempted"
|
||||||
|
rm -rf "\$TMP"
|
||||||
|
exit 0
|
||||||
|
EOS
|
||||||
|
|
||||||
|
my $fh;
|
||||||
|
if (!open($fh, '>', $script_path))
|
||||||
|
{
|
||||||
|
return component_update_response(success => 0, status => 'write_failed', message => "Cannot write updater script: $!");
|
||||||
|
}
|
||||||
|
print $fh $script;
|
||||||
|
close($fh);
|
||||||
|
chmod 0700, $script_path;
|
||||||
|
system('screen -d -m -t "component_update" -c "' . SCREENRC_FILE . '" -S component_update bash ' . component_update_shell_quote($script_path));
|
||||||
|
if ($? != 0)
|
||||||
|
{
|
||||||
|
system('bash ' . component_update_shell_quote($script_path) . ' >/dev/null 2>&1 &');
|
||||||
|
}
|
||||||
|
logger "Component update queued for $component from $repo_url branch $branch.";
|
||||||
|
return component_update_response(success => 1, status => 'queued', message => 'Component update queued on agent.', log_path => $log_path, script_path => $script_path);
|
||||||
|
}
|
||||||
|
|
||||||
sub agent_restart
|
sub agent_restart
|
||||||
{
|
{
|
||||||
return "Bad Encryption Key" unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
return "Bad Encryption Key" unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
||||||
|
|
|
||||||
|
|
@ -992,6 +992,31 @@ class OGPRemoteLibrary
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function component_update($payload)
|
||||||
|
{
|
||||||
|
$args = $this->encryptParam($payload);
|
||||||
|
$this->add_enc_chk($args);
|
||||||
|
$request = xmlrpc_encode_request("component_update", $args);
|
||||||
|
$response = $this->sendRequest($request);
|
||||||
|
if (is_array($response) && xmlrpc_is_fault($response)) {
|
||||||
|
return array('success' => false, 'status' => 'xmlrpc_fault', 'message' => isset($response['faultString']) ? $response['faultString'] : 'XML-RPC fault');
|
||||||
|
}
|
||||||
|
if (is_array($response)) {
|
||||||
|
$data = array();
|
||||||
|
foreach ((array)$response as $key => $value) {
|
||||||
|
$data[$key] = is_string($value) ? base64_decode($value) : $value;
|
||||||
|
}
|
||||||
|
if (isset($data['success'])) {
|
||||||
|
$data['success'] = in_array((string)$data['success'], array('1', 'true', 'yes'), true);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
if ($response === NULL) {
|
||||||
|
return array('success' => false, 'status' => 'agent_unreachable', 'message' => 'No response from agent.');
|
||||||
|
}
|
||||||
|
return array('success' => false, 'status' => 'unexpected_response', 'message' => (string)$response);
|
||||||
|
}
|
||||||
|
|
||||||
public function scheduler_list_tasks()
|
public function scheduler_list_tasks()
|
||||||
{
|
{
|
||||||
$args = NULL;
|
$args = NULL;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ defined('GSP_DEFAULT_REPO_URL') || define('GSP_DEFAULT_REPO_URL', 'http://forge.
|
||||||
defined('GSP_DEFAULT_BRANCH') || define('GSP_DEFAULT_BRANCH', 'Panel-unstable');
|
defined('GSP_DEFAULT_BRANCH') || define('GSP_DEFAULT_BRANCH', 'Panel-unstable');
|
||||||
defined('GSP_DEFAULT_REPO_ROOT') || define('GSP_DEFAULT_REPO_ROOT', '/var/www/html/GSP');
|
defined('GSP_DEFAULT_REPO_ROOT') || define('GSP_DEFAULT_REPO_ROOT', '/var/www/html/GSP');
|
||||||
defined('GSP_DEFAULT_PANEL_PATH') || define('GSP_DEFAULT_PANEL_PATH', '/var/www/html/GSP/Panel');
|
defined('GSP_DEFAULT_PANEL_PATH') || define('GSP_DEFAULT_PANEL_PATH', '/var/www/html/GSP/Panel');
|
||||||
|
defined('GSP_DEFAULT_WEBSITE_PATH') || define('GSP_DEFAULT_WEBSITE_PATH', '/var/www/html/GSP/Website');
|
||||||
|
defined('GSP_DEFAULT_PANEL_SOURCE') || define('GSP_DEFAULT_PANEL_SOURCE', 'Panel');
|
||||||
|
defined('GSP_DEFAULT_LINUX_AGENT_SOURCE') || define('GSP_DEFAULT_LINUX_AGENT_SOURCE', 'Agent_Linux');
|
||||||
|
defined('GSP_DEFAULT_WINDOWS_AGENT_SOURCE') || define('GSP_DEFAULT_WINDOWS_AGENT_SOURCE', 'Agent-Windows');
|
||||||
|
defined('GSP_DEFAULT_WEBSITE_SOURCE') || define('GSP_DEFAULT_WEBSITE_SOURCE', 'Website');
|
||||||
|
|
||||||
$gspPatchManager = GSP_PANEL_DIR . '/modules/update/patch_manager.php';
|
$gspPatchManager = GSP_PANEL_DIR . '/modules/update/patch_manager.php';
|
||||||
if (file_exists($gspPatchManager)) {
|
if (file_exists($gspPatchManager)) {
|
||||||
|
|
@ -151,11 +156,23 @@ function gsp_update_settings()
|
||||||
global $settings;
|
global $settings;
|
||||||
$repo_root = !empty($settings['gsp_update_repo_root']) ? (string)$settings['gsp_update_repo_root'] : GSP_DEFAULT_REPO_ROOT;
|
$repo_root = !empty($settings['gsp_update_repo_root']) ? (string)$settings['gsp_update_repo_root'] : GSP_DEFAULT_REPO_ROOT;
|
||||||
$panel_path = !empty($settings['gsp_update_panel_path']) ? (string)$settings['gsp_update_panel_path'] : GSP_DEFAULT_PANEL_PATH;
|
$panel_path = !empty($settings['gsp_update_panel_path']) ? (string)$settings['gsp_update_panel_path'] : GSP_DEFAULT_PANEL_PATH;
|
||||||
|
$website_path = !empty($settings['gsp_update_website_path']) ? (string)$settings['gsp_update_website_path'] : GSP_DEFAULT_WEBSITE_PATH;
|
||||||
return [
|
return [
|
||||||
'repo_url' => !empty($settings['gsp_update_repo_url']) ? (string)$settings['gsp_update_repo_url'] : GSP_DEFAULT_REPO_URL,
|
'repo_url' => !empty($settings['gsp_update_repo_url']) ? (string)$settings['gsp_update_repo_url'] : GSP_DEFAULT_REPO_URL,
|
||||||
'branch' => !empty($settings['gsp_update_branch']) ? (string)$settings['gsp_update_branch'] : GSP_DEFAULT_BRANCH,
|
'branch' => !empty($settings['gsp_update_branch']) ? (string)$settings['gsp_update_branch'] : GSP_DEFAULT_BRANCH,
|
||||||
'repo_root' => rtrim($repo_root, '/'),
|
'repo_root' => rtrim($repo_root, '/'),
|
||||||
'panel_path' => rtrim($panel_path, '/'),
|
'panel_path' => rtrim($panel_path, '/'),
|
||||||
|
'website_path' => rtrim($website_path, '/'),
|
||||||
|
'panel_source_path' => !empty($settings['gsp_update_panel_source_path']) ? trim((string)$settings['gsp_update_panel_source_path'], '/') : GSP_DEFAULT_PANEL_SOURCE,
|
||||||
|
'linux_agent_source_path' => !empty($settings['gsp_update_linux_agent_source_path']) ? trim((string)$settings['gsp_update_linux_agent_source_path'], '/') : GSP_DEFAULT_LINUX_AGENT_SOURCE,
|
||||||
|
'windows_agent_source_path' => !empty($settings['gsp_update_windows_agent_source_path']) ? trim((string)$settings['gsp_update_windows_agent_source_path'], '/') : GSP_DEFAULT_WINDOWS_AGENT_SOURCE,
|
||||||
|
'website_source_path' => !empty($settings['gsp_update_website_source_path']) ? trim((string)$settings['gsp_update_website_source_path'], '/') : GSP_DEFAULT_WEBSITE_SOURCE,
|
||||||
|
'git_path' => !empty($settings['gsp_update_git_path']) ? trim((string)$settings['gsp_update_git_path']) : 'git',
|
||||||
|
'backup_path' => !empty($settings['gsp_update_backup_path']) ? rtrim((string)$settings['gsp_update_backup_path'], '/') : GSP_BACKUP_BASE,
|
||||||
|
'panel_post_update_command' => !empty($settings['gsp_update_panel_post_update_command']) ? (string)$settings['gsp_update_panel_post_update_command'] : '',
|
||||||
|
'website_post_update_command' => !empty($settings['gsp_update_website_post_update_command']) ? (string)$settings['gsp_update_website_post_update_command'] : '',
|
||||||
|
'linux_agent_post_update_command' => !empty($settings['gsp_update_linux_agent_post_update_command']) ? (string)$settings['gsp_update_linux_agent_post_update_command'] : '',
|
||||||
|
'windows_agent_post_update_command' => !empty($settings['gsp_update_windows_agent_post_update_command']) ? (string)$settings['gsp_update_windows_agent_post_update_command'] : '',
|
||||||
'backup_before_update' => !isset($settings['gsp_update_backup_before']) ? '1' : (string)$settings['gsp_update_backup_before'],
|
'backup_before_update' => !isset($settings['gsp_update_backup_before']) ? '1' : (string)$settings['gsp_update_backup_before'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +192,7 @@ $errors[] = 'Repository source must be an http(s), ssh, git@ URL, or a safe abso
|
||||||
if ($is_local_path && !is_dir($repo_source)) {
|
if ($is_local_path && !is_dir($repo_source)) {
|
||||||
$errors[] = 'Repository local path does not exist or is not a directory: ' . $repo_source;
|
$errors[] = 'Repository local path does not exist or is not a directory: ' . $repo_source;
|
||||||
}
|
}
|
||||||
if (!preg_match('/^[A-Za-z0-9._\/-]{1,128}$/', (string)$cfg['branch'])) {
|
if (!preg_match('/^[A-Za-z0-9._\/-]{1,128}$/', (string)$cfg['branch']) || strpos((string)$cfg['branch'], '..') !== false) {
|
||||||
$errors[] = 'Branch/channel contains invalid characters.';
|
$errors[] = 'Branch/channel contains invalid characters.';
|
||||||
}
|
}
|
||||||
foreach (['repo_root', 'panel_path'] as $key) {
|
foreach (['repo_root', 'panel_path'] as $key) {
|
||||||
|
|
@ -183,6 +200,25 @@ if (trim((string)$cfg[$key]) === '' || strpos((string)$cfg[$key], "\0") !== fals
|
||||||
$errors[] = ucfirst(str_replace('_', ' ', $key)) . ' is invalid.';
|
$errors[] = ucfirst(str_replace('_', ' ', $key)) . ' is invalid.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach (['website_path', 'backup_path'] as $key) {
|
||||||
|
if (isset($cfg[$key]) && (trim((string)$cfg[$key]) === '' || strpos((string)$cfg[$key], "\0") !== false || strpos((string)$cfg[$key], '..') !== false || strpos((string)$cfg[$key], '/') !== 0)) {
|
||||||
|
$errors[] = ucfirst(str_replace('_', ' ', $key)) . ' must be a safe absolute path.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (['panel_source_path', 'linux_agent_source_path', 'windows_agent_source_path', 'website_source_path'] as $key) {
|
||||||
|
$value = isset($cfg[$key]) ? trim((string)$cfg[$key], '/') : '';
|
||||||
|
if ($value === '' || strpos($value, "\0") !== false || strpos($value, '..') !== false || !preg_match('/^[A-Za-z0-9._\/-]+$/', $value)) {
|
||||||
|
$errors[] = ucfirst(str_replace('_', ' ', $key)) . ' is invalid.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($cfg['git_path']) && trim((string)$cfg['git_path']) !== '' && (strpos((string)$cfg['git_path'], "\0") !== false || strpos((string)$cfg['git_path'], "\n") !== false || strpos((string)$cfg['git_path'], "\r") !== false)) {
|
||||||
|
$errors[] = 'Git executable path is invalid.';
|
||||||
|
}
|
||||||
|
foreach (['panel_post_update_command', 'website_post_update_command', 'linux_agent_post_update_command', 'windows_agent_post_update_command'] as $key) {
|
||||||
|
if (isset($cfg[$key]) && (strpos((string)$cfg[$key], "\0") !== false || strpos((string)$cfg[$key], "\n") !== false || strpos((string)$cfg[$key], "\r") !== false)) {
|
||||||
|
$errors[] = ucfirst(str_replace('_', ' ', $key)) . ' must be a single-line admin command.';
|
||||||
|
}
|
||||||
|
}
|
||||||
if (rtrim((string)$cfg['panel_path'], '/') !== rtrim((string)$cfg['repo_root'], '/') . '/Panel') {
|
if (rtrim((string)$cfg['panel_path'], '/') !== rtrim((string)$cfg['repo_root'], '/') . '/Panel') {
|
||||||
$errors[] = 'Panel Path must point to the Panel folder inside Repository Root.';
|
$errors[] = 'Panel Path must point to the Panel folder inside Repository Root.';
|
||||||
}
|
}
|
||||||
|
|
@ -264,7 +300,7 @@ $cwd = getcwd();
|
||||||
$cwd_real = $cwd ? (realpath($cwd) ?: $cwd) : '';
|
$cwd_real = $cwd ? (realpath($cwd) ?: $cwd) : '';
|
||||||
$root_path = rtrim((string)$update_cfg['repo_root'], '/');
|
$root_path = rtrim((string)$update_cfg['repo_root'], '/');
|
||||||
$panel_path = rtrim((string)$update_cfg['panel_path'], '/');
|
$panel_path = rtrim((string)$update_cfg['panel_path'], '/');
|
||||||
$website_path = $root_path . '/Website';
|
$website_path = !empty($update_cfg['website_path']) ? rtrim((string)$update_cfg['website_path'], '/') : ($root_path . '/Website');
|
||||||
$root_real = realpath($root_path) ?: $root_path;
|
$root_real = realpath($root_path) ?: $root_path;
|
||||||
$panel_real = realpath($panel_path) ?: $panel_path;
|
$panel_real = realpath($panel_path) ?: $panel_path;
|
||||||
$website_real = realpath($website_path) ?: $website_path;
|
$website_real = realpath($website_path) ?: $website_path;
|
||||||
|
|
@ -654,14 +690,14 @@ $layout = [
|
||||||
'cwd' => getcwd() ?: '',
|
'cwd' => getcwd() ?: '',
|
||||||
'live_gsp_root' => rtrim((string)$update_cfg['repo_root'], '/'),
|
'live_gsp_root' => rtrim((string)$update_cfg['repo_root'], '/'),
|
||||||
'live_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
|
'live_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
|
||||||
'live_website_path' => rtrim((string)$update_cfg['repo_root'], '/') . '/Website',
|
'live_website_path' => !empty($update_cfg['website_path']) ? rtrim((string)$update_cfg['website_path'], '/') : (rtrim((string)$update_cfg['repo_root'], '/') . '/Website'),
|
||||||
'temporary_git_checkout_path' => $temp_checkout_path,
|
'temporary_git_checkout_path' => $temp_checkout_path,
|
||||||
'source_root' => $source_root_real,
|
'source_root' => $source_root_real,
|
||||||
'source_repo_root' => $repo_root,
|
'source_repo_root' => $repo_root,
|
||||||
'source_panel_path' => $repo_root ? ($repo_root . '/Panel') : '',
|
'source_panel_path' => $repo_root ? ($repo_root . '/' . trim((string)$update_cfg['panel_source_path'], '/')) : '',
|
||||||
'source_website_path' => $repo_root ? ($repo_root . '/Website') : '',
|
'source_website_path' => $repo_root ? ($repo_root . '/' . trim((string)$update_cfg['website_source_path'], '/')) : '',
|
||||||
'destination_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
|
'destination_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
|
||||||
'destination_website_path' => rtrim((string)$update_cfg['repo_root'], '/') . '/Website',
|
'destination_website_path' => !empty($update_cfg['website_path']) ? rtrim((string)$update_cfg['website_path'], '/') : (rtrim((string)$update_cfg['repo_root'], '/') . '/Website'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
@ -1024,6 +1060,7 @@ function gsp_checkout_update_source(array $update_cfg)
|
||||||
{
|
{
|
||||||
$repo_url = (string)$update_cfg['repo_url'];
|
$repo_url = (string)$update_cfg['repo_url'];
|
||||||
$branch = (string)$update_cfg['branch'];
|
$branch = (string)$update_cfg['branch'];
|
||||||
|
$git_bin = !empty($update_cfg['git_path']) ? (string)$update_cfg['git_path'] : 'git';
|
||||||
$temp_dir = sys_get_temp_dir() . '/gsp_git_' . time() . '_' . mt_rand(1000, 9999);
|
$temp_dir = sys_get_temp_dir() . '/gsp_git_' . time() . '_' . mt_rand(1000, 9999);
|
||||||
if (!@mkdir($temp_dir, 0750, true)) {
|
if (!@mkdir($temp_dir, 0750, true)) {
|
||||||
return ['success' => false, 'error' => 'Cannot create temporary git checkout directory.'];
|
return ['success' => false, 'error' => 'Cannot create temporary git checkout directory.'];
|
||||||
|
|
@ -1034,7 +1071,7 @@ return ['success' => false, 'error' => 'PHP exec() is disabled, so the updater c
|
||||||
}
|
}
|
||||||
$out = [];
|
$out = [];
|
||||||
$ret = 0;
|
$ret = 0;
|
||||||
$cmd = 'git clone --depth 1 --branch ' . escapeshellarg($branch) . ' ' . escapeshellarg($repo_url) . ' ' . escapeshellarg($temp_dir) . ' 2>&1';
|
$cmd = escapeshellarg($git_bin) . ' clone --depth 1 --branch ' . escapeshellarg($branch) . ' ' . escapeshellarg($repo_url) . ' ' . escapeshellarg($temp_dir) . ' 2>&1';
|
||||||
gsp_update_log('Starting configured git checkout from ' . $repo_url . ' branch ' . $branch);
|
gsp_update_log('Starting configured git checkout from ' . $repo_url . ' branch ' . $branch);
|
||||||
exec($cmd, $out, $ret);
|
exec($cmd, $out, $ret);
|
||||||
if ($ret !== 0) {
|
if ($ret !== 0) {
|
||||||
|
|
@ -1191,6 +1228,65 @@ if (!$preflight['success']) {
|
||||||
return ['success' => false, 'error' => 'Preflight failed: ' . implode(' | ', $preflight['errors'])];
|
return ['success' => false, 'error' => 'Preflight failed: ' . implode(' | ', $preflight['errors'])];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$backup = gsp_create_full_backup($update_type, $ref, false);
|
||||||
|
if (!$backup['success']) {
|
||||||
|
return $backup;
|
||||||
|
}
|
||||||
|
gsp_update_log("Backup created at {$backup['backup_ts']} before {$update_type} update to {$ref}");
|
||||||
|
|
||||||
|
$temp_dir = sys_get_temp_dir() . '/gsp_dl_' . time() . '_' . mt_rand(1000, 9999);
|
||||||
|
@mkdir($temp_dir, 0750, true);
|
||||||
|
$zip_file = gsp_download_zip($repo_owner, $repo_name, $ref, $temp_dir);
|
||||||
|
if (!$zip_file) {
|
||||||
|
gsp_rmdir_recursive($temp_dir);
|
||||||
|
return ['success' => false, 'error' => 'Failed to download update ZIP from GitHub.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$apply = gsp_apply_update_from_zip($zip_file, $restart_nonce);
|
||||||
|
@unlink($zip_file);
|
||||||
|
gsp_rmdir_recursive($temp_dir);
|
||||||
|
if (!empty($apply['restart_required'])) {
|
||||||
|
$apply['backup_dir'] = $backup['backup_dir'];
|
||||||
|
$apply['success'] = false;
|
||||||
|
return $apply;
|
||||||
|
}
|
||||||
|
if (!$apply['success']) {
|
||||||
|
return $apply;
|
||||||
|
}
|
||||||
|
|
||||||
|
$commit_after = gsp_get_git_commit();
|
||||||
|
gsp_fix_permissions(GSP_ROOT_DIR);
|
||||||
|
gsp_clear_panel_cache(GSP_PANEL_DIR);
|
||||||
|
gsp_write_version_file($ref, $update_type);
|
||||||
|
$vsource = ($update_type === 'release') ? 'GitHub Releases' : $ref;
|
||||||
|
$vversion = ($update_type === 'release') ? $ref : ($commit_after ?: $ref);
|
||||||
|
gsp_write_version_json($update_type, $vsource, $vversion, $commit_after);
|
||||||
|
gsp_write_last_update_markers(GSP_ROOT_DIR);
|
||||||
|
$db->setSettings(['ogp_version' => $ref, 'version_type' => $update_type]);
|
||||||
|
|
||||||
|
if (file_exists(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php')) {
|
||||||
|
require_once(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php');
|
||||||
|
}
|
||||||
|
if (function_exists('updateAllPanelModules')) {
|
||||||
|
updateAllPanelModules();
|
||||||
|
}
|
||||||
|
if (function_exists('runPostUpdateOperations')) {
|
||||||
|
runPostUpdateOperations();
|
||||||
|
}
|
||||||
|
|
||||||
|
gsp_update_log("Update to {$ref} (type={$update_type}) complete");
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'files_copied' => $apply['files_copied'],
|
||||||
|
'panel_files_copied' => isset($apply['panel_files_copied']) ? $apply['panel_files_copied'] : 0,
|
||||||
|
'website_files_copied' => isset($apply['website_files_copied']) ? $apply['website_files_copied'] : 0,
|
||||||
|
'copied_files' => isset($apply['copied_files']) ? $apply['copied_files'] : [],
|
||||||
|
'backup_dir' => $backup['backup_dir'],
|
||||||
|
'preserved' => $apply['preserved'],
|
||||||
|
'patches' => $apply['patches'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function gsp_do_configured_git_update(array $update_cfg, $restart_nonce = '')
|
function gsp_do_configured_git_update(array $update_cfg, $restart_nonce = '')
|
||||||
{
|
{
|
||||||
global $db;
|
global $db;
|
||||||
|
|
@ -1258,65 +1354,332 @@ return [
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$backup = gsp_create_full_backup($update_type, $ref, false);
|
function gsp_safe_component_backup($component, $source_path, $backup_base)
|
||||||
|
{
|
||||||
|
$component = preg_replace('/[^A-Za-z0-9._-]/', '_', (string)$component);
|
||||||
|
$backup_base = rtrim((string)$backup_base, '/');
|
||||||
|
if ($backup_base === '' || strpos($backup_base, '/') !== 0) {
|
||||||
|
$backup_base = GSP_BACKUP_BASE;
|
||||||
|
}
|
||||||
|
if (!is_dir($backup_base) && !@mkdir($backup_base, 0755, true)) {
|
||||||
|
return ['success' => false, 'error' => 'Cannot create backup path: ' . $backup_base];
|
||||||
|
}
|
||||||
|
$ts = date('Y-m-d_H-i-s');
|
||||||
|
$backup_dir = $backup_base . '/component_' . $component . '_' . $ts;
|
||||||
|
if (!@mkdir($backup_dir, 0755, true)) {
|
||||||
|
return ['success' => false, 'error' => 'Cannot create component backup directory: ' . $backup_dir];
|
||||||
|
}
|
||||||
|
if (!is_dir($source_path)) {
|
||||||
|
return ['success' => true, 'backup_dir' => $backup_dir, 'skipped' => true];
|
||||||
|
}
|
||||||
|
$archive = $backup_dir . '/' . $component . '.tar.gz';
|
||||||
|
$archive_result = gsp_create_archive($source_path, $archive, ['./logs', './cache', './tmp', './backups', './uploads', './upload', './Cfg', './ServerFiles', './Schedule', './steamcmd', './startups']);
|
||||||
|
if (!$archive_result['success']) {
|
||||||
|
return $archive_result;
|
||||||
|
}
|
||||||
|
@file_put_contents($backup_dir . '/backup.json', json_encode([
|
||||||
|
'component' => $component,
|
||||||
|
'source_path' => $source_path,
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
'archive' => basename($archive),
|
||||||
|
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
return ['success' => true, 'backup_dir' => $backup_dir, 'archive' => $archive];
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_component_source_path($source_root, $relative)
|
||||||
|
{
|
||||||
|
$relative = trim((string)$relative, '/');
|
||||||
|
if ($relative === '' || strpos($relative, '..') !== false || strpos($relative, "\0") !== false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$path = rtrim($source_root, '/') . '/' . $relative;
|
||||||
|
$real = realpath($path);
|
||||||
|
if ($real === false || strpos($real, realpath($source_root) ?: $source_root) !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $real;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_component_copy_tree($src_root, $dst_root, $component_label)
|
||||||
|
{
|
||||||
|
$copied = 0;
|
||||||
|
$skipped = [];
|
||||||
|
$copied_files = [];
|
||||||
|
if (!is_dir($src_root)) {
|
||||||
|
return ['success' => false, 'error' => 'Source component path not found: ' . $src_root];
|
||||||
|
}
|
||||||
|
if (!is_dir($dst_root) && !@mkdir($dst_root, 0755, true)) {
|
||||||
|
return ['success' => false, 'error' => 'Destination path could not be created: ' . $dst_root];
|
||||||
|
}
|
||||||
|
$iter = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($src_root, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::SELF_FIRST
|
||||||
|
);
|
||||||
|
$protected = [
|
||||||
|
'includes/config.inc.php',
|
||||||
|
'config.inc.php',
|
||||||
|
'Cfg/',
|
||||||
|
'ServerFiles/',
|
||||||
|
'Schedule/',
|
||||||
|
'logs/',
|
||||||
|
'log/',
|
||||||
|
'backups/',
|
||||||
|
'backup/',
|
||||||
|
'cache/',
|
||||||
|
'tmp/',
|
||||||
|
'temp/',
|
||||||
|
'uploads/',
|
||||||
|
'upload/',
|
||||||
|
'home/',
|
||||||
|
'homes/',
|
||||||
|
'servers/',
|
||||||
|
'game_servers/',
|
||||||
|
'steamcmd/',
|
||||||
|
'startups/',
|
||||||
|
];
|
||||||
|
foreach ($iter as $item) {
|
||||||
|
$rel = gsp_normalize_rel(substr($item->getPathname(), strlen(rtrim($src_root, '/')) + 1));
|
||||||
|
$is_protected = false;
|
||||||
|
foreach ($protected as $prefix) {
|
||||||
|
if ($rel === rtrim($prefix, '/') || strpos($rel, $prefix) === 0) {
|
||||||
|
$is_protected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($is_protected) {
|
||||||
|
$skipped[] = $component_label . '/' . $rel;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$dst = rtrim($dst_root, '/') . '/' . $rel;
|
||||||
|
if ($item->isDir()) {
|
||||||
|
if (!is_dir($dst)) {
|
||||||
|
@mkdir($dst, 0755, true);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gsp_copy_file($item->getPathname(), $dst)) {
|
||||||
|
$copied++;
|
||||||
|
if (count($copied_files) < 200) {
|
||||||
|
$copied_files[] = $component_label . '/' . $rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ['success' => true, 'files_copied' => $copied, 'skipped' => array_values(array_unique($skipped)), 'copied_files' => $copied_files];
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_run_admin_post_update_command($command, $cwd)
|
||||||
|
{
|
||||||
|
$command = trim((string)$command);
|
||||||
|
if ($command === '') {
|
||||||
|
return ['success' => true, 'skipped' => true, 'output' => ''];
|
||||||
|
}
|
||||||
|
if (strpos($command, "\0") !== false || strpos($command, "\n") !== false || strpos($command, "\r") !== false) {
|
||||||
|
return ['success' => false, 'error' => 'Post-update command must be a single line.'];
|
||||||
|
}
|
||||||
|
$out = [];
|
||||||
|
$ret = 0;
|
||||||
|
exec('cd ' . escapeshellarg($cwd) . ' && ' . $command . ' 2>&1', $out, $ret);
|
||||||
|
return ['success' => $ret === 0, 'exit_code' => $ret, 'output' => implode("\n", $out), 'error' => $ret === 0 ? '' : implode("\n", $out)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_do_local_component_update(array $update_cfg, array $components)
|
||||||
|
{
|
||||||
|
$allowed = array('panel', 'website');
|
||||||
|
$components = array_values(array_intersect($allowed, array_unique($components)));
|
||||||
|
if (empty($components)) {
|
||||||
|
return ['success' => true, 'files_copied' => 0, 'results' => []];
|
||||||
|
}
|
||||||
|
$validation = gsp_validate_update_settings($update_cfg);
|
||||||
|
if (!empty($validation)) {
|
||||||
|
return ['success' => false, 'error' => implode(' | ', $validation)];
|
||||||
|
}
|
||||||
|
$checkout = gsp_checkout_update_source($update_cfg);
|
||||||
|
if (!$checkout['success']) {
|
||||||
|
return $checkout;
|
||||||
|
}
|
||||||
|
$results = [];
|
||||||
|
$total = 0;
|
||||||
|
$all_skipped = [];
|
||||||
|
$all_copied = [];
|
||||||
|
$map = [
|
||||||
|
'panel' => ['source' => $update_cfg['panel_source_path'], 'dest' => $update_cfg['panel_path'], 'post' => $update_cfg['panel_post_update_command']],
|
||||||
|
'website' => ['source' => $update_cfg['website_source_path'], 'dest' => $update_cfg['website_path'], 'post' => $update_cfg['website_post_update_command']],
|
||||||
|
];
|
||||||
|
foreach ($components as $component) {
|
||||||
|
$source = gsp_component_source_path($checkout['source_root'], $map[$component]['source']);
|
||||||
|
if ($source === false) {
|
||||||
|
gsp_rmdir_recursive($checkout['temp_dir']);
|
||||||
|
return ['success' => false, 'error' => 'Source folder for ' . $component . ' is invalid or missing.'];
|
||||||
|
}
|
||||||
|
$dest = rtrim((string)$map[$component]['dest'], '/');
|
||||||
|
if (strpos($dest, '/') !== 0 || strpos($dest, '..') !== false || strpos($dest, "\0") !== false) {
|
||||||
|
gsp_rmdir_recursive($checkout['temp_dir']);
|
||||||
|
return ['success' => false, 'error' => 'Destination path for ' . $component . ' is invalid.'];
|
||||||
|
}
|
||||||
|
$backup = ['success' => true, 'backup_dir' => null];
|
||||||
|
if (!empty($update_cfg['backup_before_update'])) {
|
||||||
|
$backup = gsp_safe_component_backup($component, $dest, $update_cfg['backup_path']);
|
||||||
if (!$backup['success']) {
|
if (!$backup['success']) {
|
||||||
|
gsp_rmdir_recursive($checkout['temp_dir']);
|
||||||
return $backup;
|
return $backup;
|
||||||
}
|
}
|
||||||
gsp_update_log("Backup created at {$backup['backup_ts']} before {$update_type} update to {$ref}");
|
|
||||||
|
|
||||||
$temp_dir = sys_get_temp_dir() . '/gsp_dl_' . time() . '_' . mt_rand(1000, 9999);
|
|
||||||
@mkdir($temp_dir, 0750, true);
|
|
||||||
$zip_file = gsp_download_zip($repo_owner, $repo_name, $ref, $temp_dir);
|
|
||||||
if (!$zip_file) {
|
|
||||||
gsp_rmdir_recursive($temp_dir);
|
|
||||||
return ['success' => false, 'error' => 'Failed to download update ZIP from GitHub.'];
|
|
||||||
}
|
}
|
||||||
|
$sync = gsp_component_copy_tree($source, $dest, ucfirst($component));
|
||||||
$apply = gsp_apply_update_from_zip($zip_file, $restart_nonce);
|
if (!$sync['success']) {
|
||||||
@unlink($zip_file);
|
gsp_rmdir_recursive($checkout['temp_dir']);
|
||||||
gsp_rmdir_recursive($temp_dir);
|
return $sync;
|
||||||
if (!empty($apply['restart_required'])) {
|
|
||||||
$apply['backup_dir'] = $backup['backup_dir'];
|
|
||||||
$apply['success'] = false;
|
|
||||||
return $apply;
|
|
||||||
}
|
}
|
||||||
if (!$apply['success']) {
|
$post = gsp_run_admin_post_update_command($map[$component]['post'], $dest);
|
||||||
return $apply;
|
if (!$post['success']) {
|
||||||
|
gsp_rmdir_recursive($checkout['temp_dir']);
|
||||||
|
return ['success' => false, 'error' => ucfirst($component) . ' post-update command failed: ' . $post['error'], 'results' => $results];
|
||||||
}
|
}
|
||||||
|
$results[$component] = [
|
||||||
$commit_after = gsp_get_git_commit();
|
'source' => $source,
|
||||||
gsp_fix_permissions(GSP_ROOT_DIR);
|
'destination' => $dest,
|
||||||
gsp_clear_panel_cache(GSP_PANEL_DIR);
|
'backup_dir' => $backup['backup_dir'],
|
||||||
gsp_write_version_file($ref, $update_type);
|
'files_copied' => $sync['files_copied'],
|
||||||
$vsource = ($update_type === 'release') ? 'GitHub Releases' : $ref;
|
'skipped' => $sync['skipped'],
|
||||||
$vversion = ($update_type === 'release') ? $ref : ($commit_after ?: $ref);
|
'post_update' => $post,
|
||||||
gsp_write_version_json($update_type, $vsource, $vversion, $commit_after);
|
];
|
||||||
|
$total += (int)$sync['files_copied'];
|
||||||
|
$all_skipped = array_merge($all_skipped, $sync['skipped']);
|
||||||
|
$all_copied = array_merge($all_copied, $sync['copied_files']);
|
||||||
|
gsp_update_log('Local component update complete: ' . $component . ' copied=' . intval($sync['files_copied']) . ' source=' . $source . ' dest=' . $dest);
|
||||||
|
}
|
||||||
|
gsp_rmdir_recursive($checkout['temp_dir']);
|
||||||
gsp_write_last_update_markers($update_cfg['repo_root']);
|
gsp_write_last_update_markers($update_cfg['repo_root']);
|
||||||
$db->setSettings(['ogp_version' => $ref, 'version_type' => $update_type]);
|
if (in_array('panel', $components, true)) {
|
||||||
|
gsp_clear_panel_cache($update_cfg['panel_path']);
|
||||||
if (file_exists(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php')) {
|
gsp_fix_permissions($update_cfg['panel_path']);
|
||||||
require_once(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php');
|
|
||||||
}
|
}
|
||||||
if (function_exists('updateAllPanelModules')) {
|
|
||||||
updateAllPanelModules();
|
|
||||||
}
|
|
||||||
if (function_exists('runPostUpdateOperations')) {
|
|
||||||
runPostUpdateOperations();
|
|
||||||
}
|
|
||||||
|
|
||||||
gsp_update_log("Update to {$ref} (type={$update_type}) complete");
|
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'files_copied' => $apply['files_copied'],
|
'files_copied' => $total,
|
||||||
'panel_files_copied' => isset($apply['panel_files_copied']) ? $apply['panel_files_copied'] : 0,
|
'results' => $results,
|
||||||
'website_files_copied' => isset($apply['website_files_copied']) ? $apply['website_files_copied'] : 0,
|
'preserved' => array_values(array_unique($all_skipped)),
|
||||||
'copied_files' => isset($apply['copied_files']) ? $apply['copied_files'] : [],
|
'copied_files' => array_slice(array_values(array_unique($all_copied)), 0, 200),
|
||||||
'backup_dir' => $backup['backup_dir'],
|
|
||||||
'preserved' => $apply['preserved'],
|
|
||||||
'patches' => $apply['patches'],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gsp_update_payload_encode(array $payload)
|
||||||
|
{
|
||||||
|
$lines = [];
|
||||||
|
foreach ($payload as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$value = json_encode($value, JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
$lines[] = $key . '=' . base64_encode((string)$value);
|
||||||
|
}
|
||||||
|
return implode("\n", $lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_infer_agent_os(array $remote_server, $reported_os = '')
|
||||||
|
{
|
||||||
|
$reported = strtolower((string)$reported_os);
|
||||||
|
$haystack = strtolower((string)$remote_server['remote_server_name'] . ' ' . (string)$remote_server['agent_ip'] . ' ' . $reported);
|
||||||
|
if (strpos($haystack, 'windows') !== false || strpos($haystack, 'cygwin') !== false || strpos($haystack, 'mingw') !== false) {
|
||||||
|
return 'windows';
|
||||||
|
}
|
||||||
|
return 'linux';
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_get_remote_agent_rows()
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
$rows = $db->getRemoteServers();
|
||||||
|
return is_array($rows) ? $rows : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_dispatch_remote_agent_update(array $remote_server, array $update_cfg, $component)
|
||||||
|
{
|
||||||
|
if (!function_exists('xmlrpc_encode_request') || !function_exists('xmlrpc_decode')) {
|
||||||
|
return ['success' => false, 'status' => 'missing_xmlrpc', 'message' => 'PHP xmlrpc extension is required for remote agent updates.'];
|
||||||
|
}
|
||||||
|
require_once(GSP_PANEL_DIR . '/includes/lib_remote.php');
|
||||||
|
$component = ($component === 'windows_agent') ? 'windows_agent' : 'linux_agent';
|
||||||
|
$source_path = ($component === 'windows_agent') ? $update_cfg['windows_agent_source_path'] : $update_cfg['linux_agent_source_path'];
|
||||||
|
$post_cmd = ($component === 'windows_agent') ? $update_cfg['windows_agent_post_update_command'] : $update_cfg['linux_agent_post_update_command'];
|
||||||
|
$payload = [
|
||||||
|
'component' => $component,
|
||||||
|
'repo_url' => $update_cfg['repo_url'],
|
||||||
|
'branch' => $update_cfg['branch'],
|
||||||
|
'source_path' => $source_path,
|
||||||
|
'install_path' => '',
|
||||||
|
'git_path' => $update_cfg['git_path'],
|
||||||
|
'backup_path' => '',
|
||||||
|
'post_update_command' => $post_cmd,
|
||||||
|
'requested_at' => date('Y-m-d H:i:s'),
|
||||||
|
];
|
||||||
|
$remote = new OGPRemoteLibrary($remote_server['agent_ip'], $remote_server['agent_port'], $remote_server['encryption_key'], $remote_server['timeout']);
|
||||||
|
if ((int)$remote->status_chk() !== 1) {
|
||||||
|
return ['success' => false, 'status' => 'agent_unreachable', 'message' => 'Agent unreachable or encryption mismatch.'];
|
||||||
|
}
|
||||||
|
if (!method_exists($remote, 'component_update')) {
|
||||||
|
return ['success' => false, 'status' => 'panel_missing_rpc_wrapper', 'message' => 'Panel remote library does not expose component_update().'];
|
||||||
|
}
|
||||||
|
$response = $remote->component_update(gsp_update_payload_encode($payload));
|
||||||
|
if (is_array($response)) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
return ['success' => false, 'status' => 'unexpected_response', 'message' => 'Unexpected agent response: ' . substr((string)$response, 0, 300)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function gsp_do_remote_agents_update(array $update_cfg, array $remote_ids, array $component_filters)
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
if (!function_exists('xmlrpc_encode_request') || !function_exists('xmlrpc_decode')) {
|
||||||
|
return ['success' => false, 'results' => [], 'success_count' => 0, 'message' => 'PHP xmlrpc extension is required for remote agent updates.'];
|
||||||
|
}
|
||||||
|
require_once(GSP_PANEL_DIR . '/includes/lib_remote.php');
|
||||||
|
$remote_ids = array_map('intval', $remote_ids);
|
||||||
|
$remote_ids = array_values(array_filter(array_unique($remote_ids)));
|
||||||
|
if (empty($remote_ids)) {
|
||||||
|
return ['success' => true, 'results' => [], 'message' => 'No remote agents selected.'];
|
||||||
|
}
|
||||||
|
$all = gsp_get_remote_agent_rows();
|
||||||
|
$by_id = [];
|
||||||
|
foreach ($all as $row) {
|
||||||
|
$by_id[(int)$row['remote_server_id']] = $row;
|
||||||
|
}
|
||||||
|
$results = [];
|
||||||
|
$success_count = 0;
|
||||||
|
foreach ($remote_ids as $remote_id) {
|
||||||
|
if (empty($by_id[$remote_id])) {
|
||||||
|
$results[$remote_id] = ['success' => false, 'status' => 'not_found', 'message' => 'Remote host not found.'];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$row = $by_id[$remote_id];
|
||||||
|
$reported_os = '';
|
||||||
|
try {
|
||||||
|
$probe = new OGPRemoteLibrary($row['agent_ip'], $row['agent_port'], $row['encryption_key'], $row['timeout']);
|
||||||
|
$reported_os = (string)$probe->what_os();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$reported_os = '';
|
||||||
|
}
|
||||||
|
$agent_os = gsp_infer_agent_os($row, $reported_os);
|
||||||
|
if ($agent_os === 'linux' && !in_array('linux_agent', $component_filters, true)) {
|
||||||
|
$results[$remote_id] = ['success' => true, 'status' => 'skipped', 'message' => 'Skipped Linux agent by filter.', 'agent_os' => $agent_os];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($agent_os === 'windows' && !in_array('windows_agent', $component_filters, true)) {
|
||||||
|
$results[$remote_id] = ['success' => true, 'status' => 'skipped', 'message' => 'Skipped Windows agent by filter.', 'agent_os' => $agent_os];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$component = ($agent_os === 'windows') ? 'windows_agent' : 'linux_agent';
|
||||||
|
$result = gsp_dispatch_remote_agent_update($row, $update_cfg, $component);
|
||||||
|
$result['agent_os'] = $agent_os;
|
||||||
|
$result['remote_server_name'] = $row['remote_server_name'];
|
||||||
|
$result['agent_ip'] = $row['agent_ip'];
|
||||||
|
$results[$remote_id] = $result;
|
||||||
|
if (!empty($result['success'])) {
|
||||||
|
$success_count++;
|
||||||
|
}
|
||||||
|
gsp_update_log('Remote agent update requested: id=' . $remote_id . ' component=' . $component . ' result=' . json_encode($result));
|
||||||
|
}
|
||||||
|
return ['success' => true, 'results' => $results, 'success_count' => $success_count];
|
||||||
|
}
|
||||||
|
|
||||||
function gsp_get_available_backups()
|
function gsp_get_available_backups()
|
||||||
{
|
{
|
||||||
$backups = [];
|
$backups = [];
|
||||||
|
|
@ -1947,6 +2310,17 @@ $new_cfg = [
|
||||||
'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : '',
|
'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : '',
|
||||||
'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : '',
|
'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : '',
|
||||||
'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : '',
|
'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : '',
|
||||||
|
'website_path' => isset($_POST['gsp_update_website_path']) ? rtrim(trim((string)$_POST['gsp_update_website_path']), '/') : '',
|
||||||
|
'panel_source_path' => isset($_POST['gsp_update_panel_source_path']) ? trim((string)$_POST['gsp_update_panel_source_path'], '/') : '',
|
||||||
|
'linux_agent_source_path' => isset($_POST['gsp_update_linux_agent_source_path']) ? trim((string)$_POST['gsp_update_linux_agent_source_path'], '/') : '',
|
||||||
|
'windows_agent_source_path' => isset($_POST['gsp_update_windows_agent_source_path']) ? trim((string)$_POST['gsp_update_windows_agent_source_path'], '/') : '',
|
||||||
|
'website_source_path' => isset($_POST['gsp_update_website_source_path']) ? trim((string)$_POST['gsp_update_website_source_path'], '/') : '',
|
||||||
|
'git_path' => isset($_POST['gsp_update_git_path']) ? trim((string)$_POST['gsp_update_git_path']) : '',
|
||||||
|
'backup_path' => isset($_POST['gsp_update_backup_path']) ? rtrim(trim((string)$_POST['gsp_update_backup_path']), '/') : '',
|
||||||
|
'panel_post_update_command' => isset($_POST['gsp_update_panel_post_update_command']) ? trim((string)$_POST['gsp_update_panel_post_update_command']) : '',
|
||||||
|
'website_post_update_command' => isset($_POST['gsp_update_website_post_update_command']) ? trim((string)$_POST['gsp_update_website_post_update_command']) : '',
|
||||||
|
'linux_agent_post_update_command' => isset($_POST['gsp_update_linux_agent_post_update_command']) ? trim((string)$_POST['gsp_update_linux_agent_post_update_command']) : '',
|
||||||
|
'windows_agent_post_update_command' => isset($_POST['gsp_update_windows_agent_post_update_command']) ? trim((string)$_POST['gsp_update_windows_agent_post_update_command']) : '',
|
||||||
'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0',
|
'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0',
|
||||||
];
|
];
|
||||||
$errors = gsp_validate_update_settings($new_cfg);
|
$errors = gsp_validate_update_settings($new_cfg);
|
||||||
|
|
@ -1958,22 +2332,122 @@ $db->setSettings([
|
||||||
'gsp_update_branch' => $new_cfg['branch'],
|
'gsp_update_branch' => $new_cfg['branch'],
|
||||||
'gsp_update_repo_root' => $new_cfg['repo_root'],
|
'gsp_update_repo_root' => $new_cfg['repo_root'],
|
||||||
'gsp_update_panel_path' => $new_cfg['panel_path'],
|
'gsp_update_panel_path' => $new_cfg['panel_path'],
|
||||||
|
'gsp_update_website_path' => $new_cfg['website_path'],
|
||||||
|
'gsp_update_panel_source_path' => $new_cfg['panel_source_path'],
|
||||||
|
'gsp_update_linux_agent_source_path' => $new_cfg['linux_agent_source_path'],
|
||||||
|
'gsp_update_windows_agent_source_path' => $new_cfg['windows_agent_source_path'],
|
||||||
|
'gsp_update_website_source_path' => $new_cfg['website_source_path'],
|
||||||
|
'gsp_update_git_path' => $new_cfg['git_path'],
|
||||||
|
'gsp_update_backup_path' => $new_cfg['backup_path'],
|
||||||
|
'gsp_update_panel_post_update_command' => $new_cfg['panel_post_update_command'],
|
||||||
|
'gsp_update_website_post_update_command' => $new_cfg['website_post_update_command'],
|
||||||
|
'gsp_update_linux_agent_post_update_command' => $new_cfg['linux_agent_post_update_command'],
|
||||||
|
'gsp_update_windows_agent_post_update_command' => $new_cfg['windows_agent_post_update_command'],
|
||||||
'gsp_update_backup_before' => $new_cfg['backup_before_update'],
|
'gsp_update_backup_before' => $new_cfg['backup_before_update'],
|
||||||
]);
|
]);
|
||||||
$settings['gsp_update_repo_url'] = $new_cfg['repo_url'];
|
$settings['gsp_update_repo_url'] = $new_cfg['repo_url'];
|
||||||
$settings['gsp_update_branch'] = $new_cfg['branch'];
|
$settings['gsp_update_branch'] = $new_cfg['branch'];
|
||||||
$settings['gsp_update_repo_root'] = $new_cfg['repo_root'];
|
$settings['gsp_update_repo_root'] = $new_cfg['repo_root'];
|
||||||
$settings['gsp_update_panel_path'] = $new_cfg['panel_path'];
|
$settings['gsp_update_panel_path'] = $new_cfg['panel_path'];
|
||||||
|
$settings['gsp_update_website_path'] = $new_cfg['website_path'];
|
||||||
|
$settings['gsp_update_panel_source_path'] = $new_cfg['panel_source_path'];
|
||||||
|
$settings['gsp_update_linux_agent_source_path'] = $new_cfg['linux_agent_source_path'];
|
||||||
|
$settings['gsp_update_windows_agent_source_path'] = $new_cfg['windows_agent_source_path'];
|
||||||
|
$settings['gsp_update_website_source_path'] = $new_cfg['website_source_path'];
|
||||||
|
$settings['gsp_update_git_path'] = $new_cfg['git_path'];
|
||||||
|
$settings['gsp_update_backup_path'] = $new_cfg['backup_path'];
|
||||||
|
$settings['gsp_update_panel_post_update_command'] = $new_cfg['panel_post_update_command'];
|
||||||
|
$settings['gsp_update_website_post_update_command'] = $new_cfg['website_post_update_command'];
|
||||||
|
$settings['gsp_update_linux_agent_post_update_command'] = $new_cfg['linux_agent_post_update_command'];
|
||||||
|
$settings['gsp_update_windows_agent_post_update_command'] = $new_cfg['windows_agent_post_update_command'];
|
||||||
$settings['gsp_update_backup_before'] = $new_cfg['backup_before_update'];
|
$settings['gsp_update_backup_before'] = $new_cfg['backup_before_update'];
|
||||||
$update_cfg = gsp_update_settings();
|
$update_cfg = gsp_update_settings();
|
||||||
print_success('Update settings saved.');
|
print_success('Update settings saved.');
|
||||||
}
|
}
|
||||||
|
} elseif ($action === 'update_components') {
|
||||||
|
$update_cfg = [
|
||||||
|
'repo_url' => isset($_POST['gsp_update_repo_url']) ? trim((string)$_POST['gsp_update_repo_url']) : $update_cfg['repo_url'],
|
||||||
|
'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : $update_cfg['branch'],
|
||||||
|
'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : $update_cfg['repo_root'],
|
||||||
|
'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : $update_cfg['panel_path'],
|
||||||
|
'website_path' => isset($_POST['gsp_update_website_path']) ? rtrim(trim((string)$_POST['gsp_update_website_path']), '/') : $update_cfg['website_path'],
|
||||||
|
'panel_source_path' => isset($_POST['gsp_update_panel_source_path']) ? trim((string)$_POST['gsp_update_panel_source_path'], '/') : $update_cfg['panel_source_path'],
|
||||||
|
'linux_agent_source_path' => isset($_POST['gsp_update_linux_agent_source_path']) ? trim((string)$_POST['gsp_update_linux_agent_source_path'], '/') : $update_cfg['linux_agent_source_path'],
|
||||||
|
'windows_agent_source_path' => isset($_POST['gsp_update_windows_agent_source_path']) ? trim((string)$_POST['gsp_update_windows_agent_source_path'], '/') : $update_cfg['windows_agent_source_path'],
|
||||||
|
'website_source_path' => isset($_POST['gsp_update_website_source_path']) ? trim((string)$_POST['gsp_update_website_source_path'], '/') : $update_cfg['website_source_path'],
|
||||||
|
'git_path' => isset($_POST['gsp_update_git_path']) ? trim((string)$_POST['gsp_update_git_path']) : $update_cfg['git_path'],
|
||||||
|
'backup_path' => isset($_POST['gsp_update_backup_path']) ? rtrim(trim((string)$_POST['gsp_update_backup_path']), '/') : $update_cfg['backup_path'],
|
||||||
|
'panel_post_update_command' => isset($_POST['gsp_update_panel_post_update_command']) ? trim((string)$_POST['gsp_update_panel_post_update_command']) : $update_cfg['panel_post_update_command'],
|
||||||
|
'website_post_update_command' => isset($_POST['gsp_update_website_post_update_command']) ? trim((string)$_POST['gsp_update_website_post_update_command']) : $update_cfg['website_post_update_command'],
|
||||||
|
'linux_agent_post_update_command' => isset($_POST['gsp_update_linux_agent_post_update_command']) ? trim((string)$_POST['gsp_update_linux_agent_post_update_command']) : $update_cfg['linux_agent_post_update_command'],
|
||||||
|
'windows_agent_post_update_command' => isset($_POST['gsp_update_windows_agent_post_update_command']) ? trim((string)$_POST['gsp_update_windows_agent_post_update_command']) : $update_cfg['windows_agent_post_update_command'],
|
||||||
|
'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0',
|
||||||
|
];
|
||||||
|
$errors = gsp_validate_update_settings($update_cfg);
|
||||||
|
if (!empty($errors)) {
|
||||||
|
print_failure('Update settings are invalid: ' . htmlspecialchars(implode(' | ', $errors)));
|
||||||
|
} else {
|
||||||
|
$requested_components = isset($_POST['gsp_update_components']) && is_array($_POST['gsp_update_components']) ? $_POST['gsp_update_components'] : [];
|
||||||
|
$requested_components = array_values(array_intersect(array('panel', 'website', 'linux_agent', 'windows_agent'), array_map('strval', $requested_components)));
|
||||||
|
$local_components = array_values(array_intersect(array('panel', 'website'), $requested_components));
|
||||||
|
$agent_components = array_values(array_intersect(array('linux_agent', 'windows_agent'), $requested_components));
|
||||||
|
$remote_ids = isset($_POST['gsp_update_remote_ids']) && is_array($_POST['gsp_update_remote_ids']) ? array_map('intval', $_POST['gsp_update_remote_ids']) : [];
|
||||||
|
if (!empty($_POST['gsp_update_all_agents'])) {
|
||||||
|
$remote_ids = array();
|
||||||
|
foreach (gsp_get_remote_agent_rows() as $row) {
|
||||||
|
$remote_ids[] = (int)$row['remote_server_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($requested_components)) {
|
||||||
|
print_failure('Select at least one component to update.');
|
||||||
|
} else {
|
||||||
|
$messages = [];
|
||||||
|
if (!empty($local_components)) {
|
||||||
|
$local_result = gsp_do_local_component_update($update_cfg, $local_components);
|
||||||
|
if (!$local_result['success']) {
|
||||||
|
print_failure('Local component update failed: ' . htmlspecialchars($local_result['error']));
|
||||||
|
} else {
|
||||||
|
$messages[] = 'Local components updated, files copied: ' . intval($local_result['files_copied']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($agent_components)) {
|
||||||
|
$remote_result = gsp_do_remote_agents_update($update_cfg, $remote_ids, $agent_components);
|
||||||
|
if (empty($remote_result['success']) && empty($remote_result['results'])) {
|
||||||
|
print_failure('Remote agent update failed: ' . htmlspecialchars(isset($remote_result['message']) ? $remote_result['message'] : 'Unknown error.'));
|
||||||
|
}
|
||||||
|
if (!empty($remote_result['results'])) {
|
||||||
|
echo "<h4>Remote Agent Update Results</h4><table class='center'><tr><th>Remote ID</th><th>Status</th><th>Message</th></tr>";
|
||||||
|
foreach ($remote_result['results'] as $rid => $result_row) {
|
||||||
|
$status = !empty($result_row['success']) ? (isset($result_row['status']) ? $result_row['status'] : 'queued') : (isset($result_row['status']) ? $result_row['status'] : 'failed');
|
||||||
|
$msg = isset($result_row['message']) ? $result_row['message'] : (isset($result_row['log_path']) ? $result_row['log_path'] : '');
|
||||||
|
echo "<tr><td>" . intval($rid) . "</td><td>" . htmlspecialchars($status) . "</td><td>" . htmlspecialchars($msg) . "</td></tr>";
|
||||||
|
}
|
||||||
|
echo "</table>";
|
||||||
|
}
|
||||||
|
$messages[] = 'Remote agent updates requested: ' . intval(isset($remote_result['success_count']) ? $remote_result['success_count'] : 0);
|
||||||
|
}
|
||||||
|
if (!empty($messages)) {
|
||||||
|
print_success(implode(' | ', array_map('htmlspecialchars', $messages)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} elseif ($action === 'update_configured') {
|
} elseif ($action === 'update_configured') {
|
||||||
$update_cfg = [
|
$update_cfg = [
|
||||||
'repo_url' => isset($_POST['gsp_update_repo_url']) ? trim((string)$_POST['gsp_update_repo_url']) : $update_cfg['repo_url'],
|
'repo_url' => isset($_POST['gsp_update_repo_url']) ? trim((string)$_POST['gsp_update_repo_url']) : $update_cfg['repo_url'],
|
||||||
'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : $update_cfg['branch'],
|
'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : $update_cfg['branch'],
|
||||||
'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : $update_cfg['repo_root'],
|
'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : $update_cfg['repo_root'],
|
||||||
'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : $update_cfg['panel_path'],
|
'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : $update_cfg['panel_path'],
|
||||||
|
'website_path' => isset($_POST['gsp_update_website_path']) ? rtrim(trim((string)$_POST['gsp_update_website_path']), '/') : $update_cfg['website_path'],
|
||||||
|
'panel_source_path' => isset($_POST['gsp_update_panel_source_path']) ? trim((string)$_POST['gsp_update_panel_source_path'], '/') : $update_cfg['panel_source_path'],
|
||||||
|
'linux_agent_source_path' => isset($_POST['gsp_update_linux_agent_source_path']) ? trim((string)$_POST['gsp_update_linux_agent_source_path'], '/') : $update_cfg['linux_agent_source_path'],
|
||||||
|
'windows_agent_source_path' => isset($_POST['gsp_update_windows_agent_source_path']) ? trim((string)$_POST['gsp_update_windows_agent_source_path'], '/') : $update_cfg['windows_agent_source_path'],
|
||||||
|
'website_source_path' => isset($_POST['gsp_update_website_source_path']) ? trim((string)$_POST['gsp_update_website_source_path'], '/') : $update_cfg['website_source_path'],
|
||||||
|
'git_path' => isset($_POST['gsp_update_git_path']) ? trim((string)$_POST['gsp_update_git_path']) : $update_cfg['git_path'],
|
||||||
|
'backup_path' => isset($_POST['gsp_update_backup_path']) ? rtrim(trim((string)$_POST['gsp_update_backup_path']), '/') : $update_cfg['backup_path'],
|
||||||
|
'panel_post_update_command' => isset($_POST['gsp_update_panel_post_update_command']) ? trim((string)$_POST['gsp_update_panel_post_update_command']) : $update_cfg['panel_post_update_command'],
|
||||||
|
'website_post_update_command' => isset($_POST['gsp_update_website_post_update_command']) ? trim((string)$_POST['gsp_update_website_post_update_command']) : $update_cfg['website_post_update_command'],
|
||||||
|
'linux_agent_post_update_command' => isset($_POST['gsp_update_linux_agent_post_update_command']) ? trim((string)$_POST['gsp_update_linux_agent_post_update_command']) : $update_cfg['linux_agent_post_update_command'],
|
||||||
|
'windows_agent_post_update_command' => isset($_POST['gsp_update_windows_agent_post_update_command']) ? trim((string)$_POST['gsp_update_windows_agent_post_update_command']) : $update_cfg['windows_agent_post_update_command'],
|
||||||
'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0',
|
'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0',
|
||||||
];
|
];
|
||||||
$db->setSettings([
|
$db->setSettings([
|
||||||
|
|
@ -1981,9 +2455,24 @@ $db->setSettings([
|
||||||
'gsp_update_branch' => $update_cfg['branch'],
|
'gsp_update_branch' => $update_cfg['branch'],
|
||||||
'gsp_update_repo_root' => $update_cfg['repo_root'],
|
'gsp_update_repo_root' => $update_cfg['repo_root'],
|
||||||
'gsp_update_panel_path' => $update_cfg['panel_path'],
|
'gsp_update_panel_path' => $update_cfg['panel_path'],
|
||||||
|
'gsp_update_website_path' => $update_cfg['website_path'],
|
||||||
|
'gsp_update_panel_source_path' => $update_cfg['panel_source_path'],
|
||||||
|
'gsp_update_linux_agent_source_path' => $update_cfg['linux_agent_source_path'],
|
||||||
|
'gsp_update_windows_agent_source_path' => $update_cfg['windows_agent_source_path'],
|
||||||
|
'gsp_update_website_source_path' => $update_cfg['website_source_path'],
|
||||||
|
'gsp_update_git_path' => $update_cfg['git_path'],
|
||||||
|
'gsp_update_backup_path' => $update_cfg['backup_path'],
|
||||||
|
'gsp_update_panel_post_update_command' => $update_cfg['panel_post_update_command'],
|
||||||
|
'gsp_update_website_post_update_command' => $update_cfg['website_post_update_command'],
|
||||||
|
'gsp_update_linux_agent_post_update_command' => $update_cfg['linux_agent_post_update_command'],
|
||||||
|
'gsp_update_windows_agent_post_update_command' => $update_cfg['windows_agent_post_update_command'],
|
||||||
'gsp_update_backup_before' => $update_cfg['backup_before_update'],
|
'gsp_update_backup_before' => $update_cfg['backup_before_update'],
|
||||||
]);
|
]);
|
||||||
|
if (!function_exists('gsp_do_configured_git_update')) {
|
||||||
|
$result = ['success' => false, 'error' => 'Configured Git updater helper is unavailable. Deploy the current Panel/modules/administration/panel_update.php and reload.'];
|
||||||
|
} else {
|
||||||
$result = gsp_do_configured_git_update($update_cfg, $restart_nonce);
|
$result = gsp_do_configured_git_update($update_cfg, $restart_nonce);
|
||||||
|
}
|
||||||
if (!empty($result['restart_required'])) {
|
if (!empty($result['restart_required'])) {
|
||||||
print_success('Updater files changed and were updated first. Restarting update with refreshed updater...');
|
print_success('Updater files changed and were updated first. Restarting update with refreshed updater...');
|
||||||
$auto_restart_payload = ['action' => 'update_configured', 'nonce' => $result['restart_nonce']];
|
$auto_restart_payload = ['action' => 'update_configured', 'nonce' => $result['restart_nonce']];
|
||||||
|
|
@ -2101,12 +2590,65 @@ echo "<tr><td><strong>Repository URL / Path</strong></td><td><input type='text'
|
||||||
echo "<tr><td><strong>Branch / Channel</strong></td><td><input type='text' name='gsp_update_branch' value='" . htmlspecialchars($update_cfg['branch'], ENT_QUOTES, 'UTF-8') . "' size='40'></td></tr>\n";
|
echo "<tr><td><strong>Branch / Channel</strong></td><td><input type='text' name='gsp_update_branch' value='" . htmlspecialchars($update_cfg['branch'], ENT_QUOTES, 'UTF-8') . "' size='40'></td></tr>\n";
|
||||||
echo "<tr><td><strong>Repository Root</strong></td><td><input type='text' name='gsp_update_repo_root' value='" . htmlspecialchars($update_cfg['repo_root'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
echo "<tr><td><strong>Repository Root</strong></td><td><input type='text' name='gsp_update_repo_root' value='" . htmlspecialchars($update_cfg['repo_root'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||||
echo "<tr><td><strong>Panel Path</strong></td><td><input type='text' name='gsp_update_panel_path' value='" . htmlspecialchars($update_cfg['panel_path'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
echo "<tr><td><strong>Panel Path</strong></td><td><input type='text' name='gsp_update_panel_path' value='" . htmlspecialchars($update_cfg['panel_path'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Website Path</strong></td><td><input type='text' name='gsp_update_website_path' value='" . htmlspecialchars($update_cfg['website_path'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Panel Source Folder</strong></td><td><input type='text' name='gsp_update_panel_source_path' value='" . htmlspecialchars($update_cfg['panel_source_path'], ENT_QUOTES, 'UTF-8') . "' size='45'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Linux Agent Source Folder</strong></td><td><input type='text' name='gsp_update_linux_agent_source_path' value='" . htmlspecialchars($update_cfg['linux_agent_source_path'], ENT_QUOTES, 'UTF-8') . "' size='45'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Windows Agent Source Folder</strong></td><td><input type='text' name='gsp_update_windows_agent_source_path' value='" . htmlspecialchars($update_cfg['windows_agent_source_path'], ENT_QUOTES, 'UTF-8') . "' size='45'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Website Source Folder</strong></td><td><input type='text' name='gsp_update_website_source_path' value='" . htmlspecialchars($update_cfg['website_source_path'], ENT_QUOTES, 'UTF-8') . "' size='45'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Git Executable</strong></td><td><input type='text' name='gsp_update_git_path' value='" . htmlspecialchars($update_cfg['git_path'], ENT_QUOTES, 'UTF-8') . "' size='45'> <small>Usually <code>git</code>.</small></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Backup Path</strong></td><td><input type='text' name='gsp_update_backup_path' value='" . htmlspecialchars($update_cfg['backup_path'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||||
echo "<tr><td><strong>Backup Before Update</strong></td><td><label><input type='checkbox' name='gsp_update_backup_before' value='1' " . (!empty($update_cfg['backup_before_update']) ? "checked" : "") . "> create backup before updating</label></td></tr>\n";
|
echo "<tr><td><strong>Backup Before Update</strong></td><td><label><input type='checkbox' name='gsp_update_backup_before' value='1' " . (!empty($update_cfg['backup_before_update']) ? "checked" : "") . "> create backup before updating</label></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Panel Post-update Command</strong></td><td><input type='text' name='gsp_update_panel_post_update_command' value='" . htmlspecialchars($update_cfg['panel_post_update_command'], ENT_QUOTES, 'UTF-8') . "' size='85'> <small>Admin-only dangerous command.</small></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Website Post-update Command</strong></td><td><input type='text' name='gsp_update_website_post_update_command' value='" . htmlspecialchars($update_cfg['website_post_update_command'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Linux Agent Post-update Command</strong></td><td><input type='text' name='gsp_update_linux_agent_post_update_command' value='" . htmlspecialchars($update_cfg['linux_agent_post_update_command'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||||
|
echo "<tr><td><strong>Windows Agent Post-update Command</strong></td><td><input type='text' name='gsp_update_windows_agent_post_update_command' value='" . htmlspecialchars($update_cfg['windows_agent_post_update_command'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||||
echo "</table>\n";
|
echo "</table>\n";
|
||||||
echo "<p>\n";
|
echo "<p>\n";
|
||||||
echo "<button type='submit' name='gsp_update_action' value='save_settings'>Save Settings</button> ";
|
echo "<button type='submit' name='gsp_update_action' value='save_settings'>Save Settings</button> ";
|
||||||
echo "<button type='submit' name='gsp_update_action' value='update_configured' onclick='return confirm(\"Update Panel from the configured repository and branch?\");'>Update Panel</button>\n";
|
echo "<button type='submit' name='gsp_update_action' value='update_configured' onclick='return confirm(\"Update Panel from the configured repository and branch?\");'>Update Panel</button>\n";
|
||||||
echo "</p>\n";
|
echo "</p>\n";
|
||||||
|
echo "<h3>Update Components</h3>\n";
|
||||||
|
echo "<p>Select the application components to update from the configured repository. Server homes, hosted game data, uploads, logs, cache, and agent configuration folders are protected.</p>\n";
|
||||||
|
echo "<p>";
|
||||||
|
echo "<label><input type='checkbox' name='gsp_update_components[]' value='panel'> Panel files</label> ";
|
||||||
|
echo "<label><input type='checkbox' name='gsp_update_components[]' value='website'> Website files</label> ";
|
||||||
|
echo "<label><input type='checkbox' name='gsp_update_components[]' value='linux_agent'> Linux agents</label> ";
|
||||||
|
echo "<label><input type='checkbox' name='gsp_update_components[]' value='windows_agent'> Windows agents</label> ";
|
||||||
|
echo "<label><input type='checkbox' name='gsp_update_all_agents' value='1'> All remote agents</label>";
|
||||||
|
echo "</p>\n";
|
||||||
|
$remote_rows = gsp_get_remote_agent_rows();
|
||||||
|
if (!empty($remote_rows)) {
|
||||||
|
if (!class_exists('OGPRemoteLibrary') && function_exists('xmlrpc_encode_request') && function_exists('xmlrpc_decode') && file_exists(GSP_PANEL_DIR . '/includes/lib_remote.php')) {
|
||||||
|
require_once(GSP_PANEL_DIR . '/includes/lib_remote.php');
|
||||||
|
}
|
||||||
|
echo "<table class='center'><tr><th>Select</th><th>Name</th><th>Host</th><th>Status</th><th>Reported OS</th></tr>\n";
|
||||||
|
foreach ($remote_rows as $remote_row) {
|
||||||
|
$status_text = 'not checked';
|
||||||
|
$os_text = '';
|
||||||
|
if (class_exists('OGPRemoteLibrary')) {
|
||||||
|
try {
|
||||||
|
$probe = new OGPRemoteLibrary($remote_row['agent_ip'], $remote_row['agent_port'], $remote_row['encryption_key'], 2);
|
||||||
|
$status_text = ((int)$probe->status_chk() === 1) ? 'online' : 'offline/unknown';
|
||||||
|
if ($status_text === 'online') {
|
||||||
|
$os_text = (string)$probe->what_os();
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$status_text = 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "<tr>";
|
||||||
|
echo "<td><input type='checkbox' name='gsp_update_remote_ids[]' value='" . intval($remote_row['remote_server_id']) . "'></td>";
|
||||||
|
echo "<td>" . htmlspecialchars($remote_row['remote_server_name']) . "</td>";
|
||||||
|
echo "<td>" . htmlspecialchars($remote_row['agent_ip'] . ':' . $remote_row['agent_port']) . "</td>";
|
||||||
|
echo "<td>" . htmlspecialchars($status_text) . "</td>";
|
||||||
|
echo "<td>" . htmlspecialchars($os_text !== '' ? $os_text : gsp_infer_agent_os($remote_row, '')) . "</td>";
|
||||||
|
echo "</tr>\n";
|
||||||
|
}
|
||||||
|
echo "</table>\n";
|
||||||
|
} else {
|
||||||
|
echo "<p>No remote agents are configured.</p>\n";
|
||||||
|
}
|
||||||
|
echo "<p><button type='submit' name='gsp_update_action' value='update_components' onclick='return confirm(\"Update selected components from the configured repository? Remote agents may restart.\");'>Update Selected Components</button></p>\n";
|
||||||
echo "</form>\n";
|
echo "</form>\n";
|
||||||
|
|
||||||
echo "<h3>Backup</h3>\n";
|
echo "<h3>Backup</h3>\n";
|
||||||
|
|
|
||||||
35
Panel/modules/update/tests/update_config_smoke.php
Normal file
35
Panel/modules/update/tests/update_config_smoke.php
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
$settings = array();
|
||||||
|
require_once(dirname(__DIR__) . '/../administration/panel_update.php');
|
||||||
|
|
||||||
|
function gsp_update_smoke_assert($condition, $message)
|
||||||
|
{
|
||||||
|
if (!$condition) {
|
||||||
|
fwrite(STDERR, "FAIL: {$message}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo "PASS: {$message}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$cfg = gsp_update_settings();
|
||||||
|
gsp_update_smoke_assert(function_exists('gsp_do_configured_git_update'), 'configured Git updater helper is top-level');
|
||||||
|
gsp_update_smoke_assert($cfg['repo_url'] === 'http://forge.runlevelsystems.com/dev/GSP.git', 'default repo URL is Forgejo');
|
||||||
|
gsp_update_smoke_assert($cfg['branch'] === 'Panel-unstable', 'default branch is Panel-unstable');
|
||||||
|
gsp_update_smoke_assert($cfg['panel_source_path'] === 'Panel', 'default Panel source folder');
|
||||||
|
gsp_update_smoke_assert($cfg['linux_agent_source_path'] === 'Agent_Linux', 'default Linux agent source folder');
|
||||||
|
gsp_update_smoke_assert($cfg['windows_agent_source_path'] === 'Agent-Windows', 'default Windows agent source folder');
|
||||||
|
gsp_update_smoke_assert($cfg['website_source_path'] === 'Website', 'default Website source folder');
|
||||||
|
gsp_update_smoke_assert(empty(gsp_validate_update_settings($cfg)), 'default update settings validate');
|
||||||
|
|
||||||
|
$bad = $cfg;
|
||||||
|
$bad['branch'] = '../bad';
|
||||||
|
gsp_update_smoke_assert(!empty(gsp_validate_update_settings($bad)), 'invalid branch is rejected');
|
||||||
|
|
||||||
|
$tmp = sys_get_temp_dir() . '/gsp_update_smoke_' . getmypid();
|
||||||
|
@mkdir($tmp . '/Panel', 0777, true);
|
||||||
|
gsp_update_smoke_assert(gsp_component_source_path($tmp, 'Panel') !== false, 'component source path resolves under checkout');
|
||||||
|
gsp_update_smoke_assert(gsp_component_source_path($tmp, '../Panel') === false, 'component source path rejects traversal');
|
||||||
|
@rmdir($tmp . '/Panel');
|
||||||
|
@rmdir($tmp);
|
||||||
|
|
||||||
|
echo "All update config smoke tests passed.\n";
|
||||||
|
|
@ -106,6 +106,24 @@ Useful configuration and runtime areas:
|
||||||
|
|
||||||
The agent also maintains screen logs and helper scripts inside its runtime area.
|
The agent also maintains screen logs and helper scripts inside its runtime area.
|
||||||
|
|
||||||
|
## Remote Git Self-Update
|
||||||
|
|
||||||
|
The Linux agent exposes the admin-only `component_update` XML-RPC method. The Panel update page uses this to queue a Git-based Linux agent update.
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
|
||||||
|
1. Panel sends an encrypted payload containing repo URL, branch, source folder, optional Git path, optional backup path, and optional admin post-update command.
|
||||||
|
2. Agent validates the payload.
|
||||||
|
3. Agent writes `gsp_component_update_<timestamp>.sh` under the agent run directory.
|
||||||
|
4. The updater script runs detached in `screen`.
|
||||||
|
5. The script clones the configured branch into staging.
|
||||||
|
6. It copies only the configured Linux agent source folder, usually `Agent_Linux`.
|
||||||
|
7. It preserves `Cfg/`, `ServerFiles/`, `Schedule/`, logs, screen logs, `steamcmd/`, `startups/`, temporary folders, backups, and PID files.
|
||||||
|
8. It validates the updated `ogp_agent.pl` with `perl -c`.
|
||||||
|
9. It restarts `ogp_agent.service` through `systemd` if available, otherwise uses the existing `screen` startup fallback.
|
||||||
|
|
||||||
|
The agent returns `queued` immediately with the log path `gsp_component_update.log`. A queued response means the updater launched; check the log for final success/failure.
|
||||||
|
|
||||||
## Linux-Specific Notes
|
## Linux-Specific Notes
|
||||||
|
|
||||||
- The Linux agent uses `screen` and `sudo_exec_without_decrypt`.
|
- The Linux agent uses `screen` and `sudo_exec_without_decrypt`.
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,23 @@ Relevant functions:
|
||||||
|
|
||||||
The Windows scheduler implementation should remain aligned with the Linux scheduler implementation so the Panel can treat both the same way.
|
The Windows scheduler implementation should remain aligned with the Linux scheduler implementation so the Panel can treat both the same way.
|
||||||
|
|
||||||
|
## Remote Git Self-Update
|
||||||
|
|
||||||
|
The Windows agent exposes the same admin-only `component_update` XML-RPC method as the Linux agent. In this repository the Windows agent is explicitly Cygwin-based, so the first implementation uses a Cygwin-compatible detached shell updater rather than a separate native PowerShell service wrapper.
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
|
||||||
|
1. Panel sends an encrypted payload containing repo URL, branch, Windows agent source folder, optional Git path, optional backup path, and optional admin post-update command.
|
||||||
|
2. Agent validates the request and writes `gsp_component_update_<timestamp>.sh` under the current agent run directory, usually `/OGP`.
|
||||||
|
3. The updater runs detached in `screen`.
|
||||||
|
4. The updater clones the configured branch into staging.
|
||||||
|
5. It copies only the configured Windows agent source folder, usually `Agent-Windows`.
|
||||||
|
6. It preserves `Cfg/`, `ServerFiles/`, `Schedule/`, logs, screen logs, `steamcmd/`, `startups/`, temporary folders, backups, and PID files.
|
||||||
|
7. It validates `ogp_agent.pl` with `perl -c`.
|
||||||
|
8. It restarts the agent using the existing Cygwin/screen fallback.
|
||||||
|
|
||||||
|
The immediate response is `queued` with the agent-side log path `gsp_component_update.log`.
|
||||||
|
|
||||||
## Windows-Specific Notes
|
## Windows-Specific Notes
|
||||||
|
|
||||||
- Path conversion between Cygwin and native Windows paths matters during startup.
|
- Path conversion between Cygwin and native Windows paths matters during startup.
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ Panel module
|
||||||
| `start_fastdl` / `stop_fastdl` / `restart_fastdl` / `fastdl_status` | FastDL service management | `fast_download` | Source/GoldSrc web distribution support. |
|
| `start_fastdl` / `stop_fastdl` / `restart_fastdl` / `fastdl_status` | FastDL service management | `fast_download` | Source/GoldSrc web distribution support. |
|
||||||
| `scheduler_*` methods | Task list and task CRUD | `cron` | Agent-owned scheduler implementation. |
|
| `scheduler_*` methods | Task list and task CRUD | `cron` | Agent-owned scheduler implementation. |
|
||||||
| `agent_restart` | Restart the agent itself | Admin maintenance | Node maintenance action. |
|
| `agent_restart` | Restart the agent itself | Admin maintenance | Node maintenance action. |
|
||||||
|
| `component_update` | Queue a Git-based agent code update | Update module | Admin-only. Uses encrypted XML-RPC, validates repo/branch/source/destination, launches a detached updater, preserves agent config/runtime/server data, and returns queued status plus an agent log path. |
|
||||||
| `shell_action` | Run a shell action in a controlled way | Advanced agent operations | Should remain tightly permissioned. |
|
| `shell_action` | Run a shell action in a controlled way | Advanced agent operations | Should remain tightly permissioned. |
|
||||||
| `send_steam_guard_code` | Submit Steam Guard code | Steam authenticated installs | Used for authenticated SteamCMD workflows. |
|
| `send_steam_guard_code` | Submit Steam Guard code | Steam authenticated installs | Used for authenticated SteamCMD workflows. |
|
||||||
| `steam_workshop` / `get_workshop_mods_info` | Workshop-related helper calls | Workshop/content flows | Active in current module work, but still being consolidated. |
|
| `steam_workshop` / `get_workshop_mods_info` | Workshop-related helper calls | Workshop/content flows | Active in current module work, but still being consolidated. |
|
||||||
|
|
@ -140,6 +141,23 @@ Panel
|
||||||
-> refresh dedicated preformatted log panel via AJAX
|
-> refresh dedicated preformatted log panel via AJAX
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Remote Agent Component Update
|
||||||
|
|
||||||
|
```text
|
||||||
|
Panel update page
|
||||||
|
-> component_update(payload)
|
||||||
|
Agent
|
||||||
|
-> validate repo, branch, source folder, destination
|
||||||
|
-> write detached updater script
|
||||||
|
-> return queued + log path
|
||||||
|
Updater script
|
||||||
|
-> clone/fetch repository into staging
|
||||||
|
-> copy Agent_Linux or Agent-Windows folder only
|
||||||
|
-> preserve Cfg, ServerFiles, Schedule, logs, steamcmd, startups, pid files
|
||||||
|
-> validate ogp_agent.pl
|
||||||
|
-> restart agent through systemd or screen fallback
|
||||||
|
```
|
||||||
|
|
||||||
## Error Handling Notes
|
## Error Handling Notes
|
||||||
|
|
||||||
- Do not treat query failure as a start failure by itself.
|
- Do not treat query failure as a start failure by itself.
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,16 @@ This file is the first stop for future Codex sessions working in this repository
|
||||||
3. Check the user/admin content pages.
|
3. Check the user/admin content pages.
|
||||||
4. Check whether the action should be treated as install, update, or uninstall.
|
4. Check whether the action should be treated as install, update, or uninstall.
|
||||||
|
|
||||||
|
### Debug Panel or Agent updates
|
||||||
|
|
||||||
|
1. Read `docs/modules/UPDATE.md`.
|
||||||
|
2. Check `Panel/modules/update/update.php`.
|
||||||
|
3. Check `Panel/modules/administration/panel_update.php`.
|
||||||
|
4. Check `Panel/includes/lib_remote.php` for the `component_update` wrapper.
|
||||||
|
5. Check both `Agent_Linux/ogp_agent.pl` and `Agent-Windows/ogp_agent.pl` for the `component_update` RPC.
|
||||||
|
6. Remember that local Panel/Website updates and remote agent updates both clone a configured Git branch into staging and copy only configured component folders.
|
||||||
|
7. Never let updater logic delete server homes, game install folders, user data, agent `Cfg/`, logs, uploads, backups, or runtime PID files.
|
||||||
|
|
||||||
## Things Already Investigated
|
## Things Already Investigated
|
||||||
|
|
||||||
The repository has already been mapped in these areas:
|
The repository has already been mapped in these areas:
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,14 @@ The admin page stores these settings in the Panel settings table:
|
||||||
- `gsp_update_branch`
|
- `gsp_update_branch`
|
||||||
- `gsp_update_repo_root`
|
- `gsp_update_repo_root`
|
||||||
- `gsp_update_panel_path`
|
- `gsp_update_panel_path`
|
||||||
|
- `gsp_update_website_path`
|
||||||
|
- `gsp_update_panel_source_path`
|
||||||
|
- `gsp_update_linux_agent_source_path`
|
||||||
|
- `gsp_update_windows_agent_source_path`
|
||||||
|
- `gsp_update_website_source_path`
|
||||||
|
- `gsp_update_git_path`
|
||||||
|
- `gsp_update_backup_path`
|
||||||
|
- optional admin-only post-update commands per component
|
||||||
- `gsp_update_backup_before`
|
- `gsp_update_backup_before`
|
||||||
|
|
||||||
Defaults:
|
Defaults:
|
||||||
|
|
@ -34,6 +42,11 @@ Defaults:
|
||||||
- Branch: `Panel-unstable`
|
- Branch: `Panel-unstable`
|
||||||
- Repository Root: `/var/www/html/GSP`
|
- Repository Root: `/var/www/html/GSP`
|
||||||
- Panel Path: `/var/www/html/GSP/Panel`
|
- Panel Path: `/var/www/html/GSP/Panel`
|
||||||
|
- Website Path: `/var/www/html/GSP/Website`
|
||||||
|
- Panel Source Folder: `Panel`
|
||||||
|
- Linux Agent Source Folder: `Agent_Linux`
|
||||||
|
- Windows Agent Source Folder: `Agent-Windows`
|
||||||
|
- Website Source Folder: `Website`
|
||||||
- Backup Before Update: enabled
|
- Backup Before Update: enabled
|
||||||
|
|
||||||
Important implementation note:
|
Important implementation note:
|
||||||
|
|
@ -41,6 +54,7 @@ Important implementation note:
|
||||||
- `gsp_update_settings()` and `gsp_validate_update_settings()` are defined at top level in `Panel/modules/administration/panel_update.php`.
|
- `gsp_update_settings()` and `gsp_validate_update_settings()` are defined at top level in `Panel/modules/administration/panel_update.php`.
|
||||||
- These helpers must not be nested inside another function. A previous bad edit placed `gsp_update_settings()` inside `gsp_get_git_commit()`, which caused a fatal error when the update page called the helper before `gsp_get_git_commit()` had ever executed.
|
- These helpers must not be nested inside another function. A previous bad edit placed `gsp_update_settings()` inside `gsp_get_git_commit()`, which caused a fatal error when the update page called the helper before `gsp_get_git_commit()` had ever executed.
|
||||||
- If the update page throws `Call to undefined function gsp_update_settings()`, first verify the deployed `Panel/modules/administration/panel_update.php` matches the repository version and that this helper exists near the top of the file before `gsp_panel_update_section()` is called.
|
- If the update page throws `Call to undefined function gsp_update_settings()`, first verify the deployed `Panel/modules/administration/panel_update.php` matches the repository version and that this helper exists near the top of the file before `gsp_panel_update_section()` is called.
|
||||||
|
- `gsp_do_configured_git_update()` must also remain top-level. A bad edit placed it inside `gsp_do_update()`, so `/home.php?m=update` called an undefined function until a legacy GitHub update path happened to execute first.
|
||||||
|
|
||||||
## Update Flow
|
## Update Flow
|
||||||
|
|
||||||
|
|
@ -54,6 +68,49 @@ Important implementation note:
|
||||||
8. Run module updates/post-update hooks.
|
8. Run module updates/post-update hooks.
|
||||||
9. Write version metadata and `LAST_UPDATE.txt`.
|
9. Write version metadata and `LAST_UPDATE.txt`.
|
||||||
|
|
||||||
|
## Component Updates
|
||||||
|
|
||||||
|
The update page can update selected components from one repository:
|
||||||
|
|
||||||
|
- Panel files
|
||||||
|
- Website files
|
||||||
|
- Linux agents
|
||||||
|
- Windows/Cygwin agents
|
||||||
|
|
||||||
|
Local Panel/Website updates clone the configured repository into a temporary checkout and copy only the configured component source folder into the configured destination path. Protected folders and files are not overwritten:
|
||||||
|
|
||||||
|
- `includes/config.inc.php`
|
||||||
|
- `Cfg/`
|
||||||
|
- `ServerFiles/`
|
||||||
|
- `Schedule/`
|
||||||
|
- `logs/`
|
||||||
|
- `screenlogs/`
|
||||||
|
- `cache/`
|
||||||
|
- `tmp/`
|
||||||
|
- `uploads/`
|
||||||
|
- `backups/`
|
||||||
|
- `steamcmd/`
|
||||||
|
- `startups/`
|
||||||
|
- PID files
|
||||||
|
|
||||||
|
Remote agent updates use the encrypted Panel-Agent XML-RPC channel and the `component_update` RPC. The agent writes a detached updater script, clones the repo to staging, backs up the current agent code, copies only the configured agent source folder, validates `ogp_agent.pl`, then restarts through `systemd` when available or the existing `screen` fallback.
|
||||||
|
|
||||||
|
Remote update status is queued/asynchronous. The first response confirms that the update was accepted and gives the agent-side log path.
|
||||||
|
|
||||||
|
Remote updates require PHP XML-RPC on the Panel host. If the extension is missing, the update page still loads and reports a clean `missing_xmlrpc` error when a remote agent update is requested.
|
||||||
|
|
||||||
|
## Smoke Tests
|
||||||
|
|
||||||
|
Useful validation commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php -l Panel/modules/administration/panel_update.php
|
||||||
|
php -l Panel/modules/update/update.php
|
||||||
|
php -l Panel/includes/lib_remote.php
|
||||||
|
php Panel/modules/update/tests/update_config_smoke.php
|
||||||
|
perl -c ogp_agent.pl # run from each installed agent directory with dependencies present
|
||||||
|
```
|
||||||
|
|
||||||
## Diagnostics
|
## Diagnostics
|
||||||
|
|
||||||
Apache and SSL checks are diagnostics only. Missing SSL certificates do not block Panel updates.
|
Apache and SSL checks are diagnostics only. Missing SSL certificates do not block Panel updates.
|
||||||
|
|
@ -68,6 +125,7 @@ The old repeated SSL vhost disable buttons are not part of the primary update pa
|
||||||
## Repair Notes
|
## Repair Notes
|
||||||
|
|
||||||
- The update page fatal `Call to undefined function gsp_update_settings()` means the deployed `Panel/modules/administration/panel_update.php` is missing the top-level helper or is not the current repository copy.
|
- The update page fatal `Call to undefined function gsp_update_settings()` means the deployed `Panel/modules/administration/panel_update.php` is missing the top-level helper or is not the current repository copy.
|
||||||
|
- The update page fatal `Call to undefined function gsp_do_configured_git_update()` means the configured Git update helper is missing or nested inside another helper in the deployed `panel_update.php`.
|
||||||
- `Panel/modules/update/update.php` only loads `Panel/modules/administration/panel_update.php` and calls `gsp_panel_update_section()`.
|
- `Panel/modules/update/update.php` only loads `Panel/modules/administration/panel_update.php` and calls `gsp_panel_update_section()`.
|
||||||
- The configured update action uses `git clone --depth 1 --branch <configured branch> <configured repository source> <temporary checkout>`.
|
- The configured update action uses `git clone --depth 1 --branch <configured branch> <configured repository source> <temporary checkout>`.
|
||||||
- Clone failures are logged to `logs/update_trace.log` with the configured repository source and branch.
|
- Clone failures are logged to `logs/update_trace.log` with the configured repository source and branch.
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,27 @@ Panel update and patch management.
|
||||||
|
|
||||||
## Missing Functionality
|
## Missing Functionality
|
||||||
|
|
||||||
- clearer update history and rollback guidance
|
- richer update history and rollback guidance
|
||||||
|
- live progress polling for asynchronous remote agent updates
|
||||||
|
|
||||||
## Suggested Future Improvements
|
## Suggested Future Improvements
|
||||||
|
|
||||||
- keep admin-only and document carefully
|
- keep admin-only and document carefully
|
||||||
|
- add status polling for `component_update` logs after the agent has restarted
|
||||||
|
|
||||||
## Recommendation
|
## Recommendation
|
||||||
|
|
||||||
- Keep
|
- Keep
|
||||||
|
|
||||||
|
## Current GSP Additions
|
||||||
|
|
||||||
|
The primary update implementation lives in `Panel/modules/administration/panel_update.php` and is exposed by `Panel/modules/update/update.php`.
|
||||||
|
|
||||||
|
Current update targets:
|
||||||
|
|
||||||
|
- Panel files
|
||||||
|
- Website files
|
||||||
|
- Linux agents
|
||||||
|
- Windows/Cygwin agents
|
||||||
|
|
||||||
|
The updater uses a single configured Git repository with component source folders such as `Panel`, `Website`, `Agent_Linux`, and `Agent-Windows`. Remote agents are updated through the encrypted `component_update` XML-RPC method and preserve hosted game data and agent configuration folders.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue