目次

Mastodonのインスタンスを管理してきて数ヶ月が経ちました。よって、今までハマったところなどをまとめていけたらと思います。大体はリリースノートを見ればいいと思いますが、それだけでは実際の運用を乗り切れない場合が多々存在します。

私はhttps://syui.mlにMastodonの個人インスタンスを立てて遊んでいます。リモートアドレスはWEB_DOMAINを設定しているので@syui@syui.mlです。

このインスタンスを立てた理由としては、Twitterの有料化などの話があり、そうした場合の手軽な連絡手段や投稿手段を確保するためでした。

Mastodonはgnu socialを用いてユーザー同士が手軽につながるような仕組みを利用しています。宛先は@user@example.comという形で他のインスタンスとやり取りが可能です。自分のインスタンス内だと@userで済みます。

前置きはこのくらいにして、Mastodonのインスタンス運営でハマりそうなポイントを書いていきたいと思います。

補足 :

  • 2017/04/15からはじめたmstdn.syui.cfは2017/10/10からsyui.mlに変更になりました。

Mastodon on Heroku

Deploy

Mastodon Install (Docker)

$ git clone https://github.com/tootsuite/mastodon.git
$ cd mastodon

# db, redis:volumes uncomment
$ vi docker-compose.yml
  db:
    restart: always
    image: postgres:alpine
    volumes:
      - ./postgres:/var/lib/postgresql/data
  redis:
    restart: always
    image: redis:alpine
    volumes:
      - ./redis:/data

$ mkdir -p ./{postgress,radis}

$ docker-compose build

# copy 3 key
$ zsh -c "repeat 3 docker-compose run --rm web rake secret"

$ cp .env.production.sample .env.production
$ vi .env.production
LOCAL_DOMAIN=localhost:3000
LOCAL_HTTPS=false
PAPERCLIP_SECRET=xxx1
SECRET_KEY_BASE=xxx2
OTP_SECRET=xxx3

$ docker-compose run --rm web rails db:migrate
$ docker-compose run --rm web rails assets:precompile
$ docker-compose up

# update web
$ docker-compose stop
$ docker-compose run --rm web rails assets:precompile
$ docker-compose start

# copy
$ docker-compose cp web ./file /mastodon/file

# ssh
$ docker-compose exec web /bin/bash

# reset
$ docker-compose down
$ docker-compose build
$ docker-compose run --rm web rails db:migrate
$ docker-compose run --rm web rails assets:precompile
$ docker-compose up -d
	
# source build
$ git pull ...
$ docker-compose build
$ docker-compose up -d

# open brewser, sing up
$ docker-compose run --rm web rails mastodon:confirm_email USER_EMAIL=$email
$ docker-compose run --rm web rails mastodon:make_admin USERNAME=$username

DBのリストア

私は、Heroku上でインスタンスを立ち上げています。しかし、Heorku(Free)はDBが10000レコード(ROWS)しか保存出来ませんので、無料枠で使うには、定期的にDBをリセットする必要が出てきます。

Mastodonを立ち上げ管理者でログインした直後にDBをバックアップして、そのデータを保存しておく必要があります。この際、dbとの整合性を維持するためmastodonのversionを合わせる必要があるかもしれません。

今回は、そのリストア方法になります。

Mastodonはpsqlを利用しています。よって、以下のような感じでdumpしたデータをリストアできます。

# リセット(要注意)
$ heroku pg:reset -a $appname
# リストア
$ pg_restore --verbose --clean --no-acl --no-owner -h $host -U $username -d $database ./$filename.dump

エラーが出るようならheroku pg:psqlして修正するか若しくは、mastodonのversionをバックアップ時のものにダウングレードする必要があります。

2FAを無効にする

$ heroku pg:psql
UPDATE users SET otp_required_for_login=false WHERE email='user@example.com';

500が出て投稿できない、動かない

v 1.4からは以下のコマンドを実行する必要があります。

$ heroku run rake db:migrate -a $app
$ heroku run rake assets:precompile -a $app
$ heroku ps:restart -a $app

それ以外では以下のコマンドで解決する場合があります。

$ heroku run rake webpacker:compile -a $app

Your slug size exceeds our soft limit (3XX MB) which may affect boot time.

上記のようなエラーがデプロイ時に出た場合はプラグインをインストールしてキャッシュなどを削除します。

$ heroku plugins:install heroku-repo
$ heroku repo:purge_cache -a $app

db:migrateでエラーが出る場合

以下を実行するとエラーが出る場合があります。エラー内容によって必要な処理が異なります。

$ heroku run rake db:migrate -a $app

PG::ForeignKeyViolation

v1.4.1からv1.4.2の間に、外部キーが導入されたのでそれに関するエラーです。

$ heroku run rake db:migrate -a $app
PG::ForeignKeyViolation

$ heroku run rake mastodon:maintenance:prepare_for_foreign_keys -a $app

$ heroku run rake db:migrate -a $app
$ heroku ps:restart -a $app

PG::DuplicateTable: ERROR

エラー内容にはPG::DuplicateTable: ERRORのみならずIndex name 'index_accounts_on_uri' on table 'accounts' already existsを含みます。

これはv 1.3 -> v 1.4にした時にでたエラーです。ただし、ダウングレードを数回行っているため、そこで入ったDBの値が悪さをしていたのかもしれません。

$ heroku run rake db:migrate -a $app
PG::DuplicateTable: ERROR
	account_domain_blocks
	conversations
	conversation_mutes

$ heroku pg:psql
DROP TABLE account_domain_blocks;
DROP TABLE conversations;
DROP TABLE conversation_mutes;
		
$ heroku run rake db:migrate -a $app
Index name 'index_accounts_on_uri' on table 'accounts' already exists

$ heroku pg:psql
DROP INDEX index_accounts_on_uri;

$ heroku run rake db:migrate -a $app
$ heroku ps:restart -a $app

Error massage (Sample)

$ heroku run rake db:migrate -a $app
> ActiveRecord::StatementInvalid: PG::DuplicateTable: ERROR:  relation "conversations" already exists
> : CREATE TABLE "conversations" ("id" bigserial NOT NULL PRIMARY KEY, "uri" character varying DEFAULT NULL, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
$ heroku run rake db:migrate:reset -a $app
> ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your 'production' database.
> If you are sure you want to continue, run the same command with the environment variable:
> DISABLE_DATABASE_ENVIRONMENT_CHECK=1
$ heroku run rake db:migrate -a $app
> Index name 'index_accounts_on_uri' on table 'accounts' already exists

個人用インスタンス

# https://github.com/ummjackson/mastodon-guide/blob/master/single-user-mode.md
$ heroku config:set SINGLE_USER_MODE=true -a $app

.bulidpacks

v 1.3からは必要なパッケージがいくつか増えています。Herokuサーバーにそれをインストールしてやる必要があります。Herokuではbuildpacksaptを追加することでパッケージのインストールが可能になります。インストールするパッケージはAptfileに書きます。

$ cat .buildpacks
$ heroku buildpacks:add --index 1 https://github.com/heroku/heroku-buildpack-apt

gem install idn-ruby

v 1.5では、Aptfilelibidn11-dev(centosなどではlibidn-develというパッケージ名)が追加されています。これはwgetでダウンロードされ、idn-rubyが依存しています。しかし、Herokuではこのパッケージが元から入っているためか、Aptfileからlibidn11-devの行を削除しないとgem install idn-rubyがインストールできずビルドが完了しませんでした。

v 1.5 rc1 - v 1.5 rc3

$ heroku run "dpkg -l"
$ vim Aptfile
libidn11-dev という行を削除

現在はbranch:masterにてfixされているので、libidn11-devの行を削除する必要はありません。(Heroku)

Aptfile

+ libidn11
libidn11-dev

gem install cld3

$ cat Aptfile
$ echo protobuf-lite-devel >> Aptfile

gem install charlock_holmes

$ heroku buildpacks:add --index 2 https://github.com/rcaught/heroku-buildpack-cmake 

Aptfile

libicu52
libicu-dev

https://github.com/brianmario/charlock_holmes/wiki/Installing-charlock-holmes-and-libicu-dev-on-heroku

generate_vapid_key

v 1.5からgenerate_vapid_keyを発行できるようになりました。

$ heroku run rake mastodon:webpush:generate_vapid_key -a $app
$ heroku config:set VAPID_PUBLIC_KEY=XXXXXXX -a $app
$ heroku config:set VAPID_PRIVATE_KEY=XXXXXXX -a $app

Sidekiqのスレッド数を減らす

$ heroku config:set SIDEKIQ_THREADS=2 -a $app

Push rejected, failed to compile Node.js app.

HerokuにDeployする際、以下のエラーが出る場合は、package.jsonにバージョンを指定することで回避できます。

$ git push heroku master
> ! Push rejected, failed to compile Node.js app.

package.json

{
  "engines": {
    "yarn": "0.27.5",
    "node": "8.1.3"
  }
}

https://github.com/react-boilerplate/react-boilerplate/issues/1779

tootがdeleteできなくなった

1.6(6c81f9d)でtootが削除できない状態でしたが、以下の手順で解決しました。

$ heroku buildpacks:remove https://github.com/rcaught/heroku-buildpack-cmake
$ git merge : master(efec5072)
$ heroku repo:purge_cache -a $app
$ heroku config:set MAX_THREADS=2 -a $app
## tootを削除したはずなのに残ってる
## 1.6(6c81f9d)
$ url=https://mstdn.syui.cf/api/v1/statuses/473
$ curl -X DELETE $url -H "Authorization: Bearer $access_token"
> {}
$ curl -X GET $url -H "Authorization: Bearer $access_token"
{
  "id": 473,
  "created_at": "2017-09-15",
  "in_reply_to_id": null,
  "in_reply_to_account_id": null,
  "sensitive": false,
  "spoiler_text": "",
  "visibility": "public",
  "language": "ja",
  "uri": "http://mstdn.syui.cf/users/syui/statuses/473",
  "content": "up : master(472df245)",
  "url": "http://mstdn.syui.cf/@syui/473",
  "reblogs_count": 0,
  "favourites_count": 0,
  "favourited": false,
  "reblogged": false,
  "muted": false,
  "pinned": false,
  "reblog": null,
  "application": {
    "name": "test",
    "website": null
  }
}

https://github.com/tootsuite/mastodon/issues/4928

custom emojiでSSLのエラーが出る時

以下のようなエラーが出る場合があります。なぜなら、img src=http://となっているからです。

Mixed Content: The page at xxx was loaded over HTTPS, but requested an insecure video xxxx. This content should also be served over HTTPS.

Find and fix mixed content https://developers.google.com/web/fundamentals/security/prevent-mixed-content/fixing-mixed-content

しかし、以下のように設定するとうまく動作しました。知らずにPRを出してしまいました。

$ heroku config:set CDN_HOST=https://${emojiが置いてある場所} -a $app

https://github.com/tootsuite/mastodon/pull/5033

1.6でのHeroku(Free)運用メモ

  • mastodon v1.6

  • DB recode 8000

  • toot 1500

  • follow 0

  • w 2

大体、1500tootでDBのレコードを8000程度消費しました。無料枠は10000なのでこのくらいになるとDBをリセットする必要が出てきます。この消費量はタイムラインに表示されるtootなどにも影響を及ぼします。そのため私は、フォロー0にしています。私の場合、2ヶ月ほどでDBをリセットする必要が出てきました。

DBレコードの消費量はMastodonのVersion、フォローしている人のtoot頻度(TLの流れの速さ)、インスタンスの接続数などによっても変動してくると思われます。

react-infinite-scroller

$ yarn add react-infinite-scroller

mastodon/app/javascript/mastodon/features/public_timeline/index.js

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import InfiniteScroll from 'react-infinite-scroll-component';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import {
  refreshPublicTimeline,
  expandPublicTimeline,
} from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectPublicStream } from '../../actions/streaming';


