mirror of
https://github.com/fiatjaf/nak.git
synced 2026-01-26 12:28:49 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e6a6d7506 | ||
|
|
cff60b2f9f | ||
|
|
cb2247c9da |
132
.github/workflows/release-cli.yml
vendored
132
.github/workflows/release-cli.yml
vendored
@@ -4,123 +4,49 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
make-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
build-all-for-all:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- make-release
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goos: [linux, freebsd, windows]
|
||||
goos: [linux, freebsd, darwin, windows]
|
||||
goarch: [amd64, arm64, riscv64]
|
||||
exclude:
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
- goarch: riscv64
|
||||
goos: windows
|
||||
- goarch: arm64
|
||||
goos: freebsd
|
||||
- goarch: riscv64
|
||||
goos: freebsd
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
- goarch: riscv64
|
||||
goos: windows
|
||||
- goarch: riscv64
|
||||
goos: darwin
|
||||
- goarch: arm64
|
||||
goos: freebsd
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
- uses: wangyoucao577/go-release-action@v1.40
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Install FUSE dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libfuse-dev
|
||||
- name: Install cross-compilation tools for ARM64
|
||||
if: matrix.goarch == 'arm64' && matrix.goos == 'linux'
|
||||
run: |
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu
|
||||
- name: Install cross-compilation tools for RISC-V
|
||||
if: matrix.goarch == 'riscv64' && matrix.goos == 'linux'
|
||||
run: |
|
||||
sudo apt-get install -y gcc-riscv64-linux-gnu
|
||||
- name: Install FreeBSD SDK
|
||||
if: matrix.goos == 'freebsd'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang lld binutils-riscv64-unknown-elf 2>/dev/null || true
|
||||
mkdir -p /tmp/freebsd-sdk
|
||||
cd /tmp/freebsd-sdk
|
||||
# Determine download path based on architecture
|
||||
case "${{ matrix.goarch }}" in
|
||||
arm64) DLPATH="arm64" ;;
|
||||
riscv64) DLPATH="riscv" ;;
|
||||
*) DLPATH="amd64" ;;
|
||||
esac
|
||||
wget -q "https://download.freebsd.org/releases/${DLPATH}/14.3-RELEASE/base.txz" && tar -xf base.txz
|
||||
# Download and extract libfuse source
|
||||
mkdir -p libfuse && cd libfuse
|
||||
wget -q https://github.com/libfuse/libfuse/releases/download/fuse-2.9.9/fuse-2.9.9.tar.gz
|
||||
tar -xzf fuse-2.9.9.tar.gz --strip-components=1
|
||||
mkdir -p /tmp/freebsd-sdk/usr/include
|
||||
cp include/fuse.h /tmp/freebsd-sdk/usr/include/
|
||||
cp include/fuse_*.h /tmp/freebsd-sdk/usr/include/ 2>/dev/null || true
|
||||
cd /tmp/freebsd-sdk
|
||||
- name: Set cross-compiler
|
||||
id: set-cc
|
||||
run: |
|
||||
if [ "${{ matrix.goarch }}" = "arm64" ] && [ "${{ matrix.goos }}" = "linux" ]; then
|
||||
echo "CC=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
|
||||
elif [ "${{ matrix.goarch }}" = "riscv64" ] && [ "${{ matrix.goos }}" = "linux" ]; then
|
||||
echo "CC=riscv64-linux-gnu-gcc" >> $GITHUB_ENV
|
||||
elif [ "${{ matrix.goos }}" = "freebsd" ]; then
|
||||
TRIPLE="x86_64-unknown-freebsd14.3"
|
||||
[ "${{ matrix.goarch }}" = "arm64" ] && TRIPLE="aarch64-unknown-freebsd14.3"
|
||||
[ "${{ matrix.goarch }}" = "riscv64" ] && TRIPLE="riscv64-unknown-freebsd14.3"
|
||||
echo "CC=clang --target=$TRIPLE --sysroot=/tmp/freebsd-sdk" >> $GITHUB_ENV
|
||||
echo "CGO_CFLAGS=-isystem /tmp/freebsd-sdk/usr/include --target=$TRIPLE" >> $GITHUB_ENV
|
||||
echo "CGO_LDFLAGS=-L/tmp/freebsd-sdk/usr/lib -L/tmp/freebsd-sdk/lib -L/tmp/freebsd-sdk/usr/lib64 --target=$TRIPLE" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build binary
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: ${{ matrix.goos == 'windows' && '0' || '1' }}
|
||||
run: |
|
||||
go build -ldflags "-X main.version=${{ github.ref_name }}" -o nak-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
- name: Upload Release Asset
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: ./nak-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
|
||||
build-darwin:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goarch: [amd64, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Install macFUSE
|
||||
run: brew install --cask macfuse
|
||||
- name: Build binary
|
||||
env:
|
||||
GOOS: darwin
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: '1'
|
||||
run: |
|
||||
go build -ldflags "-X main.version=${{ github.ref_name }}" -o nak-${{ github.ref_name }}-darwin-${{ matrix.goarch }}
|
||||
- name: Upload Release Asset
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: ./nak-${{ github.ref_name }}-darwin-${{ matrix.goarch }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
ldflags: -X main.version=${{ github.ref_name }}
|
||||
overwrite: true
|
||||
md5sum: false
|
||||
sha256sum: false
|
||||
compress_assets: false
|
||||
|
||||
smoke-test-linux-amd64:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
125
blossom.go
125
blossom.go
@@ -3,10 +3,15 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/keyer"
|
||||
"fiatjaf.com/nostr/nipb0/blossom"
|
||||
"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",
|
||||
Usage: "",
|
||||
Description: ``,
|
||||
Usage: "mirrors blobs from source server to target server",
|
||||
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,
|
||||
ArgsUsage: "",
|
||||
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
|
||||
},
|
||||
},
|
||||
@@ -248,3 +288,82 @@ func getBlossomClient(ctx context.Context, c *cli.Command) (*blossom.Client, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
50
fs.go
50
fs.go
@@ -13,8 +13,10 @@ import (
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/keyer"
|
||||
"github.com/fatih/color"
|
||||
"github.com/fiatjaf/nak/nostrfs"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/winfsp/cgofuse/fuse"
|
||||
)
|
||||
|
||||
var fsCmd = &cli.Command{
|
||||
@@ -62,7 +64,7 @@ var fsCmd = &cli.Command{
|
||||
apat = time.Hour * 24 * 365 * 3
|
||||
}
|
||||
|
||||
root := NewFSRoot(
|
||||
root := nostrfs.NewNostrRoot(
|
||||
context.WithValue(
|
||||
context.WithValue(
|
||||
ctx,
|
||||
@@ -73,7 +75,7 @@ var fsCmd = &cli.Command{
|
||||
sys,
|
||||
kr,
|
||||
mountpoint,
|
||||
FSOptions{
|
||||
nostrfs.Options{
|
||||
AutoPublishNotesTimeout: apnt,
|
||||
AutoPublishArticlesTimeout: apat,
|
||||
},
|
||||
@@ -81,22 +83,21 @@ var fsCmd = &cli.Command{
|
||||
|
||||
// create the server
|
||||
log("- mounting at %s... ", color.HiCyanString(mountpoint))
|
||||
|
||||
// create cgofuse host
|
||||
host := fuse.NewFileSystemHost(root)
|
||||
host.SetCapReaddirPlus(true)
|
||||
host.SetUseIno(true)
|
||||
|
||||
// mount the filesystem
|
||||
mountArgs := []string{"-s", mountpoint}
|
||||
if isVerbose {
|
||||
mountArgs = append([]string{"-d"}, mountArgs...)
|
||||
timeout := time.Second * 120
|
||||
server, err := fs.Mount(mountpoint, root, &fs.Options{
|
||||
MountOptions: fuse.MountOptions{
|
||||
Debug: isVerbose,
|
||||
Name: "nak",
|
||||
FsName: "nak",
|
||||
RememberInodes: true,
|
||||
},
|
||||
AttrTimeout: &timeout,
|
||||
EntryTimeout: &timeout,
|
||||
Logger: nostr.DebugLogger,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("mount failed: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
host.Mount("", mountArgs)
|
||||
}()
|
||||
|
||||
log("ok.\n")
|
||||
|
||||
// setup signal handling for clean unmount
|
||||
@@ -106,12 +107,17 @@ var fsCmd = &cli.Command{
|
||||
go func() {
|
||||
<-ch
|
||||
log("- unmounting... ")
|
||||
// cgofuse doesn't have explicit unmount, it unmounts on process exit
|
||||
log("ok\n")
|
||||
chErr <- nil
|
||||
err := server.Unmount()
|
||||
if err != nil {
|
||||
chErr <- fmt.Errorf("unmount failed: %w", err)
|
||||
} else {
|
||||
log("ok\n")
|
||||
chErr <- nil
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for signals
|
||||
// serve the filesystem until unmounted
|
||||
server.Wait()
|
||||
return <-chErr
|
||||
},
|
||||
}
|
||||
|
||||
5
go.mod
5
go.mod
@@ -11,8 +11,9 @@ require (
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/hanwen/go-fuse/v2 v2.7.2
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/liamg/magic v0.0.1 // indirect
|
||||
github.com/liamg/magic v0.0.1
|
||||
github.com/mailru/easyjson v0.9.1
|
||||
github.com/mark3labs/mcp-go v0.8.3
|
||||
github.com/markusmobius/go-dateparser v1.2.3
|
||||
@@ -28,6 +29,8 @@ require (
|
||||
golang.org/x/term v0.32.0
|
||||
)
|
||||
|
||||
require fiatjaf.com/lib v0.3.2
|
||||
|
||||
require (
|
||||
github.com/FastFilter/xorfilter v0.2.1 // indirect
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,3 +1,5 @@
|
||||
fiatjaf.com/lib v0.3.2 h1:RBS41z70d8Rp8e2nemQsbPY1NLLnEGShiY2c+Bom3+Q=
|
||||
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/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
@@ -142,6 +144,8 @@ github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnm
|
||||
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
||||
github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
|
||||
github.com/hablullah/go-juliandays v1.0.0/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
|
||||
github.com/hanwen/go-fuse/v2 v2.7.2 h1:SbJP1sUP+n1UF8NXBA14BuojmTez+mDgOk0bC057HQw=
|
||||
github.com/hanwen/go-fuse/v2 v2.7.2/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
@@ -165,6 +169,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
|
||||
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
@@ -194,6 +200,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
||||
Reference in New Issue
Block a user