Ubuntu

Ubuntu22.04 DockerでKopano

ホームラボでは、Ubuntu 18.04~22.04が仕事をしている。18.04で動いているシステムは、22.04に置き換えたいと思ったりして、そのためにはシステムを再構築が必要になるのだけれど、色々なシステムを同居させながら問題なく動くようにしていくには、それなりに気を遣う。



広告


多くのOSSを使わせていただいているのだけれど、一部が22.04に対応していなかったりすると、20.04で環境構築せざるを得ないため、構築のサイクルが短くなってしまう。
そこで、各システムをDockerで動かして、OSではコンテナを再生するだけにすれば、ベースとなるOSの入れ替えが簡単になるんじゃね?という発想で検討を進めている。

ということで、幾つもテーマがあるけれど、オフィシャルイメージのないKopanoに挑戦してみることにした。
正確には、開発元で作っていて、オフィシャルになろうとしているというところのようにも見えるのだが、ライセンスなどの問題も発生しそうなので、コミュニティエディションを使えるようにしていく。

計画

できあがりの構成。

┌────┐┌────┐┌──────────────────┐┌──────────┐
│        ││        ││             Container              ││     Container      │
│        ││        ││┌────┐┌────┐┌────┐││                    │
│ Apache ││Postfix │││ Apache ││Postfix ││ Kopano │├┤                    │
│        ││        ││└────┘└────┘└────┘││                    │
│(Reverse││(Relay) ││            Ubuntu:focal            ││mariadb:10.8.3-jammy│
│  Prosy)││        │├──────────────────┴┴──────────┤
│        ││        ││                       Dcoker Engine                        │
└────┘└────┘└──────────────────────────────┘
┌──────────────────────────────────────────┐
│                              Ubuntu 22.04 LTS Server                               │
└──────────────────────────────────────────┘

  • コンテナ2つを起動すれば、閉じた世界なら運用が可能なところまで整理。
    • httpでWebAppが使える。
    • WebAppでメールの送受信ができる。
    • IMAP、SMTP(SMTP AUTH)が使える。
    • ActiveSync(Z-push)が使える。
  • UbuntuにApacheをインストールして、外部にHTTPSでKopanoの機能を提供する。
  • UbuntuにPostfixをインストールして、外部と25ポートでやりとりする機能を提供する。

ApacheとPostfixは他のサーバーに立てても動作するような作りにできれば、運用がしやすくなるかと。

色々と試しまくる環境を作る

Ubuntuのオフィシャルイメージを起動しても、すぐに終了してしまう。

hello world!を表示するだけではあまり面白くない。
とりあえず起動し続けてくれて、aptでパッケージをインストールできたりして。とにかく何かができるコンテナが作りたい。

じゃないと、試行錯誤しにくい。

Dockerのインストール

公式で示されている手順を過去に整理したので、その方法でインストール。
docker-compose-pluginがインストール対象になっていた。

$ sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
$ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
$ docker -v
Docker version 20.10.17, build 100c701
$ docker compose version
Docker Compose version v2.6.0

コンテナの構築

作業ディレクトリを作り、docker-compose.ymlを作成する。

$ mkdir -p ~/kopano/packages
$ cd ~/kopano

docker-compose.yml ※新規作成

version: "3.9"
services:

  kopano:
    image: ubuntu:focal
    container_name: kopano

実行してみたけれども、すぐにコンテナが止まってしまった。bashも実行できない。

$ sudo docker compose up --build
Attaching to kopano
kopano exited with code 0

$ sudo docker exec -it kopano /bin/bash --login
Error response from daemon: Container 8f40dcb0be9cadd9e749858b106f4b04183cf2a5306f44b369030d1a8027ca3c is not running

そこで、何もせずにただ待つだけ、というentrypoint.shを作って組み込んでみる。

entrypoint.sh ※新規作成

#!/bin/bash
set -e

sig_term() {
    echo "CATCH SIGTERM"
    exit 0
}

exec "$@"

trap sig_term SIGTERM

while : ; do sleep 1 ; done

※sleep infinityではSIGTERMを受け取れなかった。

Dockerfile ※新規作成

FROM ubuntu:focal
USER root
ADD entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

※ENTRYPOINTはこの書き方(Exec形式)でないと、SIGTERMを受け取れなかった。

docker-compose.yml ※Dockerfileでビルドするように変更

version: "3.9"
services:

  kopano:
   #image: ubuntu:jammy
    build: ./
    image: custom/kopano:0.0.1
    container_name: kopano

これでもコンテナは起動したままになるので、お手軽にログイン可能になる。
でも、何かを動かしたいなら、entrypoint.sh的なものが必要になると思われる。
docker-compose.yml

version: "3.9"
services:

  kopano:
    image: ubuntu:focal
    container_name: kopano
    tty: true

 

ちょっと状態を見てみる。

$ sudo docker compose up -d --build
$ sudo docker exec -it kopano /bin/bash --login
# ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
# getent passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
# logout
$ sudo docker compose stop

これで、

  • docker-compose.yml
  • Dockerfile
  • entrypoint.sh

を色々と書き換えて試すことができるようになった。

Kopanoを動かす

とりあえず動く環境を作ってみた。
小さな範囲で運用するなら、これで十分いける気がする。というか、ホームラボならこれで十分すぎる便利さ。

ホームラボは、192.168.110.0/24で運用している。
ホームラボだけで通用するhogeserver.hogeddns.jpというドメインがあり、各種サーバーの名前解決ができるようにDNSを設定してある。

ファイル構成と内容

ファイル構成は以下の通り。

~/
 └ kopano/
     ├ docker-compose.yml
     ├ Dockerfile
     ├ entrypoint.sh
     └ packages/
         ├ config.sh
         ├ core-11.0.2.51.c08b7f4-Ubuntu_20.04-amd64.tar.gz
         ├ webapp-6.0.0.66.43d5c5d-Ubuntu_20.04-all.tar.gz
         └ cert
             ├ server.crt ※ここにこの名前でファイルが置いてあれば、Postfixがsnakeoilの代わりにこの証明書を提示。 
             └ server.key ※server.crtとセットで置く・置かないようにする。

2022/09/18
ちょっと別のテーマでKopanoを真面目に使うことにしたので、Postfixのsubmissionがホームラボだけで通用する自己署名証明書を提示できるように処理を改変した。
名前はserver.crtとserver.keyで固定だけれども、気に入らない場合は、config.shで処理するファイル名を変更する。

それぞれの中身はこんな感じ。

docker-compose.yml

~/kopano/docker-compose.yml

version: "3.9"

volumes:
  kopano:
  z-push:
  kopano_db:

services:
  kopano:
   #image: ubuntu:jammy
    build: ./
    image: custom/kopano:0.0.1
    container_name: kopano
    hostname: kopano
    restart: unless-stopped
    depends_on:
      - kopano_db
    volumes:
      - kopano:/var/lib/kopano/attachments
      - z-push:/etc/z-push
    networks:
      - kopano
    environment:
      - TZ=Asia/Tokyo
      - MYDOMAIN=hogeserver.hogeddns.jp
      - MYSMTPIP=192.168.110.34
      - SERVER_MYSQL_HOST=kopano_db
      - SERVER_MYSQL_USER=kopano
      - SERVER_MYSQL_PASSWORD=kopano
    ports:
      - 8080:80
      - 8025:25
      - 143:143
      - 587:587

  kopano_db:
    image: mariadb:10.8.3-jammy
    container_name: kopano_db
    restart: unless-stopped
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - kopano_db:/var/lib/mysql
    networks:
      - kopano
    environment:
      - TZ=Asia/Tokyo
      - MYSQL_ROOT_PASSWORD=kopano
      - MYSQL_PASSWORD=kopano
      - MYSQL_DATABASE=kopano
      - MYSQL_USER=kopano

networks:
  kopano:
    ipam:
      config:
        - subnet: "172.25.0.0/16"
          gateway: "172.25.0.1"

Kopanoの環境変数は、config.shを作り実行時に動くように仕掛けておいて、指定された値をシステムに反映していく。

  • MYDOMAINでKopanoで運営したいドメインを指定している。Kopanoはマルチドメインに対応しているが、ホームラボはドメイン1つなので、これでいいや…と。
  • MYSMTPIPで、外部とやりとりするためのPostfixのIPアドレスを指定している。

DatabaseにはMariaDBを使用している。

Postfixでメールを転送可能したいので、サブネットを指定して、ネットワークがコロコロと変わるのを止めている。

※2023/07/01 追記
実際に使う環境を作ろう!と思ったとき、以下が気になったので設定を追加している。

  • Kopano → Postfix のリレーで記録されるメールのヘッダーを編集しようと思ったら、ホスト名が確定していないと特定が難しかった。
    そのため、hostname指定でホスト名を固定した。
  • Z-pushでカレンダー共有の設定をするが、コンテナを削除したら消えてしまう。
    そのため、/etc/z-pushディレクトリをボリューム指定して永続化した。

