mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 03:07:20 +08:00
feat: 优化包大小
This commit is contained in:
@@ -17,12 +17,8 @@ import (
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/php74"
|
||||
"github.com/acepanel/panel/internal/apps/php80"
|
||||
"github.com/acepanel/panel/internal/apps/php81"
|
||||
"github.com/acepanel/panel/internal/apps/php82"
|
||||
"github.com/acepanel/panel/internal/apps/php83"
|
||||
"github.com/acepanel/panel/internal/apps/php84"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
@@ -127,12 +123,8 @@ func initWeb() (*app.Web, error) {
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
php74App := php74.NewApp(locale, taskRepo)
|
||||
php80App := php80.NewApp(locale, taskRepo)
|
||||
php81App := php81.NewApp(locale, taskRepo)
|
||||
php82App := php82.NewApp(locale, taskRepo)
|
||||
php83App := php83.NewApp(locale, taskRepo)
|
||||
php84App := php84.NewApp(locale, taskRepo)
|
||||
openrestyApp := openresty.NewApp(locale)
|
||||
perconaApp := percona.NewApp(locale, settingRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp(locale)
|
||||
@@ -141,7 +133,7 @@ func initWeb() (*app.Web, error) {
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
loader := bootstrap.NewLoader(codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
http := route.NewHttp(koanf, userService, userTokenService, homeService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, loader)
|
||||
wsService := service.NewWsService(locale, koanf, logger, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
|
||||
@@ -17,12 +17,8 @@ import (
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/php74"
|
||||
"github.com/acepanel/panel/internal/apps/php80"
|
||||
"github.com/acepanel/panel/internal/apps/php81"
|
||||
"github.com/acepanel/panel/internal/apps/php82"
|
||||
"github.com/acepanel/panel/internal/apps/php83"
|
||||
"github.com/acepanel/panel/internal/apps/php84"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
@@ -84,12 +80,8 @@ func initCli() (*app.Cli, error) {
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
php74App := php74.NewApp(locale, taskRepo)
|
||||
php80App := php80.NewApp(locale, taskRepo)
|
||||
php81App := php81.NewApp(locale, taskRepo)
|
||||
php82App := php82.NewApp(locale, taskRepo)
|
||||
php83App := php83.NewApp(locale, taskRepo)
|
||||
php84App := php84.NewApp(locale, taskRepo)
|
||||
openrestyApp := openresty.NewApp(locale)
|
||||
perconaApp := percona.NewApp(locale, settingRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp(locale)
|
||||
@@ -98,7 +90,7 @@ func initCli() (*app.Cli, error) {
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
loader := bootstrap.NewLoader(codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
appCli := app.NewCli(command, gormigrate, loader)
|
||||
return appCli, nil
|
||||
}
|
||||
|
||||
46
go.mod
46
go.mod
@@ -3,11 +3,6 @@ module github.com/acepanel/panel
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0
|
||||
github.com/bddjr/hlfhr v1.4.0
|
||||
github.com/beevik/ntp v1.5.0
|
||||
github.com/coder/websocket v1.8.14
|
||||
@@ -23,7 +18,6 @@ require (
|
||||
github.com/gookit/color v1.6.0
|
||||
github.com/gookit/validate v1.5.6
|
||||
github.com/hashicorp/go-version v1.8.0
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/klauspost/compress v1.18.2
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0
|
||||
github.com/knadh/koanf/providers/file v1.2.1
|
||||
@@ -45,12 +39,11 @@ require (
|
||||
github.com/libtnb/sessions v1.2.2
|
||||
github.com/libtnb/utils v1.2.1
|
||||
github.com/mholt/acmez/v3 v3.1.4
|
||||
github.com/moby/moby/api v1.52.0
|
||||
github.com/moby/moby/api v1.53.0-rc.1
|
||||
github.com/moby/moby/client v0.2.1
|
||||
github.com/ncruces/go-sqlite3 v0.30.4
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.30.2
|
||||
github.com/orandin/slog-gorm v1.4.0
|
||||
github.com/pkg/sftp v1.13.10
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/lo v1.52.0
|
||||
@@ -58,7 +51,6 @@ require (
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/studio-b12/gowebdav v0.11.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701
|
||||
github.com/urfave/cli/v3 v3.6.1
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
@@ -72,21 +64,6 @@ require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.3.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
@@ -94,11 +71,8 @@ require (
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gofiber/schema v1.6.0 // indirect
|
||||
@@ -109,7 +83,6 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/libtnb/securecookie v1.2.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@@ -118,6 +91,7 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
github.com/timtadh/data-structures v0.6.2 // indirect
|
||||
@@ -126,19 +100,17 @@ require (
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/jlaffaye/ftp => github.com/devhaozi/ftp v0.0.0-20251029164318-d47d120a5239
|
||||
github.com/mholt/acmez/v3 => github.com/libtnb/acmez/v3 v3.0.0-20250926183442-f904007577f7
|
||||
github.com/mholt/acmez/v3 => github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e
|
||||
github.com/moby/moby/client => github.com/libtnb/moby/client v0.0.0-20260103192150-39cfd5376055
|
||||
)
|
||||
|
||||
tool github.com/google/wire
|
||||
|
||||
93
go.sum
93
go.sum
@@ -22,49 +22,9 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/bddjr/hlfhr v1.4.0 h1:EVryUs0mLzQ455bAIKhASqwxFV9IYrGOFjuz0HE6oYE=
|
||||
github.com/bddjr/hlfhr v1.4.0/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
|
||||
github.com/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=
|
||||
@@ -95,8 +55,6 @@ github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfv
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/devhaozi/ftp v0.0.0-20251029164318-d47d120a5239 h1:SyKCrdV7GYWnPxWhe/Fc4a92aW7/mEYK5hhIruJffqc=
|
||||
github.com/devhaozi/ftp v0.0.0-20251029164318-d47d120a5239/go.mod h1:zuLAKdqFqFvNgkCrH0SC7K1XyUiydS7BFCmmoHUWWg0=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
@@ -108,15 +66,13 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
||||
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
|
||||
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
@@ -128,11 +84,6 @@ github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
@@ -169,8 +120,6 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
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/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
@@ -241,10 +190,10 @@ github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd
|
||||
github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
|
||||
github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -275,12 +224,14 @@ github.com/libdns/tencentcloud v1.4.3 h1:xJHYLL1TdPeOtUr6Bu6dHTd1TU6/VFm7BFc2EAz
|
||||
github.com/libdns/tencentcloud v1.4.3/go.mod h1:Be9gY3tDa12DuAPU79RV9NZIcjY6qg5s7zKPsP26yAM=
|
||||
github.com/libdns/westcn v1.0.2 h1:PA2M3tME5/0T3klPMzSHvGk1EWnGNNkJiKxaJjFalnM=
|
||||
github.com/libdns/westcn v1.0.2/go.mod h1:iKpk8jjOU+793Yp8nHoihPTCR6N1KWtm4/r8BB9mVnk=
|
||||
github.com/libtnb/acmez/v3 v3.0.0-20250926183442-f904007577f7 h1:lyQgUomlXxgp3e/SoK5q8S5PBAdL8P+sZ9iPFx2dhYk=
|
||||
github.com/libtnb/acmez/v3 v3.0.0-20250926183442-f904007577f7/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e h1:HPHmtFcR5VpTJTpDqG4+l16uJfSlpB/X0r3qq22znrk=
|
||||
github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/libtnb/chix v1.3.2 h1:LkA/+OaHeMyTiQoWEcCQKAMSKDZ0LxBXSENQO5d+OKk=
|
||||
github.com/libtnb/chix v1.3.2/go.mod h1:ItssStBa/ov7v4qujyYzORqIf7Z6DO7Q4carOM7pyAQ=
|
||||
github.com/libtnb/gormstore v1.1.1 h1:FG/3P4PuWM6/vB4weVJ31meiSaoeXns1NQlP66quKeg=
|
||||
github.com/libtnb/gormstore v1.1.1/go.mod h1:8A5QzeZxi1MpSmjUVsHTDAL6KnU84feIXMutFLPawwA=
|
||||
github.com/libtnb/moby/client v0.0.0-20260103192150-39cfd5376055 h1:KriYNjuNbSP6xqg36DbRHBdeITZCCq+p8blgb+PXaqU=
|
||||
github.com/libtnb/moby/client v0.0.0-20260103192150-39cfd5376055/go.mod h1:OSq/ZzzXkqIGNpzASnhZLSZMuYoPC6Wue6AV7jr/etg=
|
||||
github.com/libtnb/securecookie v1.2.0 h1:2uc0PBDm0foeSTrcZ9QTX1IEjf6kFEwfgEYSIXQSKrA=
|
||||
github.com/libtnb/securecookie v1.2.0/go.mod h1:ja+wNGnQzYqcqXQnJWu6icsaWi5JEBwNEMJ2ReTVDxA=
|
||||
github.com/libtnb/sessions v1.2.2 h1:VTTzzeBDJEkJbaPaIU9C4bRj2oAqD0rgQ7UHFkkaNT4=
|
||||
@@ -306,10 +257,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
|
||||
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
||||
github.com/moby/moby/api v1.53.0-rc.1 h1:M5SUwRbTrNy+plCTiV6gn4ZiN/Csynk0imIsUmOgHGI=
|
||||
github.com/moby/moby/api v1.53.0-rc.1/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@@ -331,8 +280,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
@@ -388,8 +335,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/studio-b12/gowebdav v0.11.0 h1:qbQzq4USxY28ZYsGJUfO5jR+xkFtcnwWgitp4Zp1irU=
|
||||
github.com/studio-b12/gowebdav v0.11.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
@@ -413,27 +358,11 @@ github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMz
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
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/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
@@ -452,8 +381,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA=
|
||||
golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
||||
@@ -12,12 +12,8 @@ import (
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/php74"
|
||||
"github.com/acepanel/panel/internal/apps/php80"
|
||||
"github.com/acepanel/panel/internal/apps/php81"
|
||||
"github.com/acepanel/panel/internal/apps/php82"
|
||||
"github.com/acepanel/panel/internal/apps/php83"
|
||||
"github.com/acepanel/panel/internal/apps/php84"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
@@ -38,12 +34,8 @@ var ProviderSet = wire.NewSet(
|
||||
minio.NewApp,
|
||||
mysql.NewApp,
|
||||
nginx.NewApp,
|
||||
php74.NewApp,
|
||||
php80.NewApp,
|
||||
php81.NewApp,
|
||||
php82.NewApp,
|
||||
php83.NewApp,
|
||||
php84.NewApp,
|
||||
openresty.NewApp,
|
||||
percona.NewApp,
|
||||
phpmyadmin.NewApp,
|
||||
podman.NewApp,
|
||||
postgresql.NewApp,
|
||||
|
||||
@@ -70,11 +70,11 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *App) ErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/%s", app.Root, "wwwlogs/nginx-error.log"))
|
||||
service.Success(w, fmt.Sprintf("%s/%s", app.Root, "server/nginx/nginx-error.log"))
|
||||
}
|
||||
|
||||
func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("cat /dev/null > %s/%s", app.Root, "wwwlogs/nginx-error.log"); err != nil {
|
||||
if _, err := shell.Execf("cat /dev/null > %s/%s", app.Root, "server/nginx/nginx-error.log"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
22
internal/apps/openresty/app.go
Normal file
22
internal/apps/openresty/app.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
nginx *nginx.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
nginx: nginx.NewApp(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.nginx.Route(r)
|
||||
}
|
||||
5
internal/apps/openresty/request.go
Normal file
5
internal/apps/openresty/request.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package openresty
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
23
internal/apps/percona/app.go
Normal file
23
internal/apps/percona/app.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package percona
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
mysql *mysql.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, setting biz.SettingRepo) *App {
|
||||
return &App{
|
||||
mysql: mysql.NewApp(t, setting),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.mysql.Route(r)
|
||||
}
|
||||
9
internal/apps/percona/request.go
Normal file
9
internal/apps/percona/request.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package percona
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
type SetRootPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
}
|
||||
@@ -10,12 +10,8 @@ import (
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/php74"
|
||||
"github.com/acepanel/panel/internal/apps/php80"
|
||||
"github.com/acepanel/panel/internal/apps/php81"
|
||||
"github.com/acepanel/panel/internal/apps/php82"
|
||||
"github.com/acepanel/panel/internal/apps/php83"
|
||||
"github.com/acepanel/panel/internal/apps/php84"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
@@ -37,12 +33,8 @@ func NewLoader(
|
||||
minio *minio.App,
|
||||
mysql *mysql.App,
|
||||
nginx *nginx.App,
|
||||
php74 *php74.App,
|
||||
php80 *php80.App,
|
||||
php81 *php81.App,
|
||||
php82 *php82.App,
|
||||
php83 *php83.App,
|
||||
php84 *php84.App,
|
||||
openresty *openresty.App,
|
||||
percona *percona.App,
|
||||
phpmyadmin *phpmyadmin.App,
|
||||
podman *podman.App,
|
||||
postgresql *postgresql.App,
|
||||
@@ -53,6 +45,6 @@ func NewLoader(
|
||||
supervisor *supervisor.App,
|
||||
) *apploader.Loader {
|
||||
loader := new(apploader.Loader)
|
||||
loader.Add(codeserver, docker, fail2ban, frp, gitea, memcached, minio, mysql, nginx, php74, php80, php81, php82, php83, php84, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor)
|
||||
loader.Add(codeserver, docker, fail2ban, frp, gitea, memcached, minio, mysql, nginx, openresty, percona, phpmyadmin, podman, postgresql, pureftpd, redis, rsync, s3fs, supervisor)
|
||||
return loader
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/httplog/v3"
|
||||
@@ -57,9 +56,6 @@ func (r *Middlewares) Globals(t *gotext.Locale, mux *chi.Mux) []func(http.Handle
|
||||
writer, _ := gzip.NewWriterLevel(w, level)
|
||||
return writer
|
||||
})
|
||||
compressor.SetEncoder("br", func(w io.Writer, level int) io.Writer {
|
||||
return brotli.NewWriterV2(w, level)
|
||||
})
|
||||
compressor.SetEncoder("zstd", func(w io.Writer, level int) io.Writer {
|
||||
writer, _ := zstd.NewWriter(w, zstd.WithEncoderLevel(zstd.SpeedBetterCompression))
|
||||
return writer
|
||||
|
||||
13
internal/service/environment.go
Normal file
13
internal/service/environment.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package service
|
||||
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
|
||||
type EnvironmentService struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewEnvironmentService(t *gotext.Locale) *EnvironmentService {
|
||||
return &EnvironmentService{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,6 @@ func (r *Loader) Add(app ...types.App) {
|
||||
}
|
||||
|
||||
func (r *Loader) Register(mux chi.Router) {
|
||||
/*for slug, item := range r.Apps {
|
||||
mux.Route("/"+slug, item.Route)
|
||||
}*/
|
||||
|
||||
apps.Range(func(key, value any) bool {
|
||||
app := value.(types.App)
|
||||
mux.Route("/"+key.(string), app.Route)
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
type FTPConfig struct {
|
||||
Host string // FTP 服务器地址
|
||||
Port int // FTP 端口,默认 21
|
||||
Username string // 用户名
|
||||
Password string // 密码
|
||||
BasePath string // 基础路径
|
||||
}
|
||||
|
||||
type FTP struct {
|
||||
config FTPConfig
|
||||
}
|
||||
|
||||
func NewFTP(config FTPConfig) (Storage, error) {
|
||||
if config.Port == 0 {
|
||||
config.Port = 21
|
||||
}
|
||||
config.BasePath = strings.Trim(config.BasePath, "/")
|
||||
|
||||
f := &FTP{
|
||||
config: config,
|
||||
}
|
||||
|
||||
if err := f.ensureBasePath(); err != nil {
|
||||
return nil, fmt.Errorf("failed to ensure base path: %w", err)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// connect 建立 FTP 连接
|
||||
func (f *FTP) connect() (*ftp.ServerConn, error) {
|
||||
addr := fmt.Sprintf("%s:%d", f.config.Host, f.config.Port)
|
||||
conn, err := ftp.Dial(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Login(f.config.Username, f.config.Password)
|
||||
if err != nil {
|
||||
_ = conn.Quit()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// ensureBasePath 确保基础路径存在
|
||||
func (f *FTP) ensureBasePath() error {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
// 递归创建路径
|
||||
parts := strings.Split(f.config.BasePath, "/")
|
||||
currentPath := ""
|
||||
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if currentPath == "" {
|
||||
currentPath = part
|
||||
} else {
|
||||
currentPath = currentPath + "/" + part
|
||||
}
|
||||
|
||||
_ = conn.MakeDir(currentPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRemotePath 获取远程路径
|
||||
func (f *FTP) getRemotePath(path string) string {
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
if f.config.BasePath == "" {
|
||||
return path
|
||||
}
|
||||
if path == "" {
|
||||
return f.config.BasePath
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", f.config.BasePath, path)
|
||||
}
|
||||
|
||||
// MakeDirectory 创建目录
|
||||
func (f *FTP) MakeDirectory(directory string) error {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(directory)
|
||||
|
||||
// 递归创建目录
|
||||
parts := strings.Split(remotePath, "/")
|
||||
currentPath := ""
|
||||
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if currentPath == "" {
|
||||
currentPath = part
|
||||
} else {
|
||||
currentPath = currentPath + "/" + part
|
||||
}
|
||||
|
||||
// 尝试创建目录
|
||||
_ = conn.MakeDir(currentPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDirectory 删除目录
|
||||
func (f *FTP) DeleteDirectory(directory string) error {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(directory)
|
||||
return conn.RemoveDir(remotePath)
|
||||
}
|
||||
|
||||
// Copy 复制文件到新位置
|
||||
func (f *FTP) Copy(oldFile, newFile string) error {
|
||||
// FTP 不支持直接复制,需要下载再上传
|
||||
data, err := f.Get(oldFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Put(newFile, string(data))
|
||||
}
|
||||
|
||||
// Delete 删除文件
|
||||
func (f *FTP) Delete(files ...string) error {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
for _, file := range files {
|
||||
remotePath := f.getRemotePath(file)
|
||||
if err := conn.Delete(remotePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists 检查文件是否存在
|
||||
func (f *FTP) Exists(file string) bool {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(file)
|
||||
_, err = conn.FileSize(remotePath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Files 获取目录下的所有文件
|
||||
func (f *FTP) Files(path string) ([]string, error) {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(path)
|
||||
entries, err := conn.List(remotePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
for _, entry := range entries {
|
||||
if entry.Type == ftp.EntryTypeFile {
|
||||
files = append(files, entry.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Get 读取文件内容
|
||||
func (f *FTP) Get(file string) ([]byte, error) {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(file)
|
||||
resp, err := conn.Retr(remotePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(resp *ftp.Response) {
|
||||
_ = resp.Close()
|
||||
}(resp)
|
||||
|
||||
return io.ReadAll(resp)
|
||||
}
|
||||
|
||||
// LastModified 获取文件最后修改时间
|
||||
func (f *FTP) LastModified(file string) (time.Time, error) {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(file)
|
||||
entries, err := conn.List(filepath.Dir(remotePath))
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
fileName := filepath.Base(remotePath)
|
||||
for _, entry := range entries {
|
||||
if entry.Name == fileName {
|
||||
return entry.Time, nil
|
||||
}
|
||||
}
|
||||
|
||||
return time.Time{}, fmt.Errorf("file not found: %s", file)
|
||||
}
|
||||
|
||||
// MimeType 获取文件的 MIME 类型
|
||||
func (f *FTP) MimeType(file string) (string, error) {
|
||||
ext := filepath.Ext(file)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
return "application/octet-stream", nil
|
||||
}
|
||||
return mimeType, nil
|
||||
}
|
||||
|
||||
// Missing 检查文件是否不存在
|
||||
func (f *FTP) Missing(file string) bool {
|
||||
return !f.Exists(file)
|
||||
}
|
||||
|
||||
// Move 移动文件到新位置
|
||||
func (f *FTP) Move(oldFile, newFile string) error {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
oldPath := f.getRemotePath(oldFile)
|
||||
newPath := f.getRemotePath(newFile)
|
||||
|
||||
// 确保目标目录存在
|
||||
newDir := filepath.Dir(newPath)
|
||||
if newDir != "." {
|
||||
f.createDirectoryPath(conn, newDir)
|
||||
}
|
||||
|
||||
return conn.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
// createDirectoryPath 递归创建目录路径
|
||||
func (f *FTP) createDirectoryPath(conn *ftp.ServerConn, path string) {
|
||||
parts := strings.Split(path, "/")
|
||||
currentPath := ""
|
||||
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if currentPath == "" {
|
||||
currentPath = part
|
||||
} else {
|
||||
currentPath = currentPath + "/" + part
|
||||
}
|
||||
|
||||
_ = conn.MakeDir(currentPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Path 获取文件的完整路径
|
||||
func (f *FTP) Path(file string) string {
|
||||
return fmt.Sprintf("ftp://%s:%d/%s", f.config.Host, f.config.Port, f.getRemotePath(file))
|
||||
}
|
||||
|
||||
// Put 写入文件内容
|
||||
func (f *FTP) Put(file, content string) error {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(file)
|
||||
|
||||
// 确保目录存在
|
||||
remoteDir := filepath.Dir(remotePath)
|
||||
if remoteDir != "." {
|
||||
f.createDirectoryPath(conn, remoteDir)
|
||||
}
|
||||
|
||||
return conn.Stor(remotePath, bytes.NewReader([]byte(content)))
|
||||
}
|
||||
|
||||
// Size 获取文件大小
|
||||
func (f *FTP) Size(file string) (int64, error) {
|
||||
conn, err := f.connect()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func(conn *ftp.ServerConn) {
|
||||
_ = conn.Quit()
|
||||
}(conn)
|
||||
|
||||
remotePath := f.getRemotePath(file)
|
||||
return conn.FileSize(remotePath)
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Local struct {
|
||||
basePath string
|
||||
}
|
||||
|
||||
func NewLocal(basePath string) Storage {
|
||||
if basePath == "" {
|
||||
basePath = "/"
|
||||
}
|
||||
return &Local{
|
||||
basePath: basePath,
|
||||
}
|
||||
}
|
||||
|
||||
// MakeDirectory 创建目录
|
||||
func (n *Local) MakeDirectory(directory string) error {
|
||||
fullPath := n.fullPath(directory)
|
||||
return os.MkdirAll(fullPath, 0755)
|
||||
}
|
||||
|
||||
// DeleteDirectory 删除目录
|
||||
func (n *Local) DeleteDirectory(directory string) error {
|
||||
fullPath := n.fullPath(directory)
|
||||
return os.RemoveAll(fullPath)
|
||||
}
|
||||
|
||||
// Copy 复制文件到新位置
|
||||
func (n *Local) Copy(oldFile, newFile string) error {
|
||||
srcPath := n.fullPath(oldFile)
|
||||
dstPath := n.fullPath(newFile)
|
||||
|
||||
// 确保目标目录存在
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = src.Close() }()
|
||||
|
||||
dst, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = dst.Close() }()
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete 删除文件
|
||||
func (n *Local) Delete(files ...string) error {
|
||||
for _, file := range files {
|
||||
fullPath := n.fullPath(file)
|
||||
if err := os.Remove(fullPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists 检查文件是否存在
|
||||
func (n *Local) Exists(file string) bool {
|
||||
fullPath := n.fullPath(file)
|
||||
_, err := os.Stat(fullPath)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// Files 获取目录下的所有文件
|
||||
func (n *Local) Files(path string) ([]string, error) {
|
||||
fullPath := n.fullPath(path)
|
||||
entries, err := os.ReadDir(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
files = append(files, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Get 读取文件内容
|
||||
func (n *Local) Get(file string) ([]byte, error) {
|
||||
fullPath := n.fullPath(file)
|
||||
return os.ReadFile(fullPath)
|
||||
}
|
||||
|
||||
// LastModified 获取文件最后修改时间
|
||||
func (n *Local) LastModified(file string) (time.Time, error) {
|
||||
fullPath := n.fullPath(file)
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return info.ModTime(), nil
|
||||
}
|
||||
|
||||
// MimeType 获取文件的 MIME 类型
|
||||
func (n *Local) MimeType(file string) (string, error) {
|
||||
ext := filepath.Ext(file)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
return "application/octet-stream", nil
|
||||
}
|
||||
return mimeType, nil
|
||||
}
|
||||
|
||||
// Missing 检查文件是否不存在
|
||||
func (n *Local) Missing(file string) bool {
|
||||
return !n.Exists(file)
|
||||
}
|
||||
|
||||
// Move 移动文件到新位置
|
||||
func (n *Local) Move(oldFile, newFile string) error {
|
||||
oldPath := n.fullPath(oldFile)
|
||||
newPath := n.fullPath(newFile)
|
||||
|
||||
// 确保目标目录存在
|
||||
if err := os.MkdirAll(filepath.Dir(newPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
// Path 获取文件的完整路径
|
||||
func (n *Local) Path(file string) string {
|
||||
return n.fullPath(file)
|
||||
}
|
||||
|
||||
// Put 写入文件内容
|
||||
func (n *Local) Put(file, content string) error {
|
||||
fullPath := n.fullPath(file)
|
||||
|
||||
// 确保目录存在
|
||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(fullPath, []byte(content), 0644)
|
||||
}
|
||||
|
||||
// Size 获取文件大小
|
||||
func (n *Local) Size(file string) (int64, error) {
|
||||
fullPath := n.fullPath(file)
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return info.Size(), nil
|
||||
}
|
||||
|
||||
// fullPath 获取文件的完整路径
|
||||
func (n *Local) fullPath(file string) string {
|
||||
if filepath.IsAbs(file) {
|
||||
return file
|
||||
}
|
||||
return filepath.Join(n.basePath, file)
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
)
|
||||
|
||||
// S3AddressingStyle S3 地址模式
|
||||
type S3AddressingStyle string
|
||||
|
||||
const (
|
||||
// S3AddressingStylePath Path 模式:https://s3.region.amazonaws.com/bucket/key
|
||||
S3AddressingStylePath S3AddressingStyle = "path"
|
||||
// S3AddressingStyleVirtualHosted Virtual Hosted 模式:https://bucket.s3.region.amazonaws.com/key
|
||||
S3AddressingStyleVirtualHosted S3AddressingStyle = "virtual-hosted"
|
||||
)
|
||||
|
||||
type S3Config struct {
|
||||
Region string // AWS 区域
|
||||
Bucket string // S3 存储桶名称
|
||||
AccessKeyID string // 访问密钥 ID
|
||||
SecretAccessKey string // 访问密钥
|
||||
Endpoint string // 自定义端点(如 MinIO)
|
||||
BasePath string // 基础路径前缀
|
||||
AddressingStyle S3AddressingStyle // 地址模式
|
||||
ForcePathStyle bool // 强制使用 Path 模式(兼容旧版本)
|
||||
}
|
||||
|
||||
type S3 struct {
|
||||
client *s3.Client
|
||||
config S3Config
|
||||
}
|
||||
|
||||
func NewS3(cfg S3Config) (Storage, error) {
|
||||
// 设置默认地址模式
|
||||
if cfg.AddressingStyle == "" {
|
||||
if cfg.ForcePathStyle {
|
||||
cfg.AddressingStyle = S3AddressingStylePath
|
||||
} else {
|
||||
cfg.AddressingStyle = S3AddressingStyleVirtualHosted
|
||||
}
|
||||
}
|
||||
|
||||
cfg.BasePath = strings.Trim(cfg.BasePath, "/")
|
||||
|
||||
var awsCfg aws.Config
|
||||
var err error
|
||||
|
||||
awsCfg, err = config.LoadDefaultConfig(context.TODO(),
|
||||
config.WithRegion(cfg.Region),
|
||||
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
|
||||
cfg.AccessKeyID, cfg.SecretAccessKey, "")),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load AWS config: %w", err)
|
||||
}
|
||||
|
||||
usePathStyle := cfg.AddressingStyle == S3AddressingStylePath || cfg.ForcePathStyle
|
||||
|
||||
var client *s3.Client
|
||||
if cfg.Endpoint != "" {
|
||||
// 自定义端点
|
||||
client = s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
||||
o.UsePathStyle = usePathStyle
|
||||
o.BaseEndpoint = aws.String(cfg.Endpoint)
|
||||
})
|
||||
} else {
|
||||
// 标准 AWS S3
|
||||
client = s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
||||
o.UsePathStyle = usePathStyle
|
||||
})
|
||||
}
|
||||
|
||||
s := &S3{
|
||||
client: client,
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
if s.config.BasePath != "" {
|
||||
if err := s.ensureBasePath(); err != nil {
|
||||
return nil, fmt.Errorf("failed to ensure base path: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ensureBasePath 确保基础路径存在
|
||||
func (s *S3) ensureBasePath() error {
|
||||
key := s.config.BasePath + "/"
|
||||
_, err := s.client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(s.config.BasePath),
|
||||
Key: aws.String(key),
|
||||
Body: bytes.NewReader([]byte{}),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// getKey 获取完整的对象键
|
||||
func (s *S3) getKey(file string) string {
|
||||
file = strings.TrimPrefix(file, "/")
|
||||
if s.config.BasePath == "" {
|
||||
return file
|
||||
}
|
||||
if file == "" {
|
||||
return s.config.BasePath
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", s.config.BasePath, file)
|
||||
}
|
||||
|
||||
// MakeDirectory 创建目录(S3中实际创建一个空的目录标记对象)
|
||||
func (s *S3) MakeDirectory(directory string) error {
|
||||
key := s.getKey(directory)
|
||||
if !strings.HasSuffix(key, "/") {
|
||||
key += "/"
|
||||
}
|
||||
|
||||
_, err := s.client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Key: aws.String(key),
|
||||
Body: bytes.NewReader([]byte{}),
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteDirectory 删除目录
|
||||
func (s *S3) DeleteDirectory(directory string) error {
|
||||
prefix := s.getKey(directory)
|
||||
if prefix != "" && !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
|
||||
// 列出所有文件
|
||||
var objects []types.ObjectIdentifier
|
||||
paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Prefix: aws.String(prefix),
|
||||
})
|
||||
|
||||
for paginator.HasMorePages() {
|
||||
output, err := paginator.NextPage(context.TODO())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, obj := range output.Contents {
|
||||
if obj.Key != nil {
|
||||
objects = append(objects, types.ObjectIdentifier{
|
||||
Key: obj.Key,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(objects) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
_, err := s.client.DeleteObjects(context.TODO(), &s3.DeleteObjectsInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Delete: &types.Delete{
|
||||
Objects: objects,
|
||||
},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy 复制文件到新位置
|
||||
func (s *S3) Copy(oldFile, newFile string) error {
|
||||
sourceKey := s.getKey(oldFile)
|
||||
destKey := s.getKey(newFile)
|
||||
|
||||
_, err := s.client.CopyObject(context.TODO(), &s3.CopyObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
CopySource: aws.String(fmt.Sprintf("%s/%s", s.config.Bucket, sourceKey)),
|
||||
Key: aws.String(destKey),
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete 删除文件
|
||||
func (s *S3) Delete(files ...string) error {
|
||||
if len(files) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
var objects []types.ObjectIdentifier
|
||||
for _, file := range files {
|
||||
key := s.getKey(file)
|
||||
objects = append(objects, types.ObjectIdentifier{
|
||||
Key: aws.String(key),
|
||||
})
|
||||
}
|
||||
|
||||
_, err := s.client.DeleteObjects(context.TODO(), &s3.DeleteObjectsInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Delete: &types.Delete{
|
||||
Objects: objects,
|
||||
},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Exists 检查文件是否存在
|
||||
func (s *S3) Exists(file string) bool {
|
||||
key := s.getKey(file)
|
||||
_, err := s.client.HeadObject(context.TODO(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Files 获取目录下的所有文件
|
||||
func (s *S3) Files(path string) ([]string, error) {
|
||||
prefix := s.getKey(path)
|
||||
if prefix != "" && !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
|
||||
var files []string
|
||||
paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Prefix: aws.String(prefix),
|
||||
Delimiter: aws.String("/"),
|
||||
})
|
||||
|
||||
for paginator.HasMorePages() {
|
||||
output, err := paginator.NextPage(context.TODO())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, obj := range output.Contents {
|
||||
if obj.Key != nil && !strings.HasSuffix(*obj.Key, "/") {
|
||||
fileName := strings.TrimPrefix(*obj.Key, prefix)
|
||||
if fileName != "" && !strings.Contains(fileName, "/") {
|
||||
files = append(files, fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Get 读取文件内容
|
||||
func (s *S3) Get(file string) ([]byte, error) {
|
||||
key := s.getKey(file)
|
||||
output, err := s.client.GetObject(context.TODO(), &s3.GetObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(body io.ReadCloser) { _ = body.Close() }(output.Body)
|
||||
|
||||
return io.ReadAll(output.Body)
|
||||
}
|
||||
|
||||
// LastModified 获取文件最后修改时间
|
||||
func (s *S3) LastModified(file string) (time.Time, error) {
|
||||
key := s.getKey(file)
|
||||
output, err := s.client.HeadObject(context.TODO(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
if output.LastModified != nil {
|
||||
return *output.LastModified, nil
|
||||
}
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
// MimeType 获取文件的 MIME 类型
|
||||
func (s *S3) MimeType(file string) (string, error) {
|
||||
key := s.getKey(file)
|
||||
output, err := s.client.HeadObject(context.TODO(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if output.ContentType != nil {
|
||||
return *output.ContentType, nil
|
||||
}
|
||||
|
||||
// 根据文件扩展名推断
|
||||
ext := filepath.Ext(file)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
return "application/octet-stream", nil
|
||||
}
|
||||
return mimeType, nil
|
||||
}
|
||||
|
||||
// Missing 检查文件是否不存在
|
||||
func (s *S3) Missing(file string) bool {
|
||||
return !s.Exists(file)
|
||||
}
|
||||
|
||||
// Move 移动文件到新位置
|
||||
func (s *S3) Move(oldFile, newFile string) error {
|
||||
// 先复制
|
||||
if err := s.Copy(oldFile, newFile); err != nil {
|
||||
return err
|
||||
}
|
||||
// 再删除原文件
|
||||
return s.Delete(oldFile)
|
||||
}
|
||||
|
||||
// Path 获取文件的完整路径
|
||||
func (s *S3) Path(file string) string {
|
||||
// 根据地址模式返回不同的 URL 格式
|
||||
key := s.getKey(file)
|
||||
|
||||
if s.config.Endpoint != "" {
|
||||
// 自定义端点
|
||||
return fmt.Sprintf("%s/%s/%s", strings.TrimSuffix(s.config.Endpoint, "/"), s.config.Bucket, key)
|
||||
}
|
||||
|
||||
switch s.config.AddressingStyle {
|
||||
case S3AddressingStyleVirtualHosted:
|
||||
// Virtual Hosted 模式:https://bucket.s3.region.amazonaws.com/key
|
||||
return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%s", s.config.Bucket, s.config.Region, key)
|
||||
case S3AddressingStylePath:
|
||||
// Path 模式:https://s3.region.amazonaws.com/bucket/key
|
||||
return fmt.Sprintf("https://s3.%s.amazonaws.com/%s/%s", s.config.Region, s.config.Bucket, key)
|
||||
default:
|
||||
// 默认返回 s3:// 协议格式
|
||||
return fmt.Sprintf("s3://%s/%s", s.config.Bucket, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Put 写入文件内容
|
||||
func (s *S3) Put(file, content string) error {
|
||||
key := s.getKey(file)
|
||||
|
||||
// 推断 MIME 类型
|
||||
ext := filepath.Ext(file)
|
||||
contentType := mime.TypeByExtension(ext)
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
_, err := s.client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Key: aws.String(key),
|
||||
Body: bytes.NewReader([]byte(content)),
|
||||
ContentType: aws.String(contentType),
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Size 获取文件大小
|
||||
func (s *S3) Size(file string) (int64, error) {
|
||||
key := s.getKey(file)
|
||||
output, err := s.client.HeadObject(context.TODO(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(s.config.Bucket),
|
||||
Key: aws.String(key),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if output.ContentLength != nil {
|
||||
return *output.ContentLength, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type SFTPConfig struct {
|
||||
Host string // SFTP 服务器地址
|
||||
Port int // SFTP 端口,默认 22
|
||||
Username string // 用户名
|
||||
Password string // 密码
|
||||
PrivateKey string // SSH 私钥路径或内容
|
||||
BasePath string // 基础路径
|
||||
Timeout time.Duration // 连接超时时间
|
||||
}
|
||||
|
||||
type SFTP struct {
|
||||
config SFTPConfig
|
||||
}
|
||||
|
||||
func NewSFTP(config SFTPConfig) (Storage, error) {
|
||||
if config.Port == 0 {
|
||||
config.Port = 22
|
||||
}
|
||||
if config.Timeout == 0 {
|
||||
config.Timeout = 30 * time.Second
|
||||
}
|
||||
config.BasePath = strings.Trim(config.BasePath, "/")
|
||||
|
||||
s := &SFTP{
|
||||
config: config,
|
||||
}
|
||||
|
||||
if err := s.ensureBasePath(); err != nil {
|
||||
return nil, fmt.Errorf("failed to ensure base path: %w", err)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// connect 建立 SFTP 连接
|
||||
func (s *SFTP) connect() (*sftp.Client, func(), error) {
|
||||
var auth []ssh.AuthMethod
|
||||
|
||||
// 密码认证
|
||||
if s.config.Password != "" {
|
||||
auth = append(auth, ssh.Password(s.config.Password))
|
||||
}
|
||||
|
||||
// 私钥认证
|
||||
if s.config.PrivateKey != "" {
|
||||
var signer ssh.Signer
|
||||
var err error
|
||||
|
||||
if _, statErr := os.Stat(s.config.PrivateKey); statErr == nil {
|
||||
// 私钥文件路径
|
||||
keyBytes, err2 := os.ReadFile(s.config.PrivateKey)
|
||||
if err2 != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read private key file: %w", err)
|
||||
}
|
||||
signer, err = ssh.ParsePrivateKey(keyBytes)
|
||||
} else {
|
||||
// 私钥内容
|
||||
signer, err = ssh.ParsePrivateKey([]byte(s.config.PrivateKey))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
auth = append(auth, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
clientConfig := &ssh.ClientConfig{
|
||||
User: s.config.Username,
|
||||
Auth: auth,
|
||||
Timeout: s.config.Timeout,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port)
|
||||
sshClient, err := ssh.Dial("tcp", addr, clientConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sftpClient, err := sftp.NewClient(sshClient)
|
||||
if err != nil {
|
||||
_ = sshClient.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
_ = sftpClient.Close()
|
||||
_ = sshClient.Close()
|
||||
}
|
||||
|
||||
return sftpClient, cleanup, nil
|
||||
}
|
||||
|
||||
// ensureBasePath 确保基础路径存在
|
||||
func (s *SFTP) ensureBasePath() error {
|
||||
if s.config.BasePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
return client.MkdirAll(s.config.BasePath)
|
||||
}
|
||||
|
||||
// getRemotePath 获取远程路径
|
||||
func (s *SFTP) getRemotePath(path string) string {
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
if s.config.BasePath == "" {
|
||||
return path
|
||||
}
|
||||
if path == "" {
|
||||
return s.config.BasePath
|
||||
}
|
||||
return filepath.Join(s.config.BasePath, path)
|
||||
}
|
||||
|
||||
// MakeDirectory 创建目录
|
||||
func (s *SFTP) MakeDirectory(directory string) error {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(directory)
|
||||
return client.MkdirAll(remotePath)
|
||||
}
|
||||
|
||||
// DeleteDirectory 删除目录
|
||||
func (s *SFTP) DeleteDirectory(directory string) error {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(directory)
|
||||
return client.RemoveDirectory(remotePath)
|
||||
}
|
||||
|
||||
// Copy 复制文件到新位置
|
||||
func (s *SFTP) Copy(oldFile, newFile string) error {
|
||||
// SFTP 不支持直接复制,需要读取再写入
|
||||
data, err := s.Get(oldFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Put(newFile, string(data))
|
||||
}
|
||||
|
||||
// Delete 删除文件
|
||||
func (s *SFTP) Delete(files ...string) error {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
for _, file := range files {
|
||||
remotePath := s.getRemotePath(file)
|
||||
if err := client.Remove(remotePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists 检查文件是否存在
|
||||
func (s *SFTP) Exists(file string) bool {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(file)
|
||||
_, err = client.Stat(remotePath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Files 获取目录下的所有文件
|
||||
func (s *SFTP) Files(path string) ([]string, error) {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(path)
|
||||
entries, err := client.ReadDir(remotePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
files = append(files, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Get 读取文件内容
|
||||
func (s *SFTP) Get(file string) ([]byte, error) {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(file)
|
||||
remoteFile, err := client.Open(remotePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = remoteFile.Close() }()
|
||||
|
||||
return io.ReadAll(remoteFile)
|
||||
}
|
||||
|
||||
// LastModified 获取文件最后修改时间
|
||||
func (s *SFTP) LastModified(file string) (time.Time, error) {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(file)
|
||||
stat, err := client.Stat(remotePath)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return stat.ModTime(), nil
|
||||
}
|
||||
|
||||
// MimeType 获取文件的 MIME 类型
|
||||
func (s *SFTP) MimeType(file string) (string, error) {
|
||||
ext := filepath.Ext(file)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
return "application/octet-stream", nil
|
||||
}
|
||||
return mimeType, nil
|
||||
}
|
||||
|
||||
// Missing 检查文件是否不存在
|
||||
func (s *SFTP) Missing(file string) bool {
|
||||
return !s.Exists(file)
|
||||
}
|
||||
|
||||
// Move 移动文件到新位置
|
||||
func (s *SFTP) Move(oldFile, newFile string) error {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
oldPath := s.getRemotePath(oldFile)
|
||||
newPath := s.getRemotePath(newFile)
|
||||
|
||||
// 确保目标目录存在
|
||||
newDir := filepath.Dir(newPath)
|
||||
if newDir != "." {
|
||||
_ = client.MkdirAll(newDir)
|
||||
}
|
||||
|
||||
return client.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
// Path 获取文件的完整路径
|
||||
func (s *SFTP) Path(file string) string {
|
||||
return fmt.Sprintf("sftp://%s:%d/%s", s.config.Host, s.config.Port, s.getRemotePath(file))
|
||||
}
|
||||
|
||||
// Put 写入文件内容
|
||||
func (s *SFTP) Put(file, content string) error {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(file)
|
||||
|
||||
// 确保目录存在
|
||||
remoteDir := filepath.Dir(remotePath)
|
||||
if remoteDir != "." {
|
||||
_ = client.MkdirAll(remoteDir)
|
||||
}
|
||||
|
||||
remoteFile, err := client.Create(remotePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = remoteFile.Close() }()
|
||||
|
||||
_, err = io.Copy(remoteFile, bytes.NewReader([]byte(content)))
|
||||
return err
|
||||
}
|
||||
|
||||
// Size 获取文件大小
|
||||
func (s *SFTP) Size(file string) (int64, error) {
|
||||
client, cleanup, err := s.connect()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
remotePath := s.getRemotePath(file)
|
||||
stat, err := client.Stat(remotePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return stat.Size(), nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
// MakeDirectory creates a directory.
|
||||
MakeDirectory(directory string) error
|
||||
// DeleteDirectory deletes the given directory.
|
||||
DeleteDirectory(directory string) error
|
||||
// Copy the given file to a new location.
|
||||
Copy(oldFile, newFile string) error
|
||||
// Delete deletes the given file(s).
|
||||
Delete(file ...string) error
|
||||
// Exists determines if a file exists.
|
||||
Exists(file string) bool
|
||||
// Files gets all the files from the given directory.
|
||||
Files(path string) ([]string, error)
|
||||
// Get gets the contents of a file.
|
||||
Get(file string) ([]byte, error)
|
||||
// LastModified gets the file's last modified time.
|
||||
LastModified(file string) (time.Time, error)
|
||||
// MimeType gets the file's mime type.
|
||||
MimeType(file string) (string, error)
|
||||
// Missing determines if a file is missing.
|
||||
Missing(file string) bool
|
||||
// Move a file to a new location.
|
||||
Move(oldFile, newFile string) error
|
||||
// Path gets the full path for the file.
|
||||
Path(file string) string
|
||||
// Put writes the contents of a file.
|
||||
Put(file, content string) error
|
||||
// Size gets the file size of a given file.
|
||||
Size(file string) (int64, error)
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/studio-b12/gowebdav"
|
||||
)
|
||||
|
||||
type WebDavConfig struct {
|
||||
URL string // WebDAV 服务器 URL
|
||||
Username string // 用户名
|
||||
Password string // 密码
|
||||
BasePath string // 基础路径
|
||||
Timeout time.Duration // 连接超时时间
|
||||
}
|
||||
|
||||
type WebDav struct {
|
||||
client *gowebdav.Client
|
||||
config WebDavConfig
|
||||
}
|
||||
|
||||
func NewWebDav(config WebDavConfig) (Storage, error) {
|
||||
if config.Timeout == 0 {
|
||||
config.Timeout = 30 * time.Second
|
||||
}
|
||||
config.BasePath = strings.Trim(config.BasePath, "/")
|
||||
|
||||
client := gowebdav.NewClient(config.URL, config.Username, config.Password)
|
||||
client.SetTimeout(config.Timeout)
|
||||
|
||||
w := &WebDav{
|
||||
client: client,
|
||||
config: config,
|
||||
}
|
||||
|
||||
if err := w.ensureBasePath(); err != nil {
|
||||
return nil, fmt.Errorf("failed to ensure base path: %w", err)
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// ensureBasePath 确保基础路径存在
|
||||
func (w *WebDav) ensureBasePath() error {
|
||||
if w.config.BasePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return w.client.MkdirAll(w.config.BasePath, 0755)
|
||||
}
|
||||
|
||||
// getRemotePath 获取远程路径
|
||||
func (w *WebDav) getRemotePath(path string) string {
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
if w.config.BasePath == "" {
|
||||
return path
|
||||
}
|
||||
if path == "" {
|
||||
return w.config.BasePath
|
||||
}
|
||||
return filepath.Join(w.config.BasePath, path)
|
||||
}
|
||||
|
||||
// MakeDirectory 创建目录
|
||||
func (w *WebDav) MakeDirectory(directory string) error {
|
||||
remotePath := w.getRemotePath(directory)
|
||||
return w.client.MkdirAll(remotePath, 0755)
|
||||
}
|
||||
|
||||
// DeleteDirectory 删除目录
|
||||
func (w *WebDav) DeleteDirectory(directory string) error {
|
||||
remotePath := w.getRemotePath(directory)
|
||||
return w.client.RemoveAll(remotePath)
|
||||
}
|
||||
|
||||
// Copy 复制文件到新位置
|
||||
func (w *WebDav) Copy(oldFile, newFile string) error {
|
||||
oldPath := w.getRemotePath(oldFile)
|
||||
newPath := w.getRemotePath(newFile)
|
||||
|
||||
// 确保目标目录存在
|
||||
newDir := filepath.Dir(newPath)
|
||||
if newDir != "." {
|
||||
_ = w.client.MkdirAll(newDir, 0755)
|
||||
}
|
||||
|
||||
return w.client.Copy(oldPath, newPath, false)
|
||||
}
|
||||
|
||||
// Delete 删除文件
|
||||
func (w *WebDav) Delete(files ...string) error {
|
||||
for _, file := range files {
|
||||
remotePath := w.getRemotePath(file)
|
||||
if err := w.client.Remove(remotePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists 检查文件是否存在
|
||||
func (w *WebDav) Exists(file string) bool {
|
||||
remotePath := w.getRemotePath(file)
|
||||
_, err := w.client.Stat(remotePath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Files 获取目录下的所有文件
|
||||
func (w *WebDav) Files(path string) ([]string, error) {
|
||||
remotePath := w.getRemotePath(path)
|
||||
entries, err := w.client.ReadDir(remotePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var files []string
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
files = append(files, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Get 读取文件内容
|
||||
func (w *WebDav) Get(file string) ([]byte, error) {
|
||||
remotePath := w.getRemotePath(file)
|
||||
reader, err := w.client.ReadStream(remotePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = reader.Close() }()
|
||||
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
|
||||
// LastModified 获取文件最后修改时间
|
||||
func (w *WebDav) LastModified(file string) (time.Time, error) {
|
||||
remotePath := w.getRemotePath(file)
|
||||
stat, err := w.client.Stat(remotePath)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return stat.ModTime(), nil
|
||||
}
|
||||
|
||||
// MimeType 获取文件的 MIME 类型
|
||||
func (w *WebDav) MimeType(file string) (string, error) {
|
||||
ext := filepath.Ext(file)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
if mimeType == "" {
|
||||
return "application/octet-stream", nil
|
||||
}
|
||||
return mimeType, nil
|
||||
}
|
||||
|
||||
// Missing 检查文件是否不存在
|
||||
func (w *WebDav) Missing(file string) bool {
|
||||
return !w.Exists(file)
|
||||
}
|
||||
|
||||
// Move 移动文件到新位置
|
||||
func (w *WebDav) Move(oldFile, newFile string) error {
|
||||
oldPath := w.getRemotePath(oldFile)
|
||||
newPath := w.getRemotePath(newFile)
|
||||
|
||||
// 确保目标目录存在
|
||||
newDir := filepath.Dir(newPath)
|
||||
if newDir != "." {
|
||||
_ = w.client.MkdirAll(newDir, 0755)
|
||||
}
|
||||
|
||||
return w.client.Rename(oldPath, newPath, false)
|
||||
}
|
||||
|
||||
// Path 获取文件的完整路径
|
||||
func (w *WebDav) Path(file string) string {
|
||||
remotePath := w.getRemotePath(file)
|
||||
return fmt.Sprintf("%s/%s", strings.TrimSuffix(w.config.URL, "/"), remotePath)
|
||||
}
|
||||
|
||||
// Put 写入文件内容
|
||||
func (w *WebDav) Put(file, content string) error {
|
||||
remotePath := w.getRemotePath(file)
|
||||
|
||||
// 确保目录存在
|
||||
remoteDir := filepath.Dir(remotePath)
|
||||
if remoteDir != "." {
|
||||
_ = w.client.MkdirAll(remoteDir, 0755)
|
||||
}
|
||||
|
||||
return w.client.WriteStream(remotePath, bytes.NewReader([]byte(content)), 0644)
|
||||
}
|
||||
|
||||
// Size 获取文件大小
|
||||
func (w *WebDav) Size(file string) (int64, error) {
|
||||
remotePath := w.getRemotePath(file)
|
||||
stat, err := w.client.Stat(remotePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return stat.Size(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user