Compare commits

..

1 Commits

Author SHA1 Message Date
ThibG 7f689df1e2
Change e-mail contact for CoC enforcement 2019-06-06 08:45:06 +02:00
6241 changed files with 89542 additions and 376258 deletions

View File

@ -1,7 +0,0 @@
[production]
defaults
not IE 11
not dead
[development]
supports es6-module

View File

@ -1,3 +1,4 @@
https://github.com/heroku/heroku-buildpack-apt
https://github.com/Scalingo/ffmpeg-buildpack
https://github.com/Scalingo/nodejs-buildpack
https://github.com/Scalingo/ruby-buildpack

View File

@ -1,225 +1,220 @@
version: 2.1
version: 2
orbs:
ruby: circleci/ruby@1.4.1
node: circleci/node@5.0.1
executors:
default:
parameters:
ruby-version:
type: string
aliases:
- &defaults
docker:
- image: cimg/ruby:<< parameters.ruby-version >>
environment:
BUNDLE_JOBS: 3
BUNDLE_RETRY: 3
CONTINUOUS_INTEGRATION: true
- image: circleci/ruby:2.6.0-stretch-node
environment: &ruby_environment
BUNDLE_APP_CONFIG: ./.bundle/
DB_HOST: localhost
DB_USER: root
DISABLE_SIMPLECOV: true
RAILS_ENV: test
- image: cimg/postgres:14.0
environment:
POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- image: cimg/redis:6.2
PARALLEL_TEST_PROCESSORS: 4
ALLOW_NOPAM: true
CONTINUOUS_INTEGRATION: true
DISABLE_SIMPLECOV: true
PAM_ENABLED: true
PAM_DEFAULT_SERVICE: pam_test
PAM_CONTROLLED_SERVICE: pam_test_controlled
working_directory: ~/projects/mastodon/
commands:
install-system-dependencies:
- &attach_workspace
attach_workspace:
at: ~/projects/
- &persist_to_workspace
persist_to_workspace:
root: ~/projects/
paths:
- ./mastodon/
- &restore_ruby_dependencies
restore_cache:
keys:
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-
- v2-ruby-dependencies-
- &install_steps
steps:
- run:
name: Install system dependencies
command: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
install-ruby-dependencies:
parameters:
ruby-version:
type: string
steps:
- run:
command: |
bundle config clean 'true'
bundle config frozen 'true'
bundle config without 'development production'
name: Set bundler settings
- ruby/install-deps:
bundler-version: '2.3.8'
key: ruby<< parameters.ruby-version >>-gems-v1
wait-db:
steps:
- run:
command: dockerize -wait tcp://localhost:5432 -wait tcp://localhost:6379 -timeout 1m
name: Wait for PostgreSQL and Redis
- checkout
- *attach_workspace
- restore_cache:
keys:
- v1-node-dependencies-{{ checksum "yarn.lock" }}
- v1-node-dependencies-
- run: yarn install --frozen-lockfile
- save_cache:
key: v1-node-dependencies-{{ checksum "yarn.lock" }}
paths:
- ./node_modules/
- *persist_to_workspace
- &install_system_dependencies
run:
name: Install system dependencies
command: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
- &install_ruby_dependencies
steps:
- *attach_workspace
- *install_system_dependencies
- run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
- *restore_ruby_dependencies
- run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production && bundle clean
- save_cache:
key: v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
paths:
- ./.bundle/
- ./vendor/bundle/
- persist_to_workspace:
root: ~/projects/
paths:
- ./mastodon/.bundle/
- ./mastodon/vendor/bundle/
- &test_steps
steps:
- *attach_workspace
- *install_system_dependencies
- run: sudo apt-get install -y ffmpeg
- run:
name: Prepare Tests
command: ./bin/rails parallel:create parallel:load_schema parallel:prepare
- run:
name: Run Tests
command: ./bin/retry bundle exec parallel_test ./spec/ --group-by filesize --type rspec
jobs:
build:
install:
<<: *defaults
<<: *install_steps
install-ruby2.6:
<<: *defaults
<<: *install_ruby_dependencies
install-ruby2.5:
<<: *defaults
docker:
- image: cimg/ruby:3.0-node
environment:
RAILS_ENV: test
- image: circleci/ruby:2.5.3-stretch-node
environment: *ruby_environment
<<: *install_ruby_dependencies
install-ruby2.4:
<<: *defaults
docker:
- image: circleci/ruby:2.4.5-stretch-node
environment: *ruby_environment
<<: *install_ruby_dependencies
build:
<<: *defaults
steps:
- checkout
- install-system-dependencies
- install-ruby-dependencies:
ruby-version: '3.0'
- node/install-packages:
cache-version: v1
pkg-manager: yarn
- *attach_workspace
- *install_system_dependencies
- run:
command: |
export NODE_OPTIONS=--openssl-legacy-provider
./bin/rails assets:precompile
name: Precompile assets
command: ./bin/rails assets:precompile
no_output_timeout: 40m
- persist_to_workspace:
root: ~/projects/
paths:
- public/assets
- public/packs-test
root: .
- ./mastodon/public/assets
- ./mastodon/public/packs-test/
test:
parameters:
ruby-version:
type: string
executor:
name: default
ruby-version: << parameters.ruby-version >>
environment:
ALLOW_NOPAM: true
PAM_ENABLED: true
PAM_DEFAULT_SERVICE: pam_test
PAM_CONTROLLED_SERVICE: pam_test_controlled
parallelism: 4
steps:
- checkout
- install-system-dependencies
- run:
command: sudo apt-get install -y ffmpeg imagemagick libpam-dev
name: Install additional system dependencies
- run:
command: bundle config with 'pam_authentication'
name: Enable PAM authentication
- install-ruby-dependencies:
ruby-version: << parameters.ruby-version >>
- attach_workspace:
at: .
- wait-db
- run:
command: ./bin/rails db:create db:schema:load db:seed
name: Load database schema
- ruby/rspec-test
test-ruby2.6:
<<: *defaults
docker:
- image: circleci/ruby:2.6.0-stretch-node
environment: *ruby_environment
- image: circleci/postgres:10.6-alpine
environment:
POSTGRES_USER: root
- image: circleci/redis:5.0.3-alpine3.8
<<: *test_steps
test-migrations:
executor:
name: default
ruby-version: '3.0'
steps:
- checkout
- install-system-dependencies
- install-ruby-dependencies:
ruby-version: '3.0'
- wait-db
- run:
command: ./bin/rails db:create
name: Create database
- run:
command: ./bin/rails db:migrate VERSION=20171010025614
name: Run migrations up to v2.0.0
- run:
command: ./bin/rails tests:migrations:populate_v2
name: Populate database with test data
- run:
command: ./bin/rails db:migrate VERSION=20180514140000
name: Run migrations up to v2.4.0
- run:
command: ./bin/rails tests:migrations:populate_v2_4
name: Populate database with test data
- run:
command: ./bin/rails db:migrate VERSION=20180707154237
name: Run migrations up to v2.4.3
- run:
command: ./bin/rails tests:migrations:populate_v2_4_3
name: Populate database with test data
- run:
command: ./bin/rails db:migrate
name: Run all remaining migrations
- run:
command: ./bin/rails tests:migrations:check_database
name: Check migration result
test-ruby2.5:
<<: *defaults
docker:
- image: circleci/ruby:2.5.3-stretch-node
environment: *ruby_environment
- image: circleci/postgres:10.6-alpine
environment:
POSTGRES_USER: root
- image: circleci/redis:4.0.12-alpine
<<: *test_steps
test-two-step-migrations:
executor:
name: default
ruby-version: '3.0'
test-ruby2.4:
<<: *defaults
docker:
- image: circleci/ruby:2.4.5-stretch-node
environment: *ruby_environment
- image: circleci/postgres:10.6-alpine
environment:
POSTGRES_USER: root
- image: circleci/redis:4.0.12-alpine
<<: *test_steps
test-webui:
<<: *defaults
docker:
- image: circleci/node:8.15.0-stretch
steps:
- checkout
- install-system-dependencies
- install-ruby-dependencies:
ruby-version: '3.0'
- wait-db
- run:
command: ./bin/rails db:create
name: Create database
- run:
command: ./bin/rails db:migrate VERSION=20171010025614
name: Run migrations up to v2.0.0
- run:
command: ./bin/rails tests:migrations:populate_v2
name: Populate database with test data
- run:
command: ./bin/rails db:migrate VERSION=20180514140000
name: Run pre-deployment migrations up to v2.4.0
environment:
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
- run:
command: ./bin/rails tests:migrations:populate_v2_4
name: Populate database with test data
- run:
command: ./bin/rails db:migrate VERSION=20180707154237
name: Run migrations up to v2.4.3
environment:
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
- run:
command: ./bin/rails tests:migrations:populate_v2_4_3
name: Populate database with test data
- run:
command: ./bin/rails db:migrate
name: Run all remaining pre-deployment migrations
environment:
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
- run:
command: ./bin/rails db:migrate
name: Run all post-deployment migrations
- run:
command: ./bin/rails tests:migrations:check_database
name: Check migration result
- *attach_workspace
- run: ./bin/retry yarn test:jest
check-i18n:
<<: *defaults
steps:
- *attach_workspace
- run: bundle exec i18n-tasks check-normalized
- run: bundle exec i18n-tasks unused
- run: bundle exec i18n-tasks missing -t plural
- run: bundle exec i18n-tasks check-consistent-interpolations
workflows:
version: 2
build-and-test:
jobs:
- build
- test:
matrix:
parameters:
ruby-version:
- '2.7'
- '3.0'
name: test-ruby<< matrix.ruby-version >>
- install
- install-ruby2.6:
requires:
- build
- test-migrations:
- install
- install-ruby2.5:
requires:
- build
- test-two-step-migrations:
- install
- install-ruby2.6
- install-ruby2.4:
requires:
- build
- node/run:
cache-version: v1
name: test-webui
pkg-manager: yarn
- install
- install-ruby2.6
- build:
requires:
- install-ruby2.6
- test-ruby2.6:
requires:
- install-ruby2.6
- build
version: lts
yarn-run: test:jest
- test-ruby2.5:
requires:
- install-ruby2.5
- build
- test-ruby2.4:
requires:
- install-ruby2.4
- build
- test-webui:
requires:
- install
- check-i18n:
requires:
- install-ruby2.6

View File

@ -1,4 +1,4 @@
version: '2'
version: "2"
checks:
argument-count:
enabled: false
@ -26,14 +26,13 @@ plugins:
bundler-audit:
enabled: true
eslint:
enabled: false
enabled: true
channel: eslint-5
rubocop:
enabled: false
enabled: true
channel: rubocop-0-71
sass-lint:
enabled: false
enabled: true
exclude_patterns:
- spec/
- vendor/asset/
- app/javascript/mastodon/locales/**/*.json
- config/locales/**/*.yml
- spec/
- vendor/asset

View File

@ -1,23 +0,0 @@
version = 1
test_patterns = ["app/javascript/mastodon/**/__tests__/**"]
exclude_patterns = [
"db/migrate/**",
"db/post_migrate/**"
]
[[analyzers]]
name = "ruby"
enabled = true
[[analyzers]]
name = "javascript"
enabled = true
[analyzers.meta]
environment = [
"browser",
"jest",
"nodejs"
]

10
.dependabot/config.yml Normal file
View File

@ -0,0 +1,10 @@
version: 1
update_configs:
- package_manager: "ruby:bundler"
directory: "/"
update_schedule: "weekly"
- package_manager: "javascript"
directory: "/"
update_schedule: "weekly"

View File

@ -1,24 +0,0 @@
# [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster
ARG VARIANT=3.1-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
# Install Rails
# RUN gem install rails webdrivers
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
# The value is a comma-separated list of allowed domains
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev"
# [Choice] Node.js version: lts/*, 16, 14, 12, 10
ARG NODE_VERSION="lts/*"
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
# [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libpam-dev
# [Optional] Uncomment this line to install additional gems.
RUN gem install foreman
# [Optional] Uncomment this line to install global node packages.
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1

View File

