diff --git a/cmd/control/autologin.go b/cmd/control/autologin.go new file mode 100644 index 00000000..483255cc --- /dev/null +++ b/cmd/control/autologin.go @@ -0,0 +1,103 @@ +package control + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/codegangsta/cli" + "github.com/rancher/os/config" + "github.com/rancher/os/log" +) + +func AutologinMain() { + log.InitLogger() + app := cli.NewApp() + + app.Name = os.Args[0] + app.Usage = "autologin console" + app.Version = config.Version + app.Author = "Rancher Labs, Inc." + app.Email = "sven@rancher.com" + app.EnableBashCompletion = true + app.Action = autologinAction + app.HideHelp = true + app.Run(os.Args) +} + +func autologinAction(c *cli.Context) error { + cmd := exec.Command("/bin/stty", "sane") + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + log.Error(err) + } + + usertty := "" + user := "root" + tty := "" + if c.NArg() > 0 { + usertty = c.Args().Get(0) + s := strings.SplitN(usertty, ":", 2) + user = s[0] + if len(s) > 1 { + tty = s[1] + } + } + + mode := filepath.Base(os.Args[0]) + console := CurrentConsole() + + cfg := config.LoadConfig() + // replace \n and \l + banner := config.Banner + banner = strings.Replace(banner, "\\v", config.Version, -1) + banner = strings.Replace(banner, "\\s", "RancherOS "+runtime.GOARCH, -1) + banner = strings.Replace(banner, "\\r", config.GetKernelVersion(), -1) + banner = strings.Replace(banner, "\\n", cfg.Hostname, -1) + banner = strings.Replace(banner, "\\l", tty, -1) + banner = strings.Replace(banner, "\\\\", "\\", -1) + banner = banner + "\n" + banner = banner + "Autologin " + console + "\n" + fmt.Printf(banner) + + loginBin := "" + args := []string{} + if console == "centos" || console == "fedora" || + mode == "recovery" { + // For some reason, centos and fedora ttyS0 and tty1 don't work with `login -f rancher` + // until I make time to read their source, lets just give us a way to get work done + loginBin = "bash" + args = append(args, "--login") + os.Setenv("PROMPT_COMMAND", `echo "[`+fmt.Sprintf("Recovery console %s@%s:${PWD}", user, cfg.Hostname)+`]"`) + } else { + loginBin = "login" + args = append(args, "-f", user) + // TODO: add a PROMPT_COMMAND if we haven't switch-rooted + } + + loginBinPath, err := exec.LookPath(loginBin) + if err != nil { + fmt.Printf("error finding %s in path: %s", cmd.Args[0], err) + return err + } + os.Setenv("TERM", "linux") + + // Causes all sorts of issues + //return syscall.Exec(loginBinPath, args, os.Environ()) + cmd = exec.Command(loginBinPath, args...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "SVEN", "MORE") + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + if err := cmd.Run(); err != nil { + log.Errorf("\nError starting %s: %s", cmd.Args[0], err) + } + return nil +} 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/config.go b/cmd/control/config.go index 91d47f62..b7ebca1e 100644 --- a/cmd/control/config.go +++ b/cmd/control/config.go @@ -225,7 +225,15 @@ func merge(c *cli.Context) error { } if err = config.Merge(bytes); err != nil { - log.Fatal(err) + log.Error(err) + validationErrors, err := config.ValidateBytes(bytes) + if err != nil { + log.Fatal(err) + } + for _, validationError := range validationErrors.Errors() { + log.Error(validationError) + } + log.Fatal("EXITING: Failed to parse configuration") } return nil @@ -255,7 +263,7 @@ func validate(c *cli.Context) error { if err != nil { log.Fatal(err) } - validationErrors, err := config.Validate(bytes) + validationErrors, err := config.ValidateBytes(bytes) if err != nil { log.Fatal(err) } diff --git a/cmd/control/console.go b/cmd/control/console.go index 883c0361..439e3feb 100644 --- a/cmd/control/console.go +++ b/cmd/control/console.go @@ -2,18 +2,19 @@ package control import ( "fmt" - "io/ioutil" "sort" "strings" "golang.org/x/net/context" "github.com/codegangsta/cli" + "github.com/docker/docker/reference" composeConfig "github.com/docker/libcompose/config" "github.com/docker/libcompose/project/options" "github.com/rancher/os/cmd/control/service" "github.com/rancher/os/compose" "github.com/rancher/os/config" + "github.com/rancher/os/docker" "github.com/rancher/os/log" "github.com/rancher/os/util" "github.com/rancher/os/util/network" @@ -57,7 +58,7 @@ func consoleSwitch(c *cli.Context) error { cfg := config.LoadConfig() validateConsole(newConsole, cfg) - if newConsole == currentConsole() { + if newConsole == CurrentConsole() { log.Warnf("Console is already set to %s", newConsole) } @@ -127,10 +128,10 @@ func consoleEnable(c *cli.Context) error { func consoleList(c *cli.Context) error { cfg := config.LoadConfig() consoles := availableConsoles(cfg) - currentConsole := currentConsole() + CurrentConsole := CurrentConsole() for _, console := range consoles { - if console == currentConsole { + if console == CurrentConsole { fmt.Printf("current %s\n", console) } else if console == cfg.Rancher.Console { fmt.Printf("enabled %s\n", console) @@ -159,12 +160,32 @@ func availableConsoles(cfg *config.CloudConfig) []string { return consoles } -func currentConsole() (console string) { - consoleBytes, err := ioutil.ReadFile("/run/console-done") - if err == nil { - console = strings.TrimSpace(string(consoleBytes)) - } else { +// CurrentConsole gets the name of the console that's running +func CurrentConsole() (console string) { + // TODO: replace this docker container look up with a libcompose service lookup? + + // sudo system-docker inspect --format "{{.Config.Image}}" console + client, err := docker.NewSystemClient() + if err != nil { log.Warnf("Failed to detect current console: %v", err) + return } + info, err := client.ContainerInspect(context.Background(), "console") + if err != nil { + log.Warnf("Failed to detect current console: %v", err) + return + } + // parse image name, then remove os- prefix and the console suffix + image, err := reference.ParseNamed(info.Config.Image) + if err != nil { + log.Warnf("Failed to detect current console(%s): %v", info.Config.Image, err) + return + } + + if strings.Contains(image.Name(), "os-console") { + console = "default" + return + } + console = strings.TrimPrefix(strings.TrimSuffix(image.Name(), "console"), "rancher/os-") return } diff --git a/cmd/control/console_init.go b/cmd/control/console_init.go index a529ad9a..0a4ea41c 100644 --- a/cmd/control/console_init.go +++ b/cmd/control/console_init.go @@ -11,7 +11,6 @@ import ( "strings" "syscall" - "github.com/SvenDowideit/cpuid" "github.com/codegangsta/cli" "github.com/rancher/os/cmd/cloudinitexecute" "github.com/rancher/os/config" @@ -82,7 +81,7 @@ func consoleInitFunc() error { log.Error(err) } - if err := writeRespawn(); err != nil { + if err := writeRespawn("rancher", cfg.Rancher.SSH.Daemon, false); err != nil { log.Error(err) } @@ -109,17 +108,7 @@ func consoleInitFunc() error { } // font backslashes need to be escaped for when issue is output! (but not the others..) - if err := ioutil.WriteFile("/etc/issue", []byte(` - , , ______ _ _____ _____TM - ,------------|'------'| | ___ \\ | | / _ / ___| - / . '-' |- | |_/ /__ _ _ __ ___| |__ ___ _ __ | | | \\ '--. - \\/| | | | // _' | '_ \\ / __| '_ \\ / _ \\ '__' | | | |'--. \\ - | .________.'----' | |\\ \\ (_| | | | | (__| | | | __/ | | \\_/ /\\__/ / - | | | | \\_| \\_\\__,_|_| |_|\\___|_| |_|\\___|_| \\___/\\____/ - \\___/ \\___/ \s \r - - RancherOS `+config.Version+` \n \l `+cpuid.CPU.HypervisorName+` - `), 0644); err != nil { + if err := ioutil.WriteFile("/etc/issue", []byte(config.Banner), 0644); err != nil { log.Error(err) } @@ -137,7 +126,7 @@ func consoleInitFunc() error { log.Error(err) } - if err := ioutil.WriteFile(consoleDone, []byte(cfg.Rancher.Console), 0644); err != nil { + if err := ioutil.WriteFile(consoleDone, []byte(CurrentConsole()), 0644); err != nil { log.Error(err) } @@ -155,15 +144,20 @@ 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, recovery bool) string { var respawnConf bytes.Buffer + autologinBin := "/usr/bin/autologin" + if recovery { + autologinBin = "/usr/bin/recovery" + } + for i := 1; i < 7; i++ { tty := fmt.Sprintf("tty%d", i) respawnConf.WriteString(gettyCmd) if strings.Contains(cmdline, fmt.Sprintf("rancher.autologin=%s", tty)) { - respawnConf.WriteString(" --autologin rancher") + respawnConf.WriteString(fmt.Sprintf(" -n -l %s -o %s:tty%d", autologinBin, user, i)) } respawnConf.WriteString(fmt.Sprintf(" --noclear %s linux\n", tty)) } @@ -175,23 +169,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(" -n -l %s -o %s:%s", autologinBin, user, tty)) } 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, recovery bool) error { cmdline, err := ioutil.ReadFile("/proc/cmdline") if err != nil { return err } - respawn := generateRespawnConf(string(cmdline)) + respawn := generateRespawnConf(string(cmdline), user, sshd, recovery) files, err := ioutil.ReadDir("/etc/respawn.conf.d") if err == nil { diff --git a/cmd/control/docker_init.go b/cmd/control/docker_init.go index 44c48ec0..ea630c7f 100644 --- a/cmd/control/docker_init.go +++ b/cmd/control/docker_init.go @@ -10,7 +10,6 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/rancher/os/config" "github.com/rancher/os/log" "github.com/rancher/os/util" ) @@ -22,6 +21,7 @@ const ( ) func dockerInitAction(c *cli.Context) error { + // TODO: this should be replaced by a "Console ready event watcher" for { if _, err := os.Stat(consoleDone); err == nil { break @@ -69,9 +69,8 @@ func dockerInitAction(c *cli.Context) error { fmt.Sprintf(`[ -e %s ] && source %s; exec /usr/bin/dockerlaunch %s %s $DOCKER_OPTS >> %s 2>&1`, dockerConf, dockerConf, dockerBin, strings.Join(c.Args(), " "), dockerLog), } - cfg := config.LoadConfig() - - if err := ioutil.WriteFile(dockerDone, []byte(cfg.Rancher.Docker.Engine), 0644); err != nil { + // TODO: this should be replaced by a "Docker ready event watcher" + if err := ioutil.WriteFile(dockerDone, []byte(CurrentEngine()), 0644); err != nil { log.Error(err) } diff --git a/cmd/control/engine.go b/cmd/control/engine.go index 4d161148..486b938c 100644 --- a/cmd/control/engine.go +++ b/cmd/control/engine.go @@ -2,17 +2,18 @@ package control import ( "fmt" - "io/ioutil" "sort" "strings" "golang.org/x/net/context" "github.com/codegangsta/cli" + "github.com/docker/docker/reference" "github.com/docker/libcompose/project/options" "github.com/rancher/os/cmd/control/service" "github.com/rancher/os/compose" "github.com/rancher/os/config" + "github.com/rancher/os/docker" "github.com/rancher/os/log" "github.com/rancher/os/util" "github.com/rancher/os/util/network" @@ -104,7 +105,7 @@ func engineEnable(c *cli.Context) error { func engineList(c *cli.Context) error { cfg := config.LoadConfig() engines := availableEngines(cfg) - currentEngine := currentEngine() + currentEngine := CurrentEngine() for _, engine := range engines { if engine == currentEngine { @@ -135,12 +136,33 @@ func availableEngines(cfg *config.CloudConfig) []string { return engines } -func currentEngine() (engine string) { - engineBytes, err := ioutil.ReadFile(dockerDone) - if err == nil { - engine = strings.TrimSpace(string(engineBytes)) - } else { - log.Warnf("Failed to detect current Docker engine: %v", err) +// CurrentEngine gets the name of the docker that's running +func CurrentEngine() (engine string) { + // sudo system-docker inspect --format "{{.Config.Image}}" docker + client, err := docker.NewSystemClient() + if err != nil { + log.Warnf("Failed to detect current docker: %v", err) + return } + info, err := client.ContainerInspect(context.Background(), "docker") + if err != nil { + log.Warnf("Failed to detect current docker: %v", err) + return + } + // parse image name, then remove os- prefix and the engine suffix + image, err := reference.ParseNamed(info.Config.Image) + if err != nil { + log.Warnf("Failed to detect current docker(%s): %v", info.Config.Image, err) + return + } + if t, ok := image.(reference.NamedTagged); ok { + tag := t.Tag() + if !strings.HasPrefix(tag, "1.") { + // TODO: this assumes we only do Docker ce :/ + tag = tag + "-ce" + } + return "docker-" + tag + } + return } diff --git a/cmd/control/entrypoint.go b/cmd/control/entrypoint.go index fa23b49f..5f700661 100644 --- a/cmd/control/entrypoint.go +++ b/cmd/control/entrypoint.go @@ -79,6 +79,8 @@ func writeFiles(cfg *config.CloudConfig) error { func setupCommandSymlinks() { for _, link := range []symlink{ + {config.RosBin, "/usr/bin/autologin"}, + {config.RosBin, "/usr/bin/recovery"}, {config.RosBin, "/usr/bin/cloud-init-execute"}, {config.RosBin, "/usr/bin/cloud-init-save"}, {config.RosBin, "/usr/bin/dockerlaunch"}, diff --git a/cmd/control/recovery_init.go b/cmd/control/recovery_init.go new file mode 100644 index 00000000..b927a6db --- /dev/null +++ b/cmd/control/recovery_init.go @@ -0,0 +1,23 @@ +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, true); err != nil { + log.Error(err) + } + + 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/config.go b/config/config.go index 259e7f0b..a6a8c05c 100644 --- a/config/config.go +++ b/config/config.go @@ -1,10 +1,25 @@ package config import ( + "io/ioutil" + "strings" + yaml "github.com/cloudfoundry-incubator/candiedyaml" "github.com/rancher/os/util" ) +const Banner = ` + , , ______ _ _____ _____TM + ,------------|'------'| | ___ \\ | | / _ / ___| + / . '-' |- | |_/ /__ _ _ __ ___| |__ ___ _ __ | | | \\ '--. + \\/| | | | // _' | '_ \\ / __| '_ \\ / _ \\ '__' | | | |'--. \\ + | .________.'----' | |\\ \\ (_| | | | | (__| | | | __/ | | \\_/ /\\__/ / + | | | | \\_| \\_\\__,_|_| |_|\\___|_| |_|\\___|_| \\___/\\____/ + \\___/ \\___/ \s \r + + RancherOS \v \n \l + ` + func Merge(bytes []byte) error { data, err := readConfigs(bytes, false, true) if err != nil { @@ -60,3 +75,12 @@ func Set(key string, value interface{}) error { return WriteToFile(modified, CloudConfigFile) } + +func GetKernelVersion() string { + b, err := ioutil.ReadFile("/proc/version") + if err != nil { + return "" + } + elem := strings.Split(string(b), " ") + return elem[2] +} diff --git a/config/disk.go b/config/disk.go index 31df5a7f..9b351c29 100755 --- a/config/disk.go +++ b/config/disk.go @@ -71,8 +71,19 @@ func LoadConfigWithPrefix(dirPrefix string) *CloudConfig { cfg := &CloudConfig{} if err := util.Convert(rawCfg, cfg); err != nil { - log.Errorf("Failed to parse configuration: %s", err) + log.Errorf("EXITING: Failed to parse configuration: %s", err) log.Debugf("Bad cfg:\n%v\n", rawCfg) + // no point returning {}, it'll just sit there broken + // TODO: print some context around what failed.. + validationErrors, err := ValidateRawCfg(rawCfg) + if err != nil { + log.Fatal(err) + } + for _, validationError := range validationErrors.Errors() { + log.Error(validationError) + } + // TODO: I'd love to panic & recover(), for issues on boot, but it doesn't work yet + os.Exit(-1) return &CloudConfig{} } cfg = amendNils(cfg) diff --git a/config/schema.go b/config/schema.go index 0876f1e7..5d0bd2b5 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"}, @@ -144,7 +145,8 @@ var schema = `{ "additionalProperties": false, "properties": { - "keys": {"type": "object"} + "keys": {"type": "object"}, + "daemon": {"type": "boolean"} } }, diff --git a/config/types.go b/config/types.go index 683c319c..9b7c41cb 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"` @@ -174,7 +175,8 @@ type DockerConfig struct { } type SSHConfig struct { - Keys map[string]string `yaml:"keys,omitempty"` + Keys map[string]string `yaml:"keys,omitempty"` + Daemon bool `yaml:"daemon,omitempty"` } type StateConfig struct { diff --git a/config/validate.go b/config/validate.go index 142ec923..7f2c243b 100644 --- a/config/validate.go +++ b/config/validate.go @@ -31,11 +31,15 @@ func ConvertKeysToStrings(item interface{}) interface{} { } } -func Validate(bytes []byte) (*gojsonschema.Result, error) { +func ValidateBytes(bytes []byte) (*gojsonschema.Result, error) { var rawCfg map[string]interface{} if err := yaml.Unmarshal([]byte(bytes), &rawCfg); err != nil { return nil, err } + return ValidateRawCfg(rawCfg) +} + +func ValidateRawCfg(rawCfg interface{}) (*gojsonschema.Result, error) { rawCfg = ConvertKeysToStrings(rawCfg).(map[string]interface{}) loader := gojsonschema.NewGoLoader(rawCfg) schemaLoader := gojsonschema.NewStringLoader(schema) diff --git a/docker/env.go b/docker/env.go index 953d691b..bbb9724c 100644 --- a/docker/env.go +++ b/docker/env.go @@ -2,7 +2,6 @@ package docker import ( "fmt" - "io/ioutil" "strings" composeConfig "github.com/docker/libcompose/config" @@ -43,11 +42,9 @@ func environmentFromCloudConfig(cfg *config.CloudConfig) map[string]string { environment["no_proxy"] = cfg.Rancher.Network.NoProxy environment["NO_PROXY"] = cfg.Rancher.Network.NoProxy } - b, err := ioutil.ReadFile("/proc/version") - if err == nil { - elem := strings.Split(string(b), " ") - environment["KERNEL_VERSION"] = elem[2] - log.Debugf("Using /proc/version to set rancher.environment.KERNEL_VERSION = %s", elem[2]) + if v := config.GetKernelVersion(); v != "" { + environment["KERNEL_VERSION"] = v + log.Debugf("Using /proc/version to set rancher.environment.KERNEL_VERSION = %s", v) } return environment } diff --git a/docs/os/configuration/adding-kernel-parameters/index.md b/docs/os/configuration/adding-kernel-parameters/index.md index 35a3bcbd..a3dc3484 100644 --- a/docs/os/configuration/adding-kernel-parameters/index.md +++ b/docs/os/configuration/adding-kernel-parameters/index.md @@ -4,9 +4,11 @@ layout: os-default --- -## Kernel parameters +## Kernel boot parameters -There are two ways to edit the kernel parameters, in-place (editing the file and reboot) or during installation to disk. +RancherOS parses the Linux kernel boot cmdline to add any keys it understands to its configuration. This allows you to modify what cloud-init sources it will use on boot, to enable `rancher.debug` logging, or to almost any other configuration setting. + +There are two ways to set or modify persistent kernel parameters, in-place (editing the file and reboot) or during installation to disk. ### In-place editing @@ -14,10 +16,6 @@ To edit the kernel boot parameters of an already installed RancherOS system, use > To activate this setting, you will need to reboot. -#### Graphical boot screen - -RancherOS v1.1.0 added a syslinux boot menu, which on desktop systems can be switched to graphical mode by adding `UI vesamenu.c32` to a new line in `global.cfg` (use `sudo ros config syslinux` to edit the file). - ### During installation If you want to set the extra kernel parameters when you are [Installing RancherOS to Disk]({{site.baseurl}}/os/running-rancheros/server/install-to-disk/) please use the `--append` parameter. @@ -25,3 +23,28 @@ If you want to set the extra kernel parameters when you are [Installing RancherO ```bash $ sudo ros install -d /dev/sda --append "rancheros.autologin=tty1" ``` + +### Graphical boot screen + +RancherOS v1.1.0 added a Syslinux boot menu, which allows you to temporarily edit the boot paramters, or to select "Debug logging", "Autologin", both "Debug logging & Autologin" and "Recovery Console". + + +On desktop systems the Syslinux boot menu can be switched to graphical mode by adding `UI vesamenu.c32` to a new line in `global.cfg` (use `sudo ros config syslinux` to edit the file). + +### Useful RancherOS cloud-init or boot settings + +#### Recovery console + +`rancher.recovery=true` will start a single user `root` bash session as easily in the boot process, with no network, or persitent filesystem mounted. This can be used to fix disk problems, or to debug your system. + +#### Enable/Disable sshd + +`rancher.ssh.daemon=false` (its enabled in the os-config) can be used to start your RancherOS with no sshd daemon. This can be used to futher reduce the ports that your system is listening on. + +#### Enable debug logging + +`rancher.debug=true` will log everything to the console for debugging. + +#### Autologin console + +`rancher.autologin=` will automatically log in the sepcified console - common values are `tty1`, `ttyS0` and `ttyAMA0` - depending on your platform. diff --git a/init/init.go b/init/init.go index 3b3444b3..5e83f2bf 100755 --- a/init/init.go +++ b/init/init.go @@ -94,6 +94,13 @@ func sysInit(c *config.CloudConfig) (*config.CloudConfig, error) { func MainInit() { log.InitDeferedLogger() + // TODO: this breaks and does nothing if the cfg is invalid (or is it due to threading?) + defer func() { + if r := recover(); r != nil { + fmt.Printf("Starting Recovery console: %v\n", r) + recovery(nil) + } + }() if err := RunInit(); err != nil { log.Fatal(err) @@ -263,6 +270,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 +424,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 +435,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..654d42d0 --- /dev/null +++ b/init/recovery.go @@ -0,0 +1,96 @@ +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" + "github.com/rancher/os/netconf" +) + +var ( + + // TODO: move this into the os-config file so it can be customised. + 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", + "/var/run:/var/run", + }, + } +) + +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: netconf.NetworkConfig{ + DNS: netconf.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, + []config.CfgFuncData{ + config.CfgFuncData{"loadImages", loadImages}, + config.CfgFuncData{"recovery console", recoveryServices}, + }) + if err != nil { + log.Fatal(err) + } +} diff --git a/main.go b/main.go index 0e24e386..ea0ea804 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ import ( ) var entrypoints = map[string]func(){ + "autologin": control.AutologinMain, "cloud-init-execute": cloudinitexecute.Main, "cloud-init-save": cloudinitsave.Main, "console": control.ConsoleInitMain, @@ -31,6 +32,7 @@ var entrypoints = map[string]func(){ "dockerlaunch": dfs.Main, "init": osInit.MainInit, "netconf": network.Main, + "recovery": control.AutologinMain, "ros-sysinit": sysinit.Main, "system-docker": systemdocker.Main, "wait-for-docker": wait.Main, diff --git a/os-config.tpl.yml b/os-config.tpl.yml index eece9d67..5c7c4a64 100644 --- a/os-config.tpl.yml +++ b/os-config.tpl.yml @@ -14,6 +14,8 @@ rancher: network: dns: nameservers: [8.8.8.8, 8.8.4.4] + ssh: + daemon: true bootstrap: bootstrap: image: {{.OS_REPO}}/os-bootstrap:{{.VERSION}}{{.SUFFIX}} diff --git a/scripts/isolinux.cfg b/scripts/isolinux.cfg index 627f72d0..48358f96 100644 --- a/scripts/isolinux.cfg +++ b/scripts/isolinux.cfg @@ -1,6 +1,6 @@ # Add `UI vesamenu.c32` to a new line in `global.cfg` to switch to GUI bootmenu (use `sudo ros config syslinux`) UI menu.c32 -TIMEOUT 20 #2s +TIMEOUT 30 #3s PROMPT 0 # doesn't appear to work here? diff --git a/scripts/isolinux_label.cfg b/scripts/isolinux_label.cfg index c4d8ae63..fb8ba440 100644 --- a/scripts/isolinux_label.cfg +++ b/scripts/isolinux_label.cfg @@ -9,23 +9,30 @@ LABEL rancheros-${LABEL} LABEL rancheros-${LABEL}-autologin SAY rancheros-${LABEL}-autologin: autologin RancherOS ${VERSION} ${KERNEL_VERSION} - MENU LABEL rancher.autologin + MENU LABEL Autologin on tty1 and ttyS0 MENU INDENT 2 COM32 cmd.c32 APPEND rancheros-${LABEL} rancher.autologin=tty1 rancher.autologin=ttyS0 LABEL rancheros-${LABEL}-debug SAY rancheros-${LABEL}-debug: debug RancherOS ${VERSION} ${KERNEL_VERSION} - MENU LABEL rancher.debug=true + MENU LABEL Debug logging MENU INDENT 2 COM32 cmd.c32 APPEND rancheros-${LABEL} rancher.debug=true LABEL rancheros-${LABEL}-debug-autologin SAY rancheros-${LABEL}-debug-autolgin: debug and autologin RancherOS ${VERSION} ${KERNEL_VERSION} - MENU LABEL rancher.debug and rancher.autologin + MENU LABEL Autologin on tty1 and ttyS0 plus Debug logging MENU INDENT 2 COM32 cmd.c32 APPEND rancheros-${LABEL} rancher.autologin=tty1 rancher.autologin=ttyS0 rancher.debug=true +LABEL rancheros-${LABEL}-recovery + SAY rancheros-${LABEL}-recovery: recovery console RancherOS ${VERSION} ${KERNEL_VERSION} + MENU LABEL Recovery console + MENU INDENT 2 + COM32 cmd.c32 + APPEND rancheros-${LABEL} rancher.recovery=true + MENU SEPARATOR diff --git a/scripts/run b/scripts/run index f50d6c67..29944846 100755 --- a/scripts/run +++ b/scripts/run @@ -171,7 +171,7 @@ if [ "$APPEND_INIT" != "" ]; then fi if [ "$BOOT_PXE" == "1" ]; then - KERNEL_ARGS="console=tty1 rancher.console=tty1 rancher.autologin=tty1 ${KERNEL_ARGS}" + KERNEL_ARGS="console=tty1 rancher.autologin=tty1 ${KERNEL_ARGS}" set -ex PIXIECORE=$(which pixiecore) sudo -E $PIXIECORE boot \ @@ -311,6 +311,7 @@ if [ "$QEMU" == "1" ]; then $(eval "${hd["$ARCH"]} ${HD}") \ ${SECOND_DRIVE_ENABLE} \ -smp 1 \ + -device virtio-rng-pci \ ${CLOUD_CONFIG_DISK} \ -fsdev local,security_model=none,id=fsdev1,path=${HOME} \ -device virtio-9p-pci,id=fs1,fsdev=fsdev1,mount_tag=home \ @@ -338,6 +339,7 @@ elif [ "$BOOT_ISO" == "1" ] || $(eval "${hd["$ARCH"]} ${HD}") \ ${SECOND_DRIVE_ENABLE} \ -smp 1 \ + -device virtio-rng-pci \ ${ISO_OPTS} \ "${@}" elif [ "$QIND" == "1" ]; then diff --git a/scripts/run-common b/scripts/run-common index d4f45093..03cc84ad 100755 --- a/scripts/run-common +++ b/scripts/run-common @@ -52,4 +52,4 @@ if [ "$ENGINE_REGISTRY_MIRROR" != "" ]; then REGISTRY_MIRROR="rancher.bootstrap_docker.registry_mirror=${ENGINE_REGISTRY_MIRROR} rancher.system_docker.registry_mirror=${ENGINE_REGISTRY_MIRROR} rancher.docker.registry_mirror=${ENGINE_REGISTRY_MIRROR}" fi -DEFAULT_KERNEL_ARGS="printk.devkmsg=on rancher.debug=true rancher.password=rancher console=${TTYCONS} rancher.autologin=${TTYCONS} ${REGISTRY_MIRROR} " +DEFAULT_KERNEL_ARGS="printk.devkmsg=on rancher.debug=true rancher.password=rancher console=tty1 rancher.autologin=tty1 console=${TTYCONS} rancher.autologin=${TTYCONS} ${REGISTRY_MIRROR} " diff --git a/scripts/schema.json b/scripts/schema.json index f16d88bb..8f11bb6d 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"}, @@ -142,7 +143,8 @@ "additionalProperties": false, "properties": { - "keys": {"type": "object"} + "keys": {"type": "object"}, + "daemon": {"type": "boolean"} } },