Compare commits
No commits in common. "038a28bb0275d65852cfe6dd7d8d75534c84ae9b" and "af69f1cfbac8b793e47a002d9006070571ab4400" have entirely different histories.
038a28bb02
...
af69f1cfba
@ -105,4 +105,7 @@ Documentation for devs:
|
|||||||
|
|
||||||
Besides documentation, there are a few other pages which might be useful:
|
Besides documentation, there are a few other pages which might be useful:
|
||||||
|
|
||||||
|
* [Roadmap][roadmap]
|
||||||
* [Glossary](docs/glossary.md)
|
* [Glossary](docs/glossary.md)
|
||||||
|
|
||||||
|
[roadmap]: docs/roadmap.md
|
||||||
|
128
docs/roadmap.md
Normal file
128
docs/roadmap.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
The following are rough outlines of upcoming work on the roadmap, roughly in the
|
||||||
|
order they will be implemented.
|
||||||
|
|
||||||
|
## Main quest
|
||||||
|
|
||||||
|
These items are listed more or less in the order they need to be completed, as
|
||||||
|
they generally depend on the items previous to them.
|
||||||
|
|
||||||
|
### Windows Support + GUI
|
||||||
|
|
||||||
|
Support for Windows is a must. This requirement also includes a simple GUI,
|
||||||
|
which would essentially act as a thin layer on top of `daemon.yml` to start
|
||||||
|
with.
|
||||||
|
|
||||||
|
Depending on difficulty level, OSX support might be added at this stage as well.
|
||||||
|
|
||||||
|
### NATS
|
||||||
|
|
||||||
|
Garage is currently used to handle eventually-consistent persistent storage, but
|
||||||
|
there is no mechanism for inter-host realtime communication as of yet. NATS
|
||||||
|
would be a good candidate for this, as it uses a gossip protocol which does not
|
||||||
|
require a central coordinator (I don't think), and is well supported.
|
||||||
|
|
||||||
|
### Integration of [Caddy](https://caddyserver.com/docs/)
|
||||||
|
|
||||||
|
Integration of Caddy's will require some plugins to be developed. We want Caddy
|
||||||
|
to be able to store cert information in S3 (garage), so that all isle lighthouse
|
||||||
|
nodes can potentially become gateways as well. Once done, it would be possible
|
||||||
|
for lighthouses to forward public traffic to inner nodes.
|
||||||
|
|
||||||
|
It should also be possible for users within the network to take use lighthouse
|
||||||
|
Caddy's to host their websites (and eventually gemini capsules) for them.
|
||||||
|
|
||||||
|
Most likely this integration will require NATS as well, to coordinate cache
|
||||||
|
invalidation and cert refreshing.
|
||||||
|
|
||||||
|
### Invitation code bootstrapping
|
||||||
|
|
||||||
|
Once an HTTP gateway/load-balancer is set up it should be possible to do host
|
||||||
|
bootstrapping using invite codes rather than manually giving new users bootstrap
|
||||||
|
files. The bootstrap file would be stored, encrypted, in garage, with the invite
|
||||||
|
code being able to both identify and decrypt it. To instantiate a host, the user
|
||||||
|
only needs to input the network domain name and the invite code.
|
||||||
|
|
||||||
|
### FUSE Mount
|
||||||
|
|
||||||
|
KBFS style. Every user should be able to mount virtual directories to their host
|
||||||
|
which correspond to various buckets in garage.
|
||||||
|
|
||||||
|
- "public": editable amongst all users on the host, shared publicly via HTTP
|
||||||
|
gateway.
|
||||||
|
|
||||||
|
- "protected": editable amongst all users on the host, but not accessible
|
||||||
|
outside the network.
|
||||||
|
|
||||||
|
- "private": only accessible to a particular user (client-side encrypted).
|
||||||
|
|
||||||
|
Whether it's necessary to support directories which are shared only between
|
||||||
|
specific users remains to be seen. The identification of a single "user" between
|
||||||
|
different hosts is also an unsolved problem.
|
||||||
|
|
||||||
|
## Side quests
|
||||||
|
|
||||||
|
These items aren't necessarily required by the main quest, and aren't dependent
|
||||||
|
on any other items being completed. They are nice-to-haves that we do want to
|
||||||
|
eventually complete, but aren't the main focus.
|
||||||
|
|
||||||
|
### Design System
|
||||||
|
|
||||||
|
It would be great to get some help from a designer or otherwise
|
||||||
|
artistically-minded person to create some kind of design framework which could
|
||||||
|
be used across publicly-facing frontends. Such a system would provide a simple
|
||||||
|
but cohesive vision for how things should look, include:
|
||||||
|
|
||||||
|
- Color schemes
|
||||||
|
- Fonts and text decoration in different situations
|
||||||
|
- Some simple, reusable layout templates (splash page, documentation, form)
|
||||||
|
- Basic components like tables, lists, media, etc..
|
||||||
|
|
||||||
|
### DHCP
|
||||||
|
|
||||||
|
Currently all hosts require a static IP to be reserved by the admin. Nebula may
|
||||||
|
support DHCP already, but if it doesn't we should look into how this could be
|
||||||
|
accomplished. Depending on how reliable DNS support is it may be possible to use
|
||||||
|
DHCP for all non-lighthouse hosts, which would be excellent.
|
||||||
|
|
||||||
|
### IPv6 network ranges
|
||||||
|
|
||||||
|
It should theoretically be possible for the internal network IP range to be on
|
||||||
|
IPv6 rather than IPv4. This may be a simple matter of just testing it to confirm
|
||||||
|
it works.
|
||||||
|
|
||||||
|
### Proper Linux Packages
|
||||||
|
|
||||||
|
Rather than distributing raw binaries for Linux we should instead be
|
||||||
|
distributing actual packages.
|
||||||
|
|
||||||
|
* deb files for debian/ubuntu
|
||||||
|
* PKGBUILD for arch (done)
|
||||||
|
* rpm for fedora?
|
||||||
|
* flatpak?
|
||||||
|
|
||||||
|
This will allow for properly setting capabilities for the binary at install
|
||||||
|
time, so that it can be run as non-root, and installing any necessary `.desktop`
|
||||||
|
files so that it can be run as a GUI application.
|
||||||
|
|
||||||
|
### Mobile app
|
||||||
|
|
||||||
|
To start with a simple mobile app which provided connectivity to the network
|
||||||
|
would be great. We are not able to use the existing nebula mobile app because it
|
||||||
|
is not actually open-source, but we can at least use it as a reference to see
|
||||||
|
how this can be accomplished.
|
||||||
|
|
||||||
|
### DNS/Firewall Configuration
|
||||||
|
|
||||||
|
Ideally Isle could detect the DNS/firewall subsystems being used on a per-OS
|
||||||
|
basis and configure them as needed. This would be simplify necessary
|
||||||
|
documentation and setup steps for operators.
|
||||||
|
|
||||||
|
### Plugins
|
||||||
|
|
||||||
|
It would not be difficult to spec out a plugin system using nix commands.
|
||||||
|
Existing components could be rigged to use this plugin system, and we could then
|
||||||
|
use the system to add future components which might prove useful. Once the
|
||||||
|
project is public such a system would be much appreciated I think, as it would
|
||||||
|
let other groups rig their binaries with all sorts of new functionality.
|
@ -47,10 +47,6 @@ type Bootstrap struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New initializes and returns a new Bootstrap file for a new host.
|
// New initializes and returns a new Bootstrap file for a new host.
|
||||||
//
|
|
||||||
// TODO in the resulting bootstrap only include this host and hosts which are
|
|
||||||
// necessary for connecting to nebula/garage. Remember to immediately re-poll
|
|
||||||
// garage for the full hosts list during network joining.
|
|
||||||
func New(
|
func New(
|
||||||
caCreds nebula.CACredentials,
|
caCreds nebula.CACredentials,
|
||||||
adminCreationParams CreationParams,
|
adminCreationParams CreationParams,
|
||||||
|
@ -6,7 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (ctx subCmdCtx) getHosts() (daemon.GetHostsResult, error) {
|
func (ctx subCmdCtx) getHosts() (daemon.GetHostsResult, error) {
|
||||||
res, err := ctx.daemonRPC.GetHosts(ctx)
|
var res daemon.GetHostsResult
|
||||||
|
err := ctx.daemonRCPClient.Call(ctx.ctx, &res, "GetHosts", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return daemon.GetHostsResult{}, fmt.Errorf("calling GetHosts: %w", err)
|
return daemon.GetHostsResult{}, fmt.Errorf("calling GetHosts: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,12 @@ import (
|
|||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO it would be good to have an `isle daemon config-check` kind of command,
|
|
||||||
// which could be run prior to a systemd service restart to make sure we don't
|
|
||||||
// restart the service into a configuration that will definitely fail.
|
|
||||||
|
|
||||||
var subCmdDaemon = subCmd{
|
var subCmdDaemon = subCmd{
|
||||||
name: "daemon",
|
name: "daemon",
|
||||||
descr: "Runs the isle daemon (Default if no sub-command given)",
|
descr: "Runs the isle daemon (Default if no sub-command given)",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
|
||||||
flags := ctx.flagSet(false)
|
flags := subCmdCtx.flagSet(false)
|
||||||
|
|
||||||
daemonConfigPath := flags.StringP(
|
daemonConfigPath := flags.StringP(
|
||||||
"config-path", "c", "",
|
"config-path", "c", "",
|
||||||
@ -36,10 +32,12 @@ var subCmdDaemon = subCmd{
|
|||||||
`Maximum log level which should be output. Values can be "debug", "info", "warn", "error", "fatal". Does not apply to sub-processes`,
|
`Maximum log level which should be output. Values can be "debug", "info", "warn", "error", "fatal". Does not apply to sub-processes`,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := subCmdCtx.ctx
|
||||||
|
|
||||||
if *dumpConfig {
|
if *dumpConfig {
|
||||||
return daemon.CopyDefaultConfig(os.Stdout, envAppDirPath)
|
return daemon.CopyDefaultConfig(os.Stdout, envAppDirPath)
|
||||||
}
|
}
|
||||||
@ -49,7 +47,7 @@ var subCmdDaemon = subCmd{
|
|||||||
return fmt.Errorf("couldn't parse log level %q", *logLevelStr)
|
return fmt.Errorf("couldn't parse log level %q", *logLevelStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := ctx.logger.WithMaxLevel(logLevel.Int())
|
logger := subCmdCtx.logger.WithMaxLevel(logLevel.Int())
|
||||||
|
|
||||||
// TODO check that daemon is either running as root, or that the
|
// TODO check that daemon is either running as root, or that the
|
||||||
// required linux capabilities are set.
|
// required linux capabilities are set.
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/daemon/jsonrpc2"
|
"isle/daemon/jsonrpc2"
|
||||||
"net"
|
"net"
|
||||||
@ -18,25 +17,11 @@ import (
|
|||||||
const daemonHTTPRPCPath = "/rpc/v0.json"
|
const daemonHTTPRPCPath = "/rpc/v0.json"
|
||||||
|
|
||||||
func newHTTPServer(
|
func newHTTPServer(
|
||||||
ctx context.Context, logger *mlog.Logger, rpc daemon.RPC,
|
ctx context.Context, logger *mlog.Logger, rpc *daemon.RPC,
|
||||||
) (
|
) (
|
||||||
*http.Server, error,
|
*http.Server, error,
|
||||||
) {
|
) {
|
||||||
socketPath := daemon.HTTPSocketPath()
|
socketPath := daemon.HTTPSocketPath()
|
||||||
ctx = mctx.Annotate(ctx, "socketPath", socketPath)
|
|
||||||
|
|
||||||
if err := os.Remove(socketPath); errors.Is(err, fs.ErrNotExist) {
|
|
||||||
// No problem
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"removing %q prior to listening: %w", socketPath, err,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
logger.WarnString(
|
|
||||||
ctx, "Deleted existing socket file prior to listening, it's possible a previous daemon failed to shutdown gracefully",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
l, err := net.Listen("unix", socketPath)
|
l, err := net.Listen("unix", socketPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
@ -50,6 +35,7 @@ func newHTTPServer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx = mctx.Annotate(ctx, "httpAddr", l.Addr().String())
|
||||||
logger.Info(ctx, "HTTP server socket created")
|
logger.Info(ctx, "HTTP server socket created")
|
||||||
|
|
||||||
rpcHandler := jsonrpc2.Chain(
|
rpcHandler := jsonrpc2.Chain(
|
||||||
|
@ -3,37 +3,20 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding"
|
"encoding"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/nebula"
|
|
||||||
"net/netip"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type textUnmarshaler[T any] interface {
|
type textUnmarshalerFlag struct {
|
||||||
|
inner interface {
|
||||||
encoding.TextUnmarshaler
|
encoding.TextUnmarshaler
|
||||||
*T
|
|
||||||
}
|
|
||||||
|
|
||||||
type textUnmarshalerFlag[T encoding.TextMarshaler, P textUnmarshaler[T]] struct {
|
|
||||||
V T
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *textUnmarshalerFlag[T, P]) Set(v string) error {
|
|
||||||
return P(&(f.V)).UnmarshalText([]byte(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *textUnmarshalerFlag[T, P]) String() string {
|
|
||||||
b, err := f.V.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("calling MarshalText on %#v: %v", f.V, err))
|
|
||||||
}
|
}
|
||||||
return string(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *textUnmarshalerFlag[T, P]) Type() string { return "string" }
|
func (f textUnmarshalerFlag) Set(v string) error {
|
||||||
|
return f.inner.UnmarshalText([]byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
func (f textUnmarshalerFlag) String() string {
|
||||||
|
return fmt.Sprint(f.inner)
|
||||||
|
}
|
||||||
|
|
||||||
type (
|
func (f textUnmarshalerFlag) Type() string { return "string" }
|
||||||
hostNameFlag = textUnmarshalerFlag[nebula.HostName, *nebula.HostName]
|
|
||||||
ipNetFlag = textUnmarshalerFlag[nebula.IPNet, *nebula.IPNet]
|
|
||||||
ipFlag = textUnmarshalerFlag[netip.Addr, *netip.Addr]
|
|
||||||
)
|
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"isle/daemon"
|
||||||
)
|
)
|
||||||
|
|
||||||
// minio-client keeps a configuration directory which contains various pieces of
|
// minio-client keeps a configuration directory which contains various pieces of
|
||||||
@ -33,9 +35,9 @@ func initMCConfigDir() (string, error) {
|
|||||||
var subCmdGarageMC = subCmd{
|
var subCmdGarageMC = subCmd{
|
||||||
name: "mc",
|
name: "mc",
|
||||||
descr: "Runs the mc (minio-client) binary. The isle garage can be accessed under the `garage` alias",
|
descr: "Runs the mc (minio-client) binary. The isle garage can be accessed under the `garage` alias",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
|
||||||
flags := ctx.flagSet(true)
|
flags := subCmdCtx.flagSet(true)
|
||||||
|
|
||||||
keyID := flags.StringP(
|
keyID := flags.StringP(
|
||||||
"key-id", "i", "",
|
"key-id", "i", "",
|
||||||
@ -47,11 +49,14 @@ var subCmdGarageMC = subCmd{
|
|||||||
"Optional key secret to use, defaults to that of the shared global key",
|
"Optional key secret to use, defaults to that of the shared global key",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
|
var clientParams daemon.GarageClientParams
|
||||||
|
err := subCmdCtx.daemonRCPClient.Call(
|
||||||
|
subCmdCtx.ctx, &clientParams, "GetGarageClientParams", nil,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
||||||
}
|
}
|
||||||
@ -113,9 +118,12 @@ var subCmdGarageMC = subCmd{
|
|||||||
var subCmdGarageCLI = subCmd{
|
var subCmdGarageCLI = subCmd{
|
||||||
name: "cli",
|
name: "cli",
|
||||||
descr: "Runs the garage binary, automatically configured to point to the garage sub-process of a running isle daemon",
|
descr: "Runs the garage binary, automatically configured to point to the garage sub-process of a running isle daemon",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
|
||||||
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
|
var clientParams daemon.GarageClientParams
|
||||||
|
err := subCmdCtx.daemonRCPClient.Call(
|
||||||
|
subCmdCtx.ctx, &clientParams, "GetGarageClientParams", nil,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
||||||
}
|
}
|
||||||
@ -126,7 +134,7 @@ var subCmdGarageCLI = subCmd{
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
binPath = binPath("garage")
|
binPath = binPath("garage")
|
||||||
args = append([]string{"garage"}, ctx.args...)
|
args = append([]string{"garage"}, subCmdCtx.args...)
|
||||||
cliEnv = append(
|
cliEnv = append(
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
"GARAGE_RPC_HOST="+clientParams.Peer.RPCPeerAddr(),
|
"GARAGE_RPC_HOST="+clientParams.Peer.RPCPeerAddr(),
|
||||||
@ -148,8 +156,8 @@ var subCmdGarageCLI = subCmd{
|
|||||||
var subCmdGarage = subCmd{
|
var subCmdGarage = subCmd{
|
||||||
name: "garage",
|
name: "garage",
|
||||||
descr: "Runs the garage binary, automatically configured to point to the garage sub-process of a running isle daemon",
|
descr: "Runs the garage binary, automatically configured to point to the garage sub-process of a running isle daemon",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return ctx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
subCmdGarageCLI,
|
subCmdGarageCLI,
|
||||||
subCmdGarageMC,
|
subCmdGarageMC,
|
||||||
)
|
)
|
||||||
|
@ -7,27 +7,31 @@ import (
|
|||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
|
"isle/nebula"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
var subCmdHostCreate = subCmd{
|
var subCmdHostsCreate = subCmd{
|
||||||
name: "create",
|
name: "create",
|
||||||
descr: "Creates a new host in the network, writing its new bootstrap.json to stdout",
|
descr: "Creates a new host in the network, writing its new bootstrap.json to stdout",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
flags = subCmdCtx.flagSet(false)
|
||||||
hostName hostNameFlag
|
hostName nebula.HostName
|
||||||
ip ipFlag
|
ip netip.Addr
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := flags.VarPF(
|
||||||
&hostName,
|
textUnmarshalerFlag{&hostName},
|
||||||
"hostname", "n",
|
"hostname", "h",
|
||||||
"Name of the host to generate bootstrap.json for",
|
"Name of the host to generate bootstrap.json for",
|
||||||
)
|
)
|
||||||
|
|
||||||
flags.VarP(&ip, "ip", "i", "IP of the new host. An available IP will be chosen if none is given.")
|
flags.VarP(
|
||||||
|
textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host",
|
||||||
|
)
|
||||||
|
|
||||||
canCreateHosts := flags.Bool(
|
canCreateHosts := flags.Bool(
|
||||||
"can-create-hosts",
|
"can-create-hosts",
|
||||||
@ -35,7 +39,7 @@ var subCmdHostCreate = subCmd{
|
|||||||
"The new host should have the ability to create hosts too",
|
"The new host should have the ability to create hosts too",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,11 +47,18 @@ var subCmdHostCreate = subCmd{
|
|||||||
return errors.New("--hostname is required")
|
return errors.New("--hostname is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := ctx.daemonRPC.CreateHost(
|
var res daemon.CreateHostResult
|
||||||
ctx, hostName.V, daemon.CreateHostOpts{
|
err := subCmdCtx.daemonRCPClient.Call(
|
||||||
IP: ip.V,
|
subCmdCtx.ctx,
|
||||||
|
&res,
|
||||||
|
"CreateHost",
|
||||||
|
daemon.CreateHostRequest{
|
||||||
|
HostName: hostName,
|
||||||
|
Opts: daemon.CreateHostOpts{
|
||||||
|
IP: ip,
|
||||||
CanCreateHosts: *canCreateHosts,
|
CanCreateHosts: *canCreateHosts,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling CreateHost: %w", err)
|
return fmt.Errorf("calling CreateHost: %w", err)
|
||||||
@ -57,11 +68,11 @@ var subCmdHostCreate = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdHostList = subCmd{
|
var subCmdHostsList = subCmd{
|
||||||
name: "list",
|
name: "list",
|
||||||
descr: "Lists all hosts in the network, and their IPs",
|
descr: "Lists all hosts in the network, and their IPs",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
hostsRes, err := ctx.getHosts()
|
hostsRes, err := subCmdCtx.getHosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetHosts: %w", err)
|
return fmt.Errorf("calling GetHosts: %w", err)
|
||||||
}
|
}
|
||||||
@ -93,22 +104,22 @@ var subCmdHostList = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdHostRemove = subCmd{
|
var subCmdHostsRemove = subCmd{
|
||||||
name: "remove",
|
name: "remove",
|
||||||
descr: "Removes a host from the network",
|
descr: "Removes a host from the network",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
flags = subCmdCtx.flagSet(false)
|
||||||
hostName hostNameFlag
|
hostName nebula.HostName
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := flags.VarPF(
|
||||||
&hostName,
|
textUnmarshalerFlag{&hostName},
|
||||||
"hostname", "n",
|
"hostname", "h",
|
||||||
"Name of the host to remove",
|
"Name of the host to remove",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +127,11 @@ var subCmdHostRemove = subCmd{
|
|||||||
return errors.New("--hostname is required")
|
return errors.New("--hostname is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := ctx.daemonRPC.RemoveHost(ctx, hostName.V)
|
err := subCmdCtx.daemonRCPClient.Call(
|
||||||
|
subCmdCtx.ctx, nil, "RemoveHost", daemon.RemoveHostRequest{
|
||||||
|
HostName: hostName,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling RemoveHost: %w", err)
|
return fmt.Errorf("calling RemoveHost: %w", err)
|
||||||
}
|
}
|
||||||
@ -125,15 +140,14 @@ var subCmdHostRemove = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdHost = subCmd{
|
var subCmdHosts = subCmd{
|
||||||
name: "host",
|
name: "hosts",
|
||||||
plural: "s",
|
|
||||||
descr: "Sub-commands having to do with configuration of hosts in the network",
|
descr: "Sub-commands having to do with configuration of hosts in the network",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return ctx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
subCmdHostCreate,
|
subCmdHostsCreate,
|
||||||
subCmdHostRemove,
|
subCmdHostsRemove,
|
||||||
subCmdHostList,
|
subCmdHostsList,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
@ -57,13 +57,13 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
err := subCmdCtx{
|
err := subCmdCtx{
|
||||||
Context: ctx,
|
|
||||||
args: os.Args[1:],
|
args: os.Args[1:],
|
||||||
|
ctx: ctx,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}.doSubCmd(
|
}.doSubCmd(
|
||||||
subCmdDaemon,
|
subCmdDaemon,
|
||||||
subCmdGarage,
|
subCmdGarage,
|
||||||
subCmdHost,
|
subCmdHosts,
|
||||||
subCmdNebula,
|
subCmdNebula,
|
||||||
subCmdNetwork,
|
subCmdNetwork,
|
||||||
subCmdVersion,
|
subCmdVersion,
|
||||||
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"isle/daemon"
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"os"
|
"os"
|
||||||
@ -11,15 +12,15 @@ import (
|
|||||||
var subCmdNebulaCreateCert = subCmd{
|
var subCmdNebulaCreateCert = subCmd{
|
||||||
name: "create-cert",
|
name: "create-cert",
|
||||||
descr: "Creates a signed nebula certificate file for an existing host and writes it to stdout",
|
descr: "Creates a signed nebula certificate file for an existing host and writes it to stdout",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
flags = subCmdCtx.flagSet(false)
|
||||||
hostName hostNameFlag
|
hostName nebula.HostName
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := flags.VarPF(
|
||||||
&hostName,
|
textUnmarshalerFlag{&hostName},
|
||||||
"hostname", "n",
|
"hostname", "h",
|
||||||
"Name of the host to generate a certificate for",
|
"Name of the host to generate a certificate for",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ var subCmdNebulaCreateCert = subCmd{
|
|||||||
`Path to PEM file containing public key which will be embedded in the cert.`,
|
`Path to PEM file containing public key which will be embedded in the cert.`,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,14 +47,21 @@ var subCmdNebulaCreateCert = subCmd{
|
|||||||
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
|
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := ctx.daemonRPC.CreateNebulaCertificate(
|
var res daemon.CreateNebulaCertificateResult
|
||||||
ctx, hostName.V, hostPub,
|
err = subCmdCtx.daemonRCPClient.Call(
|
||||||
|
subCmdCtx.ctx,
|
||||||
|
&res,
|
||||||
|
"CreateNebulaCertificate",
|
||||||
|
daemon.CreateNebulaCertificateRequest{
|
||||||
|
HostName: hostName,
|
||||||
|
HostEncryptingPublicKey: hostPub,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling CreateNebulaCertificate: %w", err)
|
return fmt.Errorf("calling CreateNebulaCertificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nebulaHostCertPEM, err := res.HostNebulaCertificate.Unwrap().MarshalToPEM()
|
nebulaHostCertPEM, err := res.HostNebulaCertifcate.Unwrap().MarshalToPEM()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshaling cert to PEM: %w", err)
|
return fmt.Errorf("marshaling cert to PEM: %w", err)
|
||||||
}
|
}
|
||||||
@ -69,19 +77,22 @@ var subCmdNebulaCreateCert = subCmd{
|
|||||||
var subCmdNebulaShow = subCmd{
|
var subCmdNebulaShow = subCmd{
|
||||||
name: "show",
|
name: "show",
|
||||||
descr: "Writes nebula network information to stdout in JSON format",
|
descr: "Writes nebula network information to stdout in JSON format",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
|
||||||
flags := ctx.flagSet(false)
|
flags := subCmdCtx.flagSet(false)
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts, err := ctx.getHosts()
|
hosts, err := subCmdCtx.getHosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting hosts: %w", err)
|
return fmt.Errorf("getting hosts: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
caPublicCreds, err := ctx.daemonRPC.GetNebulaCAPublicCredentials(ctx)
|
var caPublicCreds nebula.CAPublicCredentials
|
||||||
|
err = subCmdCtx.daemonRCPClient.Call(
|
||||||
|
subCmdCtx.ctx, &caPublicCreds, "GetNebulaCAPublicCredentials", nil,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err)
|
return fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err)
|
||||||
}
|
}
|
||||||
@ -134,8 +145,8 @@ var subCmdNebulaShow = subCmd{
|
|||||||
var subCmdNebula = subCmd{
|
var subCmdNebula = subCmd{
|
||||||
name: "nebula",
|
name: "nebula",
|
||||||
descr: "Sub-commands related to the nebula VPN",
|
descr: "Sub-commands related to the nebula VPN",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return ctx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
subCmdNebulaCreateCert,
|
subCmdNebulaCreateCert,
|
||||||
subCmdNebulaShow,
|
subCmdNebulaShow,
|
||||||
)
|
)
|
||||||
|
@ -10,50 +10,48 @@ import (
|
|||||||
var subCmdNetworkCreate = subCmd{
|
var subCmdNetworkCreate = subCmd{
|
||||||
name: "create",
|
name: "create",
|
||||||
descr: "Create's a new network, with this host being the first host in that network.",
|
descr: "Create's a new network, with this host being the first host in that network.",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
ctx = subCmdCtx.ctx
|
||||||
ipNet ipNetFlag
|
flags = subCmdCtx.flagSet(false)
|
||||||
hostName hostNameFlag
|
req daemon.CreateNetworkRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
name := flags.StringP(
|
flags.StringVarP(
|
||||||
"name", "N", "",
|
&req.Name, "name", "n", "",
|
||||||
"Human-readable name to identify the network as.",
|
"Human-readable name to identify the network as.",
|
||||||
)
|
)
|
||||||
|
|
||||||
domain := flags.StringP(
|
flags.StringVarP(
|
||||||
"domain", "d", "",
|
&req.Domain, "domain", "d", "",
|
||||||
"Domain name that should be used as the root domain in the network.",
|
"Domain name that should be used as the root domain in the network.",
|
||||||
)
|
)
|
||||||
|
|
||||||
ipNetF := flags.VarPF(
|
ipNetF := flags.VarPF(
|
||||||
&ipNet, "ip-net", "i",
|
textUnmarshalerFlag{&req.IPNet}, "ip-net", "i",
|
||||||
`An IP subnet, in CIDR form, which will be the overall range of`+
|
`An IP subnet, in CIDR form, which will be the overall range of`+
|
||||||
` possible IPs in the network. The first IP in this network`+
|
` possible IPs in the network. The first IP in this network`+
|
||||||
` range will become this first host's IP.`,
|
` range will become this first host's IP.`,
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := flags.VarPF(
|
||||||
&hostName,
|
textUnmarshalerFlag{&req.HostName},
|
||||||
"hostname", "n",
|
"hostname", "h",
|
||||||
"Name of this host, which will be the first host in the network",
|
"Name of this host, which will be the first host in the network",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *name == "" ||
|
if req.Name == "" ||
|
||||||
*domain == "" ||
|
req.Domain == "" ||
|
||||||
!ipNetF.Changed ||
|
!ipNetF.Changed ||
|
||||||
!hostNameF.Changed {
|
!hostNameF.Changed {
|
||||||
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := ctx.daemonRPC.CreateNetwork(
|
err := subCmdCtx.daemonRCPClient.Call(ctx, nil, "CreateNetwork", req)
|
||||||
ctx, *name, *domain, ipNet.V, hostName.V,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating network: %w", err)
|
return fmt.Errorf("creating network: %w", err)
|
||||||
}
|
}
|
||||||
@ -65,15 +63,17 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
var subCmdNetworkJoin = subCmd{
|
var subCmdNetworkJoin = subCmd{
|
||||||
name: "join",
|
name: "join",
|
||||||
descr: "Joins this host to an existing network",
|
descr: "Joins this host to an existing network",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
ctx = subCmdCtx.ctx
|
||||||
bootstrapPath = flags.StringP(
|
flags = subCmdCtx.flagSet(false)
|
||||||
"bootstrap-path", "b", "", "Path to a bootstrap.json file.",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
bootstrapPath := flags.StringP(
|
||||||
|
"bootstrap-path", "b", "", "Path to a bootstrap.json file.",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,16 +88,17 @@ var subCmdNetworkJoin = subCmd{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := ctx.daemonRPC.JoinNetwork(ctx, newBootstrap)
|
return subCmdCtx.daemonRCPClient.Call(
|
||||||
return err
|
ctx, nil, "JoinNetwork", newBootstrap,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdNetwork = subCmd{
|
var subCmdNetwork = subCmd{
|
||||||
name: "network",
|
name: "network",
|
||||||
descr: "Sub-commands related to network membership",
|
descr: "Sub-commands related to network membership",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return ctx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
subCmdNetworkCreate,
|
subCmdNetworkCreate,
|
||||||
subCmdNetworkJoin,
|
subCmdNetworkJoin,
|
||||||
)
|
)
|
||||||
|
@ -12,41 +12,21 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type flagSet struct {
|
|
||||||
*pflag.FlagSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs flagSet) Parse(args []string) error {
|
|
||||||
fs.VisitAll(func(f *pflag.Flag) {
|
|
||||||
if f.Shorthand == "h" {
|
|
||||||
panic(fmt.Sprintf("flag %+v has reserved shorthand `-h`", f))
|
|
||||||
}
|
|
||||||
if f.Name == "help" {
|
|
||||||
panic(fmt.Sprintf("flag %+v has reserved name `--help`", f))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return fs.FlagSet.Parse(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// subCmdCtx contains all information available to a subCmd's do method.
|
// subCmdCtx contains all information available to a subCmd's do method.
|
||||||
type subCmdCtx struct {
|
type subCmdCtx struct {
|
||||||
context.Context
|
|
||||||
|
|
||||||
subCmd subCmd // the subCmd itself
|
subCmd subCmd // the subCmd itself
|
||||||
args []string // command-line arguments, excluding the subCmd itself.
|
args []string // command-line arguments, excluding the subCmd itself.
|
||||||
subCmdNames []string // names of subCmds so far, including this one
|
subCmdNames []string // names of subCmds so far, including this one
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
logger *mlog.Logger
|
logger *mlog.Logger
|
||||||
daemonRPC daemon.RPC
|
daemonRCPClient jsonrpc2.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type subCmd struct {
|
type subCmd struct {
|
||||||
name string
|
name string
|
||||||
descr string
|
descr string
|
||||||
do func(subCmdCtx) error
|
do func(subCmdCtx) error
|
||||||
|
|
||||||
// If set then the name will be allowed to be suffixed with this string.
|
|
||||||
plural string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx subCmdCtx) usagePrefix() string {
|
func (ctx subCmdCtx) usagePrefix() string {
|
||||||
@ -59,7 +39,7 @@ func (ctx subCmdCtx) usagePrefix() string {
|
|||||||
return fmt.Sprintf("\nUSAGE: %s %s", os.Args[0], subCmdNamesStr)
|
return fmt.Sprintf("\nUSAGE: %s %s", os.Args[0], subCmdNamesStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx subCmdCtx) flagSet(withPassthrough bool) flagSet {
|
func (ctx subCmdCtx) flagSet(withPassthrough bool) *pflag.FlagSet {
|
||||||
flags := pflag.NewFlagSet(ctx.subCmd.name, pflag.ExitOnError)
|
flags := pflag.NewFlagSet(ctx.subCmd.name, pflag.ExitOnError)
|
||||||
flags.Usage = func() {
|
flags.Usage = func() {
|
||||||
|
|
||||||
@ -78,7 +58,7 @@ func (ctx subCmdCtx) flagSet(withPassthrough bool) flagSet {
|
|||||||
os.Stderr.Sync()
|
os.Stderr.Sync()
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
return flagSet{flags}
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
||||||
@ -96,11 +76,7 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
|||||||
fmt.Fprintf(os.Stderr, "\nSUB-COMMANDS:\n\n")
|
fmt.Fprintf(os.Stderr, "\nSUB-COMMANDS:\n\n")
|
||||||
|
|
||||||
for _, subCmd := range subCmds {
|
for _, subCmd := range subCmds {
|
||||||
name := subCmd.name
|
fmt.Fprintf(os.Stderr, " %s\t%s\n", subCmd.name, subCmd.descr)
|
||||||
if subCmd.plural != "" {
|
|
||||||
name += "(" + subCmd.plural + ")"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, " %s\t%s\n", name, subCmd.descr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
@ -116,10 +92,8 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
|||||||
|
|
||||||
subCmdsMap := map[string]subCmd{}
|
subCmdsMap := map[string]subCmd{}
|
||||||
for _, subCmd := range subCmds {
|
for _, subCmd := range subCmds {
|
||||||
|
// TODO allow subCmd(s) in some cases
|
||||||
subCmdsMap[subCmd.name] = subCmd
|
subCmdsMap[subCmd.name] = subCmd
|
||||||
if subCmd.plural != "" {
|
|
||||||
subCmdsMap[subCmd.name+subCmd.plural] = subCmd
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subCmdName, args := args[0], args[1:]
|
subCmdName, args := args[0], args[1:]
|
||||||
@ -129,17 +103,17 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
|||||||
printUsageExit(subCmdName)
|
printUsageExit(subCmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
daemonRPC := daemon.RPCFromClient(
|
daemonRCPClient := jsonrpc2.NewUnixHTTPClient(
|
||||||
jsonrpc2.NewUnixHTTPClient(daemon.HTTPSocketPath(), daemonHTTPRPCPath),
|
daemon.HTTPSocketPath(), daemonHTTPRPCPath,
|
||||||
)
|
)
|
||||||
|
|
||||||
err := subCmd.do(subCmdCtx{
|
err := subCmd.do(subCmdCtx{
|
||||||
Context: ctx.Context,
|
|
||||||
subCmd: subCmd,
|
subCmd: subCmd,
|
||||||
args: args,
|
args: args,
|
||||||
subCmdNames: append(ctx.subCmdNames, subCmdName),
|
subCmdNames: append(ctx.subCmdNames, subCmdName),
|
||||||
|
ctx: ctx.ctx,
|
||||||
logger: ctx.logger,
|
logger: ctx.logger,
|
||||||
daemonRPC: daemonRPC,
|
daemonRCPClient: daemonRCPClient,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
var subCmdVersion = subCmd{
|
var subCmdVersion = subCmd{
|
||||||
name: "version",
|
name: "version",
|
||||||
descr: "Dumps version and build info to stdout",
|
descr: "Dumps version and build info to stdout",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
|
||||||
versionPath := filepath.Join(envAppDirPath, "share/version")
|
versionPath := filepath.Join(envAppDirPath, "share/version")
|
||||||
|
|
||||||
|
@ -75,14 +75,6 @@ func dnsmasqPmuxProcConfig(
|
|||||||
Cmd: filepath.Join(binDirPath, "dnsmasq"),
|
Cmd: filepath.Join(binDirPath, "dnsmasq"),
|
||||||
Args: []string{"-d", "-C", confPath},
|
Args: []string{"-d", "-C", confPath},
|
||||||
StartAfterFunc: func(ctx context.Context) error {
|
StartAfterFunc: func(ctx context.Context) error {
|
||||||
// TODO consider a shared dnsmasq across all the daemon's networks.
|
|
||||||
// This would have a few benefits:
|
|
||||||
// - Less processes, less problems
|
|
||||||
// - Less configuration for the user in the case of more than one
|
|
||||||
// network.
|
|
||||||
// - Can listen on 127.0.0.x:53, rather than on the nebula address.
|
|
||||||
// This allows DNS to come up before nebula, which is helpful when
|
|
||||||
// nebula depends on DNS.
|
|
||||||
return waitForNebula(ctx, logger, hostBootstrap)
|
return waitForNebula(ctx, logger, hostBootstrap)
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
// Code generated by gowrap. DO NOT EDIT.
|
|
||||||
// template: jsonrpc2/client_gen.tpl
|
|
||||||
// gowrap: http://github.com/hexdigest/gowrap
|
|
||||||
|
|
||||||
package daemon
|
|
||||||
|
|
||||||
//go:generate gowrap gen -p isle/daemon -i RPC -t jsonrpc2/client_gen.tpl -o client.go -l ""
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"isle/daemon/jsonrpc2"
|
|
||||||
"isle/nebula"
|
|
||||||
)
|
|
||||||
|
|
||||||
type rpcClient struct {
|
|
||||||
client jsonrpc2.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPCFromClient wraps a Client so that it implements the
|
|
||||||
// RPC interface.
|
|
||||||
func RPCFromClient(client jsonrpc2.Client) RPC {
|
|
||||||
return &rpcClient{client}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) CreateHost(ctx context.Context, hostName nebula.HostName, opts CreateHostOpts) (c2 CreateHostResult, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&c2,
|
|
||||||
"CreateHost",
|
|
||||||
hostName,
|
|
||||||
opts,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) CreateNebulaCertificate(ctx context.Context, hostName nebula.HostName, hostEncryptingPublicKey nebula.EncryptingPublicKey) (c2 CreateNebulaCertificateResult, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&c2,
|
|
||||||
"CreateNebulaCertificate",
|
|
||||||
hostName,
|
|
||||||
hostEncryptingPublicKey,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) CreateNetwork(ctx context.Context, name string, domain string, ipNet nebula.IPNet, hostName nebula.HostName) (st1 struct {
|
|
||||||
}, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&st1,
|
|
||||||
"CreateNetwork",
|
|
||||||
name,
|
|
||||||
domain,
|
|
||||||
ipNet,
|
|
||||||
hostName,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) GetGarageClientParams(ctx context.Context) (g1 GarageClientParams, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&g1,
|
|
||||||
"GetGarageClientParams",
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) GetHosts(ctx context.Context) (g1 GetHostsResult, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&g1,
|
|
||||||
"GetHosts",
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) GetNebulaCAPublicCredentials(ctx context.Context) (c2 nebula.CAPublicCredentials, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&c2,
|
|
||||||
"GetNebulaCAPublicCredentials",
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) JoinNetwork(ctx context.Context, req JoiningBootstrap) (st1 struct {
|
|
||||||
}, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&st1,
|
|
||||||
"JoinNetwork",
|
|
||||||
req,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) RemoveHost(ctx context.Context, hostName nebula.HostName) (st1 struct {
|
|
||||||
}, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&st1,
|
|
||||||
"RemoveHost",
|
|
||||||
hostName,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
@ -32,8 +32,6 @@ type CreateHostOpts struct {
|
|||||||
// CanCreateHosts indicates that the bootstrap produced by CreateHost should
|
// CanCreateHosts indicates that the bootstrap produced by CreateHost should
|
||||||
// give the new host the ability to create new hosts as well.
|
// give the new host the ability to create new hosts as well.
|
||||||
CanCreateHosts bool
|
CanCreateHosts bool
|
||||||
|
|
||||||
// TODO add nebula cert tags
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Daemon presents all functionality required for client frontends to interact
|
// Daemon presents all functionality required for client frontends to interact
|
||||||
@ -87,9 +85,6 @@ type Daemon interface {
|
|||||||
// existing host, given the public key for that host. This is currently
|
// existing host, given the public key for that host. This is currently
|
||||||
// mostly useful for creating certs for mobile devices.
|
// mostly useful for creating certs for mobile devices.
|
||||||
//
|
//
|
||||||
// TODO replace this with CreateHostBootstrap, and the
|
|
||||||
// CreateNebulaCertificate RPC method can just pull cert out of that.
|
|
||||||
//
|
|
||||||
// Errors:
|
// Errors:
|
||||||
// - ErrHostNotFound
|
// - ErrHostNotFound
|
||||||
CreateNebulaCertificate(
|
CreateNebulaCertificate(
|
||||||
@ -690,7 +685,6 @@ func (d *daemon) CreateHost(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO if the ip is given, check that it's not already in use.
|
|
||||||
|
|
||||||
caSigningPrivateKey, err := getNebulaCASigningPrivateKey(
|
caSigningPrivateKey, err := getNebulaCASigningPrivateKey(
|
||||||
ctx, d.secretsStore,
|
ctx, d.secretsStore,
|
||||||
|
@ -15,5 +15,5 @@ type Client interface {
|
|||||||
//
|
//
|
||||||
// If an error result is returned from the server that will be returned as
|
// If an error result is returned from the server that will be returned as
|
||||||
// an Error struct.
|
// an Error struct.
|
||||||
Call(ctx context.Context, rcv any, method string, params ...any) error
|
Call(ctx context.Context, rcv any, method string, params any) error
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import (
|
|
||||||
"isle/daemon/jsonrpc2"
|
|
||||||
)
|
|
||||||
|
|
||||||
{{ $t := printf "%sClient" (down .Interface.Name) }}
|
|
||||||
|
|
||||||
type {{$t}} struct {
|
|
||||||
client jsonrpc2.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Interface.Name}}FromClient wraps a Client so that it implements the
|
|
||||||
// {{.Interface.Name}} interface.
|
|
||||||
func {{.Interface.Name}}FromClient(client jsonrpc2.Client) {{.Interface.Name}} {
|
|
||||||
return &{{$t}}{client}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{range $method := .Interface.Methods}}
|
|
||||||
func (c *{{$t}}) {{$method.Declaration}} {
|
|
||||||
{{- $ctx := (index $method.Params 0).Name}}
|
|
||||||
{{- $rcv := (index $method.Results 0).Name}}
|
|
||||||
{{- $err := (index $method.Results 1).Name}}
|
|
||||||
{{- $err}} = c.client.Call(
|
|
||||||
{{$ctx}},
|
|
||||||
&{{$rcv}},
|
|
||||||
"{{$method.Name}}",
|
|
||||||
{{- range $param := (slice $method.Params 1)}}
|
|
||||||
{{$param.Name}},
|
|
||||||
{{- end}}
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
{{end}}
|
|
@ -49,7 +49,7 @@ func NewUnixHTTPClient(unixSocketPath, reqPath string) Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpClient) Call(
|
func (c *httpClient) Call(
|
||||||
ctx context.Context, rcv any, method string, params ...any,
|
ctx context.Context, rcv any, method string, params any,
|
||||||
) error {
|
) error {
|
||||||
var (
|
var (
|
||||||
body = new(bytes.Buffer)
|
body = new(bytes.Buffer)
|
||||||
|
@ -19,7 +19,7 @@ func NewReadWriterClient(rw io.ReadWriter) Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c rwClient) Call(
|
func (c rwClient) Call(
|
||||||
ctx context.Context, rcv any, method string, params ...any,
|
ctx context.Context, rcv any, method string, params any,
|
||||||
) error {
|
) error {
|
||||||
id, err := encodeRequest(c.enc, method, params)
|
id, err := encodeRequest(c.enc, method, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -18,19 +18,14 @@ type methodDispatchFunc func(context.Context, Request) (any, error)
|
|||||||
func newMethodDispatchFunc(
|
func newMethodDispatchFunc(
|
||||||
method reflect.Value,
|
method reflect.Value,
|
||||||
) methodDispatchFunc {
|
) methodDispatchFunc {
|
||||||
paramTs := make([]reflect.Type, method.Type().NumIn()-1)
|
paramT := method.Type().In(1)
|
||||||
for i := range paramTs {
|
|
||||||
paramTs[i] = method.Type().In(i + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(ctx context.Context, req Request) (any, error) {
|
return func(ctx context.Context, req Request) (any, error) {
|
||||||
callVals := make([]reflect.Value, 0, len(paramTs)+1)
|
var (
|
||||||
callVals = append(callVals, reflect.ValueOf(ctx))
|
ctxV = reflect.ValueOf(ctx)
|
||||||
|
paramPtrV = reflect.New(paramT)
|
||||||
|
)
|
||||||
|
|
||||||
for i, paramT := range paramTs {
|
err := json.Unmarshal(req.Params, paramPtrV.Interface())
|
||||||
paramPtrV := reflect.New(paramT)
|
|
||||||
|
|
||||||
err := json.Unmarshal(req.Params[i], paramPtrV.Interface())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The JSON has already been validated, so this is not an
|
// The JSON has already been validated, so this is not an
|
||||||
// errCodeParse situation. We assume it's an invalid param then,
|
// errCodeParse situation. We assume it's an invalid param then,
|
||||||
@ -38,17 +33,14 @@ func newMethodDispatchFunc(
|
|||||||
// returning an Error of its own.
|
// returning an Error of its own.
|
||||||
if !errors.As(err, new(Error)) {
|
if !errors.As(err, new(Error)) {
|
||||||
err = NewInvalidParamsError(
|
err = NewInvalidParamsError(
|
||||||
"JSON unmarshaling param %d into %T: %v", i, paramT, err,
|
"JSON unmarshaling params into %T: %v", paramT, err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
callVals = append(callVals, paramPtrV.Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
callResV = method.Call(callVals)
|
callResV = method.Call([]reflect.Value{ctxV, paramPtrV.Elem()})
|
||||||
resV = callResV[0]
|
resV = callResV[0]
|
||||||
errV = callResV[1]
|
errV = callResV[1]
|
||||||
)
|
)
|
||||||
@ -94,7 +86,7 @@ func NewDispatchHandler(i any) Handler {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if !method.IsExported() ||
|
if !method.IsExported() ||
|
||||||
methodT.NumIn() < 1 ||
|
methodT.NumIn() != 2 ||
|
||||||
methodT.In(0) != ctxT ||
|
methodT.In(0) != ctxT ||
|
||||||
methodT.NumOut() != 2 ||
|
methodT.NumOut() != 2 ||
|
||||||
methodT.Out(1) != errT {
|
methodT.Out(1) != errT {
|
||||||
|
@ -3,7 +3,6 @@ package jsonrpc2
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
@ -60,14 +59,9 @@ func NewMLogMiddleware(logger *mlog.Logger) Middleware {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if logger.MaxLevel() >= mlog.LevelDebug.Int() {
|
if logger.MaxLevel() >= mlog.LevelDebug.Int() {
|
||||||
ctx := ctx
|
ctx := mctx.Annotate(
|
||||||
for i := range req.Params {
|
ctx, "rpcRequestParams", string(req.Params),
|
||||||
ctx = mctx.Annotate(
|
|
||||||
ctx,
|
|
||||||
fmt.Sprintf("rpcRequestParam%d", i),
|
|
||||||
string(req.Params[i]),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
logger.Debug(ctx, "Handling RPC request")
|
logger.Debug(ctx, "Handling RPC request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ const version = "2.0"
|
|||||||
type Request struct {
|
type Request struct {
|
||||||
Version string `json:"jsonrpc"` // must be "2.0"
|
Version string `json:"jsonrpc"` // must be "2.0"
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Params []json.RawMessage `json:"params,omitempty"`
|
Params json.RawMessage `json:"params,omitempty"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,19 +37,13 @@ func newID() string {
|
|||||||
// encodeRequest writes a request to an io.Writer, returning the ID of the
|
// encodeRequest writes a request to an io.Writer, returning the ID of the
|
||||||
// request.
|
// request.
|
||||||
func encodeRequest(
|
func encodeRequest(
|
||||||
enc *json.Encoder, method string, params []any,
|
enc *json.Encoder, method string, params any,
|
||||||
) (
|
) (
|
||||||
string, error,
|
string, error,
|
||||||
) {
|
) {
|
||||||
var (
|
paramsB, err := json.Marshal(params)
|
||||||
paramsBs = make([]json.RawMessage, len(params))
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for i := range params {
|
|
||||||
paramsBs[i], err = json.Marshal(params[i])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("encoding param %d as JSON: %w", i, err)
|
return "", fmt.Errorf("encoding params as JSON: %w", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -57,7 +51,7 @@ func encodeRequest(
|
|||||||
reqEnvelope = Request{
|
reqEnvelope = Request{
|
||||||
Version: version,
|
Version: version,
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: paramsBs,
|
Params: paramsB,
|
||||||
ID: id,
|
ID: id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -26,22 +26,14 @@ var ErrDivideByZero = Error{
|
|||||||
|
|
||||||
type dividerImpl struct{}
|
type dividerImpl struct{}
|
||||||
|
|
||||||
func (dividerImpl) Divide2(ctx context.Context, top, bottom int) (int, error) {
|
func (dividerImpl) Divide(ctx context.Context, p DivideParams) (int, error) {
|
||||||
if bottom == 0 {
|
if p.Bottom == 0 {
|
||||||
return 0, ErrDivideByZero
|
return 0, ErrDivideByZero
|
||||||
}
|
}
|
||||||
if top%bottom != 0 {
|
if p.Top%p.Bottom != 0 {
|
||||||
return 0, errors.New("numbers don't divide evenly, cannot compute!")
|
return 0, errors.New("numbers don't divide evenly, cannot compute!")
|
||||||
}
|
}
|
||||||
return top / bottom, nil
|
return p.Top / p.Bottom, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (i dividerImpl) Noop(ctx context.Context) (int, error) {
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i dividerImpl) Divide(ctx context.Context, p DivideParams) (int, error) {
|
|
||||||
return i.Divide2(ctx, p.Top, p.Bottom)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dividerImpl) Hidden(ctx context.Context, p struct{}) (int, error) {
|
func (dividerImpl) Hidden(ctx context.Context, p struct{}) (int, error) {
|
||||||
@ -49,8 +41,6 @@ func (dividerImpl) Hidden(ctx context.Context, p struct{}) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type divider interface {
|
type divider interface {
|
||||||
Noop(ctx context.Context) (int, error)
|
|
||||||
Divide2(ctx context.Context, top, bottom int) (int, error)
|
|
||||||
Divide(ctx context.Context, p DivideParams) (int, error)
|
Divide(ctx context.Context, p DivideParams) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,26 +82,6 @@ func testClient(t *testing.T, client Client) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("success/multiple_params", func(t *testing.T) {
|
|
||||||
var res int
|
|
||||||
err := client.Call(ctx, &res, "Divide2", 6, 3)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if res != 2 {
|
|
||||||
t.Fatalf("expected 2, got %d", res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("success/no_params", func(t *testing.T) {
|
|
||||||
var res int
|
|
||||||
err := client.Call(ctx, &res, "Noop")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else if res != 1 {
|
|
||||||
t.Fatalf("expected 1, got %d", res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("err/application", func(t *testing.T) {
|
t.Run("err/application", func(t *testing.T) {
|
||||||
err := client.Call(ctx, nil, "Divide", DivideParams{})
|
err := client.Call(ctx, nil, "Divide", DivideParams{})
|
||||||
if !errors.Is(err, ErrDivideByZero) {
|
if !errors.Is(err, ErrDivideByZero) {
|
||||||
|
214
go/daemon/rpc.go
214
go/daemon/rpc.go
@ -11,115 +11,49 @@ import (
|
|||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetHostsResult wraps the results from the GetHosts RPC method.
|
|
||||||
type GetHostsResult struct {
|
|
||||||
Hosts []bootstrap.Host
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHostResult wraps the results from the CreateHost RPC method.
|
|
||||||
type CreateHostResult struct {
|
|
||||||
JoiningBootstrap JoiningBootstrap
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNebulaCertificateResult wraps the results from the
|
|
||||||
// CreateNebulaCertificate RPC method.
|
|
||||||
type CreateNebulaCertificateResult struct {
|
|
||||||
HostNebulaCertificate nebula.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// RPC exposes all RPC methods which are available to be called over the RPC
|
// RPC exposes all RPC methods which are available to be called over the RPC
|
||||||
// interface.
|
// interface.
|
||||||
type RPC interface {
|
type RPC struct {
|
||||||
// CreateNetwork passes through to the Daemon method of the same name.
|
|
||||||
//
|
|
||||||
// name: Human-readable name of the network.
|
|
||||||
// domain: Primary domain name that network services are served under.
|
|
||||||
// ipNet:
|
|
||||||
// An IP subnet, in CIDR form, which will be the overall range of
|
|
||||||
// possible IPs in the network. The first IP in this network range will
|
|
||||||
// become this first host's IP.
|
|
||||||
// hostName: The name of this first host in the network.
|
|
||||||
CreateNetwork(
|
|
||||||
ctx context.Context,
|
|
||||||
name string,
|
|
||||||
domain string,
|
|
||||||
ipNet nebula.IPNet,
|
|
||||||
hostName nebula.HostName,
|
|
||||||
) (
|
|
||||||
struct{}, error,
|
|
||||||
)
|
|
||||||
|
|
||||||
// JoinNetwork passes through to the Daemon method of the same name.
|
|
||||||
JoinNetwork(
|
|
||||||
ctx context.Context, req JoiningBootstrap,
|
|
||||||
) (
|
|
||||||
struct{}, error,
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetHosts returns all hosts known to the network, sorted by their name.
|
|
||||||
GetHosts(ctx context.Context) (GetHostsResult, error)
|
|
||||||
|
|
||||||
// GetGarageClientParams passes the call through to the Daemon method of the
|
|
||||||
// same name.
|
|
||||||
GetGarageClientParams(ctx context.Context) (GarageClientParams, error)
|
|
||||||
|
|
||||||
// GetNebulaCAPublicCredentials returns the CAPublicCredentials for the
|
|
||||||
// network.
|
|
||||||
GetNebulaCAPublicCredentials(
|
|
||||||
ctx context.Context,
|
|
||||||
) (
|
|
||||||
nebula.CAPublicCredentials, error,
|
|
||||||
)
|
|
||||||
|
|
||||||
// RemoveHost passes the call through to the Daemon method of the same name.
|
|
||||||
RemoveHost(
|
|
||||||
ctx context.Context, hostName nebula.HostName,
|
|
||||||
) (
|
|
||||||
struct{}, error,
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateHost passes the call through to the Daemon method of the same name.
|
|
||||||
CreateHost(
|
|
||||||
ctx context.Context, hostName nebula.HostName, opts CreateHostOpts,
|
|
||||||
) (
|
|
||||||
CreateHostResult, error,
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateNebulaCertificate passes the call through to the Daemon method of
|
|
||||||
// the same name.
|
|
||||||
CreateNebulaCertificate(
|
|
||||||
ctx context.Context,
|
|
||||||
hostName nebula.HostName,
|
|
||||||
hostEncryptingPublicKey nebula.EncryptingPublicKey,
|
|
||||||
) (
|
|
||||||
CreateNebulaCertificateResult, error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type rpcImpl struct {
|
|
||||||
daemon Daemon
|
daemon Daemon
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRPC initializes and returns an RPC instance.
|
// NewRPC initializes and returns an RPC instance.
|
||||||
func NewRPC(daemon Daemon) RPC {
|
func NewRPC(daemon Daemon) *RPC {
|
||||||
return &rpcImpl{daemon}
|
return &RPC{daemon}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) CreateNetwork(
|
// CreateNetworkRequest contains the arguments to the CreateNetwork RPC method.
|
||||||
ctx context.Context,
|
//
|
||||||
name string,
|
// All fields are required.
|
||||||
domain string,
|
type CreateNetworkRequest struct {
|
||||||
ipNet nebula.IPNet,
|
// Human-readable name of the network.
|
||||||
hostName nebula.HostName,
|
Name string
|
||||||
|
|
||||||
|
// Primary domain name that network services are served under.
|
||||||
|
Domain string
|
||||||
|
|
||||||
|
// An IP subnet, in CIDR form, which will be the overall range of possible
|
||||||
|
// IPs in the network. The first IP in this network range will become this
|
||||||
|
// first host's IP.
|
||||||
|
IPNet nebula.IPNet
|
||||||
|
|
||||||
|
// The name of this first host in the network.
|
||||||
|
HostName nebula.HostName
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNetwork passes through to the Daemon method of the same name.
|
||||||
|
func (r *RPC) CreateNetwork(
|
||||||
|
ctx context.Context, req CreateNetworkRequest,
|
||||||
) (
|
) (
|
||||||
struct{}, error,
|
struct{}, error,
|
||||||
) {
|
) {
|
||||||
return struct{}{}, r.daemon.CreateNetwork(
|
return struct{}{}, r.daemon.CreateNetwork(
|
||||||
ctx, name, domain, ipNet, hostName,
|
ctx, req.Name, req.Domain, req.IPNet, req.HostName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) JoinNetwork(
|
// JoinNetwork passes through to the Daemon method of the same name.
|
||||||
|
func (r *RPC) JoinNetwork(
|
||||||
ctx context.Context, req JoiningBootstrap,
|
ctx context.Context, req JoiningBootstrap,
|
||||||
) (
|
) (
|
||||||
struct{}, error,
|
struct{}, error,
|
||||||
@ -127,7 +61,17 @@ func (r *rpcImpl) JoinNetwork(
|
|||||||
return struct{}{}, r.daemon.JoinNetwork(ctx, req)
|
return struct{}{}, r.daemon.JoinNetwork(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) GetHosts(ctx context.Context) (GetHostsResult, error) {
|
// GetHostsResult wraps the results from the GetHosts RPC method.
|
||||||
|
type GetHostsResult struct {
|
||||||
|
Hosts []bootstrap.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHosts returns all hosts known to the network, sorted by their name.
|
||||||
|
func (r *RPC) GetHosts(
|
||||||
|
ctx context.Context, req struct{},
|
||||||
|
) (
|
||||||
|
GetHostsResult, error,
|
||||||
|
) {
|
||||||
b, err := r.daemon.GetBootstrap(ctx)
|
b, err := r.daemon.GetBootstrap(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GetHostsResult{}, fmt.Errorf("retrieving bootstrap: %w", err)
|
return GetHostsResult{}, fmt.Errorf("retrieving bootstrap: %w", err)
|
||||||
@ -141,16 +85,19 @@ func (r *rpcImpl) GetHosts(ctx context.Context) (GetHostsResult, error) {
|
|||||||
return GetHostsResult{hosts}, nil
|
return GetHostsResult{hosts}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) GetGarageClientParams(
|
// GetGarageClientParams passes the call through to the Daemon method of the
|
||||||
ctx context.Context,
|
// same name.
|
||||||
|
func (r *RPC) GetGarageClientParams(
|
||||||
|
ctx context.Context, req struct{},
|
||||||
) (
|
) (
|
||||||
GarageClientParams, error,
|
GarageClientParams, error,
|
||||||
) {
|
) {
|
||||||
return r.daemon.GetGarageClientParams(ctx)
|
return r.daemon.GetGarageClientParams(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) GetNebulaCAPublicCredentials(
|
// GetNebulaCAPublicCredentials returns the CAPublicCredentials for the network.
|
||||||
ctx context.Context,
|
func (r *RPC) GetNebulaCAPublicCredentials(
|
||||||
|
ctx context.Context, req struct{},
|
||||||
) (
|
) (
|
||||||
nebula.CAPublicCredentials, error,
|
nebula.CAPublicCredentials, error,
|
||||||
) {
|
) {
|
||||||
@ -164,20 +111,42 @@ func (r *rpcImpl) GetNebulaCAPublicCredentials(
|
|||||||
return b.CAPublicCredentials, nil
|
return b.CAPublicCredentials, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) RemoveHost(
|
// RemoveHostRequest contains the arguments to the RemoveHost RPC method.
|
||||||
ctx context.Context, hostName nebula.HostName,
|
//
|
||||||
) (
|
// All fields are required.
|
||||||
struct{}, error,
|
type RemoveHostRequest struct {
|
||||||
) {
|
HostName nebula.HostName
|
||||||
return struct{}{}, r.daemon.RemoveHost(ctx, hostName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) CreateHost(
|
// RemoveHost passes the call through to the Daemon method of the same name.
|
||||||
ctx context.Context, hostName nebula.HostName, opts CreateHostOpts,
|
func (r *RPC) RemoveHost(ctx context.Context, req RemoveHostRequest) (struct{}, error) {
|
||||||
|
return struct{}{}, r.daemon.RemoveHost(ctx, req.HostName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHostRequest contains the arguments to the
|
||||||
|
// CreateHost RPC method.
|
||||||
|
//
|
||||||
|
// All fields are required.
|
||||||
|
type CreateHostRequest struct {
|
||||||
|
HostName nebula.HostName
|
||||||
|
Opts CreateHostOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHostResult wraps the results from the CreateHost RPC method.
|
||||||
|
type CreateHostResult struct {
|
||||||
|
JoiningBootstrap JoiningBootstrap
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHost passes the call through to the Daemon method of the
|
||||||
|
// same name.
|
||||||
|
func (r *RPC) CreateHost(
|
||||||
|
ctx context.Context, req CreateHostRequest,
|
||||||
) (
|
) (
|
||||||
CreateHostResult, error,
|
CreateHostResult, error,
|
||||||
) {
|
) {
|
||||||
joiningBootstrap, err := r.daemon.CreateHost(ctx, hostName, opts)
|
joiningBootstrap, err := r.daemon.CreateHost(
|
||||||
|
ctx, req.HostName, req.Opts,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CreateHostResult{}, err
|
return CreateHostResult{}, err
|
||||||
}
|
}
|
||||||
@ -185,19 +154,36 @@ func (r *rpcImpl) CreateHost(
|
|||||||
return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil
|
return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcImpl) CreateNebulaCertificate(
|
// CreateNebulaCertificateRequest contains the arguments to the
|
||||||
ctx context.Context,
|
// CreateNebulaCertificate RPC method.
|
||||||
hostName nebula.HostName,
|
//
|
||||||
hostEncryptingPublicKey nebula.EncryptingPublicKey,
|
// All fields are required.
|
||||||
|
type CreateNebulaCertificateRequest struct {
|
||||||
|
HostName nebula.HostName
|
||||||
|
HostEncryptingPublicKey nebula.EncryptingPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNebulaCertificateResult wraps the results from the
|
||||||
|
// CreateNebulaCertificate RPC method.
|
||||||
|
type CreateNebulaCertificateResult struct {
|
||||||
|
HostNebulaCertifcate nebula.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNebulaCertificate passes the call through to the Daemon method of the
|
||||||
|
// same name.
|
||||||
|
func (r *RPC) CreateNebulaCertificate(
|
||||||
|
ctx context.Context, req CreateNebulaCertificateRequest,
|
||||||
) (
|
) (
|
||||||
CreateNebulaCertificateResult, error,
|
CreateNebulaCertificateResult, error,
|
||||||
) {
|
) {
|
||||||
cert, err := r.daemon.CreateNebulaCertificate(
|
cert, err := r.daemon.CreateNebulaCertificate(
|
||||||
ctx, hostName, hostEncryptingPublicKey,
|
ctx, req.HostName, req.HostEncryptingPublicKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CreateNebulaCertificateResult{}, err
|
return CreateNebulaCertificateResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateNebulaCertificateResult{HostNebulaCertificate: cert}, nil
|
return CreateNebulaCertificateResult{
|
||||||
|
HostNebulaCertifcate: cert,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,6 @@ var hostNameRegexp = regexp.MustCompile(`^[a-z][a-z0-9\-]*$`)
|
|||||||
// lowercase letters, numbers, and hyphens, and must start with a letter.
|
// lowercase letters, numbers, and hyphens, and must start with a letter.
|
||||||
type HostName string
|
type HostName string
|
||||||
|
|
||||||
// MarshalText casts the HostName to a byte string and returns it.
|
|
||||||
func (h HostName) MarshalText() ([]byte, error) {
|
|
||||||
return []byte(h), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText parses and validates a HostName from a text string.
|
// UnmarshalText parses and validates a HostName from a text string.
|
||||||
func (h *HostName) UnmarshalText(b []byte) error {
|
func (h *HostName) UnmarshalText(b []byte) error {
|
||||||
if !hostNameRegexp.Match(b) {
|
if !hostNameRegexp.Match(b) {
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
buildGoModule,
|
|
||||||
fetchFromGitHub,
|
|
||||||
}: let
|
|
||||||
version = "1.4.0";
|
|
||||||
in buildGoModule {
|
|
||||||
pname = "gowrap";
|
|
||||||
inherit version;
|
|
||||||
src = fetchFromGitHub {
|
|
||||||
owner = "hexdigest";
|
|
||||||
repo = "gowrap";
|
|
||||||
rev = "v${version}";
|
|
||||||
hash = "sha256-eEaUANLnxRGfVbhOTwJV+R9iEWMObg0lHqmwO3AYuIk=";
|
|
||||||
};
|
|
||||||
vendorHash = "sha256-xIOyXXt8WSGQYIvIam+0e25VNI7awYEEYZBe7trC6zQ=";
|
|
||||||
subPackages = [ "cmd/gowrap" ];
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user