diff --git a/README.md b/README.md
index 5b3b646..ecd9b06 100644
--- a/README.md
+++ b/README.md
@@ -1,43 +1,79 @@
-# convey
+
+
+
+
+
+
+
+
-A command-line tool that makes sharing pipes between machines easy.
+
+```bash
+echo "Hello world" | convey
+21f50fba373e11e9990a72000872a940
+```
+```bash
+convey 21f50fba373e11e9990a72000872a940
+Hello world
+```
+
+# Features
+
+- Pipe between hosts with an idomatic interface using the standard `|` symbol.
+- Easily pipe files between hosts.
+- Does not require any open ports between your clients.
+- Supports colors through [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors).
+- Supports Linux, macOS and Windows.
+- No dependencies to install.
+- Powered by [NATS](https://nats.io/), a CNCF project.
+
+# Getting Started
## Install
-**Install on Linux:**
+### Linux
```bash
wget -qO convey https://get.convey.sh/linux
chmod +x ~/bin/convey
+~/bin/convey -h
```
-**Install on Mac OS:**
+### macOS
```bash
wget -qO ~/bin/convey https://get.convey.sh/macos
chmod +x ~/bin/convey
+~/bin/convey -h
```
-**Install on Windows:**
-> Download from https://get.convey.sh/windows
+### Windows
+```powershell
+Invoke-WebRequest https://get.convey.sh/windows -OutFile convey.exe
+convey -h
+```
-This will download the latest release for your platform.
-Builds are available for the `amd64` architecture.
-To directly from GitHub, visit https://github.com/derekbekoe/convey/releases/latest.
+## Demo Mode
-## Usage
+A demo mode is available using the `--demo` flag.
-In Terminal 1:
```bash
-echo "Hello world" | convey
-21f50fba373e11e9990a72000872a940
+echo "Hello world" | convey --demo
+
```
-
-In Terminal 2:
```bash
-convey 21f50fba373e11e9990a72000872a940
+convey --demo
Hello world
```
-## Configuration
+Demo mode uses the `demo.nats.io` server over a TLS connection with channels expiring after 30 minutes of creation or 10 minutes of inactivity.
+
+Use this mode for experimental and getting started purposes only.
+
+# Configuration
Set configuration with the `convey configure` command.
```bash
@@ -52,44 +88,48 @@ NatsURL: nats://localhost:4223
NatsClusterID: test-cluster
```
-Use the --config flag on the command line to change the config file if needed.
+Use the `--config` flag on the command line to change the config file used if needed.
-## Development
-```bash
-go get -u github.com/derekbekoe/convey
-cd $GOPATH/src/github.com/derekbekoe/convey
-go run main.go
-go build -o bin/convey
-```
+# NATS Streaming Server
-## Host your own NATS Streaming Server
+You can host your own [NATS Streaming Server](https://nats.io/documentation/streaming/nats-streaming-intro/) and configure `convey` to use that server.
-**Deploy to a local Docker container**
+#### Deploy to a local Docker container
```bash
docker run -p 4222:4222 nats-streaming:linux
convey configure --nats-url nats://localhost:4222 --nats-cluster test-cluster
```
-**Deploy to an Azure Container Instance**
+You will need to use the `--unsecure` flag as TLS will not be enabled through this local container.
-note: We only include this as an illustration to keep the command simple as traffic is not encrypted.
+# Development
+
+**Set up**
```bash
-az container create --image nats-streaming:linux --ports 4222 --ip-address Public -g RG -n nats1
-convey configure --nats-url nats://:4222 --nats-cluster test-cluster
+go get -u github.com/derekbekoe/convey
+cd $GOPATH/src/github.com/derekbekoe/convey
+go run main.go
```
-## Platform Builds
+**Multi-platform Builds**
```bash
go get github.com/mitchellh/gox
gox -ldflags "-X github.com/derekbekoe/convey/cmd.VersionGitCommit=$(git rev-list -1 HEAD) -X github.com/derekbekoe/convey/cmd.VersionGitTag=VERSION" -os="linux darwin" -arch="amd64" -output="bin/{{.Dir}}_{{.OS}}_{{.Arch}}"
```
See https://golang.org/doc/install/source#environment
-## FAQ
+# Examples
+
+Click to expand each gif.
-**How do I try it out?**
+
-Start the local container, download convey, specify the configuration then run.
+# License
-If you'd like to share between multiple devices, host the server in a location where your devices can access.
+Convey source code is available under the [MIT License](LICENSE).
diff --git a/cmd/configure.go b/cmd/configure.go
index b1bbcdc..ad4e9a3 100644
--- a/cmd/configure.go
+++ b/cmd/configure.go
@@ -9,9 +9,16 @@ import (
"github.com/spf13/viper"
)
+// The NATS URL passed in from command-line
var natsURL string
+
+// The NATS cluster ID passed in from command-line
var natsClusterID string
+
+// Whether short channel names should be used instead of the standard uuid format
var useShortName bool
+
+// Whether the current config file should be overwritten
var forceWrite bool
func init() {
diff --git a/cmd/root.go b/cmd/root.go
index f9c20d5..94a26ac 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -28,38 +28,41 @@ import (
"github.com/docker/docker/pkg/namesgenerator"
homedir "github.com/mitchellh/go-homedir"
+ nats "github.com/nats-io/go-nats"
stan "github.com/nats-io/go-nats-streaming"
uuid "github.com/satori/go.uuid"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
+const (
+ configKeyNatsURL = "NatsURL"
+ configKeyNatsClusterID = "NatsClusterID"
+ configKeyUseShortName = "UseShortName"
+ demoNatsURL = "tls://demo.nats.io:4443"
+ demoNatsClusterID = "convey-demo-cluster"
+)
+
+// Path to config file set by user
var cfgFile string
+
+// Whether to log verbose output
var verbose bool
-const configKeyNatsURL = "NatsURL"
-const configKeyNatsClusterID = "NatsClusterID"
-const configKeyUseShortName = "UseShortName"
+// Whether non-tls connection should be used for NATS connection
+var useUnsecure bool
-// ETX is End Of Text Sequence
-var ETX = []byte{3}
+// Whether the demo server and mode should be used
+var useDemoMode bool
+
+// etx is an identifier for End Of Text Sequence
+var etx = []byte{3}
func errorExit(msg string) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}
-// positionalArgsValidator valids the positional args
-func positionalArgsValidator(cmd *cobra.Command, args []string) error {
- if len(args) == 0 {
- return nil
- } else if len(args) == 1 {
- _, err := uuid.FromString(args[0])
- return err
- }
- return errors.New("Invalid positional arguments")
-}
-
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "convey",
@@ -72,22 +75,82 @@ var rootCmd = &cobra.Command{
// RootCommandFunc is a handler for the bare application
func RootCommandFunc(cmd *cobra.Command, args []string) {
if len(args) == 0 {
- PublishModeFunc()
+ publishModeFunc()
} else if len(args) == 1 {
- SubscribeModeFunc(args[0])
+ subscribeModeFunc(args[0])
} else {
errorExit("Too many args")
}
}
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
+ errorExit(err.Error())
+ }
+}
+
+func init() {
+ cobra.OnInitialize(initConfig)
+
+ // Here you will define your flags and configuration settings.
+ // Cobra supports persistent flags, which, if defined here,
+ // will be global for your application.
+ rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.convey.yaml)")
+ rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
+ rootCmd.PersistentFlags().BoolVar(&useUnsecure, "unsecure", false, "use unsecured connection (for development purposes only)")
+ rootCmd.PersistentFlags().BoolVar(&useDemoMode, "demo", false, "use demo mode")
+}
+
+// initConfig reads in config file and ENV variables if set.
+func initConfig() {
+ if cfgFile != "" {
+ // Use config file from the flag.
+ viper.SetConfigFile(cfgFile)
+ } else {
+ // Find home directory.
+ home, err := homedir.Dir()
+ if err != nil {
+ errorExit(err.Error())
+ }
+
+ // Search config in home directory with name ".convey" (without extension).
+ viper.AddConfigPath(home)
+ viper.SetConfigName(".convey")
+ }
+
+ viper.AutomaticEnv() // read in environment variables that match
+
+ if !verbose {
+ log.SetOutput(ioutil.Discard)
+ }
+
+ // If a config file is found, read it in.
+ if err := viper.ReadInConfig(); err == nil {
+ log.Println("Config file:", viper.ConfigFileUsed())
+ }
+}
+
+// positionalArgsValidator valids the positional args
+func positionalArgsValidator(cmd *cobra.Command, args []string) error {
+ if len(args) == 0 {
+ return nil
+ } else if len(args) == 1 {
+ _, err := uuid.FromString(args[0])
+ return err
+ }
+ return errors.New("Invalid positional arguments")
+}
+
func createChannelNameUUID() string {
- u1, err := uuid.NewV1()
+ u, err := uuid.NewV4()
if err != nil {
s := fmt.Sprintf("Failed to create channel name: %s\n", err)
errorExit(s)
}
// Remove dashes from UUID to make copy-paste easier in terminal
- return strings.Replace(u1.String(), "-", "", -1)
+ return strings.Replace(u.String(), "-", "", -1)
}
func createChannelNameShort() string {
@@ -95,30 +158,49 @@ func createChannelNameShort() string {
}
func getClientID(prefix string) string {
- u1, err := uuid.NewV1()
+ u, err := uuid.NewV4()
if err != nil {
s := fmt.Sprintf("Failed to create client ID: %s\n", err)
errorExit(s)
}
- return fmt.Sprintf("%s-%s", prefix, u1.String())
+ return fmt.Sprintf("%s-%s", prefix, u.String())
}
-func connectToStan(clientID string) stan.Conn {
+func connectToStan(clientID string) (stan.Conn, *nats.Conn) {
natsURL := viper.GetString(configKeyNatsURL)
natsClusterID := viper.GetString(configKeyNatsClusterID)
+ if useDemoMode {
+ log.Printf("Using demo mode")
+ natsURL = demoNatsURL
+ natsClusterID = demoNatsClusterID
+ }
+
if natsURL == "" || natsClusterID == "" {
- s := fmt.Sprintf("The configuration options '%s' and '%s' are not set. Use `convey configure` to set.",
+ s := fmt.Sprintf("The configuration options '%s' and '%s' are not set. Use `convey configure` to set. Use `--help` for usage.",
configKeyNatsURL,
configKeyNatsClusterID)
errorExit(s)
}
- sc, err := stan.Connect(
+ natsSecureOpt := nats.Secure()
+
+ if useUnsecure {
+ log.Printf("Using unsecure connection to server")
+ natsSecureOpt = nil
+ }
+
+ natsConn, err1 := nats.Connect(natsURL, natsSecureOpt)
+ if err1 != nil {
+ s := fmt.Sprintf("Failed to connect to NATS server due to error - %s", err1)
+ errorExit(s)
+ }
+
+ stanConn, err := stan.Connect(
natsClusterID,
clientID,
- stan.NatsURL(natsURL),
+ stan.NatsConn(natsConn),
stan.SetConnectionLostHandler(func(_ stan.Conn, err error) {
log.Printf("Lost connection due to error - %s", err)
}))
@@ -126,17 +208,24 @@ func connectToStan(clientID string) stan.Conn {
s := fmt.Sprintf("Failed to connect to streaming server due to error - %s", err)
errorExit(s)
}
- return sc
+
+ return stanConn, natsConn
}
-// PublishModeFunc handles publishing messages to message service
-func PublishModeFunc() {
+// publishModeFunc handles publishing messages to message service
+func publishModeFunc() {
clientID := getClientID("convey-pub")
- sc := connectToStan(clientID)
+ stanConn, natsConn := connectToStan(clientID)
useShortName := viper.GetBool(configKeyUseShortName)
channelName := ""
- if useShortName {
+
+ if useDemoMode && useShortName {
+ log.Printf("Short names not allowed in demo mode to prevent possible conflicts")
+ }
+
+ // Check if should use short names. Short names not allowed in demo mode to prevent possible conflicts.
+ if useShortName && !useDemoMode {
channelName = createChannelNameShort()
} else {
channelName = createChannelNameUUID()
@@ -160,28 +249,29 @@ func PublishModeFunc() {
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
for scanner.Scan() {
line := scanner.Text()
- sc.Publish(channelName, []byte(line))
+ stanConn.Publish(channelName, []byte(line))
}
donePublish <- true
}()
<-donePublish
- sc.Publish(channelName, ETX)
- sc.Close()
+ stanConn.Publish(channelName, etx)
+ stanConn.Close()
+ natsConn.Close()
}
-// SubscribeModeFunc handles reading messages from the message service
-func SubscribeModeFunc(channelName string) {
+// subscribeModeFunc handles reading messages from the message service
+func subscribeModeFunc(channelName string) {
clientID := getClientID("convey-sub")
- sc := connectToStan(clientID)
+ stanConn, natsConn := connectToStan(clientID)
log.Printf("Subscribing to channel %s\n", channelName)
doneSubscribe := make(chan bool)
- sub, subErr := sc.Subscribe(channelName, func(m *stan.Msg) {
- if reflect.DeepEqual(m.Data, ETX) {
+ sub, subErr := stanConn.Subscribe(channelName, func(m *stan.Msg) {
+ if reflect.DeepEqual(m.Data, etx) {
doneSubscribe <- true
} else {
fmt.Println(string(m.Data))
@@ -196,53 +286,6 @@ func SubscribeModeFunc(channelName string) {
<-doneSubscribe
sub.Unsubscribe()
- sc.Close()
-}
-
-// Execute adds all child commands to the root command and sets flags appropriately.
-// This is called by main.main(). It only needs to happen once to the rootCmd.
-func Execute() {
- if err := rootCmd.Execute(); err != nil {
- errorExit(err.Error())
- }
-}
-
-func init() {
- cobra.OnInitialize(initConfig)
-
- // Here you will define your flags and configuration settings.
- // Cobra supports persistent flags, which, if defined here,
- // will be global for your application.
- rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.convey.yaml)")
-
- rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
-}
-
-// initConfig reads in config file and ENV variables if set.
-func initConfig() {
- if cfgFile != "" {
- // Use config file from the flag.
- viper.SetConfigFile(cfgFile)
- } else {
- // Find home directory.
- home, err := homedir.Dir()
- if err != nil {
- errorExit(err.Error())
- }
-
- // Search config in home directory with name ".convey" (without extension).
- viper.AddConfigPath(home)
- viper.SetConfigName(".convey")
- }
-
- viper.AutomaticEnv() // read in environment variables that match
-
- if !verbose {
- log.SetOutput(ioutil.Discard)
- }
-
- // If a config file is found, read it in.
- if err := viper.ReadInConfig(); err == nil {
- log.Println("Config file:", viper.ConfigFileUsed())
- }
+ stanConn.Close()
+ natsConn.Close()
}
diff --git a/cmd/version.go b/cmd/version.go
index c22a641..29994b1 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -7,10 +7,10 @@ import (
"github.com/spf13/cobra"
)
-// VersionGitCommit The git commit this build is from
+// VersionGitCommit The git commit this build is from. Do not change variable name as used by CI build through -ldflags.
var VersionGitCommit string
-// VersionGitTag The git tag this build is from
+// VersionGitTag The git tag this build is from. Do not change variable name as used by CI build through -ldflags.
var VersionGitTag string
func init() {
@@ -19,13 +19,16 @@ func init() {
var versionCmd = &cobra.Command{
Use: "version",
- Short: "Print the version number of Convey",
- Run: func(cmd *cobra.Command, args []string) {
- if VersionGitCommit != "" {
- fmt.Printf("Convey %s -- %s\n\n", VersionGitTag, VersionGitCommit[:7])
- fmt.Printf("Commit: %s\n", VersionGitCommit)
- }
- fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
- fmt.Printf("Go version: %s\n", runtime.Version())
- },
+ Short: "Print version information",
+ Run: VersionCommandFunc,
+}
+
+// VersionCommandFunc is a handler for the version command
+func VersionCommandFunc(cmd *cobra.Command, args []string) {
+ if VersionGitCommit != "" {
+ fmt.Printf("Convey %s -- %s\n\n", VersionGitTag, VersionGitCommit[:7])
+ fmt.Printf("Commit: %s\n", VersionGitCommit)
+ }
+ fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
+ fmt.Printf("Go version: %s\n", runtime.Version())
}