Skip to content
Browse files

Add `tootctl self-destruct` (#10367)

Fix #10305
  • Loading branch information...
Gargron committed Mar 28, 2019
1 parent 24d5b6f commit 026dd75208223a8ceb8f3e82699a123d68b9a1c7
Showing with 74 additions and 1 deletion.
  1. +1 −1 .rubocop.yml
  2. +73 −0 lib/cli.rb
@@ -80,7 +80,7 @@ Rails/HttpStatus:
- 'lib/mastodon/*'
- 'lib/cli'
- 'lib/cli.rb'

Enabled: false
@@ -41,6 +41,79 @@ def self.exit_on_failure?
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
def self_destruct
require 'tty-prompt'

prompt =

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'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')

prompt.warn('Do NOT interrupt this process...')

Account.local.without_suspended.find_each do |account|
payload =
serializer: ActivityPub::DeleteActorSerializer,
adapter: ActivityPub::Adapter

json = Oj.dump(!(account))

unless options[:dry_run]
ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
[json,, inbox_url]

account.update_column(:suspended, true)

processed += 1

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

map %w(--version -v) => :version

desc 'version', 'Show version'

0 comments on commit 026dd75

Please sign in to comment.
You can’t perform that action at this time.