Creatodonで利用しているOSのバージョンを上げた話

はじめに

これは僕が運営・管理しているCreatodonというMastodonのサーバーでOSのアップデート対応をした時の備忘録になります。

普段通りにMastodonの最新のソースコードに追従したところ、ローカル環境でテストが落ちておりその余波で本番環境のOSをアップデートまでやった話になります。

背景

僕が管理しているCreatodonでは数日おきにMastodonの最新のソースコードに追従しています。 これは開発版を使うことでMasotodon本体の問題などを検知しやすくする意味合いと、単に僕が最新機能を使いたいというところからこういった対応をしています。

で、直近で入った以下のAVIF形式の画像を投稿できるようにした修正の影響でローカル環境でテストが落ちるという状況が発生していました。

github.com

修正自体は、Web UIなどからAVIF形式の画像を受け取り、それをサーバー側でPNG形式に変換するというもの。

テストが落ちていたのはこの変換部分の処理で呼び出されているImageMagickでAVIF形式の画像をサポートできていなかったためでした。

そこでImageMagickでのAVIF形式のサポートを調査し、対応を進めていた感じです。

またローカル環境としては

  • Ubuntu 20.04 LTS(WSL)
  • Ruby 3.3.0-dev
  • Node.js v20

を利用している状況でした。

また本番環境もローカルと同じものを利用している状況です。

ImageMagickでのAVIFサポートについて

現状ではImageMagick 7 と 6系列の一部ではAVIF形式の画像をサポートしているようです。

kmy.blueの鯖缶さんが書かれた以下の記事などでもわかるようにAVIF形式の画像をサポートできるのはImageMagick 7系列からのようです。

note.com

ただ上の記事中に言及されているように、Masotodon公式ではImageMagick 6系列を利用しており、CI上でもテストが落ちていませんでした。

そこで追加の調査を進めたところ、CI上などで利用されているUbuntu 22.04 LTSでインストールできるImageMagick 6は20.04のものとバージョンが異なっており、かつAVIF形式の画像をサポートしているということが分かりました。

そのため、Ubuntu 20.04を利用しているサーバーでAVIFへの対応する場合の方針としては

のどちらかになりそうでした。

最終的な対応について

結論から言うとCreatodonではUbuntu 22.04へとOSを上げる方向で対応しています。

というのも該当のバージョンでImageMagickをビルドしようとしたところlibheifなどの関連ライブラリがaptで取ってこれなかったんですよね。 なのでlibheifをソースコードからビルドしようとしたところ、さらにその依存先のライブラリがそのまますんなりビルドできなさそうな感じにもなっていたという......。

またいずれにしてもOSのアップデート対応をする必要は出てくるので、今回はOSを上げる方向で対応した感じです。

今後

一旦Ubuntu 22.04にあげる方向で対応できましたが、すべてのサーバーでそれをやるのは厳しいよなぁと思ってます。 なので、また別途ImageMagickをビルドする方向で対応できないか調査してみたいと思ってます。

esaで書いたリリースノートを自動でMastodonに投稿するようにした

はじめに

表題の通り、esaで書いたリリースノートを自動的にMastodonへ投稿するようにした時の覚書になります。

 

背景

CreatodonというMastodonのサーバーを運営しており、常に最新のMastodonソースコードに追従するようにしています。

gamelinks007.net

 

で、僕以外のユーザーさんへどういった変更が取り込まれたかをリリースノートととしてesaに以下のようにまとめていました(実際には僕が後で変更差分を振り返る時にも使ってたりもします)

 

esa-pages.io

 

このリリースノートを書いたうえで、

  • ソースコードの追従作業を行う
  • 動作確認
  • 本番環境への反映対応
  • リリースノートの投稿
  • お知らせ機能でのリリースノートの共有

などを対応していました。

 

動作確認についてはCypressでのE2Eテストやテストコードの拡充などで自動化が済んでiいます。

