ポート名を指定して既存のプリンタドライバを削除する

こちらのように”rundll32.exe printui.dll,PrintUIEntry ~”を使用すればコマンドラインからプリンタをインストールする事で設定の共通化を出来るが、既に同一ポート名でプリンタが導入されていた場合に複数のプリンタドライバをインストール出来てしまう。同一ポートを使用するプリンタドライバをインストールしてしまうと、設定⇒デバイス⇒プリンタとスキャナーで表示したときに、一つに集約されてしまい、非常に扱い難くなる。

そこで事前に以下のPowershellスクリプトでポート名を指定して既存プリンタを削除しておく事で、同一ポート名で重複登録することを防ぐとよい。

#RemovePrinterWithPort.ps1
$removePort = $args[0];
$printerNameKey = "プリンター名";
$portNameKey = "ポート名";

$printerName = "";
$portName = "";
cscript $env:SystemRoot\System32\Printing_Admin_Scripts\ja-JP\prnmngr.vbs -l | ForEach-Object {
    if ($_ -match ('^' + $printerNameKey + '.*'))
    {
        $printerName = $_.Replace(($printerNameKey + " "), "");
    }

    if ($_ -match ('^' + $portNameKey + '.*'))
    {
        $portName = $_.Replace(($portNameKey + " "), "");
        if ($portName -eq $removePort)
        {
            ("Remove Printer : " + $printerName);
            cscript $env:SystemRoot\System32\Printing_Admin_Scripts\ja-JP\prnmngr.vbs -d -p $printerName;
        }
    }
}

exit;

上記スクリプトをRemovePrinterWithPort.ps1で保存し、以下のコマンドでコマンドプロンプトから呼び出す。

 powershell.exe -Command .\RemovePrinterWithPort.ps1 IP_192.168.0.10

コマンドプロンプトからプリンタドライバをインストールする(Canon LIPS LX)

コマンドプロンプト(BATファイル)からサイレントにプリンタドライバをインストール手順です。コマンドプロンプトからインストール出来ると、キッティングやメンテナンスの手間を大幅に減らせます。CanonのLIPS LXドライバを例としていますが、他社のドライバでも本質的には同じです。

CanonのWEBサイトからダウンロードしたインストーラを起動し、展開されたファイルから.\x64\Driver下の以下のファイルを取り出します。

  • cnlb0m.cat
  • CNLB0MA64.INF
  • gpb0.cab

INFファイルの中味を確認する。以下の”Canon Generic Plus LIPSLX”がドライバ名にあたる。

; Canon Generic Plus LIPSLX printer INF for Microsoft Windows (x64)
; Copyright CANON INC. 2017
; CNLB0MA64.INF

[Version]
Signature="$Windows NT$"
Provider=%CANON%
ClassGUID={4D36E979-E325-11CE-BFC1-08002BE10318}
Class=Printer
DriverVer=06/26/2023,2.90.0.0
CatalogFile=CNLB0M.CAT

[Manufacturer]
"Canon" = Canon,NTamd64,NTamd64.6.0

;64-bit x64
[Canon.NTamd64]
"Canon Generic Plus LIPSLX"             = GPBDL,,1284_CID_CA_XPS_OIP
"Canon Generic Plus LIPSLX"             = GPBDL,,1284_CID_CA_UFRII_COLOR_OIP
"Canon Generic Plus LIPSLX"             = GPBDL,,1284_CID_CA_UFRII_BW_OIP
"Canon Generic Plus LIPSLX"             = GPBDL,CanonGeneric_V3_PrinC2DB

以下の様なバッチファイルを作成する。
1.prnmngr.vbsで既存プリンタ設定を列挙し既にインストール済みなら実行しない。
2.prnport.vbsでプリンタポートを作成
3.rundll32.exe printui.dll,PrintUIEntry~でドライバをインストールする。Canon以外のプリンタの場合には/fオプション以下のINFファイル名、/mオプション以下のプリンタドライバ名が変わる事になる。/bオプション以下は任意のプリンタ名となる。

set exe_dir=%~dp0

rem IPアドレス、ポート名を格納
set ip_addr=192.168.0.10
set ip_port=IP_192.168.0.10
set prname=iR-ADV C5850

rem ログ出力
set log_file=install.log
set driver_path=%exe_dir%

