Compare commits
No commits in common. "b26f4bdd6ac0c0b3b2ca713562b0c72c3648499c" and "9288d8cf4880630f3428e93552b17b7f3fa9e644" have entirely different histories.
b26f4bdd6a
...
9288d8cf48
@ -66,18 +66,11 @@ storage:
|
||||
#
|
||||
# The ports are all required and must all be unique within and across
|
||||
# allocations.
|
||||
#
|
||||
# THe ports are all _optional_, and will be automatically assigned if they are
|
||||
# not specified. If ports any ports are specified then all should be
|
||||
# specified, and each should be unique across all allocations.
|
||||
#
|
||||
# Once assigned (either implicitly or explicitly) the rpc_port of an
|
||||
# allocation should not be changed.
|
||||
allocations:
|
||||
|
||||
#- data_path: /foo/bar/data
|
||||
# meta_path: /foo/bar/meta
|
||||
# capacity: 1200
|
||||
# #s3_api_port: 3900
|
||||
# #rpc_port: 3901
|
||||
# #admin_port: 3902
|
||||
# api_port: 3900
|
||||
# rpc_port: 3901
|
||||
# admin_port: 3902
|
||||
|
16
default.nix
16
default.nix
@ -57,11 +57,9 @@ in rec {
|
||||
|
||||
entrypoint = pkgs.callPackage ./entrypoint {};
|
||||
|
||||
dnsmasq = (pkgs.callPackage ./nix/dnsmasq.nix {
|
||||
dnsmasq = (pkgs.callPackage ./dnsmasq {
|
||||
glibcStatic = pkgs.glibc.static;
|
||||
});
|
||||
|
||||
nebula = pkgs.callPackage ./nix/nebula.nix {};
|
||||
}).env;
|
||||
|
||||
garage = (pkgs.callPackage ./nix/garage.nix {}).env;
|
||||
|
||||
@ -71,10 +69,18 @@ in rec {
|
||||
name = "cryptic-net-AppDir";
|
||||
paths = [
|
||||
|
||||
pkgs.pkgsStatic.bash
|
||||
pkgs.pkgsStatic.coreutils
|
||||
pkgs.pkgsStatic.gnutar
|
||||
pkgs.pkgsStatic.gzip
|
||||
|
||||
# custom packages from ./pkgs.nix
|
||||
pkgs.yq-go
|
||||
pkgs.nebula
|
||||
|
||||
./AppDir
|
||||
version
|
||||
dnsmasq
|
||||
nebula
|
||||
garage
|
||||
entrypoint
|
||||
|
||||
|
36
dnsmasq/bin/dnsmasq-entrypoint
Normal file
36
dnsmasq/bin/dnsmasq-entrypoint
Normal file
@ -0,0 +1,36 @@
|
||||
# TODO implement this in go
|
||||
|
||||
set -e -o pipefail
|
||||
cd "$APPDIR"
|
||||
|
||||
conf_path="$_RUNTIME_DIR_PATH"/dnsmasq.conf
|
||||
|
||||
cat etc/dnsmasq/base.conf > "$conf_path"
|
||||
|
||||
tmp="$(mktemp -d -t cryptic-net-dnsmasq-entrypoint-XXX)"
|
||||
|
||||
( trap "rm -rf '$tmp'" EXIT
|
||||
|
||||
tar xzf "$_BOOTSTRAP_PATH" -C "$tmp" ./hosts
|
||||
|
||||
thisHostName=$(tar xzf "$_BOOTSTRAP_PATH" --to-stdout ./hostname)
|
||||
thisHostIP=$(cat "$tmp"/hosts/"$thisHostName".yml | yq '.nebula.ip')
|
||||
|
||||
domain=$(tar xzf "$_BOOTSTRAP_PATH" --to-stdout ./admin/creation-params.yml | yq '.domain')
|
||||
|
||||
echo "listen-address=$thisHostIP" >> "$conf_path"
|
||||
|
||||
ls -1 "$tmp"/hosts | while read hostYml; do
|
||||
|
||||
hostName=$(echo "$hostYml" | cut -d. -f1)
|
||||
hostIP=$(cat "$tmp"/hosts/"$hostYml" | yq '.nebula.ip')
|
||||
echo "address=/${hostName}.hosts.$domain/$hostIP" >> "$conf_path"
|
||||
|
||||
done
|
||||
)
|
||||
|
||||
cat "$_DAEMON_YML_PATH" | \
|
||||
yq '.dns.resolvers | .[] | "server=" + .' \
|
||||
>> "$conf_path"
|
||||
|
||||
exec bin/dnsmasq -d -C "$conf_path"
|
39
dnsmasq/default.nix
Normal file
39
dnsmasq/default.nix
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
|
||||
stdenv,
|
||||
buildEnv,
|
||||
glibcStatic,
|
||||
rebase,
|
||||
|
||||
}: rec {
|
||||
|
||||
dnsmasq = stdenv.mkDerivation rec {
|
||||
pname = "dnsmasq";
|
||||
version = "2.85";
|
||||
|
||||
src = builtins.fetchurl {
|
||||
url = "https://www.thekelleys.org.uk/dnsmasq/${pname}-${version}.tar.xz";
|
||||
sha256 = "sha256-rZjTgD32h+W5OAgPPSXGKP5ByHh1LQP7xhmXh/7jEvo=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ glibcStatic ];
|
||||
|
||||
makeFlags = [
|
||||
"LDFLAGS=-static"
|
||||
"DESTDIR="
|
||||
"BINDIR=$(out)/bin"
|
||||
"MANDIR=$(out)/man"
|
||||
"LOCALEDIR=$(out)/share/locale"
|
||||
];
|
||||
};
|
||||
|
||||
env = buildEnv {
|
||||
name = "cryptic-net-dnsmasq";
|
||||
paths = [
|
||||
(rebase "cryptic-net-dnsmasq-bin" ./bin "bin")
|
||||
(rebase "cryptic-net-dnsmasq-etc" ./etc "etc/dnsmasq")
|
||||
dnsmasq
|
||||
];
|
||||
};
|
||||
|
||||
}
|
41
dnsmasq/etc/base.conf
Normal file
41
dnsmasq/etc/base.conf
Normal file
@ -0,0 +1,41 @@
|
||||
# Configuration file for dnsmasq.
|
||||
#
|
||||
# Format is one option per line, legal options are the same
|
||||
# as the long options legal on the command line. See
|
||||
# "/usr/sbin/dnsmasq --help" or "man 8 dnsmasq" for details.
|
||||
|
||||
# Listen on this specific port instead of the standard DNS port
|
||||
# (53). Setting this to zero completely disables DNS function,
|
||||
# leaving only DHCP and/or TFTP.
|
||||
port=53
|
||||
|
||||
# If you don't want dnsmasq to read /etc/resolv.conf or any other
|
||||
# file, getting its servers from this file instead (see below), then
|
||||
# uncomment this.
|
||||
no-resolv
|
||||
|
||||
# On systems which support it, dnsmasq binds the wildcard address,
|
||||
# even when it is listening on only some interfaces. It then discards
|
||||
# requests that it shouldn't reply to. This has the advantage of
|
||||
# working even when interfaces come and go and change address. If you
|
||||
# want dnsmasq to really bind only the interfaces it is listening on,
|
||||
# uncomment this option. About the only time you may need this is when
|
||||
# running another nameserver on the same machine.
|
||||
bind-interfaces
|
||||
|
||||
# If you don't want dnsmasq to read /etc/hosts, uncomment the
|
||||
# following line.
|
||||
no-hosts
|
||||
|
||||
# Unset user and group so that dnsmasq doesn't drop privileges to another user.
|
||||
# If this isn't done then dnsmasq fails to start up, since it fails to access
|
||||
# /etc/passwd correctly, probably due to nix.
|
||||
user=
|
||||
group=
|
||||
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
#
|
||||
# Everything below is generated dynamically based on runtime configuration
|
||||
#
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
@ -7,7 +7,7 @@
|
||||
pname = "cryptic-net-entrypoint";
|
||||
version = "unstable";
|
||||
src = ./src;
|
||||
vendorSha256 = "sha256-1mHD0tmITlGjeo6F+Dvd2TdEPzxWtndy/J+uGHWKen4=";
|
||||
vendorSha256 = "sha256-URmrK9Sd/5yhXrWxXZq05TS7aY7IWptQFMKfXKJY7Hc=";
|
||||
subPackages = [
|
||||
"cmd/entrypoint"
|
||||
];
|
||||
|
@ -25,18 +25,6 @@ const (
|
||||
hostNamePath = "hostname"
|
||||
)
|
||||
|
||||
// DataDirPath returns the path within the user's data directory where the
|
||||
// bootstrap file is stored.
|
||||
func DataDirPath(dataDirPath string) string {
|
||||
return filepath.Join(dataDirPath, "bootstrap.tgz")
|
||||
}
|
||||
|
||||
// AppDirPath returns the path within the AppDir where an embedded bootstrap
|
||||
// file might be found.
|
||||
func AppDirPath(appDirPath string) string {
|
||||
return filepath.Join(appDirPath, "share/bootstrap.tgz")
|
||||
}
|
||||
|
||||
// Bootstrap is used for accessing all information contained within a
|
||||
// bootstrap.tgz file.
|
||||
type Bootstrap struct {
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"cryptic-net/admin"
|
||||
"cryptic-net/bootstrap"
|
||||
"cryptic-net/daemon"
|
||||
"cryptic-net/garage"
|
||||
"cryptic-net/nebula"
|
||||
"crypto/rand"
|
||||
@ -54,7 +53,7 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
|
||||
flags := subCmdCtx.flagSet(false)
|
||||
|
||||
daemonConfigPath := flags.StringP(
|
||||
daemonYmlPath := flags.StringP(
|
||||
"config-path", "c", "",
|
||||
"Optional path to a daemon.yml file to load configuration from.",
|
||||
)
|
||||
@ -83,8 +82,10 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
return fmt.Errorf("parsing flags: %w", err)
|
||||
}
|
||||
|
||||
env := subCmdCtx.env
|
||||
|
||||
if *dumpConfig {
|
||||
return daemon.CopyDefaultConfig(os.Stdout, envAppDirPath)
|
||||
return writeBuiltinDaemonYml(env, os.Stdout)
|
||||
}
|
||||
|
||||
if *domain == "" || *subnetStr == "" || *hostName == "" {
|
||||
@ -102,19 +103,36 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
return fmt.Errorf("invalid hostname %q: %w", *hostName, err)
|
||||
}
|
||||
|
||||
runtimeDirCleanup, err := setupAndLockRuntimeDir()
|
||||
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)
|
||||
adminCreationParams := admin.CreationParams{
|
||||
ID: randStr(32),
|
||||
Domain: *domain,
|
||||
}
|
||||
|
||||
if len(daemonConfig.Storage.Allocations) < 3 {
|
||||
return fmt.Errorf("daemon config with at least 3 allocations was not provided")
|
||||
{
|
||||
runtimeDirPath := env.RuntimeDirPath
|
||||
|
||||
fmt.Fprintf(os.Stderr, "will use runtime directory %q for temporary state\n", runtimeDirPath)
|
||||
|
||||
if err := os.MkdirAll(runtimeDirPath, 0700); err != nil {
|
||||
return fmt.Errorf("creating directory %q: %w", runtimeDirPath, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
fmt.Fprintf(os.Stderr, "cleaning up runtime directory %q\n", runtimeDirPath)
|
||||
if err := os.RemoveAll(runtimeDirPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error removing temporary directory %q: %v", runtimeDirPath, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil {
|
||||
return fmt.Errorf("merging and writing daemon.yml file: %w", err)
|
||||
}
|
||||
|
||||
daemon := env.ThisDaemon()
|
||||
|
||||
if len(daemon.Storage.Allocations) < 3 {
|
||||
return fmt.Errorf("daemon.yml with at least 3 allocations was not provided")
|
||||
}
|
||||
|
||||
nebulaCACert, err := nebula.NewCACert(*domain, subnet)
|
||||
@ -127,12 +145,7 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
return fmt.Errorf("creating nebula cert for host: %w", err)
|
||||
}
|
||||
|
||||
adminCreationParams := admin.CreationParams{
|
||||
ID: randStr(32),
|
||||
Domain: *domain,
|
||||
}
|
||||
|
||||
hostBootstrap := bootstrap.Bootstrap{
|
||||
env.Bootstrap = bootstrap.Bootstrap{
|
||||
AdminCreationParams: adminCreationParams,
|
||||
Hosts: map[string]bootstrap.Host{
|
||||
*hostName: bootstrap.Host{
|
||||
@ -145,20 +158,27 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
HostName: *hostName,
|
||||
NebulaHostCert: nebulaHostCert,
|
||||
GarageRPCSecret: randStr(32),
|
||||
GarageAdminToken: randStr(32),
|
||||
GarageGlobalBucketS3APICredentials: garage.NewS3APICredentials(),
|
||||
}
|
||||
|
||||
if hostBootstrap, err = mergeDaemonConfigIntoBootstrap(hostBootstrap, daemonConfig); err != nil {
|
||||
return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
|
||||
if env, err = mergeDaemonIntoBootstrap(env); err != nil {
|
||||
return fmt.Errorf("merging daemon.yml into bootstrap data: %w", err)
|
||||
}
|
||||
|
||||
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(hostBootstrap, daemonConfig)
|
||||
// TODO this can be gotten rid of once nebula-entrypoint is rolled into
|
||||
// daemon itself
|
||||
for key, val := range env.ToMap() {
|
||||
if err := os.Setenv(key, val); err != nil {
|
||||
return fmt.Errorf("failed to set %q to %q: %w", key, val, err)
|
||||
}
|
||||
}
|
||||
|
||||
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating nebula config: %w", err)
|
||||
}
|
||||
|
||||
garagePmuxProcConfigs, err := garagePmuxProcConfigs(hostBootstrap, daemonConfig)
|
||||
garagePmuxProcConfigs, err := garagePmuxProcConfigs(env)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating garage configs: %w", err)
|
||||
}
|
||||
@ -172,14 +192,12 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
),
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(subCmdCtx.ctx)
|
||||
ctx, cancel := context.WithCancel(env.Context)
|
||||
pmuxDoneCh := make(chan struct{})
|
||||
|
||||
fmt.Fprintln(os.Stderr, "starting child processes")
|
||||
go func() {
|
||||
// NOTE both stdout and stderr are sent to stderr, so that the user
|
||||
// can pipe the resulting admin.tgz to stdout.
|
||||
pmuxlib.Run(ctx, os.Stderr, os.Stderr, pmuxConfig)
|
||||
pmuxlib.Run(ctx, pmuxConfig)
|
||||
close(pmuxDoneCh)
|
||||
}()
|
||||
|
||||
@ -190,32 +208,35 @@ var subCmdAdminCreateNetwork = subCmd{
|
||||
}()
|
||||
|
||||
fmt.Fprintln(os.Stderr, "waiting for garage instances to come online")
|
||||
if err := waitForGarageAndNebula(ctx, hostBootstrap, daemonConfig); err != nil {
|
||||
if err := waitForGarageAndNebula(ctx, env); err != nil {
|
||||
return fmt.Errorf("waiting for garage to start up: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "applying initial garage layout")
|
||||
if err := garageApplyLayout(ctx, hostBootstrap, daemonConfig); err != nil {
|
||||
if err := garageApplyLayout(ctx, env); err != nil {
|
||||
return fmt.Errorf("applying initial garage layout: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "initializing garage shared global bucket")
|
||||
err = garageInitializeGlobalBucket(ctx, 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 cryptic-net being used?")
|
||||
|
||||
} else if err != nil {
|
||||
if err := garageInitializeGlobalBucket(ctx, env); err != nil {
|
||||
return fmt.Errorf("initializing garage shared global bucket: %w", err)
|
||||
}
|
||||
|
||||
garageS3Client := env.Bootstrap.GlobalBucketS3APIClient()
|
||||
|
||||
fmt.Fprintln(os.Stderr, "writing data for this host into garage")
|
||||
err = bootstrap.PutGarageBoostrapHost(ctx, garageS3Client, env.Bootstrap.ThisHost())
|
||||
if err != nil {
|
||||
return fmt.Errorf("putting host data into garage: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "cluster initialized successfully, writing admin.tgz to stdout")
|
||||
|
||||
err = admin.Admin{
|
||||
CreationParams: adminCreationParams,
|
||||
NebulaCACert: nebulaCACert,
|
||||
GarageRPCSecret: hostBootstrap.GarageRPCSecret,
|
||||
GarageGlobalBucketS3APICredentials: hostBootstrap.GarageGlobalBucketS3APICredentials,
|
||||
GarageRPCSecret: env.Bootstrap.GarageRPCSecret,
|
||||
GarageGlobalBucketS3APICredentials: env.Bootstrap.GarageGlobalBucketS3APICredentials,
|
||||
GarageAdminBucketS3APICredentials: garage.NewS3APICredentials(),
|
||||
}.WriteTo(os.Stdout)
|
||||
|
||||
@ -253,23 +274,20 @@ var subCmdAdminMakeBootstrap = subCmd{
|
||||
return errors.New("--name and --admin-path are required")
|
||||
}
|
||||
|
||||
hostBootstrap, err := loadHostBootstrap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
env := subCmdCtx.env
|
||||
|
||||
adm, err := readAdmin(*adminPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err)
|
||||
}
|
||||
|
||||
client := hostBootstrap.GlobalBucketS3APIClient()
|
||||
client := env.Bootstrap.GlobalBucketS3APIClient()
|
||||
|
||||
// NOTE this isn't _technically_ required, but if the `hosts add`
|
||||
// command for this host has been run recently then it might not have
|
||||
// made it into the bootstrap file yet, and so won't be in
|
||||
// `hostBootstrap`.
|
||||
hosts, err := bootstrap.GetGarageBootstrapHosts(subCmdCtx.ctx, client)
|
||||
// `env.Bootstrap`.
|
||||
hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving host info from garage: %w", err)
|
||||
}
|
||||
@ -289,7 +307,7 @@ var subCmdAdminMakeBootstrap = subCmd{
|
||||
return fmt.Errorf("creating new nebula host key/cert: %w", err)
|
||||
}
|
||||
|
||||
newHostBootstrap := bootstrap.Bootstrap{
|
||||
newBootstrap := bootstrap.Bootstrap{
|
||||
AdminCreationParams: adm.CreationParams,
|
||||
|
||||
Hosts: hosts,
|
||||
@ -302,7 +320,7 @@ var subCmdAdminMakeBootstrap = subCmd{
|
||||
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials,
|
||||
}
|
||||
|
||||
return newHostBootstrap.WriteTo(os.Stdout)
|
||||
return newBootstrap.WriteTo(os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
@ -311,7 +329,6 @@ var subCmdAdmin = subCmd{
|
||||
descr: "Sub-commands which only admins can run",
|
||||
do: func(subCmdCtx subCmdCtx) error {
|
||||
return subCmdCtx.doSubCmd(
|
||||
subCmdAdminCreateNetwork,
|
||||
subCmdAdminMakeBootstrap,
|
||||
)
|
||||
},
|
||||
|
@ -1,44 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cryptic-net/bootstrap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func loadHostBootstrap() (bootstrap.Bootstrap, error) {
|
||||
|
||||
dataDirPath := bootstrap.DataDirPath(envDataDirPath)
|
||||
|
||||
hostBootstrap, err := bootstrap.FromFile(dataDirPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return bootstrap.Bootstrap{}, errors.New("%q not found, has the daemon ever been run?")
|
||||
|
||||
} else if err != nil {
|
||||
return bootstrap.Bootstrap{}, fmt.Errorf("loading %q: %w", dataDirPath, err)
|
||||
}
|
||||
|
||||
return hostBootstrap, nil
|
||||
}
|
||||
|
||||
func writeBootstrapToDataDir(hostBootstrap bootstrap.Bootstrap) error {
|
||||
|
||||
path := bootstrap.DataDirPath(envDataDirPath)
|
||||
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)
|
||||
}
|
@ -5,13 +5,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
crypticnet "cryptic-net"
|
||||
"cryptic-net/bootstrap"
|
||||
"cryptic-net/daemon"
|
||||
"cryptic-net/garage"
|
||||
|
||||
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
||||
@ -26,8 +25,11 @@ import (
|
||||
// * Creates the data directory and copies the appdir bootstrap file into there,
|
||||
// if it's not already there.
|
||||
//
|
||||
// * Merges daemon configuration into the bootstrap configuration, and rewrites
|
||||
// the bootstrap file.
|
||||
// * Merges the user-provided daemon.yml file with the default, and writes the
|
||||
// result to the runtime dir.
|
||||
//
|
||||
// * Merges daemon.yml configuration into the bootstrap configuration, and
|
||||
// rewrites the bootstrap file.
|
||||
//
|
||||
// * Sets up environment variables that all other sub-processes then use, based
|
||||
// on the runtime dir.
|
||||
@ -38,112 +40,108 @@ import (
|
||||
|
||||
// creates a new bootstrap file using available information from the network. If
|
||||
// the new bootstrap file is different than the existing one, the existing one
|
||||
// is overwritten and true is returned.
|
||||
func reloadBootstrap(
|
||||
ctx context.Context,
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
s3Client garage.S3APIClient,
|
||||
) (
|
||||
bootstrap.Bootstrap, bool, error,
|
||||
) {
|
||||
// is overwritten, env's bootstrap is reloaded, true is returned.
|
||||
func reloadBootstrap(env crypticnet.Env, s3Client garage.S3APIClient) (crypticnet.Env, bool, error) {
|
||||
|
||||
newHosts, err := bootstrap.GetGarageBootstrapHosts(ctx, s3Client)
|
||||
newHosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, s3Client)
|
||||
if err != nil {
|
||||
return bootstrap.Bootstrap{}, false, fmt.Errorf("getting hosts from garage: %w", err)
|
||||
return crypticnet.Env{}, false, fmt.Errorf("getting hosts from garage: %w", err)
|
||||
}
|
||||
|
||||
newHostsHash, err := bootstrap.HostsHash(newHosts)
|
||||
if err != nil {
|
||||
return bootstrap.Bootstrap{}, false, fmt.Errorf("calculating hash of new hosts: %w", err)
|
||||
return crypticnet.Env{}, false, fmt.Errorf("calculating hash of new hosts: %w", err)
|
||||
}
|
||||
|
||||
currHostsHash, err := bootstrap.HostsHash(hostBootstrap.Hosts)
|
||||
currHostsHash, err := bootstrap.HostsHash(env.Bootstrap.Hosts)
|
||||
if err != nil {
|
||||
return bootstrap.Bootstrap{}, false, fmt.Errorf("calculating hash of current hosts: %w", err)
|
||||
return crypticnet.Env{}, false, fmt.Errorf("calculating hash of current hosts: %w", err)
|
||||
}
|
||||
|
||||
if bytes.Equal(newHostsHash, currHostsHash) {
|
||||
return hostBootstrap, false, nil
|
||||
return crypticnet.Env{}, false, nil
|
||||
}
|
||||
|
||||
newHostBootstrap := hostBootstrap.WithHosts(newHosts)
|
||||
|
||||
if err := writeBootstrapToDataDir(newHostBootstrap); err != nil {
|
||||
return bootstrap.Bootstrap{}, false, fmt.Errorf("writing new bootstrap.tgz to data dir: %w", err)
|
||||
buf := new(bytes.Buffer)
|
||||
if err := env.Bootstrap.WithHosts(newHosts).WriteTo(buf); err != nil {
|
||||
return crypticnet.Env{}, false, fmt.Errorf("writing new bootstrap file to buffer: %w", err)
|
||||
}
|
||||
|
||||
return newHostBootstrap, true, nil
|
||||
if env, err = copyBootstrapToDataDirAndReload(env, buf); err != nil {
|
||||
return crypticnet.Env{}, false, fmt.Errorf("copying new bootstrap file to data dir: %w", err)
|
||||
}
|
||||
|
||||
return env, true, nil
|
||||
}
|
||||
|
||||
// runs a single pmux process of daemon, returning only once the env.Context has
|
||||
// been canceled or bootstrap info has been changed. This will always block
|
||||
// until the spawned pmux has returned, and returns a copy of hostBootstrap with
|
||||
// updated boostrap info.
|
||||
func runDaemonPmuxOnce(
|
||||
ctx context.Context,
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) (
|
||||
bootstrap.Bootstrap, error,
|
||||
) {
|
||||
// until the spawned pmux has returned, and returns a copy of Env with updated
|
||||
// boostrap info.
|
||||
func runDaemonPmuxOnce(env crypticnet.Env) (crypticnet.Env, error) {
|
||||
|
||||
thisHost := hostBootstrap.ThisHost()
|
||||
thisHost := env.Bootstrap.ThisHost()
|
||||
thisDaemon := env.ThisDaemon()
|
||||
fmt.Fprintf(os.Stderr, "host name is %q, ip is %q\n", thisHost.Name, thisHost.Nebula.IP)
|
||||
|
||||
// create s3Client anew on every loop, in case the topology has
|
||||
// changed and we should be connecting to a different garage
|
||||
// endpoint.
|
||||
s3Client := hostBootstrap.GlobalBucketS3APIClient()
|
||||
s3Client := env.Bootstrap.GlobalBucketS3APIClient()
|
||||
|
||||
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(hostBootstrap, daemonConfig)
|
||||
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env)
|
||||
if err != nil {
|
||||
return bootstrap.Bootstrap{}, fmt.Errorf("generating nebula config: %w", err)
|
||||
return crypticnet.Env{}, fmt.Errorf("generating nebula config: %w", err)
|
||||
}
|
||||
|
||||
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(hostBootstrap, daemonConfig)
|
||||
if err != nil {
|
||||
return bootstrap.Bootstrap{}, fmt.Errorf("generating dnsmasq config: %w", err)
|
||||
}
|
||||
|
||||
garagePmuxProcConfigs, err := garagePmuxProcConfigs(hostBootstrap, daemonConfig)
|
||||
if err != nil {
|
||||
return bootstrap.Bootstrap{}, fmt.Errorf("generating garage children configs: %w", err)
|
||||
}
|
||||
|
||||
pmuxConfig := pmuxlib.Config{
|
||||
Processes: append(
|
||||
[]pmuxlib.ProcessConfig{
|
||||
pmuxProcConfigs := []pmuxlib.ProcessConfig{
|
||||
nebulaPmuxProcConfig,
|
||||
dnsmasqPmuxProcConfig,
|
||||
{
|
||||
Name: "dnsmasq",
|
||||
Cmd: "bash",
|
||||
Args: []string{"dnsmasq-entrypoint"},
|
||||
StartAfterFunc: func(ctx context.Context) error {
|
||||
return waitForNebula(ctx, env)
|
||||
},
|
||||
},
|
||||
garagePmuxProcConfigs...,
|
||||
),
|
||||
}
|
||||
|
||||
doneCh := ctx.Done()
|
||||
if len(thisDaemon.Storage.Allocations) > 0 {
|
||||
|
||||
garagePmuxProcConfigs, err := garagePmuxProcConfigs(env)
|
||||
if err != nil {
|
||||
return crypticnet.Env{}, fmt.Errorf("generating garage children configs: %w", err)
|
||||
}
|
||||
|
||||
pmuxProcConfigs = append(pmuxProcConfigs, garagePmuxProcConfigs...)
|
||||
}
|
||||
|
||||
pmuxConfig := pmuxlib.Config{Processes: pmuxProcConfigs}
|
||||
|
||||
doneCh := env.Context.Done()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
ctx, cancel := context.WithCancel(env.Context)
|
||||
defer cancel()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
pmuxlib.Run(ctx, os.Stdout, os.Stderr, pmuxConfig)
|
||||
pmuxlib.Run(ctx, pmuxConfig)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if err := waitForGarageAndNebula(ctx, hostBootstrap, daemonConfig); err != nil {
|
||||
if err := waitForGarageAndNebula(ctx, env); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
thisHost := hostBootstrap.ThisHost()
|
||||
thisHost := env.Bootstrap.ThisHost()
|
||||
|
||||
err := doOnce(ctx, func(ctx context.Context) error {
|
||||
fmt.Fprintln(os.Stderr, "updating host info in garage")
|
||||
@ -155,19 +153,19 @@ func runDaemonPmuxOnce(
|
||||
}
|
||||
}()
|
||||
|
||||
if len(daemonConfig.Storage.Allocations) > 0 {
|
||||
if len(thisDaemon.Storage.Allocations) > 0 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if err := waitForGarageAndNebula(ctx, hostBootstrap, daemonConfig); err != nil {
|
||||
if err := waitForGarageAndNebula(ctx, env); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err := doOnce(ctx, func(ctx context.Context) error {
|
||||
fmt.Fprintln(os.Stderr, "applying garage layout")
|
||||
return garageApplyLayout(ctx, hostBootstrap, daemonConfig)
|
||||
return garageApplyLayout(ctx, env)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -183,7 +181,7 @@ func runDaemonPmuxOnce(
|
||||
select {
|
||||
|
||||
case <-doneCh:
|
||||
return bootstrap.Bootstrap{}, ctx.Err()
|
||||
return crypticnet.Env{}, env.Context.Err()
|
||||
|
||||
case <-ticker.C:
|
||||
|
||||
@ -194,12 +192,12 @@ func runDaemonPmuxOnce(
|
||||
err error
|
||||
)
|
||||
|
||||
if hostBootstrap, changed, err = reloadBootstrap(ctx, hostBootstrap, s3Client); err != nil {
|
||||
return bootstrap.Bootstrap{}, fmt.Errorf("reloading bootstrap: %w", err)
|
||||
if env, changed, err = reloadBootstrap(env, s3Client); err != nil {
|
||||
return crypticnet.Env{}, fmt.Errorf("reloading bootstrap: %w", err)
|
||||
|
||||
} else if changed {
|
||||
fmt.Fprintln(os.Stderr, "bootstrap info has changed, restarting all processes")
|
||||
return hostBootstrap, nil
|
||||
return env, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,7 +210,7 @@ var subCmdDaemon = subCmd{
|
||||
|
||||
flags := subCmdCtx.flagSet(false)
|
||||
|
||||
daemonConfigPath := flags.StringP(
|
||||
daemonYmlPath := flags.StringP(
|
||||
"config-path", "c", "",
|
||||
"Optional path to a daemon.yml file to load configuration from.",
|
||||
)
|
||||
@ -231,81 +229,91 @@ var subCmdDaemon = subCmd{
|
||||
return fmt.Errorf("parsing flags: %w", err)
|
||||
}
|
||||
|
||||
env := subCmdCtx.env
|
||||
|
||||
if *dumpConfig {
|
||||
return daemon.CopyDefaultConfig(os.Stdout, envAppDirPath)
|
||||
return writeBuiltinDaemonYml(env, os.Stdout)
|
||||
}
|
||||
|
||||
runtimeDirCleanup, err := setupAndLockRuntimeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up runtime directory: %w", err)
|
||||
}
|
||||
defer runtimeDirCleanup()
|
||||
runtimeDirPath := env.RuntimeDirPath
|
||||
|
||||
var (
|
||||
bootstrapDataDirPath = bootstrap.DataDirPath(envDataDirPath)
|
||||
bootstrapAppDirPath = bootstrap.AppDirPath(envAppDirPath)
|
||||
fmt.Fprintf(os.Stderr, "will use runtime directory %q for temporary state\n", runtimeDirPath)
|
||||
|
||||
hostBootstrapPath string
|
||||
hostBootstrap bootstrap.Bootstrap
|
||||
foundHostBootstrap bool
|
||||
)
|
||||
if err := os.MkdirAll(runtimeDirPath, 0700); err != nil {
|
||||
return fmt.Errorf("creating directory %q: %w", runtimeDirPath, err)
|
||||
|
||||
tryLoadBootstrap := func(path string) bool {
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
} else if hostBootstrap, err = bootstrap.FromFile(path); errors.Is(err, fs.ErrNotExist) {
|
||||
err = nil
|
||||
return false
|
||||
|
||||
} else if err != nil {
|
||||
err = fmt.Errorf("parsing bootstrap.tgz at %q: %w", path, err)
|
||||
return false
|
||||
} else if err := crypticnet.NewProcLock(runtimeDirPath).WriteLock(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostBootstrapPath = path
|
||||
return true
|
||||
// do not defer the cleaning of the runtime directory until the lock has
|
||||
// been obtained, otherwise we might delete the directory out from under
|
||||
// the feet of an already running daemon
|
||||
defer func() {
|
||||
fmt.Fprintf(os.Stderr, "cleaning up runtime directory %q\n", runtimeDirPath)
|
||||
if err := os.RemoveAll(runtimeDirPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error removing temporary directory %q: %v", runtimeDirPath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
foundHostBootstrap = tryLoadBootstrap(bootstrapDataDirPath)
|
||||
foundHostBootstrap = !foundHostBootstrap && *bootstrapPath != "" && tryLoadBootstrap(*bootstrapPath)
|
||||
foundHostBootstrap = !foundHostBootstrap && tryLoadBootstrap(bootstrapAppDirPath)
|
||||
// If the bootstrap file is not being stored in the data dir, move it
|
||||
// there and reload the bootstrap info
|
||||
if env.BootstrapPath != env.DataDirBootstrapPath() {
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("attempting to load bootstrap.tgz file: %w", err)
|
||||
path := env.BootstrapPath
|
||||
|
||||
} else if !foundHostBootstrap {
|
||||
// If there's no BootstrapPath then no bootstrap file could be
|
||||
// found. In this case we require the user to provide one on the
|
||||
// command-line.
|
||||
if path == "" {
|
||||
|
||||
if *bootstrapPath == "" {
|
||||
return errors.New("No bootstrap.tgz file could be found, and one is not provided with --bootstrap-path")
|
||||
|
||||
} else if hostBootstrapPath != bootstrapDataDirPath {
|
||||
|
||||
// If the bootstrap file is not being stored in the data dir, copy
|
||||
// it there, so it can be loaded from there next time.
|
||||
if err := writeBootstrapToDataDir(hostBootstrap); err != nil {
|
||||
return fmt.Errorf("writing bootstrap.tgz to data dir: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
daemonConfig, err := daemon.LoadConfig(envAppDirPath, *daemonConfigPath)
|
||||
path = *bootstrapPath
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading daemon config: %w", err)
|
||||
return fmt.Errorf("opening file %q: %w", env.BootstrapPath, err)
|
||||
}
|
||||
|
||||
env, err = copyBootstrapToDataDirAndReload(env, f)
|
||||
f.Close()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("copying bootstrap file from %q: %w", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeMergedDaemonYml(env, *daemonYmlPath); err != nil {
|
||||
return fmt.Errorf("merging and writing daemon.yml file: %w", err)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// we update this Host's data using whatever configuration has been
|
||||
// provided by the daemon config. This way the daemon has the most
|
||||
// up-to-date possible bootstrap. This updated bootstrap will later get
|
||||
// updated in garage using bootstrap.PutGarageBoostrapHost, so other
|
||||
// hosts will see it as well.
|
||||
if hostBootstrap, err = mergeDaemonConfigIntoBootstrap(hostBootstrap, daemonConfig); err != nil {
|
||||
return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
|
||||
// provided by daemon.yml. This way the daemon has the most
|
||||
// up-to-date possible bootstrap. This updated bootstrap will later
|
||||
// get updated in garage using update-global-bucket, so other hosts
|
||||
// will see it as well.
|
||||
if env, err = mergeDaemonIntoBootstrap(env); err != nil {
|
||||
return fmt.Errorf("merging daemon.yml into bootstrap data: %w", err)
|
||||
}
|
||||
|
||||
// TODO once dnsmasq entrypoint is written in go and the config
|
||||
// generation is inlined into this process then this Setenv won't be
|
||||
// necessary.
|
||||
for key, val := range env.ToMap() {
|
||||
if err := os.Setenv(key, val); err != nil {
|
||||
return fmt.Errorf("failed to set %q to %q: %w", key, val, err)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
|
||||
hostBootstrap, err = runDaemonPmuxOnce(subCmdCtx.ctx, hostBootstrap, daemonConfig)
|
||||
|
||||
if errors.Is(err, context.Canceled) {
|
||||
if env, err = runDaemonPmuxOnce(env); errors.Is(err, context.Canceled) {
|
||||
return nil
|
||||
|
||||
} else if err != nil {
|
||||
|
@ -1,26 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
crypticnet "cryptic-net"
|
||||
"cryptic-net/bootstrap"
|
||||
"cryptic-net/daemon"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func mergeDaemonConfigIntoBootstrap(
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) (
|
||||
bootstrap.Bootstrap, error,
|
||||
) {
|
||||
host := hostBootstrap.ThisHost()
|
||||
func copyBootstrapToDataDirAndReload(env crypticnet.Env, r io.Reader) (crypticnet.Env, error) {
|
||||
|
||||
host.Nebula.PublicAddr = daemonConfig.VPN.PublicAddr
|
||||
path := env.DataDirBootstrapPath()
|
||||
dirPath := filepath.Dir(path)
|
||||
|
||||
if err := os.MkdirAll(dirPath, 0700); err != nil {
|
||||
return crypticnet.Env{}, fmt.Errorf("creating directory %q: %w", dirPath, err)
|
||||
}
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return crypticnet.Env{}, fmt.Errorf("creating file %q: %w", path, err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(f, r)
|
||||
f.Close()
|
||||
|
||||
if err != nil {
|
||||
return crypticnet.Env{}, fmt.Errorf("copying bootstrap file to %q: %w", path, err)
|
||||
}
|
||||
|
||||
return env.LoadBootstrap(path)
|
||||
}
|
||||
|
||||
func mergeDaemonIntoBootstrap(env crypticnet.Env) (crypticnet.Env, error) {
|
||||
daemon := env.ThisDaemon()
|
||||
host := env.Bootstrap.ThisHost()
|
||||
|
||||
host.Nebula.PublicAddr = daemon.VPN.PublicAddr
|
||||
|
||||
host.Garage = nil
|
||||
|
||||
if allocs := daemonConfig.Storage.Allocations; len(allocs) > 0 {
|
||||
if allocs := daemon.Storage.Allocations; len(allocs) > 0 {
|
||||
|
||||
host.Garage = new(bootstrap.GarageHost)
|
||||
|
||||
@ -32,13 +56,14 @@ func mergeDaemonConfigIntoBootstrap(
|
||||
}
|
||||
}
|
||||
|
||||
hostBootstrap.Hosts[host.Name] = host
|
||||
env.Bootstrap.Hosts[host.Name] = host
|
||||
|
||||
if err := writeBootstrapToDataDir(hostBootstrap); err != nil {
|
||||
return bootstrap.Bootstrap{}, fmt.Errorf("writing bootstrap file: %w", err)
|
||||
buf := new(bytes.Buffer)
|
||||
if err := env.Bootstrap.WithHosts(env.Bootstrap.Hosts).WriteTo(buf); err != nil {
|
||||
return crypticnet.Env{}, fmt.Errorf("writing new bootstrap file to buffer: %w", err)
|
||||
}
|
||||
|
||||
return hostBootstrap, nil
|
||||
return copyBootstrapToDataDirAndReload(env, buf)
|
||||
}
|
||||
|
||||
func doOnce(ctx context.Context, fn func(context.Context) error) error {
|
||||
|
72
entrypoint/src/cmd/entrypoint/daemon_yml.go
Normal file
72
entrypoint/src/cmd/entrypoint/daemon_yml.go
Normal file
@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
crypticnet "cryptic-net"
|
||||
"cryptic-net/yamlutil"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func builtinDaemonYmlPath(env crypticnet.Env) string {
|
||||
return filepath.Join(env.AppDirPath, "etc", "daemon.yml")
|
||||
}
|
||||
|
||||
func writeBuiltinDaemonYml(env crypticnet.Env, w io.Writer) error {
|
||||
|
||||
builtinDaemonYmlPath := builtinDaemonYmlPath(env)
|
||||
|
||||
builtinDaemonYml, err := os.ReadFile(builtinDaemonYmlPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading default daemon.yml at %q: %w", builtinDaemonYmlPath, err)
|
||||
}
|
||||
|
||||
if _, err := w.Write(builtinDaemonYml); err != nil {
|
||||
return fmt.Errorf("writing default daemon.yml: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeMergedDaemonYml(env crypticnet.Env, userDaemonYmlPath string) error {
|
||||
|
||||
builtinDaemonYmlPath := builtinDaemonYmlPath(env)
|
||||
|
||||
var fullDaemonYml map[string]interface{}
|
||||
|
||||
if err := yamlutil.LoadYamlFile(&fullDaemonYml, builtinDaemonYmlPath); err != nil {
|
||||
return fmt.Errorf("parsing builtin daemon.yml file: %w", err)
|
||||
}
|
||||
|
||||
if userDaemonYmlPath != "" {
|
||||
|
||||
var daemonYml map[string]interface{}
|
||||
if err := yamlutil.LoadYamlFile(&daemonYml, userDaemonYmlPath); err != nil {
|
||||
return fmt.Errorf("parsing %q: %w", userDaemonYmlPath, err)
|
||||
}
|
||||
|
||||
err := mergo.Merge(&fullDaemonYml, daemonYml, mergo.WithOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("merging contents of file %q: %w", userDaemonYmlPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
fullDaemonYmlB, err := yaml.Marshal(fullDaemonYml)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("yaml marshaling daemon config: %w", err)
|
||||
}
|
||||
|
||||
daemonYmlPath := filepath.Join(env.RuntimeDirPath, "daemon.yml")
|
||||
|
||||
if err := ioutil.WriteFile(daemonYmlPath, fullDaemonYmlB, 0400); err != nil {
|
||||
return fmt.Errorf("writing daemon.yml file to %q: %w", daemonYmlPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cryptic-net/bootstrap"
|
||||
"cryptic-net/daemon"
|
||||
"cryptic-net/dnsmasq"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
||||
)
|
||||
|
||||
func dnsmasqPmuxProcConfig(
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) (
|
||||
pmuxlib.ProcessConfig, error,
|
||||
) {
|
||||
|
||||
confPath := filepath.Join(envRuntimeDirPath, "dnsmasq.conf")
|
||||
|
||||
hostsSlice := make([]bootstrap.Host, 0, len(hostBootstrap.Hosts))
|
||||
for _, host := range hostBootstrap.Hosts {
|
||||
hostsSlice = append(hostsSlice, host)
|
||||
}
|
||||
|
||||
sort.Slice(hostsSlice, func(i, j int) bool {
|
||||
return hostsSlice[i].Nebula.IP < hostsSlice[j].Nebula.IP
|
||||
})
|
||||
|
||||
confData := dnsmasq.ConfData{
|
||||
Resolvers: daemonConfig.DNS.Resolvers,
|
||||
Domain: hostBootstrap.AdminCreationParams.Domain,
|
||||
IP: hostBootstrap.ThisHost().Nebula.IP,
|
||||
Hosts: hostsSlice,
|
||||
}
|
||||
|
||||
if err := dnsmasq.WriteConfFile(confPath, confData); err != nil {
|
||||
return pmuxlib.ProcessConfig{}, fmt.Errorf("writing dnsmasq.conf to %q: %w", confPath, err)
|
||||
}
|
||||
|
||||
return pmuxlib.ProcessConfig{
|
||||
Name: "dnsmasq",
|
||||
Cmd: "dnsmasq",
|
||||
Args: []string{"-d", "-C", confPath},
|
||||
}, nil
|
||||
}
|
@ -28,21 +28,18 @@ var subCmdGarageMC = subCmd{
|
||||
return fmt.Errorf("parsing flags: %w", err)
|
||||
}
|
||||
|
||||
hostBootstrap, err := loadHostBootstrap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
env := subCmdCtx.env
|
||||
|
||||
s3APIAddr := hostBootstrap.ChooseGaragePeer().S3APIAddr()
|
||||
s3APIAddr := env.Bootstrap.ChooseGaragePeer().S3APIAddr()
|
||||
|
||||
if *keyID == "" || *keySecret == "" {
|
||||
|
||||
if *keyID == "" {
|
||||
*keyID = hostBootstrap.GarageGlobalBucketS3APICredentials.ID
|
||||
*keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.ID
|
||||
}
|
||||
|
||||
if *keySecret == "" {
|
||||
*keyID = hostBootstrap.GarageGlobalBucketS3APICredentials.Secret
|
||||
*keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.Secret
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +52,7 @@ var subCmdGarageMC = subCmd{
|
||||
args = append([]string{"mc"}, args...)
|
||||
|
||||
var (
|
||||
binPath = "mc"
|
||||
binPath = env.BinPath("mc")
|
||||
cliEnv = append(
|
||||
os.Environ(),
|
||||
fmt.Sprintf(
|
||||
@ -86,18 +83,15 @@ var subCmdGarageCLI = subCmd{
|
||||
checkLock: true,
|
||||
do: func(subCmdCtx subCmdCtx) error {
|
||||
|
||||
hostBootstrap, err := loadHostBootstrap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
env := subCmdCtx.env
|
||||
|
||||
var (
|
||||
binPath = "garage"
|
||||
binPath = env.BinPath("garage")
|
||||
args = append([]string{"garage"}, subCmdCtx.args...)
|
||||
cliEnv = append(
|
||||
os.Environ(),
|
||||
"GARAGE_RPC_HOST="+hostBootstrap.ChooseGaragePeer().RPCAddr(),
|
||||
"GARAGE_RPC_SECRET="+hostBootstrap.GarageRPCSecret,
|
||||
"GARAGE_RPC_HOST="+env.Bootstrap.ChooseGaragePeer().RPCAddr(),
|
||||
"GARAGE_RPC_SECRET="+env.Bootstrap.GarageRPCSecret,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -2,8 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cryptic-net/bootstrap"
|
||||
"cryptic-net/daemon"
|
||||
crypticnet "cryptic-net"
|
||||
"cryptic-net/garage"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -14,47 +13,26 @@ import (
|
||||
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
||||
)
|
||||
|
||||
// newGarageAdminClient will return an AdminClient for a local garage instance,
|
||||
// or it will _panic_ if there is no local instance configured.
|
||||
func newGarageAdminClient(
|
||||
hostBootstrap bootstrap.Bootstrap, daemonConfig daemon.Config,
|
||||
) *garage.AdminClient {
|
||||
func waitForGarageAndNebula(ctx context.Context, env crypticnet.Env) error {
|
||||
|
||||
thisHost := hostBootstrap.ThisHost()
|
||||
|
||||
return garage.NewAdminClient(
|
||||
net.JoinHostPort(
|
||||
thisHost.Nebula.IP,
|
||||
strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort),
|
||||
),
|
||||
hostBootstrap.GarageAdminToken,
|
||||
)
|
||||
}
|
||||
|
||||
func waitForGarageAndNebula(
|
||||
ctx context.Context,
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) error {
|
||||
|
||||
allocs := daemonConfig.Storage.Allocations
|
||||
allocs := env.ThisDaemon().Storage.Allocations
|
||||
|
||||
// if this host doesn't have any allocations specified then fall back to
|
||||
// waiting for nebula
|
||||
if len(allocs) == 0 {
|
||||
return waitForNebula(ctx, hostBootstrap)
|
||||
return waitForNebula(ctx, env)
|
||||
}
|
||||
|
||||
for _, alloc := range allocs {
|
||||
|
||||
adminAddr := net.JoinHostPort(
|
||||
hostBootstrap.ThisHost().Nebula.IP,
|
||||
env.Bootstrap.ThisHost().Nebula.IP,
|
||||
strconv.Itoa(alloc.AdminPort),
|
||||
)
|
||||
|
||||
adminClient := garage.NewAdminClient(
|
||||
adminAddr,
|
||||
hostBootstrap.GarageAdminToken,
|
||||
env.Bootstrap.GarageAdminToken,
|
||||
)
|
||||
|
||||
if err := adminClient.Wait(ctx); err != nil {
|
||||
@ -66,9 +44,9 @@ func waitForGarageAndNebula(
|
||||
|
||||
}
|
||||
|
||||
func garageWriteChildConfig(
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
alloc daemon.ConfigStorageAllocation,
|
||||
func garageWriteChildConf(
|
||||
env crypticnet.Env,
|
||||
alloc crypticnet.DaemonYmlStorageAllocation,
|
||||
) (
|
||||
string, error,
|
||||
) {
|
||||
@ -77,7 +55,7 @@ func garageWriteChildConfig(
|
||||
return "", fmt.Errorf("making directory %q: %w", alloc.MetaPath, err)
|
||||
}
|
||||
|
||||
thisHost := hostBootstrap.ThisHost()
|
||||
thisHost := env.Bootstrap.ThisHost()
|
||||
|
||||
peer := garage.Peer{
|
||||
IP: thisHost.Nebula.IP,
|
||||
@ -98,21 +76,21 @@ func garageWriteChildConfig(
|
||||
}
|
||||
|
||||
garageTomlPath := filepath.Join(
|
||||
envRuntimeDirPath, fmt.Sprintf("garage-%d.toml", alloc.RPCPort),
|
||||
env.RuntimeDirPath, fmt.Sprintf("garage-%d.toml", alloc.RPCPort),
|
||||
)
|
||||
|
||||
err := garage.WriteGarageTomlFile(garageTomlPath, garage.GarageTomlData{
|
||||
MetaPath: alloc.MetaPath,
|
||||
DataPath: alloc.DataPath,
|
||||
|
||||
RPCSecret: hostBootstrap.GarageRPCSecret,
|
||||
AdminToken: hostBootstrap.GarageAdminToken,
|
||||
RPCSecret: env.Bootstrap.GarageRPCSecret,
|
||||
AdminToken: env.Bootstrap.GarageAdminToken,
|
||||
|
||||
RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
|
||||
APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)),
|
||||
AdminAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.AdminPort)),
|
||||
|
||||
BootstrapPeers: hostBootstrap.GarageRPCPeerAddrs(),
|
||||
BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@ -122,18 +100,13 @@ func garageWriteChildConfig(
|
||||
return garageTomlPath, nil
|
||||
}
|
||||
|
||||
func garagePmuxProcConfigs(
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) (
|
||||
[]pmuxlib.ProcessConfig, error,
|
||||
) {
|
||||
func garagePmuxProcConfigs(env crypticnet.Env) ([]pmuxlib.ProcessConfig, error) {
|
||||
|
||||
var pmuxProcConfigs []pmuxlib.ProcessConfig
|
||||
|
||||
for _, alloc := range daemonConfig.Storage.Allocations {
|
||||
for _, alloc := range env.ThisDaemon().Storage.Allocations {
|
||||
|
||||
childConfigPath, err := garageWriteChildConfig(hostBootstrap, alloc)
|
||||
childConfPath, err := garageWriteChildConf(env, alloc)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
||||
@ -142,9 +115,9 @@ func garagePmuxProcConfigs(
|
||||
pmuxProcConfigs = append(pmuxProcConfigs, pmuxlib.ProcessConfig{
|
||||
Name: fmt.Sprintf("garage-%d", alloc.RPCPort),
|
||||
Cmd: "garage",
|
||||
Args: []string{"-c", childConfigPath, "server"},
|
||||
Args: []string{"-c", childConfPath, "server"},
|
||||
StartAfterFunc: func(ctx context.Context) error {
|
||||
return waitForNebula(ctx, hostBootstrap)
|
||||
return waitForNebula(ctx, env)
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -152,15 +125,11 @@ func garagePmuxProcConfigs(
|
||||
return pmuxProcConfigs, nil
|
||||
}
|
||||
|
||||
func garageInitializeGlobalBucket(
|
||||
ctx context.Context,
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) error {
|
||||
func garageInitializeGlobalBucket(ctx context.Context, env crypticnet.Env) error {
|
||||
|
||||
var (
|
||||
adminClient = newGarageAdminClient(hostBootstrap, daemonConfig)
|
||||
globalBucketCreds = hostBootstrap.GarageGlobalBucketS3APICredentials
|
||||
adminClient = env.GarageAdminClient()
|
||||
globalBucketCreds = env.Bootstrap.GarageGlobalBucketS3APICredentials
|
||||
)
|
||||
|
||||
// first attempt to import the key
|
||||
@ -214,18 +183,14 @@ func garageInitializeGlobalBucket(
|
||||
return nil
|
||||
}
|
||||
|
||||
func garageApplyLayout(
|
||||
ctx context.Context,
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) error {
|
||||
func garageApplyLayout(ctx context.Context, env crypticnet.Env) error {
|
||||
|
||||
var (
|
||||
adminClient = newGarageAdminClient(hostBootstrap, daemonConfig)
|
||||
thisHost = hostBootstrap.ThisHost()
|
||||
adminClient = env.GarageAdminClient()
|
||||
thisHost = env.Bootstrap.ThisHost()
|
||||
hostName = thisHost.Name
|
||||
ip = thisHost.Nebula.IP
|
||||
allocs = daemonConfig.Storage.Allocations
|
||||
allocs = env.ThisDaemon().Storage.Allocations
|
||||
)
|
||||
|
||||
type peerLayout struct {
|
||||
@ -248,7 +213,6 @@ func garageApplyLayout(
|
||||
clusterLayout[peer.RPCPeerID()] = peerLayout{
|
||||
Capacity: alloc.Capacity / 100,
|
||||
Zone: hostName,
|
||||
Tags: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,12 +59,8 @@ var subCmdHostsAdd = subCmd{
|
||||
|
||||
// TODO validate that the IP is in the correct CIDR
|
||||
|
||||
hostBootstrap, err := loadHostBootstrap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
|
||||
client := hostBootstrap.GlobalBucketS3APIClient()
|
||||
env := subCmdCtx.env
|
||||
client := env.Bootstrap.GlobalBucketS3APIClient()
|
||||
|
||||
host := bootstrap.Host{
|
||||
Name: *name,
|
||||
@ -73,7 +69,7 @@ var subCmdHostsAdd = subCmd{
|
||||
},
|
||||
}
|
||||
|
||||
return bootstrap.PutGarageBoostrapHost(subCmdCtx.ctx, client, host)
|
||||
return bootstrap.PutGarageBoostrapHost(env.Context, client, host)
|
||||
},
|
||||
}
|
||||
|
||||
@ -83,14 +79,11 @@ var subCmdHostsList = subCmd{
|
||||
checkLock: true,
|
||||
do: func(subCmdCtx subCmdCtx) error {
|
||||
|
||||
hostBootstrap, err := loadHostBootstrap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
env := subCmdCtx.env
|
||||
|
||||
client := hostBootstrap.GlobalBucketS3APIClient()
|
||||
client := env.Bootstrap.GlobalBucketS3APIClient()
|
||||
|
||||
hostsMap, err := bootstrap.GetGarageBootstrapHosts(subCmdCtx.ctx, client)
|
||||
hostsMap, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving hosts from garage: %w", err)
|
||||
}
|
||||
@ -127,14 +120,10 @@ var subCmdHostsDelete = subCmd{
|
||||
return errors.New("--name is required")
|
||||
}
|
||||
|
||||
hostBootstrap, err := loadHostBootstrap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading host bootstrap: %w", err)
|
||||
}
|
||||
env := subCmdCtx.env
|
||||
client := env.Bootstrap.GlobalBucketS3APIClient()
|
||||
|
||||
client := hostBootstrap.GlobalBucketS3APIClient()
|
||||
|
||||
return bootstrap.RemoveGarageBootstrapHost(subCmdCtx.ctx, client, *name)
|
||||
return bootstrap.RemoveGarageBootstrapHost(env.Context, client, *name)
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
crypticnet "cryptic-net"
|
||||
)
|
||||
|
||||
// The purpose of this binary is to act as the entrypoint of the cryptic-net
|
||||
@ -16,42 +12,17 @@ import (
|
||||
// then passes execution along to an appropriate binary housed in AppDir/bin
|
||||
// (usually a bash script, which is more versatile than a go program).
|
||||
|
||||
func getAppDirPath() string {
|
||||
appDirPath := os.Getenv("APPDIR")
|
||||
if appDirPath == "" {
|
||||
appDirPath = "."
|
||||
}
|
||||
return appDirPath
|
||||
}
|
||||
|
||||
var (
|
||||
envAppDirPath = getAppDirPath()
|
||||
envRuntimeDirPath = filepath.Join(xdg.RuntimeDir, "cryptic-net")
|
||||
envDataDirPath = filepath.Join(xdg.DataHome, "cryptic-net")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
env, err := crypticnet.NewEnv(true)
|
||||
|
||||
signalCh := make(chan os.Signal, 2)
|
||||
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("loading environment: %v", err))
|
||||
}
|
||||
|
||||
go func() {
|
||||
sig := <-signalCh
|
||||
cancel()
|
||||
fmt.Fprintf(os.Stderr, "got signal %v, will exit gracefully\n", sig)
|
||||
|
||||
sig = <-signalCh
|
||||
fmt.Fprintf(os.Stderr, "second interrupt signal %v received, force quitting, there may be zombie children left behind, good luck!\n", sig)
|
||||
|
||||
os.Stderr.Sync()
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
err := subCmdCtx{
|
||||
err = subCmdCtx{
|
||||
args: os.Args[1:],
|
||||
ctx: ctx,
|
||||
env: env,
|
||||
}.doSubCmd(
|
||||
subCmdAdmin,
|
||||
subCmdDaemon,
|
||||
|
@ -2,12 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cryptic-net/bootstrap"
|
||||
"cryptic-net/daemon"
|
||||
crypticnet "cryptic-net"
|
||||
"cryptic-net/yamlutil"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"code.betamike.com/cryptic-io/pmux/pmuxlib"
|
||||
)
|
||||
@ -16,9 +16,9 @@ import (
|
||||
// this by attempting to create a UDP connection which has the nebula IP set as
|
||||
// its source. If this succeeds we can assume that at the very least the nebula
|
||||
// interface has been initialized.
|
||||
func waitForNebula(ctx context.Context, hostBootstrap bootstrap.Bootstrap) error {
|
||||
func waitForNebula(ctx context.Context, env crypticnet.Env) error {
|
||||
|
||||
ipStr := hostBootstrap.ThisHost().Nebula.IP
|
||||
ipStr := env.Bootstrap.ThisHost().Nebula.IP
|
||||
ip := net.ParseIP(ipStr)
|
||||
|
||||
lUdpAddr := &net.UDPAddr{IP: ip, Port: 0}
|
||||
@ -34,19 +34,14 @@ func waitForNebula(ctx context.Context, hostBootstrap bootstrap.Bootstrap) error
|
||||
})
|
||||
}
|
||||
|
||||
func nebulaPmuxProcConfig(
|
||||
hostBootstrap bootstrap.Bootstrap,
|
||||
daemonConfig daemon.Config,
|
||||
) (
|
||||
pmuxlib.ProcessConfig, error,
|
||||
) {
|
||||
func nebulaPmuxProcConfig(env crypticnet.Env) (pmuxlib.ProcessConfig, error) {
|
||||
|
||||
var (
|
||||
lighthouseHostIPs []string
|
||||
staticHostMap = map[string][]string{}
|
||||
)
|
||||
|
||||
for _, host := range hostBootstrap.Hosts {
|
||||
for _, host := range env.Bootstrap.Hosts {
|
||||
|
||||
if host.Nebula.PublicAddr == "" {
|
||||
continue
|
||||
@ -58,9 +53,9 @@ func nebulaPmuxProcConfig(
|
||||
|
||||
config := map[string]interface{}{
|
||||
"pki": map[string]string{
|
||||
"ca": hostBootstrap.NebulaHostCert.CACert,
|
||||
"cert": hostBootstrap.NebulaHostCert.HostCert,
|
||||
"key": hostBootstrap.NebulaHostCert.HostKey,
|
||||
"ca": env.Bootstrap.NebulaHostCert.CACert,
|
||||
"cert": env.Bootstrap.NebulaHostCert.HostCert,
|
||||
"key": env.Bootstrap.NebulaHostCert.HostKey,
|
||||
},
|
||||
"static_host_map": staticHostMap,
|
||||
"punchy": map[string]bool{
|
||||
@ -68,12 +63,11 @@ func nebulaPmuxProcConfig(
|
||||
"respond": true,
|
||||
},
|
||||
"tun": map[string]interface{}{
|
||||
"dev": "cryptic-net-nebula",
|
||||
"dev": "cryptic-nebula1",
|
||||
},
|
||||
"firewall": daemonConfig.VPN.Firewall,
|
||||
}
|
||||
|
||||
if publicAddr := daemonConfig.VPN.PublicAddr; publicAddr == "" {
|
||||
if publicAddr := env.ThisDaemon().VPN.PublicAddr; publicAddr == "" {
|
||||
|
||||
config["listen"] = map[string]string{
|
||||
"host": "0.0.0.0",
|
||||
@ -103,7 +97,33 @@ func nebulaPmuxProcConfig(
|
||||
}
|
||||
}
|
||||
|
||||
nebulaYmlPath := filepath.Join(envRuntimeDirPath, "nebula.yml")
|
||||
thisDaemon := env.ThisDaemon()
|
||||
|
||||
var firewallInbound []crypticnet.ConfigFirewallRule
|
||||
|
||||
for _, alloc := range thisDaemon.Storage.Allocations {
|
||||
firewallInbound = append(
|
||||
firewallInbound,
|
||||
crypticnet.ConfigFirewallRule{
|
||||
Port: strconv.Itoa(alloc.S3APIPort),
|
||||
Proto: "tcp",
|
||||
Host: "any",
|
||||
},
|
||||
crypticnet.ConfigFirewallRule{
|
||||
Port: strconv.Itoa(alloc.RPCPort),
|
||||
Proto: "tcp",
|
||||
Host: "any",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
firewall := thisDaemon.VPN.Firewall
|
||||
|
||||
firewall.Inbound = append(firewallInbound, firewall.Inbound...)
|
||||
|
||||
config["firewall"] = firewall
|
||||
|
||||
nebulaYmlPath := filepath.Join(env.RuntimeDirPath, "nebula.yml")
|
||||
|
||||
if err := yamlutil.WriteYamlFile(config, nebulaYmlPath); err != nil {
|
||||
return pmuxlib.ProcessConfig{}, fmt.Errorf("writing nebula.yml to %q: %w", nebulaYmlPath, err)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
crypticnet "cryptic-net"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@ -14,8 +14,7 @@ type subCmdCtx struct {
|
||||
subCmd subCmd // the subCmd itself
|
||||
args []string // command-line arguments, excluding the subCmd itself.
|
||||
subCmdNames []string // names of subCmds so far, including this one
|
||||
|
||||
ctx context.Context
|
||||
env crypticnet.Env
|
||||
}
|
||||
|
||||
type subCmd struct {
|
||||
@ -100,7 +99,9 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
||||
|
||||
if subCmd.checkLock {
|
||||
|
||||
if err := assertLock(); err != nil {
|
||||
err := crypticnet.NewProcLock(ctx.env.RuntimeDirPath).AssertLock()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking lock file: %w", err)
|
||||
}
|
||||
}
|
||||
@ -109,7 +110,7 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
||||
subCmd: subCmd,
|
||||
args: args,
|
||||
subCmdNames: append(ctx.subCmdNames, subCmdName),
|
||||
ctx: ctx.ctx,
|
||||
env: ctx.env,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
@ -11,7 +11,7 @@ var subCmdVersion = subCmd{
|
||||
descr: "Dumps version and build info to stdout",
|
||||
do: func(subCmdCtx subCmdCtx) error {
|
||||
|
||||
versionPath := filepath.Join(envAppDirPath, "share/version")
|
||||
versionPath := filepath.Join(subCmdCtx.env.AppDirPath, "share/version")
|
||||
|
||||
version, err := os.ReadFile(versionPath)
|
||||
|
||||
|
@ -1,85 +0,0 @@
|
||||
// Package daemon contains types and functions related specifically to the
|
||||
// cryptic-net daemon.
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"cryptic-net/yamlutil"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func defaultConfigPath(appDirPath string) string {
|
||||
return filepath.Join(appDirPath, "etc", "daemon.yml")
|
||||
}
|
||||
|
||||
// CopyDefaultConfig copies the daemon config file embedded in the AppDir into
|
||||
// the given io.Writer.
|
||||
func CopyDefaultConfig(into io.Writer, appDirPath string) error {
|
||||
|
||||
defaultConfigPath := defaultConfigPath(appDirPath)
|
||||
|
||||
f, err := os.Open(defaultConfigPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening daemon config at %q: %w", defaultConfigPath, err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(into, f); err != nil {
|
||||
return fmt.Errorf("copying daemon config from %q: %w", defaultConfigPath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadConfig loads the daemon config from userConfigPath, merges it with
|
||||
// the default found in the appDirPath, and returns the result.
|
||||
//
|
||||
// If userConfigPath is not given then the default is loaded and returned.
|
||||
func LoadConfig(
|
||||
appDirPath, userConfigPath string,
|
||||
) (
|
||||
Config, error,
|
||||
) {
|
||||
|
||||
defaultConfigPath := defaultConfigPath(appDirPath)
|
||||
|
||||
var fullDaemon map[string]interface{}
|
||||
|
||||
if err := yamlutil.LoadYamlFile(&fullDaemon, defaultConfigPath); err != nil {
|
||||
return Config{}, fmt.Errorf("parsing default daemon config file: %w", err)
|
||||
}
|
||||
|
||||
if userConfigPath != "" {
|
||||
|
||||
var daemonConfig map[string]interface{}
|
||||
if err := yamlutil.LoadYamlFile(&daemonConfig, userConfigPath); err != nil {
|
||||
return Config{}, fmt.Errorf("parsing %q: %w", userConfigPath, err)
|
||||
}
|
||||
|
||||
err := mergo.Merge(&fullDaemon, daemonConfig, mergo.WithOverride)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("merging contents of file %q: %w", userConfigPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
fullDaemonB, err := yaml.Marshal(fullDaemon)
|
||||
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("yaml marshaling: %w", err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(fullDaemonB, &config); err != nil {
|
||||
return Config{}, fmt.Errorf("yaml unmarshaling back into Config struct: %w", err)
|
||||
}
|
||||
|
||||
config.fillDefaults()
|
||||
|
||||
return config, nil
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
package daemon
|
||||
|
||||
import "strconv"
|
||||
package crypticnet
|
||||
|
||||
type ConfigFirewall struct {
|
||||
Conntrack ConfigConntrack `yaml:"conntrack"`
|
||||
@ -27,9 +25,9 @@ type ConfigFirewallRule struct {
|
||||
CAName string `yaml:"ca_name,omitempty"`
|
||||
}
|
||||
|
||||
// ConfigStorageAllocation describes the structure of each storage allocation
|
||||
// within the daemon config file.
|
||||
type ConfigStorageAllocation struct {
|
||||
// DaemonYmlStorageAllocation describes the structure of each storage allocation
|
||||
// within the daemon.yml file.
|
||||
type DaemonYmlStorageAllocation struct {
|
||||
DataPath string `yaml:"data_path"`
|
||||
MetaPath string `yaml:"meta_path"`
|
||||
Capacity int `yaml:"capacity"`
|
||||
@ -38,8 +36,8 @@ type ConfigStorageAllocation struct {
|
||||
AdminPort int `yaml:"admin_port"`
|
||||
}
|
||||
|
||||
// Config describes the structure of the daemon config file.
|
||||
type Config struct {
|
||||
// DaemonYml describes the structure of the daemon.yml file.
|
||||
type DaemonYml struct {
|
||||
DNS struct {
|
||||
Resolvers []string `yaml:"resolvers"`
|
||||
} `yaml:"dns"`
|
||||
@ -48,47 +46,6 @@ type Config struct {
|
||||
Firewall ConfigFirewall `yaml:"firewall"`
|
||||
} `yaml:"vpn"`
|
||||
Storage struct {
|
||||
Allocations []ConfigStorageAllocation
|
||||
Allocations []DaemonYmlStorageAllocation
|
||||
} `yaml:"storage"`
|
||||
}
|
||||
|
||||
func (c *Config) fillDefaults() {
|
||||
|
||||
var firewallGarageInbound []ConfigFirewallRule
|
||||
|
||||
for i := range c.Storage.Allocations {
|
||||
|
||||
if c.Storage.Allocations[i].RPCPort == 0 {
|
||||
c.Storage.Allocations[i].RPCPort = 3900 + (i * 10)
|
||||
}
|
||||
|
||||
if c.Storage.Allocations[i].S3APIPort == 0 {
|
||||
c.Storage.Allocations[i].S3APIPort = 3901 + (i * 10)
|
||||
}
|
||||
|
||||
if c.Storage.Allocations[i].AdminPort == 0 {
|
||||
c.Storage.Allocations[i].AdminPort = 3902 + (i * 10)
|
||||
}
|
||||
|
||||
alloc := c.Storage.Allocations[i]
|
||||
|
||||
firewallGarageInbound = append(
|
||||
firewallGarageInbound,
|
||||
ConfigFirewallRule{
|
||||
Port: strconv.Itoa(alloc.S3APIPort),
|
||||
Proto: "tcp",
|
||||
Host: "any",
|
||||
},
|
||||
ConfigFirewallRule{
|
||||
Port: strconv.Itoa(alloc.RPCPort),
|
||||
Proto: "tcp",
|
||||
Host: "any",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
c.VPN.Firewall.Inbound = append(
|
||||
c.VPN.Firewall.Inbound,
|
||||
firewallGarageInbound...,
|
||||
)
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
// Package dnsmasq contains helper functions and types which are useful for
|
||||
// setting up dnsmasq configs, processes, and deployments.
|
||||
package dnsmasq
|
@ -1,58 +0,0 @@
|
||||
package dnsmasq
|
||||
|
||||
import (
|
||||
"cryptic-net/bootstrap"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ConfData describes all the data needed to populate a dnsmasq.conf file.
|
||||
type ConfData struct {
|
||||
Resolvers []string
|
||||
Domain string
|
||||
IP string
|
||||
Hosts []bootstrap.Host
|
||||
}
|
||||
|
||||
var confTpl = template.Must(template.New("").Parse(`
|
||||
port=53
|
||||
|
||||
bind-interfaces
|
||||
listen-address={{ .IP }}
|
||||
|
||||
no-resolv
|
||||
no-hosts
|
||||
|
||||
user=
|
||||
group=
|
||||
|
||||
{{- range $host := .Hosts }}
|
||||
address=/{{ $host.Name }}.hosts.{{ .Domain }}/{{ $host.Nebula.IP }}
|
||||
{{ end -}}
|
||||
|
||||
{{- range .Resolvers }}
|
||||
server={{ . }}
|
||||
{{ end -}}
|
||||
`))
|
||||
|
||||
// WriteConfFile renders a dnsmasq.conf using the given data to a new
|
||||
// file at the given path.
|
||||
func WriteConfFile(path string, data ConfData) error {
|
||||
|
||||
file, err := os.OpenFile(
|
||||
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating file: %w", err)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
if err := confTpl.Execute(file, data); err != nil {
|
||||
return fmt.Errorf("rendering template to file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
3
entrypoint/src/doc.go
Normal file
3
entrypoint/src/doc.go
Normal file
@ -0,0 +1,3 @@
|
||||
// Package globals defines global constants and variables which are valid
|
||||
// across all cryptic-net processes and sub-processes.
|
||||
package crypticnet
|
241
entrypoint/src/env.go
Normal file
241
entrypoint/src/env.go
Normal file
@ -0,0 +1,241 @@
|
||||
package crypticnet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cryptic-net/bootstrap"
|
||||
"cryptic-net/garage"
|
||||
"cryptic-net/yamlutil"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
)
|
||||
|
||||
// Names of various environment variables which get set by the entrypoint.
|
||||
const (
|
||||
DaemonYmlPathEnvVar = "_DAEMON_YML_PATH"
|
||||
BootstrapPathEnvVar = "_BOOTSTRAP_PATH"
|
||||
RuntimeDirPathEnvVar = "_RUNTIME_DIR_PATH"
|
||||
DataDirPathEnvVar = "_DATA_DIR_PATH"
|
||||
)
|
||||
|
||||
// Env contains the values of environment variables, as well as other entities
|
||||
// which are useful across all processes.
|
||||
type Env struct {
|
||||
Context context.Context
|
||||
|
||||
AppDirPath string
|
||||
DaemonYmlPath string
|
||||
RuntimeDirPath string
|
||||
DataDirPath string
|
||||
|
||||
// If NewEnv is called with bootstrapOptional, and a bootstrap file is not
|
||||
// found, then these fields will not be set.
|
||||
BootstrapPath string
|
||||
Bootstrap bootstrap.Bootstrap
|
||||
|
||||
thisDaemon DaemonYml
|
||||
thisDaemonOnce sync.Once
|
||||
}
|
||||
|
||||
func getAppDirPath() string {
|
||||
appDirPath := os.Getenv("APPDIR")
|
||||
if appDirPath == "" {
|
||||
appDirPath = "."
|
||||
}
|
||||
return appDirPath
|
||||
}
|
||||
|
||||
// NewEnv calculates an Env instance based on the APPDIR and XDG envvars.
|
||||
//
|
||||
// If bootstrapOptional is true then NewEnv will first check if a bootstrap file
|
||||
// can be found in the expected places, and if not then it will not populate
|
||||
// BootstrapFS or any other fields based on it.
|
||||
func NewEnv(bootstrapOptional bool) (Env, error) {
|
||||
|
||||
runtimeDirPath := filepath.Join(xdg.RuntimeDir, "cryptic-net")
|
||||
appDirPath := getAppDirPath()
|
||||
|
||||
env := Env{
|
||||
AppDirPath: appDirPath,
|
||||
DaemonYmlPath: filepath.Join(runtimeDirPath, "daemon.yml"),
|
||||
RuntimeDirPath: runtimeDirPath,
|
||||
DataDirPath: filepath.Join(xdg.DataHome, "cryptic-net"),
|
||||
}
|
||||
|
||||
return env.init(bootstrapOptional)
|
||||
}
|
||||
|
||||
// ReadEnv reads an Env from the process's environment variables, rather than
|
||||
// calculating like NewEnv does.
|
||||
func ReadEnv() (Env, error) {
|
||||
|
||||
var err error
|
||||
|
||||
readEnv := func(key string) string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
val := os.Getenv(key)
|
||||
|
||||
if val == "" {
|
||||
err = fmt.Errorf("envvar %q not set", key)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
env := Env{
|
||||
AppDirPath: getAppDirPath(),
|
||||
DaemonYmlPath: readEnv(DaemonYmlPathEnvVar),
|
||||
RuntimeDirPath: readEnv(RuntimeDirPathEnvVar),
|
||||
DataDirPath: readEnv(DataDirPathEnvVar),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Env{}, err
|
||||
}
|
||||
|
||||
return env.init(false)
|
||||
}
|
||||
|
||||
// DataDirBootstrapPath returns the path to the bootstrap file within the user's
|
||||
// data dir. If the file does not exist there it will be found in the AppDirPath
|
||||
// by ReloadBootstrap.
|
||||
func (e Env) DataDirBootstrapPath() string {
|
||||
return filepath.Join(e.DataDirPath, "bootstrap.tgz")
|
||||
}
|
||||
|
||||
// LoadBootstrap loads a Bootstrap from the given path, and returns a copy of
|
||||
// the Env with that Bootstrap set along with the BootstrapPath (or an error).
|
||||
func (e Env) LoadBootstrap(path string) (Env, error) {
|
||||
|
||||
var err error
|
||||
|
||||
if e.Bootstrap, err = bootstrap.FromFile(path); err != nil {
|
||||
return Env{}, fmt.Errorf("parsing bootstrap.tgz at %q: %w", path, err)
|
||||
}
|
||||
|
||||
e.BootstrapPath = path
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (e Env) initBootstrap(bootstrapOptional bool) (Env, error) {
|
||||
|
||||
exists := func(path string) (bool, error) {
|
||||
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("stat'ing %q: %w", path, err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// start by checking if a bootstrap can be found in the user's data
|
||||
// directory. This will only not be the case if daemon has never been
|
||||
// successfully started.
|
||||
{
|
||||
bootstrapPath := e.DataDirBootstrapPath()
|
||||
|
||||
if exists, err := exists(bootstrapPath); err != nil {
|
||||
return Env{}, fmt.Errorf("determining if %q exists: %w", bootstrapPath, err)
|
||||
|
||||
} else if exists {
|
||||
return e.LoadBootstrap(bootstrapPath)
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to checking within the AppDir for a bootstrap which has been
|
||||
// embedded into the binary.
|
||||
{
|
||||
bootstrapPath := filepath.Join(e.AppDirPath, "share/bootstrap.tgz")
|
||||
|
||||
if exists, err := exists(bootstrapPath); err != nil {
|
||||
return Env{}, fmt.Errorf("determining if %q exists: %w", bootstrapPath, err)
|
||||
|
||||
} else if !exists && !bootstrapOptional {
|
||||
return Env{}, fmt.Errorf("boostrap file not found at %q", bootstrapPath)
|
||||
|
||||
} else if exists {
|
||||
return e.LoadBootstrap(bootstrapPath)
|
||||
}
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (e Env) init(bootstrapOptional bool) (Env, error) {
|
||||
|
||||
var cancel context.CancelFunc
|
||||
e.Context, cancel = context.WithCancel(context.Background())
|
||||
|
||||
signalCh := make(chan os.Signal, 2)
|
||||
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
sig := <-signalCh
|
||||
cancel()
|
||||
fmt.Fprintf(os.Stderr, "got signal %v, will exit gracefully\n", sig)
|
||||
|
||||
sig = <-signalCh
|
||||
fmt.Fprintf(os.Stderr, "second interrupt signal %v received, force quitting, there may be zombie children left behind, good luck!\n", sig)
|
||||
|
||||
os.Stderr.Sync()
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
return e.initBootstrap(bootstrapOptional)
|
||||
}
|
||||
|
||||
// ToMap returns the Env as a map of key/value strings. If this map is set into
|
||||
// a process's environment, then that process can read it back using ReadEnv.
|
||||
func (e Env) ToMap() map[string]string {
|
||||
return map[string]string{
|
||||
DaemonYmlPathEnvVar: e.DaemonYmlPath,
|
||||
BootstrapPathEnvVar: e.BootstrapPath,
|
||||
RuntimeDirPathEnvVar: e.RuntimeDirPath,
|
||||
DataDirPathEnvVar: e.DataDirPath,
|
||||
}
|
||||
}
|
||||
|
||||
// ThisDaemon returns the DaemonYml (loaded from DaemonYmlPath) for the
|
||||
// currently running process.
|
||||
func (e Env) ThisDaemon() DaemonYml {
|
||||
e.thisDaemonOnce.Do(func() {
|
||||
if err := yamlutil.LoadYamlFile(&e.thisDaemon, e.DaemonYmlPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
return e.thisDaemon
|
||||
}
|
||||
|
||||
// BinPath returns the absolute path to a binary in the AppDir.
|
||||
func (e Env) BinPath(name string) string {
|
||||
return filepath.Join(e.AppDirPath, "bin", name)
|
||||
}
|
||||
|
||||
// GarageAdminClient will return an AdminClient for a local garage instance, or
|
||||
// it will _panic_ if there is no local instance configured.
|
||||
func (e Env) GarageAdminClient() *garage.AdminClient {
|
||||
|
||||
thisHost := e.Bootstrap.ThisHost()
|
||||
thisDaemon := e.ThisDaemon()
|
||||
|
||||
return garage.NewAdminClient(
|
||||
net.JoinHostPort(
|
||||
thisHost.Nebula.IP,
|
||||
strconv.Itoa(thisDaemon.Storage.Allocations[0].AdminPort),
|
||||
),
|
||||
e.Bootstrap.GarageAdminToken,
|
||||
)
|
||||
}
|
@ -9,17 +9,6 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// AdminClientError gets returned from AdminClient's Do method for non-200
|
||||
// errors.
|
||||
type AdminClientError struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (e AdminClientError) Error() string {
|
||||
return fmt.Sprintf("%d response from admin: %q", e.StatusCode, e.Body)
|
||||
}
|
||||
|
||||
// AdminClient is a helper type for performing actions against the garage admin
|
||||
// interface.
|
||||
type AdminClient struct {
|
||||
@ -75,11 +64,7 @@ func (c *AdminClient) Do(
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
return AdminClientError{
|
||||
StatusCode: res.StatusCode,
|
||||
Body: body,
|
||||
}
|
||||
return fmt.Errorf("unexpected %s response returned", res.Status)
|
||||
}
|
||||
|
||||
if rcv == nil {
|
||||
|
@ -67,7 +67,9 @@ func WriteGarageTomlFile(path string, data GarageTomlData) error {
|
||||
|
||||
defer file.Close()
|
||||
|
||||
if err := garageTomlTpl.Execute(file, data); err != nil {
|
||||
err = RenderGarageToml(file, data)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("rendering template to file: %w", err)
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ module cryptic-net
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
code.betamike.com/cryptic-io/pmux v0.0.0-20221025185405-29241f144a2d
|
||||
code.betamike.com/cryptic-io/pmux v0.0.0-20221020185531-7a7868003822
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/minio/minio-go/v7 v7.0.28
|
||||
|
@ -1,7 +1,5 @@
|
||||
code.betamike.com/cryptic-io/pmux v0.0.0-20221020185531-7a7868003822 h1:c7Eu2h8gXOpOfhC1LvSYLNfiSsWTyvdI1XVpUuqMFHE=
|
||||
code.betamike.com/cryptic-io/pmux v0.0.0-20221020185531-7a7868003822/go.mod h1:cBuEN/rkaM/GH24uQroX/++qDmte+mLudDUqMt6XJWs=
|
||||
code.betamike.com/cryptic-io/pmux v0.0.0-20221025185405-29241f144a2d h1:s6nDTg23o9ujZZnl8ohZBDoG4SqPUyFfvod9DQjwmNU=
|
||||
code.betamike.com/cryptic-io/pmux v0.0.0-20221025185405-29241f144a2d/go.mod h1:cBuEN/rkaM/GH24uQroX/++qDmte+mLudDUqMt6XJWs=
|
||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package crypticnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -12,13 +12,33 @@ import (
|
||||
|
||||
var errDaemonNotRunning = errors.New("no cryptic-net daemon process running")
|
||||
|
||||
func lockFilePath() string {
|
||||
return filepath.Join(envRuntimeDirPath, "lock")
|
||||
// ProcLock is used to lock a process.
|
||||
type ProcLock interface {
|
||||
|
||||
// WriteLock creates a new lock, or errors if the lock is alread held.
|
||||
WriteLock() error
|
||||
|
||||
// AssertLock returns an error if the lock already exists.
|
||||
AssertLock() error
|
||||
}
|
||||
|
||||
func writeLock() error {
|
||||
type procLock struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
lockFilePath := lockFilePath()
|
||||
// NewProcLock returns a ProcLock which will use a file in the given directory
|
||||
// to lock the process.
|
||||
func NewProcLock(dir string) ProcLock {
|
||||
return &procLock{dir: dir}
|
||||
}
|
||||
|
||||
func (pl *procLock) path() string {
|
||||
return filepath.Join(pl.dir, "lock")
|
||||
}
|
||||
|
||||
func (pl *procLock) WriteLock() error {
|
||||
|
||||
lockFilePath := pl.path()
|
||||
|
||||
lockFile, err := os.OpenFile(
|
||||
lockFilePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0400,
|
||||
@ -43,31 +63,11 @@ func writeLock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns a cleanup function which will clean up the created runtime directory.
|
||||
func setupAndLockRuntimeDir() (func(), error) {
|
||||
|
||||
fmt.Fprintf(os.Stderr, "will use runtime directory %q for temporary state\n", envRuntimeDirPath)
|
||||
|
||||
if err := os.MkdirAll(envRuntimeDirPath, 0700); err != nil {
|
||||
return nil, fmt.Errorf("creating directory %q: %w", envRuntimeDirPath, err)
|
||||
|
||||
} else if err := writeLock(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func() {
|
||||
fmt.Fprintf(os.Stderr, "cleaning up runtime directory %q\n", envRuntimeDirPath)
|
||||
if err := os.RemoveAll(envRuntimeDirPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error removing temporary directory %q: %v", envRuntimeDirPath, err)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// checks that the lock file exists and that the process which created it also
|
||||
// still exists.
|
||||
func assertLock() error {
|
||||
func (pl *procLock) AssertLock() error {
|
||||
|
||||
lockFilePath := lockFilePath()
|
||||
lockFilePath := pl.path()
|
||||
|
||||
lockFile, err := os.Open(lockFilePath)
|
||||
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
|
||||
stdenv,
|
||||
glibcStatic,
|
||||
|
||||
}: stdenv.mkDerivation rec {
|
||||
|
||||
pname = "dnsmasq";
|
||||
version = "2.85";
|
||||
|
||||
src = builtins.fetchurl {
|
||||
url = "https://www.thekelleys.org.uk/dnsmasq/${pname}-${version}.tar.xz";
|
||||
sha256 = "sha256-rZjTgD32h+W5OAgPPSXGKP5ByHh1LQP7xhmXh/7jEvo=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ glibcStatic ];
|
||||
|
||||
makeFlags = [
|
||||
"LDFLAGS=-static"
|
||||
"DESTDIR="
|
||||
"BINDIR=$(out)/bin"
|
||||
"MANDIR=$(out)/man"
|
||||
"LOCALEDIR=$(out)/share/locale"
|
||||
];
|
||||
}
|
@ -24,7 +24,7 @@ in rec {
|
||||
env = buildEnv {
|
||||
name = "cryptic-net-garage";
|
||||
paths = [
|
||||
garage.pkgs.amd64.release
|
||||
garage
|
||||
minioClient
|
||||
];
|
||||
};
|
||||
|
13
nix/pkgs.nix
13
nix/pkgs.nix
@ -2,8 +2,7 @@ rec {
|
||||
|
||||
overlays = [
|
||||
|
||||
# Make buildGoModules use static compilation by default, and use go 1.18
|
||||
# everywhere.
|
||||
# Make both buildGoModules use static compilation by default.
|
||||
(final: prev:
|
||||
|
||||
let
|
||||
@ -18,11 +17,19 @@ rec {
|
||||
in {
|
||||
|
||||
go = prev.go_1_18;
|
||||
buildGoModule = args: prev.buildGo118Module (buildArgs // args);
|
||||
buildGoModule = args: prev.buildGoModule (buildArgs // args);
|
||||
buildGo118Module = args: prev.buildGo118Module (buildArgs // args);
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
(final: prev: { rebase = prev.callPackage ./rebase.nix {}; })
|
||||
(final: prev: { yq-go = prev.callPackage ./yq-go.nix {}; })
|
||||
|
||||
(final: prev: { nebula = prev.callPackage ./nebula.nix {
|
||||
buildGoModule = prev.buildGo118Module;
|
||||
}; })
|
||||
|
||||
];
|
||||
|
||||
version = "22-05";
|
||||
|
18
nix/rebase.nix
Normal file
18
nix/rebase.nix
Normal file
@ -0,0 +1,18 @@
|
||||
# rebase is a helper which takes all files/dirs under oldroot, and
|
||||
# creates a new derivation with those files/dirs copied under newroot
|
||||
# (where newroot is a relative path to the root of the derivation).
|
||||
|
||||
{
|
||||
|
||||
stdenv,
|
||||
|
||||
}: name: oldroot: newroot: stdenv.mkDerivation {
|
||||
|
||||
inherit name oldroot newroot;
|
||||
|
||||
builder = builtins.toFile "builder.sh" ''
|
||||
source $stdenv/setup
|
||||
mkdir -p "$out"/"$newroot"
|
||||
cp -rL "$oldroot"/* "$out"/"$newroot"
|
||||
'';
|
||||
}
|
19
nix/yq-go.nix
Normal file
19
nix/yq-go.nix
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
|
||||
buildGoModule,
|
||||
fetchFromGitHub,
|
||||
|
||||
}: buildGoModule rec {
|
||||
|
||||
pname = "yq-go";
|
||||
version = "4.21.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "mikefarah";
|
||||
repo = "yq";
|
||||
rev = "v${version}";
|
||||
sha256 = "sha256-283xe7FVHYSsRl4cZD7WDzIW1gqNAFsNrWYJkthZheU=";
|
||||
};
|
||||
|
||||
vendorSha256 = "sha256-F11FnDYJ59aKrdRXDPpKlhX52yQXdaN1sblSkVI2j9w=";
|
||||
}
|
Loading…
Reference in New Issue
Block a user