diff --git a/cmd/control/cli.go b/cmd/control/cli.go index bc7314f0..4b33118a 100644 --- a/cmd/control/cli.go +++ b/cmd/control/cli.go @@ -59,6 +59,7 @@ func Main() { HideHelp: true, Subcommands: tlsConfCommands(), }, + installCommand, } app.Run(os.Args) diff --git a/cmd/control/install.go b/cmd/control/install.go new file mode 100644 index 00000000..3a326e03 --- /dev/null +++ b/cmd/control/install.go @@ -0,0 +1,126 @@ +package control + +import ( + "bufio" + "fmt" + "os" + "os/exec" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" + "github.com/rancherio/os/cmd/power" + "github.com/rancherio/os/config" + "github.com/rancherio/os/util" +) + +var installCommand = cli.Command{ + Name: "install", + Usage: "install RancherOS to disk", + HideHelp: true, + Action: installAction, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "image, i", + Usage: "install from a certain image", + }, + cli.StringFlag{ + Name: "install-type, t", + Usage: `generic: (Default) Creates 1 ext4 partition and installs RancherOS + amazon-ebs: Installs RancherOS and sets up PV-GRUB`, + }, + cli.StringFlag{ + Name: "cloud-config, c", + Usage: "cloud-config yml file - needed for SSH authorized keys", + }, + cli.StringFlag{ + Name: "device, d", + Usage: "storage device", + }, + cli.BoolFlag{ + Name: "force, f", + Usage: "[ DANGEROUS! Data loss can happen ] partition/format without prompting", + }, + cli.BoolFlag{ + Name: "no-reboot", + Usage: "do not reboot after install", + }, + }, +} + +func installAction(c *cli.Context) { + if c.Args().Present() { + log.Fatalf("invalid arguments %v", c.Args()) + } + device := c.String("device") + if device == "" { + log.Fatal("Can not proceed without -d specified") + } + + image := c.String("image") + cfg, err := config.LoadConfig() + if err != nil { + log.WithFields(log.Fields{"err": err}).Fatal("ros install: failed to load config") + } + if image == "" { + image = cfg.Rancher.Upgrade.Image + ":" + config.VERSION + } + + installType := c.String("install-type") + if installType == "" { + log.Info("No install type specified...defaulting to generic") + installType = "generic" + } + + cloudConfig := c.String("cloud-config") + if cloudConfig == "" { + log.Warn("Cloud-config not provided: you might need to provide cloud-config on boot with ssh_authorized_keys") + } else { + uc := "/opt/user_config.yml" + if err := util.FileCopy(cloudConfig, uc); err != nil { + log.WithFields(log.Fields{"cloudConfig": cloudConfig}).Fatal("Failed to copy cloud-config") + } + cloudConfig = uc + } + + force := c.Bool("force") + reboot := !c.Bool("no-reboot") + + if err := runInstall(cfg, image, installType, cloudConfig, device, force, reboot); err != nil { + log.WithFields(log.Fields{"err": err}).Fatal("Failed to run install") + } +} + +func runInstall(cfg *config.CloudConfig, image, installType, cloudConfig, device string, force, reboot bool) error { + in := bufio.NewReader(os.Stdin) + + fmt.Printf("Installing from %s\n", image) + + if !force { + if !yes(in, "Continue") { + os.Exit(1) + } + } + + if installType == "generic" { + cmd := exec.Command("system-docker", "run", "--net=host", "--privileged", "--volumes-from=all-volumes", + "--entrypoint=/scripts/set-disk-partitions", image, device) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + return err + } + } + cmd := exec.Command("system-docker", "run", "--net=host", "--privileged", "--volumes-from=user-volumes", image, + "-d", device, "-t", installType, "-c", cloudConfig) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + return err + } + + if reboot && yes(in, "Continue with reboot") { + log.Info("Rebooting") + power.Reboot() + } + + return nil +} diff --git a/scripts/installer/conf/empty.yml b/scripts/installer/conf/empty.yml new file mode 100644 index 00000000..d59ff2e6 --- /dev/null +++ b/scripts/installer/conf/empty.yml @@ -0,0 +1,2 @@ +#cloud-config +{} diff --git a/scripts/installer/lay-down-os b/scripts/installer/lay-down-os index f3b6e087..69d01495 100755 --- a/scripts/installer/lay-down-os +++ b/scripts/installer/lay-down-os @@ -19,6 +19,7 @@ do done DIST=${DIST:-/dist} +CLOUD_CONFIG=${CLOUD_CONFIG:-/scripts/conf/empty.yml} BASE_DIR="/mnt/new_img" # TODO: Change this to a number so that users can specify. # Will need to make it so that our builds and packer APIs remain consistent. diff --git a/scripts/installer/seed-data b/scripts/installer/seed-data index 61c2b49e..53b73644 100755 --- a/scripts/installer/seed-data +++ b/scripts/installer/seed-data @@ -12,8 +12,9 @@ if [ -z ${BASE_DIR} ]; then fi mkdir -p ${BASE_DIR}/var/lib/rancher/conf/cloud-config.d - -cp ${CLOUD_DATA} ${BASE_DIR}/var/lib/rancher/conf/cloud-config.d/ +if [ "$CLOUD_DATA" != "/scripts/conf/empty.yml" ]; then + cp ${CLOUD_DATA} ${BASE_DIR}/var/lib/rancher/conf/cloud-config.d/ +fi for f in ${FILES[@]}; do IFS=":" read s d <<< "${f}" diff --git a/util/util.go b/util/util.go index 7b484c85..9d3ad777 100644 --- a/util/util.go +++ b/util/util.go @@ -152,6 +152,25 @@ func RandSeq(n int) string { return string(b) } +func FileCopy(src, dest string) (err error) { + in, err := os.Open(src) + if err != nil { + return err + } + defer func() { err = in.Close() }() + + out, err := os.Create(dest) + if err != nil { + return err + } + defer func() { err = out.Close() }() + + if _, err := io.Copy(out, in); err != nil { + return err + } + return +} + func Convert(from, to interface{}) error { bytes, err := yaml.Marshal(from) if err != nil {