Monorepo for Tangled tangled.org
4

Configure Feed

Select the types of activity you want to include in your feed.

1package keyfetch 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "os" 10 "strings" 11 12 "github.com/urfave/cli/v3" 13 "golang.org/x/crypto/ssh" 14 "tangled.org/core/log" 15) 16 17func Command() *cli.Command { 18 return &cli.Command{ 19 Name: "keys", 20 Usage: "fetch public keys from the knot server", 21 Action: Run, 22 Flags: []cli.Flag{ 23 &cli.StringFlag{ 24 Name: "output", 25 Aliases: []string{"o"}, 26 Usage: "output format (table, json, authorized-keys)", 27 Value: "table", 28 }, 29 &cli.StringFlag{ 30 Name: "internal-api", 31 Usage: "internal API endpoint", 32 Value: "http://localhost:5444", 33 }, 34 &cli.StringFlag{ 35 Name: "git-dir", 36 Usage: "base directory for git repos", 37 Value: "/home/git", 38 }, 39 &cli.StringFlag{ 40 Name: "log-path", 41 Usage: "path to log file", 42 Value: "/home/git/log", 43 }, 44 }, 45 } 46} 47 48func Run(ctx context.Context, cmd *cli.Command) error { 49 l := log.FromContext(ctx) 50 51 internalApi := cmd.String("internal-api") 52 gitDir := cmd.String("git-dir") 53 logPath := cmd.String("log-path") 54 output := cmd.String("output") 55 56 executablePath, err := os.Executable() 57 if err != nil { 58 l.Error("error getting path of executable", "error", err) 59 return err 60 } 61 62 resp, err := http.Get(internalApi + "/keys") 63 if err != nil { 64 l.Error("error reaching internal API endpoint; is the knot server running?", "error", err) 65 return err 66 } 67 defer resp.Body.Close() 68 69 body, err := io.ReadAll(resp.Body) 70 if err != nil { 71 l.Error("error reading response body", "error", err) 72 return err 73 } 74 75 var data []map[string]any 76 err = json.Unmarshal(body, &data) 77 if err != nil { 78 l.Error("error unmarshalling response body", "error", err) 79 return err 80 } 81 82 switch output { 83 case "json": 84 prettyJSON, err := json.MarshalIndent(data, "", " ") 85 if err != nil { 86 l.Error("error pretty printing JSON", "error", err) 87 return err 88 } 89 90 if _, err := os.Stdout.Write(prettyJSON); err != nil { 91 l.Error("error writing to stdout", "error", err) 92 return err 93 } 94 case "authorized-keys": 95 formatted := formatKeyData(executablePath, gitDir, logPath, internalApi, data) 96 _, err := os.Stdout.Write([]byte(formatted)) 97 if err != nil { 98 l.Error("error writing to stdout", "error", err) 99 return err 100 } 101 case "table": 102 fmt.Printf("%-40s %-40s\n", "DID", "KEY") 103 fmt.Println(strings.Repeat("-", 80)) 104 105 for _, entry := range data { 106 did, _ := entry["did"].(string) 107 key, _ := entry["key"].(string) 108 fmt.Printf("%-40s %-40s\n", did, key) 109 } 110 } 111 return nil 112} 113 114func formatKeyData(executablePath, gitDir, logPath, endpoint string, data []map[string]any) string { 115 var result string 116 for _, entry := range data { 117 raw, _ := entry["key"].(string) 118 key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(raw)) 119 if err != nil { 120 continue 121 } 122 result += fmt.Sprintf( 123 `command="%s guard -git-dir %s -user %s -log-path %s -internal-api %s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s`+"\n", 124 executablePath, gitDir, entry["did"], logPath, endpoint, ssh.MarshalAuthorizedKey(key)) 125 } 126 return result 127}