const messages = defineMessages({
  title: { id: 'column.public', defaultMessage: 'Federated timeline' },
});

const mapStateToProps = state => ({
  hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0,
});

@connect(mapStateToProps)
@injectIntl
export default class PublicTimeline extends React.PureComponent {

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    intl: PropTypes.object.isRequired,
    columnId: PropTypes.string,
    multiColumn: PropTypes.bool,
    hasUnread: PropTypes.bool,
  };

  handlePin = () => {
    const { columnId, dispatch } = this.props;

    if (columnId) {
      dispatch(removeColumn(columnId));
    } else {
      dispatch(addColumn('PUBLIC', {}));
    }
  }

  handleMove = (dir) => {
    const { columnId, dispatch } = this.props;
    dispatch(moveColumn(columnId, dir));
  }

  handleHeaderClick = () => {
    this.column.scrollTop();
  }

  componentDidMount () {
    const { dispatch } = this.props;

    dispatch(refreshPublicTimeline());
    this.disconnect = dispatch(connectPublicStream());
  }

  componentWillUnmount () {
    if (this.disconnect) {
      this.disconnect();
      this.disconnect = null;
    }
  }

  setRef = c => {
    this.column = c;
  }

  handleLoadMore = () => {
    this.props.dispatch(expandPublicTimeline());
  }

  constructor () {
    super();
    this.state = {divs: divs};
    this.generateDivs = this.generateDivs.bind(this);
  }

  generateDivs () {
    const { intl, columnId, hasUnread, multiColumn } = this.props;
    const pinned = !!columnId;
    moreDivs.push(
      <Column ref={this.setRef}>
        <ColumnHeader
          icon='globe'
          active={hasUnread}
          title={intl.formatMessage(messages.title)}
          onPin={this.handlePin}
          onMove={this.handleMove}
          onClick={this.handleHeaderClick}
          pinned={pinned}
          multiColumn={multiColumn}
        >
          <ColumnSettingsContainer />
        </ColumnHeader>

        <StatusListContainer
          timelineId='public'
          loadMore={this.handleLoadMore}
          trackScroll={!pinned}
          scrollKey={`public_timeline-${columnId}`}
          emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />}
        />
      </Column>
    );
    setTimeout(() => {
      this.setState({divs: this.state.divs.concat(moreDivs)});
    }, 500);
  }

  render () {
    return (
        <InfiniteScroll
          next={this.generateDivs}
          hasMore={true}
          height={300}
          loader={<h4>Loading...</h4>}>
          {this.state.divs}
        </InfiniteScroll>
    );
  }

}

リモートフォロー、通知、TLができなくなる問題

リセット前と同じidのアカウント作ったり、またはdbをrestoreすると、リモートフォロー、通知、メインTLなどが流れない現象が確認できました。

この際、db:restore後にdb:resetdb:migrate, redis:cliにて設定することでこの問題を解消することができるかもしれません。

なお、db:resetするためまたid(user)を最初から作り直さなければなりません。

# heroku run rake db:reset DISABLE_DATABASE_ENVIRONMENT_CHECK=1 -a $app
$ heroku pg:reset -a $app
$ heroku redis:cli -a $app
	FLUSHALL
	dbsize
	quit
$ heroku rake db:migrate -a $app
$ heroku run rake db:seed -a $app
$ heroku run rake assets:precompile -a $app
$ heroku run rake mastodon:daily -a $app
$ heroku run rake mastodon:webpush:generate_vapid_key -a $app
$ heroku config:set ...
# browserでsing up
$ open -a Google\ Chrome https://mstdn.syui.cf
$ heroku run rake mastodon:confirm_email USER_EMAIL=$mail -a $app
$ heroku run rake mastodon:make_admin USERNAME=$USER -a $app
$ heroku config:set SINGLE_USER_MODE=false -a $app 

その他:

$ heroku run rake db:reset DISABLE_DATABASE_ENVIRONMENT_CHECK=1 SAFETY_ASSURED=1 -a $app

or 

# db:migrate:reset
$ heroku run rake db:migrate:reset DISABLE_DATABASE_ENVIRONMENT_CHECK=1 -a $app

cache clear

$ heroku run rake mastodon:media:remove_remote -a $app

daily

$ heroku run rake mastodon:daily -a $app

or 

$ heroku run rake mastodon:feeds:clear -a $app
$ heroku run rake mastodon:media:clear -a $app
$ heroku run rake mastodon:users:clear -a $app
$ heroku run rake mastodon:push:refresh -a $app

sing up (admin)

$ heroku config:set SINGLE_USER_MODE=false -a $app

# まずbrowserでsing upします。そして、以下のコマンドを叩きます。$email="入力したメールアドレス", $username="入力したユーザー名"
$ heroku run rake mastodon:confirm_email USER_EMAIL=$email -a $app
$ heroku run rake mastodon:make_admin USERNAME=$username -a $app

$ heroku config:set SINGLE_USER_MODE=true -a $app

List-of-Rake-tasks

# https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/List-of-Rake-tasks.md
$ heroku run rake mastodon:push:clear -a $app
$ heroku run rake mastodon:feeds:clear_all -a $app
$ heroku run rake mastodon:feeds:build -a $app
$ heroku run rake mastodon:webpush:generate_vapid_key -a $app

アップデート時にやっといたほうが良いコマンド

$ heroku run rake db:migrate -a $app
$ heroku run rake assets:precompile -a $app
$ heroku run rake mastodon:daily -a $app
# heroku run rake mastodon:media:remove_remote -a $app
# heroku ps:restart -a $app
# heroku repo:purge_cache -a $app
# heroku run rails c -a $app
	# Rails.cache.clear

Domain names and SSL for Heroku

You should set the Heroku config vars of LOCAL_DOMAIN to your hostname, and LOCAL_HTTPS to “true” as well.

https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md

Herokuでは完全に無料でSSLを使用することは不可能

