feat: admin billing integration + migrate system (replaces clone)
Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/8940e39d-4aaa-4154-874b-74ab24d74da3 Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
parent
4db784a84a
commit
9d1999f374
8 changed files with 506 additions and 5 deletions
|
|
@ -220,4 +220,24 @@ define('OGP_LANG_ftp_account_username_too_long', "FTP username is too long. Try
|
|||
define('OGP_LANG_ftp_account_password_too_long', "FTP password is too long. Try a shorter password no longer than 20 characters.");
|
||||
define('OGP_LANG_other_servers_exist_with_path_please_change', "Other homes exist with the same path. It is recommended (but not required) that you change this path to something unique. You may have problems if you do NOT.");
|
||||
define('OGP_LANG_change_access_rights_for_selected_servers', "Change access rights for selected servers");
|
||||
|
||||
// Migrate feature (replaces Clone)
|
||||
define('OGP_LANG_migrate', "Migrate");
|
||||
define('OGP_LANG_migrate_server', "Migrate Server: %s");
|
||||
define('OGP_LANG_migrate_info', "This tool copies all files from the source server to a destination server of the same game type. The source is never deleted or modified.");
|
||||
define('OGP_LANG_migrate_bullet_no_delete', "The source server is NOT deleted.");
|
||||
define('OGP_LANG_migrate_bullet_same_game', "Destination must be the same game type.");
|
||||
define('OGP_LANG_migrate_bullet_overwrite', "All files in the destination will be overwritten.");
|
||||
define('OGP_LANG_migrate_bullet_no_billing', "Billing records are not changed automatically.");
|
||||
define('OGP_LANG_migrate_source', "Source");
|
||||
define('OGP_LANG_migrate_destination', "Destination");
|
||||
define('OGP_LANG_migrate_confirm_overwrite', "Confirm overwrite");
|
||||
define('OGP_LANG_migrate_confirm_overwrite_info', "Check this box to confirm you want to overwrite the destination server files.");
|
||||
define('OGP_LANG_migrate_start', "Start Migration");
|
||||
define('OGP_LANG_migrate_no_compatible_destinations', "No compatible destination servers found (same game type, different server).");
|
||||
define('OGP_LANG_migrate_different_game_type', "Source and destination must be the same game type.");
|
||||
define('OGP_LANG_migrate_confirm_required', "You must tick the confirmation checkbox before starting a migration.");
|
||||
define('OGP_LANG_migrate_running_background', "Migration started and is running in the background.");
|
||||
define('OGP_LANG_migrate_complete', "Migration completed successfully.");
|
||||
define('OGP_LANG_migrate_failed_code', "Migration failed with return code %d.");
|
||||
?>
|
||||
|
|
@ -20,6 +20,10 @@ function exec_ogp_module()
|
|||
{
|
||||
global $db,$view,$settings;
|
||||
|
||||
// $now is used in multiple branches below — define it once here so it is
|
||||
// always a string that date() / strtotime() can handle safely (PHP 8 fix).
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$override = isset($GLOBALS['BILLING_PROVISION_OVERRIDE']) ? $GLOBALS['BILLING_PROVISION_OVERRIDE'] : null;
|
||||
$user_id = isset($override['user_id']) ? intval($override['user_id']) : (isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0);
|
||||
$isAdmin = isset($override['is_admin']) ? (bool)$override['is_admin'] : $db->isAdmin($user_id);
|
||||
|
|
@ -80,7 +84,7 @@ function exec_ogp_module()
|
|||
$ip = $order['ip'];
|
||||
$max_players = $order['max_players'];
|
||||
$user_id = $order['user_id'];
|
||||
$extended = $order['extended'] == "1" ? TRUE : FALSE;
|
||||
$extended = isset($order['extended']) && $order['extended'] == "1" ? TRUE : FALSE;
|
||||
//Query service info
|
||||
$service = $db->resultQuery( "SELECT *
|
||||
FROM OGP_DB_PREFIXbilling_services
|
||||
|
|
@ -413,8 +417,6 @@ function exec_ogp_module()
|
|||
|
||||
$db->query( "UPDATE OGP_DB_PREFIXgame_mods SET max_players= ".$order['max_players']." WHERE home_id=".$db->realEscapeSingle($home_id));
|
||||
|
||||
}
|
||||
|
||||
// Show results and redirect
|
||||
if ($provisioned_count > 0) {
|
||||
echo "<div class='success'>";
|
||||
|
|
|
|||
|
|
@ -151,6 +151,22 @@ function exec_ogp_module()
|
|||
}
|
||||
print_success(get_lang('game_home_added'));
|
||||
$db->logger(get_lang('game_home_added')." ($server_name)");
|
||||
|
||||
// Record the server in the billing tables so it participates in the
|
||||
// normal lifecycle (renewals, expiration, admin dashboard).
|
||||
require_once('billing_integration.php');
|
||||
admin_register_server_in_billing(
|
||||
$db,
|
||||
$web_user_id,
|
||||
$home_cfg_id,
|
||||
$rserver_id,
|
||||
$server_name,
|
||||
0, // max_players — set later via edit_home
|
||||
$access_rights,
|
||||
$ftp,
|
||||
$new_home_id
|
||||
);
|
||||
|
||||
$view->refresh("?m=user_games&p=edit&home_id=$new_home_id", 0);
|
||||
}else{
|
||||
print_failure(get_lang_f("failed_to_assign_home_to_user", $new_home_id, $web_user . " " . $db->getError()));
|
||||
|
|
|
|||
13
modules/user_games/admin_billing_migration.sql
Normal file
13
modules/user_games/admin_billing_migration.sql
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
-- Admin Billing Integration Migration
|
||||
-- Run this once to add required columns to billing_orders.
|
||||
-- All statements use IF NOT EXISTS so the file is safe to re-run.
|
||||
|
||||
-- Mark orders that were created by an admin (not paid via checkout)
|
||||
ALTER TABLE `gsp_billing_orders`
|
||||
ADD COLUMN IF NOT EXISTS `created_by_admin` TINYINT(1) NOT NULL DEFAULT 0
|
||||
COMMENT 'Set to 1 when an admin manually created this server via the panel';
|
||||
|
||||
-- Track whether an order is a renewal/extension (already referenced by create_servers.php)
|
||||
ALTER TABLE `gsp_billing_orders`
|
||||
ADD COLUMN IF NOT EXISTS `extended` TINYINT(1) NOT NULL DEFAULT 0
|
||||
COMMENT 'Set to 1 when this order is a renewal of an existing server';
|
||||
163
modules/user_games/billing_integration.php
Normal file
163
modules/user_games/billing_integration.php
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
/*
|
||||
* billing_integration.php
|
||||
*
|
||||
* Shared helper for recording admin-created game servers in the billing tables,
|
||||
* so they are treated identically to FREE website orders:
|
||||
* billing_invoices (status='paid', amount=0)
|
||||
* billing_orders (status='installed', price=0, created_by_admin=1)
|
||||
*
|
||||
* This does NOT re-provision the server — the caller (add_home.php) already
|
||||
* created the server via the panel DB layer. We only write the billing ledger
|
||||
* entries so admins can track every server in one place and cron-shop.php can
|
||||
* manage renewals/suspensions uniformly.
|
||||
*
|
||||
* Usage (inside exec_ogp_module after $new_home_id is confirmed):
|
||||
* require_once 'billing_integration.php';
|
||||
* admin_register_server_in_billing($db, $user_id, $home_cfg_id,
|
||||
* $rserver_id, $home_name, $max_players, $access_rights, $ftp, $new_home_id);
|
||||
*/
|
||||
|
||||
if (!function_exists('admin_register_server_in_billing')) {
|
||||
|
||||
/**
|
||||
* Create billing_invoice + billing_order entries for an admin-provisioned
|
||||
* game server so it participates in the normal billing lifecycle.
|
||||
*
|
||||
* @param OGPDatabase $db Panel DB object (OGP_DB_PREFIX substitution works)
|
||||
* @param int $user_id Owner of the server
|
||||
* @param int $home_cfg_id config_homes primary key (game type)
|
||||
* @param int $rserver_id remote_server id (stored in billing as "ip")
|
||||
* @param string $home_name Human-readable server name
|
||||
* @param int $max_players Slot count
|
||||
* @param string $access_rights Access-rights flags string (e.g. "rgset")
|
||||
* @param bool $ftp Whether FTP was enabled for this server
|
||||
* @param int $home_id server_homes.home_id of the already-created server
|
||||
*
|
||||
* @return int|false New billing_orders.order_id on success, FALSE on error
|
||||
*/
|
||||
function admin_register_server_in_billing(
|
||||
$db,
|
||||
$user_id,
|
||||
$home_cfg_id,
|
||||
$rserver_id,
|
||||
$home_name,
|
||||
$max_players,
|
||||
$access_rights,
|
||||
$ftp,
|
||||
$home_id
|
||||
) {
|
||||
// ------------------------------------------------------------------ //
|
||||
// 1. Resolve service_id: find an existing billing_service matching //
|
||||
// this game type. Fall back to 0 (no catalogue entry) if none. //
|
||||
// ------------------------------------------------------------------ //
|
||||
$service_id = 0;
|
||||
$services = $db->resultQuery(
|
||||
"SELECT service_id FROM OGP_DB_PREFIXbilling_services
|
||||
WHERE home_cfg_id = " . intval($home_cfg_id) . "
|
||||
AND enabled = 1
|
||||
LIMIT 1"
|
||||
);
|
||||
if (!empty($services[0]['service_id'])) {
|
||||
$service_id = intval($services[0]['service_id']);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// 2. Resolve owner's name & email for the invoice record. //
|
||||
// ------------------------------------------------------------------ //
|
||||
$customer_name = '';
|
||||
$customer_email = '';
|
||||
$user_row = $db->getUserById(intval($user_id));
|
||||
if (!empty($user_row)) {
|
||||
$customer_name = trim(
|
||||
($user_row['users_fname'] ?? '') . ' ' . ($user_row['users_lname'] ?? '')
|
||||
);
|
||||
$customer_email = $user_row['users_email'] ?? '';
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$end_date = date('Y-m-d H:i:s', strtotime('+1 year'));
|
||||
$ftp_flag = $ftp ? 'enabled' : 'disabled';
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// 3. Insert billing_invoice (amount=0, already "paid"). //
|
||||
// ------------------------------------------------------------------ //
|
||||
$invoice_fields = array(
|
||||
'order_id' => 0,
|
||||
'user_id' => intval($user_id),
|
||||
'service_id' => $service_id,
|
||||
'home_name' => $home_name,
|
||||
'ip' => intval($rserver_id),
|
||||
'max_players' => intval($max_players),
|
||||
'remote_control_password' => '',
|
||||
'ftp_password' => '',
|
||||
'customer_name' => $customer_name,
|
||||
'customer_email' => $customer_email,
|
||||
'amount' => '0.00',
|
||||
'discount_amount' => '0.00',
|
||||
'currency' => 'USD',
|
||||
'status' => 'paid',
|
||||
'invoice_date' => $now,
|
||||
'due_date' => $now,
|
||||
'paid_date' => $now,
|
||||
'payment_txid' => 'admin-created',
|
||||
'payment_method' => 'admin',
|
||||
'description' => 'Admin-created server: ' . $home_name,
|
||||
'invoice_duration' => 'year',
|
||||
'qty' => 1,
|
||||
);
|
||||
|
||||
$invoice_id = $db->resultInsertId('billing_invoices', $invoice_fields);
|
||||
if ($invoice_id === FALSE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// 4. Insert billing_order (status='installed', already provisioned). //
|
||||
// ------------------------------------------------------------------ //
|
||||
$order_fields = array(
|
||||
'user_id' => intval($user_id),
|
||||
'service_id' => $service_id,
|
||||
'home_name' => $home_name,
|
||||
'ip' => intval($rserver_id),
|
||||
'qty' => 1,
|
||||
'invoice_duration' => 'year',
|
||||
'max_players' => intval($max_players),
|
||||
'price' => '0.00',
|
||||
'discount_amount' => '0.00',
|
||||
'remote_control_password' => '',
|
||||
'ftp_password' => '',
|
||||
'home_id' => intval($home_id),
|
||||
'status' => 'installed',
|
||||
'order_date' => $now,
|
||||
'end_date' => $end_date,
|
||||
'payment_txid' => 'admin-created',
|
||||
'paid_ts' => $now,
|
||||
'coupon_id' => 0,
|
||||
'extended' => 0,
|
||||
);
|
||||
|
||||
$order_id = $db->resultInsertId('billing_orders', $order_fields);
|
||||
if ($order_id === FALSE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Optionally mark as admin-created (column added by admin_billing_migration.sql)
|
||||
$db->query(
|
||||
"UPDATE OGP_DB_PREFIXbilling_orders
|
||||
SET created_by_admin = 1
|
||||
WHERE order_id = " . intval($order_id)
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// 5. Link the invoice back to the new order. //
|
||||
// ------------------------------------------------------------------ //
|
||||
$db->query(
|
||||
"UPDATE OGP_DB_PREFIXbilling_invoices
|
||||
SET order_id = " . intval($order_id) . "
|
||||
WHERE invoice_id = " . intval($invoice_id)
|
||||
);
|
||||
|
||||
return $order_id;
|
||||
}
|
||||
}
|
||||
287
modules/user_games/migrate_home.php
Normal file
287
modules/user_games/migrate_home.php
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
<?php
|
||||
/*
|
||||
*
|
||||
* OGP - Open Game Panel
|
||||
* Copyright (C) 2008 - 2018 The OGP Development Team
|
||||
* GSP / WDS customisation — Migrate replaces the old Clone feature.
|
||||
*
|
||||
* This page lets an admin copy all files from one game server to another
|
||||
* server of the SAME game type, using rsync (Linux) or robocopy (Windows
|
||||
* fallback). The source server is NOT deleted or changed.
|
||||
*
|
||||
*/
|
||||
|
||||
function exec_ogp_module()
|
||||
{
|
||||
global $db, $settings;
|
||||
|
||||
$home_id = intval($_REQUEST['home_id'] ?? 0);
|
||||
if ($home_id <= 0) {
|
||||
print_failure(get_lang('invalid_home_id'));
|
||||
return;
|
||||
}
|
||||
|
||||
$source = $db->getGameHomeWithoutMods($home_id);
|
||||
if (empty($source)) {
|
||||
print_failure(get_lang('invalid_home_id'));
|
||||
return;
|
||||
}
|
||||
|
||||
echo "<h2>" . htmlentities(get_lang_f('migrate_server', $source['home_name'])) . "</h2>";
|
||||
echo create_back_button('user_games');
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Handle the migration POST //
|
||||
// ------------------------------------------------------------------ //
|
||||
if (isset($_POST['do_migrate'])) {
|
||||
$dest_id = intval($_POST['dest_home_id'] ?? 0);
|
||||
if ($dest_id <= 0 || $dest_id === $home_id) {
|
||||
print_failure(get_lang('invalid_home_id'));
|
||||
return;
|
||||
}
|
||||
|
||||
$dest = $db->getGameHomeWithoutMods($dest_id);
|
||||
if (empty($dest)) {
|
||||
print_failure(get_lang('invalid_home_id'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate same game type
|
||||
if ($source['home_cfg_id'] != $dest['home_cfg_id']) {
|
||||
print_failure(get_lang('migrate_different_game_type'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Require explicit confirmation tick
|
||||
if (empty($_POST['confirm_overwrite'])) {
|
||||
print_failure(get_lang('migrate_confirm_required'));
|
||||
return;
|
||||
}
|
||||
|
||||
$result = migrate_game_server($db, $source, $dest, $settings);
|
||||
|
||||
if ($result === TRUE || $result === -1) {
|
||||
// -1 = async (copy running in background), TRUE = instant success
|
||||
if ($result === -1) {
|
||||
print_success(get_lang('migrate_running_background'));
|
||||
} else {
|
||||
print_success(get_lang('migrate_complete'));
|
||||
}
|
||||
echo "<p><a href='?m=user_games'><< " . get_lang('back_to_game_servers') . "</a></p>";
|
||||
} else {
|
||||
print_failure(get_lang_f('migrate_failed_code', (int)$result));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Build the list of eligible destination servers (same game type, //
|
||||
// exclude source, admin-visible only). //
|
||||
// ------------------------------------------------------------------ //
|
||||
$all_homes = $db->getGameHomes_limit(1, 9999, false, false);
|
||||
$candidates = array();
|
||||
if (!empty($all_homes)) {
|
||||
foreach ((array)$all_homes as $h) {
|
||||
if (intval($h['home_id']) === $home_id) continue;
|
||||
if (intval($h['home_cfg_id']) !== intval($source['home_cfg_id'])) continue;
|
||||
$candidates[] = $h;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($candidates)) {
|
||||
print_failure(get_lang('migrate_no_compatible_destinations'));
|
||||
echo "<p><a href='?m=user_games'><< " . get_lang('back_to_game_servers') . "</a></p>";
|
||||
return;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Show migration form //
|
||||
// ------------------------------------------------------------------ //
|
||||
echo "<p class='note'>" . get_lang('migrate_info') . "</p>";
|
||||
echo "<ul>
|
||||
<li>" . get_lang('migrate_bullet_no_delete') . "</li>
|
||||
<li>" . get_lang('migrate_bullet_same_game') . "</li>
|
||||
<li>" . get_lang('migrate_bullet_overwrite') . "</li>
|
||||
<li>" . get_lang('migrate_bullet_no_billing') . "</li>
|
||||
</ul>";
|
||||
|
||||
echo "<form method='post' action='?m=user_games&p=migrate&home_id=" . $home_id . "'>";
|
||||
echo "<table class='center'>";
|
||||
|
||||
// Source info
|
||||
echo "<tr><td class='right'><strong>" . get_lang('migrate_source') . ":</strong></td>
|
||||
<td class='left'>" . htmlentities($source['home_name']) .
|
||||
" (<em>" . htmlentities($source['agent_ip']) . "</em>)" .
|
||||
" [" . htmlentities($source['home_path']) . "]</td></tr>";
|
||||
|
||||
// Destination dropdown
|
||||
echo "<tr><td class='right'>" . get_lang('migrate_destination') . ":</td>
|
||||
<td class='left'><select name='dest_home_id'>";
|
||||
foreach ((array)$candidates as $c) {
|
||||
echo "<option value='" . intval($c['home_id']) . "'>"
|
||||
. htmlentities($c['home_name'])
|
||||
. " — " . htmlentities($c['agent_ip'])
|
||||
. " [" . htmlentities($c['home_path']) . "]"
|
||||
. "</option>";
|
||||
}
|
||||
echo "</select></td></tr>";
|
||||
|
||||
// Confirmation checkbox
|
||||
echo "<tr><td class='right'>" . get_lang('migrate_confirm_overwrite') . ":</td>
|
||||
<td class='left'>
|
||||
<input type='checkbox' name='confirm_overwrite' value='1' />
|
||||
<span class='info'>" . get_lang('migrate_confirm_overwrite_info') . "</span>
|
||||
</td></tr>";
|
||||
|
||||
echo "<tr><td colspan='2' align='center'>
|
||||
<input type='submit' name='do_migrate' value='" . get_lang('migrate_start') . "' />
|
||||
</td></tr>";
|
||||
|
||||
echo "</table></form>";
|
||||
|
||||
// Show source ports/mods for reference
|
||||
$assigned = $db->getHomeIpPorts($home_id);
|
||||
if (!empty($assigned)) {
|
||||
echo "<h3>" . get_lang('ips_and_ports_used_in_this_home') . "</h3>";
|
||||
echo "<p class='info'>" . get_lang('note_ips_and_ports_are_not_cloned') . "</p>";
|
||||
foreach ((array)$assigned as $r) {
|
||||
echo "<p>" . $r['ip'] . ":" . $r['port'] . "</p>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Core migration function //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* Copy all files from $source game server to $dest using rsync (Linux) or
|
||||
* robocopy (Windows fallback) via the remote agent's exec() call.
|
||||
*
|
||||
* Both servers must live on the SAME remote agent. Cross-node migration is
|
||||
* noted below as a limitation.
|
||||
*
|
||||
* @param OGPDatabase $db Panel DB
|
||||
* @param array $source Row from getGameHomeWithoutMods() for source
|
||||
* @param array $dest Row from getGameHomeWithoutMods() for dest
|
||||
* @param array $settings Panel settings array
|
||||
*
|
||||
* @return true|int TRUE or -1 on async success, 0 on failure, other int on error
|
||||
*/
|
||||
function migrate_game_server($db, $source, $dest, $settings)
|
||||
{
|
||||
require_once('includes/lib_remote.php');
|
||||
|
||||
$src_path = rtrim($source['home_path'], '/\\');
|
||||
$dst_path = rtrim($dest['home_path'], '/\\');
|
||||
|
||||
// Validate paths
|
||||
if (empty($src_path) || empty($dst_path)) {
|
||||
return 0;
|
||||
}
|
||||
if ($src_path === $dst_path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Cross-node migration guard //
|
||||
// rsync between two *different* agents would require SSH access //
|
||||
// between them which is not guaranteed. For now we only support //
|
||||
// same-agent migration; the UI should already have filtered this, but //
|
||||
// we double-check here. //
|
||||
// ------------------------------------------------------------------ //
|
||||
$same_node = ($source['remote_server_id'] == $dest['remote_server_id']);
|
||||
|
||||
// Build remote connection to the source agent (used for same-node ops)
|
||||
$remote_src = new OGPRemoteLibrary(
|
||||
$source['agent_ip'],
|
||||
$source['agent_port'],
|
||||
$source['encryption_key'],
|
||||
$source['timeout']
|
||||
);
|
||||
|
||||
if (!$same_node) {
|
||||
// Cross-node: attempt rsync pull from the DESTINATION agent using
|
||||
// SSH to pull files from the source agent. This requires that the
|
||||
// destination agent's OS user can reach the source via SSH without
|
||||
// a passphrase. We attempt it but return 0 on obvious failure.
|
||||
$remote_dst = new OGPRemoteLibrary(
|
||||
$dest['agent_ip'],
|
||||
$dest['agent_port'],
|
||||
$dest['encryption_key'],
|
||||
$dest['timeout']
|
||||
);
|
||||
|
||||
// Detect destination OS
|
||||
$dst_os = $remote_dst->what_os();
|
||||
if (stripos($dst_os, 'win') !== false) {
|
||||
// Windows cross-node not supported via this UI
|
||||
return 0;
|
||||
}
|
||||
|
||||
$src_user = $source['ogp_user'] ?? 'gameserver';
|
||||
$rsync_pull = sprintf(
|
||||
'rsync -avz --delete -e "ssh -o StrictHostKeyChecking=no" %s@%s:%s/ %s/',
|
||||
escapeshellarg($src_user),
|
||||
escapeshellarg($source['agent_ip']),
|
||||
escapeshellarg($src_path),
|
||||
escapeshellarg($dst_path)
|
||||
);
|
||||
|
||||
$out = $remote_dst->exec($rsync_pull);
|
||||
if ($out === NULL) {
|
||||
return -1; // running async
|
||||
}
|
||||
// Fix ownership on destination
|
||||
$dst_user = $dest['ogp_user'] ?? 'gameserver';
|
||||
$chown_cmd = sprintf(
|
||||
'chown -R %s:%s %s/',
|
||||
escapeshellarg($dst_user),
|
||||
escapeshellarg($dst_user),
|
||||
escapeshellarg($dst_path)
|
||||
);
|
||||
$remote_dst->exec($chown_cmd);
|
||||
$db->logger("Migrated (cross-node) home {$source['home_id']} -> {$dest['home_id']}");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Same-node migration via clone_home() RPC (rsync under the hood) //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
// Detect OS so we can choose the right tool
|
||||
$os = $remote_src->what_os();
|
||||
|
||||
if (stripos($os, 'win') !== false) {
|
||||
// Windows: try rsync (Cygwin/MSYS) first, fall back to robocopy
|
||||
$rsync_cmd = sprintf('rsync -avz --delete %s/ %s/',
|
||||
escapeshellarg($src_path), escapeshellarg($dst_path));
|
||||
$robocopy_cmd = sprintf('robocopy %s %s /MIR /R:1 /W:1',
|
||||
escapeshellarg($src_path), escapeshellarg($dst_path));
|
||||
|
||||
$out = $remote_src->exec($rsync_cmd);
|
||||
if ($out === NULL) {
|
||||
// rsync not available — fall back to robocopy
|
||||
$remote_src->exec($robocopy_cmd);
|
||||
}
|
||||
|
||||
$db->logger("Migrated (Windows same-node) home {$source['home_id']} -> {$dest['home_id']}");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Linux — prefer the agent's built-in clone_home (rsync -a) because it
|
||||
// runs in the background and returns -1 (async) with progress support.
|
||||
// We need to pass the owner for chown; fall back to source ogp_user.
|
||||
$owner = $dest['ogp_user'] ?? $source['ogp_user'] ?? 'gameserver';
|
||||
$rc = $remote_src->clone_home($src_path, $dst_path, $owner);
|
||||
|
||||
if ($rc === 1 || $rc === -1) {
|
||||
// Also fix ownership explicitly (clone_home may already do this)
|
||||
$chown_cmd = sprintf('chown -R %s:%s %s/',
|
||||
escapeshellarg($owner), escapeshellarg($owner), escapeshellarg($dst_path));
|
||||
$remote_src->exec($chown_cmd);
|
||||
$db->logger("Migrated home {$source['home_id']} -> {$dest['home_id']}");
|
||||
}
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<page key="mods" file="home_mods.php" access="user,admin" />
|
||||
<page key="edit" file="edit_home.php" access="admin,user,subuser" />
|
||||
<page key="del" file="del_home.php" access="admin" />
|
||||
<page key="clone" file="clone_home.php" access="admin" />
|
||||
<page key="migrate" file="migrate_home.php" access="admin" />
|
||||
<page key="default" file="show_homes.php" access="admin" />
|
||||
<page key="install_cmds" file="install_cmds.php" access="admin" />
|
||||
<page key="get_size" file="get_size.php" access="admin,user,subuser" />
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ function exec_ogp_module()
|
|||
echo "</td><td>".$expiration_date."</td><td>
|
||||
<a href='?m=user_games&p=del&home_id=$row[home_id]'>[".get_lang('delete')."]</a>
|
||||
<a href='?m=user_games&p=edit&home_id=$row[home_id]'>[".get_lang('edit')."]</a>
|
||||
<a href='?m=user_games&p=clone&home_id=$row[home_id]'>[".get_lang('clone')."]</a>
|
||||
<a href='?m=user_games&p=migrate&home_id=$row[home_id]'>[".get_lang('migrate')."]</a>
|
||||
</td></tr>";
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue