生きることは忘れること

Nextcloudでプライベートなオンラインストレージを構築する

環境構築記録です。今回は2019年夏に構築したものの式年遷宮です(もう5年以上も使っているのか……)。……え、Dockerで入れれば一発?まあそう言いなさんなって。

Preamble

Nextcloudはオープンソースのオンラインストレージソフトウェアです。これをサーバにインストールして走らせると、DropboxやBoxなどと似たオンラインストレージとして利用できます。今回はその環境を構築していくというわけです。

インストールするのはLinuxのマシンならなんでもよいですが、今回はRaspberry Piを使います。モデルはRaspberry Pi 4 Model Bのメモリ4GBを用意しました。また、ディストリビューションはRaspberry Piなら普通はRaspberry Pi OSを使うところですが、筆者はほかの環境ではArch Linuxを使っているので、管理コスト低減のためにここでもArch Linux ARMを使うことにしました。Raspberry PiにArch Linux ARMをLVMでインストールするをご覧ください。他のディストリビューションでもパッケージマネージャ周りを読み替えればだいたい動くと思います。

準備するものたち

全体構成

全体構成は次のようになります。

ユーザの作成

外部からのHTTPアクセスを受けるプログラムはrootで実行するべきではありません。Arch LinuxではApache httpdやnginxなどのHTTPサーバ用に http ユーザが存在しますが、ここではさらにそれとは別のユーザを作成しておきます(ユーザーとグループ - ArchWiki # システムユーザーを追加する例)。

$ sudo useradd --system -s /usr/bin/nologin -u 1999 -U nextcloud

PHP

ではここから一つずつ作っていきます。まずPHPです。

ビルド

パッケージでインストールすればいいという説もありますが、いろいろとモジュールが必要なので、PHPを自分でビルドします(Arch Linuxのnextcloudパッケージで入れればモジュールを含めてパッケージの依存関係で処理してくれるって?まあまあ……)。マニュアルはPHP: Unix および macOS システムでのソースコードからのインストール - Manualにありますが、基本的には PHP: Downloads からソースコードをtar.gzで落としてきて ./configure からの make というよくある流れです。

なお、筆者は旧環境からの移行の関係でPHP 8.1を使いますが、特に事情がなければ最新のPHPを入れればよいです。

$ mkdir ~/php
$ cd ~/php
$ curl -O https://www.php.net/distributions/php-8.1.31.tar.gz
$ tar -xf php-8.1.31.tar.gz
$ cd php-8.1.31
$ ./configure --enable-fpm --with-fpm-user=http --with-fpm-group=http --with-fpm-systemd --with-config-file-path=/etc --with-config-file-scan-dir=/etc/php.d --with-curl --enable-gd --with-avif --with-jpeg --with-webp --with-freetype --enable-mbstring --with-openssl --with-zip --with-zlib --with-pdo-mysql --with-mysql-sock=/run/mysqld/mysqld.sock --with-bz2 --enable-intl --with-sodium --enable-bcmath --with-gmp --enable-exif --enable-pcntl --enable-sysvsem

ここで、Nextcloudの要求するモジュールがドキュメントのPHP Modules & Configuration — Nextcloud latest Administration Manual latest documentationに載っているので、これに従ってconfigureのオプションに書き並べています(どうでもよいですが、モジュールによってconfigureのオプション名が違って、デフォルトで有効になっているモジュールもあるので、調べるのがクソ面倒です。正攻法はPHP: 拡張モジュールの一覧/分類 - Manualから各モジュールの「インストール手順」を辿ることで、抜け道は ./configure --help を目grepすることです)。optionalなモジュールは気分で入れているので、必要に応じて取捨選択してください。さらに sysvsem はドキュメント化されていませんが、Nextcloudインストール後の設定診断で推奨モジュールだと言われます。

fpmのユーザとグループはさっき作った nextcloud ではなく http を指定していますが、Nextcloud専用のPHPではないという気持ちでこうしておきます。

さて、上の通りに configure するといろんなパッケージが足りないと言われるので、適当に pacman -S します(参考までに今回は libpng libavif freetype2 oniguruma libsodium libzip でした)。それが済んだらいよいよ make です。

$ make -j4
$ make TEST_PHP_ARGS=-j4 test
$ sudo make install
$ php -v

私の環境ではmakeには30分ほどかかりました。気長に待ちましょう。

PECL拡張機能のインストール

一部のモジュールはPECL拡張機能なので、別途インストールします。昔は pecl install コマンドでインストールできたようなのですが、PHP7.4で

PEAR (PECL を含む) のインストールは、デフォルトで無効になりました。configure オプション --with-pear を使うことで明示的に有効にできますが、このオプションは推奨されなくなり、将来的に削除される可能性があります。

ということなのだそうです(PHP: その他の変更 - PHP 7.3.x から PHP 7.4.x への移行)。で、どうするかというと、書かれている通りPHPのビルドオプションでPEARを入れてもいいのですが、非推奨だしあまり気が乗らないので、PHP: phpize で共有 PECL 拡張モジュールをコンパイルする方法 - Manualでいきます。

対象は apcuimagick です。当たり前ですが imagick はpacmanで imagemagick パッケージのインストールが必要です。あと、これもドキュメント化されていないものの、Nextcloudインストール後にImageMagickはSVGサポート付きが推奨だと言われるのですが、それには librsvg が必要です。

インストールしたextensionは php.ini で読み込む必要がありますが、それは次でやります。

PHPの設定

一通り出来上がった後に書いているので天下りというか先回りなところがありますがご愛嬌。

php.ini

まず php.ini の場所ですが、上でしれっと --with-config-file-path=/etc と設定していたので /etc/php.ini になります。

extension = apcu.so
extension = imagick.so
zend_extension = /usr/local/lib/php/extensions/no-debug-non-zts-20210902/opcache.so
memory_limit = 512M
apc.enable_cli = 1

最初はさっきのextensionの読み込みです。次の opcache extensionはPHP: インストール手順 - OPcacheに書いてある通りです。それから memory_limit はNextcloudのドキュメントに書いてあります。 apc.enable_cli はNextcloudのCLIコマンドを実行するのに必要で、APCuモジュールはデフォルトではCLIだと無効という設定になっている(PHP: 実行時設定 - インストール/設定 - APCu)ため自分で書いてあげる必要があります(という旨はこれもドキュメントに明示的には書かれていなさそう……)。

また、 pdo_mysql.default_socket についても確認してください (php -i | grep pdo_mysql.default_socket)。これはPHPがMariaDBにアクセスするときに見に行く場所です。PHPのビルド時(configure実行時)にMariaDBが起動していてソケットが存在していれば、そこをデフォルトにしてビルドされるので、 php.ini で書く必要はありません。あるいはconfigure optionで指定することも可能です(上のコマンドではしれっと /run/mysqld/mysqld.sock の指定を含めておきました)。設定方法はなんでもよいし場所もどこでもよいのですが、(据わりのよい場所にするのと)MariaDBの設定と揃えることが必要です。

php-fpm.conf

次に php-fpm.conf です。場所は php.ini と同じ /etc/php-fpm.conf が無難で、 /usr/local/etc/php-fpm.conf.default からコピーして作成すると良いです。リファレンスはPHP: 設定 - FastCGI Process Manager (FPM)です。次に出てくる php-fpm.d を読み込むため

include = /etc/php-conf.d/*.conf

を書いておきます。他に必須で設定しなければいけない項目はないので、お好みで。筆者は

pid = /run/php-fpm.pid

error_log = /var/log/php-fpm.log

を設定しました。

php-fpm.d

そしてプールの設定ファイルが必要で、こちらの方が重要です。こちらも /usr/local/etc/php-fpm.d/www.conf.default からコピーして作成します。場所はさっき設定した /etc/php-fpm.d で、筆者は nextcloud.conf という名前にしました。さて内容ですが、まず

user = nextcloud
group = nextcloud

です。見ての通りphp-fpmでスクリプトを実行する際のユーザです。次に

listen = /run/php-fpm/nextcloud.sock

です。nginxとPHPの間はTCPかUNIXドメインソケットで通信するようになっていますが、ここではソケットファイルの場所を設定してUNIXドメインソケットを使うよう指示しています。nginx側の設定でも同じソケットファイルを指定してあげれば繋がるという次第です。続いて

listen.owner = http
listen.group = http
listen.mode = 0660

です。こちらのユーザとグループは、nginxが通信できるようにnginxのワーカプロセスのものにしておきます。

それから pm. で始まるプロセスマネージャ関連の設定について、Server tuning — Nextcloud latest Administration Manual latest documentation # Tune PHP-FPMでよさげな設定値を決めてあげるといいようです。

どんどんいきます。

access.log = /var/log/nextcloud/access.log

場所はお好みで(必要に応じて mkdir しておくこと)。

chdir = /opt/nextcloud

ワーキングディレクトリの設定です。後でNextcloudのスクリプトを置く予定の場所を入れておきます。

env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/bin

PATHは適当に(ここではArch Linuxの /etc/profile を眺めてそれっぽい値を入れました)。PHPをインストールした /usr/local 系のディレクトリは含めておいた方がいいでしょう、知らんけど。

php_admin_value[error_log] = /var/log/nextcloud/error.log
php_admin_value[memory_limit] = 512M
php_value[session.save_path] = /var/lib/nextcloud/session

エラーログの場所はお好みですがアクセスログと足並みを揃えておくといいでしょう。 memory_limitphp.ini と同じです。 session.save_path はなんだっけ、php-fpmのワーカプロセスがセッションのデータを読み書きするので nextcloud ユーザの権限がついている場所である必要があって、なのでデフォルトから変更した、ということだったと思います(指定したディレクトリは必要に応じて mkdir しておくこと)。

php-fpmのsystemdサービス

PHPをビルドしたディレクトリの下の sapi/fpm/php-fpm.service がサービス定義ファイルです。 /usr/local/lib/systemd/system にインストールしてくれればいいんですが、何故かそうなってません。まあ、どちらにせよコピーして /etc/systemd/system に置きます。内容は PIDFile の場所を php-fpm.conf の設定と合わせることと、 ExecStart のオプション --fpm-config の指定を php-fpm.conf の場所にすることくらいです。

PIDFile=/run/php-fpm.pid
ExecStart=/usr/local/sbin/php-fpm --nodaemonize --fpm-config /etc/php-fpm.conf
ExecStartPre=/bin/mkdir -p /run/php-fpm

nginxとcertbot

最近はこの手のリバースプロキシもいろいろ選択肢が出てきており、HTTPSを終端してHTTPでバックエンドに投げるくらいの目的であればnginx以外のソリューションも真面目に視野に入ってきます。が、今回はphp-fpmを動かそうとしているのでFastCGIが使えないといけなくて選択肢が減るのと、Nextcloudのドキュメントでnginxのサンプルconfigurationが提供されているので、とりあえずnginxでいきます(他の候補としてはCaddyが有力なんですかね……)。インストールは sudo pacman -S nginx です。

nginxの設定

それでnginxの設定ファイルですが、NGINX configuration — Nextcloud latest Administration Manual latest documentationから取ってきて適当に改造します。

パスの調整

まず、筆者は https://nextcloud.example/nextcloud のようなパスを切ってそこからNextcloudにアクセスしたいです。ところがここで提供されているconfigurationでは、そういう場合Nextcloudの本体を /var/www/nextcloud とかに置くことが前提になっています。今回は後述のように本体を /opt/nextcloud に置こうと思うので、そこを調整します。

元の設定ファイルで関係する部分を抜粋すると以下のようになります。

server {
    root /var/www;
    location ^~ /nextcloud {
        location ~ \.php(?:$|/) {
            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
            set $path_info $fastcgi_path_info;
            try_files $fastcgi_script_name =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $path_info;
            fastcgi_pass php-handler;
        }
        location /nextcloud {
            try_files $uri $uri/ /nextcloud/index.php$request_uri;
        }
    }
}

最後の location ディレクティブで(静的ファイル以外への)あらゆるリクエストを index.php に内部リダイレクトして、それを今度は手前の location ディレクティブで拾ってFastCGIに渡してやるという寸法です。それはいいのですが server ディレクティブ直下で root /var/www; としているのが気に食わないです。ルートパスの配下をどうするのか指図されたくありません。そこで外側の location ディレクティブの下に alias ディレクティブを書く形に変えてやります。

server {
    location ^~ /nextcloud {
        alias /opt/nextcloud;
        location ~ \.php(?:$|/) {
            fastcgi_split_path_info ^/nextcloud(/.+?\.php)(/.*)?$;
            set $path_info $fastcgi_path_info;
            try_files /nextcloud$fastcgi_script_name =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param SCRIPT_NAME /nextcloud$fastcgi_script_name;
            fastcgi_param PATH_INFO $path_info;
            fastcgi_pass nextcloud;
        }
        location /nextcloud {
            try_files $uri $uri/ /nextcloud/nextcloud/index.php$request_uri;
        }
    }
}

ここで、 root ディレクティブはURLパスのルートを root ディレクティブで指定したOS上の実パスに対応付けます。すなわち root /var/www; とすると、 /index.html へのリクエストは /var/www/index.html に、 /nextcloud/login へのリクエストは /var/www/nextcloud/login に対応付けられ、それは location ディレクティブ下に記述しても同じです。一方 alias ディレクティブは location コンテキストで記述する必要があり、その location のURLパスを alias ディレクティブで指定したOS上の実パスに対応付けます。具体的には、今回でいうと location ^~ /nextcloud ディレクティブのコンテキストで alias /opt/nextcloud; と書いたので、URLパス /nextcloud/opt/nextcloud に対応するという意味になり、 /nextcloud/login へのリクエストは /opt/nextcloud/login に対応付けられます。 root /opt; なんておっかなくて書けませんので、こういう形で設定をすることになります。なお、このあたりのドキュメントはModule ngx_http_core_moduleにありますが、記述が薄味すぎて正直これ単体で物事が解決したことがありません……。

さて、この変更に合わせて弄らなければいけない点が2つあります。まず1つは単純ではあるのですがふざけた話で、最後の部分で index.php に内部リダイレクトするために try_files を使っていますが、nginxには try_filesalias を組み合わせたときの挙動にバグがあり、本来は /nextcloud/index.php と指定すればいいはずのところ、 alias での対応付け元となるlocationパス /nextcloud の部分が消失して /index.php というURLパスに内部リダイレクトされてしまいます (#97 (try_files and alias problems) – nginx)。意味が分かりません。このバグが最初に報告されたのが2012年だというのでなおさら理解に苦しみます。workaroundは単純に消失してしまうことを見越して /nextcloud/nextcloud/index.php と親パスの部分を二重にした形で書いておくことです。

もう1つはFastCGIでPHPにリクエストを渡す部分です。

CGIとFastCGIあれこれ

歴史を語れば、FastCGIの源流にはCGIという技術があります (RFC 3875)。こいつは20年くらいまえに隆盛を極めたブツで、Apache httpdのようなWebサーバが裏にいるスクリプト(CGIスクリプト)にリクエストを渡し、そのスクリプトからレスポンスを返すことで動的なWebページを作成できる、というものでした。今だと動的なWebページとか当たり前すぎて何言ってんだという感じですね。CGIの時代にはリクエストが来たときにその都度スクリプトのプロセスが起動される(1リクエスト1プロセス)という仕組みになっていて、プロセスが起動されるときに環境変数でリクエストのメタ情報(パスやクエリ文字列の情報)が渡されるようになっていました。たとえば、 /cgi-bin/forum.cgi/topic/example?comment=42 というリクエストが来ると、 /cgi-bin/forum.cgi が実行され、 PATH_INFO 環境変数に /topic/example が、 QUERY_STRING 環境変数に comment=42 が入っている、という具合です。

1リクエスト1プロセスはさすがに効率が悪いので、それを改良したのがFastCGIです。FastCGIではバックエンドのプログラムは常時起動していて、TCPやUNIXドメインソケットなどでWebサーバからリクエストを受け取ります。一方でCGIの延長線上にある技術なので、CGIにおいて環境変数で受け渡していたものに相当するデータをやり取りすることができます。

今回重要な役割を果たすのは SCRIPT_FILENAMESCRIPT_NAME の2つの変数です。FastCGIでは1個のプロセスが1個のリクエストに対応しないので、プロセスの実行ファイル(たとえばPHPのバイナリ)とは別に各リクエストに対応するスクリプトファイル(たとえば .php ファイル)の情報をリクエストごとに引き渡す必要があり、それに SCRIPT_FILENAME 変数が使われます。具体的には、PHPの場合はPHPファイルのOS上の実パス(たとえば /opt/nextcloud/index.php )を入れます。これを正しく指定しないとPHP処理系が実行すべきPHPファイルを特定できないのでまったく動きません。もう1つの SCRIPT_NAME 変数は SCRIPT_FILENAME に対応するURL上のパス(たとえば /nextcloud/index.php )になります。これはCGI時代からあるもので、スクリプト上でサイト内リンクを生成する場合に SCRIPT_NAME で渡されるURLパスの情報を使って正しい階層のサイト内リンクを作る、といった使い方をされます。

イマドキのWebアプリのバックエンドではこういうCGIの変数はほぼ忘れ去られており、特にJavaScriptで実装されたバックエンドでは一切登場しません。一方でこの系譜を引き継いでいるケースもあり、たとえばRailsをはじめとしたRuby製Webアプリケーションサーバのリンガ・フランカとなっているRack SpecificationはCGI変数が基礎になっています。PHPもその例に漏れず、PHPの世界では $_SERVER 連想配列にCGI変数に相当する情報が格納された状態でスクリプトが起動されるというのが大前提になっています(PHP: $_SERVER - 定義済み変数)。これはPHPがCGIやFastCGIで実行されている場合に限らず、Apache httpdのモジュールとしてPHPが実行されている場合も同じです。そんなわけで、nginxからNextcloudにリクエストを渡す際にはCGI変数のことを気にかけてあげないといけないのです。

さて、ここでnginxの fastcgi_split_path_info ディレクティブについて説明しないといけません。かつてのCGIの流儀では、上の例にもあるように /cgi-bin/forum.cgi/topic/example といった形式のURLパスでリクエストをしていました。WebサーバはここからCGIスクリプトファイルのパスに一致する部分を切り出し、余った部分を PATH_INFO 環境変数に格納する、という処理をしていました。nginxでこの流儀に沿った処理を実現するために用意されているのが fastcgi_split_path_info です。このディレクティブには2つのキャプチャグループ(括弧)を持つ正規表現を引数に渡します。nginxがリクエストを処理する際には、リクエストのURLパスに対してこの正規表現をマッチさせ、1個目のキャプチャグループの結果をnginxの $fastcgi_script_name 変数に、2個目のキャプチャグループの結果を $fastcgi_path_info 変数に格納します。つまり先ほどの例では /cgi-bin/forum.cgi の部分が1個目のキャプチャグループにマッチし、 /topic/example の部分が2個目のキャプチャグループにマッチするような正規表現を fastcgi_split_path_info ディレクティブに書いてやればいいことになります。 fastcgi_split_path_info はnginxの変数に値を入れるだけですが、 fastcgi_param ディレクティブを使ってこれらの変数を実際にFastCGIに渡してあげれば完成です。実はnginxの設定ディレクトリには /etc/nginx/fastcgi_params というファイルが配置されており、このファイルを include することで fastcgi_param ディレクティブをいい感じに呼んでくれます。

もとのNextcloud公式の設定ファイルでは root /var/www; でしたから、この /var/www にURL上のパス /nextcloud/index.php を連結してやればOS上の実パス /var/www/nextcloud/index.php が求まります。なので fastcgi_split_path_info/nextcloud/index.php の部分を $fastcgi_script_name として切り出してやって SCRIPT_NAME に使い、さらに /var/www を前に連結したものを SCRIPT_FILENAME に指定してあげればOKでした。 /var/wwwroot ディレクティブで指定した値ということでnginxの $document_root 変数に入っていますから、 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; で実現できます。

今回Nextcloudを /opt/nextcloud に配置したので、URLパスが /nextcloud/index.php のリクエストに対しては、 /opt/nextcloud の下の /index.php を呼び出さなければなりません。これが SCRIPT_FILENAME になります。一方 SCRIPT_NAME はURLパスの話なので /nextcloud/index.php を指定してあげる必要があります。そこで fastcgi_split_path_info では $fastcgi_script_name として一旦 /nextcloud の部分を除いた /index.php がキャプチャされるようにしてやり、これを /opt/nextcloudalias ディレクティブで指定したので $document_root にはこれが入っている)と連結して fastcgi_param SCRIPT_FILENAME $document_root$fastcgit_script_name; とします(ここだけ見ると結果としての見た目はたまたま変わらずです)。そしてこのままでは SCRIPT_NAME が壊れてしまうので、 fastcgi_param SCRIPT_NAME /nextcloud$fastcgi_script_name; と明示的に指定してあげることでこちらも正しい値が入るようにします。これで設定が完成です。

……と、nginxを正しく動かすには非常に長い説明がいるのですが、私もCGI変数まわりの話を忘却していたので、先の try_filesalias のバグの件も含めてたいへん試行錯誤しました。余談ですが、上の設定ファイルで $fastcgi_path_info を一度 $path_info 変数に待避しているのは、 try_files ディレクティブを挟むと $fastcgi_path_info の値が消失するという“仕様” (#321 (try_files & $fastcgi_path_info) – nginx)があるためです。前々からApache httpdの設定ファイルは書きたくないと思っていましたが、今回nginxの設定ファイルも同じくらい書きたくない気持ちになりました。

nginxの設定におけるその他のポイント

nginxの設定に関して他のポイントを2個ほど書いておきます。

upstream nextcloud {
    server unix:/run/php-fpm/nextcloud.sock
}

ここではphp-fpmの設定で指定したUNIXドメインソケットを指定します。upstream名は任意ですがここで命名したものを fastcgi_pass ディレクティブで使います。

server {
    listen 80;
    listen [::]:80;
    server_name nextcloud.example;

    location / {
        return 301 https://$host$request_uri;
    }

    location ^~ /.well-known/acme-challenge/ {
        allow all;
        root /var/lib/certbot/nextcloud;
        default_type "text/plain";
    }
}

/.well-known/acme-challenge へのリクエストをcertbotが拾えるようにします。Certbot - ArchWiki # Webrootとかも見てください。

あとはお好みで。

certbot

インストールは sudo pacman -S certbot です。あとは

$ sudo certbot certonly --webroot -w /var/lib/certbot/nextcloud -d nextcloud.example

で。もちろん -w で渡すディレクトリはnginxで設定したものと合わせます。それから $ sudo systemctl enable certbot-renew.timer しておけば自動更新もしてくれるのですが、そのままだと自動更新が走った後にnginxをリロードしないと反映されなくて哀しいです。 $ sudo systemctl edit certbot-renew.service

[Service]
ExecStart=
ExecStart=/usr/bin/certbot renew -q --post-hook "systemctl reload nginx"

とか書いてあげればいいはずです。

MariaDB

インストールしてユーザ周りなど基本的なセットアップをするだけです。ぶっちゃけデータベースのユーザ管理は苦手分野です。

インストールは sudo pacman -S mariadb でOKです。まず /etc/my.cnf.d/server.cnf[server] オプショングループに

character-set-server = utf8mb4
collation-server = utf8mb4_general_ci

を書き、 /etc/my.cnf.d/client.cnf[client] オプショングループに

default-character-set = utf8mb4

を書きます。ちなみにMariaDB 11.8以上では utf8 文字セットが utf8mb4 の別名になるそうです (Unicode - MariaDB Knowledge Base)。時代を感じます。実はArch Linuxではかなり前から character-set-server = utf8mb4collation-server = utf8mb4_unicode_ci がビルド時に設定済みのようですが。

それから /etc/my.cnf[client-server] オプショングループに

socket = /run/mysqld/mysqld.sock

を書きます(これもArch Linuxではビルド時に設定済みなんですが)。

他の設定として、Nextcloudのドキュメントでは transaction-isolation = READ-COMMITTEDbinlog-format = ROW を書けということになっています(書くとしたら [server] に)。グローバルにDBの挙動を変えるのでちょっと躊躇いますが……

設定ファイルの整備が済んだら、MariaDB - ArchWikiDatabase configuration — Nextcloud latest Administration Manual latest documentationを眺めつつコマンドを叩くだけです。

$ sudo mariadb-install-db --user=mysql --basedir=/usr --da
tadir=/var/lib/mysql
$ sudo systemctl enable mariadb
$ sudo systemctl start mariadb
$ sudo mariadb-secure-installation
$ sudo mariadb
MariaDB [(none)]> CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY '...';
MariaDB [(none)]> CREATE DATABASE nextcloud;
MariaDB [(none)]> GRANT ALL PRIVILEGES on nextcloud.* to 'nextcloud'@'localhost';
MariaDB [(none)]> quit;

テーブル定義とかはNextcloudの初期設定中に勝手にやってくれます。

Nextcloud

本体の配置

Nextcloud本体はtarballを解凍してchownするだけです (Installing from command line — Nextcloud latest Administration Manual latest documentation)。場所ですが、Filesystem Hierarchy Standard (FHS) に則るとこういうソフトウェアは /opt に置くのが正解です(決して /usr/local 配下ではない。PHPがwwwドキュメントの延長線上だという解釈を取れば /var 配下と思えないことはないが、現代的なPHPアプリケーションではちょっとstraightforwardな解釈じゃない気がする)。

Nextcloudの最新版はDownload and install Nextcloudからダウンロードできます。古いバージョンはNextcloud server changelogにあります。

$ curl -O https://download.nextcloud.com/server/releases/nextcloud-31.0.0.tar.bz2
$ sudo tar -xf nextcloud-31.0.0.tar.bz2 -C /opt
$ sudo chown -R nextcloud:http /opt/nextcloud

ユーザは nextcloud ユーザですが、グループは http グループにしておきます。nginxのワーカプロセスが静的ファイルを読み取る必要があるためです。このあたりの所有者やパーミッションの設定はアップデート時に壊れがち(壊しがち?)なので適当にだましだまし直して使っています。

データフォルダ

Nextcloudのデータフォルダ(ユーザがアップロードするファイルが置かれるフォルダ)はNextcloudのPHPスクリプト本体が置かれている場所とは別の場所にすることができます。今回は /var/lib/nextcloud/data とし、このディレクトリに外付けストレージをマウントすることでユーザのファイルを外付けストレージ上に保存するようにします。データフォルダの場所はNextcloudの config.php で指定するようになっており、次に出てくる初期設定ウィザードに含まれているので、あらかじめマウントを済ませておきます。もちろん所有者とグループは nextcloud:nextcloud にして、パーミッションも0750か0700あたりにしておくといいでしょう。

初期設定とチューニング

この状態で既にHTTP(S)アクセスできるようになっているはずです。Nextcloudの設定を何もしていない状態でHTTPアクセスすると初期設定ウィザードが開いてデータフォルダの場所やデータベースの認証情報を聞かれるので、そのままブラウザから入力していけば使い始められます( config.php やデータベースに書き込まれる)。もしくは occ maintenance:install コマンドを叩いてもいいです(上にも貼ったcommand lineインストールのドキュメントに書いてあります)し、手で config.php を編集してもいいです。

管理者ユーザを作成して管理者設定画面を開くと、セキュリティやパフォーマンス関連の診断結果が表示されます(賢い)。不備のある部分があれば適当に修正していきましょう。一例として筆者はPHPの opcache.interned_strings_buffer の設定値を大きくしろと言われたので php.ini に書き足しました。

振り返り

それぞれのパーツがどこで繋がっていたか振り返っておきます。

結構複雑ですね。頭の中に相関図でも描きながら読んでください。

Samba

おまけのセクションです。Nextcloudと本質的な関係はほとんどありません。筆者はクライアントにあたるWindowsマシンでNextcloudの同期を設定するにあたって、所要時間やストレージの関係から一部のフォルダを除外しておこうと考えました。しかし除外したフォルダにアクセスしたいこともあります。それにはNextcloudのデータが入っているフォルダをSMBでLAN内に公開すればよく、Sambaを入れて少し設定を書けば実現できます。ぶっちゃけSMBってしょっちゅう脆弱性が見つかっている気がしてならないですが、SMBバージョン3系はそこまでではないはず……?

注:SMBをインターネットに公開しないよう注意してください。最低限でもNA(P)Tやファイアウォールの配下で利用することをおすすめします。

むかし書いたSambaの設定ファイルがあるのですが、何を参考にして作ったのかまったく思い出せません。といってもデフォルトが割とよくできているので、基本的に共有を1つ作って(= smb.conf にセクションを1つ作って) path パラメータでWindows側に公開したいディレクトリを指定してあげるだけでそんなに問題はない気がします。Nextcloudの仕様でユーザのファイルはデータディレクトリの下の <username>/files 以下に格納されているので、筆者は /var/lib/nextcloud/share にシンボリックリンクを貼りました。あと細かい部分はsmb.conf(5)の極めて長大なmanページを眺めて調整すればいいはずです。今回は valid user = nextcloudwriteable = yes あたりは書いておかないとまずいでしょうか。本当はLinuxユーザをSamba用に用意したいところですが、Nextcloudのデータディレクトリに違うユーザが所有者のファイルができても困るのでそういうわけにはいかなさそうです。まとめると最低限は以下で、後はお好みです。

[Share]
path = /var/lib/nextcloud/share
valid users = nextcloud
writeable = yes

で、 nextcloud ユーザに対応するSamba用の認証情報を手で作成する必要があります。

$ sudo pdbedit -a -u nextcloud

そしてサービスを上げましょう。

$ sudo systemctl enable --now smb

これであとはWindowsのエクスプローラーから \\192.0.2.2\Share のようにアクセスすればSMBで見れると思います。サーバ自身のファイアウォールを有効化している場合はTCPの139と445を開ければいいはずです。筆者の手元ではsystemd-resolvedでmDNSとかLLMNRが有効になっていたので、よくわかりませんがこの時点で名前でもアクセスできました(mDNSはUDPの5353番、LLMNRはTCPとUDPの5355番ポートが必要かな)。あと、ルータがDHCPのオプション12でクライアントから受け取ったホスト名を覚えていてDNSで聞かれたときに答えてくれるという動きもしているようです。

あとはネットワーク探索を有効にすればWindowsエクスプローラーの「ネットワーク」のところに出てきます。これは nmb を上げればいい……というのは古くなりつつある話で、イマドキのWindowsはNetBIOSじゃなくてWSDというのを使ってローカルネットワーク上のマシンを探すらしいです。Linuxでは wsdd を入れればWSDを喋れて、これはインストールしてサービスを上げるだけです(必要なポートは3702/udpと5357/tcp)。

今回あらためてSambaのドキュメントをつまみ読みしましたが、それにしてもSambaって本気で使うとActive Directoryドメインコントローラまで作れちゃうんですね。なんというか執念を感じます。