mirror of
https://github.com/fiatjaf/nak.git
synced 2026-01-24 19:38:52 +00:00
implement blossom mirror
This commit is contained in:
committed by
fiatjaf_
parent
686d960f62
commit
cb2247c9da
125
blossom.go
125
blossom.go
@@ -3,10 +3,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/keyer"
|
"fiatjaf.com/nostr/keyer"
|
||||||
"fiatjaf.com/nostr/nipb0/blossom"
|
"fiatjaf.com/nostr/nipb0/blossom"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
@@ -230,11 +235,46 @@ if any of the files are not found the command will fail, otherwise it will succe
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "mirror",
|
Name: "mirror",
|
||||||
Usage: "",
|
Usage: "mirrors blobs from source server to target server",
|
||||||
Description: ``,
|
Description: `lists all blobs from the source server and mirrors them to the target server using BUD-04. requires --sec to sign the authorization event.`,
|
||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
ArgsUsage: "",
|
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
targetClient, err := getBlossomClient(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create client for source server
|
||||||
|
sourceServer := c.Args().First()
|
||||||
|
keyer, _, err := gatherKeyerFromArguments(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sourceClient := blossom.NewClient(sourceServer, keyer)
|
||||||
|
|
||||||
|
// Get list of blobs from source server
|
||||||
|
bds, err := sourceClient.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list blobs from source server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirror each blob to target server
|
||||||
|
hasError := false
|
||||||
|
for _, bd := range bds {
|
||||||
|
mirrored, err := mirrorBlob(ctx, targetClient, bd.URL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to mirror %s: %s\n", bd.SHA256, err)
|
||||||
|
hasError = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
j, _ := json.Marshal(mirrored)
|
||||||
|
stdout(string(j))
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasError {
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -248,3 +288,82 @@ func getBlossomClient(ctx context.Context, c *cli.Command) (*blossom.Client, err
|
|||||||
}
|
}
|
||||||
return blossom.NewClient(c.String("server"), keyer), nil
|
return blossom.NewClient(c.String("server"), keyer), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mirrorBlob mirrors a blob from a URL to the mediaserver using BUD-04
|
||||||
|
func mirrorBlob(ctx context.Context, client *blossom.Client, url string) (*blossom.BlobDescriptor, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to download blob from URL: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to download blob: HTTP %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.Sum256(data)
|
||||||
|
hashHex := hex.EncodeToString(hash[:])
|
||||||
|
|
||||||
|
signer := client.GetSigner()
|
||||||
|
pubkey, _ := signer.GetPublicKey(ctx)
|
||||||
|
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 24242,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"t", "upload"},
|
||||||
|
{"x", hashHex},
|
||||||
|
{"expiration", fmt.Sprintf("%d", nostr.Now()+60)},
|
||||||
|
},
|
||||||
|
Content: "blossom stuff",
|
||||||
|
PubKey: pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signer.SignEvent(ctx, &evt); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to sign authorization event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
evtj, err := json.Marshal(evt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal authorization event: %w", err)
|
||||||
|
}
|
||||||
|
auth := base64.StdEncoding.EncodeToString(evtj)
|
||||||
|
|
||||||
|
mediaserver := client.GetMediaServer()
|
||||||
|
mirrorURL := mediaserver + "mirror"
|
||||||
|
|
||||||
|
requestBody := map[string]string{"url": url}
|
||||||
|
requestJSON, _ := json.Marshal(requestBody)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "PUT", mirrorURL, bytes.NewReader(requestJSON))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create mirror request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Nostr "+auth)
|
||||||
|
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
mirrorResp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to send mirror request: %w", err)
|
||||||
|
}
|
||||||
|
defer mirrorResp.Body.Close()
|
||||||
|
|
||||||
|
if mirrorResp.StatusCode < 200 || mirrorResp.StatusCode >= 300 {
|
||||||
|
body, _ := io.ReadAll(mirrorResp.Body)
|
||||||
|
return nil, fmt.Errorf("mirror request failed with HTTP %d: %s", mirrorResp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var bd blossom.BlobDescriptor
|
||||||
|
if err := json.NewDecoder(mirrorResp.Body).Decode(&bd); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode blob descriptor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bd, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user