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:
		
							parent
							
								
									a90b569350
								
							
						
					
					
						commit
						6f78500d4f
					
				| @ -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 | ||||||
|  | |||||||
| @ -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 | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								lib/mastodon/domains_cli.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/mastodon/domains_cli.rb
									
									
									
									
									
										Normal 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 | ||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user