Dockerfile

~/kopano/Dockerfile

FROM ubuntu:focal
USER root
ADD entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
COPY ./packages /root/packages
ENV DEBIAN_FRONTEND=noninteractive 
    SERVER_MYSQL_HOST=kopano_db 
    SERVER_MYSQL_PORT=3306 
    SERVER_MYSQL_USER=kopano 
    SERVER_MYSQL_PASSWORD=kopano 
    SERVER_MYSQL_DATABASE=kopano 
    LANG=en_US.UTF-8 
    LANGUAGE=en_US:en 
    LC_ALL=en_US.UTF-8
RUN cd /root/packages && 
    sed -i "s:^path-exclude=/usr/share/man:#path-exclude=/usr/share/man:" /etc/dpkg/dpkg.cfg.d/excludes && 
    apt update && 
    apt install -y 
      apache2 
      curl 
      gnupg 
      man 
      mariadb-client 
      iproute2 
      less 
      logrotate 
      php 
      postfix 
      postfix-mysql 
      sasl2-bin 
      software-properties-common 
      vim && 
    rm -f /usr/bin/man && 
    dpkg-divert --quiet --remove --rename /usr/bin/man && 
    mkdir -p /var/spool/postfix/var/run && 
    cp -ar /var/run/saslauthd /var/spool/postfix/var/run/ && 
    tar -zxvf core-11.0.2.51.c08b7f4-Ubuntu_20.04-amd64.tar.gz && 
    rm core-11.0.2.51.c08b7f4-Ubuntu_20.04-amd64.tar.gz && 
    tar -zxvf webapp-6.0.0.66.43d5c5d-Ubuntu_20.04-all.tar.gz && 
    rm webapp-6.0.0.66.43d5c5d-Ubuntu_20.04-all.tar.gz && 
    dpkg -i core-11.0.2.51.c08b7f4-Ubuntu_20.04-amd64/* || true && 
    dpkg -i webapp-6.0.0.66.43d5c5d-Ubuntu_20.04-all/* || true && 
    apt -y --fix-broken install && 
    curl https://download.kopano.io/zhub/z-push:/final/Ubuntu_20.04/Release.key -o zhub.key && 
    apt-key add zhub.key && 
    rm zhub.key && 
    add-apt-repository "deb https://download.kopano.io/zhub/z-push:/final/Ubuntu_20.04/ /" && 
    apt install -y z-push-backend-kopano z-push-ipc-sharedmemory z-push-config-apache && 
    chown www-data:www-data /var/lib/z-push/ /var/log/z-push/ && 
    a2dissite 000-default && 
    a2ensite kopano-webapp && 
    a2enmod expires headers && 
    mkdir -p /var/lib/kopano/attachments && 
    chown 999:999 /var/lib/kopano/attachments && 
    chmod 750 /var/lib/kopano/attachments && 
    locale-gen en_US.UTF-8

大まかな流れとしては、

  • 環境変数のデフォルト値を設定。これは、config.shでデフォルト値を持っておいて処理する方法もある。
  • コンテナの中で使いたいパッケージをインストール。
  • Kopano-CoreとWebAppを展開してインストールし、依存するパッケージをインストール(--fix-broken)。
    CoreとWebAppのバージョンをべた書きしているので、バージョンが変わっていたら修正が必要。
  • 公式のz-hubからZ-pushをインストールし、/var/lib/kopanoと/var/log/kopanoの所有者を変更。
  • Ubuntuパッケージに含まれているApache用のz-push.confをコピー。
  • ApacheでKopanoサイト、および、必要モジュールを有効化。
  • docker-compose.ymlでvolumes指定するディレクトリを先に作って、所有者や権限を付けておく。
    (これをやっておかないと、所有者がrootになって、kopanoからアクセスができなくなる)
  • 最後にロケールを指定している。これを最初にやってしまうと、z-push-backend-kopanoの依存関係でエラーが発生するが、理由がよく分からず解決ができなかったため。

となっている。

あれこれ使うので、ssコマンドと、vimをインストールしている。

それこそ、何度も作っては消し、作っては消しを繰り返したので、ホームラボに立てているミラーサーバーを見に行くように、RUNの最初にこれを入れたりしていた。
本番環境ではいらないので、その行だけを切り出してメモしておく。

    sed -i 's@http://.+/ubuntu@http://mirror.hogeserver.hogeddns.jp/ubuntu@' /etc/apt/sources.list && 

※2023/07/01 追記
いざ、ホームラボのKopanoはすべてDockerで動かす!と決めると、マニュアルが見られない。
多少容量は増えてしまうが、マニュアルが見られるように必要な処置を入れた。

※2023/08/05 追記
ログローテーションができていないので、インストールするパッケージを追加。

entrypoint.sh

~/kopano/entrypoint.sh

#!/bin/bash
set -e

echo "Start Kopano container with paramater : $@"

trap sig_term SIGTERM

sig_term() {
    echo "CATCH SIGTERM"
    pkill -SIGTERM kopano-server
    pkill -SIGTERM kopano-dagent
    pkill -SIGTERM kopano-gateway
    pkill -SIGTERM kopano-ical
    pkill -SIGTERM kopano-monitor
    pkill -SIGTERM kopano-search
    pkill -SIGTERM kopano-spooler
    pkill -SIGTERM kopano-statsd
    /etc/init.d/saslauthd stop
    /usr/sbin/postfix stop
    /usr/sbin/apachectl stop
    wait
    exit 0
}

# Make configuration.
/root/packages/config.sh

# Wait for service to start.
while
    mysqladmin -h $SERVER_MYSQL_HOST -u $SERVER_MYSQL_USER status -p$SERVER_MYSQL_PASSWORD
    [ $? -ne 0 ]
do
    sleep 5
done

# Start services.
/usr/sbin/apachectl start
/usr/sbin/postfix start
/etc/init.d/saslauthd start
/usr/sbin/kopano-server &
/usr/sbin/kopano-dagent -l &
/usr/sbin/kopano-gateway &
/usr/sbin/kopano-ical &
/usr/sbin/kopano-monitor &
/usr/sbin/kopano-search &
/usr/sbin/kopano-spooler &
/usr/lib/x86_64-linux-gnu/kopano/kopano-statsd &

while /etc/init.d/saslauthd status
    [ $? -ne 0 ]
do
    sleep 3
done
saslauthd -a rimap -O 127.0.0.1 -c

# Execute parameter.
exec "$@"


# Infinite loop.
while : ; do sleep 1 ; done

作ったファイルに実行権を付けておく。

$ chmod +x ~/kopano/entrypoint.sh

設定値を書き換えるconfig.shを実行した後、それぞれのサービスを起動していく。
コンテナの中でsystemdを使うためには色々とやらなきゃならないみたいなので、その方法は諦めて、コマンドを実行して常駐させていった。

MariaDBが動作していないとエラーが起きて、その後の動きがおかしくなることがあったので、MariaDBが起動していることを確認している。
また、saslauthdも起動していないうちにコマンド実行しても効果がないので、起動していることを確認している。

最後に無限ループを書いている。1秒スリープ→1秒スリープ…を繰り返す形で書いているが、このようにしないとコンテナ終了の割り込みを受け取ることができなかった。
これはもうちょっと良いものがあるのでは?と思うけれども、今はこの方法しか思いつかなかった。

config.sh

~/kopano/packages/config.sh

#!/bin/bash

#
# Postfix
#

# basic settings.
postconf -e maillog_file=/var/log/postfix.log
postconf -e myhostname=kopano
postconf -e virtual_mailbox_maps=mysql:/etc/postfix/mysql-aliases.cf
postconf -e virtual_alias_maps=mysql:/etc/postfix/mysql-groups.cf
postconf -e virtual_transport=lmtp:127.0.0.1:2003
postconf -e virtual_mailbox_domains=$MYDOMAIN
postconf -e relayhost=$MYSMTPIP

# get aliases from database.
cat <<EOF > /etc/postfix/mysql-aliases.cf
user = $SERVER_MYSQL_USER
password = $SERVER_MYSQL_PASSWORD
hosts = $SERVER_MYSQL_HOST
dbname = $SERVER_MYSQL_DATABASE
query = select value from objectproperty where objectid=(select objectid from objectproperty where value='%s' limit 1) and propname='loginname';
EOF
chmod 600 /etc/postfix/mysql-aliases.cf

cat <<EOF > /etc/postfix/mysql-groups.cf
user = $SERVER_MYSQL_USER
password = $SERVER_MYSQL_PASSWORD
hosts = $SERVER_MYSQL_HOST
dbname = $SERVER_MYSQL_DATABASE
query = select value from objectproperty where objectid in ( select objectid from objectrelation where parentobjectid in ( select objectid from objectproperty where value='%s' and propname='emailaddress' ) and relationtype=1 ) and propname='emailaddress';
EOF
chmod 600 /etc/postfix/mysql-groups.cf

# enable sasl authentication.
sed -i "s/^START=no/START=yes/" /etc/default/saslauthd
sed -i 's/^MECHANISMS="pam"/MECHANISMS="rimap"/' /etc/default/saslauthd
sed -i 's/^MECH_OPTIONS=""/MECH_OPTIONS="127.0.0.1"/' /etc/default/saslauthd
sed -i "s/^THREADS=5/THREADS=0/" /etc/default/saslauthd
sed -i 's@^OPTIONS="-c -m /var/run/saslauthd"@OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"@' /etc/default/saslauthd
adduser postfix sasl
cat <<EOF > /etc/postfix/sasl/smtpd.conf
pwcheck_method: saslauthd
mech_list: plain login
EOF
postconf -e "mua_client_restrictions=permit_sasl_authenticated reject"
sed -i "17,21 s/^#//g" /etc/postfix/master.cf
sed -i "23 s/^#//g" /etc/postfix/master.cf

# disable chroot for all acrive service.
sed -i "s/^([a-z]+ +[a-z-]+ +[a-z-]+ +[a-z-]+ +)y( +.+$)/1n2/g" /etc/postfix/master.cf

# log rotation.
cat <<EOF > /etc/logrotate.d/postfix
/var/log/postfix.log
{
        rotate 4
        weekly
        missingok
        notifempty
        compress
        delaycompress
}
EOF

#
# kopano-server
#
if [ -v SERVER_MYSQL_HOST ]; then sed -i "s/^#mysql_host = localhost$/mysql_host = $SERVER_MYSQL_HOST/" /etc/kopano/server.cfg; fi
if [ -v SERVER_MYSQL_PORT ]; then sed -i "s/^#mysql_port = 3306$/mysql_port = $SERVER_MYSQL_PORT/" /etc/kopano/server.cfg; fi
if [ -v SERVER_MYSQL_USER ]; then sed -i "s/^#mysql_user = root$/mysql_user = $SERVER_MYSQL_USER/" /etc/kopano/server.cfg; fi
if [ -v SERVER_MYSQL_PASSWORD ]; then sed -i "s/^#mysql_password =$/mysql_password = $SERVER_MYSQL_PASSWORD/" /etc/kopano/server.cfg; fi
if [ -v SERVER_MYSQL_DATABASE ]; then sed -i "s/^#mysql_database = kopano$/mysql_database = $SERVER_MYSQL_DATABASE/" /etc/kopano/server.cfg; fi
sed -i "s/^#disabled_features = imap pop3/disabled_features = pop3/" /etc/kopano/server.cfg

#
# kopano-gateway
#
sed -i "s/^#imap_listen = *%lo:143/imap_listen = 0.0.0.0:143/" /etc/kopano/gateway.cfg

#
# kopano-ical
#
sed -i "s@^#server_timezone = .+$@server_timezone = $TZ@" /etc/kopano/ical.cfg

#
# WebApp
#
sed -i 's/"SECURE_COOKIES", true/"SECURE_COOKIES", false/' /etc/kopano/webapp/config.php

#
# Z-Push
#
sed -i "s@define('TIMEZONE', '');@define('TIMEZONE', '$TZ');@" /etc/z-push/z-push.conf.php

#
# Register certificates.
#
cp -a /root/packages/cert/server.crt /etc/ssl/certs/ && 
    postconf -e smtpd_tls_cert_file=/etc/ssl/certs/server.crt
cp -a /root/packages/cert/server.key /etc/ssl/private/ && 
    postconf -e smtpd_tls_key_file=/etc/ssl/private/server.key

作ったファイルに実行権を付けておく。

$ chmod +x ~/kopano/packages/config.sh

Postfixについて、マスタープロセス設定でchrootしないように設定を変更している。
これをやっておかないと、上手く外側にあるSMTPと通信ができなかった。
ただ、全部そうしておく必要があるのかどうか、という点については未確認のままではある。

受け取ったメールはLMTPでKopanoに流し込むように設定している。

受け取ったメールアドレスの有効性は、mysql-aliases.cfとmysql-groups.cfで確認しているが、グループの方はサンプルを見つけることができなかったので自作している。
多分大丈夫だろう…とは思うものの、違う宛先にメールが届くと大変なことになるので、よく確認しておくと良い。

kopano-serverのところでは、環境変数を使って設定値を変更している。
他の設定値を変えたいときには、環境変数を追加してあげて、ここで編集するようなコードを書けば良いだろう。
(オフィシャルイメージを全然調べていないので、こんな方法がスタンダードなのかどうかは分からない)

コンテナがホスト側と通信する場合に、localhostでポートを開けても上手く動かなかった。
そのため、0.0.0.0というアドレスでサービスを起動するようにしている。

※2023/08/05 追記
Postfixのログローテーションについて、定義ファイルを追加。

CoreとWebApp

Kopanoのコミュニティーエディションはこちらからダウンロードできる。
https://download.kopano.io/community/

coreとwebappをダウンロードしてくる。

$ cd ~/kopano/packages
$ wget https://download.kopano.io/community/core%3A/core-11.0.2.51.c08b7f4-Ubuntu_20.04-amd64.tar.gz
$ wget https://download.kopano.io/community/webapp%3A/webapp-6.0.0.66.43d5c5d-Ubuntu_20.04-all.tar.gz

※この日のバージョンなので、適宜置き換える。

コンテナを起動してユーザーを作る

準備ができたので、コンテナを起動する。
色々ダウンロードするし、やることが多いので、ホームラボではコンテナが起動するまで100秒掛かっている。

$ cd ~/kopano
$ sudo docker compose up --build

起動すると、ログがどどっと出てくるので、止まるまで待つ。
このターミナルを眺めながら、別のターミナルを起動してユーザーとグループを作る。

$ sudo docker exec -it kopano /bin/bash --login
# kopano-admin -c root -p password -e root@hogeserver.hogeddns.jp -f "Adminstrator" -a yes
# kopano-admin -c hoge -p password -e hoge@hogeserver.hogeddns.jp -f "Hoge User" -a no
# kopano-admin -g first -e first@hogeserver.hogeddns.jp
# kopano-admin -i first -b root
# kopano-admin -i first -b hoge
# logout

※メールアドレスは適宜。

-aパラメーターで管理者・一般を決められる。
ここでは、rootという管理者と、hogeという一般ユーザーを作っている。

作ったユーザーは、以下のコマンドで確認できる。

$ sudo docker exec -it kopano kopano-admin -l
User list for Default(3):
        Username        Fullname        Homeserver
        -----------------------------------------------
        SYSTEM          SYSTEM          Unknown
        root            Administrator
        hoge            Hoge User

$ sudo docker exec -it kopano kopano-admin -L
Group list for Default(2):
        groupname
        -------------------------------------
        Everyone
        first

Apache

ホストにApacheをインストールして、リバースプロキシとして動作させる。

$ sudo apt install apache2
$ sudo ufw allow http
$ sudo ufw allow https

これは、単純にプロキシしているだけ。ホームラボだけで通用する自己署名証明書を使って、SSLで通信することのみ設定している。

/etc/apache2/sites-available/kopano.conf ※新規作成

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    ProxyPreserveHost On
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
</VirtualHost>

<VirtualHost *:443>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    ProxyPreserveHost On
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/

    # SSL
    SSLEngine on
    SSLCertificateFile /etc/ssl/private/wild.hogeserver.hogeddns.jp.crt
    SSLCertificateKeyFile /etc/ssl/private/wild.hogeserver.hogeddns.jp.key
</VirtualHost>

※ポート80にアクセスされたら、443に飛ばすような設定の方が良いのだろうけれども、テストの間はどちらからでもアクセスできるようにしている。

コンテナで8080ポートを開いているので、ufwの設定に関係なく、他のサーバーからリバースプロキシすることもできる。

設定を有効にする。

$ sudo a2dissite 000-default.conf
$ sudo a2ensite kopano.conf
$ sudo a2enmod proxy_http
$ sudo systemctl restart apache2

https://kopano.hogeserver.hogeddns.jp/webapp にアクセスすると、保護された接続でKopano WebAppにアクセスできる。

簡単な動作確認として、root→hoge, hoge→(グループ)firstにメールを送って、受信できることを確認する。
また、メールクライアントを使って、SMTP/MAPIを使ったメール送受信をしてみる。
これらが上手く動作すれば、あともう一息。

Postfix

ホストにPostfixをインストールして、メールを中継させる。
この設定ができると、ホームラボの他のメールサーバーとやりとりができるようになる。

$ sudo apt install postfix

■問い合わせと応答
(1) General type of mail configuration:
    Internet site
(2) System mail name:
    mx.hogeserver.hogeddns.jp

$ sudo ufw allow smtp

※System mail nameには、Kopanoのコンテナで指定したドメイン名とは違うものを設定。他のサーバーから名前解決できるホスト名が良さそうだ。

Kopanoからのメールを他のサーバーにリレーするように、Kopanoのネットワークをmynetworksに追加する。
サブネットはdocker-compose.ymlで指定したもの。docker-compose.ymlで指定していない場合、downするたびにネットワークが作り直されて、変わってしまう。
stackoverflow / How do configure docker compose to use a given subnet if a variable is set, or choose for itself if it isn't?

$ sudo docker network ls
NETWORK ID     NAME            DRIVER    SCOPE
2b8ff560b0e7   bridge          bridge    local
7e5a353618cb   host            host      local
abc762363406   kopano_kopano   bridge    local
03f4fd1bc728   none            null      local

$ sudo docker network inspect kopano_kopano | grep Subnet
                    "Subnet": "172.25.0.0/16",

$ postconf -p mynetworks
mynetworks = 192.168.110.0/24 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
$ sudo postconf -e "mynetworks=172.25.0.0/16 192.168.110.0/24 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128"

※mynetworksには、ローカルネットワークが登録されていたので、それをそのまま活かしておけば、他のローカルサーバーからのメールをKopanoにリレーできる。

kopano→rohhie.netのメールを、rohhie.netに配送。
合わせて、他のサーバー→kopanoのメールを、8025ポートに配送。

$ sudo postconf -e transport_maps=regexp:/etc/postfix/transport
$ sudo tee /etc/postfix/transport > /dev/null <<EOF
/rohhie.net$/ smtp:rohhie.net
/kopano.hogeserver.hogeddns.jp$/ smtp:127.0.0.1:8025
EOF

あと、念のためにIPv4のみでメールをやりとりするようにする。
ここまでIPv6の設定をしていないのが理由で、IPv6も意識した設定にしているならこの操作はいらない。

$ sudo postconf -e inet_protocols=ipv4

[Kopano] <---> [kopano.hogeserver.hogeddns.jp] --> [rohhie.het] というつながりができた。
この後、rohhie.net側で [kopano.hogeserver.hogeddns.jp] <-- [rohhie.het] を定義した(同じようにtransportを定義した)ので、相互にやりとりができた。

ホームラボでは、インターネットに向けたメール配信には、プロバイダーのメールサーバーを利用させていただいている。
この場合、relayhostにその設定を入れれば良い。

2023/08/05 追記
このサーバーで動作するCronからの通知メールを受け取ることができていなかった。
cronがroot宛てにメールを送信
 → Postfixが root@host.kopano.hogeserver.hogeddns.jp にアドレスを書き換え
  → Kopanoがそんなユーザーはいないとメールを拒否。

そこで、以下を設定して、root@kopano.hogeserver.hogeddns.jpになるようにした。

$ sudo postconf -e myorigin=kopano.hogeserver.hogeddns.jp

append_at_myorigin や append_dot_mydomain の設定と効果を確認したり、aliasesを試したりしたけれども上手くいかなかったので、上記設定としている。

2023/12/18 追記
グループに届いたメールを処理できていなかった。

$ postconf local_recipient_maps
local_recipient_maps = proxy:unix:passwd.byname $alias_maps

これって、getent passwdで見えるユーザーと、alias指定しているユーザーにメールが届くということ。
このPostfixは宛先がhogeserver.hoge.ddns.jpの場合にメールをKopanoに流してくれたら、Kopanoの側で判断するわけだから、チェックする必要がない。

ということで、以下の設定を入れる。

$ sudo postconf -e local_recipient_maps=

この設定でPostfixは受信者の確認をしなくなるので、コンテナの中で動くPostfixにメールを転送してくれる。
メールを受け取るかどうかの判断は、コンテナの中で動くPostfixに任せることができる。

ポートを閉じる設定

インターネットにメールサーバーを公開する場合、受信や転送に関して、しっかりとしたセキュリティ設定が必要。

それはそれとして、ネットワークのどこに置くのかによるけれども…
8080/tcpと8025/tcpはについて、よそのホストからはアクセスできないようにしておきたい。
IMAP(143/tcp)とSMTP AUTH(587/tcp)については、ローカルからの接続を許し、他からアクセスができないようにしたい。

とすると、以下のようにポートを閉じる設定が必要になってくる。
この仮想ゲストのネットワークインターフェースはens33なので、そこから入ってくるパケットをフィルターする。

$ sudo iptables -I DOCKER-USER -i ens33 ! -s 192.168.110.34 -p tcp -m multiport --dports 80,25 -j DROP
$ sudo iptables -I DOCKER-USER -i ens33 ! -s 192.168.110.0/24 -p tcp -m multiport --dports 143,587 -j DROP

※DOCKER-USERチェーンでポート指定をする場合、ホストのポート番号ではなく、コンテナのポート番号を指定しないと効果がなかった。
DOCKER COMMUNITY FORUMS / Restricting exposed Docker ports with iptables

これを実行すれば、確かにちゃんとポートは閉じるのだけれども、ホストを起動するたびにこの設定を追加したい。
ufwも動かしているし、Dockerでもnet filterは操作しているだろうから、iptables-persistentという仕組みで永続化するのはちょっと心配。

などと考えながら探していたら、かなりぴったりくる実行方法を教えてくれていた。
Qiita / docker-compose で起動させた Docker コンテナから送出するパケットを iptables で制限する

少し改変してやってみることにした。

~/kopano/add-user-rules.sh

#!/bin/bash
iptables $1 DOCKER-USER -i ens33 ! -s 192.168.110.34 -p tcp -m multiport --dports 80,25 -j DROP
iptables $1 DOCKER-USER -i ens33 ! -s 192.168.110.0/24 -p tcp -m multiport --dports 143,587 -j DROP

実行権を付ける。

$ chmod +x add-user-rules.sh

dockerのユニットファイルを編集する。

$ sudo systemctl edit docker

エディタが開くので、赤文字部分を追加する。

### Editing /etc/systemd/system/docker.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file

[Service]
ExecStartPost=/home/rohhie/kopano/add-user-rules.sh -I
ExecStop=/home/rohhie/kopano/add-user-rules.sh -D

### Lines below this comment will be discarded
…

※スクリプトを置く場所は適宜設定のこと。

ホストを再起動。

$ sudo reboot

ルールが追加されることを確認する。

$ sudo iptables --list-rules -t filter
-P INPUT DROP
-P FORWARD DROP
…
-A DOCKER-USER ! -s 192.168.110.0/24 -p tcp -m multiport --dports 143,587 -j DROP
-A DOCKER-USER ! -s 192.168.110.34/32 -p tcp -m multiport --dports 80,25 -j DROP
-A DOCKER-USER -j RETURN
…

これで、接続元を絞って安全を確保することができた。

LDAP認証

ホームラボで運用しているKopanoの1つは、ユーザー管理をSamba ad dcに任せている

最終的にDockerの環境に移行してくるために、Kopanoによるユーザー管理から、LDAP認証に変更する。
既に、Samba ad dcにスキーマ登録は済ませているので、Kopano側の設定だけをすれば良い。

LDAP

まず、LDAP simple bindを設定する。

設定方法については、ここで教えてくれていた。
Kopano Knowledge Base / Postfix

~/kopano/Dockerfile

…
RUN cd /root/packages && 
…
    apt install -y 
      apache2 
      curl 
      gnupg 
      mariadb-client 
      iproute2 
      php 
      postfix 
      postfix-ldap 
      postfix-mysql 
      sasl2-bin 
      software-properties-common 
      vim && 
…

※赤文字部分を追加。

~/kopano/docker-compose.yml

…
services:
  kopano:
…
    environment:
      - MYDOMAIN=hogeserver.hogeddns.jp
      - MYSMTPIP=192.168.110.34
      - SERVER_MYSQL_HOST=kopano_db
      - SERVER_MYSQL_USER=kopano
      - SERVER_MYSQL_PASSWORD=kopano
      - LDAP_URI=ldap://addc.hogeserver.hogeddns.jp:389
      - LDAP_BIND_USER=ssoauth@hogeserver.hogeddns.jp
      - LDAP_BIND_PASSWD=password
      - LDAP_SEARCH_BASE=cn=users,dc=hogeserver,dc=hogeddns,dc=jp
      - LDAP_USER_SEARCH_FILTER=(&(objectClass=user)(kopanoAccount=1))
      - LDAP_GROUP_SEARCH_FILTER=(&(objectClass=group)(kopanoAccount=1))
    ports:
…

※赤文字部分を追加。Active Directoryの人は、LDAP_SEARCH_BASEとかが違ってくるものと思われる。

~/kopano/packages/config.sh

…

#
# LDAP Settings
#
if [ -z "$LDAP_URI" ]; then
    exit 0
fi

sed -i "s/^#user_plugin = db$/user_plugin = ldap/" /etc/kopano/server.cfg
sed -i "s/^#user_plugin_config = .+$/user_plugin_config = /etc/kopano/ldap.cfg/" /etc/kopano/server.cfg

sed -i "s/^(!include /usr/share/kopano/ldap.openldap.cfg)$/#1/" /etc/kopano/ldap.cfg
sed -i "s/^#(!include /usr/share/kopano/ldap.active-directory.cfg)$/1/" /etc/kopano/ldap.cfg
sed -i "s@^ldap_uri =$@ldap_uri = $LDAP_URI@" /etc/kopano/ldap.cfg
sed -i "s/^ldap_bind_user =$/ldap_bind_user = $LDAP_BIND_USER/" /etc/kopano/ldap.cfg
sed -i "s/^ldap_bind_passwd =$/ldap_bind_passwd = $LDAP_BIND_PASSWD/" /etc/kopano/ldap.cfg
sed -i "s/^ldap_search_base =$/ldap_search_base = $LDAP_SEARCH_BASE/" /etc/kopano/ldap.cfg
sed -i "$ a ldap_user_search_filter = $LDAP_USER_SEARCH_FILTER" /etc/kopano/ldap.cfg
sed -i "$ a ldap_group_search_filter = $LDAP_GROUP_SEARCH_FILTER" /etc/kopano/ldap.cfg

postconf -e virtual_mailbox_maps=ldap:/etc/postfix/ldap-users.cf
postconf -e "virtual_alias_maps=ldap:/etc/postfix/ldap-aliases.cf ldap:/etc/postfix/ldap-groups.cf"

cat <<EOF > /etc/postfix/ldap-users.cf
server_host = $LDAP_URI
bind = yes
version = 3
bind_dn = $LDAP_BIND_USER
bind_pw = $LDAP_BIND_PASSWD
search_base = $LDAP_SEARCH_BASE
query_filter = (&$LDAP_USER_SEARCH_FILTER(mail=%s))
scope = sub
result_attribute = mail
EOF

cat <<EOF > /etc/postfix/ldap-aliases.cf
server_host = $LDAP_URI
bind = yes
version = 3
bind_dn = $LDAP_BIND_USER
bind_pw = $LDAP_BIND_PASSWD
search_base = $LDAP_SEARCH_BASE
query_filter = (&$LDAP_USER_SEARCH_FILTER(otherMailbox=%s))
scope = sub
result_attribute = mail
EOF

cat <<EOF > /etc/postfix/ldap-groups.cf
server_host = $LDAP_URI
bind = yes
version = 3
bind_dn = $LDAP_BIND_USER
bind_pw = $LDAP_BIND_PASSWD
search_base = $LDAP_SEARCH_BASE
query_filter = (&$LDAP_GROUP_SEARCH_FILTER(mail=%s))
scope = sub
result_attribute = mail
special_result_attribute = member
leaf_result_attribute = mail
EOF

※LDAP Settings以下を最後に追加。

ボリュームを消し、コンテナを作り直して、起動。

$ sudo docker compose down
$ sudo docker volume rm kopano_kopano kopano_kopano_db
$ sudo docker compose build --no-cache
$ sudo docker compose up -d

Kopanoがユーザーとグループを上手く検索できているか確認。

$ sudo docker exec -it kopano kopano-admin -l
        Username        Fullname                Homeserver
        --------------------------------------------------------
        SYSTEM          SYSTEM                  Unknown
        Administrator   Administrator
        hogeuser        hogeuser
        rohhie          rohhie
…

$ sudo docker exec -it kopano kopano-admin -L
Group list for Default(6):
        groupname
        -------------------------------------
        Everyone
        Family
        parent
…

続いて、WebAppを使ってメールの送受信、グループへのメール送受信。
SMTP/IMAPでユーザー、グループへのメール送受信を試して、上手く動けば設定完了。

以前構築した環境では、グループ宛てのメールを上手く処理できていなかったことが、今回見直してみてはっきり分かった。
上手く動かないから…と、/etc/aliasesにグループメンバーを登録して動かしていたので。でも、everyoneへのメール送信ができないことは悪くない、とか思っちゃってたので、きっちり問題解消させていなかった。

今回の設定では、グループが持つメールアドレスにメールを送れば、ちゃんとメンバーに展開してくれるようになっている。

LDAPS

暗号化なしのsimple bindはパスワードが平文で流れるので、TLSの設定をして保護してみる。

ホームラボには、自己署名した認証局がある(いわゆる、オレオレ認証局)。
Samba ad dcは、オレオレ認証局が署名した証明書を利用している。

この場合、コンテナの中でオレオレ認証局を信頼しておく必要がある。
そこで、packagesの下にcertというディレクトリを作り、そこに証明書を入れておいて、コンテナが作られるときにコピーされるように仕掛けておく。

~/
 └ kopano/
     ├ …
     └ packages/
         ├ …
         └ cert
             └ ca.crt

起動時に、/root/packages/certというディレクトリがあれば、中身を登録する処理を追加する。

~/kopano/packages/config.sh ※赤文字部分を追加

…
#
# Register certificates.
#
cp -a /root/packages/cert/ca.crt /usr/local/share/ca-certificates/ && 
        update-ca-certificates
cp -a /root/packages/cert/server.crt /etc/ssl/certs/ && 
        postconf -e smtpd_tls_cert_file=/etc/ssl/certs/server.crt
cp -a /root/packages/cert/server.key /etc/ssl/private/ && 
        postconf -e smtpd_tls_key_file=/etc/ssl/private/server.key

#
# LDAP Settings
#
if [ -z "$LDAP_URI" ]; then
…

LDAPサーバーのURIを修正。

~/kopano/docker-compose.yml

…
services:
  kopano:
…
    environment:
…
      - LDAP_URI=ldaps://addc.hogeserver.hogeddns.jp:636
…
    ports:
…

データー移行

現在稼働中のシステムからデーターを復元する。このことには過去に取り組んでいるので、それを実行すれば良さそうだ。
テスト用には、毎日取っているバックアップデーターを使おう。

$ sudo apt install cifs-utils unzip
$ sudo mkdir temp
$ sudo mount -o 'username=hoge,password=hogepass,vers=2.0' //linkstation/backup ./temp

といった感じでNASに接続して、そこからファイルを取り出しておく。

確認してみたところ、データーベースの名前がkopanoではなく、kopanoserverだった。
コンテナとボリュームを削除する。

$ sudo docker compose down
$ sudo docker volume rm kopano_kopano kopano_kopano_db

データーベースの名前を変更。
docker-compose.yml

…
services:
  kopano:
…
    environment:
…
      - SERVER_MYSQL_HOST=kopano_db
      - SERVER_MYSQL_USER=kopano
      - SERVER_MYSQL_PASSWORD=kopano
      - SERVER_MYSQL_DATABASE=kopanoserver
      - LDAP_URI=ldap://addc.hogeserver.hogeddns.jp:389
…
  kopano_db:
…
    environment:
      - TZ=Asia/Tokyo
      - MYSQL_ROOT_PASSWORD=kopano
      - MYSQL_PASSWORD=kopano
      - MYSQL_DATABASE=kopanoserver
      - MYSQL_USER=kopano
…

コンテナを作り直して、起動。

$ sudo docker compose build --no-cache
$ sudo docker compose up -d

問題なく動いていそうか確かめる。

$ sudo docker exec -it kopano /bin/bash --login
# kopano-admin -l
        Username        Fullname                Homeserver
        --------------------------------------------------------
        SYSTEM          SYSTEM                  Unknown
        Administrator   Administrator
        hogeuser        hogeuser
        rohhie          rohhie
…

# mysql -h kopano_db -u root -p
Enter password: パスワード[Enter]

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| kopanoserver       |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.001 sec)

MariaDB [(none)]> use kopanoserver
MariaDB [kopanoserver]> show tables;
+------------------------+
| Tables_in_kopanoserver |
+------------------------+
| abchanges              |
| acl                    |
| changes                |
…
| users                  |
| versions               |
+------------------------+
25 rows in set (0.001 sec)

MariaDB [kopanoserver]> exit

# logout

kopano-serverを止める。

$ sudo docker exec -it kopano pkill -SIGTERM kopano-server

添付ファイルを復元。

$ sudo docker cp var/lib/kopano/attachments kopano:/var/lib/kopano/
$ sudo docker exec kopano chown -R 999:999 /var/lib/kopano/attachments

※添付ファイルのバックアップをどう作ったのかによるが、ホームラボのバックアップの場合はvar/lib/kopano/attachmentsに入っている。

データベースをkopanoのコンテナにコピーして、MariaDBにリストアする。
コンテナの外からは上手く実行できなかったので、中に入って実行。

$ sudo docker cp root/work/backup/kopanoserver.dmp kopano:/
$ sudo docker exec -it kopano /bin/bash --login
# mysql -h kopano_db -u root -p -D kopanoserver < kopanoserver.dmp
Enter password: パスワード[Enter]
ERROR 1064 (42000) at line 1299: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'Process 87248 detected
Child process is finished, exiting...' at line 1

# rm kopanoserver.dmp
# logout

※添付ファイルのバックアップをどう作ったのかによるが、ホームラボのバックアップの場合はroot/work/backup/kopanoserver.dmpに入っている。

これでkopano-serverを実行し直せば動作するにはするのだけれど、kopano-searchが上手く動けない状態になるので、一旦コンテナを落として、改めて起動する。

$ sudo docker compose stop
$ sudo docker compose start

これでリストアができたはずなので、WebAppでログインして中身を確認してみる。
メールも見られて、添付ファイルも見られて…問題なし。

Z-Pushのカレンダー共有

これは、是非自動登録したいのだけれど、方法が分からないので手動設定。
やったことは、過去に書いた手順と同じ

$ sudo docker exec -it kopano /bin/bash --login
# /usr/share/z-push/backend/kopano/listfolders.php -l rohhie
Available folders in store 'rohhie':
--------------------------------------------------
…
Folder name:    Calendar
Folder ID:      5a3271c9f53f42de88d5c9702dad7602298900000000
Type:           SYNC_FOLDER_TYPE_USER_APPOINTMENT
…

として、各ユーザーのカレンダーの情報を調べておいて、z-pushの設定ファイルに追加する。
/etc/z-push/z-push.conf.php

…
    $additionalFolders = array(
        // demo entry for the synchronization of contacts from the public folder.
        // uncomment (remove '/*' '*/') and fill in the folderid
