Skip to content

Commit

Permalink
Change tootctl self-destruct to output instructions and provide feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
ClearlyClaire committed Oct 11, 2023
1 parent 58f5d3d commit 36ec12a
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 51 deletions.
3 changes: 2 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
include DomainControlHelper
include DatabaseHelper
include AuthorizedFetchHelper
include SelfDestructHelper

helper_method :current_account
helper_method :current_session
Expand Down Expand Up @@ -173,7 +174,7 @@ def respond_with_error(code)
end

def check_self_destruct!
return unless ENV.fetch('SELF_DESTRUCT', nil) && ENV['SELF_DESTRUCT'] == ENV['LOCAL_DOMAIN']
return unless self_destruct?

respond_to do |format|
format.any { render 'errors/self_destruct', layout: 'auth', status: 410, formats: [:html] }
Expand Down
4 changes: 0 additions & 4 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,6 @@ def cdn_host?
cdn_host.present?
end

def self_destruct?
ENV.fetch('SELF_DESTRUCT', nil) && ENV['SELF_DESTRUCT'] == ENV['LOCAL_DOMAIN']
end

def storage_host
"https://#{storage_host_var}"
end
Expand Down
13 changes: 13 additions & 0 deletions app/helpers/self_destruct_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module SelfDestructHelper
def self.self_destruct?
ENV.fetch('SELF_DESTRUCT', nil).present? && Rails.application.message_verifier('self-destruct').verify(ENV.fetch('SELF_DESTRUCT', nil)) == ENV['LOCAL_DOMAIN']
rescue ActiveSupport::MessageVerifier::InvalidSignature
false
end

def self_destruct?
SelfDestructHelper.self_destruct?
end
end
4 changes: 2 additions & 2 deletions app/workers/scheduler/accounts_statuses_cleanup_scheduler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
class Scheduler::AccountsStatusesCleanupScheduler
include Sidekiq::Worker
include Redisable
include SelfDestructHelper

# This limit is mostly to be nice to the fediverse at large and not
# generate too much traffic.
Expand Down Expand Up @@ -35,8 +36,7 @@ class Scheduler::AccountsStatusesCleanupScheduler
sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i

def perform
return if ENV['SELF_DESTRUCT'] && ENV['SELF_DESTRUCT'] == ENV['LOCAL_DOMAIN']
return if under_load?
return if self_destruct? || under_load?

budget = compute_budget

Expand Down
7 changes: 6 additions & 1 deletion app/workers/scheduler/self_destruct_scheduler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class Scheduler::SelfDestructScheduler
include Sidekiq::Worker
include SelfDestructHelper

MAX_ENQUEUED = 10_000
MAX_REDIS_MEM_USAGE = 0.5
Expand All @@ -10,7 +11,7 @@ class Scheduler::SelfDestructScheduler
sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i

def perform
return unless ENV['SELF_DESTRUCT'] && ENV['SELF_DESTRUCT'] == ENV['LOCAL_DOMAIN']
return unless self_destruct?
return if sidekiq_overwhelmed?

delete_accounts!
Expand All @@ -37,6 +38,10 @@ def delete_accounts!
end
end

def inboxes
@inboxes ||= Account.inboxes
end

def delete_account!(account)
payload = ActiveModelSerializers::SerializableResource.new(
account,
Expand Down
2 changes: 1 addition & 1 deletion config/navigation.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

SimpleNavigation::Configuration.run do |navigation|
self_destruct = ENV.fetch('SELF_DESTRUCT', nil) && ENV['SELF_DESTRUCT'] == ENV['LOCAL_DOMAIN']
self_destruct = SelfDestructHelper.self_destruct?

navigation.items do |n|
n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_path
Expand Down
65 changes: 23 additions & 42 deletions lib/mastodon/cli/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ class Main < Base
desc 'maintenance SUBCOMMAND ...ARGS', 'Various maintenance utilities'
subcommand 'maintenance', Maintenance

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
Expand All @@ -92,55 +91,37 @@ def self_destruct

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

unless dry_run?
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?')
end
if SelfDestructHelper.self_destruct?
prompt.ok('Self-destruct mode is already enabled for this Mastodon server')

inboxes = Account.inboxes
processed = 0
pending_accounts = Account.local.without_suspended.count + Account.local.suspended.joins(:deletion_request).count
sidekiq_stats = Sidekiq::Stats.new

Setting.registrations_mode = 'none' unless dry_run?
if pending_accounts.positive?
prompt.warn("#{pending_accounts} accounts are still pending deletion.")
elsif sidekiq_stats.enqueued.positive?
prompt.warn('Deletion notices are still being processed')
elsif sidekiq_stats.retry_size.positive?
prompt.warn('At least one delivery attempt for each deletion notice has been made, but some have failed and are scheduled for retry')
else
prompt.ok('Every deletion notice has been sent! You can safely delete all data and decomission your servers!')
end

if inboxes.empty?
Account.local.without_suspended.in_batches.update_all(suspended_at: Time.now.utc, suspension_origin: :local) unless dry_run?
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
exit(0)
end

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

delete_account = lambda 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 dry_run?
ActivityPub::DeliveryWorker.push_bulk(inboxes, limit: 1_000) do |inbox_url|
[json, account.id, inbox_url]
end

account.suspend!(block_email: false)
end
exit(1) unless prompt.ask('Type in the domain of the server to confirm:', required: true) == Rails.configuration.x.local_domain

processed += 1
end
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.')

Account.local.without_suspended.find_each { |account| delete_account.call(account) }
Account.local.suspended.joins(:deletion_request).find_each { |account| delete_account.call(account) }
exit(1) if prompt.no?('Are you sure you want to proceed?')

prompt.ok("Queued #{inboxes.size * processed} items into Sidekiq for #{processed} accounts#{dry_run_mode_suffix}")
prompt.ok('Wait until Sidekiq processes all items, then you can shut everything down and delete the data')
self_destruct_value = Rails.application.message_verifier('self-destruct').generate(Rails.configuration.x.local_domain)
prompt.ok('To switch Mastodon to self-destruct mode, add the following line to your `.env.production` and restart all Mastodon processes:')
prompt.ok(" SELF_DESTRUCT=#{self_destruct_value}")
prompt.ok("\nYou can re-run this command to see the state of the self-destruct process.")
rescue TTY::Reader::InputInterrupt
exit(1)
end
Expand Down

0 comments on commit 36ec12a

Please sign in to comment.