本番環境への反映作業についてもGitのフックを使い、GitHubからソースコードをpullする際におおよその作業は自動化していたりします。

 

ただ、リリースノートの投稿などに関しては毎回手作業で対応しており、ちょっと面倒くさくなっていました。

 

そこで、そのあたりを今回自動化してみたという感じです。

 

やったこと

 

基本的には

  • Masotodon側でAPIトークンを生成
  • esaで書いたリリースノートを外部記事として公開する際にWebhookをLambdaに投げる
  • Lambdaで受け取ったWebhookの内容を調整し、APIトークンを使ってMastodonへと投稿する

という感じで対応しました。

 

esaのWebhookについては以下のドキュメントを参照しつつ対応。

docs.esa.io

 

docs.esa.io

 

Lambdaに関しては、HTTPSエンドポイントが利用できるようになっているのでそちらを使ってサクッと対応しています。

 

dev.classmethod.jp

 

あとはLambdaで受け取ったWebhookを調整してMastodonへ投稿する処理を以下のように書いた感じです。

github.com

 

実際に投稿できたもの

gamelinks007.net

 

こんな感じで外部記事として公開すると自動的に投稿出来ていい感じですねー。

 

 

LR parser 101を完走した

はじめに

yui-knk/lr-parser-101 というRaccを使って電卓を実装しつつ、パーサージェネレーターを利用したパーサーの作り方を学ぶチュートリアルを完走した時の覚書です。

僕個人のパーサー周りの経験としては

という感じです。

なので、どちらかというと全くの初心者という感じでもないところからの覚書になります。

LR parser 101とは?

Bison Killerこと yui-knk さんが書かれた電卓を実装するチュートリアルです。

github.com

Raccというパーサージェネレーターを使いつつ、パーサーの作り方を学ぶファーストステップとして非常に良いチュートリアルです。

やってみて

基本的にリポジトリをCloneしてきて、各チャプターの内容をこなしていく感じでしたー。 チャプターのボリューム的にもちょうどよい感じで、ハンズオン的な感じで2時間くらいの枠でやってみると意外といいかもと思いましたねー。

それと個人的にいいなぁと思ったのはRubyっぽいコードからパーサーの作り方を学べる感じな点ですねー。 Rubyに慣れている人であれば、拡張BNF記法周りのドキュメントなどを参照しながら進めると捗るかなと思いました。

あと各チャプターにsample.yというサンプルがあり、それを元にパーサーを作っていくので進めやすいのも非常に良かったです。 再帰的な文法ルールの定義とか掛け算や割り算の優先順位などもわかりやすく書かれているので非常に良いチュートリアルになってます。

今後

Hamada.rbでRuby Hacking Challenge in Hamada.rbというRuby内部のソースコードを読んだりハックしたりする会をやっているので、そこでハンズオン的な感じでこのチュートリアルやってみてもいいかなと思いましたー。 で、チュートリアルをやりつつ実際のparse.yのコードと見比べてみるとかも面白そうかなと思ってますねー。

MastodonでNSFWな画像が投稿される際にNSFWを付与するように手を入れてみた

## はじめに

 

Misskey で導入されているNSFWな画像に対して自動でNSFWが付与されるという機能をMastodonにも導入してみました。

 

過去にも、以下の記事で記載しているように自動NSFW機能を導入してみたことはあるんですが、如何せん外部APIを使う関係でコストがかなりかかる状況でした。

gamelinks007.hatenablog.com

 

また画像を投稿する際に毎回チェックが実行されるため投稿処理が重くなるということも発生していました。

 

そういった背景から機能自体は廃止していたんですが、直近で作ったBotで利用しているopennsfw2をPyCallから使える可能性が出てきたので試しに実装してみました。

 

gamelinks007.hatenablog.com

 

やったこと

Rubyでopennsfw2が実行できるかを調査

実際に調査で作ったサンプルは以下になります。

github.com

 

基本的にはPyCallに記載されているサンプルコードを元にopennsfw2を呼び出し、それを使うようにしているだけです。

 