/*
        array(
            'store'     => "SYSTEM",
            'folderid'  => "",
            'name'      => "Public Contacts",
            'type'      => SYNC_FOLDER_TYPE_USER_CONTACT,
            'flags'     => DeviceManager::FLD_FLAGS_NONE,
        ),
*/
        array(
            'store'     => "rohhie",
            'folderid'  => "5a3271c9f53f42de88d5c9702dad7602298900000000",
            'name'      => "Rohhie's Calendar",
            'type'      => SYNC_FOLDER_TYPE_USER_APPOINTMENT,
            'flags'     => DeviceManager::FLD_FLAGS_NONE,
        ),
    );

追加した部分はコンテナの外にメモとして保管しておいて、コンテナを作り直したら手で追加。という運用になる。

各ユーザーが自分のカレンダーを「誰かに見せる」設定をすれば、その「誰か」にアクセス権ができて、カレンダーを参照できるようになる。

ログローテーション

※2023/08/05時点で実験中。

サービスを開始すると、アクセスに応じてログが出力されはじめる。
ローテーションさせなければ。

コンテナの中でsystemdもcronも動いていないので、ホストの側でローテーションを呼び出してみる。

/etc/crontab

0  0    * * *   root    docker exec kopano /usr/sbin/logrotate /etc/logrotate.conf