rem プリンタインストール済みかチェック
set prnmngr=C:\Windows\System32\Printing_Admin_Scripts\ja-JP\prnmngr.vbs
cscript %prnmngr% -l | findstr /l /C:"%prname%"
if %ERRORLEVEL%==0 GOTO END

:INSTALL
rem ポートの追加
set prnport=C:\Windows\System32\Printing_Admin_Scripts\ja-JP\prnport.vbs
cscript %prnport% -a -s %computername% -r %ip_port% -h %ip_addr% -o raw -n 9100 >> "%exe_dir%%log_file%

rem プリンタドライバの追加
rundll32.exe printui.dll,PrintUIEntry /if /f "%driver_path%\CNLB0MA64.INF" /v "Windows 10" /m "Canon Generic Plus LIPSLX" /r "%ip_port%" /b "%prname%"

:END

BIG-IP EDGE CLIENTが正常に動作しない

BIG-IP EDGE CLIENTが正常に動作しない場合、接続時にバックグラウンドで自動的にダウンロードするモジュールが壊れている場合があります。BIG-IP EDGE CLIENTは必要なファイルを”C:\Windows\Downloaded Program Files”にダウンロードしますので、管理者権限で当該フォルダの中にあるファイルを全て削除してから再実行してみてください。

当該フォルダはBIG-IP EDGE CLIENT以外のアプリケーションも使用している可能性があります。ですが仮に削除しても、実行時に再度ダウンロードするはずなので問題はありません。

OpenTKとC#でOpenCLプログラミング

C#でお手軽にOpenCLを使ったプログラムを作れないかと思い、OpenTKを使用。

OpenTK 3.xではOpenCLの機能がClooとして独立したようですが、現行のOpenTK 4.xでは再びOpenCLが統合されています。

まずはNugetパッケージマネージャからOpenTK 4.7.xをプロジェクトに追加します。

サンプルプログラムはhttps://github.com/opentk/opentk/tree/master/tests/OpenToolkit.OpenCL.Testsにあるので参照。

using OpenTK.Compute.OpenCL;
using System.Text;
CL.GetPlatformIds(0, null, out uint platformCount);
CLPlatform[] platformIds = new CLPlatform[platformCount];
CL.GetPlatformIds(platformCount, platformIds, out _);
Console.WriteLine(platformIds.Length);
foreach (CLPlatform platform in platformIds)
{
    Console.WriteLine(platform.Handle);
    CL.GetPlatformInfo(platform, PlatformInfo.Name, out byte[] val);
    Console.WriteLine(System.Text.Encoding.ASCII.GetString(val));
}
foreach (IntPtr platformId in platformIds)
{
    CL.GetDeviceIds(new CLPlatform(platformId), DeviceType.All, out CLDevice[] deviceIds);
    CLContext context = CL.CreateContext(IntPtr.Zero, (uint)deviceIds.Length, deviceIds, IntPtr.Zero, IntPtr.Zero, out CLResultCode result);
    if (result != CLResultCode.Success)
    {
        throw new Exception("The context couldn't be created.");
    }
    string code = @"
                __kernel void add(__global float* A, __global float* B,__global float* result, const float mul)
                {
                    int i = get_global_id(0);
                    for (int j =0;j < 60000;j++)
                    {
                        for (int k =0;k < 60000;k++)
                        {
                            result[i] = (A[i] + B[i])*mul;
                        }
                    }
                }";
    CLProgram program = CL.CreateProgramWithSource(context, code, out result);
    CL.BuildProgram(program, (uint)deviceIds.Length, deviceIds, null, IntPtr.Zero, IntPtr.Zero);
    CLKernel kernel = CL.CreateKernel(program, "add", out result);
    int arraySize = 20;
    float[] A = new float[arraySize];
    float[] B = new float[arraySize];
    for (int i = 0; i < arraySize; i++)
    {
        A[i] = 1;
        B[i] = i;
    }
    CLBuffer bufferA = CL.CreateBuffer(context, MemoryFlags.ReadOnly | MemoryFlags.CopyHostPtr, A,
        out result);
    CLBuffer bufferB = CL.CreateBuffer(context, MemoryFlags.ReadOnly | MemoryFlags.CopyHostPtr, B,
        out result);
    float[] pattern = new float[] { 1, 3, 5, 7 };
    CLBuffer resultBuffer = new CLBuffer(CL.CreateBuffer(context, MemoryFlags.WriteOnly,
        new UIntPtr((uint)(arraySize * sizeof(float))), IntPtr.Zero, out result));
    try
    {
        CL.SetKernelArg(kernel, 0, bufferA);
        CL.SetKernelArg(kernel, 1, bufferB);
        CL.SetKernelArg(kernel, 2, resultBuffer);
        CL.SetKernelArg(kernel, 3, -1f);
        CLCommandQueue commandQueue = new CLCommandQueue(
                CL.CreateCommandQueueWithProperties(context, deviceIds[0], IntPtr.Zero, out result));
        CL.EnqueueFillBuffer(commandQueue, bufferB, pattern, UIntPtr.Zero, (UIntPtr)(arraySize * sizeof(float)), null,
            out _);
        //CL.EnqueueNDRangeKernel(commandQueue, kernel, 1, null, new UIntPtr[] {new UIntPtr((uint)A.Length)},
        //	null, 0, null,  out CLEvent eventHandle);
        CL.EnqueueNDRangeKernel(commandQueue, kernel, 1, null, new UIntPtr[] { new UIntPtr((uint)A.Length) },
            null, 0, null, out CLEvent eventHandle);
        CL.Finish(commandQueue);
        CL.SetEventCallback(eventHandle, (int)CommandExecutionStatus.Complete, (waitEvent, data) =>
        {
            float[] resultValues = new float[arraySize];
            CL.EnqueueReadBuffer(commandQueue, resultBuffer, true, UIntPtr.Zero, resultValues, null, out _);
            StringBuilder line = new StringBuilder();
            foreach (float res in resultValues)
            {
                line.Append(res);
                line.Append(", ");
            }
            Console.WriteLine(line.ToString());
        });
        //get rid of the buffers because we no longer need them
        CL.ReleaseMemoryObject(bufferA);
        CL.ReleaseMemoryObject(bufferB);
        CL.ReleaseMemoryObject(resultBuffer);
        //Release the program kernels and queues
        CL.ReleaseProgram(program);
        CL.ReleaseKernel(kernel);
        CL.ReleaseCommandQueue(commandQueue);
        CL.ReleaseContext(context);
        CL.ReleaseEvent(eventHandle);
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
        throw;
    }
}

