Compare commits
9 Commits
af69f1cfba
...
038a28bb02
Author | SHA1 | Date | |
---|---|---|---|
038a28bb02 | |||
06a345ecd1 | |||
6c185f6263 | |||
53ad8a91b4 | |||
5138ed7c6a | |||
4f6a89ced0 | |||
39e12f6ebd | |||
d31be8455b | |||
ca62a37692 |
@ -105,7 +105,4 @@ 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
128
docs/roadmap.md
@ -1,128 +0,0 @@
|
|||||||
# 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,6 +47,10 @@ 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,8 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (ctx subCmdCtx) getHosts() (daemon.GetHostsResult, error) {
|
func (ctx subCmdCtx) getHosts() (daemon.GetHostsResult, error) {
|
||||||
var res daemon.GetHostsResult
|
res, err := ctx.daemonRPC.GetHosts(ctx)
|
||||||
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,12 +10,16 @@ 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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
flags := ctx.flagSet(false)
|
||||||
|
|
||||||
daemonConfigPath := flags.StringP(
|
daemonConfigPath := flags.StringP(
|
||||||
"config-path", "c", "",
|
"config-path", "c", "",
|
||||||
@ -32,12 +36,10 @@ 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(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.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)
|
||||||
}
|
}
|
||||||
@ -47,7 +49,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 := subCmdCtx.logger.WithMaxLevel(logLevel.Int())
|
logger := ctx.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,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/daemon/jsonrpc2"
|
"isle/daemon/jsonrpc2"
|
||||||
"net"
|
"net"
|
||||||
@ -17,11 +18,25 @@ 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(
|
||||||
@ -35,7 +50,6 @@ 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,20 +3,37 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding"
|
"encoding"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"isle/nebula"
|
||||||
|
"net/netip"
|
||||||
)
|
)
|
||||||
|
|
||||||
type textUnmarshalerFlag struct {
|
type textUnmarshaler[T any] interface {
|
||||||
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) Set(v string) error {
|
func (f *textUnmarshalerFlag[T, P]) Type() string { return "string" }
|
||||||
return f.inner.UnmarshalText([]byte(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f textUnmarshalerFlag) String() string {
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
return fmt.Sprint(f.inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f textUnmarshalerFlag) Type() string { return "string" }
|
type (
|
||||||
|
hostNameFlag = textUnmarshalerFlag[nebula.HostName, *nebula.HostName]
|
||||||
|
ipNetFlag = textUnmarshalerFlag[nebula.IPNet, *nebula.IPNet]
|
||||||
|
ipFlag = textUnmarshalerFlag[netip.Addr, *netip.Addr]
|
||||||
|
)
|
||||||
|
@ -6,8 +6,6 @@ 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
|
||||||
@ -35,9 +33,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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
|
||||||
flags := subCmdCtx.flagSet(true)
|
flags := ctx.flagSet(true)
|
||||||
|
|
||||||
keyID := flags.StringP(
|
keyID := flags.StringP(
|
||||||
"key-id", "i", "",
|
"key-id", "i", "",
|
||||||
@ -49,14 +47,11 @@ 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(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientParams daemon.GarageClientParams
|
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -118,12 +113,9 @@ 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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
|
||||||
var clientParams daemon.GarageClientParams
|
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -134,7 +126,7 @@ var subCmdGarageCLI = subCmd{
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
binPath = binPath("garage")
|
binPath = binPath("garage")
|
||||||
args = append([]string{"garage"}, subCmdCtx.args...)
|
args = append([]string{"garage"}, ctx.args...)
|
||||||
cliEnv = append(
|
cliEnv = append(
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
"GARAGE_RPC_HOST="+clientParams.Peer.RPCPeerAddr(),
|
"GARAGE_RPC_HOST="+clientParams.Peer.RPCPeerAddr(),
|
||||||
@ -156,8 +148,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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return ctx.doSubCmd(
|
||||||
subCmdGarageCLI,
|
subCmdGarageCLI,
|
||||||
subCmdGarageMC,
|
subCmdGarageMC,
|
||||||
)
|
)
|
||||||
|
@ -7,31 +7,27 @@ import (
|
|||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
"isle/nebula"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
var subCmdHostsCreate = subCmd{
|
var subCmdHostCreate = 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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = subCmdCtx.flagSet(false)
|
flags = ctx.flagSet(false)
|
||||||
hostName nebula.HostName
|
hostName hostNameFlag
|
||||||
ip netip.Addr
|
ip ipFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := flags.VarPF(
|
||||||
textUnmarshalerFlag{&hostName},
|
&hostName,
|
||||||
"hostname", "h",
|
"hostname", "n",
|
||||||
"Name of the host to generate bootstrap.json for",
|
"Name of the host to generate bootstrap.json for",
|
||||||
)
|
)
|
||||||
|
|
||||||
flags.VarP(
|
flags.VarP(&ip, "ip", "i", "IP of the new host. An available IP will be chosen if none is given.")
|
||||||
textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host",
|
|
||||||
)
|
|
||||||
|
|
||||||
canCreateHosts := flags.Bool(
|
canCreateHosts := flags.Bool(
|
||||||
"can-create-hosts",
|
"can-create-hosts",
|
||||||
@ -39,7 +35,7 @@ var subCmdHostsCreate = 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(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,18 +43,11 @@ var subCmdHostsCreate = subCmd{
|
|||||||
return errors.New("--hostname is required")
|
return errors.New("--hostname is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
var res daemon.CreateHostResult
|
res, err := ctx.daemonRPC.CreateHost(
|
||||||
err := subCmdCtx.daemonRCPClient.Call(
|
ctx, hostName.V, daemon.CreateHostOpts{
|
||||||
subCmdCtx.ctx,
|
IP: ip.V,
|
||||||
&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)
|
||||||
@ -68,11 +57,11 @@ var subCmdHostsCreate = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdHostsList = subCmd{
|
var subCmdHostList = 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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
hostsRes, err := subCmdCtx.getHosts()
|
hostsRes, err := ctx.getHosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetHosts: %w", err)
|
return fmt.Errorf("calling GetHosts: %w", err)
|
||||||
}
|
}
|
||||||
@ -104,22 +93,22 @@ var subCmdHostsList = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdHostsRemove = subCmd{
|
var subCmdHostRemove = subCmd{
|
||||||
name: "remove",
|
name: "remove",
|
||||||
descr: "Removes a host from the network",
|
descr: "Removes a host from the network",
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = subCmdCtx.flagSet(false)
|
flags = ctx.flagSet(false)
|
||||||
hostName nebula.HostName
|
hostName hostNameFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := flags.VarPF(
|
||||||
textUnmarshalerFlag{&hostName},
|
&hostName,
|
||||||
"hostname", "h",
|
"hostname", "n",
|
||||||
"Name of the host to remove",
|
"Name of the host to remove",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,11 +116,7 @@ var subCmdHostsRemove = subCmd{
|
|||||||
return errors.New("--hostname is required")
|
return errors.New("--hostname is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := subCmdCtx.daemonRCPClient.Call(
|
_, err := ctx.daemonRPC.RemoveHost(ctx, hostName.V)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -140,14 +125,15 @@ var subCmdHostsRemove = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdHosts = subCmd{
|
var subCmdHost = subCmd{
|
||||||
name: "hosts",
|
name: "host",
|
||||||
|
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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return ctx.doSubCmd(
|
||||||
subCmdHostsCreate,
|
subCmdHostCreate,
|
||||||
subCmdHostsRemove,
|
subCmdHostRemove,
|
||||||
subCmdHostsList,
|
subCmdHostList,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
@ -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,
|
||||||
subCmdHosts,
|
subCmdHost,
|
||||||
subCmdNebula,
|
subCmdNebula,
|
||||||
subCmdNetwork,
|
subCmdNetwork,
|
||||||
subCmdVersion,
|
subCmdVersion,
|
||||||
|
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/daemon"
|
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"os"
|
"os"
|
||||||
@ -12,15 +11,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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = subCmdCtx.flagSet(false)
|
flags = ctx.flagSet(false)
|
||||||
hostName nebula.HostName
|
hostName hostNameFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := flags.VarPF(
|
||||||
textUnmarshalerFlag{&hostName},
|
&hostName,
|
||||||
"hostname", "h",
|
"hostname", "n",
|
||||||
"Name of the host to generate a certificate for",
|
"Name of the host to generate a certificate for",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,7 +28,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(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,21 +46,14 @@ var subCmdNebulaCreateCert = subCmd{
|
|||||||
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
|
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var res daemon.CreateNebulaCertificateResult
|
res, err := ctx.daemonRPC.CreateNebulaCertificate(
|
||||||
err = subCmdCtx.daemonRCPClient.Call(
|
ctx, hostName.V, hostPub,
|
||||||
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.HostNebulaCertifcate.Unwrap().MarshalToPEM()
|
nebulaHostCertPEM, err := res.HostNebulaCertificate.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)
|
||||||
}
|
}
|
||||||
@ -77,22 +69,19 @@ 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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
flags := ctx.flagSet(false)
|
||||||
if err := flags.Parse(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts, err := subCmdCtx.getHosts()
|
hosts, err := ctx.getHosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting hosts: %w", err)
|
return fmt.Errorf("getting hosts: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var caPublicCreds nebula.CAPublicCredentials
|
caPublicCreds, err := ctx.daemonRPC.GetNebulaCAPublicCredentials(ctx)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -145,8 +134,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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return ctx.doSubCmd(
|
||||||
subCmdNebulaCreateCert,
|
subCmdNebulaCreateCert,
|
||||||
subCmdNebulaShow,
|
subCmdNebulaShow,
|
||||||
)
|
)
|
||||||
|
@ -10,48 +10,50 @@ 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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
ctx = subCmdCtx.ctx
|
flags = ctx.flagSet(false)
|
||||||
flags = subCmdCtx.flagSet(false)
|
ipNet ipNetFlag
|
||||||
req daemon.CreateNetworkRequest
|
hostName hostNameFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
flags.StringVarP(
|
name := flags.StringP(
|
||||||
&req.Name, "name", "n", "",
|
"name", "N", "",
|
||||||
"Human-readable name to identify the network as.",
|
"Human-readable name to identify the network as.",
|
||||||
)
|
)
|
||||||
|
|
||||||
flags.StringVarP(
|
domain := flags.StringP(
|
||||||
&req.Domain, "domain", "d", "",
|
"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(
|
||||||
textUnmarshalerFlag{&req.IPNet}, "ip-net", "i",
|
&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(
|
||||||
textUnmarshalerFlag{&req.HostName},
|
&hostName,
|
||||||
"hostname", "h",
|
"hostname", "n",
|
||||||
"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(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Name == "" ||
|
if *name == "" ||
|
||||||
req.Domain == "" ||
|
*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 := subCmdCtx.daemonRCPClient.Call(ctx, nil, "CreateNetwork", req)
|
_, err := ctx.daemonRPC.CreateNetwork(
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
@ -63,17 +65,15 @@ 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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
ctx = subCmdCtx.ctx
|
flags = ctx.flagSet(false)
|
||||||
flags = subCmdCtx.flagSet(false)
|
bootstrapPath = flags.StringP(
|
||||||
)
|
|
||||||
|
|
||||||
bootstrapPath := flags.StringP(
|
|
||||||
"bootstrap-path", "b", "", "Path to a bootstrap.json file.",
|
"bootstrap-path", "b", "", "Path to a bootstrap.json file.",
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if err := flags.Parse(subCmdCtx.args); err != nil {
|
if err := flags.Parse(ctx.args); err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,17 +88,16 @@ var subCmdNetworkJoin = subCmd{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return subCmdCtx.daemonRCPClient.Call(
|
_, err := ctx.daemonRPC.JoinNetwork(ctx, newBootstrap)
|
||||||
ctx, nil, "JoinNetwork", newBootstrap,
|
return err
|
||||||
)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return ctx.doSubCmd(
|
||||||
subCmdNetworkCreate,
|
subCmdNetworkCreate,
|
||||||
subCmdNetworkJoin,
|
subCmdNetworkJoin,
|
||||||
)
|
)
|
||||||
|
@ -12,21 +12,41 @@ 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
|
||||||
daemonRCPClient jsonrpc2.Client
|
daemonRPC daemon.RPC
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@ -39,7 +59,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) *pflag.FlagSet {
|
func (ctx subCmdCtx) flagSet(withPassthrough bool) flagSet {
|
||||||
flags := pflag.NewFlagSet(ctx.subCmd.name, pflag.ExitOnError)
|
flags := pflag.NewFlagSet(ctx.subCmd.name, pflag.ExitOnError)
|
||||||
flags.Usage = func() {
|
flags.Usage = func() {
|
||||||
|
|
||||||
@ -58,7 +78,7 @@ func (ctx subCmdCtx) flagSet(withPassthrough bool) *pflag.FlagSet {
|
|||||||
os.Stderr.Sync()
|
os.Stderr.Sync()
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
return flags
|
return flagSet{flags}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
||||||
@ -76,7 +96,11 @@ 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 {
|
||||||
fmt.Fprintf(os.Stderr, " %s\t%s\n", subCmd.name, subCmd.descr)
|
name := subCmd.name
|
||||||
|
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")
|
||||||
@ -92,8 +116,10 @@ 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:]
|
||||||
@ -103,17 +129,17 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
|||||||
printUsageExit(subCmdName)
|
printUsageExit(subCmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
daemonRCPClient := jsonrpc2.NewUnixHTTPClient(
|
daemonRPC := daemon.RPCFromClient(
|
||||||
daemon.HTTPSocketPath(), daemonHTTPRPCPath,
|
jsonrpc2.NewUnixHTTPClient(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,
|
||||||
daemonRCPClient: daemonRCPClient,
|
daemonRPC: daemonRPC,
|
||||||
})
|
})
|
||||||
|
|
||||||
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(subCmdCtx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
|
||||||
versionPath := filepath.Join(envAppDirPath, "share/version")
|
versionPath := filepath.Join(envAppDirPath, "share/version")
|
||||||
|
|
||||||
|
@ -75,6 +75,14 @@ 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
|
||||||
|
108
go/daemon/client.go
Normal file
108
go/daemon/client.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// 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,6 +32,8 @@ 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
|
||||||
@ -85,6 +87,9 @@ 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(
|
||||||
@ -685,6 +690,7 @@ 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
|
||||||
}
|
}
|
||||||
|
32
go/daemon/jsonrpc2/client_gen.tpl
Normal file
32
go/daemon/jsonrpc2/client_gen.tpl
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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,14 +18,19 @@ type methodDispatchFunc func(context.Context, Request) (any, error)
|
|||||||
func newMethodDispatchFunc(
|
func newMethodDispatchFunc(
|
||||||
method reflect.Value,
|
method reflect.Value,
|
||||||
) methodDispatchFunc {
|
) methodDispatchFunc {
|
||||||
paramT := method.Type().In(1)
|
paramTs := make([]reflect.Type, method.Type().NumIn()-1)
|
||||||
return func(ctx context.Context, req Request) (any, error) {
|
for i := range paramTs {
|
||||||
var (
|
paramTs[i] = method.Type().In(i + 1)
|
||||||
ctxV = reflect.ValueOf(ctx)
|
}
|
||||||
paramPtrV = reflect.New(paramT)
|
|
||||||
)
|
|
||||||
|
|
||||||
err := json.Unmarshal(req.Params, paramPtrV.Interface())
|
return func(ctx context.Context, req Request) (any, error) {
|
||||||
|
callVals := make([]reflect.Value, 0, len(paramTs)+1)
|
||||||
|
callVals = append(callVals, reflect.ValueOf(ctx))
|
||||||
|
|
||||||
|
for i, paramT := range paramTs {
|
||||||
|
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,
|
||||||
@ -33,14 +38,17 @@ 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 params into %T: %v", paramT, err,
|
"JSON unmarshaling param %d into %T: %v", i, paramT, err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callVals = append(callVals, paramPtrV.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
callResV = method.Call([]reflect.Value{ctxV, paramPtrV.Elem()})
|
callResV = method.Call(callVals)
|
||||||
resV = callResV[0]
|
resV = callResV[0]
|
||||||
errV = callResV[1]
|
errV = callResV[1]
|
||||||
)
|
)
|
||||||
@ -86,7 +94,7 @@ func NewDispatchHandler(i any) Handler {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if !method.IsExported() ||
|
if !method.IsExported() ||
|
||||||
methodT.NumIn() != 2 ||
|
methodT.NumIn() < 1 ||
|
||||||
methodT.In(0) != ctxT ||
|
methodT.In(0) != ctxT ||
|
||||||
methodT.NumOut() != 2 ||
|
methodT.NumOut() != 2 ||
|
||||||
methodT.Out(1) != errT {
|
methodT.Out(1) != errT {
|
||||||
|
@ -3,6 +3,7 @@ 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"
|
||||||
@ -59,9 +60,14 @@ func NewMLogMiddleware(logger *mlog.Logger) Middleware {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if logger.MaxLevel() >= mlog.LevelDebug.Int() {
|
if logger.MaxLevel() >= mlog.LevelDebug.Int() {
|
||||||
ctx := mctx.Annotate(
|
ctx := ctx
|
||||||
ctx, "rpcRequestParams", string(req.Params),
|
for i := range 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,13 +37,19 @@ 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,
|
||||||
) {
|
) {
|
||||||
paramsB, err := json.Marshal(params)
|
var (
|
||||||
|
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 params as JSON: %w", err)
|
return "", fmt.Errorf("encoding param %d as JSON: %w", i, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -51,7 +57,7 @@ func encodeRequest(
|
|||||||
reqEnvelope = Request{
|
reqEnvelope = Request{
|
||||||
Version: version,
|
Version: version,
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: paramsB,
|
Params: paramsBs,
|
||||||
ID: id,
|
ID: id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -26,14 +26,22 @@ var ErrDivideByZero = Error{
|
|||||||
|
|
||||||
type dividerImpl struct{}
|
type dividerImpl struct{}
|
||||||
|
|
||||||
func (dividerImpl) Divide(ctx context.Context, p DivideParams) (int, error) {
|
func (dividerImpl) Divide2(ctx context.Context, top, bottom int) (int, error) {
|
||||||
if p.Bottom == 0 {
|
if bottom == 0 {
|
||||||
return 0, ErrDivideByZero
|
return 0, ErrDivideByZero
|
||||||
}
|
}
|
||||||
if p.Top%p.Bottom != 0 {
|
if top%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 p.Top / p.Bottom, nil
|
return top / 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) {
|
||||||
@ -41,6 +49,8 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +92,26 @@ 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,49 +11,115 @@ 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 struct {
|
type RPC interface {
|
||||||
|
// 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 &RPC{daemon}
|
return &rpcImpl{daemon}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNetworkRequest contains the arguments to the CreateNetwork RPC method.
|
func (r *rpcImpl) CreateNetwork(
|
||||||
//
|
ctx context.Context,
|
||||||
// All fields are required.
|
name string,
|
||||||
type CreateNetworkRequest struct {
|
domain string,
|
||||||
// Human-readable name of the network.
|
ipNet nebula.IPNet,
|
||||||
Name string
|
hostName nebula.HostName,
|
||||||
|
|
||||||
// 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, req.Name, req.Domain, req.IPNet, req.HostName,
|
ctx, name, domain, ipNet, hostName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinNetwork passes through to the Daemon method of the same name.
|
func (r *rpcImpl) JoinNetwork(
|
||||||
func (r *RPC) JoinNetwork(
|
|
||||||
ctx context.Context, req JoiningBootstrap,
|
ctx context.Context, req JoiningBootstrap,
|
||||||
) (
|
) (
|
||||||
struct{}, error,
|
struct{}, error,
|
||||||
@ -61,17 +127,7 @@ func (r *RPC) JoinNetwork(
|
|||||||
return struct{}{}, r.daemon.JoinNetwork(ctx, req)
|
return struct{}{}, r.daemon.JoinNetwork(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHostsResult wraps the results from the GetHosts RPC method.
|
func (r *rpcImpl) GetHosts(ctx context.Context) (GetHostsResult, error) {
|
||||||
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)
|
||||||
@ -85,19 +141,16 @@ func (r *RPC) GetHosts(
|
|||||||
return GetHostsResult{hosts}, nil
|
return GetHostsResult{hosts}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGarageClientParams passes the call through to the Daemon method of the
|
func (r *rpcImpl) GetGarageClientParams(
|
||||||
// same name.
|
ctx context.Context,
|
||||||
func (r *RPC) GetGarageClientParams(
|
|
||||||
ctx context.Context, req struct{},
|
|
||||||
) (
|
) (
|
||||||
GarageClientParams, error,
|
GarageClientParams, error,
|
||||||
) {
|
) {
|
||||||
return r.daemon.GetGarageClientParams(ctx)
|
return r.daemon.GetGarageClientParams(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNebulaCAPublicCredentials returns the CAPublicCredentials for the network.
|
func (r *rpcImpl) GetNebulaCAPublicCredentials(
|
||||||
func (r *RPC) GetNebulaCAPublicCredentials(
|
ctx context.Context,
|
||||||
ctx context.Context, req struct{},
|
|
||||||
) (
|
) (
|
||||||
nebula.CAPublicCredentials, error,
|
nebula.CAPublicCredentials, error,
|
||||||
) {
|
) {
|
||||||
@ -111,42 +164,20 @@ func (r *RPC) GetNebulaCAPublicCredentials(
|
|||||||
return b.CAPublicCredentials, nil
|
return b.CAPublicCredentials, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveHostRequest contains the arguments to the RemoveHost RPC method.
|
func (r *rpcImpl) RemoveHost(
|
||||||
//
|
ctx context.Context, hostName nebula.HostName,
|
||||||
// All fields are required.
|
) (
|
||||||
type RemoveHostRequest struct {
|
struct{}, error,
|
||||||
HostName nebula.HostName
|
) {
|
||||||
|
return struct{}{}, r.daemon.RemoveHost(ctx, hostName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveHost passes the call through to the Daemon method of the same name.
|
func (r *rpcImpl) CreateHost(
|
||||||
func (r *RPC) RemoveHost(ctx context.Context, req RemoveHostRequest) (struct{}, error) {
|
ctx context.Context, hostName nebula.HostName, opts CreateHostOpts,
|
||||||
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(
|
joiningBootstrap, err := r.daemon.CreateHost(ctx, hostName, opts)
|
||||||
ctx, req.HostName, req.Opts,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CreateHostResult{}, err
|
return CreateHostResult{}, err
|
||||||
}
|
}
|
||||||
@ -154,36 +185,19 @@ func (r *RPC) CreateHost(
|
|||||||
return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil
|
return CreateHostResult{JoiningBootstrap: joiningBootstrap}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNebulaCertificateRequest contains the arguments to the
|
func (r *rpcImpl) CreateNebulaCertificate(
|
||||||
// CreateNebulaCertificate RPC method.
|
ctx context.Context,
|
||||||
//
|
hostName nebula.HostName,
|
||||||
// All fields are required.
|
hostEncryptingPublicKey nebula.EncryptingPublicKey,
|
||||||
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, req.HostName, req.HostEncryptingPublicKey,
|
ctx, hostName, hostEncryptingPublicKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CreateNebulaCertificateResult{}, err
|
return CreateNebulaCertificateResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateNebulaCertificateResult{
|
return CreateNebulaCertificateResult{HostNebulaCertificate: cert}, nil
|
||||||
HostNebulaCertifcate: cert,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,11 @@ 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) {
|
||||||
|
17
nix/gowrap.nix
Normal file
17
nix/gowrap.nix
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
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