diff --git a/cmd/flag.go b/cmd/flag.go index a9384ce3..824a0e75 100644 --- a/cmd/flag.go +++ b/cmd/flag.go @@ -117,3 +117,23 @@ var ( Usage: "An optional Google Cloud Zone (default: random)", } ) + +// SSH flags +var ( + SshFlag = cli.BoolFlag{ + Name: provider.NameSsh, + Usage: "The darknode will be installed on an existing server with SSH", + } + SshUserFlag = cli.StringFlag{ + Name: "ssh-user", + Usage: "SSH User", + } + SshHostnameFlag = cli.StringFlag{ + Name: "ssh-hostname", + Usage: "SSH Hostname", + } + SshPrivateKeyFlag = cli.StringFlag{ + Name: "ssh-private-key", + Usage: "SSH Private key", + } +) diff --git a/cmd/main.go b/cmd/main.go index 69db88ff..f2098724 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -47,6 +47,8 @@ func main() { DoFlag, DoRegionFlag, DoSizeFlag, DoTokenFlag, // Google Cloud Platform GcpFlag, GcpZoneFlag, GcpCredFlag, GcpMachineFlag, + // SSH + SshFlag, SshUserFlag, SshHostnameFlag, SshPrivateKeyFlag, }, Action: func(c *cli.Context) error { p, err := provider.ParseProvider(c) diff --git a/cmd/provider/provider.go b/cmd/provider/provider.go index 11760cb6..79784912 100644 --- a/cmd/provider/provider.go +++ b/cmd/provider/provider.go @@ -30,6 +30,7 @@ var ( NameAws = "aws" NameDo = "do" NameGcp = "gcp" + NameSsh = "ssh" ) var darknodeService = `[Unit] @@ -68,6 +69,10 @@ func ParseProvider(ctx *cli.Context) (Provider, error) { return NewGcp(ctx) } + if ctx.Bool(NameSsh) { + return NewSsh(ctx) + } + return nil, ErrUnknownProvider } diff --git a/cmd/provider/ssh.go b/cmd/provider/ssh.go new file mode 100644 index 00000000..d90c1cc1 --- /dev/null +++ b/cmd/provider/ssh.go @@ -0,0 +1,57 @@ +package provider + +import ( + "github.com/renproject/darknode-cli/darknode" + "github.com/renproject/darknode-cli/util" + "github.com/urfave/cli" +) + +type providerSsh struct { + user string + hostname string + priKeyPath string +} + +func NewSsh(ctx *cli.Context) (Provider, error) { + user := ctx.String("ssh-user") + hostname := ctx.String("ssh-hostname") + priKeyPath := ctx.String("ssh-private-key") + + return providerSsh{ + user: user, + hostname: hostname, + priKeyPath: priKeyPath, + }, nil +} + +func (p providerSsh) Name() string { + return NameSsh +} + +func (p providerSsh) Deploy(ctx *cli.Context) error { + name := ctx.String("name") + tags := ctx.String("tags") + + latestVersion, err := util.LatestStableRelease() + if err != nil { + return err + } + + // Initialization + network, err := darknode.NewNetwork(ctx.String("network")) + if err != nil { + return err + } + if err := initNode(name, tags, network); err != nil { + return err + } + + // Generate terraform config and start deploying + if err := p.tfConfig(name, latestVersion); err != nil { + return err + } + if err := runTerraform(name); err != nil { + return err + } + return outputURL(name) +} diff --git a/cmd/provider/ssh_template.go b/cmd/provider/ssh_template.go new file mode 100644 index 00000000..99c6c922 --- /dev/null +++ b/cmd/provider/ssh_template.go @@ -0,0 +1,130 @@ +package provider + +import ( + "fmt" + "os" + "path/filepath" + "text/template" + + "github.com/renproject/darknode-cli/util" +) + +type sshTerraform struct { + Name string + User string + Hostname string + PubKeyPath string + RootPriKeyPath string + ConfigPath string + ServiceFile string + LatestVersion string +} + +func (p providerSsh) tfConfig(name, latestVersion string) error { + tf := sshTerraform{ + Name: name, + User: p.user, + Hostname: p.hostname, + ConfigPath: fmt.Sprintf("~/.darknode/darknodes/%v/config.json", name), + PubKeyPath: fmt.Sprintf("~/.darknode/darknodes/%v/ssh_keypair.pub", name), + RootPriKeyPath: fmt.Sprintf(p.priKeyPath), + ServiceFile: darknodeService, + LatestVersion: latestVersion, + } + + t, err := template.New("ssh").Parse(sshTemplate) + if err != nil { + return err + } + tfFile, err := os.Create(filepath.Join(util.NodePath(name), "main.tf")) + if err != nil { + return err + } + return t.Execute(tfFile, tf) +} + +var sshTemplate = ` +resource "null_resource" "darknode" { + provisioner "remote-exec" { + inline = [ + "set -x", + "until sudo apt update; do sleep 4; done", + "sudo adduser darknode --gecos \",,,\" --disabled-password", + "sudo rsync --archive --chown=darknode:darknode ~/.ssh /home/darknode", + "until sudo apt-get -y update; do sleep 4; done", + "until sudo apt-get -y upgrade; do sleep 4; done", + "until sudo apt-get -y autoremove; do sleep 4; done", + "until sudo apt-get install ufw; do sleep 4; done", + "sudo ufw limit 22/tcp", + "sudo ufw allow 18514/tcp", + "sudo ufw allow 18515/tcp", + "sudo ufw --force enable", + "until sudo apt-get -y install jq; do sleep 4; done", + ] + + connection { + host = "{{.Hostname}}" + type = "ssh" + user = "{{.User}}" + private_key = file("{{.RootPriKeyPath}}") + } + } + + provisioner "file" { + source = "{{.ConfigPath}}" + destination = "$HOME/config.json" + + connection { + host = "{{.Hostname}}" + type = "ssh" + user = "darknode" + private_key = file("{{.RootPriKeyPath}}") + } + } + + provisioner "remote-exec" { + inline = [ + "set -x", + "mkdir -p $HOME/.darknode/bin", + "mkdir -p $HOME/.config/systemd/user", + "mv $HOME/config.json $HOME/.darknode/config.json", + "curl -sL https://www.github.com/renproject/darknode-release/releases/latest/download/darknode > ~/.darknode/bin/darknode", + "chmod +x ~/.darknode/bin/darknode", + "echo {{.LatestVersion}} > ~/.darknode/version", + < ~/.config/systemd/user/darknode.service + EOT + , + "loginctl enable-linger darknode", + "systemctl --user enable darknode.service", + "systemctl --user start darknode.service", + ] + + connection { + host = "{{.Hostname}}" + type = "ssh" + user = "darknode" + private_key = file("{{.RootPriKeyPath}}") + } + } + + provisioner "file" { + source = "{{.PubKeyPath}}" + destination = "$HOME/.ssh/authorized_keys" + + connection { + host = "{{.Hostname}}" + type = "ssh" + user = "darknode" + private_key = file("{{.RootPriKeyPath}}") + } + } +} + +output "provider" { + value = "ssh" +} + +output "ip" { + value = "{{.Hostname}}" +}`