Permalink
Browse files

Move more tasks to tootctl (#8675)

* Move more tasks to tootctl

- tootctl feeds build
- tootctl feeds clear
- tootctl accounts refresh

Clean up exit codes and help messages

* Move user modifying to tootctl

* Improve user modification through CLI, rename commands

add -> create
mod -> modify
del -> delete

To remove ambiguity

* Fix code style issues

* Fix not being able to unset admin/mod role
  • Loading branch information...
Gargron committed Sep 14, 2018
1 parent 1d8b693 commit 6a3f9b7e53e4cef0b5406676d56e904a02716ee6
Showing with 205 additions and 387 deletions.
  1. +5 −0 .rubocop.yml
  2. +5 −0 lib/cli.rb
  3. +109 −5 lib/mastodon/accounts_cli.rb
  4. +0 −4 lib/mastodon/emoji_cli.rb
  5. +81 −0 lib/mastodon/feeds_cli.rb
  6. +5 −8 lib/mastodon/media_cli.rb
  7. +0 −370 lib/tasks/mastodon.rake
@@ -77,6 +77,11 @@ Rails/SkipsModelValidations:
Rails/HttpStatus:
Enabled: false
Rails/Exit:
Exclude:
- 'lib/mastodon/*'
- 'lib/cli'
Style/ClassAndModuleChildren:
Enabled: false
@@ -4,6 +4,8 @@
require_relative 'mastodon/media_cli'
require_relative 'mastodon/emoji_cli'
require_relative 'mastodon/accounts_cli'
require_relative 'mastodon/feeds_cli'
module Mastodon
class CLI < Thor
desc 'media SUBCOMMAND ...ARGS', 'Manage media files'
@@ -14,5 +16,8 @@ class CLI < Thor
desc 'accounts SUBCOMMAND ...ARGS', 'Manage accounts'
subcommand 'accounts', Mastodon::AccountsCLI
desc 'feeds SUBCOMMAND ...ARGS', 'Manage feeds'
subcommand 'feeds', Mastodon::FeedsCLI
end
end
@@ -40,6 +40,7 @@ def rotate(username = nil)
say('OK', :green)
else
say('No account(s) given', :red)
exit(1)
end
end
@@ -48,7 +49,7 @@ def rotate(username = nil)
option :role, default: 'user'
option :reattach, type: :boolean
option :force, type: :boolean
desc 'add USERNAME', 'Create a new user'
desc 'create 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.
@@ -65,7 +66,7 @@ def rotate(username = nil)
the --force option to delete the old record and reattach the
username to the new account anyway.
LONG_DESC
def add(username)
def create(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)
@@ -98,19 +99,75 @@ def add(username)
say(key)
say(' ' + error, :red)
end
exit(1)
end
end
desc 'del USERNAME', 'Delete a user'
option :role
option :email
option :confirm, type: :boolean
option :enable, type: :boolean
option :disable, type: :boolean
option :disable_2fa, type: :boolean
desc 'modify USERNAME', 'Modify a user'
long_desc <<-LONG_DESC
Modify a user account.
With the --role option, update the user's role to one of "user",
"moderator" or "admin".
With the --email option, update the user's e-mail address. With
the --confirm option, mark the user's e-mail as confirmed.
With the --disable option, lock the user out of their account. The
--enable option is the opposite.
With the --disable-2fa option, the two-factor authentication
requirement for the user can be removed.
LONG_DESC
def modify(username)
user = Account.find_local(username)&.user
if user.nil?
say('No user with such username', :red)
exit(1)
end
if options[:role]
user.admin = options[:role] == 'admin'
user.moderator = options[:role] == 'moderator'
end
user.email = options[:email] if options[:email]
user.disabled = false if options[:enable]
user.disabled = true if options[:disable]
user.otp_required_for_login = false if options[:disable_2fa]
user.confirm if options[:confirm]
if user.save
say('OK', :green)
else
user.errors.to_h.each do |key, error|
say('Failure/Error: ', :red)
say(key)
say(' ' + error, :red)
end
exit(1)
end
end
desc 'delete USERNAME', 'Delete a user'
long_desc <<-LONG_DESC
Remove a user account with a given USERNAME.
LONG_DESC
def del(username)
def delete(username)
account = Account.find_local(username)
if account.nil?
say('No user with such username', :red)
return
exit(1)
end
say("Deleting user with #{account.statuses_count}, this might take a while...")
@@ -182,9 +239,56 @@ def cull
end
end
option :all, type: :boolean
option :domain
desc 'refresh [USERNAME]', 'Fetch remote user data and files'
long_desc <<-LONG_DESC
Fetch remote user data and files for one or multiple accounts.
With the --all option, all remote accounts will be processed.
Through the --domain option, this can be narrowed down to a
specific domain only. Otherwise, a single remote account must
be specified with USERNAME.
All processing is done in the background through Sidekiq.
LONG_DESC
def refresh(username = nil)
if options[:domain] || options[:all]
queued = 0
scope = Account.remote
scope = scope.where(domain: options[:domain]) if options[:domain]
scope.select(:id).reorder(nil).find_in_batches do |accounts|
Maintenance::RedownloadAccountMediaWorker.push_bulk(accounts.map(&:id))
queued += accounts.size
end
say("Scheduled refreshment of #{queued} accounts", :green, true)
elsif username.present?
username, domain = username.split('@')
account = Account.find_remote(username, domain)
if account.nil?
say('No such account', :red)
exit(1)
end
Maintenance::RedownloadAccountMediaWorker.perform_async(account.id)
say('OK', :green)
else
say('No account(s) given', :red)
exit(1)
end
end
private
def rotate_keys_for_account(account, delay = 0)
if account.nil?
say('No such account', :red)
exit(1)
end
old_key = account.private_key
new_key = OpenSSL::PKey::RSA.new(2048).to_pem
account.update(private_key: new_key)
@@ -5,8 +5,6 @@
require_relative '../../config/environment'
require_relative 'cli_helper'
# rubocop:disable Rails/Output
module Mastodon
class EmojiCLI < Thor
option :prefix
@@ -77,5 +75,3 @@ def color(green, _yellow, red)
end
end
end
# rubocop:enable Rails/Output
@@ -0,0 +1,81 @@
# frozen_string_literal: true
require_relative '../../config/boot'
require_relative '../../config/environment'
require_relative 'cli_helper'
module Mastodon
class FeedsCLI < Thor
option :all, type: :boolean, default: false
option :background, type: :boolean, default: false
option :dry_run, type: :boolean, default: false
option :verbose, type: :boolean, default: false
desc 'build [USERNAME]', 'Build home and list feeds for one or all users'
long_desc <<-LONG_DESC
Build home and list feeds that are stored in Redis from the database.
With the --all option, all active users will be processed.
Otherwise, a single user specified by USERNAME.
With the --background option, regeneration will be queued into Sidekiq,
and the command will exit as soon as possible.
With the --dry-run option, no work will be done.
With the --verbose option, when accounts are processed sequentially in the
foreground, the IDs of the accounts will be printed.
LONG_DESC
def build(username = nil)
dry_run = options[:dry_run] ? '(DRY RUN)' : ''
if options[:all] || username.nil?
processed = 0
queued = 0
User.active.select(:id, :account_id).reorder(nil).find_in_batches do |users|
if options[:background]
RegenerationWorker.push_bulk(users.map(&:account_id)) unless options[:dry_run]
queued += users.size
else
users.each do |user|
RegenerationWorker.new.perform(user.account_id) unless options[:dry_run]
options[:verbose] ? say(user.account_id) : say('.', :green, false)
processed += 1
end
end
end
if options[:background]
say("Scheduled feed regeneration for #{queued} accounts #{dry_run}", :green, true)
else
say
say("Regenerated feeds for #{processed} accounts #{dry_run}", :green, true)
end
elsif username.present?
account = Account.find_local(username)
if options[:background]
RegenerationWorker.perform_async(account.id) unless options[:dry_run]
else
RegenerationWorker.new.perform(account.id) unless options[:dry_run]
end
say("OK #{dry_run}", :green, true)
else
say('No account(s) given', :red)
exit(1)
end
end
desc 'clear', 'Remove all home and list feeds from Redis'
def clear
keys = Redis.current.keys('feed:*')
Redis.current.pipelined do
keys.each { |key| Redis.current.del(key) }
end
say('OK', :green)
end
end
end
@@ -4,8 +4,6 @@
require_relative '../../config/environment'
require_relative 'cli_helper'
# rubocop:disable Rails/Output
module Mastodon
class MediaCLI < Thor
option :days, type: :numeric, default: 7
@@ -25,9 +23,10 @@ class MediaCLI < Thor
it may impact other operations of the Mastodon server, and it may overload
the underlying file storage.
With the --verbose option, output deleting file ID to console (only when --background false).
With the --dry-run option, no work will be done.
With the --dry-run option, output the number of files to delete without deleting.
With the --verbose option, when media attachments are processed sequentially in the
foreground, the IDs of the media attachments will be printed.
DESC
def remove
time_ago = options[:days].days.ago
@@ -53,12 +52,10 @@ def remove
say
if options[:background]
say("Scheduled the deletion of #{queued} media attachments #{dry_run}.", :green)
say("Scheduled the deletion of #{queued} media attachments #{dry_run}", :green, true)
else
say("Removed #{processed} media attachments #{dry_run}.", :green)
say("Removed #{processed} media attachments #{dry_run}", :green, true)
end
end
end
end
# rubocop:enable Rails/Output
Oops, something went wrong.

0 comments on commit 6a3f9b7

Please sign in to comment.