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…
Reference in a new issue