diff --git a/.goreleaser-darwin.yml b/.goreleaser-darwin.yml index 36b548124..7c06a6c87 100644 --- a/.goreleaser-darwin.yml +++ b/.goreleaser-darwin.yml @@ -40,8 +40,8 @@ builds: main: ./cmd/dmsg-server/ ldflags: -s -w -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - - id: dmsgget - binary: dmsgget + - id: dmsgcurl + binary: dmsgcurl goos: - darwin goarch: @@ -49,7 +49,7 @@ builds: - amd64 env: - CGO_ENABLED=0 - main: ./cmd/dmsgget/ + main: ./cmd/dmsgcurl/ ldflags: -s -w -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - id: dmsgpty-ui @@ -98,7 +98,7 @@ archives: - dmsg-server - dmsgpty-ui - dmsgpty-host - - dmsgget + - dmsgcurl - dmsgpty-cli allow_different_binary_count: true diff --git a/.goreleaser-linux.yml b/.goreleaser-linux.yml index 055a0d187..3f4f3b719 100644 --- a/.goreleaser-linux.yml +++ b/.goreleaser-linux.yml @@ -121,8 +121,8 @@ builds: main: ./cmd/dmsg-server/ ldflags: -s -w -linkmode external -extldflags '-static' -buildid= -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - - id: dmsgget-amd64 - binary: dmsgget + - id: dmsgcurl-amd64 + binary: dmsgcurl goos: - linux goarch: @@ -130,11 +130,11 @@ builds: env: - CGO_ENABLED=1 - CC=/home/runner/work/dmsg/dmsg/musl-data/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc - main: ./cmd/dmsgget/ + main: ./cmd/dmsgcurl/ ldflags: -s -w -linkmode external -extldflags '-static' -buildid= -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - - id: dmsgget-arm64 - binary: dmsgget + - id: dmsgcurl-arm64 + binary: dmsgcurl goos: - linux goarch: @@ -142,11 +142,11 @@ builds: env: - CGO_ENABLED=1 - CC=/home/runner/work/dmsg/dmsg/musl-data/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc - main: ./cmd/dmsgget/ + main: ./cmd/dmsgcurl/ ldflags: -s -w -linkmode external -extldflags '-static' -buildid= -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - - id: dmsgget-arm - binary: dmsgget + - id: dmsgcurl-arm + binary: dmsgcurl goos: - linux goarch: @@ -156,11 +156,11 @@ builds: env: - CGO_ENABLED=1 - CC=/home/runner/work/dmsg/dmsg/musl-data/arm-linux-musleabi-cross/bin/arm-linux-musleabi-gcc - main: ./cmd/dmsgget/ + main: ./cmd/dmsgcurl/ ldflags: -s -w -linkmode external -extldflags '-static' -buildid= -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - - id: dmsgget-armhf - binary: dmsgget + - id: dmsgcurl-armhf + binary: dmsgcurl goos: - linux goarch: @@ -170,7 +170,7 @@ builds: env: - CGO_ENABLED=1 - CC=/home/runner/work/dmsg/dmsg/musl-data/arm-linux-musleabihf-cross/bin/arm-linux-musleabihf-gcc - main: ./cmd/dmsgget/ + main: ./cmd/dmsgcurl/ ldflags: -s -w -linkmode external -extldflags '-static' -buildid= -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - id: dmsgpty-ui-amd64 @@ -339,7 +339,7 @@ archives: - dmsg-server-amd64 - dmsgpty-ui-amd64 - dmsgpty-cli-amd64 - - dmsgget-amd64 + - dmsgcurl-amd64 - dmsgpty-host-amd64 - id: arm64 @@ -351,7 +351,7 @@ archives: - dmsg-server-arm64 - dmsgpty-ui-arm64 - dmsgpty-cli-arm64 - - dmsgget-arm64 + - dmsgcurl-arm64 - dmsgpty-host-arm64 - id: arm @@ -363,7 +363,7 @@ archives: - dmsg-server-arm - dmsgpty-ui-arm - dmsgpty-cli-arm - - dmsgget-arm + - dmsgcurl-arm - dmsgpty-host-arm - id: armhf @@ -375,7 +375,7 @@ archives: - dmsg-server-armhf - dmsgpty-ui-armhf - dmsgpty-cli-armhf - - dmsgget-armhf + - dmsgcurl-armhf - dmsgpty-host-armhf checksum: diff --git a/.goreleaser-windows.yml b/.goreleaser-windows.yml index 5f2c976c1..246420e1b 100644 --- a/.goreleaser-windows.yml +++ b/.goreleaser-windows.yml @@ -39,8 +39,8 @@ builds: main: ./cmd/dmsg-server/ ldflags: -s -w -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - - id: dmsgget - binary: dmsgget + - id: dmsgcurl + binary: dmsgcurl goos: - windows goarch: @@ -48,7 +48,7 @@ builds: - 386 env: - CGO_ENABLED=0 - main: ./cmd/dmsgget/ + main: ./cmd/dmsgcurl/ ldflags: -s -w -X github.com/skycoin/skywire-utilities/pkg/buildinfo.version=v{{.Version}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.commit={{.ShortCommit}} -X github.com/skycoin/skywire-utilities/pkg/buildinfo.date={{.Date}} - id: dmsgpty-ui @@ -95,7 +95,7 @@ archives: builds: - dmsg-discovery - dmsg-server - - dmsgget + - dmsgcurl - dmsgpty-cli - dmsgpty-ui - dmsgpty-host diff --git a/Makefile b/Makefile index 5febef58b..ee3ffd1bf 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,7 @@ dep: ## Sorts dependencies ${OPTS} go mod vendor -v ${OPTS} go mod tidy -v -install: ## Install `dmsg-discovery`, `dmsg-server`, `dmsgget`,`dmsgpty-cli`, `dmsgpty-host`, `dmsgpty-ui` +install: ## Install `dmsg-discovery`, `dmsg-server`, `dmsgcurl`,`dmsgpty-cli`, `dmsgpty-host`, `dmsgpty-ui` ${OPTS} go install ${BUILD_OPTS} ./cmd/* build: ## Build binaries into ./bin diff --git a/README.md b/README.md index 3c892831f..8f862e246 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ The connection between a `dmsg.Client` and `dmsg.Server` is called a `dmsg.Sessi ## Dmsg tools and libraries -- [`dmsgget`](./docs/dmsgget.md) - Simplified `wget` over `dmsg`. +- [`dmsgcurl`](./docs/dmsgcurl.md) - Simplified `curl` over `dmsg`. - [`dmsgpty`](./docs/dmsgpty.md) - Simplified `SSH` over `dmsg`. ## Additional resources - [`dmsg` examples.](./examples) diff --git a/cmd/dmsg/dmsg.go b/cmd/dmsg/dmsg.go index 94199683c..a43872de7 100644 --- a/cmd/dmsg/dmsg.go +++ b/cmd/dmsg/dmsg.go @@ -9,9 +9,8 @@ import ( dmsgdisc "github.com/skycoin/dmsg/cmd/dmsg-discovery/commands" dmsgserver "github.com/skycoin/dmsg/cmd/dmsg-server/commands" - dmsgget "github.com/skycoin/dmsg/cmd/dmsgget/commands" + dmsgcurl "github.com/skycoin/dmsg/cmd/dmsgcurl/commands" dmsghttp "github.com/skycoin/dmsg/cmd/dmsghttp/commands" - dmsgpost "github.com/skycoin/dmsg/cmd/dmsgpost/commands" dmsgptycli "github.com/skycoin/dmsg/cmd/dmsgpty-cli/commands" dmsgptyhost "github.com/skycoin/dmsg/cmd/dmsgpty-host/commands" dmsgptyui "github.com/skycoin/dmsg/cmd/dmsgpty-ui/commands" @@ -27,9 +26,8 @@ func init() { dmsgptyCmd, dmsgdisc.RootCmd, dmsgserver.RootCmd, - dmsgget.RootCmd, dmsghttp.RootCmd, - dmsgpost.RootCmd, + dmsgcurl.RootCmd, ) var helpflag bool RootCmd.SetUsageTemplate(help) diff --git a/cmd/dmsgget/commands/dmsgget.go b/cmd/dmsgcurl/commands/dmsgcurl.go similarity index 63% rename from cmd/dmsgget/commands/dmsgget.go rename to cmd/dmsgcurl/commands/dmsgcurl.go index cbff55a24..56780ab92 100644 --- a/cmd/dmsgget/commands/dmsgget.go +++ b/cmd/dmsgcurl/commands/dmsgcurl.go @@ -1,4 +1,4 @@ -// Package commands cmd/dmsgget/commands/dmsgget.go +// Package commands cmd/dmsgcurl/commands/dmsgcurl.go package commands import ( @@ -11,6 +11,7 @@ import ( "net/url" "os" "path/filepath" + "strings" "sync/atomic" "time" @@ -24,51 +25,55 @@ import ( "github.com/spf13/cobra" "github.com/skycoin/dmsg/pkg/disc" - dmsg "github.com/skycoin/dmsg/pkg/dmsg" + "github.com/skycoin/dmsg/pkg/dmsg" "github.com/skycoin/dmsg/pkg/dmsghttp" ) var ( - dmsgDisc string - dmsgSessions int - dmsggetTries int - dmsggetWait int - dmsggetOutput string - sk cipher.SecKey - dmsggetLog *logging.Logger - dmsggetAgent string - stdout bool - logLvl string + dmsgDisc string + dmsgSessions int + dmsgcurlData string + // dmsgcurlHeader string + sk cipher.SecKey + dmsgcurlLog *logging.Logger + dmsgcurlAgent string + logLvl string + dmsgcurlTries int + dmsgcurlWait int + dmsgcurlOutput string + stdout bool ) func init() { - RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "d", "", "dmsg discovery url default:\n"+skyenv.DmsgDiscAddr) + RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "c", "", "dmsg discovery url default:\n"+skyenv.DmsgDiscAddr) RootCmd.Flags().IntVarP(&dmsgSessions, "sess", "e", 1, "number of dmsg servers to connect to") RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m") - RootCmd.Flags().StringVarP(&dmsggetOutput, "out", "o", ".", "output filepath") + RootCmd.Flags().StringVarP(&dmsgcurlData, "data", "d", "", "dmsghttp POST data") + // RootCmd.Flags().StringVarP(&dmsgcurlHeader, "header", "H", "", "Pass custom header(s) to server") + RootCmd.Flags().StringVarP(&dmsgcurlOutput, "out", "o", ".", "output filepath") RootCmd.Flags().BoolVarP(&stdout, "stdout", "n", false, "output to STDOUT") - RootCmd.Flags().IntVarP(&dmsggetTries, "try", "t", 1, "download attempts (0 unlimits)") - RootCmd.Flags().IntVarP(&dmsggetWait, "wait", "w", 0, "time to wait between fetches") - RootCmd.Flags().StringVarP(&dmsggetAgent, "agent", "a", "dmsgget/"+buildinfo.Version(), "identify as `AGENT`") - if os.Getenv("DMSGGET_SK") != "" { - sk.Set(os.Getenv("DMSGGET_SK")) //nolint + RootCmd.Flags().IntVarP(&dmsgcurlTries, "try", "t", 1, "download attempts (0 unlimits)") + RootCmd.Flags().IntVarP(&dmsgcurlWait, "wait", "w", 0, "time to wait between fetches") + RootCmd.Flags().StringVarP(&dmsgcurlAgent, "agent", "a", "dmsgcurl/"+buildinfo.Version(), "identify as `AGENT`") + if os.Getenv("DMSGCURL_SK") != "" { + sk.Set(os.Getenv("DMSGCURL_SK")) //nolint } RootCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r") var helpflag bool RootCmd.SetUsageTemplate(help) - RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgget") + RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgcurl") RootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) RootCmd.PersistentFlags().MarkHidden("help") //nolint } -// RootCmd contains the root command +// RootCmd containsa the root dmsgcurl command var RootCmd = &cobra.Command{ - Use: "get", - Short: "dmsg wget implementation - wget over dmsg", + Short: "dmsgcurl", + Use: "dmsgcurl [OPTIONS] ... [URL]", Long: ` - ┌┬┐┌┬┐┌─┐┌─┐┌─┐┌─┐┌┬┐ - │││││└─┐│ ┬│ ┬├┤ │ - ─┴┘┴ ┴└─┘└─┘└─┘└─┘ ┴ `, + ┌┬┐┌┬┐┌─┐┌─┐┌─┐┬ ┬┬─┐┬ + │││││└─┐│ ┬│ │ │├┬┘│ + ─┴┘┴ ┴└─┘└─┘└─┘└─┘┴└─┴─┘`, SilenceErrors: true, SilenceUsage: true, DisableSuggestions: true, @@ -80,24 +85,16 @@ var RootCmd = &cobra.Command{ } }, RunE: func(cmd *cobra.Command, args []string) error { - if dmsggetLog == nil { - dmsggetLog = logging.MustGetLogger("dmsgget") + if dmsgcurlLog == nil { + dmsgcurlLog = logging.MustGetLogger("dmsgcurl") } if logLvl != "" { if lvl, err := logging.LevelFromString(logLvl); err == nil { logging.SetLevel(lvl) } } - //if the log level was not explicitly set but stdout was specified ; suppress all logging except panic - if logLvl == "" { - //suppress logging on stdout - if stdout { - if lvl, err := logging.LevelFromString("panic"); err == nil { - logging.SetLevel(lvl) - } - } - } - ctx, cancel := cmdutil.SignalContext(context.Background(), dmsggetLog) + + ctx, cancel := cmdutil.SignalContext(context.Background(), dmsgcurlLog) defer cancel() pk, err := sk.PubKey() @@ -107,67 +104,100 @@ var RootCmd = &cobra.Command{ u, err := parseURL(args) if err != nil { - return fmt.Errorf("failed to parse provided URL: %w", err) - } - - file, err := parseOutputFile(dmsggetOutput, u.URL.Path) - if err != nil { - return fmt.Errorf("failed to prepare output file: %w", err) + dmsgcurlLog.WithError(err).Fatal("failed to parse provided URL") } - defer func() { - if fErr := file.Close(); fErr != nil { - dmsggetLog.WithError(fErr).Warn("Failed to close output file.") - } + if dmsgcurlData != "" { + dmsgC, closeDmsg, err := startDmsg(ctx, pk, sk) if err != nil { - if rErr := os.RemoveAll(file.Name()); rErr != nil { - dmsggetLog.WithError(rErr).Warn("Failed to remove output file.") - } + dmsgcurlLog.WithError(err).Fatal("failed to start dmsg") } - }() + defer closeDmsg() - dmsgC, closeDmsg, err := startDmsg(ctx, pk, sk) - if err != nil { - return fmt.Errorf("failed to start dmsg: %w", err) - } - defer closeDmsg() + httpC := http.Client{Transport: dmsghttp.MakeHTTPTransport(ctx, dmsgC)} - httpC := http.Client{Transport: dmsghttp.MakeHTTPTransport(ctx, dmsgC)} + req, err := http.NewRequest(http.MethodPost, u.URL.String(), strings.NewReader(dmsgcurlData)) + if err != nil { + dmsgcurlLog.WithError(err).Fatal("Failed to formulate HTTP request.") + } + req.Header.Set("Content-Type", "text/plain") - for i := 0; i < dmsggetTries; i++ { - if !stdout { - dmsggetLog.Debugf("Download attempt %d/%d ...", i, dmsggetTries) + resp, err := httpC.Do(req) + if err != nil { + dmsgcurlLog.WithError(err).Fatal("Failed to execute HTTP request.") } - if _, err := file.Seek(0, 0); err != nil { - return fmt.Errorf("failed to reset file: %w", err) + defer func() { + if err := resp.Body.Close(); err != nil { + dmsgcurlLog.WithError(err).Fatal("Failed to close response body") + } + }() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + dmsgcurlLog.WithError(err).Fatal("Failed to read respose body.") } - if stdout { + fmt.Println(string(respBody)) + } else { + + file, err := parseOutputFile(dmsgcurlOutput, u.URL.Path) + if err != nil { + return fmt.Errorf("failed to prepare output file: %w", err) + } + defer func() { if fErr := file.Close(); fErr != nil { - dmsggetLog.WithError(fErr).Warn("Failed to close output file.") + dmsgcurlLog.WithError(fErr).Warn("Failed to close output file.") } if err != nil { if rErr := os.RemoveAll(file.Name()); rErr != nil { - dmsggetLog.WithError(rErr).Warn("Failed to remove output file.") + dmsgcurlLog.WithError(rErr).Warn("Failed to remove output file.") } } - file = os.Stdout + }() + + dmsgC, closeDmsg, err := startDmsg(ctx, pk, sk) + if err != nil { + return fmt.Errorf("failed to start dmsg: %w", err) } - if err := Download(ctx, dmsggetLog, &httpC, file, u.URL.String(), 0); err != nil { - dmsggetLog.WithError(err).Error() - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(time.Duration(dmsggetWait) * time.Second): - continue + defer closeDmsg() + + httpC := http.Client{Transport: dmsghttp.MakeHTTPTransport(ctx, dmsgC)} + + for i := 0; i < dmsgcurlTries; i++ { + if !stdout { + dmsgcurlLog.Debugf("Download attempt %d/%d ...", i, dmsgcurlTries) } - } - // download successful. - return nil - } + if _, err := file.Seek(0, 0); err != nil { + return fmt.Errorf("failed to reset file: %w", err) + } + if stdout { + if fErr := file.Close(); fErr != nil { + dmsgcurlLog.WithError(fErr).Warn("Failed to close output file.") + } + if err != nil { + if rErr := os.RemoveAll(file.Name()); rErr != nil { + dmsgcurlLog.WithError(rErr).Warn("Failed to remove output file.") + } + } + file = os.Stdout + } + if err := Download(ctx, dmsgcurlLog, &httpC, file, u.URL.String(), 0); err != nil { + dmsgcurlLog.WithError(err).Error() + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Duration(dmsgcurlWait) * time.Second): + continue + } + } - return errors.New("all download attempts failed") + // download successful. + return nil + } + return errors.New("all download attempts failed") + + } + return nil }, } @@ -238,18 +268,16 @@ func parseOutputFile(name string, urlPath string) (*os.File, error) { } func startDmsg(ctx context.Context, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) { - dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, dmsggetLog), &dmsg.Config{MinSessions: dmsgSessions}) + dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, dmsgcurlLog), &dmsg.Config{MinSessions: dmsgSessions}) go dmsgC.Serve(context.Background()) stop = func() { err := dmsgC.Close() - dmsggetLog.WithError(err).Debug("Disconnected from dmsg network.") + dmsgcurlLog.WithError(err).Debug("Disconnected from dmsg network.") fmt.Printf("\n") } - if !stdout { - dmsggetLog.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc). - Debug("Connecting to dmsg network...") - } + dmsgcurlLog.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc). + Debug("Connecting to dmsg network...") select { case <-ctx.Done(): @@ -257,7 +285,7 @@ func startDmsg(ctx context.Context, pk cipher.PubKey, sk cipher.SecKey) (dmsgC * return nil, nil, ctx.Err() case <-dmsgC.Ready(): - dmsggetLog.Debug("Dmsg network ready.") + dmsgcurlLog.Debug("Dmsg network ready.") return dmsgC, stop, nil } } diff --git a/cmd/dmsgcurl/dmsgcurl.go b/cmd/dmsgcurl/dmsgcurl.go new file mode 100644 index 000000000..8b308a694 --- /dev/null +++ b/cmd/dmsgcurl/dmsgcurl.go @@ -0,0 +1,8 @@ +// package main cmd/dmsgcurl/dmsgcurl.go +package main + +import "github.com/skycoin/dmsg/cmd/dmsgcurl/commands" + +func main() { + commands.Execute() +} diff --git a/cmd/dmsgget/dmsgget.go b/cmd/dmsgget/dmsgget.go deleted file mode 100644 index 8b5840739..000000000 --- a/cmd/dmsgget/dmsgget.go +++ /dev/null @@ -1,8 +0,0 @@ -// package main cmd/dmsg-discovery/dmsg-discovery.go -package main - -import "github.com/skycoin/dmsg/cmd/dmsgget/commands" - -func main() { - commands.Execute() -} diff --git a/cmd/dmsgpost/commands/dmsgpost.go b/cmd/dmsgpost/commands/dmsgpost.go deleted file mode 100644 index c90d5a936..000000000 --- a/cmd/dmsgpost/commands/dmsgpost.go +++ /dev/null @@ -1,223 +0,0 @@ -// Package commands cmd/dmsgpost/commands/dmsgpost.go -package commands - -import ( - "context" - "errors" - "fmt" - "io" - "log" - "net/http" - "net/url" - "os" - "strings" - - cc "github.com/ivanpirog/coloredcobra" - "github.com/skycoin/skywire-utilities/pkg/buildinfo" - "github.com/skycoin/skywire-utilities/pkg/cipher" - "github.com/skycoin/skywire-utilities/pkg/cmdutil" - "github.com/skycoin/skywire-utilities/pkg/logging" - "github.com/skycoin/skywire-utilities/pkg/skyenv" - "github.com/spf13/cobra" - - "github.com/skycoin/dmsg/pkg/disc" - "github.com/skycoin/dmsg/pkg/dmsg" - "github.com/skycoin/dmsg/pkg/dmsghttp" -) - -var ( - dmsgDisc string - dmsgSessions int - dmsgpostData string - // dmsgpostHeader string - sk cipher.SecKey - dmsgpostLog *logging.Logger - dmsgpostAgent string - logLvl string -) - -func init() { - RootCmd.Flags().StringVarP(&dmsgDisc, "dmsg-disc", "c", "", "dmsg discovery url default:\n"+skyenv.DmsgDiscAddr) - RootCmd.Flags().IntVarP(&dmsgSessions, "sess", "e", 1, "number of dmsg servers to connect to") - RootCmd.Flags().StringVarP(&logLvl, "loglvl", "l", "", "[ debug | warn | error | fatal | panic | trace | info ]\033[0m") - RootCmd.Flags().StringVarP(&dmsgpostData, "data", "d", "", "dmsghttp POST data") - // RootCmd.Flags().StringVarP(&dmsgpostHeader, "header", "H", "", "Pass custom header(s) to server") - RootCmd.Flags().StringVarP(&dmsgpostAgent, "agent", "a", "dmsgpost/"+buildinfo.Version(), "identify as `AGENT`") - if os.Getenv("dmsgpost_SK") != "" { - sk.Set(os.Getenv("dmsgpost_SK")) //nolint - } - RootCmd.Flags().VarP(&sk, "sk", "s", "a random key is generated if unspecified\n\r") - var helpflag bool - RootCmd.SetUsageTemplate(help) - RootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for dmsgpost") - RootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) - RootCmd.PersistentFlags().MarkHidden("help") //nolint -} - -// RootCmd containsa the root dmsgpost command -var RootCmd = &cobra.Command{ - Use: "post", - Short: "dmsgpost", - Long: ` - ┌┬┐┌┬┐┌─┐┌─┐┌─┐┌─┐┌─┐┌┬┐ - │││││└─┐│ ┬├─┘│ │└─┐ │ - ─┴┘┴ ┴└─┘└─┘┴ └─┘└─┘ ┴ `, - SilenceErrors: true, - SilenceUsage: true, - DisableSuggestions: true, - DisableFlagsInUseLine: true, - Version: buildinfo.Version(), - PreRun: func(cmd *cobra.Command, args []string) { - if dmsgDisc == "" { - dmsgDisc = skyenv.DmsgDiscAddr - } - }, - Run: func(cmd *cobra.Command, args []string) { - if dmsgpostLog == nil { - dmsgpostLog = logging.MustGetLogger("dmsgpost") - } - if logLvl != "" { - if lvl, err := logging.LevelFromString(logLvl); err == nil { - logging.SetLevel(lvl) - } - } - - ctx, cancel := cmdutil.SignalContext(context.Background(), dmsgpostLog) - defer cancel() - - pk, err := sk.PubKey() - if err != nil { - pk, sk = cipher.GenerateKeyPair() - } - - u, err := parseURL(args) - if err != nil { - dmsgpostLog.WithError(err).Fatal("failed to parse provided URL") - } - - dmsgC, closeDmsg, err := startDmsg(ctx, pk, sk) - if err != nil { - dmsgpostLog.WithError(err).Fatal("failed to start dmsg") - } - defer closeDmsg() - - httpC := http.Client{Transport: dmsghttp.MakeHTTPTransport(ctx, dmsgC)} - - req, err := http.NewRequest(http.MethodPost, u.URL.String(), strings.NewReader(dmsgpostData)) - if err != nil { - dmsgpostLog.WithError(err).Fatal("Failed to formulate HTTP request.") - } - req.Header.Set("Content-Type", "text/plain") - - resp, err := httpC.Do(req) - if err != nil { - dmsgpostLog.WithError(err).Fatal("Failed to execute HTTP request.") - } - - defer func() { - if err := resp.Body.Close(); err != nil { - dmsgpostLog.WithError(err).Fatal("Failed to close response body") - } - }() - respBody, err := io.ReadAll(resp.Body) - if err != nil { - dmsgpostLog.WithError(err).Fatal("Failed to read respose body.") - } - fmt.Println(string(respBody)) - }, -} - -// URL represents a dmsg http URL. -type URL struct { - dmsg.Addr - url.URL -} - -// Fill fills the internal fields from an URL string. -func (du *URL) fill(str string) error { - u, err := url.Parse(str) - if err != nil { - return err - } - - if u.Scheme == "" { - return errors.New("URL is missing a scheme") - } - - if u.Host == "" { - return errors.New("URL is missing a host") - } - - du.URL = *u - return du.Addr.Set(u.Host) -} - -func parseURL(args []string) (*URL, error) { - if len(args) == 0 { - return nil, errors.New("no URL(s) provided") - } - - if len(args) > 1 { - return nil, errors.New("multiple URLs is not yet supported") - } - - var out URL - if err := out.fill(args[0]); err != nil { - return nil, fmt.Errorf("provided URL is invalid: %w", err) - } - - return &out, nil -} - -func startDmsg(ctx context.Context, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) { - dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dmsgDisc, &http.Client{}, dmsgpostLog), &dmsg.Config{MinSessions: dmsgSessions}) - go dmsgC.Serve(context.Background()) - - stop = func() { - err := dmsgC.Close() - dmsgpostLog.WithError(err).Debug("Disconnected from dmsg network.") - fmt.Printf("\n") - } - dmsgpostLog.WithField("public_key", pk.String()).WithField("dmsg_disc", dmsgDisc). - Debug("Connecting to dmsg network...") - - select { - case <-ctx.Done(): - stop() - return nil, nil, ctx.Err() - - case <-dmsgC.Ready(): - dmsgpostLog.Debug("Dmsg network ready.") - return dmsgC, stop, nil - } -} - -// Execute executes root CLI command. -func Execute() { - cc.Init(&cc.Config{ - RootCmd: RootCmd, - Headings: cc.HiBlue + cc.Bold, //+ cc.Underline, - Commands: cc.HiBlue + cc.Bold, - CmdShortDescr: cc.HiBlue, - Example: cc.HiBlue + cc.Italic, - ExecName: cc.HiBlue + cc.Bold, - Flags: cc.HiBlue + cc.Bold, - //FlagsDataType: cc.HiBlue, - FlagsDescr: cc.HiBlue, - NoExtraNewlines: true, - NoBottomNewline: true, - }) - if err := RootCmd.Execute(); err != nil { - log.Fatal("Failed to execute command: ", err) - } -} - -const help = "Usage:\r\n" + - " {{.UseLine}}{{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" + - "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" + - "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " + - "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" + - "Flags:\r\n" + - "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" + - "Global Flags:\r\n" + - "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n" diff --git a/cmd/dmsgpost/dmsgpost.go b/cmd/dmsgpost/dmsgpost.go deleted file mode 100644 index 3e073e563..000000000 --- a/cmd/dmsgpost/dmsgpost.go +++ /dev/null @@ -1,8 +0,0 @@ -// package main cmd/dmsgpost/dmsgpost.go -package main - -import "github.com/skycoin/dmsg/cmd/dmsgpost/commands" - -func main() { - commands.Execute() -} diff --git a/docs/dmsgcurl.md b/docs/dmsgcurl.md new file mode 100644 index 000000000..5176b4cd6 --- /dev/null +++ b/docs/dmsgcurl.md @@ -0,0 +1,68 @@ +# Dmsgcurl + +`dmsgcurl` is a utility exec which can download/upload from HTTP servers hosted over the `dmsg` network (similar to a simplified `curl` over `dmsg`). + +``` +$ dmsgcurl --help + ┌┬┐┌┬┐┌─┐┌─┐┌─┐┬ ┬┬─┐┬ + │││││└─┐│ ┬│ │ │├┬┘│ + ─┴┘┴ ┴└─┘└─┘└─┘└─┘┴└─┴─┘ + + Usage: + dmsgcurl [OPTIONS] ... [URL] + + Flags: + -a, --agent AGENT identify as AGENT (default "dmsgcurl/v1.2.0-184-gdb24d156") + -d, --data string dmsghttp POST data + -c, --dmsg-disc string dmsg discovery url default: + http://dmsgd.skywire.skycoin.com + -l, --loglvl string [ debug | warn | error | fatal | panic | trace | info ] + -o, --out string output filepath (default ".") + -e, --sess int number of dmsg servers to connect to (default 1) + -s, --sk cipher.SecKey a random key is generated if unspecified + (default 0000000000000000000000000000000000000000000000000000000000000000) + -n, --stdout output to STDOUT + -t, --try int download attempts (0 unlimits) (default 1) + -v, --version version for dmsgcurl + -w, --wait int time to wait between fetches +``` + +### Example usage + +In this example, we will use the `dmsg` network where the `dmsg.Discovery` address is `http://dmsgd.skywire.skycoin.com`. However, any `dmsg.Discovery` would work. + +First, lets create a folder where we will host files to serve over `dmsg` and create a `hello.txt` file within. + +```shell script +// Create serving folder. +$ mkdir /tmp/dmsghttp -p + +// Create file. +$ echo 'Hello World!' > /tmp/dmsghttp/hello.txt +``` + +Next, let's serve this over `http` via `dmsg` as transport. We have an example exec for this located within `/example/dmsgget/dmsg-example-http-server`. + +```shell script +# Generate public/private key pair +$ go run ./examples/dmsgget/gen-keys/gen-keys.go +# PK: 038dde2d050803db59e2ad19e5a6db0f58f8419709fc65041c48b0cb209bb7a851 +# SK: e5740e093bd472c2730b0a58944a5dee220d415de62acf45d1c559f56eea2b2d + +# Run dmsg http server. +# (replace 'e5740e093bd472c2730b0a58944a5dee220d415de62acf45d1c559f56eea2b2d' with the SK returned from above command) +$ go run ./examples/dmsgget/dmsg-example-http-server/dmsg-example-http-server.go --dir /tmp/dmsghttp --sk e5740e093bd472c2730b0a58944a5dee220d415de62acf45d1c559f56eea2b2d +``` + +Now we can use `dmsgcurl` to download the hosted file. Open a new terminal and run the following. + +```shell script +# Replace '038dde2d050803db59e2ad19e5a6db0f58f8419709fc65041c48b0cb209bb7a851' with the generated PK. +$ dmsgcurl dmsg://038dde2d050803db59e2ad19e5a6db0f58f8419709fc65041c48b0cb209bb7a851:80/hello.txt + +# Check downloaded file. +$ cat hello.txt +# Hello World! +``` + +Note: If you set `-d` or `--data` flag, then curl work as post method (upload), and if not then work as get method (download). diff --git a/docs/dmsgget.md b/docs/dmsgget.md deleted file mode 100644 index dc643834f..000000000 --- a/docs/dmsgget.md +++ /dev/null @@ -1,65 +0,0 @@ -# Dmsgget - -`dmsgget` is a utility exec which can download from HTTP servers hosted over the `dmsg` network (similar to a simplified `wget` over `dmsg`). - -``` -$ dmsgget --help - - Skycoin dmsgget v0.1.0, wget over dmsg. - Usage: dmsgget [OPTION]... [URL] - - -O FILE - write documents to FILE (default ".") - -U AGENT - identify as AGENT (default "dmsgget/v0.1.0") - -dmsg-disc URL - dmsg discovery URL (default "http://dmsgd.skywire.skycoin.com") - -dmsg-sessions NUMBER - connect to NUMBER of dmsg servers (default 1) - -h - -help - print this help - -t NUMBER - set number of retries to NUMBER (0 unlimits) (default 1) - -w SECONDS - wait SECONDS between retrievals -``` - -### Example usage - -In this example, we will use the `dmsg` network where the `dmsg.Discovery` address is `http://dmsgd.skywire.skycoin.com`. However, any `dmsg.Discovery` would work. - -First, lets create a folder where we will host files to serve over `dmsg` and create a `hello.txt` file within. - -```shell script -// Create serving folder. -$ mkdir /tmp/dmsghttp -p - -// Create file. -$ echo 'Hello World!' > /tmp/dmsghttp/hello.txt -``` - -Next, let's serve this over `http` via `dmsg` as transport. We have an example exec for this located within `/example/dmsgget/dmsg-example-http-server`. - -```shell script -# Generate public/private key pair -$ go run ./examples/dmsgget/gen-keys/gen-keys.go -# PK: 038dde2d050803db59e2ad19e5a6db0f58f8419709fc65041c48b0cb209bb7a851 -# SK: e5740e093bd472c2730b0a58944a5dee220d415de62acf45d1c559f56eea2b2d - -# Run dmsg http server. -# (replace 'e5740e093bd472c2730b0a58944a5dee220d415de62acf45d1c559f56eea2b2d' with the SK returned from above command) -$ go run ./examples/dmsgget/dmsg-example-http-server/dmsg-example-http-server.go --dir /tmp/dmsghttp --sk e5740e093bd472c2730b0a58944a5dee220d415de62acf45d1c559f56eea2b2d -``` - -Now we can use `dsmgget` to download the hosted file. Open a new terminal and run the following. - -```shell script -# Replace '038dde2d050803db59e2ad19e5a6db0f58f8419709fc65041c48b0cb209bb7a851' with the generated PK. -$ dmsgget dmsg://038dde2d050803db59e2ad19e5a6db0f58f8419709fc65041c48b0cb209bb7a851:80/hello.txt - -# Check downloaded file. -$ cat hello.txt -# Hello World! -``` - diff --git a/pkg/dmsgget/dmsgget.go b/pkg/dmsgcurl/dmsgcurl.go similarity index 90% rename from pkg/dmsgget/dmsgget.go rename to pkg/dmsgcurl/dmsgcurl.go index 0193d6dbc..20bf02f44 100644 --- a/pkg/dmsgget/dmsgget.go +++ b/pkg/dmsgcurl/dmsgcurl.go @@ -1,5 +1,5 @@ -// Package dmsgget pkg/dmsgget/dmsgget.go -package dmsgget +// Package dmsgcurl pkg/dmsgcurl/dmsgcurl.go +package dmsgcurl import ( "context" @@ -24,8 +24,8 @@ import ( var json = jsoniter.ConfigFastest -// DmsgGet contains the logic for dmsgget (wget over dmsg). -type DmsgGet struct { +// DmsgCurl contains the logic for dmsgcurl (curl over dmsg). +type DmsgCurl struct { startF startupFlags dmsgF dmsgFlags dlF downloadFlags @@ -33,9 +33,9 @@ type DmsgGet struct { fs *flag.FlagSet } -// New creates a new DmsgGet instance. -func New(fs *flag.FlagSet) *DmsgGet { - dg := &DmsgGet{fs: fs} +// New creates a new DmsgCurl instance. +func New(fs *flag.FlagSet) *DmsgCurl { + dg := &DmsgCurl{fs: fs} for _, fg := range dg.flagGroups() { fg.Init(fs) @@ -53,7 +53,7 @@ func New(fs *flag.FlagSet) *DmsgGet { } // String implements io.Stringer -func (dg *DmsgGet) String() string { +func (dg *DmsgCurl) String() string { m := make(map[string]interface{}) for _, fg := range dg.flagGroups() { m[fg.Name()] = fg @@ -65,14 +65,14 @@ func (dg *DmsgGet) String() string { return string(j) } -func (dg *DmsgGet) flagGroups() []FlagGroup { +func (dg *DmsgCurl) flagGroups() []FlagGroup { return []FlagGroup{&dg.startF, &dg.dmsgF, &dg.dlF, &dg.httpF} } // Run runs the download logic. -func (dg *DmsgGet) Run(ctx context.Context, log *logging.Logger, skStr string, args []string) (err error) { +func (dg *DmsgCurl) Run(ctx context.Context, log *logging.Logger, skStr string, args []string) (err error) { if log == nil { - log = logging.MustGetLogger("dmsgget") + log = logging.MustGetLogger("dmsgcurl") } if dg.startF.Help { @@ -193,7 +193,7 @@ func parseOutputFile(name string, urlPath string) (*os.File, error) { } // StartDmsg create dsmg client instance -func (dg *DmsgGet) StartDmsg(ctx context.Context, log *logging.Logger, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) { +func (dg *DmsgCurl) StartDmsg(ctx context.Context, log *logging.Logger, pk cipher.PubKey, sk cipher.SecKey) (dmsgC *dmsg.Client, stop func(), err error) { dmsgC = dmsg.NewClient(pk, sk, disc.NewHTTP(dg.dmsgF.Disc, &http.Client{}, log), &dmsg.Config{MinSessions: dg.dmsgF.Sessions}) go dmsgC.Serve(context.Background()) diff --git a/pkg/dmsgget/dmsgget_test.go b/pkg/dmsgcurl/dmsgcurl_test.go similarity index 97% rename from pkg/dmsgget/dmsgget_test.go rename to pkg/dmsgcurl/dmsgcurl_test.go index 1f165ad59..aa551c48a 100644 --- a/pkg/dmsgget/dmsgget_test.go +++ b/pkg/dmsgcurl/dmsgcurl_test.go @@ -1,5 +1,5 @@ -// Package dmsgget pkg/dmsgget/dmsgget_test.go -package dmsgget +// Package dmsgcurl pkg/dmsgcurl/dmsgcurl_test.go +package dmsgcurl import ( "context" @@ -84,7 +84,7 @@ func TestDownload(t *testing.T) { } func makeFile(t *testing.T, data []byte) *os.File { - f, err := os.CreateTemp(os.TempDir(), "dmsgget_test_file_*") + f, err := os.CreateTemp(os.TempDir(), "dmsgcurl_test_file_*") require.NoError(t, err) t.Cleanup(func() { diff --git a/pkg/dmsgget/flags.go b/pkg/dmsgcurl/flags.go similarity index 94% rename from pkg/dmsgget/flags.go rename to pkg/dmsgcurl/flags.go index 22c5fae54..e763a0599 100644 --- a/pkg/dmsgget/flags.go +++ b/pkg/dmsgcurl/flags.go @@ -1,5 +1,5 @@ -// Package dmsgget pkg/dmsgget/flags.go -package dmsgget +// Package dmsgcurl pkg/dmsgcurl/flags.go +package dmsgcurl import ( "flag" @@ -8,7 +8,7 @@ import ( ) // ExecName contains the execution name. -const ExecName = "dmsgget" +const ExecName = "dmsgcurl" // Version contains the version string. var Version = buildinfo.Version() diff --git a/pkg/dmsgget/progress_writer.go b/pkg/dmsgcurl/progress_writer.go similarity index 89% rename from pkg/dmsgget/progress_writer.go rename to pkg/dmsgcurl/progress_writer.go index d9415554e..e104337a5 100644 --- a/pkg/dmsgget/progress_writer.go +++ b/pkg/dmsgcurl/progress_writer.go @@ -1,5 +1,5 @@ -// Package dmsgget pkg/dmsgget/progress_writer.go -package dmsgget +// Package dmsgcurl pkg/dmsgcurl/progress_writer.go +package dmsgcurl import ( "fmt" diff --git a/pkg/dmsgget/url.go b/pkg/dmsgcurl/url.go similarity index 92% rename from pkg/dmsgget/url.go rename to pkg/dmsgcurl/url.go index 6a3962c84..84895e9e8 100644 --- a/pkg/dmsgget/url.go +++ b/pkg/dmsgcurl/url.go @@ -1,5 +1,5 @@ -// Package dmsgget pkg/dmsgget/url.go -package dmsgget +// Package dmsgcurl pkg/dmsgcurl/url.go +package dmsgcurl import ( "errors"