about summary refs log tree commit diff
diff options
context:
space:
mode:
authorClaire <claire.github-309c@sitedethib.com>2021-02-22 15:23:46 +0100
committerClaire <claire.github-309c@sitedethib.com>2021-02-22 15:23:46 +0100
commit679642e26c20bf04ceb1a90349c23eb5950bd029 (patch)
treebfe48773e2fcf9e0fa3a7753b3d8fa2de7217f52
parent8792128f38e19b0d7882468a4f1f9362b98793a0 (diff)
parent2127f40e6bf6deab62f48030263c459d14fed364 (diff)
Merge branch 'main' into glitch-soc/merge-upstream
-rw-r--r--.deepsource.toml11
-rw-r--r--.dockerignore1
-rw-r--r--.gitignore4
-rw-r--r--Gemfile8
-rw-r--r--Gemfile.lock100
-rw-r--r--app/controllers/about_controller.rb1
-rw-r--r--app/controllers/accounts_controller.rb6
-rw-r--r--app/controllers/admin/rules_controller.rb59
-rw-r--r--app/controllers/api/v1/accounts/lookup_controller.rb16
-rw-r--r--app/controllers/api/v1/instances/rules_controller.rb17
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/media_proxy_controller.rb2
-rw-r--r--app/javascript/mastodon/components/autosuggest_input.js3
-rw-r--r--app/javascript/styles/mastodon/about.scss21
-rw-r--r--app/lib/webfinger.rb4
-rw-r--r--app/models/rule.rb22
-rw-r--r--app/policies/rule_policy.rb19
-rw-r--r--app/presenters/instance_presenter.rb4
-rw-r--r--app/serializers/rest/instance_serializer.rb4
-rw-r--r--app/serializers/rest/rule_serializer.rb9
-rw-r--r--app/views/about/more.html.haml13
-rw-r--r--app/views/admin/rules/_rule.html.haml11
-rw-r--r--app/views/admin/rules/edit.html.haml11
-rw-r--r--app/views/admin/rules/index.html.haml24
-rw-r--r--chart/Chart.lock12
-rw-r--r--chart/Chart.yaml9
-rw-r--r--chart/readme.md17
-rw-r--r--chart/templates/NOTES.txt5
-rw-r--r--chart/templates/configmap-env.yaml74
-rw-r--r--chart/templates/cronjob-media-remove.yaml14
-rw-r--r--chart/templates/deployment-sidekiq.yaml12
-rw-r--r--chart/templates/deployment-streaming.yaml8
-rw-r--r--chart/templates/deployment-web.yaml12
-rw-r--r--chart/templates/ingress.yaml17
-rw-r--r--chart/templates/job-assets-precompile.yaml12
-rw-r--r--chart/templates/job-chewy-upgrade.yaml12
-rw-r--r--chart/templates/job-create-admin.yaml18
-rw-r--r--chart/templates/job-db-migrate.yaml12
-rw-r--r--chart/templates/pvc-assets.yaml9
-rw-r--r--chart/templates/pvc-system.yaml9
-rw-r--r--chart/templates/secrets.yaml31
-rw-r--r--chart/templates/service-streaming.yaml2
-rw-r--r--chart/templates/service-web.yaml2
-rw-r--r--chart/values.yaml (renamed from chart/values.yaml.template)153
-rw-r--r--config/initializers/pagination.rb0
-rw-r--r--config/locales/en.yml7
-rw-r--r--config/locales/simple_form.en.yml4
-rw-r--r--config/navigation.rb1
-rw-r--r--config/routes.rb3
-rw-r--r--db/migrate/20210221045109_create_rules.rb11
-rw-r--r--db/schema.rb10
-rw-r--r--lib/action_dispatch/cookie_jar_extensions.rb12
-rw-r--r--package.json20
-rw-r--r--spec/fabricators/rule_fabricator.rb5
-rw-r--r--spec/models/rule_spec.rb5
-rw-r--r--yarn.lock228
56 files changed, 760 insertions, 358 deletions
diff --git a/.deepsource.toml b/.deepsource.toml
index 73d38901c..bcd310412 100644
--- a/.deepsource.toml
+++ b/.deepsource.toml
@@ -1,6 +1,11 @@
 version = 1
 
-test_patterns = ["/app/javascript/mastodon/**/__tests__/**"]
+test_patterns = ["app/javascript/mastodon/**/__tests__/**"]
+
+exclude_patterns = [
+    "db/migrate/**",
+    "db/post_migrate/**"
+]
 
 [[analyzers]]
 name = "ruby"
@@ -12,7 +17,7 @@ enabled = true
 
   [analyzers.meta]
   environment = [
-    "nodejs",
     "browser",
-    "jest"
+    "jest",
+    "nodejs"
   ]
diff --git a/.dockerignore b/.dockerignore
index bf918029b..9bc23d813 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -13,3 +13,4 @@ vendor/bundle
 postgres
 redis
 elasticsearch
+chart
diff --git a/.gitignore b/.gitignore
index 4545270b3..b4d2712ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,10 +43,8 @@
 /redis
 /elasticsearch
 
