2021-08-02 01:10:41 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2022-01-18 13:13:20 +00:00
|
|
|
"context"
|
2021-10-01 10:56:04 +00:00
|
|
|
"errors"
|
2021-10-22 09:42:47 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2021-10-17 21:50:28 +00:00
|
|
|
"os/exec"
|
2021-09-10 12:49:25 +00:00
|
|
|
"os/user"
|
2021-10-25 08:42:39 +00:00
|
|
|
"path/filepath"
|
2021-09-22 06:19:28 +00:00
|
|
|
"strings"
|
2021-08-02 01:10:41 +00:00
|
|
|
|
2021-09-10 12:49:25 +00:00
|
|
|
"coopcloud.tech/abra/cli/internal"
|
2021-09-05 19:37:03 +00:00
|
|
|
"coopcloud.tech/abra/pkg/client"
|
2021-10-25 08:42:39 +00:00
|
|
|
"coopcloud.tech/abra/pkg/config"
|
2021-11-08 14:37:23 +00:00
|
|
|
contextPkg "coopcloud.tech/abra/pkg/context"
|
2021-11-13 22:04:58 +00:00
|
|
|
"coopcloud.tech/abra/pkg/dns"
|
2021-10-02 20:30:08 +00:00
|
|
|
"coopcloud.tech/abra/pkg/server"
|
2021-10-24 21:15:38 +00:00
|
|
|
"coopcloud.tech/abra/pkg/ssh"
|
2021-10-22 09:42:47 +00:00
|
|
|
"github.com/AlecAivazis/survey/v2"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/swarm"
|
|
|
|
dockerClient "github.com/docker/docker/client"
|
2021-08-02 01:10:41 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2022-01-18 13:13:20 +00:00
|
|
|
"github.com/urfave/cli"
|
2021-08-02 01:10:41 +00:00
|
|
|
)
|
|
|
|
|
2021-10-25 08:42:39 +00:00
|
|
|
var (
|
|
|
|
dockerInstallMsg = `
|
2021-10-25 17:58:50 +00:00
|
|
|
A docker installation cannot be found on %s. This is a required system
|
2022-05-13 14:44:49 +00:00
|
|
|
dependency for running Co-op Cloud apps on your server. If you would like, Abra
|
|
|
|
can attempt to install Docker for you using the upstream non-interactive
|
2021-10-25 17:58:50 +00:00
|
|
|
installation script.
|
2021-10-25 08:42:39 +00:00
|
|
|
|
|
|
|
See the following documentation for more:
|
|
|
|
|
|
|
|
https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script
|
|
|
|
|
|
|
|
N.B Docker doesn't recommend it for production environments but many use it for
|
|
|
|
such purposes. Docker stable is now installed by default by this script. The
|
|
|
|
source for this script can be seen here:
|
|
|
|
|
|
|
|
https://github.com/docker/docker-install
|
|
|
|
`
|
|
|
|
)
|
|
|
|
|
2021-10-01 09:59:17 +00:00
|
|
|
var local bool
|
|
|
|
var localFlag = &cli.BoolFlag{
|
2022-01-18 13:13:20 +00:00
|
|
|
Name: "local, l",
|
2021-10-22 09:42:47 +00:00
|
|
|
Usage: "Use local server",
|
2021-10-01 09:59:17 +00:00
|
|
|
Destination: &local,
|
|
|
|
}
|
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
var provision bool
|
|
|
|
var provisionFlag = &cli.BoolFlag{
|
2022-01-18 13:13:20 +00:00
|
|
|
Name: "provision, p",
|
2021-10-22 09:42:47 +00:00
|
|
|
Usage: "Provision server so it can deploy apps",
|
|
|
|
Destination: &provision,
|
|
|
|
}
|
|
|
|
|
2021-10-24 21:15:38 +00:00
|
|
|
var sshAuth string
|
|
|
|
var sshAuthFlag = &cli.StringFlag{
|
2022-03-12 14:30:24 +00:00
|
|
|
Name: "ssh-auth, s",
|
2021-10-24 21:15:38 +00:00
|
|
|
Value: "identity-file",
|
|
|
|
Usage: "Select SSH authentication method (identity-file, password)",
|
|
|
|
Destination: &sshAuth,
|
|
|
|
}
|
|
|
|
|
|
|
|
var askSudoPass bool
|
|
|
|
var askSudoPassFlag = &cli.BoolFlag{
|
2022-03-12 14:30:24 +00:00
|
|
|
Name: "ask-sudo-pass, a",
|
2021-10-24 21:15:38 +00:00
|
|
|
Usage: "Ask for sudo password",
|
|
|
|
Destination: &askSudoPass,
|
|
|
|
}
|
|
|
|
|
2021-10-25 08:42:39 +00:00
|
|
|
func cleanUp(domainName string) {
|
|
|
|
logrus.Warnf("cleaning up context for %s", domainName)
|
|
|
|
if err := client.DeleteContext(domainName); err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Warnf("cleaning up server directory for %s", domainName)
|
2021-12-25 13:04:07 +00:00
|
|
|
if err := os.RemoveAll(filepath.Join(config.SERVERS_DIR, domainName)); err != nil {
|
2021-10-25 08:42:39 +00:00
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func installDockerLocal(c *cli.Context) error {
|
|
|
|
fmt.Println(fmt.Sprintf(dockerInstallMsg, "this local server"))
|
|
|
|
|
|
|
|
response := false
|
|
|
|
prompt := &survey.Confirm{
|
|
|
|
Message: fmt.Sprintf("attempt install docker on local server?"),
|
|
|
|
}
|
|
|
|
if err := survey.AskOne(prompt, &response); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !response {
|
|
|
|
logrus.Fatal("exiting as requested")
|
|
|
|
}
|
|
|
|
|
2021-12-22 12:54:13 +00:00
|
|
|
for _, exe := range []string{"wget", "bash"} {
|
|
|
|
exists, err := ensureLocalExecutable(exe)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
return fmt.Errorf("%s missing, please install it", exe)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-22 19:08:15 +00:00
|
|
|
cmd := exec.Command("bash", "-c", "wget -O- https://get.docker.com | bash")
|
2021-10-25 08:42:39 +00:00
|
|
|
if err := internal.RunCmd(cmd); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
func newLocalServer(c *cli.Context, domainName string) error {
|
|
|
|
if err := createServerDir(domainName); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cl, err := newClient(c, domainName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-10-25 22:27:45 +00:00
|
|
|
if provision {
|
2021-12-22 12:54:13 +00:00
|
|
|
exists, err := ensureLocalExecutable("docker")
|
2021-11-03 06:56:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-12-22 12:54:13 +00:00
|
|
|
|
|
|
|
if !exists {
|
2021-10-25 08:42:39 +00:00
|
|
|
if err := installDockerLocal(c); err != nil {
|
2021-10-22 09:42:47 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-10-25 22:27:45 +00:00
|
|
|
}
|
2021-12-22 12:54:13 +00:00
|
|
|
|
2021-10-25 22:27:45 +00:00
|
|
|
if err := initSwarmLocal(c, cl, domainName); err != nil {
|
|
|
|
if !strings.Contains(err.Error(), "proxy already exists") {
|
2021-10-22 09:42:47 +00:00
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Info("local server has been added")
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-25 08:42:39 +00:00
|
|
|
func newContext(c *cli.Context, domainName, username, port string) error {
|
2021-11-08 14:37:23 +00:00
|
|
|
store := contextPkg.NewDefaultDockerContextStore()
|
2021-10-22 09:42:47 +00:00
|
|
|
contexts, err := store.Store.List()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, context := range contexts {
|
|
|
|
if context.Name == domainName {
|
|
|
|
logrus.Debugf("context for %s already exists", domainName)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Debugf("creating context with domain %s, username %s and port %s", domainName, username, port)
|
|
|
|
|
|
|
|
if err := client.CreateContext(domainName, username, port); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newClient(c *cli.Context, domainName string) (*dockerClient.Client, error) {
|
|
|
|
cl, err := client.New(domainName)
|
|
|
|
if err != nil {
|
|
|
|
return &dockerClient.Client{}, err
|
|
|
|
}
|
|
|
|
return cl, nil
|
|
|
|
}
|
|
|
|
|
2021-11-08 14:37:23 +00:00
|
|
|
func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *ssh.Client, domainName string) error {
|
2021-12-22 12:54:13 +00:00
|
|
|
exists, err := ensureRemoteExecutable("docker", sshCl)
|
|
|
|
if err != nil {
|
2021-10-25 08:06:16 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-12-22 12:54:13 +00:00
|
|
|
if !exists {
|
2021-10-25 08:42:39 +00:00
|
|
|
fmt.Println(fmt.Sprintf(dockerInstallMsg, domainName))
|
2021-10-22 09:42:47 +00:00
|
|
|
|
2021-10-25 08:06:16 +00:00
|
|
|
response := false
|
|
|
|
prompt := &survey.Confirm{
|
|
|
|
Message: fmt.Sprintf("attempt install docker on %s?", domainName),
|
|
|
|
}
|
2021-12-22 19:08:15 +00:00
|
|
|
|
2021-10-25 08:06:16 +00:00
|
|
|
if err := survey.AskOne(prompt, &response); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-12-22 19:08:15 +00:00
|
|
|
|
2021-10-25 08:06:16 +00:00
|
|
|
if !response {
|
|
|
|
logrus.Fatal("exiting as requested")
|
|
|
|
}
|
|
|
|
|
2021-12-22 19:08:15 +00:00
|
|
|
exes := []string{"wget", "bash"}
|
|
|
|
if askSudoPass {
|
|
|
|
exes = append(exes, "ssh-askpass")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, exe := range exes {
|
2021-12-22 12:54:13 +00:00
|
|
|
exists, err := ensureRemoteExecutable(exe, sshCl)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
return fmt.Errorf("%s missing on remote, please install it", exe)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-25 08:06:16 +00:00
|
|
|
var sudoPass string
|
|
|
|
if askSudoPass {
|
2021-12-22 19:08:15 +00:00
|
|
|
cmd := "wget -O- https://get.docker.com | bash"
|
|
|
|
|
2021-10-25 08:06:16 +00:00
|
|
|
prompt := &survey.Password{
|
|
|
|
Message: "sudo password?",
|
2021-10-22 09:42:47 +00:00
|
|
|
}
|
2021-12-22 19:08:15 +00:00
|
|
|
|
2021-10-25 08:06:16 +00:00
|
|
|
if err := survey.AskOne(prompt, &sudoPass); err != nil {
|
2021-10-22 09:42:47 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-12-22 19:08:15 +00:00
|
|
|
|
2021-12-22 12:55:22 +00:00
|
|
|
logrus.Debugf("running %s on %s now with sudo password", cmd, domainName)
|
2021-12-22 19:08:15 +00:00
|
|
|
|
|
|
|
if sudoPass == "" {
|
|
|
|
return fmt.Errorf("missing sudo password but requested --ask-sudo-pass?")
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Warn("installing docker, this could take some time...")
|
|
|
|
|
2021-10-25 23:30:10 +00:00
|
|
|
if err := ssh.RunSudoCmd(cmd, sudoPass, sshCl); err != nil {
|
2021-12-22 19:08:15 +00:00
|
|
|
fmt.Print(fmt.Sprintf(`
|
|
|
|
Abra was unable to bootstrap Docker, see below for logs:
|
|
|
|
|
|
|
|
|
|
|
|
%s
|
|
|
|
|
2022-05-13 14:44:49 +00:00
|
|
|
If nothing works, you can try running the Docker install script manually on your server:
|
2021-12-22 19:08:15 +00:00
|
|
|
|
|
|
|
wget -O- https://get.docker.com | bash
|
|
|
|
|
|
|
|
`, string(err.Error())))
|
|
|
|
logrus.Fatal("Process exited with status 1")
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Infof("docker is installed on %s", domainName)
|
|
|
|
|
|
|
|
remoteUser := sshCl.SSHClient.Conn.User()
|
|
|
|
logrus.Infof("adding %s to docker group", remoteUser)
|
|
|
|
permsCmd := fmt.Sprintf("sudo usermod -aG docker %s", remoteUser)
|
|
|
|
if err := ssh.RunSudoCmd(permsCmd, sudoPass, sshCl); err != nil {
|
2021-10-24 21:15:38 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-10-25 08:06:16 +00:00
|
|
|
} else {
|
2021-12-22 19:08:15 +00:00
|
|
|
cmd := "wget -O- https://get.docker.com | bash"
|
|
|
|
|
2021-12-22 12:55:22 +00:00
|
|
|
logrus.Debugf("running %s on %s now without sudo password", cmd, domainName)
|
2021-12-22 19:08:15 +00:00
|
|
|
|
|
|
|
logrus.Warn("installing docker, this could take some time...")
|
|
|
|
|
|
|
|
if out, err := sshCl.Exec(cmd); err != nil {
|
|
|
|
fmt.Print(fmt.Sprintf(`
|
|
|
|
Abra was unable to bootstrap Docker, see below for logs:
|
|
|
|
|
|
|
|
|
|
|
|
%s
|
|
|
|
|
2022-05-13 14:44:49 +00:00
|
|
|
This could be due to several reasons. One of the most common is that your
|
2021-12-22 19:08:15 +00:00
|
|
|
server user account does not have sudo access, and if it does, you need to pass
|
|
|
|
"--ask-sudo-pass" in order to supply Abra with your password.
|
|
|
|
|
|
|
|
If nothing works, you try running the Docker install script manually on your server:
|
|
|
|
|
|
|
|
wget -O- https://get.docker.com | bash
|
|
|
|
|
|
|
|
`, string(out)))
|
|
|
|
logrus.Fatal(err)
|
2021-10-24 21:15:38 +00:00
|
|
|
}
|
2021-12-22 19:08:15 +00:00
|
|
|
|
|
|
|
logrus.Infof("docker is installed on %s", domainName)
|
2021-10-22 09:42:47 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-25 08:06:16 +00:00
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-25 22:27:45 +00:00
|
|
|
func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string) error {
|
|
|
|
initReq := swarm.InitRequest{ListenAddr: "0.0.0.0:2377"}
|
2022-01-18 13:13:20 +00:00
|
|
|
if _, err := cl.SwarmInit(context.Background(), initReq); err != nil {
|
2021-12-22 19:08:15 +00:00
|
|
|
if strings.Contains(err.Error(), "is already part of a swarm") ||
|
|
|
|
strings.Contains(err.Error(), "must specify a listening address") {
|
|
|
|
logrus.Infof("swarm mode already initialised on %s", domainName)
|
|
|
|
} else {
|
2021-10-25 22:27:45 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-10-25 23:30:23 +00:00
|
|
|
} else {
|
|
|
|
logrus.Infof("initialised swarm mode on local server")
|
2021-10-25 22:27:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"}
|
2022-01-18 13:13:20 +00:00
|
|
|
if _, err := cl.NetworkCreate(context.Background(), "proxy", netOpts); err != nil {
|
2021-10-25 23:30:23 +00:00
|
|
|
if !strings.Contains(err.Error(), "proxy already exists") {
|
2021-10-25 22:27:45 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-10-25 23:30:23 +00:00
|
|
|
logrus.Info("swarm overlay network already created on local server")
|
|
|
|
} else {
|
|
|
|
logrus.Infof("swarm overlay network created on local server")
|
2021-10-25 22:27:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
func initSwarm(c *cli.Context, cl *dockerClient.Client, domainName string) error {
|
2021-11-13 22:04:58 +00:00
|
|
|
ipv4, err := dns.EnsureIPv4(domainName)
|
2021-10-22 09:42:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
initReq := swarm.InitRequest{
|
|
|
|
ListenAddr: "0.0.0.0:2377",
|
|
|
|
AdvertiseAddr: ipv4,
|
|
|
|
}
|
2022-01-18 13:13:20 +00:00
|
|
|
if _, err := cl.SwarmInit(context.Background(), initReq); err != nil {
|
2021-12-22 19:08:15 +00:00
|
|
|
if strings.Contains(err.Error(), "is already part of a swarm") ||
|
|
|
|
strings.Contains(err.Error(), "must specify a listening address") {
|
|
|
|
logrus.Infof("swarm mode already initialised on %s", domainName)
|
|
|
|
} else {
|
2021-10-22 09:42:47 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-10-25 23:30:23 +00:00
|
|
|
} else {
|
|
|
|
logrus.Infof("initialised swarm mode on %s", domainName)
|
2021-10-22 09:42:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"}
|
2022-01-18 13:13:20 +00:00
|
|
|
if _, err := cl.NetworkCreate(context.Background(), "proxy", netOpts); err != nil {
|
2021-10-25 23:30:23 +00:00
|
|
|
if !strings.Contains(err.Error(), "proxy already exists") {
|
2021-10-25 22:27:45 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-10-25 23:30:23 +00:00
|
|
|
logrus.Infof("swarm overlay network already created on %s", domainName)
|
|
|
|
} else {
|
|
|
|
logrus.Infof("swarm overlay network created on %s", domainName)
|
2021-10-22 09:42:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createServerDir(domainName string) error {
|
|
|
|
if err := server.CreateServerDir(domainName); err != nil {
|
|
|
|
if !os.IsExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logrus.Debugf("server dir for %s already created", domainName)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-18 13:13:20 +00:00
|
|
|
var serverAddCommand = cli.Command{
|
|
|
|
Name: "add",
|
|
|
|
Aliases: []string{"a"},
|
|
|
|
Usage: "Add a server to your configuration",
|
2021-09-10 12:49:25 +00:00
|
|
|
Description: `
|
2022-05-13 14:44:49 +00:00
|
|
|
Add a new server to your configuration so that it can be managed by Abra. This
|
|
|
|
command can also provision your server ("--provision/-p") with a Docker
|
|
|
|
installation so that it is capable of hosting Co-op Cloud apps.
|
2021-10-22 09:42:47 +00:00
|
|
|
|
2022-02-03 12:42:49 +00:00
|
|
|
Abra will default to expecting that you have a running ssh-agent and are using
|
|
|
|
SSH keys to connect to your new server. Abra will also read your SSH config
|
|
|
|
(matching "Host" as <domain>). SSH connection details precedence follows as
|
|
|
|
such: command-line > SSH config > guessed defaults.
|
2021-10-24 21:15:38 +00:00
|
|
|
|
|
|
|
If you have no SSH key configured for this host and are instead using password
|
|
|
|
authentication, you may pass "--ssh-auth password" to have Abra ask you for the
|
|
|
|
password. "--ask-sudo-pass" may be passed if you run your provisioning commands
|
|
|
|
via sudo privilege escalation.
|
2021-09-10 12:49:25 +00:00
|
|
|
|
2022-02-03 12:42:49 +00:00
|
|
|
The <domain> argument must be a publicy accessible domain name which points to
|
2022-05-13 14:44:49 +00:00
|
|
|
your server. You should have working SSH access to this server already, Abra
|
|
|
|
will assume port 22 and will use your current system username to make an
|
|
|
|
initial connection. You can use the <user> and <port> arguments to adjust this.
|
2021-09-10 12:49:25 +00:00
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
Example:
|
2021-09-10 12:49:25 +00:00
|
|
|
|
2022-02-03 12:42:49 +00:00
|
|
|
abra server add varia.zone glodemodem 12345 -p
|
2021-09-10 12:49:25 +00:00
|
|
|
|
2021-10-25 09:13:41 +00:00
|
|
|
Abra will construct the following SSH connection and Docker context:
|
2021-09-10 12:49:25 +00:00
|
|
|
|
|
|
|
ssh://globemodem@varia.zone:12345
|
|
|
|
|
|
|
|
All communication between Abra and the server will use this SSH connection.
|
2021-10-22 09:42:47 +00:00
|
|
|
|
2022-02-03 12:42:49 +00:00
|
|
|
If "--local" is passed, then Abra assumes that the current local server is
|
|
|
|
intended as the target server. This is useful when you want to have your entire
|
|
|
|
Co-op Cloud config located on the server itself, and not on your local
|
|
|
|
developer machine.
|
2021-09-10 12:49:25 +00:00
|
|
|
`,
|
2021-10-01 09:59:17 +00:00
|
|
|
Flags: []cli.Flag{
|
2022-01-18 13:13:20 +00:00
|
|
|
internal.DebugFlag,
|
|
|
|
internal.NoInputFlag,
|
2021-10-01 09:59:17 +00:00
|
|
|
localFlag,
|
2021-10-22 09:42:47 +00:00
|
|
|
provisionFlag,
|
2021-10-24 21:15:38 +00:00
|
|
|
sshAuthFlag,
|
|
|
|
askSudoPassFlag,
|
2021-10-01 09:59:17 +00:00
|
|
|
},
|
2022-01-18 13:13:20 +00:00
|
|
|
Before: internal.SubCommandBefore,
|
2021-09-10 12:49:25 +00:00
|
|
|
ArgsUsage: "<domain> [<user>] [<port>]",
|
2021-08-02 01:10:41 +00:00
|
|
|
Action: func(c *cli.Context) error {
|
2022-01-18 13:13:20 +00:00
|
|
|
if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) {
|
2021-12-22 12:55:22 +00:00
|
|
|
err := errors.New("cannot use <domain> and --local together")
|
2021-10-01 09:59:17 +00:00
|
|
|
internal.ShowSubcommandHelpAndError(c, err)
|
|
|
|
}
|
|
|
|
|
2021-10-25 07:02:24 +00:00
|
|
|
if sshAuth != "password" && sshAuth != "identity-file" {
|
2021-12-22 12:55:22 +00:00
|
|
|
err := errors.New("--ssh-auth only accepts identity-file or password")
|
2021-10-24 21:15:38 +00:00
|
|
|
internal.ShowSubcommandHelpAndError(c, err)
|
|
|
|
}
|
|
|
|
|
2022-02-24 14:57:40 +00:00
|
|
|
domainName := internal.ValidateDomain(c)
|
|
|
|
|
2021-10-01 09:59:17 +00:00
|
|
|
if local {
|
2021-10-22 09:42:47 +00:00
|
|
|
if err := newLocalServer(c, "default"); err != nil {
|
2021-10-02 20:30:08 +00:00
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
2021-10-01 09:59:17 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-25 08:42:39 +00:00
|
|
|
username := c.Args().Get(1)
|
|
|
|
if username == "" {
|
|
|
|
systemUser, err := user.Current()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
username = systemUser.Username
|
|
|
|
}
|
|
|
|
|
|
|
|
port := c.Args().Get(2)
|
|
|
|
if port == "" {
|
|
|
|
port = "22"
|
|
|
|
}
|
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
if err := createServerDir(domainName); err != nil {
|
|
|
|
logrus.Fatal(err)
|
2021-09-10 12:49:25 +00:00
|
|
|
}
|
|
|
|
|
2021-10-25 08:42:39 +00:00
|
|
|
if err := newContext(c, domainName, username, port); err != nil {
|
2021-10-22 09:42:47 +00:00
|
|
|
logrus.Fatal(err)
|
2021-09-10 12:49:25 +00:00
|
|
|
}
|
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
cl, err := newClient(c, domainName)
|
2021-09-10 12:49:25 +00:00
|
|
|
if err != nil {
|
2022-02-03 12:43:11 +00:00
|
|
|
cleanUp(domainName)
|
|
|
|
logrus.Debugf("failed to construct client for %s, saw %s", domainName, err.Error())
|
|
|
|
logrus.Fatalf(fmt.Sprintf(internal.ServerAddFailMsg, domainName))
|
2021-09-10 12:49:25 +00:00
|
|
|
}
|
|
|
|
|
2021-10-22 09:42:47 +00:00
|
|
|
if provision {
|
2021-10-25 08:42:39 +00:00
|
|
|
logrus.Debugf("attempting to construct SSH client for %s", domainName)
|
|
|
|
sshCl, err := ssh.New(domainName, sshAuth, username, port)
|
|
|
|
if err != nil {
|
2022-02-03 12:43:11 +00:00
|
|
|
cleanUp(domainName)
|
|
|
|
logrus.Fatalf(fmt.Sprintf(internal.ServerAddFailMsg, domainName))
|
2021-10-25 08:42:39 +00:00
|
|
|
}
|
|
|
|
defer sshCl.Close()
|
|
|
|
logrus.Debugf("successfully created SSH client for %s", domainName)
|
|
|
|
|
|
|
|
if err := installDocker(c, cl, sshCl, domainName); err != nil {
|
2021-10-22 09:42:47 +00:00
|
|
|
logrus.Fatal(err)
|
2021-09-10 12:49:25 +00:00
|
|
|
}
|
2021-10-22 09:42:47 +00:00
|
|
|
if err := initSwarm(c, cl, domainName); err != nil {
|
2021-10-18 08:48:43 +00:00
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
2021-08-02 01:10:41 +00:00
|
|
|
}
|
2021-09-10 12:49:25 +00:00
|
|
|
|
2022-01-18 13:13:20 +00:00
|
|
|
if _, err := cl.Info(context.Background()); err != nil {
|
2021-10-25 21:48:19 +00:00
|
|
|
cleanUp(domainName)
|
2022-02-03 12:43:11 +00:00
|
|
|
logrus.Fatalf(fmt.Sprintf(internal.ServerAddFailMsg, domainName))
|
2021-10-25 21:48:19 +00:00
|
|
|
}
|
|
|
|
|
2021-08-02 01:10:41 +00:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2021-12-22 12:54:13 +00:00
|
|
|
|
|
|
|
// ensureLocalExecutable ensures that an executable is present on the local machine
|
|
|
|
func ensureLocalExecutable(exe string) (bool, error) {
|
|
|
|
out, err := exec.Command("which", exe).Output()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2021-12-22 19:08:15 +00:00
|
|
|
return string(out) != "", nil
|
2021-12-22 12:54:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ensureRemoteExecutable ensures that an executable is present on a remote machine
|
|
|
|
func ensureRemoteExecutable(exe string, sshCl *ssh.Client) (bool, error) {
|
|
|
|
out, err := sshCl.Exec(fmt.Sprintf("which %s", exe))
|
|
|
|
if err != nil && string(out) != "" {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2021-12-22 19:08:15 +00:00
|
|
|
return string(out) != "", nil
|
2021-12-22 12:54:13 +00:00
|
|
|
}
|