mirror of
https://github.com/clearlinux/clr-installer.git
synced 2026-09-02 19:51:30 +00:00
588 lines
15 KiB
Go
588 lines
15 KiB
Go
// Copyright © 2020 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
package args
|
|
|
|
// Arguments which influence how this program executes
|
|
// Order of Precedence
|
|
// 1. Command Line Arguments -- Highest Priority
|
|
// 2. Kernel Command Line Arguments
|
|
// 3. Program defaults -- Lowest Priority
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/user"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/clearlinux/clr-installer/conf"
|
|
"github.com/clearlinux/clr-installer/log"
|
|
"github.com/clearlinux/clr-installer/network"
|
|
spflag "github.com/spf13/pflag"
|
|
)
|
|
|
|
const (
|
|
kernelCmdlineConf = "clri.descriptor"
|
|
kernelCmdlineDemo = "clri.demo"
|
|
kernelCmdlineLog = "clri.loglevel"
|
|
kernelCmdlineHighContrast = "clri.hc"
|
|
// KernelMediaCheck is used to create a verufy ISO media boot menu
|
|
KernelMediaCheck = "clri.mediacheck"
|
|
logFileEnvironVar = "CLR_INSTALLER_LOG_FILE"
|
|
)
|
|
|
|
var (
|
|
kernelCmdlineFile = "/proc/cmdline"
|
|
)
|
|
|
|
// Args represents the user provided arguments
|
|
type Args struct {
|
|
Version bool
|
|
Reboot bool
|
|
RebootSet bool
|
|
Offline bool
|
|
OfflineSet bool
|
|
LogFile string
|
|
ConfigFile string
|
|
CfDownloaded bool
|
|
CfPurge bool
|
|
CfPurgeSet bool
|
|
AllowInsecureHTTP bool
|
|
AllowInsecureHTTPSet bool
|
|
CryptPassFile string
|
|
SwupdSkipOptional bool
|
|
SwupdSkipOptionalSet bool
|
|
SwupdMirror string
|
|
SwupdStateDir string
|
|
SwupdCertPath string
|
|
SwupdStateClean bool
|
|
SwupdFormat string
|
|
SwupdVersion string
|
|
SwupdContentURL string
|
|
SwupdVersionURL string
|
|
SwupdURL string
|
|
SwupdSkipDiskSpaceCheck bool
|
|
Telemetry bool
|
|
TelemetrySet bool
|
|
TelemetryURL string
|
|
TelemetryTID string
|
|
TelemetryPolicy string
|
|
PamSalt string
|
|
LogLevel int
|
|
ForceTUI bool
|
|
ForceGUI bool
|
|
Archive bool
|
|
ArchiveSet bool
|
|
DemoMode bool
|
|
Bundles []string
|
|
BlockDevices []string
|
|
StubImage bool
|
|
ConvertConfigFile string
|
|
TemplateConfigFile string
|
|
MakeISO bool
|
|
MakeISOSet bool
|
|
KeepImage bool
|
|
KeepImageSet bool
|
|
SystemCheck bool
|
|
CopyNetwork bool
|
|
CopySwupd bool
|
|
CopySwupdSet bool
|
|
HighContrast bool
|
|
CBMPath string
|
|
SkipValidationSize bool
|
|
SkipValidationSizeSet bool
|
|
SkipValidationAll bool
|
|
SkipValidationAllSet bool
|
|
SwapFileSize string
|
|
ForceDestructive bool
|
|
}
|
|
|
|
func (args *Args) setKernelArgs() (err error) {
|
|
var (
|
|
kernelCmd string
|
|
url string
|
|
)
|
|
|
|
if kernelCmd, err = args.readKernelCmd(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Parse the kernel command for relevant installer options
|
|
for _, curr := range strings.Split(kernelCmd, " ") {
|
|
curr = strings.TrimSpace(curr)
|
|
if strings.HasPrefix(curr, kernelCmdlineConf+"=") {
|
|
url = strings.Split(curr, "=")[1]
|
|
} else if strings.HasPrefix(curr, kernelCmdlineDemo) {
|
|
args.DemoMode = true
|
|
} else if strings.HasPrefix(curr, kernelCmdlineHighContrast) {
|
|
args.HighContrast = true
|
|
} else if strings.HasPrefix(curr, kernelCmdlineLog) {
|
|
logLevelString := strings.Split(curr, "=")[1]
|
|
if logLevel, _ := strconv.Atoi(logLevelString); err != nil {
|
|
log.Warning("Ignoring invalid kernel parameter %s='%s'", kernelCmdlineLog, logLevelString)
|
|
} else {
|
|
args.LogLevel = logLevel
|
|
}
|
|
}
|
|
}
|
|
|
|
if url != "" {
|
|
networkGood := false
|
|
downFailCount := 1
|
|
|
|
var ffile string
|
|
msg := fmt.Sprintf("Downloading configuration file %q", url)
|
|
|
|
var downloadSleep time.Duration
|
|
for { // Keep retrying to download configuration file
|
|
time.Sleep(downloadSleep * time.Second)
|
|
fmt.Println(msg)
|
|
|
|
if ffile, err = network.FetchRemoteConfigFile(url); err != nil {
|
|
downFailCount++
|
|
msg = fmt.Sprintf("Downloading configuration file %q [%d]", url, downFailCount)
|
|
// Try restarting the networking every 10th fail
|
|
if downFailCount%10 == 0 {
|
|
networkGood = false
|
|
}
|
|
|
|
failMsg := fmt.Sprintf("Failed to download: %s", err)
|
|
fmt.Println(failMsg)
|
|
downloadSleep = 5
|
|
|
|
var retrySleep time.Duration = 5
|
|
for { // Keep retrying to initialize the network
|
|
if networkGood {
|
|
break
|
|
}
|
|
|
|
time.Sleep(retrySleep * time.Second)
|
|
|
|
fmt.Println("Checking network....")
|
|
|
|
// Restart networking if we failed
|
|
// The likely gain is restarting pacdiscovery to fix autoproxy
|
|
if err := network.Restart(); err != nil {
|
|
log.Warning("Network restart failed")
|
|
fmt.Println("Warning: Network restart failed!")
|
|
retrySleep = 10
|
|
} else {
|
|
networkGood = true
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
args.ConfigFile = ffile
|
|
args.CfDownloaded = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// readKernelCmd returns the kernel command line
|
|
func (args *Args) readKernelCmd() (string, error) {
|
|
content, err := ioutil.ReadFile(kernelCmdlineFile)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(content), nil
|
|
}
|
|
|
|
func (args *Args) setCommandLineArgs() (err error) {
|
|
flag := spflag.NewFlagSet(path.Base(os.Args[0]), spflag.ExitOnError)
|
|
|
|
makeFlagHidden := func(flag *spflag.FlagSet, flagnames ...string) {
|
|
for _, flagname := range flagnames {
|
|
fflag := flag.Lookup(flagname)
|
|
if fflag != nil {
|
|
fflag.Hidden = true
|
|
}
|
|
}
|
|
}
|
|
|
|
flag.BoolVarP(
|
|
&args.Version, "version", "v", false, "Version of the Installer",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.Reboot, "reboot", true, "Reboot after finishing",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.Offline, "offline", false, "Install update content for minimal offline installation",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.CfPurge, "cfPurge", false, "Remove ConfigFile after finishing",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.ForceTUI, "tui", false, "Use TUI frontend",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.ForceGUI, "gui", false, "Use GUI frontend",
|
|
)
|
|
// We do not want this flag to be shown as part of the standard help message
|
|
makeFlagHidden(flag, "gui")
|
|
|
|
flag.StringSliceVarP(
|
|
&args.Bundles, "bundles", "B", args.Bundles, "Comma-separated list of bundles to install",
|
|
)
|
|
|
|
flag.StringSliceVarP(
|
|
&args.BlockDevices, "block-device", "b", args.BlockDevices,
|
|
"Adds a new block-device's entry to configuration file. Format: <alias:filename>",
|
|
)
|
|
|
|
flag.StringVarP(
|
|
&args.ConfigFile, "config", "c", args.ConfigFile, "Installation configuration file",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.CryptPassFile, "crypt-file", args.CryptPassFile, "File containing the cryptsetup password",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdMirror, "swupd-mirror", args.SwupdMirror, "Swupd --url; sets target mirror",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdStateDir, "swupd-state", args.SwupdStateDir, "Swupd --statedir",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdCertPath, "swupd-cert", args.SwupdCertPath, "Swupd --certpath",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.SwupdStateClean, "swupd-clean",
|
|
false, "Clean Swupd state-dir content after install",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdFormat, "swupd-format", args.SwupdFormat, "Swupd --format argument",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdVersion, "swupd-version", args.SwupdVersion, "Swupd --version argument",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdContentURL, "swupd-contenturl", args.SwupdContentURL,
|
|
"Swupd --contenturl argument",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdVersionURL, "swupd-versionurl", args.SwupdVersionURL,
|
|
"Swupd --versionurl argument",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.SwupdURL, "swupd-url", args.SwupdURL,
|
|
"Swupd --url argument; use the same for content and version",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.SwupdSkipDiskSpaceCheck, "swupd-skip-diskspace-check",
|
|
true, "Swupd --skip-diskspace-check argument",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.Telemetry, "telemetry", args.Telemetry, "Enable Telemetry",
|
|
)
|
|
|
|
flag.BoolVarP(
|
|
&args.StubImage, "stub-image", "S",
|
|
args.StubImage, "Creates the filesystems only - dont perform an actual install",
|
|
)
|
|
|
|
flag.StringVarP(
|
|
&args.ConvertConfigFile, "json-yaml", "j", args.ConvertConfigFile,
|
|
"Converts ister JSON config to clr-installer YAML config",
|
|
)
|
|
|
|
flag.StringVarP(
|
|
&args.TemplateConfigFile, "template", "T", args.TemplateConfigFile,
|
|
"Generates a template clr-installer YAML config file",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.TelemetryURL, "telemetry-url", args.TelemetryURL, "Telemetry server URL",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.TelemetryTID, "telemetry-tid", args.TelemetryTID, "Telemetry server TID",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.TelemetryPolicy, "telemetry-policy", args.TelemetryPolicy, "Telemetry Policy text",
|
|
)
|
|
|
|
flag.StringVar(
|
|
&args.PamSalt, "genpass", "",
|
|
"Generates a PAM compatible password hash based on the provided salt string",
|
|
)
|
|
|
|
flag.IntVarP(
|
|
&args.LogLevel,
|
|
"log-level",
|
|
"l",
|
|
args.LogLevel,
|
|
fmt.Sprintf("%d (debug), %d (info), %d (warning), %d (error)",
|
|
log.LogLevelDebug, log.LogLevelInfo, log.LogLevelWarning, log.LogLevelError),
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.AllowInsecureHTTP, "allow-insecure-http", false,
|
|
"Allow installation over insecure connections",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.SwupdSkipOptional, "swupd-skip-optional", false,
|
|
"Swupd --skip-optional; don't install optionally included bundles",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.Archive, "archive", true, "Archive data to target after finishing",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.DemoMode, "demo", args.DemoMode, "Demonstration mode for documentation generation",
|
|
)
|
|
// We do not want this flag to be shown as part of the standard help message
|
|
makeFlagHidden(flag, "demo")
|
|
|
|
usr, err := user.Current()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var defaultLogFile string
|
|
|
|
// use the env var CLR_INSTALLER_LOG_FILE to determine the log file path
|
|
if defaultLogFile = os.Getenv(logFileEnvironVar); defaultLogFile == "" {
|
|
defaultLogFile = filepath.Join(usr.HomeDir, conf.LogFile)
|
|
}
|
|
|
|
flag.StringVar(
|
|
&args.LogFile, "log-file", defaultLogFile, "The log file path",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.MakeISO, "iso", false, "Generate Hybrid ISO image (Legacy/UEFI bootable)",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.KeepImage, "keep-image", true, "Keep the generated image file (when creating ISO)",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.SystemCheck, "system-check", false, "Verify current system is compatible with Clear Linux and exit",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.CopyNetwork, "copy-network", true, "Copy the network interface configuration files to target",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.CopySwupd, "copy-swupd", false, "Copy /etc/swupd configuration files to target [interactive=true]",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.HighContrast, "high-contrast", false, "Use high-contrast colors for text-based UI",
|
|
)
|
|
|
|
flag.StringVarP(
|
|
&args.CBMPath, "cbm-path", "", "",
|
|
"Path to clr-boot-manager (default: the target systems /usr/bin/clr-boot-manager)",
|
|
)
|
|
// We do not want this flag to be shown as part of the standard help message
|
|
makeFlagHidden(flag, "cbm-path")
|
|
|
|
flag.BoolVar(
|
|
&args.SkipValidationSize, "skip-validation-size",
|
|
args.SkipValidationSize, "Skip the partition validation size check",
|
|
)
|
|
flag.BoolVar(
|
|
&args.SkipValidationAll, "skip-validation-all",
|
|
args.SkipValidationAll, "Skip all of the partition validation checks",
|
|
)
|
|
// We do not want this flag to be shown as part of the standard help message
|
|
makeFlagHidden(flag, "skip-validation-all")
|
|
|
|
flag.StringVar(
|
|
&args.SwapFileSize, "swap-file-size", args.SwapFileSize, "Size of the swapfile; <size>[B|K|M|G]",
|
|
)
|
|
|
|
flag.BoolVar(
|
|
&args.ForceDestructive, "force-destructive",
|
|
false,
|
|
"Enable destructive installation which may cause data loss on unselected media;"+
|
|
" "+"RAID, lvm etc. Proceed with caution!",
|
|
)
|
|
|
|
spflag.ErrHelp = errors.New("Clear Linux Installer program")
|
|
|
|
saveConfigFile := args.ConfigFile
|
|
if err := flag.Parse(os.Args); err != nil {
|
|
return fmt.Errorf("Failed to parse command line: %v", err)
|
|
}
|
|
|
|
// If we have a downloaded file, but it is overridden by command line, remove the tempfile
|
|
if args.CfDownloaded && args.ConfigFile != saveConfigFile {
|
|
_ = os.Remove(saveConfigFile)
|
|
}
|
|
|
|
// Determine whether boolean command line arguments were set or not
|
|
args.setBoolFlagCheck(flag)
|
|
|
|
if (args.TelemetryURL != "" && args.TelemetryTID == "") ||
|
|
(args.TelemetryURL == "" && args.TelemetryTID != "") {
|
|
return errors.New("Telemetry requires both --telemetry-url and --telemetry-tid")
|
|
}
|
|
|
|
if args.SwupdURL != "" {
|
|
if args.SwupdMirror != "" {
|
|
return errors.New("--swupd-url and --swupd-mirror are mutually exclusive")
|
|
}
|
|
|
|
if args.SwupdContentURL == "" {
|
|
args.SwupdContentURL = args.SwupdURL
|
|
} else {
|
|
fmt.Printf("Warning: --swupd-contenturl overrides --swupd-url\n")
|
|
}
|
|
|
|
if args.SwupdVersionURL == "" {
|
|
args.SwupdVersionURL = args.SwupdURL
|
|
} else {
|
|
fmt.Printf("Warning: --swupd-versionurl overrides --swupd-url\n")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// setBoolFlagCheck determines whether or not boolean arguments were set on
|
|
// the command line
|
|
func (args *Args) setBoolFlagCheck(flag *spflag.FlagSet) {
|
|
fflag := flag.Lookup("telemetry")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.TelemetrySet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("reboot")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.RebootSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("offline")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.OfflineSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("cfPurge")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.CfPurgeSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("allow-insecure-http")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.AllowInsecureHTTPSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("swupd-skip-optional")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.SwupdSkipOptionalSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("archive")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.ArchiveSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("iso")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.MakeISOSet = true
|
|
}
|
|
}
|
|
fflag = flag.Lookup("keep-image")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.KeepImageSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("copy-swupd")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.CopySwupdSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("skip-validation-size")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.SkipValidationSizeSet = true
|
|
}
|
|
}
|
|
|
|
fflag = flag.Lookup("skip-validation-all")
|
|
if fflag != nil {
|
|
if fflag.Changed {
|
|
args.SkipValidationAllSet = true
|
|
if !args.SkipValidationSizeSet {
|
|
args.SkipValidationSizeSet = true
|
|
args.SkipValidationSize = args.SkipValidationAll
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ParseArgs will both parse the command line arguments to the program
|
|
// and read any options set on the kernel command line from boot-time
|
|
// setting the results into the Args member variables.
|
|
func (args *Args) ParseArgs() (err error) {
|
|
// Set the default log level
|
|
args.LogLevel = log.LogLevelDebug
|
|
|
|
err = args.setKernelArgs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = args.setCommandLineArgs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|