-# ignore Helm lockfile, dependency charts, and local values file
-/chart/Chart.lock
+# ignore Helm dependency charts
 /chart/charts/*.tgz
-/chart/values.yaml
 
 # Ignore Apple files
 .DS_Store
diff --git a/Gemfile b/Gemfile
index fc38339c9..5fa7076e0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,8 +5,8 @@ ruby '>= 2.5.0', '< 3.0.0'
 
 gem 'pkg-config', '~> 1.4'
 
-gem 'puma', '~> 5.1'
-gem 'rails', '~> 5.2.4.4'
+gem 'puma', '~> 5.2'
+gem 'rails', '~> 5.2.4.5'
 gem 'sprockets', '~> 3.7.2'
 gem 'thor', '~> 1.1'
 gem 'rack', '~> 2.2.3'
@@ -88,7 +88,7 @@ gem 'sidekiq-scheduler', '~> 3.0'
 gem 'sidekiq-unique-jobs', '~> 6.0'
 gem 'sidekiq-bulk', '~>0.2.0'
 gem 'simple-navigation', '~> 4.1'
-gem 'simple_form', '~> 5.0'
+gem 'simple_form', '~> 5.1'
 gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
 gem 'stoplight', '~> 2.2.1'
 gem 'strong_migrations', '~> 0.7'
@@ -121,7 +121,7 @@ end
 group :test do
   gem 'capybara', '~> 3.35'
   gem 'climate_control', '~> 0.2'
-  gem 'faker', '~> 2.15'
+  gem 'faker', '~> 2.16'
   gem 'microformats', '~> 4.2'
   gem 'rails-controller-testing', '~> 1.0'
   gem 'rspec-sidekiq', '~> 3.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index aa8cba1f6..35c65a41b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -16,25 +16,25 @@ GIT
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (5.2.4.4)
-      actionpack (= 5.2.4.4)
+    actioncable (5.2.4.5)
+      actionpack (= 5.2.4.5)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
-    actionmailer (5.2.4.4)
-      actionpack (= 5.2.4.4)
-      actionview (= 5.2.4.4)
-      activejob (= 5.2.4.4)
+    actionmailer (5.2.4.5)
+      actionpack (= 5.2.4.5)
+      actionview (= 5.2.4.5)
+      activejob (= 5.2.4.5)
       mail (~> 2.5, >= 2.5.4)
       rails-dom-testing (~> 2.0)
-    actionpack (5.2.4.4)
-      actionview (= 5.2.4.4)
-      activesupport (= 5.2.4.4)
+    actionpack (5.2.4.5)
+      actionview (= 5.2.4.5)
+      activesupport (= 5.2.4.5)
       rack (~> 2.0, >= 2.0.8)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.0.2)
-    actionview (5.2.4.4)
-      activesupport (= 5.2.4.4)
+    actionview (5.2.4.5)
+      activesupport (= 5.2.4.5)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
@@ -45,20 +45,20 @@ GEM
       case_transform (>= 0.2)
       jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
     active_record_query_trace (1.8)
-    activejob (5.2.4.4)
-      activesupport (= 5.2.4.4)
+    activejob (5.2.4.5)
+      activesupport (= 5.2.4.5)
       globalid (>= 0.3.6)
-    activemodel (5.2.4.4)
-      activesupport (= 5.2.4.4)
-    activerecord (5.2.4.4)
-      activemodel (= 5.2.4.4)
-      activesupport (= 5.2.4.4)
+    activemodel (5.2.4.5)
+      activesupport (= 5.2.4.5)
+    activerecord (5.2.4.5)
+      activemodel (= 5.2.4.5)
+      activesupport (= 5.2.4.5)
       arel (>= 9.0)
-    activestorage (5.2.4.4)
-      actionpack (= 5.2.4.4)
-      activerecord (= 5.2.4.4)
+    activestorage (5.2.4.5)
+      actionpack (= 5.2.4.5)
+      activerecord (= 5.2.4.5)
       marcel (~> 0.3.1)
-    activesupport (5.2.4.4)
+    activesupport (5.2.4.5)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 0.7, < 2)
       minitest (~> 5.1)
@@ -79,7 +79,7 @@ GEM
       cocaine (~> 0.5.3)
     awrence (1.1.1)
     aws-eventstream (1.1.0)
-    aws-partitions (1.424.0)
+    aws-partitions (1.427.0)
     aws-sdk-core (3.112.0)
       aws-eventstream (~> 1, >= 1.0.2)
       aws-partitions (~> 1, >= 1.239.0)
@@ -88,7 +88,7 @@ GEM
     aws-sdk-kms (1.42.0)
       aws-sdk-core (~> 3, >= 3.112.0)
       aws-sigv4 (~> 1.1)
-    aws-sdk-s3 (1.88.0)
+    aws-sdk-s3 (1.88.1)
       aws-sdk-core (~> 3, >= 3.112.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.1)
@@ -210,7 +210,7 @@ GEM
       tzinfo
     excon (0.76.0)
     fabrication (2.21.1)
-    faker (2.15.1)
+    faker (2.16.0)
       i18n (>= 1.6, < 2)
     faraday (1.3.0)
       faraday-net_http (~> 1.0)
@@ -275,7 +275,7 @@ GEM
     httplog (1.4.3)
       rack (>= 1.0)
       rainbow (>= 2.0.0)
-    i18n (1.8.8)
+    i18n (1.8.9)
       concurrent-ruby (~> 1.0)
     i18n-tasks (0.9.33)
       activesupport (>= 4.0.2)
@@ -331,7 +331,7 @@ GEM
       activesupport (>= 4)
       railties (>= 4)
       request_store (~> 1.0)
-    loofah (2.8.0)
+    loofah (2.9.0)
       crass (~> 1.0.2)
       nokogiri (>= 1.5.9)
     mail (2.7.1)
@@ -361,7 +361,7 @@ GEM
     net-scp (3.0.0)
       net-ssh (>= 2.6.5, < 7.0.0)
     net-ssh (6.1.0)
-    nio4r (2.5.4)
+    nio4r (2.5.5)
     nokogiri (1.11.1)
       mini_portile2 (~> 2.5.0)
       racc (~> 1.4)
@@ -432,7 +432,7 @@ GEM
     pry-rails (0.3.9)
       pry (>= 0.10.4)
     public_suffix (4.0.6)
-    puma (5.1.1)
+    puma (5.2.1)
       nio4r (~> 2.0)
     pundit (2.1.0)
       activesupport (>= 3.0.0)
@@ -447,18 +447,18 @@ GEM
       rack
     rack-test (1.1.0)
       rack (>= 1.0, < 3)
-    rails (5.2.4.4)
-      actioncable (= 5.2.4.4)
-      actionmailer (= 5.2.4.4)
-      actionpack (= 5.2.4.4)
-      actionview (= 5.2.4.4)
-      activejob (= 5.2.4.4)
-      activemodel (= 5.2.4.4)
-      activerecord (= 5.2.4.4)
-      activestorage (= 5.2.4.4)
-      activesupport (= 5.2.4.4)
+    rails (5.2.4.5)
+      actioncable (= 5.2.4.5)
+      actionmailer (= 5.2.4.5)
+      actionpack (= 5.2.4.5)
+      actionview (= 5.2.4.5)
+      activejob (= 5.2.4.5)
+      activemodel (= 5.2.4.5)
+      activerecord (= 5.2.4.5)
+      activestorage (= 5.2.4.5)
+      activesupport (= 5.2.4.5)
       bundler (>= 1.3.0)
-      railties (= 5.2.4.4)
+      railties (= 5.2.4.5)
       sprockets-rails (>= 2.0.0)
     rails-controller-testing (1.0.5)
       actionpack (>= 5.0.1.rc1)
@@ -474,9 +474,9 @@ GEM
       railties (>= 5.0, < 6)
     rails-settings-cached (0.6.6)
       rails (>= 4.2.0)
-    railties (5.2.4.4)
-      actionpack (= 5.2.4.4)
-      activesupport (= 5.2.4.4)
+    railties (5.2.4.5)
+      actionpack (= 5.2.4.5)
+      activesupport (= 5.2.4.5)
       method_source
       rake (>= 0.8.7)
       thor (>= 0.19.0, < 2.0)
@@ -593,9 +593,9 @@ GEM
       thor (>= 0.20, < 2.0)
     simple-navigation (4.1.0)
       activesupport (>= 2.3.2)
-    simple_form (5.0.3)
-      actionpack (>= 5.0)
-      activemodel (>= 5.0)
+    simple_form (5.1.0)
+      actionpack (>= 5.2)
+      activemodel (>= 5.2)
     simplecov (0.21.2)
       docile (~> 1.1)
       simplecov-html (~> 0.11)
@@ -722,7 +722,7 @@ DEPENDENCIES
   dotenv-rails (~> 2.7)
   ed25519 (~> 1.2)
   fabrication (~> 2.21)
-  faker (~> 2.15)
+  faker (~> 2.16)
   fast_blank (~> 1.0)
   fastimage
   fog-core (<= 2.1.0)
@@ -774,12 +774,12 @@ DEPENDENCIES
   private_address_check (~> 0.5)
   pry-byebug (~> 3.9)
   pry-rails (~> 0.3)
-  puma (~> 5.1)
+  puma (~> 5.2)
   pundit (~> 2.1)
   rack (~> 2.2.3)
   rack-attack (~> 6.5)
   rack-cors (~> 1.1)
-  rails (~> 5.2.4.4)
+  rails (~> 5.2.4.5)
   rails-controller-testing (~> 1.0)
   rails-i18n (~> 5.1)
   rails-settings-cached (~> 0.6)
@@ -802,7 +802,7 @@ DEPENDENCIES
   sidekiq-scheduler (~> 3.0)
   sidekiq-unique-jobs (~> 6.0)
   simple-navigation (~> 4.1)
-  simple_form (~> 5.0)
+  simple_form (~> 5.1)
   simplecov (~> 0.21)
   sprockets (~> 3.7.2)
   sprockets-rails (~> 3.2)
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 5ff6990d7..620c0ff78 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -22,6 +22,7 @@ class AboutController < ApplicationController
 
     toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
 
+    @rules             = Rule.ordered
     @contents          = toc_generator.html
     @table_of_contents = toc_generator.toc
     @blocks            = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index dfe94af7d..ab7e1f077 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -136,15 +136,15 @@ class AccountsController < ApplicationController
   end
 
   def media_requested?
-    request.path.split('.').first.ends_with?('/media') && !tag_requested?
+    request.path.split('.').first.end_with?('/media') && !tag_requested?
   end
 
   def replies_requested?
-    request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
+    request.path.split('.').first.end_with?('/with_replies') && !tag_requested?
   end
 
   def tag_requested?
-    request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
+    request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
   end
 
   def cached_filtered_status_page
diff --git a/app/controllers/admin/rules_controller.rb b/app/controllers/admin/rules_controller.rb
new file mode 100644
index 000000000..f3bed3ad8
--- /dev/null
+++ b/app/controllers/admin/rules_controller.rb
@@ -0,0 +1,59 @@
+# 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
diff --git a/app/controllers/api/v1/accounts/lookup_controller.rb b/app/controllers/api/v1/accounts/lookup_controller.rb
new file mode 100644
index 000000000..aee6be18a
--- /dev/null
+++ b/app/controllers/api/v1/accounts/lookup_controller.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class Api::V1::Accounts::LookupController < Api::BaseController
+  before_action -> { authorize_if_got_token! :read, :'read:accounts' }
+  before_action :set_account
+
+  def show
+    render json: @account, serializer: REST::AccountSerializer
+  end
+
+  private
+
+  def set_account
+    @account = ResolveAccountService.new.call(params[:acct], skip_webfinger: true) || raise(ActiveRecord::RecordNotFound)
+  end
+end
diff --git a/app/controllers/api/v1/instances/rules_controller.rb b/app/controllers/api/v1/instances/rules_controller.rb
new file mode 100644
index 000000000..93cf3c759
--- /dev/null
+++ b/app/controllers/api/v1/instances/rules_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class Api::V1::Instances::RulesController < Api::BaseController
+  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
+
+  before_action :set_rules
+
+  def index
+    render json: @rules, each_serializer: REST::RuleSerializer
+  end
+
+  private
+
+  def set_rules
+    @rules = Rule.ordered
+  end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a4b740b89..7e97009cf 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -44,7 +44,7 @@ class ApplicationController < ActionController::Base
   private
 
   def https_enabled?
-    Rails.env.production? && !request.path.start_with?('/health') && !request.headers["Host"].ends_with?(".onion")
+    Rails.env.production? && !request.path.start_with?('/health') && !request.headers["Host"].end_with?(".onion")
   end
 
   def authorized_fetch_mode?
diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb
index 0b1d09de9..1b610318d 100644
--- a/app/controllers/media_proxy_controller.rb
+++ b/app/controllers/media_proxy_controller.rb
@@ -37,7 +37,7 @@ class MediaProxyController < ApplicationController
   end
 
   def version
-    if request.path.ends_with?('/small')
+    if request.path.end_with?('/small')
       :small
     else
       :original
diff --git a/app/javascript/mastodon/components/autosuggest_input.js b/app/javascript/mastodon/components/autosuggest_input.js
index 5187f95c8..12d44b5d0 100644
--- a/app/javascript/mastodon/components/autosuggest_input.js
+++ b/app/javascript/mastodon/components/autosuggest_input.js
@@ -6,7 +6,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import classNames from 'classnames';
-import { List as ImmutableList } from 'immutable';
 
 const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
   let word;
@@ -55,7 +54,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
 
   static defaultProps = {
     autoFocus: true,
-    searchTokens: ImmutableList(['@', ':', '#']),
+    searchTokens: ['@', ':', '#'],
   };
 
   state = {
diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss
index d6bd9e3c6..281f5b2bf 100644
--- a/app/javascript/styles/mastodon/about.scss
+++ b/app/javascript/styles/mastodon/about.scss
@@ -884,3 +884,24 @@ $small-breakpoint: 960px;
   }
 }
 
+.rules-list {
+  background: darken($ui-base-color, 2%);
+  border: 1px solid darken($ui-base-color, 8%);
+  border-radius: 4px;
+  padding: 0.5em 2.5em !important;
+  margin-top: 1.85em !important;
+
+  li {
+    border-bottom: 1px solid lighten($ui-base-color, 4%);
+    color: $dark-text-color;
+    padding: 1em;
+
+    &:last-child {
+      border-bottom: 0;
+    }
+  }
+
+  &__text {
+    color: $primary-text-color;
+  }
+}
diff --git a/app/lib/webfinger.rb b/app/lib/webfinger.rb
index 40795a7aa..e0e022cea 100644
--- a/app/lib/webfinger.rb
+++ b/app/lib/webfinger.rb
@@ -88,7 +88,7 @@ class Webfinger
   end
 
   def standard_url
-    if @domain.ends_with? ".onion"
+    if @domain.end_with? ".onion"
       "http://#{@domain}/.well-known/webfinger?resource=#{@uri}"
     else
       "https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
@@ -96,7 +96,7 @@ class Webfinger
   end
 
   def host_meta_url
-    if @domain.ends_with? ".onion"
+    if @domain.end_with? ".onion"
       "http://#{@domain}/.well-known/host-meta"
     else
       "https://#{@domain}/.well-known/host-meta"
diff --git a/app/models/rule.rb b/app/models/rule.rb
new file mode 100644
index 000000000..7b62f2b35
--- /dev/null
+++ b/app/models/rule.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: rules
+#
+#  id         :bigint(8)        not null, primary key
+#  priority   :integer          default(0), not null
+#  deleted_at :datetime
+#  text       :text             default(""), not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+class Rule < ApplicationRecord
+  include Discard::Model
+
+  self.discard_column = :deleted_at
+
+  validates :text, presence: true, length: { maximum: 300 }
+
+  scope :ordered, -> { kept.order(priority: :asc) }
+end
diff --git a/app/policies/rule_policy.rb b/app/policies/rule_policy.rb
new file mode 100644
index 000000000..6a4def009
--- /dev/null
+++ b/app/policies/rule_policy.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RulePolicy < ApplicationPolicy
+  def index?
+    staff?
+  end
+
+  def create?
+    admin?
+  end
+
+  def update?
+    admin?
+  end
+
+  def destroy?
+    admin?
+  end
+end
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index a37d904dc..345a5e5e9 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -16,6 +16,10 @@ class InstancePresenter
     Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, ''))
   end
 
+  def rules
+    Rule.ordered
+  end
+
   def user_count
     Rails.cache.fetch('user_count') { User.confirmed.joins(:account).merge(Account.without_suspended).count }
   end
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 54e7c450c..ae8b80fb7 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -9,7 +9,9 @@ class REST::InstanceSerializer < ActiveModel::Serializer
 
   has_one :contact_account, serializer: REST::AccountSerializer
 
-  delegate :contact_account, to: :instance_presenter
+  has_many :rules, serializer: REST::RuleSerializer
+
+  delegate :contact_account, :rules, to: :instance_presenter
 
   def uri
     Rails.configuration.x.local_domain
diff --git a/app/serializers/rest/rule_serializer.rb b/app/serializers/rest/rule_serializer.rb
new file mode 100644
index 000000000..fc925925a
--- /dev/null
+++ b/app/serializers/rest/rule_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::RuleSerializer < ActiveModel::Serializer
+  attributes :id, :text
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 78f54ec5a..45675224b 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -47,6 +47,16 @@
     - else
       .box-widget
         .rich-formatting
+          - unless @rules.empty?
+            %h2#rules= t('about.rules')
+
+            %p= t('about.rules_html')
+
+            %ol.rules-list
+              - @rules.each do |rule|
+                %li
+                  .rules-list__text= rule.text
+
           = @contents.html_safe
 
           - if display_blocks? && !@blocks.empty?
@@ -69,6 +79,9 @@
 
   .column-4
     %ul.table-of-contents
+      - unless @rules.empty?
+        %li= link_to t('about.rules'), '#rules'
+
       - @table_of_contents.each do |item|
         %li
           = link_to item.title, "##{item.anchor}"
diff --git a/app/views/admin/rules/_rule.html.haml b/app/views/admin/rules/_rule.html.haml
new file mode 100644
index 000000000..f8a9ac786
--- /dev/null
+++ b/app/views/admin/rules/_rule.html.haml
@@ -0,0 +1,11 @@
+.announcements-list__item
+  = link_to edit_admin_rule_path(rule), class: 'announcements-list__item__title' do
+    = "#{rule_counter + 1}."
+    = truncate(rule.text)
+
+  .announcements-list__item__action-bar
+    .announcements-list__item__meta
+      = rule.text
+
+    %div
+      = table_link_to 'trash', t('admin.rules.delete'), admin_rule_path(rule), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, rule)
diff --git a/app/views/admin/rules/edit.html.haml b/app/views/admin/rules/edit.html.haml
new file mode 100644
index 000000000..ba7e6451a
--- /dev/null
+++ b/app/views/admin/rules/edit.html.haml
@@ -0,0 +1,11 @@
+- content_for :page_title do
+  = t('admin.rules.edit')
+
+= simple_form_for @rule, url: admin_rule_path(@rule) do |f|
+  = render 'shared/error_messages', object: @rule
+
+  .fields-group
+    = f.input :text, wrapper: :with_block_label
+
+  .actions
+    = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/admin/rules/index.html.haml b/app/views/admin/rules/index.html.haml
new file mode 100644
index 000000000..3b069d083
--- /dev/null
+++ b/app/views/admin/rules/index.html.haml
@@ -0,0 +1,24 @@
+- content_for :page_title do
+  = t('admin.rules.title')
+
+.simple_form
+  %p.hint= t('admin.rules.description')
+
+- if can? :create, :rule
+  = simple_form_for @rule, url: admin_rules_path do |f|
+    = render 'shared/error_messages', object: @rule
+
+    .fields-group
+      = f.input :text, wrapper: :with_block_label
+
+    .actions
+      = f.button :button, t('admin.rules.add_new'), type: :submit
+
+  %hr.spacer/
+
+- if @rules.empty?
+  %div.muted-hint.center-text
+    = t 'admin.rules.empty'
+- else
+  .announcements-list
+    = render partial: 'rule', collection: @rules
diff --git a/chart/Chart.lock b/chart/Chart.lock
new file mode 100644
index 000000000..7ed405bf7
--- /dev/null
+++ b/chart/Chart.lock
@@ -0,0 +1,12 @@
+dependencies:
+- name: elasticsearch
+  repository: https://charts.bitnami.com/bitnami
+  version: 12.8.2
+- name: postgresql
+  repository: https://charts.bitnami.com/bitnami
+  version: 8.10.14
+- name: redis
+  repository: https://charts.bitnami.com/bitnami
+  version: 10.9.0
+digest: sha256:9e423aa9a7a46f49e44f0411d61afd685eedf4475752a2b1a24a86a83b0752d0
+generated: "2021-02-16T17:10:49.594247-08:00"
diff --git a/chart/Chart.yaml b/chart/Chart.yaml
index 19f9c64c7..72d710a05 100644
--- a/chart/Chart.yaml
+++ b/chart/Chart.yaml
@@ -15,7 +15,7 @@ type: application
 # This is the chart version. This version number should be incremented each time you make changes
 # to the chart and its templates, including the app version.
 # Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.1.2
+version: 1.1.0
 
 # This is the version number of the application being deployed. This version number should be
 # incremented each time you make changes to the application. Versions are not expected to
@@ -24,12 +24,13 @@ appVersion: 3.3.0
 
 dependencies:
   - name: elasticsearch
-    version: "12.x.x"
+    version: 12.8.2
     repository: https://charts.bitnami.com/bitnami
     condition: elasticsearch.enabled
   - name: postgresql
-    version: "8.x.x"
+    version: 8.10.14
     repository: https://charts.bitnami.com/bitnami
+    condition: postgresql.enabled
   - name: redis
-    version: "10.x.x"
+    version: 10.9.0
     repository: https://charts.bitnami.com/bitnami
diff --git a/chart/readme.md b/chart/readme.md
index 804e98094..9f7a88958 100644
--- a/chart/readme.md
+++ b/chart/readme.md
@@ -3,12 +3,9 @@
 This is a [Helm](https://helm.sh/) chart for installing Mastodon into a
 Kubernetes cluster.  The basic usage is:
 
-```
-cp values.yaml.template values.yaml
-edit values.yaml # configure required settings
-helm dep update
-helm upgrade --install my-mastodon ./
-```
+1. edit `values.yaml` or create a separate yaml file for custom values
+1. `helm dep update`
+1. `helm install --namespace mastodon --create-namespace my-mastodon ./ -f path/to/additional/values.yaml`
 
 This chart has been tested on Helm 3.0.1 and above.
 
@@ -16,21 +13,17 @@ This chart has been tested on Helm 3.0.1 and above.
 
 The variables that _must_ be configured are:
 
-- `ingress.hostname`; even if you aren’t using an Ingress, this value is used to
-  set `LOCAL_DOMAIN`.
-
-- password and keys in the `secrets`, `postgresql`, and `redis` groups; if
+- password and keys in the `mastodon.secrets`, `postgresql`, and `redis` groups; if
   left blank, some of those values will be autogenerated, but will not persist
   across upgrades.
 
-- SMTP settings for your mailer in the `smtp` group.
+- SMTP settings for your mailer in the `mastodon.smtp` group.
 
 # Missing features
 
 Currently this chart does _not_ support:
 
 - Hidden services
-- S3/Minio/GCS
 - Single Sign-On
 - Swift
 - configurations using `WEB_DOMAIN`
diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt
index 36cced67a..b09c40bec 100644
--- a/chart/templates/NOTES.txt
+++ b/chart/templates/NOTES.txt
@@ -2,7 +2,7 @@
 {{- if .Values.ingress.enabled }}
 {{- range $host := .Values.ingress.hosts }}
   {{- range .paths }}
-  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
   {{- end }}
 {{- end }}
 {{- else if contains "NodePort" .Values.service.type }}
@@ -16,6 +16,7 @@
   echo http://$SERVICE_IP:{{ .Values.service.port }}
 {{- else if contains "ClusterIP" .Values.service.type }}
   export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mastodon.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
   echo "Visit http://127.0.0.1:8080 to use your application"
-  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
 {{- end }}
diff --git a/chart/templates/configmap-env.yaml b/chart/templates/configmap-env.yaml
index 27351e97e..701368e49 100644
--- a/chart/templates/configmap-env.yaml
+++ b/chart/templates/configmap-env.yaml
@@ -5,61 +5,75 @@ metadata:
   labels:
     {{- include "mastodon.labels" . | nindent 4 }}
 data:
+  {{- if .Values.postgresql.enabled }}
   DB_HOST: {{ template "mastodon.postgresql.fullname" . }}
+  {{- else }}
+  DB_HOST: {{ .Values.postgresql.postgresqlHostname }}
+  {{- end }}
   DB_NAME: {{ .Values.postgresql.postgresqlDatabase }}
-  DB_POOL: {{ .Values.application.sidekiq.concurrency | quote }}
+  DB_POOL: {{ .Values.mastodon.sidekiq.concurrency | quote }}
   DB_PORT: "5432"
   DB_USER: {{ .Values.postgresql.postgresqlUsername }}
-  DEFAULT_LOCALE: {{ .Values.locale }}
+  DEFAULT_LOCALE: {{ .Values.mastodon.locale }}
   {{- if .Values.elasticsearch.enabled }}
   ES_ENABLED: "true"
   ES_HOST: {{ template "mastodon.elasticsearch.fullname" . }}-master
   ES_PORT: "9200"
   {{- end }}
-  LOCAL_DOMAIN: {{ .Values.ingress.hostname }}
+  LOCAL_DOMAIN: {{ .Values.mastodon.local_domain }}
   # https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior
   MALLOC_ARENA_MAX: "2"
   NODE_ENV: "production"
   RAILS_ENV: "production"
   REDIS_HOST: {{ template "mastodon.redis.fullname" . }}-master
   REDIS_PORT: "6379"
-  {{- if .Values.smtp.auth_method }}
-  SMTP_AUTH_METHOD: {{ .Values.smtp.auth_method }}
+  {{- if .Values.mastodon.s3.enabled }}
+  S3_BUCKET: {{ .Values.mastodon.s3.bucket }}
+  S3_ENABLED: "true"
+  S3_ENDPOINT: {{ .Values.mastodon.s3.endpoint }}
+  S3_HOSTNAME: {{ .Values.mastodon.s3.hostname }}
+  S3_PROTOCOL: "https"
+  {{- if .Values.mastodon.s3.region }}
+  S3_REGION: {{ .Values.mastodon.s3.region }}
+  {{- end }}
+  {{- end }}
+  {{- if .Values.mastodon.smtp.auth_method }}
+  SMTP_AUTH_METHOD: {{ .Values.mastodon.smtp.auth_method }}
   {{- end }}
-  {{- if .Values.smtp.ca_file }}
-  SMTP_CA_FILE: {{ .Values.smtp.ca_file }}
+  {{- if .Values.mastodon.smtp.ca_file }}
+  SMTP_CA_FILE: {{ .Values.mastodon.smtp.ca_file }}
   {{- end }}
-  {{- if .Values.smtp.delivery_method }}
-  SMTP_DELIVERY_METHOD: {{ .Values.smtp.delivery_method }}
+  {{- if .Values.mastodon.smtp.delivery_method }}
+  SMTP_DELIVERY_METHOD: {{ .Values.mastodon.smtp.delivery_method }}
   {{- end }}
-  {{- if .Values.smtp.domain }}
-  SMTP_DOMAIN: {{ .Values.smtp.domain }}
+  {{- if .Values.mastodon.smtp.domain }}
+  SMTP_DOMAIN: {{ .Values.mastodon.smtp.domain }}
   {{- end }}
-  {{- if .Values.smtp.enable_starttls_auto }}
-  SMTP_ENABLE_STARTTLS_AUTO: {{ .Values.smtp.enable_starttls_auto | quote }}
+  {{- if .Values.mastodon.smtp.enable_starttls_auto }}
+  SMTP_ENABLE_STARTTLS_AUTO: {{ .Values.mastodon.smtp.enable_starttls_auto | quote }}
   {{- end }}
-  {{- if .Values.smtp.from_address }}
-  SMTP_FROM_ADDRESS: {{ .Values.smtp.from_address }}
+  {{- if .Values.mastodon.smtp.from_address }}
+  SMTP_FROM_ADDRESS: {{ .Values.mastodon.smtp.from_address }}
   {{- end }}
-  {{- if .Values.smtp.login }}
-  SMTP_LOGIN: {{ .Values.smtp.login }}
+  {{- if .Values.mastodon.smtp.login }}
+  SMTP_LOGIN: {{ .Values.mastodon.smtp.login }}
   {{- end }}
-  {{- if .Values.smtp.openssl_verify_mode }}
-  SMTP_OPENSSL_VERIFY_MODE: {{ .Values.smtp.openssl_verify_mode }}
+  {{- if .Values.mastodon.smtp.openssl_verify_mode }}
+  SMTP_OPENSSL_VERIFY_MODE: {{ .Values.mastodon.smtp.openssl_verify_mode }}
   {{- end }}
-  {{- if .Values.smtp.password }}
-  SMTP_PASSWORD: {{ .Values.smtp.password }}
+  {{- if .Values.mastodon.smtp.password }}
+  SMTP_PASSWORD: {{ .Values.mastodon.smtp.password }}
   {{- end }}
-  {{- if .Values.smtp.port }}
-  SMTP_PORT: {{ .Values.smtp.port | quote }}
+  {{- if .Values.mastodon.smtp.port }}
+  SMTP_PORT: {{ .Values.mastodon.smtp.port | quote }}
   {{- end }}
-  {{- if .Values.smtp.reply_to }}
-  SMTP_REPLY_TO: {{ .Values.smtp.reply_to }}
+  {{- if .Values.mastodon.smtp.reply_to }}
+  SMTP_REPLY_TO: {{ .Values.mastodon.smtp.reply_to }}
   {{- end }}
-  {{- if .Values.smtp.server }}
-  SMTP_SERVER: {{ .Values.smtp.server }}
+  {{- if .Values.mastodon.smtp.server }}
+  SMTP_SERVER: {{ .Values.mastodon.smtp.server }}
   {{- end }}
-  {{- if .Values.smtp.tls }}
-  SMTP_TLS: {{ .Values.smtp.tls | quote }}
+  {{- if .Values.mastodon.smtp.tls }}
+  SMTP_TLS: {{ .Values.mastodon.smtp.tls | quote }}
   {{- end }}
-  STREAMING_CLUSTER_NUM: {{ .Values.application.streaming.workers | quote }}
+  STREAMING_CLUSTER_NUM: {{ .Values.mastodon.streaming.workers | quote }}
diff --git a/chart/templates/cronjob-media-remove.yaml b/chart/templates/cronjob-media-remove.yaml
index 8a01a2551..3d6e25cc6 100644
--- a/chart/templates/cronjob-media-remove.yaml
+++ b/chart/templates/cronjob-media-remove.yaml
@@ -1,4 +1,4 @@
-{{ if .Values.cron.removeMedia.enabled }}
+{{ if .Values.mastodon.cron.removeMedia.enabled }}
 apiVersion: batch/v1beta1
 kind: CronJob
 metadata:
@@ -6,7 +6,7 @@ metadata:
   labels:
     {{- include "mastodon.labels" . | nindent 4 }}
 spec:
-  schedule: {{ .Values.cron.removeMedia.schedule }}
+  schedule: {{ .Values.mastodon.cron.removeMedia.schedule }}
   jobTemplate:
     spec:
       template:
@@ -14,9 +14,10 @@ spec:
           name: {{ include "mastodon.fullname" . }}-media-remove
         spec:
           restartPolicy: OnFailure
+          {{- if (not .Values.mastodon.s3.enabled) }}
           # ensure we run on the same node as the other rails components; only
           # required when using PVCs that are ReadWriteOnce
-          {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+          {{- if or (eq "ReadWriteOnce" .Values.mastodon.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.mastodon.persistence.system.accessMode) }}
           affinity:
             podAffinity:
               requiredDuringSchedulingIgnoredDuringExecution:
@@ -35,6 +36,7 @@ spec:
             - name: system
               persistentVolumeClaim:
                 claimName: {{ template "mastodon.fullname" . }}-system
+          {{- end }}
           containers:
             - name: {{ include "mastodon.fullname" . }}-media-remove
               image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -55,7 +57,7 @@ spec:
                       {{- if .Values.postgresql.enabled }}
                       name: {{ .Release.Name }}-postgresql
                       {{- else }}
-                      name: {{ template "mastodon.fullname" . }}-postgresql
+                      name: {{ template "mastodon.fullname" . }}
                       {{- end }}
                       key: postgresql-password
                 - name: "REDIS_PASSWORD"
@@ -64,10 +66,12 @@ spec:
                       name: {{ .Release.Name }}-redis
                       key: redis-password
                 - name: "PORT"
-                  value: {{ .Values.application.web.port | quote }}
+                  value: {{ .Values.mastodon.web.port | quote }}
+              {{- if (not .Values.mastodon.s3.enabled) }}
               volumeMounts:
                 - name: assets
                   mountPath: /opt/mastodon/public/assets
                 - name: system
                   mountPath: /opt/mastodon/public/system
+              {{- end }}
 {{- end }}
diff --git a/chart/templates/deployment-sidekiq.yaml b/chart/templates/deployment-sidekiq.yaml
index 5457183a3..baf6c2b2d 100644
--- a/chart/templates/deployment-sidekiq.yaml
+++ b/chart/templates/deployment-sidekiq.yaml
@@ -31,9 +31,10 @@ spec:
       serviceAccountName: {{ include "mastodon.serviceAccountName" . }}
       securityContext:
         {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      {{- if (not .Values.mastodon.s3.enabled) }}
       # ensure we run on the same node as the other rails components; only
       # required when using PVCs that are ReadWriteOnce
-      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      {{- if or (eq "ReadWriteOnce" .Values.mastodon.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.mastodon.persistence.system.accessMode) }}
       affinity:
         podAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
@@ -52,6 +53,7 @@ spec:
         - name: system
           persistentVolumeClaim:
             claimName: {{ template "mastodon.fullname" . }}-system
+      {{- end }}
       containers:
         - name: {{ .Chart.Name }}
           securityContext:
@@ -63,7 +65,7 @@ spec:
             - exec
             - sidekiq
             - -c
-            - {{ .Values.application.sidekiq.concurrency | quote }}
+            - {{ .Values.mastodon.sidekiq.concurrency | quote }}
           envFrom:
             - configMapRef:
                 name: {{ include "mastodon.fullname" . }}-env
@@ -73,18 +75,24 @@ spec:
             - name: "DB_PASS"
               valueFrom:
                 secretKeyRef:
+                  {{- if .Values.postgresql.enabled }}
                   name: {{ .Release.Name }}-postgresql
+                  {{- else }}
+                  name: {{ template "mastodon.fullname" . }}
+                  {{- end }}
                   key: postgresql-password
             - name: "REDIS_PASSWORD"
               valueFrom:
                 secretKeyRef:
                   name: {{ .Release.Name }}-redis
                   key: redis-password
+          {{- if (not .Values.mastodon.s3.enabled) }}
           volumeMounts:
             - name: assets
               mountPath: /opt/mastodon/public/assets
             - name: system
               mountPath: /opt/mastodon/public/system
+          {{- end }}
           resources:
             {{- toYaml .Values.resources | nindent 12 }}
       {{- with .Values.nodeSelector }}
diff --git a/chart/templates/deployment-streaming.yaml b/chart/templates/deployment-streaming.yaml
index 5d642d72c..b332b686a 100644
--- a/chart/templates/deployment-streaming.yaml
+++ b/chart/templates/deployment-streaming.yaml
@@ -43,7 +43,11 @@ spec:
             - name: "DB_PASS"
               valueFrom:
                 secretKeyRef:
+                  {{- if .Values.postgresql.enabled }}
                   name: {{ .Release.Name }}-postgresql
+                  {{- else }}
+                  name: {{ template "mastodon.fullname" . }}
+                  {{- end }}
                   key: postgresql-password
             - name: "REDIS_PASSWORD"
               valueFrom:
@@ -51,10 +55,10 @@ spec:
                   name: {{ .Release.Name }}-redis
                   key: redis-password
             - name: "PORT"
-              value: {{ .Values.application.streaming.port | quote }}
+              value: {{ .Values.mastodon.streaming.port | quote }}
           ports:
             - name: streaming
-              containerPort: {{ .Values.application.streaming.port }}
+              containerPort: {{ .Values.mastodon.streaming.port }}
               protocol: TCP
           livenessProbe:
             httpGet:
diff --git a/chart/templates/deployment-web.yaml b/chart/templates/deployment-web.yaml
index 5010e567a..8b8bb4f29 100644
--- a/chart/templates/deployment-web.yaml
+++ b/chart/templates/deployment-web.yaml
@@ -31,6 +31,7 @@ spec:
       serviceAccountName: {{ include "mastodon.serviceAccountName" . }}
       securityContext:
         {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      {{- if (not .Values.mastodon.s3.enabled) }}
       volumes:
         - name: assets
           persistentVolumeClaim:
@@ -38,6 +39,7 @@ spec:
         - name: system
           persistentVolumeClaim:
             claimName: {{ template "mastodon.fullname" . }}-system
+      {{- end }}
       containers:
         - name: {{ .Chart.Name }}
           securityContext:
@@ -59,7 +61,11 @@ spec:
             - name: "DB_PASS"
               valueFrom:
                 secretKeyRef:
+                  {{- if .Values.postgresql.enabled }}
                   name: {{ .Release.Name }}-postgresql
+                  {{- else }}
+                  name: {{ template "mastodon.fullname" . }}
+                  {{- end }}
                   key: postgresql-password
             - name: "REDIS_PASSWORD"
               valueFrom:
@@ -67,15 +73,17 @@ spec:
                   name: {{ .Release.Name }}-redis
                   key: redis-password
             - name: "PORT"
-              value: {{ .Values.application.web.port | quote }}
+              value: {{ .Values.mastodon.web.port | quote }}
+          {{- if (not .Values.mastodon.s3.enabled) }}
           volumeMounts:
             - name: assets
               mountPath: /opt/mastodon/public/assets
             - name: system
               mountPath: /opt/mastodon/public/system
+          {{- end }}
           ports:
             - name: http
-              containerPort: {{ .Values.application.web.port }}
+              containerPort: {{ .Values.mastodon.web.port }}
               protocol: TCP
           livenessProbe:
             httpGet:
diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml
index 947bf5b70..8930d2c10 100644
--- a/chart/templates/ingress.yaml
+++ b/chart/templates/ingress.yaml
@@ -1,6 +1,7 @@
 {{- if .Values.ingress.enabled -}}
 {{- $fullName := include "mastodon.fullname" . -}}
-{{- $svcPort := .Values.service.port -}}
+{{- $webPort := .Values.mastodon.web.port -}}
+{{- $streamingPort := .Values.mastodon.streaming.port -}}
 {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
 apiVersion: networking.k8s.io/v1beta1
 {{- else -}}
@@ -27,15 +28,19 @@ spec:
     {{- end }}
   {{- end }}
   rules:
-    - host: {{ .Values.ingress.hostname | quote }}
+    {{- range .Values.ingress.hosts }}
+    - host: {{ .host | quote }}
       http:
         paths:
-          - path: '/'
+          {{- range .paths }}
+          - path: {{ .path }}
             backend:
               serviceName: {{ $fullName }}-web
-              servicePort: {{ $svcPort }}
-          - path: '/api/v1/streaming'
+              servicePort: {{ $webPort }}
+          - path: {{ .path }}api/v1/streaming
             backend:
               serviceName: {{ $fullName }}-streaming
-              servicePort: {{ .Values.application.streaming.port }}
+              servicePort: {{ $streamingPort }}
+          {{- end }}
+    {{- end }}
 {{- end }}
diff --git a/chart/templates/job-assets-precompile.yaml b/chart/templates/job-assets-precompile.yaml
index 5472e06d6..825a7e916 100644
--- a/chart/templates/job-assets-precompile.yaml
+++ b/chart/templates/job-assets-precompile.yaml
@@ -14,9 +14,10 @@ spec:
       name: {{ include "mastodon.fullname" . }}-assets-precompile
     spec:
       restartPolicy: Never
+      {{- if (not .Values.mastodon.s3.enabled) }}
       # ensure we run on the same node as the other rails components; only
       # required when using PVCs that are ReadWriteOnce
-      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      {{- if or (eq "ReadWriteOnce" .Values.mastodon.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.mastodon.persistence.system.accessMode) }}
       affinity:
         podAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
@@ -35,6 +36,7 @@ spec:
         - name: system
           persistentVolumeClaim:
             claimName: {{ template "mastodon.fullname" . }}-system
+      {{- end }}
       containers:
         - name: {{ include "mastodon.fullname" . }}-assets-precompile
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -53,7 +55,11 @@ spec:
             - name: "DB_PASS"
               valueFrom:
                 secretKeyRef:
+                  {{- if .Values.postgresql.enabled }}
                   name: {{ .Release.Name }}-postgresql
+                  {{- else }}
+                  name: {{ template "mastodon.fullname" . }}
+                  {{- end }}
                   key: postgresql-password
             - name: "REDIS_PASSWORD"
               valueFrom:
@@ -61,9 +67,11 @@ spec:
                   name: {{ .Release.Name }}-redis
                   key: redis-password
             - name: "PORT"
-              value: {{ .Values.application.web.port | quote }}
+              value: {{ .Values.mastodon.web.port | quote }}
+          {{- if (not .Values.mastodon.s3.enabled) }}
           volumeMounts:
             - name: assets
               mountPath: /opt/mastodon/public/assets
             - name: system
               mountPath: /opt/mastodon/public/system
+          {{- end }}
diff --git a/chart/templates/job-chewy-upgrade.yaml b/chart/templates/job-chewy-upgrade.yaml
index 789fcff83..cc68a3385 100644
--- a/chart/templates/job-chewy-upgrade.yaml
+++ b/chart/templates/job-chewy-upgrade.yaml
@@ -15,9 +15,10 @@ spec:
       name: {{ include "mastodon.fullname" . }}-chewy-upgrade
     spec:
       restartPolicy: Never
+      {{- if (not .Values.mastodon.s3.enabled) }}
       # ensure we run on the same node as the other rails components; only
       # required when using PVCs that are ReadWriteOnce
-      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      {{- if or (eq "ReadWriteOnce" .Values.mastodon.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.mastodon.persistence.system.accessMode) }}
       affinity:
         podAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
@@ -36,6 +37,7 @@ spec:
         - name: system
           persistentVolumeClaim:
             claimName: {{ template "mastodon.fullname" . }}-system
+      {{- end }}
       containers:
         - name: {{ include "mastodon.fullname" . }}-chewy-setup
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -54,7 +56,11 @@ spec:
             - name: "DB_PASS"
               valueFrom:
                 secretKeyRef:
+                  {{- if .Values.postgresql.enabled }}
                   name: {{ .Release.Name }}-postgresql
+                  {{- else }}
+                  name: {{ template "mastodon.fullname" . }}
+                  {{- end }}
                   key: postgresql-password
             - name: "REDIS_PASSWORD"
               valueFrom:
@@ -62,10 +68,12 @@ spec:
                   name: {{ .Release.Name }}-redis
                   key: redis-password
             - name: "PORT"
-              value: {{ .Values.application.web.port | quote }}
+              value: {{ .Values.mastodon.web.port | quote }}
+          {{- if (not .Values.mastodon.s3.enabled) }}
           volumeMounts:
             - name: assets
               mountPath: /opt/mastodon/public/assets
             - name: system
               mountPath: /opt/mastodon/public/system
+          {{- end }}
 {{- end }}
diff --git a/chart/templates/job-create-admin.yaml b/chart/templates/job-create-admin.yaml
index 3c5bdd6eb..ffb8bb059 100644
--- a/chart/templates/job-create-admin.yaml
+++ b/chart/templates/job-create-admin.yaml
@@ -1,4 +1,4 @@
-{{- if .Values.createAdmin.enabled }}
+{{- if .Values.mastodon.createAdmin.enabled }}
 apiVersion: batch/v1
 kind: Job
 metadata:
@@ -15,9 +15,10 @@ spec:
       name: {{ include "mastodon.fullname" . }}-create-admin
     spec:
       restartPolicy: Never
+      {{- if (not .Values.mastodon.s3.enabled) }}
       # ensure we run on the same node as the other rails components; only
       # required when using PVCs that are ReadWriteOnce
-      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      {{- if or (eq "ReadWriteOnce" .Values.mastodon.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.mastodon.persistence.system.accessMode) }}
       affinity:
         podAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
@@ -36,6 +37,7 @@ spec:
         - name: system
           persistentVolumeClaim:
             claimName: {{ template "mastodon.fullname" . }}-system
+      {{- end }}
       containers:
         - name: {{ include "mastodon.fullname" . }}-create-admin
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -44,9 +46,9 @@ spec:
             - bin/tootctl
             - accounts
             - create
-            - {{ .Values.createAdmin.username }}
+            - {{ .Values.mastodon.createAdmin.username }}
             - --email
-            - {{ .Values.createAdmin.email }}
+            - {{ .Values.mastodon.createAdmin.email }}
             - --confirmed
             - --role
             - admin
@@ -59,7 +61,11 @@ spec:
             - name: "DB_PASS"
               valueFrom:
                 secretKeyRef:
+                  {{- if .Values.postgresql.enabled }}
                   name: {{ .Release.Name }}-postgresql
+                  {{- else }}
+                  name: {{ template "mastodon.fullname" . }}
+                  {{- end }}
                   key: postgresql-password
             - name: "REDIS_PASSWORD"
               valueFrom:
@@ -67,10 +73,12 @@ spec:
                   name: {{ .Release.Name }}-redis
                   key: redis-password
             - name: "PORT"
-              value: {{ .Values.application.web.port | quote }}
+              value: {{ .Values.mastodon.web.port | quote }}
+          {{- if (not .Values.mastodon.s3.enabled) }}
           volumeMounts:
             - name: assets
               mountPath: /opt/mastodon/public/assets
             - name: system
               mountPath: /opt/mastodon/public/system
+          {{- end }}
 {{- end }}
diff --git a/chart/templates/job-db-migrate.yaml b/chart/templates/job-db-migrate.yaml
index e07832386..72f910e3b 100644
--- a/chart/templates/job-db-migrate.yaml
+++ b/chart/templates/job-db-migrate.yaml
@@ -14,9 +14,10 @@ spec:
       name: {{ include "mastodon.fullname" . }}-db-migrate
     spec:
       restartPolicy: Never
+      {{- if (not .Values.mastodon.s3.enabled) }}
       # ensure we run on the same node as the other rails components; only
       # required when using PVCs that are ReadWriteOnce
-      {{- if or (eq "ReadWriteOnce" .Values.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.persistence.system.accessMode) }}
+      {{- if or (eq "ReadWriteOnce" .Values.mastodon.persistence.assets.accessMode) (eq "ReadWriteOnce" .Values.mastodon.persistence.system.accessMode) }}
       affinity:
         podAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
@@ -35,6 +36,7 @@ spec:
         - name: system
           persistentVolumeClaim:
             claimName: {{ template "mastodon.fullname" . }}-system
+      {{- end }}
       containers:
         - name: {{ include "mastodon.fullname" . }}-db-migrate
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -53,7 +55,11 @@ spec:
             - name: "DB_PASS"
               valueFrom:
                 secretKeyRef:
+                  {{- if .Values.postgresql.enabled }}
                   name: {{ .Release.Name }}-postgresql
+                  {{- else }}
+                  name: {{ template "mastodon.fullname" . }}
+                  {{- end }}
                   key: postgresql-password
             - name: "REDIS_PASSWORD"
               valueFrom:
@@ -61,9 +67,11 @@ spec:
                   name: {{ .Release.Name }}-redis
                   key: redis-password
             - name: "PORT"
-              value: {{ .Values.application.web.port | quote }}
+              value: {{ .Values.mastodon.web.port | quote }}
+          {{- if (not .Values.mastodon.s3.enabled) }}
           volumeMounts:
             - name: assets
               mountPath: /opt/mastodon/public/assets
             - name: system
               mountPath: /opt/mastodon/public/system
+          {{- end }}
diff --git a/chart/templates/pvc-assets.yaml b/chart/templates/pvc-assets.yaml
index 5c5315100..58b2179df 100644
--- a/chart/templates/pvc-assets.yaml
+++ b/chart/templates/pvc-assets.yaml
@@ -1,4 +1,4 @@
----
+{{- if (not .Values.mastodon.s3.enabled) }}
 apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
@@ -7,7 +7,8 @@ metadata:
     {{- include "mastodon.labels" . | nindent 4 }}
 spec:
   accessModes:
-    - {{ .Values.persistence.system.accessMode }}
+    - {{ .Values.mastodon.persistence.system.accessMode }}
   resources:
-    {{- toYaml .Values.persistence.assets.resources | nindent 4}}
-  storageClassName: {{ .Values.persistence.assets.storageClassName }}
+    {{- toYaml .Values.mastodon.persistence.assets.resources | nindent 4}}
+  storageClassName: {{ .Values.mastodon.persistence.assets.storageClassName }}
+{{- end }}
diff --git a/chart/templates/pvc-system.yaml b/chart/templates/pvc-system.yaml
index 028551151..52398f0da 100644
--- a/chart/templates/pvc-system.yaml
+++ b/chart/templates/pvc-system.yaml
@@ -1,4 +1,4 @@
----
+{{- if (not .Values.mastodon.s3.enabled) }}
 apiVersion: v1
 kind: PersistentVolumeClaim
 metadata:
@@ -7,7 +7,8 @@ metadata:
     {{- include "mastodon.labels" . | nindent 4 }}
 spec:
   accessModes:
-    - {{ .Values.persistence.system.accessMode }}
+    - {{ .Values.mastodon.persistence.system.accessMode }}
   resources:
-    {{- toYaml .Values.persistence.system.resources | nindent 4}}
-  storageClassName: {{ .Values.persistence.system.storageClassName }}
+    {{- toYaml .Values.mastodon.persistence.system.resources | nindent 4}}
+  storageClassName: {{ .Values.mastodon.persistence.system.storageClassName }}
+{{- end }}
diff --git a/chart/templates/secrets.yaml b/chart/templates/secrets.yaml
index 74f4b1516..0452a8ae1 100644
--- a/chart/templates/secrets.yaml
+++ b/chart/templates/secrets.yaml
@@ -6,23 +6,30 @@ metadata:
     {{- include "mastodon.labels" . | nindent 4 }}
 type: Opaque
 data:
-  {{- if not (empty .Values.secrets.secret_key_base) }}
-  SECRET_KEY_BASE: "{{ .Values.secrets.secret_key_base | b64enc }}"
+  {{- if .Values.mastodon.s3.enabled }}
+  AWS_ACCESS_KEY_ID: "{{ .Values.mastodon.s3.access_key | b64enc }}"
+  AWS_SECRET_ACCESS_KEY: "{{ .Values.mastodon.s3.access_secret | b64enc }}"
+  {{- end }}
+  {{- if not (empty .Values.mastodon.secrets.secret_key_base) }}
+  SECRET_KEY_BASE: "{{ .Values.mastodon.secrets.secret_key_base | b64enc }}"
   {{- else }}
-  SECRET_KEY_BASE: {{ required "secret_key_base is required" .Values.secrets.secret_key_base }}
+  SECRET_KEY_BASE: {{ required "secret_key_base is required" .Values.mastodon.secrets.secret_key_base }}
   {{- end }}
-  {{- if not (empty .Values.secrets.otp_secret) }}
-  OTP_SECRET: "{{ .Values.secrets.otp_secret | b64enc }}"
+  {{- if not (empty .Values.mastodon.secrets.otp_secret) }}
+  OTP_SECRET: "{{ .Values.mastodon.secrets.otp_secret | b64enc }}"
   {{- else }}
-  OTP_SECRET: {{ required "otp_secret is required" .Values.secrets.otp_secret }}
+  OTP_SECRET: {{ required "otp_secret is required" .Values.mastodon.secrets.otp_secret }}
   {{- end }}
-  {{- if not (empty .Values.secrets.vapid.private_key) }}
-  VAPID_PRIVATE_KEY: "{{ .Values.secrets.vapid.private_key | b64enc }}"
+  {{- if not (empty .Values.mastodon.secrets.vapid.private_key) }}
+  VAPID_PRIVATE_KEY: "{{ .Values.mastodon.secrets.vapid.private_key | b64enc }}"
   {{- else }}
-  VAPID_PRIVATE_KEY: {{ required "vapid.private_key is required" .Values.secrets.vapid.private_key }}
+  VAPID_PRIVATE_KEY: {{ required "vapid.private_key is required" .Values.mastodon.secrets.vapid.private_key }}
   {{- end }}
-  {{- if not (empty .Values.secrets.vapid.public_key) }}
-  VAPID_PUBLIC_KEY: "{{ .Values.secrets.vapid.public_key | b64enc }}"
+  {{- if not (empty .Values.mastodon.secrets.vapid.public_key) }}
+  VAPID_PUBLIC_KEY: "{{ .Values.mastodon.secrets.vapid.public_key | b64enc }}"
   {{- else }}
-  VAPID_PUBLIC_KEY: {{ required "vapid.public_key is required" .Values.secrets.vapid.public_key }}
+  VAPID_PUBLIC_KEY: {{ required "vapid.public_key is required" .Values.mastodon.secrets.vapid.public_key }}
+  {{- end }}
+  {{- if not .Values.postgresql.enabled }}
+  postgresql-password: "{{ .Values.postgresql.postgresqlPassword | b64enc }}"
   {{- end }}
diff --git a/chart/templates/service-streaming.yaml b/chart/templates/service-streaming.yaml
index ff5dc13ea..a005e617c 100644
--- a/chart/templates/service-streaming.yaml
+++ b/chart/templates/service-streaming.yaml
@@ -7,7 +7,7 @@ metadata:
 spec:
   type: {{ .Values.service.type }}
   ports:
-    - port: {{ .Values.application.streaming.port }}
+    - port: {{ .Values.mastodon.streaming.port }}
       targetPort: streaming
       protocol: TCP
       name: streaming
diff --git a/chart/templates/service-web.yaml b/chart/templates/service-web.yaml
index e0df35b25..3563fde70 100644
--- a/chart/templates/service-web.yaml
+++ b/chart/templates/service-web.yaml
@@ -7,7 +7,7 @@ metadata:
 spec:
   type: {{ .Values.service.type }}
   ports:
-    - port: {{ .Values.service.port }}
+    - port: {{ .Values.mastodon.web.port }}
       targetPort: http
       protocol: TCP
       name: http
diff --git a/chart/values.yaml.template b/chart/values.yaml
index 9e50c6dac..a638f4a7d 100644
--- a/chart/values.yaml.template
+++ b/chart/values.yaml
@@ -2,16 +2,87 @@ replicaCount: 1
 
 image:
   repository: tootsuite/mastodon
-  pullPolicy: Always
   # https://hub.docker.com/r/tootsuite/mastodon/tags
-  tag: v3.3.0
+  #
   # alternatively, use `latest` for the latest release or `edge` for the image
   # built from the most recent commit
   #
   # tag: latest
+  tag: v3.3.0
+  # use `Always` when using `latest` tag
+  pullPolicy: IfNotPresent
+
+mastodon:
+  # create an initial administrator user; the password is autogenerated and will
+  # have to be reset
+  createAdmin:
+    enabled: false
+    username: not_gargron
+    email: not@example.com
+  cron:
+    # run `tootctl media remove` every week
+    removeMedia:
+      enabled: true
+      schedule: "0 0 * * 0"
+  # available locales: https://github.com/tootsuite/mastodon/blob/master/config/application.rb#L43
+  locale: en
+  local_domain: mastodon.local
+  persistence:
+    assets:
+      # ReadWriteOnce is more widely supported than ReadWriteMany, but limits
+      # scalability, since it requires the Rails and Sidekiq pods to run on the
+      # same node.
+      accessMode: ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+    system:
+      accessMode: ReadWriteOnce
+      resources:
+        requests:
+          storage: 100Gi
+  s3:
+    enabled: false
+    access_key: ""
+    access_secret: ""
+    bucket: ""
+    endpoint: https://us-east-1.linodeobjects.com
+    hostname: us-east-1.linodeobjects.com
+    region: ""
+  # these must be set manually; autogenerated keys are rotated on each upgrade
+  secrets:
+    secret_key_base: ""
+    otp_secret: ""
+    vapid:
+      private_key: ""
+      public_key: ""
+  sidekiq:
+    concurrency: 25
+  smtp:
+    auth_method: plain
+    ca_file:
+    delivery_method: smtp
+    domain:
+    enable_starttls_auto: true
+    from_address: notifications@example.com
+    login:
+    openssl_verify_mode: peer
+    password:
+    port: 587
+    reply_to:
+    server: smtp.mailgun.org
+    tls: false
+  streaming:
+    port: 4000
+    # this should be set manually since os.cpus() returns the number of CPUs on
+    # the node running the pod, which is unrelated to the resources allocated to
+    # the pod by k8s
+    workers: 1
+  web:
+    port: 3000
 
 ingress:
-  enabled: false
+  enabled: true
   annotations:
     kubernetes.io/ingress.class: nginx
     kubernetes.io/tls-acme: "true"
@@ -22,64 +93,15 @@ ingress:
     # nginx.ingress.kubernetes.io/proxy-body-size: 40m
     #   for the NGINX ingress controller:
     # nginx.org/client-max-body-size: 40m
-  # this value is used for LOCAL_DOMAIN
-  hostname: mastodon.local
+  hosts:
+    - host: mastodon.local
+      paths:
+        - path: '/'
   tls:
     - secretName: mastodon-tls
       hosts:
         - mastodon.local
 
-# create an initial administrator user; the password is autogenerated and will
-# have to be reset
-createAdmin:
-  enabled: false
-  username: not_gargron
-  email: not@example.com
-
-# available locales: https://github.com/tootsuite/mastodon/blob/master/config/application.rb#L43
-locale: en
-
-cron:
-  # run `tootctl media remove` every week
-  removeMedia:
-    enabled: true
-    schedule: "0 0 * * 0"
-
-application:
-  web:
-    port: 3000
-  streaming:
-    port: 4000
-    # this should be set manually since os.cpus() returns the number of CPUs on
-    # the node running the pod, which is unrelated to the resources allocated to
-    # the pod by k8s
-    workers: 1
-  sidekiq:
-    concurrency: 25
-
-# these must be set manually; autogenerated keys are rotated on each upgrade
-secrets:
-  secret_key_base: ""
-  otp_secret: ""
-  vapid:
-    private_key: ""
-    public_key: ""
-
-smtp:
-  auth_method: plain
-  ca_file:
-  delivery_method: smtp
-  domain:
-  enable_starttls_auto: true
-  from_address: notifications@example.com
-  login:
-  openssl_verify_mode: peer
-  password:
-  port: 587
-  reply_to:
-  server: smtp.mailgun.org
-  tls: false
-
 # https://github.com/bitnami/charts/tree/master/bitnami/elasticsearch#parameters
 elasticsearch:
   # `false` will disable full-text search
@@ -95,6 +117,10 @@ elasticsearch:
 
 # https://github.com/bitnami/charts/tree/master/bitnami/postgresql#parameters
 postgresql:
+  # disable if you want to use an existing db; in which case the values below
+  # must match those of that external postgres instance
+  enabled: true
+  # postgresqlHostname: preexisting-postgresql
   postgresqlDatabase: mastodon_production
   # you must set a password; the password generated by the postgresql chart will
   # be rotated on each upgrade:
@@ -108,21 +134,6 @@ redis:
   # rotated on each upgrade:
   password: ""
 
-persistence:
-  assets:
-    # ReadWriteOnce is more widely supported than ReadWriteMany, but limits
-    # scalability, since it requires the Rails and Sidekiq pods to run on the
-    # same node.
-    accessMode: ReadWriteOnce
-    resources:
-      requests:
-        storage: 10Gi
-  system:
-    accessMode: ReadWriteOnce
-    resources:
-      requests:
-        storage: 100Gi
-
 service:
   type: ClusterIP
   port: 80
diff --git a/config/initializers/pagination.rb b/config/initializers/pagination.rb
deleted file mode 100644
index e69de29bb..000000000
--- a/config/initializers/pagination.rb
+++ /dev/null
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8245397d7..0c38c5ae1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -26,6 +26,8 @@ en:
       It is used for federation purposes and should not be blocked unless you want to block the whole instance, in which case you should use a domain block.
     learn_more: Learn more
     privacy_policy: Privacy policy
+    rules: Server rules
+    rules_html: 'Below is a summary of rules you need to follow if you want to have an account on this server of Mastodon:'
     see_whats_happening: See what's happening
     server_stats: 'Server stats:'
     source_code: Source code
@@ -542,6 +544,11 @@ en:
       unassign: Unassign
       unresolved: Unresolved
       updated_at: Updated
+    rules:
+      add_new: Add rule
+      description: While most claim to have read and agree to the terms of service, usually people do not read through until after a problem arises. Make it easier to see your server's rules at a glance by providing them in a flat bullet point list. Try to keep individual rules short and simple, but try not to split them up into many separate items either.
+      edit: Edit rule
+      title: Server rules
     settings:
       activity_api_enabled:
         desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 20c916560..4b5ff7ae8 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -73,6 +73,8 @@ en:
           no_access: Block access to all resources
           sign_up_requires_approval: New sign-ups will require your approval
         severity: Choose what will happen with requests from this IP
+      rule:
+        text: Describe a rule or requirement for users on this server. Try to keep it short and simple
       sessions:
         otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:'
         webauthn: If it's an USB key be sure to insert it and, if necessary, tap it.
@@ -197,6 +199,8 @@ en:
         reblog: Someone boosted your status
         report: New report is submitted
         trending_tag: An unreviewed hashtag is trending
+      rule:
+        text: Rule
       tag:
         listable: Allow this hashtag to appear in searches and on the profile directory
         name: Hashtag
diff --git a/config/navigation.rb b/config/navigation.rb
index be429cfc4..0bb3189c1 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -53,6 +53,7 @@ SimpleNavigation::Configuration.run do |navigation|
     n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s|
       s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url
       s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings}
+      s.item :rules, safe_join([fa_icon('gavel fw'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules}
       s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}
       s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
       s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays}
diff --git a/config/routes.rb b/config/routes.rb
index b3eef3364..191229ed8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -222,6 +222,7 @@ Rails.application.routes.draw do
     end
 
     resources :instances, only: [:index, :show], constraints: { id: /[^\/]+/ }
+    resources :rules
 
     resources :reports, only: [:index, :show] do
       member do
@@ -408,6 +409,7 @@ Rails.application.routes.draw do
       resource :instance, only: [:show] do
         resources :peers, only: [:index], controller: 'instances/peers'
         resource :activity, only: [:show], controller: 'instances/activity'
+        resources :rules, only: [:index], controller: 'instances/rules'
       end
 
       resource :domain_blocks, only: [:show, :create, :destroy]
@@ -435,6 +437,7 @@ Rails.application.routes.draw do
         get :verify_credentials, to: 'credentials#show'
         patch :update_credentials, to: 'credentials#update'
         resource :search, only: :show, controller: :search
+        resource :lookup, only: :show, controller: :lookup
         resources :relationships, only: :index
       end
 
diff --git a/db/migrate/20210221045109_create_rules.rb b/db/migrate/20210221045109_create_rules.rb
new file mode 100644
index 000000000..abe2fd42a
--- /dev/null
+++ b/db/migrate/20210221045109_create_rules.rb
@@ -0,0 +1,11 @@
+class CreateRules < ActiveRecord::Migration[5.2]
+  def change
+    create_table :rules do |t|
+      t.integer :priority, null: false, default: 0
+      t.datetime :deleted_at
+      t.text :text, null: false, default: ''
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a76e34e95..6f19268e1 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2020_12_18_054746) do
+ActiveRecord::Schema.define(version: 2021_02_21_045109) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -723,6 +723,14 @@ ActiveRecord::Schema.define(version: 2020_12_18_054746) do
     t.index ["target_account_id"], name: "index_reports_on_target_account_id"
   end
 
+  create_table "rules", force: :cascade do |t|
+    t.integer "priority", default: 0, null: false
+    t.datetime "deleted_at"
+    t.text "text", default: "", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+  end
+
   create_table "scheduled_statuses", force: :cascade do |t|
     t.bigint "account_id"
     t.datetime "scheduled_at"
diff --git a/lib/action_dispatch/cookie_jar_extensions.rb b/lib/action_dispatch/cookie_jar_extensions.rb
index 44c39c1f8..1be9053ba 100644
--- a/lib/action_dispatch/cookie_jar_extensions.rb
+++ b/lib/action_dispatch/cookie_jar_extensions.rb
@@ -7,9 +7,19 @@ module ActionDispatch
     # Monkey-patch ActionDispatch to serve secure cookies to Tor Hidden Service
     # users. Otherwise, ActionDispatch would drop the cookie over HTTP.
     def write_cookie?(*)
-      request.headers['Host'].ends_with?('.onion') || super
+      request.host.end_with?('.onion') || super
     end
   end
 end
 
 ActionDispatch::Cookies::CookieJar.prepend(ActionDispatch::CookieJarExtensions)
+
+module Rack
+  module SessionPersistedExtensions
+    def security_matches?(request, options)
+      request.host.end_with?('.onion') || super
+    end
+  end
+end
+
+Rack::Session::Abstract::Persisted.prepend(Rack::SessionPersistedExtensions)
diff --git a/package.json b/package.json
index a3f85500e..e81844964 100644
--- a/package.json
+++ b/package.json
@@ -60,18 +60,18 @@
   },
   "private": true,
   "dependencies": {
-    "@babel/core": "^7.12.16",
+    "@babel/core": "^7.12.17",
     "@babel/plugin-proposal-class-properties": "^7.8.3",
     "@babel/plugin-proposal-decorators": "^7.12.13",
     "@babel/plugin-transform-react-inline-elements": "^7.12.13",
-    "@babel/plugin-transform-runtime": "^7.12.15",
-    "@babel/preset-env": "^7.12.16",
+    "@babel/plugin-transform-runtime": "^7.12.17",
+    "@babel/preset-env": "^7.12.17",
     "@babel/preset-react": "^7.12.13",
-    "@babel/runtime": "^7.12.13",
+    "@babel/runtime": "^7.12.18",
     "@clusterws/cws": "^3.0.0",
     "@gamestdio/websocket": "^0.3.2",
     "@github/webauthn-json": "^0.5.7",
-    "@rails/ujs": "^6.1.2",
+    "@rails/ujs": "^6.1.3",
     "array-includes": "^3.1.2",
     "atrament": "0.2.4",
     "arrow-key-navigation": "^1.2.0",
@@ -90,7 +90,7 @@
     "cross-env": "^7.0.3",
     "css-loader": "^5.0.2",
     "cssnano": "^4.1.10",
-    "detect-passive-events": "^2.0.2",
+    "detect-passive-events": "^2.0.3",
     "dotenv": "^8.2.0",
     "emoji-mart": "Gargron/emoji-mart#build",
     "es6-symbol": "^3.1.3",
@@ -114,7 +114,7 @@
     "lodash": "^4.17.19",
     "mark-loader": "^0.1.6",
     "marky": "^1.2.1",
-    "mini-css-extract-plugin": "^1.3.6",
+    "mini-css-extract-plugin": "^1.3.8",
     "mkdirp": "^1.0.4",
     "npmlog": "^4.1.2",
     "object-assign": "^4.1.1",
@@ -156,7 +156,7 @@
     "requestidlecallback": "^0.3.0",
     "reselect": "^4.0.0",
     "rimraf": "^3.0.2",
-    "sass": "^1.32.7",
+    "sass": "^1.32.8",
     "sass-loader": "^10.1.1",
     "stacktrace-js": "^2.0.2",
     "stringz": "^2.1.0",
@@ -171,14 +171,14 @@
     "webpack-bundle-analyzer": "^4.4.0",
     "webpack-cli": "^3.3.12",
     "webpack-merge": "^5.7.3",
-    "wicg-inert": "^3.1.0"
+    "wicg-inert": "^3.1.1"
   },
   "devDependencies": {
     "@testing-library/jest-dom": "^5.11.9",
     "@testing-library/react": "^11.2.5",
     "babel-eslint": "^10.1.0",
     "babel-jest": "^26.6.3",
-    "eslint": "^7.19.0",
+    "eslint": "^7.20.0",
     "eslint-plugin-import": "~2.22.1",
     "eslint-plugin-jsx-a11y": "~6.4.1",
     "eslint-plugin-promise": "~4.3.1",
diff --git a/spec/fabricators/rule_fabricator.rb b/spec/fabricators/rule_fabricator.rb
new file mode 100644
index 000000000..4bdfd05e0
--- /dev/null
+++ b/spec/fabricators/rule_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:rule) do
+  priority   ""
+  deleted_at "2021-02-21 05:51:09"
+  text       "MyText"
+end
\ No newline at end of file
diff --git a/spec/models/rule_spec.rb b/spec/models/rule_spec.rb
new file mode 100644
index 000000000..8666bda71
--- /dev/null
+++ b/spec/models/rule_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Rule, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/yarn.lock b/yarn.lock
index e221ab3cd..f30132abc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,13 @@
 # yarn lockfile v1
 
 
+"@babel/code-frame@7.12.11":
+  version "7.12.11"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
+  integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
+  dependencies:
+    "@babel/highlight" "^7.10.4"
+
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13":
   version "7.12.13"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
@@ -14,19 +21,19 @@
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.12.13.tgz#27e19e0ed3726ccf54067ced4109501765e7e2e8"
   integrity sha512-U/hshG5R+SIoW7HVWIdmy1cB7s3ki+r3FpyEZiCgpi4tFgPnX/vynY80ZGSASOIrUM6O7VxOgCZgdt7h97bUGg==
 
-"@babel/core@^7.1.0", "@babel/core@^7.12.16", "@babel/core@^7.7.2", "@babel/core@^7.7.5":
-  version "7.12.16"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.16.tgz#8c6ba456b23b680a6493ddcfcd9d3c3ad51cab7c"
-  integrity sha512-t/hHIB504wWceOeaOoONOhu+gX+hpjfeN6YRBT209X/4sibZQfSF1I0HFRRlBe97UZZosGx5XwUg1ZgNbelmNw==
+"@babel/core@^7.1.0", "@babel/core@^7.12.17", "@babel/core@^7.7.2", "@babel/core@^7.7.5":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.17.tgz#993c5e893333107a2815d8e0d73a2c3755e280b2"
+  integrity sha512-V3CuX1aBywbJvV2yzJScRxeiiw0v2KZZYYE3giywxzFJL13RiyPjaaDwhDnxmgFTTS7FgvM2ijr4QmKNIu0AtQ==
   dependencies:
     "@babel/code-frame" "^7.12.13"
-    "@babel/generator" "^7.12.15"
-    "@babel/helper-module-transforms" "^7.12.13"
-    "@babel/helpers" "^7.12.13"
-    "@babel/parser" "^7.12.16"
+    "@babel/generator" "^7.12.17"
+    "@babel/helper-module-transforms" "^7.12.17"
+    "@babel/helpers" "^7.12.17"
+    "@babel/parser" "^7.12.17"
     "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.12.13"
-    "@babel/types" "^7.12.13"
+    "@babel/traverse" "^7.12.17"
+    "@babel/types" "^7.12.17"
     convert-source-map "^1.7.0"
     debug "^4.1.0"
     gensync "^1.0.0-beta.1"
@@ -35,12 +42,12 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.12.13", "@babel/generator@^7.12.15":
-  version "7.12.15"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.15.tgz#4617b5d0b25cc572474cc1aafee1edeaf9b5368f"
-  integrity sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==
+"@babel/generator@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.17.tgz#9ef1dd792d778b32284411df63f4f668a9957287"
+  integrity sha512-DSA7ruZrY4WI8VxuS1jWSRezFnghEoYEFrZcw9BizQRmOZiUsiHl59+qEARGPqPikwA/GPTyRCi7isuCK/oyqg==
   dependencies:
-    "@babel/types" "^7.12.13"
+    "@babel/types" "^7.12.17"
     jsesc "^2.5.1"
     source-map "^0.5.0"
 
@@ -74,13 +81,13 @@
     "@babel/helper-annotate-as-pure" "^7.12.13"
     "@babel/types" "^7.12.13"
 
-"@babel/helper-compilation-targets@^7.12.16":
-  version "7.12.16"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.16.tgz#6905238b4a5e02ba2d032c1a49dd1820fe8ce61b"
-  integrity sha512-dBHNEEaZx7F3KoUYqagIhRIeqyyuI65xMndMZ3WwGwEBI609I4TleYQHcrS627vbKyNTXqShoN+fvYD9HuQxAg==
+"@babel/helper-compilation-targets@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.17.tgz#91d83fae61ef390d39c3f0507cb83979bab837c7"
+  integrity sha512-5EkibqLVYOuZ89BSg2lv+GG8feywLuvMXNYgf0Im4MssE0mFWPztSpJbildNnUgw0bLI2EsIN4MpSHC2iUJkQA==
   dependencies:
     "@babel/compat-data" "^7.12.13"
-    "@babel/helper-validator-option" "^7.12.16"
+    "@babel/helper-validator-option" "^7.12.17"
     browserslist "^4.14.5"
     semver "^5.5.0"
 
@@ -147,10 +154,10 @@
   dependencies:
     "@babel/types" "^7.12.13"
 
-"@babel/helper-module-transforms@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz#01afb052dcad2044289b7b20beb3fa8bd0265bea"
-  integrity sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA==
+"@babel/helper-module-transforms@^7.12.13", "@babel/helper-module-transforms@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.17.tgz#7c75b987d6dfd5b48e575648f81eaac891539509"
+  integrity sha512-sFL+p6zOCQMm9vilo06M4VHuTxUAwa6IxgL56Tq1DVtA0ziAGTH1ThmJq7xwPqdQlgAbKX3fb0oZNbtRIyA5KQ==
   dependencies:
     "@babel/helper-module-imports" "^7.12.13"
     "@babel/helper-replace-supers" "^7.12.13"
@@ -158,8 +165,8 @@
     "@babel/helper-split-export-declaration" "^7.12.13"
     "@babel/helper-validator-identifier" "^7.12.11"
     "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.12.13"
-    "@babel/types" "^7.12.13"
+    "@babel/traverse" "^7.12.17"
+    "@babel/types" "^7.12.17"
     lodash "^4.17.19"
 
 "@babel/helper-optimise-call-expression@^7.12.13":
@@ -219,10 +226,10 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
   integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
 
-"@babel/helper-validator-option@^7.12.16":
-  version "7.12.16"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.16.tgz#f73cbd3bbba51915216c5dea908e9b206bb10051"
-  integrity sha512-uCgsDBPUQDvzr11ePPo4TVEocxj8RXjUVSC/Y8N1YpVAI/XDdUwGJu78xmlGhTxj2ntaWM7n9LQdRtyhOzT2YQ==
+"@babel/helper-validator-option@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831"
+  integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==
 
 "@babel/helper-wrap-function@^7.12.13":
   version "7.12.13"
@@ -234,16 +241,16 @@
     "@babel/traverse" "^7.12.13"
     "@babel/types" "^7.12.13"
 
-"@babel/helpers@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.13.tgz#3c75e993632e4dadc0274eae219c73eb7645ba47"
-  integrity sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==
+"@babel/helpers@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.17.tgz#71e03d2981a6b5ee16899964f4101dc8471d60bc"
+  integrity sha512-tEpjqSBGt/SFEsFikKds1sLNChKKGGR17flIgQKXH4fG6m9gTgl3gnOC1giHNyaBCSKuTfxaSzHi7UnvqiVKxg==
   dependencies:
     "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.12.13"
-    "@babel/types" "^7.12.13"
+    "@babel/traverse" "^7.12.17"
+    "@babel/types" "^7.12.17"
 
-"@babel/highlight@^7.12.13":
+"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
   version "7.12.13"
   resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c"
   integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==
@@ -252,10 +259,10 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.12.16", "@babel/parser@^7.7.0":
-  version "7.12.16"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.16.tgz#cc31257419d2c3189d394081635703f549fc1ed4"
-  integrity sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==
+"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.12.17", "@babel/parser@^7.7.0":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.17.tgz#bc85d2d47db38094e5bb268fc761716e7d693848"
+  integrity sha512-r1yKkiUTYMQ8LiEI0UcQx5ETw5dpTLn9wijn9hk6KkTtOK95FndDN10M+8/s6k/Ymlbivw0Av9q4SlgF80PtHg==
 
 "@babel/plugin-proposal-async-generator-functions@^7.12.13":
   version "7.12.13"
@@ -283,10 +290,10 @@
     "@babel/helper-plugin-utils" "^7.12.13"
     "@babel/plugin-syntax-decorators" "^7.12.13"
 
-"@babel/plugin-proposal-dynamic-import@^7.12.16":
-  version "7.12.16"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.16.tgz#b9f33b252e3406d492a15a799c9d45a9a9613473"
-  integrity sha512-yiDkYFapVxNOCcBfLnsb/qdsliroM+vc3LHiZwS4gh7pFjo5Xq3BDhYBNn3H3ao+hWPvqeeTdU+s+FIvokov+w==
+"@babel/plugin-proposal-dynamic-import@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.17.tgz#e0ebd8db65acc37eac518fa17bead2174e224512"
+  integrity sha512-ZNGoFZqrnuy9H2izB2jLlnNDAfVPlGl5NhFEiFe4D84ix9GQGygF+CWMGHKuE+bpyS/AOuDQCnkiRNqW2IzS1Q==
   dependencies:
     "@babel/helper-plugin-utils" "^7.12.13"
     "@babel/plugin-syntax-dynamic-import" "^7.8.0"
@@ -348,10 +355,10 @@
     "@babel/helper-plugin-utils" "^7.12.13"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
 
-"@babel/plugin-proposal-optional-chaining@^7.12.16":
-  version "7.12.16"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.16.tgz#600c7531f754186b0f2096e495a92da7d88aa139"
-  integrity sha512-O3ohPwOhkwji5Mckb7F/PJpJVJY3DpPsrt/F0Bk40+QMk9QpAIqeGusHWqu/mYqsM8oBa6TziL/2mbERWsUZjg==
+"@babel/plugin-proposal-optional-chaining@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.17.tgz#e382becadc2cb16b7913b6c672d92e4b33385b5c"
+  integrity sha512-TvxwI80pWftrGPKHNfkvX/HnoeSTR7gC4ezWnAL39PuktYUe6r8kEpOLTYnkBTsaoeazXm2jHJ22EQ81sdgfcA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.12.13"
     "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
@@ -723,10 +730,10 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-runtime@^7.12.15":
-  version "7.12.15"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.15.tgz#4337b2507288007c2b197059301aa0af8d90c085"
-  integrity sha512-OwptMSRnRWJo+tJ9v9wgAf72ydXWfYSXWhnQjZing8nGZSDFqU1MBleKM3+DriKkcbv7RagA8gVeB0A1PNlNow==
+"@babel/plugin-transform-runtime@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.17.tgz#329cb61d293b7e60a7685b91dda7c300668cee18"
+  integrity sha512-s+kIJxnaTj+E9Q3XxQZ5jOo+xcogSe3V78/iFQ5RmoT0jROdpcdxhfGdq/VLqW1hFSzw6VjqN8aQqTaAMixWsw==
   dependencies:
     "@babel/helper-module-imports" "^7.12.13"
     "@babel/helper-plugin-utils" "^7.12.13"
@@ -783,19 +790,19 @@
     "@babel/helper-create-regexp-features-plugin" "^7.12.13"
     "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/preset-env@^7.12.16":
-  version "7.12.16"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.16.tgz#16710e3490e37764b2f41886de0a33bc4ae91082"
-  integrity sha512-BXCAXy8RE/TzX416pD2hsVdkWo0G+tYd16pwnRV4Sc0fRwTLRS/Ssv8G5RLXUGQv7g4FG7TXkdDJxCjQ5I+Zjg==
+"@babel/preset-env@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.12.17.tgz#94a3793ff089c32ee74d76a3c03a7597693ebaaa"
+  integrity sha512-9PMijx8zFbCwTHrd2P4PJR5nWGH3zWebx2OcpTjqQrHhCiL2ssSR2Sc9ko2BsI2VmVBfoaQmPrlMTCui4LmXQg==
   dependencies:
     "@babel/compat-data" "^7.12.13"
-    "@babel/helper-compilation-targets" "^7.12.16"
+    "@babel/helper-compilation-targets" "^7.12.17"
     "@babel/helper-module-imports" "^7.12.13"
     "@babel/helper-plugin-utils" "^7.12.13"
-    "@babel/helper-validator-option" "^7.12.16"
+    "@babel/helper-validator-option" "^7.12.17"
     "@babel/plugin-proposal-async-generator-functions" "^7.12.13"
     "@babel/plugin-proposal-class-properties" "^7.12.13"
-    "@babel/plugin-proposal-dynamic-import" "^7.12.16"
+    "@babel/plugin-proposal-dynamic-import" "^7.12.17"
     "@babel/plugin-proposal-export-namespace-from" "^7.12.13"
     "@babel/plugin-proposal-json-strings" "^7.12.13"
     "@babel/plugin-proposal-logical-assignment-operators" "^7.12.13"
@@ -803,7 +810,7 @@
     "@babel/plugin-proposal-numeric-separator" "^7.12.13"
     "@babel/plugin-proposal-object-rest-spread" "^7.12.13"
     "@babel/plugin-proposal-optional-catch-binding" "^7.12.13"
-    "@babel/plugin-proposal-optional-chaining" "^7.12.16"
+    "@babel/plugin-proposal-optional-chaining" "^7.12.17"
     "@babel/plugin-proposal-private-methods" "^7.12.13"
     "@babel/plugin-proposal-unicode-property-regex" "^7.12.13"
     "@babel/plugin-syntax-async-generators" "^7.8.0"
@@ -851,7 +858,7 @@
     "@babel/plugin-transform-unicode-escapes" "^7.12.13"
     "@babel/plugin-transform-unicode-regex" "^7.12.13"
     "@babel/preset-modules" "^0.1.3"
-    "@babel/types" "^7.12.13"
+    "@babel/types" "^7.12.17"
     core-js-compat "^3.8.0"
     semver "^5.5.0"
 
@@ -892,10 +899,10 @@
   dependencies:
     regenerator-runtime "^0.12.0"
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
-  integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.18", "@babel/runtime@^7.12.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+  version "7.12.18"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.18.tgz#af137bd7e7d9705a412b3caaf991fe6aaa97831b"
+  integrity sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg==
   dependencies:
     regenerator-runtime "^0.13.4"
 
@@ -908,25 +915,25 @@
     "@babel/parser" "^7.12.13"
     "@babel/types" "^7.12.13"
 
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.13", "@babel/traverse@^7.7.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.13.tgz#689f0e4b4c08587ad26622832632735fb8c4e0c0"
-  integrity sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.13", "@babel/traverse@^7.12.17", "@babel/traverse@^7.7.0":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.17.tgz#40ec8c7ffb502c4e54c7f95492dc11b88d718619"
+  integrity sha512-LGkTqDqdiwC6Q7fWSwQoas/oyiEYw6Hqjve5KOSykXkmFJFqzvGMb9niaUEag3Rlve492Mkye3gLw9FTv94fdQ==
   dependencies:
     "@babel/code-frame" "^7.12.13"
-    "@babel/generator" "^7.12.13"
+    "@babel/generator" "^7.12.17"
     "@babel/helper-function-name" "^7.12.13"
     "@babel/helper-split-export-declaration" "^7.12.13"
-    "@babel/parser" "^7.12.13"
-    "@babel/types" "^7.12.13"
+    "@babel/parser" "^7.12.17"
+    "@babel/types" "^7.12.17"
     debug "^4.1.0"
     globals "^11.1.0"
     lodash "^4.17.19"
 
-"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.10.4", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.13.tgz#8be1aa8f2c876da11a9cf650c0ecf656913ad611"
-  integrity sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==
+"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.10.4", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.17", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.17.tgz#9d711eb807e0934c90b8b1ca0eb1f7230d150963"
+  integrity sha512-tNMDjcv/4DIcHxErTgwB9q2ZcYyN0sUfgGKUK/mm1FJK7Wz+KstoEekxrl/tBiNDgLK1HGi+sppj1An/1DR4fQ==
   dependencies:
     "@babel/helper-validator-identifier" "^7.12.11"
     lodash "^4.17.19"
@@ -1279,10 +1286,10 @@
   resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71"
   integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==
 
-"@rails/ujs@^6.1.2":
-  version "6.1.2"
-  resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.2.tgz#ccafb8d8e7ae3bfd21824b018be9f1c417bef5b4"
-  integrity sha512-WEXne46Srl3CegO4ngPSWLL7UYWoL1N78254LSzWJgE2mr7XZlbX8S0vltLHDYj5YRFCYEslsBMPisVb59tpYA==
+"@rails/ujs@^6.1.3":
+  version "6.1.3"
+  resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.3.tgz#90ef26caa0925492b1a3b1495db09cfbe49e745e"
+  integrity sha512-9mip5o+LVouWAqLMNJWhxda+D5uP+4RziNECgOGJlL6k3rc5SC/ljCHpV9Cym4i3oeGZkpZJ2tu4frCwt84kzQ==
 
 "@sinonjs/commons@^1.7.0":
   version "1.8.1"
@@ -3647,6 +3654,11 @@ detect-file@^1.0.0:
   resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
   integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
 
+detect-it@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/detect-it/-/detect-it-4.0.1.tgz#3f8de6b8330f5086270571251bedf10aec049e18"
+  integrity sha512-dg5YBTJYvogK1+dA2mBUDKzOWfYZtHVba89SyZUhc4+e3i2tzgjANFg5lDRCd3UOtRcw00vUTMK8LELcMdicug==
+
 detect-newline@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -3657,10 +3669,12 @@ detect-node@^2.0.4:
   resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
   integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
 
-detect-passive-events@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/detect-passive-events/-/detect-passive-events-2.0.2.tgz#229d02a20c47371194a2def84144c07d83e324c2"
-  integrity sha512-Ru7Eiz+pCy++QpqaNOgaIjlM0PzxEsh3Etg3wtntNhW2r3H1/8KkcR8bX9ApPIZChiNO2Pz//60uUg29DPNh3Q==
+detect-passive-events@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/detect-passive-events/-/detect-passive-events-2.0.3.tgz#1f75ebf80660a66c615d8be23c3241cdda6977e0"
+  integrity sha512-QN/1X65Axis6a9D8qg8Py9cwY/fkWAmAH/edTbmLMcv4m5dboLJ7LcAi8CfaCON2tjk904KwKX/HTdsHC6yeRg==
+  dependencies:
+    detect-it "^4.0.1"
 
 diff-sequences@^25.2.6:
   version "25.2.6"
@@ -4241,12 +4255,12 @@ eslint@^2.7.0:
     text-table "~0.2.0"
     user-home "^2.0.0"
 
-eslint@^7.19.0:
-  version "7.19.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.19.0.tgz#6719621b196b5fad72e43387981314e5d0dc3f41"
-  integrity sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==
+eslint@^7.20.0:
+  version "7.20.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.20.0.tgz#db07c4ca4eda2e2316e7aa57ac7fc91ec550bdc7"
+  integrity sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==
   dependencies:
-    "@babel/code-frame" "^7.0.0"
+    "@babel/code-frame" "7.12.11"
     "@eslint/eslintrc" "^0.3.0"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -4258,7 +4272,7 @@ eslint@^7.19.0:
     eslint-utils "^2.1.0"
     eslint-visitor-keys "^2.0.0"
     espree "^7.3.1"
-    esquery "^1.2.0"
+    esquery "^1.4.0"
     esutils "^2.0.2"
     file-entry-cache "^6.0.0"
     functional-red-black-tree "^1.0.1"
@@ -4306,10 +4320,10 @@ esprima@^4.0.0, esprima@^4.0.1:
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
 
-esquery@^1.2.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
-  integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
+esquery@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
+  integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
   dependencies:
     estraverse "^5.1.0"
 
@@ -7034,10 +7048,10 @@ min-indent@^1.0.0:
   resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
-mini-css-extract-plugin@^1.3.6:
-  version "1.3.6"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.6.tgz#02e2b477aa7ab2579c7ea2854a875897a8b8dad0"
-  integrity sha512-t86rLnySRQgN2+58gAIARTEtnClLNZoC99shNrvQ960V/wB9n50AUKJyqly76/s4fT0zwaLFIDFZAW7aK25pvg==
+mini-css-extract-plugin@^1.3.8:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.8.tgz#639047b78c2ee728704285aa468d2a5a8d91d566"
+  integrity sha512-u+2kVov/Gcs74iz+x3phEBWMAGw2djjnKfYez+Pl/b5dyXL7aM4Lp5QQtIq16CDwRHT/woUJki49gBNMhfm1eA==
   dependencies:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
@@ -9446,10 +9460,10 @@ sass-loader@^10.1.1:
     schema-utils "^3.0.0"
     semver "^7.3.2"
 
-sass@^1.32.7:
-  version "1.32.7"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.7.tgz#632a9df2b85dc4b346977fcaf2d5e6f2b7039fd8"
-  integrity sha512-C8Z4bjqGWnsYa11o8hpKAuoyFdRhrSHcYjCr+XAWVPSIQqC8mp2f5Dx4em0dKYehPzg5XSekmCjqJnEZbIls9A==
+sass@^1.32.8:
+  version "1.32.8"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc"
+  integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==
   dependencies:
     chokidar ">=2.0.0 <4.0.0"
 
@@ -11195,10 +11209,10 @@ which@^2.0.1, which@^2.0.2:
   dependencies:
     isexe "^2.0.0"
 
-wicg-inert@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/wicg-inert/-/wicg-inert-3.1.0.tgz#6525f12db188b83f0051bed2ddcf6c1aa5b17590"
-  integrity sha512-P0ZiWaN9SxOkJbYtF/PIwmIRO8UTqTJtyl33QTQlHfAb6h15T0Dp5m7WTJ8N6UWIoj+KU5M0a8EtfRZLlHiP0Q==
+wicg-inert@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/wicg-inert/-/wicg-inert-3.1.1.tgz#b033fd4fbfb9e3fd709e5d84becbdf2e06e5c229"
+  integrity sha512-PhBaNh8ur9Xm4Ggy4umelwNIP6pPP1bv3EaWaKqfb/QNme2rdLjm7wIInvV4WhxVHhzA4Spgw9qNSqWtB/ca2A==
 
 wide-align@^1.1.0:
   version "1.1.3"