GPU上で実行されるプログラムはC言語で記述できます。

拍子抜けするぐらい簡単にOpenCLを使用したプログラムを作れるんですね。

どうしてもGPUへのデータ転送がボトルネックになりやすいので、高速化するのは中々難しいですが、機会があれば活用したいところ。

Docker上で実行しているNextCloudで”Updates between multiple major versions and downgrades are unsupported. Update failed.”が発生してアップデートに失敗する。

Dockerを使ってNextCloudを運用して居る。久しぶりにNextCloudバージョン画面を見たところ、「保守終了したバージョン」との警告が。あわててアップデートを行ったところ、ずっとメンテナンスモードから戻らなくなってしまった。そう、うっかりとversion 23→version 25にアップデートを掛けてしまったため、処理が正常に終了せずに環境が壊れてしまったのだ。

以下のコマンドを実行してメンテナンスモードを強制的に解除してNextCloudをWERBで開くがUpgradeの確認画面になり、そしてUpgradeは失敗してしまう。

$ sudo docker exec -u www-data <container name> php occ maintenance:mode --off

以下のコマンドを実行してコマンドラインからUpgradeを実施するが、「Updates between multiple major versions and downgrades are unsupported. Update failed.」のエラーになってしまう。

$ sudo docker exec -u www-data <container name> php occ upgrade

リカバリ方法がないかと探していたところ「How to fix an accidental Nextcloud docker image update」にたどり着いたので、こちらの方法でリカバリを試みることにする。

まずhtml/config/config.phpを開き、正確なバージョンを確認する。ここでは「’version’ => ‘23.0.0.10’,」とあるとおり、23.0.0.10が正確なージョンになる。

  'datadirectory' => '/var/www/html/data',
  'dbtype' => 'mysql',
  'version' => '23.0.0.10',
  'overwrite.cli.url' => 'https://example.net',
  'dbname' => 'nextcloud',

次にhtml/version.phpを編集する。既にversion 25のファイルに置き換わってしまっているため、これを書き換えて23→24へのアップグレードを通すために、$OC_Versionをarray(25,0,2,3)からarray(23,0,0,10)に、$OC_VersionStringを’25.0.2’から’23.0.0’に、array (‘24.0’ => true, ‘25.0’ => true,)をarray (‘23.0’ => true, ‘24.0’ => true,)に、下記の通りに書き換えます。

