install.go 31 KB

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