SSL証明書を登録できるアプリはhobbyからなので、freeでは不可能です。

$ su
$ cd /etc/letsencrypt/live
$ ls
README          chain.pem       privkey.pem
cert.pem        fullchain.pem
$ heroku certs:add fullchain.pem privkey.pem -a $app --type sni
 ▸    You need to be running on either Hobby or Professional dynos to be
 ▸    able to use SNI SSL.

You need to be running on either Hobby or Professional dynos to be

able to use SNI SSL.

CloudFlareを利用することで見た目上のhttpsは実現可能

CloudFlareのCNAMEを設定することで見た目上のhttpsは実現可能です。しかし、この場合、LOCAL_DOMAINをmstdn.syui.cfみたいな形にした上でLOCAL_HTTPSをfalseにしなければならないので、色々な問題が生じる可能性があります。

# cloudflare
CNAME mstdn appname.herokuapp.com
# heroku
$ heroku config:set LOCAL_DOMAIN=www.example.com -a $app
$ heroku config:set LOCAL_HTTPS=false -a $app

無料で証明書を手動更新する方法

MastodonのDocsでみると、手動でやれば無料で使えるみたいな書き方なのですが、いまいちよく分かりません。検索してみると、m/7$が必要みたいな書き方が多いので、多分ですが、dynoのAppでは登録できないということだと思います。

Heroku SSL Dev Centerの記事に記載されている手順に従って、独自の証明書をアップロードしてください。

Herokuは、有料のdynosで動作するすべてのアプリケーションに対して、無料の自動証明書管理(ACM)を提供します。ACMを使用すると、Herokuは自動的にSSL証明書をプロビジョニングし、更新します。独自の証明書を手動でアップロードする場合は、この記事の手順に従ってください。

# 証明書の発行はLet's Encryptを使用
$ brew install certbot
$ sudo certbot certonly --manual
-------------------------------------------------
$ mkdir -p mastodon/public/.well-known/acme-challenge
$ echo "yyyyyyyyyyyyyyyyyyy" > mastodon/public/.well-known/acme-challenge/xxxxxxxxxxxxxx
$ cd mastodon
$ git add ./public/.well-known
$ git commit -m "upload file"
$ git push heroku master

これによって公開鍵と秘密鍵が作成されましたので、それをHerokuの方に登録します。登録にはhobby以上の料金設定が必要です。

$ su
$ cd /etc/letsencrypt/live
$ ls
README          chain.pem       privkey.pem
cert.pem        fullchain.pem
$ heroku certs:add fullchain.pem privkey.pem -a $app --type sni
# Use the --type sni flag if you have an SSL Endpoint add-on on your application already.
$ heroku certs:info
$ curl -vI https://www.example.com

今日では、新しい無料のSSLサービスと無料のdyno時間を使用するためのより柔軟な方法という2つの重要なアップデートを発表しています。Heroku SSLは今日ベータ版として導入されており、今後数週間から数ヶ月にわたって公開される予定です。6月1日に開始するフレキシブルなdyno時間の開始が予定されています。

2016年5月18日 : https://blog.heroku.com/announcing_heroku_free_ssl_beta_and_flexible_dyno_hours

2016年9月22日 : https://blog.heroku.com/ssl-is-now-included-on-all-paid-dynos

# これらのコマンドは現在必要ありません
$ heroku labs:enable http-sni -a <your app>
$ heroku plugins:install heroku-certs
# Use the --type sni flag if you have an SSL Endpoint add-on on your application already.
$ heroku certs:add example.crt example.key -a <your app>

https://devcenter.heroku.com/articles/ssl#manually-uploading-certificates-and-intermediaries

https://medium.com/should-designers-code/how-to-set-up-ssl-with-lets-encrypt-on-heroku-for-free-266c185630db

Let’s Encrypt

$ brew install certbot
$ sudo certbot certonly --manual
$ sudo heroku certs:add /etc/letsencrypt/live/plumfeed.com/fullchain.pem /etc/letsencrypt/keys/0000_key-certbot.pem
$ curl -vI https://plumfeed.com

http://blog.jonnew.com/posts/heroku-ssl-and-a-free-cert-from-lets-encrypt

$ openssl genrsa -des3 -out server.pass.key 2048
$ openssl rsa -in server.pass.key -out server.key
$ openssl req -nodes -new -key server.key -out server.csr

https://devcenter.heroku.com/articles/acquiring-an-ssl-certificate

https://blog.heroku.com/ssl-is-now-included-on-all-paid-dynos#feedback

Let’s encrypt + dmathieu/sabayon

$ git clone https://github.com/dmathieu/sabayon.git
$ cd sabayon
$ heroku create letsencrypt-app-for-<name>
$ git push heroku

https://blog.odoruinu.net/2017/03/07/setup-lets-encrypt-on-heroku-with-automated-renewal/

有料で証明書を自動更新する方法

# こちらはweb=hobbyです, 有料設定なので気をつけてください
$ heroku ps:resize web=hobby
$ heroku certs:auto:enable

https://devcenter.heroku.com/articles/automated-certificate-management

https://devcenter.heroku.com/articles/ssl-endpoint

Enable streaming on heroku

PR : https://github.com/tootsuite/mastodon/pull/5158

issue : https://github.com/tootsuite/mastodon/issues/1119

$ heroku config:set NODE_ENV=production -a $app
$ heroku config:set PGSSLMODE=require -a $app

Sub Domain

サブドメインに設定する場合は、WEB_DOMAINを使用すると、@user@sub.main.comのような形ではなく@user@main.comという形でアドレスがサブを除くトップドメインを利用できる形になります。但し、ドキュメントにもあるように様々な問題を発生させます。

$ heroku config:set WEB_DOMAIN=mstdn.syui.cf -a $app
$ heroku config:set LOCAL_DOMAIN=syui.cf -a $app

$ curl -O mstdn.syui.cf/.well-known/host-meta

