Do not remove "dead" domains in tootctl accounts cull (#9108)

Leave `tootctl accounts cull` to simply check removed accounts from
live domains, and skip temporarily unavailable domains, while listing
them in the final output for further action.

Add `tootctl domains purge DOMAIN` to be able to purge a domain from
that list manually
This commit is contained in:
Eugen Rochko 2018-10-27 22:56:16 +02:00 committed by GitHub
parent a90b569350
commit 6f78500d4f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 62 additions and 33 deletions

View file

@ -6,6 +6,7 @@ require_relative 'mastodon/emoji_cli'
require_relative 'mastodon/accounts_cli' require_relative 'mastodon/accounts_cli'
require_relative 'mastodon/feeds_cli' require_relative 'mastodon/feeds_cli'
require_relative 'mastodon/settings_cli' require_relative 'mastodon/settings_cli'
require_relative 'mastodon/domains_cli'
module Mastodon module Mastodon
class CLI < Thor class CLI < Thor
@ -27,5 +28,8 @@ module Mastodon
desc 'settings SUBCOMMAND ...ARGS', 'Manage dynamic settings' desc 'settings SUBCOMMAND ...ARGS', 'Manage dynamic settings'
subcommand 'settings', Mastodon::SettingsCLI subcommand 'settings', Mastodon::SettingsCLI
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
subcommand 'domains', Mastodon::DomainsCLI
end end
end end

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rubygems/package' require 'set'
require_relative '../../config/boot' require_relative '../../config/boot'
require_relative '../../config/environment' require_relative '../../config/environment'
require_relative 'cli_helper' require_relative 'cli_helper'
@ -10,6 +10,7 @@ module Mastodon
def self.exit_on_failure? def self.exit_on_failure?
true true
end end
option :all, type: :boolean option :all, type: :boolean
desc 'rotate [USERNAME]', 'Generate and broadcast new keys' desc 'rotate [USERNAME]', 'Generate and broadcast new keys'
long_desc <<-LONG_DESC long_desc <<-LONG_DESC
@ -210,33 +211,25 @@ module Mastodon
Accounts that have had confirmed activity within the last week Accounts that have had confirmed activity within the last week
are excluded from the checks. are excluded from the checks.
If 10 or more accounts from the same domain cannot be queried Domains that are unreachable are not checked.
due to a connection error (such as missing DNS records) then
the domain is considered dead, and all other accounts from it
are deleted without further querying.
With the --dry-run option, no deletes will actually be carried With the --dry-run option, no deletes will actually be carried
out. out.
LONG_DESC LONG_DESC
def cull def cull
domain_thresholds = Hash.new { |hash, key| hash[key] = 0 } skip_threshold = 7.days.ago
skip_threshold = 7.days.ago culled = 0
culled = 0 skip_domains = Set.new
dead_servers = [] dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
Account.remote.where(protocol: :activitypub).partitioned.find_each do |account| Account.remote.where(protocol: :activitypub).partitioned.find_each do |account|
next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold) next if account.updated_at >= skip_threshold || (account.last_webfingered_at.present? && account.last_webfingered_at >= skip_threshold)
unless dead_servers.include?(account.domain) unless skip_domains.include?(account.domain)
begin begin
code = Request.new(:head, account.uri).perform(&:code) code = Request.new(:head, account.uri).perform(&:code)
rescue HTTP::ConnectionError rescue HTTP::ConnectionError
domain_thresholds[account.domain] += 1 skip_domains << account.domain
if domain_thresholds[account.domain] >= 10
dead_servers << account.domain
end
rescue StandardError rescue StandardError
next next
end end
@ -255,24 +248,12 @@ module Mastodon
end end
end end
# Remove dead servers
unless dead_servers.empty? || options[:dry_run]
dead_servers.each do |domain|
Account.where(domain: domain).find_each do |account|
SuspendAccountService.new.call(account)
account.destroy
culled += 1
say('.', :green, false)
end
end
end
say say
say("Removed #{culled} accounts (#{dead_servers.size} dead servers)#{dry_run}", :green) say("Removed #{culled} accounts. #{skip_domains.size} servers skipped#{dry_run}", skip_domains.empty? ? :green : :yellow)
unless dead_servers.empty? unless skip_domains.empty?
say('R.I.P.:', :yellow) say('The following servers were not available during the check:', :yellow)
dead_servers.each { |domain| say(' ' + domain) } skip_domains.each { |domain| say(' ' + domain) }
end end
end end

View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
require_relative '../../config/boot'
require_relative '../../config/environment'
require_relative 'cli_helper'
module Mastodon
class DomainsCLI < Thor
def self.exit_on_failure?
true
end
option :dry_run, type: :boolean
desc 'purge DOMAIN', 'Remove accounts from a DOMAIN without a trace'
long_desc <<-LONG_DESC
Remove all accounts from a given DOMAIN without leaving behind any
records. Unlike a suspension, if the DOMAIN still exists in the wild,
it means the accounts could return if they are resolved again.
LONG_DESC
def purge(domain)
removed = 0
dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
Account.where(domain: domain).find_each do |account|
unless options[:dry_run]
SuspendAccountService.new.call(account)
account.destroy
end
removed += 1
say('.', :green, false)
end
DomainBlock.where(domain: domain).destroy_all
say
say("Removed #{removed} accounts#{dry_run}", :green)
end
end
end

View file

@ -10,6 +10,7 @@ module Mastodon
def self.exit_on_failure? def self.exit_on_failure?
true true
end end
option :prefix option :prefix
option :suffix option :suffix
option :overwrite, type: :boolean option :overwrite, type: :boolean

View file

@ -9,6 +9,7 @@ module Mastodon
def self.exit_on_failure? def self.exit_on_failure?
true true
end end
option :all, type: :boolean, default: false option :all, type: :boolean, default: false
option :background, type: :boolean, default: false option :background, type: :boolean, default: false
option :dry_run, type: :boolean, default: false option :dry_run, type: :boolean, default: false
@ -58,7 +59,7 @@ module Mastodon
account = Account.find_local(username) account = Account.find_local(username)
if account.nil? if account.nil?
say("Account #{username} is not found", :red) say('No such account', :red)
exit(1) exit(1)
end end

View file

@ -9,6 +9,7 @@ module Mastodon
def self.exit_on_failure? def self.exit_on_failure?
true true
end end
option :days, type: :numeric, default: 7 option :days, type: :numeric, default: 7
option :background, type: :boolean, default: false option :background, type: :boolean, default: false
option :verbose, type: :boolean, default: false option :verbose, type: :boolean, default: false

View file

@ -9,6 +9,7 @@ module Mastodon
def self.exit_on_failure? def self.exit_on_failure?
true true
end end
desc 'open', 'Open registrations' desc 'open', 'Open registrations'
def open def open
Setting.open_registrations = true Setting.open_registrations = true