当面の間、-vオプションで詳細情報を出してもらって、動作を確認する。
動作が確認できれば、記事を更新する。

他のサービスを動かしてみる

他の機能を組み込んで使っているケースもあるかもしれない。
systemctlが使えない環境でどうやって動かすのか、というところだけ試してみた。

パッケージをインストールした結果、ユニットファイルは
 /lib/systemd/system/kopano-server.service
にできている。

中を見てみると、これだけのサービスがインストールされている。

#ll /lib/systemd/system/kopano-*
-rw-r--r-- 1 root root 465 Feb 2 11:04 /lib/systemd/system/kopano-dagent.service
-rw-r--r-- 1 root root 468 Feb 2 11:04 /lib/systemd/system/kopano-gateway.service
-rw-r--r-- 1 root root 575 Apr 29 2021 /lib/systemd/system/kopano-grapi.service
-rw-r--r-- 1 root root 461 Feb 2 11:04 /lib/systemd/system/kopano-ical.service
-rw-r--r-- 1 root root 431 Oct 28 2020 /lib/systemd/system/kopano-kapid.service
-rw-r--r-- 1 root root 695 Jun 17 2021 /lib/systemd/system/kopano-kidmd.service
-rw-r--r-- 1 root root 464 May 6 2021 /lib/systemd/system/kopano-konnectd.service
-rw-r--r-- 1 root root 762 Sep 30 2020 /lib/systemd/system/kopano-kwebd.service
-rw-r--r-- 1 root root 443 Feb 2 11:04 /lib/systemd/system/kopano-monitor.service
-rw-r--r-- 1 root root 440 Feb 2 11:02 /lib/systemd/system/kopano-search.service
-rw-r--r-- 1 root root 530 Feb 2 11:04 /lib/systemd/system/kopano-server.service
-rw-r--r-- 1 root root 554 Jun 12 2021 /lib/systemd/system/kopano-smtpstd.service
-rw-r--r-- 1 root root 472 Feb 2 11:02 /lib/systemd/system/kopano-spamd.service
-rw-r--r-- 1 root root 437 Feb 2 11:04 /lib/systemd/system/kopano-spooler.service
-rw-r--r-- 1 root root 237 Feb 2 11:04 /lib/systemd/system/kopano-statsd.service