# 私の場合はtop domainをgitlabでhostしていますので以下のようにします。
# 具体的にはダウンロードしたhost-metaを同じ場所に置きます。
$ mv syui.gitlab.io/public/.well-known/host-meta
$ cd syui.gitlab.io
$ git add public/.well-known/host-meta
$ git commit -m "add host-meta"
$ git push -u origin gh-pages

.well-known/host-meta

<?xml version="1.0"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
  <Link rel="lrdd" type="application/xrd+xml" template="http://mstdn.syui.cf/.well-known/webfinger?resource={uri}"/>
</XRD>

https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md

just leave the WEB_DOMAIN key commented out. If you do need it however, you’ll still have to enter a redirect rule for your root domain that points https://rootdomain/.well-known/host-meta to https://subdomain.rootdomain/.well-known/host-meta. I added a rule on Cloudflare to achieve this, but any approach will do.

https://www.herebedragons.io/running-mastodon

I’m using a different WEB_DOMAIN (mastodon.develry.be) and LOCAL_DOMAIN (develry.be).

issue : https://github.com/tootsuite/mastodon/issues/2025

リモートフォロー時の砂時計のマーク

The requirement for new followers to be approved is something you can enable for your own profile under preferences.

https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md

OStatusからActivityPubへの移行

自動的に承認されはするものの、何らかの理由で「承認」メッセージが受け取れない場合に承認待ち状態が続く可能性があります。承認待ちマーク(砂時計)のクリックでリクエストをキャンセルできるようになっているので、もし上手く承認できない場合はキャンセルしてやりなおしてみてください。

https://hackmd.io/s/H1KSg6ttW

1.6から1.5のダウングレード

db:migrateの関係で1.6から1.5のダウングレードは推奨されてなかった気がするのでおすすめしません。私の場合、db:resetしてるのでもはやなんでもできますが(捨てるものがないという意味で)

LOCAL_HTTPS=false

現在の状況(不都合)をまとめます。これらが通知、リモートフォロー、TLが流れない問題に影響を及ぼしているかどうかは不明。個人的には無関係であり、db:reset, db:restoreなどの実行が影響したものだと考えていますが、複合的要因があるかもしれません。

herokuがhobby以上でないと無料でSSL証明書を登録できませんので、LOCAL_HTTPS=falseにしています。これによって動作不良が起こる可能性を否定できません。

$ heroku certs:add fullchain.pem privkey.pem -a app --type sni
 ▸    You need to be running on either Hobby or Professional dynos to be
 ▸    able to use SNI SSL.

Streaming not working

Webのユーザー画面、管理画面にてStreaming not workingのエラーが起こり、Chromeでは安全でないスクリプトを読み込むを実行しなければ正常に動作しません。これはheroku特有の問題も含みます。

issue : https://github.com/tootsuite/mastodon/issues/1119

1.6.1(releases)からmaster(latest)までのアップデート

latest=1.6.1

バージョン(commit)を上げていくと成功するみたいです。

  • 1.6.1(releases)からmaster(latest)へのアップデートを行った場合、heroku rake db:migrate -a $appで非常に深刻なエラーが出ます。

  • master(latest)へのアップデートを完了しました。db:migrationへのエラーは出ませんでした。db:reset後は徐々にバージョン(commit)を上げていくと成功するみたいです。

2017/10/09に起こっている不都合

version : 1.6.1, 2.0.0rc1

  • リモートフォローができない

  • 通知が一切来ない(これはインスタンス内を確認していない、あくまで他インスタンスからのメンション, favを飛ばした形で検証)

  • ローカルTL、グローバルTLが流れない

  • 通知、フォロー、TLはすべて9月中には動作しているのを確認済み。いつの間にか動作しなくなっており、その後も一切機能しない。

  • ログイン画面でStreaming not workingになる(これは特定のバージョンでは発生しない)。この際、httpsがエラーになり、安全でないスクリプトを読み込まないと画面が表示できない(by Chrome)

  • 自動ログインができずパスワード入力などを都度要求される(これは特定のバージョンでは発生しない)

これらは、Rails, Postgres, Mastodonをリセットし、Mastodonに関しては1.6.1のうちの問題が発生していなかった時のバージョンを使用しても解消できなかった。残る手としては、バージョンを1.6.1以外の1.3や1.5などにダウングレードするほかなく、この際、再び問題が生じないよう予めdb:resetしたほうが良さそうです。

リモートフォローなどができない問題の解決

原因がわかりました。原因はCloudFlareの設定にあったみたいで、SSLをEnableにしているとこの問題が発生しやすいです。

最も単純に動作するのは以下の設定のみを行った場合です。

  • LOCAL_HTTPS=false

  • LOCAL_DOMAIN=xxx.herokuapp.com

独自ドメインを使ったうえでやる場合は、CloudFlareのSSL設定にあるリダイレクトや強制的に書き換える機能をOFFにします。

LOCAL_HTTPSなどの挙動

CloudFlareと連携して独自ドメインを使う場合を想定します。挙動にかなり違いが出てくるし、曖昧なので注意が必要です。

最も単純に動作するのは以下の設定のみを行った場合です。

  • LOCAL_HTTPS=false

  • LOCAL_DOMAIN=xxx.herokuapp.com

xxxはherokuの$appnameです。

これによってhttp://xxx.herokuapp.comにアクセスできます。

この場合、リモートアドレスは@user@xxx.herokuapp.com

しかし、通常は独自ドメインを設定しSSL接続も有効にしたいだろうと思われます。

まず、cloudflare:ssl=flexibleでやる場合はLOCAL_HTTPS=falseでないとアクセスできない。trueではそもそも表示されない。

しかし、これでも危険なスクリプトを実行するか?というChromeのエラーが出てhttpsもエラーが出る。remote addressは@user@mstdn.syui.cfというようなsub domainの形をとる。アイコンは正常に表示される。

次に、cloudflare:ssl=fullLOCAL_HTTPS=falseでも上記と同様である。

これをLOCAL_HTTPS=trueにするとssl:fullではアイコンは表示されないが、remote addressがtop domainの形になる。@user@syui.cf

