mastodon
Este commit está contenido en:
267
development/mastodon/.env.production
Archivo normal
267
development/mastodon/.env.production
Archivo normal
@@ -0,0 +1,267 @@
|
||||
# Service dependencies
|
||||
# You may set REDIS_URL instead for more advanced options
|
||||
# You may also set REDIS_NAMESPACE to share Redis between multiple Mastodon servers
|
||||
REDIS_HOST=redis-mastodon
|
||||
REDIS_PORT=6379
|
||||
# You may set DATABASE_URL instead for more advanced options
|
||||
DB_HOST=postgres-mastodon
|
||||
DB_USER=postgres
|
||||
DB_NAME=mastodon
|
||||
DB_PASS=m4st0d0n.
|
||||
DB_PORT=5432
|
||||
# Optional ElasticSearch configuration
|
||||
# You may also set ES_PREFIX to share the same cluster between multiple Mastodon servers (falls back to REDIS_NAMESPACE if not set)
|
||||
ES_ENABLED=false
|
||||
ES_HOST=elastic-mastodon
|
||||
ES_PORT=9200
|
||||
|
||||
# Federation
|
||||
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
|
||||
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
|
||||
LOCAL_DOMAIN=mastodon.hatthieves.es
|
||||
|
||||
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
|
||||
#LOCAL_HTTPS=true
|
||||
|
||||
# Use this only if you need to run mastodon on a different domain than the one used for federation.
|
||||
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
|
||||
# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
|
||||
# WEB_DOMAIN=mastodon.example.com
|
||||
|
||||
# Use this if you want to have several aliases handler@example1.com
|
||||
# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not
|
||||
# be added. Comma separated values
|
||||
# ALTERNATE_DOMAINS=example1.com,example2.com
|
||||
|
||||
# Application secrets
|
||||
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
|
||||
SECRET_KEY_BASE=f3547824e9d0dd55998bdb68fd535e492317c79cd203ac23b5edf8c2e0cb354f0c0629e8ee753336a45f844a7ac0a33a5c7a2cc07aacd063138f100c54528586
|
||||
OTP_SECRET=
|
||||
|
||||
# VAPID keys (used for push notifications
|
||||
# You can generate the keys using the following command (first is the private key, second is the public one)
|
||||
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
||||
# be invalidated, requiring the users to access the website again to resubscribe.
|
||||
#
|
||||
# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose)
|
||||
#
|
||||
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
||||
VAPID_PRIVATE_KEY=5RY2CYjaovGoOEVXr9uElbbAf2tJI-2vsvQFmRL8wNk=
|
||||
VAPID_PUBLIC_KEY=BAsz5tqs1CigXepyU3L5oDVnOGnCH0W-BoED-r0k9P6y2vdUNXf0mukB3-Ri0Mykrthu5oqx94YXSIWiFNk0CLk=
|
||||
|
||||
# Registrations
|
||||
# Single user mode will disable registrations and redirect frontpage to the first profile
|
||||
# SINGLE_USER_MODE=true
|
||||
# Prevent registrations with following e-mail domains
|
||||
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
|
||||
# Only allow registrations with the following e-mail domains
|
||||
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
|
||||
|
||||
# Optionally change default language
|
||||
DEFAULT_LOCALE=es
|
||||
|
||||
# E-mail configuration
|
||||
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
|
||||
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
|
||||
# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
|
||||
# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
|
||||
SMTP_SERVER=172.200.0.101
|
||||
SMTP_PORT=587
|
||||
SMTP_LOGIN=webmaster@hatthieves.es
|
||||
SMTP_PASSWORD=w3bm4st3r.
|
||||
SMTP_FROM_ADDRESS=notifications@mastodon.hatthieves.es
|
||||
#SMTP_REPLY_TO=
|
||||
SMTP_DOMAIN=@hatthieves.es
|
||||
SMTP_DELIVERY_METHOD=smtp
|
||||
SMTP_AUTH_METHOD=plain
|
||||
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
SMTP_OPENSSL_VERIFY_MODE=none
|
||||
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
SMTP_TLS=true
|
||||
|
||||
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
|
||||
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
|
||||
# PAPERCLIP_ROOT_URL=/system
|
||||
|
||||
# Optional asset host for multi-server setups
|
||||
# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN
|
||||
# if WEB_DOMAIN is not set. For example, the server may have the
|
||||
# following header field:
|
||||
# Access-Control-Allow-Origin: https://example.com/
|
||||
# CDN_HOST=https://assets.example.com
|
||||
|
||||
# S3 (optional)
|
||||
# The attachment host must allow cross origin request from WEB_DOMAIN or
|
||||
# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the
|
||||
# following header field:
|
||||
# Access-Control-Allow-Origin: https://192.168.1.123:9000/
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=http
|
||||
# S3_HOSTNAME=192.168.1.123:9000
|
||||
|
||||
# S3 (Minio Config (optional) Please check Minio instance for details)
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=
|
||||
# S3_ENDPOINT=
|
||||
# S3_SIGNATURE_VERSION=
|
||||
|
||||
# Google Cloud Storage (optional)
|
||||
# Use S3 compatible API. Since GCS does not support Multipart Upload,
|
||||
# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# S3_ENABLED=true
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=storage.googleapis.com
|
||||
# S3_ENDPOINT=https://storage.googleapis.com
|
||||
# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
|
||||
|
||||
# Swift (optional)
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# SWIFT_ENABLED=true
|
||||
# SWIFT_USERNAME=
|
||||
# For Keystone V3, the value for SWIFT_TENANT should be the project name
|
||||
# SWIFT_TENANT=
|
||||
# SWIFT_PASSWORD=
|
||||
# Some OpenStack V3 providers require PROJECT_ID (optional)
|
||||
# SWIFT_PROJECT_ID=
|
||||
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
|
||||
# issues with token rate-limiting during high load.
|
||||
# SWIFT_AUTH_URL=
|
||||
# SWIFT_CONTAINER=
|
||||
# SWIFT_OBJECT_URL=
|
||||
# SWIFT_REGION=
|
||||
# Defaults to 'default'
|
||||
# SWIFT_DOMAIN_NAME=
|
||||
# Defaults to 60 seconds. Set to 0 to disable
|
||||
# SWIFT_CACHE_TTL=
|
||||
|
||||
# Optional alias for S3 (e.g. to serve files on a custom domain, possibly using Cloudfront or Cloudflare)
|
||||
# S3_ALIAS_HOST=
|
||||
|
||||
# Streaming API integration
|
||||
# STREAMING_API_BASE_URL=https://mastodon.hatthieves.es/api/v1/streaming
|
||||
|
||||
# Advanced settings
|
||||
# If you need to use pgBouncer, you need to disable prepared statements:
|
||||
# PREPARED_STATEMENTS=false
|
||||
|
||||
# Cluster number setting for streaming API server.
|
||||
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
|
||||
STREAMING_CLUSTER_NUM=3
|
||||
WEB_CONCURRENCY=2
|
||||
MAX_THREADS=2
|
||||
|
||||
# Docker mastodon user
|
||||
# If you use Docker, you may want to assign UID/GID manually.
|
||||
# UID=1000
|
||||
# GID=1000
|
||||
|
||||
# LDAP authentication (optional)
|
||||
# LDAP_ENABLED=true
|
||||
# LDAP_HOST=localhost
|
||||
# LDAP_PORT=389
|
||||
# LDAP_METHOD=simple_tls
|
||||
# LDAP_BASE=
|
||||
# LDAP_BIND_DN=
|
||||
# LDAP_PASSWORD=
|
||||
# LDAP_UID=cn
|
||||
# LDAP_MAIL=mail
|
||||
# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email}))
|
||||
# LDAP_UID_CONVERSION_ENABLED=true
|
||||
# LDAP_UID_CONVERSION_SEARCH=., -
|
||||
# LDAP_UID_CONVERSION_REPLACE=_
|
||||
|
||||
# PAM authentication (optional)
|
||||
# PAM authentication uses for the email generation the "email" pam variable
|
||||
# and optional as fallback PAM_DEFAULT_SUFFIX
|
||||
# The pam environment variable "email" is provided by:
|
||||
# https://github.com/devkral/pam_email_extractor
|
||||
# PAM_ENABLED=true
|
||||
# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
|
||||
# PAM_EMAIL_DOMAIN=example.com
|
||||
# Name of the pam service (pam "auth" section is evaluated)
|
||||
# PAM_DEFAULT_SERVICE=rpam
|
||||
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
|
||||
# PAM_CONTROLLED_SERVICE=rpam
|
||||
|
||||
# Global OAuth settings (optional) :
|
||||
# If you have only one strategy, you may want to enable this
|
||||
# OAUTH_REDIRECT_AT_SIGN_IN=true
|
||||
|
||||
# Optional CAS authentication (cf. omniauth-cas) :
|
||||
# CAS_ENABLED=true
|
||||
# CAS_URL=https://sso.myserver.com/
|
||||
# CAS_HOST=sso.myserver.com/
|
||||
# CAS_PORT=443
|
||||
# CAS_SSL=true
|
||||
# CAS_VALIDATE_URL=
|
||||
# CAS_CALLBACK_URL=
|
||||
# CAS_LOGOUT_URL=
|
||||
# CAS_LOGIN_URL=
|
||||
# CAS_UID_FIELD='user'
|
||||
# CAS_CA_PATH=
|
||||
# CAS_DISABLE_SSL_VERIFICATION=false
|
||||
# CAS_UID_KEY='user'
|
||||
# CAS_NAME_KEY='name'
|
||||
# CAS_EMAIL_KEY='email'
|
||||
# CAS_NICKNAME_KEY='nickname'
|
||||
# CAS_FIRST_NAME_KEY='firstname'
|
||||
# CAS_LAST_NAME_KEY='lastname'
|
||||
# CAS_LOCATION_KEY='location'
|
||||
# CAS_IMAGE_KEY='image'
|
||||
# CAS_PHONE_KEY='phone'
|
||||
|
||||
# Optional SAML authentication (cf. omniauth-saml)
|
||||
# SAML_ENABLED=true
|
||||
# SAML_ACS_URL=http://localhost:3000/auth/auth/saml/callback
|
||||
# SAML_ISSUER=https://example.com
|
||||
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
|
||||
# SAML_IDP_CERT=
|
||||
# SAML_IDP_CERT_FINGERPRINT=
|
||||
# SAML_NAME_IDENTIFIER_FORMAT=
|
||||
# SAML_CERT=
|
||||
# SAML_PRIVATE_KEY=
|
||||
# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
|
||||
# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
|
||||
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
|
||||
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
|
||||
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
|
||||
|
||||
# Use HTTP proxy for outgoing request (optional)
|
||||
# http_proxy=http://gateway.local:8118
|
||||
# Access control for hidden service.
|
||||
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
|
||||
|
||||
# Authorized fetch mode (optional)
|
||||
# Require remote servers to authentify when fetching toots, see
|
||||
# https://docs.joinmastodon.org/admin/config/#authorized_fetch
|
||||
# AUTHORIZED_FETCH=true
|
||||
|
||||
# Whitelist mode (optional)
|
||||
# Only allow federation with whitelisted domains, see
|
||||
# https://docs.joinmastodon.org/admin/config/#whitelist_mode
|
||||
# WHITELIST_MODE=true
|
||||
|
||||
TRUSTED_PROXY_IP=82.223.3.135
|
||||
136
development/mastodon/application.rb
Archivo normal
136
development/mastodon/application.rb
Archivo normal
@@ -0,0 +1,136 @@
|
||||
require_relative 'boot'
|
||||
|
||||
require 'rails/all'
|
||||
|
||||
# Require the gems listed in Gemfile, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
require_relative '../app/lib/exceptions'
|
||||
require_relative '../lib/paperclip/attachment_extensions'
|
||||
require_relative '../lib/paperclip/lazy_thumbnail'
|
||||
require_relative '../lib/paperclip/gif_transcoder'
|
||||
require_relative '../lib/paperclip/video_transcoder'
|
||||
require_relative '../lib/paperclip/type_corrector'
|
||||
require_relative '../lib/mastodon/snowflake'
|
||||
require_relative '../lib/mastodon/version'
|
||||
require_relative '../lib/devise/two_factor_ldap_authenticatable'
|
||||
require_relative '../lib/devise/two_factor_pam_authenticatable'
|
||||
require_relative '../lib/chewy/strategy/custom_sidekiq'
|
||||
|
||||
Dotenv::Railtie.load
|
||||
|
||||
Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true'
|
||||
|
||||
require_relative '../lib/mastodon/redis_config'
|
||||
|
||||
module Mastodon
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
config.load_defaults 5.2
|
||||
|
||||
# Settings in config/environments/* take precedence over those specified here.
|
||||
# Application configuration should go into files in config/initializers
|
||||
# -- all .rb files in that directory are automatically loaded.
|
||||
|
||||
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
||||
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
||||
# config.time_zone = 'Central Time (US & Canada)'
|
||||
|
||||
# All translations from config/locales/*.rb,yml are auto loaded.
|
||||
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
||||
config.i18n.available_locales = [
|
||||
:ar,
|
||||
:ast,
|
||||
:bg,
|
||||
:bn,
|
||||
:br,
|
||||
:ca,
|
||||
:co,
|
||||
:cs,
|
||||
:cy,
|
||||
:da,
|
||||
:de,
|
||||
:el,
|
||||
:en,
|
||||
:eo,
|
||||
:'es-AR',
|
||||
:es,
|
||||
:et,
|
||||
:eu,
|
||||
:fa,
|
||||
:fi,
|
||||
:fr,
|
||||
:ga,
|
||||
:gl,
|
||||
:he,
|
||||
:hi,
|
||||
:hr,
|
||||
:hu,
|
||||
:hy,
|
||||
:id,
|
||||
:io,
|
||||
:is,
|
||||
:it,
|
||||
:ja,
|
||||
:ka,
|
||||
:kab,
|
||||
:kk,
|
||||
:kn,
|
||||
:ko,
|
||||
:lt,
|
||||
:lv,
|
||||
:mk,
|
||||
:ml,
|
||||
:mr,
|
||||
:ms,
|
||||
:nl,
|
||||
:nn,
|
||||
:no,
|
||||
:oc,
|
||||
:pl,
|
||||
:'pt-BR',
|
||||
:'pt-PT',
|
||||
:ro,
|
||||
:ru,
|
||||
:sk,
|
||||
:sl,
|
||||
:sq,
|
||||
:'sr-Latn',
|
||||
:sr,
|
||||
:sv,
|
||||
:ta,
|
||||
:te,
|
||||
:th,
|
||||
:tr,
|
||||
:uk,
|
||||
:ur,
|
||||
:'zh-CN',
|
||||
:'zh-HK',
|
||||
:'zh-TW',
|
||||
]
|
||||
|
||||
config.i18n.default_locale = ENV['DEFAULT_LOCALE']&.to_sym
|
||||
|
||||
unless config.i18n.available_locales.include?(config.i18n.default_locale)
|
||||
config.i18n.default_locale = :en
|
||||
end
|
||||
|
||||
# config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
||||
# config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
|
||||
|
||||
config.active_job.queue_adapter = :sidekiq
|
||||
|
||||
# config.middleware.use Rack::Attack
|
||||
config.middleware.use Rack::Deflater
|
||||
|
||||
config.to_prepare do
|
||||
Doorkeeper::AuthorizationsController.layout 'modal'
|
||||
Doorkeeper::AuthorizedApplicationsController.layout 'admin'
|
||||
Doorkeeper::Application.send :include, ApplicationExtension
|
||||
Devise::FailureApp.send :include, AbstractController::Callbacks
|
||||
Devise::FailureApp.send :include, HttpAcceptLanguage::EasyAccess
|
||||
Devise::FailureApp.send :include, Localized
|
||||
end
|
||||
end
|
||||
end
|
||||
124
development/mastodon/base_controller.rb
Archivo normal
124
development/mastodon/base_controller.rb
Archivo normal
@@ -0,0 +1,124 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::BaseController < ApplicationController
|
||||
DEFAULT_STATUSES_LIMIT = 200
|
||||
DEFAULT_ACCOUNTS_LIMIT = 400
|
||||
|
||||
include RateLimitHeaders
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||
before_action :set_cache_headers
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
skip_around_action :set_locale
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotUnique do
|
||||
render json: { error: 'Duplicate record' }, status: 422
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render json: { error: 'Record not found' }, status: 404
|
||||
end
|
||||
|
||||
rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
|
||||
render json: { error: 'Remote data could not be fetched' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from OpenSSL::SSL::SSLError do
|
||||
render json: { error: 'Remote SSL certificate could not be verified' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from Mastodon::NotPermittedError do
|
||||
render json: { error: 'This action is not allowed' }, status: 403
|
||||
end
|
||||
|
||||
rescue_from Mastodon::RaceConditionError do
|
||||
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from Mastodon::RateLimitExceededError do
|
||||
render json: { error: I18n.t('errors.429') }, status: 429
|
||||
end
|
||||
|
||||
rescue_from ActionController::ParameterMissing do |e|
|
||||
render json: { error: e.to_s }, status: 400
|
||||
end
|
||||
|
||||
def doorkeeper_unauthorized_render_options(error: nil)
|
||||
{ json: { error: (error.try(:description) || 'Not authorized') } }
|
||||
end
|
||||
|
||||
def doorkeeper_forbidden_render_options(*)
|
||||
{ json: { error: 'This action is outside the authorized scopes' } }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def set_pagination_headers(next_path = nil, prev_path = nil)
|
||||
links = []
|
||||
links << [next_path, [%w(rel next)]] if next_path
|
||||
links << [prev_path, [%w(rel prev)]] if prev_path
|
||||
response.headers['Link'] = LinkHeader.new(links) unless links.empty?
|
||||
end
|
||||
|
||||
def limit_param(default_limit)
|
||||
return default_limit unless params[:limit]
|
||||
[params[:limit].to_i.abs, default_limit * 2].min
|
||||
end
|
||||
|
||||
def params_slice(*keys)
|
||||
params.slice(*keys).permit(*keys)
|
||||
end
|
||||
|
||||
def current_resource_owner
|
||||
@current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
|
||||
end
|
||||
|
||||
def current_user
|
||||
current_resource_owner || super
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
def require_authenticated_user!
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user
|
||||
end
|
||||
|
||||
def require_user!
|
||||
if !current_user
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||
elsif current_user.disabled?
|
||||
render json: { error: 'Your login is currently disabled' }, status: 403
|
||||
elsif !current_user.confirmed?
|
||||
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
|
||||
elsif !current_user.approved?
|
||||
render json: { error: 'Your login is currently pending approval' }, status: 403
|
||||
else
|
||||
set_user_activity
|
||||
end
|
||||
end
|
||||
|
||||
def render_empty
|
||||
render json: {}, status: 200
|
||||
end
|
||||
|
||||
def authorize_if_got_token!(*scopes)
|
||||
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
authorized_fetch_mode?
|
||||
end
|
||||
end
|
||||
467
development/mastodon/base_optimizer.js
Archivo normal
467
development/mastodon/base_optimizer.js
Archivo normal
@@ -0,0 +1,467 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
var _fs = require('fs');
|
||||
|
||||
var _os = require('os');
|
||||
|
||||
var _os2 = _interopRequireDefault(_os);
|
||||
|
||||
var _boom = require('boom');
|
||||
|
||||
var _boom2 = _interopRequireDefault(_boom);
|
||||
|
||||
var _miniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
var _miniCssExtractPlugin2 = _interopRequireDefault(_miniCssExtractPlugin);
|
||||
|
||||
var _terserWebpackPlugin = require('terser-webpack-plugin');
|
||||
|
||||
var _terserWebpackPlugin2 = _interopRequireDefault(_terserWebpackPlugin);
|
||||
|
||||
var _webpack = require('webpack');
|
||||
|
||||
var _webpack2 = _interopRequireDefault(_webpack);
|
||||
|
||||
var _Stats = require('webpack/lib/Stats');
|
||||
|
||||
var _Stats2 = _interopRequireDefault(_Stats);
|
||||
|
||||
var _threadLoader = require('thread-loader');
|
||||
|
||||
var threadLoader = _interopRequireWildcard(_threadLoader);
|
||||
|
||||
var _webpackMerge = require('webpack-merge');
|
||||
|
||||
var _webpackMerge2 = _interopRequireDefault(_webpackMerge);
|
||||
|
||||
var _dynamic_dll_plugin = require('./dynamic_dll_plugin');
|
||||
|
||||
var _lodash = require('lodash');
|
||||
|
||||
var _utils = require('../utils');
|
||||
|
||||
var _public_path_placeholder = require('./public_path_placeholder');
|
||||
|
||||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
const POSTCSS_CONFIG_PATH = require.resolve('./postcss.config');
|
||||
const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset');
|
||||
const BABEL_EXCLUDE_RE = [/[\/\\](webpackShims|node_modules|bower_components)[\/\\]/];
|
||||
const STATS_WARNINGS_FILTER = new RegExp(['(export .* was not found in)', '|(chunk .* \\[mini-css-extract-plugin\\]\\\nConflicting order between:)'].join(''));
|
||||
|
||||
class BaseOptimizer {
|
||||
constructor(opts) {
|
||||
this.logWithMetadata = opts.logWithMetadata || (() => null);
|
||||
this.uiBundles = opts.uiBundles;
|
||||
this.profile = opts.profile || false;
|
||||
|
||||
switch (opts.sourceMaps) {
|
||||
case true:
|
||||
this.sourceMaps = 'source-map';
|
||||
break;
|
||||
|
||||
case 'fast':
|
||||
this.sourceMaps = 'cheap-module-eval-source-map';
|
||||
break;
|
||||
|
||||
default:
|
||||
this.sourceMaps = opts.sourceMaps || false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Run some pre loading in order to prevent
|
||||
// high delay when booting thread loader workers
|
||||
this.warmupThreadLoaderPool();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.compiler) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const compilerConfig = this.getConfig();
|
||||
this.compiler = (0, _webpack2.default)(compilerConfig);
|
||||
|
||||
// register the webpack compiler hooks
|
||||
// for the base optimizer
|
||||
this.registerCompilerHooks();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
registerCompilerHooks() {
|
||||
this.registerCompilerDoneHook();
|
||||
}
|
||||
|
||||
registerCompilerDoneHook() {
|
||||
this.compiler.hooks.done.tap('base_optimizer-done', stats => {
|
||||
// We are not done while we have an additional
|
||||
// compilation pass to run
|
||||
// We also don't need to emit the stats if we don't have
|
||||
// the profile option set
|
||||
if (!this.profile || stats.compilation.needAdditionalPass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = this.uiBundles.resolvePath('stats.json');
|
||||
const content = JSON.stringify(stats.toJson());
|
||||
(0, _fs.writeFile)(path, content, function (err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
warmupThreadLoaderPool() {
|
||||
const baseModules = ['babel-loader', BABEL_PRESET_PATH];
|
||||
|
||||
const nonDistributableOnlyModules = !_utils.IS_KIBANA_DISTRIBUTABLE ? ['ts-loader'] : [];
|
||||
|
||||
threadLoader.warmup(
|
||||
// pool options, like passed to loader options
|
||||
// must match loader options to boot the correct pool
|
||||
this.getThreadLoaderPoolConfig(), [
|
||||
// modules to load on the pool
|
||||
...baseModules, ...nonDistributableOnlyModules]);
|
||||
}
|
||||
|
||||
getThreadPoolCpuCount() {
|
||||
const cpus = _os2.default.cpus();
|
||||
if (!cpus) {
|
||||
// sometimes this call returns undefined so we fall back to 1: https://github.com/nodejs/node/issues/19022
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Math.max(1, Math.min(cpus.length - 1, 7));
|
||||
}
|
||||
|
||||
getThreadLoaderPoolConfig() {
|
||||
// Calculate the node options from the NODE_OPTIONS env var
|
||||
const parsedNodeOptions = process.env.NODE_OPTIONS ? process.env.NODE_OPTIONS.split(/\s/) : [];
|
||||
|
||||
return {
|
||||
name: 'optimizer-thread-loader-main-pool',
|
||||
workers: 1,
|
||||
workerParallelJobs: 1,
|
||||
// This is a safe check in order to set
|
||||
// the parent node options applied from
|
||||
// the NODE_OPTIONS env var for every launched worker.
|
||||
// Otherwise, if the user sets max_old_space_size, as they
|
||||
// are used to, into NODE_OPTIONS, it won't affect the workers.
|
||||
workerNodeArgs: parsedNodeOptions,
|
||||
poolParallelJobs: 1,
|
||||
poolTimeout: this.uiBundles.isDevMode() ? Infinity : 2000
|
||||
};
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
function getStyleLoaderExtractor() {
|
||||
return [_miniCssExtractPlugin2.default.loader];
|
||||
}
|
||||
|
||||
function getStyleLoaders(preProcessors = [], postProcessors = []) {
|
||||
return [...postProcessors, {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
// importLoaders needs to know the number of loaders that follow this one,
|
||||
// so we add 1 (for the postcss-loader) to the length of the preProcessors
|
||||
// array that we merge into this array
|
||||
importLoaders: 1 + preProcessors.length
|
||||
}
|
||||
}, {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
config: {
|
||||
path: POSTCSS_CONFIG_PATH
|
||||
}
|
||||
}
|
||||
}, ...preProcessors];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a cache loader if we're running in dev mode. The reason we're not adding
|
||||
* the cache-loader when running in production mode is that it creates cache
|
||||
* files in optimize/.cache that are not necessary for distributable versions
|
||||
* of Kibana and just make compressing and extracting it more difficult.
|
||||
*/
|
||||
const maybeAddCacheLoader = (cacheName, loaders) => {
|
||||
if (_utils.IS_KIBANA_DISTRIBUTABLE) {
|
||||
return loaders;
|
||||
}
|
||||
|
||||
return [{
|
||||
loader: 'cache-loader',
|
||||
options: {
|
||||
cacheDirectory: this.uiBundles.getCacheDirectory(cacheName)
|
||||
}
|
||||
}, ...loaders];
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the selection rules for a loader that will only pass for
|
||||
* source files that are eligible for automatic transpilation.
|
||||
*/
|
||||
const createSourceFileResourceSelector = test => {
|
||||
return [{
|
||||
test,
|
||||
exclude: BABEL_EXCLUDE_RE.concat(this.uiBundles.getWebpackNoParseRules())
|
||||
}, {
|
||||
test,
|
||||
include: /[\/\\]node_modules[\/\\]x-pack[\/\\]/,
|
||||
exclude: /[\/\\]node_modules[\/\\]x-pack[\/\\](.+?[\/\\])*node_modules[\/\\]/
|
||||
}];
|
||||
};
|
||||
|
||||
const commonConfig = {
|
||||
mode: 'development',
|
||||
node: { fs: 'empty' },
|
||||
context: (0, _utils.fromRoot)('.'),
|
||||
cache: true,
|
||||
entry: this.uiBundles.toWebpackEntries(),
|
||||
|
||||
devtool: this.sourceMaps,
|
||||
profile: this.profile || false,
|
||||
|
||||
output: {
|
||||
path: this.uiBundles.getWorkingDir(),
|
||||
filename: '[name].bundle.js',
|
||||
sourceMapFilename: '[file].map',
|
||||
publicPath: _public_path_placeholder.PUBLIC_PATH_PLACEHOLDER,
|
||||
devtoolModuleFilenameTemplate: '[absolute-resource-path]'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
commons: {
|
||||
name: 'commons',
|
||||
chunks: 'initial',
|
||||
minChunks: 2,
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
},
|
||||
noEmitOnErrors: true
|
||||
},
|
||||
|
||||
plugins: [new _dynamic_dll_plugin.DynamicDllPlugin({
|
||||
uiBundles: this.uiBundles,
|
||||
threadLoaderPoolConfig: this.getThreadLoaderPoolConfig(),
|
||||
logWithMetadata: this.logWithMetadata
|
||||
}), new _miniCssExtractPlugin2.default({
|
||||
filename: '[name].style.css'
|
||||
}),
|
||||
|
||||
// replace imports for `uiExports/*` modules with a synthetic module
|
||||
// created by create_ui_exports_module.js
|
||||
new _webpack2.default.NormalModuleReplacementPlugin(/^uiExports\//, resource => {
|
||||
// the map of uiExport types to module ids
|
||||
const extensions = this.uiBundles.getAppExtensions();
|
||||
|
||||
// everything following the first / in the request is
|
||||
// treated as a type of appExtension
|
||||
const type = resource.request.slice(resource.request.indexOf('/') + 1);
|
||||
|
||||
resource.request = [
|
||||
// the "val-loader" is used to execute create_ui_exports_module
|
||||
// and use its return value as the source for the module in the
|
||||
// bundle. This allows us to bypass writing to the file system
|
||||
require.resolve('val-loader'), '!', require.resolve('./create_ui_exports_module'), '?',
|
||||
// this JSON is parsed by create_ui_exports_module and determines
|
||||
// what require() calls it will execute within the bundle
|
||||
JSON.stringify({ type, modules: extensions[type] || [] })].join('');
|
||||
}), ...this.uiBundles.getWebpackPluginProviders().map(provider => provider(_webpack2.default))],
|
||||
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.less$/,
|
||||
use: [...getStyleLoaderExtractor(), ...getStyleLoaders(['less-loader'], maybeAddCacheLoader('less', []))]
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
use: [...getStyleLoaderExtractor(), ...getStyleLoaders([], maybeAddCacheLoader('css', []))]
|
||||
}, {
|
||||
test: /\.(html|tmpl)$/,
|
||||
loader: 'raw-loader'
|
||||
}, {
|
||||
test: /\.(png|jpg|gif|jpeg)$/,
|
||||
loader: ['url-loader']
|
||||
}, {
|
||||
test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/,
|
||||
loader: 'file-loader'
|
||||
}, {
|
||||
resource: createSourceFileResourceSelector(/\.js$/),
|
||||
use: maybeAddCacheLoader('babel', [{
|
||||
loader: 'thread-loader',
|
||||
options: this.getThreadLoaderPoolConfig()
|
||||
}, {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: false,
|
||||
presets: [BABEL_PRESET_PATH]
|
||||
}
|
||||
}])
|
||||
}, ...this.uiBundles.getPostLoaders().map(loader => _extends({
|
||||
enforce: 'post'
|
||||
}, loader))],
|
||||
noParse: this.uiBundles.getWebpackNoParseRules()
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js', '.json'],
|
||||
mainFields: ['browser', 'browserify', 'main'],
|
||||
modules: ['webpackShims', (0, _utils.fromRoot)('webpackShims'), 'node_modules', (0, _utils.fromRoot)('node_modules')],
|
||||
alias: this.uiBundles.getAliases()
|
||||
},
|
||||
|
||||
performance: {
|
||||
// NOTE: we are disabling this as those hints
|
||||
// are more tailored for the final bundles result
|
||||
// and not for the webpack compilations performance itself
|
||||
hints: false
|
||||
}
|
||||
};
|
||||
|
||||
// when running from the distributable define an environment variable we can use
|
||||
// to exclude chunks of code, modules, etc.
|
||||
const isDistributableConfig = {
|
||||
plugins: [new _webpack2.default.DefinePlugin({
|
||||
'process.env': {
|
||||
'IS_KIBANA_DISTRIBUTABLE': `"true"`
|
||||
}
|
||||
})]
|
||||
};
|
||||
|
||||
// when running from source transpile TypeScript automatically
|
||||
const getSourceConfig = () => {
|
||||
// dev/typescript is deleted from the distributable, so only require it if we actually need the source config
|
||||
const { Project } = require('../dev/typescript');
|
||||
const browserProject = new Project((0, _utils.fromRoot)('tsconfig.browser.json'));
|
||||
|
||||
return {
|
||||
module: {
|
||||
rules: [{
|
||||
resource: createSourceFileResourceSelector(/\.tsx?$/),
|
||||
use: maybeAddCacheLoader('typescript', [{
|
||||
loader: 'thread-loader',
|
||||
options: this.getThreadLoaderPoolConfig()
|
||||
}, {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
happyPackMode: true,
|
||||
transpileOnly: true,
|
||||
experimentalWatchApi: true,
|
||||
onlyCompileBundledFiles: true,
|
||||
configFile: (0, _utils.fromRoot)('tsconfig.json'),
|
||||
compilerOptions: _extends({}, browserProject.config.compilerOptions, {
|
||||
sourceMap: Boolean(this.sourceMaps)
|
||||
})
|
||||
}
|
||||
}])
|
||||
}]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx']
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// We need to add react-addons (and a few other bits) for enzyme to work.
|
||||
// https://github.com/airbnb/enzyme/blob/master/docs/guides/webpack.md
|
||||
const supportEnzymeConfig = {
|
||||
externals: {
|
||||
'mocha': 'mocha',
|
||||
'react/lib/ExecutionEnvironment': true,
|
||||
'react/addons': true,
|
||||
'react/lib/ReactContext': true
|
||||
}
|
||||
};
|
||||
|
||||
const watchingConfig = {
|
||||
plugins: [new _webpack2.default.WatchIgnorePlugin([
|
||||
// When our bundle entry files are fresh they cause webpack
|
||||
// to think they might have changed since the watcher was
|
||||
// initialized, which triggers a second compilation on startup.
|
||||
// Since we can't reliably update these files anyway, we can
|
||||
// just ignore them in the watcher and prevent the extra compilation
|
||||
/bundles[\/\\].+\.entry\.js/])]
|
||||
};
|
||||
|
||||
// in production we set the process.env.NODE_ENV and run
|
||||
// the terser minimizer over our bundles
|
||||
const productionConfig = {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimizer: [new _terserWebpackPlugin2.default({
|
||||
parallel: true,
|
||||
sourceMap: false,
|
||||
terserOptions: {
|
||||
compress: false,
|
||||
mangle: false
|
||||
}
|
||||
})]
|
||||
}
|
||||
};
|
||||
|
||||
return (0, _webpackMerge2.default)(commonConfig, _utils.IS_KIBANA_DISTRIBUTABLE ? isDistributableConfig : getSourceConfig(), this.uiBundles.isDevMode() ? (0, _webpackMerge2.default)(watchingConfig, supportEnzymeConfig) : productionConfig);
|
||||
}
|
||||
|
||||
isFailure(stats) {
|
||||
if (stats.hasErrors()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { warnings } = stats.toJson({ all: false, warnings: true });
|
||||
|
||||
// 1 - when typescript doesn't do a full type check, as we have the ts-loader
|
||||
// configured here, it does not have enough information to determine
|
||||
// whether an imported name is a type or not, so when the name is then
|
||||
// exported, typescript has no choice but to emit the export. Fortunately,
|
||||
// the extraneous export should not be harmful, so we just suppress these warnings
|
||||
// https://github.com/TypeStrong/ts-loader#transpileonly-boolean-defaultfalse
|
||||
//
|
||||
// 2 - Mini Css Extract plugin tracks the order for each css import we have
|
||||
// through the project (and it's successive imports) since version 0.4.2.
|
||||
// In case we have the same imports more than one time with different
|
||||
// sequences, this plugin will throw a warning. This should not be harmful,
|
||||
// but the an issue was opened and can be followed on:
|
||||
// https://github.com/webpack-contrib/mini-css-extract-plugin/issues/250#issuecomment-415345126
|
||||
const filteredWarnings = _Stats2.default.filterWarnings(warnings, STATS_WARNINGS_FILTER);
|
||||
|
||||
return filteredWarnings.length > 0;
|
||||
}
|
||||
|
||||
failedStatsToError(stats) {
|
||||
const details = stats.toString((0, _lodash.defaults)({ colors: true, warningsFilter: STATS_WARNINGS_FILTER }, _Stats2.default.presetToOptions('minimal')));
|
||||
|
||||
return _boom2.default.internal(`Optimizations failure.\n${details.split('\n').join('\n ')}\n`, stats.toJson((0, _lodash.defaults)(_extends({
|
||||
warningsFilter: STATS_WARNINGS_FILTER
|
||||
}, _Stats2.default.presetToOptions('detailed')))));
|
||||
}
|
||||
}
|
||||
exports.default = BaseOptimizer;
|
||||
module.exports = exports['default'];
|
||||
140
development/mastodon/docker-compose.yml
Archivo normal
140
development/mastodon/docker-compose.yml
Archivo normal
@@ -0,0 +1,140 @@
|
||||
version: '2.2'
|
||||
|
||||
services:
|
||||
# mastodon:
|
||||
# image: tootsuite/mastodon:edge
|
||||
# hostname: mastodon
|
||||
# container_name: mastodon
|
||||
# restart: always
|
||||
# env_file: .env.production
|
||||
# entrypoint:
|
||||
# - /bin/bash
|
||||
# - entrypoint.sh
|
||||
# cpus: 3
|
||||
## mem_limit: 1g
|
||||
## mem_reservation: 512m
|
||||
# volumes:
|
||||
# - ./entrypoint.sh:/opt/mastodon/entrypoint.sh
|
||||
# - ./mastodon:/opt/mastodon/public/system
|
||||
# - ./application.rb:/mastodon/config/application.rb
|
||||
# - ./media_attachment.rb:/mastodon/app/models/media_attachment.rb
|
||||
# - ./base_controller.rb:/mastodon/app/controllers/api/base_controller.rb
|
||||
# - ./follow_limit_validator.rb:/mastodon/app/validators/follow_limit_validator.rb
|
||||
# expose:
|
||||
# - 3000
|
||||
# - 4000
|
||||
# networks:
|
||||
# mynet:
|
||||
# ipv4_address: 172.1.0.101
|
||||
# haraka:
|
||||
#
|
||||
# postgres:
|
||||
# image: postgres
|
||||
# hostname: postgres-mastodon
|
||||
# container_name: postgres-mastodon
|
||||
# restart: always
|
||||
# shm_size: '1gb'
|
||||
# command: >
|
||||
# -c 'max_connections=150'
|
||||
# -c 'shared_buffers=512MB'
|
||||
# -c 'effective_cache_size=1536MB'
|
||||
# -c 'maintenance_work_mem=128MB'
|
||||
# -c 'checkpoint_completion_target=0.7'
|
||||
# -c 'wal_buffers=16MB'
|
||||
# -c 'default_statistics_target=100'
|
||||
# -c 'random_page_cost=1.1'
|
||||
# -c 'effective_io_concurrency=200'
|
||||
# -c 'work_mem=1747kB'
|
||||
# -c 'min_wal_size=1GB'
|
||||
# -c 'max_wal_size=2GB'
|
||||
# -c 'max_worker_processes=4'
|
||||
# -c 'max_parallel_workers_per_gather=2'
|
||||
# -c 'max_parallel_workers=4'
|
||||
# -c 'autovacuum_max_workers=1'
|
||||
# -c 'autovacuum_work_mem=128MB'
|
||||
# -h '*'
|
||||
## -c 'statement_timeout=60000'
|
||||
# environment:
|
||||
# - POSTGRES_USER=postgres
|
||||
# - POSTGRES_DB=mastodon
|
||||
# - POSTGRES_PASSWORD=m4st0d0n.
|
||||
# volumes:
|
||||
# - ./data:/var/lib/postgresql/data
|
||||
# expose:
|
||||
# - 5432
|
||||
# networks:
|
||||
# mynet:
|
||||
# ipv4_address: 172.1.0.102
|
||||
#
|
||||
# redis:
|
||||
# image: redis
|
||||
# hostname: redis-mastodon
|
||||
# container_name: redis-mastodon
|
||||
# restart: always
|
||||
# sysctls:
|
||||
# - net.core.somaxconn=1024
|
||||
# volumes:
|
||||
# - ./redis:/data
|
||||
# - ./redis.conf:/usr/local/etc/redis/redis.conf
|
||||
# - ./redis-enabled:/sys/kernel/mm/transparent_hugepage/enabled
|
||||
# networks:
|
||||
# mynet:
|
||||
# ipv4_address: 172.1.0.103
|
||||
|
||||
elastic:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:6.8.6
|
||||
hostname: elastic-mastodon
|
||||
container_name: elastic-mastodon
|
||||
restart: always
|
||||
environment:
|
||||
- node.name=mastodon
|
||||
- cluster.name=cluster01
|
||||
# - bootstrap.memory_lock=true
|
||||
- ES_JAVA_OPTS=-Xms1g -Xmx1g
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
volumes:
|
||||
- ./elastic:/usr/share/elasticsearch/data
|
||||
- ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
|
||||
expose:
|
||||
- 9200
|
||||
- 9300
|
||||
networks:
|
||||
mynet:
|
||||
ipv4_address: 172.1.0.104
|
||||
|
||||
# kibana:
|
||||
# image: docker.elastic.co/kibana/kibana:6.8.6
|
||||
# hostname: kibana-mastodon
|
||||
# container_name: kibana-mastodon
|
||||
# restart: always
|
||||
# environment:
|
||||
# SERVER_NAME: kibana.hatthieves.es
|
||||
# ELASTICSEARCH_HOSTS: http://elastic-mastodon
|
||||
# XPACK_MONITORING_ENABLED: 'false'
|
||||
# NODE_OPTIONS: '--max-old-space-size=512'
|
||||
# expose:
|
||||
# - 5601
|
||||
# volumes:
|
||||
# - ./base_optimizer.js:/usr/share/kibana/src/optimize/base_optimizer.js
|
||||
## mem_limit: 1024m
|
||||
## mem_reservation: 512m
|
||||
# cpus: 1
|
||||
## cpu_percent: 50
|
||||
## cpu_shares: 256
|
||||
# networks:
|
||||
# mynet:
|
||||
# ipv4_address: 172.1.0.105
|
||||
|
||||
networks:
|
||||
mynet:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.1.0.0/24
|
||||
|
||||
haraka:
|
||||
external:
|
||||
name: harakawildduck_mynet
|
||||
3
development/mastodon/elasticsearch.yml
Archivo normal
3
development/mastodon/elasticsearch.yml
Archivo normal
@@ -0,0 +1,3 @@
|
||||
cluster.name: "docker-cluster"
|
||||
network.host: 0.0.0.0
|
||||
cluster.routing.allocation.disk.threshold_enabled: false
|
||||
5
development/mastodon/entrypoint.sh
Archivo normal
5
development/mastodon/entrypoint.sh
Archivo normal
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
rm -f /mastodon/tmp/pids/server.pid
|
||||
bundle exec sidekiq -c 25 &
|
||||
node ./streaming &
|
||||
bundle exec rails s -p 3000
|
||||
27
development/mastodon/follow_limit_validator.rb
Archivo normal
27
development/mastodon/follow_limit_validator.rb
Archivo normal
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class FollowLimitValidator < ActiveModel::Validator
|
||||
LIMIT = ENV.fetch('MAX_FOLLOWS_THRESHOLD', 99_500).to_i
|
||||
RATIO = ENV.fetch('MAX_FOLLOWS_RATIO', 1.1).to_f
|
||||
|
||||
def validate(follow)
|
||||
return if follow.account.nil? || !follow.account.local?
|
||||
follow.errors.add(:base, I18n.t('users.follow_limit_reached', limit: self.class.limit_for_account(follow.account))) if limit_reached?(follow.account)
|
||||
end
|
||||
|
||||
class << self
|
||||
def limit_for_account(account)
|
||||
if account.following_count < LIMIT
|
||||
LIMIT
|
||||
else
|
||||
[(account.followers_count * RATIO).round, LIMIT].max
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def limit_reached?(account)
|
||||
account.following_count >= self.class.limit_for_account(account)
|
||||
end
|
||||
end
|
||||
374
development/mastodon/media_attachment.rb
Archivo normal
374
development/mastodon/media_attachment.rb
Archivo normal
@@ -0,0 +1,374 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: media_attachments
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# status_id :bigint(8)
|
||||
# file_file_name :string
|
||||
# file_content_type :string
|
||||
# file_file_size :integer
|
||||
# file_updated_at :datetime
|
||||
# remote_url :string default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# shortcode :string
|
||||
# type :integer default("image"), not null
|
||||
# file_meta :json
|
||||
# account_id :bigint(8)
|
||||
# description :text
|
||||
# scheduled_status_id :bigint(8)
|
||||
# blurhash :string
|
||||
# processing :integer
|
||||
#
|
||||
|
||||
class MediaAttachment < ApplicationRecord
|
||||
self.inheritance_column = nil
|
||||
|
||||
enum type: [:image, :gifv, :video, :unknown, :audio]
|
||||
enum processing: [:queued, :in_progress, :complete, :failed], _prefix: true
|
||||
|
||||
MAX_DESCRIPTION_LENGTH = 1_500
|
||||
|
||||
IMAGE_FILE_EXTENSIONS = %w(.jpg .jpeg .png .gif).freeze
|
||||
VIDEO_FILE_EXTENSIONS = %w(.webm .mp4 .m4v .mov).freeze
|
||||
AUDIO_FILE_EXTENSIONS = %w(.ogg .oga .mp3 .wav .flac .opus .aac .m4a .3gp .wma).freeze
|
||||
|
||||
IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif).freeze
|
||||
VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg).freeze
|
||||
VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime).freeze
|
||||
AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/ogg audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze
|
||||
|
||||
BLURHASH_OPTIONS = {
|
||||
x_comp: 4,
|
||||
y_comp: 4,
|
||||
}.freeze
|
||||
|
||||
IMAGE_STYLES = {
|
||||
original: {
|
||||
pixels: 1_638_400, # 1280x1280px
|
||||
file_geometry_parser: FastGeometryParser,
|
||||
},
|
||||
|
||||
small: {
|
||||
pixels: 160_000, # 400x400px
|
||||
file_geometry_parser: FastGeometryParser,
|
||||
blurhash: BLURHASH_OPTIONS,
|
||||
},
|
||||
}.freeze
|
||||
|
||||
VIDEO_FORMAT = {
|
||||
format: 'mp4',
|
||||
content_type: 'video/mp4',
|
||||
convert_options: {
|
||||
output: {
|
||||
'loglevel' => 'fatal',
|
||||
'movflags' => 'faststart',
|
||||
'pix_fmt' => 'yuv420p',
|
||||
'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
|
||||
'vsync' => 'cfr',
|
||||
'c:v' => 'h264',
|
||||
'maxrate' => '1300K',
|
||||
'bufsize' => '1300K',
|
||||
'frames:v' => 60 * 60 * 3,
|
||||
'crf' => 18,
|
||||
'map_metadata' => '-1',
|
||||
'threads' => 1
|
||||
},
|
||||
},
|
||||
}.freeze
|
||||
|
||||
VIDEO_PASSTHROUGH_OPTIONS = {
|
||||
video_codecs: ['h264'],
|
||||
audio_codecs: ['aac', nil],
|
||||
colorspaces: ['yuv420p'],
|
||||
options: {
|
||||
format: 'mp4',
|
||||
convert_options: {
|
||||
output: {
|
||||
'loglevel' => 'fatal',
|
||||
'map_metadata' => '-1',
|
||||
'c:v' => 'copy',
|
||||
'c:a' => 'copy',
|
||||
'threads' => 1
|
||||
},
|
||||
},
|
||||
},
|
||||
}.freeze
|
||||
|
||||
VIDEO_STYLES = {
|
||||
small: {
|
||||
convert_options: {
|
||||
output: {
|
||||
'loglevel' => 'fatal',
|
||||
vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
|
||||
},
|
||||
},
|
||||
format: 'png',
|
||||
time: 0,
|
||||
file_geometry_parser: FastGeometryParser,
|
||||
blurhash: BLURHASH_OPTIONS,
|
||||
},
|
||||
|
||||
original: VIDEO_FORMAT.merge(passthrough_options: VIDEO_PASSTHROUGH_OPTIONS),
|
||||
}.freeze
|
||||
|
||||
AUDIO_STYLES = {
|
||||
original: {
|
||||
format: 'mp3',
|
||||
content_type: 'audio/mpeg',
|
||||
convert_options: {
|
||||
output: {
|
||||
'loglevel' => 'fatal',
|
||||
'map_metadata' => '-1',
|
||||
'q:a' => 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}.freeze
|
||||
|
||||
VIDEO_CONVERTED_STYLES = {
|
||||
small: VIDEO_STYLES[:small],
|
||||
original: VIDEO_FORMAT,
|
||||
}.freeze
|
||||
|
||||
IMAGE_LIMIT = 10.megabytes
|
||||
VIDEO_LIMIT = 40.megabytes
|
||||
|
||||
MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
|
||||
MAX_VIDEO_FRAME_RATE = 60
|
||||
|
||||
belongs_to :account, inverse_of: :media_attachments, optional: true
|
||||
belongs_to :status, inverse_of: :media_attachments, optional: true
|
||||
belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
|
||||
|
||||
has_attached_file :file,
|
||||
styles: ->(f) { file_styles f },
|
||||
processors: ->(f) { file_processors f },
|
||||
convert_options: { all: '-quality 90 -strip +set modify-date +set create-date' }
|
||||
|
||||
validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
|
||||
validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :larger_media_format?
|
||||
validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :larger_media_format?
|
||||
remotable_attachment :file, VIDEO_LIMIT, suppress_errors: false
|
||||
|
||||
include Attachmentable
|
||||
|
||||
validates :account, presence: true
|
||||
validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local?
|
||||
validates :file, presence: true, if: :local?
|
||||
|
||||
scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
|
||||
scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
|
||||
scope :local, -> { where(remote_url: '') }
|
||||
scope :remote, -> { where.not(remote_url: '') }
|
||||
scope :cached, -> { remote.where.not(file_file_name: nil) }
|
||||
|
||||
default_scope { order(id: :asc) }
|
||||
|
||||
def local?
|
||||
remote_url.blank?
|
||||
end
|
||||
|
||||
def not_processed?
|
||||
processing.present? && !processing_complete?
|
||||
end
|
||||
|
||||
def needs_redownload?
|
||||
file.blank? && remote_url.present?
|
||||
end
|
||||
|
||||
def larger_media_format?
|
||||
video? || gifv? || audio?
|
||||
end
|
||||
|
||||
def audio_or_video?
|
||||
audio? || video?
|
||||
end
|
||||
|
||||
def variant?(other_file_name)
|
||||
return true if file_file_name == other_file_name
|
||||
return false if file_file_name.nil?
|
||||
|
||||
formats = file.styles.values.map(&:format).compact
|
||||
|
||||
return false if formats.empty?
|
||||
|
||||
extension = File.extname(other_file_name)
|
||||
|
||||
formats.include?(extension.delete('.')) && File.basename(other_file_name, extension) == File.basename(file_file_name, File.extname(file_file_name))
|
||||
end
|
||||
|
||||
def to_param
|
||||
shortcode
|
||||
end
|
||||
|
||||
def focus=(point)
|
||||
return if point.blank?
|
||||
|
||||
x, y = (point.is_a?(Enumerable) ? point : point.split(',')).map(&:to_f)
|
||||
|
||||
meta = file.instance_read(:meta) || {}
|
||||
meta['focus'] = { 'x' => x, 'y' => y }
|
||||
|
||||
file.instance_write(:meta, meta)
|
||||
end
|
||||
|
||||
def focus
|
||||
x = file.meta['focus']['x']
|
||||
y = file.meta['focus']['y']
|
||||
|
||||
"#{x},#{y}"
|
||||
end
|
||||
|
||||
attr_writer :delay_processing
|
||||
|
||||
def delay_processing?
|
||||
@delay_processing
|
||||
end
|
||||
|
||||
after_commit :enqueue_processing, on: :create
|
||||
after_commit :reset_parent_cache, on: :update
|
||||
|
||||
before_create :prepare_description, unless: :local?
|
||||
before_create :set_shortcode
|
||||
before_create :set_processing
|
||||
|
||||
before_post_process :set_type_and_extension
|
||||
before_post_process :check_video_dimensions
|
||||
|
||||
before_save :set_meta
|
||||
|
||||
class << self
|
||||
def supported_mime_types
|
||||
IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
|
||||
end
|
||||
|
||||
def supported_file_extensions
|
||||
IMAGE_FILE_EXTENSIONS + VIDEO_FILE_EXTENSIONS + AUDIO_FILE_EXTENSIONS
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def file_styles(f)
|
||||
if f.instance.file_content_type == 'image/gif' || VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type)
|
||||
VIDEO_CONVERTED_STYLES
|
||||
elsif IMAGE_MIME_TYPES.include?(f.instance.file_content_type)
|
||||
IMAGE_STYLES
|
||||
elsif VIDEO_MIME_TYPES.include?(f.instance.file_content_type)
|
||||
VIDEO_STYLES
|
||||
else
|
||||
AUDIO_STYLES
|
||||
end
|
||||
end
|
||||
|
||||
def file_processors(f)
|
||||
if f.file_content_type == 'image/gif'
|
||||
[:gif_transcoder, :blurhash_transcoder]
|
||||
elsif VIDEO_MIME_TYPES.include?(f.file_content_type)
|
||||
[:video_transcoder, :blurhash_transcoder, :type_corrector]
|
||||
elsif AUDIO_MIME_TYPES.include?(f.file_content_type)
|
||||
[:transcoder, :type_corrector]
|
||||
else
|
||||
[:lazy_thumbnail, :blurhash_transcoder, :type_corrector]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_shortcode
|
||||
self.type = :unknown if file.blank? && !type_changed?
|
||||
|
||||
return unless local?
|
||||
|
||||
loop do
|
||||
self.shortcode = SecureRandom.urlsafe_base64(14)
|
||||
break if MediaAttachment.find_by(shortcode: shortcode).nil?
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_description
|
||||
self.description = description.strip[0...MAX_DESCRIPTION_LENGTH] unless description.nil?
|
||||
end
|
||||
|
||||
def set_type_and_extension
|
||||
self.type = begin
|
||||
if VIDEO_MIME_TYPES.include?(file_content_type)
|
||||
:video
|
||||
elsif AUDIO_MIME_TYPES.include?(file_content_type)
|
||||
:audio
|
||||
else
|
||||
:image
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_processing
|
||||
self.processing = delay_processing? ? :queued : :complete
|
||||
end
|
||||
|
||||
def check_video_dimensions
|
||||
return unless (video? || gifv?) && file.queued_for_write[:original].present?
|
||||
|
||||
movie = FFMPEG::Movie.new(file.queued_for_write[:original].path)
|
||||
|
||||
return unless movie.valid?
|
||||
|
||||
raise Mastodon::DimensionsValidationError, "#{movie.width}x#{movie.height} videos are not supported" if movie.width * movie.height > MAX_VIDEO_MATRIX_LIMIT
|
||||
raise Mastodon::DimensionsValidationError, "#{movie.frame_rate.to_i}fps videos are not supported" if movie.frame_rate > MAX_VIDEO_FRAME_RATE
|
||||
end
|
||||
|
||||
def set_meta
|
||||
meta = populate_meta
|
||||
|
||||
return if meta == {}
|
||||
|
||||
file.instance_write :meta, meta
|
||||
end
|
||||
|
||||
def populate_meta
|
||||
meta = file.instance_read(:meta) || {}
|
||||
|
||||
file.queued_for_write.each do |style, file|
|
||||
meta[style] = style == :small || image? ? image_geometry(file) : video_metadata(file)
|
||||
end
|
||||
|
||||
meta
|
||||
end
|
||||
|
||||
def image_geometry(file)
|
||||
width, height = FastImage.size(file.path)
|
||||
|
||||
return {} if width.nil?
|
||||
|
||||
{
|
||||
width: width,
|
||||
height: height,
|
||||
size: "#{width}x#{height}",
|
||||
aspect: width.to_f / height,
|
||||
}
|
||||
end
|
||||
|
||||
def video_metadata(file)
|
||||
movie = FFMPEG::Movie.new(file.path)
|
||||
|
||||
return {} unless movie.valid?
|
||||
|
||||
{
|
||||
width: movie.width,
|
||||
height: movie.height,
|
||||
frame_rate: movie.frame_rate,
|
||||
duration: movie.duration,
|
||||
bitrate: movie.bitrate,
|
||||
}.compact
|
||||
end
|
||||
|
||||
def enqueue_processing
|
||||
PostProcessMediaWorker.perform_async(id) if delay_processing?
|
||||
end
|
||||
|
||||
def reset_parent_cache
|
||||
Rails.cache.delete("statuses/#{status_id}") if status_id.present?
|
||||
end
|
||||
end
|
||||
1
development/mastodon/redis-enabled
Archivo normal
1
development/mastodon/redis-enabled
Archivo normal
@@ -0,0 +1 @@
|
||||
always madvise [never]
|
||||
1373
development/mastodon/redis.conf
Archivo normal
1373
development/mastodon/redis.conf
Archivo normal
La diferencia del archivo ha sido suprimido porque es demasiado grande
Cargar Diff
Referencia en una nueva incidencia
Block a user