Windows11にwsl2+Dockerで自宅Nextcloud構築

# 概要
Windows11にwsl2+Dockerで自宅用Nextcloudを構築する手順です。
インターネットには公開せず、自宅LAN内での稼働を想定しています。

## きっかけ
子どもが生まれ、増え続ける写真データを家族で共有する方法を探していました。
Googleフォトを有料プランにするのも、NASを購入するのも、結構お金がかかるので、自宅に転がっている機器を使って、ファイルサーバを構築すれば~~タダじゃん~~楽しそうと考えたのがきっかけです。

## イメージ図
![自宅Nextcloud_概要-01_Overview.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407204/dfca5ee2-5b3e-b9cd-d073-aaed7c48bea9.jpeg)


## 使用したもの
・[HeroBox2023 Mini PC](https://www.amazon.co.jp/gp/product/B07XYLSNZH/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1)　（OS：Windows11 home）
・[Crucial X9 外付け SSD 1TB](https://www.amazon.co.jp/dp/B0CGW1FQV4?psc=1&ref=ppx_yo2ov_dt_b_product_details)


# 1. WSL2(Ubuntu)の導入
### 1.1. WSL2をインストールする
管理者権限でPowerShellを起動して、以下のコマンドを実行する。
`wsl --install`
※--distribution:というオプションをつけると、インストールするLinuxディストリビューションを指定することが可能ですが、指定しない場合は、デフォルトのLinuxディストリビューションであるUbuntuがインストールされます。

再起動を求められたら再起動します。
WindowsにUbuntuアプリがインストールされます。
Ubuntu初回起動時にユーザ名・パスワードを入力します。

### 1.2. Windows起動時にWSL2が起動するようにする
#### ・以下のバッチファイルを用意する
```powershell:open-wsl.bat
wsl
```

#### ・スタートアップフォルダに配置する
C:\Users\\(Username)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\
※「Windowsキー+R」を押して「ファイル名を指定して実行」のダイアログボックスに「shell:startup」と入力して実行することでもスタートアップフォルダを開くことが可能。

# 2. USB外付けSSDのマウント
### 2.1. USB外付けSSDをext4にフォーマットする
私はLinuxがインストールされたノートPCが手元にあったので、それにUSB外付けSSDを接続して、mkfs.ext4コマンドでフォーマットしました。

#### [別の手段の候補]
・USB外付けSSDをWSL2にマウント（手順2.2.）した後に、WSL2からmkfs.ext4コマンドを実行してもできそうです。（先にe4fsprogsパッケージをインストールする必要あり）
・Windowsでもフリーソフトを使ってext4にフォーマットできそうです。

#### [フォーマットが必要な理由]
フォーマットせずにNextcloudを構築した場合、初めにブラウザからNextcloudを開いた画面で[インストール]ボタンを押すと、「エラー あなたのデータディレクトリは他の人が読み取ることができます ディレクトリが他人から見えないように、パーミッションを 0770 に変更してください。」と表示されます。
これは、USB外付けSSDのファイルシステムが、購入時の状態ではFAT32だったためです。パーミッションが755になっていて、FAT32はLinuxのパーミッションの概念がなくchmodすることもできません。
別の回避方法として、Nextcloudのインストール時にパーミッションのチェックをしないようにする方法もあるようですが、ここは素直にext4にフォーマットしました。


### 2.2. USB外付けSSDをWSL2(Ubuntu)にマウントする
#### ・WSL2側からデバイスを見る
Ubuntuを起動して`lsblk`を実行
```bash
jo@herobox:~/docker-nextcloud$ lsblk
NAME MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda    8:0    0 388.6M  1 disk
sdb    8:16   0     1T  0 disk /var/lib/docker
                               /snap
                               /mnt/wslg/distro
                               /
```
→ディスクを認識する前の状態を確認しておく。

#### ・Windowsで使用可能なディスクの一覧を表示する
Powershellで以下のコマンドを実行する。
`GET-CimInstance -query "SELECT * from Win32_DiskDrive"`
```Powershell
PS C:\Windows\system32> GET-CimInstance -query "SELECT * from Win32_DiskDrive"

DeviceID           Caption                              Partitions Size          Model
--------           -------                              ---------- ----          -----
\\.\PHYSICALDRIVE0 AirDisk 256GB SSD                    3          256052966400  AirDisk 256GB SSD
\\.\PHYSICALDRIVE1 Micron CT1000X9SSD9 SCSI Disk Device 1          1000202273280 Micron CT1000X9SSD9 SCSI Disk Device
```
→USB外付けSSDのDeviceIDとPartitionsを控えておく。
　　DeviceID：\\\\\\.\PHYSICALDRIVE1
　　Partitions：1

#### ・ディスクを認識させる
Powershellで以下のコマンドを実行
Partitionsが0の場合：`wsl --mount <DiskPath>`
Partitionsが1以上の場合：`wsl --mount <DiskPath> --bare`
※\<DiskPath>は先ほど控えたDeviceID（今回は\\\\\\.\PHYSICALDRIVE1）
```Powershell
PS C:\Windows\system32> wsl --mount \\.\PHYSICALDRIVE1 --bare
この操作を正しく終了しました。
```

#### ・WSL2側からデバイスを見る
Ubuntuを起動して`lsblk`を実行
```bash
jo@herobox:~/docker-nextcloud$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda      8:0    0 388.6M  1 disk
sdb      8:16   0     1T  0 disk /var/lib/docker
                                 /snap
                                 /mnt/wslg/distro
                                 /
sdc      8:32   0 931.5G  0 disk
├─sdc1   8:33   0   128M  0 part
└─sdc2   8:34   0 931.4G  0 part
```
→新たにディスクが認識されたことを確認。今回マウントしたいのはsdcのパーティション番号2である。

#### ・パーティションをマウントする
Powershellで以下のコマンドを実行
`wsl --mount <DiskPath> --partition <PartitionNumber>`
```Powershell
PS C:\Windows\system32> wsl --mount \\.\PHYSICALDRIVE1 --partition 2
ディスクは '/mnt/wsl/PHYSICALDRIVE1p2' として正常にマウントされました。
注: /etc/wsl.conf で automount.root 設定を変更した場合、場所は異なります。
ディスクのマウントを解除してデタッチするには、'wsl.exe --unmount \\.\PHYSICALDRIVE1' を実行してください。
```
→/mnt/wsl/PHYSICALDRIVE1p2にマウントされたと出力されている。

#### ・WSL2側からデバイスを見る
Ubuntuを起動して`lsblk`を実行
```bash
jo@herobox:~/docker-nextcloud$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda      8:0    0 388.6M  1 disk
sdb      8:16   0     1T  0 disk /var/lib/docker
                                 /snap
                                 /mnt/wslg/distro
                                 /
sdc      8:32   0 931.5G  0 disk
├─sdc1   8:33   0   128M  0 part
└─sdc2   8:34   0 931.4G  0 part /mnt/wsl/PHYSICALDRIVE1p2
```
→/mnt/wsl/PHYSICALDRIVE1p2にマウントされていることを確認。後でcompose.ymlに書くので控えておく。


#### ・Windows起動時にマウントされるようにする
ディスクをマウントするには管理者権限が必要なため、タスクスケジューラを設定します。
 - [全般]タブ
		名前：USB外付けSSDマウント
		「ユーザーがログオンしているかどうかにかかわらず実行する」のラジオボタンをON
		「最上位の特権で実行する」のチェックをON
 - [トリガー]タブ-[新規]→
		タスクの開始：スタートアップ時
 - [操作]タブ-[新規]→
		操作：プログラムの開始
		プログラム/スクリプト：wsl
		引数の追加：--mount \\.\PHYSICALDRIVE1 --partition 2

### 参考

https://learn.microsoft.com/ja-jp/windows/wsl/wsl2-mount-disk

https://zenn.dev/booink/articles/ext4-drive-mount-to-wsl


# 3. WSL2(Ubuntu)にDocker CEをインストール
### 3.1. Ubuntuを起動して、以下の手順通りコマンドを実行する

### 参考

https://docs.docker.com/engine/install/ubuntu/

```markdown:引用
1. Set up Docker's apt repository.

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg　##すでにインストール済みなので不要
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

2. Install the Docker packages.
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

3. Verify that the Docker Engine installation is successful by running the hello-world image.
sudo docker run hello-world
```


# 4. Dockerコンテナの作成と実行
### 4.1. WSL2(Ubuntu)にDocker composeをインストールする
Ubuntuを起動して、以下のコマンドを実行
```bash
jo@herobox:~$ sudo apt install docker-compose
```

### 4.2. Nextcloud用のディレクトリを作成する
```bash
jo@herobox:~$ mkdir docker-nextcloud
```

### 4.3. compose.ymlファイルを作成する
```bash
jo@herobox:~$ cd docker-nextcloud/
jo@herobox:~/docker-nextcloud$ vi compose.yml
```
以下の内容を記述する。

```YAML:compose.yml
services:
  db:
    image: mariadb:10.6
    container_name: nextcloud_db
    restart: always
    command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
    volumes:
      - /mnt/wsl/PHYSICALDRIVE1p2/nextcloud/db:/var/lib/mysql #'/mnt/wsl/PHYSICALDRIVE1p2'の部分はWSL2にマウントしたパスを記述する。
    environment:
      MYSQL_ROOT_PASSWORD: P@ssw0rd #任意のパスワードを記述する。
      MYSQL_PASSWORD: P@ssw0rd #任意のパスワードを記述する。
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      TZ: Asia/Tokyo

  nextcloud:
    image: nextcloud
    container_name: nextcloud_app
    restart: always
    ports:
      - 8686:80 #他のWebアプリで8080ポートを使うつもりなので、今回は8686ポートにした。
    depends_on:
      - db
    volumes:
      - /mnt/wsl/PHYSICALDRIVE1p2/nextcloud/data:/var/www/html/data #'/mnt/wsl/PHYSICALDRIVE1p2'の部分はWSL2にマウントしたパスを記述する。
      - /mnt/wsl/PHYSICALDRIVE1p2/nextcloud/config:/var/www/html/config #'/mnt/wsl/PHYSICALDRIVE1p2'の部分はWSL2にマウントしたパスを記述する。

    environment:
      MYSQL_PASSWORD: P@ssw0rd #任意のパスワードを記述する。
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_HOST: db
      TZ: Asia/Tokyo
```

[補足]
volumes:の記述によって、WSL2(Ubuntu)でマウントしたUSB外付けSSDのパスを、Dockerコンテナにバインドマウントします。これにより、コンテナを削除しても写真データは消えない状態（永続化）になります。
![自宅Nextcloud_概要-02_mount.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407204/280b18e8-69fa-0fda-7b44-bc6b351ea5dc.jpeg)


### 4.4. コンテナを作成・起動する
```bash
jo@herobox:~$ cd docker-nextcloud/
jo@herobox:~/docker-nextcloud$ sudo docker compose up -d
```

### 4.5. コンテナができたか確認する
```bash
jo@herobox:~/docker-nextcloud$ sudo docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                   NAMES
15fa22bb3b8a   nextcloud      "/entrypoint.sh apac…"   2 minutes ago   Up 2 minutes   0.0.0.0:8686->80/tcp, :::8686->80/tcp   nextcloud_app
5f7ed9bae989   mariadb:10.6   "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   3306/tcp                                nextcloud_db
```

### 4.6. ブラウザからnextcloudの起動を確認する
ブラウザ上で以下のアドレスを入力する。
http://localhost:8686/

### 4.7. Nextcloudの管理者アカウントを作成する
「管理者アカウントを作成してください」という画面が表示されているはずです。
管理者アカウントとなるユーザ名・パスワードを入力して、[インストール]ボタンを押します。
その後、推奨アプリを入れるか聞いてきますが、必要なければスキップでOK.


# 5. ネットワーク周りの設定
### 5.1. Windowsファイアウォールでポートを解放する
[コントロールパネル]-[システムとセキュリティ]-[Windows Defender ファイアウォール]-[詳細設定]-[受信の規則]
→ポート8686を解放
![スクリーンショット 2024-07-27 17.32.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407204/cdf3e9a2-b95b-0277-3c1a-35f1de0392c1.png)

### 5.2. Port Forwardingを設定する
ホストOSのIPアドレス宛ての通信をWSL2(Ubuntu)にポートフォワーディングする設定を行っていきます。

#### ・ホストOSのIPアドレスを固定化する
192.168.0.100

#### ・UbuntuのIPアドレスを確認する
Ubuntuを起動して、以下のコマンドを実行
`ip a show dev eth0`
→192.168.204.7

#### ・ポートフォワーディングを設定する
管理者権限でPowerShellを起動して、以下のコマンドを実行する
```PowerShell
PS C:\Windows\system32> netsh interface portproxy add v4tov4 listenaddress=* listenport=8686 connectaddress=192.168.204.7 connectport=8686
```

設定できたか確認する
```Powershell
PS C:\Windows\system32> netsh interface portproxy show all

ipv4 をリッスンする:         ipv4 に接続する:

Address         Port        Address         Port
--------------- ----------  --------------- ----------
*               8686        192.168.204.7   8686
```

### 5.3. 他端末のブラウザからNextcloudが開けるか確認する
他端末のブラウザから http://192.168.0.100:8686/ を開く

### 参考

https://arubeh.com/archives/1749

https://gunmagisgeek.com/blog/other/7171


# 6. 動画のサムネイル生成
画像のサムネイルは何もしなくても生成されるのですが、動画のサムネイル生成は一手間加える必要があります。

### 6.1. ffmpegをインストールする
#### ・docker composeで立てたコンテナの中にログインする
Ubuntuを起動して、以下のコマンドを実行
`docker compose exec (サービス名) /bin/bash`
```bash
sudo docker compose exec nextcloud /bin/bash
```

#### ・apt-get updateして、ffmpegとviをインストールする
```bash
root@15fa22bb3b8a:/var/www/html# apt-get update
root@15fa22bb3b8a:/var/www/html# apt-get install ffmpeg
root@15fa22bb3b8a:/var/www/html# apt-get install vi
```

#### ・config.phpを修正する
```bash
vi config/config.php
```

以下を追記する
```php
'enabledPreviewProviders' =>
  array (
    0 => 'OC\\Preview\\TXT',
    1 => 'OC\\Preview\\MarkDown',
    2 => 'OC\\Preview\\OpenDocument',
    3 => 'OC\\Preview\\PDF',
    4 => 'OC\\Preview\\MSOffice2003',
    5 => 'OC\\Preview\\MSOfficeDoc',
    6 => 'OC\\Preview\\Image',
    7 => 'OC\\Preview\\Photoshop',
    8 => 'OC\\Preview\\TIFF',
    9 => 'OC\\Preview\\SVG',
   10 => 'OC\\Preview\\Font',
   11 => 'OC\\Preview\\MP3',
   12 => 'OC\\Preview\\Movie',
   13 => 'OC\\Preview\\MKV',
   14 => 'OC\\Preview\\MP4',
   15 => 'OC\\Preview\\AVI',
 )
```

### 参考

https://github.com/nextcloud/docker/issues/1432


# 7. バックグラウンドジョブを「Cron(推奨)」に変更
### 7.1. バックグラウンドジョブの設定を変更する
ブラウザでNextcloudを開き、管理者アカウントでログインし、
[管理者設定]-[基本設定]-[バックグラウンドジョブ]→「Cron(推奨)」を選択する。
→[バックグラウンドジョブ]画面に「最終ジョブ実行は xx時間前 です。何か問題が発生しています。」と表示される。

### 7.2. 対策を講じる

#### ・試行1
www-data 権限 #33 でコンテナに入って、cron.phpを実行する。
```bash
jo@herobox:~/docker-nextcloud$ sudo docker compose exec -u 33 nextcloud /bin/bash
www-data@15fa22bb3b8a:~/html$ php cron.php
www-data@15fa22bb3b8a:~/html$
```
→直後、[バックグラウンドジョブ]画面に「最終ジョブ実行は 数秒前 です。」と表示されたので、成功したと思われる。

#### ・試行2
WSL2(Ubuntu)からcron.phpを実行する。
```bash
jo@herobox:~/docker-nextcloud$ sudo docker compose exec -u 33 nextcloud php /var/www/html/cron.php
jo@herobox:~/docker-nextcloud$
```
→直後、[バックグラウンドジョブ]画面に「最終ジョブ実行は 数秒前 です。」と表示されたので、成功したと思われる。

#### ・WSL2(Ubuntu)のrootにて、cronを設定する

cronの設定を実施
```bash
sudo crontab -e
```
以下を追加
```bash
*/5 * * * * cd /home/jo/docker-nextcloud && docker compose exec -u 33 nextcloud php /var/www/html/cron.php
```
→以降、[バックグラウンドジョブ]画面に「最終ジョブ実行は x分前 です。」と常に5分以内の時間が表示されるようになった。

### 参考

https://help.nextcloud.com/t/nextcloud-docker-container-best-way-to-run-cron-job/157734/3

https://blog.nekoteam.com/archives/3061


# 8. 【おまけ】バックアップ運用
ちょっと変わったバックアップの取り方をしています。
まず、バックアップ対象は、Nextcloud上に保存されたデータのみ（写真データ等）です。
もっと正確に言うと、以下の図の、My PCに同期されたデータのみをUSB外付けHDDにバックアップしています。
Nextclud自体のバックアップは取っていません。
![自宅Nextcloud_概要-03_Backup.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407204/908a76e7-fb6e-e661-ab28-29fa854cdb9f.jpeg)


なぜこのようなバックアップ体制になっているかというと、理由は2つあります。
 1. もしハード故障などでNextcloudが動かなくなってしまっても、我が家ではそんなに困らないので、のんびり復旧or再構築するつもり。
 2. 以前からMy PCのデータをUSB外付けHDDにバックアップしていたので、運用負荷は変わらない。

メリットとしては、NextcloudとMy PCの間でデータを同期しているため、どちらかのディスクが壊れてもデータが失われない冗長構成になっている点が挙げられます。

### 8.1. 自動アップロード
スマホにNextcloudアプリをインストールして設定すればOK.
以下のNextcloudの公式サイトからダウンロード可能。

### 8.2. 自動同期
パソコンにNextcloudデスクトップクライアントをインストールして設定すればOK.
こちらも以下のNextcloudの公式サイトからダウンロード可能。

https://nextcloud.com/install/

### 8.3. バックアップ
My PCの内蔵HDD(F:)にあるデータをUSB外付けHDD(X:)にバックアップする方法をご紹介します。
Windowsのrobocopyコマンドを以下のようにバッチファイル化して、My PCから実行しています。
src,dst,logfileの値を適宜修正すれば使い回せます。
robocopyの結果がログファイルに出力されるようにしています。出力先のフォルダはあらかじめ作っておく必要があります。

```powershell:robocopy_nextcloud_F_to_X.bat
@echo off

set src="F:\Nextcloud"
set dst="X:\Nextcloud_BAK_X"
set logfile="C:\log\robocopy_nextcloud_F_to_X_%date:/=%.log"

robocopy %src% %dst% /COPYALL /E /NP /V /R:1 /W:1 /LOG+:%logfile%

rem robocopy %src% %dst% /COPYALL /MIR /NP /V /R:1 /W:1 /LOG+:%logfile%
```

:::note warn
警告
robocopyコマンドに、`/MIR`オプションをつけて実行する際は注意してください。
`/MIR`はディレクトリツリーをミラー化するオプションです。したがって、コピー元に存在しないディレクトリ及びファイルは、コピー先から削除されます。
万が一、コピー元やコピー先のパスを間違えていた場合、必要なデータが削除されてしまう可能性があります。
:::

繰り返しますが、`/MIR`オプションは非常に危険です。これからバックアップを取ろうという時に、誤ってデータを削除してしまっては絶望するしかありません。
そこで、初めてrobocopyを実行する時は`/MIR`をつけずに、代わりに`/E`をつけて実行するのが安全です。そうすることで、コピー元からコピー先へのコピーは行われますが、コピー先からデータが削除されることはありません。
上記のバッチファイル例では、`/MIR`をつけたコマンドの行を`rem`でコメントアウトしてあります。
1度目は`/E`で実行し、コピー元とコピー先が間違っていないことを確認した後、2度目以降は`/MIR`で実行すると良いでしょう。

その他のオプションの説明については、Microsoftのドキュメントをご参照ください。

https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/robocopy

以上