画像アイコン等の問題

私は、AWS S3を使っていませんので、別途ストレージを用意する必要があります。

例えば、ユーザーアイコンの扱いについてですが、これは基本的にRails側(Herokuの場合, /var/lib/mastodon/public-system)に保存されると思われます。Heroku Railsのストレージは定期的に再起動し、再起動されるとストレージに保存されたファイルを削除します。

ここでPAPERCLIP_ROOT_URLなどを使うことによって保存先を指定することができます。

# インスタンス内で表示するaccounts/の保存先を指定する
$ heroku config:set PAPERCLIP_ROOT_URL=https://syui.gitlab.io/img/mastodon -a $app
# 他インスタンスで参照されるaccounts/の保存先を指定する
$ heroku config:set PAPERCLIP_ROOT_PATH=/app/public -a $app

通常は、/system/accounts/avatars/000/000/001/original/xxx.pngという画像URLが参照される。

PAPERCLIP_ROOT_URLを設定することでこれが変更されます。

heroku run pwd -a $app : /app/public

root : /home/mastodon/live/public

$ docker-compose run --rm web rails mastodon:media:remove_remote NUM_DAYS=0
# 画像を再ダウンロードし、画像URLも変更される
$ docker-compose run --rm web rails mastodon:media:redownload_avatars

ここで、APIを使ってデフォルトと設定後のユーザー情報を引き出してみました。

デフォルトの設定

{
  "id": "1",
  "username": "syui",
  "acct": "syui",
  "display_name": "",
  "locked": false,
  "created_at": "2017-10-14",
  "note": "<p></p>",
  "url": "http://mstdn-syui-2.herokuapp.com/@syui",
  "avatar": "http://mstdn-syui-2.herokuapp.com/system/accounts/avatars/000/000/001/original/2fe43582417bfd37.png?1507954652",
  "avatar_static": "http://mstdn-syui-2.herokuapp.com/system/accounts/avatars/000/000/001/original/2fe43582417bfd37.png?1507954652",
  "header": "http://mstdn-syui-2.herokuapp.com/system/accounts/headers/000/000/001/original/c5968223d7486ab8.png?1507942898",
  "header_static": "http://mstdn-syui-2.herokuapp.com/system/accounts/headers/000/000/001/original/c5968223d7486ab8.png?1507942898",
  "followers_count": 0,
  "following_count": 0,
  "statuses_count": 1
}

PAPERCLIP_ROOT_PATH=/app/public, PAPERCLIP_ROOT_ROOT=https://syui.gitlab.io/img/mastodon

{
  "id": "1",
  "username": "syui",
  "acct": "syui",
  "display_name": "",
  "locked": false,
  "created_at": "2017-10-13",
  "note": "<p></p>",
  "url": "http://syui.ml/@syui",
  "avatar": "https://syui.gitlab.io/img/mastodon/accounts/avatars/000/000/001/original/e5415b7a2f69e2cb.png",
  "avatar_static": "https://syui.gitlab.io/img/mastodon/accounts/avatars/000/000/001/original/e5415b7a2f69e2cb.png",
  "header": "https://syui.gitlab.io/img/mastodon/accounts/headers/000/000/001/original/11bfcd687af9cad0.png",
  "header_static": "https://syui.gitlab.io/img/mastodon/accounts/headers/000/000/001/original/11bfcd687af9cad0.png",
  "followers_count": 1,
  "following_count": 1,
  "statuses_count": 8,
  "source": {
    "privacy": "public",
    "sensitive": false,
    "note": ""
  }
}

一度、他インスタンスで画像が認識, ダウンロードされてしまうとなかなかその設定が変更されません(情報によるとリモート時などに更新されるらしい)。mastodon:dailyなどが関係しているのかもしれませんが、よくわかっていません。

私のインスタンスユーザーは他インスタンス上でアイコンが表示されなかった問題があったのですが、以下のコマンドでいけたような気がします。色々やってた時、タイミングよくmastodon:daily(他インスタンス上で)などが発動された可能性とかもあるので、まだ確定ではありませんが。

$ heroku config:set PAPERCLIP_ROOT_URL=https://syui.gitlab.io/img/mastodon -a $app
$ heroku config:set PAPERCLIP_ROOT_PATH=/app/public -a $app

私は画像URLを調べてGitLab Pagesの/public/img/mastodonにアップロードし、なおかつMastodonのリポジトリにある/publicにaccoutns/をコピーし、git push heroku masterします。

example img url: accounts/avatars/000/000/001/original/xxx.png

loadaverage.orgとのやり取りがうまく行かなかった問題

SSL周りの問題?で他のMastodonインスタンスのやり取りがうまく行かなかった問題を解消すると、今度は、Mastodon間はOKなのに、loadaverage.orgとのやり取りが上手く行かなかったことがありました。

この問題を一旦解決した手順を記述します。

まずドメインを新しいものに変更しました。昔のは色々と紐付けすぎてしまい、あちらが立てば、こちらが立たずという状況になってしまっていました。具体的には、Mastodonで正常な動作を確保したと思ったら、今度は他のサイトがだめになったり、反対に、他のサイトを正常に戻す設定をすると、今度はMastodonに問題が発生したり。

ここで、loadaverageとのやり取りが上手く行った手順を記録します。

  • domainを新しくし、top domainをmastodonに割当てる, WEB_DOMAINは設定しない

  • heroku deployをしてuser設定をする, アイコンなどを設定する

LOCAL_DOMAIN=xxx.herokuapp.com
LOCAL_HTTPS=false

$ app="heroku app name"
$ heroku run rake mastodon:confirm_email USER_EMAIL=$email -a $app
$ heroku run rake mastodon:make_admin USERNAME=$username -a $app
  • envを変更して、updateする, 画像アイコンなどのURLはページからコピーしておく(同じディレクトリ構造でPAPERCLIP_ROOT_URL, PAPERCLIP_ROOT_PATHに置くため)
