tootctl accounts [add|del|cull] (#8642)

* CLI interface for creating/deleting local users

- tootctl accounts add USERNAME
- tootctl accounts del USERNAME

* Add CLI interface for culling remote users that no longer exist

- tootctl accounts cull
shrike
Eugen Rochko 2018-09-09 13:33:36 +02:00 committed by GitHub
parent 2288d50a7b
commit 47ea318479
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 0 deletions

View File

@ -43,6 +43,145 @@ module Mastodon
end
end
option :email, required: true
option :confirmed, type: :boolean
option :role, default: 'user'
option :reattach, type: :boolean
option :force, type: :boolean
desc 'add USERNAME', 'Create a new user'
long_desc <<-LONG_DESC
Create a new user account with a given USERNAME and an
e-mail address provided with --email.
With the --confirmed option, the confirmation e-mail will
be skipped and the account will be active straight away.
With the --role option one of "user", "admin" or "moderator"
can be supplied. Defaults to "user"
With the --reattach option, the new user will be reattached
to a given existing username of an old account. If the old
account is still in use by someone else, you can supply
the --force option to delete the old record and reattach the
username to the new account anyway.
LONG_DESC
def add(username)
account = Account.new(username: username)
password = SecureRandom.hex
user = User.new(email: options[:email], password: password, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: Time.now.utc)
if options[:reattach]
account = Account.find_local(username) || Account.new(username: username)
if account.user.present? && !options[:force]
say('The chosen username is currently in use', :red)
say('Use --force to reattach it anyway and delete the other user')
return
elsif account.user.present?
account.user.destroy!
end
end
user.account = account
if user.save
if options[:confirmed]
user.confirmed_at = nil
user.confirm!
end
say('OK', :green)
say("New password: #{password}")
else
user.errors.to_h.each do |key, error|
say('Failure/Error: ', :red)
say(key)
say(' ' + error, :red)
end
end
end
desc 'del USERNAME', 'Delete a user'
long_desc <<-LONG_DESC
Remove a user account with a given USERNAME.
LONG_DESC
def del(username)
account = Account.find_local(username)
if account.nil?
say('No user with such username', :red)
return
end
say("Deleting user with #{account.statuses_count}, this might take a while...")
SuspendAccountService.new.call(account, remove_user: true)
say('OK', :green)
end
option :dry_run, type: :boolean
desc 'cull', 'Remove remote accounts that no longer exist'
long_desc <<-LONG_DESC
Query every single remote account in the database to determine
if it still exists on the origin server, and if it doesn't,
remove it from the database.
Accounts that have had confirmed activity within the last week
are excluded from the checks.
If 10 or more accounts from the same domain cannot be queried
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
out.
LONG_DESC
def cull
domain_thresholds = Hash.new { |hash, key| hash[key] = 0 }
skip_threshold = 7.days.ago
culled = 0
dead_servers = []
dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
Account.remote.where(protocol: :activitypub).partitioned.find_each do |account|
next if account.updated_at >= skip_threshold || account.last_webfingered_at >= skip_threshold
unless dead_servers.include?(account.domain)
begin
code = Request.new(:head, account.uri).perform(&:code)
rescue HTTP::ConnectionError
domain_thresholds[account.domain] += 1
if domain_thresholds[account.domain] >= 10
dead_servers << account.domain
end
rescue StandardError
next
end
end
if [404, 410].include?(code) || dead_servers.include?(account.domain)
unless options[:dry_run]
SuspendAccountService.new.call(account)
account.destroy
end
culled += 1
say('.', :green, false)
else
say('.', nil, false)
end
end
say
say("Removed #{culled} accounts (#{dead_servers.size} dead servers)#{dry_run}", :green)
unless dead_servers.empty?
say('R.I.P.:', :yellow)
dead_servers.each { |domain| say(' ' + domain) }
end
end
private
def rotate_keys_for_account(account, delay = 0)

View File

@ -4,5 +4,6 @@ dev_null = Logger.new('/dev/null')
Rails.logger = dev_null
ActiveRecord::Base.logger = dev_null
ActiveJob::Base.logger = dev_null
HttpLog.configuration.logger = dev_null
Paperclip.options[:log] = false