Nextcloud in dockerで自宅クラウド構築(80,443ポートが開けない人向け)

## ※Github
小改良して再利用しやすくしました
たぶんReadmeどおりにやればいけるはず
WindowsのDocker Desktopはファイル権限絡みでバグりやすいので、Linux奨励です

https://github.com/Aiuthss/Nextcloud_freeport


# きっかけ
これまで写真はAmazon photos、ファイルはGoogleDrive 100GBに保存してきたが、Amazon Drive廃止、Google Photos無制限廃止などあって不安になってきた。
Amazon photosもアルバムにして整理している分以外の写真の一括ダウンロードが難しいので仕様変更あると困るなと思い、自宅にもデータを置いておきたくなった。
あとはよくわからない理由で垢バンされて過去の写真全部パアとかになった例を聞くので。
# 方針
+ 移植のしやすさ、安定性、セキュリティ等々考えてDockerで構築
+ 証明書はLet's encrypt
+ DDNSはMyDNS
+ DNS通知とか証明書更新の定期実行も一包化したい
+ 管理しやすくするために変数は変数ファイルに格納してスクリプトと分けておく

# やる
nextcloudイメージのgithubにdocker-composeで構築する[サンプル](https://github.com/nextcloud/docker/tree/master/.examples/docker-compose/with-nginx-proxy/mariadb/apache)があるのでこれを参考に作る。
## 構成
最初に構成を載せておきます。
```
nextcloud_docker
│  .env
│  db.env
│  docker-compose.yml
│  Readme.txt
│
├─mydns_letsencrypt
│  │  Dockerfile
│  │  letsencrypt-cert.sh
│  │  txtedit.conf
│  │
│  ├─crontabs
│  │      root
│  │
│  └─mydns
│          mydns-notify.sh
│          mydns.env
│
└─proxy
    └─conf.d
            uploadsize.conf
```
## 変数ファイル
.envはdocker-compose.yml内の変数を指定します。
letsencrypt-cert.shでもこれを読み込んでいます。
```sh:.env
MAILADDR=<メールアドレス>
DOMAIN=<ドメイン>
CERT_NAME=nextcloud
DATA_DIR=<Nextcloudデータの格納場所>
```
MariaDBのパラメータです。
```sh:db.env
MYSQL_PASSWORD=<MySQLのパスワード>
MYSQL_ROOT_PASSWORD=<MySQLのルートパスワード>
MYSQL_DATABASE=nextcloud
MYSQL_USER=nextcloud
```
MyDNSのログイン情報
```sh:mydns.env
MYDNSID='<MyDNSのID>'
MYDNSPW='<MyDNSのパスワード>'
```

## ポート開放
使いたいポートを開放しておいてください。今回はルータではポート変換しない設定で行きます。
## MyDNSでのドメイン取得
MyDNSでのドメイン取得方法は多くの記事があるのでそちらに譲ります。
## Let's encryptでの証明書取得
サンプル内で使われているnginxproxy/acme-companionイメージはHTTP-01チャレンジのみ対応のため、80, 443ポートを開かないと使えない。我が家はOCNバーチャルコネクトのため、開けるポートが制限されており、80も443も開けません。
そこでMyDNSJPの方が用意してくれている[DNS-01チャレンジ用のスクリプト](https://github.com/disco-v8/DirectEdit)をDockerイメージにして使います。
加えて初回取得は手動で行い、その後の更新は定期実行とするため、busyboxを使えるようにしておきます。
```dockerfile:Dockerfile
FROM debian:stable
WORKDIR /DirectEdit/

RUN apt-get -y update && \
    apt-get -y install busybox-static wget unzip certbot php php7.4-mbstring && \
    apt-get -y autoremove && apt-get -y clean
RUN wget 'https://github.com/disco-v8/DirectEdit/archive/master.zip' -O DirectEdit-master.zip && unzip ./DirectEdit-master.zip

WORKDIR /DirectEdit/DirectEdit-master
RUN chmod 700 ./*.php && chmod 600 ./*.conf
COPY txtedit.conf .

CMD busybox crond -f
```
このイメージを下記のコードでビルドしてcertbotを走らせてやると証明書が取得できる。
```sh:letsencrypt-cert.sh
source $(cd $(dirname $0); pwd)/../.env

docker build -t certbot $(cd $(dirname $0); pwd)
docker run -it --rm \
	--name certbot \
	-v /etc/letsencrypt:/etc/letsencrypt \
	-v /var/lib/letsencrypt:/var/lib/letsencrypt \
	certbot \
	certbot certonly --manual --keep\
        --preferred-challenges=dns \
        --manual-auth-hook /DirectEdit/DirectEdit-master/txtregist.php \
        --manual-cleanup-hook /DirectEdit/DirectEdit-master/txtdelete.php \
        -d ${DOMAIN} -d *.${DOMAIN} \
        --server https://acme-v02.api.letsencrypt.org/directory \
        --agree-tos -m ${MAILADDR} \
        --manual-public-ip-logging-ok
# nginxから認識できるように鍵と証明書の名前を変更しておく  
cp /etc/letsencrypt/live/${DOMAIN}/cert.pem /etc/letsencrypt/live/${DOMAIN}/${CERT_NAME}.crt
cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem /etc/letsencrypt/live/${DOMAIN}/${CERT_NAME}.key
```
## DDNS通知と証明書更新の定期実行
MyDNSはDDNSのため、定期的に通知を行ってあげないと無効化します。また、証明書は期限付きのため、定期的に更新する必要があります。定期実行はbusybox crondで行います。今回は証明書更新は週1回、DDNS通知は毎日行うことにしました。
使うイメージは証明書取得に使ったのと同じものです。ここで引っかかったのが、crontabs/rootの所有者をrootにしておかないとジョブが実行されないこと。
```sudo chown root:root root```で変更しておきます。
```crontab:root
0 0 * * 0 certbot renew
0 0 * * * bash /mydns/mydns-notify.sh
```
DDNS通知
```sh:mydns-notify.sh
source $(cd $(dirname $0); pwd)/mydns.env
wget -O - http://${MYDNSID}:${MYDNSPW}@www.mydns.jp/login.html
```
## docker-composeにまとめる
proxyコンテナに入るところでポートを変換しています。
```yml:docker-compose.yml
version: '3'

services:
  proxy:
    image: nginxproxy/nginx-proxy:alpine
    restart: always
    ports:
      - 1750:80
      - 1751:443
    volumes:
      - /etc/letsencrypt/live/${DOMAIN}:/etc/nginx/certs:ro
      - ./proxy/conf.d:/etc/nginx/conf.d
      - vhost.d:/etc/nginx/vhost.d
      - html:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    environment:
      - TZ=Asia/Tokyo
    networks:
      - proxy-tier
  db:
    image: mariadb:10.5
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    volumes:
      - db:/var/lib/mysql
    env_file:
      - db.env
  redis:
    image: redis:alpine
    restart: always
  app:
    image: nextcloud:apache
    restart: always
    volumes:
      - ${DATA_DIR}:/var/www/html
    environment:
      - NEXTCLOUD_TRUSTED_DOMAINS=${DOMAIN}
      - VIRTUAL_HOST=${DOMAIN}
      - OVERWRITEPROTOCOL=https
      - CERT_NAME=${CERT_NAME}
      - MYSQL_HOST=db
      - REDIS_HOST=redis
    env_file:
      - db.env
    depends_on:
      - db
      - redis
    networks:
      - proxy-tier
      - default
  cron:
    image: nextcloud:apache
    restart: always
    volumes:
      - ${DATA_DIR}:/var/www/html
    entrypoint: /cron.sh
    depends_on:
      - db
      - redis
  mydns_letsencrypt:
    build: ./mydns_letsencrypt
    restart: always
    tty: true
    volumes:
      - ./mydns_letsencrypt/crontabs:/var/spool/cron/crontabs
      - ./mydns_letsencrypt/mydns:/mydns
      - /etc/letsencrypt:/etc/letsencrypt
      - /var/lib/letsencrypt:/var/lib/letsencrypt
    environment:
      - TZ=Asia/Tokyo

volumes:
  db:
  nextcloud:
  vhost.d:
  html:

networks:
  proxy-tier:
```

## 起動
これで```docker-compose up -d```してあげれば、Nextcloudを起動できるかと思います。
## 信頼できるドメインの追加
これで接続できるのだが、信頼されていないドメインというエラーが出る。
nextcloudのdockerイメージにNEXTCLOUD_TRUSTED_DOMAINSという変数があり、そこに自分のドメインを示しておけば行けるはずなのだが、どうもバグで機能しない例が多いみたいなので
```sudo docker-compose exec --user www-data app php occ config:system:set trusted_domains 2 --value=<ドメイン>```
を実行して追加してあげましょう。