install.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. package control
  2. import (
  3. "bufio"
  4. "bytes"
  5. "crypto/md5"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "runtime"
  13. "strconv"
  14. "strings"
  15. "github.com/rancher/os/log"
  16. "github.com/codegangsta/cli"
  17. "github.com/rancher/catalog-service/utils/version"
  18. "github.com/rancher/os/cmd/control/install"
  19. "github.com/rancher/os/cmd/power"
  20. "github.com/rancher/os/config"
  21. "github.com/rancher/os/dfs" // TODO: move CopyFile into util or something.
  22. "github.com/rancher/os/util"
  23. )
  24. var installCommand = cli.Command{
  25. Name: "install",
  26. Usage: "install RancherOS to disk",
  27. HideHelp: true,
  28. Action: installAction,
  29. Flags: []cli.Flag{
  30. cli.StringFlag{
  31. // TODO: need to validate ? -i rancher/os:v0.3.1 just sat there.
  32. Name: "image, i",
  33. Usage: `install from a certain image (e.g., 'rancher/os:v0.7.0')
  34. use 'ros os list' to see what versions are available.`,
  35. },
  36. cli.StringFlag{
  37. Name: "install-type, t",
  38. Usage: `generic: (Default) Creates 1 ext4 partition and installs RancherOS (syslinux)
  39. amazon-ebs: Installs RancherOS and sets up PV-GRUB
  40. gptsyslinux: partition and format disk (gpt), then install RancherOS and setup Syslinux
  41. `,
  42. },
  43. cli.StringFlag{
  44. Name: "cloud-config, c",
  45. Usage: "cloud-config yml file - needed for SSH authorized keys",
  46. },
  47. cli.StringFlag{
  48. Name: "device, d",
  49. Usage: "storage device",
  50. },
  51. cli.StringFlag{
  52. Name: "partition, p",
  53. Usage: "partition to install to",
  54. },
  55. cli.StringFlag{
  56. Name: "statedir",
  57. Usage: "install to rancher.state.directory",
  58. },
  59. cli.BoolFlag{
  60. Name: "force, f",
  61. Usage: "[ DANGEROUS! Data loss can happen ] partition/format without prompting",
  62. },
  63. cli.BoolFlag{
  64. Name: "no-reboot",
  65. Usage: "do not reboot after install",
  66. },
  67. cli.StringFlag{
  68. Name: "append, a",
  69. Usage: "append additional kernel parameters",
  70. },
  71. cli.StringFlag{
  72. Name: "rollback, r",
  73. Usage: "rollback version",
  74. Hidden: true,
  75. },
  76. cli.BoolFlag{
  77. Name: "isoinstallerloaded",
  78. Usage: "INTERNAL use only: mount the iso to get kernel and initrd",
  79. Hidden: true,
  80. },
  81. cli.BoolFlag{
  82. Name: "kexec, k",
  83. Usage: "reboot using kexec",
  84. },
  85. cli.BoolFlag{
  86. Name: "debug",
  87. Usage: "Run installer with debug output",
  88. },
  89. },
  90. }
  91. func installAction(c *cli.Context) error {
  92. if runtime.GOARCH != "amd64" {
  93. log.Fatalf("ros install / upgrade only supported on 'amd64', not '%s'", runtime.GOARCH)
  94. }
  95. if c.Args().Present() {
  96. log.Fatalf("invalid arguments %v", c.Args())
  97. }
  98. debug := c.Bool("debug")
  99. if debug {
  100. originalLevel := log.GetLevel()
  101. defer log.SetLevel(originalLevel)
  102. log.SetLevel(log.DebugLevel)
  103. }
  104. kappend := strings.TrimSpace(c.String("append"))
  105. force := c.Bool("force")
  106. kexec := c.Bool("kexec")
  107. reboot := !c.Bool("no-reboot")
  108. isoinstallerloaded := c.Bool("isoinstallerloaded")
  109. image := c.String("image")
  110. cfg := config.LoadConfig()
  111. if image == "" {
  112. image = cfg.Rancher.Upgrade.Image + ":" + config.Version + config.Suffix
  113. }
  114. installType := c.String("install-type")
  115. if installType == "" {
  116. log.Info("No install type specified...defaulting to generic")
  117. installType = "generic"
  118. }
  119. if installType == "rancher-upgrade" ||
  120. installType == "upgrade" {
  121. installType = "upgrade" // rancher-upgrade is redundant!
  122. force = true // the os.go upgrade code already asks
  123. reboot = false
  124. isoinstallerloaded = true // OMG this flag is aweful - kill it with fire
  125. }
  126. device := c.String("device")
  127. partition := c.String("partition")
  128. statedir := c.String("statedir")
  129. if statedir != "" && installType != "noformat" {
  130. log.Fatal("--statedir %s requires --type noformat", statedir)
  131. }
  132. if installType != "noformat" &&
  133. installType != "raid" &&
  134. installType != "bootstrap" &&
  135. installType != "upgrade" {
  136. // These can use RANCHER_BOOT or RANCHER_STATE labels..
  137. if device == "" {
  138. log.Fatal("Can not proceed without -d <dev> specified")
  139. }
  140. }
  141. cloudConfig := c.String("cloud-config")
  142. if cloudConfig == "" {
  143. if installType != "upgrade" {
  144. // TODO: I wonder if its plausible to merge a new cloud-config into an existing one on upgrade - so for now, i'm only turning off the warning
  145. log.Warn("Cloud-config not provided: you might need to provide cloud-config on boot with ssh_authorized_keys")
  146. }
  147. } else {
  148. os.MkdirAll("/opt", 0755)
  149. uc := "/opt/user_config.yml"
  150. if err := util.FileCopy(cloudConfig, uc); err != nil {
  151. log.WithFields(log.Fields{"cloudConfig": cloudConfig, "error": err}).Fatal("Failed to copy cloud-config")
  152. }
  153. cloudConfig = uc
  154. }
  155. if err := runInstall(image, installType, cloudConfig, device, partition, statedir, kappend, force, kexec, isoinstallerloaded, debug); err != nil {
  156. log.WithFields(log.Fields{"err": err}).Fatal("Failed to run install")
  157. return err
  158. }
  159. if !kexec && reboot && (force || yes("Continue with reboot")) {
  160. log.Info("Rebooting")
  161. power.Reboot()
  162. }
  163. return nil
  164. }
  165. func runInstall(image, installType, cloudConfig, device, partition, statedir, kappend string, force, kexec, isoinstallerloaded, debug bool) error {
  166. fmt.Printf("Installing from %s\n", image)
  167. if !force {
  168. if util.IsRunningInTty() && !yes("Continue") {
  169. log.Infof("Not continuing with installation due to user not saying 'yes'")
  170. os.Exit(1)
  171. }
  172. }
  173. diskType := "msdos"
  174. if installType == "gptsyslinux" {
  175. diskType = "gpt"
  176. }
  177. // Versions before 0.8.0-rc3 use the old calling convention (from the lay-down-os shell script)
  178. imageVersion := strings.TrimPrefix(image, "rancher/os:")
  179. if version.GreaterThan("v0.8.0-rc3", imageVersion) {
  180. log.Infof("user specified to install pre v0.8.0: %s", image)
  181. imageVersion = strings.Replace(imageVersion, "-", ".", -1)
  182. vArray := strings.Split(imageVersion, ".")
  183. if len(vArray) >= 2 {
  184. v, _ := strconv.ParseFloat(vArray[0]+"."+vArray[1], 32)
  185. if v < 0.8 || imageVersion == "0.8.0-rc1" {
  186. log.Infof("starting installer container for %s", image)
  187. if installType == "generic" ||
  188. installType == "syslinux" ||
  189. installType == "gptsyslinux" {
  190. cmd := exec.Command("system-docker", "run", "--net=host", "--privileged", "--volumes-from=all-volumes",
  191. "--entrypoint=/scripts/set-disk-partitions", image, device, diskType)
  192. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  193. if err := cmd.Run(); err != nil {
  194. return err
  195. }
  196. }
  197. cmd := exec.Command("system-docker", "run", "--net=host", "--privileged", "--volumes-from=user-volumes",
  198. "--volumes-from=command-volumes", image, "-d", device, "-t", installType, "-c", cloudConfig,
  199. "-a", kappend)
  200. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  201. return cmd.Run()
  202. }
  203. }
  204. }
  205. if _, err := os.Stat("/usr/bin/system-docker"); os.IsNotExist(err) {
  206. if err := os.Symlink("/usr/bin/ros", "/usr/bin/system-docker"); err != nil {
  207. log.Errorf("ln error %s", err)
  208. }
  209. }
  210. useIso := false
  211. // --isoinstallerloaded is used if the ros has created the installer container from and image that was on the booted iso
  212. if !isoinstallerloaded {
  213. log.Infof("start !isoinstallerloaded")
  214. if _, err := os.Stat("/dist/initrd-" + config.Version); os.IsNotExist(err) {
  215. if err = mountBootIso(); err != nil {
  216. log.Debugf("mountBootIso error %s", err)
  217. } else {
  218. log.Infof("trying to load /bootiso/rancheros/installer.tar.gz")
  219. if _, err := os.Stat("/bootiso/rancheros/"); err == nil {
  220. cmd := exec.Command("system-docker", "load", "-i", "/bootiso/rancheros/installer.tar.gz")
  221. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  222. if err := cmd.Run(); err != nil {
  223. log.Infof("failed to load images from /bootiso/rancheros: %s", err)
  224. } else {
  225. log.Infof("Loaded images from /bootiso/rancheros/installer.tar.gz")
  226. //TODO: add if os-installer:latest exists - we might have loaded a full installer?
  227. useIso = true
  228. // now use the installer image
  229. cfg := config.LoadConfig()
  230. if image == cfg.Rancher.Upgrade.Image+":"+config.Version+config.Suffix {
  231. // TODO: fix the fullinstaller Dockerfile to use the ${VERSION}${SUFFIX}
  232. image = cfg.Rancher.Upgrade.Image + "-installer" + ":latest"
  233. }
  234. }
  235. }
  236. // TODO: also poke around looking for the /boot/vmlinuz and initrd...
  237. }
  238. log.Infof("starting installer container for %s (new)", image)
  239. installerCmd := []string{
  240. "run", "--rm", "--net=host", "--privileged",
  241. // bind mount host fs to access its ros, vmlinuz, initrd and /dev (udev isn't running in container)
  242. "-v", "/:/host",
  243. "--volumes-from=all-volumes",
  244. image,
  245. // "install",
  246. "-t", installType,
  247. "-d", device,
  248. "-i", image, // TODO: this isn't used - I'm just using it to over-ride the defaulting
  249. }
  250. // Need to call the inner container with force - the outer one does the "are you sure"
  251. installerCmd = append(installerCmd, "-f")
  252. // The outer container does the reboot (if needed)
  253. installerCmd = append(installerCmd, "--no-reboot")
  254. if cloudConfig != "" {
  255. installerCmd = append(installerCmd, "-c", cloudConfig)
  256. }
  257. if kappend != "" {
  258. installerCmd = append(installerCmd, "-a", kappend)
  259. }
  260. if useIso {
  261. installerCmd = append(installerCmd, "--isoinstallerloaded=1")
  262. }
  263. if kexec {
  264. installerCmd = append(installerCmd, "--kexec")
  265. }
  266. if debug {
  267. installerCmd = append(installerCmd, "--debug")
  268. }
  269. if partition != "" {
  270. installerCmd = append(installerCmd, "--partition", partition)
  271. }
  272. if statedir != "" {
  273. installerCmd = append(installerCmd, "--statedir", statedir)
  274. }
  275. // TODO: mount at /mnt for shared mount?
  276. if useIso {
  277. util.Unmount("/bootiso")
  278. }
  279. cmd := exec.Command("system-docker", installerCmd...)
  280. log.Debugf("Run(%v)", cmd)
  281. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  282. return cmd.Run()
  283. }
  284. }
  285. // TODO: needs to pass the log level on to the container
  286. log.InitLogger()
  287. log.SetLevel(log.InfoLevel)
  288. log.Debugf("running installation")
  289. if partition == "" {
  290. if installType == "generic" ||
  291. installType == "syslinux" ||
  292. installType == "gptsyslinux" {
  293. diskType := "msdos"
  294. if installType == "gptsyslinux" {
  295. diskType = "gpt"
  296. }
  297. log.Debugf("running setDiskpartitions")
  298. err := setDiskpartitions(device, diskType)
  299. if err != nil {
  300. log.Errorf("error setDiskpartitions %s", err)
  301. return err
  302. }
  303. // use the bind mounted host filesystem to get access to the /dev/vda1 device that udev on the host sets up (TODO: can we run a udevd inside the container? `mknod b 253 1 /dev/vda1` doesn't work)
  304. device = "/host" + device
  305. //# TODO: Change this to a number so that users can specify.
  306. //# Will need to make it so that our builds and packer APIs remain consistent.
  307. partition = device + "1" //${partition:=${device}1}
  308. }
  309. }
  310. if installType == "upgrade" {
  311. isoinstallerloaded = false
  312. }
  313. if isoinstallerloaded {
  314. log.Debugf("running isoinstallerloaded...")
  315. // TODO: detect if its not mounted and then optionally mount?
  316. if err := mountBootIso(); err != nil {
  317. log.Errorf("error mountBootIso %s", err)
  318. //return err
  319. }
  320. }
  321. err := layDownOS(image, installType, cloudConfig, device, partition, statedir, kappend, kexec)
  322. if err != nil {
  323. log.Errorf("error layDownOS %s", err)
  324. return err
  325. }
  326. return nil
  327. }
  328. func mountBootIso() error {
  329. deviceName := "/dev/sr0"
  330. deviceType := "iso9660"
  331. if d, t := util.Blkid("RancherOS"); d != "" {
  332. deviceName = d
  333. deviceType = t
  334. }
  335. mountsFile, err := os.Open("/proc/mounts")
  336. if err != nil {
  337. log.Errorf("failed to read /proc/mounts %s", err)
  338. return err
  339. }
  340. defer mountsFile.Close()
  341. if partitionMounted(deviceName, mountsFile) {
  342. return nil
  343. }
  344. os.MkdirAll("/bootiso", 0755)
  345. cmd := exec.Command("mount", "-t", deviceType, deviceName, "/bootiso")
  346. log.Debugf("mount (%#v)", cmd)
  347. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  348. err = cmd.Run()
  349. if err != nil {
  350. log.Errorf("tried and failed to mount %s: %s", deviceName, err)
  351. } else {
  352. log.Debugf("Mounted %s", deviceName)
  353. }
  354. return err
  355. }
  356. func layDownOS(image, installType, cloudConfig, device, partition, statedir, kappend string, kexec bool) error {
  357. // ENV == installType
  358. //[[ "$ARCH" == "arm" && "$ENV" != "upgrade" ]] && ENV=arm
  359. // image == rancher/os:v0.7.0_arm
  360. // TODO: remove the _arm suffix (but watch out, its not always there..)
  361. VERSION := image[strings.Index(image, ":")+1:]
  362. var FILES []string
  363. DIST := "/dist" //${DIST:-/dist}
  364. //cloudConfig := SCRIPTS_DIR + "/conf/empty.yml" //${cloudConfig:-"${SCRIPTS_DIR}/conf/empty.yml"}
  365. CONSOLE := "tty0"
  366. baseName := "/mnt/new_img"
  367. kernelArgs := "printk.devkmsg=on rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait" // console="+CONSOLE
  368. if statedir != "" {
  369. kernelArgs = kernelArgs + " rancher.state.directory=" + statedir
  370. }
  371. // unmount on trap
  372. defer util.Unmount(baseName)
  373. diskType := "msdos"
  374. if installType == "gptsyslinux" {
  375. diskType = "gpt"
  376. }
  377. switch installType {
  378. case "syslinux":
  379. fallthrough
  380. case "gptsyslinux":
  381. fallthrough
  382. case "generic":
  383. log.Debugf("formatAndMount")
  384. var err error
  385. device, partition, err = formatAndMount(baseName, device, partition)
  386. if err != nil {
  387. log.Errorf("formatAndMount %s", err)
  388. return err
  389. }
  390. err = installSyslinux(device, baseName, diskType)
  391. if err != nil {
  392. log.Errorf("installSyslinux %s", err)
  393. return err
  394. }
  395. err = seedData(baseName, cloudConfig, FILES)
  396. if err != nil {
  397. log.Errorf("seedData %s", err)
  398. return err
  399. }
  400. case "arm":
  401. var err error
  402. device, partition, err = formatAndMount(baseName, device, partition)
  403. if err != nil {
  404. return err
  405. }
  406. seedData(baseName, cloudConfig, FILES)
  407. case "amazon-ebs-pv":
  408. fallthrough
  409. case "amazon-ebs-hvm":
  410. CONSOLE = "ttyS0"
  411. var err error
  412. device, partition, err = formatAndMount(baseName, device, partition)
  413. if err != nil {
  414. return err
  415. }
  416. if installType == "amazon-ebs-hvm" {
  417. installSyslinux(device, baseName, diskType)
  418. }
  419. //# AWS Networking recommends disabling.
  420. seedData(baseName, cloudConfig, FILES)
  421. case "googlecompute":
  422. CONSOLE = "ttyS0"
  423. var err error
  424. device, partition, err = formatAndMount(baseName, device, partition)
  425. if err != nil {
  426. return err
  427. }
  428. installSyslinux(device, baseName, diskType)
  429. seedData(baseName, cloudConfig, FILES)
  430. case "noformat":
  431. var err error
  432. device, partition, err = install.MountDevice(baseName, device, partition, false)
  433. if err != nil {
  434. return err
  435. }
  436. installSyslinux(device, baseName, diskType)
  437. if err := os.MkdirAll(filepath.Join(baseName, statedir), 0755); err != nil {
  438. return err
  439. }
  440. case "raid":
  441. var err error
  442. device, partition, err = install.MountDevice(baseName, device, partition, false)
  443. if err != nil {
  444. return err
  445. }
  446. installSyslinux(device, baseName, diskType)
  447. case "bootstrap":
  448. CONSOLE = "ttyS0"
  449. var err error
  450. device, partition, err = install.MountDevice(baseName, device, partition, true)
  451. if err != nil {
  452. return err
  453. }
  454. kernelArgs = kernelArgs + " rancher.cloud_init.datasources=[ec2,gce]"
  455. case "rancher-upgrade":
  456. installType = "upgrade" // rancher-upgrade is redundant
  457. fallthrough
  458. case "upgrade":
  459. var err error
  460. device, partition, err = install.MountDevice(baseName, device, partition, false)
  461. if err != nil {
  462. return err
  463. }
  464. log.Debugf("upgrading - %s, %s, %s, %s", device, baseName, diskType)
  465. // TODO: detect pv-grub, and don't kill it with syslinux
  466. upgradeBootloader(device, baseName, diskType)
  467. default:
  468. return fmt.Errorf("unexpected install type %s", installType)
  469. }
  470. kernelArgs = kernelArgs + " console=" + CONSOLE
  471. if kappend == "" {
  472. preservedAppend, _ := ioutil.ReadFile(filepath.Join(baseName, install.BootDir+"append"))
  473. kappend = string(preservedAppend)
  474. } else {
  475. ioutil.WriteFile(filepath.Join(baseName, install.BootDir+"append"), []byte(kappend), 0644)
  476. }
  477. if installType == "amazon-ebs-pv" {
  478. menu := install.BootVars{
  479. BaseName: baseName,
  480. BootDir: install.BootDir,
  481. Timeout: 0,
  482. Fallback: 0, // need to be conditional on there being a 'rollback'?
  483. Entries: []install.MenuEntry{
  484. install.MenuEntry{"RancherOS-current", install.BootDir, VERSION, kernelArgs, kappend},
  485. },
  486. }
  487. install.PvGrubConfig(menu)
  488. }
  489. log.Debugf("installRancher")
  490. _, err := installRancher(baseName, VERSION, DIST, kernelArgs+" "+kappend)
  491. if err != nil {
  492. log.Errorf("%s", err)
  493. return err
  494. }
  495. log.Debugf("installRancher done")
  496. if kexec {
  497. power.Kexec(false, filepath.Join(baseName, install.BootDir), kernelArgs+" "+kappend)
  498. }
  499. return nil
  500. }
  501. // files is an array of 'sourcefile:destination' - but i've not seen any examples of it being used.
  502. func seedData(baseName, cloudData string, files []string) error {
  503. log.Debugf("seedData")
  504. _, err := os.Stat(baseName)
  505. if err != nil {
  506. return err
  507. }
  508. if err = os.MkdirAll(filepath.Join(baseName, "/var/lib/rancher/conf/cloud-config.d"), 0700); err != nil {
  509. return err
  510. }
  511. if !strings.HasSuffix(cloudData, "empty.yml") {
  512. if err = dfs.CopyFile(cloudData, baseName+"/var/lib/rancher/conf/cloud-config.d/", filepath.Base(cloudData)); err != nil {
  513. return err
  514. }
  515. }
  516. for _, f := range files {
  517. e := strings.Split(f, ":")
  518. if err = dfs.CopyFile(e[0], baseName, e[1]); err != nil {
  519. return err
  520. }
  521. }
  522. return nil
  523. }
  524. // set-disk-partitions is called with device == **/dev/sda**
  525. func setDiskpartitions(device, diskType string) error {
  526. log.Debugf("setDiskpartitions")
  527. d := strings.Split(device, "/")
  528. if len(d) != 3 {
  529. return fmt.Errorf("bad device name (%s)", device)
  530. }
  531. deviceName := d[2]
  532. file, err := os.Open("/proc/partitions")
  533. if err != nil {
  534. log.Debugf("failed to read /proc/partitions %s", err)
  535. return err
  536. }
  537. defer file.Close()
  538. exists := false
  539. haspartitions := false
  540. scanner := bufio.NewScanner(file)
  541. for scanner.Scan() {
  542. str := scanner.Text()
  543. last := strings.LastIndex(str, " ")
  544. if last > -1 {
  545. dev := str[last+1:]
  546. if strings.HasPrefix(dev, deviceName) {
  547. if dev == deviceName {
  548. exists = true
  549. } else {
  550. haspartitions = true
  551. }
  552. }
  553. }
  554. }
  555. if !exists {
  556. return fmt.Errorf("disk %s not found: %s", device, err)
  557. }
  558. if haspartitions {
  559. log.Debugf("device %s already partitioned - checking if any are mounted", device)
  560. file, err := os.Open("/proc/mounts")
  561. if err != nil {
  562. log.Errorf("failed to read /proc/mounts %s", err)
  563. return err
  564. }
  565. defer file.Close()
  566. if partitionMounted(device, file) {
  567. err = fmt.Errorf("partition %s mounted, cannot repartition", device)
  568. log.Errorf("%s", err)
  569. return err
  570. }
  571. cmd := exec.Command("system-docker", "ps", "-q")
  572. var outb bytes.Buffer
  573. cmd.Stdout = &outb
  574. if err := cmd.Run(); err != nil {
  575. log.Printf("ps error: %s", err)
  576. return err
  577. }
  578. for _, image := range strings.Split(outb.String(), "\n") {
  579. if image == "" {
  580. continue
  581. }
  582. r, w := io.Pipe()
  583. go func() {
  584. // TODO: consider a timeout
  585. // TODO:some of these containers don't have cat / shell
  586. cmd := exec.Command("system-docker", "exec", image, "cat /proc/mount")
  587. cmd.Stdout = w
  588. if err := cmd.Run(); err != nil {
  589. log.Debugf("%s cat %s", image, err)
  590. }
  591. w.Close()
  592. }()
  593. if partitionMounted(device, r) {
  594. err = fmt.Errorf("partition %s mounted in %s, cannot repartition", device, image)
  595. log.Errorf("k? %s", err)
  596. return err
  597. }
  598. }
  599. }
  600. //do it!
  601. log.Debugf("running dd")
  602. cmd := exec.Command("dd", "if=/dev/zero", "of="+device, "bs=512", "count=2048")
  603. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  604. if err := cmd.Run(); err != nil {
  605. log.Errorf("dd error %s", err)
  606. return err
  607. }
  608. log.Debugf("running partprobe")
  609. cmd = exec.Command("partprobe", device)
  610. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  611. if err := cmd.Run(); err != nil {
  612. log.Errorf("partprobe error %s", err)
  613. return err
  614. }
  615. log.Debugf("making single RANCHER_STATE partition")
  616. cmd = exec.Command("parted", "-s", "-a", "optimal", device,
  617. "mklabel "+diskType, "--",
  618. "mkpart primary ext4 1 -1")
  619. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  620. if err := cmd.Run(); err != nil {
  621. log.Errorf("parted: %s", err)
  622. return err
  623. }
  624. return setBootable(device, diskType)
  625. }
  626. func partitionMounted(device string, file io.Reader) bool {
  627. scanner := bufio.NewScanner(file)
  628. for scanner.Scan() {
  629. str := scanner.Text()
  630. // /dev/sdb1 /data ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
  631. ele := strings.Split(str, " ")
  632. if len(ele) > 5 {
  633. if strings.HasPrefix(ele[0], device) {
  634. return true
  635. }
  636. }
  637. if err := scanner.Err(); err != nil {
  638. log.Errorf("scanner %s", err)
  639. return false
  640. }
  641. }
  642. return false
  643. }
  644. func formatdevice(device, partition string) error {
  645. log.Debugf("formatdevice %s", partition)
  646. //mkfs.ext4 -F -i 4096 -L RANCHER_STATE ${partition}
  647. // -O ^64bit: for syslinux: http://www.syslinux.org/wiki/index.php?title=Filesystem#ext
  648. cmd := exec.Command("mkfs.ext4", "-F", "-i", "4096", "-O", "^64bit", "-L", "RANCHER_STATE", partition)
  649. log.Debugf("Run(%v)", cmd)
  650. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  651. if err := cmd.Run(); err != nil {
  652. log.Errorf("mkfs.ext4: %s", err)
  653. return err
  654. }
  655. return nil
  656. }
  657. func formatAndMount(baseName, device, partition string) (string, string, error) {
  658. log.Debugf("formatAndMount")
  659. err := formatdevice(device, partition)
  660. if err != nil {
  661. log.Errorf("formatdevice %s", err)
  662. return device, partition, err
  663. }
  664. device, partition, err = install.MountDevice(baseName, device, partition, false)
  665. if err != nil {
  666. log.Errorf("mountdevice %s", err)
  667. return device, partition, err
  668. }
  669. return device, partition, nil
  670. }
  671. func setBootable(device, diskType string) error {
  672. // TODO make conditional - if there is a bootable device already, don't break it
  673. // TODO: make RANCHER_BOOT bootable - it might not be device 1
  674. bootflag := "boot"
  675. if diskType == "gpt" {
  676. bootflag = "legacy_boot"
  677. }
  678. log.Debugf("making device 1 on %s bootable as %s", device, diskType)
  679. cmd := exec.Command("parted", "-s", "-a", "optimal", device, "set 1 "+bootflag+" on")
  680. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  681. if err := cmd.Run(); err != nil {
  682. log.Errorf("parted: %s", err)
  683. return err
  684. }
  685. return nil
  686. }
  687. func upgradeBootloader(device, baseName, diskType string) error {
  688. log.Debugf("start upgradeBootloader")
  689. grubDir := filepath.Join(baseName, install.BootDir+"grub")
  690. if _, err := os.Stat(grubDir); os.IsNotExist(err) {
  691. log.Debugf("%s does not exist - no need to upgrade bootloader", grubDir)
  692. // we've already upgraded
  693. // TODO: in v0.9.0, need to detect what version syslinux we have
  694. return nil
  695. }
  696. // deal with systems which were previously upgraded, then rolled back, and are now being re-upgraded
  697. grubBackup := filepath.Join(baseName, install.BootDir+"grub_backup")
  698. if err := os.RemoveAll(grubBackup); err != nil {
  699. log.Errorf("RemoveAll (%s): %s", grubBackup, err)
  700. return err
  701. }
  702. backupSyslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux_backup")
  703. if _, err := os.Stat(backupSyslinuxDir); !os.IsNotExist(err) {
  704. backupSyslinuxLdlinuxSys := filepath.Join(backupSyslinuxDir, "ldlinux.sys")
  705. if _, err := os.Stat(backupSyslinuxLdlinuxSys); !os.IsNotExist(err) {
  706. //need a privileged container that can chattr -i ldlinux.sys
  707. cmd := exec.Command("chattr", "-i", backupSyslinuxLdlinuxSys)
  708. if err := cmd.Run(); err != nil {
  709. log.Errorf("%s", err)
  710. return err
  711. }
  712. }
  713. if err := os.RemoveAll(backupSyslinuxDir); err != nil {
  714. log.Errorf("RemoveAll (%s): %s", backupSyslinuxDir, err)
  715. return err
  716. }
  717. }
  718. if err := os.Rename(grubDir, grubBackup); err != nil {
  719. log.Errorf("Rename(%s): %s", grubDir, err)
  720. return err
  721. }
  722. syslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux")
  723. // it seems that v0.5.0 didn't have a syslinux dir, while 0.7 does
  724. if _, err := os.Stat(syslinuxDir); !os.IsNotExist(err) {
  725. if err := os.Rename(syslinuxDir, backupSyslinuxDir); err != nil {
  726. log.Infof("error Rename(%s, %s): %s", syslinuxDir, backupSyslinuxDir, err)
  727. } else {
  728. //mv the old syslinux into linux-previous.cfg
  729. oldSyslinux, err := ioutil.ReadFile(filepath.Join(backupSyslinuxDir, "syslinux.cfg"))
  730. if err != nil {
  731. log.Infof("error read(%s / syslinux.cfg): %s", backupSyslinuxDir, err)
  732. } else {
  733. cfg := string(oldSyslinux)
  734. //DEFAULT RancherOS-current
  735. //
  736. //LABEL RancherOS-current
  737. // LINUX ../vmlinuz-v0.7.1-rancheros
  738. // APPEND rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait console=tty0 rancher.password=rancher
  739. // INITRD ../initrd-v0.7.1-rancheros
  740. cfg = strings.Replace(cfg, "current", "previous", -1)
  741. // TODO consider removing the APPEND line - as the global.cfg should have the same result
  742. ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "linux-current.cfg"), []byte(cfg), 0644)
  743. lines := strings.Split(cfg, "\n")
  744. for _, line := range lines {
  745. line = strings.TrimSpace(line)
  746. if strings.HasPrefix(line, "APPEND") {
  747. log.Errorf("write new (%s) %s", filepath.Join(baseName, install.BootDir, "global.cfg"), err)
  748. // TODO: need to append any extra's the user specified
  749. ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "global.cfg"), []byte(cfg), 0644)
  750. break
  751. }
  752. }
  753. }
  754. }
  755. }
  756. return installSyslinux(device, baseName, diskType)
  757. }
  758. func installSyslinux(device, baseName, diskType string) error {
  759. log.Debugf("installSyslinux(%s)", device)
  760. mbrFile := "mbr.bin"
  761. if diskType == "gpt" {
  762. mbrFile = "gptmbr.bin"
  763. }
  764. //dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=${device}
  765. // ubuntu: /usr/lib/syslinux/mbr/mbr.bin
  766. // alpine: /usr/share/syslinux/mbr.bin
  767. if device == "/dev/" {
  768. log.Debugf("installSyslinuxRaid(%s)", device)
  769. //RAID - assume sda&sdb
  770. //TODO: fix this - not sure how to detect what disks should have mbr - perhaps we need a param
  771. // perhaps just assume and use the devices that make up the raid - mdadm
  772. device = "/dev/sda"
  773. if err := setBootable(device, diskType); err != nil {
  774. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  775. //return err
  776. }
  777. cmd := exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  778. if err := cmd.Run(); err != nil {
  779. log.Errorf("%s", err)
  780. return err
  781. }
  782. device = "/dev/sdb"
  783. if err := setBootable(device, diskType); err != nil {
  784. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  785. //return err
  786. }
  787. cmd = exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  788. if err := cmd.Run(); err != nil {
  789. log.Errorf("%s", err)
  790. return err
  791. }
  792. } else {
  793. if err := setBootable(device, diskType); err != nil {
  794. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  795. //return err
  796. }
  797. log.Debugf("installSyslinux(%s)", device)
  798. cmd := exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  799. log.Debugf("Run(%v)", cmd)
  800. if err := cmd.Run(); err != nil {
  801. log.Errorf("dd: %s", err)
  802. return err
  803. }
  804. }
  805. sysLinuxDir := filepath.Join(baseName, install.BootDir, "syslinux")
  806. if err := os.MkdirAll(sysLinuxDir, 0755); err != nil {
  807. log.Errorf("MkdirAll(%s)): %s", sysLinuxDir, err)
  808. //return err
  809. }
  810. //cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
  811. files, _ := ioutil.ReadDir("/usr/share/syslinux/")
  812. for _, file := range files {
  813. if file.IsDir() {
  814. continue
  815. }
  816. if err := dfs.CopyFile(filepath.Join("/usr/share/syslinux/", file.Name()), sysLinuxDir, file.Name()); err != nil {
  817. log.Errorf("copy syslinux: %s", err)
  818. return err
  819. }
  820. }
  821. //extlinux --install ${baseName}/${bootDir}syslinux
  822. cmd := exec.Command("extlinux", "--install", sysLinuxDir)
  823. if device == "/dev/" {
  824. //extlinux --install --raid ${baseName}/${bootDir}syslinux
  825. cmd = exec.Command("extlinux", "--install", "--raid", sysLinuxDir)
  826. }
  827. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  828. log.Debugf("Run(%v)", cmd)
  829. if err := cmd.Run(); err != nil {
  830. log.Errorf("extlinux: %s", err)
  831. return err
  832. }
  833. return nil
  834. }
  835. func different(existing, new string) bool {
  836. // assume existing file exists
  837. if _, err := os.Stat(new); os.IsNotExist(err) {
  838. return true
  839. }
  840. data, err := ioutil.ReadFile(existing)
  841. if err != nil {
  842. return true
  843. }
  844. newData, err := ioutil.ReadFile(new)
  845. if err != nil {
  846. return true
  847. }
  848. md5sum := md5.Sum(data)
  849. newmd5sum := md5.Sum(newData)
  850. if md5sum != newmd5sum {
  851. return true
  852. }
  853. return false
  854. }
  855. func installRancher(baseName, VERSION, DIST, kappend string) (string, error) {
  856. log.Debugf("installRancher")
  857. // detect if there already is a linux-current.cfg, if so, move it to linux-previous.cfg,
  858. currentCfg := filepath.Join(baseName, install.BootDir, "linux-current.cfg")
  859. if _, err := os.Stat(currentCfg); !os.IsNotExist(err) {
  860. existingCfg := filepath.Join(DIST, "linux-current.cfg")
  861. // only remove previous if there is a change to the current
  862. if different(currentCfg, existingCfg) {
  863. previousCfg := filepath.Join(baseName, install.BootDir, "linux-previous.cfg")
  864. if _, err := os.Stat(previousCfg); !os.IsNotExist(err) {
  865. if err := os.Remove(previousCfg); err != nil {
  866. return currentCfg, err
  867. }
  868. }
  869. os.Rename(currentCfg, previousCfg)
  870. // TODO: now that we're parsing syslinux.cfg files, maybe we can delete old kernels and initrds
  871. }
  872. }
  873. // The image/ISO have all the files in it - the syslinux cfg's and the kernel&initrd, so we can copy them all from there
  874. files, _ := ioutil.ReadDir(DIST)
  875. for _, file := range files {
  876. if file.IsDir() {
  877. continue
  878. }
  879. // TODO: should overwrite anything other than the global.cfg
  880. overwrite := true
  881. if file.Name() == "global.cfg" {
  882. overwrite = false
  883. }
  884. if err := dfs.CopyFileOverwrite(filepath.Join(DIST, file.Name()), filepath.Join(baseName, install.BootDir), file.Name(), overwrite); err != nil {
  885. log.Errorf("copy %s: %s", file.Name(), err)
  886. //return err
  887. }
  888. }
  889. // the general INCLUDE syslinuxcfg
  890. isolinuxFile := filepath.Join(DIST, "isolinux", "isolinux.cfg")
  891. syslinuxDir := filepath.Join(baseName, install.BootDir, "syslinux")
  892. if err := dfs.CopyFileOverwrite(isolinuxFile, syslinuxDir, "syslinux.cfg", true); err != nil {
  893. log.Errorf("copy global syslinux.cfgS%s: %s", "syslinux.cfg", err)
  894. //return err
  895. } else {
  896. log.Debugf("installRancher copy global syslinux.cfgS OK")
  897. }
  898. // The global.cfg INCLUDE - useful for over-riding the APPEND line
  899. globalFile := filepath.Join(filepath.Join(baseName, install.BootDir), "global.cfg")
  900. if _, err := os.Stat(globalFile); !os.IsNotExist(err) {
  901. err := ioutil.WriteFile(globalFile, []byte("APPEND "+kappend), 0644)
  902. if err != nil {
  903. log.Errorf("write (%s) %s", "global.cfg", err)
  904. return currentCfg, err
  905. }
  906. }
  907. return currentCfg, nil
  908. }