install.go 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  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. err = seedData(baseName, cloudConfig, FILES)
  484. if err != nil {
  485. log.Errorf("seedData %s", err)
  486. return err
  487. }
  488. case "raid":
  489. var err error
  490. device, partition, err = install.MountDevice(baseName, device, partition, false)
  491. if err != nil {
  492. return err
  493. }
  494. installSyslinux(device, baseName, diskType)
  495. case "bootstrap":
  496. CONSOLE = "ttyS0"
  497. var err error
  498. device, partition, err = install.MountDevice(baseName, device, partition, true)
  499. if err != nil {
  500. return err
  501. }
  502. kernelArgs = kernelArgs + " rancher.cloud_init.datasources=[ec2,gce]"
  503. case "rancher-upgrade":
  504. installType = "upgrade" // rancher-upgrade is redundant
  505. fallthrough
  506. case "upgrade":
  507. var err error
  508. device, partition, err = install.MountDevice(baseName, device, partition, false)
  509. if err != nil {
  510. return err
  511. }
  512. log.Debugf("upgrading - %s, %s, %s, %s", device, baseName, diskType)
  513. // TODO: detect pv-grub, and don't kill it with syslinux
  514. upgradeBootloader(device, baseName, diskType)
  515. default:
  516. return fmt.Errorf("unexpected install type %s", installType)
  517. }
  518. kernelArgs = kernelArgs + " console=" + CONSOLE
  519. if kappend == "" {
  520. preservedAppend, _ := ioutil.ReadFile(filepath.Join(baseName, install.BootDir+"append"))
  521. kappend = string(preservedAppend)
  522. } else {
  523. ioutil.WriteFile(filepath.Join(baseName, install.BootDir+"append"), []byte(kappend), 0644)
  524. }
  525. if installType == "amazon-ebs-pv" {
  526. menu := install.BootVars{
  527. BaseName: baseName,
  528. BootDir: install.BootDir,
  529. Timeout: 0,
  530. Fallback: 0, // need to be conditional on there being a 'rollback'?
  531. Entries: []install.MenuEntry{
  532. install.MenuEntry{"RancherOS-current", install.BootDir, VERSION, kernelArgs, kappend},
  533. },
  534. }
  535. install.PvGrubConfig(menu)
  536. }
  537. log.Debugf("installRancher")
  538. _, err := installRancher(baseName, VERSION, DIST, kernelArgs+" "+kappend)
  539. if err != nil {
  540. log.Errorf("%s", err)
  541. return err
  542. }
  543. log.Debugf("installRancher done")
  544. if kexec {
  545. power.Kexec(false, filepath.Join(baseName, install.BootDir), kernelArgs+" "+kappend)
  546. }
  547. return nil
  548. }
  549. // files is an array of 'sourcefile:destination' - but i've not seen any examples of it being used.
  550. func seedData(baseName, cloudData string, files []string) error {
  551. log.Debugf("seedData")
  552. _, err := os.Stat(baseName)
  553. if err != nil {
  554. return err
  555. }
  556. stateSeedDir := "state_seed"
  557. cloudConfigBase := "/var/lib/rancher/conf/cloud-config.d"
  558. cloudConfigDir := ""
  559. // If there is a separate boot partition, cloud-config should be written to RANCHER_STATE partition.
  560. bootPartition, _, err := util.Blkid("RANCHER_BOOT")
  561. if err != nil {
  562. log.Errorf("Failed to run blkid: %s", err)
  563. }
  564. if bootPartition != "" {
  565. stateSeedFullPath := filepath.Join(baseName, stateSeedDir)
  566. if err = os.MkdirAll(stateSeedFullPath, 0700); err != nil {
  567. return err
  568. }
  569. defer util.Unmount(stateSeedFullPath)
  570. statePartition := install.GetStatePartition()
  571. cmd := exec.Command("mount", statePartition, stateSeedFullPath)
  572. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  573. log.Debugf("seedData: mount %s to %s", statePartition, stateSeedFullPath)
  574. if err = cmd.Run(); err != nil {
  575. return err
  576. }
  577. cloudConfigDir = filepath.Join(baseName, stateSeedDir, cloudConfigBase)
  578. } else {
  579. cloudConfigDir = filepath.Join(baseName, cloudConfigBase)
  580. }
  581. if err = os.MkdirAll(cloudConfigDir, 0700); err != nil {
  582. return err
  583. }
  584. if !strings.HasSuffix(cloudData, "empty.yml") {
  585. if err = dfs.CopyFile(cloudData, cloudConfigDir, filepath.Base(cloudData)); err != nil {
  586. return err
  587. }
  588. }
  589. for _, f := range files {
  590. e := strings.Split(f, ":")
  591. if err = dfs.CopyFile(e[0], baseName, e[1]); err != nil {
  592. return err
  593. }
  594. }
  595. return nil
  596. }
  597. // set-disk-partitions is called with device == **/dev/sda**
  598. func setDiskpartitions(device, diskType string) error {
  599. log.Debugf("setDiskpartitions")
  600. d := strings.Split(device, "/")
  601. if len(d) != 3 {
  602. return fmt.Errorf("bad device name (%s)", device)
  603. }
  604. deviceName := d[2]
  605. file, err := os.Open("/proc/partitions")
  606. if err != nil {
  607. log.Debugf("failed to read /proc/partitions %s", err)
  608. return err
  609. }
  610. defer file.Close()
  611. exists := false
  612. haspartitions := false
  613. scanner := bufio.NewScanner(file)
  614. for scanner.Scan() {
  615. str := scanner.Text()
  616. last := strings.LastIndex(str, " ")
  617. if last > -1 {
  618. dev := str[last+1:]
  619. if strings.HasPrefix(dev, deviceName) {
  620. if dev == deviceName {
  621. exists = true
  622. } else {
  623. haspartitions = true
  624. }
  625. }
  626. }
  627. }
  628. if !exists {
  629. return fmt.Errorf("disk %s not found: %s", device, err)
  630. }
  631. if haspartitions {
  632. log.Debugf("device %s already partitioned - checking if any are mounted", device)
  633. file, err := os.Open("/proc/mounts")
  634. if err != nil {
  635. log.Errorf("failed to read /proc/mounts %s", err)
  636. return err
  637. }
  638. defer file.Close()
  639. if partitionMounted(device, file) {
  640. err = fmt.Errorf("partition %s mounted, cannot repartition", device)
  641. log.Errorf("%s", err)
  642. return err
  643. }
  644. cmd := exec.Command("system-docker", "ps", "-q")
  645. var outb bytes.Buffer
  646. cmd.Stdout = &outb
  647. if err := cmd.Run(); err != nil {
  648. log.Printf("ps error: %s", err)
  649. return err
  650. }
  651. for _, image := range strings.Split(outb.String(), "\n") {
  652. if image == "" {
  653. continue
  654. }
  655. r, w := io.Pipe()
  656. go func() {
  657. // TODO: consider a timeout
  658. // TODO:some of these containers don't have cat / shell
  659. cmd := exec.Command("system-docker", "exec", image, "cat /proc/mount")
  660. cmd.Stdout = w
  661. if err := cmd.Run(); err != nil {
  662. log.Debugf("%s cat %s", image, err)
  663. }
  664. w.Close()
  665. }()
  666. if partitionMounted(device, r) {
  667. err = fmt.Errorf("partition %s mounted in %s, cannot repartition", device, image)
  668. log.Errorf("k? %s", err)
  669. return err
  670. }
  671. }
  672. }
  673. //do it!
  674. log.Debugf("running dd device: %s", device)
  675. cmd := exec.Command("dd", "if=/dev/zero", "of="+device, "bs=512", "count=2048")
  676. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  677. if err := cmd.Run(); err != nil {
  678. log.Errorf("dd error %s", err)
  679. return err
  680. }
  681. log.Debugf("running partprobe: %s", device)
  682. cmd = exec.Command("partprobe", device)
  683. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  684. if err := cmd.Run(); err != nil {
  685. log.Errorf("Failed to partprobe device %s: %v", device, err)
  686. return err
  687. }
  688. log.Debugf("making single RANCHER_STATE partition, device: %s", device)
  689. cmd = exec.Command("parted", "-s", "-a", "optimal", device,
  690. "mklabel "+diskType, "--",
  691. "mkpart primary ext4 1 -1")
  692. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  693. if err := cmd.Run(); err != nil {
  694. log.Errorf("Failed to parted device %s: %v", device, err)
  695. return err
  696. }
  697. return setBootable(device, diskType)
  698. }
  699. func partitionMounted(device string, file io.Reader) bool {
  700. scanner := bufio.NewScanner(file)
  701. for scanner.Scan() {
  702. str := scanner.Text()
  703. // /dev/sdb1 /data ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
  704. ele := strings.Split(str, " ")
  705. if len(ele) > 5 {
  706. if strings.HasPrefix(ele[0], device) {
  707. return true
  708. }
  709. }
  710. if err := scanner.Err(); err != nil {
  711. log.Errorf("scanner %s", err)
  712. return false
  713. }
  714. }
  715. return false
  716. }
  717. func formatdevice(device, partition string) error {
  718. log.Debugf("formatdevice %s", partition)
  719. //mkfs.ext4 -F -i 4096 -L RANCHER_STATE ${partition}
  720. // -O ^64bit: for syslinux: http://www.syslinux.org/wiki/index.php?title=Filesystem#ext
  721. cmd := exec.Command("mkfs.ext4", "-F", "-i", "4096", "-O", "^64bit", "-L", "RANCHER_STATE", partition)
  722. log.Debugf("Run(%v)", cmd)
  723. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  724. if err := cmd.Run(); err != nil {
  725. log.Errorf("mkfs.ext4: %s", err)
  726. return err
  727. }
  728. return nil
  729. }
  730. func formatAndMount(baseName, device, partition string) (string, string, error) {
  731. log.Debugf("formatAndMount")
  732. err := formatdevice(device, partition)
  733. if err != nil {
  734. log.Errorf("formatdevice %s", err)
  735. return device, partition, err
  736. }
  737. device, partition, err = install.MountDevice(baseName, device, partition, false)
  738. if err != nil {
  739. log.Errorf("mountdevice %s", err)
  740. return device, partition, err
  741. }
  742. return device, partition, nil
  743. }
  744. func setBootable(device, diskType string) error {
  745. // TODO make conditional - if there is a bootable device already, don't break it
  746. // TODO: make RANCHER_BOOT bootable - it might not be device 1
  747. bootflag := "boot"
  748. if diskType == "gpt" {
  749. bootflag = "legacy_boot"
  750. }
  751. log.Debugf("making device 1 on %s bootable as %s", device, diskType)
  752. cmd := exec.Command("parted", "-s", "-a", "optimal", device, "set 1 "+bootflag+" on")
  753. cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  754. if err := cmd.Run(); err != nil {
  755. log.Errorf("parted: %s", err)
  756. return err
  757. }
  758. return nil
  759. }
  760. func upgradeBootloader(device, baseName, diskType string) error {
  761. log.Debugf("start upgradeBootloader")
  762. grubDir := filepath.Join(baseName, install.BootDir+"grub")
  763. if _, err := os.Stat(grubDir); os.IsNotExist(err) {
  764. log.Debugf("%s does not exist - no need to upgrade bootloader", grubDir)
  765. // we've already upgraded
  766. // TODO: in v0.9.0, need to detect what version syslinux we have
  767. return nil
  768. }
  769. // deal with systems which were previously upgraded, then rolled back, and are now being re-upgraded
  770. grubBackup := filepath.Join(baseName, install.BootDir+"grub_backup")
  771. if err := os.RemoveAll(grubBackup); err != nil {
  772. log.Errorf("RemoveAll (%s): %s", grubBackup, err)
  773. return err
  774. }
  775. backupSyslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux_backup")
  776. if _, err := os.Stat(backupSyslinuxDir); !os.IsNotExist(err) {
  777. backupSyslinuxLdlinuxSys := filepath.Join(backupSyslinuxDir, "ldlinux.sys")
  778. if _, err := os.Stat(backupSyslinuxLdlinuxSys); !os.IsNotExist(err) {
  779. //need a privileged container that can chattr -i ldlinux.sys
  780. cmd := exec.Command("chattr", "-i", backupSyslinuxLdlinuxSys)
  781. if err := cmd.Run(); err != nil {
  782. log.Errorf("%s", err)
  783. return err
  784. }
  785. }
  786. if err := os.RemoveAll(backupSyslinuxDir); err != nil {
  787. log.Errorf("RemoveAll (%s): %s", backupSyslinuxDir, err)
  788. return err
  789. }
  790. }
  791. if err := os.Rename(grubDir, grubBackup); err != nil {
  792. log.Errorf("Rename(%s): %s", grubDir, err)
  793. return err
  794. }
  795. syslinuxDir := filepath.Join(baseName, install.BootDir+"syslinux")
  796. // it seems that v0.5.0 didn't have a syslinux dir, while 0.7 does
  797. if _, err := os.Stat(syslinuxDir); !os.IsNotExist(err) {
  798. if err := os.Rename(syslinuxDir, backupSyslinuxDir); err != nil {
  799. log.Infof("error Rename(%s, %s): %s", syslinuxDir, backupSyslinuxDir, err)
  800. } else {
  801. //mv the old syslinux into linux-previous.cfg
  802. oldSyslinux, err := ioutil.ReadFile(filepath.Join(backupSyslinuxDir, "syslinux.cfg"))
  803. if err != nil {
  804. log.Infof("error read(%s / syslinux.cfg): %s", backupSyslinuxDir, err)
  805. } else {
  806. cfg := string(oldSyslinux)
  807. //DEFAULT RancherOS-current
  808. //
  809. //LABEL RancherOS-current
  810. // LINUX ../vmlinuz-v0.7.1-rancheros
  811. // APPEND rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait console=tty0 rancher.password=rancher
  812. // INITRD ../initrd-v0.7.1-rancheros
  813. cfg = strings.Replace(cfg, "current", "previous", -1)
  814. // TODO consider removing the APPEND line - as the global.cfg should have the same result
  815. ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "linux-current.cfg"), []byte(cfg), 0644)
  816. lines := strings.Split(cfg, "\n")
  817. for _, line := range lines {
  818. line = strings.TrimSpace(line)
  819. if strings.HasPrefix(line, "APPEND") {
  820. log.Errorf("write new (%s) %s", filepath.Join(baseName, install.BootDir, "global.cfg"), err)
  821. // TODO: need to append any extra's the user specified
  822. ioutil.WriteFile(filepath.Join(baseName, install.BootDir, "global.cfg"), []byte(cfg), 0644)
  823. break
  824. }
  825. }
  826. }
  827. }
  828. }
  829. return installSyslinux(device, baseName, diskType)
  830. }
  831. func installSyslinux(device, baseName, diskType string) error {
  832. log.Debugf("installSyslinux(%s)", device)
  833. mbrFile := "mbr.bin"
  834. if diskType == "gpt" {
  835. mbrFile = "gptmbr.bin"
  836. }
  837. //dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=${device}
  838. // ubuntu: /usr/lib/syslinux/mbr/mbr.bin
  839. // alpine: /usr/share/syslinux/mbr.bin
  840. if device == "/dev/" {
  841. log.Debugf("installSyslinuxRaid(%s)", device)
  842. //RAID - assume sda&sdb
  843. //TODO: fix this - not sure how to detect what disks should have mbr - perhaps we need a param
  844. // perhaps just assume and use the devices that make up the raid - mdadm
  845. device = "/dev/sda"
  846. if err := setBootable(device, diskType); err != nil {
  847. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  848. //return err
  849. }
  850. cmd := exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  851. if err := cmd.Run(); err != nil {
  852. log.Errorf("%s", err)
  853. return err
  854. }
  855. device = "/dev/sdb"
  856. if err := setBootable(device, diskType); err != nil {
  857. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  858. //return err
  859. }
  860. cmd = exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  861. if err := cmd.Run(); err != nil {
  862. log.Errorf("%s", err)
  863. return err
  864. }
  865. } else {
  866. if err := setBootable(device, diskType); err != nil {
  867. log.Errorf("setBootable(%s, %s): %s", device, diskType, err)
  868. //return err
  869. }
  870. log.Debugf("installSyslinux(%s)", device)
  871. cmd := exec.Command("dd", "bs=440", "count=1", "if=/usr/share/syslinux/"+mbrFile, "of="+device)
  872. log.Debugf("Run(%v)", cmd)
  873. if err := cmd.Run(); err != nil {
  874. log.Errorf("dd: %s", err)
  875. return err
  876. }
  877. }
  878. sysLinuxDir := filepath.Join(baseName, install.BootDir, "syslinux")
  879. if err := os.MkdirAll(sysLinuxDir, 0755); err != nil {
  880. log.Errorf("MkdirAll(%s)): %s", sysLinuxDir, err)
  881. //return err
  882. }
  883. //cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
  884. files, _ := ioutil.ReadDir("/usr/share/syslinux/")
  885. for _, file := range files {
  886. if file.IsDir() {
  887. continue
  888. }
  889. if err := dfs.CopyFile(filepath.Join("/usr/share/syslinux/", file.Name()), sysLinuxDir, file.Name()); err != nil {
  890. log.Errorf("copy syslinux: %s", err)
  891. return err
  892. }
  893. }
  894. //extlinux --install ${baseName}/${bootDir}syslinux
  895. cmd := exec.Command("extlinux", "--install", sysLinuxDir)
  896. if device == "/dev/" {
  897. //extlinux --install --raid ${baseName}/${bootDir}syslinux
  898. cmd = exec.Command("extlinux", "--install", "--raid", sysLinuxDir)
  899. }
  900. //cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
  901. log.Debugf("Run(%v)", cmd)
  902. if err := cmd.Run(); err != nil {
  903. log.Errorf("extlinux: %s", err)
  904. return err
  905. }
  906. return nil
  907. }
  908. func different(existing, new string) bool {
  909. // assume existing file exists
  910. if _, err := os.Stat(new); os.IsNotExist(err) {
  911. return true
  912. }
  913. data, err := ioutil.ReadFile(existing)
  914. if err != nil {
  915. return true
  916. }
  917. newData, err := ioutil.ReadFile(new)
  918. if err != nil {
  919. return true
  920. }
  921. md5sum := md5.Sum(data)
  922. newmd5sum := md5.Sum(newData)
  923. if md5sum != newmd5sum {
  924. return true
  925. }
  926. return false
  927. }
  928. func installRancher(baseName, VERSION, DIST, kappend string) (string, error) {
  929. log.Debugf("installRancher")
  930. // detect if there already is a linux-current.cfg, if so, move it to linux-previous.cfg,
  931. currentCfg := filepath.Join(baseName, install.BootDir, "linux-current.cfg")
  932. if _, err := os.Stat(currentCfg); !os.IsNotExist(err) {
  933. existingCfg := filepath.Join(DIST, "linux-current.cfg")
  934. // only remove previous if there is a change to the current
  935. if different(currentCfg, existingCfg) {
  936. previousCfg := filepath.Join(baseName, install.BootDir, "linux-previous.cfg")
  937. if _, err := os.Stat(previousCfg); !os.IsNotExist(err) {
  938. if err := os.Remove(previousCfg); err != nil {
  939. return currentCfg, err
  940. }
  941. }
  942. os.Rename(currentCfg, previousCfg)
  943. // TODO: now that we're parsing syslinux.cfg files, maybe we can delete old kernels and initrds
  944. }
  945. }
  946. // 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
  947. files, _ := ioutil.ReadDir(DIST)
  948. for _, file := range files {
  949. if file.IsDir() {
  950. continue
  951. }
  952. // TODO: should overwrite anything other than the global.cfg
  953. overwrite := true
  954. if file.Name() == "global.cfg" {
  955. overwrite = false
  956. }
  957. if err := dfs.CopyFileOverwrite(filepath.Join(DIST, file.Name()), filepath.Join(baseName, install.BootDir), file.Name(), overwrite); err != nil {
  958. log.Errorf("copy %s: %s", file.Name(), err)
  959. //return err
  960. }
  961. }
  962. // the general INCLUDE syslinuxcfg
  963. isolinuxFile := filepath.Join(DIST, "isolinux", "isolinux.cfg")
  964. syslinuxDir := filepath.Join(baseName, install.BootDir, "syslinux")
  965. if err := dfs.CopyFileOverwrite(isolinuxFile, syslinuxDir, "syslinux.cfg", true); err != nil {
  966. log.Errorf("copy global syslinux.cfgS%s: %s", "syslinux.cfg", err)
  967. //return err
  968. } else {
  969. log.Debugf("installRancher copy global syslinux.cfgS OK")
  970. }
  971. // The global.cfg INCLUDE - useful for over-riding the APPEND line
  972. globalFile := filepath.Join(filepath.Join(baseName, install.BootDir), "global.cfg")
  973. if _, err := os.Stat(globalFile); !os.IsNotExist(err) {
  974. err := ioutil.WriteFile(globalFile, []byte("APPEND "+kappend), 0644)
  975. if err != nil {
  976. log.Errorf("write (%s) %s", "global.cfg", err)
  977. return currentCfg, err
  978. }
  979. }
  980. return currentCfg, nil
  981. }