Replace admin create-network
with network create
over RPC
This commit is contained in:
parent
f9d033b89f
commit
279c79a9f1
@ -62,7 +62,7 @@ Operator hosts will need at least one of the following to be useful:
|
|||||||
|
|
||||||
* A static public IP, or a dynamic public IP with [dDNS][ddns] set up.
|
* A static public IP, or a dynamic public IP with [dDNS][ddns] set up.
|
||||||
|
|
||||||
* At least 100GB of unused storage which can be reserved for the network. (TODO review storage requirements)
|
* At least 1GB of unused storage which can be reserved for the network.
|
||||||
|
|
||||||
Operators are expected to be familiar with server administration, and to not be
|
Operators are expected to be familiar with server administration, and to not be
|
||||||
afraid of a terminal.
|
afraid of a terminal.
|
||||||
|
@ -27,16 +27,16 @@ The requirements for this host are:
|
|||||||
behind a NAT, and/or allowing traffic on that UDP port in your hosts
|
behind a NAT, and/or allowing traffic on that UDP port in your hosts
|
||||||
firewall.
|
firewall.
|
||||||
|
|
||||||
* At least 300 GB of disk storage space. (TODO double check the storage space requirements)
|
* At least 3 GB of disk storage space.
|
||||||
|
|
||||||
* At least 3 directories should be chosen, each of which will be committing at
|
* At least 3 directories should be chosen, each of which will be committing at
|
||||||
least 100GB. Ideally these directories should be on different physical
|
least 1GB. Ideally these directories should be on different physical disks,
|
||||||
disks, but if that's not possible it's ok. See the Next Steps section.
|
but if that's not possible it's ok. See the Next Steps section.
|
||||||
|
|
||||||
* None of the resources being used for this network (the UDP port or storage
|
* None of the resources being used for this network (the UDP port or storage
|
||||||
locations) should be being used by other networks.
|
locations) should be being used by other networks.
|
||||||
|
|
||||||
## Step 1: Edit the `daemon.yml` File
|
## Step 1: Configure the isle Daemon
|
||||||
|
|
||||||
Open `/etc/isle/daemon.yml` in a text editor and perform the following changes:
|
Open `/etc/isle/daemon.yml` in a text editor and perform the following changes:
|
||||||
|
|
||||||
@ -48,6 +48,12 @@ Open `/etc/isle/daemon.yml` in a text editor and perform the following changes:
|
|||||||
|
|
||||||
Save and close the file.
|
Save and close the file.
|
||||||
|
|
||||||
|
Run the following to restart the daemon with the new configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo systemctl restart isle
|
||||||
|
```
|
||||||
|
|
||||||
## Step 2: Choose Parameters
|
## Step 2: Choose Parameters
|
||||||
|
|
||||||
There are some key parameters which must be chosen when creating a new network.
|
There are some key parameters which must be chosen when creating a new network.
|
||||||
@ -92,12 +98,10 @@ if you care to use a different method.
|
|||||||
|
|
||||||
## Step 4: Create the `admin.json` File
|
## Step 4: Create the `admin.json` File
|
||||||
|
|
||||||
To create the `admin.json` file, which effectively creates the network itself,
|
To create the network, and the `admin.json` file in the process, run:
|
||||||
you can run:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo isle admin create-network \
|
sudo isle network create \
|
||||||
--config-path /etc/isle/daemon.yml \
|
|
||||||
--name <name> \
|
--name <name> \
|
||||||
--ip-net <ip/subnet-prefix> \
|
--ip-net <ip/subnet-prefix> \
|
||||||
--domain <domain> \
|
--domain <domain> \
|
||||||
@ -111,34 +115,19 @@ A couple of notes here:
|
|||||||
* The `--ip-net` parameter is formed from both the subnet and the IP you chose
|
* The `--ip-net` parameter is formed from both the subnet and the IP you chose
|
||||||
within it. So if your subnet is `10.10.0.0/16`, and your chosen IP in that
|
within it. So if your subnet is `10.10.0.0/16`, and your chosen IP in that
|
||||||
subnet is `10.10.4.20`, then your `--ip-net` parameter will be
|
subnet is `10.10.4.20`, then your `--ip-net` parameter will be
|
||||||
`10.10.4.20/16`. (TODO expand a bit on what IP is being chosen).
|
`10.10.4.20/16`.
|
||||||
|
|
||||||
* Only one gpg recipient is specified. If you intend on including other users as
|
* Only one gpg recipient is specified. If you intend on including other users as
|
||||||
network administrators you can add them to the recipients list at this step,
|
network administrators you can add them to the recipients list at this step,
|
||||||
so they will be able to use the `admin.json` file as well. You can also
|
so they will be able to use the `admin.json` file as well. You can also
|
||||||
manually add them as recipients later.
|
manually add them as recipients later.
|
||||||
|
|
||||||
You will see a lot of output, as `create-network` starts up many child processes
|
The `isle network create` command may take up to a minute to complete. Once
|
||||||
in order to set the network up. It should exit successfully on its own after a
|
completed you should have an `admin.json.gpg` file in your current directory.
|
||||||
few seconds.
|
|
||||||
|
|
||||||
At this point you should have an `admin.json.gpg` file in your current directory.
|
At this point your host, and your network, are ready to go! To add other hosts
|
||||||
|
to the network you can reference the [Adding a Host to the Network][add-host]
|
||||||
## Step 5: Run the Daemon
|
document.
|
||||||
|
|
||||||
The isle daemon can be run now, using the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo isle daemon -c /path/to/daemon.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**NOTE** that you _must_ use the same `daemon.yml` file used when creating the
|
|
||||||
network for the daemon itself.
|
|
||||||
|
|
||||||
At this point your host, and your network, are ready to go! You can reference
|
|
||||||
the [Getting Started](../user/getting-started.md) document to set up your
|
|
||||||
host's daemon process in a more permanent way. (TODO once creating a network is
|
|
||||||
done via RPC then this will be out-of-date. Better to direct them to the
|
|
||||||
operator docs, or maybe adding a new host).
|
|
||||||
|
|
||||||
|
[add-host]: ./adding-a-host-to-the-network.md
|
||||||
[ddns]: https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/
|
[ddns]: https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Contributing Storage
|
# Contributing Storage
|
||||||
|
|
||||||
If your host machine can be reasonably sure of being online most, if not all, of
|
If your host machine can be reasonably sure of being online most, if not all, of
|
||||||
the time, and has 100GB or more of unused drive space you'd like to contribute
|
the time, and has 1GB or more of unused drive space you'd like to contribute to
|
||||||
to the network, then this document is for you.
|
the network, then this document is for you.
|
||||||
|
|
||||||
## Edit `daemon.yml`
|
## Edit `daemon.yml`
|
||||||
|
|
||||||
|
@ -7,14 +7,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"isle/admin"
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
|
||||||
"isle/garage"
|
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func randStr(l int) string {
|
func randStr(l int) string {
|
||||||
@ -46,192 +41,6 @@ func readAdmin(path string) (admin.Admin, error) {
|
|||||||
return admin.FromReader(f)
|
return admin.FromReader(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmdAdminCreateNetwork = subCmd{
|
|
||||||
name: "create-network",
|
|
||||||
descr: "Creates a new isle network, outputting the resulting admin.json to stdout",
|
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
|
||||||
|
|
||||||
flags := subCmdCtx.flagSet(false)
|
|
||||||
|
|
||||||
daemonConfigPath := flags.StringP(
|
|
||||||
"config-path", "c", "",
|
|
||||||
"Optional path to a daemon.yml file to load configuration from.",
|
|
||||||
)
|
|
||||||
|
|
||||||
dumpConfig := flags.Bool(
|
|
||||||
"dump-config", false,
|
|
||||||
"Write the default configuration file to stdout and exit.",
|
|
||||||
)
|
|
||||||
|
|
||||||
name := flags.StringP(
|
|
||||||
"name", "n", "",
|
|
||||||
"Human-readable name to identify the network as.",
|
|
||||||
)
|
|
||||||
|
|
||||||
domain := flags.StringP(
|
|
||||||
"domain", "d", "",
|
|
||||||
"Domain name that should be used as the root domain in the network.",
|
|
||||||
)
|
|
||||||
|
|
||||||
ipNetStr := flags.StringP(
|
|
||||||
"ip-net", "i", "",
|
|
||||||
`IP+prefix (e.g. "10.10.0.1/16") which denotes the IP of this host, which will be the first host in the network, and the range of IPs which other hosts in the network can be assigned`,
|
|
||||||
)
|
|
||||||
|
|
||||||
hostName := flags.StringP(
|
|
||||||
"hostname", "h", "",
|
|
||||||
"Name of this host, which will be the first host in the network",
|
|
||||||
)
|
|
||||||
|
|
||||||
logLevelStr := flags.StringP(
|
|
||||||
"log-level", "l", "info",
|
|
||||||
`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 {
|
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := subCmdCtx.ctx
|
|
||||||
|
|
||||||
if *dumpConfig {
|
|
||||||
return daemon.CopyDefaultConfig(os.Stdout, envAppDirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *name == "" || *domain == "" || *ipNetStr == "" || *hostName == "" {
|
|
||||||
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
|
||||||
}
|
|
||||||
|
|
||||||
logLevel := mlog.LevelFromString(*logLevelStr)
|
|
||||||
if logLevel == nil {
|
|
||||||
return fmt.Errorf("couldn't parse log level %q", *logLevelStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := subCmdCtx.logger.WithMaxLevel(logLevel.Int())
|
|
||||||
|
|
||||||
*domain = strings.TrimRight(strings.TrimLeft(*domain, "."), ".")
|
|
||||||
|
|
||||||
ip, subnet, err := net.ParseCIDR(*ipNetStr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing %q as a CIDR: %w", *ipNetStr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateHostName(*hostName); err != nil {
|
|
||||||
return fmt.Errorf("invalid hostname %q: %w", *hostName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeDirCleanup, err := setupAndLockRuntimeDir(ctx, logger)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setting up runtime directory: %w", err)
|
|
||||||
}
|
|
||||||
defer runtimeDirCleanup()
|
|
||||||
|
|
||||||
daemonConfig, err := daemon.LoadConfig(envAppDirPath, *daemonConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loading daemon config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(daemonConfig.Storage.Allocations) < 3 {
|
|
||||||
return fmt.Errorf("daemon config with at least 3 allocations was not provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
nebulaCACreds, err := nebula.NewCACredentials(*domain, subnet)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating nebula CA cert: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
adminCreationParams := admin.CreationParams{
|
|
||||||
ID: randStr(32),
|
|
||||||
Name: *name,
|
|
||||||
Domain: *domain,
|
|
||||||
}
|
|
||||||
|
|
||||||
garageBootstrap := bootstrap.Garage{
|
|
||||||
RPCSecret: randStr(32),
|
|
||||||
AdminToken: randStr(32),
|
|
||||||
}
|
|
||||||
|
|
||||||
hostBootstrap, err := bootstrap.New(
|
|
||||||
nebulaCACreds,
|
|
||||||
adminCreationParams,
|
|
||||||
garageBootstrap,
|
|
||||||
*hostName,
|
|
||||||
ip,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("initializing bootstrap data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hostBootstrap, err = coalesceDaemonConfigAndBootstrap(hostBootstrap, daemonConfig); err != nil {
|
|
||||||
return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
children, err := daemon.NewChildren(
|
|
||||||
ctx,
|
|
||||||
logger.WithNamespace("daemon"),
|
|
||||||
daemonConfig,
|
|
||||||
hostBootstrap,
|
|
||||||
envBinDirPath,
|
|
||||||
&daemon.Opts{
|
|
||||||
// NOTE both stdout and stderr are sent to stderr, so that the
|
|
||||||
// user can pipe the resulting admin.json to stdout.
|
|
||||||
Stdout: os.Stderr,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("initializing children: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
logger.Info(ctx, "Shutting down child processes")
|
|
||||||
if err := children.Shutdown(); err != nil {
|
|
||||||
logger.Error(ctx, "Failed to shut down children cleanly, there may be zombie children leftover", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
logger.Info(ctx, "Applying garage layout")
|
|
||||||
if err := daemon.GarageApplyLayout(
|
|
||||||
ctx, logger, daemonConfig, hostBootstrap,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("applying garage layout: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info(ctx, "initializing garage shared global bucket")
|
|
||||||
garageGlobalBucketCreds, err := garageInitializeGlobalBucket(
|
|
||||||
ctx, logger, hostBootstrap, daemonConfig,
|
|
||||||
)
|
|
||||||
|
|
||||||
if cErr := (garage.AdminClientError{}); errors.As(err, &cErr) && cErr.StatusCode == 409 {
|
|
||||||
return fmt.Errorf("shared global bucket has already been created, are the storage allocations from a previously initialized isle being used?")
|
|
||||||
|
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("initializing garage shared global bucket: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hostBootstrap.Garage.GlobalBucketS3APICredentials = garageGlobalBucketCreds
|
|
||||||
|
|
||||||
// rewrite the bootstrap now that the global bucket creds have been
|
|
||||||
// added to it.
|
|
||||||
if err := writeBootstrapToStateDir(hostBootstrap); err != nil {
|
|
||||||
return fmt.Errorf("writing bootstrap file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info(ctx, "Network initialized successfully, writing admin.json to stdout")
|
|
||||||
|
|
||||||
adm := admin.Admin{
|
|
||||||
CreationParams: adminCreationParams,
|
|
||||||
}
|
|
||||||
adm.Nebula.CACredentials = nebulaCACreds
|
|
||||||
adm.Garage.RPCSecret = hostBootstrap.Garage.RPCSecret
|
|
||||||
adm.Garage.GlobalBucketS3APICredentials = hostBootstrap.Garage.GlobalBucketS3APICredentials
|
|
||||||
|
|
||||||
if err := adm.WriteTo(os.Stdout); err != nil {
|
|
||||||
return fmt.Errorf("writing admin.json to stdout")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var subCmdAdminCreateBootstrap = subCmd{
|
var subCmdAdminCreateBootstrap = subCmd{
|
||||||
name: "create-bootstrap",
|
name: "create-bootstrap",
|
||||||
descr: "Creates a new bootstrap.json file for a particular host and writes it to stdout",
|
descr: "Creates a new bootstrap.json file for a particular host and writes it to stdout",
|
||||||
@ -392,7 +201,6 @@ var subCmdAdmin = subCmd{
|
|||||||
descr: "Sub-commands which only admins can run",
|
descr: "Sub-commands which only admins can run",
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
subCmdAdminCreateNetwork,
|
|
||||||
subCmdAdminCreateBootstrap,
|
subCmdAdminCreateBootstrap,
|
||||||
subCmdAdminCreateNebulaCert,
|
subCmdAdminCreateNebulaCert,
|
||||||
)
|
)
|
||||||
|
@ -5,8 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadHostBootstrap() (bootstrap.Bootstrap, error) {
|
func loadHostBootstrap() (bootstrap.Bootstrap, error) {
|
||||||
@ -26,22 +24,3 @@ func loadHostBootstrap() (bootstrap.Bootstrap, error) {
|
|||||||
|
|
||||||
return hostBootstrap, nil
|
return hostBootstrap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeBootstrapToStateDir(hostBootstrap bootstrap.Bootstrap) error {
|
|
||||||
|
|
||||||
path := bootstrap.StateDirPath(daemonEnvVars.StateDirPath)
|
|
||||||
dirPath := filepath.Dir(path)
|
|
||||||
|
|
||||||
if err := os.MkdirAll(dirPath, 0700); err != nil {
|
|
||||||
return fmt.Errorf("creating directory %q: %w", dirPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating file %q: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return hostBootstrap.WriteTo(f)
|
|
||||||
}
|
|
||||||
|
@ -4,10 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/bootstrap"
|
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/daemon/jsonrpc2"
|
"isle/daemon/jsonrpc2"
|
||||||
"isle/garage/garagesrv"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -15,50 +13,6 @@ import (
|
|||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func coalesceDaemonConfigAndBootstrap(
|
|
||||||
hostBootstrap bootstrap.Bootstrap, daemonConfig daemon.Config,
|
|
||||||
) (
|
|
||||||
bootstrap.Bootstrap, error,
|
|
||||||
) {
|
|
||||||
host := bootstrap.Host{
|
|
||||||
HostAssigned: hostBootstrap.HostAssigned,
|
|
||||||
HostConfigured: bootstrap.HostConfigured{
|
|
||||||
Nebula: bootstrap.NebulaHost{
|
|
||||||
PublicAddr: daemonConfig.VPN.PublicAddr,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if allocs := daemonConfig.Storage.Allocations; len(allocs) > 0 {
|
|
||||||
|
|
||||||
for i, alloc := range allocs {
|
|
||||||
|
|
||||||
id, rpcPort, err := garagesrv.InitAlloc(alloc.MetaPath, alloc.RPCPort)
|
|
||||||
if err != nil {
|
|
||||||
return bootstrap.Bootstrap{}, fmt.Errorf(
|
|
||||||
"initializing alloc at %q: %w", alloc.MetaPath, err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
host.Garage.Instances = append(host.Garage.Instances, bootstrap.GarageHostInstance{
|
|
||||||
ID: id,
|
|
||||||
RPCPort: rpcPort,
|
|
||||||
S3APIPort: alloc.S3APIPort,
|
|
||||||
})
|
|
||||||
|
|
||||||
allocs[i].RPCPort = rpcPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostBootstrap.Hosts[host.Name] = host
|
|
||||||
|
|
||||||
if err := writeBootstrapToStateDir(hostBootstrap); err != nil {
|
|
||||||
return bootstrap.Bootstrap{}, fmt.Errorf("writing bootstrap file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostBootstrap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const daemonHTTPRPCPath = "/rpc/v0.json"
|
const daemonHTTPRPCPath = "/rpc/v0.json"
|
||||||
|
|
||||||
func newHTTPServer(
|
func newHTTPServer(
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"isle/bootstrap"
|
|
||||||
"isle/daemon"
|
|
||||||
"isle/garage"
|
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func garageInitializeGlobalBucket(
|
|
||||||
ctx context.Context,
|
|
||||||
logger *mlog.Logger,
|
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
|
||||||
daemonConfig daemon.Config,
|
|
||||||
) (
|
|
||||||
garage.S3APICredentials, error,
|
|
||||||
) {
|
|
||||||
adminClient := daemon.NewGarageAdminClient(
|
|
||||||
logger, daemonConfig, hostBootstrap,
|
|
||||||
)
|
|
||||||
|
|
||||||
creds, err := adminClient.CreateS3APICredentials(
|
|
||||||
ctx, garage.GlobalBucketS3APICredentialsName,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return creds, fmt.Errorf("creating global bucket credentials: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bucketID, err := adminClient.CreateBucket(ctx, garage.GlobalBucket)
|
|
||||||
if err != nil {
|
|
||||||
return creds, fmt.Errorf("creating global bucket: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := adminClient.GrantBucketPermissions(
|
|
||||||
ctx,
|
|
||||||
bucketID,
|
|
||||||
creds.ID,
|
|
||||||
garage.BucketPermissionRead,
|
|
||||||
garage.BucketPermissionWrite,
|
|
||||||
); err != nil {
|
|
||||||
return creds, fmt.Errorf(
|
|
||||||
"granting permissions to shared global bucket key: %w", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return creds, nil
|
|
||||||
}
|
|
@ -3,9 +3,70 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
|
"isle/daemon"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var subCmdNetworkCreate = subCmd{
|
||||||
|
name: "create",
|
||||||
|
descr: "Create's a new network, with this host being the first host in that network. The resulting admin.json is output to stdout.",
|
||||||
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
|
var (
|
||||||
|
ctx = subCmdCtx.ctx
|
||||||
|
flags = subCmdCtx.flagSet(false)
|
||||||
|
req daemon.CreateNetworkRequest
|
||||||
|
)
|
||||||
|
|
||||||
|
flags.StringVarP(
|
||||||
|
&req.Name, "name", "n", "",
|
||||||
|
"Human-readable name to identify the network as.",
|
||||||
|
)
|
||||||
|
|
||||||
|
flags.StringVarP(
|
||||||
|
&req.Domain, "domain", "d", "",
|
||||||
|
"Domain name that should be used as the root domain in the network.",
|
||||||
|
)
|
||||||
|
|
||||||
|
flags.StringVarP(
|
||||||
|
&req.IPNet, "ip-net", "i", "",
|
||||||
|
`IP+prefix (e.g. "10.10.0.1/16") which denotes the IP of this`+
|
||||||
|
` host, which will be the first host in the network, and the`+
|
||||||
|
` range of IPs which other hosts in the network can be`+
|
||||||
|
` assigned`,
|
||||||
|
)
|
||||||
|
|
||||||
|
flags.StringVarP(
|
||||||
|
&req.HostName, "hostname", "h", "",
|
||||||
|
"Name of this host, which will be the first host in the network",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
||||||
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == "" ||
|
||||||
|
req.Domain == "" ||
|
||||||
|
req.IPNet == "" ||
|
||||||
|
req.HostName == "" {
|
||||||
|
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var adm admin.Admin
|
||||||
|
err := subCmdCtx.daemonRCPClient.Call(ctx, &adm, "CreateNetwork", req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating network: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := adm.WriteTo(os.Stdout); err != nil {
|
||||||
|
return fmt.Errorf("writing admin.json to stdout")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
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",
|
||||||
@ -45,6 +106,7 @@ var subCmdNetwork = subCmd{
|
|||||||
descr: "Sub-commands related to network membership",
|
descr: "Sub-commands related to network membership",
|
||||||
do: func(subCmdCtx subCmdCtx) error {
|
do: func(subCmdCtx subCmdCtx) error {
|
||||||
return subCmdCtx.doSubCmd(
|
return subCmdCtx.doSubCmd(
|
||||||
|
subCmdNetworkCreate,
|
||||||
subCmdNetworkJoin,
|
subCmdNetworkJoin,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -349,24 +349,37 @@ func (d *daemon) postInit(ctx context.Context) bool {
|
|||||||
// TODO this is pretty hacky, but there doesn't seem to be a better way to
|
// TODO this is pretty hacky, but there doesn't seem to be a better way to
|
||||||
// manage it at the moment.
|
// manage it at the moment.
|
||||||
if d.currBootstrap.Garage.GlobalBucketS3APICredentials == (garage.S3APICredentials{}) {
|
if d.currBootstrap.Garage.GlobalBucketS3APICredentials == (garage.S3APICredentials{}) {
|
||||||
var garageGlobalBucketCreds garage.S3APICredentials
|
currBootstrap := d.currBootstrap
|
||||||
if !until(
|
if !until(
|
||||||
ctx,
|
ctx,
|
||||||
d.logger,
|
d.logger,
|
||||||
"Initializing garage shared global bucket",
|
"Initializing garage shared global bucket",
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
var err error
|
garageGlobalBucketCreds, err := garageInitializeGlobalBucket(
|
||||||
garageGlobalBucketCreds, err = garageInitializeGlobalBucket(
|
|
||||||
ctx, d.logger, d.daemonConfig, d.currBootstrap,
|
ctx, d.logger, d.daemonConfig, d.currBootstrap,
|
||||||
)
|
)
|
||||||
return err
|
if err != nil {
|
||||||
|
return fmt.Errorf("initializing global bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currBootstrap.Garage.GlobalBucketS3APICredentials = garageGlobalBucketCreds
|
||||||
|
|
||||||
|
d.logger.Info(ctx, "Writing bootstrap to state directory")
|
||||||
|
err = writeBootstrapToStateDir(
|
||||||
|
d.opts.EnvVars.StateDirPath, currBootstrap,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writing bootstrap to state dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
d.l.Lock()
|
d.l.Lock()
|
||||||
d.currBootstrap.Garage.GlobalBucketS3APICredentials = garageGlobalBucketCreds
|
d.currBootstrap = currBootstrap
|
||||||
d.l.Unlock()
|
d.l.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,17 +4,13 @@ import (
|
|||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"isle/admin"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 struct {
|
||||||
@ -26,6 +22,35 @@ func NewRPC(daemon Daemon) *RPC {
|
|||||||
return &RPC{daemon}
|
return &RPC{daemon}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateNetworkRequest contains the arguments to the CreateNetwork RPC method.
|
||||||
|
//
|
||||||
|
// All fields are required.
|
||||||
|
type CreateNetworkRequest struct {
|
||||||
|
// Human-readable name of the network.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Primary domain name that network services are served under.
|
||||||
|
Domain string
|
||||||
|
|
||||||
|
// An IP + subnet mask which represents both the IP of this first host in
|
||||||
|
// the network, as well as the overall range of possible IPs in the network.
|
||||||
|
IPNet string
|
||||||
|
|
||||||
|
// The name of this first host in the network.
|
||||||
|
HostName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNetwork passes through to the Daemon method of the same name.
|
||||||
|
func (r *RPC) CreateNetwork(
|
||||||
|
ctx context.Context, req CreateNetworkRequest,
|
||||||
|
) (
|
||||||
|
admin.Admin, error,
|
||||||
|
) {
|
||||||
|
return r.daemon.CreateNetwork(
|
||||||
|
ctx, req.Name, req.Domain, req.IPNet, req.HostName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// JoinNetwork passes through to the Daemon method of the same name.
|
// JoinNetwork passes through to the Daemon method of the same name.
|
||||||
func (r *RPC) JoinNetwork(
|
func (r *RPC) JoinNetwork(
|
||||||
ctx context.Context, req bootstrap.Bootstrap,
|
ctx context.Context, req bootstrap.Bootstrap,
|
||||||
@ -35,6 +60,11 @@ 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.
|
||||||
|
type GetHostsResult struct {
|
||||||
|
Hosts []bootstrap.Host
|
||||||
|
}
|
||||||
|
|
||||||
// GetHosts returns all hosts known to the network, sorted by their name.
|
// GetHosts returns all hosts known to the network, sorted by their name.
|
||||||
func (r *RPC) GetHosts(
|
func (r *RPC) GetHosts(
|
||||||
ctx context.Context, req struct{},
|
ctx context.Context, req struct{},
|
||||||
|
@ -49,22 +49,21 @@ if [ ! -d "$XDG_RUNTIME_DIR/isle" ]; then
|
|||||||
capacity: 1
|
capacity: 1
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
isle daemon -l debug --config-path daemon.yml >daemon.log 2>&1 &
|
||||||
|
pid="$!"
|
||||||
|
$SHELL "$UTILS/register-cleanup.sh" "$pid" "1-data-1-empty-node-network/primus"
|
||||||
|
|
||||||
|
echo "Waiting for primus daemon (process $pid) to start"
|
||||||
|
while ! [ -e "$ISLE_DAEMON_HTTP_SOCKET_PATH" ]; do sleep 1; done
|
||||||
|
|
||||||
echo "Creating 1-data-1-empty network"
|
echo "Creating 1-data-1-empty network"
|
||||||
isle admin create-network \
|
isle network create \
|
||||||
--config-path daemon.yml \
|
|
||||||
--domain shared.test \
|
--domain shared.test \
|
||||||
--hostname primus \
|
--hostname primus \
|
||||||
--ip-net "$current_ip/24" \
|
--ip-net "$current_ip/24" \
|
||||||
--name "testing" \
|
--name "testing" \
|
||||||
> admin.json
|
> admin.json
|
||||||
|
|
||||||
isle daemon -l debug --config-path daemon.yml >daemon.log 2>&1 &
|
|
||||||
pid="$!"
|
|
||||||
$SHELL "$UTILS/register-cleanup.sh" "$pid" "1-data-1-empty-node-network/primus"
|
|
||||||
|
|
||||||
echo "Waiting for primus daemon (process $pid) to initialize"
|
|
||||||
while ! isle hosts list >/dev/null; do sleep 1; done
|
|
||||||
|
|
||||||
echo "Creating secondus bootstrap"
|
echo "Creating secondus bootstrap"
|
||||||
isle admin create-bootstrap \
|
isle admin create-bootstrap \
|
||||||
--admin-path admin.json \
|
--admin-path admin.json \
|
||||||
|
Loading…
Reference in New Issue
Block a user