Split off cli into separate binaries (#37)

* 🎨 Split off cli into separate binaries

This commit splits off the cli into 3 binaries:
- agent
- cli
- provider

The provider now is a separate component that can be tested by itself
and have its own lifecycle. This paves the way to a ligher c3os variant,
HA support and other features that can be provided on runtime.

This is working, but still there are low hanging fruit to care about.

Fixes #14

* 🤖 Add provider bin to releases

* ⚙️ Handle signals

* ⚙️ Reduce buildsize footprint

* 🎨 Scan for providers also in /system/providers

* 🤖 Run goreleaser

* 🎨 Refactoring
This commit is contained in:
Ettore Di Giacinto
2022-07-04 22:39:34 +02:00
committed by Itxaka
commit b2e49776a3
17 changed files with 2199 additions and 0 deletions

118
cmd/cli/bridge.go Normal file
View File

@@ -0,0 +1,118 @@
package main
import (
"context"
"fmt"
"net"
"time"
"github.com/c3os-io/c3os/internal/utils"
"github.com/c3os-io/c3os/pkg/config"
"github.com/ipfs/go-log"
"github.com/mudler/edgevpn/api"
"github.com/mudler/edgevpn/pkg/logger"
"github.com/mudler/edgevpn/pkg/node"
"github.com/mudler/edgevpn/pkg/services"
"github.com/mudler/edgevpn/pkg/vpn"
qr "github.com/mudler/go-nodepair/qrcode"
"github.com/urfave/cli"
)
// bridge is just starting a VPN with edgevpn to the given network token.
func bridge(c *cli.Context) error {
qrCodePath := ""
fromQRCode := false
var serviceUUID, sshPassword string
if c.String("qr-code-image") != "" {
qrCodePath = c.String("qr-code-image")
fromQRCode = true
}
if c.Bool("qr-code-snapshot") {
qrCodePath = ""
fromQRCode = true
}
token := c.String("network-token")
if fromQRCode {
recoveryToken := qr.Reader(qrCodePath)
data := utils.DecodeRecoveryToken(recoveryToken)
if len(data) != 3 {
fmt.Println("Token not decoded correctly")
return fmt.Errorf("invalid token")
}
token = data[0]
serviceUUID = data[1]
sshPassword = data[2]
if serviceUUID == "" || sshPassword == "" || token == "" {
return fmt.Errorf("decoded invalid values")
}
}
ctx := context.Background()
nc := config.Network(token, c.String("address"), c.String("log-level"), "c3os0")
lvl, err := log.LevelFromString(nc.LogLevel)
if err != nil {
lvl = log.LevelError
}
llger := logger.New(lvl)
o, vpnOpts, err := nc.ToOpts(llger)
if err != nil {
llger.Fatal(err.Error())
}
opts := []node.Option{}
if !fromQRCode {
// We just connect to a VPN token
o = append(o,
services.Alive(
time.Duration(20)*time.Second,
time.Duration(10)*time.Second,
time.Duration(10)*time.Second)...)
if c.Bool("dhcp") {
// Adds DHCP server
address, _, err := net.ParseCIDR(c.String("address"))
if err != nil {
return err
}
nodeOpts, vO := vpn.DHCP(llger, 15*time.Minute, c.String("lease-dir"), address.String())
o = append(o, nodeOpts...)
vpnOpts = append(vpnOpts, vO...)
}
opts, err = vpn.Register(vpnOpts...)
if err != nil {
return err
}
} else {
// We hook into a service
llger.Info("Connecting to service", serviceUUID)
llger.Info("SSH access password is", sshPassword)
llger.Info("SSH server reachable at 127.0.0.1:2200")
opts = append(opts, node.WithNetworkService(
services.ConnectNetworkService(
30*time.Second,
serviceUUID,
"127.0.0.1:2200",
),
))
llger.Info("To connect, keep this terminal open and run in another terminal 'ssh 127.0.0.1 -p 2200' the password is ", sshPassword)
llger.Info("Note: the connection might not be available instantly and first attempts will likely fail.")
llger.Info(" Few attempts might be required before establishing a tunnel to the host.")
}
e, err := node.New(append(o, opts...)...)
if err != nil {
return err
}
go api.API(ctx, c.String("api"), 5*time.Second, 20*time.Second, e, nil, false)
return e.Start(ctx)
}

181
cmd/cli/main.go Normal file
View File

@@ -0,0 +1,181 @@
package main
import (
//"fmt"
"fmt"
"os"
cmd "github.com/c3os-io/c3os/internal/cmd"
"github.com/urfave/cli"
)
var cliCmd = []cli.Command{
{
Name: "register",
UsageText: "register --reboot --device /dev/sda /image/snapshot.png",
Usage: "Registers and bootstraps a node",
Description: `
Bootstraps a node which is started in pairing mode. It can send over a configuration file used to install the c3os node.
For example:
$ c3os register --config config.yaml --device /dev/sda ~/Downloads/screenshot.png
will decode the QR code from ~/Downloads/screenshot.png and bootstrap the node remotely.
If the image is omitted, a screenshot will be taken and used to decode the QR code.
See also https://docs.c3os.io/installation/device_pairing/ for documentation.
`,
ArgsUsage: "Register optionally accepts an image. If nothing is passed will take a screenshot of the screen and try to decode the QR code",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "C3OS YAML configuration file",
},
&cli.StringFlag{
Name: "device",
Usage: "Device used for the installation target",
},
&cli.BoolFlag{
Name: "reboot",
Usage: "Reboot node after installation",
},
&cli.BoolFlag{
Name: "poweroff",
Usage: "Shutdown node after installation",
},
&cli.StringFlag{
Name: "log-level",
Usage: "Set log level",
},
},
Action: func(c *cli.Context) error {
args := c.Args()
var ref string
if len(args) == 1 {
ref = args[0]
}
return register(c.String("log-level"), ref, c.String("config"), c.String("device"), c.Bool("reboot"), c.Bool("poweroff"))
},
},
{
Name: "bridge",
UsageText: "bridge --network-token XXX",
Usage: "Connect to a c3os VPN network",
Description: `
Starts a bridge with a c3os network or a node.
# With a network
By default, "bridge" will create a VPN network connection to the node with the token supplied, thus it requires elevated permissions in order to work.
For example:
$ sudo c3os bridge --network-token <TOKEN>
Will start a VPN, which local ip is fixed to 10.1.0.254 (tweakable with --address).
The API will also be accessible at http://127.0.0.1:8080
# With a node
"c3os bridge" can be used also to connect over to a node in recovery mode. When operating in this modality c3os bridge requires no specific permissions, indeed a tunnel
will be created locally to connect to the machine remotely.
For example:
$ c3os bridge --qr-code-image /path/to/image.png
Will scan the QR code in the image and connect over. Further instructions on how to connect over will be printed out to the screen.
See also: https://docs.c3os.io/after_install/troubleshooting/#connect-to-the-cluster-network and https://docs.c3os.io/after_install/recovery_mode/
`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "network-token",
Required: false,
EnvVar: "NETWORK_TOKEN",
Usage: "Network token to connect over",
},
&cli.StringFlag{
Name: "log-level",
Required: false,
EnvVar: "LOGLEVEL",
Value: "info",
Usage: "Bridge log level",
},
&cli.BoolFlag{
Name: "qr-code-snapshot",
Required: false,
Usage: "Bool to take a local snapshot instead of reading from an image file for recovery",
EnvVar: "QR_CODE_SNAPSHOT",
},
&cli.StringFlag{
Name: "qr-code-image",
Usage: "Path to an image containing a valid QR code for recovery mode",
Required: false,
EnvVar: "QR_CODE_IMAGE",
},
&cli.StringFlag{
Name: "api",
Value: "127.0.0.1:8080",
Usage: "Listening API url",
},
&cli.BoolFlag{
Name: "dhcp",
EnvVar: "DHCP",
Usage: "Enable DHCP",
},
&cli.StringFlag{
Value: "10.1.0.254/24",
Name: "address",
EnvVar: "ADDRESS",
Usage: "Specify an address for the bridge",
},
&cli.StringFlag{
Value: "/tmp/c3os",
Name: "lease-dir",
EnvVar: "lease-dir",
Usage: "DHCP Lease directory",
},
},
Action: bridge,
},
}
func main() {
app := &cli.App{
Name: "c3os",
Version: "0.1",
Author: "Ettore Di Giacinto",
Usage: "c3os CLI to bootstrap, upgrade, connect and manage a c3os network",
Description: `
The c3os CLI can be used to manage a c3os box and perform all day-two tasks, like:
- register a node
- connect to a node in recovery mode
- to establish a VPN connection
- set, list roles
- interact with the network API
and much more.
For all the example cases, see: https://docs.c3os.io .
`,
UsageText: ``,
Copyright: "Ettore Di Giacinto",
Commands: cmd.CommonCommand(cliCmd...),
}
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

46
cmd/cli/register.go Normal file
View File

@@ -0,0 +1,46 @@
package main
import (
"context"
"fmt"
"io/ioutil"
nodepair "github.com/mudler/go-nodepair"
qr "github.com/mudler/go-nodepair/qrcode"
)
func register(loglevel, 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),
nodepair.WithLogLevel(loglevel),
)
if err != nil {
return err
}
fmt.Println("Payload sent, installation will start on the machine briefly")
return nil
}