2015-08-18 14:07:00 +00:00
package control
import (
2016-12-01 03:55:19 +00:00
"bufio"
"bytes"
2015-08-18 14:07:00 +00:00
"fmt"
2016-12-01 03:55:19 +00:00
"io"
"io/ioutil"
2015-08-18 14:07:00 +00:00
"os"
"os/exec"
2016-12-01 03:55:19 +00:00
"path/filepath"
2016-12-07 11:56:32 +00:00
"strconv"
2016-09-27 00:27:53 +00:00
"strings"
2015-08-18 14:07:00 +00:00
2016-11-23 10:49:35 +00:00
"github.com/rancher/os/log"
2015-08-18 14:07:00 +00:00
"github.com/codegangsta/cli"
2016-12-20 12:49:34 +00:00
"github.com/rancher/os/cmd/control/install"
2015-10-12 11:50:17 +00:00
"github.com/rancher/os/cmd/power"
"github.com/rancher/os/config"
2016-12-01 03:55:19 +00:00
"github.com/rancher/os/dfs" // TODO: move CopyFile into util or something.
2015-10-12 11:50:17 +00:00
"github.com/rancher/os/util"
2015-08-18 14:07:00 +00:00
)
var installCommand = cli . Command {
Name : "install" ,
Usage : "install RancherOS to disk" ,
HideHelp : true ,
Action : installAction ,
Flags : [ ] cli . Flag {
cli . StringFlag {
2016-10-12 12:41:52 +00:00
// TODO: need to validate ? -i rancher/os:v0.3.1 just sat there.
Name : "image, i" ,
Usage : ` install from a certain image ( e . g . , ' rancher / os : v0 .7 .0 ' )
use ' ros os list ' to see what versions are available . ` ,
2015-08-18 14:07:00 +00:00
} ,
cli . StringFlag {
Name : "install-type, t" ,
2016-12-15 01:54:43 +00:00
Usage : ` generic : ( Default ) Creates 1 ext4 partition and installs RancherOS ( syslinux )
2016-12-14 11:01:58 +00:00
amazon - ebs : Installs RancherOS and sets up PV - GRUB
2017-01-05 11:29:09 +00:00
gptsyslinux : partition and format disk ( gpt ) , then install RancherOS and setup Syslinux
2016-12-14 11:01:58 +00:00
` ,
2015-08-18 14:07:00 +00:00
} ,
cli . StringFlag {
Name : "cloud-config, c" ,
Usage : "cloud-config yml file - needed for SSH authorized keys" ,
} ,
cli . StringFlag {
Name : "device, d" ,
Usage : "storage device" ,
} ,
cli . BoolFlag {
Name : "force, f" ,
Usage : "[ DANGEROUS! Data loss can happen ] partition/format without prompting" ,
} ,
cli . BoolFlag {
Name : "no-reboot" ,
Usage : "do not reboot after install" ,
} ,
2016-09-27 00:27:53 +00:00
cli . StringFlag {
Name : "append, a" ,
Usage : "append additional kernel parameters" ,
} ,
2016-12-20 12:49:34 +00:00
cli . StringFlag { // TODO: hide..
Name : "rollback, r" ,
Usage : "rollback version" ,
} ,
cli . BoolFlag { // TODO: this should be hidden and internal only
Name : "isoinstallerloaded" ,
Usage : "INTERNAL use only: mount the iso to get kernel and initrd" ,
Hidden : true ,
} ,
2016-12-07 03:33:43 +00:00
cli . BoolFlag {
2016-12-20 12:49:34 +00:00
Name : "kexec" ,
Usage : "reboot using kexec" ,
2016-12-07 03:33:43 +00:00
} ,
2015-08-18 14:07:00 +00:00
} ,
}
2016-05-17 03:36:08 +00:00
func installAction ( c * cli . Context ) error {
2015-08-18 14:07:00 +00:00
if c . Args ( ) . Present ( ) {
log . Fatalf ( "invalid arguments %v" , c . Args ( ) )
}
2016-12-20 12:49:34 +00:00
kappend := strings . TrimSpace ( c . String ( "append" ) )
force := c . Bool ( "force" )
kexec := c . Bool ( "kexec" )
reboot := ! c . Bool ( "no-reboot" )
isoinstallerloaded := c . Bool ( "isoinstallerloaded" )
2015-08-18 14:07:00 +00:00
image := c . String ( "image" )
2016-06-02 01:41:55 +00:00
cfg := config . LoadConfig ( )
2015-08-18 14:07:00 +00:00
if image == "" {
2016-11-28 08:06:00 +00:00
image = cfg . Rancher . Upgrade . Image + ":" + config . Version + config . Suffix
2015-08-18 14:07:00 +00:00
}
installType := c . String ( "install-type" )
if installType == "" {
log . Info ( "No install type specified...defaulting to generic" )
installType = "generic"
}
2016-12-20 12:49:34 +00:00
if installType == "rancher-upgrade" ||
installType == "upgrade" {
force = true // the os.go upgrade code already asks
reboot = false
isoinstallerloaded = true // OMG this flag is aweful - kill it with fire
}
device := c . String ( "device" )
if installType != "noformat" &&
installType != "raid" &&
installType != "bootstrap" &&
installType != "upgrade" &&
installType != "rancher-upgrade" {
// These can use RANCHER_BOOT or RANCHER_STATE labels..
if device == "" {
log . Fatal ( "Can not proceed without -d <dev> specified" )
}
}
2015-08-18 14:07:00 +00:00
cloudConfig := c . String ( "cloud-config" )
if cloudConfig == "" {
2016-12-01 03:55:19 +00:00
log . Warn ( "Cloud-config not provided: you might need to provide cloud-config on bootDir with ssh_authorized_keys" )
2015-08-18 14:07:00 +00:00
} else {
uc := "/opt/user_config.yml"
if err := util . FileCopy ( cloudConfig , uc ) ; err != nil {
log . WithFields ( log . Fields { "cloudConfig" : cloudConfig } ) . Fatal ( "Failed to copy cloud-config" )
}
cloudConfig = uc
}
2016-12-20 12:49:34 +00:00
if err := runInstall ( image , installType , cloudConfig , device , kappend , force , reboot , kexec , isoinstallerloaded ) ; err != nil {
2015-08-18 14:07:00 +00:00
log . WithFields ( log . Fields { "err" : err } ) . Fatal ( "Failed to run install" )
}
2016-05-17 03:36:08 +00:00
return nil
2015-08-18 14:07:00 +00:00
}
2016-12-20 12:49:34 +00:00
func runInstall ( image , installType , cloudConfig , device , kappend string , force , reboot , kexec , isoinstallerloaded bool ) error {
2015-08-18 14:07:00 +00:00
fmt . Printf ( "Installing from %s\n" , image )
if ! force {
2016-10-12 12:41:52 +00:00
if ! yes ( "Continue" ) {
2016-12-01 03:55:19 +00:00
log . Infof ( "Not continuing with installation due to user not saying 'yes'" )
2015-08-18 14:07:00 +00:00
os . Exit ( 1 )
}
}
2016-12-14 11:01:58 +00:00
diskType := "msdos"
2016-12-20 12:49:34 +00:00
2016-12-14 11:01:58 +00:00
if installType == "gptsyslinux" {
diskType = "gpt"
}
2016-12-07 11:56:32 +00:00
// Versions before 0.8.0-rc2 use the old calling convention (from the lay-down-os shell script)
2017-01-17 01:16:49 +00:00
// TODO: This needs fixing before 0.8.0 GA's - actually parse the version string, and use for any numbered version before 0.8.0-rc3
imageVersion := strings . TrimPrefix ( image , "rancher/os:" )
2017-01-17 10:40:21 +00:00
if imageVersion == "v0.7.1" || imageVersion == "v0.7.0" || imageVersion == "v0.5.0" {
2017-01-17 01:16:49 +00:00
log . Infof ( "user specified to install 0.7.0/0.7.1: %s" , image )
2016-12-07 11:56:32 +00:00
imageVersion = strings . Replace ( imageVersion , "-" , "." , - 1 )
vArray := strings . Split ( imageVersion , "." )
v , _ := strconv . ParseFloat ( vArray [ 0 ] + "." + vArray [ 1 ] , 32 )
if v < 0.8 || imageVersion == "0.8.0-rc1" {
log . Infof ( "starting installer container for %s" , image )
2016-12-15 01:54:43 +00:00
if installType == "generic" ||
installType == "syslinux" ||
installType == "gptsyslinux" {
2016-12-07 11:56:32 +00:00
cmd := exec . Command ( "system-docker" , "run" , "--net=host" , "--privileged" , "--volumes-from=all-volumes" ,
"--entrypoint=/scripts/set-disk-partitions" , image , device , diskType )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
return err
}
}
cmd := exec . Command ( "system-docker" , "run" , "--net=host" , "--privileged" , "--volumes-from=user-volumes" ,
2016-12-15 01:54:43 +00:00
"--volumes-from=command-volumes" , image , "-d" , device , "-t" , installType , "-c" , cloudConfig ,
"-a" , kappend )
2016-12-01 03:55:19 +00:00
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
return err
}
2016-12-07 11:56:32 +00:00
return nil
2016-12-01 03:55:19 +00:00
}
2015-08-18 14:07:00 +00:00
}
2016-12-01 03:55:19 +00:00
2016-12-20 12:49:34 +00:00
if _ , err := os . Stat ( "/usr/bin/system-docker" ) ; os . IsNotExist ( err ) {
if err := os . Symlink ( "/usr/bin/ros" , "/usr/bin/system-docker" ) ; err != nil {
log . Errorf ( "ln error %s" , err )
}
}
2016-12-06 03:16:18 +00:00
useIso := false
2016-12-20 12:49:34 +00:00
// --isoinstallerloaded is used if the ros has created the installer container from and image that was on the booted iso
if ! isoinstallerloaded {
2017-01-17 01:16:49 +00:00
log . Infof ( "start !isoinstallerloaded" )
2016-12-07 03:33:43 +00:00
if _ , err := os . Stat ( "/dist/initrd" ) ; os . IsNotExist ( err ) {
2016-12-20 12:49:34 +00:00
if err = mountBootIso ( ) ; err != nil {
log . Debugf ( "mountBootIso error %s" , err )
} else {
2017-01-17 01:16:49 +00:00
log . Infof ( "trying to load /bootiso/rancheros/installer.tar.gz" )
2016-12-07 03:33:43 +00:00
if _ , err := os . Stat ( "/bootiso/rancheros/" ) ; err == nil {
cmd := exec . Command ( "system-docker" , "load" , "-i" , "/bootiso/rancheros/installer.tar.gz" )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
log . Infof ( "failed to load images from /bootiso/rancheros: %s" , err )
} else {
log . Infof ( "Loaded images from /bootiso/rancheros/installer.tar.gz" )
//TODO: add if os-installer:latest exists - we might have loaded a full installer?
useIso = true
// now use the installer image
cfg := config . LoadConfig ( )
// TODO: fix the fullinstaller Dockerfile to use the ${VERSION}${SUFFIX}
image = cfg . Rancher . Upgrade . Image + "-installer" + ":latest"
}
2016-12-01 03:55:19 +00:00
}
2016-12-07 03:33:43 +00:00
// TODO: also poke around looking for the /boot/vmlinuz and initrd...
2016-12-01 03:55:19 +00:00
}
2016-12-07 03:33:43 +00:00
log . Infof ( "starting installer container for %s (new)" , image )
installerCmd := [ ] string {
"run" , "--rm" , "--net=host" , "--privileged" ,
// bind mount host fs to access its ros, vmlinuz, initrd and /dev (udev isn't running in container)
"-v" , "/:/host" ,
2016-12-15 01:54:43 +00:00
"--volumes-from=all-volumes" ,
2016-12-07 03:33:43 +00:00
image ,
2016-12-20 12:49:34 +00:00
// "install",
2016-12-07 03:33:43 +00:00
"-t" , installType ,
"-d" , device ,
2017-01-17 01:16:49 +00:00
"-i" , image , // TODO: this isn't used - I'm just using it to over-ride the defaulting
2016-12-07 03:33:43 +00:00
}
if force {
installerCmd = append ( installerCmd , "-f" )
}
if ! reboot {
installerCmd = append ( installerCmd , "--no-reboot" )
}
if cloudConfig != "" {
installerCmd = append ( installerCmd , "-c" , cloudConfig )
}
if kappend != "" {
installerCmd = append ( installerCmd , "-a" , kappend )
}
if useIso {
2016-12-20 12:49:34 +00:00
installerCmd = append ( installerCmd , "--isoinstallerloaded=1" )
}
if kexec {
installerCmd = append ( installerCmd , "--kexec" )
2016-12-07 03:33:43 +00:00
}
2016-12-01 03:55:19 +00:00
2017-01-17 01:16:49 +00:00
// TODO: mount at /mnt for shared mount?
if useIso {
util . Unmount ( "/bootiso" )
}
2016-12-07 03:33:43 +00:00
cmd := exec . Command ( "system-docker" , installerCmd ... )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
return err
}
return nil
2016-12-06 03:16:18 +00:00
}
2016-12-01 03:55:19 +00:00
}
// TODO: needs to pass the log level on to the container
log . InitLogger ( )
2016-12-07 11:56:32 +00:00
log . SetLevel ( log . InfoLevel )
2016-12-01 03:55:19 +00:00
2016-12-15 13:15:49 +00:00
log . Debugf ( "running installation" )
2016-12-01 03:55:19 +00:00
2016-12-15 01:54:43 +00:00
if installType == "generic" ||
installType == "syslinux" ||
installType == "gptsyslinux" {
diskType := "msdos"
if installType == "gptsyslinux" {
diskType = "gpt"
}
2016-12-15 13:15:49 +00:00
log . Debugf ( "running setDiskpartitions" )
2016-12-15 01:54:43 +00:00
err := setDiskpartitions ( device , diskType )
2016-12-01 03:55:19 +00:00
if err != nil {
2016-12-07 03:33:43 +00:00
log . Errorf ( "error setDiskpartitions %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
// 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)
device = "/host" + device
2016-12-07 03:33:43 +00:00
}
2016-12-20 12:49:34 +00:00
if installType == "rancher-upgrade" ||
installType == "upgrade" {
isoinstallerloaded = false
}
if isoinstallerloaded {
log . Debugf ( "running isoinstallerloaded..." )
2017-01-17 01:16:49 +00:00
// TODO: detect if its not mounted and then optionally mount?
2016-12-07 11:56:32 +00:00
if err := mountBootIso ( ) ; err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "error mountBootIso %s" , err )
2016-12-07 11:56:32 +00:00
return err
2016-12-07 03:33:43 +00:00
}
2016-12-01 03:55:19 +00:00
}
2016-12-20 12:49:34 +00:00
err := layDownOS ( image , installType , cloudConfig , device , kappend , kexec )
2016-12-01 03:55:19 +00:00
if err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "error layDownOS %s" , err )
2015-08-18 14:07:00 +00:00
return err
}
2016-12-20 12:49:34 +00:00
if ! kexec && reboot && ( force || yes ( "Continue with reboot" ) ) {
2015-08-18 14:07:00 +00:00
log . Info ( "Rebooting" )
power . Reboot ( )
}
return nil
}
2016-12-01 03:55:19 +00:00
2016-12-07 03:33:43 +00:00
func mountBootIso ( ) error {
2016-12-07 11:56:32 +00:00
deviceName := "/dev/sr0"
deviceType := "iso9660"
2016-12-15 01:54:43 +00:00
{ // force the defer
mountsFile , err := os . Open ( "/proc/mounts" )
if err != nil {
log . Errorf ( "failed to read /proc/mounts %s" , err )
return err
}
defer mountsFile . Close ( )
if partitionMounted ( deviceName , mountsFile ) {
return nil
}
}
os . MkdirAll ( "/bootiso" , 0755 )
2016-12-07 11:56:32 +00:00
// find the installation device
cmd := exec . Command ( "blkid" , "-L" , "RancherOS" )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
out , err := cmd . Output ( )
if err != nil {
log . Errorf ( "Failed to get RancherOS boot device: %s" , err )
return err
}
deviceName = strings . TrimSpace ( string ( out ) )
log . Debugf ( "blkid found -L RancherOS: %s" , deviceName )
cmd = exec . Command ( "blkid" , deviceName )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err = cmd . Output ( ) ; err != nil {
log . Errorf ( "Failed to get RancherOS boot device type: %s" , err )
return err
}
deviceType = strings . TrimSpace ( string ( out ) )
s1 := strings . Split ( deviceType , "TYPE=\"" )
s2 := strings . Split ( s1 [ 1 ] , "\"" )
deviceType = s2 [ 0 ]
log . Debugf ( "blkid type of %s: %s" , deviceName , deviceType )
cmd = exec . Command ( "mount" , "-t" , deviceType , deviceName , "/bootiso" )
log . Debugf ( "Run(%v)" , cmd )
2016-12-07 03:33:43 +00:00
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
2016-12-07 11:56:32 +00:00
err = cmd . Run ( )
2016-12-07 03:33:43 +00:00
if err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "tried and failed to mount %s: %s" , deviceName , err )
2016-12-07 03:33:43 +00:00
} else {
2016-12-07 11:56:32 +00:00
log . Debugf ( "Mounted %s" , deviceName )
2016-12-07 03:33:43 +00:00
}
return err
}
2016-12-20 12:49:34 +00:00
func layDownOS ( image , installType , cloudConfig , device , kappend string , kexec bool ) error {
2016-12-01 03:55:19 +00:00
// ENV == installType
//[[ "$ARCH" == "arm" && "$ENV" != "rancher-upgrade" ]] && ENV=arm
// image == rancher/os:v0.7.0_arm
// TODO: remove the _arm suffix (but watch out, its not always there..)
VERSION := image [ strings . Index ( image , ":" ) + 1 : ]
var FILES [ ] string
DIST := "/dist" //${DIST:-/dist}
//cloudConfig := SCRIPTS_DIR + "/conf/empty.yml" //${cloudConfig:-"${SCRIPTS_DIR}/conf/empty.yml"}
CONSOLE := "tty0"
baseName := "/mnt/new_img"
2016-12-20 12:49:34 +00:00
bootDir := "boot/"
2016-12-01 03:55:19 +00:00
//# TODO: Change this to a number so that users can specify.
//# Will need to make it so that our builds and packer APIs remain consistent.
partition := device + "1" //${partition:=${device}1}
kernelArgs := "rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait" // console="+CONSOLE
// unmount on trap
defer util . Unmount ( baseName )
2016-12-15 01:54:43 +00:00
diskType := "msdos"
if installType == "gptsyslinux" {
diskType = "gpt"
}
2016-12-01 03:55:19 +00:00
switch installType {
2016-12-15 01:54:43 +00:00
case "syslinux" :
fallthrough
case "gptsyslinux" :
fallthrough
2016-12-01 03:55:19 +00:00
case "generic" :
2016-12-07 11:56:32 +00:00
log . Debugf ( "formatAndMount" )
2016-12-01 03:55:19 +00:00
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = formatAndMount ( baseName , bootDir , device , partition )
2016-12-01 03:55:19 +00:00
if err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "formatAndMount %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-15 01:54:43 +00:00
err = installSyslinux ( device , baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
if err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "installSyslinux %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
err = seedData ( baseName , cloudConfig , FILES )
if err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "seedData %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
case "arm" :
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = formatAndMount ( baseName , bootDir , device , partition )
2016-12-01 03:55:19 +00:00
if err != nil {
return err
}
seedData ( baseName , cloudConfig , FILES )
case "amazon-ebs-pv" :
fallthrough
case "amazon-ebs-hvm" :
CONSOLE = "ttyS0"
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = formatAndMount ( baseName , bootDir , device , partition )
2016-12-01 03:55:19 +00:00
if err != nil {
return err
}
if installType == "amazon-ebs-hvm" {
2016-12-20 12:49:34 +00:00
installSyslinux ( device , baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
}
//# AWS Networking recommends disabling.
seedData ( baseName , cloudConfig , FILES )
case "googlecompute" :
CONSOLE = "ttyS0"
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = formatAndMount ( baseName , bootDir , device , partition )
2016-12-01 03:55:19 +00:00
if err != nil {
return err
}
2016-12-20 12:49:34 +00:00
installSyslinux ( device , baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
seedData ( baseName , cloudConfig , FILES )
case "noformat" :
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = mountdevice ( baseName , bootDir , partition , false )
2016-12-01 03:55:19 +00:00
if err != nil {
return err
}
2016-12-15 01:54:43 +00:00
installSyslinux ( device , baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
case "raid" :
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = mountdevice ( baseName , bootDir , partition , false )
2016-12-01 03:55:19 +00:00
if err != nil {
return err
}
2016-12-15 01:54:43 +00:00
installSyslinuxRaid ( baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
case "bootstrap" :
CONSOLE = "ttyS0"
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = mountdevice ( baseName , bootDir , partition , true )
2016-12-01 03:55:19 +00:00
if err != nil {
return err
}
kernelArgs = kernelArgs + " rancher.cloud_init.datasources=[ec2,gce]"
2016-12-20 12:49:34 +00:00
case "upgrade" :
fallthrough
2016-12-01 03:55:19 +00:00
case "rancher-upgrade" :
var err error
2016-12-20 12:49:34 +00:00
device , partition , err = mountdevice ( baseName , bootDir , partition , false )
2016-12-01 03:55:19 +00:00
if err != nil {
return err
}
2016-12-20 12:49:34 +00:00
// TODO: detect pv-grub, and don't kill it with syslinux
upgradeBootloader ( device , baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
default :
return fmt . Errorf ( "unexpected install type %s" , installType )
}
kernelArgs = kernelArgs + " console=" + CONSOLE
if kappend == "" {
preservedAppend , _ := ioutil . ReadFile ( filepath . Join ( baseName , bootDir + "append" ) )
kappend = string ( preservedAppend )
} else {
ioutil . WriteFile ( filepath . Join ( baseName , bootDir + "append" ) , [ ] byte ( kappend ) , 0644 )
}
2016-12-20 12:49:34 +00:00
if installType == "amazon-ebs-pv" {
menu := install . BootVars {
BaseName : baseName ,
BootDir : bootDir ,
Timeout : 0 ,
Fallback : 0 , // need to be conditional on there being a 'rollback'?
Entries : [ ] install . MenuEntry {
install . MenuEntry { "RancherOS-current" , bootDir , VERSION , kernelArgs , kappend } ,
} ,
}
install . PvGrubConfig ( menu )
}
2016-12-01 03:55:19 +00:00
log . Debugf ( "installRancher" )
2016-12-07 10:55:38 +00:00
err := installRancher ( baseName , bootDir , VERSION , DIST , kernelArgs + " " + kappend )
2016-12-01 03:55:19 +00:00
if err != nil {
log . Errorf ( "%s" , err )
return err
}
log . Debugf ( "installRancher done" )
2016-12-20 12:49:34 +00:00
// Used by upgrade
if kexec {
// kexec -l ${DIST}/vmlinuz --initrd=${DIST}/initrd --append="${kernelArgs} ${APPEND}" -f
cmd := exec . Command ( "kexec" , "-l " + DIST + "/vmlinuz" ,
"--initrd=" + DIST + "/initrd" ,
"--append='" + kernelArgs + " " + kappend + "'" ,
"-f" )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if _ , err := cmd . Output ( ) ; err != nil {
log . Errorf ( "Failed to kexec: %s" , err )
return err
}
log . Infof ( "kexec'd to new install" )
}
2016-12-01 03:55:19 +00:00
return nil
}
// files is an array of 'sourcefile:destination' - but i've not seen any examples of it being used.
func seedData ( baseName , cloudData string , files [ ] string ) error {
log . Debugf ( "seedData" )
_ , err := os . Stat ( baseName )
if err != nil {
return err
}
if err = os . MkdirAll ( filepath . Join ( baseName , "/var/lib/rancher/conf/cloud-config.d" ) , 0755 ) ; err != nil {
return err
}
2016-12-20 12:49:34 +00:00
if ! strings . HasSuffix ( cloudData , "empty.yml" ) {
2016-12-01 03:55:19 +00:00
if err = dfs . CopyFile ( cloudData , baseName + "/var/lib/rancher/conf/cloud-config.d/" , filepath . Base ( cloudData ) ) ; err != nil {
return err
}
}
for _ , f := range files {
e := strings . Split ( f , ":" )
if err = dfs . CopyFile ( e [ 0 ] , baseName , e [ 1 ] ) ; err != nil {
return err
}
}
return nil
}
// set-disk-partitions is called with device == **/dev/sda**
2016-12-15 01:54:43 +00:00
func setDiskpartitions ( device , diskType string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "setDiskpartitions" )
d := strings . Split ( device , "/" )
if len ( d ) != 3 {
return fmt . Errorf ( "bad device name (%s)" , device )
}
deviceName := d [ 2 ]
file , err := os . Open ( "/proc/partitions" )
if err != nil {
2016-12-07 03:33:43 +00:00
log . Debugf ( "failed to read /proc/partitions %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
defer file . Close ( )
exists := false
haspartitions := false
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
str := scanner . Text ( )
last := strings . LastIndex ( str , " " )
if last > - 1 {
dev := str [ last + 1 : ]
if strings . HasPrefix ( dev , deviceName ) {
if dev == deviceName {
exists = true
} else {
haspartitions = true
}
}
}
}
if ! exists {
2016-12-07 11:56:32 +00:00
return fmt . Errorf ( "disk %s not found: %s" , device , err )
2016-12-01 03:55:19 +00:00
}
if haspartitions {
2016-12-07 03:33:43 +00:00
log . Debugf ( "device %s already partitioned - checking if any are mounted" , device )
2016-12-01 03:55:19 +00:00
file , err := os . Open ( "/proc/mounts" )
if err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "failed to read /proc/mounts %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
defer file . Close ( )
if partitionMounted ( device , file ) {
err = fmt . Errorf ( "partition %s mounted, cannot repartition" , device )
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-15 13:15:49 +00:00
2016-12-01 03:55:19 +00:00
cmd := exec . Command ( "system-docker" , "ps" , "-q" )
var outb bytes . Buffer
cmd . Stdout = & outb
if err := cmd . Run ( ) ; err != nil {
2016-12-15 13:15:49 +00:00
log . Printf ( "ps error: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
for _ , image := range strings . Split ( outb . String ( ) , "\n" ) {
if image == "" {
continue
}
r , w := io . Pipe ( )
go func ( ) {
// TODO: consider a timeout
2016-12-15 13:15:49 +00:00
// TODO:some of these containers don't have cat / shell
2016-12-01 03:55:19 +00:00
cmd := exec . Command ( "system-docker" , "exec" , image , "cat /proc/mount" )
cmd . Stdout = w
if err := cmd . Run ( ) ; err != nil {
2016-12-15 13:15:49 +00:00
log . Debugf ( "%s cat %s" , image , err )
2016-12-01 03:55:19 +00:00
}
2016-12-15 13:15:49 +00:00
w . Close ( )
2016-12-01 03:55:19 +00:00
} ( )
if partitionMounted ( device , r ) {
err = fmt . Errorf ( "partition %s mounted in %s, cannot repartition" , device , image )
2016-12-15 13:15:49 +00:00
log . Errorf ( "k? %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
}
}
//do it!
2016-12-07 03:33:43 +00:00
log . Debugf ( "running dd" )
2016-12-01 03:55:19 +00:00
cmd := exec . Command ( "dd" , "if=/dev/zero" , "of=" + device , "bs=512" , "count=2048" )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "dd error %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-07 03:33:43 +00:00
log . Debugf ( "running partprobe" )
2016-12-01 03:55:19 +00:00
cmd = exec . Command ( "partprobe" , device )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "partprobe error %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-20 12:49:34 +00:00
log . Debugf ( "making single RANCHER_STATE partition" )
2016-12-15 01:54:43 +00:00
cmd = exec . Command ( "parted" , "-s" , "-a" , "optimal" , device ,
"mklabel " + diskType , "--" ,
2016-12-20 12:49:34 +00:00
"mkpart primary ext4 1 -1" )
2016-12-15 01:54:43 +00:00
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "parted: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-20 12:49:34 +00:00
if err := setBootable ( device , diskType ) ; err != nil {
return err
}
2016-12-01 03:55:19 +00:00
return nil
}
func partitionMounted ( device string , file io . Reader ) bool {
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
str := scanner . Text ( )
// /dev/sdb1 /data ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
ele := strings . Split ( str , " " )
if len ( ele ) > 5 {
if strings . HasPrefix ( ele [ 0 ] , device ) {
return true
}
}
if err := scanner . Err ( ) ; err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "scanner %s" , err )
2016-12-01 03:55:19 +00:00
return false
}
}
return false
}
func formatdevice ( device , partition string ) error {
log . Debugf ( "formatdevice %s" , partition )
//mkfs.ext4 -F -i 4096 -L RANCHER_STATE ${partition}
2016-12-04 11:27:06 +00:00
// -O ^64bit: for syslinux: http://www.syslinux.org/wiki/index.php?title=Filesystem#ext
cmd := exec . Command ( "mkfs.ext4" , "-F" , "-i" , "4096" , "-O" , "^64bit" , "-L" , "RANCHER_STATE" , partition )
2016-12-01 03:55:19 +00:00
log . Debugf ( "Run(%v)" , cmd )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "mkfs.ext4: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
2016-12-20 12:49:34 +00:00
func mountdevice ( baseName , bootDir , partition string , raw bool ) ( string , string , error ) {
2016-12-01 03:55:19 +00:00
log . Debugf ( "mountdevice %s, raw %v" , partition , raw )
2016-12-20 12:49:34 +00:00
device := ""
2016-12-01 03:55:19 +00:00
if raw {
log . Debugf ( "util.Mount (raw) %s, %s" , partition , baseName )
2016-12-20 12:49:34 +00:00
cmd := exec . Command ( "lsblk" , "-no" , "pkname" , partition )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err := cmd . Output ( ) ; err == nil {
device = "/dev/" + strings . TrimSpace ( string ( out ) )
}
return device , partition , util . Mount ( partition , baseName , "" , "" )
2016-12-01 03:55:19 +00:00
}
2016-12-20 12:49:34 +00:00
//rootfs := partition
2016-12-01 03:55:19 +00:00
// Don't use ResolveDevice - it can fail, whereas `blkid -L LABEL` works more often
//if dev := util.ResolveDevice("LABEL=RANCHER_BOOT"); dev != "" {
cmd := exec . Command ( "blkid" , "-L" , "RANCHER_BOOT" )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err := cmd . Output ( ) ; err == nil {
2016-12-20 12:49:34 +00:00
partition = strings . TrimSpace ( string ( out ) )
2016-12-01 03:55:19 +00:00
} else {
cmd := exec . Command ( "blkid" , "-L" , "RANCHER_STATE" )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err := cmd . Output ( ) ; err == nil {
2016-12-20 12:49:34 +00:00
partition = strings . TrimSpace ( string ( out ) )
2016-12-01 03:55:19 +00:00
}
}
2016-12-20 12:49:34 +00:00
cmd = exec . Command ( "lsblk" , "-no" , "pkname" , partition )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err := cmd . Output ( ) ; err == nil {
device = "/dev/" + strings . TrimSpace ( string ( out ) )
}
2016-12-01 03:55:19 +00:00
2016-12-20 12:49:34 +00:00
log . Debugf ( "util.Mount %s, %s" , partition , baseName )
2016-12-01 03:55:19 +00:00
os . MkdirAll ( baseName , 0755 )
2016-12-20 12:49:34 +00:00
cmd = exec . Command ( "mount" , partition , baseName )
2016-12-01 03:55:19 +00:00
log . Debugf ( "Run(%v)" , cmd )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-20 12:49:34 +00:00
return device , partition , cmd . Run ( )
2016-12-01 03:55:19 +00:00
}
2016-12-20 12:49:34 +00:00
func formatAndMount ( baseName , bootDir , device , partition string ) ( string , string , error ) {
2016-12-01 03:55:19 +00:00
log . Debugf ( "formatAndMount" )
err := formatdevice ( device , partition )
if err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "formatdevice %s" , err )
2016-12-20 12:49:34 +00:00
return device , partition , err
2016-12-01 03:55:19 +00:00
}
2016-12-20 12:49:34 +00:00
device , partition , err = mountdevice ( baseName , bootDir , partition , false )
2016-12-01 03:55:19 +00:00
if err != nil {
2016-12-15 13:15:49 +00:00
log . Errorf ( "mountdevice %s" , err )
2016-12-20 12:49:34 +00:00
return device , partition , err
2016-12-01 03:55:19 +00:00
}
2016-12-20 12:49:34 +00:00
//err = createbootDirs(baseName, bootDir)
//if err != nil {
// log.Errorf("createbootDirs %s", err)
// return bootDir, err
//}
return device , partition , nil
2016-12-01 03:55:19 +00:00
}
2016-12-20 12:49:34 +00:00
func NOPEcreatebootDir ( baseName , bootDir string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "createbootDirs" )
if err := os . MkdirAll ( filepath . Join ( baseName , bootDir + "grub" ) , 0755 ) ; err != nil {
return err
}
if err := os . MkdirAll ( filepath . Join ( baseName , bootDir + "syslinux" ) , 0755 ) ; err != nil {
return err
}
return nil
}
2016-12-20 12:49:34 +00:00
func setBootable ( device , diskType string ) error {
// TODO make conditional - if there is a bootable device already, don't break it
// TODO: make RANCHER_BOOT bootable - it might not be device 1
bootflag := "boot"
if diskType == "gpt" {
bootflag = "legacy_boot"
}
log . Debugf ( "making device 1 on %s bootable as %s" , device , diskType )
cmd := exec . Command ( "parted" , "-s" , "-a" , "optimal" , device , "set 1 " + bootflag + " on" )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
log . Errorf ( "parted: %s" , err )
return err
}
return nil
}
func upgradeBootloader ( device , baseName , bootDir , diskType string ) error {
log . Debugf ( "start upgradeBootloader" )
grubDir := filepath . Join ( baseName , bootDir + "grub" )
if _ , err := os . Stat ( grubDir ) ; os . IsNotExist ( err ) {
log . Debugf ( "%s does not exist - no need to upgrade bootloader" , grubDir )
// we've already upgraded
// TODO: in v0.9.0, need to detect what version syslinux we have
return nil
}
if err := setBootable ( device , diskType ) ; err != nil {
log . Debugf ( "setBootable(%s, %s): %s" , device , diskType , err )
//return err
}
if err := os . Rename ( grubDir , filepath . Join ( baseName , bootDir + "grub_backup" ) ) ; err != nil {
log . Debugf ( "Rename(%s): %s" , grubDir , err )
return err
}
syslinuxDir := filepath . Join ( baseName , bootDir + "syslinux" )
backupSyslinuxDir := filepath . Join ( baseName , bootDir + "syslinux_backup" )
if err := os . Rename ( syslinuxDir , backupSyslinuxDir ) ; err != nil {
log . Debugf ( "Rename(%s, %s): %s" , syslinuxDir , backupSyslinuxDir , err )
return err
}
//mv the old syslinux into linux-previous.cfg
oldSyslinux , err := ioutil . ReadFile ( filepath . Join ( backupSyslinuxDir , "syslinux.cfg" ) )
if err != nil {
log . Debugf ( "read(%s / syslinux.cfg): %s" , backupSyslinuxDir , err )
return err
}
cfg := string ( oldSyslinux )
//DEFAULT RancherOS-current
//
//LABEL RancherOS-current
// LINUX ../vmlinuz-v0.7.1-rancheros
// APPEND rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait console=tty0 rancher.password=rancher
// INITRD ../initrd-v0.7.1-rancheros
cfg = strings . Replace ( cfg , "current" , "previous" , - 1 )
cfg = strings . Replace ( cfg , "../" , "/boot/" , - 1 )
// TODO consider removing the APPEND line - as the global.cfg should have the same result
ioutil . WriteFile ( filepath . Join ( baseName , bootDir , "linux-current.cfg" ) , [ ] byte ( cfg ) , 0644 )
return installSyslinux ( device , baseName , bootDir , diskType )
}
2016-12-15 01:54:43 +00:00
func installSyslinux ( device , baseName , bootDir , diskType string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "installSyslinux" )
2016-12-15 01:54:43 +00:00
mbrFile := "mbr.bin"
if diskType == "gpt" {
mbrFile = "gptmbr.bin"
}
2016-12-01 03:55:19 +00:00
//dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=${device}
2016-12-04 11:27:06 +00:00
// ubuntu: /usr/lib/syslinux/mbr/mbr.bin
// alpine: /usr/share/syslinux/mbr.bin
2016-12-15 01:54:43 +00:00
cmd := exec . Command ( "dd" , "bs=440" , "count=1" , "if=/usr/share/syslinux/" + mbrFile , "of=" + device )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-04 11:27:06 +00:00
log . Debugf ( "Run(%v)" , cmd )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "dd: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-20 12:49:34 +00:00
if err := os . MkdirAll ( filepath . Join ( baseName , bootDir + "syslinux" ) , 0755 ) ; err != nil {
return err
}
2016-12-01 03:55:19 +00:00
//cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
2016-12-04 11:27:06 +00:00
files , _ := ioutil . ReadDir ( "/usr/share/syslinux/" )
for _ , file := range files {
if file . IsDir ( ) {
continue
}
if err := dfs . CopyFile ( filepath . Join ( "/usr/share/syslinux/" , file . Name ( ) ) , filepath . Join ( baseName , bootDir , "syslinux" ) , file . Name ( ) ) ; err != nil {
log . Errorf ( "copy syslinux: %s" , err )
return err
}
2016-12-01 03:55:19 +00:00
}
2016-12-04 11:27:06 +00:00
2016-12-01 03:55:19 +00:00
//extlinux --install ${baseName}/${bootDir}syslinux
cmd = exec . Command ( "extlinux" , "--install" , filepath . Join ( baseName , bootDir + "syslinux" ) )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-04 11:27:06 +00:00
log . Debugf ( "Run(%v)" , cmd )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "extlinuux: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
2016-12-15 01:54:43 +00:00
func installSyslinuxRaid ( baseName , bootDir , diskType string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "installSyslinuxRaid" )
2016-12-15 01:54:43 +00:00
mbrFile := "mbr.bin"
if diskType == "gpt" {
mbrFile = "gptmbr.bin"
}
2016-12-01 03:55:19 +00:00
//dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=/dev/sda
//dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=/dev/sdb
//cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
//extlinux --install --raid ${baseName}/${bootDir}syslinux
2016-12-15 01:54:43 +00:00
cmd := exec . Command ( "dd" , "bs=440" , "count=1" , "if=/usr/share/syslinux/" + mbrFile , "of=/dev/sda" )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-15 01:54:43 +00:00
cmd = exec . Command ( "dd" , "bs=440" , "count=1" , "if=/usr/share/syslinux/" + mbrFile , "of=/dev/sdb" )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-20 12:49:34 +00:00
if err := os . MkdirAll ( filepath . Join ( baseName , bootDir + "syslinux" ) , 0755 ) ; err != nil {
return err
}
2016-12-04 11:27:06 +00:00
//cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
files , _ := ioutil . ReadDir ( "/usr/share/syslinux/" )
for _ , file := range files {
if file . IsDir ( ) {
continue
}
if err := dfs . CopyFile ( filepath . Join ( "/usr/share/syslinux/" , file . Name ( ) ) , filepath . Join ( baseName , bootDir , "syslinux" ) , file . Name ( ) ) ; err != nil {
log . Errorf ( "copy syslinux: %s" , err )
return err
}
2016-12-01 03:55:19 +00:00
}
2016-12-20 12:49:34 +00:00
cmd = exec . Command ( "extlinux" , "--install" , "--raid" , filepath . Join ( baseName , bootDir + "syslinux" ) )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
2016-12-07 10:55:38 +00:00
func installRancher ( baseName , bootDir , VERSION , DIST , kappend string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "installRancher" )
2016-12-20 12:49:34 +00:00
// detect if there already is a linux-current.cfg, if so, move it to linux-previous.cfg,
currentCfg := filepath . Join ( baseName , bootDir , "linux-current.cfg" )
if _ , err := os . Stat ( currentCfg ) ; ! os . IsNotExist ( err ) {
previousCfg := filepath . Join ( baseName , bootDir , "linux-previous.cfg" )
if _ , err := os . Stat ( previousCfg ) ; ! os . IsNotExist ( err ) {
if err := os . Remove ( previousCfg ) ; err != nil {
return err
}
}
os . Rename ( currentCfg , previousCfg )
}
2016-12-07 03:33:43 +00:00
2016-12-07 10:55:38 +00:00
// 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
2016-12-07 03:33:43 +00:00
files , _ := ioutil . ReadDir ( DIST )
for _ , file := range files {
if file . IsDir ( ) {
continue
}
if err := dfs . CopyFile ( filepath . Join ( DIST , file . Name ( ) ) , filepath . Join ( baseName , bootDir ) , file . Name ( ) ) ; err != nil {
log . Errorf ( "copy %s: %s" , file . Name ( ) , err )
return err
}
}
2016-12-20 12:49:34 +00:00
// the general INCLUDE syslinuxcfg
2016-12-07 03:33:43 +00:00
if err := dfs . CopyFile ( filepath . Join ( DIST , "isolinux" , "isolinux.cfg" ) , filepath . Join ( baseName , bootDir , "syslinux" ) , "syslinux.cfg" ) ; err != nil {
log . Errorf ( "copy %s: %s" , "syslinux.cfg" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-20 12:49:34 +00:00
2016-12-07 10:55:38 +00:00
// The global.cfg INCLUDE - useful for over-riding the APPEND line
err := ioutil . WriteFile ( filepath . Join ( filepath . Join ( baseName , bootDir ) , "global.cfg" ) , [ ] byte ( "APPEND " + kappend ) , 0644 )
if err != nil {
log . Errorf ( "write (%s) %s" , "global.cfg" , err )
return err
}
2016-12-01 03:55:19 +00:00
return nil
}