$ heroku config:set PAPERCLIP_ROOT_URL=https://syui.gitlab.io/img/mastodon -a $app
$ heroku config:set PAPERCLIP_ROOT_PATH=/app/public -a $app

$ cp -rf accounts mastodn/public/
$ cd mastodon
$ git add .
$ git commit -m "img upload"
$ git push heroku master

# mastodon version=2.0
$ heroku run rake mastodon:media:remove_remote -a $app
$ heroku run rake mastodon:media:remove_silenced -a $app
$ heroku run rake mastodon:daily -a $app
$ heroku run rake db:migrate -a $app
$ heroku run rake assets:precompile -a $app
$ heroku ps:restart -a $app
  • cloudflareで以下の構成で設定する
DNS : syui.ml xxx.herokuapp.com
DNS : www xxx.xx.herokudns.com
SSL : Flexible
	HTTP Strict Transport Security (HSTS)
		Enforce web security policy for your website.
		Status: On
		Max-Age: 0 (Disable)
		Include subdomains: On
		Preload: On
		No-sniff: On
	Always use HTTPS: OFF
	Authenticated Origin Pulls: OFF
	Opportunistic Encryption: OFF
	Automatic HTTPS Rewrites: ON # cachetがfavicon.pngでhttpsエラーを吐くからである
Firewall : 
	Security Level : Essentially Off

GitLab Pages (Image Storage)

現在、画像ファイルの保存、読み込みを行うサーバーにgitlab.io(具体的には、GitLab Pages)を利用しています。

しかし、このサーバーは反応が遅いので、できれば、GitHubの方に移行するか、若しくはアップロードや管理がしやすい他のサーバーに移行することを考えています。

どうやってアップロードするのかというと、これは手動(自動化は可能)になるのですが、以下の方法が最も適切です。

host_meta=".well-known//webfinger?resource"

surl=$protocol://$host/$api_url/accounts/1/followers
sjson=`curl -sSL $surl -H "Authorization: Bearer $access_token"`
sbody=`echo $sjson|jq -r '.[]|.acct'`
sn=`echo "$sjson" | jq length`
if [ "$sn" != "0" ];then
	sn=$((${sn} - 1))
fi

for ((i=0;i<=$sn;i++))
do
	avatar=`echo $sjson|jq -r ".[$i]|.avatar"`
	echo $avatar
	acct=`echo $sjson|jq -r ".[$i]|.acct"`
	url=`echo $sjson|jq -r ".[$i]|.url"|cut -d / -f -3`
	output=`curl -sSL -H "Accept: application/json" "$url/${host_meta}=acct:${acct}"|jq -r '.links|.[]|select(.type == "application/atom+xml")|.href'`
	curl -sL $output | awk -vRS="</logo>" '/<logo>/{gsub(/.*<logo>|\n+/,"");print;exit}'
done

gurl=$protocol://$host/$api_url/accounts/1/following
gjson=`curl -sSL $gurl -H "Authorization: Bearer $access_token"`
gbody=`echo $gjson|jq -r '.[]|{avatar,acct,url}'`
gn=`echo "$gjson" | jq length`
if [ "$gn" != "0" ];then
	gn=$((${gn} - 1))
fi

for ((i=0;i<=$gn;i++))
do
	avatar=`echo $gjson|jq -r ".[$i]|.avatar"`
	acct=`echo $gjson|jq -r ".[$i]|.acct"`
	url=`echo $gjson|jq -r ".[$i]|.url"|cut -d / -f -3`
	if ! echo "$gjson"| jq -r ".[]|select(.acct == \"${acct}\")" > /dev/null 2>&1;then
		output=`curl -sSL -H "Accept: application/json" "$url/${host_meta}=acct:${acct}"|jq -r '.links|.[]|select(.type == "application/atom+xml")|.href'`
		curl -sL $output | awk -vRS="</logo>" '/<logo>/{gsub(/.*<logo>|\n+/,"");print;exit}'
	fi
done

GitLabへのpushはtokenなどを使って自動化することもできます。

このコードは.well-known//webfinger?resourceからatomを取得して、atomからavatarのURLを取得します。元となるデータはフォロー、フォロワーを参照します。本来的にはタイムラインも含めるべきですが(リツイートなどで表示する用)、タイムラインを気にしだすとそれを実行する頻度を頻繁にしなければなりませんし、それをやるストレージを用意しようとすると、やはり料金がかかってくると思われます。また、個人インスタンスなのでそれほど必要にはなりません。

DBのROWS消費が激しい問題

特定の時期に必ずDBのROWSが一気に増加する現象に度々遭遇しています。

それは、インスタンスの接続数が増えてきた時に起こりますが、接続数だけが問題かというとそうではないと考えています。

ここで、以下を実行することによりROWSを抑え、減少させることができました。

  • 管理 -> アカウント -> 特定のユーザー -> 完全に活動を停止させる -> 停止から戻す(復帰させる, 再開する)

これを全体的に実行させた後、DBのROWSがかなり減少しました。つまり、これらを実行することで、DBに保存されていたデータが削除される処理が実行されるようです。

と言っても、接続数が50程度でDBのROWSを1日で10,000消費することは通常ありえません。ということは、特定のユーザーないしはbotがDBの消費量を一気に上げているのではないかと考えられます。そういったインスタンスと接続している故なのか、若しくは大手インスタンスにそういったユーザー、ないしはbotが紛れ込んでいるのかは分かりませんが、DBを節約したいならそのへんは考えて、それらを抑制することが重要になってきます。

しかし、完全に活動を停止させると、そのユーザー情報に異変が生じます。具体的には、フォローしていた状態でそれを実行することで、そのユーザーへのフォローが解除され、復活させると再びフォローした状態に戻るのですが、そこで相手方にフォロー通知が届きました(他のMastodonインスタンスで確認, loadaverageのインスタンスでは確認されなかった模様)。

次の問題は、フォロワー欄に相手方が復活しない問題です。これは相手方が一旦こちらをフォロー解除し、再びフォローを実行することで復活する模様。

