install.go 31 KB

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