mirror of
https://github.com/fiatjaf/nak.git
synced 2026-01-24 19:38:52 +00:00
blossom mirror to only take a URL and do its thing, not try to list blobs.
This commit is contained in:
153
blossom.go
153
blossom.go
@@ -3,15 +3,10 @@ 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"
|
||||||
@@ -234,47 +229,56 @@ if any of the files are not found the command will fail, otherwise it will succe
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "mirror",
|
Name: "mirror",
|
||||||
Usage: "mirrors blobs from source server to target server",
|
Usage: "mirrors a from a server to another",
|
||||||
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.`,
|
Description: `examples:
|
||||||
|
mirroring a single blob:
|
||||||
|
nak blossom mirror https://nostr.download/5672be22e6da91c12b929a0f46b9e74de8b5366b9b19a645ff949c24052f9ad4 -s blossom.band
|
||||||
|
|
||||||
|
mirroring all blobs from a certain pubkey from one server to the other:
|
||||||
|
nak blossom list 78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d -s nostr.download | nak blossom mirror -s blossom.band`,
|
||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
targetClient, err := getBlossomClient(ctx, c)
|
client, err := getBlossomClient(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create client for source server
|
var bd blossom.BlobDescriptor
|
||||||
sourceServer := c.Args().First()
|
if input := c.Args().First(); input != "" {
|
||||||
keyer, _, err := gatherKeyerFromArguments(ctx, c)
|
blobURL := input
|
||||||
if err != nil {
|
if err := json.Unmarshal([]byte(input), &bd); err == nil {
|
||||||
return err
|
blobURL = bd.URL
|
||||||
}
|
}
|
||||||
sourceClient := blossom.NewClient(sourceServer, keyer)
|
bd, err := client.MirrorBlob(ctx, blobURL)
|
||||||
|
|
||||||
// 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 {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to mirror %s: %s\n", bd.SHA256, err)
|
return err
|
||||||
hasError = true
|
}
|
||||||
continue
|
out, _ := json.Marshal(bd)
|
||||||
|
stdout(out)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
for input := range getJsonsOrBlank() {
|
||||||
|
if input == "{}" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
blobURL := input
|
||||||
|
if err := json.Unmarshal([]byte(input), &bd); err == nil {
|
||||||
|
blobURL = bd.URL
|
||||||
|
}
|
||||||
|
bd, err := client.MirrorBlob(ctx, blobURL)
|
||||||
|
if err != nil {
|
||||||
|
ctx = lineProcessingError(ctx, "failed to mirror '%s': %w", blobURL, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out, _ := json.Marshal(bd)
|
||||||
|
stdout(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
j, _ := json.Marshal(mirrored)
|
exitIfLineProcessingError(ctx)
|
||||||
stdout(string(j))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasError {
|
|
||||||
os.Exit(3)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -288,82 +292,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|||||||
2
event.go
2
event.go
@@ -145,7 +145,7 @@ example:
|
|||||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||||
relays = connectToAllRelays(ctx, c, relayUrls, nil,
|
relays = connectToAllRelays(ctx, c, relayUrls, nil,
|
||||||
nostr.PoolOptions{
|
nostr.PoolOptions{
|
||||||
AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
AuthRequiredHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
||||||
return authSigner(ctx, c, func(s string, args ...any) {}, authEvent)
|
return authSigner(ctx, c, func(s string, args ...any) {}, authEvent)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/fiatjaf/nak
|
|||||||
go 1.25
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6
|
fiatjaf.com/nostr v0.0.0-20260118173002-57d595a5b4c7
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/bep/debounce v1.2.1
|
github.com/bep/debounce v1.2.1
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.6
|
github.com/btcsuite/btcd/btcec/v2 v2.3.6
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -2,6 +2,8 @@ fiatjaf.com/lib v0.3.2 h1:RBS41z70d8Rp8e2nemQsbPY1NLLnEGShiY2c+Bom3+Q=
|
|||||||
fiatjaf.com/lib v0.3.2/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8=
|
fiatjaf.com/lib v0.3.2/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8=
|
||||||
fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6 h1:yH+cU9ZNgUdMCRa5eS3pmqTPP/QdZtSmQAIrN/U5nEc=
|
fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6 h1:yH+cU9ZNgUdMCRa5eS3pmqTPP/QdZtSmQAIrN/U5nEc=
|
||||||
fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
||||||
|
fiatjaf.com/nostr v0.0.0-20260118173002-57d595a5b4c7 h1:CkMr8zFLfoOO59+oNlBXXrga00lTKyl2A4fUXAJQ7fY=
|
||||||
|
fiatjaf.com/nostr v0.0.0-20260118173002-57d595a5b4c7/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||||
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ example:
|
|||||||
relayUrls = nostr.AppendUnique(relayUrls, c.Args().Slice()...)
|
relayUrls = nostr.AppendUnique(relayUrls, c.Args().Slice()...)
|
||||||
relays := connectToAllRelays(ctx, c, relayUrls, nil,
|
relays := connectToAllRelays(ctx, c, relayUrls, nil,
|
||||||
nostr.PoolOptions{
|
nostr.PoolOptions{
|
||||||
AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
AuthRequiredHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
||||||
return authSigner(ctx, c, func(s string, args ...any) {}, authEvent)
|
return authSigner(ctx, c, func(s string, args ...any) {}, authEvent)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
2
req.go
2
req.go
@@ -138,7 +138,7 @@ example:
|
|||||||
relayUrls,
|
relayUrls,
|
||||||
forcePreAuthSigner,
|
forcePreAuthSigner,
|
||||||
nostr.PoolOptions{
|
nostr.PoolOptions{
|
||||||
AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
AuthRequiredHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
||||||
return authSigner(ctx, c, func(s string, args ...any) {
|
return authSigner(ctx, c, func(s string, args ...any) {
|
||||||
if strings.HasPrefix(s, "authenticating as") {
|
if strings.HasPrefix(s, "authenticating as") {
|
||||||
cleanUrl, _ := strings.CutPrefix(
|
cleanUrl, _ := strings.CutPrefix(
|
||||||
|
|||||||
Reference in New Issue
Block a user