github.com

 

実際にNSFWな画像を用意し、動作確認なども行ってみました。

NSFWな画像を正しく検知できていました。

 

Mastodon側のコードを修正

次にMastodon側のコードを修正し、投稿に紐づいた画像をチェックするようにしてみました。

 

実際に作ったものとしては以下になります。

github.com

 

実際にローカルの開発環境下で確認し、正常にNSFWを付与できているようでした。

 

ただ、画像自体のチェックにかなり時間がかかるため投稿処理がかなり重くなります。

実運用まで考えるのであれば、はより軽量なモデルを使うか処理のボトルネックを調査していく必要はありそうです。

 

今後

 

とりあえず、機能の実装自体は出来たので良かったですねー。

今後実運用までもっていくかどうかは検討していきたいかなと思います。

 

まあ、より軽量なモデルがあればとかにはなりそうですが......。

Mastodonの自鯖にRBSを導入した

はじめに

この記事はMastodonRBSを導入したときの導入背景や手順などをまとめた記事になります。 主に僕個人の備忘録としてまとめています。

導入背景

2017年5月から6年ほどCreatodonというMastodonのサーバーを個人的に運営しています。

gamelinks007.net

直近で開発環境周りの体験向上としてCIの修正とかE2Eテストの追加とか色々やっていたんですが、型関係はまだ手を入れていませんでした。 また独自機能の実装やそのメンテナンスなども行っており、既存のModelServiceなどの型情報が簡単に確認できる状況が欲しくもなっていました。

そこで

  • TypeProfを使い、既存のModelServiceなどのRBSを自動生成
  • 既存のライブラリの型情報などはgem_rbs_collectionを使って導入
  • Steepで生成したRBSの型情報をVS Codeで表示&型情報のチェック

までをMastodonに導入してみました

やったこと

TypeProfを使い、既存のModelやServiceなどのRBSを自動生成

まずはMastodonにある既存のModelServiceContorollerなどの型をTypeProfを使って自動生成しました。

コードとしては以下のような型の自動生成用タスクを追加して対応しました。

# frozen_string_literal: true

require 'fileutils'

top_level = self

using(Module.new do
  refine(top_level.singleton_class) do
    def generate_type_signature(dir_name)
      files = Dir.glob("app/#{dir_name}/**/*")

      files.each do |file|
        next unless file.include?('.rb')

        result = `bundle exec typeprof #{file}`

        dir_name = File.dirname(file)

        FileUtils.mkdir_p("sig/#{dir_name}")

        File.write("sig/#{file}s", result)
      end
    end
  end
end)

namespace :type do
  namespace :prof do
    task models: :environment do
      generate_type_signature('models')
    end

    task controllers: :environment do
      generate_type_signature('controllers')
    end

    task helpers: :environment do
      generate_type_signature('helpers')
    end

    task lib: :environment do
      generate_type_signature('lib')
    end

    task presenters: :environment do
      generate_type_signature('presenters')
    end

    task serializers: :environment do
      generate_type_signature('serializers')
    end

    task services: :environment do
      generate_type_signature('services')
    end

    task validators: :environment do
      generate_type_signature('validators')
    end

    task workers: :environment do
      generate_type_signature('workers')
    end
  end

  task prof: ['prof:models', 'prof:controllers', 'prof:helpers', 'prof:lib', 'prof:presenters', 'prof:serializers', 'prof:services', 'prof:validators', 'prof:workers']
end

あとは欲しい場所(Modelならbundle exec rake type:prof:modelsのような感じで)の型情報をタスクとして実行すればOKです。

既存のライブラリの型情報などはgem_rbs_collectionを使って導入

次に既存のライブラリの型情報をgem_rbs_collectionを使って導入しました。

こちらのドキュメントを参考に以下のコマンドを実行。

rbs collection init

実行するとrbs_collection.yamlが生成されます。これを以下のように変更しています。

