install.go 33 KB

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