Browse Source

Add `tootctl self-destruct` (#10367)

Fix #10305
Eugen Rochko 5 months ago
parent
commit
026dd75208
No account linked to committer's email address
2 changed files with 74 additions and 1 deletions
  1. 1
    1
      .rubocop.yml
  2. 73
    0
      lib/cli.rb

+ 1
- 1
.rubocop.yml View File

@@ -80,7 +80,7 @@ Rails/HttpStatus:
80 80
 Rails/Exit:
81 81
   Exclude:
82 82
     - 'lib/mastodon/*'
83
-    - 'lib/cli'
83
+    - 'lib/cli.rb'
84 84
 
85 85
 Style/ClassAndModuleChildren:
86 86
   Enabled: false

+ 73
- 0
lib/cli.rb View File

@@ -41,6 +41,79 @@ module Mastodon
41 41
     desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
42 42
     subcommand 'domains', Mastodon::DomainsCLI
43 43
 
44
+    option :dry_run, type: :boolean
45
+    desc 'self-destruct', 'Erase the server from the federation'
46
+    long_desc <<~LONG_DESC
47
+      Erase the server from the federation by broadcasting account delete
48
+      activities to all known other servers. This allows a "clean exit" from
49
+      running a Mastodon server, as it leaves next to no cache behind on
50
+      other servers.
51
+
52
+      This command is always interactive and requires confirmation twice.
53
+
54
+      No local data is actually deleted, because emptying the
55
+      database or removing files is much faster through other, external
56
+      means, such as e.g. deleting the entire VPS. However, because other
57
+      servers will delete data about local users, but no local data will be
58
+      updated (such as e.g. followers), there will be a state mismatch
59
+      that will lead to glitches and issues if you then continue to run and use
60
+      the server.
61
+
62
+      So either you know exactly what you are doing, or you are starting
63
+      from a blank slate afterwards by manually clearing out all the local
64
+      data!
65
+    LONG_DESC
66
+    def self_destruct
67
+      require 'tty-prompt'
68
+
69
+      prompt = TTY::Prompt.new
70
+
71
+      exit(1) unless prompt.ask('Type in the domain of the server to confirm:', required: true) == Rails.configuration.x.local_domain
72
+
73
+      prompt.warn('This operation WILL NOT be reversible. It can also take a long time.')
74
+      prompt.warn('While the data won\'t be erased locally, the server will be in a BROKEN STATE afterwards.')
75
+      prompt.warn('A running Sidekiq process is required. Do not shut it down until queues clear.')
76
+
77
+      exit(1) if prompt.no?('Are you sure you want to proceed?')
78
+
79
+      inboxes   = Account.inboxes
80
+      processed = 0
81
+      dry_run   = options[:dry_run] ? ' (DRY RUN)' : ''
82
+
83
+      if inboxes.empty?
84
+        prompt.ok('It seems like your server has not federated with anything')
85
+        prompt.ok('You can shut it down and delete it any time')
86
+        return
87
+      end
88
+
89
+      prompt.warn('Do NOT interrupt this process...')
90
+
91
+      Account.local.without_suspended.find_each do |account|
92
+        payload = ActiveModelSerializers::SerializableResource.new(
93
+          account,
94
+          serializer: ActivityPub::DeleteActorSerializer,
95
+          adapter: ActivityPub::Adapter
96
+        ).as_json
97
+
98
+        json = Oj.dump(ActivityPub::LinkedDataSignature.new(payload).sign!(account))
99
+
100
+        unless options[:dry_run]
101
+          ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
102
+            [json, account.id, inbox_url]
103
+          end
104
+
105
+          account.update_column(:suspended, true)
106
+        end
107
+
108
+        processed += 1
109
+      end
110
+
111
+      prompt.ok("Queued #{inboxes.size * processed} items into Sidekiq for #{processed} accounts#{dry_run}")
112
+      prompt.ok('Wait until Sidekiq processes all items, then you can shut everything down and delete the data')
113
+    rescue TTY::Reader::InputInterrupt
114
+      exit(1)
115
+    end
116
+
44 117
     map %w(--version -v) => :version
45 118
 
46 119
     desc 'version', 'Show version'

Loading…
Cancel
Save