Merge pull request #11519 from MrPetovan/task/11511-console-domain-move

Add relocate console command and fix the missing DB tables for the relocation
This commit is contained in:
Michael Vogel 2022-05-29 20:42:32 +02:00 committed by GitHub
commit 47a3d8e6ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 661 additions and 521 deletions

205
src/Console/Relocate.php Normal file
View file

@ -0,0 +1,205 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Console;
use Asika\SimpleConsole\Console;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Worker;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
class Relocate extends Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var IManageConfigValues
*/
private $config;
/**
* @var \Friendica\App\BaseURL
*/
private $baseUrl;
/**
* @var \Friendica\Database\Database
*/
private $database;
protected function getHelp()
{
$help = <<<HELP
console relocate - Update the node base URL
Usage
bin/console relocate <new base URL> [-h|--help|-?] [-v]
Description
Warning! Advanced function. Could make this server unreachable.
Change the base URL for this server. Sends relocation message to all the Friendica and Diaspora* contacts of all local users.
This process updates all the database fields that may contain a URL pointing at the current domain, as a result it takes
a while and the node will be in maintenance mode for the whole duration.
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
public function __construct(\Friendica\App\BaseURL $baseUrl, \Friendica\Database\Database $database, IManageConfigValues $config, $argv = null)
{
parent::__construct($argv);
$this->baseUrl = $baseUrl;
$this->database = $database;
$this->config = $config;
}
protected function doExecute()
{
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
if (count($this->args) > 1) {
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
$new_url = rtrim($this->getArgument(0), '/');
$parsed = @parse_url($new_url);
if (!is_array($parsed) || empty($parsed['host']) || empty($parsed['scheme'])) {
throw new \InvalidArgumentException('Can not parse new base URL. Must have at least <scheme>://<domain>');
}
$this->out(sprintf('Relocation started from %s to %s. Could take a while to complete.', $this->baseUrl->get(true), $this->getArgument(0)));
$old_url = $this->baseUrl->get(true);
// Generate host names for relocation the addresses in the format user@address.tld
$new_host = str_replace('http://', '@', Strings::normaliseLink($new_url));
$old_host = str_replace('http://', '@', Strings::normaliseLink($old_url));
$this->out('Entering maintenance mode');
$this->config->set('system', 'maintenance', true);
$this->config->set('system', 'maintenance_reason', 'Relocating node to ' . $new_url);
try {
if (!$this->database->transaction()) {
throw new \Exception('Unable to start a transaction, please retry later.');
}
// update tables
$this->out('Updating apcontact table fields');
$this->database->replaceInTableFields('apcontact', ['url', 'inbox', 'outbox', 'sharedinbox', 'photo', 'header', 'alias', 'subscribe', 'baseurl'], $old_url, $new_url);
$this->database->replaceInTableFields('apcontact', ['addr'], $old_host, $new_host);
$this->out('Updating contact table fields');
$this->database->replaceInTableFields('contact', ['photo', 'thumb', 'micro', 'url', 'alias', 'request', 'batch', 'notify', 'poll', 'subscribe', 'baseurl', 'confirm', 'poco', 'avatar', 'header'], $old_url, $new_url);
$this->database->replaceInTableFields('contact', ['nurl'], Strings::normaliseLink($old_url), Strings::normaliseLink($new_url));
$this->database->replaceInTableFields('contact', ['addr'], $old_host, $new_host);
$this->out('Updating conv table fields');
$this->database->replaceInTableFields('conv', ['creator', 'recips'], $old_host, $new_host);
$this->out('Updating delayed-post table fields');
$this->database->replaceInTableFields('delayed-post', ['uri'], $old_url, $new_url);
$this->out('Updating endpoint table fields');
$this->database->replaceInTableFields('endpoint', ['url'], $old_url, $new_url);
$this->out('Updating event table fields');
$this->database->replaceInTableFields('event', ['uri'], $old_url, $new_url);
$this->out('Updating fcontact table fields');
$this->database->replaceInTableFields('fcontact', ['url', 'photo', 'request', 'batch', 'poll', 'confirm', 'alias'], $old_url, $new_url);
$this->database->replaceInTableFields('fcontact', ['addr'], $old_host, $new_host);
$this->out('Updating fsuggest table fields');
$this->database->replaceInTableFields('fsuggest', ['url', 'request', 'photo'], $old_url, $new_url);
$this->out('Updating gserver table fields');
$this->database->replaceInTableFields('gserver', ['url'], $old_url, $new_url);
$this->database->replaceInTableFields('gserver', ['nurl'], Strings::normaliseLink($old_url), Strings::normaliseLink($new_url));
$this->out('Updating inbox-status table fields');
$this->database->replaceInTableFields('inbox-status', ['url'], $old_url, $new_url);
$this->out('Updating item-uri table fields');
$this->database->replaceInTableFields('item-uri', ['uri'], $old_url, $new_url);
$this->out('Updating mail table fields');
$this->database->replaceInTableFields('mail', ['from-photo', 'from-url', 'uri', 'thr-parent'], $old_url, $new_url);
$this->database->replaceInTableFields('mail', ['parent-uri'], $old_host, $new_host);
$this->out('Updating notify table fields');
$this->database->replaceInTableFields('notify', ['url', 'photo', 'link', 'msg', 'name_cache', 'msg_cache'], $old_url, $new_url);
$this->out('Updating profile table fields');
$this->database->replaceInTableFields('profile', ['photo', 'thumb'], $old_url, $new_url);
$this->out('Updating post-content table fields');
$this->database->replaceInTableFields('post-content', ['body', 'raw-body', 'rendered-html', 'target', 'plink'], $old_url, $new_url);
$this->database->replaceInTableFields('post-content', ['body', 'raw-body', 'rendered-html', 'target'], $old_host, $new_host);
$this->out('Updating post-history table fields');
$this->database->replaceInTableFields('post-history', ['body', 'raw-body', 'rendered-html', 'target', 'plink'], $old_url, $new_url);
$this->database->replaceInTableFields('post-history', ['body', 'raw-body', 'rendered-html', 'target'], $old_host, $new_host);
$this->out('Updating post-link table fields');
$this->database->replaceInTableFields('post-link', ['url'], $old_url, $new_url);
$this->out('Updating post-media table fields');
$this->database->replaceInTableFields('post-media', ['url', 'preview', 'author-url', 'author-image', 'publisher-url', 'publisher-image'], $old_url, $new_url);
$this->out('Updating tag table fields');
$this->database->replaceInTableFields('tag', ['url'], $old_url, $new_url);
// update config
$this->out('Updating config values');
$this->config->set('system', 'url', $new_url);
$this->baseUrl->saveByURL($new_url);
$this->database->commit();
} catch (\Throwable $e) {
$this->database->rollback();
$this->out('Process aborted with message: ' . $e->getMessage() . ' thrown in ' . $e->getFile() . ':' . $e->getLine());
return 1;
} finally {
$this->out('Leaving maintenance mode');
$this->config->set('system', 'maintenance', false);
$this->config->set('system', 'maintenance_reason', '');
}
// send relocate
$this->out('Schedule relocation messages to remote Friendica and Diaspora hosts');
$users = $this->database->selectToArray('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
foreach ($users as $user) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
return 0;
}
}

View file

@ -65,6 +65,7 @@ Commands:
po2php Generate a strings.php file from a messages.po file
typo Checks for parse errors in Friendica files
postupdate Execute pending post update scripts (can last days)
relocate Update node base URL
serverblock Manage blocked servers
storage Manage storage backend
relay Manage ActivityPub relay servers
@ -97,6 +98,7 @@ HELP;
'postupdate' => Friendica\Console\PostUpdate::class,
'po2php' => Friendica\Console\PoToPhp::class,
'relay' => Friendica\Console\Relay::class,
'relocate' => Friendica\Console\Relocate::class,
'serverblock' => Friendica\Console\ServerBlock::class,
'storage' => Friendica\Console\Storage::class,
'test' => Friendica\Console\Test::class,

View file

@ -1153,7 +1153,7 @@ class Database
*
* @return boolean Was the command executed successfully?
*/
public function transaction()
public function transaction(): bool
{
if (!$this->performCommit()) {
return false;
@ -1790,4 +1790,32 @@ class Database
{
array_walk($arr, [$this, 'escapeArrayCallback'], $add_quotation);
}
/**
* Replaces a string in the provided fields of the provided table
*
* @param string $table_name
* @param array $fields List of field names in the provided table
* @param string $search
* @param string $replace
* @throws \Exception
*/
public function replaceInTableFields(string $table_name, array $fields, string $search, string $replace)
{
$search = $this->escape($search);
$replace = $this->escape($replace);
$upd = [];
foreach ($fields as $field) {
$field = DBA::quoteIdentifier($field);
$upd[] = "$field = REPLACE($field, '$search', '$replace')";
}
$upds = implode(', ', $upd);
$r = $this->e(sprintf("UPDATE %s SET %s;", $table_name, $upds));
if (!$this->isResult($r)) {
throw new \RuntimeException("Failed updating `$table_name`: " . $this->errorMessage());
}
}
}

View file

@ -22,6 +22,7 @@
namespace Friendica\Module\Admin;
use Friendica\App;
use Friendica\Core\Relocate;
use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\Core\System;
@ -60,74 +61,6 @@ class Site extends BaseAdmin
return;
}
// relocate
// @TODO This file could benefit from moving this feature away in a Module\Admin\Relocate class for example
if (!empty($_POST['relocate']) && !empty($_POST['relocate_url']) && $_POST['relocate_url'] != "") {
$new_url = $_POST['relocate_url'];
$new_url = rtrim($new_url, "/");
$parsed = @parse_url($new_url);
if (!is_array($parsed) || empty($parsed['host']) || empty($parsed['scheme'])) {
notice(DI::l10n()->t("Can not parse base url. Must have at least <scheme>://<domain>"));
DI::baseUrl()->redirect('admin/site');
}
/* steps:
* replace all "baseurl" to "new_url" in config, profile, term, items and contacts
* send relocate for every local user
* */
$old_url = DI::baseUrl()->get(true);
// Generate host names for relocation the addresses in the format user@address.tld
$new_host = str_replace("http://", "@", Strings::normaliseLink($new_url));
$old_host = str_replace("http://", "@", Strings::normaliseLink($old_url));
function update_table(App $a, $table_name, $fields, $old_url, $new_url)
{
$dbold = DBA::escape($old_url);
$dbnew = DBA::escape($new_url);
$upd = [];
foreach ($fields as $f) {
$upd[] = "`$f` = REPLACE(`$f`, '$dbold', '$dbnew')";
}
$upds = implode(", ", $upd);
$r = DBA::e(sprintf("UPDATE %s SET %s;", $table_name, $upds));
if (!DBA::isResult($r)) {
notice("Failed updating '$table_name': " . DBA::errorMessage());
DI::baseUrl()->redirect('admin/site');
}
}
// update tables
// update profile links in the format "http://server.tld"
update_table($a, "profile", ['photo', 'thumb'], $old_url, $new_url);
update_table($a, "contact", ['photo', 'thumb', 'micro', 'url', 'nurl', 'alias', 'request', 'notify', 'poll', 'confirm', 'poco', 'avatar'], $old_url, $new_url);
update_table($a, "post-content", ['body'], $old_url, $new_url);
// update profile addresses in the format "user@server.tld"
update_table($a, "contact", ['addr'], $old_host, $new_host);
// update config
DI::config()->set('system', 'url', $new_url);
DI::baseUrl()->saveByURL($new_url);
// send relocate
$usersStmt = DBA::select('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
while ($user = DBA::fetch($usersStmt)) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
DBA::close($usersStmt);
info(DI::l10n()->t("Relocation started. Could take a while to complete."));
DI::baseUrl()->redirect('admin/site');
}
// end relocate
$sitename = (!empty($_POST['sitename']) ? trim($_POST['sitename']) : '');
$sender_email = (!empty($_POST['sender_email']) ? trim($_POST['sender_email']) : '');
$banner = (!empty($_POST['banner']) ? trim($_POST['banner']) : false);
@ -512,8 +445,9 @@ class Site extends BaseAdmin
'$no_relay_list' => DI::l10n()->t('The system is not subscribed to any relays at the moment.'),
'$relay_list_title' => DI::l10n()->t('The system is currently subscribed to the following relays:'),
'$relay_list' => Relay::getList(['url']),
'$relocate' => DI::l10n()->t('Relocate Instance'),
'$relocate_warning' => DI::l10n()->t('<strong>Warning!</strong> Advanced function. Could make this server unreachable.'),
'$relocate' => DI::l10n()->t('Relocate Node'),
'$relocate_msg' => DI::l10n()->t('Relocating your node enables you to change the DNS domain of this node and keep all the existing users and posts. This process takes a while and can only be started from the relocate console command like this:'),
'$relocate_cmd' => DI::l10n()->t('(Friendica directory)# bin/console relocate https://newdomain.com'),
'$baseurl' => DI::baseUrl()->get(true),
// name, label, value, help string, extra data...
@ -601,8 +535,6 @@ class Site extends BaseAdmin
'$temppath' => ['temppath', DI::l10n()->t('Temp path'), DI::config()->get('system', 'temppath'), DI::l10n()->t('If you have a restricted system where the webserver can\'t access the system temp path, enter another path here.')],
'$only_tag_search' => ['only_tag_search', DI::l10n()->t('Only search in tags'), DI::config()->get('system', 'only_tag_search'), DI::l10n()->t('On large systems the text search can slow down the system extremely.')],
'$relocate_url' => ['relocate_url', DI::l10n()->t('New base url'), DI::baseUrl()->get(), DI::l10n()->t('Change base url for this server. Sends relocate message to all Friendica and Diaspora* contacts of all users.')],
'$worker_queues' => ['worker_queues', DI::l10n()->t('Maximum number of parallel workers'), DI::config()->get('system', 'worker_queues'), DI::l10n()->t('On shared hosters set this to %d. On larger systems, values of %d are great. Default value is %d.', 5, 20, 10)],
'$worker_fastlane' => ['worker_fastlane', DI::l10n()->t('Enable fastlane'), DI::config()->get('system', 'worker_fastlane'), DI::l10n()->t('When enabed, the fastlane mechanism starts an additional worker if processes with higher priority are blocked by processes of lower priority.')],

View file

@ -70,7 +70,7 @@ class StaticDatabase extends Database
*
* @return bool
*/
public function transaction()
public function transaction(): bool
{
if (!$this->in_transaction && !$this->connection->beginTransaction()) {
return false;

File diff suppressed because it is too large Load diff

View file

@ -148,14 +148,10 @@
</form>
{{* separate form for relocate... *}}
<form action="{{$baseurl}}/admin/site" method="post">
<input type='hidden' name='form_security_token' value='{{$form_security_token}}'>
<div>
<h2>{{$relocate}}</h2>
<p>{{$relocate_warning nofilter}}</p>
{{include file="field_input.tpl" field=$relocate_url}}
<input type="hidden" name="page_site" value="{{$submit}}">
<div class="submit"><input type="submit" name="relocate" value="{{$relocate_button}}"/></div>
</form>
<p>{{$relocate_msg}}</p>
<p><code>{{$relocate_cmd}}</code></p>
</div>
</div>

View file

@ -321,14 +321,7 @@
</div>
</form>
<!--
/*
* Relocate
*/ -->
<form id="relocate-form" class="panel" action="{{$baseurl}}/admin/site" method="post">
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
<input type="hidden" name="page_site" value="{{$submit}}">
<input type="hidden" name="active_panel" value="admin-settings-relocate-collapse">
<div class="panel">
<div class="section-subtitle-wrapper panel-heading" role="tab" id="admin-settings-relocate">
<h2>
<button class="btn-link accordion-toggle collapsed" data-toggle="collapse" data-parent="#admin-settings" href="#admin-settings-relocate-collapse" aria-expanded="false" aria-controls="admin-settings-relocate-collapse">
@ -338,15 +331,12 @@
</div>
<div id="admin-settings-relocate-collapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="admin-settings-relocate">
<div class="panel-body">
<div class="alert alert-danger alert-dismissible">
{{$relocate_warning nofilter}}
</div>
{{include file="field_input.tpl" field=$relocate_url}}
</div>
<div class="panel-footer">
<input type="submit" name="relocate" class="btn btn-primary" value="{{$relocate_button}}"/>
<p>
{{$relocate_msg}}
</p>
<p><code>{{$relocate_cmd}}</code></p>
</div>
</div>
</form>
</div>
</div>
</div>