10 Commits

19 changed files with 125 additions and 26 deletions

View File

@ -1,5 +1,23 @@
# Service dependencies
REDIS_HOST=redis
REDIS_PORT=6379
DB_HOST=db
DB_USER=postgres
DB_NAME=postgres
DB_PASS=
DB_PORT=5432
# Federation
LOCAL_DOMAIN=example.com
LOCAL_HTTPS=true
# Application secrets
PAPERCLIP_SECRET=
SECRET_KEY_BASE=
# E-mail configuration
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=notifications@example.com

View File

@ -2,7 +2,7 @@ FROM ruby:2.2.4
ENV RAILS_ENV=production
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev && rm -rf /var/lib/apt/lists/*
RUN mkdir /mastodon
WORKDIR /mastodon
@ -13,3 +13,5 @@ ADD Gemfile.lock /mastodon/Gemfile.lock
RUN bundle install --deployment --without test --without development
ADD . /mastodon
VOLUME ["/mastodon/public/system", "/mastodon/public/assets"]

View File

@ -21,8 +21,8 @@ Mastodon is a federated microblogging engine. An alternative implementation of t
Missing:
- Media attachments (photos, videos)
- UI to post, reblog, favourite, follow and unfollow
- Streaming API
- Blocking users, blocking remote instances
## Configuration
@ -30,6 +30,8 @@ Missing:
- `LOCAL_HTTPS` set it to `true` if HTTPS works on your website. This is used to generate canonical URLs, which is also important when generating and parsing federation-related IDs
- `HUB_URL` should be the URL of the PubsubHubbub service that your instance is going to use. By default it is the open service of Superfeedr
Consult the example configuration file, `.env.production.sample` for the full list.
## Requirements
- PostgreSQL
@ -37,7 +39,7 @@ Missing:
## Running with Docker and Docker-Compose
The project now includes a Dockerfile and a docker-compose.yml. You need to turn .env.production sample into .env.production with all the variables set before you can:
The project now includes a `Dockerfile` and a `docker-compose.yml`. You need to turn `.env.production.sample` into `.env.production` with all the variables set before you can:
docker-compose build
@ -48,3 +50,9 @@ And finally
As usual, the first thing you would need to do would be to run migrations:
docker-compose run web rake db:migrate
And since the instance running in the container will be running in production mode, you need to pre-compile assets:
docker-compose run web rake assets:precompile
The container has two volumes, for the assets and for user uploads. The default docker-compose.yml maps them to the repository's `public/assets` and `public/system` directories, you may wish to put them somewhere else. Likewise, the PostgreSQL and Redis images have data containers that you may wish to map somewhere where you know how to find them and back them up.

View File

@ -214,6 +214,12 @@
text-align: center;
}
.error_notification {
color: #df405a;
font-weight: 500;
margin-bottom: 15px;
}
.input {
margin-bottom: 15px;
@ -238,7 +244,7 @@
font-size: 14px;
font-family: 'Roboto', sans-serif;
&:focus {
&:focus, &:active {
border-bottom: 2px solid #2b90d9;
padding-bottom: 5px;
}
@ -253,6 +259,24 @@
margin-top: 5px;
color: lighten(#282c37, 25%);
}
&.field_with_errors {
input[type=text], input[type=email], input[type=password], textarea {
border-bottom: 2px solid #df405a;
padding-bottom: 5px;
&:focus, &:active {
border-bottom: 2px solid #2b90d9;
padding-bottom: 5px;
}
}
.error {
display: block;
margin-top: 5px;
color: #df405a;
}
}
}
}

View File

@ -6,7 +6,7 @@ class XrdController < ApplicationController
end
def webfinger
@account = Account.find_by!(username: username_from_resource, domain: nil)
@account = Account.find_local!(username_from_resource)
@canonical_account_uri = "acct:#{@account.username}@#{Rails.configuration.x.local_domain}"
@magic_key = pem_to_magic_key(@account.keypair.public_key)
rescue ActiveRecord::RecordNotFound
@ -21,10 +21,10 @@ class XrdController < ApplicationController
end
def username_from_resource
if params[:resource].start_with?('acct:')
params[:resource].split('@').first.gsub('acct:', '')
if resource_param.start_with?('acct:')
resource_param.split('@').first.gsub('acct:', '')
else
url = Addressable::URI.parse(params[:resource])
url = Addressable::URI.parse(resource_param)
url.path.gsub('/users/', '')
end
end
@ -43,4 +43,8 @@ class XrdController < ApplicationController
(["RSA"] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
end
def resource_param
params.require(:resource)
end
end

View File

@ -25,7 +25,7 @@ module StreamEntriesHelper
status.mentions.each { |m| mention_hash[m.acct] = m }
coder = HTMLEntities.new
auto_link(coder.encode(status.text), link: :urls, html: { target: '_blank', rel: 'nofollow' }).gsub(Account::MENTION_RE) do |m|
auto_link(coder.encode(status.text), link: :urls, html: { rel: 'nofollow noopener' }).gsub(Account::MENTION_RE) do |m|
account = mention_hash[Account::MENTION_RE.match(m)[1]]
"#{m.split('@').first}<a href=\"#{url_for_target(account)}\" class=\"mention\">@<span>#{account.acct}</span></a>"
end.html_safe

View File

@ -1,7 +1,7 @@
class Account < ActiveRecord::Base
# Local users
has_one :user, inverse_of: :account
validates :username, uniqueness: { scope: :domain }
validates :username, uniqueness: { scope: :domain, case_sensitive: false }
# Avatar upload
attr_reader :avatar_remote_url
@ -12,6 +12,10 @@ class Account < ActiveRecord::Base
has_attached_file :header, styles: { medium: '700x335#' }
validates_attachment_content_type :header, content_type: /\Aimage\/.*\Z/
# Local user profile validations
validates :display_name, length: { maximum: 30 }, if: 'local?'
validates :note, length: { maximum: 124 }, if: 'local?'
# Timelines
has_many :stream_entries, inverse_of: :account
has_many :statuses, inverse_of: :account
@ -32,7 +36,8 @@ class Account < ActiveRecord::Base
end
def unfollow!(other_account)
self.active_relationships.find_by(target_account: other_account).destroy
follow = self.active_relationships.find_by(target_account: other_account)
follow.destroy unless follow.nil?
end
def following?(other_account)
@ -93,6 +98,11 @@ class Account < ActiveRecord::Base
self.username
end
def self.find_local!(username)
table = self.arel_table
self.where(table[:username].matches(username)).where(domain: nil).take!
end
before_create do
if local?
keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 1024 : 2048)

View File

@ -2,7 +2,7 @@ class Favourite < ActiveRecord::Base
belongs_to :account, inverse_of: :favourites
belongs_to :status, inverse_of: :favourites
has_one :stream_entry, as: :activity, dependent: :destroy
has_one :stream_entry, as: :activity
def verb
:favorite

View File

@ -2,7 +2,7 @@ class Follow < ActiveRecord::Base
belongs_to :account
belongs_to :target_account, class_name: 'Account'
has_one :stream_entry, as: :activity, dependent: :destroy
has_one :stream_entry, as: :activity
validates :account, :target_account, presence: true
validates :account_id, uniqueness: { scope: :target_account_id }

View File

@ -98,7 +98,7 @@ class ProcessFeedService < BaseService
account = Account.find_by(username: username, domain: domain)
if account.nil?
account = follow_remote_account_service.("acct:#{username}@#{domain}", false)
account = follow_remote_account_service.("#{username}@#{domain}", false)
return nil if account.nil?
end

View File

@ -14,7 +14,7 @@ class ProcessInteractionService < BaseService
account = Account.find_by(username: username, domain: domain)
if account.nil?
account = follow_remote_account_service.("acct:#{username}@#{domain}", false)
account = follow_remote_account_service.("#{username}@#{domain}", false)
return if account.nil?
end
@ -48,7 +48,7 @@ class ProcessInteractionService < BaseService
end
def verb(xml)
xml.at_xpath('//activity:verb').content.gsub('http://activitystrea.ms/schema/1.0/', '').to_sym
xml.at_xpath('//activity:verb').content.gsub('http://activitystrea.ms/schema/1.0/', '').gsub('http://ostatus.org/schema/1.0/', '').to_sym
rescue
:post
end

View File

@ -11,7 +11,7 @@ class ProcessMentionsService < BaseService
mentioned_account = Account.find_by(username: username, domain: domain)
if mentioned_account.nil?
mentioned_account = follow_remote_account_service.("acct:#{match.first}")
mentioned_account = follow_remote_account_service.("#{match.first}")
end
mentioned_account.mentions.first_or_create(status: status)

View File

@ -17,7 +17,8 @@ test:
production:
<<: *default
database: postgres
username: postgres
password:
host: db
database: <%= ENV['DB_NAME'] || 'mastodon_production' %>
username: <%= ENV['DB_USER'] || 'mastodon' %>
password: <%= ENV['DB_PASS'] || '' %>
host: <%= ENV['DB_HOST'] || 'localhost' %>
port: <%= ENV['DB_PORT'] || 5432 %>

View File

@ -22,7 +22,7 @@ Rails.application.configure do
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.serve_static_files = true
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
@ -42,7 +42,7 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
config.force_ssl = ENV['LOCAL_HTTPS'] == 'true'
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
@ -76,4 +76,16 @@ Rails.application.configure do
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# E-mails
config.action_mailer.smtp_settings = {
:port => ENV['SMTP_PORT'],
:address => ENV['SMTP_SERVER'],
:user_name => ENV['SMTP_LOGIN'],
:password => ENV['SMTP_PASSWORD'],
:domain => config.x.local_domain,
:authentication => :plain,
}
config.action_mailer.delivery_method = :smtp
end

View File

@ -12,7 +12,7 @@ Devise.setup do |config|
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class
# with default "from" parameter.
config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
config.mailer_sender = ENV['SMTP_FROM_ADDRESS'] || 'notifications@localhost'
# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'

View File

@ -23,7 +23,7 @@ Doorkeeper.configure do
# Access token expiration time (default 2 hours).
# If you want to disable expiration, set this to nil.
# access_token_expires_in nil
access_token_expires_in nil
# Assign a custom TTL for implicit grants.
# custom_access_token_expires_in do |oauth_client|

View File

@ -0,0 +1,10 @@
class AddMissingIndices < ActiveRecord::Migration
def change
add_index :users, :account_id
add_index :statuses, :account_id
add_index :statuses, :in_reply_to_id
add_index :statuses, :reblog_of_id
add_index :stream_entries, :account_id
add_index :stream_entries, [:activity_id, :activity_type]
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160314164231) do
ActiveRecord::Schema.define(version: 20160316103650) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -125,6 +125,9 @@ ActiveRecord::Schema.define(version: 20160314164231) do
t.string "url"
end
add_index "statuses", ["account_id"], name: "index_statuses_on_account_id", using: :btree
add_index "statuses", ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree
add_index "statuses", ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree
add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree
create_table "stream_entries", force: :cascade do |t|
@ -135,6 +138,9 @@ ActiveRecord::Schema.define(version: 20160314164231) do
t.datetime "updated_at", null: false
end
add_index "stream_entries", ["account_id"], name: "index_stream_entries_on_account_id", using: :btree
add_index "stream_entries", ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type", using: :btree
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.integer "account_id", null: false
@ -151,6 +157,7 @@ ActiveRecord::Schema.define(version: 20160314164231) do
t.inet "last_sign_in_ip"
end
add_index "users", ["account_id"], name: "index_users_on_account_id", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree

View File

@ -12,4 +12,7 @@ services:
depends_on:
- db
- redis
volumes:
- ./public/assets:/mastodon/public/assets
- ./public/system:/mastodon/public/system
env_file: .env.production