From bffe7bde12447ccef0f4e4559bd33ed3731369c0 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 26 Mar 2026 09:22:59 -0700 Subject: [PATCH] Add remote listing support to sgard list via -r flag. Co-Authored-By: Claude Opus 4.6 (1M context) --- PROGRESS.md | 1 + client/client.go | 17 +++++++++++ cmd/sgard/list.go | 75 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 72 insertions(+), 21 deletions(-) diff --git a/PROGRESS.md b/PROGRESS.md index f9acf41..1df9a4b 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -111,3 +111,4 @@ Phase 6: Manifest Signing (to be planned). | 2026-03-25 | — | `sgard info` command: shows detailed file information (status, hash, timestamps, mode, encryption, targeting). 5 tests. | | 2026-03-25 | — | Deploy sgardd to rift: Dockerfile, docker-compose, mc-proxy L4 route on :9443, Metacrypt TLS cert, DNS. | | 2026-03-25 | — | `sgard remote set/show`: persistent remote config in `/remote.yaml` (addr, tls, tls_ca). | +| 2026-03-26 | — | `sgard list` remote support: uses `resolveRemoteConfig()` to list server manifest via `PullManifest` RPC. Client `List()` method added. | diff --git a/client/client.go b/client/client.go index 0c15c9c..56af50d 100644 --- a/client/client.go +++ b/client/client.go @@ -8,6 +8,7 @@ import ( "io" "github.com/kisom/sgard/garden" + "github.com/kisom/sgard/manifest" "github.com/kisom/sgard/server" "github.com/kisom/sgard/sgardpb" "golang.org/x/crypto/ssh" @@ -273,6 +274,22 @@ func (c *Client) doPull(ctx context.Context, g *garden.Garden) (int, error) { return blobCount, nil } +// List fetches the server's manifest and returns its entries without +// downloading any blobs. Automatically re-authenticates if needed. +func (c *Client) List(ctx context.Context) ([]manifest.Entry, error) { + var entries []manifest.Entry + err := c.retryOnAuth(ctx, func() error { + resp, err := c.rpc.PullManifest(ctx, &sgardpb.PullManifestRequest{}) + if err != nil { + return fmt.Errorf("list remote: %w", err) + } + m := server.ProtoToManifest(resp.GetManifest()) + entries = m.Files + return nil + }) + return entries, err +} + // Prune requests the server to remove orphaned blobs. Returns the count removed. // Automatically re-authenticates if needed. func (c *Client) Prune(ctx context.Context) (int, error) { diff --git a/cmd/sgard/list.go b/cmd/sgard/list.go index d692936..0326e65 100644 --- a/cmd/sgard/list.go +++ b/cmd/sgard/list.go @@ -1,41 +1,74 @@ package main import ( + "context" "fmt" "github.com/kisom/sgard/garden" + "github.com/kisom/sgard/manifest" "github.com/spf13/cobra" ) +var listRemoteFlag bool + var listCmd = &cobra.Command{ Use: "list", Short: "List all tracked files", + Long: "List all tracked files locally, or on the remote server with -r.", RunE: func(cmd *cobra.Command, args []string) error { - g, err := garden.Open(repoFlag) - if err != nil { - return err + if listRemoteFlag { + return listRemote() } - - entries := g.List() - for _, e := range entries { - switch e.Type { - case "file": - hash := e.Hash - if len(hash) > 8 { - hash = hash[:8] - } - fmt.Printf("%-6s %s\t%s\n", "file", e.Path, hash) - case "link": - fmt.Printf("%-6s %s\t-> %s\n", "link", e.Path, e.Target) - case "directory": - fmt.Printf("%-6s %s\n", "dir", e.Path) - } - } - - return nil + return listLocal() }, } +func listLocal() error { + g, err := garden.Open(repoFlag) + if err != nil { + return err + } + + printEntries(g.List()) + return nil +} + +func listRemote() error { + ctx := context.Background() + + c, cleanup, err := dialRemote(ctx) + if err != nil { + return err + } + defer cleanup() + + entries, err := c.List(ctx) + if err != nil { + return err + } + + printEntries(entries) + return nil +} + +func printEntries(entries []manifest.Entry) { + for _, e := range entries { + switch e.Type { + case "file": + hash := e.Hash + if len(hash) > 8 { + hash = hash[:8] + } + fmt.Printf("%-6s %s\t%s\n", "file", e.Path, hash) + case "link": + fmt.Printf("%-6s %s\t-> %s\n", "link", e.Path, e.Target) + case "directory": + fmt.Printf("%-6s %s\n", "dir", e.Path) + } + } +} + func init() { + listCmd.Flags().BoolVarP(&listRemoteFlag, "use-remote", "r", false, "list files on the remote server") rootCmd.AddCommand(listCmd) }