- commit
- 9d325fb
- parent
- 59eee85
- author
- Antonio Mika
- date
- 2025-03-13 13:37:55 -0400 EDT
Merge pull request #190 from picosh/am/ssh-server Started work on a crypto/ssh based ssh server
+1,
-0
1@@ -9,3 +9,4 @@ build/*
2 data/*
3 !data/.gitkeep
4 Dockerfile
5+ssh_data
+3,
-3
1@@ -54,8 +54,8 @@ ARG APP=prose
2
3 COPY --from=builder-web /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
4 COPY --from=builder-web /go/bin/${APP}-web ./web
5-COPY --from=builder-web /app/${APP}/html ./${APP}/html
6-COPY --from=builder-web /app/${APP}/public ./${APP}/public
7+COPY --from=builder-web /app/pkg/apps/${APP}/html ./${APP}/html
8+COPY --from=builder-web /app/pkg/apps/${APP}/public ./${APP}/public
9
10 ENTRYPOINT ["/app/web"]
11
12@@ -69,7 +69,7 @@ ARG APP=prose
13 COPY --from=builder-ssh /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
14 COPY --from=builder-ssh /go/bin/${APP}-ssh ./ssh
15 # some services require the html folder
16-COPY --from=builder-ssh /app/${APP}/html ./${APP}/html
17+COPY --from=builder-ssh /app/pkg/apps/${APP}/html ./${APP}/html
18
19
20 ENTRYPOINT ["/app/ssh"]
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/auth"
5+import "github.com/picosh/pico/pkg/apps/auth"
6
7 func main() {
8 auth.StartApiServer()
+2,
-2
1@@ -4,8 +4,8 @@ import (
2 "fmt"
3
4 "github.com/mmcdole/gofeed"
5- "github.com/picosh/pico/db/postgres"
6- "github.com/picosh/pico/feeds"
7+ "github.com/picosh/pico/pkg/apps/feeds"
8+ "github.com/picosh/pico/pkg/db/postgres"
9 )
10
11 func main() {
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/feeds"
5+import "github.com/picosh/pico/pkg/apps/feeds"
6
7 func main() {
8 feeds.StartSshServer()
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/feeds"
5+import "github.com/picosh/pico/pkg/apps/feeds"
6
7 func main() {
8 feeds.StartApiServer()
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/pastes"
5+import "github.com/picosh/pico/pkg/apps/pastes"
6
7 func main() {
8 pastes.StartSshServer()
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/pastes"
5+import "github.com/picosh/pico/pkg/apps/pastes"
6
7 func main() {
8 pastes.StartApiServer()
+4,
-4
1@@ -1,10 +1,10 @@
2 package main
3
4 import (
5- "github.com/picosh/pico/pgs"
6- pgsdb "github.com/picosh/pico/pgs/db"
7- "github.com/picosh/pico/shared"
8- "github.com/picosh/pico/shared/storage"
9+ "github.com/picosh/pico/pkg/apps/pgs"
10+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
11+ "github.com/picosh/pico/pkg/shared"
12+ "github.com/picosh/pico/pkg/shared/storage"
13 "github.com/picosh/utils"
14 )
15
+4,
-4
1@@ -1,10 +1,10 @@
2 package main
3
4 import (
5- "github.com/picosh/pico/pgs"
6- pgsdb "github.com/picosh/pico/pgs/db"
7- "github.com/picosh/pico/shared"
8- "github.com/picosh/pico/shared/storage"
9+ "github.com/picosh/pico/pkg/apps/pgs"
10+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
11+ "github.com/picosh/pico/pkg/shared"
12+ "github.com/picosh/pico/pkg/shared/storage"
13 "github.com/picosh/utils"
14 )
15
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/pico"
5+import "github.com/picosh/pico/pkg/apps/pico"
6
7 func main() {
8 pico.StartSshServer()
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/pipe"
5+import "github.com/picosh/pico/pkg/apps/pipe"
6
7 func main() {
8 pipe.StartSshServer()
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/pipe"
5+import "github.com/picosh/pico/pkg/apps/pipe"
6
7 func main() {
8 pipe.StartApiServer()
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/prose"
5+import "github.com/picosh/pico/pkg/apps/prose"
6
7 func main() {
8 prose.StartSshServer()
+1,
-1
1@@ -1,6 +1,6 @@
2 package main
3
4-import "github.com/picosh/pico/prose"
5+import "github.com/picosh/pico/pkg/apps/prose"
6
7 func main() {
8 prose.StartApiServer()
+2,
-2
1@@ -4,8 +4,8 @@ import (
2 "log/slog"
3 "os"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7+ "github.com/picosh/pico/pkg/db"
8+ "github.com/picosh/pico/pkg/db/postgres"
9 "github.com/picosh/utils"
10 )
11
+2,
-2
1@@ -5,8 +5,8 @@ import (
2 "log/slog"
3 "os"
4
5- "github.com/picosh/pico/db/postgres"
6- "github.com/picosh/pico/shared"
7+ "github.com/picosh/pico/pkg/db/postgres"
8+ "github.com/picosh/pico/pkg/shared"
9 )
10
11 func main() {
1@@ -5,11 +5,11 @@ import (
2 "os"
3 "strings"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/pgs"
7- pgsdb "github.com/picosh/pico/pgs/db"
8- "github.com/picosh/pico/shared"
9- "github.com/picosh/pico/shared/storage"
10+ "github.com/picosh/pico/pkg/apps/pgs"
11+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
12+ "github.com/picosh/pico/pkg/db"
13+ "github.com/picosh/pico/pkg/shared"
14+ "github.com/picosh/pico/pkg/shared/storage"
15 "github.com/picosh/utils"
16 )
17
+3,
-3
1@@ -7,9 +7,9 @@ import (
2 "os"
3 "time"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/db/postgres"
10+ "github.com/picosh/pico/pkg/shared"
11 )
12
13 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
+2,
-2
1@@ -5,8 +5,8 @@ import (
2 "log/slog"
3 "os"
4
5- "github.com/picosh/pico/db/postgres"
6- "github.com/picosh/pico/shared"
7+ "github.com/picosh/pico/pkg/db/postgres"
8+ "github.com/picosh/pico/pkg/shared"
9 )
10
11 func bail(err error) {
+3,
-3
1@@ -7,9 +7,9 @@ import (
2 "log/slog"
3 "os"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/db/postgres"
10+ "github.com/picosh/pico/pkg/shared"
11 )
12
13 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
+1,
-1
1@@ -4,7 +4,7 @@ import (
2 "log/slog"
3 "os"
4
5- "github.com/picosh/pico/db/postgres"
6+ "github.com/picosh/pico/pkg/db/postgres"
7 )
8
9 func main() {
1@@ -7,13 +7,13 @@ import (
2 "path/filepath"
3 "time"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7- "github.com/picosh/pico/prose"
8- "github.com/picosh/pico/shared"
9- "github.com/picosh/pico/shared/storage"
10- sst "github.com/picosh/pobj/storage"
11- sendUtils "github.com/picosh/send/utils"
12+ "github.com/picosh/pico/pkg/apps/prose"
13+ "github.com/picosh/pico/pkg/db"
14+ "github.com/picosh/pico/pkg/db/postgres"
15+ sst "github.com/picosh/pico/pkg/pobj/storage"
16+ sendUtils "github.com/picosh/pico/pkg/send/utils"
17+ "github.com/picosh/pico/pkg/shared"
18+ "github.com/picosh/pico/pkg/shared/storage"
19 )
20
21 func bail(err error) {
1@@ -6,9 +6,9 @@ import (
2
3 "github.com/minio/minio-go/v7"
4 "github.com/minio/minio-go/v7/pkg/credentials"
5- "github.com/picosh/pico/db/postgres"
6- "github.com/picosh/pico/prose"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/apps/prose"
9+ "github.com/picosh/pico/pkg/db/postgres"
10+ "github.com/picosh/pico/pkg/shared"
11 )
12
13 func bail(err error) {
+2,
-2
1@@ -4,8 +4,8 @@ import (
2 "log/slog"
3 "os"
4
5- "github.com/picosh/pico/db/postgres"
6- "github.com/picosh/pico/shared"
7+ "github.com/picosh/pico/pkg/db/postgres"
8+ "github.com/picosh/pico/pkg/shared"
9 "github.com/picosh/utils"
10 )
11
1@@ -5,9 +5,9 @@ import (
2 "log/slog"
3 "os"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/db/postgres"
10+ "github.com/picosh/pico/pkg/shared"
11 )
12
13 func findPosts(dbpool *sql.DB) ([]*db.Post, error) {
+0,
-114
1@@ -1,114 +0,0 @@
2-package feeds
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "os/signal"
9- "syscall"
10- "time"
11-
12- "github.com/charmbracelet/promwish"
13- "github.com/charmbracelet/ssh"
14- "github.com/charmbracelet/wish"
15- "github.com/picosh/pico/db/postgres"
16- "github.com/picosh/pico/filehandlers"
17- "github.com/picosh/pico/shared"
18- wsh "github.com/picosh/pico/wish"
19- "github.com/picosh/send/auth"
20- "github.com/picosh/send/list"
21- "github.com/picosh/send/pipe"
22- wishrsync "github.com/picosh/send/protocols/rsync"
23- "github.com/picosh/send/protocols/scp"
24- "github.com/picosh/send/protocols/sftp"
25- "github.com/picosh/send/proxy"
26- "github.com/picosh/utils"
27-)
28-
29-func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
30- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
31- return []wish.Middleware{
32- pipe.Middleware(handler, ".txt"),
33- list.Middleware(handler),
34- scp.Middleware(handler),
35- wishrsync.Middleware(handler),
36- auth.Middleware(handler),
37- wsh.PtyMdw(wsh.DeprecatedNotice()),
38- WishMiddleware(handler.DBPool, handler.Cfg),
39- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool),
40- }
41- }
42-}
43-
44-func withProxy(handler *filehandlers.FileHandlerRouter, otherMiddleware ...wish.Middleware) ssh.Option {
45- return func(server *ssh.Server) error {
46- err := sftp.SSHOption(handler)(server)
47- if err != nil {
48- return err
49- }
50-
51- newSubsystemHandlers := map[string]ssh.SubsystemHandler{}
52-
53- for name, subsystemHandler := range server.SubsystemHandlers {
54- newSubsystemHandlers[name] = func(s ssh.Session) {
55- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool)(ssh.Handler(subsystemHandler))(s)
56- }
57- }
58-
59- server.SubsystemHandlers = newSubsystemHandlers
60-
61- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
62- }
63-}
64-
65-func StartSshServer() {
66- host := utils.GetEnv("LISTS_HOST", "0.0.0.0")
67- port := utils.GetEnv("LISTS_SSH_PORT", "2222")
68- promPort := utils.GetEnv("LISTS_PROM_PORT", "9222")
69- cfg := NewConfigSite("feeds-ssh")
70- logger := cfg.Logger
71- dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
72- defer dbh.Close()
73-
74- hooks := &FeedHooks{
75- Cfg: cfg,
76- Db: dbh,
77- }
78-
79- fileMap := map[string]filehandlers.ReadWriteHandler{
80- "fallback": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
81- }
82- handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
83-
84- sshAuth := shared.NewSshAuthHandler(dbh, logger)
85- s, err := wish.NewServer(
86- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
87- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
88- wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
89- withProxy(
90- handler,
91- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "feeds-ssh"),
92- ),
93- )
94- if err != nil {
95- logger.Error(err.Error())
96- return
97- }
98-
99- done := make(chan os.Signal, 1)
100- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
101- logger.Info("Starting SSH server", "host", host, "port", port)
102- go func() {
103- if err = s.ListenAndServe(); err != nil {
104- logger.Error(err.Error())
105- }
106- }()
107-
108- <-done
109- logger.Info("Stopping SSH server")
110- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
111- defer func() { cancel() }()
112- if err := s.Shutdown(ctx); err != nil {
113- logger.Error(err.Error())
114- }
115-}
M
go.mod
+126,
-158
1@@ -4,23 +4,32 @@ go 1.24
2
3 toolchain go1.24.0
4
5+// replace github.com/picosh/tunkit => ../tunkit
6+
7 // replace github.com/picosh/send => ../send
8-// replace git.sr.ht/~rockorager/vaxis => ../../../src/vaxis
9+
10+// replace github.com/picosh/go-rsync-receiver => ../go-rsync-receiver
11+
12+// replace github.com/picosh/pobj => ../pobj
13+
14+// replace github.com/picosh/pubsub => ../pubsub
15+
16+// replace github.com/picosh/utils => ../utils
17+
18+// replace git.sr.ht/~delthas/senpai => ../../senpai
19+
20+// replace git.sr.ht/~rockorager/vaxis => ../../vaxis
21
22 require (
23 git.sr.ht/~delthas/senpai v0.3.1-0.20250311003540-18f699aaf9b0
24 git.sr.ht/~rockorager/vaxis v0.12.1-0.20250312161844-81636f76af83
25- github.com/alecthomas/chroma/v2 v2.14.0
26+ github.com/alecthomas/chroma/v2 v2.15.0
27 github.com/antoniomika/syncmap v1.0.0
28 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
29- github.com/charmbracelet/lipgloss v1.0.0
30- github.com/charmbracelet/promwish v0.7.0
31- github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef
32- github.com/charmbracelet/wish v1.4.6
33 github.com/containerd/console v1.0.4
34 github.com/darkweak/souin v1.7.5
35 github.com/darkweak/souin/plugins/souin/storages v1.7.5
36- github.com/darkweak/storages/core v0.0.11
37+ github.com/darkweak/storages/core v0.0.13
38 github.com/gkampitakis/go-snaps v0.5.7
39 github.com/google/go-cmp v0.7.0
40 github.com/google/uuid v1.6.0
41@@ -28,116 +37,87 @@ require (
42 github.com/gorilla/websocket v1.5.3
43 github.com/jmoiron/sqlx v1.4.0
44 github.com/lib/pq v1.10.9
45+ github.com/matryer/is v1.4.1
46 github.com/microcosm-cc/bluemonday v1.0.27
47- github.com/minio/minio-go/v7 v7.0.87
48+ github.com/minio/madmin-go/v3 v3.0.97
49+ github.com/minio/minio-go/v7 v7.0.88
50 github.com/mmcdole/gofeed v1.3.0
51- github.com/muesli/termenv v0.16.0
52 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577
53- github.com/picosh/pobj v0.0.0-20250304201248-a9c7179aa49b
54+ github.com/picosh/go-rsync-receiver v0.0.0-20250304201040-fcc11dd22d79
55 github.com/picosh/pubsub v0.0.0-20241114191831-ec8f16c0eb88
56- github.com/picosh/send v0.0.0-20250304201154-e36cd3bbbb35
57- github.com/picosh/tunkit v0.0.0-20240905223921-532404cef9d9
58 github.com/picosh/utils v0.0.0-20241120033529-8ca070c09bf4
59- github.com/pkg/sftp v1.13.7
60- github.com/prometheus/client_golang v1.21.0-rc.0
61+ github.com/pkg/sftp v1.13.8
62+ github.com/prometheus/client_golang v1.21.1
63 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
64 github.com/sendgrid/sendgrid-go v3.16.0+incompatible
65 github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d
66- github.com/x-way/crawlerdetect v0.2.24
67+ github.com/x-way/crawlerdetect v0.2.28
68 github.com/yuin/goldmark v1.7.8
69 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
70 github.com/yuin/goldmark-meta v1.1.0
71- go.abhg.dev/goldmark/anchor v0.1.1
72+ go.abhg.dev/goldmark/anchor v0.2.0
73 go.abhg.dev/goldmark/hashtag v0.3.1
74- go.abhg.dev/goldmark/toc v0.10.0
75+ go.abhg.dev/goldmark/toc v0.11.0
76 golang.org/x/crypto v0.36.0
77 google.golang.org/protobuf v1.36.5
78 gopkg.in/yaml.v2 v2.4.0
79 )
80
81 require (
82+ cel.dev/expr v0.22.0 // indirect
83 codeberg.org/emersion/go-scfg v0.1.0 // indirect
84- dario.cat/mergo v1.0.0 // indirect
85+ dario.cat/mergo v1.0.1 // indirect
86 filippo.io/edwards25519 v1.1.0 // indirect
87 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
88 github.com/Masterminds/goutils v1.1.1 // indirect
89- github.com/Masterminds/semver/v3 v3.2.0 // indirect
90- github.com/Masterminds/sprig/v3 v3.2.3 // indirect
91- github.com/Microsoft/go-winio v0.6.1 // indirect
92- github.com/PuerkitoBio/goquery v1.10.0 // indirect
93- github.com/RoaringBitmap/roaring v1.2.3 // indirect
94- github.com/andybalholm/cascadia v1.3.2 // indirect
95- github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
96- github.com/antlabs/stl v0.0.1 // indirect
97- github.com/antlabs/timer v0.0.11 // indirect
98- github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
99+ github.com/Masterminds/semver/v3 v3.3.1 // indirect
100+ github.com/Masterminds/sprig/v3 v3.3.0 // indirect
101+ github.com/Microsoft/go-winio v0.6.2 // indirect
102+ github.com/PuerkitoBio/goquery v1.10.2 // indirect
103+ github.com/RoaringBitmap/roaring v1.9.4 // indirect
104+ github.com/andybalholm/cascadia v1.3.3 // indirect
105+ github.com/antlabs/stl v0.0.2 // indirect
106+ github.com/antlabs/timer v0.1.4 // indirect
107+ github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
108 github.com/armon/go-metrics v0.4.1 // indirect
109 github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
110- github.com/aws/aws-sdk-go-v2 v1.36.2 // indirect
111- github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
112 github.com/aws/aws-sdk-go-v2/config v1.29.7 // indirect
113- github.com/aws/aws-sdk-go-v2/credentials v1.17.60 // indirect
114- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 // indirect
115- github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.63 // indirect
116- github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 // indirect
117- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 // indirect
118- github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
119- github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.33 // indirect
120- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
121- github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.1 // indirect
122- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 // indirect
123- github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.14 // indirect
124- github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1 // indirect
125- github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 // indirect
126- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 // indirect
127- github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 // indirect
128 github.com/aws/smithy-go v1.22.3 // indirect
129- github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
130 github.com/aymerick/douceur v0.2.0 // indirect
131 github.com/beorn7/perks v1.0.1 // indirect
132- github.com/bits-and-blooms/bitset v1.5.0 // indirect
133+ github.com/bits-and-blooms/bitset v1.22.0 // indirect
134 github.com/buraksezer/consistent v0.10.0 // indirect
135- github.com/buraksezer/olric v0.5.6 // indirect
136+ github.com/buraksezer/olric v0.5.7 // indirect
137 github.com/bwmarrin/snowflake v0.3.0 // indirect
138- github.com/caddyserver/caddy/v2 v2.8.4 // indirect
139- github.com/caddyserver/certmagic v0.21.3 // indirect
140+ github.com/caddyserver/caddy/v2 v2.9.1 // indirect
141+ github.com/caddyserver/certmagic v0.22.0 // indirect
142 github.com/caddyserver/zerossl v0.1.3 // indirect
143+ github.com/ccoveille/go-safecast v1.6.0 // indirect
144 github.com/cespare/xxhash v1.1.0 // indirect
145 github.com/cespare/xxhash/v2 v2.3.0 // indirect
146- github.com/charmbracelet/bubbletea v1.3.4 // indirect
147- github.com/charmbracelet/keygen v0.5.1 // indirect
148- github.com/charmbracelet/log v0.4.0 // indirect
149- github.com/charmbracelet/x/ansi v0.8.0 // indirect
150- github.com/charmbracelet/x/conpty v0.1.0 // indirect
151- github.com/charmbracelet/x/errors v0.0.0-20250226164017-59292a315e58 // indirect
152- github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b // indirect
153- github.com/charmbracelet/x/input v0.3.1 // indirect
154- github.com/charmbracelet/x/term v0.2.1 // indirect
155- github.com/charmbracelet/x/termios v0.1.1 // indirect
156- github.com/charmbracelet/x/windows v0.2.0 // indirect
157 github.com/chzyer/readline v1.5.1 // indirect
158+ github.com/coreos/go-oidc/v3 v3.12.0 // indirect
159 github.com/coreos/go-semver v0.3.1 // indirect
160 github.com/coreos/go-systemd/v22 v22.5.0 // indirect
161- github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
162- github.com/creack/pty v1.1.24 // indirect
163- github.com/darkweak/go-esi v0.0.5 // indirect
164- github.com/darkweak/storages/badger v0.0.8 // indirect
165- github.com/darkweak/storages/etcd v0.0.8 // indirect
166- github.com/darkweak/storages/nats v0.0.8 // indirect
167- github.com/darkweak/storages/nuts v0.0.8 // indirect
168- github.com/darkweak/storages/olric v0.0.8 // indirect
169- github.com/darkweak/storages/otter v0.0.11 // indirect
170- github.com/darkweak/storages/redis v0.0.8 // indirect
171+ github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
172+ github.com/darkweak/go-esi v0.0.6 // indirect
173+ github.com/darkweak/storages/badger v0.0.13 // indirect
174+ github.com/darkweak/storages/etcd v0.0.13 // indirect
175+ github.com/darkweak/storages/nats v0.0.13 // indirect
176+ github.com/darkweak/storages/nuts v0.0.13 // indirect
177+ github.com/darkweak/storages/olric v0.0.13 // indirect
178+ github.com/darkweak/storages/otter v0.0.13 // indirect
179+ github.com/darkweak/storages/redis v0.0.13 // indirect
180 github.com/delthas/go-libnp v0.0.0-20250105150050-96674b98150e // indirect
181 github.com/delthas/go-localeinfo v0.0.0-20240813094314-e5413e186769 // indirect
182 github.com/dgraph-io/badger v1.6.2 // indirect
183 github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
184 github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
185- github.com/dgraph-io/ristretto v0.1.1 // indirect
186- github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
187+ github.com/dgraph-io/ristretto v0.2.0 // indirect
188+ github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
189 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
190 github.com/disintegration/imaging v1.6.2 // indirect
191- github.com/dlclark/regexp2 v1.11.4 // indirect
192+ github.com/dlclark/regexp2 v1.11.5 // indirect
193 github.com/dolthub/maphash v0.1.0 // indirect
194 github.com/dsoprea/go-exif v0.0.0-20230826092837-6579e82b732d // indirect
195 github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d // indirect
196@@ -147,79 +127,69 @@ require (
197 github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d // indirect
198 github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 // indirect
199 github.com/dustin/go-humanize v1.0.1 // indirect
200- github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
201- github.com/forPelevin/gomoji v1.2.0 // indirect
202- github.com/gammazero/deque v0.2.1 // indirect
203+ github.com/forPelevin/gomoji v1.3.0 // indirect
204+ github.com/francoispqt/gojay v1.2.13 // indirect
205+ github.com/gammazero/deque v1.0.0 // indirect
206 github.com/gkampitakis/ciinfo v0.3.0 // indirect
207 github.com/gkampitakis/go-diff v1.3.2 // indirect
208 github.com/go-errors/errors v1.5.1 // indirect
209 github.com/go-ini/ini v1.67.0 // indirect
210- github.com/go-jose/go-jose/v3 v3.0.3 // indirect
211- github.com/go-kit/kit v0.13.0 // indirect
212- github.com/go-kit/log v0.2.1 // indirect
213- github.com/go-logfmt/logfmt v0.6.0 // indirect
214+ github.com/go-jose/go-jose/v3 v3.0.4 // indirect
215+ github.com/go-jose/go-jose/v4 v4.0.5 // indirect
216 github.com/go-ole/go-ole v1.3.0 // indirect
217 github.com/go-redis/redis/v8 v8.11.5 // indirect
218- github.com/go-sql-driver/mysql v1.8.1 // indirect
219- github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
220- github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
221+ github.com/go-sql-driver/mysql v1.9.0 // indirect
222+ github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
223+ github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
224 github.com/goccy/go-json v0.10.5 // indirect
225 github.com/godbus/dbus/v5 v5.1.0 // indirect
226- github.com/gofrs/flock v0.8.1 // indirect
227+ github.com/gofrs/flock v0.12.1 // indirect
228 github.com/gogo/protobuf v1.3.2 // indirect
229 github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
230 github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
231- github.com/golang/glog v1.2.3 // indirect
232- github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
233+ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
234 github.com/golang/protobuf v1.5.4 // indirect
235- github.com/golang/snappy v0.0.4 // indirect
236- github.com/google/btree v1.1.2 // indirect
237- github.com/google/cel-go v0.20.1 // indirect
238- github.com/google/flatbuffers v23.1.21+incompatible // indirect
239- github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
240+ github.com/golang/snappy v1.0.0 // indirect
241+ github.com/google/btree v1.1.3 // indirect
242+ github.com/google/cel-go v0.24.1 // indirect
243+ github.com/google/flatbuffers v25.2.10+incompatible // indirect
244+ github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 // indirect
245 github.com/gorilla/css v1.0.1 // indirect
246 github.com/hashicorp/errwrap v1.1.0 // indirect
247 github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
248- github.com/hashicorp/go-msgpack v0.5.5 // indirect
249+ github.com/hashicorp/go-metrics v0.5.4 // indirect
250+ github.com/hashicorp/go-msgpack/v2 v2.1.3 // indirect
251 github.com/hashicorp/go-multierror v1.1.1 // indirect
252- github.com/hashicorp/go-sockaddr v1.0.2 // indirect
253- github.com/hashicorp/golang-lru v0.6.0 // indirect
254+ github.com/hashicorp/go-sockaddr v1.0.7 // indirect
255+ github.com/hashicorp/golang-lru v1.0.2 // indirect
256 github.com/hashicorp/logutils v1.0.0 // indirect
257- github.com/hashicorp/memberlist v0.5.0 // indirect
258- github.com/huandu/xstrings v1.3.3 // indirect
259- github.com/imdario/mergo v0.3.16 // indirect
260+ github.com/hashicorp/memberlist v0.5.3 // indirect
261+ github.com/huandu/xstrings v1.5.0 // indirect
262 github.com/inconshreveable/mousetrap v1.1.0 // indirect
263- github.com/jackc/chunkreader/v2 v2.0.1 // indirect
264- github.com/jackc/pgconn v1.14.3 // indirect
265- github.com/jackc/pgio v1.0.0 // indirect
266 github.com/jackc/pgpassfile v1.0.0 // indirect
267- github.com/jackc/pgproto3/v2 v2.3.3 // indirect
268- github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
269- github.com/jackc/pgtype v1.14.0 // indirect
270- github.com/jackc/pgx/v4 v4.18.3 // indirect
271+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
272+ github.com/jackc/pgx/v5 v5.7.2 // indirect
273+ github.com/jackc/puddle/v2 v2.2.2 // indirect
274 github.com/json-iterator/go v1.1.12 // indirect
275 github.com/klauspost/compress v1.18.0 // indirect
276 github.com/klauspost/cpuid/v2 v2.2.10 // indirect
277 github.com/kr/fs v0.1.0 // indirect
278 github.com/kr/pretty v0.3.1 // indirect
279 github.com/kr/text v0.2.0 // indirect
280- github.com/libdns/libdns v0.2.2 // indirect
281- github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
282- github.com/lufia/plan9stats v0.0.0-20250224150550-a661cff19cfb // indirect
283+ github.com/libdns/libdns v0.2.3 // indirect
284+ github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect
285 github.com/manifoldco/promptui v0.9.0 // indirect
286 github.com/maruel/natural v1.1.1 // indirect
287- github.com/mattn/go-colorable v0.1.13 // indirect
288+ github.com/mattn/go-colorable v0.1.14 // indirect
289 github.com/mattn/go-isatty v0.0.20 // indirect
290- github.com/mattn/go-localereader v0.0.1 // indirect
291 github.com/mattn/go-runewidth v0.0.16 // indirect
292 github.com/mattn/go-sixel v0.0.5 // indirect
293 github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
294- github.com/maypok86/otter v1.2.1 // indirect
295+ github.com/maypok86/otter v1.2.4 // indirect
296 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
297- github.com/mholt/acmez/v2 v2.0.1 // indirect
298+ github.com/mholt/acmez/v3 v3.1.0 // indirect
299 github.com/miekg/dns v1.1.63 // indirect
300 github.com/minio/crc64nvme v1.0.1 // indirect
301- github.com/minio/madmin-go/v3 v3.0.94 // indirect
302 github.com/minio/md5-simd v1.1.2 // indirect
303 github.com/mitchellh/copystructure v1.2.0 // indirect
304 github.com/mitchellh/go-ps v1.0.0 // indirect
305@@ -229,18 +199,15 @@ require (
306 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
307 github.com/modern-go/reflect2 v1.0.2 // indirect
308 github.com/mschoch/smat v0.2.0 // indirect
309- github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
310- github.com/muesli/cancelreader v0.2.2 // indirect
311 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
312- github.com/nats-io/nats.go v1.36.0 // indirect
313- github.com/nats-io/nkeys v0.4.7 // indirect
314+ github.com/nats-io/nats.go v1.39.1 // indirect
315+ github.com/nats-io/nkeys v0.4.10 // indirect
316 github.com/nats-io/nuid v1.0.1 // indirect
317 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e // indirect
318 github.com/nutsdb/nutsdb v1.0.4 // indirect
319- github.com/onsi/ginkgo/v2 v2.15.0 // indirect
320+ github.com/onsi/ginkgo/v2 v2.23.0 // indirect
321 github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
322- github.com/picosh/go-rsync-receiver v0.0.0-20250304201040-fcc11dd22d79 // indirect
323- github.com/pierrec/lz4/v4 v4.1.21 // indirect
324+ github.com/pierrec/lz4/v4 v4.1.22 // indirect
325 github.com/pkg/errors v0.9.1 // indirect
326 github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
327 github.com/pquerna/cachecontrol v0.2.0 // indirect
328@@ -249,9 +216,9 @@ require (
329 github.com/prometheus/procfs v0.15.1 // indirect
330 github.com/prometheus/prom2json v1.4.1 // indirect
331 github.com/prometheus/prometheus v0.302.1 // indirect
332- github.com/quic-go/qpack v0.4.0 // indirect
333- github.com/quic-go/quic-go v0.44.0 // indirect
334- github.com/redis/rueidis v1.0.39 // indirect
335+ github.com/quic-go/qpack v0.5.1 // indirect
336+ github.com/quic-go/quic-go v0.50.0 // indirect
337+ github.com/redis/rueidis v1.0.55 // indirect
338 github.com/rivo/uniseg v0.4.7 // indirect
339 github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a // indirect
340 github.com/rs/xid v1.6.0 // indirect
341@@ -262,65 +229,66 @@ require (
342 github.com/sendgrid/rest v2.6.9+incompatible // indirect
343 github.com/shirou/gopsutil/v3 v3.24.5 // indirect
344 github.com/shoenig/go-m1cpu v0.1.6 // indirect
345- github.com/shopspring/decimal v1.2.0 // indirect
346+ github.com/shopspring/decimal v1.4.0 // indirect
347 github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
348- github.com/slackhq/nebula v1.6.1 // indirect
349- github.com/smallstep/certificates v0.26.1 // indirect
350- github.com/smallstep/nosql v0.6.1 // indirect
351- github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
352- github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
353+ github.com/slackhq/nebula v1.9.5 // indirect
354+ github.com/smallstep/certificates v0.28.2 // indirect
355+ github.com/smallstep/cli-utils v0.12.1 // indirect
356+ github.com/smallstep/linkedca v0.23.0 // indirect
357+ github.com/smallstep/nosql v0.7.0 // indirect
358+ github.com/smallstep/pkcs7 v0.2.1 // indirect
359+ github.com/smallstep/scep v0.0.0-20250221100424-171a5fa4fb1b // indirect
360 github.com/smallstep/truststore v0.13.0 // indirect
361 github.com/soniakeys/quant v1.0.0 // indirect
362- github.com/spf13/cast v1.4.1 // indirect
363- github.com/spf13/cobra v1.8.0 // indirect
364- github.com/spf13/pflag v1.0.5 // indirect
365- github.com/stoewer/go-strcase v1.2.0 // indirect
366- github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect
367- github.com/tidwall/btree v1.6.0 // indirect
368+ github.com/spf13/cast v1.7.1 // indirect
369+ github.com/spf13/cobra v1.9.1 // indirect
370+ github.com/spf13/pflag v1.0.6 // indirect
371+ github.com/stoewer/go-strcase v1.3.0 // indirect
372+ github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect
373+ github.com/tidwall/btree v1.7.0 // indirect
374 github.com/tidwall/gjson v1.17.0 // indirect
375 github.com/tidwall/match v1.1.1 // indirect
376 github.com/tidwall/pretty v1.2.1 // indirect
377 github.com/tidwall/redcon v1.6.2 // indirect
378 github.com/tidwall/sjson v1.2.5 // indirect
379 github.com/tinylib/msgp v1.2.5 // indirect
380- github.com/tklauser/go-sysconf v0.3.14 // indirect
381- github.com/tklauser/numcpus v0.9.0 // indirect
382- github.com/urfave/cli v1.22.14 // indirect
383- github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
384+ github.com/tklauser/go-sysconf v0.3.15 // indirect
385+ github.com/tklauser/numcpus v0.10.0 // indirect
386+ github.com/urfave/cli v1.22.16 // indirect
387+ github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
388 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
389- github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
390 github.com/xujiajun/mmap-go v1.0.1 // indirect
391 github.com/xujiajun/utils v0.0.0-20220904132955-5f7c5b914235 // indirect
392 github.com/yusufpapurcu/wmi v1.2.4 // indirect
393- github.com/zeebo/blake3 v0.2.3 // indirect
394- go.etcd.io/bbolt v1.3.9 // indirect
395- go.etcd.io/etcd/api/v3 v3.5.14 // indirect
396- go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
397- go.etcd.io/etcd/client/v3 v3.5.14 // indirect
398+ github.com/zeebo/blake3 v0.2.4 // indirect
399+ go.etcd.io/bbolt v1.4.0 // indirect
400+ go.etcd.io/etcd/api/v3 v3.5.19 // indirect
401+ go.etcd.io/etcd/client/pkg/v3 v3.5.19 // indirect
402+ go.etcd.io/etcd/client/v3 v3.5.19 // indirect
403 go.opencensus.io v0.24.0 // indirect
404- go.step.sm/cli-utils v0.9.0 // indirect
405- go.step.sm/crypto v0.45.0 // indirect
406- go.step.sm/linkedca v0.20.1 // indirect
407+ go.step.sm/crypto v0.59.1 // indirect
408 go.uber.org/automaxprocs v1.6.0 // indirect
409- go.uber.org/mock v0.4.0 // indirect
410+ go.uber.org/mock v0.5.0 // indirect
411 go.uber.org/multierr v1.11.0 // indirect
412 go.uber.org/zap v1.27.0 // indirect
413- go.uber.org/zap/exp v0.2.0 // indirect
414- golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect
415- golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
416+ go.uber.org/zap/exp v0.3.0 // indirect
417+ golang.org/x/crypto/x509roots/fallback v0.0.0-20250312005926-b369b723c8ad // indirect
418+ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
419 golang.org/x/image v0.25.0 // indirect
420- golang.org/x/mod v0.23.0 // indirect
421+ golang.org/x/mod v0.24.0 // indirect
422 golang.org/x/net v0.37.0 // indirect
423+ golang.org/x/oauth2 v0.28.0 // indirect
424 golang.org/x/sync v0.12.0 // indirect
425 golang.org/x/sys v0.31.0 // indirect
426 golang.org/x/term v0.30.0 // indirect
427 golang.org/x/text v0.23.0 // indirect
428 golang.org/x/time v0.11.0 // indirect
429- golang.org/x/tools v0.30.0 // indirect
430- google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
431- google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
432- google.golang.org/grpc v1.70.0 // indirect
433+ golang.org/x/tools v0.31.0 // indirect
434+ google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf // indirect
435+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect
436+ google.golang.org/grpc v1.71.0 // indirect
437+ google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
438 gopkg.in/yaml.v3 v3.0.1 // indirect
439- howett.net/plist v1.0.0 // indirect
440+ howett.net/plist v1.0.1 // indirect
441 mvdan.cc/xurls/v2 v2.6.0 // indirect
442 )
M
go.sum
+422,
-446
1@@ -1,56 +1,64 @@
2+cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8=
3+cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
4 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
5-cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
6-cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM=
7-cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A=
8+cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
9+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
10+cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
11+cloud.google.com/go v0.118.2 h1:bKXO7RXMFDkniAAvvuMrAPtQ/VHrs9e7J5UT3yrGdTY=
12+cloud.google.com/go v0.118.2/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M=
13+cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
14+cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
15 cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
16 cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
17 cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
18 cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
19-cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
20-cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
21-cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
22-cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
23-cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
24-cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
25+cloud.google.com/go/iam v1.4.0 h1:ZNfy/TYfn2uh/ukvhp783WhnbVluqf/tzOaqVUPlIPA=
26+cloud.google.com/go/iam v1.4.0/go.mod h1:gMBgqPaERlriaOV0CUl//XUzDhSfXevn4OEUbg6VRs4=
27+cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI=
28+cloud.google.com/go/kms v1.21.0/go.mod h1:zoFXMhVVK7lQ3JC9xmhHMoQhnjEDZFoLAr5YMwzBLtk=
29+cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg=
30+cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs=
31 codeberg.org/emersion/go-scfg v0.1.0 h1:6dnGU0ZI4gX+O5rMjwhoaySItzHG710eXL5TIQKl+uM=
32 codeberg.org/emersion/go-scfg v0.1.0/go.mod h1:0nooW1ufBB4SlJEdTtiVN9Or+bnNM1icOkQ6Tbrq6O0=
33-dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
34-dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
35+dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
36+dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
37+dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
38+dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
39+dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
40+dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
41 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
42 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
43+git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
44 git.sr.ht/~delthas/senpai v0.3.1-0.20250311003540-18f699aaf9b0 h1:Knm2mHQwLsh1svD15lE27Cr6BMV2wH2t0OKUoSCNhuY=
45 git.sr.ht/~delthas/senpai v0.3.1-0.20250311003540-18f699aaf9b0/go.mod h1:RzVz1R7QRHGcRDnJTcr7AN/cD3rj9scdgvupkXTJLYk=
46-git.sr.ht/~rockorager/vaxis v0.12.1-0.20250309233058-d6d466f8f9b1 h1:o8opVUAysn+cQAnSadL1BVMhr2YcdjynRzPBz4fa9q0=
47-git.sr.ht/~rockorager/vaxis v0.12.1-0.20250309233058-d6d466f8f9b1/go.mod h1:RSNtZnMeIwpyQzgIEYo9EHJb8Wcl/RhFSxypLpD/ajg=
48 git.sr.ht/~rockorager/vaxis v0.12.1-0.20250312161844-81636f76af83 h1:9eVqJxJzMdnpfqfKKjvEvNDpVg6sIBvbI4FdTjhHqx8=
49 git.sr.ht/~rockorager/vaxis v0.12.1-0.20250312161844-81636f76af83/go.mod h1:h94aKek3frIV1hJbdXjqnBqaLkbWXvV+UxAsQHg9bns=
50 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
51 github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
52 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
53-github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
54+github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
55 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
56 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
57 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
58-github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
59-github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
60-github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
61-github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
62-github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
63+github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
64+github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
65+github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
66+github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
67 github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
68-github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
69-github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
70+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
71+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
72 github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
73 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
74-github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
75-github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
76+github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
77+github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
78 github.com/RoaringBitmap/roaring v1.2.1/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
79-github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
80-github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
81-github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
82-github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
83+github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
84+github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
85+github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
86+github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
87 github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
88-github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
89-github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
90+github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
91+github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
92 github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
93 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
94 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
95@@ -58,16 +66,16 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
96 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
97 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
98 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
99-github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
100-github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
101-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
102-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
103-github.com/antlabs/stl v0.0.1 h1:TRD3csCrjREeLhLoQ/supaoCvFhNLBTNIwuRGrDIs6Q=
104-github.com/antlabs/stl v0.0.1/go.mod h1:wvVwP1loadLG3cRjxUxK8RL4Co5xujGaZlhbztmUEqQ=
105-github.com/antlabs/timer v0.0.11 h1:z75oGFLeTqJHMOcWzUPBKsBbQAz4Ske3AfqJ7bsdcwU=
106-github.com/antlabs/timer v0.0.11/go.mod h1:JNV8J3yGvMKhCavGXgj9HXrVZkfdQyKCcqXBT8RdyuU=
107-github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
108-github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
109+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
110+github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
111+github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
112+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
113+github.com/antlabs/stl v0.0.2 h1:sna1AXR5yIkNE9lWhCcKbheFJSVfCa3vugnGyakI79s=
114+github.com/antlabs/stl v0.0.2/go.mod h1:kKrO4xrn9cfS1mJVo+/BqePZjAYMXqD0amGF2Ouq7ac=
115+github.com/antlabs/timer v0.1.4 h1:MHdE00MDnNfhJCmqSOdLXs35uGNwfkMwfbynxrGmQ1c=
116+github.com/antlabs/timer v0.1.4/go.mod h1:mpw4zlD5KVjstEyUDp43DGLWsY076Mdo4bS78NTseRE=
117+github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
118+github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
119 github.com/antoniomika/syncmap v1.0.0 h1:iFSfbQFQOvHZILFZF+hqWosO0no+W9+uF4y2VEyMKWU=
120 github.com/antoniomika/syncmap v1.0.0/go.mod h1:fK2829foEYnO4riNfyUn0SHQZt4ue3DStYjGU+sJj38=
121 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
122@@ -81,36 +89,24 @@ github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o
123 github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
124 github.com/aws/aws-sdk-go-v2 v1.36.2 h1:Ub6I4lq/71+tPb/atswvToaLGVMxKZvjYDVOWEExOcU=
125 github.com/aws/aws-sdk-go-v2 v1.36.2/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
126-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
127-github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
128 github.com/aws/aws-sdk-go-v2/config v1.29.7 h1:71nqi6gUbAUiEQkypHQcNVSFJVUFANpSeUNShiwWX2M=
129 github.com/aws/aws-sdk-go-v2/config v1.29.7/go.mod h1:yqJQ3nh2HWw/uxd56bicyvmDW4KSc+4wN6lL8pYjynU=
130 github.com/aws/aws-sdk-go-v2/credentials v1.17.60 h1:1dq+ELaT5ogfmqtV1eocq8SpOK1NRsuUfmhQtD/XAh4=
131 github.com/aws/aws-sdk-go-v2/credentials v1.17.60/go.mod h1:HDes+fn/xo9VeszXqjBVkxOo/aUy8Mc6QqKvZk32GlE=
132 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 h1:JO8pydejFKmGcUNiiwt75dzLHRWthkwApIvPoyUtXEg=
133 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29/go.mod h1:adxZ9i9DRmB8zAT0pO0yGnsmu0geomp5a3uq5XpgOJ8=
134-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.63 h1:cTR4L7zlqh2YJjOWF62sMCyJWhm9ItUN3h/eOKh0xlU=
135-github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.63/go.mod h1:ryx0BXDm9YKRus5qaDeKcMh+XiEQ5uok/mJHkuGg4to=
136 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 h1:knLyPMw3r3JsU8MFHWctE4/e2qWbPaxDYLlohPvnY8c=
137 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33/go.mod h1:EBp2HQ3f+XCB+5J+IoEbGhoV7CpJbnrsd4asNXmTL0A=
138 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 h1:K0+Ne08zqti8J9jwENxZ5NoUyBnaFDTu3apwQJWrwwA=
139 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33/go.mod h1:K97stwwzaWzmqxO8yLGHhClbVW1tC6VT1pDLk1pGrq4=
140 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
141 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
142-github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.33 h1:/frG8aV09yhCVSOEC2pzktflJJO48NwY3xntHBwxHiA=
143-github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.33/go.mod h1:8vwASlAcV366M+qxZnjNzCjeastk1Rt1bpSRaGZanGU=
144 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
145 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
146-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.1 h1:7SuukGpyIgF5EiAbf1dZRxP+xSnY1WjiHBjL08fjJeE=
147-github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.1/go.mod h1:k+Vce/8R28tSozjdWphkrNhK8zLmdS9RgiDNZl6p8Rw=
148 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 h1:2scbY6//jy/s8+5vGrk7l1+UtHl0h9A4MjOO2k/TM2E=
149 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14/go.mod h1:bRpZPHZpSe5YRHmPfK3h1M7UBFCn2szHzyx0rw04zro=
150-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.14 h1:fgdkfsxTehqPcIQa24G/Omwv9RocTq2UcONNX/OnrZI=
151-github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.14/go.mod h1:wMxQ3OE8fiM8z2YRAeb2J8DLTTWMvRyYYuQOs26AbTQ=
152-github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0=
153-github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ=
154-github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1 h1:5bI9tJL2Z0FGFtp/LPDv0eyliFBHCn7LAhqpQuL+7kk=
155-github.com/aws/aws-sdk-go-v2/service/s3 v1.77.1/go.mod h1:njj3tSJONkfdLt4y6X8pyqeM6sJLNZxmzctKKV+n1GM=
156+github.com/aws/aws-sdk-go-v2/service/kms v1.37.18 h1:pi9M/9n1PLayBXjia7LfwgXwcpFdFO7Q2cqKOZa1ZmM=
157+github.com/aws/aws-sdk-go-v2/service/kms v1.37.18/go.mod h1:vZXvmzfhdsPj/axc8+qk/2fSCP4hGyaZ1MAduWEHAxM=
158 github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 h1:YV6xIKDJp6U7YB2bxfud9IENO1LRpGhe2Tv/OKtPrOQ=
159 github.com/aws/aws-sdk-go-v2/service/sso v1.24.16/go.mod h1:DvbmMKgtpA6OihFJK13gHMZOZrCHttz8wPHGKXqU+3o=
160 github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 h1:kMyK3aKotq1aTBsj1eS8ERJLjqYRRRcsmP33ozlCvlk=
161@@ -119,10 +115,6 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 h1:ht1jVmeeo2anR7zDiYJLSnRYnO/
162 github.com/aws/aws-sdk-go-v2/service/sts v1.33.15/go.mod h1:xWZ5cOiFe3czngChE4LhCBqUxNwgfwndEF7XlYP/yD8=
163 github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
164 github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
165-github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
166-github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
167-github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
168-github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
169 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
170 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
171 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
172@@ -131,20 +123,25 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
173 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
174 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
175 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
176-github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8=
177-github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
178+github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
179+github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
180+github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
181+github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
182+github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
183 github.com/buraksezer/consistent v0.10.0 h1:hqBgz1PvNLC5rkWcEBVAL9dFMBWz6I0VgUCW25rrZlU=
184 github.com/buraksezer/consistent v0.10.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw=
185-github.com/buraksezer/olric v0.5.6 h1:fYvf+jMZ20igrxmnw+QdY3dKPQb3+6WUONCKkUEgnU8=
186-github.com/buraksezer/olric v0.5.6/go.mod h1:ndjlnRvJfFrE8eJlQNBJsDJa11tIsb5BXSfPmTi7qjE=
187+github.com/buraksezer/olric v0.5.7 h1:K8ypVViiPkXiqBz3UyDAY99cHvvofAR65fmH7ElPEWE=
188+github.com/buraksezer/olric v0.5.7/go.mod h1:S1R+9Zt7P9TCbvQZvY/RYuRehLLRPDfbJNkukQsLJ4k=
189 github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
190 github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
191-github.com/caddyserver/caddy/v2 v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=
192-github.com/caddyserver/caddy/v2 v2.8.4/go.mod h1:vmDAHp3d05JIvuhc24LmnxVlsZmWnUwbP5WMjzcMPWw=
193-github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
194-github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
195+github.com/caddyserver/caddy/v2 v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=
196+github.com/caddyserver/caddy/v2 v2.9.1/go.mod h1:ImUELya2el1FDVp3ahnSO2iH1or1aHxlQEQxd/spP68=
197+github.com/caddyserver/certmagic v0.22.0 h1:hi2skv2jouUw9uQUEyYSTTmqPZPHgf61dOANSIVCLOw=
198+github.com/caddyserver/certmagic v0.22.0/go.mod h1:Vc0msarAPhOagbDc/SU6M2zbzdwVuZ0lkTh2EqtH4vs=
199 github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
200 github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
201+github.com/ccoveille/go-safecast v1.6.0 h1:kxc0VIsdEaYoKZbDiGBZBV62zAp0RdtFNH6E3Krev8s=
202+github.com/ccoveille/go-safecast v1.6.0/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
203 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
204 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
205 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
206@@ -152,36 +149,6 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
207 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
208 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
209 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
210-github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
211-github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
212-github.com/charmbracelet/keygen v0.5.1 h1:zBkkYPtmKDVTw+cwUyY6ZwGDhRxXkEp0Oxs9sqMLqxI=
213-github.com/charmbracelet/keygen v0.5.1/go.mod h1:zznJVmK/GWB6dAtjluqn2qsttiCBhA5MZSiwb80fcHw=
214-github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
215-github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
216-github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
217-github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
218-github.com/charmbracelet/promwish v0.7.0 h1:oaMH+ey6W4DDIv1xucS8jL1ik/Q46qxjNXlh6XxEm+s=
219-github.com/charmbracelet/promwish v0.7.0/go.mod h1:WbRJN9irg8LmsBU8G2rFF8md9O3rSg63qrnqquP/+cs=
220-github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef h1:dNZwn4is5svUd+sQEGsrXtp7VwD2ipYaCkKMzcpAEIE=
221-github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef/go.mod h1:hg+I6gvlMl16nS9ZzQNgBIrrCasGwEw0QiLsDcP01Ko=
222-github.com/charmbracelet/wish v1.4.6 h1:27WRqMTUmyFoZASoaAaEe78Je7LTU4VqyoBxnl4d9XA=
223-github.com/charmbracelet/wish v1.4.6/go.mod h1:RRy2LFW3WQ3tlPmMMGgEeSMDVlFd5yqklGBVZWQSHmk=
224-github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
225-github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
226-github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
227-github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
228-github.com/charmbracelet/x/errors v0.0.0-20250226164017-59292a315e58 h1:UWrrJJrdFfi7Y5XNKjz8/1RtZzGbshaFEZzlI7CgJ7M=
229-github.com/charmbracelet/x/errors v0.0.0-20250226164017-59292a315e58/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
230-github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
231-github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
232-github.com/charmbracelet/x/input v0.3.1 h1:TE4s3fTRj+OUpJ86dKphrN99+NgBnto//EkWncMJQIg=
233-github.com/charmbracelet/x/input v0.3.1/go.mod h1:4w9jS/NW62WrHSdmjbpzydvnbqkd+mtyK8WOWbHCdvs=
234-github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
235-github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
236-github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
237-github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
238-github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw=
239-github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s=
240 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
241 github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
242 github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
243@@ -195,49 +162,45 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
244 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
245 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
246 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
247-github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
248-github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
249 github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
250 github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
251 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
252 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
253+github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
254+github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
255 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
256 github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
257 github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
258-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
259-github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
260+github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
261 github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
262 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
263 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
264-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
265-github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
266-github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
267-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
268+github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
269+github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
270+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
271 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
272-github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
273-github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
274-github.com/darkweak/go-esi v0.0.5 h1:b9LHI8Tz46R+i6p8avKPHAIBRQUCZDebNmKm5w/Zrns=
275-github.com/darkweak/go-esi v0.0.5/go.mod h1:koCJqwum1u6mslyZuq/Phm6hfG1K3ZK5Y7jrUBTH654=
276+github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8=
277+github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk=
278 github.com/darkweak/souin v1.7.5 h1:drNhZc0GhSbGcugiGfcYdLDTcx3DCZW6o13wwRj5o5Y=
279 github.com/darkweak/souin v1.7.5/go.mod h1:PcP+hhvYOdqn4OmeScKKvit0TihYVYS1o154mhfWT/s=
280 github.com/darkweak/souin/plugins/souin/storages v1.7.5 h1:KlHnwecr1WVNdWJ2OfhBsY6OujWvzkomowjSoBAJgxY=
281 github.com/darkweak/souin/plugins/souin/storages v1.7.5/go.mod h1:ok3cIKHdIWjMtM6EgC8fBwNJL9hWG3deyTwN+M0zA4g=
282-github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k=
283-github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o=
284-github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew=
285-github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY=
286-github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik=
287-github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY=
288-github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg=
289-github.com/darkweak/storages/nats v0.0.8/go.mod h1:ap1RYc9aQHYylUDVKh2G2KF2GTzxoB+oFk3tZ9lxJLE=
290-github.com/darkweak/storages/nuts v0.0.8 h1:obxlGOyvOGZw6TGTAMVrYqbH46z5kuHaF2zjbumTNUk=
291-github.com/darkweak/storages/nuts v0.0.8/go.mod h1:1vGrcWTRLMXamfubwyEfVf/AJA4L7DjYzezBko5ODPM=
292-github.com/darkweak/storages/olric v0.0.8 h1:QSRIBb8IyBlt/wxh5DkGiSsaeE6SiRtJZeJy3dNf4nU=
293-github.com/darkweak/storages/olric v0.0.8/go.mod h1:Qk7FK28K/ogIdneaZzoSLmTGfNlCxvvN7yhkEqjh/TE=
294-github.com/darkweak/storages/otter v0.0.11 h1:j4uOAycRMZVVKZhrqxCJoOVezfPv3pHB5YQ6iX9aKQo=
295-github.com/darkweak/storages/otter v0.0.11/go.mod h1:SI70zPux7Q//J+cFq3KOgRthBENXusziKK7ztV9vpDo=
296-github.com/darkweak/storages/redis v0.0.8 h1:0CHLkImyaI/sYs+IOurYLAxFkrmz5dFblhfpF7oGhQc=
297-github.com/darkweak/storages/redis v0.0.8/go.mod h1:pypJ5T3hweQWfHzFUjmZWeb1KaNK3ikNg1+rn0G+rD0=
298+github.com/darkweak/storages/badger v0.0.13 h1:p54l0LGJUM33DaVWAc59B7RMqpWdVFOJLF1oOSioz7w=
299+github.com/darkweak/storages/badger v0.0.13/go.mod h1:JegE7I+ThF+OMORiSX6od9Hgmf6r/g64EiOswFtqNNg=
300+github.com/darkweak/storages/core v0.0.13 h1:T2nA306M9eXBEpIlSf8czCzXrJ4MAS62fJhOV7ijI9U=
301+github.com/darkweak/storages/core v0.0.13/go.mod h1:3qJqrenCLpu+0bWPOAq36CmGpzL3SGWAz6KGZGnur1U=
302+github.com/darkweak/storages/etcd v0.0.13 h1:oO5XfoOrSSvzM4X2vacFHCv2CNQvG9h4XVTizLcpGAU=
303+github.com/darkweak/storages/etcd v0.0.13/go.mod h1:CfZVDHanzCNVCL3ZW52LPTIxnDoqQKXTRXGGOJTsmPw=
304+github.com/darkweak/storages/nats v0.0.13 h1:WTKclCfKsgzGuvx4yodcyEryGDj8nMMXbwj7k6IwX7k=
305+github.com/darkweak/storages/nats v0.0.13/go.mod h1:awloCHr4IGJrQMolc8d9kIkwUhgCpo9hYb23aTicDms=
306+github.com/darkweak/storages/nuts v0.0.13 h1:skhTxSXcAfOBDJAk6KbIZyY9g+HqlmWPwGup7nPEL5c=
307+github.com/darkweak/storages/nuts v0.0.13/go.mod h1:lj62qLEtxwMnrR+JhFSJGfShbBZMBRZ7d870Ia36AqU=
308+github.com/darkweak/storages/olric v0.0.13 h1:rmCsisM1nNl248whoAP+f4qP73e8wREBsn2r6RbQ37Y=
309+github.com/darkweak/storages/olric v0.0.13/go.mod h1:disvKQFUbpJ/TMl7w6Fb08Ulhzil0Mr4QKVNi4K99HI=
310+github.com/darkweak/storages/otter v0.0.13 h1:sH2wukx9W5gnH/NR9R+NJAAP7yhQ0lHmwtYizPm8jfw=
311+github.com/darkweak/storages/otter v0.0.13/go.mod h1:heF8STOMydF+u7QwE1guEfNheMhHCsjv+3LJFRj+HGs=
312+github.com/darkweak/storages/redis v0.0.13 h1:AI0El75BaNDY+tVtsqu67VUV9gSTJSckB4HbyLdASfc=
313+github.com/darkweak/storages/redis v0.0.13/go.mod h1:87/MeVNG/u0a0k8cwAQcobyoeKeNjKgS7PMg7uXNPbE=
314 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
315 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
316 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
317@@ -254,19 +217,20 @@ github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0
318 github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
319 github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
320 github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
321-github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
322 github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
323+github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
324+github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
325 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
326-github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
327-github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
328+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
329+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
330 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
331 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
332 github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
333 github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
334 github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
335 github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
336-github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
337-github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
338+github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
339+github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
340 github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
341 github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
342 github.com/dsoprea/go-exif v0.0.0-20230826092837-6579e82b732d h1:ygcRCGNKuEiA98k7X35hknEN8RIRUF1jrz7k1rZCvsk=
343@@ -300,25 +264,30 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
344 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
345 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
346 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
347-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
348-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
349 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
350 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
351 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
352-github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8=
353-github.com/forPelevin/gomoji v1.2.0/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+gL9NAwMXg=
354+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
355+github.com/forPelevin/gomoji v1.3.0 h1:WPIOLWB1bvRYlKZnSSEevLt3IfKlLs+tK+YA9fFYlkE=
356+github.com/forPelevin/gomoji v1.3.0/go.mod h1:mM6GtmCgpoQP2usDArc6GjbXrti5+FffolyQfGgPboQ=
357+github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
358+github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
359+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
360+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
361 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
362 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
363 github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
364 github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
365-github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
366-github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
367+github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34=
368+github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo=
369+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
370 github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8=
371 github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
372 github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
373 github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
374 github.com/gkampitakis/go-snaps v0.5.7 h1:uVGjHR4t4pPHU944udMx7VKHpwepZXmvDMF+yDmI0rg=
375 github.com/gkampitakis/go-snaps v0.5.7/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
376+github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
377 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
378 github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
379 github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
380@@ -326,22 +295,16 @@ github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8b
381 github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
382 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
383 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
384-github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
385-github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
386-github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
387+github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
388+github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
389+github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
390+github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
391 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
392 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
393-github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
394-github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
395 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
396-github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
397-github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
398 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
399 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
400 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
401-github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
402-github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
403-github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
404 github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
405 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
406 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
407@@ -351,25 +314,23 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
408 github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
409 github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
410 github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
411-github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
412 github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
413-github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
414+github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
415+github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
416 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
417 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
418-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
419-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
420+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
421+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
422 github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
423-github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U=
424-github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
425+github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
426+github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
427 github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
428 github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
429 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
430 github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
431 github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
432-github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
433-github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
434-github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
435-github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
436+github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
437+github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
438 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
439 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
440 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
441@@ -380,13 +341,13 @@ github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgR
442 github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
443 github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
444 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
445-github.com/golang/glog v1.2.3 h1:oDTdz9f5VGVVNGu/Q7UXKWYsD0873HXLHdJUNBsSEKM=
446-github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
447 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
448 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
449-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
450-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
451+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
452+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
453+github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
454 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
455+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
456 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
457 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
458 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
459@@ -403,18 +364,18 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
460 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
461 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
462 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
463-github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
464-github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
465+github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
466+github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
467 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
468-github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
469-github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
470-github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
471-github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
472+github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
473+github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
474+github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
475+github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
476 github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
477 github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
478 github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
479-github.com/google/flatbuffers v23.1.21+incompatible h1:bUqzx/MXCDxuS0hRJL2EfjyZL3uQrPbMocUa8zGqsTA=
480-github.com/google/flatbuffers v23.1.21+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
481+github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
482+github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
483 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
484 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
485 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
486@@ -424,35 +385,44 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
487 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
488 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
489 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
490+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
491 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
492 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
493-github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
494-github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
495-github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
496-github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
497+github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
498+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
499+github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
500+github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
501+github.com/google/go-tpm-tools v0.4.5 h1:3fhthtyMDbIZFR5/0y1hvUoZ1Kf4i1eZ7C73R4Pvd+k=
502+github.com/google/go-tpm-tools v0.4.5/go.mod h1:ktjTNq8yZFD6TzdBFefUfen96rF3NpYwpSb2d8bc+Y8=
503 github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
504 github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
505 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
506+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
507+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
508 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
509-github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
510-github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
511-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
512+github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
513+github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
514 github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
515 github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
516-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
517 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
518 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
519 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
520 github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
521 github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
522+github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
523+github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
524+github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
525 github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
526 github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
527+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
528 github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
529 github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
530 github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
531 github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
532 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
533 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
534+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
535+github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
536 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
537 github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
538 github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
539@@ -460,104 +430,72 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
540 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
541 github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
542 github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
543+github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
544+github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
545 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
546-github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
547-github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
548+github.com/hashicorp/go-msgpack/v2 v2.1.3 h1:cB1w4Zrk0O3jQBTcFMKqYQWRFfsSQ/TYKNyUUVyCP2c=
549+github.com/hashicorp/go-msgpack/v2 v2.1.3/go.mod h1:SjlwKKFnwBXvxD/I1bEcfJIBbEJ+MCUn39TxymNR5ZU=
550 github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
551 github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
552 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
553 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
554 github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
555-github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
556 github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
557-github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
558+github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
559+github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
560 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
561+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
562+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
563 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
564-github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
565-github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
566+github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
567+github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
568 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
569 github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
570 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
571-github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
572 github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
573+github.com/hashicorp/memberlist v0.5.3 h1:tQ1jOCypD0WvMemw/ZhhtH+PWpzcftQvgCorLu0hndk=
574+github.com/hashicorp/memberlist v0.5.3/go.mod h1:h60o12SZn/ua/j0B6iKAZezA4eDaGsIuPO70eOaJ6WE=
575 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
576 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
577 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
578-github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
579-github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
580+github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
581+github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
582 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
583-github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
584-github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
585-github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
586 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
587 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
588 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
589-github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
590-github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
591-github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
592-github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
593-github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
594-github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
595-github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
596-github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
597-github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
598-github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
599-github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
600-github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
601-github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
602-github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
603-github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
604-github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
605-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
606-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
607 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
608 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
609-github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
610-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
611-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
612-github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
613-github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
614-github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
615-github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
616-github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
617-github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
618-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
619-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
620-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
621-github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
622-github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
623-github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
624-github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
625-github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
626-github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
627-github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
628-github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
629-github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
630-github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
631-github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
632-github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
633-github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
634-github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
635-github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
636+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
637+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
638+github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
639+github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
640+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
641+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
642+github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
643 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
644 github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
645 github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
646+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
647 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
648 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
649+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
650+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
651 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
652 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
653+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
654 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
655+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
656 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
657 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
658 github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
659 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
660 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
661 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
662-github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
663 github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
664 github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
665 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
666-github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
667+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
668 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
669 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
670 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
671@@ -566,25 +504,21 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
672 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
673 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
674 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
675-github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
676+github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
677 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
678 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
679 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
680 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
681 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
682-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
683-github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
684-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
685-github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
686 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
687 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
688-github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
689-github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
690-github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
691-github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
692-github.com/lufia/plan9stats v0.0.0-20250224150550-a661cff19cfb h1:YU0XAr3+rMpM8fP80KEesn32Qa9qkbquokvuwzWyYuA=
693-github.com/lufia/plan9stats v0.0.0-20250224150550-a661cff19cfb/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
694+github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
695+github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
696+github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844=
697+github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
698+github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
699 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
700+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
701 github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
702 github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
703 github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
704@@ -592,19 +526,11 @@ github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRH
705 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
706 github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
707 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
708-github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
709-github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
710-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
711-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
712+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
713+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
714 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
715-github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
716-github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
717-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
718-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
719 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
720 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
721-github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
722-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
723 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
724 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
725 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
726@@ -615,12 +541,13 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU
727 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
728 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
729 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
730-github.com/maypok86/otter v1.2.1 h1:xyvMW+t0vE1sKt/++GTkznLitEl7D/msqXkAbLwiC1M=
731-github.com/maypok86/otter v1.2.1/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
732+github.com/maypok86/otter v1.2.4 h1:HhW1Pq6VdJkmWwcZZq19BlEQkHtI8xgsQzBVXJU0nfc=
733+github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
734 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
735 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
736-github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
737-github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
738+github.com/mholt/acmez/v3 v3.1.0 h1:RlOx2SSZ8dIAM5GfkMe8TdaxjjkiHTGorlMUt8GeMzg=
739+github.com/mholt/acmez/v3 v3.1.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
740+github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
741 github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
742 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
743 github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
744@@ -629,14 +556,13 @@ github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
745 github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
746 github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
747 github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
748-github.com/minio/madmin-go/v3 v3.0.94 h1:n2S8zgm0eRJ09YC7qrDTFDQDQOEUzCTiKEPywCitO/s=
749-github.com/minio/madmin-go/v3 v3.0.94/go.mod h1:pMLdj9OtN0CANNs5tdm6opvOlDFfj0WhbztboZAjRWE=
750+github.com/minio/madmin-go/v3 v3.0.97 h1:P7XO+ofPexkXy9akxG9Xoif1zIkbmWKeKtG3AquVzEY=
751+github.com/minio/madmin-go/v3 v3.0.97/go.mod h1:pMLdj9OtN0CANNs5tdm6opvOlDFfj0WhbztboZAjRWE=
752 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
753 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
754-github.com/minio/minio-go/v7 v7.0.87 h1:nkr9x0u53PespfxfUqxP3UYWiE2a41gaofgNnC4Y8WQ=
755-github.com/minio/minio-go/v7 v7.0.87/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg=
756+github.com/minio/minio-go/v7 v7.0.88 h1:v8MoIJjwYxOkehp+eiLIuvXk87P2raUtoU5klrAAshs=
757+github.com/minio/minio-go/v7 v7.0.88/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg=
758 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
759-github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
760 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
761 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
762 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
763@@ -644,7 +570,6 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
764 github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
765 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
766 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
767-github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
768 github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
769 github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
770 github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
771@@ -662,21 +587,18 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
772 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
773 github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
774 github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
775-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
776-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
777-github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
778-github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
779-github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
780-github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
781 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
782 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
783 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
784-github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
785-github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
786-github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
787-github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
788+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
789+github.com/nats-io/nats.go v1.39.1 h1:oTkfKBmz7W047vRxV762M67ZdXeOtUgvbBaNoQ+3PPk=
790+github.com/nats-io/nats.go v1.39.1/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM=
791+github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc=
792+github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U=
793 github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
794 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
795+github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
796+github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
797 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577 h1:hVmVNttSLNloGsbFKVXAUHonXTd8KKrv30U/8UkloKI=
798 github.com/neurosnap/go-exif-remove v0.0.0-20221010134343-50d1e3c35577/go.mod h1:G3Cu1AW+dmRLDFpOi8eUAfc3cGoRHUjTkGjeRcndgl4=
799 github.com/neurosnap/go-jpeg-image-structure v0.0.0-20221010133817-70b1c1ff679e h1:76Dng5ms0fR+26doKZAvNqhi2UPfnLxGfPIDEr+BBlM=
800@@ -692,14 +614,15 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
801 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
802 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
803 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
804-github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
805-github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
806+github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
807+github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
808 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
809 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
810 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
811 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
812-github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
813-github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
814+github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
815+github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
816+github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
817 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
818 github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
819 github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
820@@ -710,25 +633,19 @@ github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1Gsh
821 github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
822 github.com/picosh/go-rsync-receiver v0.0.0-20250304201040-fcc11dd22d79 h1:MyB9P43hlQ6A2FoP9LGeiTBL3WKToW4gcWd6lQPg/Zg=
823 github.com/picosh/go-rsync-receiver v0.0.0-20250304201040-fcc11dd22d79/go.mod h1:4ZICsr6bESoHP8He9DqROlZiMw4hHHjcbDzhtTTDQzA=
824-github.com/picosh/pobj v0.0.0-20250304201248-a9c7179aa49b h1:lOsbgawiv0DXWcQQzey4TOY3Y/WTwgNpyUa4kneiKwI=
825-github.com/picosh/pobj v0.0.0-20250304201248-a9c7179aa49b/go.mod h1:R6NE9t8BYGpU+n03geGNnZZC0REa99epFioDtnx6c0Y=
826 github.com/picosh/pubsub v0.0.0-20241114191831-ec8f16c0eb88 h1:hdxE6rquHHw1/eeqS1b+ojLaxGtN8zOiTUclPwaVbPg=
827 github.com/picosh/pubsub v0.0.0-20241114191831-ec8f16c0eb88/go.mod h1:+9hDKIDHQCvGFigCVlIl589BwpT9R4boKhUVc/OgRU4=
828-github.com/picosh/send v0.0.0-20250304201154-e36cd3bbbb35 h1:qC8Rqijwi0I4gC+zIEAyJr46J6MSb4FdgzbnbMhGMgY=
829-github.com/picosh/send v0.0.0-20250304201154-e36cd3bbbb35/go.mod h1:8330Tuhd4sBd7bDVxzcO47kORSq/aCHo+DEURecjj24=
830-github.com/picosh/tunkit v0.0.0-20240905223921-532404cef9d9 h1:g5oZmnDFr11HarA8IAXcc4o9PBlolSM59QIATCSoato=
831-github.com/picosh/tunkit v0.0.0-20240905223921-532404cef9d9/go.mod h1:UrDH/VCIc1wg/L6iY2zSYt4TiGw+25GsKSnkVkU40Dw=
832 github.com/picosh/utils v0.0.0-20241120033529-8ca070c09bf4 h1:pwbgY9shKyMlpYvpUalTyV0ZVd5paj8pSEYT4OPOYTk=
833 github.com/picosh/utils v0.0.0-20241120033529-8ca070c09bf4/go.mod h1:HogYEyJ43IGXrOa3D/kjM1pkzNAyh+pejRyv8Eo//pk=
834-github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
835-github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
836+github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
837+github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
838 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
839 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
840 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
841 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
842 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
843-github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
844-github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
845+github.com/pkg/sftp v1.13.8 h1:Xt7eJ/xqXv7s0VuzFw7JXhZj6Oc1zI6l4GK8KP9sFB0=
846+github.com/pkg/sftp v1.13.8/go.mod h1:DmvEkvKE2lshEeuo2JMp06yqcx9HVnR7e3zqQl42F3U=
847 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
848 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
849 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
850@@ -739,49 +656,54 @@ github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEn
851 github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
852 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
853 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
854+github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
855 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
856 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
857 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
858-github.com/prometheus/client_golang v1.21.0-rc.0 h1:bR+RxBlwcr4q8hXkgSOA/J18j6n0/qH0Gb0DH+8c+RY=
859-github.com/prometheus/client_golang v1.21.0-rc.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
860+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
861+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
862+github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
863+github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
864 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
865 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
866 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
867 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
868 github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
869 github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
870+github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
871 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
872 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
873+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
874+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
875 github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
876 github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
877+github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
878 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
879 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
880 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
881+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
882+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
883 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
884 github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
885 github.com/prometheus/prom2json v1.4.1 h1:7McxdrHgPEOtMwWjkKtd0v5AhpR2Q6QAnlHKVxq0+tQ=
886 github.com/prometheus/prom2json v1.4.1/go.mod h1:CzOQykSKFxXuC7ELUZHOHQvwKesQ3eN0p2PWLhFitQM=
887 github.com/prometheus/prometheus v0.302.1 h1:xqVdrwrB4WNpdgJqxsz5loqFWNUZitsK8myqLuSZ6Ag=
888 github.com/prometheus/prometheus v0.302.1/go.mod h1:YcyCoTbUR/TM8rY3Aoeqr0AWTu/pu1Ehh+trpX3eRzg=
889-github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
890-github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
891-github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
892-github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
893-github.com/redis/rueidis v1.0.39 h1:RNMbL7/tMkiVga/0ukbbFFslcPQckq4zs7c81mkIfTk=
894-github.com/redis/rueidis v1.0.39/go.mod h1:bnbkk4+CkXZgDPEbUtSos/o55i4RhFYYesJ4DS2zmq0=
895+github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
896+github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
897+github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
898+github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
899+github.com/redis/rueidis v1.0.55 h1:PrRv6eETcanBgYVNdwxn6RyUaPfxN6H+b5jUA4mfpkw=
900+github.com/redis/rueidis v1.0.55/go.mod h1:cr7ILwt1AqyMRfjWlA9Orubj6gp1xzn1DPyhmrhv/x0=
901 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
902 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
903 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
904 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
905-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
906 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
907 github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM=
908 github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
909-github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
910 github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
911 github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
912-github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
913-github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
914 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
915 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
916 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
917@@ -790,7 +712,6 @@ github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDj
918 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
919 github.com/safchain/ethtool v0.5.10 h1:Im294gZtuf4pSGJRAOGKaASNi3wMeFaGaWuSaomedpc=
920 github.com/safchain/ethtool v0.5.10/go.mod h1:w9jh2Lx7YBR4UwzLkzCmWl85UY0W2uZdd7/DckVE5+c=
921-github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
922 github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
923 github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
924 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
925@@ -802,66 +723,93 @@ github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekuei
926 github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
927 github.com/sendgrid/sendgrid-go v3.16.0+incompatible h1:i8eE6IMkiCy7vusSdacHHSBUpXyTcTXy/Rl9N9aZ/Qw=
928 github.com/sendgrid/sendgrid-go v3.16.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
929+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
930 github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
931 github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
932 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
933 github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
934 github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
935 github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
936-github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
937-github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
938-github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
939+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
940+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
941+github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
942+github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
943+github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
944+github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
945+github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
946+github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
947+github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
948+github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
949+github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
950+github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
951+github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
952+github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
953+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
954+github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
955+github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
956+github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
957+github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
958+github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
959+github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
960+github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
961 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
962 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
963+github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
964+github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
965 github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d h1:4FkGkGts6gLznca6fgclIvbupwbq543mb/fFkog4VIg=
966 github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d/go.mod h1:fTTj1EOmRdtuwYw3jF/1X2dTa0N1BdbZhrpA21N/S4I=
967 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
968-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
969 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
970+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
971 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
972 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
973 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
974-github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
975-github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
976+github.com/slackhq/nebula v1.9.5 h1:ZrxcvP/lxwFglaijmiwXLuCSkybZMJnqSYI1S8DtGnY=
977+github.com/slackhq/nebula v1.9.5/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
978 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
979 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
980-github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o=
981-github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis=
982+github.com/smallstep/certificates v0.28.2 h1:KN7Ytxatoa35CnuDKw/tX49SF+2f6ilB2vyMqIS1mtc=
983+github.com/smallstep/certificates v0.28.2/go.mod h1:UWVIqq5A3toV9rSzYUnimqRNeU6xCt3YWB2mTaCxm28=
984+github.com/smallstep/cli-utils v0.12.1 h1:D9QvfbFqiKq3snGZ2xDcXEFrdFJ1mQfPHZMq/leerpE=
985+github.com/smallstep/cli-utils v0.12.1/go.mod h1:skV2Neg8qjiKPu2fphM89H9bIxNpKiiRTnX9Q6Lc+20=
986 github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA=
987 github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
988-github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y=
989-github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y=
990-github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg=
991-github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y=
992-github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw=
993-github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
994+github.com/smallstep/linkedca v0.23.0 h1:5W/7EudlK1HcCIdZM68dJlZ7orqCCCyv6bm2l/0JmLU=
995+github.com/smallstep/linkedca v0.23.0/go.mod h1:7cyRM9soAYySg9ag65QwytcgGOM+4gOlkJ/YA58A9E8=
996+github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE=
997+github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU=
998+github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA=
999+github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0=
1000+github.com/smallstep/scep v0.0.0-20250221100424-171a5fa4fb1b h1:A1pVOceKLBY+QT10XcMFMWTaxct5zFjwIFMnDk13dhA=
1001+github.com/smallstep/scep v0.0.0-20250221100424-171a5fa4fb1b/go.mod h1:QQhwLqCS13nhv8L5ov7NgusowENUtXdEzdytjmJHdZQ=
1002 github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
1003 github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
1004 github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
1005 github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
1006+github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
1007+github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
1008 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
1009 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
1010 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
1011 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
1012 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
1013-github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
1014-github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
1015-github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
1016+github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
1017+github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
1018 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
1019-github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
1020-github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
1021+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
1022+github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
1023 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
1024 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
1025-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
1026-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1027+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
1028+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1029 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
1030-github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
1031-github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
1032+github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
1033+github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
1034 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1035 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1036-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
1037 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
1038 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
1039+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
1040 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
1041 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
1042 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
1043@@ -872,13 +820,15 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
1044 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
1045 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
1046 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
1047+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1048 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
1049 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1050-github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU=
1051-github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
1052+github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
1053+github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
1054+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
1055 github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
1056-github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
1057-github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
1058+github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
1059+github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
1060 github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
1061 github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
1062 github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
1063@@ -893,22 +843,23 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
1064 github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
1065 github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
1066 github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
1067-github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
1068-github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
1069-github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
1070-github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
1071+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
1072+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
1073+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
1074+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
1075 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
1076 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
1077-github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
1078-github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
1079-github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
1080+github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
1081+github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
1082+github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
1083+github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
1084 github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
1085+github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
1086+github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
1087 github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
1088 github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
1089-github.com/x-way/crawlerdetect v0.2.24 h1:kZDzSeiXB64M+Bknopn5GddHT+LBocD61jEjqDOufLE=
1090-github.com/x-way/crawlerdetect v0.2.24/go.mod h1:s6iUJZPq/WNBJThPRK+zk8ah7iIbGUZn9nYWMls3YP0=
1091-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
1092-github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
1093+github.com/x-way/crawlerdetect v0.2.28 h1:3G88EmiFv6+xXFGOv98s4GQaGzIp2Go2/03LVg+aIW8=
1094+github.com/x-way/crawlerdetect v0.2.28/go.mod h1:UNantmBXjcJBve2lS8YN2XlgHmR6MKY8abq/AyTJI9g=
1095 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
1096 github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc=
1097 github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg=
1098@@ -929,32 +880,32 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
1099 github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
1100 github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
1101 github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
1102-github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
1103-github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
1104+github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
1105+github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
1106 github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
1107 github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
1108-github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
1109-go.abhg.dev/goldmark/anchor v0.1.1 h1:NUH3hAzhfeymRqZKOkSoFReZlEAmfXBZlbXEzpD2Qgc=
1110-go.abhg.dev/goldmark/anchor v0.1.1/go.mod h1:zYKiaHXTdugwVJRZqInVdmNGQRM3ZRJ6AGBC7xP7its=
1111+go.abhg.dev/goldmark/anchor v0.2.0 h1:RQZTodRc6VHSUoQYKFlyH0pokbhk1klwUuGgDmjGp2E=
1112+go.abhg.dev/goldmark/anchor v0.2.0/go.mod h1:Ym74zBV+QBKxK9ITOty680N9FT8otgGYvtYXroJUWms=
1113 go.abhg.dev/goldmark/hashtag v0.3.1 h1:k0FQwEtVQ1SstIRR2fqDJ4VNYUS0AXLp869V0qHOZMg=
1114 go.abhg.dev/goldmark/hashtag v0.3.1/go.mod h1:rXtvxXPL7auhPMGRdG02UrXn/9LMm6PNdP5HO64zbVU=
1115-go.abhg.dev/goldmark/toc v0.10.0 h1:de3LrIimwtGhBMKh7aEl1c6n4XWwOdukIO5wOAMYZzg=
1116-go.abhg.dev/goldmark/toc v0.10.0/go.mod h1:OpH0qqRP9v/eosCV28ZeqGI78jZ8rri3C7Jh8fzEo2M=
1117-go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
1118-go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
1119-go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0=
1120-go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU=
1121-go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ=
1122-go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI=
1123-go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg=
1124-go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk=
1125+go.abhg.dev/goldmark/toc v0.11.0 h1:IRixVy3/yVPKvFBc37EeBPi8XLTXrtH6BYaonSjkF8o=
1126+go.abhg.dev/goldmark/toc v0.11.0/go.mod h1:XMFIoI1Sm6dwF9vKzVDOYE/g1o5BmKXghLG8q/wJNww=
1127+go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
1128+go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
1129+go.etcd.io/etcd/api/v3 v3.5.19 h1:w3L6sQZGsWPuBxRQ4m6pPP3bVUtV8rjW033EGwlr0jw=
1130+go.etcd.io/etcd/api/v3 v3.5.19/go.mod h1:QqKGViq4KTgOG43dr/uH0vmGWIaoJY3ggFi6ZH0TH/U=
1131+go.etcd.io/etcd/client/pkg/v3 v3.5.19 h1:9VsyGhg0WQGjDWWlDI4VuaS9PZJGNbPkaHEIuLwtixk=
1132+go.etcd.io/etcd/client/pkg/v3 v3.5.19/go.mod h1:qaOi1k4ZA9lVLejXNvyPABrVEe7VymMF2433yyRQ7O0=
1133+go.etcd.io/etcd/client/v3 v3.5.19 h1:+4byIz6ti3QC28W0zB0cEZWwhpVHXdrKovyycJh1KNo=
1134+go.etcd.io/etcd/client/v3 v3.5.19/go.mod h1:FNzyinmMIl0oVsty1zA3hFeUrxXI/JpEnz4sG+POzjU=
1135+go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
1136 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
1137 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
1138 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
1139 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
1140 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
1141-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
1142-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
1143+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
1144+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
1145 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
1146 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
1147 go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
1148@@ -963,89 +914,79 @@ go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS
1149 go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
1150 go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
1151 go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
1152-go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
1153-go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
1154+go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
1155+go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
1156 go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
1157 go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
1158-go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
1159-go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
1160-go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
1161-go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY=
1162-go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU=
1163-go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw=
1164-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
1165-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
1166-go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
1167-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
1168+go.step.sm/crypto v0.59.1 h1:jUL+5p19YS9YJKLaPUgkS2OdGm7s0+hwP7AqTFyF9Cg=
1169+go.step.sm/crypto v0.59.1/go.mod h1:XHavmnzfTyPpQE/n4YokEtjiBzP3LZI9/1O061f5y0o=
1170 go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
1171 go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
1172 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
1173 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
1174-go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
1175-go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
1176-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
1177-go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
1178-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
1179+go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
1180+go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
1181 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
1182 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
1183-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
1184-go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
1185-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
1186-go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
1187 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
1188 go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
1189-go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
1190-go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
1191+go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
1192+go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
1193+go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
1194+golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
1195 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
1196+golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
1197 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
1198 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1199-golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
1200-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1201-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1202+golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1203 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
1204 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1205 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
1206 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
1207-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
1208-golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
1209-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
1210 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
1211-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
1212-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
1213+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
1214 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
1215+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
1216+golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
1217+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
1218+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
1219 golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
1220 golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
1221-golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
1222-golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
1223+golang.org/x/crypto/x509roots/fallback v0.0.0-20250312005926-b369b723c8ad h1:FujBcZXke9mOYht0C528NPgcM3mW5yqv8Nc07SGpvTk=
1224+golang.org/x/crypto/x509roots/fallback v0.0.0-20250312005926-b369b723c8ad/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
1225 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
1226-golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
1227-golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
1228+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
1229+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
1230 golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
1231 golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
1232 golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
1233+golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
1234 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
1235 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
1236 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
1237-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
1238-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
1239-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
1240 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
1241 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
1242 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
1243 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
1244 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
1245-golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
1246-golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
1247+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
1248+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
1249+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
1250+golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
1251+golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
1252 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1253 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1254 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1255+golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1256+golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1257 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1258+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1259 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1260 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1261+golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1262 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1263 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1264 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1265-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1266 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1267 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1268 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1269@@ -1054,6 +995,7 @@ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/
1270 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1271 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1272 golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
1273+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
1274 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
1275 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
1276 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
1277@@ -1061,15 +1003,22 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
1278 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
1279 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
1280 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
1281-golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
1282 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
1283-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
1284 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
1285+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
1286+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
1287+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
1288+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
1289+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
1290 golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
1291 golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
1292 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
1293-golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
1294-golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
1295+golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
1296+golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
1297+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1298+golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
1299+golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
1300+golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
1301 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1302 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1303 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1304@@ -1077,27 +1026,33 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
1305 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1306 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1307 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1308+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1309 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1310 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1311 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1312+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
1313+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1314+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1315+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1316+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1317+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1318 golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
1319 golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
1320 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1321 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1322 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1323 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1324+golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1325 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1326 golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1327 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1328 golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1329 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1330-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1331-golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1332+golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1333 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1334 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1335 golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1336 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1337-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1338 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1339 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1340 golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1341@@ -1106,113 +1061,134 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
1342 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1343 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1344 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1345-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1346+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1347 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1348-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1349 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1350 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1351+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1352+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1353 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1354 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1355 golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1356 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1357+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1358 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1359 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1360 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1361+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1362 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1363 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1364-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1365 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1366 golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1367 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1368 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1369 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1370-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1371 golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1372 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1373-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1374 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1375 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1376-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1377 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1378-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1379+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1380 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1381+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1382+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1383+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1384 golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1385+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1386 golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
1387 golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
1388-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
1389+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
1390 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
1391 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
1392-golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
1393 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
1394-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
1395 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
1396-golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
1397+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
1398 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
1399+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
1400+golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
1401+golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
1402+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
1403 golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
1404 golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
1405 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1406+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1407 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
1408 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1409-golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1410 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
1411 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
1412-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
1413 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
1414 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
1415+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
1416 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
1417+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
1418+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
1419+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
1420+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
1421 golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
1422 golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
1423+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1424+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
1425 golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
1426 golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
1427+golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1428 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1429+golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1430 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
1431 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
1432 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
1433-golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
1434 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
1435-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
1436-golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1437 golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1438-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1439-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1440 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
1441-golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
1442 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
1443 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
1444 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
1445 golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
1446 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
1447 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
1448-golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
1449-golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
1450-golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1451-golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1452+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
1453+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
1454+golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
1455+golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
1456 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1457 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1458 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1459 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1460-google.golang.org/api v0.218.0 h1:x6JCjEWeZ9PFCRe9z0FBrNwj7pB7DOAqT35N+IPnAUA=
1461-google.golang.org/api v0.218.0/go.mod h1:5VGHBAkxrA/8EFjLVEYmMUJ8/8+gWWQ3s4cFH0FxG2M=
1462+google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1463+google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
1464+google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
1465+google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc=
1466+google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M=
1467 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
1468+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1469+google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1470 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1471 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1472+google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1473+google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1474+google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
1475+google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
1476 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
1477 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
1478 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
1479-google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
1480-google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
1481-google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
1482-google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
1483-google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
1484-google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
1485+google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s=
1486+google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE=
1487+google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf h1:BdIVRm+fyDUn8lrZLPSlBCfM/YKDwUBYgDoLv9+DYo0=
1488+google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
1489+google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY=
1490+google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
1491+google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
1492+google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
1493+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
1494 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
1495 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
1496 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
1497 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
1498 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
1499 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
1500-google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
1501-google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
1502+google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
1503+google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
1504+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
1505+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
1506 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
1507 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
1508 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
1509@@ -1228,15 +1204,11 @@ google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwl
1510 google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
1511 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
1512 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1513-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1514 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1515 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
1516 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
1517-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
1518 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
1519-gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
1520-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
1521-gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
1522+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
1523 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
1524 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
1525 gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
1526@@ -1251,12 +1223,16 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
1527 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1528 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1529 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1530+grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
1531+honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1532 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1533+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1534 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
1535-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
1536-howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
1537-howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
1538+howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
1539+howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
1540 mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
1541 mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
1542 pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
1543 pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
1544+sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
1545+sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
+0,
-105
1@@ -1,105 +0,0 @@
2-package pastes
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "os/signal"
9- "syscall"
10- "time"
11-
12- "github.com/charmbracelet/promwish"
13- "github.com/charmbracelet/ssh"
14- "github.com/charmbracelet/wish"
15- "github.com/picosh/pico/db/postgres"
16- "github.com/picosh/pico/filehandlers"
17- "github.com/picosh/pico/shared"
18- wsh "github.com/picosh/pico/wish"
19- "github.com/picosh/send/auth"
20- "github.com/picosh/send/list"
21- "github.com/picosh/send/pipe"
22- wishrsync "github.com/picosh/send/protocols/rsync"
23- "github.com/picosh/send/protocols/scp"
24- "github.com/picosh/send/proxy"
25- "github.com/picosh/utils"
26-)
27-
28-func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
29- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
30- return []wish.Middleware{
31- pipe.Middleware(handler, ""),
32- list.Middleware(handler),
33- scp.Middleware(handler),
34- wishrsync.Middleware(handler),
35- auth.Middleware(handler),
36- wsh.PtyMdw(wsh.DeprecatedNotice()),
37- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool),
38- }
39- }
40-}
41-
42-func withProxy(handler *filehandlers.FileHandlerRouter, otherMiddleware ...wish.Middleware) ssh.Option {
43- return func(server *ssh.Server) error {
44- newSubsystemHandlers := map[string]ssh.SubsystemHandler{}
45-
46- for name, subsystemHandlers := range server.SubsystemHandlers {
47- newSubsystemHandlers[name] = func(s ssh.Session) {
48- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool)(ssh.Handler(subsystemHandlers))
49- }
50- }
51-
52- server.SubsystemHandlers = newSubsystemHandlers
53-
54- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
55- }
56-}
57-
58-func StartSshServer() {
59- host := utils.GetEnv("PASTES_HOST", "0.0.0.0")
60- port := utils.GetEnv("PASTES_SSH_PORT", "2222")
61- promPort := utils.GetEnv("PASTES_PROM_PORT", "9222")
62- cfg := NewConfigSite("pastes-ssh")
63- logger := cfg.Logger
64- dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
65- defer dbh.Close()
66- hooks := &FileHooks{
67- Cfg: cfg,
68- Db: dbh,
69- }
70-
71- fileMap := map[string]filehandlers.ReadWriteHandler{
72- "fallback": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
73- }
74- handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
75- sshAuth := shared.NewSshAuthHandler(dbh, logger)
76- s, err := wish.NewServer(
77- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
78- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
79- wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
80- withProxy(
81- handler,
82- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pastes-ssh"),
83- ),
84- )
85- if err != nil {
86- logger.Error(err.Error())
87- return
88- }
89-
90- done := make(chan os.Signal, 1)
91- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
92- logger.Info("Starting SSH server", "host", host, "port", port)
93- go func() {
94- if err = s.ListenAndServe(); err != nil {
95- logger.Error(err.Error())
96- }
97- }()
98-
99- <-done
100- logger.Info("Stopping SSH server")
101- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
102- defer func() { cancel() }()
103- if err := s.Shutdown(ctx); err != nil {
104- logger.Error(err.Error())
105- }
106-}
+0,
-129
1@@ -1,129 +0,0 @@
2-package pgs
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "os/signal"
9- "syscall"
10- "time"
11-
12- "github.com/charmbracelet/promwish"
13- "github.com/charmbracelet/ssh"
14- "github.com/charmbracelet/wish"
15- "github.com/picosh/pico/shared"
16- wsh "github.com/picosh/pico/wish"
17- "github.com/picosh/send/auth"
18- "github.com/picosh/send/list"
19- "github.com/picosh/send/pipe"
20- wishrsync "github.com/picosh/send/protocols/rsync"
21- "github.com/picosh/send/protocols/scp"
22- "github.com/picosh/send/protocols/sftp"
23- "github.com/picosh/send/proxy"
24- "github.com/picosh/tunkit"
25- "github.com/picosh/utils"
26-)
27-
28-func createRouter(handler *UploadAssetHandler) proxy.Router {
29- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
30- return []wish.Middleware{
31- pipe.Middleware(handler, ""),
32- list.Middleware(handler),
33- scp.Middleware(handler),
34- wishrsync.Middleware(handler),
35- auth.Middleware(handler),
36- wsh.PtyMdw(wsh.DeprecatedNotice()),
37- WishMiddleware(handler),
38- wsh.LogMiddleware(handler.GetLogger(s), handler.Cfg.DB),
39- }
40- }
41-}
42-
43-func withProxy(handler *UploadAssetHandler, otherMiddleware ...wish.Middleware) ssh.Option {
44- return func(server *ssh.Server) error {
45- err := sftp.SSHOption(handler)(server)
46- if err != nil {
47- return err
48- }
49-
50- newSubsystemHandlers := map[string]ssh.SubsystemHandler{}
51-
52- for name, subsystemHandler := range server.SubsystemHandlers {
53- newSubsystemHandlers[name] = func(s ssh.Session) {
54- wsh.LogMiddleware(handler.GetLogger(s), handler.Cfg.DB)(ssh.Handler(subsystemHandler))(s)
55- }
56- }
57-
58- server.SubsystemHandlers = newSubsystemHandlers
59-
60- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
61- }
62-}
63-
64-func StartSshServer(cfg *PgsConfig, killCh chan error) {
65- host := utils.GetEnv("PGS_HOST", "0.0.0.0")
66- port := utils.GetEnv("PGS_SSH_PORT", "2222")
67- promPort := utils.GetEnv("PGS_PROM_PORT", "9222")
68- logger := cfg.Logger
69-
70- ctx := context.Background()
71- defer ctx.Done()
72-
73- cacheClearingQueue := make(chan string, 100)
74- handler := NewUploadAssetHandler(
75- cfg,
76- cacheClearingQueue,
77- ctx,
78- )
79-
80- webTunnel := &tunkit.WebTunnelHandler{
81- Logger: logger,
82- HttpHandler: createHttpHandler(cfg),
83- }
84-
85- sshAuth := shared.NewSshAuthHandler(cfg.DB, logger)
86- s, err := wish.NewServer(
87- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
88- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
89- wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
90- tunkit.WithWebTunnel(webTunnel),
91- withProxy(
92- handler,
93- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pgs-ssh"),
94- ),
95- )
96- if err != nil {
97- logger.Error(err.Error())
98- return
99- }
100-
101- done := make(chan os.Signal, 1)
102- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
103- logger.Info("starting SSH server on", "host", host, "port", port)
104-
105- go func() {
106- if err = s.ListenAndServe(); err != nil {
107- if err != ssh.ErrServerClosed {
108- logger.Error("serve", "err", err.Error())
109- os.Exit(1)
110- }
111- }
112- }()
113-
114- exit := func() {
115- logger.Info("stopping ssh server")
116- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
117- defer func() { cancel() }()
118- if err := s.Shutdown(ctx); err != nil {
119- logger.Error("shutdown", "err", err.Error())
120- os.Exit(1)
121- }
122- }
123-
124- select {
125- case <-killCh:
126- exit()
127- case <-done:
128- exit()
129- }
130-}
+0,
-132
1@@ -1,132 +0,0 @@
2-package pico
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "os/signal"
9- "syscall"
10- "time"
11-
12- "git.sr.ht/~rockorager/vaxis"
13- "github.com/charmbracelet/promwish"
14- "github.com/charmbracelet/ssh"
15- "github.com/charmbracelet/wish"
16- "github.com/picosh/pico/db/postgres"
17- "github.com/picosh/pico/shared"
18- "github.com/picosh/pico/tui"
19- wsh "github.com/picosh/pico/wish"
20- "github.com/picosh/send/auth"
21- "github.com/picosh/send/list"
22- "github.com/picosh/send/pipe"
23- wishrsync "github.com/picosh/send/protocols/rsync"
24- "github.com/picosh/send/protocols/scp"
25- "github.com/picosh/send/protocols/sftp"
26- "github.com/picosh/send/proxy"
27- "github.com/picosh/utils"
28-)
29-
30-func createRouterVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler) proxy.Router {
31- return func(sh ssh.Handler, sesh ssh.Session) []wish.Middleware {
32- shrd := &tui.SharedModel{
33- Session: sesh,
34- Cfg: cfg,
35- Dbpool: handler.DBPool,
36- Logger: cfg.Logger,
37- }
38- return []wish.Middleware{
39- pipe.Middleware(handler, ""),
40- list.Middleware(handler),
41- scp.Middleware(handler),
42- wishrsync.Middleware(handler),
43- auth.Middleware(handler),
44- wsh.PtyMdw(createTui(shrd)),
45- WishMiddleware(cliHandler),
46- wsh.LogMiddleware(handler.GetLogger(sesh), handler.DBPool),
47- }
48- }
49-}
50-
51-func withProxyVaxis(cfg *shared.ConfigSite, handler *UploadHandler, cliHandler *CliHandler, otherMiddleware ...wish.Middleware) ssh.Option {
52- return func(server *ssh.Server) error {
53- err := sftp.SSHOption(handler)(server)
54- if err != nil {
55- return err
56- }
57-
58- return proxy.WithProxy(createRouterVaxis(cfg, handler, cliHandler), otherMiddleware...)(server)
59- }
60-}
61-
62-func createTui(shrd *tui.SharedModel) wish.Middleware {
63- return func(next ssh.Handler) ssh.Handler {
64- return func(sesh ssh.Session) {
65- vty, err := shared.NewVConsole(sesh)
66- if err != nil {
67- panic(err)
68- }
69- opts := vaxis.Options{
70- WithConsole: vty,
71- }
72- tui.NewTui(opts, shrd)
73- }
74- }
75-}
76-
77-func StartSshServer() {
78- host := utils.GetEnv("PICO_HOST", "0.0.0.0")
79- port := utils.GetEnv("PICO_SSH_PORT", "2222")
80- promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
81- cfg := NewConfigSite("pico-ssh")
82- logger := cfg.Logger
83- dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
84- defer dbpool.Close()
85-
86- handler := NewUploadHandler(
87- dbpool,
88- cfg,
89- )
90- cliHandler := &CliHandler{
91- Logger: logger,
92- DBPool: dbpool,
93- }
94-
95- sshAuth := shared.NewSshAuthHandler(dbpool, logger)
96- s, err := wish.NewServer(
97- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
98- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
99- wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
100- sshAuth.PubkeyAuthHandler(ctx, key)
101- return true
102- }),
103- withProxyVaxis(
104- cfg,
105- handler,
106- cliHandler,
107- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pico-ssh"),
108- ),
109- )
110- if err != nil {
111- logger.Error(err.Error())
112- return
113- }
114-
115- done := make(chan os.Signal, 1)
116- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
117- logger.Info("starting SSH server on", "host", host, "port", port)
118- go func() {
119- if err = s.ListenAndServe(); err != nil {
120- logger.Error("serve", "err", err.Error())
121- os.Exit(1)
122- }
123- }()
124-
125- <-done
126- logger.Info("stopping SSH server")
127- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
128- defer func() { cancel() }()
129- if err := s.Shutdown(ctx); err != nil {
130- logger.Error("shutdown", "err", err.Error())
131- os.Exit(1)
132- }
133-}
+0,
-80
1@@ -1,80 +0,0 @@
2-package pipe
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "os/signal"
9- "syscall"
10- "time"
11-
12- "github.com/antoniomika/syncmap"
13- "github.com/charmbracelet/promwish"
14- "github.com/charmbracelet/ssh"
15- "github.com/charmbracelet/wish"
16- "github.com/picosh/pico/db/postgres"
17- "github.com/picosh/pico/shared"
18- wsh "github.com/picosh/pico/wish"
19- psub "github.com/picosh/pubsub"
20- "github.com/picosh/utils"
21-)
22-
23-func StartSshServer() {
24- host := utils.GetEnv("PIPE_HOST", "0.0.0.0")
25- port := utils.GetEnv("PIPE_SSH_PORT", "2222")
26- portOverride := utils.GetEnv("PIPE_SSH_PORT_OVERRIDE", port)
27- promPort := utils.GetEnv("PIPE_PROM_PORT", "9222")
28- cfg := NewConfigSite("pipe-ssh")
29- logger := cfg.Logger
30- dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
31- defer dbh.Close()
32-
33- cfg.Port = port
34- cfg.PortOverride = portOverride
35-
36- pubsub := psub.NewMulticast(logger)
37- handler := &CliHandler{
38- Logger: logger,
39- DBPool: dbh,
40- PubSub: pubsub,
41- Cfg: cfg,
42- Waiters: syncmap.New[string, []string](),
43- Access: syncmap.New[string, []string](),
44- }
45-
46- sshAuth := shared.NewSshAuthHandler(dbh, logger)
47- s, err := wish.NewServer(
48- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
49- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
50- wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
51- sshAuth.PubkeyAuthHandler(ctx, key)
52- return true
53- }),
54- wish.WithMiddleware(
55- WishMiddleware(handler),
56- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pipe-ssh"),
57- wsh.LogMiddleware(logger, dbh),
58- ),
59- )
60- if err != nil {
61- logger.Error("wish server", "err", err.Error())
62- return
63- }
64-
65- done := make(chan os.Signal, 1)
66- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
67- logger.Info("Starting SSH server", "host", host, "port", port)
68- go func() {
69- if err = s.ListenAndServe(); err != nil {
70- logger.Error("listen", "err", err.Error())
71- }
72- }()
73-
74- <-done
75- logger.Info("Stopping SSH server")
76- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
77- defer func() { cancel() }()
78- if err := s.Shutdown(ctx); err != nil {
79- logger.Error("shutdown", "err", err.Error())
80- }
81-}
R auth/__snapshots__/api_test.snap =>
pkg/apps/auth/__snapshots__/api_test.snap
+0,
-0
R auth/api.go =>
pkg/apps/auth/api.go
+3,
-3
1@@ -16,9 +16,9 @@ import (
2 "strings"
3 "time"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/db/postgres"
10+ "github.com/picosh/pico/pkg/shared"
11 "github.com/picosh/utils"
12 "github.com/picosh/utils/pipe/metrics"
13 "github.com/prometheus/client_golang/prometheus/promhttp"
R auth/api_test.go =>
pkg/apps/auth/api_test.go
+3,
-3
1@@ -12,9 +12,9 @@ import (
2 "time"
3
4 "github.com/gkampitakis/go-snaps/snaps"
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/stub"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/db/stub"
10+ "github.com/picosh/pico/pkg/shared"
11 )
12
13 var testUserID = "user-1"
R auth/html/base.layout.tmpl =>
pkg/apps/auth/html/base.layout.tmpl
+0,
-0
R auth/html/redirect.page.tmpl =>
pkg/apps/auth/html/redirect.page.tmpl
+0,
-0
R auth/public/apple-touch-icon.png =>
pkg/apps/auth/public/apple-touch-icon.png
+0,
-0
R auth/public/favicon-16x16.png =>
pkg/apps/auth/public/favicon-16x16.png
+0,
-0
R auth/public/favicon.ico =>
pkg/apps/auth/public/favicon.ico
+0,
-0
R auth/public/main.css =>
pkg/apps/auth/public/main.css
+0,
-0
R auth/public/robots.txt =>
pkg/apps/auth/public/robots.txt
+0,
-0
R feeds/api.go =>
pkg/apps/feeds/api.go
+2,
-2
1@@ -6,8 +6,8 @@ import (
2 "net/url"
3 "time"
4
5- "github.com/picosh/pico/db/postgres"
6- "github.com/picosh/pico/shared"
7+ "github.com/picosh/pico/pkg/db/postgres"
8+ "github.com/picosh/pico/pkg/shared"
9 "github.com/prometheus/client_golang/prometheus/promhttp"
10 )
11
R feeds/cli.go =>
pkg/apps/feeds/cli.go
+34,
-38
1@@ -5,34 +5,31 @@ import (
2 "text/tabwriter"
3 "time"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7- "github.com/picosh/pico/db"
8- "github.com/picosh/pico/shared"
9-
10- wsh "github.com/picosh/pico/wish"
11+ "github.com/picosh/pico/pkg/db"
12+ "github.com/picosh/pico/pkg/pssh"
13+ "github.com/picosh/pico/pkg/shared"
14 )
15
16-func WishMiddleware(dbpool db.DB, cfg *shared.ConfigSite) wish.Middleware {
17- return func(next ssh.Handler) ssh.Handler {
18- return func(sesh ssh.Session) {
19+func Middleware(dbpool db.DB, cfg *shared.ConfigSite) pssh.SSHServerMiddleware {
20+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
21+ return func(sesh *pssh.SSHServerConnSession) error {
22 args := sesh.Command()
23 if len(args) == 0 {
24- next(sesh)
25- return
26+ return next(sesh)
27 }
28
29- logger := wsh.GetLogger(sesh)
30- user := wsh.GetUser(sesh)
31+ logger := pssh.GetLogger(sesh)
32+ user := pssh.GetUser(sesh)
33
34 if user == nil {
35- wish.Errorln(sesh, fmt.Errorf("user not found"))
36- return
37+ err := fmt.Errorf("user not found")
38+ fmt.Fprintln(sesh.Stderr(), err)
39+ return err
40 }
41
42 cmd := args[0]
43 if cmd == "help" {
44- wish.Printf(sesh, "Commands: [help, ls, rm, run]\n\n")
45+ fmt.Fprintf(sesh, "Commands: [help, ls, rm, run]\n\n")
46 writer := tabwriter.NewWriter(sesh, 0, 0, 1, ' ', tabwriter.TabIndent)
47 fmt.Fprintln(writer, "Cmd\tDesc")
48 fmt.Fprintf(
49@@ -55,17 +52,16 @@ func WishMiddleware(dbpool db.DB, cfg *shared.ConfigSite) wish.Middleware {
50 "%s\t%s\n",
51 "run {filename}", "runs the feed digest post immediately, ignoring last digest time validation",
52 )
53- writer.Flush()
54- return
55+ return writer.Flush()
56 } else if cmd == "ls" {
57 posts, err := dbpool.FindPostsForUser(&db.Pager{Page: 0, Num: 1000}, user.ID, "feeds")
58 if err != nil {
59- wish.Errorln(sesh, err)
60- return
61+ fmt.Fprintln(sesh.Stderr(), err)
62+ return err
63 }
64
65 if len(posts.Data) == 0 {
66- wish.Println(sesh, "no posts found")
67+ fmt.Fprintln(sesh, "no posts found")
68 }
69
70 writer := tabwriter.NewWriter(sesh, 0, 0, 1, ' ', tabwriter.TabIndent)
71@@ -83,11 +79,10 @@ func WishMiddleware(dbpool db.DB, cfg *shared.ConfigSite) wish.Middleware {
72 post.Data.Attempts,
73 )
74 }
75- writer.Flush()
76- return
77+ return writer.Flush()
78 } else if cmd == "rm" {
79 filename := args[1]
80- wish.Printf(sesh, "removing digest post %s\n", filename)
81+ fmt.Fprintf(sesh, "removing digest post %s\n", filename)
82 write := false
83 if len(args) > 2 {
84 writeRaw := args[2]
85@@ -98,41 +93,42 @@ func WishMiddleware(dbpool db.DB, cfg *shared.ConfigSite) wish.Middleware {
86
87 post, err := dbpool.FindPostWithFilename(filename, user.ID, "feeds")
88 if err != nil {
89- wish.Errorln(sesh, err)
90- return
91+ fmt.Fprintln(sesh.Stderr(), err)
92+ return err
93 }
94 if write {
95 err = dbpool.RemovePosts([]string{post.ID})
96 if err != nil {
97- wish.Errorln(sesh, err)
98+ fmt.Fprintln(sesh.Stderr(), err)
99 }
100 }
101- wish.Printf(sesh, "digest post removed %s\n", filename)
102+ fmt.Fprintf(sesh, "digest post removed %s\n", filename)
103 if !write {
104- wish.Println(sesh, "WARNING: *must* append with `--write` for the changes to persist.")
105+ fmt.Fprintln(sesh, "WARNING: *must* append with `--write` for the changes to persist.")
106 }
107- return
108+ return err
109 } else if cmd == "run" {
110 if len(args) < 2 {
111- wish.Errorln(sesh, "must provide filename of post to run")
112- return
113+ err := fmt.Errorf("must provide filename of post to run")
114+ fmt.Fprintln(sesh.Stderr(), err)
115+ return err
116 }
117 filename := args[1]
118 post, err := dbpool.FindPostWithFilename(filename, user.ID, "feeds")
119 if err != nil {
120- wish.Errorln(sesh, err)
121- return
122+ fmt.Fprintln(sesh.Stderr(), err)
123+ return err
124 }
125- wish.Printf(sesh, "running feed post: %s\n", filename)
126+ fmt.Fprintf(sesh, "running feed post: %s\n", filename)
127 fetcher := NewFetcher(dbpool, cfg)
128 err = fetcher.RunPost(logger, user, post, true)
129 if err != nil {
130- wish.Errorln(sesh, err)
131+ fmt.Fprintln(sesh.Stderr(), err)
132 }
133- return
134+ return err
135 }
136
137- next(sesh)
138+ return next(sesh)
139 }
140 }
141 }
R feeds/config.go =>
pkg/apps/feeds/config.go
+1,
-1
1@@ -1,7 +1,7 @@
2 package feeds
3
4 import (
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 "github.com/picosh/utils"
8 )
9
R feeds/cron.go =>
pkg/apps/feeds/cron.go
+2,
-2
1@@ -15,8 +15,8 @@ import (
2 "time"
3
4 "github.com/mmcdole/gofeed"
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/shared"
7+ "github.com/picosh/pico/pkg/db"
8+ "github.com/picosh/pico/pkg/shared"
9 "github.com/sendgrid/sendgrid-go"
10 "github.com/sendgrid/sendgrid-go/helpers/mail"
11 )
R feeds/html/base.layout.tmpl =>
pkg/apps/feeds/html/base.layout.tmpl
+0,
-0
R feeds/html/digest.page.tmpl =>
pkg/apps/feeds/html/digest.page.tmpl
+0,
-0
R feeds/html/digest_text.page.tmpl =>
pkg/apps/feeds/html/digest_text.page.tmpl
+0,
-0
R feeds/html/marketing.page.tmpl =>
pkg/apps/feeds/html/marketing.page.tmpl
+0,
-0
R feeds/public/apple-touch-icon.png =>
pkg/apps/feeds/public/apple-touch-icon.png
+0,
-0
R feeds/public/card.png =>
pkg/apps/feeds/public/card.png
+0,
-0
R feeds/public/favicon-16x16.png =>
pkg/apps/feeds/public/favicon-16x16.png
+0,
-0
R feeds/public/favicon.ico =>
pkg/apps/feeds/public/favicon.ico
+0,
-0
R feeds/public/main.css =>
pkg/apps/feeds/public/main.css
+0,
-0
R feeds/public/robots.txt =>
pkg/apps/feeds/public/robots.txt
+0,
-0
R feeds/scp_hooks.go =>
pkg/apps/feeds/scp_hooks.go
+6,
-6
1@@ -8,10 +8,10 @@ import (
2 "strings"
3 "time"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/filehandlers"
8- "github.com/picosh/pico/shared"
9+ "github.com/picosh/pico/pkg/db"
10+ "github.com/picosh/pico/pkg/filehandlers"
11+ "github.com/picosh/pico/pkg/pssh"
12+ "github.com/picosh/pico/pkg/shared"
13 "github.com/picosh/utils"
14 )
15
16@@ -20,7 +20,7 @@ type FeedHooks struct {
17 Db db.DB
18 }
19
20-func (p *FeedHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData) (bool, error) {
21+func (p *FeedHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
22 if !utils.IsTextFile(string(data.Text)) {
23 err := fmt.Errorf(
24 "WARNING: (%s) invalid file must be plain text (utf-8), skipping",
25@@ -73,7 +73,7 @@ func (p *FeedHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData)
26 return true, nil
27 }
28
29-func (p *FeedHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
30+func (p *FeedHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
31 if data.Data.LastDigest == nil {
32 now := time.Now()
33 // let it run on the next loop
+97,
-0
1@@ -0,0 +1,97 @@
2+package feeds
3+
4+import (
5+ "context"
6+ "os"
7+ "os/signal"
8+ "syscall"
9+
10+ "github.com/picosh/pico/pkg/db/postgres"
11+ "github.com/picosh/pico/pkg/filehandlers"
12+ "github.com/picosh/pico/pkg/pssh"
13+ "github.com/picosh/pico/pkg/send/auth"
14+ "github.com/picosh/pico/pkg/send/list"
15+ "github.com/picosh/pico/pkg/send/pipe"
16+ "github.com/picosh/pico/pkg/send/protocols/rsync"
17+ "github.com/picosh/pico/pkg/send/protocols/scp"
18+ "github.com/picosh/pico/pkg/send/protocols/sftp"
19+ "github.com/picosh/pico/pkg/shared"
20+ "github.com/picosh/utils"
21+)
22+
23+func StartSshServer() {
24+ appName := "feeds-ssh"
25+
26+ host := utils.GetEnv("FEEDS_HOST", "0.0.0.0")
27+ port := utils.GetEnv("FEEDS_SSH_PORT", "2222")
28+ promPort := utils.GetEnv("FEEDS_PROM_PORT", "9222")
29+ cfg := NewConfigSite(appName)
30+ logger := cfg.Logger
31+
32+ ctx, cancel := context.WithCancel(context.Background())
33+ defer cancel()
34+
35+ dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
36+ defer dbh.Close()
37+
38+ hooks := &FeedHooks{
39+ Cfg: cfg,
40+ Db: dbh,
41+ }
42+
43+ fileMap := map[string]filehandlers.ReadWriteHandler{
44+ "fallback": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
45+ }
46+ handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
47+
48+ sshAuth := shared.NewSshAuthHandler(dbh, logger)
49+
50+ // Create a new SSH server
51+ server, err := pssh.NewSSHServerWithConfig(
52+ ctx,
53+ logger,
54+ appName,
55+ host,
56+ port,
57+ promPort,
58+ sshAuth.PubkeyAuthHandler,
59+ []pssh.SSHServerMiddleware{
60+ pipe.Middleware(handler, ".txt"),
61+ list.Middleware(handler),
62+ scp.Middleware(handler),
63+ rsync.Middleware(handler),
64+ auth.Middleware(handler),
65+ pssh.PtyMdw(pssh.DeprecatedNotice()),
66+ pssh.LogMiddleware(handler, dbh),
67+ },
68+ []pssh.SSHServerMiddleware{
69+ sftp.Middleware(handler),
70+ pssh.LogMiddleware(handler, dbh),
71+ },
72+ nil,
73+ )
74+
75+ if err != nil {
76+ logger.Error("failed to create ssh server", "err", err.Error())
77+ os.Exit(1)
78+ }
79+
80+ done := make(chan os.Signal, 1)
81+
82+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
83+ logger.Info("Starting SSH server", "addr", server.Config.ListenAddr)
84+ go func() {
85+ if err = server.ListenAndServe(); err != nil {
86+ logger.Error("serve", "err", err.Error())
87+ os.Exit(1)
88+ }
89+ }()
90+
91+ exit := func() {
92+ logger.Info("stopping ssh server")
93+ cancel()
94+ }
95+
96+ <-done
97+ exit()
98+}
R pastes/api.go =>
pkg/apps/pastes/api.go
+3,
-3
1@@ -8,9 +8,9 @@ import (
2 "os"
3 "time"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/db/postgres"
10+ "github.com/picosh/pico/pkg/shared"
11 "github.com/picosh/utils"
12 "github.com/prometheus/client_golang/prometheus/promhttp"
13 )
R pastes/config.go =>
pkg/apps/pastes/config.go
+1,
-1
1@@ -1,7 +1,7 @@
2 package pastes
3
4 import (
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 "github.com/picosh/utils"
8 )
9
R pastes/cron.go =>
pkg/apps/pastes/cron.go
+2,
-2
1@@ -3,8 +3,8 @@ package pastes
2 import (
3 "time"
4
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/shared"
7+ "github.com/picosh/pico/pkg/db"
8+ "github.com/picosh/pico/pkg/shared"
9 )
10
11 func deleteExpiredPosts(cfg *shared.ConfigSite, dbpool db.DB) error {
R pastes/html/base.layout.tmpl =>
pkg/apps/pastes/html/base.layout.tmpl
+0,
-0
R pastes/html/blog.page.tmpl =>
pkg/apps/pastes/html/blog.page.tmpl
+0,
-0
R pastes/html/marketing.page.tmpl =>
pkg/apps/pastes/html/marketing.page.tmpl
+0,
-0
R pastes/html/post.page.tmpl =>
pkg/apps/pastes/html/post.page.tmpl
+0,
-0
R pastes/parser.go =>
pkg/apps/pastes/parser.go
+0,
-0
R pastes/public/apple-touch-icon.png =>
pkg/apps/pastes/public/apple-touch-icon.png
+0,
-0
R pastes/public/card.png =>
pkg/apps/pastes/public/card.png
+0,
-0
R pastes/public/favicon-16x16.png =>
pkg/apps/pastes/public/favicon-16x16.png
+0,
-0
R pastes/public/favicon.ico =>
pkg/apps/pastes/public/favicon.ico
+0,
-0
R pastes/public/main.css =>
pkg/apps/pastes/public/main.css
+0,
-0
R pastes/public/robots.txt =>
pkg/apps/pastes/public/robots.txt
+0,
-0
R pastes/public/smol.css =>
pkg/apps/pastes/public/smol.css
+0,
-0
R pastes/public/syntax.css =>
pkg/apps/pastes/public/syntax.css
+0,
-0
R pastes/scp_hooks.go =>
pkg/apps/pastes/scp_hooks.go
+6,
-6
1@@ -7,10 +7,10 @@ import (
2 "time"
3
4 "github.com/araddon/dateparse"
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/filehandlers"
8- "github.com/picosh/pico/shared"
9+ "github.com/picosh/pico/pkg/db"
10+ "github.com/picosh/pico/pkg/filehandlers"
11+ "github.com/picosh/pico/pkg/pssh"
12+ "github.com/picosh/pico/pkg/shared"
13 "github.com/picosh/utils"
14 )
15
16@@ -21,7 +21,7 @@ type FileHooks struct {
17 Db db.DB
18 }
19
20-func (p *FileHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData) (bool, error) {
21+func (p *FileHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
22 if !utils.IsTextFile(string(data.Text)) {
23 err := fmt.Errorf(
24 "ERROR: (%s) invalid file must be plain text (utf-8), skipping",
25@@ -42,7 +42,7 @@ func (p *FileHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData)
26 return true, nil
27 }
28
29-func (p *FileHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
30+func (p *FileHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
31 data.Title = utils.ToUpper(data.Slug)
32 // we want the slug to be the filename for pastes
33 data.Slug = data.Filename
+94,
-0
1@@ -0,0 +1,94 @@
2+package pastes
3+
4+import (
5+ "context"
6+ "os"
7+ "os/signal"
8+ "syscall"
9+
10+ "github.com/picosh/pico/pkg/db/postgres"
11+ "github.com/picosh/pico/pkg/filehandlers"
12+ "github.com/picosh/pico/pkg/pssh"
13+ "github.com/picosh/pico/pkg/send/auth"
14+ "github.com/picosh/pico/pkg/send/list"
15+ "github.com/picosh/pico/pkg/send/pipe"
16+ "github.com/picosh/pico/pkg/send/protocols/rsync"
17+ "github.com/picosh/pico/pkg/send/protocols/scp"
18+ "github.com/picosh/pico/pkg/send/protocols/sftp"
19+ "github.com/picosh/pico/pkg/shared"
20+ "github.com/picosh/utils"
21+)
22+
23+func StartSshServer() {
24+ appName := "pastes-ssh"
25+
26+ host := utils.GetEnv("PASTES_HOST", "0.0.0.0")
27+ port := utils.GetEnv("PASTES_SSH_PORT", "2222")
28+ promPort := utils.GetEnv("PASTES_PROM_PORT", "9222")
29+ cfg := NewConfigSite(appName)
30+ logger := cfg.Logger
31+
32+ ctx, cancel := context.WithCancel(context.Background())
33+ defer cancel()
34+
35+ dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
36+ defer dbh.Close()
37+ hooks := &FileHooks{
38+ Cfg: cfg,
39+ Db: dbh,
40+ }
41+
42+ fileMap := map[string]filehandlers.ReadWriteHandler{
43+ "fallback": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
44+ }
45+ handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
46+ sshAuth := shared.NewSshAuthHandler(dbh, logger)
47+
48+ // Create a new SSH server
49+ server, err := pssh.NewSSHServerWithConfig(
50+ ctx,
51+ logger,
52+ appName,
53+ host,
54+ port,
55+ promPort,
56+ sshAuth.PubkeyAuthHandler,
57+ []pssh.SSHServerMiddleware{
58+ pipe.Middleware(handler, ""),
59+ list.Middleware(handler),
60+ scp.Middleware(handler),
61+ rsync.Middleware(handler),
62+ auth.Middleware(handler),
63+ pssh.PtyMdw(pssh.DeprecatedNotice()),
64+ pssh.LogMiddleware(handler, dbh),
65+ },
66+ []pssh.SSHServerMiddleware{
67+ sftp.Middleware(handler),
68+ pssh.LogMiddleware(handler, dbh),
69+ },
70+ nil,
71+ )
72+
73+ if err != nil {
74+ logger.Error("failed to create ssh server", "err", err.Error())
75+ os.Exit(1)
76+ }
77+
78+ done := make(chan os.Signal, 1)
79+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
80+ logger.Info("Starting SSH server", "addr", server.Config.ListenAddr)
81+ go func() {
82+ if err = server.ListenAndServe(); err != nil {
83+ logger.Error("serve", "err", err.Error())
84+ os.Exit(1)
85+ }
86+ }()
87+
88+ exit := func() {
89+ logger.Info("stopping ssh server")
90+ cancel()
91+ }
92+
93+ <-done
94+ exit()
95+}
R pgs/access.go =>
pkg/apps/pgs/access.go
+1,
-1
1@@ -3,7 +3,7 @@ package pgs
2 import (
3 "slices"
4
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "golang.org/x/crypto/ssh"
8 )
9
R pgs/calc_route.go =>
pkg/apps/pgs/calc_route.go
+3,
-3
1@@ -8,9 +8,9 @@ import (
2 "regexp"
3 "strings"
4
5- "github.com/picosh/pico/shared"
6- "github.com/picosh/pico/shared/storage"
7- "github.com/picosh/send/utils"
8+ "github.com/picosh/pico/pkg/send/utils"
9+ "github.com/picosh/pico/pkg/shared"
10+ "github.com/picosh/pico/pkg/shared/storage"
11 )
12
13 type HttpReply struct {
R pgs/calc_route_test.go =>
pkg/apps/pgs/calc_route_test.go
+0,
-0
R pgs/cli.go =>
pkg/apps/pgs/cli.go
+97,
-117
1@@ -4,120 +4,45 @@ import (
2 "context"
3 "errors"
4 "fmt"
5+ "io"
6 "log/slog"
7 "path/filepath"
8 "strings"
9+ "text/tabwriter"
10 "time"
11
12- "github.com/charmbracelet/lipgloss"
13- "github.com/charmbracelet/lipgloss/table"
14- "github.com/picosh/pico/db"
15- pgsdb "github.com/picosh/pico/pgs/db"
16- "github.com/picosh/pico/shared"
17- sst "github.com/picosh/pobj/storage"
18+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
19+ "github.com/picosh/pico/pkg/db"
20+ sst "github.com/picosh/pico/pkg/pobj/storage"
21+ "github.com/picosh/pico/pkg/shared"
22 "github.com/picosh/utils"
23 )
24
25-func projectTable(projects []*db.Project, width int) *table.Table {
26- headers := []string{
27- "Name",
28- "Last Updated",
29- "Links To",
30- "ACL Type",
31- "ACL",
32- "Blocked",
33- }
34- data := [][]string{}
35+func NewTabWriter(out io.Writer) *tabwriter.Writer {
36+ return tabwriter.NewWriter(out, 0, 0, 1, ' ', tabwriter.TabIndent)
37+}
38+
39+func projectTable(sesh io.Writer, projects []*db.Project) {
40+ writer := NewTabWriter(sesh)
41+ fmt.Fprintln(writer, "Name\tLast Updated\tLinks To\tACL Type\tACL\tBlocked")
42+
43 for _, project := range projects {
44- row := []string{
45- project.Name,
46- project.UpdatedAt.Format("2006-01-02 15:04:05"),
47- }
48 links := ""
49 if project.ProjectDir != project.Name {
50 links = project.ProjectDir
51 }
52- row = append(row, links)
53- row = append(row,
54+ fmt.Fprintf(
55+ writer,
56+ "%s\t%s\t%s\t%s\t%s\t%s\n",
57+ project.Name,
58+ project.UpdatedAt.Format("2006-01-02 15:04:05"),
59+ links,
60 project.Acl.Type,
61 strings.Join(project.Acl.Data, " "),
62+ project.Blocked,
63 )
64- row = append(row, project.Blocked)
65- data = append(data, row)
66- }
67-
68- t := table.New().
69- Width(width).
70- Headers(headers...).
71- Rows(data...)
72- return t
73-}
74-
75-func getHelpText(width int) string {
76- helpStr := "Commands: [help, stats, ls, fzf, rm, link, unlink, prune, retain, depends, acl, cache]\n"
77- helpStr += "NOTICE:" + " *must* append with `--write` for the changes to persist.\n"
78-
79- projectName := "projA"
80- headers := []string{"Cmd", "Description"}
81- data := [][]string{
82- {
83- "help",
84- "prints this screen",
85- },
86- {
87- "stats",
88- "usage statistics",
89- },
90- {
91- "ls",
92- "lists projects",
93- },
94- {
95- fmt.Sprintf("fzf %s", projectName),
96- fmt.Sprintf("lists urls of all assets in %s", projectName),
97- },
98- {
99- fmt.Sprintf("rm %s", projectName),
100- fmt.Sprintf("delete %s", projectName),
101- },
102- {
103- fmt.Sprintf("link %s --to projB", projectName),
104- fmt.Sprintf("symbolic link `%s` to `projB`", projectName),
105- },
106- {
107- fmt.Sprintf("unlink %s", projectName),
108- fmt.Sprintf("removes symbolic link for `%s`", projectName),
109- },
110- {
111- fmt.Sprintf("prune %s", projectName),
112- fmt.Sprintf("removes projects that match prefix `%s`", projectName),
113- },
114- {
115- fmt.Sprintf("retain %s", projectName),
116- "alias to `prune` but keeps last N projects",
117- },
118- {
119- fmt.Sprintf("depends %s", projectName),
120- fmt.Sprintf("lists all projects linked to `%s`", projectName),
121- },
122- {
123- fmt.Sprintf("acl %s", projectName),
124- fmt.Sprintf("access control for `%s`", projectName),
125- },
126- {
127- fmt.Sprintf("cache %s", projectName),
128- fmt.Sprintf("clear http cache for `%s`", projectName),
129- },
130 }
131-
132- t := table.New().
133- Width(width).
134- Border(lipgloss.RoundedBorder()).
135- Headers(headers...).
136- Rows(data...)
137-
138- helpStr += t.String()
139- return helpStr
140+ writer.Flush()
141 }
142
143 type Cmd struct {
144@@ -201,7 +126,68 @@ func (c *Cmd) RmProjectAssets(projectName string) error {
145 }
146
147 func (c *Cmd) help() {
148- c.output(getHelpText(c.Width))
149+ helpStr := "Commands: [help, stats, ls, fzf, rm, link, unlink, prune, retain, depends, acl, cache]\n"
150+ helpStr += "NOTICE:" + " *must* append with `--write` for the changes to persist.\n"
151+ c.output(helpStr)
152+ projectName := "projA"
153+
154+ data := [][]string{
155+ {
156+ "help",
157+ "prints this screen",
158+ },
159+ {
160+ "stats",
161+ "usage statistics",
162+ },
163+ {
164+ "ls",
165+ "lists projects",
166+ },
167+ {
168+ fmt.Sprintf("fzf %s", projectName),
169+ fmt.Sprintf("lists urls of all assets in %s", projectName),
170+ },
171+ {
172+ fmt.Sprintf("rm %s", projectName),
173+ fmt.Sprintf("delete %s", projectName),
174+ },
175+ {
176+ fmt.Sprintf("link %s --to projB", projectName),
177+ fmt.Sprintf("symbolic link `%s` to `projB`", projectName),
178+ },
179+ {
180+ fmt.Sprintf("unlink %s", projectName),
181+ fmt.Sprintf("removes symbolic link for `%s`", projectName),
182+ },
183+ {
184+ fmt.Sprintf("prune %s", projectName),
185+ fmt.Sprintf("removes projects that match prefix `%s`", projectName),
186+ },
187+ {
188+ fmt.Sprintf("retain %s", projectName),
189+ "alias to `prune` but keeps last N projects",
190+ },
191+ {
192+ fmt.Sprintf("depends %s", projectName),
193+ fmt.Sprintf("lists all projects linked to `%s`", projectName),
194+ },
195+ {
196+ fmt.Sprintf("acl %s", projectName),
197+ fmt.Sprintf("access control for `%s`", projectName),
198+ },
199+ {
200+ fmt.Sprintf("cache %s", projectName),
201+ fmt.Sprintf("clear http cache for `%s`", projectName),
202+ },
203+ }
204+
205+ writer := NewTabWriter(c.Session)
206+ fmt.Fprintln(writer, "Cmd\tDescription")
207+ for _, dat := range data {
208+ fmt.Fprintf(writer, "%s\t%s\n", dat[0], dat[1])
209+ }
210+ writer.Flush()
211 }
212
213 func (c *Cmd) stats(cfgMaxSize uint64) error {
214@@ -229,20 +215,17 @@ func (c *Cmd) stats(cfgMaxSize uint64) error {
215 return err
216 }
217
218- headers := []string{"Used (GB)", "Quota (GB)", "Used (%)", "Projects (#)"}
219- data := []string{
220- fmt.Sprintf("%.4f", utils.BytesToGB(int(totalFileSize))),
221- fmt.Sprintf("%.4f", utils.BytesToGB(int(storageMax))),
222- fmt.Sprintf("%.4f", (float32(totalFileSize)/float32(storageMax))*100),
223- fmt.Sprintf("%d", len(projects)),
224- }
225-
226- t := table.New().
227- Width(c.Width).
228- Border(lipgloss.RoundedBorder()).
229- Headers(headers...).
230- Rows(data)
231- c.output(t.String())
232+ writer := NewTabWriter(c.Session)
233+ fmt.Fprintln(writer, "Used (GB)\tQuota (GB)\tUsed (%)\tProjects (#)")
234+ fmt.Fprintf(
235+ writer,
236+ "%.4f\t%.4f\t%.4f\t%d\n",
237+ utils.BytesToGB(int(totalFileSize)),
238+ utils.BytesToGB(int(storageMax)),
239+ (float32(totalFileSize)/float32(storageMax))*100,
240+ len(projects),
241+ )
242+ writer.Flush()
243
244 return nil
245 }
246@@ -257,8 +240,7 @@ func (c *Cmd) ls() error {
247 c.output("no projects found")
248 }
249
250- t := projectTable(projects, c.Width)
251- c.output(t.String())
252+ projectTable(c.Session, projects)
253
254 return nil
255 }
256@@ -374,9 +356,7 @@ func (c *Cmd) depends(projectName string) error {
257 return nil
258 }
259
260- t := projectTable(projects, c.Width)
261- c.output(t.String())
262-
263+ projectTable(c.Session, projects)
264 return nil
265 }
266
R pgs/cli_wish.go =>
pkg/apps/pgs/cli_wish.go
+42,
-45
1@@ -6,17 +6,14 @@ import (
2 "slices"
3 "strings"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7- bm "github.com/charmbracelet/wish/bubbletea"
8- "github.com/muesli/termenv"
9- "github.com/picosh/pico/db"
10- pgsdb "github.com/picosh/pico/pgs/db"
11- sendutils "github.com/picosh/send/utils"
12+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
13+ "github.com/picosh/pico/pkg/db"
14+ "github.com/picosh/pico/pkg/pssh"
15+ sendutils "github.com/picosh/pico/pkg/send/utils"
16 "github.com/picosh/utils"
17 )
18
19-func getUser(s ssh.Session, dbpool pgsdb.PgsDB) (*db.User, error) {
20+func getUser(s *pssh.SSHServerConnSession, dbpool pgsdb.PgsDB) (*db.User, error) {
21 if s.PublicKey() == nil {
22 return nil, fmt.Errorf("key not found")
23 }
24@@ -46,7 +43,7 @@ func (i *arrayFlags) Set(value string) error {
25 return nil
26 }
27
28-func flagSet(cmdName string, sesh ssh.Session) (*flag.FlagSet, *bool) {
29+func flagSet(cmdName string, sesh *pssh.SSHServerConnSession) (*flag.FlagSet, *bool) {
30 cmd := flag.NewFlagSet(cmdName, flag.ContinueOnError)
31 cmd.SetOutput(sesh)
32 write := cmd.Bool("write", false, "apply changes")
33@@ -63,18 +60,17 @@ func flagCheck(cmd *flag.FlagSet, posArg string, cmdArgs []string) bool {
34 return true
35 }
36
37-func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
38+func Middleware(handler *UploadAssetHandler) pssh.SSHServerMiddleware {
39 dbpool := handler.Cfg.DB
40 log := handler.Cfg.Logger
41 cfg := handler.Cfg
42 store := handler.Cfg.Storage
43
44- return func(next ssh.Handler) ssh.Handler {
45- return func(sesh ssh.Session) {
46+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
47+ return func(sesh *pssh.SSHServerConnSession) error {
48 args := sesh.Command()
49 if len(args) == 0 {
50- next(sesh)
51- return
52+ return next(sesh)
53 }
54
55 // default width and height when no pty
56@@ -89,11 +85,12 @@ func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
57 user, err := getUser(sesh, dbpool)
58 if err != nil {
59 sendutils.ErrorHandler(sesh, err)
60- return
61+ return err
62 }
63
64- renderer := bm.MakeRenderer(sesh)
65- renderer.SetColorProfile(termenv.TrueColor)
66+ // renderer := bm.MakeRenderer(sesh)
67+ // renderer.SetColorProfile(termenv.TrueColor)
68+ // styles := common.DefaultStyles(renderer)
69
70 opts := Cmd{
71 Session: sesh,
72@@ -102,33 +99,33 @@ func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
73 Log: log,
74 Dbpool: dbpool,
75 Write: false,
76- Width: width,
77- Height: height,
78- Cfg: handler.Cfg,
79+ // Styles: styles,
80+ Width: width,
81+ Height: height,
82+ Cfg: handler.Cfg,
83 }
84
85 cmd := strings.TrimSpace(args[0])
86 if len(args) == 1 {
87 if cmd == "help" {
88 opts.help()
89- return
90+ return nil
91 } else if cmd == "stats" {
92 err := opts.stats(cfg.MaxSize)
93 opts.bail(err)
94- return
95+ return err
96 } else if cmd == "ls" {
97 err := opts.ls()
98 opts.bail(err)
99- return
100+ return err
101 } else if cmd == "cache-all" {
102 opts.Write = true
103 err := opts.cacheAll()
104 opts.notice()
105 opts.bail(err)
106- return
107+ return err
108 } else {
109- next(sesh)
110- return
111+ return next(sesh)
112 }
113 }
114
115@@ -145,12 +142,12 @@ func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
116 if cmd == "fzf" {
117 err := opts.fzf(projectName)
118 opts.bail(err)
119- return
120+ return err
121 } else if cmd == "link" {
122 linkCmd, write := flagSet("link", sesh)
123 linkTo := linkCmd.String("to", "", "symbolic link to this project")
124 if !flagCheck(linkCmd, projectName, cmdArgs) {
125- return
126+ return nil
127 }
128 opts.Write = *write
129
130@@ -159,7 +156,7 @@ func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
131 "must provide `--to` flag",
132 )
133 opts.bail(err)
134- return
135+ return err
136 }
137
138 err := opts.link(projectName, *linkTo)
139@@ -167,67 +164,67 @@ func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
140 if err != nil {
141 opts.bail(err)
142 }
143- return
144+ return err
145 } else if cmd == "unlink" {
146 unlinkCmd, write := flagSet("unlink", sesh)
147 if !flagCheck(unlinkCmd, projectName, cmdArgs) {
148- return
149+ return nil
150 }
151 opts.Write = *write
152
153 err := opts.unlink(projectName)
154 opts.notice()
155 opts.bail(err)
156- return
157+ return err
158 } else if cmd == "depends" {
159 err := opts.depends(projectName)
160 opts.bail(err)
161- return
162+ return err
163 } else if cmd == "retain" {
164 retainCmd, write := flagSet("retain", sesh)
165 retainNum := retainCmd.Int("n", 3, "latest number of projects to keep")
166 if !flagCheck(retainCmd, projectName, cmdArgs) {
167- return
168+ return nil
169 }
170 opts.Write = *write
171
172 err := opts.prune(projectName, *retainNum)
173 opts.notice()
174 opts.bail(err)
175- return
176+ return err
177 } else if cmd == "prune" {
178 pruneCmd, write := flagSet("prune", sesh)
179 if !flagCheck(pruneCmd, projectName, cmdArgs) {
180- return
181+ return nil
182 }
183 opts.Write = *write
184
185 err := opts.prune(projectName, 0)
186 opts.notice()
187 opts.bail(err)
188- return
189+ return err
190 } else if cmd == "rm" {
191 rmCmd, write := flagSet("rm", sesh)
192 if !flagCheck(rmCmd, projectName, cmdArgs) {
193- return
194+ return nil
195 }
196 opts.Write = *write
197
198 err := opts.rm(projectName)
199 opts.notice()
200 opts.bail(err)
201- return
202+ return err
203 } else if cmd == "cache" {
204 cacheCmd, write := flagSet("cache", sesh)
205 if !flagCheck(cacheCmd, projectName, cmdArgs) {
206- return
207+ return nil
208 }
209 opts.Write = *write
210
211 err := opts.cache(projectName)
212 opts.notice()
213 opts.bail(err)
214- return
215+ return err
216 } else if cmd == "acl" {
217 aclCmd, write := flagSet("acl", sesh)
218 aclType := aclCmd.String("type", "", "access type: public, pico, pubkeys")
219@@ -238,7 +235,7 @@ func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
220 "list of pico usernames or sha256 public keys, delimited by commas",
221 )
222 if !flagCheck(aclCmd, projectName, cmdArgs) {
223- return
224+ return nil
225 }
226 opts.Write = *write
227
228@@ -248,15 +245,15 @@ func WishMiddleware(handler *UploadAssetHandler) wish.Middleware {
229 *aclType,
230 )
231 opts.bail(err)
232- return
233+ return err
234 }
235
236 err := opts.acl(projectName, *aclType, acls)
237 opts.notice()
238 opts.bail(err)
239+ return err
240 } else {
241- next(sesh)
242- return
243+ return next(sesh)
244 }
245 }
246 }
R pgs/config.go =>
pkg/apps/pgs/config.go
+2,
-2
1@@ -6,8 +6,8 @@ import (
2 "path/filepath"
3 "time"
4
5- pgsdb "github.com/picosh/pico/pgs/db"
6- "github.com/picosh/pico/shared/storage"
7+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
8+ "github.com/picosh/pico/pkg/shared/storage"
9 "github.com/picosh/utils"
10 )
11
R pgs/db/db.go =>
pkg/apps/pgs/db/db.go
+1,
-1
1@@ -1,6 +1,6 @@
2 package pgsdb
3
4-import "github.com/picosh/pico/db"
5+import "github.com/picosh/pico/pkg/db"
6
7 type PgsDB interface {
8 FindUser(userID string) (*db.User, error)
R pgs/db/memory.go =>
pkg/apps/pgs/db/memory.go
+1,
-1
1@@ -6,7 +6,7 @@ import (
2 "time"
3
4 "github.com/google/uuid"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils"
8 )
9
R pgs/db/postgres.go =>
pkg/apps/pgs/db/postgres.go
+1,
-1
1@@ -7,7 +7,7 @@ import (
2
3 "github.com/jmoiron/sqlx"
4 _ "github.com/lib/pq"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils"
8 )
9
R pgs/fs.go =>
pkg/apps/pgs/fs.go
+0,
-0
R pgs/header.go =>
pkg/apps/pgs/header.go
+0,
-0
R pgs/header_test.go =>
pkg/apps/pgs/header_test.go
+0,
-0
R pgs/html/base.layout.tmpl =>
pkg/apps/pgs/html/base.layout.tmpl
+0,
-0
R pgs/html/marketing.page.tmpl =>
pkg/apps/pgs/html/marketing.page.tmpl
+0,
-0
R pgs/public/card.png =>
pkg/apps/pgs/public/card.png
+0,
-0
R pgs/public/favicon-16x16.png =>
pkg/apps/pgs/public/favicon-16x16.png
+0,
-0
R pgs/public/favicon.ico =>
pkg/apps/pgs/public/favicon.ico
+0,
-0
R pgs/public/main.css =>
pkg/apps/pgs/public/main.css
+0,
-0
R pgs/public/robots.txt =>
pkg/apps/pgs/public/robots.txt
+0,
-0
R pgs/redirect.go =>
pkg/apps/pgs/redirect.go
+0,
-0
R pgs/redirect_test.go =>
pkg/apps/pgs/redirect_test.go
+0,
-0
+98,
-0
1@@ -0,0 +1,98 @@
2+package pgs
3+
4+import (
5+ "context"
6+ "os"
7+ "os/signal"
8+ "syscall"
9+
10+ "github.com/picosh/pico/pkg/pssh"
11+ "github.com/picosh/pico/pkg/send/auth"
12+ "github.com/picosh/pico/pkg/send/list"
13+ "github.com/picosh/pico/pkg/send/pipe"
14+ "github.com/picosh/pico/pkg/send/protocols/rsync"
15+ "github.com/picosh/pico/pkg/send/protocols/scp"
16+ "github.com/picosh/pico/pkg/send/protocols/sftp"
17+ "github.com/picosh/pico/pkg/shared"
18+ "github.com/picosh/pico/pkg/tunkit"
19+ "github.com/picosh/utils"
20+)
21+
22+func StartSshServer(cfg *PgsConfig, killCh chan error) {
23+ host := utils.GetEnv("PGS_HOST", "0.0.0.0")
24+ port := utils.GetEnv("PGS_SSH_PORT", "2222")
25+ promPort := utils.GetEnv("PGS_PROM_PORT", "9222")
26+ logger := cfg.Logger
27+
28+ ctx, cancel := context.WithCancel(context.Background())
29+ defer cancel()
30+
31+ cacheClearingQueue := make(chan string, 100)
32+ handler := NewUploadAssetHandler(
33+ cfg,
34+ cacheClearingQueue,
35+ ctx,
36+ )
37+
38+ sshAuth := shared.NewSshAuthHandler(cfg.DB, logger)
39+
40+ webTunnel := &tunkit.WebTunnelHandler{
41+ Logger: logger,
42+ HttpHandler: createHttpHandler(cfg),
43+ }
44+
45+ // Create a new SSH server
46+ server, err := pssh.NewSSHServerWithConfig(
47+ ctx,
48+ logger,
49+ "pgs-ssh",
50+ host,
51+ port,
52+ promPort,
53+ sshAuth.PubkeyAuthHandler,
54+ []pssh.SSHServerMiddleware{
55+ pipe.Middleware(handler, ""),
56+ list.Middleware(handler),
57+ scp.Middleware(handler),
58+ rsync.Middleware(handler),
59+ auth.Middleware(handler),
60+ pssh.PtyMdw(pssh.DeprecatedNotice()),
61+ Middleware(handler),
62+ pssh.LogMiddleware(handler, handler.Cfg.DB),
63+ },
64+ []pssh.SSHServerMiddleware{
65+ sftp.Middleware(handler),
66+ pssh.LogMiddleware(handler, handler.Cfg.DB),
67+ },
68+ map[string]pssh.SSHServerChannelMiddleware{
69+ "direct-tcpip": tunkit.LocalForwardHandler(webTunnel),
70+ },
71+ )
72+
73+ if err != nil {
74+ logger.Error("failed to create ssh server", "err", err.Error())
75+ os.Exit(1)
76+ }
77+
78+ done := make(chan os.Signal, 1)
79+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
80+ logger.Info("Starting SSH server", "addr", server.Config.ListenAddr)
81+ go func() {
82+ if err = server.ListenAndServe(); err != nil {
83+ logger.Error("serve", "err", err.Error())
84+ os.Exit(1)
85+ }
86+ }()
87+
88+ exit := func() {
89+ logger.Info("stopping ssh server")
90+ cancel()
91+ }
92+
93+ select {
94+ case <-killCh:
95+ exit()
96+ case <-done:
97+ exit()
98+ }
99+}
R pgs/ssh_test.go =>
pkg/apps/pgs/ssh_test.go
+5,
-4
1@@ -14,9 +14,9 @@ import (
2 "testing"
3 "time"
4
5- "github.com/picosh/pico/db"
6- pgsdb "github.com/picosh/pico/pgs/db"
7- "github.com/picosh/pico/shared/storage"
8+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
9+ "github.com/picosh/pico/pkg/db"
10+ "github.com/picosh/pico/pkg/shared/storage"
11 "github.com/picosh/utils"
12 "github.com/pkg/sftp"
13 "github.com/prometheus/client_golang/prometheus"
14@@ -408,7 +408,8 @@ func WriteFileWithSftp(cfg *PgsConfig, conn *ssh.Client) (*os.FileInfo, error) {
15 cfg.Logger.Error("could not write to file", "err", err)
16 return nil, err
17 }
18- f.Close()
19+
20+ cfg.Logger.Info("closing", "err", f.Close())
21
22 // check it's there
23 fi, err := client.Lstat("test/hello.txt")
R pgs/tunnel.go =>
pkg/apps/pgs/tunnel.go
+6,
-5
1@@ -6,9 +6,10 @@ import (
2 "strings"
3 "time"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/pssh"
10+ "github.com/picosh/pico/pkg/shared"
11+ "golang.org/x/crypto/ssh"
12 )
13
14 type TunnelWebRouter struct {
15@@ -33,7 +34,7 @@ func (web *TunnelWebRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
16 web.UserRouter.ServeHTTP(w, r.WithContext(ctx))
17 }
18
19-type CtxHttpBridge = func(ssh.Context) http.Handler
20+type CtxHttpBridge = func(*pssh.SSHServerConnSession) http.Handler
21
22 func getInfoFromUser(user string) (string, string) {
23 if strings.Contains(user, "__") {
24@@ -45,7 +46,7 @@ func getInfoFromUser(user string) (string, string) {
25 }
26
27 func createHttpHandler(cfg *PgsConfig) CtxHttpBridge {
28- return func(ctx ssh.Context) http.Handler {
29+ return func(ctx *pssh.SSHServerConnSession) http.Handler {
30 logger := cfg.Logger
31 asUser, subdomain := getInfoFromUser(ctx.User())
32 log := logger.With(
R pgs/uploader.go =>
pkg/apps/pgs/uploader.go
+41,
-40
1@@ -15,15 +15,13 @@ import (
2 "sync"
3 "time"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7- "github.com/picosh/pico/db"
8- pgsdb "github.com/picosh/pico/pgs/db"
9- "github.com/picosh/pico/shared"
10- wsh "github.com/picosh/pico/wish"
11- "github.com/picosh/pobj"
12- sst "github.com/picosh/pobj/storage"
13- sendutils "github.com/picosh/send/utils"
14+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
15+ "github.com/picosh/pico/pkg/db"
16+ "github.com/picosh/pico/pkg/pobj"
17+ sst "github.com/picosh/pico/pkg/pobj/storage"
18+ "github.com/picosh/pico/pkg/pssh"
19+ sendutils "github.com/picosh/pico/pkg/send/utils"
20+ "github.com/picosh/pico/pkg/shared"
21 "github.com/picosh/utils"
22 ignore "github.com/sabhiram/go-gitignore"
23 )
24@@ -37,7 +35,7 @@ type DenyList struct {
25 Denylist string
26 }
27
28-func getDenylist(s ssh.Session) *DenyList {
29+func getDenylist(s *pssh.SSHServerConnSession) *DenyList {
30 v := s.Context().Value(ctxDenylistKey{})
31 if v == nil {
32 return nil
33@@ -46,11 +44,11 @@ func getDenylist(s ssh.Session) *DenyList {
34 return denylist
35 }
36
37-func setDenylist(s ssh.Session, denylist string) {
38- s.Context().SetValue(ctxDenylistKey{}, &DenyList{Denylist: denylist})
39+func setDenylist(s *pssh.SSHServerConnSession, denylist string) {
40+ s.SetValue(ctxDenylistKey{}, &DenyList{Denylist: denylist})
41 }
42
43-func getProject(s ssh.Session) *db.Project {
44+func getProject(s *pssh.SSHServerConnSession) *db.Project {
45 v := s.Context().Value(ctxProjectKey{})
46 if v == nil {
47 return nil
48@@ -59,11 +57,11 @@ func getProject(s ssh.Session) *db.Project {
49 return project
50 }
51
52-func setProject(s ssh.Session, project *db.Project) {
53- s.Context().SetValue(ctxProjectKey{}, project)
54+func setProject(s *pssh.SSHServerConnSession, project *db.Project) {
55+ s.SetValue(ctxProjectKey{}, project)
56 }
57
58-func getBucket(s ssh.Session) (sst.Bucket, error) {
59+func getBucket(s *pssh.SSHServerConnSession) (sst.Bucket, error) {
60 bucket := s.Context().Value(ctxBucketKey{}).(sst.Bucket)
61 if bucket.Name == "" {
62 return bucket, fmt.Errorf("bucket not set on `ssh.Context()` for connection")
63@@ -71,11 +69,11 @@ func getBucket(s ssh.Session) (sst.Bucket, error) {
64 return bucket, nil
65 }
66
67-func getStorageSize(s ssh.Session) uint64 {
68+func getStorageSize(s *pssh.SSHServerConnSession) uint64 {
69 return s.Context().Value(ctxStorageSizeKey{}).(uint64)
70 }
71
72-func incrementStorageSize(s ssh.Session, fileSize int64) uint64 {
73+func incrementStorageSize(s *pssh.SSHServerConnSession, fileSize int64) uint64 {
74 curSize := getStorageSize(s)
75 var nextStorageSize uint64
76 if fileSize < 0 {
77@@ -83,7 +81,7 @@ func incrementStorageSize(s ssh.Session, fileSize int64) uint64 {
78 } else {
79 nextStorageSize = curSize + uint64(fileSize)
80 }
81- s.Context().SetValue(ctxStorageSizeKey{}, nextStorageSize)
82+ s.SetValue(ctxStorageSizeKey{}, nextStorageSize)
83 return nextStorageSize
84 }
85
86@@ -113,13 +111,13 @@ func NewUploadAssetHandler(cfg *PgsConfig, ch chan string, ctx context.Context)
87 }
88 }
89
90-func (h *UploadAssetHandler) GetLogger(s ssh.Session) *slog.Logger {
91- return wsh.GetLogger(s)
92+func (h *UploadAssetHandler) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
93+ return pssh.GetLogger(s)
94 }
95
96-func (h *UploadAssetHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
97- logger := wsh.GetLogger(s)
98- user := wsh.GetUser(s)
99+func (h *UploadAssetHandler) Read(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
100+ logger := pssh.GetLogger(s)
101+ user := pssh.GetUser(s)
102
103 if user == nil {
104 err := fmt.Errorf("could not get user from ctx")
105@@ -153,11 +151,11 @@ func (h *UploadAssetHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os
106 return fileInfo, reader, nil
107 }
108
109-func (h *UploadAssetHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
110+func (h *UploadAssetHandler) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
111 var fileList []os.FileInfo
112
113- logger := wsh.GetLogger(s)
114- user := wsh.GetUser(s)
115+ logger := pssh.GetLogger(s)
116+ user := pssh.GetUser(s)
117
118 if user == nil {
119 err := fmt.Errorf("could not get user from ctx")
120@@ -201,9 +199,9 @@ func (h *UploadAssetHandler) List(s ssh.Session, fpath string, isDir bool, recur
121 return fileList, nil
122 }
123
124-func (h *UploadAssetHandler) Validate(s ssh.Session) error {
125- logger := wsh.GetLogger(s)
126- user := wsh.GetUser(s)
127+func (h *UploadAssetHandler) Validate(s *pssh.SSHServerConnSession) error {
128+ logger := pssh.GetLogger(s)
129+ user := pssh.GetUser(s)
130
131 if user == nil {
132 err := fmt.Errorf("could not get user from ctx")
133@@ -217,14 +215,14 @@ func (h *UploadAssetHandler) Validate(s ssh.Session) error {
134 return err
135 }
136
137- s.Context().SetValue(ctxBucketKey{}, bucket)
138+ s.SetValue(ctxBucketKey{}, bucket)
139
140 totalStorageSize, err := h.Cfg.Storage.GetBucketQuota(bucket)
141 if err != nil {
142 return err
143 }
144
145- s.Context().SetValue(ctxStorageSizeKey{}, totalStorageSize)
146+ s.SetValue(ctxStorageSizeKey{}, totalStorageSize)
147
148 logger.Info(
149 "bucket size",
150@@ -279,9 +277,9 @@ func findPlusFF(dbpool pgsdb.PgsDB, cfg *PgsConfig, userID string) *db.FeatureFl
151 return ff
152 }
153
154-func (h *UploadAssetHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
155- logger := wsh.GetLogger(s)
156- user := wsh.GetUser(s)
157+func (h *UploadAssetHandler) Write(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (string, error) {
158+ logger := pssh.GetLogger(s)
159+ user := pssh.GetUser(s)
160
161 if user == nil {
162 err := fmt.Errorf("could not get user from ctx")
163@@ -380,7 +378,10 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (s
164 remaining := int64(storageMax) - int64(curStorageSize)
165 sizeRemaining := min(remaining+curFileSize, fileMax)
166 if sizeRemaining <= 0 {
167- wish.Fatalln(s, "storage quota reached")
168+ fmt.Fprintln(s.Stderr(), "storage quota reached")
169+ fmt.Fprintf(s.Stderr(), "\r")
170+ _ = s.Exit(1)
171+ _ = s.Close()
172 return "", fmt.Errorf("storage quota reached")
173 }
174 logger = logger.With(
175@@ -442,9 +443,9 @@ func isSpecialFile(entry *sendutils.FileEntry) bool {
176 return fname == "_headers" || fname == "_redirects"
177 }
178
179-func (h *UploadAssetHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
180- logger := wsh.GetLogger(s)
181- user := wsh.GetUser(s)
182+func (h *UploadAssetHandler) Delete(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) error {
183+ logger := pssh.GetLogger(s)
184+ user := pssh.GetUser(s)
185
186 if user == nil {
187 err := fmt.Errorf("could not get user from ctx")
188@@ -537,7 +538,7 @@ func (h *UploadAssetHandler) validateAsset(data *FileData) (bool, error) {
189 return true, nil
190 }
191
192-func (h *UploadAssetHandler) writeAsset(s ssh.Session, reader io.Reader, data *FileData) (int64, error) {
193+func (h *UploadAssetHandler) writeAsset(s *pssh.SSHServerConnSession, reader io.Reader, data *FileData) (int64, error) {
194 assetFilepath := shared.GetAssetFileName(data.FileEntry)
195
196 logger := h.GetLogger(s)
R pgs/web.go =>
pkg/apps/pgs/web.go
+4,
-4
1@@ -20,10 +20,10 @@ import (
2 "github.com/darkweak/souin/plugins/souin/storages"
3 "github.com/darkweak/storages/core"
4 "github.com/gorilla/feeds"
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/shared"
7- "github.com/picosh/pico/shared/storage"
8- sst "github.com/picosh/pobj/storage"
9+ "github.com/picosh/pico/pkg/db"
10+ sst "github.com/picosh/pico/pkg/pobj/storage"
11+ "github.com/picosh/pico/pkg/shared"
12+ "github.com/picosh/pico/pkg/shared/storage"
13 "github.com/prometheus/client_golang/prometheus/promhttp"
14 "google.golang.org/protobuf/proto"
15 )
R pgs/web_asset_handler.go =>
pkg/apps/pgs/web_asset_handler.go
+2,
-2
1@@ -14,8 +14,8 @@ import (
2 "net/http/httputil"
3 _ "net/http/pprof"
4
5- "github.com/picosh/pico/shared/storage"
6- sst "github.com/picosh/pobj/storage"
7+ sst "github.com/picosh/pico/pkg/pobj/storage"
8+ "github.com/picosh/pico/pkg/shared/storage"
9 )
10
11 type ApiAssetHandler struct {
R pgs/web_cache.go =>
pkg/apps/pgs/web_cache.go
+1,
-1
1@@ -6,7 +6,7 @@ import (
2 "log/slog"
3 "time"
4
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 "github.com/picosh/utils/pipe"
8 )
9
R pgs/web_test.go =>
pkg/apps/pgs/web_test.go
+4,
-4
1@@ -10,10 +10,10 @@ import (
2 "testing"
3 "time"
4
5- pgsdb "github.com/picosh/pico/pgs/db"
6- "github.com/picosh/pico/shared"
7- "github.com/picosh/pico/shared/storage"
8- sst "github.com/picosh/pobj/storage"
9+ pgsdb "github.com/picosh/pico/pkg/apps/pgs/db"
10+ sst "github.com/picosh/pico/pkg/pobj/storage"
11+ "github.com/picosh/pico/pkg/shared"
12+ "github.com/picosh/pico/pkg/shared/storage"
13 )
14
15 type ApiExample struct {
R pico/cli.go =>
pkg/apps/pico/cli.go
+33,
-34
1@@ -8,16 +8,15 @@ import (
2 "log/slog"
3 "strings"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7- "github.com/picosh/pico/db"
8- "github.com/picosh/pico/shared"
9+ "github.com/picosh/pico/pkg/db"
10+ "github.com/picosh/pico/pkg/pssh"
11+ "github.com/picosh/pico/pkg/shared"
12 "github.com/picosh/utils"
13
14 pipeLogger "github.com/picosh/utils/pipe/log"
15 )
16
17-func getUser(s ssh.Session, dbpool db.DB) (*db.User, error) {
18+func getUser(s *pssh.SSHServerConnSession, dbpool db.DB) (*db.User, error) {
19 if s.PublicKey() == nil {
20 return nil, fmt.Errorf("key not found")
21 }
22@@ -38,7 +37,7 @@ func getUser(s ssh.Session, dbpool db.DB) (*db.User, error) {
23
24 type Cmd struct {
25 User *db.User
26- SshSession ssh.Session
27+ SshSession *pssh.SSHServerConnSession
28 Session utils.CmdSession
29 Log *slog.Logger
30 Dbpool db.DB
31@@ -77,7 +76,7 @@ func (c *Cmd) logs(ctx context.Context) error {
32 user := utils.AnyToStr(parsedData, "user")
33 userId := utils.AnyToStr(parsedData, "userId")
34 if user == c.User.Name || userId == c.User.ID {
35- wish.Println(c.SshSession, line)
36+ fmt.Fprintln(c.SshSession, line)
37 }
38 }
39 return scanner.Err()
40@@ -88,34 +87,34 @@ type CliHandler struct {
41 Logger *slog.Logger
42 }
43
44-func WishMiddleware(handler *CliHandler) wish.Middleware {
45+func Middleware(handler *CliHandler) pssh.SSHServerMiddleware {
46 dbpool := handler.DBPool
47 log := handler.Logger
48
49- return func(next ssh.Handler) ssh.Handler {
50- return func(sesh ssh.Session) {
51+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
52+ return func(sesh *pssh.SSHServerConnSession) error {
53 args := sesh.Command()
54 if len(args) == 0 {
55- next(sesh)
56- return
57+ return next(sesh)
58 }
59
60 user, err := getUser(sesh, dbpool)
61 if err != nil {
62- wish.Errorf(sesh, "detected ssh command: %s\n", args)
63+ fmt.Fprintf(sesh.Stderr(), "detected ssh command: %s\n", args)
64 s := fmt.Errorf("error: you need to create an account before using the remote cli: %w", err)
65- wish.Fatalln(sesh, s)
66- return
67+ sesh.Fatal(s)
68+ return s
69 }
70
71 if len(args) > 0 && args[0] == "chat" {
72 _, _, hasPty := sesh.Pty()
73 if !hasPty {
74- wish.Fatalln(
75- sesh,
76- "In order to render chat you need to enable PTY with the `ssh -t` flag",
77+ err := fmt.Errorf(
78+ "in order to render chat you need to enable PTY with the `ssh -t` flag",
79 )
80- return
81+
82+ sesh.Fatal(err)
83+ return err
84 }
85
86 ff, err := dbpool.FindFeatureForUser(user.ID, "plus")
87@@ -124,29 +123,30 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
88 ff, err = dbpool.FindFeatureForUser(user.ID, "bouncer")
89 if err != nil {
90 handler.Logger.Error("Unable to find bouncer feature flag", "err", err, "user", user, "command", args)
91- wish.Fatalln(sesh, "Unable to find plus or bouncer feature flag")
92- return
93+ sesh.Fatal(err)
94+ return err
95 }
96 }
97
98 if ff == nil {
99- wish.Fatalln(sesh, "Unable to find plus or bouncer feature flag")
100- return
101+ err = fmt.Errorf("unable to find plus or bouncer feature flag")
102+ sesh.Fatal(err)
103+ return err
104 }
105
106 pass, err := dbpool.UpsertToken(user.ID, "pico-chat")
107 if err != nil {
108- wish.Fatalln(sesh, err)
109- return
110+ sesh.Fatal(err)
111+ return err
112 }
113 app, err := shared.NewSenpaiApp(sesh, user.Name, pass)
114 if err != nil {
115- wish.Fatalln(sesh, err)
116- return
117+ sesh.Fatal(err)
118+ return err
119 }
120 app.Run()
121 app.Close()
122- return
123+ return err
124 }
125
126 opts := Cmd{
127@@ -162,20 +162,19 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
128 if len(args) == 1 {
129 if cmd == "help" {
130 opts.help()
131- return
132+ return nil
133 } else if cmd == "logs" {
134 err = opts.logs(sesh.Context())
135 if err != nil {
136- wish.Fatalln(sesh, err)
137+ sesh.Fatal(err)
138 }
139- return
140+ return nil
141 } else {
142- next(sesh)
143- return
144+ return next(sesh)
145 }
146 }
147
148- next(sesh)
149+ return next(sesh)
150 }
151 }
152 }
R pico/config.go =>
pkg/apps/pico/config.go
+1,
-1
1@@ -1,7 +1,7 @@
2 package pico
3
4 import (
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 "github.com/picosh/utils"
8 )
9
R pico/file_handler.go =>
pkg/apps/pico/file_handler.go
+28,
-29
1@@ -11,13 +11,12 @@ import (
2 "strings"
3 "time"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7- "github.com/picosh/pico/db"
8- "github.com/picosh/pico/shared"
9- wsh "github.com/picosh/pico/wish"
10- sendutils "github.com/picosh/send/utils"
11+ "github.com/picosh/pico/pkg/db"
12+ "github.com/picosh/pico/pkg/pssh"
13+ sendutils "github.com/picosh/pico/pkg/send/utils"
14+ "github.com/picosh/pico/pkg/shared"
15 "github.com/picosh/utils"
16+ "golang.org/x/crypto/ssh"
17 )
18
19 type UploadHandler struct {
20@@ -52,13 +51,13 @@ func (h *UploadHandler) getAuthorizedKeyFile(user *db.User) (*sendutils.VirtualF
21 return fileInfo, text, nil
22 }
23
24-func (h *UploadHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
25+func (h *UploadHandler) Delete(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) error {
26 return errors.New("unsupported")
27 }
28
29-func (h *UploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
30- logger := wsh.GetLogger(s)
31- user := wsh.GetUser(s)
32+func (h *UploadHandler) Read(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
33+ logger := pssh.GetLogger(s)
34+ user := pssh.GetUser(s)
35
36 if user == nil {
37 err := fmt.Errorf("could not get user from ctx")
38@@ -84,11 +83,11 @@ func (h *UploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.File
39 return nil, nil, os.ErrNotExist
40 }
41
42-func (h *UploadHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
43+func (h *UploadHandler) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
44 var fileList []os.FileInfo
45
46- logger := wsh.GetLogger(s)
47- user := wsh.GetUser(s)
48+ logger := pssh.GetLogger(s)
49+ user := pssh.GetUser(s)
50
51 if user == nil {
52 err := fmt.Errorf("could not get user from ctx")
53@@ -127,11 +126,11 @@ func (h *UploadHandler) List(s ssh.Session, fpath string, isDir bool, recursive
54 return fileList, nil
55 }
56
57-func (h *UploadHandler) GetLogger(s ssh.Session) *slog.Logger {
58- return wsh.GetLogger(s)
59+func (h *UploadHandler) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
60+ return pssh.GetLogger(s)
61 }
62
63-func (h *UploadHandler) Validate(s ssh.Session) error {
64+func (h *UploadHandler) Validate(s *pssh.SSHServerConnSession) error {
65 var err error
66 key, err := sendutils.KeyText(s)
67 if err != nil {
68@@ -169,7 +168,7 @@ func authorizedKeysDiff(keyInUse ssh.PublicKey, curKeys []KeyWithId, nextKeys []
69 for _, nk := range nextKeys {
70 found := false
71 for _, ck := range curKeys {
72- if ssh.KeysEqual(nk.Pk, ck.Pk) {
73+ if pssh.KeysEqual(nk.Pk, ck.Pk) {
74 found = true
75
76 // update the comment field
77@@ -189,13 +188,13 @@ func authorizedKeysDiff(keyInUse ssh.PublicKey, curKeys []KeyWithId, nextKeys []
78 for _, ck := range curKeys {
79 // we never want to remove the key that's in the current ssh session
80 // in an effort to avoid mistakenly removing their current key
81- if ssh.KeysEqual(ck.Pk, keyInUse) {
82+ if pssh.KeysEqual(ck.Pk, keyInUse) {
83 continue
84 }
85
86 found := false
87 for _, nk := range nextKeys {
88- if ssh.KeysEqual(ck.Pk, nk.Pk) {
89+ if pssh.KeysEqual(ck.Pk, nk.Pk) {
90 found = true
91 break
92 }
93@@ -212,7 +211,7 @@ func authorizedKeysDiff(keyInUse ssh.PublicKey, curKeys []KeyWithId, nextKeys []
94 }
95 }
96
97-func (h *UploadHandler) ProcessAuthorizedKeys(text []byte, logger *slog.Logger, user *db.User, s ssh.Session) error {
98+func (h *UploadHandler) ProcessAuthorizedKeys(text []byte, logger *slog.Logger, user *db.User, s *pssh.SSHServerConnSession) error {
99 logger.Info("processing new authorized_keys")
100 dbpool := h.DBPool
101
102@@ -245,12 +244,12 @@ func (h *UploadHandler) ProcessAuthorizedKeys(text []byte, logger *slog.Logger,
103 for _, pk := range diff.Add {
104 key := utils.KeyForKeyText(pk.Pk)
105
106- wish.Errorf(s, "adding pubkey (%s)\n", key)
107+ fmt.Fprintf(s.Stderr(), "adding pubkey (%s)\n", key)
108 logger.Info("adding pubkey", "pubkey", key)
109
110 err = dbpool.InsertPublicKey(user.ID, key, pk.Comment, nil)
111 if err != nil {
112- wish.Errorf(s, "error: could not insert pubkey: %s (%s)\n", err.Error(), key)
113+ fmt.Fprintf(s.Stderr(), "error: could not insert pubkey: %s (%s)\n", err.Error(), key)
114 logger.Error("could not insert pubkey", "err", err.Error())
115 }
116 }
117@@ -258,7 +257,7 @@ func (h *UploadHandler) ProcessAuthorizedKeys(text []byte, logger *slog.Logger,
118 for _, pk := range diff.Update {
119 key := utils.KeyForKeyText(pk.Pk)
120
121- wish.Errorf(s, "updating pubkey with comment: %s (%s)\n", pk.Comment, key)
122+ fmt.Fprintf(s.Stderr(), "updating pubkey with comment: %s (%s)\n", pk.Comment, key)
123 logger.Info(
124 "updating pubkey with comment",
125 "pubkey", key,
126@@ -267,18 +266,18 @@ func (h *UploadHandler) ProcessAuthorizedKeys(text []byte, logger *slog.Logger,
127
128 _, err = dbpool.UpdatePublicKey(pk.ID, pk.Comment)
129 if err != nil {
130- wish.Errorf(s, "error: could not update pubkey: %s (%s)\n", err.Error(), key)
131+ fmt.Fprintf(s.Stderr(), "error: could not update pubkey: %s (%s)\n", err.Error(), key)
132 logger.Error("could not update pubkey", "err", err.Error(), "key", key)
133 }
134 }
135
136 if len(diff.Rm) > 0 {
137- wish.Errorf(s, "removing pubkeys: %s\n", diff.Rm)
138+ fmt.Fprintf(s.Stderr(), "removing pubkeys: %s\n", diff.Rm)
139 logger.Info("removing pubkeys", "pubkeys", diff.Rm)
140
141 err = dbpool.RemoveKeys(diff.Rm)
142 if err != nil {
143- wish.Errorf(s, "error: could not rm pubkeys: %s\n", err.Error())
144+ fmt.Fprintf(s.Stderr(), "error: could not rm pubkeys: %s\n", err.Error())
145 logger.Error("could not remove pubkey", "err", err.Error())
146 }
147 }
148@@ -286,9 +285,9 @@ func (h *UploadHandler) ProcessAuthorizedKeys(text []byte, logger *slog.Logger,
149 return nil
150 }
151
152-func (h *UploadHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
153- logger := wsh.GetLogger(s)
154- user := wsh.GetUser(s)
155+func (h *UploadHandler) Write(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (string, error) {
156+ logger := pssh.GetLogger(s)
157+ user := pssh.GetUser(s)
158
159 if user == nil {
160 err := fmt.Errorf("could not get user from ctx")
R pico/html/.gitkeep =>
pkg/apps/pico/html/.gitkeep
+0,
-0
+136,
-0
1@@ -0,0 +1,136 @@
2+package pico
3+
4+import (
5+ "context"
6+ "os"
7+ "os/signal"
8+ "syscall"
9+
10+ "git.sr.ht/~rockorager/vaxis"
11+ "github.com/picosh/pico/pkg/db/postgres"
12+ "github.com/picosh/pico/pkg/pssh"
13+ "github.com/picosh/pico/pkg/send/auth"
14+ "github.com/picosh/pico/pkg/send/list"
15+ "github.com/picosh/pico/pkg/send/pipe"
16+ "github.com/picosh/pico/pkg/send/protocols/rsync"
17+ "github.com/picosh/pico/pkg/send/protocols/scp"
18+ "github.com/picosh/pico/pkg/send/protocols/sftp"
19+ "github.com/picosh/pico/pkg/shared"
20+ "github.com/picosh/pico/pkg/tui"
21+ "github.com/picosh/utils"
22+ "golang.org/x/crypto/ssh"
23+)
24+
25+func createTui(shrd *tui.SharedModel) pssh.SSHServerMiddleware {
26+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
27+ return func(sesh *pssh.SSHServerConnSession) error {
28+ vty, err := shared.NewVConsole(sesh)
29+ if err != nil {
30+ return err
31+ }
32+ opts := vaxis.Options{
33+ WithConsole: vty,
34+ }
35+ tui.NewTui(opts, shrd)
36+ return nil
37+ }
38+ }
39+}
40+
41+func StartSshServer() {
42+ appName := "pico-ssh"
43+
44+ host := utils.GetEnv("PICO_HOST", "0.0.0.0")
45+ port := utils.GetEnv("PICO_SSH_PORT", "2222")
46+ promPort := utils.GetEnv("PICO_PROM_PORT", "9222")
47+ cfg := NewConfigSite(appName)
48+ logger := cfg.Logger
49+
50+ ctx, cancel := context.WithCancel(context.Background())
51+ defer cancel()
52+
53+ dbpool := postgres.NewDB(cfg.DbURL, cfg.Logger)
54+ defer dbpool.Close()
55+
56+ handler := NewUploadHandler(
57+ dbpool,
58+ cfg,
59+ )
60+
61+ cliHandler := &CliHandler{
62+ Logger: logger,
63+ DBPool: dbpool,
64+ }
65+
66+ sshAuth := shared.NewSshAuthHandler(dbpool, logger)
67+
68+ // Create a new SSH server
69+ server, err := pssh.NewSSHServerWithConfig(
70+ ctx,
71+ logger,
72+ appName,
73+ host,
74+ port,
75+ promPort,
76+ func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
77+ perms, _ := sshAuth.PubkeyAuthHandler(conn, key)
78+ if perms == nil {
79+ perms = &ssh.Permissions{
80+ Extensions: map[string]string{
81+ "pubkey": utils.KeyForKeyText(key),
82+ },
83+ }
84+ }
85+
86+ return perms, nil
87+ },
88+ []pssh.SSHServerMiddleware{
89+ pipe.Middleware(handler, ""),
90+ list.Middleware(handler),
91+ scp.Middleware(handler),
92+ rsync.Middleware(handler),
93+ auth.Middleware(handler),
94+ func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
95+ return func(sesh *pssh.SSHServerConnSession) error {
96+ shrd := &tui.SharedModel{
97+ Session: sesh,
98+ Cfg: cfg,
99+ Dbpool: handler.DBPool,
100+ Logger: cfg.Logger,
101+ }
102+ return pssh.PtyMdw(createTui(shrd))(next)(sesh)
103+ }
104+ },
105+ Middleware(cliHandler),
106+ pssh.LogMiddleware(handler, dbpool),
107+ },
108+ []pssh.SSHServerMiddleware{
109+ sftp.Middleware(handler),
110+ pssh.LogMiddleware(handler, dbpool),
111+ },
112+ nil,
113+ )
114+
115+ if err != nil {
116+ logger.Error("failed to create ssh server", "err", err.Error())
117+ os.Exit(1)
118+ }
119+
120+ done := make(chan os.Signal, 1)
121+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
122+ logger.Info("Starting SSH server", "addr", server.Config.ListenAddr)
123+ go func() {
124+ if err = server.ListenAndServe(); err != nil {
125+ logger.Error("serve", "err", err.Error())
126+ os.Exit(1)
127+ }
128+ }()
129+
130+ exit := func() {
131+ logger.Info("stopping ssh server")
132+ cancel()
133+ }
134+
135+ <-done
136+ exit()
137+}
R pipe/api.go =>
pkg/apps/pipe/api.go
+2,
-2
1@@ -16,8 +16,8 @@ import (
2
3 "github.com/google/uuid"
4 "github.com/gorilla/websocket"
5- "github.com/picosh/pico/db/postgres"
6- "github.com/picosh/pico/shared"
7+ "github.com/picosh/pico/pkg/db/postgres"
8+ "github.com/picosh/pico/pkg/shared"
9 "github.com/picosh/utils/pipe"
10 "github.com/prometheus/client_golang/prometheus/promhttp"
11 )
R pipe/cli.go =>
pkg/apps/pipe/cli.go
+44,
-40
1@@ -13,17 +13,15 @@ import (
2 "time"
3
4 "github.com/antoniomika/syncmap"
5- "github.com/charmbracelet/ssh"
6- "github.com/charmbracelet/wish"
7 "github.com/google/uuid"
8- "github.com/picosh/pico/db"
9- "github.com/picosh/pico/shared"
10- wsh "github.com/picosh/pico/wish"
11+ "github.com/picosh/pico/pkg/db"
12+ "github.com/picosh/pico/pkg/pssh"
13+ "github.com/picosh/pico/pkg/shared"
14 psub "github.com/picosh/pubsub"
15 gossh "golang.org/x/crypto/ssh"
16 )
17
18-func flagSet(cmdName string, sesh ssh.Session) *flag.FlagSet {
19+func flagSet(cmdName string, sesh *pssh.SSHServerConnSession) *flag.FlagSet {
20 cmd := flag.NewFlagSet(cmdName, flag.ContinueOnError)
21 cmd.SetOutput(sesh)
22 cmd.Usage = func() {
23@@ -73,7 +71,7 @@ func clientInfo(clients []*psub.Client, clientType string) string {
24 }
25
26 var helpStr = func(sshCmd string) string {
27- return fmt.Sprintf(`Command: ssh %s <help | ls | pub | sub | pipe> <topic> [-h | args...]
28+ data := fmt.Sprintf(`Command: ssh %s <help | ls | pub | sub | pipe> <topic> [-h | args...]
29
30 The simplest authenticated pubsub system. Send messages through
31 user-defined topics. Topics are private to the authenticated
32@@ -91,6 +89,10 @@ data is being sent:
33 - sub => reads from client
34 - pipe => read and write between clients
35 `, sshCmd)
36+
37+ data = strings.ReplaceAll(data, "\n", "\r\n")
38+
39+ return data
40 }
41
42 type CliHandler struct {
43@@ -102,6 +104,10 @@ type CliHandler struct {
44 Access *syncmap.Map[string, []string]
45 }
46
47+func (h *CliHandler) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
48+ return h.Logger
49+}
50+
51 func toSshCmd(cfg *shared.ConfigSite) string {
52 port := ""
53 if cfg.PortOverride != "22" {
54@@ -120,7 +126,7 @@ func parseArgList(arg string) []string {
55 }
56
57 // checkAccess checks if the user has access to a topic based on an access list.
58-func checkAccess(accessList []string, userName string, sesh ssh.Session) bool {
59+func checkAccess(accessList []string, userName string, sesh *pssh.SSHServerConnSession) bool {
60 for _, acc := range accessList {
61 if acc == userName {
62 return true
63@@ -134,21 +140,20 @@ func checkAccess(accessList []string, userName string, sesh ssh.Session) bool {
64 return false
65 }
66
67-func WishMiddleware(handler *CliHandler) wish.Middleware {
68+func Middleware(handler *CliHandler) pssh.SSHServerMiddleware {
69 pubsub := handler.PubSub
70
71- return func(next ssh.Handler) ssh.Handler {
72- return func(sesh ssh.Session) {
73+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
74+ return func(sesh *pssh.SSHServerConnSession) error {
75 ctx := sesh.Context()
76- logger := wsh.GetLogger(sesh)
77- user := wsh.GetUser(sesh)
78+ logger := pssh.GetLogger(sesh)
79+ user := pssh.GetUser(sesh)
80
81 args := sesh.Command()
82
83 if len(args) == 0 {
84- wish.Println(sesh, helpStr(toSshCmd(handler.Cfg)))
85- next(sesh)
86- return
87+ fmt.Fprintln(sesh, helpStr(toSshCmd(handler.Cfg)))
88+ return next(sesh)
89 }
90
91 userName := "public"
92@@ -188,13 +193,13 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
93
94 cmd := strings.TrimSpace(args[0])
95 if cmd == "help" {
96- wish.Println(sesh, helpStr(toSshCmd(handler.Cfg)))
97- next(sesh)
98- return
99+ fmt.Fprintln(sesh, helpStr(toSshCmd(handler.Cfg)))
100+ return next(sesh)
101 } else if cmd == "ls" {
102 if userName == "public" {
103- wish.Fatalln(sesh, "access denied")
104- return
105+ err := fmt.Errorf("access denied")
106+ sesh.Fatal(err)
107+ return err
108 }
109
110 topicFilter := fmt.Sprintf("%s/", userName)
111@@ -221,7 +226,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
112 }
113
114 if len(channels) == 0 && len(waitingChannels) == 0 {
115- wish.Println(sesh, "no pubsub channels found")
116+ fmt.Fprintln(sesh, "no pubsub channels found")
117 } else {
118 var outputData string
119 if len(channels) > 0 || len(waitingChannels) > 0 {
120@@ -273,8 +278,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
121 _, _ = sesh.Write([]byte(outputData))
122 }
123
124- next(sesh)
125- return
126+ return next(sesh)
127 }
128
129 topic := ""
130@@ -306,7 +310,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
131 clean := pubCmd.Bool("c", false, "Don't send status messages")
132
133 if !flagCheck(pubCmd, topic, cmdArgs) {
134- return
135+ return err
136 }
137
138 if pubCmd.NArg() == 1 && topic == "" {
139@@ -382,7 +386,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
140 }
141
142 if !*clean {
143- wish.Printf(
144+ fmt.Fprintf(
145 sesh,
146 "subscribe to this channel:\n ssh %s sub %s%s\n",
147 toSshCmd(handler.Cfg),
148@@ -415,7 +419,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
149 }
150
151 if !*clean {
152- wish.Println(sesh, termMsg)
153+ fmt.Fprintln(sesh, termMsg)
154 }
155
156 ready := make(chan struct{})
157@@ -454,7 +458,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
158 cancel()
159
160 if !*clean {
161- wish.Fatalln(sesh, "timeout reached, exiting ...")
162+ sesh.Fatal(fmt.Errorf("timeout reached, exiting"))
163 } else {
164 err = sesh.Exit(1)
165 if err != nil {
166@@ -486,7 +490,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
167 }
168
169 if !*clean {
170- wish.Println(sesh, "sending msg ...")
171+ fmt.Fprintln(sesh, "sending msg ...")
172 }
173
174 err = pubsub.Pub(
175@@ -500,11 +504,11 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
176 )
177
178 if !*clean {
179- wish.Println(sesh, "msg sent!")
180+ fmt.Fprintln(sesh, "msg sent!")
181 }
182
183 if err != nil && !*clean {
184- wish.Errorln(sesh, err)
185+ fmt.Fprintln(sesh.Stderr(), err)
186 }
187 } else if cmd == "sub" {
188 subCmd := flagSet("sub", sesh)
189@@ -514,7 +518,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
190 clean := subCmd.Bool("c", false, "Don't send status messages")
191
192 if !flagCheck(subCmd, topic, cmdArgs) {
193- return
194+ return err
195 }
196
197 if subCmd.NArg() == 1 && topic == "" {
198@@ -569,8 +573,8 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
199 } else if !*public {
200 name = toTopic(userName, withoutUser)
201 } else {
202- wish.Errorln(sesh, "access denied")
203- return
204+ fmt.Fprintln(sesh.Stderr(), "access denied")
205+ return err
206 }
207 }
208
209@@ -585,7 +589,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
210 )
211
212 if err != nil && !*clean {
213- wish.Errorln(sesh, err)
214+ fmt.Fprintln(sesh.Stderr(), err)
215 }
216 } else if cmd == "pipe" {
217 pipeCmd := flagSet("pipe", sesh)
218@@ -595,7 +599,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
219 clean := pipeCmd.Bool("c", false, "Don't send status messages")
220
221 if !flagCheck(pipeCmd, topic, cmdArgs) {
222- return
223+ return err
224 }
225
226 if pipeCmd.NArg() == 1 && topic == "" {
227@@ -663,7 +667,7 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
228 }
229
230 if isCreator && !*clean {
231- wish.Printf(
232+ fmt.Fprintf(
233 sesh,
234 "subscribe to this topic:\n ssh %s sub %s%s\n",
235 toSshCmd(handler.Cfg),
236@@ -683,15 +687,15 @@ func WishMiddleware(handler *CliHandler) wish.Middleware {
237 )
238
239 if readErr != nil && !*clean {
240- wish.Errorln(sesh, "error reading from pipe", readErr)
241+ fmt.Fprintln(sesh.Stderr(), "error reading from pipe", readErr)
242 }
243
244 if writeErr != nil && !*clean {
245- wish.Errorln(sesh, "error writing to pipe", writeErr)
246+ fmt.Fprintln(sesh.Stderr(), "error writing to pipe", writeErr)
247 }
248 }
249
250- next(sesh)
251+ return next(sesh)
252 }
253 }
254 }
R pipe/config.go =>
pkg/apps/pipe/config.go
+1,
-1
1@@ -1,7 +1,7 @@
2 package pipe
3
4 import (
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 "github.com/picosh/utils"
8 )
9
R pipe/html/base.layout.tmpl =>
pkg/apps/pipe/html/base.layout.tmpl
+0,
-0
R pipe/html/marketing.page.tmpl =>
pkg/apps/pipe/html/marketing.page.tmpl
+0,
-0
R pipe/public/anim.js =>
pkg/apps/pipe/public/anim.js
+0,
-0
R pipe/public/apple-touch-icon.png =>
pkg/apps/pipe/public/apple-touch-icon.png
+0,
-0
R pipe/public/favicon-16x16.png =>
pkg/apps/pipe/public/favicon-16x16.png
+0,
-0
R pipe/public/favicon.ico =>
pkg/apps/pipe/public/favicon.ico
+0,
-0
R pipe/public/robots.txt =>
pkg/apps/pipe/public/robots.txt
+0,
-0
+99,
-0
1@@ -0,0 +1,99 @@
2+package pipe
3+
4+import (
5+ "context"
6+ "os"
7+ "os/signal"
8+ "syscall"
9+
10+ "github.com/antoniomika/syncmap"
11+ "github.com/picosh/pico/pkg/db/postgres"
12+ "github.com/picosh/pico/pkg/pssh"
13+ "github.com/picosh/pico/pkg/shared"
14+ psub "github.com/picosh/pubsub"
15+ "github.com/picosh/utils"
16+ "golang.org/x/crypto/ssh"
17+)
18+
19+func StartSshServer() {
20+ appName := "pipe-ssh"
21+
22+ host := utils.GetEnv("PIPE_HOST", "0.0.0.0")
23+ port := utils.GetEnv("PIPE_SSH_PORT", "2222")
24+ portOverride := utils.GetEnv("PIPE_SSH_PORT_OVERRIDE", port)
25+ promPort := utils.GetEnv("PIPE_PROM_PORT", "9222")
26+ cfg := NewConfigSite(appName)
27+ logger := cfg.Logger
28+
29+ ctx, cancel := context.WithCancel(context.Background())
30+ defer cancel()
31+
32+ dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
33+ defer dbh.Close()
34+
35+ cfg.Port = port
36+ cfg.PortOverride = portOverride
37+
38+ pubsub := psub.NewMulticast(logger)
39+ handler := &CliHandler{
40+ Logger: logger,
41+ DBPool: dbh,
42+ PubSub: pubsub,
43+ Cfg: cfg,
44+ Waiters: syncmap.New[string, []string](),
45+ Access: syncmap.New[string, []string](),
46+ }
47+
48+ sshAuth := shared.NewSshAuthHandler(dbh, logger)
49+
50+ // Create a new SSH server
51+ server, err := pssh.NewSSHServerWithConfig(
52+ ctx,
53+ logger,
54+ appName,
55+ host,
56+ port,
57+ promPort,
58+ func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
59+ perms, _ := sshAuth.PubkeyAuthHandler(conn, key)
60+ if perms == nil {
61+ perms = &ssh.Permissions{
62+ Extensions: map[string]string{
63+ "pubkey": utils.KeyForKeyText(key),
64+ },
65+ }
66+ }
67+
68+ return perms, nil
69+ },
70+ []pssh.SSHServerMiddleware{
71+ Middleware(handler),
72+ pssh.LogMiddleware(handler, dbh),
73+ },
74+ nil,
75+ nil,
76+ )
77+
78+ if err != nil {
79+ logger.Error("failed to create ssh server", "err", err.Error())
80+ os.Exit(1)
81+ }
82+
83+ done := make(chan os.Signal, 1)
84+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
85+ logger.Info("Starting SSH server", "addr", server.Config.ListenAddr)
86+ go func() {
87+ if err = server.ListenAndServe(); err != nil {
88+ logger.Error("serve", "err", err.Error())
89+ os.Exit(1)
90+ }
91+ }()
92+
93+ exit := func() {
94+ logger.Info("stopping ssh server")
95+ cancel()
96+ }
97+
98+ <-done
99+ exit()
100+}
R prose/api.go =>
pkg/apps/prose/api.go
+4,
-4
1@@ -15,10 +15,10 @@ import (
2 "slices"
3
4 "github.com/gorilla/feeds"
5- "github.com/picosh/pico/db"
6- "github.com/picosh/pico/db/postgres"
7- "github.com/picosh/pico/shared"
8- "github.com/picosh/pico/shared/storage"
9+ "github.com/picosh/pico/pkg/db"
10+ "github.com/picosh/pico/pkg/db/postgres"
11+ "github.com/picosh/pico/pkg/shared"
12+ "github.com/picosh/pico/pkg/shared/storage"
13 "github.com/picosh/utils"
14 "github.com/prometheus/client_golang/prometheus/promhttp"
15 )
R prose/artifacts/main.css =>
pkg/apps/prose/artifacts/main.css
+0,
-0
R prose/config.go =>
pkg/apps/prose/config.go
+1,
-1
1@@ -1,7 +1,7 @@
2 package prose
3
4 import (
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 "github.com/picosh/utils"
8 )
9
R prose/html/base.layout.tmpl =>
pkg/apps/prose/html/base.layout.tmpl
+0,
-0
R prose/html/blog-aside.partial.tmpl =>
pkg/apps/prose/html/blog-aside.partial.tmpl
+0,
-0
R prose/html/blog-default.partial.tmpl =>
pkg/apps/prose/html/blog-default.partial.tmpl
+0,
-0
R prose/html/blog.page.tmpl =>
pkg/apps/prose/html/blog.page.tmpl
+0,
-0
R prose/html/imgs.page.tmpl =>
pkg/apps/prose/html/imgs.page.tmpl
+0,
-0
R prose/html/post.page.tmpl =>
pkg/apps/prose/html/post.page.tmpl
+0,
-0
R prose/html/read.page.tmpl =>
pkg/apps/prose/html/read.page.tmpl
+0,
-0
R prose/html/rss.page.tmpl =>
pkg/apps/prose/html/rss.page.tmpl
+0,
-0
R prose/public/card.png =>
pkg/apps/prose/public/card.png
+0,
-0
R prose/public/favicon-16x16.png =>
pkg/apps/prose/public/favicon-16x16.png
+0,
-0
R prose/public/favicon.ico =>
pkg/apps/prose/public/favicon.ico
+0,
-0
R prose/public/robots.txt =>
pkg/apps/prose/public/robots.txt
+0,
-0
R prose/public/smol-v2.css =>
pkg/apps/prose/public/smol-v2.css
+0,
-0
R prose/public/smol.css =>
pkg/apps/prose/public/smol.css
+0,
-0
R prose/public/syntax.css =>
pkg/apps/prose/public/syntax.css
+0,
-0
R prose/scp_hooks.go =>
pkg/apps/prose/scp_hooks.go
+6,
-6
1@@ -6,10 +6,10 @@ import (
2
3 "slices"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/filehandlers"
8- "github.com/picosh/pico/shared"
9+ "github.com/picosh/pico/pkg/db"
10+ "github.com/picosh/pico/pkg/filehandlers"
11+ "github.com/picosh/pico/pkg/pssh"
12+ "github.com/picosh/pico/pkg/shared"
13 "github.com/picosh/utils"
14 pipeUtil "github.com/picosh/utils/pipe"
15 )
16@@ -20,7 +20,7 @@ type MarkdownHooks struct {
17 Pipe *pipeUtil.ReconnectReadWriteCloser
18 }
19
20-func (p *MarkdownHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData) (bool, error) {
21+func (p *MarkdownHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
22 if !utils.IsTextFile(data.Text) {
23 err := fmt.Errorf(
24 "ERROR: (%s) invalid file must be plain text (utf-8), skipping",
25@@ -57,7 +57,7 @@ func (p *MarkdownHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaD
26 return true, nil
27 }
28
29-func (p *MarkdownHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
30+func (p *MarkdownHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
31 parsedText, err := shared.ParseText(data.Text)
32 if err != nil {
33 return fmt.Errorf("%s: %w", data.Filename, err)
+113,
-0
1@@ -0,0 +1,113 @@
2+package prose
3+
4+import (
5+ "context"
6+ "os"
7+ "os/signal"
8+ "syscall"
9+
10+ "github.com/picosh/pico/pkg/db/postgres"
11+ "github.com/picosh/pico/pkg/filehandlers"
12+ uploadimgs "github.com/picosh/pico/pkg/filehandlers/imgs"
13+ "github.com/picosh/pico/pkg/pssh"
14+ "github.com/picosh/pico/pkg/send/auth"
15+ "github.com/picosh/pico/pkg/send/list"
16+ "github.com/picosh/pico/pkg/send/pipe"
17+ "github.com/picosh/pico/pkg/send/protocols/rsync"
18+ "github.com/picosh/pico/pkg/send/protocols/scp"
19+ "github.com/picosh/pico/pkg/send/protocols/sftp"
20+ "github.com/picosh/pico/pkg/shared"
21+ "github.com/picosh/pico/pkg/shared/storage"
22+ "github.com/picosh/utils"
23+)
24+
25+func StartSshServer() {
26+ appName := "prose-ssh"
27+
28+ host := utils.GetEnv("PROSE_HOST", "0.0.0.0")
29+ port := utils.GetEnv("PROSE_SSH_PORT", "2222")
30+ promPort := utils.GetEnv("PROSE_PROM_PORT", "9222")
31+ cfg := NewConfigSite(appName)
32+ logger := cfg.Logger
33+
34+ ctx, cancel := context.WithCancel(context.Background())
35+ defer cancel()
36+
37+ dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
38+ defer dbh.Close()
39+
40+ hooks := &MarkdownHooks{
41+ Cfg: cfg,
42+ Db: dbh,
43+ }
44+
45+ var st storage.StorageServe
46+ var err error
47+ if cfg.MinioURL == "" {
48+ st, err = storage.NewStorageFS(cfg.Logger, cfg.StorageDir)
49+ } else {
50+ st, err = storage.NewStorageMinio(cfg.Logger, cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
51+ }
52+
53+ if err != nil {
54+ logger.Error("storage", "err", err.Error())
55+ return
56+ }
57+
58+ fileMap := map[string]filehandlers.ReadWriteHandler{
59+ ".md": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
60+ ".css": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
61+ "fallback": uploadimgs.NewUploadImgHandler(dbh, cfg, st),
62+ }
63+ handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
64+
65+ sshAuth := shared.NewSshAuthHandler(dbh, logger)
66+
67+ // Create a new SSH server
68+ server, err := pssh.NewSSHServerWithConfig(
69+ ctx,
70+ logger,
71+ appName,
72+ host,
73+ port,
74+ promPort,
75+ sshAuth.PubkeyAuthHandler,
76+ []pssh.SSHServerMiddleware{
77+ pipe.Middleware(handler, ".md"),
78+ list.Middleware(handler),
79+ scp.Middleware(handler),
80+ rsync.Middleware(handler),
81+ auth.Middleware(handler),
82+ pssh.PtyMdw(pssh.DeprecatedNotice()),
83+ pssh.LogMiddleware(handler, dbh),
84+ },
85+ []pssh.SSHServerMiddleware{
86+ sftp.Middleware(handler),
87+ pssh.LogMiddleware(handler, dbh),
88+ },
89+ nil,
90+ )
91+
92+ if err != nil {
93+ logger.Error("failed to create ssh server", "err", err.Error())
94+ os.Exit(1)
95+ }
96+
97+ done := make(chan os.Signal, 1)
98+ signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
99+ logger.Info("Starting SSH server", "addr", server.Config.ListenAddr)
100+ go func() {
101+ if err = server.ListenAndServe(); err != nil {
102+ logger.Error("serve", "err", err.Error())
103+ os.Exit(1)
104+ }
105+ }()
106+
107+ exit := func() {
108+ logger.Info("stopping ssh server")
109+ cancel()
110+ }
111+
112+ <-done
113+ exit()
114+}
R db/db.go =>
pkg/db/db.go
+0,
-0
R db/postgres/storage.go =>
pkg/db/postgres/storage.go
+1,
-1
1@@ -13,7 +13,7 @@ import (
2 "slices"
3
4 _ "github.com/lib/pq"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils"
8 )
9
R db/stub/stub.go =>
pkg/db/stub/stub.go
+1,
-1
1@@ -6,7 +6,7 @@ import (
2 "log/slog"
3 "time"
4
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 )
8
9 type StubDB struct {
R db/util.go =>
pkg/db/util.go
+0,
-0
R filehandlers/imgs/handler.go =>
pkg/filehandlers/imgs/handler.go
+21,
-22
1@@ -11,15 +11,14 @@ import (
2 "slices"
3 "strings"
4
5- "github.com/charmbracelet/ssh"
6 exifremove "github.com/neurosnap/go-exif-remove"
7- "github.com/picosh/pico/db"
8- "github.com/picosh/pico/shared"
9- "github.com/picosh/pico/shared/storage"
10- "github.com/picosh/pico/wish"
11- "github.com/picosh/pobj"
12- sst "github.com/picosh/pobj/storage"
13- sendutils "github.com/picosh/send/utils"
14+ "github.com/picosh/pico/pkg/db"
15+ "github.com/picosh/pico/pkg/pobj"
16+ sst "github.com/picosh/pico/pkg/pobj/storage"
17+ "github.com/picosh/pico/pkg/pssh"
18+ sendutils "github.com/picosh/pico/pkg/send/utils"
19+ "github.com/picosh/pico/pkg/shared"
20+ "github.com/picosh/pico/pkg/shared/storage"
21 "github.com/picosh/utils"
22 )
23
24@@ -53,11 +52,11 @@ func (h *UploadImgHandler) getObjectPath(fpath string) string {
25 return filepath.Join("prose", fpath)
26 }
27
28-func (h *UploadImgHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
29+func (h *UploadImgHandler) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
30 var fileList []os.FileInfo
31
32- logger := wish.GetLogger(s)
33- user := wish.GetUser(s)
34+ logger := pssh.GetLogger(s)
35+ user := pssh.GetUser(s)
36
37 if user == nil {
38 err := fmt.Errorf("could not get user from ctx")
39@@ -102,9 +101,9 @@ func (h *UploadImgHandler) List(s ssh.Session, fpath string, isDir bool, recursi
40 return fileList, nil
41 }
42
43-func (h *UploadImgHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
44- logger := wish.GetLogger(s)
45- user := wish.GetUser(s)
46+func (h *UploadImgHandler) Read(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
47+ logger := pssh.GetLogger(s)
48+ user := pssh.GetUser(s)
49
50 if user == nil {
51 err := fmt.Errorf("could not get user from ctx")
52@@ -139,9 +138,9 @@ func (h *UploadImgHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.F
53 return fileInfo, reader, nil
54 }
55
56-func (h *UploadImgHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
57- logger := wish.GetLogger(s)
58- user := wish.GetUser(s)
59+func (h *UploadImgHandler) Write(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (string, error) {
60+ logger := pssh.GetLogger(s)
61+ user := pssh.GetUser(s)
62
63 if user == nil {
64 err := fmt.Errorf("could not get user from ctx")
65@@ -222,9 +221,9 @@ func (h *UploadImgHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (str
66 return str, nil
67 }
68
69-func (h *UploadImgHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
70- logger := wish.GetLogger(s)
71- user := wish.GetUser(s)
72+func (h *UploadImgHandler) Delete(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) error {
73+ logger := pssh.GetLogger(s)
74+ user := pssh.GetUser(s)
75
76 if user == nil {
77 err := fmt.Errorf("could not get user from ctx")
78@@ -309,13 +308,13 @@ func (h *UploadImgHandler) metaImg(data *PostMetaData) error {
79 return nil
80 }
81
82-func (h *UploadImgHandler) writeImg(s ssh.Session, data *PostMetaData) error {
83+func (h *UploadImgHandler) writeImg(s *pssh.SSHServerConnSession, data *PostMetaData) error {
84 valid, err := h.validateImg(data)
85 if !valid {
86 return err
87 }
88
89- logger := wish.GetLogger(s)
90+ logger := pssh.GetLogger(s)
91 logger = logger.With(
92 "filename", data.Filename,
93 )
R filehandlers/post_handler.go =>
pkg/filehandlers/post_handler.go
+16,
-17
1@@ -10,11 +10,10 @@ import (
2 "strings"
3 "time"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/shared"
8- "github.com/picosh/pico/wish"
9- sendutils "github.com/picosh/send/utils"
10+ "github.com/picosh/pico/pkg/db"
11+ "github.com/picosh/pico/pkg/pssh"
12+ sendutils "github.com/picosh/pico/pkg/send/utils"
13+ "github.com/picosh/pico/pkg/shared"
14 "github.com/picosh/utils"
15 )
16
17@@ -28,8 +27,8 @@ type PostMetaData struct {
18 }
19
20 type ScpFileHooks interface {
21- FileValidate(s ssh.Session, data *PostMetaData) (bool, error)
22- FileMeta(s ssh.Session, data *PostMetaData) error
23+ FileValidate(s *pssh.SSHServerConnSession, data *PostMetaData) (bool, error)
24+ FileMeta(s *pssh.SSHServerConnSession, data *PostMetaData) error
25 }
26
27 type ScpUploadHandler struct {
28@@ -46,13 +45,13 @@ func NewScpPostHandler(dbpool db.DB, cfg *shared.ConfigSite, hooks ScpFileHooks)
29 }
30 }
31
32-func (r *ScpUploadHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
33+func (r *ScpUploadHandler) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
34 return BaseList(s, fpath, isDir, recursive, []string{r.Cfg.Space}, r.DBPool)
35 }
36
37-func (h *ScpUploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
38- logger := wish.GetLogger(s)
39- user := wish.GetUser(s)
40+func (h *ScpUploadHandler) Read(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (os.FileInfo, sendutils.ReadAndReaderAtCloser, error) {
41+ logger := pssh.GetLogger(s)
42+ user := pssh.GetUser(s)
43
44 if user == nil {
45 err := fmt.Errorf("could not get user from ctx")
46@@ -83,9 +82,9 @@ func (h *ScpUploadHandler) Read(s ssh.Session, entry *sendutils.FileEntry) (os.F
47 return fileInfo, reader, nil
48 }
49
50-func (h *ScpUploadHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (string, error) {
51- logger := wish.GetLogger(s)
52- user := wish.GetUser(s)
53+func (h *ScpUploadHandler) Write(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) (string, error) {
54+ logger := pssh.GetLogger(s)
55+ user := pssh.GetUser(s)
56
57 if user == nil {
58 err := fmt.Errorf("could not get user from ctx")
59@@ -271,9 +270,9 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (str
60 return h.Cfg.FullPostURL(curl, user.Name, metadata.Slug), nil
61 }
62
63-func (h *ScpUploadHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
64- logger := wish.GetLogger(s)
65- user := wish.GetUser(s)
66+func (h *ScpUploadHandler) Delete(s *pssh.SSHServerConnSession, entry *sendutils.FileEntry) error {
67+ logger := pssh.GetLogger(s)
68+ user := pssh.GetUser(s)
69
70 if user == nil {
71 err := fmt.Errorf("could not get user from ctx")
R filehandlers/router_handler.go =>
pkg/filehandlers/router_handler.go
+20,
-21
1@@ -8,18 +8,17 @@ import (
2 "os"
3 "path/filepath"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/shared"
8- "github.com/picosh/pico/wish"
9- "github.com/picosh/send/utils"
10+ "github.com/picosh/pico/pkg/db"
11+ "github.com/picosh/pico/pkg/pssh"
12+ "github.com/picosh/pico/pkg/send/utils"
13+ "github.com/picosh/pico/pkg/shared"
14 )
15
16 type ReadWriteHandler interface {
17- List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error)
18- Write(ssh.Session, *utils.FileEntry) (string, error)
19- Read(ssh.Session, *utils.FileEntry) (os.FileInfo, utils.ReadAndReaderAtCloser, error)
20- Delete(ssh.Session, *utils.FileEntry) error
21+ List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error)
22+ Write(*pssh.SSHServerConnSession, *utils.FileEntry) (string, error)
23+ Read(*pssh.SSHServerConnSession, *utils.FileEntry) (os.FileInfo, utils.ReadAndReaderAtCloser, error)
24+ Delete(*pssh.SSHServerConnSession, *utils.FileEntry) error
25 }
26
27 type FileHandlerRouter struct {
28@@ -54,7 +53,7 @@ func (r *FileHandlerRouter) findHandler(fp string) (ReadWriteHandler, error) {
29 return handler, nil
30 }
31
32-func (r *FileHandlerRouter) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
33+func (r *FileHandlerRouter) Write(s *pssh.SSHServerConnSession, entry *utils.FileEntry) (string, error) {
34 if entry.Mode.IsDir() {
35 return "", os.ErrInvalid
36 }
37@@ -66,7 +65,7 @@ func (r *FileHandlerRouter) Write(s ssh.Session, entry *utils.FileEntry) (string
38 return handler.Write(s, entry)
39 }
40
41-func (r *FileHandlerRouter) Delete(s ssh.Session, entry *utils.FileEntry) error {
42+func (r *FileHandlerRouter) Delete(s *pssh.SSHServerConnSession, entry *utils.FileEntry) error {
43 handler, err := r.findHandler(entry.Filepath)
44 if err != nil {
45 return err
46@@ -74,7 +73,7 @@ func (r *FileHandlerRouter) Delete(s ssh.Session, entry *utils.FileEntry) error
47 return handler.Delete(s, entry)
48 }
49
50-func (r *FileHandlerRouter) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReadAndReaderAtCloser, error) {
51+func (r *FileHandlerRouter) Read(s *pssh.SSHServerConnSession, entry *utils.FileEntry) (os.FileInfo, utils.ReadAndReaderAtCloser, error) {
52 handler, err := r.findHandler(entry.Filepath)
53 if err != nil {
54 return nil, nil, err
55@@ -82,7 +81,7 @@ func (r *FileHandlerRouter) Read(s ssh.Session, entry *utils.FileEntry) (os.File
56 return handler.Read(s, entry)
57 }
58
59-func (r *FileHandlerRouter) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
60+func (r *FileHandlerRouter) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
61 files := []os.FileInfo{}
62 for key, handler := range r.FileMap {
63 // TODO: hack because we have duplicate keys for .md and .css
64@@ -100,13 +99,13 @@ func (r *FileHandlerRouter) List(s ssh.Session, fpath string, isDir bool, recurs
65 return files, nil
66 }
67
68-func (r *FileHandlerRouter) GetLogger(s ssh.Session) *slog.Logger {
69- return wish.GetLogger(s)
70+func (r *FileHandlerRouter) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
71+ return pssh.GetLogger(s)
72 }
73
74-func (r *FileHandlerRouter) Validate(s ssh.Session) error {
75- logger := wish.GetLogger(s)
76- user := wish.GetUser(s)
77+func (r *FileHandlerRouter) Validate(s *pssh.SSHServerConnSession) error {
78+ logger := pssh.GetLogger(s)
79+ user := pssh.GetUser(s)
80
81 if user == nil {
82 err := fmt.Errorf("could not get user from ctx")
83@@ -122,10 +121,10 @@ func (r *FileHandlerRouter) Validate(s ssh.Session) error {
84 return nil
85 }
86
87-func BaseList(s ssh.Session, fpath string, isDir bool, recursive bool, spaces []string, dbpool db.DB) ([]os.FileInfo, error) {
88+func BaseList(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool, spaces []string, dbpool db.DB) ([]os.FileInfo, error) {
89 var fileList []os.FileInfo
90- logger := wish.GetLogger(s)
91- user := wish.GetUser(s)
92+ logger := pssh.GetLogger(s)
93+ user := pssh.GetUser(s)
94
95 var err error
96
+45,
-0
1@@ -0,0 +1,45 @@
2+package pobj
3+
4+import (
5+ "fmt"
6+
7+ "github.com/picosh/pico/pkg/pssh"
8+ "github.com/picosh/pico/pkg/send/utils"
9+)
10+
11+type AssetNames interface {
12+ BucketName(sesh *pssh.SSHServerConnSession) (string, error)
13+ ObjectName(sesh *pssh.SSHServerConnSession, entry *utils.FileEntry) (string, error)
14+ PrintObjectName(sesh *pssh.SSHServerConnSession, entry *utils.FileEntry, bucketName string) (string, error)
15+}
16+
17+type AssetNamesBasic struct{}
18+
19+var _ AssetNames = &AssetNamesBasic{}
20+var _ AssetNames = (*AssetNamesBasic)(nil)
21+
22+func (an *AssetNamesBasic) BucketName(sesh *pssh.SSHServerConnSession) (string, error) {
23+ return sesh.User(), nil
24+}
25+func (an *AssetNamesBasic) ObjectName(sesh *pssh.SSHServerConnSession, entry *utils.FileEntry) (string, error) {
26+ return entry.Filepath, nil
27+}
28+func (an *AssetNamesBasic) PrintObjectName(sesh *pssh.SSHServerConnSession, entry *utils.FileEntry, bucketName string) (string, error) {
29+ objectName, err := an.ObjectName(sesh, entry)
30+ if err != nil {
31+ return "", err
32+ }
33+ return fmt.Sprintf("%s%s", bucketName, objectName), nil
34+}
35+
36+type AssetNamesForceBucket struct {
37+ *AssetNamesBasic
38+ Name string
39+}
40+
41+var _ AssetNames = &AssetNamesForceBucket{}
42+var _ AssetNames = (*AssetNamesForceBucket)(nil)
43+
44+func (an *AssetNamesForceBucket) BucketName(sesh *pssh.SSHServerConnSession) (string, error) {
45+ return an.Name, nil
46+}
+269,
-0
1@@ -0,0 +1,269 @@
2+package pobj
3+
4+import (
5+ "bytes"
6+ "encoding/binary"
7+ "fmt"
8+ "io"
9+ "log/slog"
10+ "os"
11+ "path/filepath"
12+ "time"
13+
14+ "github.com/picosh/pico/pkg/pobj/storage"
15+ "github.com/picosh/pico/pkg/pssh"
16+ "github.com/picosh/pico/pkg/send/utils"
17+)
18+
19+type ctxBucketKey struct{}
20+
21+func getBucket(ctx *pssh.SSHServerConnSession) (storage.Bucket, error) {
22+ bucket, ok := ctx.Value(ctxBucketKey{}).(storage.Bucket)
23+ if !ok {
24+ return bucket, fmt.Errorf("bucket not set on `ssh.Context()` for connection")
25+ }
26+ if bucket.Name == "" {
27+ return bucket, fmt.Errorf("bucket not set on `ssh.Context()` for connection")
28+ }
29+ return bucket, nil
30+}
31+func setBucket(ctx *pssh.SSHServerConnSession, bucket storage.Bucket) {
32+ ctx.SetValue(ctxBucketKey{}, bucket)
33+}
34+
35+type FileData struct {
36+ *utils.FileEntry
37+ Text []byte
38+ User string
39+ Bucket storage.Bucket
40+}
41+
42+type Config struct {
43+ Logger *slog.Logger
44+ Storage storage.ObjectStorage
45+ AssetNames AssetNames
46+}
47+
48+type UploadAssetHandler struct {
49+ Cfg *Config
50+}
51+
52+var _ utils.CopyFromClientHandler = &UploadAssetHandler{}
53+var _ utils.CopyFromClientHandler = (*UploadAssetHandler)(nil)
54+
55+func NewUploadAssetHandler(cfg *Config) *UploadAssetHandler {
56+ if cfg.AssetNames == nil {
57+ cfg.AssetNames = &AssetNamesBasic{}
58+ }
59+
60+ return &UploadAssetHandler{
61+ Cfg: cfg,
62+ }
63+}
64+
65+func (h *UploadAssetHandler) GetLogger(s *pssh.SSHServerConnSession) *slog.Logger {
66+ return h.Cfg.Logger
67+}
68+
69+func (h *UploadAssetHandler) Delete(s *pssh.SSHServerConnSession, entry *utils.FileEntry) error {
70+ h.Cfg.Logger.Info("deleting file", "file", entry.Filepath)
71+ bucket, err := getBucket(s)
72+ if err != nil {
73+ h.Cfg.Logger.Error(err.Error())
74+ return err
75+ }
76+
77+ objectFileName, err := h.Cfg.AssetNames.ObjectName(s, entry)
78+ if err != nil {
79+ return err
80+ }
81+ return h.Cfg.Storage.DeleteObject(bucket, objectFileName)
82+}
83+
84+func (h *UploadAssetHandler) Read(s *pssh.SSHServerConnSession, entry *utils.FileEntry) (os.FileInfo, utils.ReadAndReaderAtCloser, error) {
85+ fileInfo := &utils.VirtualFile{
86+ FName: filepath.Base(entry.Filepath),
87+ FIsDir: false,
88+ FSize: entry.Size,
89+ FModTime: time.Unix(entry.Mtime, 0),
90+ }
91+ h.Cfg.Logger.Info("reading file", "file", fileInfo)
92+
93+ bucketName, err := h.Cfg.AssetNames.BucketName(s)
94+ if err != nil {
95+ return nil, nil, err
96+ }
97+ bucket, err := h.Cfg.Storage.GetBucket(bucketName)
98+ if err != nil {
99+ return nil, nil, err
100+ }
101+
102+ fname, err := h.Cfg.AssetNames.ObjectName(s, entry)
103+ if err != nil {
104+ return nil, nil, err
105+ }
106+ contents, info, err := h.Cfg.Storage.GetObject(bucket, fname)
107+ if err != nil {
108+ return nil, nil, err
109+ }
110+
111+ fileInfo.FSize = info.Size
112+ fileInfo.FModTime = info.LastModified
113+
114+ reader := NewAllReaderAt(contents)
115+
116+ return fileInfo, reader, nil
117+}
118+
119+func (h *UploadAssetHandler) List(s *pssh.SSHServerConnSession, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
120+ h.Cfg.Logger.Info(
121+ "listing path",
122+ "dir", fpath,
123+ "isDir", isDir,
124+ "recursive", recursive,
125+ )
126+ var fileList []os.FileInfo
127+
128+ cleanFilename := fpath
129+
130+ bucketName, err := h.Cfg.AssetNames.BucketName(s)
131+ if err != nil {
132+ return fileList, err
133+ }
134+ bucket, err := h.Cfg.Storage.GetBucket(bucketName)
135+ if err != nil {
136+ return fileList, err
137+ }
138+
139+ fname, err := h.Cfg.AssetNames.ObjectName(s, &utils.FileEntry{Filepath: cleanFilename})
140+ if err != nil {
141+ return fileList, err
142+ }
143+
144+ if fname == "" || fname == "." {
145+ name := fname
146+ if name == "" {
147+ name = "/"
148+ }
149+
150+ info := &utils.VirtualFile{
151+ FName: name,
152+ FIsDir: true,
153+ }
154+
155+ fileList = append(fileList, info)
156+ } else {
157+ name := fname
158+ if name != "/" && isDir {
159+ name += "/"
160+ }
161+
162+ foundList, err := h.Cfg.Storage.ListObjects(bucket, name, recursive)
163+ if err != nil {
164+ return fileList, err
165+ }
166+
167+ fileList = append(fileList, foundList...)
168+ }
169+
170+ return fileList, nil
171+}
172+
173+func (h *UploadAssetHandler) Validate(s *pssh.SSHServerConnSession) error {
174+ var err error
175+ userName := s.User()
176+
177+ assetBucket, err := h.Cfg.AssetNames.BucketName(s)
178+ if err != nil {
179+ return err
180+ }
181+ bucket, err := h.Cfg.Storage.UpsertBucket(assetBucket)
182+ if err != nil {
183+ return err
184+ }
185+ setBucket(s, bucket)
186+
187+ pk, _ := utils.KeyText(s)
188+ h.Cfg.Logger.Info(
189+ "attempting to upload files",
190+ "user", userName,
191+ "bucket", bucket.Name,
192+ "publicKey", pk,
193+ )
194+ return nil
195+}
196+
197+func (h *UploadAssetHandler) Write(s *pssh.SSHServerConnSession, entry *utils.FileEntry) (string, error) {
198+ var origText []byte
199+ if b, err := io.ReadAll(entry.Reader); err == nil {
200+ origText = b
201+ }
202+ fileSize := binary.Size(origText)
203+ // TODO: hack for now until I figure out how to get correct
204+ // filesize from sftp,scp,rsync
205+ entry.Size = int64(fileSize)
206+ userName := s.User()
207+
208+ bucket, err := getBucket(s)
209+ if err != nil {
210+ h.Cfg.Logger.Error(err.Error())
211+ return "", err
212+ }
213+
214+ data := &FileData{
215+ FileEntry: entry,
216+ User: userName,
217+ Text: origText,
218+ Bucket: bucket,
219+ }
220+ err = h.writeAsset(s, data)
221+ if err != nil {
222+ h.Cfg.Logger.Error(err.Error())
223+ return "", err
224+ }
225+
226+ url, err := h.Cfg.AssetNames.PrintObjectName(s, entry, bucket.Name)
227+ if err != nil {
228+ return "", err
229+ }
230+ return url, nil
231+}
232+
233+func (h *UploadAssetHandler) validateAsset(_ *FileData) (bool, error) {
234+ return true, nil
235+}
236+
237+func (h *UploadAssetHandler) writeAsset(s *pssh.SSHServerConnSession, data *FileData) error {
238+ valid, err := h.validateAsset(data)
239+ if !valid {
240+ return err
241+ }
242+
243+ objectFileName, err := h.Cfg.AssetNames.ObjectName(s, data.FileEntry)
244+ if err != nil {
245+ return err
246+ }
247+ reader := bytes.NewReader(data.Text)
248+
249+ h.Cfg.Logger.Info(
250+ "uploading file to bucket",
251+ "user",
252+ data.User,
253+ "bucket",
254+ data.Bucket.Name,
255+ "object",
256+ objectFileName,
257+ )
258+
259+ _, _, err = h.Cfg.Storage.PutObject(
260+ data.Bucket,
261+ objectFileName,
262+ utils.NopReadAndReaderAtCloser(reader),
263+ data.FileEntry,
264+ )
265+ if err != nil {
266+ return err
267+ }
268+
269+ return nil
270+}
+42,
-0
1@@ -0,0 +1,42 @@
2+package pobj
3+
4+import (
5+ "errors"
6+ "io"
7+ "net/http"
8+
9+ "github.com/minio/minio-go/v7"
10+ "github.com/picosh/pico/pkg/send/utils"
11+)
12+
13+type AllReaderAt struct {
14+ Reader utils.ReadAndReaderAtCloser
15+}
16+
17+func NewAllReaderAt(reader utils.ReadAndReaderAtCloser) *AllReaderAt {
18+ return &AllReaderAt{reader}
19+}
20+
21+func (a *AllReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
22+ n, err = a.Reader.ReadAt(p, off)
23+
24+ if errors.Is(err, io.EOF) {
25+ return
26+ }
27+
28+ resp := minio.ToErrorResponse(err)
29+
30+ if resp.StatusCode == http.StatusRequestedRangeNotSatisfiable {
31+ err = io.EOF
32+ }
33+
34+ return
35+}
36+
37+func (a *AllReaderAt) Read(p []byte) (int, error) {
38+ return a.Reader.Read(p)
39+}
40+
41+func (a *AllReaderAt) Close() error {
42+ return a.Reader.Close()
43+}
+218,
-0
1@@ -0,0 +1,218 @@
2+package storage
3+
4+import (
5+ "fmt"
6+ "io"
7+ "io/fs"
8+ "log/slog"
9+ "os"
10+ "path"
11+ "path/filepath"
12+ "strings"
13+ "time"
14+
15+ "github.com/picosh/pico/pkg/send/utils"
16+)
17+
18+// https://stackoverflow.com/a/32482941
19+func dirSize(path string) (int64, error) {
20+ var size int64
21+ err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
22+ if err != nil {
23+ return err
24+ }
25+ if !info.IsDir() {
26+ size += info.Size()
27+ }
28+ return err
29+ })
30+
31+ return size, err
32+}
33+
34+type StorageFS struct {
35+ Dir string
36+ Logger *slog.Logger
37+}
38+
39+var _ ObjectStorage = &StorageFS{}
40+var _ ObjectStorage = (*StorageFS)(nil)
41+
42+func NewStorageFS(logger *slog.Logger, dir string) (*StorageFS, error) {
43+ return &StorageFS{Logger: logger, Dir: dir}, nil
44+}
45+
46+func (s *StorageFS) GetBucket(name string) (Bucket, error) {
47+ dirPath := filepath.Join(s.Dir, name)
48+ bucket := Bucket{
49+ Name: name,
50+ Path: dirPath,
51+ }
52+ s.Logger.Info("get bucket", "dir", dirPath)
53+
54+ info, err := os.Stat(dirPath)
55+ if os.IsNotExist(err) {
56+ return bucket, fmt.Errorf("directory does not exist: %v %w", dirPath, err)
57+ }
58+
59+ if err != nil {
60+ return bucket, fmt.Errorf("directory error: %v %w", dirPath, err)
61+
62+ }
63+
64+ if !info.IsDir() {
65+ return bucket, fmt.Errorf("directory is a file, not a directory: %#v", dirPath)
66+ }
67+
68+ return bucket, nil
69+}
70+
71+func (s *StorageFS) UpsertBucket(name string) (Bucket, error) {
72+ s.Logger.Info("upsert bucket", "name", name)
73+ bucket, err := s.GetBucket(name)
74+ if err == nil {
75+ return bucket, nil
76+ }
77+
78+ dir := filepath.Join(s.Dir, bucket.Path)
79+ s.Logger.Info("bucket not found, creating", "dir", dir, "err", err)
80+ err = os.MkdirAll(dir, os.ModePerm)
81+ if err != nil {
82+ return bucket, err
83+ }
84+
85+ return bucket, nil
86+}
87+
88+func (s *StorageFS) GetBucketQuota(bucket Bucket) (uint64, error) {
89+ dsize, err := dirSize(bucket.Path)
90+ return uint64(dsize), err
91+}
92+
93+func (s *StorageFS) DeleteBucket(bucket Bucket) error {
94+ return os.RemoveAll(bucket.Path)
95+}
96+
97+func (s *StorageFS) GetObject(bucket Bucket, fpath string) (utils.ReadAndReaderAtCloser, *ObjectInfo, error) {
98+ objInfo := &ObjectInfo{
99+ LastModified: time.Time{},
100+ Metadata: nil,
101+ UserMetadata: map[string]string{},
102+ }
103+
104+ dat, err := os.Open(filepath.Join(bucket.Path, fpath))
105+ if err != nil {
106+ return nil, objInfo, err
107+ }
108+
109+ info, err := dat.Stat()
110+ if err != nil {
111+ return nil, objInfo, err
112+ }
113+
114+ objInfo.Size = info.Size()
115+ objInfo.LastModified = info.ModTime()
116+ return dat, objInfo, nil
117+}
118+
119+func (s *StorageFS) PutObject(bucket Bucket, fpath string, contents io.Reader, entry *utils.FileEntry) (string, int64, error) {
120+ loc := filepath.Join(bucket.Path, fpath)
121+ err := os.MkdirAll(filepath.Dir(loc), os.ModePerm)
122+ if err != nil {
123+ return "", 0, err
124+ }
125+ f, err := os.OpenFile(loc, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
126+ if err != nil {
127+ return "", 0, err
128+ }
129+
130+ size, err := io.Copy(f, contents)
131+ if err != nil {
132+ return "", 0, err
133+ }
134+
135+ f.Close()
136+
137+ if entry.Mtime > 0 {
138+ uTime := time.Unix(entry.Mtime, 0)
139+ _ = os.Chtimes(loc, uTime, uTime)
140+ }
141+
142+ return loc, size, nil
143+}
144+
145+func (s *StorageFS) DeleteObject(bucket Bucket, fpath string) error {
146+ loc := filepath.Join(bucket.Path, fpath)
147+ err := os.Remove(loc)
148+ if err != nil {
149+ return err
150+ }
151+
152+ return nil
153+}
154+
155+func (s *StorageFS) ListBuckets() ([]string, error) {
156+ return []string{}, fmt.Errorf("not implemented")
157+}
158+
159+func (s *StorageFS) ListObjects(bucket Bucket, dir string, recursive bool) ([]os.FileInfo, error) {
160+ var fileList []os.FileInfo
161+
162+ fpath := path.Join(bucket.Path, dir)
163+
164+ info, err := os.Stat(fpath)
165+ if err != nil {
166+ return fileList, err
167+ }
168+
169+ if info.IsDir() && !strings.HasSuffix(dir, "/") {
170+ fileList = append(fileList, &utils.VirtualFile{
171+ FName: "",
172+ FIsDir: info.IsDir(),
173+ FSize: info.Size(),
174+ FModTime: info.ModTime(),
175+ })
176+
177+ return fileList, err
178+ }
179+
180+ var files []fs.DirEntry
181+
182+ if recursive {
183+ err = filepath.WalkDir(fpath, func(s string, d fs.DirEntry, err error) error {
184+ if err != nil {
185+ return err
186+ }
187+ files = append(files, d)
188+ return nil
189+ })
190+ if err != nil {
191+ fileList = append(fileList, info)
192+ return fileList, nil
193+ }
194+ } else {
195+ files, err = os.ReadDir(fpath)
196+ if err != nil {
197+ fileList = append(fileList, info)
198+ return fileList, nil
199+ }
200+ }
201+
202+ for _, f := range files {
203+ info, err := f.Info()
204+ if err != nil {
205+ return fileList, err
206+ }
207+
208+ i := &utils.VirtualFile{
209+ FName: f.Name(),
210+ FIsDir: f.IsDir(),
211+ FSize: info.Size(),
212+ FModTime: info.ModTime(),
213+ }
214+
215+ fileList = append(fileList, i)
216+ }
217+
218+ return fileList, err
219+}
+208,
-0
1@@ -0,0 +1,208 @@
2+package storage
3+
4+import (
5+ "fmt"
6+ "io"
7+ "os"
8+ "path/filepath"
9+ "strings"
10+ "sync"
11+ "time"
12+
13+ "github.com/picosh/pico/pkg/send/utils"
14+)
15+
16+type StorageMemory struct {
17+ storage map[string]map[string]string
18+ mu sync.RWMutex
19+}
20+
21+var _ ObjectStorage = &StorageMemory{}
22+var _ ObjectStorage = (*StorageMemory)(nil)
23+
24+func NewStorageMemory(st map[string]map[string]string) (*StorageMemory, error) {
25+ return &StorageMemory{
26+ storage: st,
27+ }, nil
28+}
29+
30+func (s *StorageMemory) GetBucket(name string) (Bucket, error) {
31+ s.mu.RLock()
32+ defer s.mu.RUnlock()
33+
34+ bucket := Bucket{
35+ Name: name,
36+ Path: name,
37+ }
38+
39+ _, ok := s.storage[name]
40+ if !ok {
41+ return bucket, fmt.Errorf("bucket does not exist")
42+ }
43+
44+ return bucket, nil
45+}
46+
47+func (s *StorageMemory) UpsertBucket(name string) (Bucket, error) {
48+ bucket, err := s.GetBucket(name)
49+ if err == nil {
50+ return bucket, nil
51+ }
52+
53+ s.mu.Lock()
54+ defer s.mu.Unlock()
55+
56+ s.storage[name] = map[string]string{}
57+ return bucket, nil
58+}
59+
60+func (s *StorageMemory) GetBucketQuota(bucket Bucket) (uint64, error) {
61+ s.mu.RLock()
62+ defer s.mu.RUnlock()
63+
64+ objects := s.storage[bucket.Path]
65+ size := 0
66+ for _, val := range objects {
67+ size += len([]byte(val))
68+ }
69+ return uint64(size), nil
70+}
71+
72+func (s *StorageMemory) DeleteBucket(bucket Bucket) error {
73+ s.mu.Lock()
74+ defer s.mu.Unlock()
75+
76+ delete(s.storage, bucket.Path)
77+ return nil
78+}
79+
80+func (s *StorageMemory) GetObject(bucket Bucket, fpath string) (utils.ReadAndReaderAtCloser, *ObjectInfo, error) {
81+ s.mu.RLock()
82+ defer s.mu.RUnlock()
83+
84+ if !strings.HasPrefix(fpath, "/") {
85+ fpath = "/" + fpath
86+ }
87+
88+ objInfo := &ObjectInfo{
89+ LastModified: time.Time{},
90+ Metadata: nil,
91+ UserMetadata: map[string]string{},
92+ }
93+
94+ dat, ok := s.storage[bucket.Path][fpath]
95+ if !ok {
96+ return nil, objInfo, fmt.Errorf("object does not exist: %s", fpath)
97+ }
98+
99+ objInfo.Size = int64(len([]byte(dat)))
100+ reader := utils.NopReadAndReaderAtCloser(strings.NewReader(dat))
101+ return reader, objInfo, nil
102+}
103+
104+func (s *StorageMemory) PutObject(bucket Bucket, fpath string, contents io.Reader, entry *utils.FileEntry) (string, int64, error) {
105+ s.mu.Lock()
106+ defer s.mu.Unlock()
107+
108+ d, err := io.ReadAll(contents)
109+ if err != nil {
110+ return "", 0, err
111+ }
112+
113+ s.storage[bucket.Path][fpath] = string(d)
114+ return fmt.Sprintf("%s%s", bucket.Path, fpath), int64(len(d)), nil
115+}
116+
117+func (s *StorageMemory) DeleteObject(bucket Bucket, fpath string) error {
118+ s.mu.Lock()
119+ defer s.mu.Unlock()
120+
121+ delete(s.storage[bucket.Path], fpath)
122+ return nil
123+}
124+
125+func (s *StorageMemory) ListBuckets() ([]string, error) {
126+ s.mu.RLock()
127+ defer s.mu.RUnlock()
128+
129+ buckets := []string{}
130+ for key := range s.storage {
131+ buckets = append(buckets, key)
132+ }
133+ return buckets, nil
134+}
135+
136+func (s *StorageMemory) ListObjects(bucket Bucket, dir string, recursive bool) ([]os.FileInfo, error) {
137+ s.mu.RLock()
138+ defer s.mu.RUnlock()
139+
140+ var fileList []os.FileInfo
141+
142+ resolved := dir
143+
144+ if !strings.HasPrefix(resolved, "/") {
145+ resolved = "/" + resolved
146+ }
147+
148+ objects := s.storage[bucket.Path]
149+ // dir is actually an object
150+ oval, ok := objects[resolved]
151+ if ok {
152+ fileList = append(fileList, &utils.VirtualFile{
153+ FName: filepath.Base(resolved),
154+ FIsDir: false,
155+ FSize: int64(len([]byte(oval))),
156+ FModTime: time.Time{},
157+ })
158+ return fileList, nil
159+ }
160+
161+ for key, val := range objects {
162+ if !strings.HasPrefix(key, resolved) {
163+ continue
164+ }
165+
166+ rep := strings.Replace(key, resolved, "", 1)
167+ fdir := filepath.Dir(rep)
168+ fname := filepath.Base(rep)
169+ paths := strings.Split(fdir, "/")
170+
171+ if fdir == "/" {
172+ ffname := filepath.Base(resolved)
173+ fileList = append(fileList, &utils.VirtualFile{
174+ FName: ffname,
175+ FIsDir: true,
176+ })
177+ }
178+
179+ for _, p := range paths {
180+ if p == "" || p == "/" || p == "." {
181+ continue
182+ }
183+ fileList = append(fileList, &utils.VirtualFile{
184+ FName: p,
185+ FIsDir: true,
186+ })
187+ }
188+
189+ trimRes := strings.TrimSuffix(resolved, "/")
190+ dirKey := filepath.Dir(key)
191+ if recursive {
192+ fileList = append(fileList, &utils.VirtualFile{
193+ FName: fname,
194+ FIsDir: false,
195+ FSize: int64(len([]byte(val))),
196+ FModTime: time.Time{},
197+ })
198+ } else if resolved == dirKey || trimRes == dirKey {
199+ fileList = append(fileList, &utils.VirtualFile{
200+ FName: fname,
201+ FIsDir: false,
202+ FSize: int64(len([]byte(val))),
203+ FModTime: time.Time{},
204+ })
205+ }
206+ }
207+
208+ return fileList, nil
209+}
+216,
-0
1@@ -0,0 +1,216 @@
2+package storage
3+
4+import (
5+ "context"
6+ "errors"
7+ "fmt"
8+ "io"
9+ "net/url"
10+ "os"
11+ "strconv"
12+ "strings"
13+ "time"
14+
15+ "github.com/minio/madmin-go/v3"
16+ "github.com/minio/minio-go/v7"
17+ "github.com/minio/minio-go/v7/pkg/credentials"
18+ "github.com/picosh/pico/pkg/send/utils"
19+)
20+
21+type StorageMinio struct {
22+ Client *minio.Client
23+ Admin *madmin.AdminClient
24+}
25+
26+var _ ObjectStorage = &StorageMinio{}
27+var _ ObjectStorage = (*StorageMinio)(nil)
28+
29+func NewStorageMinio(address, user, pass string) (*StorageMinio, error) {
30+ endpoint, err := url.Parse(address)
31+ if err != nil {
32+ return nil, err
33+ }
34+ ssl := endpoint.Scheme == "https"
35+
36+ mClient, err := minio.New(endpoint.Host, &minio.Options{
37+ Creds: credentials.NewStaticV4(user, pass, ""),
38+ Secure: ssl,
39+ })
40+ if err != nil {
41+ return nil, err
42+ }
43+
44+ aClient, err := madmin.NewWithOptions(
45+ endpoint.Host,
46+ &madmin.Options{
47+ Creds: credentials.NewStaticV4(user, pass, ""),
48+ Secure: ssl,
49+ },
50+ )
51+ if err != nil {
52+ return nil, err
53+ }
54+
55+ mini := &StorageMinio{
56+ Client: mClient,
57+ Admin: aClient,
58+ }
59+ return mini, err
60+}
61+
62+func (s *StorageMinio) GetBucket(name string) (Bucket, error) {
63+ bucket := Bucket{
64+ Name: name,
65+ }
66+
67+ exists, err := s.Client.BucketExists(context.TODO(), bucket.Name)
68+ if err != nil || !exists {
69+ if err == nil {
70+ err = errors.New("bucket does not exist")
71+ }
72+ return bucket, err
73+ }
74+
75+ return bucket, nil
76+}
77+
78+func (s *StorageMinio) UpsertBucket(name string) (Bucket, error) {
79+ bucket, err := s.GetBucket(name)
80+ if err == nil {
81+ return bucket, nil
82+ }
83+
84+ err = s.Client.MakeBucket(context.TODO(), name, minio.MakeBucketOptions{})
85+ if err != nil {
86+ return bucket, err
87+ }
88+
89+ return bucket, nil
90+}
91+
92+func (s *StorageMinio) GetBucketQuota(bucket Bucket) (uint64, error) {
93+ info, err := s.Admin.AccountInfo(context.TODO(), madmin.AccountOpts{})
94+ if err != nil {
95+ return 0, nil
96+ }
97+ for _, b := range info.Buckets {
98+ if b.Name == bucket.Name {
99+ return b.Size, nil
100+ }
101+ }
102+
103+ return 0, fmt.Errorf("%s bucket not found in account info", bucket.Name)
104+}
105+
106+func (s *StorageMinio) ListBuckets() ([]string, error) {
107+ bcks := []string{}
108+ buckets, err := s.Client.ListBuckets(context.Background())
109+ if err != nil {
110+ return bcks, err
111+ }
112+ for _, bucket := range buckets {
113+ bcks = append(bcks, bucket.Name)
114+ }
115+
116+ return bcks, nil
117+}
118+
119+func (s *StorageMinio) ListObjects(bucket Bucket, dir string, recursive bool) ([]os.FileInfo, error) {
120+ var fileList []os.FileInfo
121+
122+ resolved := strings.TrimPrefix(dir, "/")
123+
124+ opts := minio.ListObjectsOptions{Prefix: resolved, Recursive: recursive, WithMetadata: true}
125+ for obj := range s.Client.ListObjects(context.Background(), bucket.Name, opts) {
126+ if obj.Err != nil {
127+ return fileList, obj.Err
128+ }
129+
130+ isDir := strings.HasSuffix(obj.Key, string(os.PathSeparator))
131+
132+ modTime := obj.LastModified
133+
134+ if mtime, ok := obj.UserMetadata["Mtime"]; ok {
135+ mtimeUnix, err := strconv.Atoi(mtime)
136+ if err == nil {
137+ modTime = time.Unix(int64(mtimeUnix), 0)
138+ }
139+ }
140+
141+ info := &utils.VirtualFile{
142+ FName: strings.TrimSuffix(strings.TrimPrefix(obj.Key, resolved), "/"),
143+ FIsDir: isDir,
144+ FSize: obj.Size,
145+ FModTime: modTime,
146+ }
147+ fileList = append(fileList, info)
148+ }
149+
150+ return fileList, nil
151+}
152+
153+func (s *StorageMinio) DeleteBucket(bucket Bucket) error {
154+ return s.Client.RemoveBucket(context.TODO(), bucket.Name)
155+}
156+
157+func (s *StorageMinio) GetObject(bucket Bucket, fpath string) (utils.ReadAndReaderAtCloser, *ObjectInfo, error) {
158+ objInfo := &ObjectInfo{
159+ Size: 0,
160+ LastModified: time.Time{},
161+ ETag: "",
162+ }
163+
164+ info, err := s.Client.StatObject(context.Background(), bucket.Name, fpath, minio.StatObjectOptions{})
165+ if err != nil {
166+ return nil, objInfo, err
167+ }
168+
169+ objInfo.LastModified = info.LastModified
170+ objInfo.ETag = info.ETag
171+ objInfo.Metadata = info.Metadata
172+ objInfo.UserMetadata = info.UserMetadata
173+ objInfo.Size = info.Size
174+
175+ obj, err := s.Client.GetObject(context.Background(), bucket.Name, fpath, minio.GetObjectOptions{})
176+ if err != nil {
177+ return nil, objInfo, err
178+ }
179+
180+ if mtime, ok := info.UserMetadata["Mtime"]; ok {
181+ mtimeUnix, err := strconv.Atoi(mtime)
182+ if err == nil {
183+ objInfo.LastModified = time.Unix(int64(mtimeUnix), 0)
184+ }
185+ }
186+
187+ return obj, objInfo, nil
188+}
189+
190+func (s *StorageMinio) PutObject(bucket Bucket, fpath string, contents io.Reader, entry *utils.FileEntry) (string, int64, error) {
191+ opts := minio.PutObjectOptions{
192+ UserMetadata: map[string]string{
193+ "Mtime": fmt.Sprint(time.Now().Unix()),
194+ },
195+ }
196+
197+ if entry.Mtime > 0 {
198+ opts.UserMetadata["Mtime"] = fmt.Sprint(entry.Mtime)
199+ }
200+
201+ var objSize int64 = -1
202+ if entry.Size > 0 {
203+ objSize = entry.Size
204+ }
205+ info, err := s.Client.PutObject(context.TODO(), bucket.Name, fpath, contents, objSize, opts)
206+
207+ if err != nil {
208+ return "", 0, err
209+ }
210+
211+ return fmt.Sprintf("%s/%s", info.Bucket, info.Key), info.Size, nil
212+}
213+
214+func (s *StorageMinio) DeleteObject(bucket Bucket, fpath string) error {
215+ err := s.Client.RemoveObject(context.TODO(), bucket.Name, fpath, minio.RemoveObjectOptions{})
216+ return err
217+}
+37,
-0
1@@ -0,0 +1,37 @@
2+package storage
3+
4+import (
5+ "io"
6+ "net/http"
7+ "os"
8+ "time"
9+
10+ "github.com/picosh/pico/pkg/send/utils"
11+)
12+
13+type Bucket struct {
14+ Name string
15+ Path string
16+ Root string
17+}
18+
19+type ObjectStorage interface {
20+ GetBucket(name string) (Bucket, error)
21+ GetBucketQuota(bucket Bucket) (uint64, error)
22+ UpsertBucket(name string) (Bucket, error)
23+ ListBuckets() ([]string, error)
24+ DeleteBucket(bucket Bucket) error
25+
26+ GetObject(bucket Bucket, fpath string) (utils.ReadAndReaderAtCloser, *ObjectInfo, error)
27+ PutObject(bucket Bucket, fpath string, contents io.Reader, entry *utils.FileEntry) (string, int64, error)
28+ DeleteObject(bucket Bucket, fpath string) error
29+ ListObjects(bucket Bucket, dir string, recursive bool) ([]os.FileInfo, error)
30+}
31+
32+type ObjectInfo struct {
33+ Size int64
34+ LastModified time.Time
35+ ETag string
36+ Metadata http.Header
37+ UserMetadata map[string]string
38+}
+39,
-0
1@@ -0,0 +1,39 @@
2+package pobj
3+
4+import (
5+ "log/slog"
6+ "os"
7+
8+ "github.com/picosh/pico/pkg/pobj/storage"
9+)
10+
11+func GetEnv(key string, defaultVal string) string {
12+ if value, exists := os.LookupEnv(key); exists {
13+ return value
14+ }
15+ return defaultVal
16+}
17+
18+func EnvDriverDetector(logger *slog.Logger) (storage.ObjectStorage, error) {
19+ driver := GetEnv("OBJECT_DRIVER", "fs")
20+ logger.Info("driver detected", "driver", driver)
21+
22+ if driver == "memory" {
23+ return storage.NewStorageMemory(map[string]map[string]string{})
24+ } else if driver == "minio" {
25+ url := GetEnv("MINIO_URL", "")
26+ user := GetEnv("MINIO_ROOT_USER", "")
27+ pass := GetEnv("MINIO_ROOT_PASSWORD", "")
28+ logger.Info(
29+ "object config detected",
30+ "url", url,
31+ "user", user,
32+ )
33+ return storage.NewStorageMinio(url, user, pass)
34+ }
35+
36+ // implied driver == "fs"
37+ storageDir := GetEnv("OBJECT_URL", "./.storage")
38+ logger.Info("object config detected", "dir", storageDir)
39+ return storage.NewStorageFS(logger, storageDir)
40+}
+111,
-0
1@@ -0,0 +1,111 @@
2+package pssh
3+
4+import (
5+ "log/slog"
6+ "time"
7+
8+ "github.com/picosh/pico/pkg/db"
9+)
10+
11+type ctxLoggerKey struct{}
12+type ctxUserKey struct{}
13+
14+type FindUserInterface interface {
15+ FindUserByPubkey(string) (*db.User, error)
16+}
17+
18+type GetLoggerInterface interface {
19+ GetLogger(s *SSHServerConnSession) *slog.Logger
20+}
21+
22+func LogMiddleware(getLogger GetLoggerInterface, db FindUserInterface) SSHServerMiddleware {
23+ return func(sshHandler SSHServerHandler) SSHServerHandler {
24+ return func(s *SSHServerConnSession) error {
25+ ct := time.Now()
26+
27+ logger := GetLogger(s)
28+ if logger == slog.Default() {
29+ logger = getLogger.GetLogger(s)
30+
31+ user := GetUser(s)
32+ if user == nil {
33+ user, err := db.FindUserByPubkey(s.Permissions().Extensions["pubkey"])
34+ if err == nil && user != nil {
35+ logger = logger.With(
36+ "user", user.Name,
37+ "userId", user.ID,
38+ "ip", s.RemoteAddr().String(),
39+ )
40+ s.SetValue(ctxUserKey{}, user)
41+ }
42+ }
43+
44+ s.SetValue(ctxLoggerKey{}, logger)
45+ }
46+
47+ pty, _, ok := s.Pty()
48+
49+ width, height := 0, 0
50+ term := ""
51+ if pty != nil {
52+ term = pty.Term
53+ width = pty.Window.Width
54+ height = pty.Window.Height
55+ }
56+
57+ logger.Info(
58+ "connect",
59+ "sshUser", s.User(),
60+ "pty", ok,
61+ "term", term,
62+ "windowWidth", width,
63+ "windowHeight", height,
64+ )
65+
66+ err := sshHandler(s)
67+ if err != nil {
68+ logger.Error("error", "err", err)
69+ }
70+
71+ if pty != nil {
72+ term = pty.Term
73+ width = pty.Window.Width
74+ height = pty.Window.Height
75+ }
76+
77+ logger.Info(
78+ "disconnect",
79+ "sshUser", s.User(),
80+ "pty", ok,
81+ "term", term,
82+ "windowWidth", width,
83+ "windowHeight", height,
84+ "duration", time.Since(ct),
85+ "err", err,
86+ )
87+
88+ return err
89+ }
90+ }
91+}
92+
93+func GetLogger(s *SSHServerConnSession) *slog.Logger {
94+ logger := slog.Default()
95+ if s == nil {
96+ return logger
97+ }
98+
99+ if v, ok := s.Context().Value(ctxLoggerKey{}).(*slog.Logger); ok {
100+ return v
101+ }
102+
103+ return logger
104+}
105+
106+func GetUser(s *SSHServerConnSession) *db.User {
107+ if v, ok := s.Context().Value(ctxUserKey{}).(*db.User); ok {
108+ return v
109+ }
110+
111+ return nil
112+}
+122,
-0
1@@ -0,0 +1,122 @@
2+package pssh
3+
4+import (
5+ "encoding/binary"
6+ "fmt"
7+)
8+
9+func SessionMessage(sesh *SSHServerConnSession, msg string) {
10+ _, _ = sesh.Write([]byte(msg + "\r\n"))
11+}
12+
13+func DeprecatedNotice() SSHServerMiddleware {
14+ return func(next SSHServerHandler) SSHServerHandler {
15+ return func(sesh *SSHServerConnSession) error {
16+ msg := fmt.Sprintf(
17+ "%s\r\n\r\nRun %s to access pico's TUI",
18+ "DEPRECATED",
19+ "ssh pico.sh",
20+ )
21+ SessionMessage(sesh, msg)
22+ return next(sesh)
23+ }
24+ }
25+}
26+
27+func PtyMdw(mdw SSHServerMiddleware) SSHServerMiddleware {
28+ return func(next SSHServerHandler) SSHServerHandler {
29+ return func(sesh *SSHServerConnSession) error {
30+ _, _, ok := sesh.Pty()
31+ if !ok {
32+ return next(sesh)
33+ }
34+ return mdw(next)(sesh)
35+ }
36+ }
37+}
38+
39+type Window struct {
40+ Width int
41+ Height int
42+ HeightPixels int
43+ WidthPixels int
44+}
45+
46+type Pty struct {
47+ Term string
48+ Window Window
49+}
50+
51+func (p *Pty) Resize(width, height int) error {
52+ return nil
53+}
54+
55+func (p *Pty) Name() string {
56+ return ""
57+}
58+
59+func parsePtyRequest(s []byte) (pty Pty, ok bool) {
60+ term, s, ok := parseString(s)
61+ if !ok {
62+ return
63+ }
64+ width32, s, ok := parseUint32(s)
65+ if !ok {
66+ return
67+ }
68+ height32, _, ok := parseUint32(s)
69+ if !ok {
70+ return
71+ }
72+ pty = Pty{
73+ Term: term,
74+ Window: Window{
75+ Width: int(width32),
76+ Height: int(height32),
77+ },
78+ }
79+ return
80+}
81+
82+func parseWinchRequest(s []byte) (win Window, ok bool) {
83+ width32, s, ok := parseUint32(s)
84+ if width32 < 1 {
85+ ok = false
86+ }
87+ if !ok {
88+ return
89+ }
90+ height32, _, ok := parseUint32(s)
91+ if height32 < 1 {
92+ ok = false
93+ }
94+ if !ok {
95+ return
96+ }
97+ win = Window{
98+ Width: int(width32),
99+ Height: int(height32),
100+ }
101+ return
102+}
103+
104+func parseString(in []byte) (out string, rest []byte, ok bool) {
105+ if len(in) < 4 {
106+ return
107+ }
108+ length := binary.BigEndian.Uint32(in)
109+ if uint32(len(in)) < 4+length {
110+ return
111+ }
112+ out = string(in[4 : 4+length])
113+ rest = in[4+length:]
114+ ok = true
115+ return
116+}
117+
118+func parseUint32(in []byte) (uint32, []byte, bool) {
119+ if len(in) < 4 {
120+ return 0, nil, false
121+ }
122+ return binary.BigEndian.Uint32(in), in[4:], true
123+}
+702,
-0
1@@ -0,0 +1,702 @@
2+package pssh
3+
4+import (
5+ "context"
6+ "crypto/ed25519"
7+ "crypto/rand"
8+ "crypto/subtle"
9+ "encoding/pem"
10+ "errors"
11+ "fmt"
12+ "io"
13+ "log/slog"
14+ "net"
15+ "net/http"
16+ "os"
17+ "strings"
18+ "sync"
19+ "time"
20+
21+ "github.com/antoniomika/syncmap"
22+ "github.com/prometheus/client_golang/prometheus"
23+ "github.com/prometheus/client_golang/prometheus/promauto"
24+ "github.com/prometheus/client_golang/prometheus/promhttp"
25+ "golang.org/x/crypto/ssh"
26+)
27+
28+type SSHServerConn struct {
29+ Ctx context.Context
30+ CancelFunc context.CancelFunc
31+ Logger *slog.Logger
32+ Conn *ssh.ServerConn
33+ SSHServer *SSHServer
34+ Start time.Time
35+
36+ mu sync.Mutex
37+}
38+
39+func (s *SSHServerConn) Context() context.Context {
40+ s.mu.Lock()
41+ defer s.mu.Unlock()
42+
43+ return s.Ctx
44+}
45+
46+func (sc *SSHServerConn) Close() error {
47+ sc.CancelFunc()
48+ return nil
49+}
50+
51+type SSHServerConnSession struct {
52+ ssh.Channel
53+ *SSHServerConn
54+
55+ Ctx context.Context
56+ CancelFunc context.CancelFunc
57+
58+ pty *Pty
59+ winch chan Window
60+
61+ mu sync.Mutex
62+}
63+
64+// Deadline implements context.Context.
65+func (s *SSHServerConnSession) Deadline() (deadline time.Time, ok bool) {
66+ s.mu.Lock()
67+ defer s.mu.Unlock()
68+
69+ return s.Ctx.Deadline()
70+}
71+
72+// Done implements context.Context.
73+func (s *SSHServerConnSession) Done() <-chan struct{} {
74+ s.mu.Lock()
75+ defer s.mu.Unlock()
76+
77+ return s.Ctx.Done()
78+}
79+
80+// Err implements context.Context.
81+func (s *SSHServerConnSession) Err() error {
82+ s.mu.Lock()
83+ defer s.mu.Unlock()
84+
85+ return s.Ctx.Err()
86+}
87+
88+// Value implements context.Context.
89+func (s *SSHServerConnSession) Value(key any) any {
90+ s.mu.Lock()
91+ defer s.mu.Unlock()
92+
93+ return s.Ctx.Value(key)
94+}
95+
96+// SetValue implements context.Context.
97+func (s *SSHServerConnSession) SetValue(key any, data any) {
98+ s.mu.Lock()
99+ defer s.mu.Unlock()
100+
101+ s.Ctx = context.WithValue(s.Ctx, key, data)
102+}
103+
104+func (s *SSHServerConnSession) Context() context.Context {
105+ s.mu.Lock()
106+ defer s.mu.Unlock()
107+
108+ return s.Ctx
109+}
110+
111+func (s *SSHServerConnSession) Permissions() *ssh.Permissions {
112+ return s.Conn.Permissions
113+}
114+
115+func (s *SSHServerConnSession) User() string {
116+ return s.Conn.User()
117+}
118+
119+func (s *SSHServerConnSession) PublicKey() ssh.PublicKey {
120+ key, ok := s.Conn.Permissions.Extensions["pubkey"]
121+ if !ok {
122+ return nil
123+ }
124+
125+ pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
126+ if err != nil {
127+ return nil
128+ }
129+ return pk
130+}
131+
132+func (s *SSHServerConnSession) RemoteAddr() net.Addr {
133+ return s.Conn.RemoteAddr()
134+}
135+
136+func (s *SSHServerConnSession) Command() []string {
137+ cmd, _ := s.Value("command").([]string)
138+ return cmd
139+}
140+
141+func (s *SSHServerConnSession) Close() error {
142+ s.CancelFunc()
143+ return s.Channel.Close()
144+}
145+
146+func (s *SSHServerConnSession) Exit(code int) error {
147+ status := struct{ Status uint32 }{uint32(code)}
148+ _, err := s.Channel.SendRequest("exit-status", false, ssh.Marshal(&status))
149+ return err
150+}
151+
152+func (s *SSHServerConnSession) Fatal(err error) {
153+ fmt.Fprintln(s.Stderr(), err)
154+ fmt.Fprintf(s.Stderr(), "\r")
155+ _ = s.Exit(1)
156+ _ = s.Close()
157+}
158+
159+func (s *SSHServerConnSession) Pty() (*Pty, <-chan Window, bool) {
160+ s.mu.Lock()
161+ defer s.mu.Unlock()
162+
163+ if s.pty == nil {
164+ return nil, nil, false
165+ }
166+
167+ return s.pty, s.winch, true
168+}
169+
170+var _ context.Context = &SSHServerConnSession{}
171+
172+func (sc *SSHServerConn) Handle(chans <-chan ssh.NewChannel, reqs <-chan *ssh.Request) error {
173+ defer sc.Close()
174+
175+ for {
176+ select {
177+ case <-sc.Context().Done():
178+ return nil
179+ case newChan, ok := <-chans:
180+ if !ok {
181+ return nil
182+ }
183+
184+ sc.Logger.Info("new channel", "type", newChan.ChannelType(), "extraData", newChan.ExtraData())
185+ chanFunc, ok := sc.SSHServer.Config.ChannelMiddleware[newChan.ChannelType()]
186+ if !ok {
187+ sc.Logger.Info("no channel middleware for type", "type", newChan.ChannelType())
188+ continue
189+ }
190+
191+ go func() {
192+ err := chanFunc(newChan, sc)
193+ if err != nil {
194+ sc.Logger.Error("channel middleware", "err", err)
195+ }
196+ }()
197+ case req, ok := <-reqs:
198+ if !ok {
199+ return nil
200+ }
201+ sc.Logger.Info("new request", "type", req.Type, "wantReply", req.WantReply, "payload", req.Payload)
202+ }
203+ }
204+}
205+
206+func NewSSHServerConn(
207+ ctx context.Context,
208+ logger *slog.Logger,
209+ conn *ssh.ServerConn,
210+ server *SSHServer,
211+) *SSHServerConn {
212+ if ctx == nil {
213+ ctx = context.Background()
214+ }
215+
216+ cancelCtx, cancelFunc := context.WithCancel(ctx)
217+
218+ if logger == nil {
219+ logger = slog.Default()
220+ }
221+
222+ return &SSHServerConn{
223+ Ctx: cancelCtx,
224+ CancelFunc: cancelFunc,
225+ Logger: logger,
226+ Conn: conn,
227+ SSHServer: server,
228+ Start: time.Now(),
229+ }
230+}
231+
232+type SSHServerHandler func(*SSHServerConnSession) error
233+type SSHServerMiddleware func(SSHServerHandler) SSHServerHandler
234+type SSHServerChannelMiddleware func(ssh.NewChannel, *SSHServerConn) error
235+
236+type SSHServerConfig struct {
237+ *ssh.ServerConfig
238+ App string
239+ ListenAddr string
240+ PromListenAddr string
241+ Middleware []SSHServerMiddleware
242+ SubsystemMiddleware []SSHServerMiddleware
243+ ChannelMiddleware map[string]SSHServerChannelMiddleware
244+}
245+
246+type SSHServer struct {
247+ Ctx context.Context
248+ CancelFunc context.CancelFunc
249+ Logger *slog.Logger
250+ Config *SSHServerConfig
251+ Listener net.Listener
252+ Conns *syncmap.Map[string, *SSHServerConn]
253+
254+ SessionsCreated *prometheus.CounterVec
255+ SessionsFinished *prometheus.CounterVec
256+ SessionsDuration *prometheus.CounterVec
257+}
258+
259+func (s *SSHServer) ListenAndServe() error {
260+ if s.Config.PromListenAddr != "" {
261+ s.SessionsCreated = promauto.With(prometheus.DefaultRegisterer).NewCounterVec(prometheus.CounterOpts{
262+ Name: "pssh_sessions_created_total",
263+ Help: "The total number of sessions created",
264+ ConstLabels: prometheus.Labels{
265+ "app": s.Config.App,
266+ },
267+ }, []string{"command"})
268+
269+ s.SessionsFinished = promauto.With(prometheus.DefaultRegisterer).NewCounterVec(prometheus.CounterOpts{
270+ Name: "pssh_sessions_finished_total",
271+ Help: "The total number of sessions created",
272+ ConstLabels: prometheus.Labels{
273+ "app": s.Config.App,
274+ },
275+ }, []string{"command"})
276+
277+ s.SessionsDuration = promauto.With(prometheus.DefaultRegisterer).NewCounterVec(prometheus.CounterOpts{
278+ Name: "pssh_sessions_duration_seconds",
279+ Help: "The total sessions duration in seconds",
280+ ConstLabels: prometheus.Labels{
281+ "app": s.Config.App,
282+ },
283+ }, []string{"command"})
284+
285+ go func() {
286+ mux := http.NewServeMux()
287+ mux.Handle("/metrics", promhttp.Handler())
288+
289+ srv := &http.Server{Addr: s.Config.PromListenAddr, Handler: mux}
290+
291+ go func() {
292+ <-s.Ctx.Done()
293+ s.Logger.Info("Prometheus server shutting down")
294+ srv.Close()
295+ }()
296+
297+ s.Logger.Info("Starting Prometheus server", "addr", s.Config.PromListenAddr)
298+
299+ err := srv.ListenAndServe()
300+ if err != nil {
301+ if errors.Is(err, http.ErrServerClosed) {
302+ s.Logger.Info("Prometheus server shut down")
303+ return
304+ }
305+
306+ s.Logger.Error("Prometheus serve error", "err", err)
307+ panic(err)
308+ }
309+ }()
310+ }
311+
312+ listen, err := net.Listen("tcp", s.Config.ListenAddr)
313+ if err != nil {
314+ return err
315+ }
316+
317+ s.Listener = listen
318+ defer s.Listener.Close()
319+
320+ go func() {
321+ <-s.Ctx.Done()
322+ s.Close()
323+ }()
324+
325+ var retErr error
326+
327+ for {
328+ conn, err := s.Listener.Accept()
329+ if err != nil {
330+ s.Logger.Error("accept", "err", err)
331+ if errors.Is(err, net.ErrClosed) {
332+ retErr = err
333+ break
334+ }
335+ continue
336+ }
337+
338+ go func() {
339+ if err := s.HandleConn(conn); err != nil && !errors.Is(err, io.EOF) {
340+ s.Logger.Error("Error handling connection", "err", err, "remoteAddr", conn.RemoteAddr().String())
341+ }
342+ }()
343+ }
344+
345+ if errors.Is(retErr, net.ErrClosed) {
346+ return nil
347+ }
348+
349+ return retErr
350+}
351+
352+func (s *SSHServer) HandleConn(conn net.Conn) error {
353+ defer conn.Close()
354+
355+ sshConn, chans, reqs, err := ssh.NewServerConn(conn, s.Config.ServerConfig)
356+ if err != nil {
357+ return err
358+ }
359+
360+ newLogger := s.Logger.With(
361+ "remoteAddr", conn.RemoteAddr().String(),
362+ "user", sshConn.User(),
363+ "pubkey", sshConn.Permissions.Extensions["pubkey"],
364+ )
365+
366+ newConn := NewSSHServerConn(
367+ s.Ctx,
368+ newLogger,
369+ sshConn,
370+ s,
371+ )
372+
373+ s.Conns.Store(sshConn.RemoteAddr().String(), newConn)
374+
375+ err = newConn.Handle(chans, reqs)
376+
377+ s.Conns.Delete(sshConn.RemoteAddr().String())
378+
379+ return err
380+}
381+
382+func (s *SSHServer) Close() error {
383+ s.CancelFunc()
384+ return s.Listener.Close()
385+}
386+
387+func NewSSHServer(ctx context.Context, logger *slog.Logger, config *SSHServerConfig) *SSHServer {
388+ if ctx == nil {
389+ ctx = context.Background()
390+ }
391+
392+ cancelCtx, cancelFunc := context.WithCancel(ctx)
393+
394+ if logger == nil {
395+ logger = slog.Default()
396+ }
397+
398+ if config == nil {
399+ config = &SSHServerConfig{}
400+ }
401+
402+ if config.ChannelMiddleware == nil {
403+ config.ChannelMiddleware = map[string]SSHServerChannelMiddleware{}
404+ }
405+
406+ if _, ok := config.ChannelMiddleware["session"]; !ok {
407+ config.ChannelMiddleware["session"] = func(newChan ssh.NewChannel, sc *SSHServerConn) error {
408+ channel, requests, err := newChan.Accept()
409+ if err != nil {
410+ sc.Logger.Error("accept session channel", "err", err)
411+ return err
412+ }
413+
414+ ctx, cancelFunc := context.WithCancel(sc.Ctx)
415+
416+ sesh := &SSHServerConnSession{
417+ Channel: channel,
418+ SSHServerConn: sc,
419+ Ctx: ctx,
420+ CancelFunc: cancelFunc,
421+ }
422+
423+ for {
424+ select {
425+ case <-sesh.Done():
426+ return nil
427+ case req, ok := <-requests:
428+ if !ok {
429+ return nil
430+ }
431+
432+ go func() {
433+ sc.Logger.Info("new session request", "type", req.Type, "wantReply", req.WantReply, "payload", req.Payload)
434+ switch req.Type {
435+ case "subsystem":
436+ if len(sc.SSHServer.Config.SubsystemMiddleware) == 0 {
437+ err := req.Reply(false, nil)
438+ if err != nil {
439+ sc.Logger.Error("subsystem reply", "err", err)
440+ }
441+
442+ err = sc.Close()
443+ if err != nil {
444+ sc.Logger.Error("subsystem close", "err", err)
445+ }
446+
447+ sesh.Fatal(err)
448+ return
449+ }
450+
451+ h := func(*SSHServerConnSession) error { return nil }
452+ for _, m := range sc.SSHServer.Config.SubsystemMiddleware {
453+ h = m(h)
454+ }
455+
456+ err := req.Reply(true, nil)
457+ if err != nil {
458+ sc.Logger.Error("subsystem reply", "err", err)
459+ sesh.Fatal(err)
460+ return
461+ }
462+
463+ if err := h(sesh); err != nil && !errors.Is(err, io.EOF) {
464+ sc.Logger.Error("subsystem middleware", "err", err)
465+ sesh.Fatal(err)
466+ return
467+ }
468+
469+ err = sesh.Exit(0)
470+ if err != nil {
471+ sc.Logger.Error("subsystem exit", "err", err)
472+ }
473+
474+ err = sesh.Close()
475+ if err != nil {
476+ sc.Logger.Error("subsystem close", "err", err)
477+ }
478+ case "shell", "exec":
479+ if len(sc.SSHServer.Config.Middleware) == 0 {
480+ err := req.Reply(false, nil)
481+ if err != nil {
482+ sc.Logger.Error("shell/exec reply", "err", err)
483+ }
484+ sesh.Fatal(err)
485+ return
486+ }
487+
488+ if len(req.Payload) > 0 {
489+ var payload = struct{ Value string }{}
490+ err := ssh.Unmarshal(req.Payload, &payload)
491+ if err != nil {
492+ sc.Logger.Error("shell/exec unmarshal", "err", err)
493+ sesh.Fatal(err)
494+ return
495+ }
496+
497+ if sc.SSHServer.Config.PromListenAddr != "" {
498+ sc.SSHServer.SessionsCreated.WithLabelValues(payload.Value).Inc()
499+ defer func() {
500+ sc.SSHServer.SessionsFinished.WithLabelValues(payload.Value).Inc()
501+ sc.SSHServer.SessionsDuration.WithLabelValues(payload.Value).Add(time.Since(sc.Start).Seconds())
502+ }()
503+ }
504+
505+ sesh.SetValue("command", strings.Fields(payload.Value))
506+ }
507+
508+ h := func(*SSHServerConnSession) error { return nil }
509+ for _, m := range sc.SSHServer.Config.Middleware {
510+ h = m(h)
511+ }
512+
513+ err = req.Reply(true, nil)
514+ if err != nil {
515+ sc.Logger.Error("shell/exec reply", "err", err)
516+ sesh.Fatal(err)
517+ return
518+ }
519+
520+ if err := h(sesh); err != nil && !errors.Is(err, io.EOF) {
521+ sc.Logger.Error("exec middleware", "err", err)
522+ sesh.Fatal(err)
523+ return
524+ }
525+
526+ err = sesh.Exit(0)
527+ if err != nil {
528+ sc.Logger.Error("subsystem exit", "err", err)
529+ }
530+
531+ err = sesh.Close()
532+ if err != nil {
533+ sc.Logger.Error("subsystem close", "err", err)
534+ }
535+ case "pty-req":
536+ sesh.mu.Lock()
537+ found := sesh.pty != nil
538+ sesh.mu.Unlock()
539+ if found {
540+ err := req.Reply(false, nil)
541+ if err != nil {
542+ sc.Logger.Error("pty-req reply", "err", err)
543+ }
544+ return
545+ }
546+
547+ ptyReq, ok := parsePtyRequest(req.Payload)
548+ if !ok {
549+ err := req.Reply(false, nil)
550+ if err != nil {
551+ sc.Logger.Error("pty-req reply", "err", err)
552+ }
553+ return
554+ }
555+
556+ sesh.mu.Lock()
557+ sesh.pty = &ptyReq
558+ sesh.winch = make(chan Window, 1)
559+ sesh.mu.Unlock()
560+
561+ sesh.winch <- ptyReq.Window
562+ err := req.Reply(ok, nil)
563+ if err != nil {
564+ sc.Logger.Error("pty-req reply", "err", err)
565+ }
566+ case "window-change":
567+ sesh.mu.Lock()
568+ found := sesh.pty != nil
569+ sesh.mu.Unlock()
570+
571+ if !found {
572+ err := req.Reply(false, nil)
573+ if err != nil {
574+ sc.Logger.Error("pty-req reply", "err", err)
575+ }
576+ return
577+ }
578+
579+ win, ok := parseWinchRequest(req.Payload)
580+ if ok {
581+ sesh.mu.Lock()
582+ sesh.pty.Window = win
583+ sesh.winch <- win
584+ sesh.mu.Unlock()
585+ }
586+
587+ err := req.Reply(ok, nil)
588+ if err != nil {
589+ sc.Logger.Error("window-change reply", "err", err)
590+ }
591+ }
592+ }()
593+ }
594+ }
595+ }
596+ }
597+
598+ server := &SSHServer{
599+ Ctx: cancelCtx,
600+ CancelFunc: cancelFunc,
601+ Logger: logger,
602+ Config: config,
603+ Conns: syncmap.New[string, *SSHServerConn](),
604+ }
605+
606+ return server
607+}
608+
609+type PubKeyAuthHandler func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error)
610+
611+func NewSSHServerWithConfig(
612+ ctx context.Context,
613+ logger *slog.Logger,
614+ app, host, port, promPort string,
615+ pubKeyAuthHandler PubKeyAuthHandler,
616+ middleware, subsystemMiddleware []SSHServerMiddleware,
617+ channelMiddleware map[string]SSHServerChannelMiddleware) (*SSHServer, error) {
618+ server := NewSSHServer(ctx, logger, &SSHServerConfig{
619+ App: app,
620+ ListenAddr: fmt.Sprintf("%s:%s", host, port),
621+ ServerConfig: &ssh.ServerConfig{
622+ PublicKeyCallback: pubKeyAuthHandler,
623+ },
624+ Middleware: middleware,
625+ SubsystemMiddleware: subsystemMiddleware,
626+ ChannelMiddleware: channelMiddleware,
627+ })
628+
629+ if promPort != "" {
630+ server.Config.PromListenAddr = fmt.Sprintf("%s:%s", host, promPort)
631+ }
632+
633+ pemBytes, err := os.ReadFile("ssh_data/term_info_ed25519")
634+ if err != nil {
635+ logger.Error("failed to read private key file", "error", err)
636+ if !os.IsNotExist(err) {
637+ return nil, err
638+ }
639+
640+ logger.Info("generating new private key")
641+
642+ pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
643+ if err != nil {
644+ logger.Error("failed to generate private key", "error", err)
645+ return nil, err
646+ }
647+
648+ privb, err := ssh.MarshalPrivateKey(privKey, "")
649+ if err != nil {
650+ logger.Error("failed to marshal private key", "error", err)
651+ return nil, err
652+ }
653+
654+ block := &pem.Block{
655+ Type: "OPENSSH PRIVATE KEY",
656+ Bytes: privb.Bytes,
657+ }
658+
659+ if err = os.MkdirAll("ssh_data", 0700); err != nil {
660+ logger.Error("failed to create ssh_data directory", "error", err)
661+ return nil, err
662+ }
663+
664+ pemBytes = pem.EncodeToMemory(block)
665+
666+ if err = os.WriteFile("ssh_data/term_info_ed25519", pemBytes, 0600); err != nil {
667+ logger.Error("failed to write private key", "error", err)
668+ return nil, err
669+ }
670+
671+ sshPubKey, err := ssh.NewPublicKey(pubKey)
672+ if err != nil {
673+ logger.Error("failed to create public key", "error", err)
674+ return nil, err
675+ }
676+
677+ pubb := ssh.MarshalAuthorizedKey(sshPubKey)
678+ if err = os.WriteFile("ssh_data/term_info_ed25519.pub", pubb, 0600); err != nil {
679+ logger.Error("failed to write public key", "error", err)
680+ return nil, err
681+ }
682+ }
683+
684+ signer, err := ssh.ParsePrivateKey(pemBytes)
685+ if err != nil {
686+ logger.Error("failed to parse private key", "error", err)
687+ return nil, err
688+ }
689+
690+ server.Config.AddHostKey(signer)
691+
692+ return server, nil
693+}
694+
695+func KeysEqual(a, b ssh.PublicKey) bool {
696+ if a == nil || b == nil {
697+ return false
698+ }
699+
700+ am := a.Marshal()
701+ bm := b.Marshal()
702+ return (len(am) == len(bm) && subtle.ConstantTimeCompare(am, bm) == 1)
703+}
+275,
-0
1@@ -0,0 +1,275 @@
2+package pssh_test
3+
4+import (
5+ "context"
6+ "errors"
7+ "log/slog"
8+ "net"
9+ "testing"
10+ "time"
11+
12+ "github.com/picosh/pico/pkg/pssh"
13+ "golang.org/x/crypto/ssh"
14+)
15+
16+func TestNewSSHServer(t *testing.T) {
17+ ctx := context.Background()
18+ logger := slog.Default()
19+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
20+
21+ if server == nil {
22+ t.Fatal("expected non-nil server")
23+ }
24+
25+ if server.Ctx == nil {
26+ t.Error("expected non-nil context")
27+ }
28+
29+ if server.Logger == nil {
30+ t.Error("expected non-nil logger")
31+ }
32+
33+ if server.Config == nil {
34+ t.Error("expected non-nil config")
35+ }
36+
37+ if server.Conns == nil {
38+ t.Error("expected non-nil connections map")
39+ }
40+}
41+
42+func TestNewSSHServerConn(t *testing.T) {
43+ ctx := context.Background()
44+ logger := slog.Default()
45+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
46+ conn := &ssh.ServerConn{}
47+
48+ serverConn := pssh.NewSSHServerConn(ctx, logger, conn, server)
49+
50+ if serverConn == nil {
51+ t.Fatal("expected non-nil server connection")
52+ }
53+
54+ if serverConn.Ctx == nil {
55+ t.Error("expected non-nil context")
56+ }
57+
58+ if serverConn.Logger == nil {
59+ t.Error("expected non-nil logger")
60+ }
61+
62+ if serverConn.Conn != conn {
63+ t.Error("expected conn to match")
64+ }
65+
66+ if serverConn.SSHServer != server {
67+ t.Error("expected server to match")
68+ }
69+}
70+
71+func TestSSHServerConnClose(t *testing.T) {
72+ ctx := context.Background()
73+ logger := slog.Default()
74+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
75+ conn := &ssh.ServerConn{}
76+
77+ serverConn := pssh.NewSSHServerConn(ctx, logger, conn, server)
78+ err := serverConn.Close()
79+
80+ if err != nil {
81+ t.Errorf("unexpected error: %v", err)
82+ }
83+
84+ // Should be canceled after close
85+ select {
86+ case <-serverConn.Ctx.Done():
87+ // Context was canceled as expected
88+ default:
89+ t.Error("context was not canceled after Close()")
90+ }
91+}
92+
93+func TestSSHServerClose(t *testing.T) {
94+ ctx := context.Background()
95+ logger := slog.Default()
96+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
97+
98+ // Create a mock listener to test Close()
99+ listener, err := net.Listen("tcp", "127.0.0.1:0")
100+ if err != nil {
101+ t.Fatalf("failed to create listener: %v", err)
102+ }
103+
104+ server.Listener = listener
105+ err = server.Close()
106+
107+ if err != nil {
108+ t.Errorf("unexpected error: %v", err)
109+ }
110+
111+ // Should be canceled after close
112+ select {
113+ case <-server.Ctx.Done():
114+ // Context was canceled as expected
115+ default:
116+ t.Error("context was not canceled after Close()")
117+ }
118+}
119+
120+func TestSSHServerNilParams(t *testing.T) {
121+ // Test with nil context and logger
122+ //nolint:staticcheck // SA1012 ignores nil check
123+ //lint:ignore SA1012 ignores nil check
124+ server := pssh.NewSSHServer(nil, nil, nil)
125+
126+ if server == nil {
127+ t.Fatal("expected non-nil server")
128+ }
129+
130+ if server.Ctx == nil {
131+ t.Error("expected non-nil context even when nil is passed")
132+ }
133+
134+ if server.Logger == nil {
135+ t.Error("expected non-nil logger even when nil is passed")
136+ }
137+
138+ // Test with nil context and logger for connection
139+ //nolint:staticcheck // SA1012 ignores nil check
140+ //lint:ignore SA1012 ignores nil check
141+ conn := pssh.NewSSHServerConn(nil, nil, &ssh.ServerConn{}, server)
142+
143+ if conn == nil {
144+ t.Fatal("expected non-nil server connection")
145+ }
146+
147+ if conn.Ctx == nil {
148+ t.Error("expected non-nil context even when nil is passed")
149+ }
150+
151+ if conn.Logger == nil {
152+ t.Error("expected non-nil logger even when nil is passed")
153+ }
154+}
155+
156+func TestSSHServerHandleConn(t *testing.T) {
157+ ctx, cancel := context.WithCancel(context.Background())
158+ defer cancel()
159+ logger := slog.Default()
160+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
161+
162+ // Setup a basic SSH server config
163+ config := &ssh.ServerConfig{
164+ NoClientAuth: true,
165+ }
166+
167+ server.Config.ServerConfig = config
168+
169+ // Create a mock connection
170+ client, server_conn := net.Pipe()
171+ defer client.Close()
172+
173+ // Start HandleConn in a goroutine
174+ errChan := make(chan error, 1)
175+ go func() {
176+ errChan <- server.HandleConn(server_conn)
177+ }()
178+
179+ // Configure SSH client
180+ clientConfig := &ssh.ClientConfig{
181+ User: "testuser",
182+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
183+ }
184+
185+ // Try to establish SSH connection
186+ _, _, _, err := ssh.NewClientConn(client, "", clientConfig)
187+
188+ // It should fail since we're using a pipe and not a proper SSH handshake
189+ if err == nil {
190+ t.Error("expected SSH handshake to fail with test pipe")
191+ }
192+
193+ // Close connections to ensure HandleConn returns
194+ client.Close()
195+ server_conn.Close()
196+
197+ // Wait for HandleConn to return
198+ select {
199+ case <-errChan:
200+ // Expected HandleConn to return
201+ case <-time.After(2 * time.Second):
202+ t.Error("HandleConn did not return after connection closed")
203+ }
204+}
205+
206+func TestSSHServerListenAndServe(t *testing.T) {
207+ ctx, cancel := context.WithCancel(context.Background())
208+ defer cancel()
209+ logger := slog.Default()
210+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
211+
212+ config := &ssh.ServerConfig{
213+ NoClientAuth: true,
214+ }
215+
216+ // Set a random port
217+ port := "127.0.0.1:0"
218+ server.Config.ListenAddr = port
219+ server.Config.ServerConfig = config
220+
221+ // Start server in a goroutine
222+ errChan := make(chan error, 1)
223+ go func() {
224+ err := server.ListenAndServe()
225+ errChan <- err
226+ }()
227+
228+ // Wait a bit for the server to start
229+ time.Sleep(100 * time.Millisecond)
230+
231+ // Trigger cancellation to stop the server
232+ cancel()
233+
234+ // Wait for server to stop
235+ select {
236+ case err := <-errChan:
237+ if err != nil && !errors.Is(err, net.ErrClosed) {
238+ t.Errorf("unexpected error: %v", err)
239+ }
240+ case <-time.After(2 * time.Second):
241+ t.Error("server did not shut down in time")
242+ }
243+}
244+
245+func TestSSHServerConnHandle(t *testing.T) {
246+ ctx, cancel := context.WithCancel(context.Background())
247+ defer cancel()
248+ logger := slog.Default()
249+ server := pssh.NewSSHServer(ctx, logger, &pssh.SSHServerConfig{})
250+ conn := &ssh.ServerConn{}
251+
252+ serverConn := pssh.NewSSHServerConn(ctx, logger, conn, server)
253+
254+ // Create channels for testing
255+ chans := make(chan ssh.NewChannel)
256+ reqs := make(chan *ssh.Request)
257+
258+ // Start handle in a goroutine
259+ errChan := make(chan error, 1)
260+ go func() {
261+ errChan <- serverConn.Handle(chans, reqs)
262+ }()
263+
264+ // Ensure handle returns when context is canceled
265+ cancel()
266+
267+ // Wait for handle to return
268+ select {
269+ case err := <-errChan:
270+ if err != nil {
271+ t.Errorf("unexpected error: %v", err)
272+ }
273+ case <-time.After(2 * time.Second):
274+ t.Error("Handle did not return after context canceled")
275+ }
276+}
+26,
-0
1@@ -0,0 +1,26 @@
2+package auth
3+
4+import (
5+ "github.com/picosh/pico/pkg/pssh"
6+ "github.com/picosh/pico/pkg/send/utils"
7+)
8+
9+func Middleware(writeHandler utils.CopyFromClientHandler) pssh.SSHServerMiddleware {
10+ return func(sshHandler pssh.SSHServerHandler) pssh.SSHServerHandler {
11+ return func(session *pssh.SSHServerConnSession) error {
12+ defer func() {
13+ if r := recover(); r != nil {
14+ writeHandler.GetLogger(session).Error("error running auth middleware", "err", r)
15+ }
16+ }()
17+
18+ err := writeHandler.Validate(session)
19+ if err != nil {
20+ utils.ErrorHandler(session, err)
21+ return err
22+ }
23+
24+ return sshHandler(session)
25+ }
26+ }
27+}
+44,
-0
1@@ -0,0 +1,44 @@
2+package list
3+
4+import (
5+ "sort"
6+ "strings"
7+
8+ "github.com/picosh/pico/pkg/pssh"
9+ "github.com/picosh/pico/pkg/send/utils"
10+)
11+
12+func Middleware(writeHandler utils.CopyFromClientHandler) pssh.SSHServerMiddleware {
13+ return func(sshHandler pssh.SSHServerHandler) pssh.SSHServerHandler {
14+ return func(session *pssh.SSHServerConnSession) error {
15+ cmd := session.Command()
16+ if !(len(cmd) > 1 && cmd[0] == "command" && cmd[1] == "ls") {
17+ return sshHandler(session)
18+ }
19+
20+ fileList, err := writeHandler.List(session, "/", true, false)
21+ if err != nil {
22+ utils.ErrorHandler(session, err)
23+ return err
24+ }
25+
26+ var data []string
27+ for _, file := range fileList {
28+ name := strings.ReplaceAll(file.Name(), "/", "")
29+ if file.IsDir() {
30+ name += "/"
31+ }
32+
33+ data = append(data, name)
34+ }
35+
36+ sort.Strings(data)
37+
38+ _, err = session.Write([]byte(strings.Join(data, "\r\n")))
39+ if err != nil {
40+ utils.ErrorHandler(session, err)
41+ }
42+ return err
43+ }
44+ }
45+}
+64,
-0
1@@ -0,0 +1,64 @@
2+package pipe
3+
4+import (
5+ "fmt"
6+ "io/fs"
7+ "strconv"
8+ "strings"
9+ "time"
10+
11+ "github.com/picosh/pico/pkg/pssh"
12+ "github.com/picosh/pico/pkg/send/utils"
13+)
14+
15+func Middleware(writeHandler utils.CopyFromClientHandler, ext string) pssh.SSHServerMiddleware {
16+ return func(sshHandler pssh.SSHServerHandler) pssh.SSHServerHandler {
17+ return func(session *pssh.SSHServerConnSession) error {
18+ _, _, activePty := session.Pty()
19+ if activePty {
20+ _ = session.Exit(0)
21+ err := session.Close()
22+ return err
23+ }
24+
25+ cmd := session.Command()
26+
27+ name := ""
28+ if len(cmd) > 0 {
29+ name = strings.TrimSpace(cmd[0])
30+ if strings.Contains(name, "=") {
31+ name = ""
32+ }
33+ }
34+
35+ postTime := time.Now()
36+
37+ if name == "" {
38+ name = fmt.Sprintf("%s%s", strconv.Itoa(int(postTime.UnixNano())), ext)
39+ }
40+
41+ result, err := writeHandler.Write(session, &utils.FileEntry{
42+ Filepath: name,
43+ Mode: fs.FileMode(0777),
44+ Size: 0,
45+ Mtime: postTime.Unix(),
46+ Atime: postTime.Unix(),
47+ Reader: session,
48+ })
49+ if err != nil {
50+ utils.ErrorHandler(session, err)
51+ return err
52+ }
53+
54+ if result != "" {
55+ _, err = session.Write([]byte(fmt.Sprintf("%s\r\n", result)))
56+ if err != nil {
57+ utils.ErrorHandler(session, err)
58+ }
59+ return err
60+ }
61+
62+ return sshHandler(session)
63+ }
64+ }
65+}
+244,
-0
1@@ -0,0 +1,244 @@
2+package rsync
3+
4+import (
5+ "errors"
6+ "fmt"
7+ "io/fs"
8+ "os"
9+ "path"
10+ "slices"
11+ "strings"
12+
13+ "github.com/picosh/go-rsync-receiver/rsyncopts"
14+ "github.com/picosh/go-rsync-receiver/rsyncreceiver"
15+ "github.com/picosh/go-rsync-receiver/rsyncsender"
16+ rsyncutils "github.com/picosh/go-rsync-receiver/utils"
17+ "github.com/picosh/pico/pkg/pssh"
18+ "github.com/picosh/pico/pkg/send/utils"
19+)
20+
21+type handler struct {
22+ session *pssh.SSHServerConnSession
23+ writeHandler utils.CopyFromClientHandler
24+ root string
25+ recursive bool
26+ ignoreTimes bool
27+}
28+
29+func (h *handler) List(rPath string) ([]fs.FileInfo, error) {
30+ isDir := false
31+ if rPath == "." {
32+ rPath = "/"
33+ isDir = true
34+ }
35+
36+ list, err := h.writeHandler.List(h.session, rPath, isDir, h.recursive)
37+ if err != nil {
38+ return nil, err
39+ }
40+
41+ var dirs []string
42+
43+ var newList []fs.FileInfo
44+
45+ for _, f := range list {
46+ if !f.IsDir() && f.Size() == 0 {
47+ continue
48+ }
49+
50+ fname := f.Name()
51+ if strings.HasPrefix(f.Name(), "/") {
52+ fname = path.Join(rPath, f.Name())
53+ }
54+
55+ if fname == "" && !f.IsDir() {
56+ fname = path.Base(rPath)
57+ }
58+
59+ newFile := &utils.VirtualFile{
60+ FName: fname,
61+ FIsDir: f.IsDir(),
62+ FSize: f.Size(),
63+ FModTime: f.ModTime(),
64+ FSys: f.Sys(),
65+ }
66+
67+ newList = append(newList, newFile)
68+
69+ parts := strings.Split(newFile.Name(), string(os.PathSeparator))
70+ lastDir := newFile.Name()
71+ for i := 0; i < len(parts); i++ {
72+ lastDir, _ = path.Split(lastDir)
73+ if lastDir == "" {
74+ continue
75+ }
76+
77+ lastDir = lastDir[:len(lastDir)-1]
78+ dirs = append(dirs, lastDir)
79+ }
80+ }
81+
82+ for _, dir := range dirs {
83+ newList = append(newList, &utils.VirtualFile{
84+ FName: dir,
85+ FIsDir: true,
86+ })
87+ }
88+
89+ slices.Reverse(newList)
90+
91+ onlyEmpty := true
92+ for _, f := range newList {
93+ if f.Name() != "" {
94+ onlyEmpty = false
95+ }
96+ }
97+
98+ if len(newList) == 0 || onlyEmpty {
99+ return nil, errors.New("no files to send, the directory may not exist or could be empty")
100+ }
101+
102+ return newList, nil
103+}
104+
105+func (h *handler) Read(file *rsyncutils.SenderFile) (os.FileInfo, rsyncutils.ReaderAtCloser, error) {
106+ filePath := file.WPath
107+
108+ if strings.HasSuffix(h.root, file.WPath) {
109+ filePath = h.root
110+ } else if !strings.HasPrefix(filePath, h.root) {
111+ filePath = path.Join(h.root, file.Path, file.WPath)
112+ }
113+
114+ return h.writeHandler.Read(h.session, &utils.FileEntry{Filepath: filePath})
115+}
116+
117+func (h *handler) Put(file *rsyncutils.ReceiverFile) (int64, error) {
118+ fileEntry := &utils.FileEntry{
119+ Filepath: path.Join("/", h.root, file.Name),
120+ Mode: fs.FileMode(0600),
121+ Size: file.Length,
122+ Mtime: file.ModTime.Unix(),
123+ Atime: file.ModTime.Unix(),
124+ }
125+ fileEntry.Reader = file.Reader
126+
127+ msg, err := h.writeHandler.Write(h.session, fileEntry)
128+ if err != nil {
129+ errMsg := fmt.Sprintf("%s\r\n", err.Error())
130+ _, err = h.session.Stderr().Write([]byte(errMsg))
131+ }
132+ if msg != "" {
133+ nMsg := fmt.Sprintf("%s\r\n", msg)
134+ _, err = h.session.Stderr().Write([]byte(nMsg))
135+ }
136+ return 0, err
137+}
138+
139+func (h *handler) Remove(willReceive []*rsyncutils.ReceiverFile) error {
140+ entries, err := h.writeHandler.List(h.session, path.Join("/", h.root), true, true)
141+ if err != nil {
142+ return err
143+ }
144+
145+ var toDelete []string
146+
147+ for _, entry := range entries {
148+ exists := slices.ContainsFunc(willReceive, func(rf *rsyncutils.ReceiverFile) bool {
149+ return rf.Name == entry.Name()
150+ })
151+
152+ if !exists && entry.Name() != "._pico_keep_dir" {
153+ toDelete = append(toDelete, entry.Name())
154+ }
155+ }
156+
157+ var errs []error
158+
159+ for _, file := range toDelete {
160+ errs = append(errs, h.writeHandler.Delete(h.session, &utils.FileEntry{Filepath: path.Join("/", h.root, file)}))
161+ _, err = h.session.Stderr().Write([]byte(fmt.Sprintf("deleting %s\r\n", file)))
162+ errs = append(errs, err)
163+ }
164+
165+ return errors.Join(errs...)
166+}
167+
168+func Middleware(writeHandler utils.CopyFromClientHandler) pssh.SSHServerMiddleware {
169+ return func(sshHandler pssh.SSHServerHandler) pssh.SSHServerHandler {
170+ return func(session *pssh.SSHServerConnSession) error {
171+ cmd := session.Command()
172+ if len(cmd) == 0 || cmd[0] != "rsync" {
173+ return sshHandler(session)
174+ }
175+
176+ logger := writeHandler.GetLogger(session).With(
177+ "rsync", true,
178+ "cmd", cmd,
179+ )
180+
181+ defer func() {
182+ if r := recover(); r != nil {
183+ logger.Error("error running rsync middleware", "err", r)
184+ _, _ = session.Stderr().Write([]byte("error running rsync middleware, check the flags you are using\r\n"))
185+ }
186+ }()
187+
188+ cmdFlags := session.Command()
189+
190+ optsCtx, err := rsyncopts.ParseArguments(cmdFlags[1:], true)
191+ if err != nil {
192+ fmt.Fprintf(session.Stderr(), "error parsing rsync arguments: %s\r\n", err.Error())
193+ return err
194+ }
195+
196+ if optsCtx.Options.Compress() {
197+ err := fmt.Errorf("compression is currently unsupported")
198+ fmt.Fprintf(session.Stderr(), "error: %s\r\n", err.Error())
199+ return err
200+ }
201+
202+ if optsCtx.Options.AlwaysChecksum() {
203+ err := fmt.Errorf("checksum is currently unsupported")
204+ fmt.Fprintf(session.Stderr(), "error: %s\r\n", err.Error())
205+ return err
206+ }
207+
208+ if len(optsCtx.RemainingArgs) != 2 {
209+ err := fmt.Errorf("missing source and destination arguments")
210+ fmt.Fprintf(session.Stderr(), "error: %s\r\n", err.Error())
211+ return err
212+ }
213+
214+ root := strings.TrimPrefix(optsCtx.RemainingArgs[len(optsCtx.RemainingArgs)-1], "/")
215+ if root == "" {
216+ root = "/"
217+ }
218+
219+ fileHandler := &handler{
220+ session: session,
221+ writeHandler: writeHandler,
222+ root: root,
223+ recursive: optsCtx.Options.Recurse(),
224+ ignoreTimes: !optsCtx.Options.PreserveMTimes(),
225+ }
226+
227+ for _, arg := range cmd {
228+ if arg == "--sender" {
229+ err := rsyncsender.ClientRun(logger, optsCtx.Options, session, fileHandler, []string{fileHandler.root}, true)
230+ if err != nil {
231+ logger.Error("error running rsync sender", "err", err)
232+ }
233+ return err
234+ }
235+ }
236+
237+ err = rsyncreceiver.ClientRun(logger, optsCtx.Options, session, fileHandler, []string{fileHandler.root}, true)
238+ if err != nil {
239+ logger.Error("error running rsync receiver", "err", err)
240+ }
241+
242+ return err
243+ }
244+ }
245+}
+141,
-0
1@@ -0,0 +1,141 @@
2+package scp
3+
4+import (
5+ "bufio"
6+ "errors"
7+ "fmt"
8+ "io"
9+ "io/fs"
10+ "path/filepath"
11+ "regexp"
12+ "strconv"
13+
14+ "github.com/picosh/pico/pkg/pssh"
15+ "github.com/picosh/pico/pkg/send/utils"
16+)
17+
18+var (
19+ reTimestamp = regexp.MustCompile(`^T(\d{10}) 0 (\d{10}) 0$`)
20+ reNewFolder = regexp.MustCompile(`^D(\d{4}) 0 (.*)$`)
21+ reNewFile = regexp.MustCompile(`^C(\d{4}) (\d+) (.*)$`)
22+)
23+
24+type parseError struct {
25+ subject string
26+}
27+
28+func (e parseError) Error() string {
29+ return fmt.Sprintf("failed to parse: %q", e.subject)
30+}
31+
32+func copyFromClient(session *pssh.SSHServerConnSession, info Info, handler utils.CopyFromClientHandler) error {
33+ // accepts the request
34+ _, _ = session.Write(utils.NULL)
35+
36+ writeErrors := []error{}
37+ writeSuccess := []string{}
38+
39+ var (
40+ path = info.Path
41+ r = bufio.NewReader(session)
42+ mtime int64
43+ atime int64
44+ )
45+
46+ for {
47+ line, _, err := r.ReadLine()
48+ if err != nil {
49+ if errors.Is(err, io.EOF) {
50+ break
51+ }
52+ return fmt.Errorf("failed to read line: %w", err)
53+ }
54+
55+ if matches := reTimestamp.FindAllStringSubmatch(string(line), 2); matches != nil {
56+ mtime, err = strconv.ParseInt(matches[0][1], 10, 64)
57+ if err != nil {
58+ return parseError{string(line)}
59+ }
60+ atime, err = strconv.ParseInt(matches[0][2], 10, 64)
61+ if err != nil {
62+ return parseError{string(line)}
63+ }
64+
65+ // accepts the header
66+ _, _ = session.Write(utils.NULL)
67+ continue
68+ }
69+
70+ if matches := reNewFile.FindAllStringSubmatch(string(line), 3); matches != nil {
71+ if len(matches) != 1 || len(matches[0]) != 4 {
72+ return parseError{string(line)}
73+ }
74+
75+ mode, err := strconv.ParseUint(matches[0][1], 8, 32)
76+ if err != nil {
77+ return parseError{string(line)}
78+ }
79+
80+ size, err := strconv.ParseInt(matches[0][2], 10, 64)
81+ if err != nil {
82+ return parseError{string(line)}
83+ }
84+ name := matches[0][3]
85+
86+ // accepts the header
87+ _, _ = session.Write(utils.NULL)
88+
89+ result, err := handler.Write(session, &utils.FileEntry{
90+ Filepath: filepath.Join(path, name),
91+ Mode: fs.FileMode(mode),
92+ Size: size,
93+ Mtime: mtime,
94+ Atime: atime,
95+ Reader: utils.NewLimitReader(r, int(size)),
96+ })
97+
98+ if err == nil {
99+ writeSuccess = append(writeSuccess, result)
100+ } else {
101+ writeErrors = append(writeErrors, err)
102+ fmt.Printf("failed to write file: %q: %v\n", name, err)
103+ }
104+
105+ // read the trailing nil char
106+ _, _ = r.ReadByte() // TODO: check if it is indeed a utils.NULL?
107+
108+ mtime = 0
109+ atime = 0
110+ // says 'hey im done'
111+ _, _ = session.Write(utils.NULL)
112+ continue
113+ }
114+
115+ if matches := reNewFolder.FindAllStringSubmatch(string(line), 2); matches != nil {
116+ if len(matches) != 1 || len(matches[0]) != 3 {
117+ return parseError{string(line)}
118+ }
119+
120+ name := matches[0][2]
121+ path = filepath.Join(path, name)
122+ // says 'hey im done'
123+ _, _ = session.Write(utils.NULL)
124+ continue
125+ }
126+
127+ if string(line) == "E" {
128+ path = filepath.Dir(path)
129+
130+ // says 'hey im done'
131+ _, _ = session.Write(utils.NULL)
132+ continue
133+ }
134+
135+ return fmt.Errorf("unhandled input: %q", string(line))
136+ }
137+
138+ utils.PrintMsg(session, writeSuccess, writeErrors)
139+
140+ _, _ = session.Write(utils.NULL)
141+ return nil
142+}
+12,
-0
1@@ -0,0 +1,12 @@
2+package scp
3+
4+import (
5+ "errors"
6+
7+ "github.com/picosh/pico/pkg/pssh"
8+ "github.com/picosh/pico/pkg/send/utils"
9+)
10+
11+func copyToClient(session *pssh.SSHServerConnSession, info Info, handler utils.CopyFromClientHandler) error {
12+ return errors.New("unsupported, use rsync or sftp")
13+}
+107,
-0
1@@ -0,0 +1,107 @@
2+package scp
3+
4+import (
5+ "fmt"
6+
7+ "github.com/picosh/pico/pkg/pssh"
8+ "github.com/picosh/pico/pkg/send/utils"
9+)
10+
11+func Middleware(writeHandler utils.CopyFromClientHandler) pssh.SSHServerMiddleware {
12+ return func(sshHandler pssh.SSHServerHandler) pssh.SSHServerHandler {
13+ return func(session *pssh.SSHServerConnSession) error {
14+ cmd := session.Command()
15+ if len(cmd) == 0 || cmd[0] != "scp" {
16+ return sshHandler(session)
17+ }
18+
19+ logger := writeHandler.GetLogger(session).With(
20+ "scp", true,
21+ "cmd", cmd,
22+ )
23+
24+ defer func() {
25+ if r := recover(); r != nil {
26+ logger.Error("error running scp middleware", "err", r)
27+ _, _ = session.Stderr().Write([]byte("error running scp middleware, check the flags you are using\r\n"))
28+ }
29+ }()
30+
31+ info := GetInfo(cmd)
32+ if !info.Ok {
33+ return sshHandler(session)
34+ }
35+
36+ var err error
37+
38+ switch info.Op {
39+ case OpCopyToClient:
40+ if writeHandler == nil {
41+ err = fmt.Errorf("no handler provided for scp -t")
42+ break
43+ }
44+ err = copyToClient(session, info, writeHandler)
45+ case OpCopyFromClient:
46+ if writeHandler == nil {
47+ err = fmt.Errorf("no handler provided for scp -t")
48+ break
49+ }
50+ err = copyFromClient(session, info, writeHandler)
51+ }
52+ if err != nil {
53+ utils.ErrorHandler(session, err)
54+ }
55+
56+ return err
57+ }
58+ }
59+}
60+
61+// Op defines which kind of SCP Operation is going on.
62+type Op byte
63+
64+const (
65+ // OpCopyToClient is when a file is being copied from the server to the client.
66+ OpCopyToClient Op = 'f'
67+
68+ // OpCopyFromClient is when a file is being copied from the client into the server.
69+ OpCopyFromClient Op = 't'
70+)
71+
72+// Info provides some information about the current SCP Operation.
73+type Info struct {
74+ // Ok is true if the current session is a SCP.
75+ Ok bool
76+
77+ // Recursice is true if its a recursive SCP.
78+ Recursive bool
79+
80+ // Path is the server path of the scp operation.
81+ Path string
82+
83+ // Op is the SCP operation kind.
84+ Op Op
85+}
86+
87+func GetInfo(cmd []string) Info {
88+ info := Info{}
89+ if len(cmd) == 0 || cmd[0] != "scp" {
90+ return info
91+ }
92+
93+ for i, p := range cmd {
94+ switch p {
95+ case "-r":
96+ info.Recursive = true
97+ case "-f":
98+ info.Op = OpCopyToClient
99+ info.Path = cmd[i+1]
100+ case "-t":
101+ info.Op = OpCopyFromClient
102+ info.Path = cmd[i+1]
103+ }
104+ }
105+
106+ info.Ok = true
107+ return info
108+}
+176,
-0
1@@ -0,0 +1,176 @@
2+package sftp
3+
4+import (
5+ "bytes"
6+ "errors"
7+ "fmt"
8+ "io"
9+ "io/fs"
10+ "os"
11+ "path/filepath"
12+
13+ "slices"
14+
15+ "github.com/picosh/pico/pkg/pssh"
16+ "github.com/picosh/pico/pkg/send/utils"
17+ "github.com/pkg/sftp"
18+)
19+
20+type listerat []os.FileInfo
21+
22+func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
23+ var n int
24+ if offset >= int64(len(f)) {
25+ return 0, io.EOF
26+ }
27+ n = copy(ls, f[offset:])
28+ if n < len(ls) {
29+ return n, io.EOF
30+ }
31+ return n, nil
32+}
33+
34+type handler struct {
35+ session *pssh.SSHServerConnSession
36+ writeHandler utils.CopyFromClientHandler
37+}
38+
39+func (f *handler) Filecmd(r *sftp.Request) error {
40+ switch r.Method {
41+ case "Rmdir", "Remove":
42+ entry := toFileEntry(r)
43+
44+ if r.Method == "Rmdir" {
45+ entry.Mode = os.ModeDir
46+ }
47+
48+ return f.writeHandler.Delete(f.session, entry)
49+ case "Mkdir":
50+ entry := toFileEntry(r)
51+
52+ entry.Mode = os.ModeDir
53+
54+ _, err := f.writeHandler.Write(f.session, entry)
55+
56+ return err
57+ case "Setstat":
58+ return nil
59+ }
60+ return errors.New("unsupported")
61+}
62+
63+func (f *handler) Filelist(r *sftp.Request) (sftp.ListerAt, error) {
64+ switch r.Method {
65+ case "List", "Stat":
66+ list := r.Method == "List"
67+
68+ listData, err := f.writeHandler.List(f.session, r.Filepath, list, false)
69+ if err != nil {
70+ return nil, err
71+ }
72+
73+ // an empty string from minio or exact match from filepath base name is what we want
74+
75+ if !list {
76+ listData = slices.DeleteFunc(listData, func(f os.FileInfo) bool {
77+ return !(f.Name() == "" || f.Name() == filepath.Base(r.Filepath))
78+ })
79+ }
80+
81+ if r.Filepath == "/" {
82+ listData = slices.DeleteFunc(listData, func(f os.FileInfo) bool {
83+ return f.Name() == "/"
84+ })
85+ listData = slices.Insert(listData, 0, os.FileInfo(&utils.VirtualFile{
86+ FName: ".",
87+ FIsDir: true,
88+ }))
89+ }
90+
91+ return listerat(listData), nil
92+ }
93+
94+ return nil, errors.New("unsupported")
95+}
96+
97+func toFileEntry(r *sftp.Request) *utils.FileEntry {
98+ attrs := r.Attributes()
99+ var size int64 = 0
100+ var mtime int64 = 0
101+ var atime int64 = 0
102+ var mode fs.FileMode
103+ if attrs != nil {
104+ mode = attrs.FileMode()
105+ size = int64(attrs.Size)
106+ mtime = int64(attrs.Mtime)
107+ atime = int64(attrs.Atime)
108+ }
109+
110+ entry := &utils.FileEntry{
111+ Filepath: r.Filepath,
112+ Mode: mode,
113+ Size: size,
114+ Mtime: mtime,
115+ Atime: atime,
116+ }
117+ return entry
118+}
119+
120+func (f *handler) Filewrite(r *sftp.Request) (io.WriterAt, error) {
121+ entry := toFileEntry(r)
122+ entry.Reader = bytes.NewReader([]byte{})
123+
124+ _, err := f.writeHandler.Write(f.session, entry)
125+ if err != nil {
126+ return nil, err
127+ }
128+
129+ buf := &buffer{}
130+ entry.Reader = buf
131+
132+ return fakeWrite{fileEntry: entry, buf: buf, handler: f}, nil
133+}
134+
135+func (f *handler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
136+ if r.Filepath == "/" {
137+ return nil, os.ErrInvalid
138+ }
139+
140+ fileEntry := toFileEntry(r)
141+ _, reader, err := f.writeHandler.Read(f.session, fileEntry)
142+
143+ return reader, err
144+}
145+
146+type handlererr struct {
147+ Handler *handler
148+}
149+
150+func (f *handlererr) Filecmd(r *sftp.Request) error {
151+ err := f.Handler.Filecmd(r)
152+ if err != nil {
153+ fmt.Fprintln(f.Handler.session.Stderr(), err)
154+ }
155+ return err
156+}
157+func (f *handlererr) Filelist(r *sftp.Request) (sftp.ListerAt, error) {
158+ result, err := f.Handler.Filelist(r)
159+ if err != nil {
160+ fmt.Fprintln(f.Handler.session.Stderr(), err)
161+ }
162+ return result, err
163+}
164+func (f *handlererr) Filewrite(r *sftp.Request) (io.WriterAt, error) {
165+ result, err := f.Handler.Filewrite(r)
166+ if err != nil {
167+ fmt.Fprintln(f.Handler.session.Stderr(), err)
168+ }
169+ return result, err
170+}
171+func (f *handlererr) Fileread(r *sftp.Request) (io.ReaderAt, error) {
172+ result, err := f.Handler.Fileread(r)
173+ if err != nil {
174+ fmt.Fprintln(f.Handler.session.Stderr(), err)
175+ }
176+ return result, err
177+}
+69,
-0
1@@ -0,0 +1,69 @@
2+package sftp
3+
4+import (
5+ "errors"
6+ "fmt"
7+ "io"
8+
9+ "github.com/picosh/pico/pkg/pssh"
10+ "github.com/picosh/pico/pkg/send/utils"
11+ "github.com/pkg/sftp"
12+)
13+
14+// func SSHOption(writeHandler utils.CopyFromClientHandler) ssh.Option {
15+// return func(server *ssh.Server) error {
16+// if server.SubsystemHandlers == nil {
17+// server.SubsystemHandlers = map[string]ssh.SubsystemHandler{}
18+// }
19+
20+// server.SubsystemHandlers["sftp"] = SubsystemHandler(writeHandler)
21+// return nil
22+// }
23+// }
24+
25+func Middleware(writeHandler utils.CopyFromClientHandler) pssh.SSHServerMiddleware {
26+ return func(next pssh.SSHServerHandler) pssh.SSHServerHandler {
27+ return func(session *pssh.SSHServerConnSession) error {
28+ logger := writeHandler.GetLogger(session).With(
29+ "sftp", true,
30+ )
31+
32+ defer func() {
33+ if r := recover(); r != nil {
34+ logger.Error("error running sftp middleware", "err", r)
35+ fmt.Fprintln(session, "error running sftp middleware, check the flags you are using")
36+ }
37+ }()
38+
39+ err := writeHandler.Validate(session)
40+ if err != nil {
41+ fmt.Fprintln(session.Stderr(), err)
42+ return err
43+ }
44+
45+ handler := &handlererr{
46+ Handler: &handler{
47+ session: session,
48+ writeHandler: writeHandler,
49+ },
50+ }
51+
52+ handlers := sftp.Handlers{
53+ FilePut: handler,
54+ FileList: handler,
55+ FileGet: handler,
56+ FileCmd: handler,
57+ }
58+
59+ requestServer := sftp.NewRequestServer(session, handlers)
60+
61+ err = requestServer.Serve()
62+ if err != nil && !errors.Is(err, io.EOF) {
63+ fmt.Fprintln(session.Stderr(), err)
64+ logger.Error("Error serving sftp subsystem", "err", err)
65+ }
66+
67+ return err
68+ }
69+ }
70+}
+75,
-0
1@@ -0,0 +1,75 @@
2+package sftp
3+
4+import (
5+ "fmt"
6+ "io"
7+ "sync"
8+
9+ "github.com/picosh/pico/pkg/send/utils"
10+)
11+
12+type buffer struct {
13+ buf []byte
14+ m sync.Mutex
15+ off int
16+}
17+
18+func (b *buffer) WriteAt(p []byte, pos int64) (n int, err error) {
19+ pLen := len(p)
20+ expLen := pos + int64(pLen)
21+ b.m.Lock()
22+ defer b.m.Unlock()
23+ if int64(len(b.buf)) < expLen {
24+ if int64(cap(b.buf)) < expLen {
25+ newBuf := make([]byte, expLen)
26+ copy(newBuf, b.buf)
27+ b.buf = newBuf
28+ }
29+ b.buf = b.buf[:expLen]
30+ }
31+ copy(b.buf[pos:], p)
32+ return pLen, nil
33+}
34+
35+func (b *buffer) Read(p []byte) (n int, err error) {
36+ b.m.Lock()
37+ defer b.m.Unlock()
38+ if len(b.buf) <= b.off {
39+ if len(p) == 0 {
40+ return 0, nil
41+ }
42+ return 0, io.EOF
43+ }
44+ n = copy(p, b.buf[b.off:])
45+ b.off += n
46+ return n, nil
47+}
48+
49+func (b *buffer) Close() error {
50+ b.buf = []byte{}
51+ return nil
52+}
53+
54+type fakeWrite struct {
55+ fileEntry *utils.FileEntry
56+ handler *handler
57+ buf *buffer
58+}
59+
60+func (f fakeWrite) WriteAt(p []byte, off int64) (int, error) {
61+ return f.buf.WriteAt(p, off)
62+}
63+
64+func (f fakeWrite) Close() error {
65+ msg, err := f.handler.writeHandler.Write(f.handler.session, f.fileEntry)
66+ if err != nil {
67+ errMsg := fmt.Sprintf("%s\r\n", err.Error())
68+ _, err = f.handler.session.Stderr().Write([]byte(errMsg))
69+ }
70+ if msg != "" {
71+ nMsg := fmt.Sprintf("%s\r\n", msg)
72+ _, err = f.handler.session.Stderr().Write([]byte(nMsg))
73+ }
74+ f.buf.Close()
75+ return err
76+}
+31,
-0
1@@ -0,0 +1,31 @@
2+package utils
3+
4+import (
5+ "os"
6+ "time"
7+)
8+
9+type VirtualFile struct {
10+ FName string
11+ FIsDir bool
12+ FSize int64
13+ FModTime time.Time
14+ FSys any
15+}
16+
17+func (f *VirtualFile) Name() string { return f.FName }
18+func (f *VirtualFile) Size() int64 { return f.FSize }
19+func (f *VirtualFile) Mode() os.FileMode {
20+ if f.FIsDir {
21+ return os.FileMode(0755) | os.ModeDir
22+ }
23+ return os.FileMode(0644)
24+}
25+func (f *VirtualFile) ModTime() time.Time {
26+ if f.FModTime.IsZero() {
27+ return time.Now()
28+ }
29+ return f.FModTime
30+}
31+func (f *VirtualFile) IsDir() bool { return f.FIsDir }
32+func (f *VirtualFile) Sys() any { return f.FSys }
+26,
-0
1@@ -0,0 +1,26 @@
2+package utils
3+
4+import (
5+ "io"
6+)
7+
8+type ReadAndReaderAt interface {
9+ io.ReaderAt
10+ io.Reader
11+}
12+
13+type ReadAndReaderAtCloser interface {
14+ io.Reader
15+ io.ReaderAt
16+ io.ReadCloser
17+}
18+
19+func NopReadAndReaderAtCloser(r ReadAndReaderAt) ReadAndReaderAtCloser {
20+ return nopReadAndReaderAt{r}
21+}
22+
23+type nopReadAndReaderAt struct {
24+ ReadAndReaderAt
25+}
26+
27+func (nopReadAndReaderAt) Close() error { return nil }
+35,
-0
1@@ -0,0 +1,35 @@
2+package utils
3+
4+import (
5+ "io"
6+ "sync"
7+)
8+
9+func NewLimitReader(r io.Reader, limit int) io.Reader {
10+ return &LimitReader{
11+ r: r,
12+ left: limit,
13+ }
14+}
15+
16+type LimitReader struct {
17+ r io.Reader
18+
19+ lock sync.Mutex
20+ left int
21+}
22+
23+func (r *LimitReader) Read(b []byte) (int, error) {
24+ r.lock.Lock()
25+ defer r.lock.Unlock()
26+
27+ if r.left <= 0 {
28+ return 0, io.EOF
29+ }
30+ if len(b) > r.left {
31+ b = b[0:r.left]
32+ }
33+ n, err := r.r.Read(b)
34+ r.left -= n
35+ return n, err
36+}
+44,
-0
1@@ -0,0 +1,44 @@
2+package utils
3+
4+import (
5+ "bytes"
6+ "io"
7+ "testing"
8+
9+ "github.com/matryer/is"
10+)
11+
12+func TestLimitedReader(t *testing.T) {
13+ t.Run("partial", func(t *testing.T) {
14+ is := is.New(t)
15+ var b bytes.Buffer
16+ b.WriteString("writing some bytes")
17+ r := NewLimitReader(&b, 7)
18+
19+ bts, err := io.ReadAll(r)
20+ is.NoErr(err)
21+ is.Equal("writing", string(bts))
22+ })
23+
24+ t.Run("full", func(t *testing.T) {
25+ is := is.New(t)
26+ var b bytes.Buffer
27+ b.WriteString("some text")
28+ r := NewLimitReader(&b, b.Len())
29+
30+ bts, err := io.ReadAll(r)
31+ is.NoErr(err)
32+ is.Equal("some text", string(bts))
33+ })
34+
35+ t.Run("pass limit", func(t *testing.T) {
36+ is := is.New(t)
37+ var b bytes.Buffer
38+ b.WriteString("another text")
39+ r := NewLimitReader(&b, b.Len()+10)
40+
41+ bts, err := io.ReadAll(r)
42+ is.NoErr(err)
43+ is.Equal("another text", string(bts))
44+ })
45+}
+101,
-0
1@@ -0,0 +1,101 @@
2+package utils
3+
4+import (
5+ "encoding/base64"
6+ "fmt"
7+ "io"
8+ "io/fs"
9+ "log/slog"
10+ "os"
11+ "path/filepath"
12+ "strconv"
13+
14+ "github.com/picosh/pico/pkg/pssh"
15+)
16+
17+// NULL is an array with a single NULL byte.
18+var NULL = []byte{'\x00'}
19+
20+// FileEntry is an Entry that reads from a Reader, defining a file and
21+// its contents.
22+type FileEntry struct {
23+ Filepath string
24+ Mode fs.FileMode
25+ Size int64
26+ Reader io.Reader
27+ Atime int64
28+ Mtime int64
29+ Metadata map[string]string
30+}
31+
32+// Write a file to the given writer.
33+func (e *FileEntry) Write(w io.Writer) error {
34+ if e.Mtime > 0 && e.Atime > 0 {
35+ if _, err := fmt.Fprintf(w, "T%d 0 %d 0\n", e.Mtime, e.Atime); err != nil {
36+ return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
37+ }
38+ }
39+ fname := filepath.Base(e.Filepath)
40+ if _, err := fmt.Fprintf(w, "C%s %d %s\n", octalPerms(e.Mode), e.Size, fname); err != nil {
41+ return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
42+ }
43+
44+ if _, err := io.Copy(w, e.Reader); err != nil {
45+ return fmt.Errorf("failed to read file: %q: %w", e.Filepath, err)
46+ }
47+
48+ if _, err := w.Write(NULL); err != nil {
49+ return fmt.Errorf("failed to write file: %q: %w", e.Filepath, err)
50+ }
51+ return nil
52+}
53+
54+func octalPerms(info fs.FileMode) string {
55+ return "0" + strconv.FormatUint(uint64(info.Perm()), 8)
56+}
57+
58+// CopyFromClientHandler is a handler that can be implemented to handle files
59+// being copied from the client to the server.
60+type CopyFromClientHandler interface {
61+ // Write should write the given file.
62+ Delete(*pssh.SSHServerConnSession, *FileEntry) error
63+ Write(*pssh.SSHServerConnSession, *FileEntry) (string, error)
64+ Read(*pssh.SSHServerConnSession, *FileEntry) (os.FileInfo, ReadAndReaderAtCloser, error)
65+ List(*pssh.SSHServerConnSession, string, bool, bool) ([]os.FileInfo, error)
66+ GetLogger(*pssh.SSHServerConnSession) *slog.Logger
67+ Validate(*pssh.SSHServerConnSession) error
68+}
69+
70+func KeyText(session *pssh.SSHServerConnSession) (string, error) {
71+ if session.PublicKey() == nil {
72+ return "", fmt.Errorf("session doesn't have public key")
73+ }
74+ kb := base64.StdEncoding.EncodeToString(session.PublicKey().Marshal())
75+ return fmt.Sprintf("%s %s", session.PublicKey().Type(), kb), nil
76+}
77+
78+func ErrorHandler(session *pssh.SSHServerConnSession, err error) {
79+ _, _ = fmt.Fprint(session.Stderr(), err, "\r\n")
80+ _ = session.Exit(1)
81+ _ = session.Close()
82+}
83+
84+func PrintMsg(session *pssh.SSHServerConnSession, stdout []string, stderr []error) {
85+ output := ""
86+ if len(stdout) > 0 {
87+ for _, msg := range stdout {
88+ if msg != "" {
89+ output += fmt.Sprintf("%s\r\n", msg)
90+ }
91+ }
92+ _, _ = fmt.Fprintln(session.Stderr(), output)
93+ }
94+
95+ outputErr := ""
96+ if len(stderr) > 0 {
97+ for _, err := range stderr {
98+ outputErr += fmt.Sprintf("%v\r\n", err)
99+ }
100+ _, _ = fmt.Fprintln(session.Stderr(), outputErr)
101+ }
102+}
1@@ -15,7 +15,7 @@ import (
2 "strings"
3 "time"
4
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils/pipe/metrics"
8 "github.com/simplesurance/go-ip-anonymizer/ipanonymizer"
9 "github.com/x-way/crawlerdetect"
1@@ -8,9 +8,9 @@ import (
2 "os"
3 "strings"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7+ "github.com/picosh/pico/pkg/db"
8 "github.com/picosh/utils"
9+ "golang.org/x/crypto/ssh"
10 )
11
12 type SubdomainProps struct {
1@@ -6,7 +6,7 @@ import (
2 "path/filepath"
3 "strings"
4
5- "github.com/picosh/send/utils"
6+ "github.com/picosh/pico/pkg/send/utils"
7 )
8
9 func GetImgsBucketName(userID string) string {
1@@ -12,7 +12,7 @@ import (
2 "strings"
3 "time"
4
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils"
8
9 pipeLogger "github.com/picosh/utils/pipe/log"
1@@ -5,7 +5,7 @@ import (
2 "time"
3
4 "github.com/gorilla/feeds"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 )
8
9 func UserFeed(me db.DB, userID, token string) (*feeds.Feed, error) {
1@@ -10,9 +10,9 @@ import (
2 "regexp"
3 "strings"
4
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/shared/storage"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/pssh"
10+ "github.com/picosh/pico/pkg/shared/storage"
11 )
12
13 type Route struct {
14@@ -173,12 +173,12 @@ type ctxCfg struct{}
15
16 type CtxSubdomainKey struct{}
17 type ctxKey struct{}
18-type CtxSshKey struct{}
19+type CtxSessionKey struct{}
20
21-func GetSshCtx(r *http.Request) (ssh.Context, error) {
22- payload, ok := r.Context().Value(CtxSshKey{}).(ssh.Context)
23+func GetSshCtx(r *http.Request) (*pssh.SSHServerConnSession, error) {
24+ payload, ok := r.Context().Value(CtxSessionKey{}).(*pssh.SSHServerConnSession)
25 if payload == nil || !ok {
26- return payload, fmt.Errorf("sshCtx not set on `r.Context()` for connection")
27+ return payload, fmt.Errorf("ssh session not set on `r.Context()` for connection")
28 }
29 return payload, nil
30 }
1@@ -6,8 +6,8 @@ import (
2 "sync"
3
4 "git.sr.ht/~delthas/senpai"
5- "github.com/charmbracelet/ssh"
6 "github.com/containerd/console"
7+ "github.com/picosh/pico/pkg/pssh"
8 )
9
10 type consoleData struct {
11@@ -16,13 +16,13 @@ type consoleData struct {
12 }
13
14 type VConsole struct {
15- ssh.Session
16- pty ssh.Pty
17+ Session *pssh.SSHServerConnSession
18+ pty *pssh.Pty
19
20 sizeEnableOnce sync.Once
21
22 windowMu sync.Mutex
23- currentWindow ssh.Window
24+ currentWindow pssh.Window
25
26 readReq chan []byte
27 dataChan chan consoleData
28@@ -94,7 +94,7 @@ func (v *VConsole) DisableEcho() error {
29 }
30
31 func (v *VConsole) Reset() error {
32- _, err := v.Write([]byte("\033[?25h\033[0 q\033[34h\033[?25h\033[39;49m\033[m^O\033[H\033[J\033[?1049l\033[?1l\033>\033[?1000l\033[?1002l\033[?1003l\033[?1006l\033[?2004l"))
33+ _, err := v.Session.Write([]byte("\033[?25h\033[0 q\033[34h\033[?25h\033[39;49m\033[m^O\033[H\033[J\033[?1049l\033[?1l\033>\033[?1000l\033[?1002l\033[?1003l\033[?1006l\033[?2004l"))
34 return err
35 }
36
37@@ -108,7 +108,7 @@ func (v *VConsole) Size() (console.WinSize, error) {
38 }
39
40 func (v *VConsole) Fd() uintptr {
41- return v.pty.Slave.Fd()
42+ return 0
43 }
44
45 func (v *VConsole) Name() string {
46@@ -123,7 +123,11 @@ func (v *VConsole) Close() error {
47 return err
48 }
49
50-func NewVConsole(sesh ssh.Session) (*VConsole, error) {
51+func (v *VConsole) Write(p []byte) (int, error) {
52+ return v.Session.Write(p)
53+}
54+
55+func NewVConsole(sesh *pssh.SSHServerConnSession) (*VConsole, error) {
56 pty, win, ok := sesh.Pty()
57 if !ok {
58 return nil, fmt.Errorf("PTY not found")
59@@ -178,7 +182,7 @@ func NewVConsole(sesh ssh.Session) (*VConsole, error) {
60 return vty, nil
61 }
62
63-func NewSenpaiApp(sesh ssh.Session, username, pass string) (*senpai.App, error) {
64+func NewSenpaiApp(sesh *pssh.SSHServerConnSession, username, pass string) (*senpai.App, error) {
65 vty, err := NewVConsole(sesh)
66 if err != nil {
67 slog.Error("PTY not found")
1@@ -1,11 +1,12 @@
2 package shared
3
4 import (
5+ "fmt"
6 "log/slog"
7
8- "github.com/charmbracelet/ssh"
9- "github.com/picosh/pico/db"
10+ "github.com/picosh/pico/pkg/db"
11 "github.com/picosh/utils"
12+ "golang.org/x/crypto/ssh"
13 )
14
15 type SshAuthHandler struct {
16@@ -24,7 +25,7 @@ func NewSshAuthHandler(dbh AuthFindUser, logger *slog.Logger) *SshAuthHandler {
17 }
18 }
19
20-func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) bool {
21+func (r *SshAuthHandler) PubkeyAuthHandler(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
22 pubkey := utils.KeyForKeyText(key)
23 user, err := r.DB.FindUserByPubkey(pubkey)
24 if err != nil {
25@@ -34,20 +35,20 @@ func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) b
26 "key", string(key.Marshal()),
27 "err", err,
28 )
29- return false
30+ return nil, err
31 }
32
33 if user.Name == "" {
34 r.Logger.Error("username is not set")
35- return false
36+ return nil, fmt.Errorf("username is not set")
37 }
38
39- if ctx.Permissions().Extensions == nil {
40- ctx.Permissions().Extensions = map[string]string{}
41- }
42- ctx.Permissions().Extensions["user_id"] = user.ID
43- ctx.Permissions().Extensions["pubkey"] = pubkey
44- return true
45+ return &ssh.Permissions{
46+ Extensions: map[string]string{
47+ "user_id": user.ID,
48+ "pubkey": pubkey,
49+ },
50+ }, nil
51 }
52
53 func FindPlusFF(dbpool db.DB, cfg *ConfigSite, userID string) *db.FeatureFlag {
1@@ -8,7 +8,7 @@ import (
2 "path/filepath"
3 "strings"
4
5- sst "github.com/picosh/pobj/storage"
6+ sst "github.com/picosh/pico/pkg/pobj/storage"
7 )
8
9 type StorageFS struct {
1@@ -5,7 +5,7 @@ import (
2 "net/http"
3 "time"
4
5- sst "github.com/picosh/pobj/storage"
6+ sst "github.com/picosh/pico/pkg/pobj/storage"
7 )
8
9 type StorageMemory struct {
1@@ -8,7 +8,7 @@ import (
2 "path/filepath"
3 "strings"
4
5- sst "github.com/picosh/pobj/storage"
6+ sst "github.com/picosh/pico/pkg/pobj/storage"
7 )
8
9 type StorageMinio struct {
1@@ -15,7 +15,7 @@ import (
2 "strings"
3 "time"
4
5- "github.com/picosh/pobj/storage"
6+ "github.com/picosh/pico/pkg/pobj/storage"
7 )
8
9 func GetMimeType(fpath string) string {
1@@ -3,7 +3,7 @@ package storage
2 import (
3 "io"
4
5- sst "github.com/picosh/pobj/storage"
6+ sst "github.com/picosh/pico/pkg/pobj/storage"
7 )
8
9 type StorageServe interface {
R tui/analytics.go =>
pkg/tui/analytics.go
+1,
-1
1@@ -10,7 +10,7 @@ import (
2 "git.sr.ht/~rockorager/vaxis/vxfw/list"
3 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
4 "git.sr.ht/~rockorager/vaxis/vxfw/text"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils"
8 )
9
R tui/border.go =>
pkg/tui/border.go
+0,
-0
R tui/chat.go =>
pkg/tui/chat.go
+0,
-0
R tui/group.go =>
pkg/tui/group.go
+0,
-0
R tui/info.go =>
pkg/tui/info.go
+1,
-1
1@@ -7,7 +7,7 @@ import (
2 "git.sr.ht/~rockorager/vaxis"
3 "git.sr.ht/~rockorager/vaxis/vxfw"
4 "git.sr.ht/~rockorager/vaxis/vxfw/text"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 )
8
9 type UsageInfo struct {
R tui/input.go =>
pkg/tui/input.go
+0,
-0
R tui/kv.go =>
pkg/tui/kv.go
+0,
-0
R tui/logs.go =>
pkg/tui/logs.go
+1,
-1
1@@ -13,7 +13,7 @@ import (
2 "git.sr.ht/~rockorager/vaxis/vxfw/list"
3 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
4 "git.sr.ht/~rockorager/vaxis/vxfw/text"
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 "github.com/picosh/utils"
8 pipeLogger "github.com/picosh/utils/pipe/log"
9 )
1@@ -5,7 +5,7 @@ import (
2 "git.sr.ht/~rockorager/vaxis/vxfw"
3 "git.sr.ht/~rockorager/vaxis/vxfw/list"
4 "git.sr.ht/~rockorager/vaxis/vxfw/text"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 )
8
9 var menuChoices = []string{
R tui/pager.go =>
pkg/tui/pager.go
+0,
-0
R tui/plus.go =>
pkg/tui/plus.go
+0,
-0
R tui/pubkeys.go =>
pkg/tui/pubkeys.go
+1,
-1
1@@ -10,7 +10,7 @@ import (
2 "git.sr.ht/~rockorager/vaxis/vxfw/list"
3 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
4 "git.sr.ht/~rockorager/vaxis/vxfw/text"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils"
8 "golang.org/x/crypto/ssh"
9 )
R tui/senpai.go =>
pkg/tui/senpai.go
+1,
-1
1@@ -3,7 +3,7 @@ package tui
2 import (
3 "io"
4
5- "github.com/picosh/pico/shared"
6+ "github.com/picosh/pico/pkg/shared"
7 )
8
9 type SenpaiCmd struct {
R tui/signup.go =>
pkg/tui/signup.go
+1,
-1
1@@ -8,7 +8,7 @@ import (
2 "git.sr.ht/~rockorager/vaxis/vxfw/button"
3 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
4 "git.sr.ht/~rockorager/vaxis/vxfw/text"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 "github.com/picosh/utils"
8 "golang.org/x/crypto/ssh"
9 )
R tui/tokens.go =>
pkg/tui/tokens.go
+1,
-1
1@@ -10,7 +10,7 @@ import (
2 "git.sr.ht/~rockorager/vaxis/vxfw/list"
3 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
4 "git.sr.ht/~rockorager/vaxis/vxfw/text"
5- "github.com/picosh/pico/db"
6+ "github.com/picosh/pico/pkg/db"
7 )
8
9 type TokensPage struct {
R tui/ui.go =>
pkg/tui/ui.go
+4,
-4
1@@ -9,9 +9,9 @@ import (
2 "git.sr.ht/~rockorager/vaxis"
3 "git.sr.ht/~rockorager/vaxis/vxfw"
4 "git.sr.ht/~rockorager/vaxis/vxfw/richtext"
5- "github.com/charmbracelet/ssh"
6- "github.com/picosh/pico/db"
7- "github.com/picosh/pico/shared"
8+ "github.com/picosh/pico/pkg/db"
9+ "github.com/picosh/pico/pkg/pssh"
10+ "github.com/picosh/pico/pkg/shared"
11 "github.com/picosh/utils"
12 )
13
14@@ -19,7 +19,7 @@ var HOME = "dash"
15
16 type SharedModel struct {
17 Logger *slog.Logger
18- Session ssh.Session
19+ Session *pssh.SSHServerConnSession
20 Cfg *shared.ConfigSite
21 Dbpool db.DB
22 User *db.User
+108,
-0
1@@ -0,0 +1,108 @@
2+package tunkit
3+
4+import (
5+ "errors"
6+ "io"
7+ "log/slog"
8+ "net"
9+ "sync"
10+
11+ "github.com/picosh/pico/pkg/pssh"
12+ "golang.org/x/crypto/ssh"
13+)
14+
15+type forwardedTCPPayload struct {
16+ Addr string
17+ Port uint32
18+ OriginAddr string
19+ OriginPort uint32
20+}
21+
22+type Tunnel interface {
23+ CreateConn(ctx *pssh.SSHServerConnSession) (net.Conn, error)
24+ GetLogger() *slog.Logger
25+ Close(ctx *pssh.SSHServerConnSession) error
26+}
27+
28+func LocalForwardHandler(handler Tunnel) pssh.SSHServerChannelMiddleware {
29+ return func(newChan ssh.NewChannel, sc *pssh.SSHServerConn) error {
30+ check := &forwardedTCPPayload{}
31+ err := ssh.Unmarshal(newChan.ExtraData(), check)
32+ logger := handler.GetLogger()
33+ if err != nil {
34+ logger.Error(
35+ "error unmarshaling information",
36+ "err", err,
37+ )
38+ return err
39+ }
40+
41+ log := logger.With(
42+ "addr", check.Addr,
43+ "port", check.Port,
44+ "origAddr", check.OriginAddr,
45+ "origPort", check.OriginPort,
46+ )
47+ log.Info("local forward request")
48+
49+ ch, reqs, err := newChan.Accept()
50+ if err != nil {
51+ log.Error("cannot accept new channel", "err", err)
52+ return err
53+ }
54+
55+ ctx := &pssh.SSHServerConnSession{
56+ Channel: ch,
57+ SSHServerConn: sc,
58+ }
59+
60+ go ssh.DiscardRequests(reqs)
61+
62+ go func() {
63+ downConn, err := handler.CreateConn(ctx)
64+ if err != nil {
65+ log.Error("unable to connect to conn", "err", err)
66+ ch.Close()
67+ return
68+ }
69+ defer downConn.Close()
70+
71+ var wg sync.WaitGroup
72+ wg.Add(2)
73+
74+ go func() {
75+ defer wg.Done()
76+ defer func() {
77+ _ = ch.CloseWrite()
78+ }()
79+ defer downConn.Close()
80+ _, err := io.Copy(ch, downConn)
81+ if err != nil {
82+ if !errors.Is(err, net.ErrClosed) {
83+ log.Error("io copy", "err", err)
84+ }
85+ }
86+ }()
87+ go func() {
88+ defer wg.Done()
89+ defer ch.Close()
90+ defer downConn.Close()
91+ _, err := io.Copy(downConn, ch)
92+ if err != nil {
93+ if !errors.Is(err, net.ErrClosed) {
94+ log.Error("io copy", "err", err)
95+ }
96+ }
97+ }()
98+
99+ wg.Wait()
100+ }()
101+
102+ <-ctx.Done()
103+ err = handler.Close(ctx)
104+ if err != nil {
105+ log.Error("tunnel handler error", "err", err)
106+ }
107+ return err
108+ }
109+}
+92,
-0
1@@ -0,0 +1,92 @@
2+package tunkit
3+
4+import (
5+ "fmt"
6+ "log/slog"
7+ "net"
8+ "os"
9+
10+ "github.com/picosh/pico/pkg/pssh"
11+)
12+
13+type ctxAddressKey struct{}
14+
15+func getAddressCtx(ctx *pssh.SSHServerConnSession) (string, error) {
16+ address, ok := ctx.Value(ctxAddressKey{}).(string)
17+ if address == "" || !ok {
18+ return address, fmt.Errorf("address not set on `*pssh.SSHServerConnSession()` for connection")
19+ }
20+ return address, nil
21+}
22+func setAddressCtx(ctx *pssh.SSHServerConnSession, address string) {
23+ ctx.SetValue(ctxAddressKey{}, address)
24+}
25+
26+type WebTunnelHandler struct {
27+ HttpHandler HttpHandlerFn
28+ Logger *slog.Logger
29+}
30+
31+func NewWebTunnelHandler(handler HttpHandlerFn, logger *slog.Logger) *WebTunnelHandler {
32+ return &WebTunnelHandler{
33+ HttpHandler: handler,
34+ Logger: logger,
35+ }
36+}
37+
38+func (wt *WebTunnelHandler) GetLogger() *slog.Logger {
39+ return wt.Logger
40+}
41+
42+func (wt *WebTunnelHandler) GetHttpHandler() HttpHandlerFn {
43+ return wt.HttpHandler
44+}
45+
46+func (wt *WebTunnelHandler) Close(ctx *pssh.SSHServerConnSession) error {
47+ listener, err := getListenerCtx(ctx)
48+ if err != nil {
49+ return err
50+ }
51+
52+ if listener != nil {
53+ _ = listener.Close()
54+ setListenerCtx(ctx, nil)
55+ }
56+
57+ return nil
58+}
59+
60+func (wt *WebTunnelHandler) CreateListener(ctx *pssh.SSHServerConnSession) (net.Listener, error) {
61+ tempFile, err := os.CreateTemp("", "")
62+ if err != nil {
63+ return nil, err
64+ }
65+
66+ tempFile.Close()
67+ address := tempFile.Name()
68+ os.Remove(address)
69+
70+ connListener, err := net.Listen("unix", address)
71+ if err != nil {
72+ return nil, err
73+ }
74+ setAddressCtx(ctx, address)
75+ setListenerCtx(ctx, connListener)
76+
77+ return connListener, nil
78+}
79+
80+func (wt *WebTunnelHandler) CreateConn(ctx *pssh.SSHServerConnSession) (net.Conn, error) {
81+ _, err := httpServe(wt, ctx, wt.GetLogger())
82+ if err != nil {
83+ wt.GetLogger().Info("unable to create listener", "err", err)
84+ return nil, err
85+ }
86+
87+ address, err := getAddressCtx(ctx)
88+ if err != nil {
89+ return nil, err
90+ }
91+
92+ return net.Dial("unix", address)
93+}
+57,
-0
1@@ -0,0 +1,57 @@
2+package tunkit
3+
4+import (
5+ "fmt"
6+ "log/slog"
7+ "net"
8+ "net/http"
9+
10+ "github.com/picosh/pico/pkg/pssh"
11+)
12+
13+type HttpHandlerFn = func(ctx *pssh.SSHServerConnSession) http.Handler
14+
15+type WebTunnel interface {
16+ GetHttpHandler() HttpHandlerFn
17+ CreateListener(ctx *pssh.SSHServerConnSession) (net.Listener, error)
18+ CreateConn(ctx *pssh.SSHServerConnSession) (net.Conn, error)
19+ GetLogger() *slog.Logger
20+ Close(ctx *pssh.SSHServerConnSession) error
21+}
22+
23+type ctxListenerKey struct{}
24+
25+func getListenerCtx(ctx *pssh.SSHServerConnSession) (net.Listener, error) {
26+ listener, ok := ctx.Value(ctxListenerKey{}).(net.Listener)
27+ if listener == nil || !ok {
28+ return nil, fmt.Errorf("listener not set on `*pssh.SSHServerConnSession()` for connection")
29+ }
30+ return listener, nil
31+}
32+
33+func setListenerCtx(ctx *pssh.SSHServerConnSession, listener net.Listener) {
34+ ctx.SetValue(ctxListenerKey{}, listener)
35+}
36+
37+func httpServe(handler WebTunnel, ctx *pssh.SSHServerConnSession, log *slog.Logger) (net.Listener, error) {
38+ cached, _ := getListenerCtx(ctx)
39+ if cached != nil {
40+ return cached, nil
41+ }
42+
43+ listener, err := handler.CreateListener(ctx)
44+ if err != nil {
45+ return nil, err
46+ }
47+ setListenerCtx(ctx, listener)
48+
49+ go func() {
50+ httpHandler := handler.GetHttpHandler()
51+ err := http.Serve(listener, httpHandler(ctx))
52+ if err != nil {
53+ log.Error("serving http content", "err", err)
54+ }
55+ }()
56+
57+ return listener, nil
58+}
+0,
-130
1@@ -1,130 +0,0 @@
2-package prose
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "os/signal"
9- "syscall"
10- "time"
11-
12- "github.com/charmbracelet/promwish"
13- "github.com/charmbracelet/ssh"
14- "github.com/charmbracelet/wish"
15- "github.com/picosh/pico/db/postgres"
16- "github.com/picosh/pico/filehandlers"
17- uploadimgs "github.com/picosh/pico/filehandlers/imgs"
18- "github.com/picosh/pico/shared"
19- "github.com/picosh/pico/shared/storage"
20- wsh "github.com/picosh/pico/wish"
21- "github.com/picosh/send/auth"
22- "github.com/picosh/send/list"
23- "github.com/picosh/send/pipe"
24- wishrsync "github.com/picosh/send/protocols/rsync"
25- "github.com/picosh/send/protocols/scp"
26- "github.com/picosh/send/protocols/sftp"
27- "github.com/picosh/send/proxy"
28- "github.com/picosh/utils"
29-)
30-
31-func createRouter(handler *filehandlers.FileHandlerRouter) proxy.Router {
32- return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
33- return []wish.Middleware{
34- pipe.Middleware(handler, ".md"),
35- list.Middleware(handler),
36- scp.Middleware(handler),
37- wishrsync.Middleware(handler),
38- auth.Middleware(handler),
39- wsh.PtyMdw(wsh.DeprecatedNotice()),
40- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool),
41- }
42- }
43-}
44-
45-func withProxy(handler *filehandlers.FileHandlerRouter, otherMiddleware ...wish.Middleware) ssh.Option {
46- return func(server *ssh.Server) error {
47- err := sftp.SSHOption(handler)(server)
48- if err != nil {
49- return err
50- }
51-
52- newSubsystemHandlers := map[string]ssh.SubsystemHandler{}
53-
54- for name, subsystemHandler := range server.SubsystemHandlers {
55- newSubsystemHandlers[name] = func(s ssh.Session) {
56- wsh.LogMiddleware(handler.GetLogger(s), handler.DBPool)(ssh.Handler(subsystemHandler))(s)
57- }
58- }
59-
60- server.SubsystemHandlers = newSubsystemHandlers
61-
62- return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
63- }
64-}
65-
66-func StartSshServer() {
67- host := utils.GetEnv("PROSE_HOST", "0.0.0.0")
68- port := utils.GetEnv("PROSE_SSH_PORT", "2222")
69- promPort := utils.GetEnv("PROSE_PROM_PORT", "9222")
70- cfg := NewConfigSite("prose-ssh")
71- logger := cfg.Logger
72- dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
73- defer dbh.Close()
74-
75- hooks := &MarkdownHooks{
76- Cfg: cfg,
77- Db: dbh,
78- }
79-
80- var st storage.StorageServe
81- var err error
82- if cfg.MinioURL == "" {
83- st, err = storage.NewStorageFS(cfg.Logger, cfg.StorageDir)
84- } else {
85- st, err = storage.NewStorageMinio(cfg.Logger, cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
86- }
87-
88- if err != nil {
89- logger.Error("storage", "err", err.Error())
90- return
91- }
92-
93- fileMap := map[string]filehandlers.ReadWriteHandler{
94- ".md": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
95- ".css": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
96- "fallback": uploadimgs.NewUploadImgHandler(dbh, cfg, st),
97- }
98- handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
99-
100- sshAuth := shared.NewSshAuthHandler(dbh, logger)
101- s, err := wish.NewServer(
102- wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
103- wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
104- wish.WithPublicKeyAuth(sshAuth.PubkeyAuthHandler),
105- withProxy(
106- handler,
107- promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "prose-ssh"),
108- ),
109- )
110- if err != nil {
111- logger.Error("wish server", "err", err.Error())
112- return
113- }
114-
115- done := make(chan os.Signal, 1)
116- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
117- logger.Info("Starting SSH server", "host", host, "port", port)
118- go func() {
119- if err = s.ListenAndServe(); err != nil {
120- logger.Error(err.Error())
121- }
122- }()
123-
124- <-done
125- logger.Info("Stopping SSH server")
126- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
127- defer func() { cancel() }()
128- if err := s.Shutdown(ctx); err != nil {
129- logger.Error(err.Error())
130- }
131-}
+0,
-88
1@@ -1,88 +0,0 @@
2-package wish
3-
4-import (
5- "log/slog"
6- "time"
7-
8- "github.com/charmbracelet/ssh"
9- "github.com/charmbracelet/wish"
10- "github.com/picosh/pico/db"
11- "github.com/picosh/pico/shared"
12-)
13-
14-type ctxLoggerKey struct{}
15-type ctxUserKey struct{}
16-
17-type FindUserInterface interface {
18- FindUserByPubkey(string) (*db.User, error)
19-}
20-
21-func LogMiddleware(defaultLogger *slog.Logger, db FindUserInterface) wish.Middleware {
22- return func(sh ssh.Handler) ssh.Handler {
23- return func(s ssh.Session) {
24- ct := time.Now()
25-
26- logger := GetLogger(s)
27- if logger == slog.Default() {
28- logger = defaultLogger
29-
30- user := GetUser(s)
31- if user == nil {
32- user, err := db.FindUserByPubkey(s.Permissions().Extensions["pubkey"])
33- if err == nil && user != nil {
34- logger = shared.LoggerWithUser(logger, user).With(
35- "ip", s.RemoteAddr().String(),
36- )
37- s.Context().SetValue(ctxUserKey{}, user)
38- }
39- }
40-
41- s.Context().SetValue(ctxLoggerKey{}, logger)
42- }
43-
44- pty, _, ok := s.Pty()
45-
46- logger.Info(
47- "connect",
48- "sshUser", s.User(),
49- "pty", ok,
50- "term", pty.Term,
51- "windowWidth", pty.Window.Width,
52- "windowHeight", pty.Window.Height,
53- )
54-
55- sh(s)
56-
57- logger.Info(
58- "disconnect",
59- "sshUser", s.User(),
60- "pty", ok,
61- "term", pty.Term,
62- "windowWidth", pty.Window.Width,
63- "windowHeight", pty.Window.Height,
64- "duration", time.Since(ct),
65- )
66- }
67- }
68-}
69-
70-func GetLogger(s ssh.Session) *slog.Logger {
71- logger := slog.Default()
72- if s == nil {
73- return logger
74- }
75-
76- if v, ok := s.Context().Value(ctxLoggerKey{}).(*slog.Logger); ok {
77- return v
78- }
79-
80- return logger
81-}
82-
83-func GetUser(s ssh.Session) *db.User {
84- if v, ok := s.Context().Value(ctxUserKey{}).(*db.User); ok {
85- return v
86- }
87-
88- return nil
89-}
+0,
-39
1@@ -1,39 +0,0 @@
2-package wish
3-
4-import (
5- "fmt"
6-
7- "github.com/charmbracelet/ssh"
8- "github.com/charmbracelet/wish"
9-)
10-
11-func SessionMessage(sesh ssh.Session, msg string) {
12- _, _ = sesh.Write([]byte(msg + "\r\n"))
13-}
14-
15-func DeprecatedNotice() wish.Middleware {
16- return func(next ssh.Handler) ssh.Handler {
17- return func(sesh ssh.Session) {
18- msg := fmt.Sprintf(
19- "%s\n\nRun %s to access pico's TUI",
20- "DEPRECATED",
21- "ssh pico.sh",
22- )
23- SessionMessage(sesh, msg)
24- next(sesh)
25- }
26- }
27-}
28-
29-func PtyMdw(mdw wish.Middleware) wish.Middleware {
30- return func(next ssh.Handler) ssh.Handler {
31- return func(sesh ssh.Session) {
32- _, _, ok := sesh.Pty()
33- if !ok {
34- next(sesh)
35- return
36- }
37- mdw(next)(sesh)
38- }
39- }
40-}