Gerbera 1.11.0のインポートスクリプトをいじる

Gerbera 1.4.0相当までは、旧mediatombのインポートスクリプトがそのまま使えました。

しかし、Gerbera 1.11.0になってから、インポート方法が変わり、インポートそのものは今まで通りaddCdsObject()を使ってインポートするのですが、第二引数であるコンテナツリー指定の形式が変わったようで、今までは単なる配列を指定するだけでしたが、これからはいったんaddContainerTree()を使って変数にコンテナツリーを代入してから、addCdsObject()の第二引数にその変数を指定する方法に変わりました。

面倒そうな話ですが、こうなった理由はとても合理的で、複数値を持ったメタデータに対応するためです。
一番わかりやすいのがジャンル(genre)
音楽のジャンルが多様化しているので、テクノポップなんかはGENRE=["POP","TECNO","TECNO_POP"]みたいな感じになると思うんですが、今までだとこの曲は
Audio - Genre - POP TECNO TECNO_POP
となるか
Audio - Genre - POP
となって、TECNO以下切り捨てみたいになっていたと思います。
(ジャンルを例に出しましたが、それほどジャンル検索をしたことがないです。)

それを、
Audio - Genre - POP
Audio - Genre - TECNO
Audio - Genre - TECNO_POP
どこからでもアクセスできるようにする、という設計思想になっているようです。
理にかなっていますが、配列の中に配列を入れられるよう、コンテナツリーをaddContainerTree()を使って代入することになったようです。

更に、これらのコンテナツリーは、どのようなオブジェクトなのか、オブジェクトタイプを予め定義する必要があるようです。

んで、本題。
うちの音楽ライブラリは4つのフォルダ(ディレクトリ)に分けて格納しています。
gentoolinux.hatenablog.com
今回、HDD交換を機に、ディレクトリがツリー分下がっています。つまり、
/home/files/mp3/Album
/home/files/mp3/Single
/home/files/mp3/SuperEurobeat
/home/files/mp3/Wife
という構造に変えました。
この第4階層をAudioコンテナの次に表示し、DLNA上では
Audio - Album
Audio - Aingle
Audio - SuperEurobeat
Ausio - Wife
と、一つ階層を増やし、膨大な音楽ファイルを選びやすくしたいと思います。

これを実現するため、今まではimport.jsを改変していましたが、Gerbera 1. 11.0からはcommon.jsを改変する必要があります。
import.jsもcommon.jsも、1.11.0デフォルトのスクリプトとし、common.jsを、この1.11.0のデフォルトのものから改変していきます。
まずは、音楽ファイルのフルパスを変数tmpfilelocに格納します。パスが格納されているのはorigオブジェクトのlocationプロパティです。なので、
var tmpfileloc = orig.location;
そこから、/で分割した値をfilelocの変数に配列として代入します。
var fileloc = tmpfileloc.split('/');
最終的にディレクトリの第4階層をコンテナ名の変数u_pathに代入します。
var u_path = fileloc[4];

これらを、できるだけ音楽ファイルが読み込まれるインポートファンクションの前段に記載します。
今回、音楽ファイルのインポートはaddAudio()とaddAudioStructured()がありますが、今回はaddAudio()を使います。

// doc-add-audio-begin
function addAudio(obj)
の直後くらいに記述しました

