Gab Social. All are welcome.

This commit is contained in:
robcolbert
2019-07-02 03:10:25 -04:00
commit bd0b5afc92
5366 changed files with 222812 additions and 0 deletions

20
lib/tasks/assets.rake Normal file
View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
def render_static_page(action, dest:, **opts)
html = ApplicationController.render(action, opts)
File.write(dest, html)
end
namespace :assets do
desc 'Generate static pages'
task generate_static_pages: :environment do
render_static_page 'errors/500', layout: 'error', dest: Rails.root.join('public', 'assets', '500.html')
end
end
if Rake::Task.task_defined?('assets:precompile')
Rake::Task['assets:precompile'].enhance do
Webpacker.manifest.refresh
Rake::Task['assets:generate_static_pages'].invoke
end
end

View File

@@ -0,0 +1,45 @@
# frozen_string_literal: true
if Rails.env.development?
task :set_annotation_options do
Annotate.set_defaults(
'routes' => 'false',
'position_in_routes' => 'before',
'position_in_class' => 'before',
'position_in_test' => 'before',
'position_in_fixture' => 'before',
'position_in_factory' => 'before',
'position_in_serializer' => 'before',
'show_foreign_keys' => 'false',
'show_indexes' => 'false',
'simple_indexes' => 'false',
'model_dir' => 'app/models',
'root_dir' => '',
'include_version' => 'false',
'require' => '',
'exclude_tests' => 'true',
'exclude_fixtures' => 'true',
'exclude_factories' => 'true',
'exclude_serializers' => 'true',
'exclude_scaffolds' => 'true',
'exclude_controllers' => 'true',
'exclude_helpers' => 'true',
'ignore_model_sub_dir' => 'false',
'ignore_columns' => nil,
'ignore_routes' => nil,
'ignore_unknown_models' => 'false',
'hide_limit_column_types' => 'integer,boolean',
'skip_on_db_migrate' => 'false',
'format_bare' => 'true',
'format_rdoc' => 'false',
'format_markdown' => 'false',
'sort' => 'false',
'force' => 'false',
'trace' => 'false',
'wrapper_open' => nil,
'wrapper_close' => nil
)
end
Annotate.load_tasks
end

75
lib/tasks/db.rake Normal file
View File

@@ -0,0 +1,75 @@
# frozen_string_literal: true
require_relative '../gabsocial/snowflake'
def each_schema_load_environment
# If we're in development, also run this for the test environment.
# This is a somewhat hacky way to do this, so here's why:
# 1. We have to define this before we load the schema, or we won't
# have a timestamp_id function when we get to it in the schema.
# 2. db:setup calls db:schema:load_if_ruby, which calls
# db:schema:load, which we define above as having a prerequisite
# of this task.
# 3. db:schema:load ends up running
# ActiveRecord::Tasks::DatabaseTasks.load_schema_current, which
# calls a private method `each_current_configuration`, which
# explicitly also does the loading for the `test` environment
# if the current environment is `development`, so we end up
# needing to do the same, and we can't even use the same method
# to do it.
if Rails.env.development?
test_conf = ActiveRecord::Base.configurations['test']
if test_conf['database']&.present?
ActiveRecord::Base.establish_connection(:test)
yield
ActiveRecord::Base.establish_connection(Rails.env.to_sym)
end
end
yield
end
namespace :db do
namespace :migrate do
desc 'Setup the db or migrate depending on state of db'
task setup: :environment do
begin
if ActiveRecord::Migrator.current_version.zero?
Rake::Task['db:migrate'].invoke
Rake::Task['db:seed'].invoke
end
rescue ActiveRecord::NoDatabaseError
Rake::Task['db:setup'].invoke
else
Rake::Task['db:migrate'].invoke
end
end
end
# Before we load the schema, define the timestamp_id function.
# Idiomatically, we might do this in a migration, but then it
# wouldn't end up in schema.rb, so we'd need to figure out a way to
# get it in before doing db:setup as well. This is simpler, and
# ensures it's always in place.
Rake::Task['db:schema:load'].enhance ['db:define_timestamp_id']
# After we load the schema, make sure we have sequences for each
# table using timestamp IDs.
Rake::Task['db:schema:load'].enhance do
Rake::Task['db:ensure_id_sequences_exist'].invoke
end
task :define_timestamp_id do
each_schema_load_environment do
GabSocial::Snowflake.define_timestamp_id
end
end
task :ensure_id_sequences_exist do
each_schema_load_environment do
GabSocial::Snowflake.ensure_id_sequences_exist
end
end
end

58
lib/tasks/emojis.rake Normal file
View File

@@ -0,0 +1,58 @@
# frozen_string_literal: true
def codepoints_to_filename(codepoints)
codepoints.downcase.gsub(/\A[0]+/, '').tr(' ', '-')
end
def codepoints_to_unicode(codepoints)
if codepoints.include?(' ')
codepoints.split(' ').map(&:hex).pack('U*')
else
[codepoints.hex].pack('U')
end
end
namespace :emojis do
desc 'Generate a unicode to filename mapping'
task :generate do
source = 'http://www.unicode.org/Public/emoji/11.0/emoji-test.txt'
codes = []
dest = Rails.root.join('app', 'javascript', 'gabsocial', 'features', 'emoji', 'emoji_map.json')
puts "Downloading emojos from source... (#{source})"
HTTP.get(source).to_s.split("\n").each do |line|
next if line.start_with? '#'
parts = line.split(';').map(&:strip)
next if parts.size < 2
codes << [parts[0], parts[1].start_with?('fully-qualified')]
end
grouped_codes = codes.reduce([]) do |agg, current|
if current[1]
agg << [current[0]]
else
agg.last << current[0]
agg
end
end
existence_maps = grouped_codes.map { |c| c.map { |cc| [cc, File.exist?(Rails.root.join('public', 'emoji', codepoints_to_filename(cc) + '.svg'))] }.to_h }
map = {}
existence_maps.each do |group|
existing_one = group.key(true)
next if existing_one.nil?
group.each_key do |key|
map[codepoints_to_unicode(key)] = codepoints_to_filename(existing_one)
end
end
map = map.sort { |a, b| a[0].size <=> b[0].size }.to_h
File.write(dest, Oj.dump(map))
puts "Wrote emojo to destination! (#{dest})"
end
end

