Skip to content

Commit 5529709

Browse files
Gargronroot
authored andcommitted
Move more tasks to tootctl (mastodon#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
1 parent 47607b8 commit 5529709

7 files changed

Lines changed: 205 additions & 387 deletions

File tree

.rubocop.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ Rails/SkipsModelValidations:
7777
Rails/HttpStatus:
7878
Enabled: false
7979

80+
Rails/Exit:
81+
Exclude:
82+
- 'lib/mastodon/*'
83+
- 'lib/cli'
84+
8085
Style/ClassAndModuleChildren:
8186
Enabled: false
8287

lib/cli.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
require_relative 'mastodon/media_cli'
55
require_relative 'mastodon/emoji_cli'
66
require_relative 'mastodon/accounts_cli'
7+
require_relative 'mastodon/feeds_cli'
8+
79
module Mastodon
810
class CLI < Thor
911
desc 'media SUBCOMMAND ...ARGS', 'Manage media files'
@@ -14,5 +16,8 @@ class CLI < Thor
1416

1517
desc 'accounts SUBCOMMAND ...ARGS', 'Manage accounts'
1618
subcommand 'accounts', Mastodon::AccountsCLI
19+
20+
desc 'feeds SUBCOMMAND ...ARGS', 'Manage feeds'
21+
subcommand 'feeds', Mastodon::FeedsCLI
1722
end
1823
end

lib/mastodon/accounts_cli.rb

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def rotate(username = nil)
4040
say('OK', :green)
4141
else
4242
say('No account(s) given', :red)
43+
exit(1)
4344
end
4445
end
4546

@@ -48,7 +49,7 @@ def rotate(username = nil)
4849
option :role, default: 'user'
4950
option :reattach, type: :boolean
5051
option :force, type: :boolean
51-
desc 'add USERNAME', 'Create a new user'
52+
desc 'create USERNAME', 'Create a new user'
5253
long_desc <<-LONG_DESC
5354
Create a new user account with a given USERNAME and an
5455
e-mail address provided with --email.
@@ -65,7 +66,7 @@ def rotate(username = nil)
6566
the --force option to delete the old record and reattach the
6667
username to the new account anyway.
6768
LONG_DESC
68-
def add(username)
69+
def create(username)
6970
account = Account.new(username: username)
7071
password = SecureRandom.hex
7172
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)
9899
say(key)
99100
say(' ' + error, :red)
100101
end
102+
103+
exit(1)
101104
end
102105
end
103106

104-
desc 'del USERNAME', 'Delete a user'
107+
option :role
108+
option :email
109+
option :confirm, type: :boolean
110+
option :enable, type: :boolean
111+
option :disable, type: :boolean
112+
option :disable_2fa, type: :boolean
113+
desc 'modify USERNAME', 'Modify a user'
114+
long_desc <<-LONG_DESC
115+
Modify a user account.
116+
117+
With the --role option, update the user's role to one of "user",
118+
"moderator" or "admin".
119+
120+
With the --email option, update the user's e-mail address. With
121+
the --confirm option, mark the user's e-mail as confirmed.
122+
123+
With the --disable option, lock the user out of their account. The
124+
--enable option is the opposite.
125+
126+
With the --disable-2fa option, the two-factor authentication
127+
requirement for the user can be removed.
128+
LONG_DESC
129+
def modify(username)
130+
user = Account.find_local(username)&.user
131+
132+
if user.nil?
133+
say('No user with such username', :red)
134+
exit(1)
135+
end
136+
137+
if options[:role]
138+
user.admin = options[:role] == 'admin'
139+
user.moderator = options[:role] == 'moderator'
140+
end
141+
142+
user.email = options[:email] if options[:email]
143+
user.disabled = false if options[:enable]
144+
user.disabled = true if options[:disable]
145+
user.otp_required_for_login = false if options[:disable_2fa]
146+
user.confirm if options[:confirm]
147+
148+
if user.save
149+
say('OK', :green)
150+
else
151+
user.errors.to_h.each do |key, error|
152+
say('Failure/Error: ', :red)
153+
say(key)
154+
say(' ' + error, :red)
155+
end
156+
157+
exit(1)
158+
end
159+
end
160+
161+
desc 'delete USERNAME', 'Delete a user'
105162
long_desc <<-LONG_DESC
106163
Remove a user account with a given USERNAME.
107164
LONG_DESC
108-
def del(username)
165+
def delete(username)
109166
account = Account.find_local(username)
110167

111168
if account.nil?
112169
say('No user with such username', :red)
113-
return
170+
exit(1)
114171
end
115172

116173
say("Deleting user with #{account.statuses_count}, this might take a while...")
@@ -182,9 +239,56 @@ def cull
182239
end
183240
end
184241

242+
option :all, type: :boolean
243+
option :domain
244+
desc 'refresh [USERNAME]', 'Fetch remote user data and files'
245+
long_desc <<-LONG_DESC
246+
Fetch remote user data and files for one or multiple accounts.
247+
248+
With the --all option, all remote accounts will be processed.
249+
Through the --domain option, this can be narrowed down to a
250+
specific domain only. Otherwise, a single remote account must
251+
be specified with USERNAME.
252+
253+
All processing is done in the background through Sidekiq.
254+
LONG_DESC
255+
def refresh(username = nil)
256+
if options[:domain] || options[:all]
257+
queued = 0
258+
scope = Account.remote
259+
scope = scope.where(domain: options[:domain]) if options[:domain]
260+
261+
scope.select(:id).reorder(nil).find_in_batches do |accounts|
262+
Maintenance::RedownloadAccountMediaWorker.push_bulk(accounts.map(&:id))
263+
queued += accounts.size
264+
end
265+
266+
say("Scheduled refreshment of #{queued} accounts", :green, true)
267+
elsif username.present?
268+
username, domain = username.split('@')
269+
account = Account.find_remote(username, domain)
270+
271+
if account.nil?
272+
say('No such account', :red)
273+
exit(1)
274+
end
275+
276+
Maintenance::RedownloadAccountMediaWorker.perform_async(account.id)
277+
say('OK', :green)
278+
else
279+
say('No account(s) given', :red)
280+
exit(1)
281+
end
282+
end
283+
185284
private
186285