// doc-add-audio-begin
function addAudio(obj) {
    // Note the difference between obj.title and obj.metaData[M_TITLE] -
    // while object.title will originally be set to the file name,
    // obj.metaData[M_TITLE] will contain the parsed title - in this
    // particular example the ID3 title of an MP3.
    var title = obj.title;
    var tmpfileloc = orig.location;
    var fileloc = tmpfileloc.split('/');
    var u_path = fileloc[4];

続いて、今までにないコンテナとなるわけなので、コンテナ定義を追加します。
コンテナの定義は
const chain = {
という変数代入に続いて、1行づつ定義されています。例えば
audio: { title: 'Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: },
という定義は、Audioという固定名称のコンテナです。オブジェクトの内容によって変更されることはありません。
対して、
composer: { title: composer, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER, metaData:
, res: obj.res, aux: obj.aux, refID: obj.id },
は、作曲家名のコンテナになるので、オブジェクト(=音楽ファイル)によって、コンテナ名は変動します。composer変数に代入されている作曲家名がコンテナ名になります。どのオブジェクト(=音楽ファイル)の作曲家名をコンテナにするのか? を、後半のres: obj.res, aux: obj.aux, refID: obj.idで、定義しています。(多分obj.idが重要なのだと思う・・・。)
なので、先ほどのu_pathに代入されている値をコンテナ名=titleにします。
u_path: { title: u_path, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },

このあたりのコンテナ定義をさらすと

    // The UPnP class argument to addCdsObject() is optional, if it is
    // not supplied the default UPnP class will be used. However, it
    // is suggested to correctly set UPnP classes of containers and
    // objects - this information may be used by some renderers to
    // identify the type of the container and present the content in a
    // different manner.

    // Remember, the server will sort all items by ID3 track if the
    // container class is set to UPNP_CLASS_CONTAINER_MUSIC_ALBUM.

    const chain = {
        audio: { title: 'Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [] },
        allAudio: { title: 'All Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allArtists: { title: 'Artists', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allGenres: { title: 'Genres', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allAlbums: { title: 'Albums', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allYears: { title: 'Year', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allComposers: { title: 'Composers', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allSongs: { title: 'All Songs', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allFull: { title: 'All - full name', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        artist: { searchable: false, title: artist[0], objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        album: { searchable: false, title: album, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        genre: { title: genre, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        year: { title: date, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        composer: { title: composer, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        u_path: { title: u_path, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },

   };

定義したら、いよいよaddContainerTree()にu_pathを定義していきます。順番としてはAll Audioの次に、第4ディレクトリを表示してもらいます。
例として、All Audioにパスのコンテナを追加します。

var container = addContainerTree([chain.audio, chain.u_path, chain.allAudio]);
addCdsObject(obj, container);

このあたりのデータベースを代入数するスクリプトを表示します。

    const chain = {
        audio: { title: 'Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [] },
        allAudio: { title: 'All Audio', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allArtists: { title: 'Artists', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allGenres: { title: 'Genres', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allAlbums: { title: 'Albums', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allYears: { title: 'Year', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allComposers: { title: 'Composers', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allSongs: { title: 'All Songs', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        allFull: { title: 'All - full name', objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
        artist: { searchable: false, title: artist[0], objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        album: { searchable: false, title: album, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        genre: { title: genre, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        year: { title: date, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        composer: { title: composer, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },
        u_path: { title: u_path, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER, metaData: [], res: obj.res, aux: obj.aux, refID: obj.id },

   };

    chain.audio.metaData[M_CONTENT_CLASS] = [ UPNP_CLASS_AUDIO_ITEM ];
    chain.album.metaData[M_ARTIST] = artist;
    chain.album.metaData[M_ALBUMARTIST] = artist;
    chain.album.metaData[M_GENRE] = [ genre ];
    chain.album.metaData[M_DATE] = obj.metaData[M_DATE];
    chain.album.metaData[M_UPNP_DATE] = obj.metaData[M_UPNP_DATE];
    chain.album.metaData[M_ALBUM] = [ album ];
    chain.artist.metaData[M_ARTIST] = artist;
    chain.artist.metaData[M_ALBUMARTIST] = artist;
    chain.genre.metaData[M_GENRE] = [ genre ];
    chain.year.metaData[M_DATE] = [ date ];
    chain.year.metaData[M_UPNP_DATE] = [ date ];
    chain.composer.metaData[M_COMPOSER] = [ composer ];
//    chain.u_path.metaData[M_U_PATH] = [ u_path ];

    var container = addContainerTree([chain.audio, chain.u_path, chain.allAudio]);
    addCdsObject(obj, container);

    container = addContainerTree([chain.audio, chain.u_path, chain.allArtists, chain.artist, chain.allSongs]);
    addCdsObject(obj, container);

    var temp = '';
    if (artist_full) {
        temp = artist_full;
    }

    if (album_full) {
        temp = temp + ' - ' + album_full + ' - ';
    } else {
        temp = temp + ' - ';
    }

    obj.title = temp + title;
    container = addContainerTree([chain.audio, chain.u_path, chain.allFull]);
    addCdsObject(obj, container);

    const artCnt = artist.length;
    var i;
    for (i = 0; i < artCnt; i++) {
        chain.artist.title = artist[i];
        container = addContainerTree([chain.audio, chain.u_path, chain.allArtists, chain.artist, chain.allFull]);
        addCdsObject(obj, container);
    }

    obj.title = track + title;
    for (i = 0; i < artCnt; i++) {
        chain.artist.title = artist[i];
        chain.artist.searchable = true;
        container = addContainerTree([chain.audio, chain.u_path, chain.allArtists, chain.artist, chain.album]);
        addCdsObject(obj, container);
    }

    chain.album.searchable = true;
    container = addContainerTree([chain.audio, chain.u_path, chain.allAlbums, chain.album]);
    obj.title = track + title;
    addCdsObject(obj, container);

    chain.genre.searchable = true;
    if (obj.metaData[M_GENRE]) {
        for (var oneGenre in obj.metaData[M_GENRE]) {
            chain.genre.title = obj.metaData[M_GENRE][oneGenre];
            chain.genre.metaData[M_GENRE] = [ oneGenre ];
            container = addContainerTree([chain.audio, chain.u_path, chain.allGenres, chain.genre]);
            addCdsObject(obj, container);
        }
    }

    container = addContainerTree([chain.audio, chain.u_path, chain.allYears, chain.year]);
    addCdsObject(obj, container);

    container = addContainerTree([chain.audio, chain.u_path, chain.allComposers, chain.composer]);
    addCdsObject(obj, container);
}
// doc-add-audio-end

これで、Audioの下に、フォルダ第4階層が疑似的に表示されるようになります。

Garbera P1.11.0で音楽ファイルがインポートされない場合の対処法

Gerbera on GentooをP1.11.0にバージョンアップしてからだと思いますが、音楽ファイルがインポートされなくなりました。
当初、インポートされなかったのは1アルバム分だったので、「後で対処しよう。」と、思っていたのですが、音楽ファイルを大量に保存したHDDを交換ついでに、保存ディレクトリを1階層下げたため、dbを消去し、全ての音楽ファイルをインポートしようとしたところで気づきました。

Gerbera 1.11.0 import option.

上記はインポート画面のオプション指定。
ごくごく普通のinotifyオプションです。
ですが、イメージファイルもビデオファイルもプレイリストファイルもインポート出来るのに、音楽ファイルだけがインポートできません。

インポート方法を指定するjsファイルである、import.jsやcommon.jsを疑ったのですが、インポート途中にprint命令を入れて、ログに何か残るかと見てみても、イメージファイルとビデオファイルはupnpmimetypes属性がしっかりと表示され、ファイルスキャン順に与えられるIDもログに表示されましたが、音楽ファイルはいっこうに表示されません。
試しに、config.xlm内のインポート方法をbuiltinに変え、jsファイルを使わずにインポートをしてみても、さっぱりです。この場合、ログにも一切表示されません。
mapがおかしいのかとにらめっこしても、保存されているFLAC、mp3、m4aは全てaudio/*というmipe typesが指定されています。
万事休す・・・。

ところで、と思ってインポートオプションを変更してみたらどうなるか? と、思い、All Audioだけでなく、Musicにもチェックを入れてインポートしてみたところビンゴ!
しっかりと全ての音楽ファイルがインポートされました。
All Audioって一体・・・。

Gerbera 1.11.0 Check Music for import music files.

上記のMusicにチェックすることが大事です。

困っている人のために、英語でも書いておきます。

In "Gerbera 1.11.0", if you can not import only music files, FLAC,MP3,M4A and so on, you can import music files by checking not only "All Audio" but also "Music" in the import settings.
Refer to the image above.

clamav-unofficial-sigsからfangfrischへ

Gentoo WiKiによると、clamav-unofficial-sigsはセキュアじゃないとのこと。

wiki.gentoo.org

上記によると、ClamAVでUnofficial Signaturesを扱う方法は2つ。
ひとつめはfangfrischを使う方法。
もうひとつはfreshclamにDatabaseCustomURLを追加する方法。

簡単なのはfreshclamだけど、この場合sanesecurityのシグネチャーしか使えなさそうで、しかも、シグネチャーの名称変更などには追従できないそうです。
そうすると、fangfrischを使うほうが良さげ。

では、clamav-unofficial-sigsをアンインストールしましょうか。

# emerge -C clamav-unofficial-sigs
(進行ログ省略)
# rm -r /etc/clamav-unofficial-sigs
# rm -r /var/lib/clamav-unofficial-sigs
<||

でもって、fangfrischをインストール
>|sh|
# emerge fangfrisch

最初にディレクトリを作成するそうです。

# mkdir -m 0770 /var/lib/fangfrisch
# chgrp clamav /var/lib/fangfrisch

んで、コンフィグ/etc/fangfrisch.confをチェック。

# See https://rseichter.github.io/fangfrisch/ for detailed documentation

[DEFAULT]
db_url = sqlite:////var/lib/fangfrisch/db.sqlite
local_directory = /var/lib/clamav
log_method = syslog
log_target = /dev/log
on_update_exec = clamdscan --reload

[malwarepatrol]
enabled = no
# Replace with your personal Malwarepatrol receipt
#receipt = abcd1234

[sanesecurity]
enabled = yes

[securiteinfo]
enabled = yes
# Replace with your personal SecuriteInfo customer ID
customer_id = abcd1234

[urlhaus]
enabled = yes

malwarepatrolはフリーだと商用利用不可とのことだったので、実家のサーバーには使わないことにしました。
securiteinfoはBasicで登録したので、1日遅れのシグネチャーでダウンロードが300kbpsに制限されるそうです。
もちろんcustomer_idはabcd1234ではありません。

で、データベースファイルを作ります。

# sudo -u clamav -- fangfrisch -c /etc/fangfrisch.conf initdb

あとは、cronで自動的にアップデートさせます。
/etc/cron.d/fangfrischの先頭コメントアウトを外して待つだけです。

HOME=/var/lib/fangfrisch
LOG_LEVEL=WARNING
# minute hour day-of-month month day-of-week user command
*/10 * * * * clamav fangfrisch -c /etc/fangfrisch.conf refresh

さて、うまく動作するでしょうか?

Raspberry piのgmediarenderをgmrender-resurrectに入れ替え

今までRaspberry piDLNA (UPnP)のrendererをgmediarenderにしてきました。

gentoolinux.hatenablog.com

しかし、gmediarenderは古くてメンテナンスされていないせいか、Raspberry piには荷が重く、挙動がおかしくなります。
挙動がおかしくなる一例として
・再生後、停止や一時停止ができない。
・次の曲に行かない。
DMCのシークバーも再生時間も動かない。
・曲が停止してもALSAを占有し続ける。
4項目はPulseAudioにでもすればよいのでしょうが、そでも1~3は解決しません。
(PluseAudioは今、悪戦苦闘中)

で、これを解決する軽量レンダラーとしてgmrender-resurrectがgitに上がっています。

github.com

一般的なインストール方法が書かれていますが、Raspberry pi向けに参考にしたのがコチラ

blog.scphillips.com

英語で書かれているのですが、わかりやすいです。
Raspberry piでmakeするので、だいぶ時間かかるかと思いましたが、ものの数分でした。(離席している間に終わってました。)

まず、古いgmediarenderとその時入れた関連パッケージをpurgeします。

apt purge gstreamer1.0-alsa gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad gmediarender

そして、事前に必要なパッケージをインストールします。

apt install libupnp-dev libgstreamer1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-alsa gstreamer1.0-pulseaudio

んでgitからgmrender-resurrectのソースを拾ってきてmakeします。
あ、途中でautoconfもインストールしますが、先にインストールしても構いません。

git clone https://github.com/hzeller/gmrender-resurrect.git
cd gmrender-resurrect
apt install autoconf
./autogen.sh
./configure
make
make install

続いて、systemd用の起動スクリプトをコピーします。
(OpenRC派なんですけど、最近の主流はsystemdですね・・・。)

cp dist-scripts/debian/gmrender-resurrect.service /etc/systemd/system
chmod 775 /etc/systemd/system/gmrender-resurrect.service

設定ファイルはないようなので、etc/systemd/system/gmrender-resurrect.serviceのファイルを直接編集する必要があるようです。
デフォルトでは、

ExecStart=/usr/local/bin/gmediarender -f "$UPNP_DEVICE_NAME" -u "$UPNP_UUID" \
                                      --gstout-audiosink=alsasink --gstout-audiodevice=sysdefault \
                                      --logfile=/tmp/gmediarenderer.log --gstout-initial-volume-db=-10

というふうになってますので、ボリュームを0.0にしたり、アウトプットを変えたりしましょう。

また、pluseaudioを出力にする際は、pulsesinkに変更し、デバイスはpulseaudioで検出されたデバイス番号を指定します。

ExecStart=/usr/local/bin/gmediarender -f "$UPNP_DEVICE_NAME" -u "$UPNP_UUID" \
                                      --gstout-audiosink=pulsesink --gstout-audiodevice=0 \
                                      --logfile=/tmp/gmediarenderer.log --gstout-initial-volume-db=0.0

PulseAudioのデバイス番号の調べ方は

pactl list sinks

で、ずらーっと出てきますので、先頭のSink #0とかSink #1とかがデバイス番号のようです。

Home Assistantが動かないとき

Home Assistantが動かない!
特に、Androidアプリはエラー画面が出て前に進みません。

f:id:naoyukinagano:20220104203743j:plain
Android Home Assistantアプリエラー画面

Python環境をアップデートして動かなくなったのかしら?
と、思いましたが、PCのブラウザからアクセスすると、単に証明書の期限切れでアクセスできなかったようです。

そこで、証明書をコピーしてからhomeassistantデーモンが起動するように、起動スクリプトを変更するとともに、cronで週1回再起動するように仕込みます。

まず、Gentooなので、/etc/local.d配下にスクリプトを配置していますので、そこを書き換えます。
また、コピー元フォルダはLet's Encryptを利用している前提で記載しています。
(hogehoge.netがドメイン名ですね。)

/etc/local.d/homeassistant.start

#!/bin/sh

/bin/cp -Lf /etc/letsencrypt/live/hogehoge.net/privkey.pem /home/homeassistant/.homeassistant
/bin/cp -Lf /etc/letsencrypt/live/hogehoge.net/fullchain.pem /home/homeassistant/.homeassistant
/bin/chown homeassistant:homeassistant /home/homeassistant/.homeassistant/privkey.pem
/bin/chown homeassistant:homeassistant /home/homeassistant/.homeassistant/fullchain.pem

/bin/chmod 600 /home/homeassistant/.homeassistant/privkey.pem
/bin/chmod 600 /home/homeassistant/.homeassistant/fullchain.pem

sudo -u homeassistant -H /home/homeassistant/bin/hass --pid-file /home/homeassistant/hass.pid  --daemon > /var/log/home-assistant.log 2>&1

echo "Homeassistant Started"

さらに、週に1回homeassistantを再起動させます。

/etc/cron.weekly/homeassistant.sh

#!/bin/sh

/etc/local.d/homeassistant.stop
sleep 1
/etc/local.d/homeassistant.start

これで、証明書問題でHome Assistantアプリが起動しない! なんてことがなくなるハズです。

DovecotのSSLエラー

2021/9/30になって、iPadで自宅のメールサーバーからメールを取り出せなくなりました。
Dovecotのログを見ると、

Oct  2 15:23:18 gentoo dovecot[24406]: pop3-login: Disconnected (no auth attempts in 0 secs): user=<>, rip=0.0.0.0, lip=192.168.0.0, TLS handshaking: SSL_accept() failed: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown: SSL alert number 46, session=<xxxxxxxxxxxxx>

というようなエラーログが残っていました。

iPadメーラーを強制終了し、再度メーラーを起動すると、

「サーバーの識別情報を検証できません」と表示され、有効期限が「2021/9/30」のようになっています。
詳細を確認しても、サーバ証明書の有効期限は2021/11/18とまだ先です。
どうやらこれは、古いルート証明書「DST Root CA X3」の期限が2021/9/30までだからのようです。
古いOSで問題になるようですが、しかし、iPadは先日iOS15にしたばかり。
iOSの問題かと思ったのですが・・・。

なんと、Galaxy S20にあっては、7月からエラーでメールが取得できていなかった模様。
Galaxy S20はセキュリティタイプを「SSL(すべての証明書を許可)」という設定にすると、メールを取得し始めました。

Let's Encryptで「DST Root CA X3」ではなく、新しいルート証明書「ISRG Root X1」を使うように、強制的に更新してみました。

gentoo # certbot renew --force-renewal --preferred-chain "ISRG Root X1"
gentoo # /etc/init.d/dovecot restart

これをやっても、iPadメーラーは取得しに行かず、エラーログも変わりません。

いろいろと試してみましたが、Dovecotのconfが間違っているのかなぁ、と思い、初心に帰ってLet's Encryptを使った10-ssl.confを書いている人のサイトを見てみると、間違いを発見!!

ssl = yes
ssl_cert = </etc/letsencrypt/live/nns-fact.2y.net/cert.pem
ssl_key = </etc/letsencrypt/live/nns-fact.2y.net/privkey.pem

このssl_certに、中間証明書を含めないサーバー証明書「cert.pem」を指定していたため、iPadもGalaxy S20もルート証明書が「DST Root CA X3」だと思い、エラーが出ていたようです。
中間証明書を含む証明書に変更します。

ssl = yes
ssl_cert = </etc/letsencrypt/live/nns-fact.2y.net/fullchain.pem
ssl_key = </etc/letsencrypt/live/nns-fact.2y.net/privkey.pem

これで、iPadから無事にメールを取得でき、Galaxy S20もセキュリティタイプを「SSL(すべての証明書を許可)」から「SSL」に戻してもエラーが出なくなりました。

めでたしめでたし。

ruby2.5のせいでslot conflict

またもやemerge -uDN @worldでコンフリクトです。

lang-ruby/rakeやlang-ruby/rdoc、そしてrubygems(dev-ruby/rubygemsとvirtual/rubygems)がコンフリクトしているそうです。
(すみません。ログを確保し忘れました。)

emergeのメッセージやpackage.gentoo.orgによると、
"Mask ruby25 for removal. This version is no longer supported upstream. Use a newer ruby version, e.g ruby26. Masked for removal in 30 days."
とのことで、どうやらruby25が、もうサポートされなくなったようです。
Gentoo WikiのRubyには、「rubyのアップデートはemerge -uDN @worldで万事解決だぜ!」みたいなことが書いてますが、そのemerge -uDN @worldがrubyのせいで通らないんだっつーの。

というわけで、ググると自分の備忘録が!

gentoolinux.hatenablog.com

では、ruby:25を削除するとどうなるか、-pで見てみます。
(その前に、eselect ruby listでruby26がsetされていることを確認しましょう。
セットはeselect ruby set ruby26で。)

gentoo ~ $ emerge --depclean -pv dev-lang/ruby:2.5

Calculating dependencies... done!
  dev-lang/ruby-2.5.8 pulled in by:
    dev-ruby/did_you_mean-1.2.1 requires dev-lang/ruby:2.5
    dev-ruby/htmlentities-4.3.4 requires dev-lang/ruby:2.5
    dev-ruby/json-2.3.0 requires dev-lang/ruby:2.5
    dev-ruby/minitest-5.11.3 requires dev-lang/ruby:2.5
    dev-ruby/net-telnet-0.2.0 requires dev-lang/ruby:2.5
    dev-ruby/power_assert-1.1.5 requires dev-lang/ruby:2.5
    dev-ruby/rake-12.3.3 requires dev-lang/ruby:2.5
    dev-ruby/rdoc-6.1.2 requires dev-lang/ruby:2.5
    dev-ruby/rubygems-3.0.9 requires dev-lang/ruby:2.5
    dev-ruby/test-unit-3.3.3 requires dev-lang/ruby:2.5
    dev-ruby/xmlrpc-0.3.0 requires dev-lang/ruby:2.5
    virtual/rubygems-15 requires dev-lang/ruby:2.5

>>> No packages selected for removal by depclean
Packages installed:   717
Packages in world:    180
Packages in system:   44
Required packages:    717
Number to remove:     0

なかなかのパッケージ数ですね。
これを削除しましょう。

gentoo ~ $ emerge -C =dev-ruby/did_you_mean-1.2.1 =dev-ruby/htmlentities-4.3.4 =dev-ruby/json-2.3.0 =dev-ruby/minitest-5.11.3 =dev-ruby/net-telnet-0.2.0 =dev-ruby/power_assert-1.1.5 =dev-ruby/rake-12.3.3 =dev-ruby/rdoc-6.1.2 =dev-ruby/rubygems-3.0.9 =dev-ruby/test-unit-3.3.3 =dev-ruby/xmlrpc-0.3.0 =virtual/rubygems-15

(中略)

All selected packages: =virtual/rubygems-15 =dev-ruby/json-2.3.0 =dev-ruby/power_assert-1.1.5 =dev-ruby/htmlentities-4.3.4 =dev-ruby/rdoc-6.1.2 =dev-ruby/net-telnet-0.2.0 =dev-ruby/did_you_mean-1.2.1 =dev-ruby/test-unit-3.3.3 =dev-ruby/xmlrpc-0.3.0 =dev-ruby/rubygems-3.0.9 =dev-ruby/minitest-5.11.3 =dev-ruby/rake-12.3.3

>>> 'Selected' packages are slated for removal.
>>> 'Protected' and 'omitted' packages will not be removed.

>>> Waiting 5 seconds before starting...
>>> (Control-C to abort)...
>>> Unmerging in: 5 4 3 2 1
>>> Unmerging (1 of 12) dev-ruby/did_you_mean-1.2.1...
>>> Unmerging (2 of 12) dev-ruby/htmlentities-4.3.4...
>>> Unmerging (3 of 12) dev-ruby/json-2.3.0...
>>> Unmerging (4 of 12) dev-ruby/minitest-5.11.3...
>>> Unmerging (5 of 12) dev-ruby/net-telnet-0.2.0...
>>> Unmerging (6 of 12) dev-ruby/power_assert-1.1.5...
>>> Unmerging (7 of 12) dev-ruby/rake-12.3.3...
>>> Unmerging (8 of 12) dev-ruby/rdoc-6.1.2...
>>> Unmerging (9 of 12) dev-ruby/rubygems-3.0.9...
>>> Unmerging (10 of 12) dev-ruby/test-unit-3.3.3...
>>> Unmerging (11 of 12) dev-ruby/xmlrpc-0.3.0...
>>> Unmerging (12 of 12) virtual/rubygems-15...

削除できたようです。
では、肝心のruby25を削除しましょう。

gentoo ~ $ emerge --depclean -v dev-lang/ruby:2.5

Calculating dependencies... done!
>>> Calculating removal order...

 dev-lang/ruby
    selected: 2.5.8
   protected: none
     omitted: 2.6.8

All selected packages: =dev-lang/ruby-2.5.8

>>> 'Selected' packages are slated for removal.
>>> 'Protected' and 'omitted' packages will not be removed.

>>> Waiting 5 seconds before starting...
>>> (Control-C to abort)...
>>> Unmerging in: 5 4 3 2 1
>>> Unmerging (1 of 1) dev-lang/ruby-2.5.8...
No package files given... Grabbing a set.

(以下略)

これで、ruby25が削除されました。
あ、make.confのRUBY_TARGETSは
RUBY_TARGETS="ruby26"
など、ruby25を削除するのをお忘れなく。

めでたくemerge-uDN @worldが通りそうです。