Reimplement dnsmasq-entrypoint in go

This allowed for deleting all script utilities and environment variable
logic.
This commit is contained in:
Brian Picciano 2022-10-26 22:18:16 +02:00
parent 2200d85992
commit 03618ba72c
14 changed files with 146 additions and 247 deletions

View File

@ -57,9 +57,11 @@ in rec {
entrypoint = pkgs.callPackage ./entrypoint {}; entrypoint = pkgs.callPackage ./entrypoint {};
dnsmasq = (pkgs.callPackage ./dnsmasq { dnsmasq = (pkgs.callPackage ./nix/dnsmasq.nix {
glibcStatic = pkgs.glibc.static; glibcStatic = pkgs.glibc.static;
}).env; });
nebula = pkgs.callPackage ./nix/nebula.nix {};
garage = (pkgs.callPackage ./nix/garage.nix {}).env; garage = (pkgs.callPackage ./nix/garage.nix {}).env;
@ -69,18 +71,10 @@ in rec {
name = "cryptic-net-AppDir"; name = "cryptic-net-AppDir";
paths = [ paths = [
pkgs.pkgsStatic.bash
pkgs.pkgsStatic.coreutils
pkgs.pkgsStatic.gnutar
pkgs.pkgsStatic.gzip
# custom packages from ./pkgs.nix
pkgs.yq-go
pkgs.nebula
./AppDir ./AppDir
version version
dnsmasq dnsmasq
nebula
garage garage
entrypoint entrypoint

View File

@ -1,36 +0,0 @@
# 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"

View File

@ -1,39 +0,0 @@
{
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
];
};
}

View File

@ -1,41 +0,0 @@
# 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
#
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

View File

@ -94,16 +94,14 @@ func runDaemonPmuxOnce(env crypticnet.Env) (crypticnet.Env, error) {
return crypticnet.Env{}, fmt.Errorf("generating nebula config: %w", err) return crypticnet.Env{}, fmt.Errorf("generating nebula config: %w", err)
} }
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(env)
if err != nil {
return crypticnet.Env{}, fmt.Errorf("generating dnsmasq config: %w", err)
}
pmuxProcConfigs := []pmuxlib.ProcessConfig{ pmuxProcConfigs := []pmuxlib.ProcessConfig{
nebulaPmuxProcConfig, nebulaPmuxProcConfig,
{ dnsmasqPmuxProcConfig,
Name: "dnsmasq",
Cmd: "bash",
Args: []string{"dnsmasq-entrypoint"},
StartAfterFunc: func(ctx context.Context) error {
return waitForNebula(ctx, env)
},
},
} }
if len(thisDaemon.Storage.Allocations) > 0 { if len(thisDaemon.Storage.Allocations) > 0 {
@ -302,15 +300,6 @@ var subCmdDaemon = subCmd{
return fmt.Errorf("merging daemon.yml into bootstrap data: %w", err) 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 { for {
if env, err = runDaemonPmuxOnce(env); errors.Is(err, context.Canceled) { if env, err = runDaemonPmuxOnce(env); errors.Is(err, context.Canceled) {

View File

@ -0,0 +1,45 @@
package main
import (
crypticnet "cryptic-net"
"cryptic-net/bootstrap"
"cryptic-net/dnsmasq"
"fmt"
"path/filepath"
"sort"
"code.betamike.com/cryptic-io/pmux/pmuxlib"
)
func dnsmasqPmuxProcConfig(env crypticnet.Env) (pmuxlib.ProcessConfig, error) {
thisDaemon := env.ThisDaemon()
confPath := filepath.Join(env.RuntimeDirPath, "dnsmasq.conf")
hostsSlice := make([]bootstrap.Host, 0, len(env.Bootstrap.Hosts))
for _, host := range env.Bootstrap.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: thisDaemon.DNS.Resolvers,
Domain: env.Bootstrap.AdminCreationParams.Domain,
IP: env.Bootstrap.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
}

View File

@ -0,0 +1,3 @@
// Package dnsmasq contains helper functions and types which are useful for
// setting up dnsmasq configs, processes, and deployments.
package dnsmasq

View File

@ -0,0 +1,58 @@
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
}

View File

@ -19,14 +19,6 @@ import (
"github.com/adrg/xdg" "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 // Env contains the values of environment variables, as well as other entities
// which are useful across all processes. // which are useful across all processes.
type Env struct { type Env struct {
@ -74,40 +66,6 @@ func NewEnv(bootstrapOptional bool) (Env, error) {
return env.init(bootstrapOptional) 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 // 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 // data dir. If the file does not exist there it will be found in the AppDirPath
// by ReloadBootstrap. // by ReloadBootstrap.
@ -197,17 +155,6 @@ func (e Env) init(bootstrapOptional bool) (Env, error) {
return e.initBootstrap(bootstrapOptional) 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 // ThisDaemon returns the DaemonYml (loaded from DaemonYmlPath) for the
// currently running process. // currently running process.
func (e Env) ThisDaemon() DaemonYml { func (e Env) ThisDaemon() DaemonYml {

View File

@ -67,9 +67,7 @@ func WriteGarageTomlFile(path string, data GarageTomlData) error {
defer file.Close() defer file.Close()
err = RenderGarageToml(file, data) if err := garageTomlTpl.Execute(file, data); err != nil {
if err != nil {
return fmt.Errorf("rendering template to file: %w", err) return fmt.Errorf("rendering template to file: %w", err)
} }

25
nix/dnsmasq.nix Normal file
View File

@ -0,0 +1,25 @@
{
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"
];
}

View File

@ -2,7 +2,8 @@ rec {
overlays = [ overlays = [
# Make both buildGoModules use static compilation by default. # Make buildGoModules use static compilation by default, and use go 1.18
# everywhere.
(final: prev: (final: prev:
let let
@ -17,19 +18,11 @@ rec {
in { in {
go = prev.go_1_18; go = prev.go_1_18;
buildGoModule = args: prev.buildGoModule (buildArgs // args); buildGoModule = args: prev.buildGo118Module (buildArgs // args);
buildGo118Module = args: prev.buildGo118Module (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"; version = "22-05";

View File

@ -1,18 +0,0 @@
# 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"
'';
}

View File

@ -1,19 +0,0 @@
{
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=";
}