187286
def rotate_keys_for_account(account, delay = 0)
287+
if account.nil?
288+
say('No such account', :red)
289+
exit(1)
290+
end
291+
188292
old_key = account.private_key
189293
new_key = OpenSSL::PKey::RSA.new(2048).to_pem
190294
account.update(private_key: new_key)

lib/mastodon/emoji_cli.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
require_relative '../../config/environment'
66
require_relative 'cli_helper'
77

8-
# rubocop:disable Rails/Output
9-
108
module Mastodon
119
class EmojiCLI < Thor
1210
option :prefix
@@ -77,5 +75,3 @@ def color(green, _yellow, red)
7775
end
7876
end
7977
end
80-
81-
# rubocop:enable Rails/Output

lib/mastodon/feeds_cli.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../config/boot'
4+
require_relative '../../config/environment'
5+
require_relative 'cli_helper'
6+
7+
module Mastodon
8+
class FeedsCLI < Thor
9+
option :all, type: :boolean, default: false
10+
option :background, type: :boolean, default: false
11+
option :dry_run, type: :boolean, default: false
12+
option :verbose, type: :boolean, default: false
13+
desc 'build [USERNAME]', 'Build home and list feeds for one or all users'
14+
long_desc <<-LONG_DESC
15+
Build home and list feeds that are stored in Redis from the database.
16+
17+
With the --all option, all active users will be processed.
18+
Otherwise, a single user specified by USERNAME.
19+
20+
With the --background option, regeneration will be queued into Sidekiq,
21+
and the command will exit as soon as possible.
22+
23+
With the --dry-run option, no work will be done.
24+
25+
With the --verbose option, when accounts are processed sequentially in the
26+
foreground, the IDs of the accounts will be printed.
27+
LONG_DESC
28+
def build(username = nil)
29+
dry_run = options[:dry_run] ? '(DRY RUN)' : ''
30+
31+
if options[:all] || username.nil?
32+
processed = 0
33+
queued = 0
34+
35+
User.active.select(:id, :account_id).reorder(nil).find_in_batches do |users|
36+
if options[:background]
37+
RegenerationWorker.push_bulk(users.map(&:account_id)) unless options[:dry_run]
38+
queued += users.size
39+
else
40+
users.each do |user|
41+
RegenerationWorker.new.perform(user.account_id) unless options[:dry_run]
42+
options[:verbose] ? say(user.account_id) : say('.', :green, false)
43+
processed += 1
44+
end
45+
end
46+
end
47+
48+
if options[:background]
49+
say("Scheduled feed regeneration for #{queued} accounts #{dry_run}", :green, true)
50+
else
51+
say
52+
say("Regenerated feeds for #{processed} accounts #{dry_run}", :green, true)
53+
end
54+
elsif username.present?
55+
account = Account.find_local(username)
56+
57+
if options[:background]
58+
RegenerationWorker.perform_async(account.id) unless options[:dry_run]
59+
else
60+
RegenerationWorker.new.perform(account.id) unless options[:dry_run]
61+
end
62+
63+
say("OK #{dry_run}", :green, true)
64+
else
65+
say('No account(s) given', :red)
66+
exit(1)
67+
end
68+
end
69+
70+
desc 'clear', 'Remove all home and list feeds from Redis'
71+
def clear
72+
keys = Redis.current.keys('feed:*')
73+
74+
Redis.current.pipelined do
75+
keys.each { |key| Redis.current.del(key) }
76+
end
77+
78+
say('OK', :green)
79+
end
80+
end
81+
end

lib/mastodon/media_cli.rb

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
require_relative '../../config/environment'
55
require_relative 'cli_helper'
66

7-
# rubocop:disable Rails/Output
8-
97
module Mastodon
108
class MediaCLI < Thor
119
option :days, type: :numeric, default: 7
@@ -25,9 +23,10 @@ class MediaCLI < Thor
2523
it may impact other operations of the Mastodon server, and it may overload
2624
the underlying file storage.
2725
28-
With the --verbose option, output deleting file ID to console (only when --background false).
26+
With the --dry-run option, no work will be done.
2927
30-
With the --dry-run option, output the number of files to delete without deleting.
28+
With the --verbose option, when media attachments are processed sequentially in the
29+
foreground, the IDs of the media attachments will be printed.
3130
DESC
3231
def remove
3332
time_ago = options[:days].days.ago
@@ -53,12 +52,10 @@ def remove
5352
say
5453

5554
if options[:background]
56-
say("Scheduled the deletion of #{queued} media attachments #{dry_run}.", :green)
55+
say("Scheduled the deletion of #{queued} media attachments #{dry_run}", :green, true)
5756
else
58-
say("Removed #{processed} media attachments #{dry_run}.", :green)
57+
say("Removed #{processed} media attachments #{dry_run}", :green, true)
5958
end
6059
end
6160
end
6261
end
63-
64-
# rubocop:enable Rails/Output

0 commit comments

Comments
 (0)