# Download sources
sources:
  - type: git
    name: ruby/gem_rbs_collection
    remote: https://github.com/ruby/gem_rbs_collection.git
    revision: main
    repo_dir: gems

# You can specify local directories as sources also.
# - type: local
#   path: path/to/your/local/repository

# A directory to install the downloaded RBSs
path: .gem_rbs_collection

gems:
  # Skip loading rbs gem's RBS.
  # It's unnecessary if you don't use rbs as a library.
  - name: rbs
    ignore: true
  - name: steep
    ignore: true
  - name: ipaddr

変更内容としては後述するSteepでの型チェック時にパスするために必要なライブラリの設定だけ追加してる感じです。

あとは.gem_rbs_collection.gitignoreに追加して、以下のコマンドを実行するだけです。

rbs collection install

これで既存のライブラリの型情報が導入できました。

Steepで生成したRBSの型情報をVS Codeで表示

次にSteepを導入し、TypeProfgem_rbs_collectionで導入した型情報をVS Code上に表示させます。

まずはGemfileSteepを追加してbundle installを実行。

gem 'steep'

その後bundle exec steep initを実行して、設定ファイルを生成します。

bundle exec steep init

生成されたSteepfileを以下のように変更します。

target :app do
  check 'app'

  signature 'sig'
end

checkには型情報をチェックしたい場所のディレクトリを指定し、signatureRBSがあるディレクトリを指定している感じです。

あとは以下のVS CodeExtensionをインストールし、VS Codeを再起動すればOKです。

github.com

再起動すると以下の画像のように型情報が表示されているかと思います。

型チェックがパスするように修正

次にbundle exec steep checkがパスするように修正しました。

やったこととしては

  • デフォルトの設定だと大量のエラーが出るので一時的にIgnore
  • TypeProfで生成された一部のRBSでの型情報が正しくないと表示されたのを修正
  • app/lib配下の一部のコードで重複エラーが返されたので修正

という感じです。

エラーを一時的にIgnoreするのは以下のようにSteepfileを修正しました。

ignores = [
  Steep::Diagnostic::Ruby::MethodDefinitionMissing,
  Steep::Diagnostic::Ruby::UnknownConstant,
  Steep::Diagnostic::Ruby::NoMethod,
  Steep::Diagnostic::Ruby::UnexpectedBlockGiven,
  Steep::Diagnostic::Ruby::IncompatibleAssignment,
  Steep::Diagnostic::Ruby::UnknownInstanceVariable,
  Steep::Diagnostic::Ruby::UnexpectedPositionalArgument,
  Steep::Diagnostic::Ruby::UnresolvedOverloading,
  Steep::Diagnostic::Ruby::InsufficientPositionalArguments,
  Steep::Diagnostic::Ruby::UnknownGlobalVariable,
  Steep::Diagnostic::Ruby::UnexpectedError,
  Steep::Diagnostic::Ruby::MethodBodyTypeMismatch,
  Steep::Diagnostic::Ruby::ArgumentTypeMismatch,
  Steep::Diagnostic::Ruby::UnsupportedSyntax,
  Steep::Diagnostic::Ruby::UnsupportedSyntax,
  Steep::Diagnostic::Ruby::UnexpectedSuper,
  Steep::Diagnostic::Ruby::UnexpectedYield,
  Steep::Diagnostic::Ruby::BreakTypeMismatch,
  Steep::Diagnostic::Ruby::RequiredBlockMissing,
  Steep::Diagnostic::Ruby::ReturnTypeMismatch,
  Steep::Diagnostic::Ruby::ImplicitBreakValueMismatch,
]

target :app do
  check 'app'

  signature 'sig'

  configure_code_diagnostics do |hash|
    ignores.each do |ignore|
      hash[ignore] = :information
    end
  end
end

あとTypeProfで生成されたRBSの型情報が正しくない件については、以下のようなRangeが返ってくると期待されているものでエラーが返ってきました。

    def show: -> Range