中をよく見て動かしてみる。

konnectを動かす

konnectは認証サービスのようで、他のサービス(NextCloudとか)に認証サービスを提供できるようだ。

~kopano/Dockerfile

…
RUN cd /root/packages && 
…
    a2enmod expires headers && 
    # konnect
    cp /root/packages/kopano-konnect.conf /etc/apache2/conf-available && 
    a2enconf kopano-konnect && 
    a2enmod proxy_http && 
    #
    mkdir -p /var/lib/kopano/attachments && 
…

※赤文字部分を追加。

~kopano/entrypoint.sh

…
sig_term() {
    echo "CATCH SIGTERM"
    pkill -SIGTERM kopano-server
…
    pkill -SIGTERM konnectd
    /etc/init.d/saslauthd stop
…
}
…

/usr/lib/x86_64-linux-gnu/kopano/kopano-statsd &
/root/packages/start_konnect

while /etc/init.d/saslauthd status
…

※赤文字部分を追加。

~/kopano/packages/kopano-konnect.conf ※新規作成

ProxyPreserveHost On

ProxyPass /.well-known/openid-configuration http://localhost:8777/.well-known/openid-configuration retry=0
ProxyPass /konnect/v1/jwks.json http://localhost:8777/konnect/v1/jwks.json retry=0
ProxyPass /konnect/v1/token http://localhost:8777/konnect/v1/token retry=0
ProxyPass /konnect/v1/userinfo http://localhost:8777/konnect/v1/userinfo retry=0
ProxyPass /konnect/v1/static http://localhost:8777/konnect/v1/static retry=0
ProxyPass /konnect/v1/session http://localhost:8777//konnect/v1/session retry=0

