2016-11-15 16:56:29 +01:00
# frozen_string_literal: true
2017-12-24 16:14:33 +01:00
require 'optparse'
require 'colorize'
2018-05-02 18:58:48 +02:00
require 'tty-command'
require 'tty-prompt'
2017-12-24 16:14:33 +01:00
2016-09-10 16:21:17 +02:00
namespace :mastodon do
2018-02-11 18:40:57 +01:00
desc 'Configure the instance for production use'
task :setup do
prompt = TTY :: Prompt . new
env = { }
begin
prompt . say ( 'Your instance is identified by its domain name. Changing it afterward will break things.' )
env [ 'LOCAL_DOMAIN' ] = prompt . ask ( 'Domain name:' ) do | q |
q . required true
q . modify :strip
q . validate ( / \ A[a-z0-9 \ . \ -]+ \ z /i )
q . messages [ :valid? ] = 'Invalid domain. If you intend to use unicode characters, enter punycode here'
end
prompt . say " \n "
prompt . say ( 'Single user mode disables registrations and redirects the landing page to your public profile.' )
env [ 'SINGLE_USER_MODE' ] = prompt . yes? ( 'Do you want to enable single user mode?' , default : false )
2018-02-26 01:31:44 +01:00
%w( SECRET_KEY_BASE OTP_SECRET ) . each do | key |
2018-02-11 18:40:57 +01:00
env [ key ] = SecureRandom . hex ( 64 )
end
vapid_key = Webpush . generate_key
env [ 'VAPID_PRIVATE_KEY' ] = vapid_key . private_key
env [ 'VAPID_PUBLIC_KEY' ] = vapid_key . public_key
prompt . say " \n "
using_docker = prompt . yes? ( 'Are you using Docker to run Mastodon?' )
db_connection_works = false
prompt . say " \n "
loop do
env [ 'DB_HOST' ] = prompt . ask ( 'PostgreSQL host:' ) do | q |
q . required true
q . default using_docker ? 'db' : '/var/run/postgresql'
q . modify :strip
end
env [ 'DB_PORT' ] = prompt . ask ( 'PostgreSQL port:' ) do | q |
q . required true
q . default 5432
q . convert :int
end
env [ 'DB_NAME' ] = prompt . ask ( 'Name of PostgreSQL database:' ) do | q |
q . required true
q . default using_docker ? 'postgres' : 'mastodon_production'
q . modify :strip
end
env [ 'DB_USER' ] = prompt . ask ( 'Name of PostgreSQL user:' ) do | q |
q . required true
q . default using_docker ? 'postgres' : 'mastodon'
q . modify :strip
end
env [ 'DB_PASS' ] = prompt . ask ( 'Password of PostgreSQL user:' ) do | q |
q . echo false
end
# The chosen database may not exist yet. Connect to default database
# to avoid "database does not exist" error.
db_options = {
adapter : :postgresql ,
database : 'postgres' ,
host : env [ 'DB_HOST' ] ,
port : env [ 'DB_PORT' ] ,
user : env [ 'DB_USER' ] ,
password : env [ 'DB_PASS' ] ,
}
begin
ActiveRecord :: Base . establish_connection ( db_options )
ActiveRecord :: Base . connection
prompt . ok 'Database configuration works! 🎆'
db_connection_works = true
break
rescue StandardError = > e
prompt . error 'Database connection could not be established with this configuration, try again.'
prompt . error e . message
break unless prompt . yes? ( 'Try again?' )
end
end
prompt . say " \n "
loop do
env [ 'REDIS_HOST' ] = prompt . ask ( 'Redis host:' ) do | q |
q . required true
q . default using_docker ? 'redis' : 'localhost'
q . modify :strip
end
env [ 'REDIS_PORT' ] = prompt . ask ( 'Redis port:' ) do | q |
q . required true
q . default 6379
q . convert :int
end
2018-04-22 11:49:16 +02:00
env [ 'REDIS_PASSWORD' ] = prompt . ask ( 'Redis password:' ) do | q |
q . required false
2018-04-23 16:03:58 +02:00
q . default nil
2018-04-22 11:49:16 +02:00
q . modify :strip
end
2018-02-11 18:40:57 +01:00
redis_options = {
host : env [ 'REDIS_HOST' ] ,
port : env [ 'REDIS_PORT' ] ,
2018-04-22 11:49:16 +02:00
password : env [ 'REDIS_PASSWORD' ] ,
2018-02-11 18:40:57 +01:00
driver : :hiredis ,
}
begin
redis = Redis . new ( redis_options )
redis . ping
prompt . ok 'Redis configuration works! 🎆'
break
rescue StandardError = > e
prompt . error 'Redis connection could not be established with this configuration, try again.'
prompt . error e . message
break unless prompt . yes? ( 'Try again?' )
end
end
prompt . say " \n "
if prompt . yes? ( 'Do you want to store uploaded files on the cloud?' , default : false )
case prompt . select ( 'Provider' , [ 'Amazon S3' , 'Wasabi' , 'Minio' ] )
when 'Amazon S3'
env [ 'S3_ENABLED' ] = 'true'
env [ 'S3_PROTOCOL' ] = 'https'
env [ 'S3_BUCKET' ] = prompt . ask ( 'S3 bucket name:' ) do | q |
q . required true
q . default " files. #{ env [ 'LOCAL_DOMAIN' ] } "
q . modify :strip
end
env [ 'S3_REGION' ] = prompt . ask ( 'S3 region:' ) do | q |
q . required true
q . default 'us-east-1'
q . modify :strip
end
env [ 'S3_HOSTNAME' ] = prompt . ask ( 'S3 hostname:' ) do | q |
q . required true
q . default 's3-us-east-1.amazonaws.com'
q . modify :strip
end
env [ 'AWS_ACCESS_KEY_ID' ] = prompt . ask ( 'S3 access key:' ) do | q |
q . required true
q . modify :strip
end
env [ 'AWS_SECRET_ACCESS_KEY' ] = prompt . ask ( 'S3 secret key:' ) do | q |
q . required true
q . modify :strip
end
when 'Wasabi'
env [ 'S3_ENABLED' ] = 'true'
env [ 'S3_PROTOCOL' ] = 'https'
env [ 'S3_REGION' ] = 'us-east-1'
env [ 'S3_HOSTNAME' ] = 's3.wasabisys.com'
env [ 'S3_ENDPOINT' ] = 'https://s3.wasabisys.com/'
env [ 'S3_BUCKET' ] = prompt . ask ( 'Wasabi bucket name:' ) do | q |
q . required true
q . default " files. #{ env [ 'LOCAL_DOMAIN' ] } "
q . modify :strip
end
env [ 'AWS_ACCESS_KEY_ID' ] = prompt . ask ( 'Wasabi access key:' ) do | q |
q . required true
q . modify :strip
end
env [ 'AWS_SECRET_ACCESS_KEY' ] = prompt . ask ( 'Wasabi secret key:' ) do | q |
q . required true
q . modify :strip
end
when 'Minio'
env [ 'S3_ENABLED' ] = 'true'
env [ 'S3_PROTOCOL' ] = 'https'
env [ 'S3_REGION' ] = 'us-east-1'
env [ 'S3_ENDPOINT' ] = prompt . ask ( 'Minio endpoint URL:' ) do | q |
q . required true
q . modify :strip
end
env [ 'S3_PROTOCOL' ] = env [ 'S3_ENDPOINT' ] . start_with? ( 'https' ) ? 'https' : 'http'
env [ 'S3_HOSTNAME' ] = env [ 'S3_ENDPOINT' ] . gsub ( / \ Ahttps?: \/ \/ / , '' )
env [ 'S3_BUCKET' ] = prompt . ask ( 'Minio bucket name:' ) do | q |
q . required true
q . default " files. #{ env [ 'LOCAL_DOMAIN' ] } "
q . modify :strip
end
env [ 'AWS_ACCESS_KEY_ID' ] = prompt . ask ( 'Minio access key:' ) do | q |
q . required true
q . modify :strip
end
env [ 'AWS_SECRET_ACCESS_KEY' ] = prompt . ask ( 'Minio secret key:' ) do | q |
q . required true
q . modify :strip
end
end
if prompt . yes? ( 'Do you want to access the uploaded files from your own domain?' )
env [ 'S3_CLOUDFRONT_HOST' ] = prompt . ask ( 'Domain for uploaded files:' ) do | q |
q . required true
q . default " files. #{ env [ 'LOCAL_DOMAIN' ] } "
q . modify :strip
end
end
end
prompt . say " \n "
loop do
2018-03-12 21:41:26 +01:00
if prompt . yes? ( 'Do you want to send e-mails from localhost?' , default : false )
env [ 'SMTP_SERVER' ] = 'localhost'
env [ 'SMTP_PORT' ] = 25
env [ 'SMTP_AUTH_METHOD' ] = 'none'
env [ 'SMTP_OPENSSL_VERIFY_MODE' ] = 'none'
else
env [ 'SMTP_SERVER' ] = prompt . ask ( 'SMTP server:' ) do | q |
q . required true
q . default 'smtp.mailgun.org'
q . modify :strip
end
2018-02-11 18:40:57 +01:00
2018-03-12 21:41:26 +01:00
env [ 'SMTP_PORT' ] = prompt . ask ( 'SMTP port:' ) do | q |
q . required true
q . default 587
q . convert :int
end
2018-02-11 18:40:57 +01:00
2018-03-12 21:41:26 +01:00
env [ 'SMTP_LOGIN' ] = prompt . ask ( 'SMTP username:' ) do | q |
q . modify :strip
end
2018-02-11 18:40:57 +01:00
2018-03-12 21:41:26 +01:00
env [ 'SMTP_PASSWORD' ] = prompt . ask ( 'SMTP password:' ) do | q |
q . echo false
end
env [ 'SMTP_AUTH_METHOD' ] = prompt . ask ( 'SMTP authentication:' ) do | q |
q . required
q . default 'plain'
q . modify :strip
end
2018-04-02 19:19:51 +02:00
env [ 'SMTP_OPENSSL_VERIFY_MODE' ] = prompt . select ( 'SMTP OpenSSL verify mode:' , %w( none peer client_once fail_if_no_peer_cert ) )
2018-02-11 18:40:57 +01:00
end
env [ 'SMTP_FROM_ADDRESS' ] = prompt . ask ( 'E-mail address to send e-mails "from":' ) do | q |
q . required true
q . default " Mastodon <notifications@ #{ env [ 'LOCAL_DOMAIN' ] } > "
q . modify :strip
end
break unless prompt . yes? ( 'Send a test e-mail with this configuration right now?' )
send_to = prompt . ask ( 'Send test e-mail to:' , required : true )
begin
ActionMailer :: Base . smtp_settings = {
:port = > env [ 'SMTP_PORT' ] ,
:address = > env [ 'SMTP_SERVER' ] ,
:user_name = > env [ 'SMTP_LOGIN' ] . presence ,
:password = > env [ 'SMTP_PASSWORD' ] . presence ,
:domain = > env [ 'LOCAL_DOMAIN' ] ,
2018-03-12 21:41:26 +01:00
:authentication = > env [ 'SMTP_AUTH_METHOD' ] == 'none' ? nil : env [ 'SMTP_AUTH_METHOD' ] || :plain ,
:openssl_verify_mode = > env [ 'SMTP_OPENSSL_VERIFY_MODE' ] ,
2018-02-11 18:40:57 +01:00
:enable_starttls_auto = > true ,
}
ActionMailer :: Base . default_options = {
from : env [ 'SMTP_FROM_ADDRESS' ] ,
}
mail = ActionMailer :: Base . new . mail to : send_to , subject : 'Test' , body : 'Mastodon SMTP configuration works!'
mail . deliver
2018-03-12 21:41:26 +01:00
break
2018-02-11 18:40:57 +01:00
rescue StandardError = > e
prompt . error 'E-mail could not be sent with this configuration, try again.'
prompt . error e . message
break unless prompt . yes? ( 'Try again?' )
end
end
prompt . say " \n "
prompt . say 'This configuration will be written to .env.production'
if prompt . yes? ( 'Save configuration?' )
cmd = TTY :: Command . new ( printer : :quiet )
File . write ( Rails . root . join ( '.env.production' ) , " # Generated with mastodon:setup on #{ Time . now . utc } \n \n " + env . each_pair . map { | key , value | " #{ key } = #{ value } " } . join ( " \n " ) + " \n " )
2018-03-09 11:52:18 +01:00
if using_docker
prompt . ok 'Below is your configuration, save it to an .env.production file outside Docker:'
prompt . say " \n "
prompt . say File . read ( Rails . root . join ( '.env.production' ) )
prompt . say " \n "
prompt . ok 'It is also saved within this container so you can proceed with this wizard.'
end
2018-02-11 18:40:57 +01:00
prompt . say " \n "
prompt . say 'Now that configuration is saved, the database schema must be loaded.'
prompt . warn 'If the database already exists, this will erase its contents.'
if prompt . yes? ( 'Prepare the database now?' )
prompt . say 'Running `RAILS_ENV=production rails db:setup` ...'
prompt . say " \n "
2018-03-12 16:21:48 +01:00
if cmd . run! ( { RAILS_ENV : 'production' , SAFETY_ASSURED : 1 } , :rails , 'db:setup' ) . failure?
2018-02-11 18:40:57 +01:00
prompt . say " \n "
prompt . error 'That failed! Perhaps your configuration is not right'
else
prompt . say " \n "
prompt . ok 'Done!'
end
end
prompt . say " \n "
prompt . say 'The final step is compiling CSS/JS assets.'
prompt . say 'This may take a while and consume a lot of RAM.'
if prompt . yes? ( 'Compile the assets now?' )
prompt . say 'Running `RAILS_ENV=production rails assets:precompile` ...'
prompt . say " \n "
if cmd . run! ( { RAILS_ENV : 'production' } , :rails , 'assets:precompile' ) . failure?
prompt . say " \n "
prompt . error 'That failed! Maybe you need swap space?'
else
prompt . say " \n "
prompt . say 'Done!'
end
end
prompt . say " \n "
prompt . ok 'All done! You can now power on the Mastodon server 🐘'
prompt . say " \n "
if db_connection_works && prompt . yes? ( 'Do you want to create an admin user straight away?' )
env . each_pair do | key , value |
ENV [ key ] = value . to_s
end
require_relative '../../config/environment'
disable_log_stdout!
username = prompt . ask ( 'Username:' ) do | q |
q . required true
q . default 'admin'
q . validate ( / \ A[a-z0-9_]+ \ z /i )
q . modify :strip
end
email = prompt . ask ( 'E-mail:' ) do | q |
q . required true
q . modify :strip
end
password = SecureRandom . hex ( 16 )
user = User . new ( admin : true , email : email , password : password , confirmed_at : Time . now . utc , account_attributes : { username : username } )
user . save ( validate : false )
prompt . ok " You can login with the password: #{ password } "
prompt . warn 'You can change your password once you login.'
end
else
prompt . warn 'Nothing saved. Bye!'
end
rescue TTY :: Reader :: InputInterrupt
prompt . ok 'Aborting. Bye!'
end
end
2017-07-24 16:17:55 +02:00
desc 'Execute daily tasks (deprecated)'
2017-04-16 12:53:58 +02:00
task :daily do
2017-07-24 16:17:55 +02:00
# No-op
# All of these tasks are now executed via sidekiq-scheduler
2017-04-16 12:53:58 +02:00
end
desc 'Turn a user into an admin, identified by the USERNAME environment variable'
2017-02-13 20:41:48 +01:00
task make_admin : :environment do
include RoutingHelper
2017-11-11 20:23:33 +01:00
2017-05-08 23:08:55 +02:00
account_username = ENV . fetch ( 'USERNAME' )
2017-11-11 20:23:33 +01:00
user = User . joins ( :account ) . where ( accounts : { username : account_username } )
2017-02-13 20:41:48 +01:00
2017-05-08 23:08:55 +02:00
if user . present?
user . update ( admin : true )
puts " Congrats! #{ account_username } is now an admin. \\ o/ \n Navigate to #{ edit_admin_settings_url } to get started "
else
2017-11-11 20:23:33 +01:00
puts " User could not be found; please make sure an account with the ` #{ account_username } ` username exists. "
end
end
desc 'Turn a user into a moderator, identified by the USERNAME environment variable'
task make_mod : :environment do
account_username = ENV . fetch ( 'USERNAME' )
user = User . joins ( :account ) . where ( accounts : { username : account_username } )
if user . present?
user . update ( moderator : true )
puts " Congrats! #{ account_username } is now a moderator \\ o/ "
else
puts " User could not be found; please make sure an account with the ` #{ account_username } ` username exists. "
end
end
desc 'Remove admin and moderator privileges from user identified by the USERNAME environment variable'
task revoke_staff : :environment do
account_username = ENV . fetch ( 'USERNAME' )
user = User . joins ( :account ) . where ( accounts : { username : account_username } )
if user . present?
user . update ( moderator : false , admin : false )
puts " #{ account_username } is no longer admin or moderator. "
else
puts " User could not be found; please make sure an account with the ` #{ account_username } ` username exists. "
2017-05-08 23:08:55 +02:00
end
2017-02-13 20:41:48 +01:00
end
2017-04-09 01:09:46 +02:00
desc 'Manually confirms a user with associated user email address stored in USER_EMAIL environment variable.'
task confirm_email : :environment do
email = ENV . fetch ( 'USER_EMAIL' )
2017-04-16 12:53:58 +02:00
user = User . find_by ( email : email )
2017-04-11 00:13:08 +02:00
if user
user . update ( confirmed_at : Time . now . utc )
2017-04-16 12:53:58 +02:00
puts " #{ email } confirmed "
2017-04-11 00:13:08 +02:00
else
2017-04-16 12:53:58 +02:00
abort " #{ email } not found "
2017-04-11 00:13:08 +02:00
end
2017-04-09 01:09:46 +02:00
end
2017-06-27 14:18:53 +02:00
desc 'Add a user by providing their email, username and initial password.' \
'The user will receive a confirmation email, then they must reset their password before logging in.'
task add_user : :environment do
2018-02-11 18:40:57 +01:00
disable_log_stdout!
prompt = TTY :: Prompt . new
begin
email = prompt . ask ( 'E-mail:' , required : true ) do | q |
q . modify :strip
end
username = prompt . ask ( 'Username:' , required : true ) do | q |
q . modify :strip
end
role = prompt . select ( 'Role:' , %w( user moderator admin ) )
if prompt . yes? ( 'Proceed to create the user?' )
user = User . new ( email : email , password : SecureRandom . hex , admin : role == 'admin' , moderator : role == 'moderator' , account_attributes : { username : username } )
if user . save
prompt . ok 'User created and confirmation mail sent to the user\'s email address.'
2018-03-17 13:27:19 +01:00
prompt . ok " Here is the random password generated for the user: #{ user . password } "
2018-02-11 18:40:57 +01:00
else
prompt . warn 'User was not created because of the following errors:'
user . errors . each do | key , val |
prompt . error " #{ key } : #{ val } "
end
2017-06-27 14:18:53 +02:00
end
2018-02-11 18:40:57 +01:00
else
prompt . ok 'Aborting. Bye!'
2017-06-27 14:18:53 +02:00
end
2018-02-11 18:40:57 +01:00
rescue TTY :: Reader :: InputInterrupt
prompt . ok 'Aborting. Bye!'
2017-06-27 14:18:53 +02:00
end
end
2016-09-10 16:21:17 +02:00
namespace :media do
2017-07-11 17:25:49 +02:00
desc 'Removes media attachments that have not been assigned to any status for longer than a day (deprecated)'
2016-09-10 16:21:17 +02:00
task clear : :environment do
2017-05-20 19:42:58 +02:00
# No-op
# This task is now executed via sidekiq-scheduler
2016-09-10 16:21:17 +02:00
end
2017-01-12 04:19:04 +01:00
desc 'Remove media attachments attributed to silenced accounts'
task remove_silenced : :environment do
2018-08-11 22:02:55 +02:00
nb_media_attachments = 0
2018-08-21 12:25:50 +02:00
MediaAttachment . where ( account : Account . silenced ) . select ( :id ) . reorder ( nil ) . find_in_batches do | media_attachments |
2018-08-11 22:02:55 +02:00
nb_media_attachments += media_attachments . length
2018-05-30 21:09:30 +02:00
Maintenance :: DestroyMediaWorker . push_bulk ( media_attachments . map ( & :id ) )
end
2018-08-11 22:02:55 +02:00
puts " Scheduled the deletion of #{ nb_media_attachments } media attachments "
2017-01-12 04:19:04 +01:00
end
2017-04-16 12:53:58 +02:00
2017-07-14 03:51:17 +02:00
desc 'Remove cached remote media attachments that are older than NUM_DAYS. By default 7 (week)'
2017-04-16 12:53:58 +02:00
task remove_remote : :environment do
2017-07-14 03:51:17 +02:00
time_ago = ENV . fetch ( 'NUM_DAYS' ) { 7 } . to_i . days . ago
2018-08-11 22:02:55 +02:00
nb_media_attachments = 0
2017-07-14 03:51:17 +02:00
2018-08-21 12:25:50 +02:00
MediaAttachment . where . not ( remote_url : '' ) . where . not ( file_file_name : nil ) . where ( 'created_at < ?' , time_ago ) . select ( :id ) . reorder ( nil ) . find_in_batches do | media_attachments |
2018-08-11 22:02:55 +02:00
nb_media_attachments += media_attachments . length
2018-05-30 21:09:30 +02:00
Maintenance :: UncacheMediaWorker . push_bulk ( media_attachments . map ( & :id ) )
2017-04-16 12:53:58 +02:00
end
2018-08-11 22:02:55 +02:00
puts " Scheduled the deletion of #{ nb_media_attachments } media attachments "
2017-04-16 12:53:58 +02:00
end
2017-04-29 02:44:03 +02:00
desc 'Set unknown attachment type for remote-only attachments'
task set_unknown : :environment do
2017-09-12 00:16:03 +02:00
puts 'Setting unknown attachment type for remote-only attachments...'
2017-04-29 02:44:03 +02:00
MediaAttachment . where ( file_file_name : nil ) . where . not ( type : :unknown ) . in_batches . update_all ( type : :unknown )
2017-09-12 00:16:03 +02:00
puts 'Done!'
2017-04-29 02:44:03 +02:00
end
2017-07-11 17:25:49 +02:00
desc 'Redownload avatars/headers of remote users. Optionally limit to a particular domain with DOMAIN'
task redownload_avatars : :environment do
accounts = Account . remote
accounts = accounts . where ( domain : ENV [ 'DOMAIN' ] ) if ENV [ 'DOMAIN' ] . present?
2018-08-11 22:02:55 +02:00
nb_accounts = 0
2017-07-11 17:25:49 +02:00
2018-08-21 12:25:50 +02:00
accounts . select ( :id ) . reorder ( nil ) . find_in_batches do | accounts_batch |
2018-08-11 22:02:55 +02:00
nb_accounts += accounts_batch . length
2018-05-30 21:09:30 +02:00
Maintenance :: RedownloadAccountMediaWorker . push_bulk ( accounts_batch . map ( & :id ) )
2017-07-11 17:25:49 +02:00
end
2018-08-11 22:02:55 +02:00
puts " Scheduled the download of avatars/headers for #{ nb_accounts } remote users "
2017-07-11 17:25:49 +02:00
end
2016-09-10 16:21:17 +02:00
end
namespace :push do
desc 'Unsubscribes from PuSH updates of feeds nobody follows locally'
task clear : :environment do
2017-08-21 01:14:40 +02:00
Pubsubhubbub :: UnsubscribeWorker . push_bulk ( Account . remote . without_followers . where . not ( subscription_expires_at : nil ) . pluck ( :id ) )
2016-09-20 00:39:03 +02:00
end
2017-07-11 17:25:49 +02:00
desc 'Re-subscribes to soon expiring PuSH subscriptions (deprecated)'
2016-09-20 00:39:03 +02:00
task refresh : :environment do
2017-05-05 02:23:01 +02:00
# No-op
# This task is now executed via sidekiq-scheduler
2016-09-10 16:21:17 +02:00
end
end
namespace :feeds do
2017-07-11 17:25:49 +02:00
desc 'Clear timelines of inactive users (deprecated)'
2016-09-10 16:21:17 +02:00
task clear : :environment do
2017-05-20 19:42:58 +02:00
# No-op
# This task is now executed via sidekiq-scheduler
2016-11-24 18:17:58 +01:00
end
2017-07-11 17:25:49 +02:00
desc 'Clear all timelines without regenerating them'
2016-11-24 18:17:58 +01:00
task clear_all : :environment do
2016-11-15 16:56:29 +01:00
Redis . current . keys ( 'feed:*' ) . each { | key | Redis . current . del ( key ) }
2016-09-10 16:21:17 +02:00
end
2017-07-23 01:15:04 +02:00
desc 'Generates home timelines for users who logged in in the past two weeks'
task build : :environment do
2018-08-21 12:25:50 +02:00
User . active . select ( :id , :account_id ) . reorder ( nil ) . find_in_batches do | users |
2018-05-30 21:09:30 +02:00
RegenerationWorker . push_bulk ( users . map ( & :account_id ) )
2017-07-23 01:15:04 +02:00
end
end
2016-09-10 16:21:17 +02:00
end
2017-03-03 23:45:48 +01:00
namespace :emails do
2018-01-15 04:34:28 +01:00
desc 'Send out digest e-mails (deprecated)'
2017-03-03 23:45:48 +01:00
task digest : :environment do
2018-01-15 04:34:28 +01:00
# No-op
# This task is now executed via sidekiq-scheduler
2017-03-03 23:45:48 +01:00
end
end
2017-03-31 13:01:00 +02:00
2017-04-11 22:51:17 +02:00
namespace :users do
2017-07-24 16:17:55 +02:00
desc 'Clear out unconfirmed users (deprecated)'
2017-04-11 22:51:17 +02:00
task clear : :environment do
2017-07-24 16:17:55 +02:00
# No-op
# This task is now executed via sidekiq-scheduler
2017-04-11 22:51:17 +02:00
end
2017-04-18 01:21:55 +02:00
2017-07-11 17:25:49 +02:00
desc 'List e-mails of all admin users'
2017-04-18 01:21:55 +02:00
task admins : :environment do
puts 'Admin user emails:'
puts User . admins . map ( & :email ) . join ( " \n " )
end
2017-04-11 22:51:17 +02:00
end
2017-04-15 13:17:07 +02:00
namespace :settings do
desc 'Open registrations on this instance'
task open_registrations : :environment do
2017-07-11 15:27:59 +02:00
Setting . open_registrations = true
2017-04-15 13:17:07 +02:00
end
desc 'Close registrations on this instance'
task close_registrations : :environment do
2017-07-11 15:27:59 +02:00
Setting . open_registrations = false
2017-04-15 13:17:07 +02:00
end
end
2017-07-14 12:13:43 +02:00
namespace :webpush do
desc 'Generate VAPID key'
task generate_vapid_key : :environment do
vapid_key = Webpush . generate_key
puts " VAPID_PRIVATE_KEY= #{ vapid_key . private_key } "
puts " VAPID_PUBLIC_KEY= #{ vapid_key . public_key } "
end
end
2017-03-31 13:01:00 +02:00
namespace :maintenance do
desc 'Update counter caches'
task update_counter_caches : :environment do
2017-09-12 00:16:03 +02:00
puts 'Updating counter caches for accounts...'
2017-03-31 13:01:00 +02:00
2017-09-12 00:16:03 +02:00
Account . unscoped . where . not ( protocol : :activitypub ) . select ( 'id' ) . find_in_batches do | batch |
2017-03-31 13:01:00 +02:00
Account . where ( id : batch . map ( & :id ) ) . update_all ( 'statuses_count = (select count(*) from statuses where account_id = accounts.id), followers_count = (select count(*) from follows where target_account_id = accounts.id), following_count = (select count(*) from follows where account_id = accounts.id)' )
end
2017-09-12 00:16:03 +02:00
puts 'Updating counter caches for statuses...'
2017-03-31 13:01:00 +02:00
Status . unscoped . select ( 'id' ) . find_in_batches do | batch |
Status . where ( id : batch . map ( & :id ) ) . update_all ( 'favourites_count = (select count(*) from favourites where favourites.status_id = statuses.id), reblogs_count = (select count(*) from statuses as reblogs where reblogs.reblog_of_id = statuses.id)' )
end
2017-09-12 00:16:03 +02:00
puts 'Done!'
2017-03-31 13:01:00 +02:00
end
2017-04-11 00:38:58 +02:00
desc 'Generate static versions of GIF avatars/headers'
task add_static_avatars : :environment do
2017-09-12 00:16:03 +02:00
puts 'Generating static avatars/headers for GIF ones...'
2017-04-11 00:38:58 +02:00
Account . unscoped . where ( avatar_content_type : 'image/gif' ) . or ( Account . unscoped . where ( header_content_type : 'image/gif' ) ) . find_each do | account |
2017-04-16 12:53:58 +02:00
begin
2017-04-17 19:58:54 +02:00
account . avatar . reprocess! if account . avatar_content_type == 'image/gif' && ! account . avatar . exists? ( :static )
account . header . reprocess! if account . header_content_type == 'image/gif' && ! account . header . exists? ( :static )
2017-04-16 12:53:58 +02:00
rescue StandardError = > e
Rails . logger . error " Error while generating static avatars/headers for account #{ account . id } : #{ e } "
next
end
2017-04-11 00:38:58 +02:00
end
2017-09-12 00:16:03 +02:00
puts 'Done!'
2017-04-11 00:38:58 +02:00
end
2017-06-07 20:16:53 +02:00
desc 'Ensure referencial integrity'
task prepare_for_foreign_keys : :environment do
# All the deletes:
ActiveRecord :: Base . connection . execute ( 'DELETE FROM statuses USING statuses s LEFT JOIN accounts a ON a.id = s.account_id WHERE statuses.id = s.id AND a.id IS NULL' )
2017-07-16 20:01:21 +02:00
if ActiveRecord :: Base . connection . table_exists? :account_domain_blocks
ActiveRecord :: Base . connection . execute ( 'DELETE FROM account_domain_blocks USING account_domain_blocks adb LEFT JOIN accounts a ON a.id = adb.account_id WHERE account_domain_blocks.id = adb.id AND a.id IS NULL' )
end
if ActiveRecord :: Base . connection . table_exists? :conversation_mutes
ActiveRecord :: Base . connection . execute ( 'DELETE FROM conversation_mutes USING conversation_mutes cm LEFT JOIN accounts a ON a.id = cm.account_id WHERE conversation_mutes.id = cm.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM conversation_mutes USING conversation_mutes cm LEFT JOIN conversations c ON c.id = cm.conversation_id WHERE conversation_mutes.id = cm.id AND c.id IS NULL' )
end
2017-06-07 20:16:53 +02:00
ActiveRecord :: Base . connection . execute ( 'DELETE FROM favourites USING favourites f LEFT JOIN accounts a ON a.id = f.account_id WHERE favourites.id = f.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM favourites USING favourites f LEFT JOIN statuses s ON s.id = f.status_id WHERE favourites.id = f.id AND s.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM blocks USING blocks b LEFT JOIN accounts a ON a.id = b.account_id WHERE blocks.id = b.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM blocks USING blocks b LEFT JOIN accounts a ON a.id = b.target_account_id WHERE blocks.id = b.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM follow_requests USING follow_requests fr LEFT JOIN accounts a ON a.id = fr.account_id WHERE follow_requests.id = fr.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM follow_requests USING follow_requests fr LEFT JOIN accounts a ON a.id = fr.target_account_id WHERE follow_requests.id = fr.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM follows USING follows f LEFT JOIN accounts a ON a.id = f.account_id WHERE follows.id = f.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM follows USING follows f LEFT JOIN accounts a ON a.id = f.target_account_id WHERE follows.id = f.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM mutes USING mutes m LEFT JOIN accounts a ON a.id = m.account_id WHERE mutes.id = m.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM mutes USING mutes m LEFT JOIN accounts a ON a.id = m.target_account_id WHERE mutes.id = m.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM imports USING imports i LEFT JOIN accounts a ON a.id = i.account_id WHERE imports.id = i.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM mentions USING mentions m LEFT JOIN accounts a ON a.id = m.account_id WHERE mentions.id = m.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM mentions USING mentions m LEFT JOIN statuses s ON s.id = m.status_id WHERE mentions.id = m.id AND s.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM notifications USING notifications n LEFT JOIN accounts a ON a.id = n.account_id WHERE notifications.id = n.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM notifications USING notifications n LEFT JOIN accounts a ON a.id = n.from_account_id WHERE notifications.id = n.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM preview_cards USING preview_cards pc LEFT JOIN statuses s ON s.id = pc.status_id WHERE preview_cards.id = pc.id AND s.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM reports USING reports r LEFT JOIN accounts a ON a.id = r.account_id WHERE reports.id = r.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM reports USING reports r LEFT JOIN accounts a ON a.id = r.target_account_id WHERE reports.id = r.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM statuses_tags USING statuses_tags st LEFT JOIN statuses s ON s.id = st.status_id WHERE statuses_tags.tag_id = st.tag_id AND statuses_tags.status_id = st.status_id AND s.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM statuses_tags USING statuses_tags st LEFT JOIN tags t ON t.id = st.tag_id WHERE statuses_tags.tag_id = st.tag_id AND statuses_tags.status_id = st.status_id AND t.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM stream_entries USING stream_entries se LEFT JOIN accounts a ON a.id = se.account_id WHERE stream_entries.id = se.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM subscriptions USING subscriptions s LEFT JOIN accounts a ON a.id = s.account_id WHERE subscriptions.id = s.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM users USING users u LEFT JOIN accounts a ON a.id = u.account_id WHERE users.id = u.id AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM web_settings USING web_settings ws LEFT JOIN users u ON u.id = ws.user_id WHERE web_settings.id = ws.id AND u.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM oauth_access_grants USING oauth_access_grants oag LEFT JOIN users u ON u.id = oag.resource_owner_id WHERE oauth_access_grants.id = oag.id AND oag.resource_owner_id IS NOT NULL AND u.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM oauth_access_grants USING oauth_access_grants oag LEFT JOIN oauth_applications a ON a.id = oag.application_id WHERE oauth_access_grants.id = oag.id AND oag.application_id IS NOT NULL AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM oauth_access_tokens USING oauth_access_tokens oat LEFT JOIN users u ON u.id = oat.resource_owner_id WHERE oauth_access_tokens.id = oat.id AND oat.resource_owner_id IS NOT NULL AND u.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'DELETE FROM oauth_access_tokens USING oauth_access_tokens oat LEFT JOIN oauth_applications a ON a.id = oat.application_id WHERE oauth_access_tokens.id = oat.id AND oat.application_id IS NOT NULL AND a.id IS NULL' )
# All the nullifies:
ActiveRecord :: Base . connection . execute ( 'UPDATE statuses SET in_reply_to_id = NULL FROM statuses s LEFT JOIN statuses rs ON rs.id = s.in_reply_to_id WHERE statuses.id = s.id AND s.in_reply_to_id IS NOT NULL AND rs.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'UPDATE statuses SET in_reply_to_account_id = NULL FROM statuses s LEFT JOIN accounts a ON a.id = s.in_reply_to_account_id WHERE statuses.id = s.id AND s.in_reply_to_account_id IS NOT NULL AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'UPDATE media_attachments SET status_id = NULL FROM media_attachments ma LEFT JOIN statuses s ON s.id = ma.status_id WHERE media_attachments.id = ma.id AND ma.status_id IS NOT NULL AND s.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'UPDATE media_attachments SET account_id = NULL FROM media_attachments ma LEFT JOIN accounts a ON a.id = ma.account_id WHERE media_attachments.id = ma.id AND ma.account_id IS NOT NULL AND a.id IS NULL' )
ActiveRecord :: Base . connection . execute ( 'UPDATE reports SET action_taken_by_account_id = NULL FROM reports r LEFT JOIN accounts a ON a.id = r.action_taken_by_account_id WHERE reports.id = r.id AND r.action_taken_by_account_id IS NOT NULL AND a.id IS NULL' )
end
2017-09-01 16:20:16 +02:00
desc 'Remove deprecated preview cards'
task remove_deprecated_preview_cards : :environment do
2017-09-03 12:39:39 +02:00
next unless ActiveRecord :: Base . connection . table_exists? 'deprecated_preview_cards'
2017-09-01 16:20:16 +02:00
2017-09-03 07:58:58 +02:00
class DeprecatedPreviewCard < ActiveRecord :: Base
self . inheritance_column = false
path = '/preview_cards/:attachment/:id_partition/:style/:filename'
if ENV [ 'S3_ENABLED' ] != 'true'
path = ( ENV [ 'PAPERCLIP_ROOT_PATH' ] || ':rails_root/public/system' ) + path
end
has_attached_file :image , styles : { original : '280x120>' } , convert_options : { all : '-quality 80 -strip' } , path : path
2017-09-01 16:20:16 +02:00
end
puts 'Delete records and associated files from deprecated preview cards? [y/N]: '
confirm = STDIN . gets . chomp
2017-09-07 03:55:06 +02:00
if confirm . casecmp ( 'y' ) . zero?
2017-09-01 16:20:16 +02:00
DeprecatedPreviewCard . in_batches . destroy_all
puts 'Drop deprecated preview cards table? [y/N]: '
confirm = STDIN . gets . chomp
2017-09-07 03:55:06 +02:00
if confirm . casecmp ( 'y' ) . zero?
2017-09-01 16:20:16 +02:00
ActiveRecord :: Migration . drop_table :deprecated_preview_cards
end
end
end
2017-12-07 03:37:43 +01:00
desc 'Migrate photo preview cards made before 2.1'
task migrate_photo_preview_cards : :environment do
status_ids = Status . joins ( :preview_cards )
. where ( preview_cards : { embed_url : '' , type : :photo } )
. reorder ( nil )
. group ( :id )
. pluck ( :id )
PreviewCard . where ( embed_url : '' , type : :photo ) . delete_all
LinkCrawlWorker . push_bulk status_ids
end
2017-12-24 16:14:33 +01:00
2018-03-27 04:33:57 +02:00
desc 'Find case-insensitive username duplicates of local users'
task find_duplicate_usernames : :environment do
include RoutingHelper
disable_log_stdout!
duplicate_masters = Account . find_by_sql ( 'SELECT * FROM accounts WHERE id IN (SELECT min(id) FROM accounts WHERE domain IS NULL GROUP BY lower(username) HAVING count(*) > 1)' )
pastel = Pastel . new
duplicate_masters . each do | account |
puts pastel . yellow ( " First of their name: " ) + pastel . bold ( account . username ) + " ( #{ admin_account_url ( account . id ) } ) "
Account . where ( 'lower(username) = ?' , account . username . downcase ) . where . not ( id : account . id ) . each do | duplicate |
puts " " + pastel . red ( " Duplicate: " ) + admin_account_url ( duplicate . id )
end
end
end
2018-01-18 20:29:56 +01:00
desc 'Remove all home feed regeneration markers'
task remove_regeneration_markers : :environment do
keys = Redis . current . keys ( 'account:*:regeneration' )
Redis . current . pipelined do
keys . each { | key | Redis . current . del ( key ) }
end
end
2017-12-24 16:14:33 +01:00
desc 'Check every known remote account and delete those that no longer exist in origin'
task purge_removed_accounts : :environment do
prepare_for_options!
options = { }
OptionParser . new do | opts |
opts . banner = 'Usage: rails mastodon:maintenance:purge_removed_accounts [options]'
opts . on ( '-f' , '--force' , 'Remove all encountered accounts without asking for confirmation' ) do
options [ :force ] = true
end
opts . on ( '-h' , '--help' , 'Display this message' ) do
puts opts
exit
end
end . parse!
disable_log_stdout!
total = Account . remote . where ( protocol : :activitypub ) . count
progress_bar = ProgressBar . create ( total : total , format : '%c/%C |%w>%i| %e' )
Account . remote . where ( protocol : :activitypub ) . partitioned . find_each do | account |
progress_bar . increment
begin
2018-03-24 12:49:54 +01:00
code = Request . new ( :head , account . uri ) . perform ( & :code )
2017-12-24 16:14:33 +01:00
rescue StandardError
# This could happen due to network timeout, DNS timeout, wrong SSL cert, etc,
# which should probably not lead to perceiving the account as deleted, so
# just skip till next time
next
end
2018-03-24 12:49:54 +01:00
if [ 404 , 410 ] . include? ( code )
2017-12-24 16:14:33 +01:00
if options [ :force ]
2018-02-21 16:30:46 +01:00
SuspendAccountService . new . call ( account )
2017-12-24 16:14:33 +01:00
account . destroy
else
progress_bar . pause
progress_bar . clear
print " \n It seems like #{ account . acct } no longer exists. Purge the account from the database? [Y/n]: " . colorize ( :yellow )
confirm = STDIN . gets . chomp
puts ''
progress_bar . resume
if confirm . casecmp ( 'n' ) . zero?
next
else
2018-02-21 16:30:46 +01:00
SuspendAccountService . new . call ( account )
2017-12-24 16:14:33 +01:00
account . destroy
end
end
end
end
end
2017-03-31 13:01:00 +02:00
end
2016-09-10 16:21:17 +02:00
end
2017-12-24 16:14:33 +01:00
def disable_log_stdout!
dev_null = Logger . new ( '/dev/null' )
Rails . logger = dev_null
ActiveRecord :: Base . logger = dev_null
HttpLog . configuration . logger = dev_null
Paperclip . options [ :log ] = false
end
def prepare_for_options!
2 . times { ARGV . shift }
end