現状では泣く泣くuntypedに置き換えて、一旦パスするようにしています......。

    def show: -> untyped

app/lib配下のコードの重複エラーについては以下のように対応しました。

    def current_time: -> Float
                    | ...

今後

おおよその型情報についてはTypeProfgem_rbs_collectionのおかげでなんとかなっていますが、untypedなままのものも多い状況です。 なので、この辺を少しづつ片付けていきたいですねー。

参考

github.com

github.com

github.com

github.com

techlife.cookpad.com

pocke.hatenablog.com

github.com

zenn.dev

sinsoku.hatenablog.com

pocke.hatenablog.com

joker1007.hatenablog.com

Mastodonにリビジョンアップでの自動リロード機能を導入してみた

はじめに

この記事はMastodonにリビジョンアップという機能を追加したときの導入背景や実装などをまとめた記事になります。 主に僕個人の備忘録としてまとめています。

導入の背景などあれこれ

2017年5月から6年ほどCreatodonというMastodonのサーバーを個人的に運営しています。

gamelinks007.net

この半年くらいでユーザー数も増え、また連合するサーバーなども増え色々と管理コストなどもかかるようになってきていました。 (まあ、ユーザー数に関しては僕がサーバー運営周りで疲れ果てて、ユーザーの皆さんを引っ掻き回したので現状だと半年前と同じくらいまで減りましたけど......)

その辺の管理コストを削減するべく、最近いろいろと手を入れている状況でした。

Creatodonではmain追従と言い、最新のMastodonソースコードを定期的に追従して本番環境で動かすという運用をしています。 そのため頻繁にサーバーメンテナンスを行うことがありました。 その際に画面周りの新しい変更が反映されないことがあり、その辺の問い合わせ対応とかやってたりするのが面倒くさかったので今回リビジョンアップを導入した感じです。

なので、リビジョンアップを導入する前は - サーバーメンテナンス後にブラウザのリロードをお願いする(実際はほとんど画面周りの変更がなかったのであまりやってないですが) - 「画面周りの新機能が表示されないです」という問い合わせを起点に、「ブラウザのリロードかけてください」とアナウンス みたいなことをやってました。

で、こういうのは正直僕も面倒ですし、一々ブラウザをリロードしなきゃならないユーザーの皆さんも面倒です。

そこでリビジョンアップという機能を導入するべく対応を進めました。

ちなみにリビジョンアップについてはこちらの記事が詳しいですが、簡単に説明すると - SPAなWebアプリケーションで、新しいリリースがされた場合に古いバージョンがキャッシュされたままになっていることがある - そのためバックエンド側とのデータ不整合が発生する可能性やフロント側がクラッシュする可能性がある - それをフロント側とバックエンド側とでバージョン情報を保持し、バージョンが変更されたことを検知し、クラッシュなどを回避する というのがリビジョンアップという機能になります。

実際の挙動

以下の動画のように自動的にリロードを行うように動きます。 PWAなどで利用している場合でも自動的にリロードしてくれます。

gamelinks007.net

やったこと

実際のPRなど

実際の変更差分としては以下のPRを参照して頂ければわかるかと思います。

github.com

github.com

github.com

github.com

バックエンド側でリリースされているバージョン情報を返すように修正

MastodonではMASTODON_VERSION_SUFFIXMASTODON_VERSION_FLAGSという環境変数を設定するとv4.1.2@74c535e6f11a58acbba3bd0a3c1df283105c82afのようにAPIが返すバージョン情報を変更することができます。

これを流用し、リリースされているバージョン情報として最新のコミットのハッシュを使うようにしました。

まず以下のようなRubyのコードを追加しました。

# frozen_string_literal: true

# .env.productionを読み込む
env = File.read('.env.production')

# 最新のコミットのハッシュを取得
hash = `git show --format='%H' --no-patch`

# 最新のコミットのハッシュをMASTODON_VERSION_SUFFIXに設定
env = env.gsub!(/^MASTODON_VERSION_SUFFIX=.+$/, "MASTODON_VERSION_SUFFIX=#{hash}")