<?php
$OC_Version = array(23,0,0,10);
$OC_VersionString = '23.0.0';
$OC_Edition = '';
$OC_Channel = 'stable';
$OC_VersionCanBeUpgradedFrom = array (
  'nextcloud' =>
  array (
    '23.0' => true,
    '24.0' => true,
  ),
  'owncloud' =>
  array (
    '10.11' => true,
  ),
);
$OC_Build = '2022-12-08T11:32:17+00:00 9c791972de9c2561a9b36c1a60e48c25315ecca5';
$vendor = 'nextcloud';

続いてdocker-compose.ymlを編集します。下記のようにnextcloud:24のようにアップデート先となる旧バージョンのdockerイメージを指定します。

  nextcloud:
    image: nextcloud:24

これで23→24にアップデートする環境が整うので、以下のコマンドを実行してNextCloudを起動し、23から24へのアップデートを実行します。

$ sudo docker-compose up --build -d
$ sudo docker exec -u www-data <container name> php occ upgrade

version 24へのアップデートが正常に終了したら、再びdocker-compose.ymlを編集して元に戻します。その後に再び下記のコマンドを実行すれば、24から25へのバージョンアップを行えます。

$ sudo docker-compose up --build -d
$ sudo docker exec -u www-data <container name> php occ upgrade

PowershellからBITS(Background Intelligent Transfer Service)を使用して大容量ファイルを配布する

BITSはMicrosoftがWindowsに標準機能として載せている分散ダウンロード機能です。WindowsUpdateもバックグラウンドでBITSを利用しており、LAN内の複数のPCからWindowsUpdateをダウンロードする場合には、他のパソコンが自動的にキャッシュサーバーとなることで、インターネットとの通信負荷を押させてくれます。このBITSはWindowsUpdate専用の機能というわけではなく、簡単なプログラムを用意すれば、大容量ファイルを配布するときに自由に活用することができます。

昨今、プログラムやセキュリティパッチのフットプリント(ファイルサイズ)が大きく鳴り続ける、パッチ配布のネットワーク負荷が原因でインターネットが輻輳するなんて事件もありましたね。社内ネットワーク(WAN)はその構造上、どうしても一か所に負荷が集中しやすく、分散ダウンロードができると随分と助かりますね。

BITSでダウンロードするための一連の流れは次のようになります。

  1. HTTPサーバー上にダウンロード元となるファイルを用意します。
  2. Start-BitsTransferでBITSに新しいダウンロード要求を登録します。
  3. 定期的にGet-BitsTransferを呼び出しダウンロードの完了を待ちます。
  4. ダウンロード完了後にComplete-BitsTransferでファイルに書き出します。

上記をPowershell Scriptで記述したのが下記です。このスクリプトをダウンロードが完了するまで定周期で実行します。

$displayName = 'BITS_Sample'; # BITSにダウンロード要求を登録する時の表示名
$fromURL = 'http://www.example.co.jp/BITS_Sample.zip'; # ダウンロード元のURL
$destFile = 'C:\TEMP\BITS_Sample.zip'; # ダウンロード先のファイル名
$logFile = 'C:\TEMP\BITS_Sample.log' # ログ出力先のファイル名

$noBitsInstance = $true;
$completeDownload = $false;

Add-Content -Path $logFile -Value ('Start Script:' + (Get-Date));

# ダウンロード先フォルダが無ければ作成しておく
if ($false -eq (Test-Path 'C:\TEMP')){
    mkdir 'C:\TEMP';
}