# Kopano Konnect login area
ProxyPass /signin/ http://localhost:8777/signin/ retry=0

~/kopano/packages/start_konnect ※新規作成

#!/bin/bash
LC_CTYPE=en_US.UTF-8
#su -s /bin/bash -c "/usr/sbin/kopano-konnectd setup" konnect
su -s /bin/bash -c "/usr/sbin/kopano-konnectd serve --log-timestamp=false &" konnect

※setupの方は/etc/kopano/konnectkeysというディレクトリを必要としているのだが、なくても動作しているようなので止めている。

実行権を付ける。

$ chmod +x ~/kopano/packages/start_konnect

ファイルの変更・追加ができたら以下を実行。

$ sudo docker compose up --build

起動したら、ここにアクセスして、テストしてみる。
https://kopano.hogeserver.hogeddns.jp/signin/v1/welcome
※サイトの部分は環境に合わせて変える。ポイントは赤文字のところ。

サインイン画面が表示されるので、登録済みのユーザーで試してみたところ、ログインができた。

他のサービスで認証できるところまでは試していないが、動きそうな気配。

Kraphを動かす(途中まで)

Kopano Meet等を利用するために必要。Microsoft Graphと互換性を持たせられるそうだが、一応動くっぽいところまで試してみる。
Github / Kopano-dev / grapi

~kopano/Dockerfile

…
RUN cd /root/packages && 
…
    a2enmod expires headers && 
    # Kraph
    cp /root/packages/kopano-kraph.conf /etc/apache2/conf-available && 
    a2enconf kopano-kraph && 
    a2enmod proxy_http && 
    openssl rand -out /etc/kopano/kapid-pubs-secret.key -hex 64 && 
    mkdir /var/run/kopano-grapi && 
    chown kapi:kapi /var/run/kopano-grapi/ && 
    #
    mkdir -p /var/lib/kopano/attachments && 
…

※赤文字部分を追加。

~kopano/entrypoint.sh