# .env.productionへ置き換えたものを書き込む
File.write('.env.production', env)

やっていることとしては最新のコミットのハッシュを取得し、正規表現を使って.env.productionにあるMASTODON_VERSION_SUFFIXへ置き換えつつ渡すようにしています。

あとはGitのpost-mergeというフックで以下のようなShellが実行されるようにしています。

#!/bin/sh

`ruby revision.rb`

post-mergeを使っている背景としては - 元々Creatodonは、さくらインターネットさんが提供しているMastodonスタートアップスクリプトで建てた - その関係でサーバーにログインし、Gitで新しいバージョンなどをpullする運用でメンテナンスしていた - なのでその運用フローをそのまま使いつつ、自動的にバージョン情報を更新するのにpost-mergeが良さそうだった という感じですね

フロント側でバージョン情報を取得し、バージョンが変わっていればリロードする機能の追加

フロント側ではAPIから取得できるバージョン情報をブラウザのlocalStorageに保存するようにしています。 またlocalStorageに保存されているバージョンとAPIが返すバージョンが異なっている場合にリロードする処理も追加しています。

import axios from 'axios'

const setLocalStorageVersion = (version) => {
  localStorage.setItem('VERSION', version)
}

const getLocalStorageRevision = () => {
  if (!localStorage.getItem('VERSION')) {
    return ''
  }
  return localStorage.getItem('VERSION')
}

const checkRevision = () => {
  axios.get('/api/v2/instance').then(response => {

    const current_version = response.data.version;

    const user_cached_version = getLocalStorageRevision();

    if (user_cached_version !== current_version) {
      alert('新しいリリースが出ています。自動的にリロードを行います。');
      setLocalStorageVersion(current_version);
      location.reload(true)
    }
  }).catch(error => { // eslint-disable-line
  });
}

あとは適当な場所にcheckRevision()を追加してる感じですね。

おわりに

とりあえず、こんな感じでリビジョンアップをMastodon本体に最小限の変更で導入できました。 今後は、画面に表示されているメッセージが簡素すぎるのでその辺をいじりたいですねー。

参考

qiita.com

dev.classmethod.jp

tech.smartcamp.co.jp

zenn.dev

Creatodonの独自機能向けにテストコードを追加した

はじめに

以前からCreatodonはMastodonの最新のソースコードに追従する運用で動かしていました。

ただ、ここ半年の間で独自機能などをいくつか導入した影響もあり、最新のソースコードに追従する際に独自機能周りの挙動を手動で確認するという面倒くさいことをやっていました。

 

さすがにこういうのをずっと続けるのは現実的ではないなと思い、独自機能をテストするコードを追加しました。

 

これはその時にやったあれこれのまとめになります。

 

やったこと

まずService層などのテストコードを以下のPRで追加しました。

 

github.com

 

github.com

 

次に、UI上からしか確認できない挙動などに関しては以下のPRでE2Eテストを追加しました。

github.com

 

E2Eテストで利用するフレームワークとしてはCypressを使いました。

 

www.cypress.io

 

基本的にはCypressのドキュメントにある手順で設定などを追加。

 

困った点としては、僕が利用している環境がWSLだったので、ypress openでCypressの設定画面がそのままでは表示されないという......

なのでVcXsrv周りの設定などで少し時間がかかりました......。

 

あとはWebpacker側でビルドしたアセットなどが正しく読み込まれないこともあり、以下のIssueを参考に「experimentalSourceRewriting: true,」などの設定を追加して対応。

 

github.com

 

とりあえず、これでE2Eテストが実行できるようになりました。

 

今後など

今後はできるだけ既存機能のテスト追加をしつつ、最新のソースコード追従への対応コストを削減できるようにしたいですねー。

 

あと基本的にはE2Eテストはできるだけ追加しない方向でいきたい......(E2Eテストはメンテナンスがしづらくなりやすいので......)