# ダウンロードファイルが
if ($false -eq (Test-Path $destFile)){
    # BITSへのダウンロード要求を列挙する
    Get-BitsTransfer | Where-Object {
        Add-Content -Path $logFile -Value ('BITS Status:' + $_.DisplayName + '-' + $_.JobState);
        # 表示名の一致しているダウンロード要求が転送終了になるまで待機
        if ($_.DisplayName -eq $displayName){
            $noBitsInstance = $false;
            if ($_.JobState -eq "Transferred") {
         # ダウンロード完了した転送要求を完了させる
                Complete-BitsTransfer $_;
                $completeDownload = $true;
            }
        }
    }

    # BITSにダウンロード要求が登録されていなければ、新たに登録する。
    if ($noBitsInstance -eq $true){
        $delayMinute = Get-Random -Maximum 240;
        $kickDateTime = (Get-Date).AddMinutes($delayMinute);

        # 新規ダウンロード登録までランダムに待機する
        Add-Content -Path $logFile -Value ('Wait ' + $delayMinute + ' Minutes');
        While ($kickDateTime -ge (Get-Date)){
            Add-Content -Path $logFile -Value ('delay - ' + (Get-Date));
            sleep 60;
        }

        # 新規にダウンロードを登録する
        Add-Content -Path $logFile -Value ('Start BitsTransfer:' + $displayName + '-' + $destFile);
        Start-BitsTransfer -Source $fromURL -Destination $destFile -Asynchronous -Priority Normal -DisplayName $displayName
    }

    if ($completeDownload -eq $true){
        # ダウンロード完了後の処理
        Add-Content -Path $logFile -Value ('Complte Download:' + $displayName + '-' + $destFile);
    }
}
Add-Content -Path $logFile -Value ('End Script:' + (Get-Date));

私はActive Directoryのグループポリシーでログオンスクリプトとして登録しました。コントロールパネルのタスクで定周期に起動してもよいでしょう。

BITSで使用する帯域の制限などはレジストリに記述するか、ActiveDirectoryのグループポリシーで定義します。

Nextcloudに大きなファイルをアップロードするとRequestTimeTooSkewedが発生する

NextcloudでストレージにS3を使用している場合に、約500MBを超える大きなファイルをアップロードすると、以下のようなエラーが発生する場合がある。

An exception occurred while uploading parts to a multipart upload. The following parts had errors:
- Part 1: Error executing "UploadPart" on "https://nextcloud-xxxx.s3.us-west-1.wasabisys.com/xxxx"; AWS HTTP error: Client error: `PUT https://nextcloud-xxxx.s3.us-west-1.wasabisys.com/xxxx` resulted in a `403 Forbidden` response:
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>RequestTimeTooSkewed</Code><Message>The difference between the reque (truncated...)
RequestTimeTooSkewed (client): The difference between the request time and the current time is too large. - <?xml version="1.0" encoding="UTF-8"?>
<Error><Code>RequestTimeTooSkewed</Code><Message>The difference between the request time and the current time is too large.</Message><RequestTime>20220101T141414Z</RequestTime><ServerTime>2022-01-01T14:32:28Z</ServerTime><MaxAllowedSkewMilliseconds>900000</MaxAllowedSkewMilliseconds><RequestId>xxxx</RequestId><HostId>xxxx/xxxx</HostId></Error>

この問題を解決するには./html/config/config.phpに以下の行を追加し、500MBよりも適当に小さなサイズで分割してアップロードするように設定する。下記の例では約20MBに設定を変更している。

    array (
      'bucket' => 'nextcloud-bucket',
      'key' => '{key}',
      'secret' => '{secret}',
      'region' => 'us-west-1',
      'hostname' => 's3.us-west-1.wasabisys.com',
      'port' => '443',
      'objectPrefix' => 'urn:oid:',
      'autocreate' => false,
      'use_ssl' => true,
      'use_path_style' => false,
      'legacy_auth' => false,
      'uploadPartSize' => 20971520,
    ),

S3にsinglepartでアップロードできるファイルサイズの上限は5GBとなり、より大きなファイルをアップロードするときにはmultipartでアップロードする必要がある。標準設定のNextcloudでは約500MBを超えるファイルをアップロードするときにはmultipartアップロード を使用する。

S3のmultipartアップロードがもつ仕様上の問題で、通信帯域の不足等によりデータのアップロードに約15分以上かかると、HTTPヘッダに記載されている時刻とAWS側サーバーとの時刻の差がMaxAllowedSkewMillisecondsを超えるために”RequestTimeTooSkewed: The difference between the reque (truncated…)
RequestTimeTooSkewed (client): The difference between the request time and the current time is too large.”のエラーが発生する。

MaxAllowedSkewMillisecondsは900000msに固定されいる。HTTPのリクエストデータを複製することによる第三者の攻撃を防ぐために設けられている値で、ユーザー側でこの値を任意に変更する事は出来ない。この問題を避けるには15分以内にアップロードが終わる程度の適当なサイズに分割してアップロードする必要がある。

