forked from cybrespace/mastodon
		
	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
					
				
					 7 changed files with 62 additions and 33 deletions
				
			
		| 
						 | 
					@ -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…
	
	Add table
		
		Reference in a new issue