install.go 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  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. d, t, err := util.Blkid("RancherOS")
  332. if err != nil {
  333. log.Errorf("Failed to run blkid: %s", err)
  334. }
  335. if d != "" {
  336. deviceName = d
  337. deviceType = t
  338. }
  339. mountsFile, err := os.Open("/proc/mounts")
  340. if err != nil {
  341. log.Errorf("failed to read /proc/mounts %s", err)
  342. return err
  343. }
  344. defer mountsFile.Close()
  345. if partitionMounted(deviceName, mountsFile) {
  346. return nil
  347. }
  348. os.MkdirAll("/bootiso", 0755)
  349. cmd := exec.Command("mount", "-t", deviceType, deviceName, "/bootiso")
  350. log.Debugf("mount (%#v)", cmd)
  351. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  352. err = cmd.Run()
  353. if err != nil {
  354. log.Errorf("tried and failed to mount %s: %s", deviceName, err)
  355. } else {
  356. log.Debugf("Mounted %s", deviceName)
  357. }
  358. return err
  359. }
  360. func layDownOS(image, installType, cloudConfig, device, partition, statedir, kappend string, kexec bool) error {
  361. // ENV == installType
  362. //[[ "$ARCH" == "arm" && "$ENV" != "upgrade" ]] && ENV=arm
  363. // image == rancher/os:v0.7.0_arm
  364. // TODO: remove the _arm suffix (but watch out, its not always there..)
  365. VERSION := image[strings.Index(image, ":")+1:]
  366. var FILES []string
  367. DIST := "/dist" //${DIST:-/dist}
  368. //cloudConfig := SCRIPTS_DIR + "/conf/empty.yml" //${cloudConfig:-"${SCRIPTS_DIR}/conf/empty.yml"}
  369. CONSOLE := "tty0"
  370. baseName := "/mnt/new_img"
  371. kernelArgs := "printk.devkmsg=on rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait" // console="+CONSOLE
  372. if statedir != "" {
  373. kernelArgs = kernelArgs + " rancher.state.directory=" + statedir
  374. }
  375. // unmount on trap
  376. defer util.Unmount(baseName)
  377. diskType := "msdos"
  378. if installType == "gptsyslinux" {
  379. diskType = "gpt"
  380. }
  381. switch installType {
  382. case "syslinux":
  383. fallthrough
  384. case "gptsyslinux":
  385. fallthrough
  386. case "generic":
  387. log.Debugf("formatAndMount")
  388. var err error
  389. device, partition, err = formatAndMount(baseName, device, partition)
  390. if err != nil {
  391. log.Errorf("formatAndMount %s", err)
  392. return err
  393. }
  394. err = installSyslinux(device, baseName, diskType)
  395. if err != nil {
  396. log.Errorf("installSyslinux %s", err)
  397. return err
  398. }
  399. err = seedData(baseName, cloudConfig, FILES)
  400. if err != nil {
  401. log.Errorf("seedData %s", err)
  402. return err
  403. }
  404. case "arm":
  405. var err error
  406. device, partition, err = formatAndMount(baseName, device, partition)
  407. if err != nil {
  408. return err
  409. }
  410. seedData(baseName, cloudConfig, FILES)
  411. case "amazon-ebs-pv":
  412. fallthrough
  413. case "amazon-ebs-hvm":
  414. CONSOLE = "ttyS0"
  415. var err error
  416. device, partition, err = formatAndMount(baseName, device, partition)
  417. if err != nil {
  418. return err
  419. }
  420. if installType == "amazon-ebs-hvm" {
  421. installSyslinux(device, baseName, diskType)
  422. }
  423. //# AWS Networking recommends disabling.
  424. seedData(baseName, cloudConfig, FILES)
  425. case "googlecompute":
  426. CONSOLE = "ttyS0"
  427. var err error
  428. device, partition, err = formatAndMount(baseName, device, partition)
  429. if err != nil {
  430. return err
  431. }
  432. installSyslinux(device, baseName, diskType)
  433. seedData(baseName, cloudConfig, FILES)
  434. case "noformat":
  435. var err error
  436. device, partition, err = install.MountDevice(baseName, device, partition, false)
  437. if err != nil {
  438. return err
  439. }
  440. installSyslinux(device, baseName, diskType)
  441. if err := os.MkdirAll(filepath.Join(baseName, statedir), 0755); err != nil {
  442. return err
  443. }
  444. case "raid":
  445. var err error
  446. device, partition, err = install.MountDevice(baseName, device, partition, false)
  447. if err != nil {
  448. return err
  449. }
  450. installSyslinux(device, baseName, diskType)
  451. case "bootstrap":
  452. CONSOLE = "ttyS0"
  453. var err error
  454. device, partition, err = install.MountDevice(baseName, device, partition, true)
  455. if err != nil {
  456. return err
  457. }
  458. kernelArgs = kernelArgs + " rancher.cloud_init.datasources=[ec2,gce]"
  459. case "rancher-upgrade":
  460. installType = "upgrade" // rancher-upgrade is redundant
  461. fallthrough
  462. case "upgrade":
  463. var err error
  464. device, partition, err = install.MountDevice(baseName, device, partition, false)
  465. if err != nil {
  466. return err
  467. }
  468. log.Debugf("upgrading - %s, %s, %s, %s", device, baseName, diskType)
  469. // TODO: detect pv-grub, and don't kill it with syslinux
  470. upgradeBootloader(device, baseName, diskType)
  471. default:
  472. return fmt.Errorf("unexpected install type %s", installType)
  473. }
  474. kernelArgs = kernelArgs + " console=" + CONSOLE
  475. if kappend == "" {
  476. preservedAppend, _ := ioutil.ReadFile(filepath.Join(baseName, install.BootDir+"append"))
  477. kappend = string(preservedAppend)
  478. } else {
  479. ioutil.WriteFile(filepath.Join(baseName, install.BootDir+"append"), []byte(kappend), 0644)
  480. }
  481. if installType == "amazon-ebs-pv" {
  482. menu := install.BootVars{
  483. BaseName: baseName,
  484. BootDir: install.BootDir,
  485. Timeout: 0,
  486. Fallback: 0, // need to be conditional on there being a 'rollback'?
  487. Entries: []install.MenuEntry{
  488. install.MenuEntry{"RancherOS-current", install.BootDir, VERSION, kernelArgs, kappend},
  489. },
  490. }
  491. install.PvGrubConfig(menu)
  492. }
  493. log.Debugf("installRancher")
  494. _, err := installRancher(baseName, VERSION, DIST, kernelArgs+" "+kappend)
  495. if err != nil {
  496. log.Errorf("%s", err)
  497. return err
  498. }
  499. log.Debugf("installRancher done")
  500. if kexec {
  501. power.Kexec(false, filepath.Join(baseName, install.BootDir), kernelArgs+" "+kappend)
  502. }
  503. return nil
  504. }
  505. // files is an array of 'sourcefile:destination' - but i've not seen any examples of it being used.
  506. func seedData(baseName, cloudData string, files []string) error {
  507. log.Debugf("seedData")
  508. _, err := os.Stat(baseName)
  509. if err != nil {
  510. return err
  511. }
  512. if err = os.MkdirAll(filepath.Join(baseName, "/var/lib/rancher/conf/cloud-config.d"), 0700); err != nil {
  513. return err
  514. }
  515. if !strings.HasSuffix(cloudData, "empty.yml") {
  516. if err = dfs.CopyFile(cloudData, baseName+"/var/lib/rancher/conf/cloud-config.d/", filepath.Base(cloudData)); err != nil {
  517. return err
  518. }
  519. }
  520. for _, f := range files {
  521. e := strings.Split(f, ":")
  522. if err = dfs.CopyFile(e[0], baseName, e[1]); err != nil {
  523. return err
  524. }
  525. }
  526. return nil
  527. }
  528. // set-disk-partitions is called with device == **/dev/sda**
  529. func setDiskpartitions(device, diskType string) error {
  530. log.Debugf("setDiskpartitions")
  531. d := strings.Split(device, "/")
  532. if len(d) != 3 {
  533. return fmt.Errorf("bad device name (%s)", device)
  534. }
  535. deviceName := d[2]
  536. file, err := os.Open("/proc/partitions")
  537. if err != nil {
  538. log.Debugf("failed to read /proc/partitions %s", err)
  539. return err
  540. }
  541. defer file.Close()
  542. exists := false
  543. haspartitions := false
  544. scanner := bufio.NewScanner(file)
  545. for scanner.Scan() {
  546. str := scanner.Text()
  547. last := strings.LastIndex(str, " ")
  548. if last > -1 {
  549. dev := str[last+1:]
  550. if strings.HasPrefix(dev, deviceName) {
  551. if dev == deviceName {
  552. exists = true
  553. } else {
  554. haspartitions = true
  555. }
  556. }
  557. }
  558. }
  559. if !exists {
  560. return fmt.Errorf("disk %s not found: %s", device, err)
  561. }
  562. if haspartitions {
  563. log.Debugf("device %s already partitioned - checking if any are mounted", device)
  564. file, err := os.Open("/proc/mounts")
  565. if err != nil {
  566. log.Errorf("failed to read /proc/mounts %s", err)
  567. return err
  568. }
  569. defer file.Close()
  570. if partitionMounted(device, file) {
  571. err = fmt.Errorf("partition %s mounted, cannot repartition", device)
  572. log.Errorf("%s", err)
  573. return err
  574. }
  575. cmd := exec.Command("system-docker", "ps", "-q")
  576. var outb bytes.Buffer
  577. cmd.Stdout = &outb
  578. if err := cmd.Run(); err != nil {
  579. log.Printf("ps error: %s", err)
  580. return err
  581. }
  582. for _, image := range strings.Split(outb.String(), "\n") {
  583. if image == "" {
  584. continue
  585. }
  586. r, w := io.Pipe()
  587. go func() {
  588. // TODO: consider a timeout
  589. // TODO:some of these containers don't have cat / shell
  590. cmd := exec.Command("system-docker", "exec", image, "cat /proc/mount")
  591. cmd.Stdout = w
  592. if err := cmd.Run(); err != nil {
  593. log.Debugf("%s cat %s", image, err)
  594. }
  595. w.Close()
  596. }()
  597. if partitionMounted(device, r) {
  598. err = fmt.Errorf("partition %s mounted in %s, cannot repartition", device, image)
  599. log.Errorf("k? %s", err)
  600. return err
  601. }
  602. }
  603. }
  604. //do it!
  605. log.Debugf("running dd")
  606. cmd := exec.Command("dd", "if=/dev/zero", "of="+device, "bs=512", "count=2048")
  607. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  608. if err := cmd.Run(); err != nil {
  609. log.Errorf("dd error %s", err)
  610. return err
  611. }
  612. log.Debugf("running partprobe")
  613. cmd = exec.Command("partprobe", device)
  614. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  615. if err := cmd.Run(); err != nil {
  616. log.Errorf("partprobe error %s", err)
  617. return err
  618. }
  619. log.Debugf("making single RANCHER_STATE partition")
  620. cmd = exec.Command("parted", "-s", "-a", "optimal", device,
  621. "mklabel "+diskType, "--",
  622. "mkpart primary ext4 1 -1")
  623. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  624. if err := cmd.Run(); err != nil {
  625. log.Errorf("parted: %s", err)
  626. return err
  627. }
  628. return setBootable(device, diskType)
  629. }
  630. func partitionMounted(device string, file io.Reader) bool {
  631. scanner := bufio.NewScanner(file)
  632. for scanner.Scan() {
  633. str := scanner.Text()
  634. // /dev/sdb1 /data ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
  635. ele := strings.Split(str, " ")
  636. if len(ele) > 5 {
  637. if strings.HasPrefix(ele[0], device) {
  638. return true
  639. }
  640. }
  641. if err := scanner.Err(); err != nil {
  642. log.Errorf("scanner %s", err)
  643. return false
  644. }
  645. }
  646. return false
  647. }
  648. func formatdevice(device, partition string) error {
  649. log.Debugf("formatdevice %s", partition)
  650. //mkfs.ext4 -F -i 4096 -L RANCHER_STATE ${partition}
  651. // -O ^64bit: for syslinux: http://www.syslinux.org/wiki/index.php?title=Filesystem#ext
  652. cmd := exec.Command("mkfs.ext4", "-F", "-i", "4096", "-O", "^64bit", "-L", "RANCHER_STATE", partition)
  653. log.Debugf("Run(%v)", cmd)
  654. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  655. if err := cmd.Run(); err != nil {
  656. log.Errorf("mkfs.ext4: %s", err)
  657. return err
  658. }
  659. return nil
  660. }
  661. func mountdevice(baseName, bootDir, device, partition string, raw bool) (string, string, error) {
  662. log.Debugf("mountdevice %s, raw %v", partition, raw)
  663. if partition == "" {
  664. if raw {
  665. log.Debugf("util.Mount (raw) %s, %s", partition, baseName)
  666. cmd := exec.Command("lsblk", "-no", "pkname", partition)
  667. log.Debugf("Run(%v)", cmd)
  668. cmd.Stderr = os.Stderr
  669. device := ""
  670. // TODO: out can == "" - this is used to "detect software RAID" which is terrible
  671. if out, err := cmd.Output(); err == nil {
  672. device = "/dev/" + strings.TrimSpace(string(out))
  673. }
  674. log.Debugf("mountdevice return -> d: %s, p: %s", device, partition)
  675. return device, partition, util.Mount(partition, baseName, "", "")
  676. }
  677. //rootfs := partition
  678. // Don't use ResolveDevice - it can fail, whereas `blkid -L LABEL` works more often
  679. cfg := config.LoadConfig()
  680. d, _, err := util.Blkid("RANCHER_BOOT")
  681. if err != nil {
  682. log.Errorf("Failed to run blkid: %s", err)
  683. }
  684. if d != "" {
  685. partition = d
  686. baseName = filepath.Join(baseName, "boot")
  687. } else {
  688. if dev := util.ResolveDevice(cfg.Rancher.State.Dev); dev != "" {
  689. // try the rancher.state.dev setting
  690. partition = dev
  691. } else {
  692. d, _, err := util.Blkid("RANCHER_STATE")
  693. if err != nil {
  694. log.Errorf("Failed to run blkid: %s", err)
  695. }
  696. if d != "" {
  697. partition = d
  698. }
  699. }
  700. }
  701. cmd := exec.Command("lsblk", "-no", "pkname", partition)
  702. log.Debugf("Run(%v)", cmd)
  703. cmd.Stderr = os.Stderr
  704. // TODO: out can == "" - this is used to "detect software RAID" which is terrible
  705. if out, err := cmd.Output(); err == nil {
  706. device = "/dev/" + strings.TrimSpace(string(out))
  707. }
  708. }
  709. os.MkdirAll(baseName, 0755)
  710. cmd := exec.Command("mount", partition, baseName)
  711. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  712. log.Debugf("mountdevice return2 -> d: %s, p: %s", device, partition)
  713. return device, partition, cmd.Run()
  714. }
  715. func formatAndMount(baseName, device, partition string) (string, string, error) {
  716. log.Debugf("formatAndMount")
  717. err := formatdevice(device, partition)
  718. if err != nil {
  719. log.Errorf("formatdevice %s", err)
  720. return device, partition, err
  721. }
  722. device, partition, err = install.MountDevice(baseName, device, partition, false)
  723. if err != nil {
  724. log.Errorf("mountdevice %s", err)
  725. return device, partition, err
  726. }
  727. return device, partition, nil
  728. }
  729. func setBootable(device, diskType string) error {
  730. // TODO make conditional - if there is a bootable device already, don't break it
  731. // TODO: make RANCHER_BOOT bootable - it might not be device 1
  732. bootflag := "boot"
  733. if diskType == "gpt" {
  734. bootflag = "legacy_boot"
  735. }
  736. log.Debugf("making device 1 on %s bootable as %s", device, diskType)
  737. cmd := exec.Command("parted", "-s", "-a", "optimal", device, "set 1 "+bootflag+" on")
  738. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  739. if err := cmd.Run(); err != nil {
  740. log.Errorf("parted: %s", err)
  741. return err
  742. }
  743. return nil
  744. }
  745. func upgradeBootloader(device, baseName, diskType string) error {
  746. log.Debugf("start upgradeBootloader")
  747. grubDir := filepath.Join(baseName, install.BootDir+"grub")
  748. if _, err := os.Stat(grubDir); os.IsNotExist(err) {
  749. log.Debugf("%s does not exist - no need to upgrade bootloader", grubDir)
  750. // we've already upgraded
  751. // TODO: in v0.9.0, need to detect what version syslinux we have
  752. return nil
  753. }
  754. // deal with systems which were previously upgraded, then rolled back, and are now being re-upgraded
  755. grubBackup := filepath.Join(baseName, install.BootDir+"grub_backup")
  756. if err := os.RemoveAll(grubBackup); err != nil {
  757. log.Errorf("RemoveAll (%s): %s", grubBackup, err)
  758. return err
  759. }
  760. backupSyslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux_backup")
  761. if _, err := os.Stat(backupSyslinuxDir); !os.IsNotExist(err) {
  762. backupSyslinuxLdlinuxSys := filepath.Join(backupSyslinuxDir, "ldlinux.sys")
  763. if _, err := os.Stat(backupSyslinuxLdlinuxSys); !os.IsNotExist(err) {
  764. //need a privileged container that can chattr -i ldlinux.sys
  765. cmd := exec.Command("chattr", "-i", backupSyslinuxLdlinuxSys)
  766. if err := cmd.Run(); err != nil {
  767. log.Errorf("%s", err)
  768. return err
  769. }
  770. }
  771. if err := os.RemoveAll(backupSyslinuxDir); err != nil {
  772. log.Errorf("RemoveAll (%s): %s", backupSyslinuxDir, err)
  773. return err
  774. }
  775. }
  776. if err := os.Rename(grubDir, grubBackup); err != nil {
  777. log.Errorf("Rename(%s): %s", grubDir, err)
  778. return err
  779. }
  780. syslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux")
  781. // it seems that v0.5.0 didn't have a syslinux dir, while 0.7 does
  782. if _, err := os.Stat(syslinuxDir); !os.IsNotExist(err) {
  783. if err := os.Rename(syslinuxDir, backupSyslinuxDir); err != nil {
  784. log.Infof("error Rename(%s, %s): %s", syslinuxDir, backupSyslinuxDir, err)
  785. } else {
  786. //mv the old syslinux into linux-previous.cfg
  787. oldSyslinux, err := ioutil.ReadFile(filepath.Join(backupSyslinuxDir, "syslinux.cfg"))
  788. if err != nil {
  789. log.Infof("error read(%s / syslinux.cfg): %s", backupSyslinuxDir, err)
  790. } else {
  791. cfg := string(oldSyslinux)
  792. //DEFAULT RancherOS-current
  793. //
  794. //LABEL RancherOS-current
  795. // LINUX ../vmlinuz-v0.7.1-rancheros
  796. // APPEND rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait console=tty0 rancher.password=rancher
  797. // INITRD ../initrd-v0.7.1-rancheros
  798. cfg = strings.Replace(cfg, "current", "previous", -1)
  799. // TODO consider removing the APPEND line - as the global.cfg should have the same result
  800. ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "linux-current.cfg"), []byte(cfg), 0644)
  801. lines := strings.Split(cfg, "\n")
  802. for _, line := range lines {
  803. line = strings.TrimSpace(line)
  804. if strings.HasPrefix(line, "APPEND") {
  805. log.Errorf("write new (%s) %s", filepath.Join(baseName, install.BootDir, "global.cfg"), err)
  806. // TODO: need to append any extra's the user specified
  807. ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "global.cfg"), []byte(cfg), 0644)
  808. break
  809. }
  810. }
  811. }
  812. }
  813. }
  814. return installSyslinux(device, baseName, diskType)
  815. }
  816. func installSyslinux(device, baseName, diskType string) error {
  817. log.Debugf("installSyslinux(%s)", device)
  818. mbrFile := "mbr.bin"
  819. if diskType == "gpt" {
  820. mbrFile = "gptmbr.bin"
  821. }
  822. //dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=${device}
  823. // ubuntu: /usr/lib/syslinux/mbr/mbr.bin
  824. // alpine: /usr/share/syslinux/mbr.bin
  825. if device == "/dev/" {
  826. log.Debugf("installSyslinuxRaid(%s)", device)
  827. //RAID - assume sda&sdb
  828. //TODO: fix this - not sure how to detect what disks should have mbr - perhaps we need a param
  829. // perhaps just assume and use the devices that make up the raid - mdadm
  830. device = "/dev/sda"
  831. if err := setBootable(device, diskType); err != nil {
  832. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  833. //return err
  834. }
  835. cmd := exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  836. if err := cmd.Run(); err != nil {
  837. log.Errorf("%s", err)
  838. return err
  839. }
  840. device = "/dev/sdb"
  841. if err := setBootable(device, diskType); err != nil {
  842. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  843. //return err
  844. }
  845. cmd = exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  846. if err := cmd.Run(); err != nil {
  847. log.Errorf("%s", err)
  848. return err
  849. }
  850. } else {
  851. if err := setBootable(device, diskType); err != nil {
  852. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  853. //return err
  854. }
  855. log.Debugf("installSyslinux(%s)", device)
  856. cmd := exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  857. log.Debugf("Run(%v)", cmd)
  858. if err := cmd.Run(); err != nil {
  859. log.Errorf("dd: %s", err)
  860. return err
  861. }
  862. }
  863. sysLinuxDir := filepath.Join(baseName, install.BootDir, "syslinux")
  864. if err := os.MkdirAll(sysLinuxDir, 0755); err != nil {
  865. log.Errorf("MkdirAll(%s)): %s", sysLinuxDir, err)
  866. //return err
  867. }
  868. //cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
  869. files, _ := ioutil.ReadDir("/usr/share/syslinux/")
  870. for _, file := range files {
  871. if file.IsDir() {
  872. continue
  873. }
  874. if err := dfs.CopyFile(filepath.Join("/usr/share/syslinux/", file.Name()), sysLinuxDir, file.Name()); err != nil {
  875. log.Errorf("copy syslinux: %s", err)
  876. return err
  877. }
  878. }
  879. //extlinux --install ${baseName}/${bootDir}syslinux
  880. cmd := exec.Command("extlinux", "--install", sysLinuxDir)
  881. if device == "/dev/" {
  882. //extlinux --install --raid ${baseName}/${bootDir}syslinux
  883. cmd = exec.Command("extlinux", "--install", "--raid", sysLinuxDir)
  884. }
  885. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  886. log.Debugf("Run(%v)", cmd)
  887. if err := cmd.Run(); err != nil {
  888. log.Errorf("extlinux: %s", err)
  889. return err
  890. }
  891. return nil
  892. }
  893. func different(existing, new string) bool {
  894. // assume existing file exists
  895. if _, err := os.Stat(new); os.IsNotExist(err) {
  896. return true
  897. }
  898. data, err := ioutil.ReadFile(existing)
  899. if err != nil {
  900. return true
  901. }
  902. newData, err := ioutil.ReadFile(new)
  903. if err != nil {
  904. return true
  905. }
  906. md5sum := md5.Sum(data)
  907. newmd5sum := md5.Sum(newData)
  908. if md5sum != newmd5sum {
  909. return true
  910. }
  911. return false
  912. }
  913. func installRancher(baseName, VERSION, DIST, kappend string) (string, error) {
  914. log.Debugf("installRancher")
  915. // detect if there already is a linux-current.cfg, if so, move it to linux-previous.cfg,
  916. currentCfg := filepath.Join(baseName, install.BootDir, "linux-current.cfg")
  917. if _, err := os.Stat(currentCfg); !os.IsNotExist(err) {
  918. existingCfg := filepath.Join(DIST, "linux-current.cfg")
  919. // only remove previous if there is a change to the current
  920. if different(currentCfg, existingCfg) {
  921. previousCfg := filepath.Join(baseName, install.BootDir, "linux-previous.cfg")
  922. if _, err := os.Stat(previousCfg); !os.IsNotExist(err) {
  923. if err := os.Remove(previousCfg); err != nil {
  924. return currentCfg, err
  925. }
  926. }
  927. os.Rename(currentCfg, previousCfg)
  928. // TODO: now that we're parsing syslinux.cfg files, maybe we can delete old kernels and initrds
  929. }
  930. }
  931. // 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
  932. files, _ := ioutil.ReadDir(DIST)
  933. for _, file := range files {
  934. if file.IsDir() {
  935. continue
  936. }
  937. // TODO: should overwrite anything other than the global.cfg
  938. overwrite := true
  939. if file.Name() == "global.cfg" {
  940. overwrite = false
  941. }
  942. if err := dfs.CopyFileOverwrite(filepath.Join(DIST, file.Name()), filepath.Join(baseName, install.BootDir), file.Name(), overwrite); err != nil {
  943. log.Errorf("copy %s: %s", file.Name(), err)
  944. //return err
  945. }
  946. }
  947. // the general INCLUDE syslinuxcfg
  948. isolinuxFile := filepath.Join(DIST, "isolinux", "isolinux.cfg")
  949. syslinuxDir := filepath.Join(baseName, install.BootDir, "syslinux")
  950. if err := dfs.CopyFileOverwrite(isolinuxFile, syslinuxDir, "syslinux.cfg", true); err != nil {
  951. log.Errorf("copy global syslinux.cfgS%s: %s", "syslinux.cfg", err)
  952. //return err
  953. } else {
  954. log.Debugf("installRancher copy global syslinux.cfgS OK")
  955. }
  956. // The global.cfg INCLUDE - useful for over-riding the APPEND line
  957. globalFile := filepath.Join(filepath.Join(baseName, install.BootDir), "global.cfg")
  958. if _, err := os.Stat(globalFile); !os.IsNotExist(err) {
  959. err := ioutil.WriteFile(globalFile, []byte("APPEND "+kappend), 0644)
  960. if err != nil {
  961. log.Errorf("write (%s) %s", "global.cfg", err)
  962. return currentCfg, err
  963. }
  964. }
  965. return currentCfg, nil
  966. }