408
lib/tasks/gabsocial.rake Normal file
View File

@@ -0,0 +1,408 @@
# frozen_string_literal: true
require 'tty-command'
require 'tty-prompt'
namespace :gabsocial do
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)
%w(SECRET_KEY_BASE OTP_SECRET).each do |key|
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 Gab Social?')
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' : 'gabsocial_production'
q.modify :strip
end
env['DB_USER'] = prompt.ask('Name of PostgreSQL user:') do |q|
q.required true
q.default using_docker ? 'postgres' : 'gabsocial'
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
env['REDIS_PASSWORD'] = prompt.ask('Redis password:') do |q|
q.required false
q.default nil
q.modify :strip
end
redis_options = {
host: env['REDIS_HOST'],
port: env['REDIS_PORT'],
password: env['REDIS_PASSWORD'],
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_ALIAS_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
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
env['SMTP_PORT'] = prompt.ask('SMTP port:') do |q|
q.required true
q.default 587
q.convert :int
end
env['SMTP_LOGIN'] = prompt.ask('SMTP username:') do |q|
q.modify :strip
end
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
env['SMTP_OPENSSL_VERIFY_MODE'] = prompt.select('SMTP OpenSSL verify mode:', %w(none peer client_once fail_if_no_peer_cert))
end
env['SMTP_FROM_ADDRESS'] = prompt.ask('E-mail address to send e-mails "from":') do |q|
q.required true
q.default "Gab Social <noreply@#{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'],
authentication: env['SMTP_AUTH_METHOD'] == 'none' ? nil : env['SMTP_AUTH_METHOD'] || :plain,
openssl_verify_mode: env['SMTP_OPENSSL_VERIFY_MODE'],
enable_starttls_auto: true,
}
ActionMailer::Base.default_options = {
from: env['SMTP_FROM_ADDRESS'],
}
mail = ActionMailer::Base.new.mail to: send_to, subject: 'Test', body: 'Gab Social SMTP configuration works!'
mail.deliver
break
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 gabsocial:setup on #{Time.now.utc}\n\n" + env.each_pair.map { |key, value| "#{key}=#{value}" }.join("\n") + "\n")
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
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\n"
if cmd.run!({ RAILS_ENV: 'production', SAFETY_ASSURED: 1 }, :rails, 'db:setup').failure?
prompt.error 'That failed! Perhaps your configuration is not right'
else
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\n"
if cmd.run!({ RAILS_ENV: 'production' }, :rails, 'assets:precompile').failure?
prompt.error 'That failed! Maybe you need swap space?'
else
prompt.say 'Done!'
end
end
prompt.say "\n"
prompt.ok 'All done! You can now power on the Gab Social 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
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
end
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

57
lib/tasks/repo.rake Normal file
View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
namespace :repo do
desc 'Generate the AUTHORS.md file'
task :authors do
# Gab AI, Inc., does not disclose information about contributors to the Gab
# Social project in this way. Please refer to our public git repository or
# contact us directly at legal@gab.com with any questions about the
# contributors to Gab Social.
end
desc 'Replace pull requests with authors in the CHANGELOG.md file'
task :changelog do
path = Rails.root.join('CHANGELOG.md')
tmp = Tempfile.new
HttpLog.config.compact_log = true
begin
File.open(path, 'r') do |file|
file.each_line do |line|
if line.start_with?('-')
new_line = line.gsub(/#([[:digit:]]+)*/) do |pull_request_reference|
pull_request_number = pull_request_reference[1..-1]
response = nil
loop do
response = HTTP.headers('Authorization' => "token #{ENV['GITHUB_API_TOKEN']}").get("https://api.github.com/repos/gab-ai-inc/gab-social/pulls/#{pull_request_number}")
if response.code == 403
sleep_for = (response.headers['X-RateLimit-Reset'].to_i - Time.now.to_i).abs
puts "Sleeping for #{sleep_for} seconds to get over rate limit"
sleep sleep_for
else
break
end
end
pull_request = Oj.load(response.to_s)
"[#{pull_request['user']['login']}](#{pull_request['html_url']})"
end
tmp.puts new_line
else
tmp.puts line
end
end
end
tmp.close
FileUtils.mv(tmp.path, path)
ensure
tmp.close
tmp.unlink
end
end
end

19
lib/tasks/statistics.rake Normal file
View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
task stats: 'gabsocial:stats'
namespace :gabsocial do
desc 'Report code statistics (KLOCs, etc)'
task :stats do
require 'rails/code_statistics'
[
%w(App\ Libraries app/lib),
%w(Presenters app/presenters),
%w(Services app/services),
%w(Validators app/validators),
%w(Workers app/workers),
].each do |name, dir|
::STATS_DIRECTORIES << [name, Rails.root.join(dir)]
end
end
end