diff --git a/Dockerfile b/Dockerfile index 608a3d4a8..6c2c056fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ RUN apk add --no-cache bash curl jq gcc git go musl-dev COPY go.mod go.sum ./ RUN go mod download COPY ./ ./ +# Convert CRLF (Windows) to LF so bash scripts parse correctly inside the container +RUN sed -i 's/\r$//' build.sh entrypoint.sh RUN bash build.sh release docker FROM openlistteam/openlist-base-image:${BASE_IMAGE_TAG} @@ -26,6 +28,8 @@ RUN addgroup -g ${GID} ${USER} && \ COPY --from=builder --chmod=755 --chown=${UID}:${GID} /app/bin/openlist ./ COPY --chmod=755 --chown=${UID}:${GID} entrypoint.sh /entrypoint.sh +# Ensure entrypoint.sh has LF line endings when COPYed from a Windows checkout +RUN sed -i 's/\r$//' /entrypoint.sh USER ${USER} RUN /entrypoint.sh version diff --git a/go.mod b/go.mod index 23e27e3fd..17fa8fb57 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/fclairamb/ftpserverlib v0.26.1-0.20250709223522-4a925d79caf6 github.com/foxxorcat/mopan-sdk-go v0.1.6 github.com/foxxorcat/weiyun-sdk-go v0.1.4 + github.com/fsnotify/fsnotify v1.10.1 github.com/gin-contrib/cors v1.7.7 github.com/gin-gonic/gin v1.12.0 github.com/glebarez/sqlite v1.11.0 diff --git a/go.sum b/go.sum index 32de1c28f..71e72c464 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,24 @@ cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= -cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= -cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 h1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0/go.mod h1:/WYEx9pcM9Y+Dd/APJaNlSvVSvzl54rrMdZT5+Oi2LM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZYLBR2kBz5C8Tg0fw5w5Y7meRXWI= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0 h1:irsmOWwkp0KCTTNS5e2hdFeIvSQClQo2No3IaNmL3Vw= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.8.0/go.mod h1:GWcBkQj3MqN7ozHKLaCCAuNLiXoIGv2RtanfAwSjY/Y= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Da3zKi7/saferith v0.33.0-fixed h1:fnIWTk7EP9mZAICf7aQjeoAwpfrlCrkOvqmi6CbWdTk= @@ -41,10 +29,6 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE= github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc= -github.com/OpenListTeam/115-sdk-go v0.2.4 h1:dVoEz3Pm996n/ZCuRckQvz36w938uNHjPrS7E/SVpBM= -github.com/OpenListTeam/115-sdk-go v0.2.4/go.mod h1:cfvitk2lwe6036iNi2h+iNxwxWDifKZsSvNtrur5BqU= -github.com/OpenListTeam/115-sdk-go v0.2.5 h1:E4O7GZmEXlGnouZ/e9//xU7NepT9JJkWtnF3O/VSb08= -github.com/OpenListTeam/115-sdk-go v0.2.5/go.mod h1:cfvitk2lwe6036iNi2h+iNxwxWDifKZsSvNtrur5BqU= github.com/OpenListTeam/115-sdk-go v0.2.6 h1:ehXyStvncvn4qRBuknor3kyGZtUmHc0+stjF2ByVdFo= github.com/OpenListTeam/115-sdk-go v0.2.6/go.mod h1:cfvitk2lwe6036iNi2h+iNxwxWDifKZsSvNtrur5BqU= github.com/OpenListTeam/go-cache v0.1.0 h1:eV2+FCP+rt+E4OCJqLUW7wGccWZNJMV0NNkh+uChbAI= @@ -75,16 +59,12 @@ github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1J github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= github.com/ProtonMail/gopenpgp/v2 v2.10.0 h1:llCzLvntC9+iH+if/na4AgKTef/Zm4vpaRrR3+JdKvo= github.com/ProtonMail/gopenpgp/v2 v2.10.0/go.mod h1:dc0h9Pg3ftfN0U4pfRzujilfh61A2R52wgMkZWcWm2I= -github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= -github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= -github.com/SheltonZhu/115driver v1.3.4 h1:joUFKN591n7KiTEGnBw0ZHjPzhl3G1PpZrwv2YHhoC8= -github.com/SheltonZhu/115driver v1.3.4/go.mod h1:OujS7azslg1/bn85sPSHnNsp4/WBI9/TiijtZL9kuSQ= github.com/SheltonZhu/115driver v1.3.5 h1:NlJA61RskdwHhIZyg4519696lCk1n4aid0lKARsMwFg= github.com/SheltonZhu/115driver v1.3.5/go.mod h1:ag0B8Vs6TPFQjTNFLVVxA7dINPt4q+WR47+JIPJhmTk= github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= @@ -111,32 +91,32 @@ github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevB github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= -github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= -github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= -github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.49 h1:7gss+6H2mrrFtBrkokJRR2TzQD9qkpGA4N6BvIP/pCM= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.49/go.mod h1:30PBx0ENoUCJm2AxzgCue8j7KEjb9ci4enxy6CCOjbE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2 h1:BCG7DCXEXpNCcpwCxg1oi9pkJWH2+eZzTn9MY56MbVw= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.72.3 h1:WZOmJfCDV+4tYacLxpiojoAdT5sxTfB3nTqQNtZu+J4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.72.3/go.mod h1:xMekrnhmJ5aqmyxtmALs7mlvXw5xRh+eYjOjvrIIFJ4= -github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= -github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.13 h1:uMC4oL6G3MNhodo358QEqSDjrgvzV3TUQ58nyQSGq2E= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.13/go.mod h1:Cer86AE2686DvVUe57LPve3jUBmbujuaonSX8pNzGgw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 h1:hlSuz394kV0vhv9drL5lhuEFbEOEP1VyQpy15qWh1Pk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= +github.com/aws/smithy-go v1.24.3 h1:XgOAaUgx+HhVBoP4v8n6HCQoTRDhoMghKqw4LNHsDNg= +github.com/aws/smithy-go v1.24.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -198,7 +178,6 @@ github.com/bodgit/sevenzip v1.6.5 h1:7H7BxgmeX0j6UX42lH+KXQ92WgMQJ49DoocFdfHbCng github.com/bodgit/sevenzip v1.6.5/go.mod h1:GhuB6Lq1xCpP1sps+horjZ8lgiKPJcy2zUX3prla9wc= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= -github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo= github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -239,13 +218,9 @@ github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+ github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= -github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ= -github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudsoda/go-smb2 v0.0.0-20260701064823-d8c5600d73b8 h1:+KC2I+emnT6ccWmo7IHbtchGc/vFqAxUuovo2eUgMuM= @@ -258,8 +233,6 @@ github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83 github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -288,8 +261,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM= github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8= -github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= -github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE= github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emersion/go-message v0.18.2 h1:rl55SQdjd9oJcIoQNhubD2Acs1E6IzlZISRTK7x/Lpg= @@ -309,10 +280,10 @@ github.com/foxxorcat/mopan-sdk-go v0.1.6/go.mod h1:UaY6D88yBXWGrcu/PcyLWyL4lzrk5 github.com/foxxorcat/weiyun-sdk-go v0.1.4 h1:X2tFvdqikkJ7awCBbMH7XXk7+uQoJlQksJz9CUU6ZgA= github.com/foxxorcat/weiyun-sdk-go v0.1.4/go.mod h1:TPxzN0d2PahweUEHlOBWlwZSA+rELSUlGYMWgXRn9ps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= -github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/geoffgarside/ber v1.2.0 h1:/loowoRcs/MWLYmGX9QtIAbA+V/FrnVLsMMPhwiRm64= @@ -327,8 +298,6 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-darwin/apfs v0.0.0-20211011131704-f84b94dbf348 h1:JnrjqG5iR07/8k7NqrLNilRsl3s1EPRQEGvbPyOce68= @@ -355,8 +324,6 @@ github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy0 github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-webauthn/webauthn v0.13.4 h1:q68qusWPcqHbg9STSxBLBHnsKaLxNO0RnVKaAqMuAuQ= @@ -367,13 +334,10 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= -github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -398,10 +362,10 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= -github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= +github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI= +github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -439,8 +403,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/boxo v0.12.0 h1:AXHg/1ONZdRQHQLgG5JHsSC3XoE4DjCAMgK+asZvUcQ= github.com/ipfs/boxo v0.12.0/go.mod h1:xAnfiU6PtxWCnRqu7dcXQ10bB5/kvI1kXRotuGqGBhg= -github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= -github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= github.com/ipfs/go-cid v0.6.2 h1:VuGwJd+KJTaMJ4S4d5EEf9SXc17YUblS5axCbocn9YE= github.com/ipfs/go-cid v0.6.2/go.mod h1:Xhwg8NzHeK9xPCEZkCw4idzPiuNMpX3fARuI5Iwj1Lo= github.com/ipfs/go-ipfs-api v0.7.0 h1:CMBNCUl0b45coC+lQCXEVpMhwoqjiaCwUIrM+coYW2Q= @@ -471,8 +433,6 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3 h1:ZxO6Qr2GOXPdcW80Mcn3nemvilMPvpWqxrNfK2ZnNNs= -github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3/go.mod h1:dvLUr/8Fs9a2OBrEnCC5duphbkz/k/mSy5OkXg3PAgI= github.com/jlaffaye/ftp v0.2.1-0.20251026020404-6602e981a1bb h1:6vkM8gO+zFV2m21QzGYyUSq5TP0VQgP2Xz3UQyCN2kI= github.com/jlaffaye/ftp v0.2.1-0.20251026020404-6602e981a1bb/go.mod h1:H1+whwD0Qe3YOunlXIWhh3rlvzW5cZfkMDYGQPg+KAM= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -507,8 +467,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lanrat/extsort v1.0.2 h1:p3MLVpQEPwEGPzeLBb+1eSErzRl6Bgjgr+qnIs2RxrU= -github.com/lanrat/extsort v1.0.2/go.mod h1:ivzsdLm8Tv+88qbdpMElV6Z15StlzPUtZSKsGb51hnQ= github.com/lanrat/extsort v1.4.2 h1:akbLIdo4PhNZtvjpaWnbXtGMmLtnGzXplkzfgl+XTTY= github.com/lanrat/extsort v1.4.2/go.mod h1:hceP6kxKPKebjN1RVrDBXMXXECbaI41Y94tt6MDazc4= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= @@ -521,11 +479,8 @@ github.com/libp2p/go-libp2p v0.27.8 h1:IX5x/4yKwyPQeVS2AXHZ3J4YATM9oHBGH1gBc23jB github.com/libp2p/go-libp2p v0.27.8/go.mod h1:eCFFtd0s5i/EVKR7+5Ki8bM7qwkNW3TPTTSSW9sz8NE= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= -github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM= github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= @@ -539,8 +494,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.22 h1:76lXsPn6FyHtTY+jt2fTTvsMUCZq1k0qwRsAMuxzKAk= github.com/mattn/go-runewidth v0.0.22/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= @@ -570,8 +523,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI= github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= @@ -588,8 +539,6 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= -github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= -github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multibase v0.3.0 h1:8helZD2+4Db7NNWFiktk2NePbF0boolBe6bDQvM4r68= github.com/multiformats/go-multibase v0.3.0/go.mod h1:MoBLQPCkRTOL3eveIPO81860j2AQY8JwcnNlRkGRUfI= github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= @@ -598,8 +547,6 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -608,13 +555,13 @@ github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4 github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= github.com/ncw/swift/v2 v2.0.5 h1:9o5Gsd7bInAFEqsGPcaUdsboMbqf8lnNtxqWKFT9iz8= github.com/ncw/swift/v2 v2.0.5/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk= -github.com/nwaples/rardecode/v2 v2.2.5 h1:L5doqgGfQwI7qADJMqnkrSB86rpPsqQDrHeO0HWa5JY= -github.com/nwaples/rardecode/v2 v2.2.5/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/nwaples/rardecode/v2 v2.3.0 h1:CtgyxWm8ClLcSh1u4M58fOz6lmeb/j4V7KpaEi/6UtM= github.com/nwaples/rardecode/v2 v2.3.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= +github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -624,8 +571,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= -github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= -github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= +github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM= +github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -635,18 +582,12 @@ github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8 github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= @@ -655,19 +596,13 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0= github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk= -github.com/rclone/rclone v1.70.3 h1:rg/WNh4DmSVZyKP2tHZ4lAaWEyMi7h/F0r7smOMA3IE= -github.com/rclone/rclone v1.70.3/go.mod h1:nLyN+hpxAsQn9Rgt5kM774lcRDad82x/KqQeBZ83cMo= github.com/rclone/rclone v1.74.4 h1:/c6LMO2kPQjJa6a/PBFcIYJHDvfNOAtfDBJy44mGMpc= github.com/rclone/rclone v1.74.4/go.mod h1:M8GXX+n9vrP5HwMt0O6wxQcAjlUTUdSQugFrjXQI8kI= -github.com/relvacode/iso8601 v1.6.0 h1:eFXUhMJN3Gz8Rcq82f9DTMW0svjtAVuIEULglM7QHTU= -github.com/relvacode/iso8601 v1.6.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/relvacode/iso8601 v1.7.0 h1:BXy+V60stMP6cpswc+a93Mq3e65PfXCgDFfhvNNGrdo= github.com/relvacode/iso8601 v1.7.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4= -github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k= github.com/rfjakob/eme v1.2.0 h1:8dAHL+WVAw06+7DkRKnRiFp1JL3QjcJEZFqDnndUaSI= github.com/rfjakob/eme v1.2.0/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -681,8 +616,6 @@ github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJ github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY= github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM= github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc= -github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs= -github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shirou/gopsutil/v4 v4.26.7 h1:IXzpHz/dkMRYAhKkOXr1HB6SuzWU3eoyyeWe7g3bNZc= github.com/shirou/gopsutil/v4 v4.26.7/go.mod h1:5O9FjBiXoTDFatIWjZZosqj4pV0DRtLx598xGbBehzM= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= @@ -723,8 +656,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/t3rm1n4l/go-mega v0.0.0-20260630094356-4de2f127f34a h1:VQ9CmpYeTgdFvg748N6tIOHBZn+hXjJe2xNuOJHW07E= -github.com/t3rm1n4l/go-mega v0.0.0-20260630094356-4de2f127f34a/go.mod h1:BF/l2jNyK+2h/BJZ7VLMAz6m/IWjA2F67gTjV1C/+Bo= github.com/t3rm1n4l/go-mega v0.0.0-20260717075258-c6acd6a5bd04 h1:s30A8dMuZ55lUOi5xUTh1hlfLqHpVBsRMfpk2iKCayk= github.com/t3rm1n4l/go-mega v0.0.0-20260717075258-c6acd6a5bd04/go.mod h1:BF/l2jNyK+2h/BJZ7VLMAz6m/IWjA2F67gTjV1C/+Bo= github.com/taruti/bytepool v0.0.0-20160310082835-5e3a9ea56543 h1:6Y51mutOvRGRx6KqyMNo//xk8B8o6zW9/RVmy1VamOs= @@ -756,29 +687,31 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= github.com/zzzhr1990/go-common-entity v0.0.0-20250202070650-1a200048f0d3 h1:PSRwrE5QBufPnOjdgIkRs5KBV1Avq3SY8oksj2Z+k3o= github.com/zzzhr1990/go-common-entity v0.0.0-20250202070650-1a200048f0d3/go.mod h1:CKriYB8bkNgSbYUQF1khSpejKb5IsV6cR7MdaAR7Fc0= -go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= -go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= -go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= @@ -789,8 +722,8 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -811,17 +744,11 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= -golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= -golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= -golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA= golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY= golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -843,8 +770,6 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= @@ -879,8 +804,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= @@ -893,8 +816,6 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -912,8 +833,6 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -930,8 +849,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ= -google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= +google.golang.org/api v0.275.0 h1:vfY5d9vFVJeWEZT65QDd9hbndr7FyZ2+6mIzGAh71NI= +google.golang.org/api v0.275.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= @@ -951,8 +870,7 @@ gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKK gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE= gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -963,15 +881,12 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= -gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= -gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= @@ -984,6 +899,8 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= resty.dev/v3 v3.0.0-beta.2 h1:xu4mGAdbCLuc3kbk7eddWfWm4JfhwDtdapwss5nCjnQ= resty.dev/v3 v3.0.0-beta.2/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/bootstrap/config.go b/internal/bootstrap/config.go index 830446808..650f17f77 100644 --- a/internal/bootstrap/config.go +++ b/internal/bootstrap/config.go @@ -94,36 +94,84 @@ func InitConfig() { } } if !conf.Conf.Force { - confFromEnv() + confFromEnv(conf.Conf) } - if conf.Conf.MaxConcurrency > math.MaxInt32 { + applyMaxConcurrency(conf.Conf) + applyMemoryConfig(conf.Conf) + + if len(conf.Conf.Log.Filter.Filters) == 0 { + conf.Conf.Log.Filter.Enable = false + } + convertAbsPaths(conf.Conf, pwd) + + err := os.MkdirAll(conf.Conf.TempDir, 0o777) + if err != nil { + log.Fatalf("create temp dir error: %+v", err) + } + log.Debugf("config: %+v", conf.Conf) + + // Validate and display proxy configuration status + validateProxyConfig() + + base.InitClient() + conf.URL = initURL(conf.Conf) +} + +func confFromEnv(c *conf.Config) { + prefix := "OPENLIST_" + if flags.NoPrefix { + prefix = "" + } + log.Infof("load config from env with prefix: %s", prefix) + if err := env.ParseWithOptions(c, env.Options{ + Prefix: prefix, + }); err != nil { + log.Fatalf("load config from env error: %+v", err) + } +} + +func initURL(c *conf.Config) *url.URL { + if !strings.Contains(c.SiteURL, "://") { + c.SiteURL = utils.FixAndCleanPath(c.SiteURL) + } + u, err := url.Parse(c.SiteURL) + if err != nil { + utils.Log.Fatalf("can't parse site_url: %+v", err) + } + return u +} + +func applyMaxConcurrency(c *conf.Config) { + if c.MaxConcurrency > math.MaxInt32 { net.DefaultConcurrencyLimit = &net.ConcurrencyLimit{Limit: math.MaxInt32} - } else if conf.Conf.MaxConcurrency > 0 { - net.DefaultConcurrencyLimit = &net.ConcurrencyLimit{Limit: uint32(conf.Conf.MaxConcurrency)} + } else if c.MaxConcurrency > 0 { + net.DefaultConcurrencyLimit = &net.ConcurrencyLimit{Limit: uint32(c.MaxConcurrency)} } +} +func applyMemoryConfig(c *conf.Config) { memStat, _ := mem.VirtualMemory() if memStat != nil { log.Infof("total memory: %dMB, available: %dMB", memStat.Total>>20, memStat.Available>>20) - if conf.Conf.MinFreeMemory < 0 { + if c.MinFreeMemory < 0 { conf.MinFreeMemory = 0 log.Info("disable memory cache") } else { - if conf.Conf.MinFreeMemory < 16 { + if c.MinFreeMemory < 16 { t := (memStat.Total >> 20) / 10 conf.MinFreeMemory = max(16, min(t, 1024)) << 20 } else { - conf.MinFreeMemory = uint64(conf.Conf.MinFreeMemory) << 20 + conf.MinFreeMemory = uint64(c.MinFreeMemory) << 20 } log.Infof("min free memory: %dMB", conf.MinFreeMemory>>20) } - if conf.Conf.MaxBlockLimit < 4 { + if c.MaxBlockLimit < 4 { t := (memStat.Total >> 20) * 3 / 100 conf.MaxBlockLimit = max(4, min(uint64(t), 64)) << 20 } else { - conf.MaxBlockLimit = uint64(conf.Conf.MaxBlockLimit) << 20 + conf.MaxBlockLimit = uint64(c.MaxBlockLimit) << 20 } log.Infof("max block limit: %dMB", conf.MaxBlockLimit>>20) } else { @@ -131,66 +179,28 @@ func InitConfig() { log.Warn("failed to get memory info, disable memory cache") } - if conf.Conf.AutoMemoryLimit > 0 { - conf.AutoMemoryLimit = uint64(conf.Conf.AutoMemoryLimit) << 20 + if c.AutoMemoryLimit > 0 { + conf.AutoMemoryLimit = uint64(c.AutoMemoryLimit) << 20 } else { conf.AutoMemoryLimit = 0 } log.Infof("auto memory limit: %dMB", conf.AutoMemoryLimit>>20) +} - if len(conf.Conf.Log.Filter.Filters) == 0 { - conf.Conf.Log.Filter.Enable = false - } - // convert abs path +func convertAbsPaths(c *conf.Config, pwd string) { convertAbsPath := func(path *string) { if *path != "" && !filepath.IsAbs(*path) { *path = filepath.Join(pwd, *path) } } - convertAbsPath(&conf.Conf.Database.DBFile) - convertAbsPath(&conf.Conf.Scheme.CertFile) - convertAbsPath(&conf.Conf.Scheme.KeyFile) - convertAbsPath(&conf.Conf.Scheme.UnixFile) - convertAbsPath(&conf.Conf.Log.Name) - convertAbsPath(&conf.Conf.TempDir) - convertAbsPath(&conf.Conf.BleveDir) - convertAbsPath(&conf.Conf.DistDir) - - err := os.MkdirAll(conf.Conf.TempDir, 0o777) - if err != nil { - log.Fatalf("create temp dir error: %+v", err) - } - log.Debugf("config: %+v", conf.Conf) - - // Validate and display proxy configuration status - validateProxyConfig() - - base.InitClient() - initURL() -} - -func confFromEnv() { - prefix := "OPENLIST_" - if flags.NoPrefix { - prefix = "" - } - log.Infof("load config from env with prefix: %s", prefix) - if err := env.ParseWithOptions(conf.Conf, env.Options{ - Prefix: prefix, - }); err != nil { - log.Fatalf("load config from env error: %+v", err) - } -} - -func initURL() { - if !strings.Contains(conf.Conf.SiteURL, "://") { - conf.Conf.SiteURL = utils.FixAndCleanPath(conf.Conf.SiteURL) - } - u, err := url.Parse(conf.Conf.SiteURL) - if err != nil { - utils.Log.Fatalf("can't parse site_url: %+v", err) - } - conf.URL = u + convertAbsPath(&c.Database.DBFile) + convertAbsPath(&c.Scheme.CertFile) + convertAbsPath(&c.Scheme.KeyFile) + convertAbsPath(&c.Scheme.UnixFile) + convertAbsPath(&c.Log.Name) + convertAbsPath(&c.TempDir) + convertAbsPath(&c.BleveDir) + convertAbsPath(&c.DistDir) } func CleanTempDir() { diff --git a/internal/bootstrap/reload.go b/internal/bootstrap/reload.go new file mode 100644 index 000000000..4978a9e0e --- /dev/null +++ b/internal/bootstrap/reload.go @@ -0,0 +1,195 @@ +package bootstrap + +import ( + "context" + "os" + "reflect" + + "github.com/OpenListTeam/OpenList/v4/cmd/flags" + "github.com/OpenListTeam/OpenList/v4/drivers/base" + "github.com/OpenListTeam/OpenList/v4/internal/conf" + "github.com/OpenListTeam/OpenList/v4/internal/db" + "github.com/OpenListTeam/OpenList/v4/internal/search" + "github.com/OpenListTeam/OpenList/v4/pkg/utils" + "github.com/OpenListTeam/OpenList/v4/server" + "github.com/OpenListTeam/OpenList/v4/server/common" + "github.com/OpenListTeam/OpenList/v4/server/middlewares" + "github.com/OpenListTeam/OpenList/v4/server/static" + log "github.com/sirupsen/logrus" +) + +// configWatcher watches config.json for changes and triggers reloadConfig. +var configWatcher *conf.ConfigWatcher + +// reloadConfig rebuilds the configuration from the provided raw config.json +// bytes and atomically swaps conf.Conf / conf.URL, then applies only the +// side effects for fields that actually changed. It never mutates the live +// conf.Conf in place: a brand-new *conf.Config is built offline and swapped +// in as a whole, so readers always see a consistent (old or new) config. +// +// On parse failure it returns a non-nil error and leaves the running config +// untouched, so the watcher retries on the next file event. +func reloadConfig(data []byte) error { + pwd := PWD() + dataDir := flags.DataDir + + newConf := conf.DefaultConfig(dataDir) + if err := utils.Json.Unmarshal(data, newConf); err != nil { + return err + } + + // LastLaunchedVersion is a one-shot startup field; preserve the running + // value so reload does not re-trigger upgrade patches or produce spurious diffs. + newConf.LastLaunchedVersion = conf.Conf.LastLaunchedVersion + + if !newConf.Force { + confFromEnv(newConf) + } + + // Mirror the startup sanity check: an empty filter list disables filtering. + if len(newConf.Log.Filter.Filters) == 0 { + newConf.Log.Filter.Enable = false + } + + convertAbsPaths(newConf, pwd) + + // Ensure the (possibly new) temp dir exists. Non-fatal on reload. + if err := os.MkdirAll(newConf.TempDir, 0o777); err != nil { + log.Warnf("create temp dir on reload error: %+v", err) + } + + // Derive conf.URL from the new SiteURL offline (also fixes newConf.SiteURL). + newURL := initURL(newConf) + + // Snapshot the old config, then swap the pointer and URL as a whole. + oldConf := conf.Conf + conf.Conf = newConf + conf.URL = newURL + + applyReloadSideEffects(oldConf, newConf) + + log.Info("config reloaded successfully") + return nil +} + +// applyReloadSideEffects applies runtime side effects only for config fields +// that changed between oldConf and newConf. Unchanged subsystems are left +// untouched to minimise disruption. +func applyReloadSideEffects(oldConf, newConf *conf.Config) { + // --- Log output configuration (lumberjack rotation / file) --- + logOutputChanged := oldConf.Log.Enable != newConf.Log.Enable || + oldConf.Log.Name != newConf.Log.Name || + oldConf.Log.MaxSize != newConf.Log.MaxSize || + oldConf.Log.MaxBackups != newConf.Log.MaxBackups || + oldConf.Log.MaxAge != newConf.Log.MaxAge || + oldConf.Log.Compress != newConf.Log.Compress + if logOutputChanged { + Log() + log.Info("log output config reloaded") + } + + // --- Log filter rules --- + if !reflect.DeepEqual(oldConf.Log.Filter, newConf.Log.Filter) { + middlewares.ReloadFilterList() + log.Info("log filter list reloaded") + } + + // --- TLS verification / proxy (global HTTP clients) --- + if oldConf.TlsInsecureSkipVerify != newConf.TlsInsecureSkipVerify || + oldConf.ProxyAddress != newConf.ProxyAddress { + base.InitClient() + validateProxyConfig() + log.Info("http clients reloaded") + } + + // --- JWT secret (invalidates already-issued tokens) --- + if oldConf.JwtSecret != newConf.JwtSecret { + common.SecretKey = []byte(newConf.JwtSecret) + log.Info("jwt secret reloaded; previously issued tokens are now invalid") + } + + // --- Site URL (conf.URL already updated above) --- + if oldConf.SiteURL != newConf.SiteURL { + log.Infof("site_url reloaded: %s", newConf.SiteURL) + } + + // --- Max concurrency --- + if oldConf.MaxConcurrency != newConf.MaxConcurrency { + applyMaxConcurrency(newConf) + log.Infof("max concurrency reloaded: %d", newConf.MaxConcurrency) + } + + // --- Memory thresholds --- + if oldConf.MinFreeMemory != newConf.MinFreeMemory || + oldConf.MaxBlockLimit != newConf.MaxBlockLimit || + oldConf.AutoMemoryLimit != newConf.AutoMemoryLimit { + applyMemoryConfig(newConf) + log.Info("memory limits reloaded") + } + + // --- Group A: listener restart (scheme.* / s3.* / ftp.* / sftp.*) --- + applyEndpointChanges(oldConf, newConf) + + // --- Group C: CORS live reload --- + if !reflect.DeepEqual(oldConf.Cors, newConf.Cors) { + server.ReloadCors() + log.Info("cors config reloaded") + } + + // --- Group D: subsystem re-init --- + if !reflect.DeepEqual(oldConf.Database, newConf.Database) { + log.Warn("database config changed; reinitializing (HIGH RISK: in-flight queries on old handle may fail)") + if err := db.Reinit(newConf); err != nil { + log.Errorf("database reinit failed: %v", err) + } else { + log.Info("database reinitialized successfully; old handle closes after 5 min") + } + } + if oldConf.BleveDir != newConf.BleveDir || !reflect.DeepEqual(oldConf.Meilisearch, newConf.Meilisearch) { + log.Info("search config changed; reinitializing search index") + if oldConf.BleveDir != newConf.BleveDir { + log.Warn("bleve_dir changed; index content will be empty — admin must rebuild via rescan") + } + if err := search.Reinit(); err != nil { + log.Errorf("search reinit failed: %v", err) + } + } + if oldConf.DistDir != newConf.DistDir || oldConf.Cdn != newConf.Cdn { + static.Reload() + log.Info("static resources (dist_dir/cdn) reloaded") + } + + // --- Group E: no-op fields (take effect at next start) --- + if oldConf.DelayedStart != newConf.DelayedStart { + log.Info("delayed_start changed; takes effect at next start") + } + if oldConf.Force != newConf.Force { + log.Info("force changed; takes effect at next start") + } + if oldConf.LastLaunchedVersion != newConf.LastLaunchedVersion { + log.Info("last_launched_version changed; takes effect at next start") + } +} + +// startConfigWatcher starts the config.json hot-reload watcher. +func startConfigWatcher() { + if conf.ConfigPath == "" { + log.Warn("config path is empty, skipping config file watcher") + return + } + w := conf.NewConfigWatcher(conf.ConfigPath, reloadConfig) + if err := w.Start(context.Background()); err != nil { + log.Errorf("failed to start config file watcher: %v", err) + return + } + configWatcher = w + log.Infof("config file watcher started: %s", conf.ConfigPath) +} + +// stopConfigWatcher stops the config.json hot-reload watcher. Idempotent. +func stopConfigWatcher() { + if configWatcher != nil { + configWatcher.Stop() + configWatcher = nil + } +} diff --git a/internal/bootstrap/run.go b/internal/bootstrap/run.go index 6740dba65..16cfcafba 100644 --- a/internal/bootstrap/run.go +++ b/internal/bootstrap/run.go @@ -6,6 +6,7 @@ import ( "net" "net/http" "os" + "reflect" "strconv" "sync" "time" @@ -61,6 +62,17 @@ var ( sftpDriver *server.SftpDriver sftpServer *sftpd.SftpServer sftpRunning bool + + // ginEngine is the single gin engine shared by all HTTP-family listeners. + // It is built once in Start() and never rebuilt on listener restart, so + // the route tree and registered middlewares persist across hot-reloads. + ginEngine *gin.Engine + // httpHandler is ginEngine, optionally wrapped with h2c. Referenced by + // the HTTP and Unix listeners so they pick up h2c wrapping changes. + httpHandler http.Handler + // h3AltSvcRegistered guards the Alt-Svc response middleware so startH3 + // can be called multiple times without registering duplicate middlewares. + h3AltSvcRegistered bool ) // Called by OpenList-Mobile @@ -95,266 +107,467 @@ func Start() { if !flags.Debug && !flags.Dev { gin.SetMode(gin.ReleaseMode) } - r := gin.New() + ginEngine = gin.New() // gin log if conf.Conf.Log.Filter.Enable { - r.Use(middlewares.FilteredLogger()) + ginEngine.Use(middlewares.FilteredLogger()) } else { - r.Use(gin.LoggerWithWriter(log.StandardLogger().Out)) + ginEngine.Use(gin.LoggerWithWriter(log.StandardLogger().Out)) } - r.Use(gin.RecoveryWithWriter(log.StandardLogger().Out)) + ginEngine.Use(gin.RecoveryWithWriter(log.StandardLogger().Out)) + + server.Init(ginEngine) - server.Init(r) - var httpHandler http.Handler = r + httpHandler = ginEngine if conf.Conf.Scheme.EnableH2c { - httpHandler = h2c.NewHandler(r, &http2.Server{}) - } - if conf.Conf.Scheme.HttpPort != -1 { - httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort) - fmt.Printf("start HTTP server @ %s\n", httpBase) - utils.Log.Infof("start HTTP server @ %s", httpBase) - httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler} - go func() { - httpRunning = true - err := httpSrv.ListenAndServe() - httpRunning = false - if err != nil && !errors.Is(err, http.ErrServerClosed) { - handleEndpointStartFailedHooks("http", err) - utils.Log.Errorf("failed to start http: %s", err.Error()) - } else { - handleEndpointShutdownHooks("http") - } - }() - } - if conf.Conf.Scheme.HttpsPort != -1 { - httpsBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpsPort) - fmt.Printf("start HTTPS server @ %s\n", httpsBase) - utils.Log.Infof("start HTTPS server @ %s", httpsBase) - httpsSrv = &http.Server{Addr: httpsBase, Handler: r} - go func() { - httpsRunning = true - err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) - httpsRunning = false - if err != nil && !errors.Is(err, http.ErrServerClosed) { - handleEndpointStartFailedHooks("https", err) - utils.Log.Errorf("failed to start https: %s", err.Error()) - } else { - handleEndpointShutdownHooks("https") - } - }() - if conf.Conf.Scheme.EnableH3 { - fmt.Printf("start HTTP3 (quic) server @ %s\n", httpsBase) - utils.Log.Infof("start HTTP3 (quic) server @ %s", httpsBase) - r.Use(func(c *gin.Context) { - if c.Request.TLS != nil { - port := conf.Conf.Scheme.HttpsPort - c.Header("Alt-Svc", fmt.Sprintf("h3=\":%d\"; ma=86400", port)) - } - c.Next() - }) - quicSrv = &http3.Server{Addr: httpsBase, Handler: r} - go func() { - quicRunning = true - err := quicSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) - quicRunning = false - if err != nil && !errors.Is(err, http.ErrServerClosed) { - handleEndpointStartFailedHooks("quic", err) - utils.Log.Errorf("failed to start http3 (quic): %s", err.Error()) - } else { - handleEndpointShutdownHooks("quic") - } - }() + httpHandler = h2c.NewHandler(ginEngine, &http2.Server{}) + } + + startHTTP() + startHTTPS() + startH3() + startUnix() + startS3() + startFTP() + startSFTP() + + startConfigWatcher() + running = true +} + +// startHTTP starts the HTTP listener. Idempotent: if already running, stops +// first then starts. Skipped when scheme.http_port == -1. +func startHTTP() { + if conf.Conf.Scheme.HttpPort == -1 { + return + } + if httpRunning { + stopHTTP() + } + httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort) + fmt.Printf("start HTTP server @ %s\n", httpBase) + utils.Log.Infof("start HTTP server @ %s", httpBase) + httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler} + go func() { + httpRunning = true + err := httpSrv.ListenAndServe() + httpRunning = false + if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("http", err) + utils.Log.Errorf("failed to start http: %s", err.Error()) + } else { + handleEndpointShutdownHooks("http") + } + }() +} + +// stopHTTP gracefully shuts down the HTTP listener with a 5s timeout. +// No-op if the server is not running. +func stopHTTP() { + if httpSrv == nil { + httpRunning = false + return + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpSrv.Shutdown(ctx); err != nil { + utils.Log.Error("HTTP server shutdown err: ", err) + } + httpSrv = nil + httpRunning = false +} + +// startHTTPS starts the HTTPS listener. Idempotent: if already running, stops +// first then starts. Skipped when scheme.https_port == -1. +func startHTTPS() { + if conf.Conf.Scheme.HttpsPort == -1 { + return + } + if httpsRunning { + stopHTTPS() + } + httpsBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpsPort) + fmt.Printf("start HTTPS server @ %s\n", httpsBase) + utils.Log.Infof("start HTTPS server @ %s", httpsBase) + httpsSrv = &http.Server{Addr: httpsBase, Handler: ginEngine} + go func() { + httpsRunning = true + err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + httpsRunning = false + if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("https", err) + utils.Log.Errorf("failed to start https: %s", err.Error()) + } else { + handleEndpointShutdownHooks("https") } + }() +} + +// stopHTTPS gracefully shuts down the HTTPS listener with a 5s timeout. +// No-op if the server is not running. +func stopHTTPS() { + if httpsSrv == nil { + httpsRunning = false + return } - if conf.Conf.Scheme.UnixFile != "" { - fmt.Printf("start unix server @ %s\n", conf.Conf.Scheme.UnixFile) - utils.Log.Infof("start unix server @ %s", conf.Conf.Scheme.UnixFile) - unixSrv = &http.Server{Handler: httpHandler} - go func() { - listener, err := net.Listen("unix", conf.Conf.Scheme.UnixFile) - if err != nil { - utils.Log.Errorf("failed to listen unix: %+v", err) - return + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpsSrv.Shutdown(ctx); err != nil { + utils.Log.Error("HTTPS server shutdown err: ", err) + } + httpsSrv = nil + httpsRunning = false +} + +// startH3 starts the HTTP3/QUIC listener. Idempotent: if already running, +// stops first then starts. Only starts when enable_h3 is true AND +// https_port != -1 (H3 shares the HTTPS port and cert). The Alt-Svc response +// header middleware is registered on ginEngine once (guarded by +// h3AltSvcRegistered); on stopH3 the middleware is left in place because gin +// cannot unregister middleware — it harmlessly no-ops when H3 is not desired. +func startH3() { + if !conf.Conf.Scheme.EnableH3 || conf.Conf.Scheme.HttpsPort == -1 { + return + } + if quicRunning { + stopH3() + } + httpsBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpsPort) + fmt.Printf("start HTTP3 (quic) server @ %s\n", httpsBase) + utils.Log.Infof("start HTTP3 (quic) server @ %s", httpsBase) + if !h3AltSvcRegistered { + ginEngine.Use(func(c *gin.Context) { + if c.Request.TLS != nil { + port := conf.Conf.Scheme.HttpsPort + c.Header("Alt-Svc", fmt.Sprintf("h3=\":%d\"; ma=86400", port)) } - unixRunning = true - // set socket file permission - mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32) + c.Next() + }) + h3AltSvcRegistered = true + } + quicSrv = &http3.Server{Addr: httpsBase, Handler: ginEngine} + go func() { + quicRunning = true + err := quicSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + quicRunning = false + if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("quic", err) + utils.Log.Errorf("failed to start http3 (quic): %s", err.Error()) + } else { + handleEndpointShutdownHooks("quic") + } + }() +} + +// stopH3 gracefully shuts down the HTTP3/QUIC listener with a 5s timeout. +// No-op if the server is not running. +func stopH3() { + if quicSrv == nil { + quicRunning = false + return + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := quicSrv.Shutdown(ctx); err != nil { + utils.Log.Error("HTTP3 (quic) server shutdown err: ", err) + } + quicSrv = nil + quicRunning = false +} + +// startUnix starts the Unix socket listener. Idempotent: if already running, +// stops first then starts. Skipped when scheme.unix_file == "". +func startUnix() { + if conf.Conf.Scheme.UnixFile == "" { + return + } + if unixRunning { + stopUnix() + } + fmt.Printf("start unix server @ %s\n", conf.Conf.Scheme.UnixFile) + utils.Log.Infof("start unix server @ %s", conf.Conf.Scheme.UnixFile) + unixSrv = &http.Server{Handler: httpHandler} + go func() { + listener, err := net.Listen("unix", conf.Conf.Scheme.UnixFile) + if err != nil { + utils.Log.Errorf("failed to listen unix: %+v", err) + return + } + unixRunning = true + // set socket file permission + mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32) + if err != nil { + utils.Log.Errorf("failed to parse socket file permission: %+v", err) + } else { + err = os.Chmod(conf.Conf.Scheme.UnixFile, os.FileMode(mode)) if err != nil { - utils.Log.Errorf("failed to parse socket file permission: %+v", err) - } else { - err = os.Chmod(conf.Conf.Scheme.UnixFile, os.FileMode(mode)) - if err != nil { - utils.Log.Errorf("failed to chmod socket file: %+v", err) - } + utils.Log.Errorf("failed to chmod socket file: %+v", err) } - err = unixSrv.Serve(listener) - unixRunning = false - if err != nil && !errors.Is(err, http.ErrServerClosed) { - handleEndpointStartFailedHooks("unix", err) - utils.Log.Errorf("failed to start unix: %s", err.Error()) - } else { - handleEndpointShutdownHooks("unix") - } - }() - } - if conf.Conf.S3.Port != -1 && conf.Conf.S3.Enable { - s3r := gin.New() - s3r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out)) - server.InitS3(s3r) - s3Base := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.S3.Port) - fmt.Printf("start S3 server @ %s\n", s3Base) - utils.Log.Infof("start S3 server @ %s", s3Base) - go func() { - s3Running = true - var err error - if conf.Conf.S3.SSL { - s3Srv = &http.Server{Addr: s3Base, Handler: s3r} - err = s3Srv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) - } else { - s3Srv = &http.Server{Addr: s3Base, Handler: s3r} - err = s3Srv.ListenAndServe() - } - s3Running = false - if err != nil && !errors.Is(err, http.ErrServerClosed) { - handleEndpointStartFailedHooks("s3", err) - utils.Log.Errorf("failed to start s3 server: %s", err.Error()) - } else { - handleEndpointShutdownHooks("s3") - } - }() + } + err = unixSrv.Serve(listener) + unixRunning = false + if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("unix", err) + utils.Log.Errorf("failed to start unix: %s", err.Error()) + } else { + handleEndpointShutdownHooks("unix") + } + }() +} + +// stopUnix gracefully shuts down the Unix socket listener with a 5s timeout. +// No-op if the server is not running. +func stopUnix() { + if unixSrv == nil { + unixRunning = false + return + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := unixSrv.Shutdown(ctx); err != nil { + utils.Log.Error("Unix server shutdown err: ", err) + } + unixSrv = nil + unixRunning = false +} + +// startS3 starts the standalone S3 listener (port 5246 by default), distinct +// from the /s3 route on the main gin engine. Idempotent: if already running, +// stops first then starts. Skipped when s3.enable is false or s3.port == -1. +// A fresh gin engine is built for this listener (it does not share ginEngine). +func startS3() { + if !conf.Conf.S3.Enable || conf.Conf.S3.Port == -1 { + return + } + if s3Running { + stopS3() } - if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable { + s3r := gin.New() + s3r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out)) + server.InitS3(s3r) + s3Base := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.S3.Port) + fmt.Printf("start S3 server @ %s\n", s3Base) + utils.Log.Infof("start S3 server @ %s", s3Base) + go func() { + s3Running = true var err error - ftpDriver, err = server.NewMainDriver() + if conf.Conf.S3.SSL { + s3Srv = &http.Server{Addr: s3Base, Handler: s3r} + err = s3Srv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile) + } else { + s3Srv = &http.Server{Addr: s3Base, Handler: s3r} + err = s3Srv.ListenAndServe() + } + s3Running = false + if err != nil && !errors.Is(err, http.ErrServerClosed) { + handleEndpointStartFailedHooks("s3", err) + utils.Log.Errorf("failed to start s3 server: %s", err.Error()) + } else { + handleEndpointShutdownHooks("s3") + } + }() +} + +// stopS3 gracefully shuts down the standalone S3 listener with a 5s timeout. +// No-op if the server is not running. +func stopS3() { + if s3Srv == nil { + s3Running = false + return + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := s3Srv.Shutdown(ctx); err != nil { + utils.Log.Error("S3 server shutdown err: ", err) + } + s3Srv = nil + s3Running = false +} + +// startFTP starts the FTP server. Idempotent: if already running, stops first +// then starts. Skipped when ftp.enable is false or ftp.listen == "". +func startFTP() { + if !conf.Conf.FTP.Enable || conf.Conf.FTP.Listen == "" { + return + } + if ftpRunning { + stopFTP() + } + var err error + ftpDriver, err = server.NewMainDriver() + if err != nil { + utils.Log.Errorf("failed to start ftp driver: %s", err.Error()) + return + } + fmt.Printf("start ftp server on %s\n", conf.Conf.FTP.Listen) + utils.Log.Infof("start ftp server on %s", conf.Conf.FTP.Listen) + go func() { + ftpServer = ftpserver.NewFtpServer(ftpDriver) + ftpRunning = true + err = ftpServer.ListenAndServe() + ftpRunning = false if err != nil { - utils.Log.Errorf("failed to start ftp driver: %s", err.Error()) + handleEndpointStartFailedHooks("ftp", err) + utils.Log.Errorf("problem ftp server listening: %s", err.Error()) } else { - fmt.Printf("start ftp server on %s\n", conf.Conf.FTP.Listen) - utils.Log.Infof("start ftp server on %s", conf.Conf.FTP.Listen) - go func() { - ftpServer = ftpserver.NewFtpServer(ftpDriver) - ftpRunning = true - err = ftpServer.ListenAndServe() - ftpRunning = false - if err != nil { - handleEndpointStartFailedHooks("ftp", err) - utils.Log.Errorf("problem ftp server listening: %s", err.Error()) - } else { - handleEndpointShutdownHooks("ftp") - } - }() + handleEndpointShutdownHooks("ftp") } + }() +} + +// stopFTP gracefully shuts down the FTP server by stopping the driver (which +// closes active clients) and then the server. No-op if the server is not +// running. +func stopFTP() { + if ftpServer == nil { + ftpRunning = false + ftpDriver = nil + return } - if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable { - var err error - sftpDriver, err = server.NewSftpDriver() + if ftpDriver != nil { + ftpDriver.Stop() + ftpDriver = nil + } + if err := ftpServer.Stop(); err != nil { + utils.Log.Error("FTP server shutdown err: ", err) + } + ftpServer = nil + ftpRunning = false +} + +// startSFTP starts the SFTP server. Idempotent: if already running, stops +// first then starts. Skipped when sftp.enable is false or sftp.listen == "". +func startSFTP() { + if !conf.Conf.SFTP.Enable || conf.Conf.SFTP.Listen == "" { + return + } + if sftpRunning { + stopSFTP() + } + var err error + sftpDriver, err = server.NewSftpDriver() + if err != nil { + utils.Log.Errorf("failed to start sftp driver: %s", err.Error()) + return + } + fmt.Printf("start sftp server on %s", conf.Conf.SFTP.Listen) + utils.Log.Infof("start sftp server on %s", conf.Conf.SFTP.Listen) + go func() { + sftpServer = sftpd.NewSftpServer(sftpDriver) + sftpRunning = true + err = sftpServer.RunServer() + sftpRunning = false if err != nil { - utils.Log.Errorf("failed to start sftp driver: %s", err.Error()) + handleEndpointStartFailedHooks("sftp", err) + utils.Log.Errorf("problem sftp server listening: %s", err.Error()) } else { - fmt.Printf("start sftp server on %s", conf.Conf.SFTP.Listen) - utils.Log.Infof("start sftp server on %s", conf.Conf.SFTP.Listen) - go func() { - sftpServer = sftpd.NewSftpServer(sftpDriver) - sftpRunning = true - err = sftpServer.RunServer() - sftpRunning = false - if err != nil { - handleEndpointStartFailedHooks("sftp", err) - utils.Log.Errorf("problem sftp server listening: %s", err.Error()) - } else { - handleEndpointShutdownHooks("sftp") - } - }() + handleEndpointShutdownHooks("sftp") } + }() +} + +// stopSFTP gracefully shuts down the SFTP server. No-op if the server is not +// running. +func stopSFTP() { + if sftpServer == nil { + sftpRunning = false + sftpDriver = nil + return } - running = true + if err := sftpServer.Close(); err != nil { + utils.Log.Error("SFTP server shutdown err: ", err) + } + sftpServer = nil + sftpDriver = nil + sftpRunning = false } func Shutdown(timeout time.Duration) { utils.Log.Println("Shutdown server...") + stopConfigWatcher() fs.ArchiveContentUploadTaskManager.RemoveAll() - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() + // Each stop* is internally bounded by a 5s timeout and is a no-op when + // the corresponding server is nil. They run concurrently to mirror the + // original Shutdown semantics. The `timeout` parameter is retained for + // API compatibility; per-endpoint graceful shutdown uses a fixed 5s. var wg sync.WaitGroup - if httpSrv != nil && conf.Conf.Scheme.HttpPort != -1 { - wg.Add(1) - go func() { - defer wg.Done() - if err := httpSrv.Shutdown(ctx); err != nil { - utils.Log.Error("HTTP server shutdown err: ", err) - } - httpSrv = nil - }() - } - if httpsSrv != nil && conf.Conf.Scheme.HttpsPort != -1 { - wg.Add(1) - go func() { - defer wg.Done() - if err := httpsSrv.Shutdown(ctx); err != nil { - utils.Log.Error("HTTPS server shutdown err: ", err) + wg.Add(7) + go func() { defer wg.Done(); stopHTTP() }() + go func() { defer wg.Done(); stopHTTPS() }() + go func() { defer wg.Done(); stopH3() }() + go func() { defer wg.Done(); stopUnix() }() + go func() { defer wg.Done(); stopS3() }() + go func() { defer wg.Done(); stopFTP() }() + go func() { defer wg.Done(); stopSFTP() }() + wg.Wait() + utils.Log.Println("Server exit") + running = false +} + +// applyEndpointChanges restarts only the network endpoints whose config +// changed between oldConf and newConf. Endpoints that didn't change are +// left untouched. Called from reload.go during hot-reload. +func applyEndpointChanges(oldConf, newConf *conf.Config) { + // HTTP: address, http_port, enable_h2c (affects httpHandler) + if oldConf.Scheme.Address != newConf.Scheme.Address || + oldConf.Scheme.HttpPort != newConf.Scheme.HttpPort || + oldConf.Scheme.EnableH2c != newConf.Scheme.EnableH2c { + // h2c change requires rebuilding httpHandler + if oldConf.Scheme.EnableH2c != newConf.Scheme.EnableH2c { + httpHandler = ginEngine + if newConf.Scheme.EnableH2c { + httpHandler = h2c.NewHandler(ginEngine, &http2.Server{}) } - httpsSrv = nil - }() - if quicSrv != nil && conf.Conf.Scheme.EnableH3 { - wg.Add(1) - go func() { - defer wg.Done() - if err := quicSrv.Shutdown(ctx); err != nil { - utils.Log.Error("HTTP3 (quic) server shutdown err: ", err) - } - quicSrv = nil - }() } + log.Info("restarting HTTP listener due to scheme change") + stopHTTP() + startHTTP() } - if unixSrv != nil && conf.Conf.Scheme.UnixFile != "" { - wg.Add(1) - go func() { - defer wg.Done() - if err := unixSrv.Shutdown(ctx); err != nil { - utils.Log.Error("Unix server shutdown err: ", err) - } - unixSrv = nil - }() - } - if s3Srv != nil && conf.Conf.S3.Port != -1 && conf.Conf.S3.Enable { - wg.Add(1) - go func() { - defer wg.Done() - if err := s3Srv.Shutdown(ctx); err != nil { - utils.Log.Error("S3 server shutdown err: ", err) - } - s3Srv = nil - }() - } - if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable && ftpServer != nil { - wg.Add(1) - go func() { - defer wg.Done() - if ftpDriver != nil { - ftpDriver.Stop() - ftpDriver = nil - } - if err := ftpServer.Stop(); err != nil { - utils.Log.Error("FTP server shutdown err: ", err) - } - ftpServer = nil - }() - } - if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable && sftpServer != nil { - wg.Add(1) - go func() { - defer wg.Done() - if err := sftpServer.Close(); err != nil { - utils.Log.Error("SFTP server shutdown err: ", err) - } - sftpServer = nil - sftpDriver = nil - }() + // HTTPS: address, https_port, cert_file, key_file + if oldConf.Scheme.Address != newConf.Scheme.Address || + oldConf.Scheme.HttpsPort != newConf.Scheme.HttpsPort || + oldConf.Scheme.CertFile != newConf.Scheme.CertFile || + oldConf.Scheme.KeyFile != newConf.Scheme.KeyFile { + log.Info("restarting HTTPS listener due to scheme change") + stopHTTPS() + startHTTPS() + } + // H3: enable_h3 (and depends on https) + if oldConf.Scheme.EnableH3 != newConf.Scheme.EnableH3 { + log.Info("restarting HTTP3 listener due to enable_h3 change") + stopH3() + if newConf.Scheme.EnableH3 { + startH3() + } + } + // Unix: unix_file, unix_file_perm + if oldConf.Scheme.UnixFile != newConf.Scheme.UnixFile || + oldConf.Scheme.UnixFilePerm != newConf.Scheme.UnixFilePerm { + log.Info("restarting Unix listener due to scheme change") + stopUnix() + startUnix() + } + // S3 standalone: s3.enable, s3.port, s3.ssl + if oldConf.S3.Enable != newConf.S3.Enable || + oldConf.S3.Port != newConf.S3.Port || + oldConf.S3.SSL != newConf.S3.SSL { + log.Info("restarting S3 standalone listener due to s3 config change") + stopS3() + startS3() + } + // FTP: any ftp.* field + if !reflect.DeepEqual(oldConf.FTP, newConf.FTP) { + log.Info("restarting FTP server due to ftp config change") + stopFTP() + startFTP() + } + // SFTP: any sftp.* field + if !reflect.DeepEqual(oldConf.SFTP, newConf.SFTP) { + log.Info("restarting SFTP server due to sftp config change") + stopSFTP() + startSFTP() } - wg.Wait() - utils.Log.Println("Server exit") - running = false } type EndpointStartFailedHook func(string, string) diff --git a/internal/conf/watcher.go b/internal/conf/watcher.go new file mode 100644 index 000000000..74579f86e --- /dev/null +++ b/internal/conf/watcher.go @@ -0,0 +1,238 @@ +// watcher.go implements a config file hot-reload watcher for the conf package. +// It debounces noisy filesystem events, deduplicates reloads via SHA-256 content +// hashing, and handles the atomic-replace pattern (editor writes a temp file then +// renames it over the target) by re-adding the watch after a short settle delay. + +package conf + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/fsnotify/fsnotify" + log "github.com/sirupsen/logrus" +) + +const ( + // configReloadDebounce coalesces rapid successive write events into a single reload. + configReloadDebounce = 150 * time.Millisecond + // replaceCheckDelay is a short settle window used after a Remove/Rename event to + // decide whether the config file reappeared (atomic replace) or was truly deleted. + replaceCheckDelay = 50 * time.Millisecond +) + +// ConfigWatcher watches the OpenList config file and invokes the supplied +// reloadFn with the raw file bytes whenever the content actually changes. +// Reloads are debounced and de-duplicated by SHA-256 hash. +type ConfigWatcher struct { + configPath string + reloadFn func(data []byte) error + watcher *fsnotify.Watcher + lastHash string + mu sync.Mutex // guards lastHash and reloadTimer + reloadTimer *time.Timer + stopped atomic.Bool + stopOnce sync.Once + cancel context.CancelFunc +} + +// NewConfigWatcher creates a watcher for the given config file path. +// reloadFn is called with the raw file bytes when the file content actually +// changed (debounced + hash-checked). If reloadFn returns a non-nil error, +// the lastHash is NOT updated so the next event retries. +func NewConfigWatcher(configPath string, reloadFn func(data []byte) error) *ConfigWatcher { + return &ConfigWatcher{ + configPath: configPath, + reloadFn: reloadFn, + } +} + +// Start begins watching the config file. It adds the file to fsnotify and +// spawns an event-loop goroutine that runs until ctx is cancelled or Stop is called. +// Start does NOT perform an initial reload (the config is already loaded at boot). +func (w *ConfigWatcher) Start(ctx context.Context) error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + w.watcher = watcher + + if err := w.watcher.Add(w.configPath); err != nil { + log.Errorf("failed to watch config file %s: %v", w.configPath, err) + _ = w.watcher.Close() + w.watcher = nil + return err + } + log.Debugf("watching config file: %s", w.configPath) + + // Derive a cancellable context so Stop can break the event loop goroutine. + ctx, cancel := context.WithCancel(ctx) + w.cancel = cancel + + go w.processEvents(ctx) + return nil +} + +// Stop stops the watcher. It is idempotent (safe to call multiple times). +func (w *ConfigWatcher) Stop() { + w.stopOnce.Do(func() { + w.stopped.Store(true) + if w.cancel != nil { + w.cancel() + } + w.mu.Lock() + if w.reloadTimer != nil { + w.reloadTimer.Stop() + w.reloadTimer = nil + } + w.mu.Unlock() + if w.watcher != nil { + _ = w.watcher.Close() + } + }) +} + +// processEvents is the event-loop goroutine. It exits when ctx is cancelled, +// the watcher channels are closed, or the watcher has been stopped. +func (w *ConfigWatcher) processEvents(ctx context.Context) { + for { + if w.stopped.Load() { + return + } + select { + case <-ctx.Done(): + return + case event, ok := <-w.watcher.Events: + if !ok { + return + } + if w.stopped.Load() { + return + } + w.handleEvent(event) + case errWatch, ok := <-w.watcher.Errors: + if !ok { + return + } + if w.stopped.Load() { + return + } + log.Errorf("file watcher error: %v", errWatch) + } + } +} + +// handleEvent filters fsnotify events down to those targeting the config file +// and dispatches Write/Create to a debounced reload, while Remove/Rename are +// treated as the atomic-replace case. +func (w *ConfigWatcher) handleEvent(event fsnotify.Event) { + configOps := fsnotify.Write | fsnotify.Create | fsnotify.Rename | fsnotify.Remove + if w.normalizePath(event.Name) != w.normalizePath(w.configPath) { + return + } + if event.Op&configOps == 0 { + return + } + + log.Debugf("config file event detected: %s %s", event.Op.String(), event.Name) + + if event.Op&(fsnotify.Write|fsnotify.Create) != 0 { + w.scheduleConfigReload() + return + } + + // Remove or Rename: atomic-replace case (editor writes temp file then renames + // over the target). Wait briefly; if the file reappears, re-add the watch and + // reload. If it is genuinely gone, warn and skip reloading. + if event.Op&(fsnotify.Remove|fsnotify.Rename) != 0 { + go func() { + time.Sleep(replaceCheckDelay) + if w.stopped.Load() { + return + } + if _, statErr := os.Stat(w.configPath); statErr == nil { + if errAdd := w.watcher.Add(w.configPath); errAdd != nil { + log.Debugf("failed to re-add config file watch: %v", errAdd) + } + w.scheduleConfigReload() + } else { + log.Warnf("config file disappeared, not reloading: %s", w.configPath) + } + }() + } +} + +// scheduleConfigReload debounces reload triggers: any pending reload is cancelled +// and replaced with a fresh timer firing after configReloadDebounce. +func (w *ConfigWatcher) scheduleConfigReload() { + w.mu.Lock() + defer w.mu.Unlock() + if w.reloadTimer != nil { + w.reloadTimer.Stop() + } + w.reloadTimer = time.AfterFunc(configReloadDebounce, w.reloadIfChanged) +} + +// reloadIfChanged reads the config file, compares its SHA-256 hash against the +// last successfully reloaded content, and invokes reloadFn only on a real change. +// On reloadFn error the hash is left untouched so the next event retries. +func (w *ConfigWatcher) reloadIfChanged() { + if w.stopped.Load() { + return + } + data, err := os.ReadFile(w.configPath) + if err != nil { + log.Errorf("failed to read config file for reload: %v", err) + return + } + if len(data) == 0 { + log.Debugf("ignoring empty config file write event") + return + } + sum := sha256.Sum256(data) + newHash := hex.EncodeToString(sum[:]) + + w.mu.Lock() + lastHash := w.lastHash + w.mu.Unlock() + + if lastHash != "" && lastHash == newHash { + log.Debugf("config file content unchanged (hash match), skipping reload") + return + } + + log.Infof("config file changed, reloading: %s", w.configPath) + if err := w.reloadFn(data); err != nil { + log.Errorf("failed to reload config: %v", err) + return + } + + w.mu.Lock() + w.lastHash = newHash + w.mu.Unlock() +} + +// normalizePath cleans a path and strips Windows extended-length prefixes +// (\\?\ and \\?\UNC\), then lowercases it for case-insensitive comparison. +// Lowercasing is applied cross-platform; it is a no-op for the equality check +// on case-sensitive filesystems since both sides are normalized the same way. +func (w *ConfigWatcher) normalizePath(path string) string { + trimmed := strings.TrimSpace(path) + if trimmed == "" { + return "" + } + cleaned := filepath.Clean(trimmed) + if strings.HasPrefix(cleaned, `\\?\UNC\`) { + cleaned = `\\` + strings.TrimPrefix(cleaned, `\\?\UNC\`) + } else { + cleaned = strings.TrimPrefix(cleaned, `\\?\`) + } + return strings.ToLower(cleaned) +} diff --git a/internal/db/db.go b/internal/db/db.go index 96529c15d..37158f156 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -1,14 +1,28 @@ package db import ( + "fmt" + stdlog "log" + "strings" + "sync" + "time" + log "github.com/sirupsen/logrus" + "github.com/OpenListTeam/OpenList/v4/cmd/flags" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/model" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/schema" ) -var db *gorm.DB +var ( + db *gorm.DB + dbMu sync.RWMutex +) func Init(d *gorm.DB) { db = d @@ -28,7 +42,11 @@ func AutoMigrate(dst ...interface{}) error { return err } +// GetDb returns the current db handle. Callers that hold the handle briefly +// (single query) are safe; long-lived references should re-fetch via GetDb. func GetDb() *gorm.DB { + dbMu.RLock() + defer dbMu.RUnlock() return db } @@ -45,3 +63,129 @@ func Close() { return } } + +// Reinit opens a new gorm.DB using the same construction logic as +// bootstrap.InitDB, runs AutoMigrate on it, then atomically swaps the +// package-level db handle under dbMu. The previous handle is closed after +// 5 minutes to allow in-flight queries to drain. +// +// This is HIGH RISK: in-flight queries that captured the old handle before +// the swap may fail when the old handle is eventually closed. Callers should +// prefer GetDb() per-query so they always observe the current handle. +func Reinit(newConf *conf.Config) error { + newDB, err := openDB(newConf) + if err != nil { + return fmt.Errorf("open db on reinit failed: %w", err) + } + + // Run AutoMigrate on the new DB before swapping it in so the new handle + // is fully schema-consistent with the rest of the system. + migrateModels := []interface{}{ + new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), + new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey), new(model.SharingDB), + } + if newConf.Database.Type == "mysql" { + err = newDB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(migrateModels...) + } else { + err = newDB.AutoMigrate(migrateModels...) + } + if err != nil { + // Close the new DB since we will not swap it in. + if sqlDB, dbErr := newDB.DB(); dbErr == nil { + _ = sqlDB.Close() + } + return fmt.Errorf("auto migrate on reinit failed: %w", err) + } + + dbMu.Lock() + oldDB := db + db = newDB + dbMu.Unlock() + + log.Warn("database reinitialized at runtime (HIGH RISK): in-flight queries on the old handle may fail; old handle closes after 5 min") + + // Schedule the old DB closure after 5 minutes to let in-flight queries drain. + if oldDB != nil { + time.AfterFunc(5*time.Minute, func() { + sqlDB, err := oldDB.DB() + if err != nil { + log.Errorf("failed to get old db for deferred close: %s", err.Error()) + return + } + if err := sqlDB.Close(); err != nil { + log.Errorf("failed to close old db: %s", err.Error()) + return + } + log.Info("old db handle closed after reinit grace period") + }) + } + + return nil +} + +// openDB constructs a *gorm.DB from the provided config using the same logic +// as bootstrap.InitDB: sqlite3/mysql/postgres driver selection, gorm logger, +// naming strategy, and DSN construction. Mirrors internal/bootstrap/db.go. +func openDB(newConf *conf.Config) (*gorm.DB, error) { + logLevel := logger.Silent + if flags.Debug || flags.Dev { + logLevel = logger.Info + } + newLogger := logger.New( + stdlog.New(log.StandardLogger().Out, "\r\n", stdlog.LstdFlags), + logger.Config{ + SlowThreshold: time.Second, + LogLevel: logLevel, + IgnoreRecordNotFoundError: true, + Colorful: true, + }, + ) + gormConfig := &gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + TablePrefix: newConf.Database.TablePrefix, + }, + Logger: newLogger, + } + + if flags.Dev { + return gorm.Open(openSQLite("file::memory:?cache=shared"), gormConfig) + } + + database := newConf.Database + switch database.Type { + case "sqlite3": + { + if !(strings.HasSuffix(database.DBFile, ".db") && len(database.DBFile) > 3) { + return nil, fmt.Errorf("db name error: %s", database.DBFile) + } + return gorm.Open(openSQLite(fmt.Sprintf("%s?_journal=WAL&_vacuum=incremental", + database.DBFile)), gormConfig) + } + case "mysql": + { + dsn := database.DSN + if dsn == "" { + //[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] + dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s", + database.User, database.Password, database.Host, database.Port, database.Name, database.SSLMode) + } + return gorm.Open(mysql.Open(dsn), gormConfig) + } + case "postgres": + { + dsn := database.DSN + if dsn == "" { + if database.Password != "" { + dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Shanghai", + database.Host, database.User, database.Password, database.Name, database.Port, database.SSLMode) + } else { + dsn = fmt.Sprintf("host=%s user=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Shanghai", + database.Host, database.User, database.Name, database.Port, database.SSLMode) + } + } + return gorm.Open(postgres.Open(dsn), gormConfig) + } + default: + return nil, fmt.Errorf("not supported database type: %s", database.Type) + } +} diff --git a/internal/db/sqlite_driver_glebarez.go b/internal/db/sqlite_driver_glebarez.go new file mode 100644 index 000000000..d1eab57cc --- /dev/null +++ b/internal/db/sqlite_driver_glebarez.go @@ -0,0 +1,12 @@ +//go:build !sqlite_cgo_compat && !(linux && (mips || mips64 || mips64le || mipsle || loong64)) && !(windows && 386) + +package db + +import ( + "github.com/glebarez/sqlite" + "gorm.io/gorm" +) + +func openSQLite(dsn string) gorm.Dialector { + return sqlite.Open(dsn) +} diff --git a/internal/db/sqlite_driver_gorm.go b/internal/db/sqlite_driver_gorm.go new file mode 100644 index 000000000..7c6ead30d --- /dev/null +++ b/internal/db/sqlite_driver_gorm.go @@ -0,0 +1,12 @@ +//go:build sqlite_cgo_compat || (linux && (mips || mips64 || mips64le || mipsle || loong64)) || (windows && 386) + +package db + +import ( + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func openSQLite(dsn string) gorm.Dialector { + return sqlite.Open(dsn) +} diff --git a/internal/search/bleve/search.go b/internal/search/bleve/search.go index ed1518711..5cbb84a17 100644 --- a/internal/search/bleve/search.go +++ b/internal/search/bleve/search.go @@ -135,6 +135,18 @@ func (b *Bleve) Release(ctx context.Context) error { return nil } +// Close releases the underlying bleve index. It is idempotent and safe to +// call multiple times: after the first call the BIndex field is cleared so +// subsequent calls (and calls to Release) become no-ops. +func (b *Bleve) Close() error { + if b.BIndex != nil { + err := b.BIndex.Close() + b.BIndex = nil + return err + } + return nil +} + func (b *Bleve) Clear(ctx context.Context) error { err := b.Release(ctx) if err != nil { diff --git a/internal/search/meilisearch/search.go b/internal/search/meilisearch/search.go index 75283228c..a27b826d0 100644 --- a/internal/search/meilisearch/search.go +++ b/internal/search/meilisearch/search.go @@ -216,6 +216,22 @@ func (m *Meilisearch) Release(ctx context.Context) error { return nil } +// Close releases the meilisearch client and stops the task queue. It is +// idempotent and safe to call multiple times: fields are cleared after the +// first call so subsequent calls (and calls to Release) become no-ops. +// Note: meilisearch-go's ServiceManager.Close() returns no error. +func (m *Meilisearch) Close() error { + if m.taskQueue != nil { + m.taskQueue.Stop() + m.taskQueue = nil + } + if m.Client != nil { + m.Client.Close() + m.Client = nil + } + return nil +} + func (m *Meilisearch) Clear(ctx context.Context) error { _, err := m.Client.Index(m.IndexUid).DeleteAllDocumentsWithContext(ctx) // task was enqueued (if succeed), no need to wait diff --git a/internal/search/search.go b/internal/search/search.go index 0ca1e2417..06d450812 100644 --- a/internal/search/search.go +++ b/internal/search/search.go @@ -9,6 +9,7 @@ import ( "github.com/OpenListTeam/OpenList/v4/internal/model" "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/internal/search/searcher" + "github.com/OpenListTeam/OpenList/v4/internal/setting" log "github.com/sirupsen/logrus" ) @@ -47,6 +48,28 @@ func Init(mode string) error { return err } +// Reinit rebuilds the search instance using the current config. It is called +// by the hot-reload applier when bleve_dir / meilisearch.* changes — cases +// where the mode (stored in settings) is unchanged but the underlying +// resources must be re-opened. Init(mode) alone would no-op in that case +// (same mode), so Reinit force-releases the existing instance first. +func Reinit() error { + if instance != nil { + // Prefer Close() when the searcher supports it so client/task-queue + // resources are released; Release() alone may leave them dangling. + if closer, ok := instance.(interface{ Close() error }); ok { + _ = closer.Close() + } + err := instance.Release(context.Background()) + if err != nil { + log.Errorf("release search instance err: %+v", err) + } + instance = nil + } + mode := setting.GetStr(conf.SearchIndex) + return Init(mode) +} + func Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) { return instance.Search(ctx, req) } diff --git a/server/mcp.go b/server/mcp.go index c036ce389..9d85c02cc 100644 --- a/server/mcp.go +++ b/server/mcp.go @@ -1,18 +1,10 @@ package server import ( - "github.com/OpenListTeam/OpenList/v4/internal/conf" - "github.com/OpenListTeam/OpenList/v4/server/common" "github.com/OpenListTeam/OpenList/v4/server/mcp" "github.com/gin-gonic/gin" ) func MCP(g *gin.RouterGroup) { - if !conf.Conf.MCP.Enable { - g.Any("/mcp", func(c *gin.Context) { - common.ErrorStrResp(c, "MCP server is not enabled", 403) - }) - return - } mcp.Register(g) } diff --git a/server/mcp/handler.go b/server/mcp/handler.go index 191977eb4..eea963e7d 100644 --- a/server/mcp/handler.go +++ b/server/mcp/handler.go @@ -78,12 +78,25 @@ var supportedProtocolVersions = map[string]struct{}{ } func Register(g *gin.RouterGroup) { - mcpGroup := g.Group("/mcp", middlewares.Auth(false), middlewares.AuthAdmin) + mcpGroup := g.Group("/mcp", mcpEnabledGuard, middlewares.Auth(false), middlewares.AuthAdmin) mcpGroup.GET("", defaultServer.handleGet) mcpGroup.POST("", defaultServer.handlePost) mcpGroup.DELETE("", defaultServer.handleDelete) } +// mcpEnabledGuard hot-reloads conf.Conf.MCP.Enable on every request so that +// toggling the flag in config.json takes effect without a restart. It runs +// before the auth middlewares so a disabled MCP returns 403 without requiring +// authentication, matching the previous startup-time placeholder behavior. +func mcpEnabledGuard(c *gin.Context) { + if !conf.Conf.MCP.Enable { + common.ErrorStrResp(c, "MCP server is not enabled", 403) + c.Abort() + return + } + c.Next() +} + func (s *Server) handleGet(c *gin.Context) { if !validateOrigin(c.Request) { c.Status(http.StatusForbidden) diff --git a/server/middlewares/filtered_logger.go b/server/middlewares/filtered_logger.go index 2a51a14c4..036d2da40 100644 --- a/server/middlewares/filtered_logger.go +++ b/server/middlewares/filtered_logger.go @@ -50,6 +50,17 @@ func initFilterList() { log.Infof("Loaded %d log filters.", len(filterList)) } +// ReloadFilterList resets and rebuilds the log filter list from the current +// config. It is safe to call repeatedly (e.g. on config hot-reload). When log +// filtering is disabled (Log.Filter.Enable == false) the list is cleared so an +// already-installed FilteredLogger middleware stops filtering requests. +func ReloadFilterList() { + filterList = nil + if conf.Conf.Log.Filter.Enable { + initFilterList() + } +} + func skiperDecider(c *gin.Context) bool { // every filter need metch all condithon as filter match // so if any condithon not metch, skip this filter diff --git a/server/middlewares/limit.go b/server/middlewares/limit.go index 70a4a525d..34b0c308e 100644 --- a/server/middlewares/limit.go +++ b/server/middlewares/limit.go @@ -2,7 +2,10 @@ package middlewares import ( "io" + "net/http" + "sync/atomic" + "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/stream" "github.com/gin-gonic/gin" ) @@ -18,6 +21,26 @@ func MaxAllowed(n int) gin.HandlerFunc { } } +var maxConnCounter atomic.Int32 + +func MaxAllowedLive() gin.HandlerFunc { + return func(c *gin.Context) { + limit := conf.Conf.MaxConnections + if limit <= 0 { + c.Next() + return + } + current := maxConnCounter.Add(1) + if current > int32(limit) { + maxConnCounter.Add(-1) + c.AbortWithStatus(http.StatusServiceUnavailable) + return + } + defer maxConnCounter.Add(-1) + c.Next() + } +} + func UploadRateLimiter(limiter stream.Limiter) gin.HandlerFunc { return func(c *gin.Context) { c.Request.Body = &stream.RateLimitReader{ diff --git a/server/router.go b/server/router.go index 737bfd9f5..9209ce14d 100644 --- a/server/router.go +++ b/server/router.go @@ -1,6 +1,10 @@ package server import ( + "fmt" + "strings" + "sync/atomic" + "github.com/OpenListTeam/OpenList/v4/cmd/flags" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/message" @@ -25,9 +29,7 @@ func Init(e *gin.Engine) { } Cors(e) g := e.Group(conf.URL.Path) - if conf.Conf.Scheme.HttpPort != -1 && conf.Conf.Scheme.HttpsPort != -1 && conf.Conf.Scheme.ForceHttps { - e.Use(middlewares.ForceHttps) - } + e.Use(forceHttpsGuard) g.Any("/ping", func(c *gin.Context) { c.String(200, "pong") }) @@ -37,9 +39,7 @@ func Init(e *gin.Engine) { g.GET("/i/:link_name", handles.Plist) common.SecretKey = []byte(conf.Conf.JwtSecret) g.Use(middlewares.StoragesLoaded) - if conf.Conf.MaxConnections > 0 { - g.Use(middlewares.MaxAllowed(conf.Conf.MaxConnections)) - } + g.Use(middlewares.MaxAllowedLive()) WebDav(g.Group("/dav")) S3(g.Group("/s3")) MCP(g) @@ -250,13 +250,42 @@ func _sharing(g *gin.RouterGroup) { g.POST("/disable", handles.SetEnableSharing(true)) } -func Cors(r *gin.Engine) { +var corsHolder atomic.Pointer[gin.HandlerFunc] + +func buildCorsHandler() gin.HandlerFunc { config := cors.DefaultConfig() // config.AllowAllOrigins = true config.AllowOrigins = conf.Conf.Cors.AllowOrigins config.AllowHeaders = conf.Conf.Cors.AllowHeaders config.AllowMethods = conf.Conf.Cors.AllowMethods - r.Use(cors.New(config)) + return cors.New(config) +} + +func Cors(r *gin.Engine) { + h := buildCorsHandler() + corsHolder.Store(&h) + r.Use(func(c *gin.Context) { + if h := corsHolder.Load(); h != nil { + (*h)(c) + } + }) +} + +func ReloadCors() { + h := buildCorsHandler() + corsHolder.Store(&h) +} + +func forceHttpsGuard(c *gin.Context) { + if conf.Conf.Scheme.ForceHttps && c.Request.TLS == nil { + host := c.Request.Host + // change port to https port + host = strings.Replace(host, fmt.Sprintf(":%d", conf.Conf.Scheme.HttpPort), fmt.Sprintf(":%d", conf.Conf.Scheme.HttpsPort), 1) + c.Redirect(302, "https://"+host+c.Request.RequestURI) + c.Abort() + return + } + c.Next() } func InitS3(e *gin.Engine) { diff --git a/server/s3.go b/server/s3.go index 1c0b5925a..f5debfc2e 100644 --- a/server/s3.go +++ b/server/s3.go @@ -12,25 +12,26 @@ import ( ) func S3(g *gin.RouterGroup) { + h, _ := s3.NewServer(context.Background()) + g.Any("/*path", s3EnabledGuard, func(c *gin.Context) { + adjustedPath := strings.TrimPrefix(c.Request.URL.Path, path.Join(conf.URL.Path, "/s3")) + c.Request.URL.Path = adjustedPath + gin.WrapH(h)(c) + }) +} + +func s3EnabledGuard(c *gin.Context) { if !conf.Conf.S3.Enable { - g.Any("/*path", func(c *gin.Context) { - common.ErrorStrResp(c, "S3 server is not enabled", 403) - }) + common.ErrorStrResp(c, "S3 server is not enabled", 403) + c.Abort() return } if conf.Conf.S3.Port != -1 { - g.Any("/*path", func(c *gin.Context) { - common.ErrorStrResp(c, "S3 server bound to single port", 403) - }) + common.ErrorStrResp(c, "S3 server bound to single port", 403) + c.Abort() return } - h, _ := s3.NewServer(context.Background()) - - g.Any("/*path", func(c *gin.Context) { - adjustedPath := strings.TrimPrefix(c.Request.URL.Path, path.Join(conf.URL.Path, "/s3")) - c.Request.URL.Path = adjustedPath - gin.WrapH(h)(c) - }) + c.Next() } func S3Server(g *gin.RouterGroup) { diff --git a/server/static/static.go b/server/static/static.go index 38eee2dfa..8e1207a69 100644 --- a/server/static/static.go +++ b/server/static/static.go @@ -16,6 +16,7 @@ import ( "github.com/OpenListTeam/OpenList/v4/pkg/utils" "github.com/OpenListTeam/OpenList/v4/public" "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" ) type ManifestIcon struct { @@ -106,6 +107,15 @@ func initIndex(siteConfig SiteConfig) { UpdateIndex() } +// Reload re-derives the static fs and index.html from the current config. +// Called by the hot-reload applier when dist_dir / cdn changes. +func Reload() { + siteConfig := getSiteConfig() + initStatic() + initIndex(siteConfig) + log.Info("static resources reloaded") +} + func UpdateIndex() { utils.Log.Debug("Updating index.html with settings...") favicon := setting.GetStr(conf.Favicon)