…
sig_term() {
    echo "CATCH SIGTERM"
    pkill -SIGTERM kopano-server
…
    pkill -SIGTERM kapid
    pkill -SIGTERM grapi
    /etc/init.d/saslauthd stop
…
/usr/lib/x86_64-linux-gnu/kopano/kopano-statsd &
/root/packages/start_grapi
/root/packages/start_kapi

while /etc/init.d/saslauthd status
…

※赤文字部分を追加。

~/kopano/packages/config.sh

#
# kopano-kapi
#
sed -i "s/^#oidc_issuer_identifier=$/oidc_issuer_identifier=https://$MYDOMAIN/" /etc/kopano/kapid.cfg

#
# LDAP Settings
#
if [ -z "$LDAP_URI" ]; then
…

※赤文字部分を追加。

~/kopano/packages/kopano-kraph.conf ※新規作成

ProxyPreserveHost On

ProxyPass /api/gc/ http://localhost:8039/api/gc/ retry=0
ProxyPass /api/pubs/ http://localhost:8039/api/pubs/ retry=0

~/kopano/packages/start_kapi ※新規作成

#!/bin/bash
LC_CTYPE=en_US.UTF-8
su -s /bin/bash -c "/usr/sbin/kopano-kapid setup" kapi
su -s /bin/bash -c "/usr/sbin/kopano-kapid serve --log-timestamp=false &" kapi

~/kopano/packages/start_grapi ※新規作成

#!/bin/bash
LC_CTYPE=en_US.UTF-8
socket_path=/var/run/kopano-grapi
persistency_path=/var/lib/kopano-grapi
su -s /bin/bash -c "/usr/sbin/kopano-grapi setup kapi:kopano" kapi
su -s /bin/bash -c "/usr/sbin/kopano-grapi serve &" kapi

実行権を付ける。

$ chmod +x ~/kopano/packages/start_kapi ~/kopano/packages/start_grapi

ファイルの変更・追加ができたら以下を実行。

$ sudo docker compose up --build

これで、起動はしたけれども、Meetを動かしていないので正しく動いているのか確認はできていない。

やったこと

オフィシャルイメージを使った構築の場合は、環境変数をどう設定するのかがメインの作業になる。
今回は、Ubuntuイメージに各種アプリをインストールしていって、少しずつ設定していくことになったので、コンテナの中で色々と試行錯誤をした。

PostfixのLDAP設定を確認

KopanoをLDAP認証に対応させたら、PostfixもLDAPでユーザーやグループの情報を見て配信するように設定する必要があった。
でも、全然上手く動かなかった。

設定したLDAPの設定が上手く動くかどうか、試す方法があった。

# postmap -q rohhie@hogeserver.hogeddns.jp ldap:/etc/postfix/ldap-users.cf
rohhie@hogeserver.hogeddns.jp

# postmap -q administrator@hogeserver.hogeddns.jp ldap://etc/postfix/ldap-aliases.cf
root@hogeserver.hogeddns.jp

# postmap -q parent@hogeserver.hogeddns.jp ldap://etc/postfix/ldap-groups.cf
hogewife@hogeserver.hogeddns.jp,hoge@hogeserver.hogeddns.jp

ちゃんと設定ができてもメールが配信されないのは何でだろ…とやっていったら、alias_mapsにこの3つを入れていても上手く動かないのだった。

virtaul_mailbox_mapsにldap-users.cf、virtual_alias_mapsにldap-aliases.cfとldap-groups.cfを設定し、virtual_transportにlmtp:127.0.0.1:2003を設定したところ、上手く配信されるようになった。

なお、これはLDAPに限られる訳ではなく、mysqlでも使うことができた。

# postmap -q parent@hogeserver.hogeddns.jp mysql:/etc/postfix/mysql-groups.cf
hogewife@hogeserver.hogeddns.jp,hoge@hogeserver.hogeddns.jp

グループ宛のメール

ホームラボではLDAP認証を使っているが、グループ宛のメールはWebAppから出す場合にしか働いていなかった。
仕方がないので、グループ宛のメールは/etc/aliasesで設定して動作させていた。

今回色々と試してみて、ようやくグループ宛のメールが上手く配信されるようになった。
しかし、LDAPではなく、Kopanoでユーザー管理をしている場合、グループ宛のメールを上手く配信するにはどうしたらよいのだろう…

結論からすると、こうなった。
/etc/postfix/mysql-groups.cf

user = kopano
password = kopano
hosts = kopano_db
dbname = kopanoserver
query = select value from objectproperty where objectid in ( select objectid from objectrelation where parentobjectid in ( select objectid from objectproperty where value='%s' and propname='emailaddress' ) and relationtype=1 ) and propname='emailaddress';

/etc/postfix/main.cf

virtual_mailbox_maps = mysql:/etc/postfix/mysql-aliases.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-groups.cf
virtual_transport = lmtp:127.0.0.1:2003
virtual_mailbox_domains=$MYDOMAIN ← docker-compose.ymlで指定したドメイン名

この結論を導き出すために、データーベースを少しだけ見てみた。

ユーザーとグループはobjectpropertyというテーブルに入っている。

意味
objectid1から1つずつ増えて行く整数。
複数行からなるユーザーとグループの詳細情報を、1つにまとめるために使われている。
propname1行に1つの情報を持っていて、7行でユーザーの情報を表す。
emailaddress, fullname, isadmin, ishidden, loginname, modtime, password
グループは4行で表している。
emailaddress, groupname, ishidden, modtime
valuepropnameに対応する値。

具体的にはこのような値。

# mysql -h kopano_db -u root -p
MariaDB [(none)]> use kopanoserver;
MariaDB [kopanoserver]> select * from objectproperty;
+----------+--------------+------------------------------------------+
| objectid | propname     | value                                    |
+----------+--------------+------------------------------------------+
|        1 | emailaddress | root@hogeserver.hogedns.jp               |
|        1 | fullname     | Administrator                            |
|        1 | isadmin      | 1                                        |
|        1 | ishidden     | 0                                        |
|        1 | loginname    | root                                     |
|        1 | modtime      | 2022-07-31 11:14:31                      |
|        1 | password     | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|        2 | emailaddress | hoge@hogeserver.hogedns.jp               |
|        2 | fullname     | Hoge User                                |
|        2 | isadmin      | 0                                        |
|        2 | ishidden     | 0                                        |
|        2 | loginname    | hoge                                     |
|        2 | modtime      | 2022-07-31 11:14:31                      |
|        2 | password     | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|        3 | emailaddress | first@hogeserver.hogedns.jp              |
|        3 | groupname    | first                                    |
|        3 | ishidden     | 0                                        |
|        3 | modtime      | 2022-07-31 11:14:31                      |
+----------+--------------+------------------------------------------+

一方、objectrelationというテーブルでは、オブジェクト同士のつながりを表現している。

意味
objectid子供のID。
parentobjectid親のID。
relationtype今のところ1しか見たことがなく、他にどのような値があるのかは分かっていない。

具体的にはこのような値。

MariaDB [kopanoserver]> select * from objectrelation;
+----------+----------------+--------------+
| objectid | parentobjectid | relationtype |
+----------+----------------+--------------+
|        1 |              3 |            1 |
|        2 |              3 |            1 |
+----------+----------------+--------------+

これで、以下の関係を示しているようだった。

┌───────┐
│Group: Family │
│  objectid=3  │
└──┬────┘
      │  ┌───────┐
      ├─┤User: root    │
      │  │  objectid=1  │
      │  └───────┘
      │  ┌───────┐
      └─┤User: hoge    │
          │  objectid=2  │
          └───────┘

そこで、

  • objectpropertyテーブルを宛先メールアドレスで検索し、IDを取り出す。
    事前にユーザーは探しているであろうことから、グループしか見つからないことが想定される。
  • objectrelationテーブルを親であろうグループのIDで検索し、子供のIDを取り出す。
  • objectpropertyテーブルを子供のIDで検索し、メールアドレスを取り出す。

というSQL文を書いて、グループに含まれているユーザーのメールアドレスが取り出せるようにした。

これで、グループ宛にメールが配信されるようになったが、パターンをすべて調査したわけではないので、漏れに気付いたらぜひ教えていただきたい。

SMTP AUTH

IMAPはKopanoが用意してくれているので、サービスを起動すれば使えるようになる。

でも、メール送信するためにはPostfixを動かしておく必要がある。
サーバーをインターネットに公開することも意識するなら、SMTP AUTHを実装しなければ…ということで、教えていただいたとおりにやったらできた。
Zarafa Wiki / SMTP-Auth for IMAP users

本編では、entrypoint.shから呼び出すconfig.shで事前設定をしておいて、postfixを起動してから

saslauthd -a rimap -O 127.0.0.1 -c

を実行している。

記事自体はZarafa時代のものだけれども、ほぼそのままで上手く動いてくれた。

volumes指定したディレクトリの所有者

volumes指定をすると、ディレクトリの所有者が変わってしまう。

■volumes指定前
# ls -ld /var/lib/kopano/attachments/
drwxr-x--- 2 kopano kopano 4096 Jul 18 08:08 /var/lib/kopano/attachments/

■volumes指定後
# ls -ld /var/lib/kopano/attachments/
drwxr-xr-x 2 root root 4096 Jul 18 08:06 /var/lib/kopano/attachments/

Dockerfileで先にディレクトリを作り、所有者とパーミッションを設定してみた。

…
RUN cd /root/packages && 
…
    mkdir -p /var/lib/kopano/attachments && 
    chown 999:999 /var/lib/kopano/attachments && 
    chmod 750 /var/lib/kopano/attachments
…

結果として、所有者と権限は想定通りのものになった。

# ls -ld /var/lib/kopano/attachments/
drwxr-x--- 2 kopano kopano 4096 Jul 18 08:19 /var/lib/kopano/attachments/

Kopanoは先に /var/lib/kopano/attachments が作られていても、それによって問題が引き起こされるような作りにはなっていない。
むしろ、Kopanoのバックアップ→リストアでこのディレクトリを先に復旧するようなことをやっていて、上手くデーターが引き継げている。

このことから、まっさらからスタートしたとしても、先にディレクトリができていたからといって問題はないと思われ、これを対策とした。

デフォルトのエディタを変更

systemdのユニットファイルを編集しようと、以下のコマンドを実行したところ、nanoが起動した。

$ sudo systemctl edit docker

これをvimに変更しようと思ったら、幾つかやり方があるらしい。
StackExchange / Change default editor to vim for _ sudo systemctl edit [unit-file] _

デフォルトのエディタを変えてしまおうと思った。

$ sudo update-alternatives --config editor
There are 4 choices for the alternative editor (providing /usr/bin/editor).

  Selection    Path                Priority   Status
------------------------------------------------------------
* 0            /bin/nano            40        auto mode
  1            /bin/ed             -100       manual mode
  2            /bin/nano            40        manual mode
  3            /usr/bin/vim.basic   30        manual mode
  4            /usr/bin/vim.tiny    15        manual mode

Press <enter> to keep the current choice[*], or type selection number: 3[Enter]

これでvimが起動してくるようになった。

使用するメモリ

コンテナを止めて、再起動してホストのメモリーの使用量を見てみる。

$ free
               total        used        free      shared  buff/cache   available
Mem:         3984356      385176     2907772        2032      691408     3359932
Swap:        3983356           0     3983356

コンテナを起動し、WebAppやZ-pushでアクセスをした後に、ホストのメモリーの使用量を見てみる。

$ free
               total        used        free      shared  buff/cache   available
Mem:         3984356     1686564      666636       11532     1631156     2002900
Swap:        3983356           0     3983356

ざっくり、メモリーの消費量は1.2GB程度とみられる。ホストで動いているApacheやPostfix含めても1.6GB。
本気で色々とアクセスがあれば、その分だけメモリーの消費は増えるだろうけれども、これだけたくさんの機能を持ったサービスにしては、非常に少ないリソースで動いていると言って良いのではないだろうか。

起きたこと

色々と試している中で、起きてしまった問題への対処をメモしておく。

宛先のないメール

テストの段階で宛先のないメールができてしまった。
Qiita / Postfixのメールキューを確認、削除する方法

$ postqueue -p
-Queue ID-  --Size-- ----Arrival Time---- -Sender/Recipient-------
B6C8C2245FA    1388 Mon Jul 18 13:59:12  rohhie@rohhie.net
(Host or domain name not found. Name service error for name=kopano type=AAAA: Host not found, try again)
                                         hoge@kopano

-- 1 Kbytes in 1 Request.

※hoge@kopano という訳の分からない宛先のメールができてしまった…

キューにあるメールを削除する。

$ sudo postsuper -d B6C8C2245FA
postsuper: B6C8C2245FA: removed
postsuper: Deleted: 1 message

または

$ sudo postsuper -d ALL
postsuper: Deleted: 1 message

メールを再送させるには、以下のコマンドを実行すれば良かった。

$ sudo postfix fulsh

Dockerfileを変更しているのに再構築されない

どの条件でイメージが再構築されるのかがよく分からない。でも、Dockerfile変更の結果を反映することができないでいた。
探してみると、以下で再構築ができることが分かった。

$ sudo docker compose build --no-cache

dockerが容量を大量に使っていた

色々とやっているうちに、容量不足になった。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           390M  2.1M  388M   1% /run
/dev/sda2        39G   35G  1.5G  97% /
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/sda1       1.1G  5.3M  1.1G   1% /boot/efi
tmpfs           390M  4.0K  390M   1% /run/user/1000

最初イメージがとても大きいように見えた。

$ sudo docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          35        6         22.22GB   20.22GB (90%)
Containers      6         6         664.3MB   0B (0%)
Local Volumes   5         5         795.8MB   0B (0%)
Build Cache     101       7         123.6MB   0B

イメージが巨大化していたので削除。

$ sudo docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:558bed5eaed24de5c902ffcc843559a51e662eeeef8b24792f521d4fa95207cb
…
deleted: sha256:19052a86b47aec58ff0d626973f3c0ca03fc93a9ba58a483751a9778857f8788

Total reclaimed space: 0B

しかし、容量が減っていない。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           390M  2.3M  387M   1% /run
/dev/sda2        39G   36G  579M  99% /
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/sda1       1.1G  5.3M  1.1G   1% /boot/efi
tmpfs           390M  4.0K  390M   1% /run/user/1000

ImageからBuild Cacheに移動したようだ。

$ sudo docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          11        7         5.926GB   3.084GB (52%)
Containers      8         8         665.3MB   0B (0%)
Local Volumes   6         6         952.4MB   0B (0%)
Build Cache     101       0         17.12GB   17.12GB

Build Cacheを削除したら、容量が解放できた。コマンド実行まで、ちょっと待たされるけれども。
Qiita / DockerのBuild Cacheの削除

$ sudo docker builder prune
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y
Deleted build cache objects:
4xtb74e12euukxwoxobw3urhg
…
ng4skeex414lbfx2jd730y52f

Total reclaimed space: 17.12GB

確認してみる。

$ sudo docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          11        7         5.926GB   3.084GB (52%)
Containers      8         8         665.3MB   0B (0%)
Local Volumes   6         6         952.4MB   0B (0%)
Build Cache     37        0         0B        0B

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           390M  2.3M  387M   1% /run
/dev/sda2        39G   19G   18G  52% /
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock

環境変数がコンテナで使えない

ホームラボのデーターベースはkopanoではなく、kopanoserverとなっていた。
そこで、環境変数でkopanoserverを設定したのだけれど、何故かコンテナの中で使えない。

~/kopano/Dockerfile

FROM ubuntu:focal
USER root
ADD entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
COPY ./packages /root/packages
ENV TZ=Asia/Tokyo 
    DEBIAN_FRONTEND=noninteractive 
    SERVER_MYSQL_HOST=kopano_db 
    SERVER_MYSQL_PORT=3306 
    SERVER_MYSQL_USER=kopano 
    SERVER_MYSQL_PASSWORD=kopano 
    SERVER_MYSQL_DATABASE=kopano

~/kopano/docker-compose.yml

version: "3.9"

volumes:
  kopano:
  kopano_db:

services:
  kopano:
…
    environment:
      - MYDOMAIN=hogeserver.hogeddns.jp
      - MYSMTPIP=192.168.110.34
      - SERVER_MYSQL_HOST=kopano_db
      - SERVER_MYSQL_USER=kopano
      - SERVER_MYSQL_PASSWORD=kopano
      - SERVER_MYSQL_DATABASE = kopanoserver

これで上手く動くだろう…と思ってコンテナを起動してみたら、エラーが出てる。
/etc/kopano/server.cfgが上手く設定できていない。

コンテナの中に入って環境変数を見てみると…

$ sudo docker exec -it kopano /bin/bash --login
# echo $SERVER_MYSQL_DATABASE
kopano

# printenv SERVER_MYSQL_DATABASE
kopano

# env | grep SERVER_MYSQL_DATABASE
SERVER_MYSQL_DATABASE = kopanoserver
SERVER_MYSQL_DATABASE=kopano

あ!! ということで、以下を修正。

~/kopano/docker-compose.yml

      - SERVER_MYSQL_DATABASE=kopanoserver ←イコール記号の前後にあった空白を削除

これで問題が解消した。

さいごに

現段階では、確実に動作するであろうところまで試したところで、本番運用はしていない。
一応、できあがった記事で最初からやってみて動くことは確認している。
本番運用していないのは、他にもたくさんこの形にしたいシステムがあり、どう進めていくかを思案しているから。

今後、本番運用に入って気付いた問題があれば、都度記事を修正して、将来に役立てようと思う。
現に、過去のメモがだいぶ役に立っている。

一方で、公開しているメモにもかかわらず、足りてないところも見えてきている。
今なら分かるけれど、当時は全然分からなかったこと多数。
分かる人にはちゃんちゃらおかしい話かもしれないが、仕方がない、少しずつ理解して前に進んでいくしかない。

しかし…
ホントKopanoって凄いシステムだー、提供していただきありがとうございます。
メンテナンスが続いていくことを願っています。

広告

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他