その他にも完全停止させている状態の時は、通知が来ませんし、またアバターも消えてしまいます。つまり、やり取りが事実上できない状態になってしまうのです。これでは、SNSとしての機能を失ってしまいますので、よくありません。

ということで、DBの消費を抑えつつ、最低限、相手とのやり取りに通知が可能な状態を維持することが目標になります。

ここで、サイレンスという機能を使うことで、通知は可能ですし、サイレンスはフォローしている人以外タイムラインに表示しない機能です。これを利用することで、DBへの消費が抑えられるかもしれません。

現在、DB消費が激しい問題は、/timeline/publicの情報をDBに保存しているからだと考えられます。このtimeline/publicは、当該インスタンスが接続しているフォローしていないユーザーの情報が書き込まれるため、それが原因だと思われます。

ということで、ドメインブロックで一括してサイレントを実行すれば、DBのROWSをある程度抑えることができると思います。

しかし、このサイレント(silence)は一括して実行するコマンドがありません。管理者画面で操作するしかなさそうな感じなので、そのあたりが辛い。

keybase.io

この度、keybase.ioでsyui.mlのドメインを認証しました。管理者の場合はドキュメントに従ってファイルをアップロードするわけですが、ユーザーの場合は以下のように署名メッセージをtootすればいいでしょう。

$ keybase sign -m "Hi! I'm @syui@syui.ml on Mastodon."
$ echo "BEGIN KEYBASE SALTPACK SIGNED MESSAGE. kXR7VktZdyH7rvq v5weRa0zkUoNUqZ OqTECTjU1QiHAv4 xzoy3uHNorgXQOG 8NRRqG1wSjpk1ty ibQ3OlfRNMpV1Cf badGHr1vJQZbAuE KVGaeoWXBTeOxLk gu1tp0ukOw2Xkg5 KdKa0J90MTq7C5J LeEdPwW9j1LFKa8 59LLeCUIq6kBIki HhsAMggwl33bcy8 hlNaQLwskpC98fi Yhb4C0iqjFXx9uI NAj6QuM. END KEYBASE SALTPACK SIGNED MESSAGE." | keybase verify

db:resetの弊害

db:resetしたあとは、一定期間、接続されているインスタンスとのやり取りが不可能になるかもしれない。

一旦、db:resetしてアカウントを作成し直すと通知などが以前接続していた先に届かなくなります。つまり、やり取りができなくなるということですが、これを回避する方法が分かりません。

db:reset前のgenerate_vapid_keyを保存しておいて、これを使用することでもしかしたらやり取りができるようになるかもしれません。(結論として無理でした)

$ heroku config:set VAPID_PUBLIC_KEY=XXXXXXX -a $app
$ heroku config:set VAPID_PRIVATE_KEY=XXXXXXX -a $app

その他、SECRET_KEY_BASE, PAPERCLIP_SECRETPROCESS_SCHEDULER_URL, OTP_SECRETなるものがあります。これらを保存しておいて、作り直す際は、これらを使用することで、もしかしたら他インスタンスとの接続において以前の状態を復元できるかもしれません。

ここで特に重要になりそうなのがSECRET_KEY_BASE, PAPERCLIP_SECRET, OTP_SECRET ,VAPID_PRIVATE_KEY, VAPID_PUBLIC_KEYです。

実験として以下の様なことをやってみました

  • 過去に作ったサブのインスタンスを復活させた

  • 接続には入っていた。既知のインスタンスにも登録されていた。以前フォローしたことがあるが、解除していた。復活後は普通にやり取りは可能であり、通知も可能だった。

  • 再度、そのインスタンスを削除して、再び同じものを作成してみると、こちら側(sub)は通知が来たが、相手には行かなかった。これは、mastodon:dailyなどを実行しても変わらずだった。また、SECRET_KEY_BASE, PAPERCLIP_SECRET, OTP_SECRETを以前のものにしてみるも、やり取りが可能になることはなかった(相手に通知が行くことはなかった)。完全停止させて、復活させてみても同様。

  • そして、試しに復活させたインスタンスのアカウントからmastodon.socialに投げてみると、通知が行ったし、以前設定したアイコンが設定されていた。しかし、SECRET_KEY_BASE, PAPERCLIP_SECRET, OTP_SECRETも以前とは違うものなので、通知の可否において、この変数は無関係かもしれない。

間を置いてやり取り可能になっているというのは、期限が切れたものを再取得する際に更新される情報が鍵なのかもしれません。つまり、dailyやsidekiqで期限切れを起こさない限り以前のインスタンス接続情報が有効になるので、新しく作った同じアドレスのアカウントではやり取りができないということです。一度期限切れを起こすことで、初めてやり取りが可能になるのかもしれません。あと、復活させるまでアドレスは完全にない状態で404でした。その辺も関係してくるのだろうか。

http://cryks.hateblo.jp/entry/2017/04/18/125351

とりあえず実行したコマンド一覧

heroku run rake db:migrate -a $app
heroku run rake db:seed -a $app
heroku run rake assets:precompile -a $app
heroku run rake mastodon:daily -a $app
heroku run rake mastodon:media:remove_remote -a $app
heroku run rake mastodon:media:remove_silenced -a $app
heroku run rake mastodon:push:clear -a $app
heroku run rake mastodon:feeds:clear_all -a $app
heroku run rake mastodon:feeds:build -a $app
heroku repo:purge_cache -a $app
heroku ps:restart -a $app

とりあえずsidekiqで色々と手動実行する

# 管理 -> sidekiq
# heroku run rake mastodon:push:refresh -a $app
# スケジュールに設定されている項目を手動実行してみます
  • メインの方で当該インスタンスアカウントを完全に停止させるとメインの方のフォロワー欄から削除されました。それまではフォロワー欄から消えることはなく、残り続けていました。

  • vapid_keyを同じものにしてもやはりダメだった。

  • その後、やり取りができたインスタンスのDBをrestoreしてみたけど、無理だった(userは同じものを作り直した)。

参考

https://github.com/zunda/mastodon/wiki