diff --git a/install.sh b/install.sh new file mode 100644 index 000000000..d8c494ce9 --- /dev/null +++ b/install.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -e + +: ${BINARY_NAME:="c3os"} +: ${INSTALL_DIR:="/usr/local/bin"} +: ${USE_SUDO:="true"} +: ${INSTALL_K3S:="true"} +: ${INSTALL_EDGEVPN:="true"} +: ${DOWNLOADER:="curl"} + +install_k3s() { + export INSTALL_K3S_VERSION=${K3S_VERSION} + export INSTALL_K3S_BIN_DIR="/usr/bin" + export INSTALL_K3S_SKIP_START="true" + export INSTALL_K3S_SKIP_ENABLE="true" + curl -sfL https://get.k3s.io | sh - +} + +c3os_github_version() { + set +e + curl -s https://api.github.com/repos/mudler/c3os/releases/latest | \ + grep tag_name | \ + awk '{ print $2 }' | \ + sed -e 's/\"//g' -e 's/,//g' || echo "v0.8.5" + set -e +} + +install_edgevpn() { + curl -sfL https://raw.githubusercontent.com/mudler/edgevpn/master/install.sh | sh - +} + +download() { + [ $# -eq 2 ] || fatal 'download needs exactly 2 arguments' + + case $DOWNLOADER in + curl) + curl -o $1 -sfL $2 + ;; + wget) + wget -qO $1 $2 + ;; + *) + fatal "Incorrect executable '$DOWNLOADER'" + ;; + esac + + # Abort if download command failed + [ $? -eq 0 ] || fatal 'Download failed' +} + +SUDO=sudo +if [ $(id -u) -eq 0 ]; then + SUDO= +fi + +c3os_version="${C3OS_VERSION:-$(c3os_github_version)}" + +echo "Downloading c3os $c3os_version" + +if [ "${INSTALL_EDGEVPN}" == "true" ]; then + install_edgevpn +fi + +if [ "${INSTALL_K3S}" == "true" ]; then + install_k3s +fi + +TMP_DIR=$(mktemp -d -t c3os-install.XXXXXXXXXX) + +download $TMP_DIR/out.tar.gz https://github.com/mudler/c3os/releases/download/$c3os_version/c3os-$c3os_version-Linux-x86_64.tar.gz + +# TODO verify w/ checksum +tar xvf $TMP_DIR/out.tar.gz -C $TMP_DIR + +$SUDO cp -rf $TMP_DIR/c3os $INSTALL_DIR/ + +# TODO trap +rm -rf $TMP_DIR + +if [ ! -d "/etc/systemd/system.conf.d/" ]; then + $SUDO mkdir -p /etc/systemd/system.conf.d +fi + +if [ ! -d "/etc/sysconfig" ]; then + $SUDO mkdir -p /etc/sysconfig +fi \ No newline at end of file diff --git a/installer/config.go b/installer/config.go index ed8980407..936e0cb91 100644 --- a/installer/config.go +++ b/installer/config.go @@ -13,6 +13,7 @@ type C3OSConfig struct { Offline bool `yaml:"offline"` Reboot bool `yaml:"reboot"` Device string `yaml:"device"` + Poweroff bool `yaml:"poweroff"` } type Config struct { diff --git a/installer/installer.go b/installer/installer.go index 1347e7a9b..64bba5f11 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -1,17 +1,24 @@ package main import ( + "context" + "errors" "fmt" "io/ioutil" "os" "os/exec" + "time" + systemd "github.com/mudler/c3os/installer/systemd" + nodepair "github.com/mudler/go-nodepair" + qr "github.com/mudler/go-nodepair/qrcode" + "github.com/pterm/pterm" "gopkg.in/yaml.v2" ) func optsToArgs(options map[string]string) (res []string) { for k, v := range options { - if k != "device" && k != "cc" && k != "reboot" { + if k != "device" && k != "cc" && k != "reboot" && k != "poweroff" { res = append(res, fmt.Sprintf("--%s", k)) res = append(res, fmt.Sprintf("%s", v)) } @@ -19,8 +26,75 @@ func optsToArgs(options map[string]string) (res []string) { return } +func install(dir string) error { + // Reads config, and if present and offline is defined, + // runs the installation + cc, err := ScanConfig(dir) + if err == nil && cc.C3OS != nil && cc.C3OS.Offline { + runInstall(map[string]string{ + "device": cc.C3OS.Device, + "cc": cc.cloudFileContent, + }) + + svc, err := systemd.Getty(1) + if err == nil { + svc.Start() + } + + return nil + } + + printBanner(banner) + tk := nodepair.GenerateToken() + + pterm.DefaultBox.WithTitle("Installation").WithTitleBottomRight().WithRightPadding(0).WithBottomPadding(0).Println( + `Welcome to c3os! +p2p device installation enrollment is starting. +A QR code will be displayed below. +In another machine, run "c3os register" with the QR code visible on screen, +or "c3os register " to register the machine from a photo. +IF the qrcode is not displaying correctly, +try booting with another vga option from the boot cmdline (e.g. vga=791).`) + + pterm.Info.Println("Starting in 5 seconds...") + pterm.Print("\n\n") // Add two new lines as spacer. + + time.Sleep(5 * time.Second) + + qr.Print(tk) + + r := map[string]string{} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + prompt("Waiting for registration, press any key to abort pairing. To restart run 'c3os install'.") + // give tty1 back + svc, err := systemd.Getty(1) + if err == nil { + svc.Start() + } + cancel() + }() + + if err := nodepair.Receive(ctx, &r, nodepair.WithToken(tk)); err != nil { + return err + } + + if len(r) == 0 { + return errors.New("no configuration, stopping installation") + } + + pterm.Info.Println("Starting installation") + + runInstall(r) + + pterm.Info.Println("Installation completed, press enter to go back to the shell.") + + return nil +} + func runInstall(options map[string]string) { - fmt.Println("Running install", options) f, _ := ioutil.TempFile("", "xxxx") device, ok := options["device"] @@ -39,6 +113,7 @@ func runInstall(options map[string]string) { yaml.Unmarshal([]byte(cloudInit), c) _, reboot := options["reboot"] + _, poweroff := options["poweroff"] ioutil.WriteFile(f.Name(), []byte(cloudInit), os.ModePerm) args := []string{} @@ -55,7 +130,11 @@ func runInstall(options map[string]string) { os.Exit(1) } - if reboot || c.C3OS.Reboot { + if reboot || c.C3OS != nil && c.C3OS.Reboot { Reboot() } + + if poweroff || c.C3OS != nil && c.C3OS.Poweroff { + PowerOFF() + } } diff --git a/installer/main.go b/installer/main.go index 4eb794e55..24ad1f1e7 100644 --- a/installer/main.go +++ b/installer/main.go @@ -3,24 +3,17 @@ package main import ( //"fmt" - "context" "encoding/base64" - "errors" "fmt" - "io/ioutil" "os" "strconv" "strings" - "time" - "github.com/mudler/c3os/installer/systemd" edgeVPNClient "github.com/mudler/edgevpn/api/client" service "github.com/mudler/edgevpn/api/client/service" "github.com/mudler/edgevpn/pkg/node" - nodepair "github.com/mudler/go-nodepair" - qr "github.com/mudler/go-nodepair/qrcode" - "github.com/pterm/pterm" "github.com/urfave/cli" + "gopkg.in/yaml.v2" ) func main() { @@ -46,6 +39,9 @@ func main() { &cli.BoolFlag{ Name: "reboot", }, + &cli.BoolFlag{ + Name: "poweroff", + }, }, Action: func(c *cli.Context) error { args := c.Args() @@ -54,34 +50,24 @@ func main() { ref = args[0] } - b, _ := ioutil.ReadFile(c.String("config")) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - // dmesg -D to suppress tty ev - - fmt.Println("Sending registration payload, please wait") - - config := map[string]string{ - "device": c.String("device"), - "cc": string(b), - } - - if c.Bool("reboot") { - config["reboot"] = "" - } - - err := nodepair.Send( - ctx, - config, - nodepair.WithReader(qr.Reader), - nodepair.WithToken(ref), - ) - if err != nil { - return err + return register(ref, c.String("config"), c.String("device"), c.Bool("reboot"), c.Bool("poweroff")) + }, + }, + { + Name: "create-config", + Aliases: []string{"c"}, + UsageText: "Create a config with a generated network token", + Action: func(c *cli.Context) error { + l := int(^uint(0) >> 1) + args := c.Args() + if len(args) > 0 { + if i, err := strconv.Atoi(args[0]); err == nil { + l = i + } } - - fmt.Println("Payload sent, installation will start on the machine briefly") - + cc := &Config{C3OS: &C3OSConfig{NetworkToken: node.GenerateNewConnectionData(l).Base64()}} + y, _ := yaml.Marshal(cc) + fmt.Println(string(y)) return nil }, }, @@ -138,72 +124,7 @@ func main() { Name: "install", Aliases: []string{"i"}, Action: func(c *cli.Context) error { - - // Reads config, and if present and offline is defined, - // runs the installation - cc, err := ScanConfig("/oem") - if err == nil && cc.C3OS != nil && cc.C3OS.Offline { - runInstall(map[string]string{ - "device": cc.C3OS.Device, - "cc": cc.cloudFileContent, - }) - if cc.C3OS.Reboot { - Reboot() - } else { - svc, err := systemd.Getty(1) - if err == nil { - svc.Start() - } - } - return nil - } - - printBanner(banner) - tk := nodepair.GenerateToken() - - pterm.DefaultBox.WithTitle("Installation").WithTitleBottomRight().WithRightPadding(0).WithBottomPadding(0).Println( - `Welcome to c3os! -p2p device installation enrollment is starting. -A QR code will be displayed below. -In another machine, run "c3os register" with the QR code visible on screen, -or "c3os register " to register the machine from a photo. -IF the qrcode is not displaying correctly, -try booting with another vga option from the boot cmdline (e.g. vga=791).`) - - pterm.Info.Println("Starting in 5 seconds...") - pterm.Print("\n\n") // Add two new lines as spacer. - - time.Sleep(5 * time.Second) - - qr.Print(tk) - - r := map[string]string{} - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - go func() { - prompt("Waiting for registration, press any key to abort pairing. To restart run 'c3os install'.") - // give tty1 back - svc, err := systemd.Getty(1) - if err == nil { - svc.Start() - } - cancel() - }() - - if err := nodepair.Receive(ctx, &r, nodepair.WithToken(tk)); err != nil { - return err - } - - if len(r) == 0 { - return errors.New("no configuration, stopping installation") - } - - pterm.Info.Println("Starting installation") - runInstall(r) - - pterm.Info.Println("Installation completed, press enter to go back to the shell.") - return nil + return install("/oem") }, }, }, diff --git a/installer/register.go b/installer/register.go new file mode 100644 index 000000000..8fa6e0cdf --- /dev/null +++ b/installer/register.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "fmt" + "io/ioutil" + + nodepair "github.com/mudler/go-nodepair" + qr "github.com/mudler/go-nodepair/qrcode" +) + +func register(arg, configFile, device string, reboot, poweroff bool) error { + b, _ := ioutil.ReadFile(configFile) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // dmesg -D to suppress tty ev + fmt.Println("Sending registration payload, please wait") + + config := map[string]string{ + "device": device, + "cc": string(b), + } + + if reboot { + config["reboot"] = "" + } + + if poweroff { + config["poweroff"] = "" + } + + err := nodepair.Send( + ctx, + config, + nodepair.WithReader(qr.Reader), + nodepair.WithToken(arg), + ) + if err != nil { + return err + } + + fmt.Println("Payload sent, installation will start on the machine briefly") + return nil +} diff --git a/installer/system.go b/installer/system.go index ec13eb30a..946fb465c 100644 --- a/installer/system.go +++ b/installer/system.go @@ -9,3 +9,8 @@ func Reboot() { pterm.Info.Println("Rebooting node") utils.SH("reboot") } + +func PowerOFF() { + pterm.Info.Println("Shutdown node") + utils.SH("shutdown -h now") +}