summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-03-28 17:56:25 +0100
committerGitHub <noreply@github.com>2019-03-28 17:56:25 +0100
commit026dd75208223a8ceb8f3e82699a123d68b9a1c7 (patch)
treea76b5ae9bf59cf3fc2526d5677a99eb6cd2fd6ca /lib
parent24d5b6f9e39d2ac62a9657c7d19bc8c437b0735b (diff)
Add `tootctl self-destruct` (#10367)
Fix #10305
Diffstat (limited to 'lib')
-rw-r--r--lib/cli.rb73
1 files changed, 73 insertions, 0 deletions
diff --git a/lib/cli.rb b/lib/cli.rb
index 65a5ae69623..b56c6e76f5a 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -41,6 +41,79 @@ module Mastodon
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
subcommand 'domains', Mastodon::DomainsCLI
+ option :dry_run, type: :boolean
+ desc 'self-destruct', 'Erase the server from the federation'
+ long_desc <<~LONG_DESC
+ Erase the server from the federation by broadcasting account delete
+ activities to all known other servers. This allows a "clean exit" from
+ running a Mastodon server, as it leaves next to no cache behind on
+ other servers.
+
+ This command is always interactive and requires confirmation twice.
+
+ No local data is actually deleted, because emptying the
+ database or removing files is much faster through other, external
+ means, such as e.g. deleting the entire VPS. However, because other
+ servers will delete data about local users, but no local data will be
+ updated (such as e.g. followers), there will be a state mismatch
+ that will lead to glitches and issues if you then continue to run and use
+ the server.
+
+ So either you know exactly what you are doing, or you are starting
+ from a blank slate afterwards by manually clearing out all the local
+ data!
+ LONG_DESC
+ def self_destruct
+ require 'tty-prompt'
+
+ prompt = TTY::Prompt.new
+
+ exit(1) unless prompt.ask('Type in the domain of the server to confirm:', required: true) == Rails.configuration.x.local_domain
+
+ prompt.warn('This operation WILL NOT be reversible. It can also take a long time.')
+ prompt.warn('While the data won\'t be erased locally, the server will be in a BROKEN STATE afterwards.')
+ prompt.warn('A running Sidekiq process is required. Do not shut it down until queues clear.')
+
+ exit(1) if prompt.no?('Are you sure you want to proceed?')
+
+ inboxes = Account.inboxes
+ processed = 0
+ dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
+
+ if inboxes.empty?
+ prompt.ok('It seems like your server has not federated with anything')
+ prompt.ok('You can shut it down and delete it any time')
+ return
+ end
+
+ prompt.warn('Do NOT interrupt this process...')
+
+ Account.local.without_suspended.find_each do |account|
+ payload = ActiveModelSerializers::SerializableResource.new(
+ account,
+ serializer: ActivityPub::DeleteActorSerializer,
+ adapter: ActivityPub::Adapter
+ ).as_json
+
+ json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(account))
+
+ unless options[:dry_run]
+ ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
+ [json, account.id, inbox_url]
+ end
+
+ account.update_column(:suspended, true)
+ end
+
+ processed += 1
+ end
+
+ prompt.ok("Queued #{inboxes.size * processed} items into Sidekiq for #{processed} accounts#{dry_run}")
+ prompt.ok('Wait until Sidekiq processes all items, then you can shut everything down and delete the data')
+ rescue TTY::Reader::InputInterrupt
+ exit(1)
+ end
+
map %w(--version -v) => :version
desc 'version', 'Show version'