@ -1,27 +0,0 @@
{
"name": "Mastodon",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/mastodon",
// Set *default* container specific settings.json values on container create.
"settings": {},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"EditorConfig.EditorConfig",
"dbaeumer.vscode-eslint",
"rebornix.Ruby",
"webben.browserslist"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// This can be used to network with other containers or the host.
"forwardPorts": [3000, 4000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View File

@ -1,90 +0,0 @@
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
args:
# Update 'VARIANT' to pick a version of Ruby: 3, 3.1, 3.0, 2, 2.7, 2.6
# Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: '3.0-bullseye'
# Optional Node.js version to install
NODE_VERSION: '14'
volumes:
- ..:/workspaces/mastodon:cached
environment:
RAILS_ENV: development
NODE_ENV: development
REDIS_HOST: redis
REDIS_PORT: '6379'
DB_HOST: db
DB_USER: postgres
DB_PASS: postgres
DB_PORT: '5432'
ES_ENABLED: 'true'
ES_HOST: es
ES_PORT: '9200'
LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
networks:
- external_network
- internal_network
user: vscode
db:
image: postgres:14-alpine
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
networks:
- internal_network
redis:
image: redis:6-alpine
restart: unless-stopped
volumes:
- redis-data:/data
networks:
- internal_network
es:
image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
restart: unless-stopped
environment:
ES_JAVA_OPTS: -Xms512m -Xmx512m
cluster.name: es-mastodon
discovery.type: single-node
bootstrap.memory_lock: 'true'
volumes:
- es-data:/usr/share/elasticsearch/data
networks:
- internal_network
ulimits:
memlock:
soft: -1
hard: -1
libretranslate:
image: libretranslate/libretranslate:v1.2.9
restart: unless-stopped
networks:
- internal_network
volumes:
postgres-data:
redis-data:
es-data:
networks:
external_network:
internal_network:
internal: true

View File

@ -1,10 +1,6 @@
.bundle
.env
.env.*
.git
.gitattributes
.gitignore
.github
public/system
public/assets
public/packs
@ -15,7 +11,5 @@ vendor/bundle
*.swp
*~
postgres
postgres14
redis
elasticsearch
chart

226
.env.nanobox Normal file
View File

@ -0,0 +1,226 @@
# Service dependencies
# You may set REDIS_URL instead for more advanced options
REDIS_HOST=$DATA_REDIS_HOST
REDIS_PORT=6379
# REDIS_DB=0
# You may set DATABASE_URL instead for more advanced options
DB_HOST=$DATA_DB_HOST
DB_USER=$DATA_DB_USER
DB_NAME=gonano
DB_PASS=$DATA_DB_PASS
DB_PORT=5432
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
# Optional ElasticSearch configuration
ES_ENABLED=true
ES_HOST=$DATA_ELASTIC_HOST
ES_PORT=9200
# Optimizations
LD_PRELOAD=/data/lib/libjemalloc.so
# ImageMagick optimizations
MAGICK_TEMPORARY_PATH=/app/tmp
MAGICK_MEMORY_LIMIT=128MiB
MAGICK_MAP_LIMIT=64MiB
MAGICK_TIME_LIMIT=15
MAGICK_AREA_LIMIT=16MP
MAGICK_WIDTH_LIMIT=8KP
MAGICK_HEIGHT_LIMIT=8KP
# 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=${APP_NAME}.nanoapp.io
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
# 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 `rake secret` task (`nanobox run bundle exec rake secret`)
SECRET_KEY_BASE=$SECRET_KEY_BASE
OTP_SECRET=$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 `rake mastodon:webpush:generate_vapid_key` task (`nanobox run bundle exec rake mastodon:webpush:generate_vapid_key`)
#
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY
VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY
# 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=de
# 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=$SMTP_SERVER
SMTP_PORT=587
SMTP_LOGIN=$SMTP_LOGIN
SMTP_PASSWORD=$SMTP_PASSWORD
SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
#SMTP_OPENSSL_VERIFY_MODE=peer
#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
# CDN_HOST=https://assets.example.com
# S3 (optional)
# 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)
# 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=
# Swift (optional)
# SWIFT_ENABLED=true
# SWIFT_USERNAME=
# For Keystone V3, the value for SWIFT_TENANT should be the project name
# SWIFT_TENANT=
# SWIFT_PASSWORD=
# 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=
# 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=1
# 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
# 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 Suffix for email address generation (nil by default)
# PAM_DEFAULT_SUFFIX=pam
# 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=
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
# 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.5.4.42"
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=

View File

@ -1,23 +1,29 @@
# This is a sample configuration file. You can generate your configuration
# with the `rake mastodon:setup` interactive setup wizard, but to customize
# your setup even further, you'll need to edit it manually. This sample does
# not demonstrate all available configuration options. Please look at
# https://docs.joinmastodon.org/admin/config/ for the full documentation.
# Note that this file accepts slightly different syntax depending on whether
# you are using `docker-compose` or not. In particular, if you use
# `docker-compose`, the value of each declared variable will be taken verbatim,
# including surrounding quotes.
# See: https://github.com/mastodon/mastodon/issues/16895
# 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
REDIS_PORT=6379
# You may set DATABASE_URL instead for more advanced options
DB_HOST=db
DB_USER=postgres
DB_NAME=postgres
DB_PASS=
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=true
# ES_HOST=es
# ES_PORT=9200
# Federation
# ----------
# This identifies your server and cannot be changed safely later
# ----------
# 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=example.com
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
# 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://docs.joinmastodon.org/admin/config/#web-domain
# 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
@ -26,102 +32,87 @@ LOCAL_DOMAIN=example.com
# be added. Comma separated values
# ALTERNATE_DOMAINS=example1.com,example2.com
# 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
# Limited federation mode (optional)
# Only allow federation with specific domains, see
# https://docs.joinmastodon.org/admin/config/#whitelist_mode
# LIMITED_FEDERATION_MODE=true
# Redis
# -----
REDIS_HOST=localhost
REDIS_PORT=6379
# PostgreSQL
# ----------
DB_HOST=/var/run/postgresql
DB_USER=mastodon
DB_NAME=mastodon_production
DB_PASS=
DB_PORT=5432
# Elasticsearch (optional)
# ------------------------
#ES_ENABLED=true
#ES_HOST=localhost
#ES_PORT=9200
# Authentication for ES (optional)
#ES_USER=elastic
#ES_PASS=password
# Secrets
# -------
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web bundle exec rake secret` if you use docker compose)
# -------
# 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=
OTP_SECRET=
# Web Push
# --------
# Generate with `rake mastodon:webpush:generate_vapid_key` (first is the private key, second is the public one)
# 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=
VAPID_PUBLIC_KEY=
# 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_DENYLIST=example1.com|example2.de|etc
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
# Only allow registrations with the following e-mail domains
# EMAIL_DOMAIN_ALLOWLIST=example1.com|example2.de|etc
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
#TODO move this
# Optionally change default language
# DEFAULT_LOCALE=de
# Sending mail
# ------------
# 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=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=notifications@example.com
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
#SMTP_OPENSSL_VERIFY_MODE=peer
#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
# File storage (optional)
# -----------------------
# 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=files.example.com
#AWS_ACCESS_KEY_ID=
#AWS_SECRET_ACCESS_KEY=
#S3_ALIAS_HOST=files.example.com
# 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=
# Swift (optional)
# The attachment host must allow cross origin request - see the description
@ -144,27 +135,49 @@ SMTP_FROM_ADDRESS=notifications@example.com
# Defaults to 60 seconds. Set to 0 to disable
# SWIFT_CACHE_TTL=
# 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
# Optional list of hosts that are allowed to serve media for your instance
# This is useful if you include external media in your custom CSS or about page,
# or if your data storage provider makes use of redirects to other domains.
# EXTRA_DATA_HOSTS=https://data.example1.com|https://data.example2.com
# 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=
# 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=1
# Docker mastodon user
# If you use Docker, you may want to assign UID/GID manually.
# UID=1000
# GID=1000
# Maximum allowed character count
# MAX_TOOT_CHARS=500
# Maximum number of pinned posts
# MAX_PINNED_TOOTS=5
# Maximum allowed bio characters
# MAX_BIO_CHARS=500
# Maximim number of profile fields allowed
# MAX_PROFILE_FIELDS=4
# Maximum allowed display name characters
# MAX_DISPLAY_NAME_CHARS=30
# Maximum image and video upload sizes
# Units are in bytes
# 1048576 bytes equals 1 megabyte
# MAX_IMAGE_SIZE=8388608
# MAX_VIDEO_SIZE=41943040
# Maximum length of audio uploads in seconds
# MAX_AUDIO_LENGTH=60
# External authentication (optional)
# ----------------------------------
# LDAP authentication (optional)
# LDAP_ENABLED=true
# LDAP_HOST=localhost
@ -174,11 +187,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
# 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=_
# LDAP_SEARCH_FILTER="%{uid}=%{email}"
# PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable
@ -222,8 +231,8 @@ SMTP_FROM_ADDRESS=notifications@example.com
# 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_ACS_URL=
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
# SAML_IDP_CERT=
# SAML_IDP_CERT_FINGERPRINT=
@ -242,62 +251,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
# Custom settings
# ---------------
# Various ways to customize Mastodon's behavior
# ---------------
# Maximum allowed character count
MAX_TOOT_CHARS=500
# Maximum number of pinned posts
MAX_PINNED_TOOTS=5
# Maximum allowed bio characters
MAX_BIO_CHARS=500
# Maximim number of profile fields allowed
MAX_PROFILE_FIELDS=4
# Maximum allowed display name characters
MAX_DISPLAY_NAME_CHARS=30
# Maximum allowed poll options
MAX_POLL_OPTIONS=5
# Maximum allowed poll option characters
MAX_POLL_OPTION_CHARS=100
# Maximum image and video/audio upload sizes
# Units are in bytes
# 1048576 bytes equals 1 megabyte
# MAX_IMAGE_SIZE=8388608
# MAX_VIDEO_SIZE=41943040
# Maximum search results to display
# Only relevant when elasticsearch is installed
# MAX_SEARCH_RESULTS=20
# Maximum hashtags to display
# Customize the number of hashtags shown in 'Explore'
# MAX_TRENDING_TAGS=10
# Maximum custom emoji file sizes
# If undefined or smaller than MAX_EMOJI_SIZE, the value
# of MAX_EMOJI_SIZE will be used for MAX_REMOTE_EMOJI_SIZE
# Units are in bytes
# MAX_EMOJI_SIZE=262144
# MAX_REMOTE_EMOJI_SIZE=262144
# Optional hCaptcha support
# HCAPTCHA_SECRET_KEY=
# HCAPTCHA_SITE_KEY=
# IP and session retention
# -----------------------
# Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml
# to be less than daily if you lower IP_RETENTION_PERIOD below two days (172800).
# -----------------------
IP_RETENTION_PERIOD=31556952
SESSION_RETENTION_PERIOD=31556952
# Use HTTP proxy for outgoing request (optional)
# http_proxy=http://gateway.local:8118
# Access control for hidden service.
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true

View File

@ -1,5 +1,5 @@
# Node.js
NODE_ENV=tests
NODE_ENV=test
# Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true

View File

@ -1,4 +1,2 @@
VAGRANT=true
LOCAL_DOMAIN=mastodon.local
BIND=0.0.0.0
DB_HOST=/var/run/postgresql/

View File

@ -12,7 +12,7 @@ module.exports = {
ATTACHMENT_HOST: false,
},
parser: '@babel/eslint-parser',
parser: 'babel-eslint',
plugins: [
'react',
@ -27,7 +27,7 @@ module.exports = {
experimentalObjectRestSpread: true,
jsx: true,
},
ecmaVersion: 2021,
ecmaVersion: 2018,
},
settings: {
@ -79,11 +79,6 @@ module.exports = {
'no-irregular-whitespace': 'error',
'no-mixed-spaces-and-tabs': 'warn',
'no-nested-ternary': 'warn',
'no-restricted-properties': [
'error',
{ property: 'substring', message: 'Use .slice instead of .substring.' },
{ property: 'substr', message: 'Use .slice instead of .substr.' },
],
'no-trailing-spaces': 'warn',
'no-undef': 'error',
'no-unreachable': 'error',
@ -204,11 +199,6 @@ module.exports = {
'import/no-unresolved': 'error',
'import/no-webpack-loader-syntax': 'error',
'promise/catch-or-return': [
'error',
{
allowFinally: true,
},
],
'promise/catch-or-return': 'error',
},
};

32
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,32 @@
# CODEOWNERS for tootsuite/mastodon
# Translators
# To add translator, copy these lines, replace `fr` with appropriate language code and replace `@żelipapą` with user's GitHub nickname preceded by `@` sign or e-mail address.
# /app/javascript/mastodon/locales/fr.json @żelipapą
# /app/views/user_mailer/*.fr.html.erb @żelipapą
# /app/views/user_mailer/*.fr.text.erb @żelipapą
# /config/locales/*.fr.yml @żelipapą
# /config/locales/fr.yml @żelipapą
# Polish
/app/javascript/mastodon/locales/pl.json @m4sk1n
/app/views/user_mailer/*.pl.html.erb @m4sk1n
/app/views/user_mailer/*.pl.text.erb @m4sk1n
/config/locales/*.pl.yml @m4sk1n
/config/locales/pl.yml @m4sk1n
# French
/app/javascript/mastodon/locales/fr.json @aldarone
/app/javascript/mastodon/locales/whitelist_fr.json @aldarone
/app/views/user_mailer/*.fr.html.erb @aldarone
/app/views/user_mailer/*.fr.text.erb @aldarone
/config/locales/*.fr.yml @aldarone
/config/locales/fr.yml @aldarone
# Dutch
/app/javascript/mastodon/locales/nl.json @jeroenpraat
/app/javascript/mastodon/locales/whitelist_nl.json @jeroenpraat
/app/views/user_mailer/*.nl.html.erb @jeroenpraat
/app/views/user_mailer/*.nl.text.erb @jeroenpraat
/config/locales/*.nl.yml @jeroenpraat
/config/locales/nl.yml @jeroenpraat

1
.github/FUNDING.yml vendored
View File

@ -1,3 +1,2 @@
patreon: mastodon
open_collective: mastodon
custom: https://sponsor.joinmastodon.org

View File

@ -1,56 +0,0 @@
name: Bug Report
description: If something isn't working as expected
labels: [bug]
body:
- type: markdown
attributes:
value: |
Make sure that you are submitting a new bug that was not previously reported or already fixed.
Please use a concise and distinct title for the issue.
- type: textarea
attributes:
label: Steps to reproduce the problem
description: What were you trying to do?
value: |
1.
2.
3.
...
validations:
required: true
- type: input
attributes:
label: Expected behaviour
description: What should have happened?
validations:
required: true
- type: input
attributes:
label: Actual behaviour
description: What happened?
validations:
required: true
- type: textarea
attributes:
label: Detailed description
validations:
required: false
- type: textarea
attributes:
label: Specifications
description: |
What version or commit hash of Mastodon did you find this bug in?
If a front-end issue, what browser and operating systems were you using?
placeholder: |
Mastodon 3.5.3 (or Edge)
Ruby 2.7.6 (or v3.1.2)
Node.js 16.18.0
Google Chrome 106.0.5249.119
Firefox 105.0.3
etc...
validations:
required: true

View File

@ -1,22 +0,0 @@
name: Feature Request
description: I have a suggestion
labels: [suggestion]
body:
- type: markdown
attributes:
value: |
Please use a concise and distinct title for the issue.
Consider: Could it be implemented as a 3rd party app using the REST API instead?
- type: textarea
attributes:
label: Pitch
description: Describe your idea for a feature. Make sure it has not already been suggested/implemented/turned down before.
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: Why do you think this feature is needed? Who would benefit from it?
validations:
required: true

12
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,12 @@
---
name: Bug Report
about: Create a report to help us improve
---
[Issue text goes here].
* * * *
- [ ] I searched or browsed the repos other issues to ensure this is not a duplicate.
- [ ] This bugs also occur on vanilla Mastodon

View File

@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Discussions
url: https://github.com/mastodon/mastodon/discussions
about: Please ask and answer questions here.

View File

@ -0,0 +1,17 @@
---
name: Feature Request
about: I have a suggestion
---
<!-- Please use a concise and distinct title for the issue -->
<!-- Consider: Could it be implemented as a 3rd party app using the REST API instead? -->
### Pitch
<!-- Describe your idea for a feature. Make sure it has not already been suggested/implemented/turned down before -->
### Motivation
<!-- Why do you think this feature is needed? Who would benefit from it? -->

10
.github/ISSUE_TEMPLATE/support.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: Support
about: Ask for help with your deployment
---
We primarily use GitHub as a bug and feature tracker. For usage questions, troubleshooting of deployments and other individual technical assistance, please use one of the resources below:
- https://discourse.joinmastodon.org
- #mastodon on irc.freenode.net

10
.github/stale.yml vendored
View File

@ -1,10 +0,0 @@
daysUntilStale: 120
daysUntilClose: 7
exemptLabels:
- security
staleLabel: wontfix
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
only: pulls

View File

@ -1,21 +0,0 @@
{
"problemMatcher": [
{
"owner": "stylelint",
"pattern": [
{
"regexp": "^([^\\s].*)$",
"file": 1
},
{
"regexp": "^\\s+((\\d+):(\\d+))?\\s+(✖|×)\\s+(.*)\\s{2,}(.*)$",
"line": 2,
"column": 3,
"message": 5,
"code": 6,
"loop": true
}
]
}
]
}

View File

@ -1,44 +0,0 @@
name: Build container image
on:
workflow_dispatch:
push:
branches:
- 'main'
pull_request:
paths:
- .github/workflows/build-image.yml
- Dockerfile
permissions:
contents: read
packages: write
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
if: github.event_name != 'pull_request'
- uses: docker/metadata-action@v4
id: meta
with:
images: ghcr.io/${{ github.repository_owner }}/mastodon
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=edge,branch=main
type=sha,prefix=,format=long
- uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64
builder: ${{ steps.buildx.outputs.name }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -1,37 +0,0 @@
name: Check i18n
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
RAILS_ENV: test
permissions:
contents: read
jobs:
check-i18n:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- name: Check locale file normalization
run: bundle exec i18n-tasks check-normalized
- name: Check for unused strings
run: bundle exec i18n-tasks unused -l en
- name: Check for wrong string interpolations
run: bundle exec i18n-tasks check-consistent-interpolations
- name: Check that all required locale files exist
run: bundle exec rake repo:check_locales_files

View File

@ -1,83 +0,0 @@
---
#################################
#################################
## Super Linter GitHub Actions ##
#################################
#################################
name: Lint Code Base
#
# Documentation:
# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
#
#############################
# Start the job on all push #
#############################
on:
push:
branches-ignore: [main]
# Remove the line above to run when pushing to master
pull_request:
branches: [main]
###############
# Set the Job #
###############
permissions:
checks: write
contents: read
pull-requests: write
statuses: write
jobs:
build:
# Name the Job
name: Lint Code Base
# Set the agent to run on
runs-on: ubuntu-latest
##################
# Load all steps #
##################
steps:
##########################
# Checkout the code base #
##########################
- name: Checkout Code
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set-up Node.js
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: yarn
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Set-up RuboCop Problem Mathcher
uses: r7kamura/rubocop-problem-matchers-action@v1
- name: Set-up Stylelint Problem Matcher
uses: xt0rted/stylelint-problem-matcher@v1
# https://github.com/xt0rted/stylelint-problem-matcher/issues/360
- run: echo "::add-matcher::.github/stylelint-matcher.json"
################################
# Run Linter against code base #
################################
- name: Lint Code Base
uses: github/super-linter@v4
env:
CSS_FILE_NAME: stylelint.config.js
DEFAULT_BRANCH: main
NO_COLOR: 1 # https://github.com/xt0rted/stylelint-problem-matcher/issues/360
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.js
LINTER_RULES_PATH: .
RUBY_CONFIG_FILE: .rubocop.yml
VALIDATE_ALL_CODEBASE: false
VALIDATE_CSS: true
VALIDATE_JAVASCRIPT_ES: true
VALIDATE_RUBY: true

View File

@ -1,138 +0,0 @@
# This is a GitHub workflow defining a set of jobs with a set of steps.
# ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
#
name: Test chart
on:
pull_request:
paths:
- "chart/**"
- "!**.md"
- ".github/workflows/test-chart.yml"
push:
paths:
- "chart/**"
- "!**.md"
- ".github/workflows/test-chart.yml"
branches-ignore:
- "dependabot/**"
workflow_dispatch:
permissions:
contents: read
defaults:
run:
working-directory: chart
jobs:
lint-templates:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install dependencies (yamllint)
run: pip install yamllint
- run: helm dependency update
- name: helm lint
run: |
helm lint . \
--values dev-values.yaml
- name: helm template
run: |
helm template . \
--values dev-values.yaml \
--output-dir rendered-templates
- name: yamllint (only on templates we manage)
run: |
rm -rf rendered-templates/mastodon/charts
yamllint rendered-templates \
--config-data "{rules: {indentation: {spaces: 2}, line-length: disable}}"
# This job helps us validate that rendered templates are valid k8s resources
# against a k8s api-server, via "helm template --validate", but also that a
# basic configuration can be used to successfully startup mastodon.
#
test-install:
runs-on: ubuntu-22.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
# k3s-channel reference: https://update.k3s.io/v1-release/channels
- k3s-channel: latest
- k3s-channel: stable
# This represents the oldest configuration we test against.
#
# The k8s version chosen is based on the oldest still supported k8s
# version among two managed k8s services, GKE, EKS.
# - GKE: https://endoflife.date/google-kubernetes-engine
# - EKS: https://endoflife.date/amazon-eks
#
# The helm client's version can influence what helper functions is
# available for use in the templates, currently we need v3.6.0 or
# higher.
#
- k3s-channel: v1.21
helm-version: v3.6.0
steps:
- uses: actions/checkout@v3
# This action starts a k8s cluster with NetworkPolicy enforcement and
# installs both kubectl and helm.
#
# ref: https://github.com/jupyterhub/action-k3s-helm#readme
#
- uses: jupyterhub/action-k3s-helm@v3
with:
k3s-channel: ${{ matrix.k3s-channel }}
helm-version: ${{ matrix.helm-version }}
metrics-enabled: false
traefik-enabled: false
docker-enabled: false
- run: helm dependency update
# Validate rendered helm templates against the k8s api-server
- name: helm template --validate
run: |
helm template --validate mastodon . \
--values dev-values.yaml
- name: helm install
run: |
helm install mastodon . \
--values dev-values.yaml \
--timeout 10m
# This actions provides a report about the state of the k8s cluster,
# providing logs etc on anything that has failed and workloads marked as
# important.
#
# ref: https://github.com/jupyterhub/action-k8s-namespace-report#readme
#
- name: Kubernetes namespace report
uses: jupyterhub/action-k8s-namespace-report@v1
if: always()
with:
important-workloads: >-
deploy/mastodon-sidekiq
deploy/mastodon-streaming
deploy/mastodon-web
job/mastodon-assets-precompile
job/mastodon-chewy-upgrade
job/mastodon-create-admin
job/mastodon-db-migrate

35
.gitignore vendored
View File

@ -13,42 +13,33 @@
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
.eslintcache
/log/*
!/log/.keep
/tmp
/coverage
/public/system
/public/assets
/public/packs
/public/packs-test
coverage
public/system
public/assets
public/packs
public/packs-test
.env
.env.production
.env.development
/node_modules/
/build/
node_modules/
build/
# Ignore Vagrant files
.vagrant/
# Ignore Capistrano customizations
/config/deploy/*
config/deploy/*
# Ignore IDE files
.vscode/
.idea/
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
/postgres
/postgres14
/redis
/elasticsearch
# ignore Helm charts
/chart/*.tgz
# ignore Helm dependency charts
/chart/charts/*.tgz
postgres
redis
elasticsearch
# Ignore Apple files
.DS_Store
@ -64,8 +55,6 @@ npm-debug.log
yarn-error.log
yarn-debug.log
# Ignore vagrant log files
*-cloudimg-console.log
# Ignore Docker option files
docker-compose.override.yml

2
.nvmrc
View File

@ -1 +1 @@
14
8

View File

@ -1,78 +0,0 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config and downloaded libraries.
/.bundle
/vendor/bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
.eslintcache
/log/*
!/log/.keep
/tmp
/coverage
/public/system
/public/assets
/public/packs
/public/packs-test
.env
.env.production
.env.development
/node_modules/
/build/
# Ignore Vagrant files
.vagrant/
# Ignore Capistrano customizations
/config/deploy/*
# Ignore IDE files
.vscode/
.idea/
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
/postgres
/postgres14
/redis
/elasticsearch
# ignore Helm dependency charts
/chart/charts/*.tgz
# Ignore Apple files
.DS_Store
# Ignore vim files
*~
*.swp
# Ignore npm debug log
npm-debug.log
# Ignore yarn log files
yarn-error.log
yarn-debug.log
# Ignore vagrant log files
*-cloudimg-console.log
# Ignore Docker option files
docker-compose.override.yml
# Ignore Helm files
/chart
# Ignore emoji map file
/app/javascript/mastodon/features/emoji/emoji_map.json
# Ignore locale files
/app/javascript/mastodon/locales
/config/locales

View File

@ -1,3 +0,0 @@
module.exports = {
singleQuote: true
}

View File

@ -1,21 +1,17 @@
require:
- rubocop-rails
AllCops:
TargetRubyVersion: 2.7
NewCops: disable
TargetRubyVersion: 2.3
Exclude:
- 'spec/**/*'
- 'db/**/*'
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
- 'Rakefile'
- 'node_modules/**/*'
- 'Vagrantfile'
- 'vendor/**/*'
- 'lib/json_ld/*'
- 'lib/templates/**/*'
- 'spec/**/*'
- 'db/**/*'
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
- 'Rakefile'
- 'node_modules/**/*'
- 'Vagrantfile'
- 'vendor/**/*'
- 'lib/json_ld/*'
- 'lib/templates/**/*'
Bundler/OrderedGems:
Enabled: false
@ -26,82 +22,34 @@ Layout/AccessModifierIndentation:
Layout/EmptyLineAfterMagicComment:
Enabled: false
Layout/EmptyLineAfterGuardClause:
Enabled: false
Layout/EmptyLineBetweenDefs:
AllowAdjacentOneLineDefs: true
Layout/EmptyLinesAroundAttributeAccessor:
Enabled: true
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
Layout/HashAlignment:
Enabled: false
Layout/SpaceAroundMethodCallOperator:
Enabled: true
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: space
Lint/DeprecatedOpenSSLConstant:
Enabled: true
Lint/DuplicateElsifCondition:
Enabled: true
Lint/MixedRegexpCaptureTypes:
Enabled: true
Lint/RaiseException:
Enabled: true
Lint/StructNewOverride:
Enabled: true
Lint/UselessAccessModifier:
ContextCreatingMethods:
- class_methods
Metrics/AbcSize:
Max: 115
Exclude:
- 'lib/mastodon/*_cli.rb'
Max: 100
Metrics/BlockLength:
Max: 55
Max: 35
Exclude:
- 'lib/tasks/**/*'
- 'lib/mastodon/*_cli.rb'
Metrics/BlockNesting:
Max: 3
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/ClassLength:
CountComments: false
Max: 500
Exclude:
- 'lib/mastodon/*_cli.rb'
Max: 300
Metrics/CyclomaticComplexity:
Max: 25
Exclude:
- 'lib/mastodon/*_cli.rb'
Layout/LineLength:
Metrics/LineLength:
AllowURI: true
Enabled: false
Metrics/MethodLength:
CountComments: false
Max: 65
Exclude:
- 'lib/mastodon/*_cli.rb'
Max: 55
Metrics/ModuleLength:
CountComments: false
@ -112,29 +60,21 @@ Metrics/ParameterLists:
CountKeywordArgs: true
Metrics/PerceivedComplexity:
Max: 25
Max: 20
Naming/MemoizedInstanceVariableName:
Enabled: false
Naming/MethodParameterName:
Enabled: true
Rails:
Enabled: true
Rails/ApplicationController:
Enabled: false
Exclude:
- 'app/controllers/well_known/**/*.rb'
Rails/BelongsTo:
Rails/HasAndBelongsToMany:
Enabled: false
Rails/ContentTag:
Rails/SkipsModelValidations:
Enabled: false
Rails/EnumHash:
Rails/HttpStatus:
Enabled: false
Rails/Exit:
@ -142,60 +82,6 @@ Rails/Exit:
- 'lib/mastodon/*'
- 'lib/cli.rb'
Rails/FilePath:
Enabled: false
Rails/HasAndBelongsToMany:
Enabled: false
Rails/HasManyOrHasOneDependent:
Enabled: false
Rails/HelperInstanceVariable:
Enabled: false
Rails/HttpStatus:
Enabled: false
Rails/IndexBy:
Enabled: false
Rails/InverseOf:
Enabled: false
Rails/LexicallyScopedActionFilter:
Enabled: false
Rails/OutputSafety:
Enabled: true
Rails/RakeEnvironment:
Enabled: false
Rails/RedundantForeignKey:
Enabled: false
Rails/SkipsModelValidations:
Enabled: false
Rails/UniqueValidationWithoutIndex:
Enabled: false
Style/AccessorGrouping:
Enabled: true
Style/AccessModifierDeclarations:
Enabled: false
Style/ArrayCoercion:
Enabled: true
Style/BisectedAttrAccessor:
Enabled: true
Style/CaseLikeIf:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
@ -210,55 +96,15 @@ Style/Documentation:
Style/DoubleNegation:
Enabled: true
Style/ExpandPathArguments:
Enabled: false
Style/ExponentialNotation:
Enabled: true
Style/FormatString:
Enabled: false
Style/FormatStringToken:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: true
Style/GuardClause:
Enabled: false
Style/HashAsLastArrayItem:
Enabled: false
Style/HashEachMethods:
Enabled: true
Style/HashLikeCase:
Enabled: true
Style/HashTransformKeys:
Enabled: true
Style/HashTransformValues:
Enabled: false
Style/HashSyntax:
Enabled: true
EnforcedStyle: ruby19_no_mixed_keys
Style/IfUnlessModifier:
Enabled: false
Style/InverseMethods:
Enabled: false
Style/Lambda:
Enabled: false
Style/MutableConstant:
Enabled: false
Style/PercentLiteralDelimiters:
PreferredDelimiters:
'%i': '()'
@ -267,39 +113,9 @@ Style/PercentLiteralDelimiters:
Style/PerlBackrefs:
AutoCorrect: false
Style/RedundantAssignment:
Enabled: false
Style/RedundantFetchBlock:
Enabled: true
Style/RedundantFileExtensionInRequire:
Enabled: true
Style/RedundantRegexpCharacterClass:
Enabled: false
Style/RedundantRegexpEscape:
Enabled: false
Style/RedundantReturn:
Enabled: true
Style/RedundantBegin:
Enabled: false
Style/RegexpLiteral:
Enabled: false
Style/RescueStandardError:
Enabled: false
Style/SignalException:
Enabled: false
Style/SlicingWithRange:
Enabled: true
Style/SymbolArray:
Enabled: false
@ -308,6 +124,3 @@ Style/TrailingCommaInArrayLiteral:
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: 'comma'
Style/UnpackFirst:
Enabled: false

View File

@ -1 +0,0 @@
mastodon

View File

@ -1 +1 @@
3.0.4
2.6.1

264
.sass-lint.yml Normal file
View File

@ -0,0 +1,264 @@
# Linter Documentation:
# https://github.com/sasstools/sass-lint/tree/v1.13.1/docs/options
files:
include: app/javascript/styles/**/*.scss
ignore:
- app/javascript/styles/reset.scss
linters:
# Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags.
BangFormat:
enabled: false
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
# Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes).
ChainedClasses:
enabled: false
# Prefer hexadecimal color codes over color keywords.
# (e.g. `color: green` is a color keyword)
ColorKeyword:
enabled: false
# Prefer color literals (keywords or hexadecimal codes) to be used only in
# variable declarations. They should be referred to via variables everywhere
# else.
ColorVariable:
enabled: true
# Which form of comments to prefer in CSS.
Comment:
enabled: false
# Reports @debug statements (which you probably left behind accidentally).
DebugStatement:
enabled: false
# Rule sets should be ordered as follows:
# - @extend declarations
# - @include declarations without inner @content
# - properties, @include declarations with inner @content
# - nested rule sets.
DeclarationOrder:
enabled: false
# `scss-lint:disable` control comments should be preceded by a comment
# explaining why these linters are being disabled for this file.
# See https://github.com/brigade/scss-lint#disabling-linters-via-source for
# more information.
DisableLinterReason:
enabled: true
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
enabled: true
# Reports when you have an empty rule set.
EmptyRule:
enabled: true
# Reports when you have an @extend directive.
ExtendDirective:
enabled: false
# Files should always have a final newline. This results in better diffs
# when adding lines to the file, since SCM systems such as git won't
# think that you touched the last line.
FinalNewline:
enabled: false
# HEX colors should use three-character values where possible.
HexLength:
enabled: false
# HEX color values should use lower-case colors to differentiate between
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
HexNotation:
enabled: true
# Avoid using ID selectors.
IdSelector:
enabled: false
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath:
enabled: false
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule:
enabled: false
# Indentation should always be done in increments of 2 spaces.
Indentation:
enabled: true
width: 2
# Don't write leading zeros for numeric values with a decimal point.
LeadingZero:
enabled: false
# Reports when you define the same selector twice in a single sheet.
MergeableSelector:
enabled: false
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
NameFormat:
enabled: false
# Avoid nesting selectors too deeply.
NestingDepth:
enabled: false
# Always use placeholder selectors in @extend.
PlaceholderInExtend:
enabled: false
# Sort properties in a strict order.
PropertySortOrder:
enabled: false
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling:
enabled: false
# Configure which units are allowed for property values.
PropertyUnits:
enabled: false
# Pseudo-elements, like ::before, and ::first-letter, should be declared
# with two colons. Pseudo-classes, like :hover and :first-child, should
# be declared with one colon.
PseudoElement:
enabled: true
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement:
enabled: false
# Don't write selectors with a depth of applicability greater than 3.
SelectorDepth:
enabled: false
# Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case.
SelectorFormat:
enabled: false
convention: hyphenated_lowercase
# Prefer the shortest shorthand form possible for properties that support it.
Shorthand:
enabled: true
# Each property should have its own line, except in the special case of
# single line rulesets.
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: true
# Split selectors onto separate lines after each comma, and have each
# individual selector occupy a single line.
SingleLinePerSelector:
enabled: true
# Commas in lists should be followed by a space.
SpaceAfterComma:
enabled: false
# Properties should be formatted with a single space separating the colon
# from the property's value.
SpaceAfterPropertyColon:
enabled: true
# Properties should be formatted with no space between the name and the
# colon.
SpaceAfterPropertyName:
enabled: true
# Variables should be formatted with a single space separating the colon
# from the variable's value.
SpaceAfterVariableColon:
enabled: true
# Variables should be formatted with no space between the name and the
# colon.
SpaceAfterVariableName:
enabled: false
# Operators should be formatted with a single space on both sides of an
# infix operator.
SpaceAroundOperator:
enabled: true
# Opening braces should be preceded by a single space.
SpaceBeforeBrace:
enabled: true
# Parentheses should not be padded with spaces.
SpaceBetweenParens:
enabled: false
# Enforces that string literals should be written with a consistent form
# of quotes (single or double).
StringQuotes:
enabled: false
# Property values, @extend, @include, and @import directives, and variable
# declarations should always end with a semicolon.
TrailingSemicolon:
enabled: true
# Reports lines containing trailing whitespace.
TrailingWhitespace:
enabled: true
# Don't write trailing zeros for numeric values with a decimal point.
TrailingZero:
enabled: false
# Don't use the `all` keyword to specify transition properties.
TransitionAll:
enabled: false
# Numeric values should not contain unnecessary fractional portions.
UnnecessaryMantissa:
enabled: false
# Do not use parent selector references (&) when they would otherwise
# be unnecessary.
UnnecessaryParentReference:
enabled: false
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
enabled: true
# URLs should always be enclosed within quotes.
UrlQuotes:
enabled: true
# Properties, like color and font, are easier to read and maintain
# when defined using variables rather than literals.
VariableForProperty:
enabled: false
# Avoid vendor prefixes. Or rather: don't write them yourself.
VendorPrefix:
enabled: false
# Omit length units on zero values, e.g. `0px` vs. `0`.
ZeroUnit:
enabled: true

1891
AUTHORS.md

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,14 @@
ffmpeg
libicu[0-9][0-9]
libicu-dev
libidn12
libidn-dev
libidn11
libidn11-dev
libpq-dev
libprotobuf-dev
libssl-dev
libxdamage1
libxfixes3
protobuf-compiler
zlib1g-dev
libcairo2
libcroco3
@ -20,7 +23,7 @@ libpixman-1-0
librsvg2-2
libthai-data
libthai0
libvpx[5-9]
libvpx5
libxcb-render0
libxcb-shm0
libxrender1

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ See the guidelines below.
- - -
You should also try to follow the guidelines set out in the original `CONTRIBUTING.md` from `mastodon/mastodon`, reproduced below.
You should also try to follow the guidelines set out in the original `CONTRIBUTING.md` from `tootsuite/mastodon`, reproduced below.
<blockquote>
@ -48,27 +48,17 @@ If your contributions are accepted into Mastodon, you can request to be paid thr
## Bug reports
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/mastodon/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles.
## Translations
You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). They are periodically merged into the codebase.
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)](https://crowdin.com/project/mastodon)
You can submit translations via pull request.
## Pull requests
**Please use clean, concise titles for your pull requests.** Unless the pull request is about refactoring code, updating dependencies or other internal tasks, assume that the person reading the pull request title is not a programmer or Mastodon developer, but instead a Mastodon user or server administrator, and **try to describe your change or fix from their perspective**. We use commit squashing, so the final commit in the main branch will carry the title of the pull request, and commits from the main branch are fed into the changelog. The changelog is separated into [keepachangelog.com categories](https://keepachangelog.com/en/1.0.0/), and while that spec does not prescribe how the entries ought to be named, for easier sorting, start your pull request titles using one of the verbs "Add", "Change", "Deprecate", "Remove", or "Fix" (present tense).
Please use clean, concise titles for your pull requests. We use commit squashing, so the final commit in the master branch will carry the title of the pull request.
Example:
|Not ideal|Better|
|---|----|
|Fixed NoMethodError in RemovalWorker|Fix nil error when removing statuses caused by race condition|
It is not always possible to phrase every change in such a manner, but it is desired.
**The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable.
The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged. Splitting tasks into multiple smaller pull requests is often preferable.
**Pull requests that do not pass automated checks may not be reviewed**. In particular, you need to keep in mind:
@ -76,10 +66,8 @@ It is not always possible to phrase every change in such a manner, but it is des
- Code style rules (rubocop, eslint)
- Normalization of locale files (i18n-tasks)
**Note**: You may need to log in and authorise the GitHub account your fork of this repository belongs to with CircleCI to enable some of the automated checks to run.
## Documentation
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/docs](https://source.joinmastodon.org/mastodon/docs).
</blockquote>

View File

@ -1,96 +1,126 @@
# syntax=docker/dockerfile:1.4
# This needs to be bullseye-slim because the Ruby image is built on bullseye-slim
ARG NODE_VERSION="16.17.1-bullseye-slim"
FROM ubuntu:18.04 as build-dep
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.0.4-slim as ruby
FROM node:${NODE_VERSION} as build
# Use bash for the shell
SHELL ["bash", "-c"]
COPY --link --from=ruby /opt/ruby /opt/ruby
# Install Node
ENV NODE_VER="8.15.0"
RUN echo "Etc/UTC" > /etc/localtime && \
apt update && \
apt -y install wget make gcc g++ python && \
cd ~ && \
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \
tar xf node-v$NODE_VER.tar.gz && \
cd node-v$NODE_VER && \
./configure --prefix=/opt/node && \
make -j$(nproc) > /dev/null && \
make install
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin"
# Install jemalloc
ENV JE_VER="5.1.0"
RUN apt update && \
apt -y install autoconf && \
cd ~ && \
wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \
tar xf $JE_VER.tar.gz && \
cd jemalloc-$JE_VER && \
./autogen.sh && \
./configure --prefix=/opt/jemalloc && \
make -j$(nproc) > /dev/null && \
make install_bin install_include install_lib
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install ruby
ENV RUBY_VER="2.6.1"
ENV CPPFLAGS="-I/opt/jemalloc/include"
ENV LDFLAGS="-L/opt/jemalloc/lib/"
RUN apt update && \
apt -y install build-essential \
bison libyaml-dev libgdbm-dev libreadline-dev \
libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \
cd ~ && \
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
tar xf ruby-$RUBY_VER.tar.gz && \
cd ruby-$RUBY_VER && \
./configure --prefix=/opt/ruby \
--with-jemalloc \
--with-shared \
--disable-install-doc && \
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
make -j$(nproc) > /dev/null && \
make install
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
RUN npm install -g yarn && \
gem install bundler && \
apt update && \
apt -y install git libicu-dev libidn11-dev \
libpq-dev libprotobuf-dev protobuf-compiler
WORKDIR /opt/mastodon
COPY Gemfile* package.json yarn.lock /opt/mastodon/
RUN cd /opt/mastodon && \
bundle install -j$(nproc) --deployment --without development test && \
yarn install --pure-lockfile
FROM ubuntu:18.04
# Copy over all the langs needed for runtime
COPY --from=build-dep /opt/node /opt/node
COPY --from=build-dep /opt/ruby /opt/ruby
COPY --from=build-dep /opt/jemalloc /opt/jemalloc
# Add more PATHs to the PATH
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
# Create the mastodon user
ARG UID=991
ARG GID=991
RUN apt update && \
apt-get install -y --no-install-recommends build-essential \
ca-certificates \
git \
libicu-dev \
libidn11-dev \
libpq-dev \
libjemalloc-dev \
zlib1g-dev \
libgdbm-dev \
libgmp-dev \
libssl-dev \
libyaml-0-2 \
ca-certificates \
libreadline8 \
python3 \
shared-mime-info && \
bundle config set --local deployment 'true' && \
bundle config set --local without 'development test' && \
bundle config set silence_root_warning true && \
bundle install -j"$(nproc)" && \
yarn install --pure-lockfile
echo "Etc/UTC" > /etc/localtime && \
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
apt install -y whois wget && \
addgroup --gid $GID mastodon && \
useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \
echo "mastodon:`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256`" | chpasswd
FROM node:${NODE_VERSION}
# Install mastodon runtime deps
RUN apt -y --no-install-recommends install \
libssl1.1 libpq5 imagemagick ffmpeg \
libicu60 libprotobuf10 libidn11 libyaml-0-2 \
file ca-certificates tzdata libreadline7 && \
apt -y install gcc && \
ln -s /opt/mastodon /mastodon && \
gem install bundler && \
rm -rf /var/cache && \
rm -rf /var/lib/apt/lists/*
ARG UID="991"
ARG GID="991"
COPY --link --from=ruby /opt/ruby /opt/ruby
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
RUN apt-get update && \
echo "Etc/UTC" > /etc/localtime && \
groupadd -g "${GID}" mastodon && \
useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
apt-get -y --no-install-recommends install whois \
wget \
procps \
libssl1.1 \
libpq5 \
imagemagick \
ffmpeg \
libjemalloc2 \
libicu67 \
libidn11 \
libyaml-0-2 \
file \
ca-certificates \
tzdata \
libreadline8 \
tini && \
ln -s /opt/mastodon /mastodon
# Note: no, cleaning here since Debian does this automatically
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
# Add tini
ENV TINI_VERSION="0.18.0"
ENV TINI_SUM="12d20136605531b09a2c2dac02ccee85e1b874eb322ef6baf7561cd93f93c855"
ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tini
RUN echo "$TINI_SUM tini" | sha256sum -c -
RUN chmod +x /tini
# Copy over mastodon source, and dependencies from building, and set permissions
COPY --chown=mastodon:mastodon . /opt/mastodon
COPY --chown=mastodon:mastodon --from=build /opt/mastodon /opt/mastodon
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
ENV RAILS_ENV="production" \
NODE_ENV="production" \
RAILS_SERVE_STATIC_FILES="true" \
BIND="0.0.0.0"
# Run mastodon services in prod mode
ENV RAILS_ENV="production"
ENV NODE_ENV="production"
# Tell rails to serve static files
ENV RAILS_SERVE_STATIC_FILES="true"
# Set the run user
USER mastodon
WORKDIR /opt/mastodon
# Precompile assets
RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
yarn cache clean
RUN cd ~ && \
OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
yarn cache clean
# Set the work dir and the container entry point
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 3000 4000
WORKDIR /opt/mastodon
ENTRYPOINT ["/tini", "--"]

View File

@ -1,30 +0,0 @@
## ActivityPub federation in Mastodon
Mastodon largely follows the ActivityPub server-to-server specification but it makes uses of some non-standard extensions, some of which are required for interacting with Mastodon at all.
Supported vocabulary: https://docs.joinmastodon.org/spec/activitypub/
### Required extensions
#### Webfinger
In Mastodon, users are identified by a `username` and `domain` pair (e.g., `Gargron@mastodon.social`).
This is used both for discovery and for unambiguously mentioning users across the fediverse. Furthermore, this is part of Mastodon's database design from its very beginnings.
As a result, Mastodon requires that each ActivityPub actor uniquely maps back to an `acct:` URI that can be resolved via WebFinger.
More information and examples are available at: https://docs.joinmastodon.org/spec/webfinger/
#### HTTP Signatures
In order to authenticate activities, Mastodon relies on HTTP Signatures, signing every `POST` and `GET` request to other ActivityPub implementations on behalf of the user authoring an activity (for `POST` requests) or an actor representing the Mastodon server itself (for most `GET` requests).
Mastodon requires all `POST` requests to be signed, and MAY require `GET` requests to be signed, depending on the configuration of the Mastodon server.
More information on HTTP Signatures, as well as examples, can be found here: https://docs.joinmastodon.org/spec/security/#http
### Optional extensions
- Linked-Data Signatures: https://docs.joinmastodon.org/spec/security/#ld
- Bearcaps: https://docs.joinmastodon.org/spec/bearcaps/
- Followers collection synchronization: https://git.activitypub.dev/ActivityPubDev/Fediverse-Enhancement-Proposals/src/branch/main/feps/fep-8fcf.md

191
Gemfile
View File

@ -1,111 +1,109 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 2.7.0', '< 3.1.0'
ruby '>= 2.4.0', '< 2.7.0'
gem 'pkg-config', '~> 1.4'
gem 'rexml', '~> 3.2'
gem 'pkg-config', '~> 1.3'
gem 'puma', '~> 5.6'
gem 'rails', '~> 6.1.7'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.4'
gem 'puma', '~> 3.12'
gem 'rails', '~> 5.2.3'
gem 'thor', '~> 0.20'
gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.4'
gem 'makara', '~> 0.5'
gem 'pghero', '~> 2.8'
gem 'dotenv-rails', '~> 2.8'
gem 'pg', '~> 1.1'
gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.2'
gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.114', require: false
gem 'aws-sdk-s3', '~> 1.41', require: false
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
gem 'kt-paperclip', '~> 7.1'
gem 'paperclip', '~> 6.0'
gem 'paperclip-av-transcoder', '~> 0.6'
gem 'streamio-ffmpeg', '~> 3.0'
gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.14.0', require: false
gem 'addressable', '~> 2.6'
gem 'bootsnap', '~> 1.4', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.2'
gem 'devise', '~> 4.8'
gem 'devise-two-factor', '~> 4.0'
gem 'charlock_holmes', '~> 0.7.6'
gem 'iso-639'
gem 'chewy', '~> 5.0'
gem 'cld3', '~> 3.2.4'
gem 'devise', '~> 4.6'
gem 'devise-two-factor', '~> 3.0'
group :pam_authentication, optional: true do
gem 'devise_pam_authenticatable2', '~> 9.2'
end
gem 'net-ldap', '~> 0.17'
gem 'omniauth-cas', '~> 2.0'
gem 'net-ldap', '~> 0.10'
gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10'
gem 'gitlab-omniauth-openid-connect', '~>0.10.0', require: 'omniauth_openid_connect'
gem 'omniauth', '~> 1.9'
gem 'omniauth-rails_csrf_protection', '~> 0.1'
gem 'color_diff', '~> 0.1'
gem 'discard', '~> 1.2'
gem 'doorkeeper', '~> 5.6'
gem 'ed25519', '~> 1.3'
gem 'doorkeeper', '~> 5.1'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'goldfinger', '~> 2.1'
gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.9'
gem 'redis-namespace', '~> 1.5'
gem 'html2text'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 5.1'
gem 'http', '~> 3.3'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.6.2'
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
gem 'httplog', '~> 1.3'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2'
gem 'kaminari', '~> 1.1'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.13'
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.10'
gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.13'
gem 'ox', '~> 2.14'
gem 'parslet'
gem 'posix-spawn'
gem 'pundit', '~> 2.2'
gem 'oj', '~> 3.7'
gem 'ostatus2', '~> 2.0'
gem 'ox', '~> 2.10'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
gem 'pundit', '~> 2.0'
gem 'premailer-rails'
gem 'rack-attack', '~> 6.6'
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
gem 'rails-i18n', '~> 6.0'
gem 'rack-attack', '~> 6.0'
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
gem 'rails-i18n', '~> 5.1'
gem 'rails-settings-cached', '~> 0.6'
gem 'redcarpet', '~> 3.5'
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
gem 'redis', '~> 4.1', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 2.1'
gem 'ruby-progressbar', '~> 1.11'
gem 'sanitize', '~> 6.0'
gem 'scenic', '~> 1.6'
gem 'sidekiq', '~> 6.5'
gem 'sidekiq-scheduler', '~> 4.0'
gem 'sidekiq-unique-jobs', '~> 7.1'
gem 'sidekiq-bulk', '~> 0.2.0'
gem 'simple-navigation', '~> 4.4'
gem 'simple_form', '~> 5.1'
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
gem 'stoplight', '~> 3.0.0'
gem 'strong_migrations', '~> 0.7'
gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
gem 'tzinfo-data', '~> 1.2022'
gem 'webpacker', '~> 5.4'
gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
gem 'webauthn', '~> 2.5'
gem 'rqrcode', '~> 0.10'
gem 'sanitize', '~> 5.0'
gem 'sidekiq', '~> 5.2'
gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0'
gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.0'
gem 'simple_form', '~> 4.1'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.1.3'
gem 'strong_migrations', '~> 0.4'
gem 'tty-command', '~> 0.8', require: false
gem 'tty-prompt', '~> 0.19', require: false
gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2019'
gem 'webpacker', '~> 4.0'
gem 'webpush'
gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.2'
gem 'rdf-normalize', '~> 0.5'
gem 'json-ld', '~> 3.0'
gem 'json-ld-preloaded', '~> 3.0'
gem 'rdf-normalize', '~> 0.3'
gem 'redcarpet', '~> 3.4'
group :development, :test do
gem 'fabrication', '~> 2.30'
gem 'fuubar', '~> 2.5'
gem 'i18n-tasks', '~> 1.0', require: false
gem 'pry-byebug', '~> 3.10'
gem 'fabrication', '~> 2.20'
gem 'fuubar', '~> 2.4'
gem 'i18n-tasks', '~> 0.9', require: false
gem 'pry-byebug', '~> 3.7'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 5.1'
gem 'rspec-rails', '~> 3.8'
end
group :production, :test do
@ -113,47 +111,42 @@ group :production, :test do
end
group :test do
gem 'capybara', '~> 3.38'
gem 'capybara', '~> 3.22'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.23'
gem 'microformats', '~> 4.4'
gem 'faker', '~> 1.9'
gem 'microformats', '~> 4.1'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.21', require: false
gem 'webmock', '~> 3.18'
gem 'rspec_junit_formatter', '~> 0.6'
gem 'rack-test', '~> 2.0'
gem 'rspec-sidekiq', '~> 3.0'
gem 'simplecov', '~> 0.16', require: false
gem 'webmock', '~> 3.5'
gem 'parallel_tests', '~> 2.29'
end
group :development do
gem 'active_record_query_trace', '~> 1.8'
gem 'annotate', '~> 3.2'
gem 'better_errors', '~> 2.9'
gem 'binding_of_caller', '~> 1.0'
gem 'bullet', '~> 7.0'
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0'
gem 'active_record_query_trace', '~> 1.6'
gem 'annotate', '~> 2.7'
gem 'better_errors', '~> 2.5'
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.0'
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler'
gem 'rubocop', '~> 1.30', require: false
gem 'rubocop-rails', '~> 2.15', require: false
gem 'brakeman', '~> 5.4', require: false
gem 'bundler-audit', '~> 0.9', require: false
gem 'rubocop', '~> 0.71', require: false
gem 'brakeman', '~> 4.5', require: false
gem 'bundler-audit', '~> 0.6', require: false
gem 'capistrano', '~> 3.17'
gem 'capistrano-rails', '~> 1.6'
gem 'capistrano-rbenv', '~> 2.2'
gem 'capistrano', '~> 3.11'
gem 'capistrano-rails', '~> 1.4'
gem 'capistrano-rbenv', '~> 2.1'
gem 'capistrano-yarn', '~> 2.0'
gem 'derailed_benchmarks'
gem 'stackprof'
end
group :production do
gem 'lograge', '~> 0.12'
gem 'lograge', '~> 0.11'
gem 'redis-rails', '~> 5.0'
end
gem 'concurrent-ruby', require: false
gem 'connection_pool', require: false
gem 'xorcist', '~> 1.1'
gem 'hcaptcha', '~> 7.1'
gem 'cocoon', '~> 1.2'

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,2 @@
web: bin/heroku-web
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq
# For the streaming API, you need a separate app that shares Postgres and Redis:
#
# heroku create
# heroku buildpacks:add heroku/nodejs
# heroku config:set RUN_STREAMING=true
# heroku addons:attach <main-app>::DATABASE
# heroku addons:attach <main-app>::REDIS
#
# and let the main app use the separate app:
#
# heroku config:set STREAMING_API_BASE_URL=wss://<streaming-app>.herokuapp.com -a <main-app>

View File

@ -1,4 +1,4 @@
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
web: env PORT=3000 bundle exec puma -C config/puma.rb
sidekiq: env PORT=3000 bundle exec sidekiq
stream: env PORT=4000 yarn run start
webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0

View File

@ -3,12 +3,10 @@
> Now with automated deploys!
[![Build Status](https://img.shields.io/circleci/project/github/glitch-soc/mastodon.svg)][circleci]
[![Code Climate](https://img.shields.io/codeclimate/maintainability/glitch-soc/mastodon.svg)][code_climate]
[circleci]: https://circleci.com/gh/glitch-soc/mastodon
[code_climate]: https://codeclimate.com/github/glitch-soc/mastodon
So here's the deal: we all work on this code, and anyone who uses that does so absolutely at their own risk. can you dig it?
So here's the deal: we all work on this code, and then it runs on dev.glitch.social and anyone who uses that does so absolutely at their own risk. can you dig it?
- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).

View File

@ -1,17 +0,0 @@
# Security Policy
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can reach us at <security@joinmastodon.org>.
You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
## Scope
A "vulnerability in Mastodon" is a vulnerability in the code distributed through our main source code repository on GitHub. Vulnerabilities that are specific to a given installation (e.g. misconfiguration) should be reported to the owner of that installation and not us.
## Supported Versions
| Version | Supported |
| ------- | ----------|
| 4.0.x | Yes |
| 3.5.x | Yes |
| < 3.5 | No |

20
Vagrantfile vendored
View File

@ -12,7 +12,7 @@ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash -
curl -sL https://deb.nodesource.com/setup_8.x | sudo bash -
# Add firewall rule to redirect 80 to PORT and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
@ -33,9 +33,11 @@ sudo apt-get install \
redis-tools \
postgresql \
postgresql-contrib \
protobuf-compiler \
yarn \
libicu-dev \
libidn11-dev \
libprotobuf-dev \
libreadline-dev \
libpam0g-dev \
-y
@ -43,8 +45,16 @@ sudo apt-get install \
# Install rvm
read RUBY_VERSION < .ruby-version
curl -sSL https://rvm.io/mpapis.asc | gpg --import
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import
gpg_command="gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB"
$($gpg_command)
if [ $? -ne 0 ];then
echo "GPG command failed, This prevented RVM from installing."
echo "Retrying once..." && $($gpg_command)
if [ $? -ne 0 ];then
echo "GPG failed for the second time, please ensure network connectivity."
echo "Exiting..." && exit 1
fi
fi
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
source /home/vagrant/.rvm/scripts/rvm
@ -62,12 +72,10 @@ bundle install
yarn install
# Build Mastodon
export RAILS_ENV=development
export $(cat ".env.vagrant" | xargs)
bundle exec rails db:setup
# Configure automatic loading of environment variable
echo 'export RAILS_ENV=development' >> ~/.bash_profile
echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
SCRIPT
@ -83,7 +91,7 @@ VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.box = "ubuntu/xenial64"
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"

View File

@ -1,8 +1,8 @@
{
"name": "Mastodon",
"description": "A GNU Social-compatible microblogging server",
"repository": "https://github.com/mastodon/mastodon",
"logo": "https://github.com/mastodon.png",
"repository": "https://github.com/tootsuite/mastodon",
"logo": "https://github.com/tootsuite.png",
"env": {
"HEROKU": {
"description": "Leave this as true",
@ -13,6 +13,15 @@
"description": "The domain that your Mastodon instance will run on (this can be appname.herokuapp.com or a custom domain)",
"required": true
},
"LOCAL_HTTPS": {
"description": "Will your domain support HTTPS? (Automatic for herokuapp, requires manual configuration for custom domains)",
"value": "false",
"required": true
},
"PAPERCLIP_SECRET": {
"description": "The secret key for storing media files",
"generator": "secret"
},
"SECRET_KEY_BASE": {
"description": "The secret key base",
"generator": "secret"
@ -79,13 +88,8 @@
"description": "SMTP server certificate verification mode. Defaults is 'peer'.",
"required": false
},
"SMTP_ENABLE_STARTTLS": {
"description": "Enable STARTTLS? Default is 'auto'.",
"value": "auto",
"required": false
},
"SMTP_ENABLE_STARTTLS_AUTO": {
"description": "Enable STARTTLS if SMTP server supports it? Deprecated by SMTP_ENABLE_STARTTLS.",
"description": "Enable STARTTLS if SMTP server supports it? Default is true.",
"required": false
}
},
@ -93,6 +97,9 @@
{
"url": "https://github.com/heroku/heroku-buildpack-apt"
},
{
"url": "heroku/nodejs"
},
{
"url": "heroku/ruby"
}
@ -100,5 +107,8 @@
"scripts": {
"postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
},
"addons": ["heroku-postgresql", "heroku-redis"]
"addons": [
"heroku-postgresql",
"heroku-redis"
]
}

View File

@ -1,43 +0,0 @@
# frozen_string_literal: true
class AccountsIndex < Chewy::Index
settings index: { refresh_interval: '30s' }, analysis: {
analyzer: {
content: {
tokenizer: 'whitespace',
filter: %w(lowercase asciifolding cjk_width),
},
edge_ngram: {
tokenizer: 'edge_ngram',
filter: %w(lowercase asciifolding cjk_width),
},
},
tokenizer: {
edge_ngram: {
type: 'edge_ngram',
min_gram: 1,
max_gram: 15,
},
},
}
index_scope ::Account.searchable.includes(:account_stat)
root date_detection: false do
field :id, type: 'long'
field :display_name, type: 'text', analyzer: 'content' do
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
end
field :acct, type: 'text', analyzer: 'content', value: ->(account) { [account.username, account.domain].compact.join('@') } do
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
end
field :following_count, type: 'long', value: ->(account) { account.following_count }
field :followers_count, type: 'long', value: ->(account) { account.followers_count }
field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }
end
end

View File

@ -1,9 +1,7 @@
# frozen_string_literal: true
class StatusesIndex < Chewy::Index
include FormattingHelper
settings index: { refresh_interval: '30s' }, analysis: {
settings index: { refresh_interval: '15m' }, analysis: {
filter: {
english_stop: {
type: 'stop',
@ -33,43 +31,31 @@ class StatusesIndex < Chewy::Index
},
}
# We do not use delete_if option here because it would call a method that we
# expect to be called with crutches without crutches, causing n+1 queries
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll)
crutch :mentions do |collection|
data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local, silent: false).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :favourites do |collection|
data = ::Favourite.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :reblogs do |collection|
data = ::Status.where(reblog_of_id: collection.map(&:id)).where(account: Account.local).pluck(:reblog_of_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :bookmarks do |collection|
data = ::Bookmark.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :votes do |collection|
data = ::PollVote.joins(:poll).where(poll: { status_id: collection.map(&:id) }).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
root date_detection: false do
field :id, type: 'long'
field :account_id, type: 'long'
field :text, type: 'text', value: ->(status) { status.searchable_text } do
field :stemmed, type: 'text', analyzer: 'content'
define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do
crutch :mentions do |collection|
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
crutch :favourites do |collection|
data = ::Favourite.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :reblogs do |collection|
data = ::Status.where(reblog_of_id: collection.map(&:id)).pluck(:reblog_of_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
root date_detection: false do
field :id, type: 'long'
field :account_id, type: 'long'
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).join("\n\n") } do
field :stemmed, type: 'text', analyzer: 'content'
end
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
end
end
end

View File

@ -1,41 +0,0 @@
# frozen_string_literal: true
class TagsIndex < Chewy::Index
settings index: { refresh_interval: '30s' }, analysis: {
analyzer: {
content: {
tokenizer: 'keyword',
filter: %w(lowercase asciifolding cjk_width),
},
edge_ngram: {
tokenizer: 'edge_ngram',
filter: %w(lowercase asciifolding cjk_width),
},
},
tokenizer: {
edge_ngram: {
type: 'edge_ngram',
min_gram: 2,
max_gram: 15,
},
},
}
index_scope ::Tag.listable
crutch :time_period do
7.days.ago.to_date..0.days.ago.to_date
end
root date_detection: false do
field :name, type: 'text', analyzer: 'content' do
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
end
field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
field :usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts }
field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
end
end

View File

@ -1,18 +1,34 @@
# frozen_string_literal: true
class AboutController < ApplicationController
include WebAppControllerConcern
before_action :set_pack
layout 'public'
skip_before_action :require_functional!
before_action :set_instance_presenter
before_action :set_instance_presenter, only: [:show, :more, :terms]
def show
expires_in 0, public: true unless user_signed_in?
@hide_navbar = true
end
def more; end
def terms; end
private
def new_user
User.new.tap do |user|
user.build_account
user.build_invite_request
end
end
helper_method :new_user
def set_pack
use_pack 'common'
end
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class AccountFollowController < ApplicationController
include AccountControllerConcern
before_action :authenticate_user!
def create
FollowService.new.call(current_user.account, @account.acct)
redirect_to account_path(@account)
end
end

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class AccountUnfollowController < ApplicationController
include AccountControllerConcern
before_action :authenticate_user!
def create
UnfollowService.new.call(current_user.account, @account)
redirect_to account_path(@account)
end
end

View File

@ -1,43 +1,67 @@
# frozen_string_literal: true
class AccountsController < ApplicationController
PAGE_SIZE = 20
PAGE_SIZE_MAX = 200
PAGE_SIZE = 20
include AccountControllerConcern
include SignatureAuthentication
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
skip_before_action :require_functional!, unless: :whitelist_mode?
def show
respond_to do |format|
format.html do
expires_in 0, public: true unless user_signed_in?
use_pack 'public'
mark_cacheable! unless user_signed_in?
@rss_url = rss_url
@body_classes = 'with-modals'
@pinned_statuses = []
@endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
if current_account && @account.blocking?(current_account)
@statuses = []
return
end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = filtered_status_page(params)
@statuses = cache_collection(@statuses, Status)
unless @statuses.empty?
@older_url = older_url if @statuses.last.id > filtered_statuses.last.id
@newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
end
end
format.atom do
mark_cacheable!
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? || entry.status.local_only? }))
end
format.rss do
expires_in 1.minute, public: true
mark_cacheable!
limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
@statuses = filtered_statuses.without_reblogs.limit(limit)
@statuses = cache_collection(@statuses, Status)
@statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status)
render xml: RSS::AccountSerializer.render(@account, @statuses)
end
format.json do
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
mark_cacheable!
render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
end
end
end
end
private
def show_pinned_statuses?
[replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
end
def filtered_statuses
default_statuses.tap do |statuses|
statuses.merge!(hashtag_scope) if tag_requested?
@ -51,7 +75,11 @@ class AccountsController < ApplicationController
end
def only_media_scope
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
Status.where(id: account_media_status_ids)
end
def account_media_status_ids
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
end
def no_replies_scope
@ -72,40 +100,43 @@ class AccountsController < ApplicationController
params[:username]
end
def skip_temporary_suspension_response?
request.format == :json
def older_url
pagination_url(max_id: @statuses.last.id)
end
def rss_url
def newer_url
pagination_url(min_id: @statuses.first.id)
end
def pagination_url(max_id: nil, min_id: nil)
if tag_requested?
short_account_tag_url(@account, params[:tag], format: 'rss')
short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
elsif media_requested?
short_account_media_url(@account, max_id: max_id, min_id: min_id)
elsif replies_requested?
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
else
short_account_url(@account, format: 'rss')
short_account_url(@account, max_id: max_id, min_id: min_id)
end
end
def media_requested?
request.path.split('.').first.end_with?('/media') && !tag_requested?
request.path.ends_with?('/media')
end
def replies_requested?
request.path.split('.').first.end_with?('/with_replies') && !tag_requested?
request.path.ends_with?('/with_replies')
end
def tag_requested?
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end
def cached_filtered_status_page
cache_collection_paginated_by_id(
filtered_statuses,
Status,
PAGE_SIZE,
params_slice(:max_id, :min_id, :since_id)
)
end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
def filtered_status_page(params)
if params[:min_id].present?
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
else
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
end
end
end

View File

@ -1,17 +0,0 @@
# frozen_string_literal: true
class ActivityPub::BaseController < Api::BaseController
skip_before_action :require_authenticated_user!
skip_before_action :require_not_suspended!
skip_around_action :set_locale
private
def set_cache_headers
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
end
def skip_temporary_suspension_response?
false
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
class ActivityPub::ClaimsController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
skip_before_action :authenticate_user!
before_action :require_account_signature!
before_action :set_claim_result
def create
render json: @claim_result, serializer: ActivityPub::OneTimeKeySerializer
end
private
def set_claim_result
@claim_result = ::Keys::ClaimService.new.call(@account.id, params[:id])
end
end

View File

@ -1,74 +1,63 @@
# frozen_string_literal: true
class ActivityPub::CollectionsController < ActivityPub::BaseController
class ActivityPub::CollectionsController < Api::BaseController
include SignatureVerification
include AccountOwnedConcern
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_items
before_action :set_account
before_action :set_size
before_action :set_type
before_action :set_statuses
before_action :set_cache_headers
def show
expires_in 3.minutes, public: public_fetch_mode?
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
skip_session!
render_cached_json(['activitypub', 'collection', @account, params[:id]], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(
collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
skip_activities: true
)
end
end
private
def set_items
case params[:id]
when 'featured'
@items = for_signed_account { cache_collection(@account.pinned_statuses.not_local_only, Status) }
@items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) }
when 'tags'
@items = for_signed_account { @account.featured_tags }
when 'devices'
@items = @account.devices
else
not_found
end
def set_account
@account = Account.find_local!(params[:account_username])
end
def set_statuses
@statuses = scope_for_collection
@statuses = cache_collection(@statuses, Status)
end
def set_size
case params[:id]
when 'featured', 'devices', 'tags'
@size = @items.size
when 'featured'
@account.pinned_statuses.count
else
not_found
raise ActiveRecord::RecordNotFound
end
end
def set_type
def scope_for_collection
case params[:id]
when 'featured'
@type = :ordered
when 'devices', 'tags'
@type = :unordered
@account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
scope.merge!(@account.pinned_statuses)
end
else
not_found
raise ActiveRecord::RecordNotFound
end
end
def collection_presenter
ActivityPub::CollectionPresenter.new(
id: account_collection_url(@account, params[:id]),
type: @type,
type: :ordered,
size: @size,
items: @items
items: @statuses
)
end
def for_signed_account
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
[]
else
yield
end
end
end

View File

@ -1,36 +0,0 @@
# frozen_string_literal: true
class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
before_action :require_account_signature!
before_action :set_items
before_action :set_cache_headers
def show
expires_in 0, public: false
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
private
def uri_prefix
signed_request_account.uri[Account::URL_PREFIX_RE]
end
def set_items
@items = @account.followers.where(Account.arel_table[:uri].matches("#{Account.sanitize_sql_like(uri_prefix)}/%", false, true)).or(@account.followers.where(uri: uri_prefix)).pluck(:uri)
end
def collection_presenter
ActivityPub::CollectionPresenter.new(
id: account_followers_synchronization_url(@account),
type: :ordered,
items: @items
)
end
end

View File

@ -1,76 +1,54 @@
# frozen_string_literal: true
class ActivityPub::InboxesController < ActivityPub::BaseController
class ActivityPub::InboxesController < Api::BaseController
include SignatureVerification
include JsonLdHelper
include AccountOwnedConcern
before_action :skip_unknown_actor_activity
before_action :require_actor_signature!
skip_before_action :authenticate_user!
before_action :set_account
def create
upgrade_account
process_collection_synchronization
process_payload
head 202
if unknown_deleted_account?
head 202
elsif signed_request_account
upgrade_account
process_payload
head 202
else
render plain: signature_verification_failure_reason, status: 401
end
end
private
def skip_unknown_actor_activity
head 202 if unknown_affected_account?
end
def unknown_affected_account?
def unknown_deleted_account?
json = Oj.load(body, mode: :strict)
json.is_a?(Hash) && %w(Delete Update).include?(json['type']) && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
rescue Oj::ParseError
false
end
def account_required?
params[:account_username].present?
end
def skip_temporary_suspension_response?
true
def set_account
@account = Account.find_local!(params[:account_username]) if params[:account_username]
end
def body
return @body if defined?(@body)
@body = request.body.read
@body.force_encoding('UTF-8') if @body.present?
@body = request.body.read.force_encoding('UTF-8')
request.body.rewind if request.body.respond_to?(:rewind)
@body
end
def upgrade_account
if signed_request_account&.ostatus?
if signed_request_account.ostatus?
signed_request_account.update(last_webfingered_at: nil)
ResolveAccountWorker.perform_async(signed_request_account.acct)
end
DeliveryFailureTracker.reset!(signed_request_actor.inbox_url)
end
def process_collection_synchronization
raw_params = request.headers['Collection-Synchronization']
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' || signed_request_account.nil?
# Re-using the syntax for signature parameters
tree = SignatureParamsParser.new.parse(raw_params)
params = SignatureParamsTransformer.new.apply(tree)
ActivityPub::PrepareFollowersSynchronizationService.new.call(signed_request_account, params)
rescue Parslet::ParseFailed
Rails.logger.warn 'Error parsing Collection-Synchronization header'
Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) if signed_request_account.subscribed?
DeliveryFailureTracker.track_inverse_success!(signed_request_account)
end
def process_payload
ActivityPub::ProcessingWorker.perform_async(signed_request_actor.id, body, @account&.id, signed_request_actor.class.name)
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
end
end

View File

@ -1,87 +1,71 @@
# frozen_string_literal: true
class ActivityPub::OutboxesController < ActivityPub::BaseController
class ActivityPub::OutboxesController < Api::BaseController
LIMIT = 20
include SignatureVerification
include AccountOwnedConcern
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_account
before_action :set_statuses
before_action :set_cache_headers
def show
if page_requested?
expires_in(1.minute, public: public_fetch_mode? && signed_request_account.nil?)
else
expires_in(3.minutes, public: public_fetch_mode?)
unless page_requested?
skip_session!
expires_in 1.minute, public: true
end
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
private
def set_account
@account = Account.find_local!(params[:account_username])
end
def outbox_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
id: outbox_url(**page_params),
id: account_outbox_url(@account, page_params),
type: :ordered,
part_of: outbox_url,
part_of: account_outbox_url(@account),
prev: prev_page,
next: next_page,
items: @statuses
)
else
ActivityPub::CollectionPresenter.new(
id: outbox_url,
id: account_outbox_url(@account),
type: :ordered,
size: @account.statuses_count,
first: outbox_url(page: true),
last: outbox_url(page: true, min_id: 0)
first: account_outbox_url(@account, page: true),
last: account_outbox_url(@account, page: true, min_id: 0)
)
end
end
def outbox_url(**kwargs)
if params[:account_username].present?
account_outbox_url(@account, **kwargs)
else
instance_actor_outbox_url(**kwargs)
end
end
def next_page
outbox_url(page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
end
def prev_page
outbox_url(page: true, min_id: @statuses.first.id) unless @statuses.empty?
account_outbox_url(@account, page: true, min_id: @statuses.first.id) unless @statuses.empty?
end
def set_statuses
return unless page_requested?
@statuses = cache_collection_paginated_by_id(
AccountStatusesFilter.new(@account, signed_request_account).results,
Status,
LIMIT,
params_slice(:max_id, :min_id, :since_id)
)
@statuses = @account.statuses.permitted_for(@account, signed_request_account)
@statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id])
@statuses = cache_collection(@statuses, Status)
end
def page_requested?
truthy_param?(:page)
params[:page] == 'true'
end
def page_params
{ page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact
end
def set_account
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
end
def set_cache_headers
response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested?
end
end

View File

@ -1,94 +0,0 @@
# frozen_string_literal: true
class ActivityPub::RepliesController < ActivityPub::BaseController
include SignatureVerification
include Authorization
include AccountOwnedConcern
DESCENDANTS_LIMIT = 60
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_status
before_action :set_cache_headers
before_action :set_replies
def index
expires_in 0, public: public_fetch_mode?
render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
end
private
def pundit_user
signed_request_account
end
def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_replies
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end
def replies_collection_presenter
page = ActivityPub::CollectionPresenter.new(
id: account_status_replies_url(@account, @status, page_params),
type: :unordered,
part_of: account_status_replies_url(@account, @status),
next: next_page,
items: @replies.map { |status| status.local? ? status : status.uri }
)
return page if page_requested?
ActivityPub::CollectionPresenter.new(
id: account_status_replies_url(@account, @status),
type: :unordered,
first: page
)
end
def page_requested?
truthy_param?(:page)
end
def only_other_accounts?
truthy_param?(:only_other_accounts)
end
def next_page
if only_other_accounts?
# Only consider remote accounts
return nil if @replies.size < DESCENDANTS_LIMIT
account_status_replies_url(
@account,
@status,
page: true,
min_id: @replies&.last&.id,
only_other_accounts: true
)
else
# For now, we're serving only self-replies, but next page might be other accounts
next_only_other_accounts = @replies&.last&.account_id != @account.id || @replies.size < DESCENDANTS_LIMIT
account_status_replies_url(
@account,
@status,
page: true,
min_id: next_only_other_accounts ? nil : @replies&.last&.id,
only_other_accounts: next_only_other_accounts
)
end
end
def page_params
params_slice(:only_other_accounts, :min_id).merge(page: true)
end
end

View File

@ -5,15 +5,11 @@ module Admin
before_action :set_account
def new
authorize @account, :show?
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true, include_statuses: true)
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true)
@warning_presets = AccountWarningPreset.all
end
def create
authorize @account, :show?
account_action = Admin::AccountAction.new(resource_params)
account_action.target_account = @account
account_action.current_account = current_account
@ -34,7 +30,7 @@ module Admin
end
def resource_params
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses)
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification)
end
end
end

View File

@ -14,7 +14,7 @@ module Admin
else
@account = @account_moderation_note.target_account
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.strikes.custom.latest
@warnings = @account.targeted_account_warnings.latest.custom
render template: 'admin/accounts/show'
end

View File

@ -2,94 +2,73 @@
module Admin
class AccountsController < BaseController
before_action :set_account, except: [:index, :batch]
before_action :require_remote_account!, only: [:redownload]
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
def index
authorize :account, :index?
@accounts = filtered_accounts.page(params[:page])
@form = Form::AccountBatch.new
end
def batch
authorize :account, :index?
@form = Form::AccountBatch.new(form_account_batch_params)
@form.current_account = current_account
@form.action = action_from_button
@form.select_all_matching = params[:select_all_matching]
@form.query = filtered_accounts
@form.save
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
ensure
redirect_to admin_accounts_path(filter_params)
end
def show
authorize @account, :show?
@deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.strikes.includes(:target_account, :account, :appeal).latest
@domain_block = DomainBlock.rule_for(@account.domain)
@warnings = @account.targeted_account_warnings.latest.custom
end
def subscribe
authorize @account, :subscribe?
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id)
end
def unsubscribe
authorize @account, :unsubscribe?
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id)
end
def memorialize
authorize @account, :memorialize?
@account.memorialize!
log_action :memorialize, @account
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def approve
authorize @account.user, :approve?
@account.user.approve!
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
redirect_to admin_accounts_path(pending: '1')
end
def reject
authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
end
def destroy
authorize @account, :destroy?
Admin::AccountDeletionWorker.perform_async(@account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end
def unsensitive
authorize @account, :unsensitive?
@account.unsensitize!
log_action :unsensitive, @account
redirect_to admin_account_path(@account.id)
SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
redirect_to admin_accounts_path(pending: '1')
end
def unsilence
authorize @account, :unsilence?
@account.unsilence!
log_action :unsilence, @account
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def redownload
@ -98,7 +77,7 @@ module Admin
@account.update!(last_webfingered_at: nil)
ResolveAccountService.new.call(@account)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def remove_avatar
@ -109,7 +88,7 @@ module Admin
log_action :remove_avatar, @account.user
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
def remove_header
@ -120,17 +99,7 @@ module Admin
log_action :remove_header, @account.user
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
end
def unblock_email
authorize @account, :unblock_email?
CanonicalEmailBlock.where(reference_account: @account).delete_all
log_action :unblock_email, @account
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unblocked_email_msg', username: @account.acct)
redirect_to admin_account_path(@account.id)
end
private
@ -148,25 +117,24 @@ module Admin
end
def filtered_accounts
AccountFilter.new(filter_params.with_defaults(order: 'recent')).results
AccountFilter.new(filter_params).results
end
def filter_params
params.slice(:page, *AccountFilter::KEYS).permit(:page, *AccountFilter::KEYS)
end
def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: [])
end
def action_from_button
if params[:suspend]
'suspend'
elsif params[:approve]
'approve'
elsif params[:reject]
'reject'
end
params.permit(
:local,
:remote,
:by_domain,
:active,
:pending,
:silenced,
:suspended,
:username,
:display_name,
:email,
:ip,
:staff
)
end
end
end

View File

@ -2,21 +2,8 @@
module Admin
class ActionLogsController < BaseController
before_action :set_action_logs
def index
authorize :audit_log, :index?
@auditable_accounts = Account.where(id: Admin::ActionLog.reorder(nil).select('distinct account_id')).select(:id, :username)
end
private
def set_action_logs
@action_logs = Admin::ActionLogFilter.new(filter_params).results.page(params[:page])
end
def filter_params
params.slice(:page, *Admin::ActionLogFilter::KEYS).permit(:page, *Admin::ActionLogFilter::KEYS)
@action_logs = Admin::ActionLog.page(params[:page])
end
end
end

View File

@ -1,88 +0,0 @@
# frozen_string_literal: true
class Admin::AnnouncementsController < Admin::BaseController
before_action :set_announcements, only: :index
before_action :set_announcement, except: [:index, :new, :create]
def index
authorize :announcement, :index?
end
def new
authorize :announcement, :create?
@announcement = Announcement.new
end
def create
authorize :announcement, :create?
@announcement = Announcement.new(resource_params)
if @announcement.save
PublishScheduledAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
log_action :create, @announcement
redirect_to admin_announcements_path, notice: @announcement.published? ? I18n.t('admin.announcements.published_msg') : I18n.t('admin.announcements.scheduled_msg')
else
render :new
end
end
def edit
authorize :announcement, :update?
end
def update
authorize :announcement, :update?
if @announcement.update(resource_params)
PublishScheduledAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
log_action :update, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.updated_msg')
else
render :edit
end
end
def publish
authorize :announcement, :update?
@announcement.publish!
PublishScheduledAnnouncementWorker.perform_async(@announcement.id)
log_action :update, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.published_msg')
end
def unpublish
authorize :announcement, :update?
@announcement.unpublish!
UnpublishAnnouncementWorker.perform_async(@announcement.id)
log_action :update, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.unpublished_msg')
end
def destroy
authorize :announcement, :destroy?
@announcement.destroy!
UnpublishAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
log_action :destroy, @announcement
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.destroyed_msg')
end
private
def set_announcements
@announcements = AnnouncementFilter.new(filter_params).results.reverse_chronological.page(params[:page])
end
def set_announcement
@announcement = Announcement.find(params[:id])
end
def filter_params
params.slice(*AnnouncementFilter::KEYS).permit(*AnnouncementFilter::KEYS)
end
def resource_params
params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
end
end

View File

@ -7,9 +7,9 @@ module Admin
layout 'admin'
before_action :require_staff!
before_action :set_pack
before_action :set_body_classes
after_action :verify_authorized
private

View File

@ -17,7 +17,7 @@ module Admin
@user.resend_confirmation_instructions
log_action :resend, @user
log_action :confirm, @user
flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success')
redirect_to admin_accounts_path

View File

@ -2,16 +2,19 @@
module Admin
class CustomEmojisController < BaseController
before_action :set_custom_emoji, except: [:index, :new, :create]
before_action :set_filter_params
include ObfuscateFilename
obfuscate_filename [:custom_emoji, :image]
def index
authorize :custom_emoji, :index?
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
@form = Form::CustomEmojiBatch.new
end
def new
authorize :custom_emoji, :create?
@custom_emoji = CustomEmoji.new
end
@ -28,24 +31,69 @@ module Admin
end
end
def batch
authorize :custom_emoji, :index?
def update
authorize @custom_emoji, :update?
@form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.custom_emojis.no_emoji_selected')
rescue Mastodon::NotPermittedError
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
rescue ActiveRecord::RecordInvalid => e
error_message = action_from_button == 'copy' ? 'admin.custom_emojis.batch_copy_error' : 'admin.custom_emojis.batch_error'
flash[:alert] = I18n.t(error_message, message: e.message)
ensure
redirect_to admin_custom_emojis_path(filter_params)
if @custom_emoji.update(resource_params)
log_action :update, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.updated_msg')
else
flash[:alert] = I18n.t('admin.custom_emojis.update_failed_msg')
end
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def destroy
authorize @custom_emoji, :destroy?
@custom_emoji.destroy!
log_action :destroy, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.destroyed_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def copy
authorize @custom_emoji, :copy?
emoji = CustomEmoji.find_or_initialize_by(domain: nil,
shortcode: @custom_emoji.shortcode)
emoji.image = @custom_emoji.image
if emoji.save
log_action :create, emoji
flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
else
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
end
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def enable
authorize @custom_emoji, :enable?
@custom_emoji.update!(disabled: false)
log_action :enable, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.enabled_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def disable
authorize @custom_emoji, :disable?
@custom_emoji.update!(disabled: true)
log_action :disable, @custom_emoji
flash[:notice] = I18n.t('admin.custom_emojis.disabled_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
private
def set_custom_emoji
@custom_emoji = CustomEmoji.find(params[:id])
end
def set_filter_params
@filter_params = filter_params.to_hash.symbolize_keys
end
def resource_params
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
end
@ -55,29 +103,12 @@ module Admin
end
def filter_params
params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS)
end
def action_from_button
if params[:update]
'update'
elsif params[:list]
'list'
elsif params[:unlist]
'unlist'
elsif params[:enable]
'enable'
elsif params[:disable]
'disable'
elsif params[:copy]
'copy'
elsif params[:delete]
'delete'
end
end
def form_custom_emoji_batch_params
params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
params.permit(
:local,
:remote,
:by_domain,
:shortcode
)
end
end
end

View File

@ -1,30 +1,46 @@
# frozen_string_literal: true
require 'sidekiq/api'
module Admin
class DashboardController < BaseController
include Redisable
def index
authorize :dashboard, :index?
@system_checks = Admin::SystemCheck.perform(current_user)
@time_period = (29.days.ago.to_date...Time.now.utc.to_date)
@pending_users_count = User.pending.count
@pending_reports_count = Report.unresolved.count
@pending_tags_count = Tag.pending_review.count
@pending_appeals_count = Appeal.pending.count
@users_count = User.count
@registrations_week = Redis.current.get("activity:accounts:local:#{current_week}") || 0
@logins_week = Redis.current.pfcount("activity:logins:#{current_week}")
@interactions_week = Redis.current.get("activity:interactions:#{current_week}") || 0
@relay_enabled = Relay.enabled.exists?
@single_user_mode = Rails.configuration.x.single_user_mode
@registrations_enabled = Setting.registrations_mode != 'none'
@deletions_enabled = Setting.open_deletion
@invites_enabled = Setting.min_invite_role == 'user'
@search_enabled = Chewy.enabled?
@version = Mastodon::Version.to_s
@database_version = ActiveRecord::Base.connection.execute('SELECT VERSION()').first['version'].match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
@redis_version = redis_info['redis_version']
@reports_count = Report.unresolved.count
@queue_backlog = Sidekiq::Stats.new.enqueued
@recent_users = User.confirmed.recent.includes(:account).limit(4)
@database_size = ActiveRecord::Base.connection.execute('SELECT pg_database_size(current_database())').first['pg_database_size']
@redis_size = redis_info['used_memory']
@ldap_enabled = ENV['LDAP_ENABLED'] == 'true'
@cas_enabled = ENV['CAS_ENABLED'] == 'true'
@saml_enabled = ENV['SAML_ENABLED'] == 'true'
@pam_enabled = ENV['PAM_ENABLED'] == 'true'
@hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
@trending_hashtags = TrendingTags.get(7)
@profile_directory = Setting.profile_directory
@timeline_preview = Setting.timeline_preview
@keybase_integration = Setting.enable_keybase
end
private
def current_week
@current_week ||= Time.now.utc.to_date.cweek
end
def redis_info
@redis_info ||= begin
if redis.is_a?(Redis::Namespace)
redis.redis.info
else
redis.info
end
end
@redis_info ||= Redis.current.info
end
end
end

View File

@ -1,40 +0,0 @@
# frozen_string_literal: true
class Admin::Disputes::AppealsController < Admin::BaseController
before_action :set_appeal, except: :index
def index
authorize :appeal, :index?
@appeals = filtered_appeals.page(params[:page])
end
def approve
authorize @appeal, :approve?
log_action :approve, @appeal
ApproveAppealService.new.call(@appeal, current_account)
redirect_to disputes_strike_path(@appeal.strike)
end
def reject
authorize @appeal, :approve?
log_action :reject, @appeal
@appeal.reject!(current_account)
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
redirect_to disputes_strike_path(@appeal.strike)
end
private
def filtered_appeals
Admin::AppealFilter.new(filter_params.with_defaults(status: 'pending')).results.includes(strike: :account)
end
def filter_params
params.slice(:page, *Admin::AppealFilter::KEYS).permit(:page, *Admin::AppealFilter::KEYS)
end
def set_appeal
@appeal = Appeal.find(params[:id])
end
end

View File

@ -1,40 +0,0 @@
# frozen_string_literal: true
class Admin::DomainAllowsController < Admin::BaseController
before_action :set_domain_allow, only: [:destroy]
def new
authorize :domain_allow, :create?
@domain_allow = DomainAllow.new(domain: params[:_domain])
end
def create
authorize :domain_allow, :create?
@domain_allow = DomainAllow.new(resource_params)
if @domain_allow.save
log_action :create, @domain_allow
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.created_msg')
else
render :new
end
end
def destroy
authorize @domain_allow, :destroy?
UnallowDomainService.new.call(@domain_allow)
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
end
private
def set_domain_allow
@domain_allow = DomainAllow.find(params[:id])
end
def resource_params
params.require(:domain_allow).permit(:domain)
end
end

View File

@ -2,46 +2,29 @@
module Admin
class DomainBlocksController < BaseController
before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
def batch
authorize :domain_block, :create?
@form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.domain_blocks.no_domain_block_selected')
rescue Mastodon::NotPermittedError
flash[:alert] = I18n.t('admin.domain_blocks.not_permitted')
else
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
end
before_action :set_domain_block, only: [:show, :destroy]
def new
authorize :domain_block, :create?
@domain_block = DomainBlock.new(domain: params[:_domain])
end
def edit
authorize :domain_block, :create?
end
def create
authorize :domain_block, :create?
@domain_block = DomainBlock.new(resource_params)
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
existing_domain_block = resource_params[:domain].present? ? DomainBlock.find_by(domain: resource_params[:domain]) : nil
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.save
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
@domain_block.errors.delete(:domain)
flash[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
@domain_block.errors[:domain].clear
render :new
else
if existing_domain_block.present?
@domain_block = existing_domain_block
@domain_block.update(resource_params)
end
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block
@ -52,20 +35,8 @@ module Admin
end
end
def update
authorize :domain_block, :update?
@domain_block.update(update_params)
severity_changed = @domain_block.severity_changed?
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
log_action :update, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else
render :edit
end
def show
authorize @domain_block, :show?
end
def destroy
@ -81,22 +52,8 @@ module Admin
@domain_block = DomainBlock.find(params[:id])
end
def update_params
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
end
def resource_params
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
end
def form_domain_block_batch_params
params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate])
end
def action_from_button
if params[:save]
'save'
end
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports)
end
end
end

View File

@ -6,27 +6,12 @@ module Admin
def index
authorize :email_domain_block, :index?
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
@form = Form::EmailDomainBlockBatch.new
end
def batch
authorize :email_domain_block, :index?
@form = Form::EmailDomainBlockBatch.new(form_email_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.email_domain_blocks.no_email_domain_block_selected')
rescue Mastodon::NotPermittedError
flash[:alert] = I18n.t('admin.email_domain_blocks.not_permitted')
ensure
redirect_to admin_email_domain_blocks_path
@email_domain_blocks = EmailDomainBlock.page(params[:page])
end
def new
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new(domain: params[:_domain])
@email_domain_block = EmailDomainBlock.new
end
def create
@ -34,27 +19,19 @@ module Admin
@email_domain_block = EmailDomainBlock.new(resource_params)
if action_from_button == 'save'
EmailDomainBlock.transaction do
@email_domain_block.save!
log_action :create, @email_domain_block
(@email_domain_block.other_domains || []).uniq.each do |domain|
next if EmailDomainBlock.where(domain: domain).exists?
other_email_domain_block = EmailDomainBlock.create!(domain: domain, parent: @email_domain_block)
log_action :create, other_email_domain_block
end
end
if @email_domain_block.save
log_action :create, @email_domain_block
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else
set_resolved_records
render :new
end
rescue ActiveRecord::RecordInvalid
set_resolved_records
render :new
end
def destroy
authorize @email_domain_block, :destroy?
@email_domain_block.destroy!
log_action :destroy, @email_domain_block
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
end
private
@ -63,27 +40,8 @@ module Admin
@email_domain_block = EmailDomainBlock.find(params[:id])
end
def set_resolved_records
Resolv::DNS.open do |dns|
dns.timeouts = 5
@resolved_records = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
end
end
def resource_params
params.require(:email_domain_block).permit(:domain, other_domains: [])
end
def form_email_domain_block_batch_params
params.require(:form_email_domain_block_batch).permit(email_domain_block_ids: [])
end
def action_from_button
if params[:delete]
'delete'
elsif params[:save]
'save'
end
params.require(:email_domain_block).permit(:domain)
end
end
end

View File

@ -1,60 +0,0 @@
# frozen_string_literal: true
require 'csv'
module Admin
class ExportDomainAllowsController < BaseController
include AdminExportControllerConcern
before_action :set_dummy_import!, only: [:new]
def new
authorize :domain_allow, :create?
end
def export
authorize :instance, :index?
send_export_file
end
def import
authorize :domain_allow, :create?
begin
@import = Admin::Import.new(import_params)
return render :new unless @import.validate
parse_import_data!(export_headers)
@data.take(Admin::Import::ROWS_PROCESSING_LIMIT).each do |row|
domain = row['#domain'].strip
next if DomainAllow.allowed?(domain)
domain_allow = DomainAllow.new(domain: domain)
log_action :create, domain_allow if domain_allow.save
end
flash[:notice] = I18n.t('admin.domain_allows.created_msg')
rescue ActionController::ParameterMissing
flash[:error] = I18n.t('admin.export_domain_allows.no_file')
end
redirect_to admin_instances_path
end
private
def export_filename
'domain_allows.csv'
end
def export_headers
%w(#domain)
end
def export_data
CSV.generate(headers: export_headers, write_headers: true) do |content|
DomainAllow.allowed_domains.each do |instance|
content << [instance.domain]
end
end
end
end
end

View File

@ -1,71 +0,0 @@
# frozen_string_literal: true
require 'csv'
module Admin
class ExportDomainBlocksController < BaseController
include AdminExportControllerConcern
before_action :set_dummy_import!, only: [:new]
def new
authorize :domain_block, :create?
end
def export
authorize :instance, :index?
send_export_file
end
def import
authorize :domain_block, :create?
@import = Admin::Import.new(import_params)
return render :new unless @import.validate
parse_import_data!(export_headers)
@global_private_comment = I18n.t('admin.export_domain_blocks.import.private_comment_template', source: @import.data_file_name, date: I18n.l(Time.now.utc))
@form = Form::DomainBlockBatch.new
@domain_blocks = @data.take(Admin::Import::ROWS_PROCESSING_LIMIT).filter_map do |row|
domain = row['#domain'].strip
next if DomainBlock.rule_for(domain).present?
domain_block = DomainBlock.new(domain: domain,
severity: row['#severity'].strip,
reject_media: row['#reject_media'].strip,
reject_reports: row['#reject_reports'].strip,
private_comment: @global_private_comment,
public_comment: row['#public_comment']&.strip,
obfuscate: row['#obfuscate'].strip)
domain_block if domain_block.valid?
end
@warning_domains = Instance.where(domain: @domain_blocks.map(&:domain)).where('EXISTS (SELECT 1 FROM follows JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id WHERE accounts.domain = instances.domain)').pluck(:domain)
rescue ActionController::ParameterMissing
flash.now[:alert] = I18n.t('admin.export_domain_blocks.no_file')
set_dummy_import!
render :new
end
private
def export_filename
'domain_blocks.csv'
end
def export_headers
%w(#domain #severity #reject_media #reject_reports #public_comment #obfuscate)
end
def export_data
CSV.generate(headers: export_headers, write_headers: true) do |content|
DomainBlock.with_limitations.each do |instance|
content << [instance.domain, instance.severity, instance.reject_media, instance.reject_reports, instance.public_comment, instance.obfuscate]
end
end
end
end
end

View File

@ -1,55 +0,0 @@
# frozen_string_literal: true
module Admin
class FollowRecommendationsController < BaseController
before_action :set_language
def show
authorize :follow_recommendation, :show?
@form = Form::AccountBatch.new
@accounts = filtered_follow_recommendations
end
def update
authorize :follow_recommendation, :show?
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
# Do nothing
ensure
redirect_to admin_follow_recommendations_path(filter_params)
end
private
def set_language
@language = follow_recommendation_filter.language
end
def filtered_follow_recommendations
follow_recommendation_filter.results
end
def follow_recommendation_filter
@follow_recommendation_filter ||= FollowRecommendationFilter.new(filter_params)
end
def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: [])
end
def filter_params
params.slice(*FollowRecommendationFilter::KEYS).permit(*FollowRecommendationFilter::KEYS)
end
def action_from_button
if params[:suppress]
'suppress_follow_recommendation'
elsif params[:unsuppress]
'unsuppress_follow_recommendation'
end
end
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Admin
class FollowersController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize :account, :index?
@followers = @account.followers.local.recent.page(params[:page]).per(PER_PAGE)
end
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@ -2,74 +2,43 @@
module Admin
class InstancesController < BaseController
before_action :set_instances, only: :index
before_action :set_instance, except: :index
def index
authorize :instance, :index?
preload_delivery_failures!
@instances = ordered_instances
end
def show
authorize :instance, :show?
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
end
def destroy
authorize :instance, :destroy?
Admin::DomainPurgeWorker.perform_async(@instance.domain)
log_action :destroy, @instance
redirect_to admin_instances_path, notice: I18n.t('admin.instances.destroyed_msg', domain: @instance.domain)
end
def clear_delivery_errors
authorize :delivery, :clear_delivery_errors?
@instance.delivery_failure_tracker.clear_failures!
redirect_to admin_instance_path(@instance.domain)
end
def restart_delivery
authorize :delivery, :restart_delivery?
if @instance.unavailable?
@instance.delivery_failure_tracker.track_success!
log_action :destroy, @instance.unavailable_domain
end
redirect_to admin_instance_path(@instance.domain)
end
def stop_delivery
authorize :delivery, :stop_delivery?
unavailable_domain = UnavailableDomain.create!(domain: @instance.domain)
log_action :create, unavailable_domain
redirect_to admin_instance_path(@instance.domain)
@instance = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
@following_count = Follow.where(account: Account.where(domain: params[:id])).count
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@domain_block = DomainBlock.find_by(domain: params[:id])
end
private
def set_instance
@instance = Instance.find(params[:id])
end
def set_instances
@instances = filtered_instances.page(params[:page])
end
def preload_delivery_failures!
warning_domains_map = DeliveryFailureTracker.warning_domains_map(@instances.map(&:domain))
@instances.each do |instance|
instance.failure_days = warning_domains_map[instance.domain]
end
end
def filtered_instances
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
InstanceFilter.new(filter_params).results
end
def paginated_instances
filtered_instances.page(params[:page])
end
helper_method :paginated_instances
def ordered_instances
paginated_instances.map { |resource| Instance.new(resource) }
end
def filter_params
params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS)
params.permit(:limited, :by_domain)
end
end
end

View File

@ -47,7 +47,7 @@ module Admin
end
def filter_params
params.slice(*InviteFilter::KEYS).permit(*InviteFilter::KEYS)
params.permit(:available, :expired)
end
end
end

View File

@ -1,58 +0,0 @@
# frozen_string_literal: true
module Admin
class IpBlocksController < BaseController
def index
authorize :ip_block, :index?
@ip_blocks = IpBlock.order(ip: :asc).page(params[:page])
@form = Form::IpBlockBatch.new
end
def new
authorize :ip_block, :create?
@ip_block = IpBlock.new(ip: '', severity: :no_access, expires_in: 1.year)
end
def create
authorize :ip_block, :create?
@ip_block = IpBlock.new(resource_params)
if @ip_block.save
log_action :create, @ip_block
redirect_to admin_ip_blocks_path, notice: I18n.t('admin.ip_blocks.created_msg')
else
render :new
end
end
def batch
authorize :ip_block, :index?
@form = Form::IpBlockBatch.new(form_ip_block_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.ip_blocks.no_ip_block_selected')
rescue Mastodon::NotPermittedError
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
ensure
redirect_to admin_ip_blocks_path
end
private
def resource_params
params.require(:ip_block).permit(:ip, :severity, :comment, :expires_in)
end
def action_from_button
'delete' if params[:delete]
end
def form_ip_block_batch_params
params.require(:form_ip_block_batch).permit(ip_block_ids: [])
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
module Admin
class PendingAccountsController < BaseController
before_action :set_accounts, only: :index
def index
@form = Form::AccountBatch.new
end
def batch
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
@form.save
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
ensure
redirect_to admin_pending_accounts_path(current_params)
end
def approve_all
Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'approve').save
redirect_to admin_pending_accounts_path(current_params)
end
def reject_all
Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'reject').save
redirect_to admin_pending_accounts_path(current_params)
end
private
def set_accounts
@accounts = Account.joins(:user).merge(User.pending.recent).includes(user: :invite_request).page(params[:page])
end
def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: [])
end
def action_from_button
if params[:approve]
'approve'
elsif params[:reject]
'reject'
end
end
def current_params
params.slice(:page).permit(:page)
end
end
end

View File

@ -1,26 +0,0 @@
# frozen_string_literal: true
module Admin
class RelationshipsController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize @account, :show?
@accounts = RelationshipFilter.new(@account, filter_params).results.includes(:account_stat, user: [:ips, :invite_request]).page(params[:page]).per(PER_PAGE)
@form = Form::AccountBatch.new
end
private
def set_account
@account = Account.find(params[:account_id])
end
def filter_params
params.slice(*RelationshipFilter::KEYS).permit(*RelationshipFilter::KEYS)
end
end
end

View File

@ -3,7 +3,6 @@
module Admin
class RelaysController < BaseController
before_action :set_relay, except: [:index, :new, :create]
before_action :require_signatures_enabled!, only: [:new, :create, :enable]
def index
authorize :relay, :update?
@ -12,7 +11,7 @@ module Admin
def new
authorize :relay, :update?
@relay = Relay.new
@relay = Relay.new(inbox_url: Relay::PRESET_RELAY)
end
def create
@ -55,9 +54,5 @@ module Admin
def resource_params
params.require(:relay).permit(:inbox_url)
end
def require_signatures_enabled!
redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
end
end
end

View File

@ -5,26 +5,30 @@ module Admin
before_action :set_report_note, only: [:destroy]
def create
authorize :report_note, :create?
authorize ReportNote, :create?
@report_note = current_account.report_notes.new(resource_params)
@report = @report_note.report
@report = @report_note.report
if @report_note.save
if params[:create_and_resolve]
@report.resolve!(current_account)
log_action :resolve, @report
elsif params[:create_and_unresolve]
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
return
end
if params[:create_and_unresolve]
@report.unresolve!
log_action :reopen, @report
end
redirect_to after_create_redirect_path, notice: I18n.t('admin.report_notes.created_msg')
redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
else
@report_notes = @report.notes.includes(:account).order(id: :desc)
@action_logs = @report.history.includes(:target)
@form = Admin::StatusBatchAction.new
@statuses = @report.statuses.with_includes
@report_notes = @report.notes.latest
@report_history = @report.history
@form = Form::StatusBatch.new
render template: 'admin/reports/show'
end
@ -38,14 +42,6 @@ module Admin
private
def after_create_redirect_path
if params[:create_and_resolve]
admin_reports_path
else
admin_report_path(@report)
end
end
def resource_params
params.require(:report_note).permit(
:content,

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
module Admin
class ReportedStatusesController < BaseController
before_action :set_report
def create
authorize :status, :update?
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
redirect_to admin_report_path(@report)
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
redirect_to admin_report_path(@report)
end
private
def status_params
params.require(:status).permit(:sensitive)
end
def form_status_batch_params
params.require(:form_status_batch).permit(status_ids: [])
end
def action_from_button
if params[:nsfw_on]
'nsfw_on'
elsif params[:nsfw_off]
'nsfw_off'
elsif params[:delete]
'delete'
end
end
def set_report
@report = Report.find(params[:report_id])
end
end
end

View File

@ -1,52 +0,0 @@
# frozen_string_literal: true
class Admin::Reports::ActionsController < Admin::BaseController
before_action :set_report
def create
authorize @report, :show?
case action_from_button
when 'delete', 'mark_as_sensitive'
status_batch_action = Admin::StatusBatchAction.new(
type: action_from_button,
status_ids: @report.status_ids,
current_account: current_account,
report_id: @report.id,
send_email_notification: !@report.spam?
)
status_batch_action.save!
when 'silence', 'suspend'
account_action = Admin::AccountAction.new(
type: action_from_button,
report_id: @report.id,
target_account: @report.target_account,
current_account: current_account,
send_email_notification: !@report.spam?
)
account_action.save!
end
redirect_to admin_reports_path
end
private
def set_report
@report = Report.find(params[:report_id])
end
def action_from_button
if params[:delete]
'delete'
elsif params[:mark_as_sensitive]
'mark_as_sensitive'
elsif params[:silence]
'silence'
elsif params[:suspend]
'suspend'
end
end
end

View File

@ -13,10 +13,8 @@ module Admin
authorize @report, :show?
@report_note = @report.notes.new
@report_notes = @report.notes.includes(:account).order(id: :desc)
@action_logs = @report.history.includes(:target)
@form = Admin::StatusBatchAction.new
@statuses = @report.statuses.with_includes
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
@form = Form::StatusBatch.new
end
def assign_to_self
@ -54,7 +52,11 @@ module Admin
end
def filter_params
params.slice(*ReportFilter::KEYS).permit(*ReportFilter::KEYS)
params.permit(
:account_id,
:resolved,
:target_account_id
)
end
def set_report

View File

@ -6,9 +6,9 @@ module Admin
def create
authorize @user, :reset_password?
@user.reset_password!
@user.send_reset_password_instructions
log_action :reset_password, @user
redirect_to admin_account_path(@user.account_id)
redirect_to admin_accounts_path
end
end
end

View File

@ -2,66 +2,20 @@
module Admin
class RolesController < BaseController
before_action :set_role, except: [:index, :new, :create]
before_action :set_user
def index
authorize :user_role, :index?
@roles = UserRole.order(position: :desc).page(params[:page])
def promote
authorize @user, :promote?
@user.promote!
log_action :promote, @user
redirect_to admin_account_path(@user.account_id)
end
def new
authorize :user_role, :create?
@role = UserRole.new
end
def create
authorize :user_role, :create?
@role = UserRole.new(resource_params)
@role.current_account = current_account
if @role.save
log_action :create, @role
redirect_to admin_roles_path
else
render :new
end
end
def edit
authorize @role, :update?
end
def update
authorize @role, :update?
@role.current_account = current_account
if @role.update(resource_params)
log_action :update, @role
redirect_to admin_roles_path
else
render :edit
end
end
def destroy
authorize @role, :destroy?
@role.destroy!
log_action :destroy, @role
redirect_to admin_roles_path
end
private
def set_role
@role = UserRole.find(params[:id])
end
def resource_params
params.require(:user_role).permit(:name, :color, :highlighted, :position, permissions_as_keys: [])
def demote
authorize @user, :demote?
@user.demote!
log_action :demote, @user
redirect_to admin_account_path(@user.account_id)
end
end
end

View File

@ -1,59 +0,0 @@
# frozen_string_literal: true
module Admin
class RulesController < BaseController
before_action :set_rule, except: [:index, :create]
def index
authorize :rule, :index?
@rules = Rule.ordered
@rule = Rule.new
end
def create
authorize :rule, :create?
@rule = Rule.new(resource_params)
if @rule.save
redirect_to admin_rules_path
else
@rules = Rule.ordered
render :index
end
end
def edit
authorize @rule, :update?
end
def update
authorize @rule, :update?
if @rule.update(resource_params)
redirect_to admin_rules_path
else
render :edit
end
end
def destroy
authorize @rule, :destroy?
@rule.discard
redirect_to admin_rules_path
end
private
def set_rule
@rule = Rule.find(params[:id])
end
def resource_params
params.require(:rule).permit(:text, :priority)
end
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Admin::Settings::AboutController < Admin::SettingsController
private
def after_update_redirect_path
admin_settings_about_path
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Admin::Settings::AppearanceController < Admin::SettingsController
private
def after_update_redirect_path
admin_settings_appearance_path
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Admin::Settings::BrandingController < Admin::SettingsController
private
def after_update_redirect_path
admin_settings_branding_path
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Admin::Settings::ContentRetentionController < Admin::SettingsController
private
def after_update_redirect_path
admin_settings_content_retention_path
end
end

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Admin::Settings::DiscoveryController < Admin::SettingsController
private
def after_update_redirect_path
admin_settings_discovery_path
end
end

Some files were not shown because too many files have changed in this diff Show More