diff --git a/cmd/control/cli.go b/cmd/control/cli.go index 048ba747..b3b92192 100644 --- a/cmd/control/cli.go +++ b/cmd/control/cli.go @@ -101,6 +101,13 @@ func Main() { SkipFlagParsing: true, Action: preloadImagesAction, }, + { + Name: "recovery-init", + Hidden: true, + HideHelp: true, + SkipFlagParsing: true, + Action: recoveryInitAction, + }, { Name: "switch-console", Hidden: true, diff --git a/cmd/control/console_init.go b/cmd/control/console_init.go index a529ad9a..660bd5d4 100644 --- a/cmd/control/console_init.go +++ b/cmd/control/console_init.go @@ -82,7 +82,7 @@ func consoleInitFunc() error { log.Error(err) } - if err := writeRespawn(); err != nil { + if err := writeRespawn("rancher", true); err != nil { log.Error(err) } @@ -155,7 +155,7 @@ func consoleInitFunc() error { return syscall.Exec(respawnBinPath, []string{"respawn", "-f", "/etc/respawn.conf"}, os.Environ()) } -func generateRespawnConf(cmdline string) string { +func generateRespawnConf(cmdline, user string, sshd bool) string { var respawnConf bytes.Buffer for i := 1; i < 7; i++ { @@ -163,7 +163,7 @@ func generateRespawnConf(cmdline string) string { respawnConf.WriteString(gettyCmd) if strings.Contains(cmdline, fmt.Sprintf("rancher.autologin=%s", tty)) { - respawnConf.WriteString(" --autologin rancher") + respawnConf.WriteString(fmt.Sprintf(" --autologin %s", user)) } respawnConf.WriteString(fmt.Sprintf(" --noclear %s linux\n", tty)) } @@ -175,23 +175,25 @@ func generateRespawnConf(cmdline string) string { respawnConf.WriteString(gettyCmd) if strings.Contains(cmdline, fmt.Sprintf("rancher.autologin=%s", tty)) { - respawnConf.WriteString(" --autologin rancher") + respawnConf.WriteString(fmt.Sprintf(" --autologin %s", user)) } respawnConf.WriteString(fmt.Sprintf(" %s\n", tty)) } - respawnConf.WriteString("/usr/sbin/sshd -D") + if sshd { + respawnConf.WriteString("/usr/sbin/sshd -D") + } return respawnConf.String() } -func writeRespawn() error { +func writeRespawn(user string, sshd bool) error { cmdline, err := ioutil.ReadFile("/proc/cmdline") if err != nil { return err } - respawn := generateRespawnConf(string(cmdline)) + respawn := generateRespawnConf(string(cmdline), user, sshd) files, err := ioutil.ReadDir("/etc/respawn.conf.d") if err == nil { diff --git a/cmd/control/recovery_init.go b/cmd/control/recovery_init.go new file mode 100644 index 00000000..68d6be33 --- /dev/null +++ b/cmd/control/recovery_init.go @@ -0,0 +1,25 @@ +package control + +import ( + "os" + "os/exec" + "syscall" + + log "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" +) + +func recoveryInitAction(c *cli.Context) error { + if err := writeRespawn("root", false); err != nil { + log.Error(err) + } + + os.Setenv("TERM", "linux") + + respawnBinPath, err := exec.LookPath("respawn") + if err != nil { + return err + } + + return syscall.Exec(respawnBinPath, []string{"respawn", "-f", "/etc/respawn.conf"}, os.Environ()) +} diff --git a/config/schema.go b/config/schema.go index 0876f1e7..15b36ebb 100644 --- a/config/schema.go +++ b/config/schema.go @@ -37,6 +37,7 @@ var schema = `{ "no_sharedroot": {"type": "boolean"}, "log": {"type": "boolean"}, "force_console_rebuild": {"type": "boolean"}, + "recovery": {"type": "boolean"}, "disable": {"$ref": "#/definitions/list_of_strings"}, "services_include": {"type": "object"}, "modules": {"$ref": "#/definitions/list_of_strings"}, diff --git a/config/types.go b/config/types.go index 683c319c..3dc980cf 100755 --- a/config/types.go +++ b/config/types.go @@ -113,6 +113,7 @@ type RancherConfig struct { NoSharedRoot bool `yaml:"no_sharedroot,omitempty"` Log bool `yaml:"log,omitempty"` ForceConsoleRebuild bool `yaml:"force_console_rebuild,omitempty"` + Recovery bool `yaml:"recovery,omitempty"` Disable []string `yaml:"disable,omitempty"` ServicesInclude map[string]bool `yaml:"services_include,omitempty"` Modules []string `yaml:"modules,omitempty"` diff --git a/init/init.go b/init/init.go index 3b3444b3..c9139f90 100755 --- a/init/init.go +++ b/init/init.go @@ -263,6 +263,12 @@ func RunInit() error { return cfg, nil }}, config.CfgFuncData{"load modules", loadModules}, + config.CfgFuncData("recovery console", func(cfg *config.CloudConfig) (*config.CloudConfig, error) { + if cfg.Rancher.Recovery { + recovery(nil) + } + return cfg, nil + }, config.CfgFuncData{"b2d env", func(cfg *config.CloudConfig) (*config.CloudConfig, error) { if dev := util.ResolveDevice("LABEL=B2D_STATE"); dev != "" { boot2DockerEnvironment = true @@ -411,7 +417,7 @@ func RunInit() error { cfg, err := config.ChainCfgFuncs(nil, initFuncs) if err != nil { - return err + recovery(err) } launchConfig, args := getLaunchConfig(cfg, &cfg.Rancher.SystemDocker) @@ -422,6 +428,7 @@ func RunInit() error { _, err = dfs.LaunchDocker(launchConfig, config.SystemDockerBin, args...) if err != nil { log.Errorf("Error Launching System Docker: %s", err) + recovery(err) return err } // Code never gets here - rancher.system_docker.exec=true diff --git a/init/recovery.go b/init/recovery.go new file mode 100644 index 00000000..eb6e4ff5 --- /dev/null +++ b/init/recovery.go @@ -0,0 +1,90 @@ +package init + +import ( + log "github.com/Sirupsen/logrus" + composeConfig "github.com/docker/libcompose/config" + "github.com/docker/libcompose/yaml" + "github.com/rancher/os/compose" + "github.com/rancher/os/config" +) + +var ( + recoveryDockerService = composeConfig.ServiceConfigV1{ + Image: config.OsBase, + Command: yaml.Command{ + "ros", + "recovery-init", + }, + Labels: map[string]string{ + config.DetachLabel: "false", + config.ScopeLabel: "system", + }, + LogDriver: "json-file", + Net: "host", + Uts: "host", + Pid: "host", + Ipc: "host", + Privileged: true, + Volumes: []string{ + "/dev:/host/dev", + "/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt.rancher", + "/lib/modules:/lib/modules", + "/lib/firmware:/lib/firmware", + "/usr/bin/ros:/usr/bin/ros:ro", + "/usr/bin/ros:/usr/bin/cloud-init-save", + "/usr/bin/ros:/usr/bin/respawn:ro", + "/usr/share/ros:/usr/share/ros:ro", + "/var/lib/rancher:/var/lib/rancher", + "/var/lib/rancher/conf:/var/lib/rancher/conf", + }, + } +) + +func recoveryServices(cfg *config.CloudConfig) (*config.CloudConfig, error) { + _, err := compose.RunServiceSet("recovery", cfg, map[string]*composeConfig.ServiceConfigV1{ + "recovery": &recoveryDockerService, + }) + return nil, err +} + +func recovery(initFailure error) { + if initFailure != nil { + log.Errorf("RancherOS has failed to boot: %v", initFailure) + } + log.Info("Launching recovery console") + + var recoveryConfig config.CloudConfig + recoveryConfig.Rancher.Defaults = config.Defaults{ + Network: config.NetworkConfig{ + DNS: config.DNSConfig{ + Nameservers: []string{ + "8.8.8.8", + "8.8.4.4", + }, + }, + }, + } + recoveryConfig.Rancher.BootstrapDocker = config.DockerConfig{ + EngineOpts: config.EngineOpts{ + Bridge: "none", + StorageDriver: "overlay", + Restart: &[]bool{false}[0], + Graph: "/var/lib/recovery-docker", + Group: "root", + Host: []string{"unix:///var/run/system-docker.sock"}, + UserlandProxy: &[]bool{false}[0], + }, + } + + _, err := startDocker(&recoveryConfig) + if err != nil { + log.Fatal(err) + } + + _, err = config.ChainCfgFuncs(&recoveryConfig, + loadImages, + recoveryServices) + if err != nil { + log.Fatal(err) + } +} diff --git a/scripts/schema.json b/scripts/schema.json index f16d88bb..e882cc22 100644 --- a/scripts/schema.json +++ b/scripts/schema.json @@ -35,6 +35,7 @@ "no_sharedroot": {"type": "boolean"}, "log": {"type": "boolean"}, "force_console_rebuild": {"type": "boolean"}, + "recovery": {"type": "boolean"}, "disable": {"$ref": "#/definitions/list_of_strings"}, "services_include": {"type": "object"}, "modules": {"$ref": "#/definitions/list_of_strings"},