ただし小さく分割するとS3にアップロードできる最大ファイルサイズが小さくなる事にも注意しなくてはならない。S3には最大で5TBのファイルを保管できるが、 multipart アップロード時には10,000分割以上に分ける事ができない。仮に上記のように20MBで分割した場合には、200GBを超えるファイルをアップロード出来ない。(Amazon S3 multipart upload limits

NextcloudでRedisによるLock管理を有効にする

NextcloudのWindowsアプリからフォルダの同期を行っている途中、~ Locked errorが度々表示されていました。Nextcloudはデフォルトだとmysqlを使用してロック管理をしていますが、高負荷時に問題がおこる場合があるようです。ファイルのロックにかかる問題を回避するために、Redisを導入してTransactional file lockingを有効にします。

Docker環境でRedisを有効にするにはdocker-conpose.ymlに、redisのイメージを追加し、nextcloudのenvironemntにREDIS_HOST、REDIS_HOST_PORT、REDIS_HOST_PASSWORDを、下記の様に追加します。

version: '3'

volumes:
  nextcloud:
  nextcloud-db:
  nextcloud-redis:

services:
  nextcloud-redis:
    image: redis
    ports:
      - 6379:6379
    volumes:
      - ./redis:/data

  nextcloud-db:
    image: mariadb
      ...

  nextcloud:
    image: nextcloud
      ...
    environment:
      - REDIS_HOST=nextcloud-redis
      - REDIS_HOST_PORT=6379
      - REDIS_HOST_PASSWORD=${redis_password}

その後、以下のコマンドを実行します。

$ sudo docker-compose up -d

docker-compose.ymlの設定だけでは Transactional file locking は有効になっていません。./html/config/config.phpを編集して、「’filelocking.enabled’ => true,」を追記します。

  'filelocking.enabled' => true,
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'redis' =>
  array (
    'host' => 'nextcloud-redis',
    'password' => '',
    'port' => 6379,
  ),

Nextcloudの暗号化を有効にするときの覚書

Nextcloud:Encryption configuration

暗号鍵の保存場所

暗号化を有効にしたとき、暗号鍵は./html/data/files_encryptionおよび ./html/data/ユーザー名/files_encryptionに保存されます。基本的にアップロードしたデータと同じ場所に鍵も保管されるため、プライマリストレージをS3等のストレージに変更しているか、あるいは外部ストレージを使用していない限り暗号化によるセキュリティ向上の恩恵はありません。

またこれらの暗号鍵を安全な場所にバックアップしておく必要があります。

回復パスワードの保存

上記フォルダの暗号鍵はログインパスワードを元にして暗号化されているため、ログインパスワードが分からなくなった場合に、パスワードの初期化を行うと暗号化したファイルを読むことが出来なくなります。

これを避ける為には事前に回復パスワードを作成しておく必要があります。

LDAPなど外部の認証システムとの連携

前項の理由により、LDAPなど外部の認証システムと連携している場合に、ファイブのシステム上でパスワードの変更を行う場合、そのままでは暗号ファイルを読み取ることが出来なくなります。Nextcloud上で新旧のパスワード入力を求められるため、そこで旧パスワードを入力する必要があります。

NextCloudでアップロード時に不明なエラーが発生する。

大きなファイルをアップロードしていると「不明なエラー」が発生します。ログを見ると以下の様なエラーが発生していました。どうも帯域が狭いために、アップロード中にタイムアウトエラーが発生しているようです。

Sabre\DAV\Exception\BadRequest: Expected filesize of 10485760 bytes but read (from Nextcloud client) and wrote (to Nextcloud storage) 5368032 bytes. Could either be a network problem on the sending side or a problem writing to the storage on the server side.

10,485,760 bytesは標準のチャンクサイズです。Nextcloudではアップロードをチャンクサイズのデータに分解して処理しています。10MB分送信する前にタイムアウトが発生してしまっていたようです。

タイムアウトを長くしても良いのですが、設定対象が多岐に渡るので、ここはチャンクサイズを5MBに調整する方向で対処します。occコマンドで設定を変更できますので、以下のコマンドを実行してチャンクサイズを変更します。

$ sudo docker-compose exec --user www-data nextcloud php occ config:app:set files max_chunk_size --value 5242880
Config value max_chunk_size for app files set to 5242880

再度ファイルをアップロードしたところ、エラーになる事無く上手く動いているようです。