initial commit
This commit is contained in:
commit
010643757e
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
*.7z
|
||||||
|
*.bak
|
||||||
|
*.deb
|
||||||
|
*.jar
|
||||||
|
*.rar
|
||||||
|
*.run
|
||||||
|
*.sig
|
||||||
|
*.tar
|
||||||
|
*.tar.bz2
|
||||||
|
*.tar.gz
|
||||||
|
*.tar.xz
|
||||||
|
*.tbz
|
||||||
|
*.tbz2
|
||||||
|
*.tgz
|
||||||
|
*.txz
|
||||||
|
*.zip
|
||||||
|
.*.swp
|
||||||
|
.editix
|
||||||
|
|
||||||
|
# https://github.com/github/gitignore/blob/master/Go.gitignore
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
#*.test
|
||||||
|
test.sh
|
||||||
|
|
||||||
|
# Built binary
|
||||||
|
bin/*
|
||||||
|
poc/*
|
||||||
|
_poc/*
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Example configs.
|
||||||
|
_exampledata/
|
||||||
|
|
||||||
|
# Don't include rendered doc
|
||||||
|
#/README.html
|
||||||
|
|
||||||
|
# Example code.
|
||||||
|
_demo/
|
87
build.sh
Executable file
87
build.sh
Executable file
@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# This is not portable. It has bashisms.
|
||||||
|
|
||||||
|
BUILD_TIME="$(date '+%s')"
|
||||||
|
BUILD_USER="$(whoami)"
|
||||||
|
BUILD_SUDO_USER="${SUDO_USER}"
|
||||||
|
BUILD_HOST="$(hostname)"
|
||||||
|
|
||||||
|
# Check to make sure git is available.
|
||||||
|
if ! command -v git &> /dev/null;
|
||||||
|
then
|
||||||
|
echo "Git is not available; automatic version handling unsupported."
|
||||||
|
echo "You must build by calling 'go build' directly in the respective directories."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check git directory/repository.
|
||||||
|
if ! git rev-parse --is-inside-work-tree &>/dev/null;
|
||||||
|
then
|
||||||
|
echo "Not running inside a git work tree; automatic version handling unsupported/build script unsupported."
|
||||||
|
echo "You must build by calling 'go build' directly in the respective directories instead."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If it has a tag in the path of the current HEAD that matches a version string...
|
||||||
|
# I wish git describe supported regex. It does not; only globs. Gross.
|
||||||
|
# If there's a bug anywhere, it's here.
|
||||||
|
if git describe --tags --abbrev=0 --match "v[0-9]*" HEAD &> /dev/null;
|
||||||
|
then
|
||||||
|
# It has a tag we can use.
|
||||||
|
CURRENT_VER="$(git describe --tags --abbrev=0 --match "v[0-9]*" HEAD)"
|
||||||
|
COMMITS_SINCE="$(git rev-list --count ${CURRENT_VER}..HEAD)"
|
||||||
|
else
|
||||||
|
# No tag available.
|
||||||
|
CURRENT_VER=""
|
||||||
|
COMMITS_SINCE=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If it's dirty (staged but not committed or unstaged files)...
|
||||||
|
if ! git diff-index --quiet HEAD;
|
||||||
|
then
|
||||||
|
# It's dirty.
|
||||||
|
IS_DIRTY="1"
|
||||||
|
else
|
||||||
|
# It's clean.
|
||||||
|
IS_DIRTY="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the commit hash of the *most recent* commit in the path of current HEAD...
|
||||||
|
CURRENT_HASH="$(git rev-parse --verify HEAD)"
|
||||||
|
# The same as above, but abbreviated.
|
||||||
|
CURRENT_SHORT="$(git rev-parse --verify --short HEAD)"
|
||||||
|
|
||||||
|
# Get the module name.
|
||||||
|
MODPATH="$(sed -n -re 's@^\s*module\s+(.*)(//.*)?$@\1@p' go.mod)"
|
||||||
|
|
||||||
|
# Build the ldflags string.
|
||||||
|
# BEHOLD! BASH WITCHCRAFT.
|
||||||
|
LDFLAGS_STR="\
|
||||||
|
-X '${MODPATH}/version.sourceControl=git' \
|
||||||
|
-X '${MODPATH}/version.version=${CURRENT_VER}' \
|
||||||
|
-X '${MODPATH}/version.commitHash=${CURRENT_HASH}' \
|
||||||
|
-X '${MODPATH}/version.commitShort=${CURRENT_SHORT}' \
|
||||||
|
-X '${MODPATH}/version.numCommitsAfterTag=${COMMITS_SINCE}' \
|
||||||
|
-X '${MODPATH}/version.isDirty=${IS_DIRTY}' \
|
||||||
|
-X '${MODPATH}/version.buildTime=${BUILD_TIME}' \
|
||||||
|
-X '${MODPATH}/version.buildUser=${BUILD_USER}' \
|
||||||
|
-X '${MODPATH}/version.buildSudoUser=${BUILD_SUDO_USER}' \
|
||||||
|
-X '${MODPATH}/version.buildHost=${BUILD_HOST}'"
|
||||||
|
|
||||||
|
# And finally build.
|
||||||
|
mkdir -p ./bin/
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
|
cmd="gobroke"
|
||||||
|
# Linux
|
||||||
|
echo -n "Building ./bin/${cmd}..."
|
||||||
|
go build \
|
||||||
|
-o "./bin/${cmd}" \
|
||||||
|
-ldflags \
|
||||||
|
"${LDFLAGS_STR}" \
|
||||||
|
cmd/${cmd}/*.go
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
|
echo "Build complete."
|
31
cachedb/_static/cache.schema.sql
Normal file
31
cachedb/_static/cache.schema.sql
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
CREATE TABLE tunnels (
|
||||||
|
tun_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
cksum_crc32 INTEGER NOT NULL,
|
||||||
|
"desc" TEXT,
|
||||||
|
server_v4 TEXT NOT NULL,
|
||||||
|
current_client_v4 TEXT NOT NULL,
|
||||||
|
tunnel_server_v6 TEXT NOT NULL,
|
||||||
|
tunnel_client_v6 TEXT NOT NULL,
|
||||||
|
prefix_64 TEXT NOT NULL,
|
||||||
|
prefix_48 TEXT,
|
||||||
|
rdns_1 TEXT,
|
||||||
|
rdns_2 TEXT,
|
||||||
|
rdns_3 TEXT,
|
||||||
|
rdns_4 TEXT,
|
||||||
|
rdns_5 TEXT,
|
||||||
|
created INTEGER NOT NULL,
|
||||||
|
checked INTEGER NOT NULL,
|
||||||
|
updated INTEGER
|
||||||
|
);
|
||||||
|
CREATE TABLE client_ips (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
tun_id INTEGER NOT NULL,
|
||||||
|
client_ip INTEGER NOT NULL,
|
||||||
|
when_set INTEGER NOT NULL, when_fetched INTEGER,
|
||||||
|
CONSTRAINT client_ips_tunnels_FK FOREIGN KEY (tun_id) REFERENCES tunnels(tun_id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO sqlite_sequence VALUES('client_ips',0);
|
||||||
|
COMMIT;
|
||||||
|
PRAGMA foreign_keys=ON;
|
15
cachedb/consts.go
Normal file
15
cachedb/consts.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package cachedb
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed "_static/cache.schema.sql"
|
||||||
|
schemaBytes []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SelectTunnels string = ""
|
||||||
|
SelectTunnelById string = ""
|
||||||
|
)
|
35
cachedb/funcs.go
Normal file
35
cachedb/funcs.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package cachedb
|
||||||
|
|
||||||
|
import (
|
||||||
|
`os`
|
||||||
|
`path/filepath`
|
||||||
|
|
||||||
|
`r00t2.io/sysutils/paths`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewCache returns a Cache from path to SQLite file `db` (or ":memory:" for an in-memory one).
|
||||||
|
|
||||||
|
It will be created if it doesn't exist for persistent caches.
|
||||||
|
*/
|
||||||
|
func NewCache(db string) (c *Cache, err error) {
|
||||||
|
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
switch db {
|
||||||
|
case ":memory:":
|
||||||
|
default:
|
||||||
|
if exists, err = paths.RealPathExists(&db); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
if err = os.MkdirAll(filepath.Dir(db), 0700); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.WriteFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
27
cachedb/types.go
Normal file
27
cachedb/types.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package cachedb
|
||||||
|
|
||||||
|
import (
|
||||||
|
`time`
|
||||||
|
|
||||||
|
`github.com/jmoiron/sqlx`
|
||||||
|
`r00t2.io/gobroke/tunnelbroker`
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type TunnelDB struct {
|
||||||
|
*tunnelbroker.Tunnel
|
||||||
|
Created time.Time `db:"created"`
|
||||||
|
Checked time.Time `db:"checked"`
|
||||||
|
Updated time.Time `db:"updated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientIpDB struct {
|
||||||
|
ID uint64 `db:"id"`
|
||||||
|
TunID uint64 `db:"tun_id"`
|
||||||
|
*tunnelbroker.FetchedIP
|
||||||
|
Set time.Time `db:"when_set"`
|
||||||
|
Fetched time.Time `db:"when_fetched"`
|
||||||
|
}
|
1
cmd/gobroke/main.go
Normal file
1
cmd/gobroke/main.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package gobroke
|
82
conf/_testdata/test.json
Normal file
82
conf/_testdata/test.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"default_username": "default_user",
|
||||||
|
"1tun": true,
|
||||||
|
"cache_db": "/var/cache/gobroke.db",
|
||||||
|
"cache_perms": {
|
||||||
|
"file": {
|
||||||
|
"user": "",
|
||||||
|
"group": "",
|
||||||
|
"mode": 384
|
||||||
|
},
|
||||||
|
"dir": {
|
||||||
|
"user": "",
|
||||||
|
"group": "",
|
||||||
|
"mode": 448
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tunnels": [
|
||||||
|
{
|
||||||
|
"tun_id": 123,
|
||||||
|
"addr": "203.0.113.1",
|
||||||
|
"mtu": 1450,
|
||||||
|
"username": "specific_user",
|
||||||
|
"update_key": "abcdef",
|
||||||
|
"cfg_tpls": [
|
||||||
|
{
|
||||||
|
"tpl": "/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl",
|
||||||
|
"dest": "/etc/dnsmasq.d/ra_dhcpv6.conf",
|
||||||
|
"perms": {
|
||||||
|
"file": {
|
||||||
|
"user": "",
|
||||||
|
"group": "",
|
||||||
|
"mode": 384
|
||||||
|
},
|
||||||
|
"dir": {
|
||||||
|
"user": "",
|
||||||
|
"group": "",
|
||||||
|
"mode": 448
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cmds": [
|
||||||
|
{
|
||||||
|
"bin": "/usr/local/bin/somecmd",
|
||||||
|
"args": [
|
||||||
|
"-f", "foo"
|
||||||
|
],
|
||||||
|
"isol8_env": false,
|
||||||
|
"env": [
|
||||||
|
"SOMEENV=SOMEVAL"
|
||||||
|
],
|
||||||
|
"on_change": true,
|
||||||
|
"is_tpl": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tpl": "/etc/gobroke/tpl/stat.tpl",
|
||||||
|
"dest": "/tmp/gobroke.dump"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cmds": [
|
||||||
|
{
|
||||||
|
"bin": "systemctl",
|
||||||
|
"args": [
|
||||||
|
"restart",
|
||||||
|
"someservice"
|
||||||
|
],
|
||||||
|
"on_change": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tun_id": 456,
|
||||||
|
"username": "specific_user",
|
||||||
|
"update_key": "defghi"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cmds": [
|
||||||
|
{
|
||||||
|
"bin": "/usr/local/bin/alltunsprogram"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
289
conf/_testdata/test.toml
Normal file
289
conf/_testdata/test.toml
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
# DefaultUsername specifies the default username to use for
|
||||||
|
# authenticating to tunnelbroker.net.
|
||||||
|
# It is optional, as the username can be specified for each Tunnel,
|
||||||
|
# but at least one or the other *must* be provided.
|
||||||
|
# This makes it easier if you have multiple tunnels under the same account
|
||||||
|
# (as possible in higher levels of HE IPv6 certification).
|
||||||
|
# If a username is specified in Tunnel.Username, it will be used.
|
||||||
|
# If not (and, of course, DefaultUsername is specified), then
|
||||||
|
# DefaultUsername will be used for that Tunnel.
|
||||||
|
DefaultUsername = "default_user"
|
||||||
|
|
||||||
|
# If SingleTunnel is true, each Tunnel below will be run in order instead of
|
||||||
|
# concurrently.
|
||||||
|
# If there is any concern about race conditions (e.g. the same service being
|
||||||
|
# restarted by multiple tunnels, etc.), then it is HIGHLY RECOMMENDED
|
||||||
|
# you set this to true.
|
||||||
|
SingleTunnel = true
|
||||||
|
|
||||||
|
# CacheDbPath is entirely optional.
|
||||||
|
# If not provided, results will be cached in RAM (and thus lost on reboot
|
||||||
|
# or program termination/restart).
|
||||||
|
# (This can be explicitly specified by using the value ':memory:'.)
|
||||||
|
# If provided, it should be a path to a file to use as a SQLite3 database
|
||||||
|
# that holds cached information.
|
||||||
|
# The information that is cached contains only:
|
||||||
|
# * each Tunnel.TunnelID
|
||||||
|
# * the associated tunnelbroker.FetchedTunnel
|
||||||
|
# * a CRC32 of all configuration (as defined in this file) for that Tunnel
|
||||||
|
# The UpdateKey and other configuration defined here (aside from
|
||||||
|
# Tunnel.TunnelID, and Tunnel.ExplicitClientIP if specified) are
|
||||||
|
# NOT stored.
|
||||||
|
# Any tunnel present in a persistent cache DB but *not* defined in the
|
||||||
|
# running GoBroke config will be removed.
|
||||||
|
# Note that the cache DB primary key is based on the Tunnel.TunnelID,
|
||||||
|
# as one cannot define multiple client endpoints for the same tunnel.
|
||||||
|
CacheDbPath = '/var/cache/gobroke.db'
|
||||||
|
|
||||||
|
# CacheDbPerms specify the permissions for CacheDbPath.
|
||||||
|
# This directive is completely optional, and is
|
||||||
|
# ignored if CacheDbPath is ":memory:" (or unspecified).
|
||||||
|
# If not specified (and CacheDbPath is persistent),
|
||||||
|
# then the runtime user's umask and effective UID/GID
|
||||||
|
# is used if creating a new database file.
|
||||||
|
# If the file exists and permissions are defined, they will
|
||||||
|
# be enforced.
|
||||||
|
# If the file exists but no permissions are defined, they
|
||||||
|
# will be left as-is.
|
||||||
|
[CacheDbPerms]
|
||||||
|
# Permissions are/may be defined for both the file being written
|
||||||
|
# and the parent directory (see below).
|
||||||
|
[CacheDbPerms.File]
|
||||||
|
# The User is optional.
|
||||||
|
# If unspecified, the default behavir mentioned above is performed.
|
||||||
|
# If specified as an empty string, the runtime EUID is enforced.
|
||||||
|
# Otherwise it may be a username or a UID (checked in that order).
|
||||||
|
User = ""
|
||||||
|
# Group is also optional, and follows the same logic except
|
||||||
|
# for EGID/groupnames/GIDs.
|
||||||
|
Group = ""
|
||||||
|
# Mode is optional also.
|
||||||
|
# It *must* be equal to the octal mode bits (e.g. it must be an
|
||||||
|
# unsigned integer), but may be represented in multiple ways.
|
||||||
|
# e.g.:
|
||||||
|
# Mode = 0o0600
|
||||||
|
# Mode = 0o600
|
||||||
|
# Mode = 0x0180
|
||||||
|
# Mode = 0x180
|
||||||
|
# Mode = 0b110000000
|
||||||
|
# Mode = 384
|
||||||
|
# All evaluate to the exact same value in TOML:
|
||||||
|
# https://toml.io/en/v1.0.0#integer
|
||||||
|
# For consistency with `chmod(1)`, it is recommended to use the
|
||||||
|
# octal representation (0o0600 or 0o600 above).
|
||||||
|
# If you need help determining what number you should actually use,
|
||||||
|
# you can use the calculator here:
|
||||||
|
# https://rubendougall.co.uk/projects/permissions-calculator/
|
||||||
|
# (source: https://github.com/Ruben9922/permissions-calculator )
|
||||||
|
# (Supports "special" bits)
|
||||||
|
# or here:
|
||||||
|
# https://wintelguy.com/permissions-calc.pl
|
||||||
|
# (beware of ads)
|
||||||
|
# (provides an explanation of the bits)
|
||||||
|
# Or see https://en.wikipedia.org/wiki/Chmod
|
||||||
|
Mode = 0o0600
|
||||||
|
# Dir permissions specifiy permissions/ownership of the parent directory of the cache DB.
|
||||||
|
# The same rules, logic, behavior, etc. as in CacheDbPerms.File apply here.
|
||||||
|
[CacheDbPerms.Dir]
|
||||||
|
User = ""
|
||||||
|
Group = ""
|
||||||
|
Mode = 0o0700
|
||||||
|
|
||||||
|
|
||||||
|
#############
|
||||||
|
## Tunnels ##
|
||||||
|
#############
|
||||||
|
|
||||||
|
# Each Tunnel represents a single tunnelbroker.net tunnel configuration.
|
||||||
|
# Note that each Tunnel is run concurrently. If this is undesired due to
|
||||||
|
# potential race conditions, set the root-level directive SingleTunnel
|
||||||
|
# to true.
|
||||||
|
[[Tunnel]]
|
||||||
|
# The TunnelID can be found by logging into https://tunnelbroker.net/ and,
|
||||||
|
# at the "Main Page" that loads when logging in, clicking on the desired
|
||||||
|
# tunnel name.
|
||||||
|
# The tunnel ID is then displayed in both the URL bar:
|
||||||
|
# https://tunnelbroker.net/tunnel_detail.php?tid=<TunnelID>
|
||||||
|
# And as the first line on the first tab ("IPv6 Tunnel" tab),
|
||||||
|
# labeled "Tunnel ID".
|
||||||
|
TunnelID = 123
|
||||||
|
# If you wish to use a different or explicit "Client IPv4 address",
|
||||||
|
# this can be specified via ExplicitClientIP.
|
||||||
|
# If it is empty or is not specified, the public IP of this host will be determined
|
||||||
|
# via an external service.
|
||||||
|
# This *must* be an IPv4 address (if specified).
|
||||||
|
ExplicitClientIP = '203.0.113.1'
|
||||||
|
# If you have specified a custom MTU under the "Advanced" tab for this tunnel,
|
||||||
|
# you can set this value here.
|
||||||
|
# If you have not set a custom one, leave this option unspecified;
|
||||||
|
# the default (and maximum allowed), 1480 MTU, will be used in that case.
|
||||||
|
MTU = 1450
|
||||||
|
# The Username field is optional IF DefaultUsername was specified.
|
||||||
|
# This also allows you to specify tunnels from different accounts
|
||||||
|
# by providing a tunnel-specific username.
|
||||||
|
Username = "specific_user"
|
||||||
|
# The UpdateKey can be found under the "Advanced" tab on your tunnelbroker.net
|
||||||
|
# tunnel's page, labeled "Update Key".
|
||||||
|
# Your real token is likely to be a bit longer and more random.
|
||||||
|
# This token is used to not only update the client-side tunnel IP but also to
|
||||||
|
# query the HE Tunnelbroker "API" (it's really just a single endpoint)
|
||||||
|
# to get the tunnel configuration.
|
||||||
|
UpdateKey = "abcdef"
|
||||||
|
|
||||||
|
|
||||||
|
######################
|
||||||
|
## Config Templates ##
|
||||||
|
######################
|
||||||
|
|
||||||
|
# Each ConfigTemplate consists of a path to a template file and a destination
|
||||||
|
# file at the bere minimum. In addition, Commands may be provided.
|
||||||
|
# Any paths leading up to Destination that don't exist will (attempt to be)
|
||||||
|
# created.
|
||||||
|
# The template is always rendered in memory, but the destination is only written
|
||||||
|
# if:
|
||||||
|
# * The Destination doesn't exist
|
||||||
|
# * The Destination differs from the buffered rendering of the template
|
||||||
|
# Commands are optional, and are a list of commands to be run.
|
||||||
|
# Their running may be restricted to only if the tunnel information/IP
|
||||||
|
# information has changed, always run, or the inverse of all conditions.
|
||||||
|
[[Tunnel.ConfigTemplate]]
|
||||||
|
# Template points to where the template file can be found.
|
||||||
|
# It must be in a Golang text/template syntax/format; see:
|
||||||
|
# https://pkg.go.dev/text/template
|
||||||
|
# Refer to the library's definition of the tunnelbroker.FetchedTunnel struct;
|
||||||
|
# this is the object that is passed to the template.
|
||||||
|
Template = "/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl"
|
||||||
|
# Destination is the file to write to.
|
||||||
|
# It will only be written to if:
|
||||||
|
# * The path does not exist
|
||||||
|
# * The path exists but is different from the in-memory rendered buffer
|
||||||
|
# An attempt will be made to create any leading components that are not
|
||||||
|
# present.
|
||||||
|
# It is recommended to enforce permissions/ownership of these via the
|
||||||
|
# Commands.
|
||||||
|
Destination = "/etc/dnsmasq.d/ra_dhcpv6.conf"
|
||||||
|
|
||||||
|
|
||||||
|
#################################
|
||||||
|
## Config Template Permissions ##
|
||||||
|
#################################
|
||||||
|
|
||||||
|
# Permissions can be defined for the Destionation file.
|
||||||
|
# They are completely optional, in which case the default umask, user,
|
||||||
|
# group, etc. for the runtime user will be used, and permissions/ownership
|
||||||
|
# will not be enforced for existing Destination files.
|
||||||
|
# It follows the same syntax, logic, behavior, etc. as CacheDbPerms.
|
||||||
|
[[Tunnel.ConfigTemplate.Permissions]]
|
||||||
|
[[Tunnel.ConfigTemplate.Permissions.File]]
|
||||||
|
User = ""
|
||||||
|
Group = ""
|
||||||
|
Mode = 0o0600
|
||||||
|
[[Tunnel.ConfigTemplate.Permissions.Dir]]
|
||||||
|
User = ""
|
||||||
|
Group = ""
|
||||||
|
Mode = 0o0700
|
||||||
|
|
||||||
|
|
||||||
|
##############################
|
||||||
|
## Config Template Commands ##
|
||||||
|
##############################
|
||||||
|
|
||||||
|
# Commands are a collection of commands to run as part of this template
|
||||||
|
# run.
|
||||||
|
# Multiple Commands may be specified; they will be run in the order specified.
|
||||||
|
# The below Command would be equivalent to:
|
||||||
|
# SOMEENV=SOMEVAL /usr/local/bin/somecmd -f foo
|
||||||
|
# on the shell.
|
||||||
|
[[Tunnel.ConfigTemplate.Command]]
|
||||||
|
# ProgramPath should be the absolute path to the binary to run.
|
||||||
|
# It behaves as an (os/)exec.Cmd.Path (https://pkg.go.dev/os/exec#Cmd),
|
||||||
|
# It is recommended to use an absolute path.
|
||||||
|
ProgramPath = '/usr/local/bin/somecmd'
|
||||||
|
# Args are optional for a Command.
|
||||||
|
# They should conform to the rules for (os/)exec.Cmd.Args.
|
||||||
|
Args = [
|
||||||
|
'-f', 'foo',
|
||||||
|
]
|
||||||
|
# If IsolatedEnv is false (the default), the runtime environment variables
|
||||||
|
# will be applied to the command.
|
||||||
|
# If true, *only* the EnvVars, if specified, will be used for the spawned
|
||||||
|
# command (an empty environment will be used if IsolateEnv is true and
|
||||||
|
# no EnvVars are specified).
|
||||||
|
IsolatedEnv = false
|
||||||
|
# If provided, EnvVars can be used to add/replace environment variables.
|
||||||
|
# They should conform to the rules for (os/)exec.Cmd.Env.
|
||||||
|
# Whether they are added to/selectively replace or completely replace
|
||||||
|
# the current runtime environment variables depends on how IsolateEnv
|
||||||
|
# is configured.
|
||||||
|
EnvVars = [
|
||||||
|
'SOMEENV=SOMEVAL',
|
||||||
|
]
|
||||||
|
# If OnChange is true, this Command will run *only if SOMETHING CHANGED*.
|
||||||
|
# (e.g. a /48 was added to the tunnel, the client IP is different, etc.)
|
||||||
|
# If false, this Command will run *only if NOTHING CHANGED*.
|
||||||
|
# If unspecified, the default is to always run this command regardless
|
||||||
|
# of change status.
|
||||||
|
# The very first (successful) run of a Tunnel is considered a "change",
|
||||||
|
# as is writing out this template to disk as a new file.
|
||||||
|
OnChange = true
|
||||||
|
# By default, this Command will be run literally/as-is.
|
||||||
|
# However, in some cases it may be useful to dynamically template out
|
||||||
|
# commands to run.
|
||||||
|
# If IsTemplate is set to true, then this Command.ProgramPath, each
|
||||||
|
# of the Command.Args, and each of the Command.EnvVars will be
|
||||||
|
# treated as Golang text/template strings as well, and will also
|
||||||
|
# be passed a tunnelbroker.FetchedTunnel.
|
||||||
|
# Note that if IsolateEnv is false, runtime/inherited environment
|
||||||
|
# variables will *not* be templated.
|
||||||
|
# It is recommended to not enable this unless necessary as it can add
|
||||||
|
# a non-negligible amount of resource overhead/execution time.
|
||||||
|
IsTemplate = false
|
||||||
|
|
||||||
|
#######################################################################
|
||||||
|
|
||||||
|
# Multiple ConfigTemplates may be specified.
|
||||||
|
[[Tunnel.ConfigTemplate]]
|
||||||
|
Template = "/etc/gobroke/tpl/stat.tpl"
|
||||||
|
Destination = "/tmp/gobroke.dump"
|
||||||
|
|
||||||
|
|
||||||
|
#####################
|
||||||
|
## Tunnel Commands ##
|
||||||
|
#####################
|
||||||
|
|
||||||
|
# Each Tunnel also supports its *own* commands. The syntax, spcification,
|
||||||
|
# behavior, etc. is the same as the Tunnel.ConfigTemplate.Command.
|
||||||
|
# These are executed after all Tunnel.ConfigTemplate (if any) are executed.
|
||||||
|
# This is particularly useful for consolidating service restarts.
|
||||||
|
[[Tunnel.Command]]
|
||||||
|
ProgramPath = 'systemctl'
|
||||||
|
Args = [
|
||||||
|
'restart',
|
||||||
|
'someservice',
|
||||||
|
]
|
||||||
|
# OnChange in a Tunnel.Command is scoped to any updates of the tunnel
|
||||||
|
# and any changes in ANY of the Tunnel.ConfigTemplate specified
|
||||||
|
# for this Tunnel (if true and ConfigTemplate were specified).
|
||||||
|
OnChange = true
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# Multiple tunnel configurations are supported as well.
|
||||||
|
[[Tunnel]]
|
||||||
|
TunnelID = 456
|
||||||
|
Username = "specific_user"
|
||||||
|
UpdateKey = "defghi"
|
||||||
|
|
||||||
|
|
||||||
|
######################
|
||||||
|
## General Commands ##
|
||||||
|
######################
|
||||||
|
|
||||||
|
# Command items may be specified at the root level as well.
|
||||||
|
# The syntax is like all other Commands items, with two exceptions:
|
||||||
|
# * There is no templating performed...
|
||||||
|
# * As such, there is no IsTemplate directive for these.
|
||||||
|
# A root-level Command is run after all tunnels complete.
|
||||||
|
# The OnChange directive is true if any Tunnels result in any changes.
|
||||||
|
[[Command]]
|
||||||
|
ProgramPath = "/usr/local/bin/alltunpsrogram"
|
68
conf/_testdata/test.xml
Normal file
68
conf/_testdata/test.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<!--
|
||||||
|
See the example TOML for detailed comments and explanations.
|
||||||
|
-->
|
||||||
|
<config defaultUser="default_user"
|
||||||
|
oneTun="true"
|
||||||
|
cacheDb="/var/cache/gobroke.db">
|
||||||
|
<cachePerms>
|
||||||
|
<file user=""
|
||||||
|
group=""
|
||||||
|
mode="384"/>
|
||||||
|
<dir user=""
|
||||||
|
group=""
|
||||||
|
mode="448"/>
|
||||||
|
</cachePerms>
|
||||||
|
<tunnels>
|
||||||
|
<tunnel id="123"
|
||||||
|
addr="203.0.113.1"
|
||||||
|
mtu="1450"
|
||||||
|
username="specific_user"
|
||||||
|
key="abcdef">
|
||||||
|
<config>
|
||||||
|
<tpl tpl="/etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl"
|
||||||
|
dest="/etc/dnsmasq.d/ra_dhcpv6.conf">
|
||||||
|
<perms>
|
||||||
|
<file user=""
|
||||||
|
group=""
|
||||||
|
mode="384"/>
|
||||||
|
<dir user=""
|
||||||
|
group=""
|
||||||
|
mode="448"/>
|
||||||
|
</perms>
|
||||||
|
<cmds>
|
||||||
|
<cmd bin="/usr/local/bin/somecmd"
|
||||||
|
isol8Env="false"
|
||||||
|
onChange="true"
|
||||||
|
isTpl="false">
|
||||||
|
<args>
|
||||||
|
<arg>-f</arg>
|
||||||
|
<arg>foo</arg>
|
||||||
|
</args>
|
||||||
|
<envs>
|
||||||
|
<env>SOMEENV=SOMEVAL</env>
|
||||||
|
</envs>
|
||||||
|
</cmd>
|
||||||
|
</cmds>
|
||||||
|
</tpl>
|
||||||
|
<tpl tpl="/etc/gobroke/tpl/stat.tpl"
|
||||||
|
dest="/tmp/gobroke.dump"/>
|
||||||
|
</config>
|
||||||
|
<commands>
|
||||||
|
<cmd bin="systemctl"
|
||||||
|
onChange="true">
|
||||||
|
<args>
|
||||||
|
<arg>restart</arg>
|
||||||
|
<arg>someservice</arg>
|
||||||
|
</args>
|
||||||
|
</cmd>
|
||||||
|
</commands>
|
||||||
|
</tunnel>
|
||||||
|
<tunnel id="456"
|
||||||
|
mtu="1480"
|
||||||
|
username="specific_user"
|
||||||
|
key="defghi"/>
|
||||||
|
</tunnels>
|
||||||
|
<commands>
|
||||||
|
<cmd bin="/usr/local/bin/alltunsprogram" isol8Env="false"/>
|
||||||
|
</commands>
|
||||||
|
</config>
|
59
conf/_testdata/test.yml
Normal file
59
conf/_testdata/test.yml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# See the example TOML for detailed comments and explanations.
|
||||||
|
Default Username: default_user
|
||||||
|
|
||||||
|
NoGoTunnel: true
|
||||||
|
|
||||||
|
Cache Database Path: /var/cache/gobroke.db
|
||||||
|
|
||||||
|
Cache Database Permissions:
|
||||||
|
File:
|
||||||
|
User: ''
|
||||||
|
Group: ''
|
||||||
|
Mode: 384
|
||||||
|
Directory:
|
||||||
|
User: ''
|
||||||
|
Group: ''
|
||||||
|
Mode: 448
|
||||||
|
|
||||||
|
Tunnels:
|
||||||
|
- Tunnel ID: 123
|
||||||
|
Explicit Client IP Address: 203.0.113.1
|
||||||
|
MTU: 1450
|
||||||
|
Username: specific_user
|
||||||
|
Update Key: abcdef
|
||||||
|
Configuration File Templates:
|
||||||
|
- Template File Path: /etc/gobroke/tpl/dnsmasq/ra_dhcpv6.conf.tpl
|
||||||
|
Destination File Path: /etc/dnsmasq.d/ra_dhcpv6.conf
|
||||||
|
Permissions and Ownership:
|
||||||
|
File:
|
||||||
|
User: ''
|
||||||
|
Group: ''
|
||||||
|
Mode: 384
|
||||||
|
Directory:
|
||||||
|
User: ''
|
||||||
|
Group: ''
|
||||||
|
Mode: 448
|
||||||
|
Commands:
|
||||||
|
- Program Path: /usr/local/bin/somecmd
|
||||||
|
Arguments:
|
||||||
|
- '-f'
|
||||||
|
- 'foo'
|
||||||
|
Isolated Environment: false
|
||||||
|
Environment Variables:
|
||||||
|
- SOMEENV=SOMEVAL
|
||||||
|
On Change: true
|
||||||
|
Is Template: false
|
||||||
|
- Template File Path: /etc/gobroke/tpl/stat.tpl
|
||||||
|
Destination File Path: /tmp/gobroke.dump
|
||||||
|
Commands:
|
||||||
|
- Program Path: systemctl
|
||||||
|
Arguments:
|
||||||
|
- restart
|
||||||
|
- someservice
|
||||||
|
On Change: true
|
||||||
|
- Tunnel ID: 456
|
||||||
|
Username: specific_user
|
||||||
|
Update Key: defghi
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
- Program Path: /usr/local/bin/alltunsprogram
|
9
conf/consts.go
Normal file
9
conf/consts.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled())
|
||||||
|
)
|
10
conf/errs.go
Normal file
10
conf/errs.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
`errors`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrMissingUser error = errors.New("at least one tunnel is missing a username and no default username was provided")
|
||||||
|
ErrUnkownSyntax error = errors.New("unknown/unsupported configuration syntax")
|
||||||
|
)
|
75
conf/funcs.go
Normal file
75
conf/funcs.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
`encoding/json`
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/creasty/defaults"
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"r00t2.io/sysutils/paths"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewConfig(path string) (cfg *Config, err error) {
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
|
||||||
|
if err = paths.RealPath(&path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err = NewConfigFromBytes(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfigFromBytes(b []byte) (cfg *Config, err error) {
|
||||||
|
|
||||||
|
if err = json.Unmarshal(b, &cfg); err != nil {
|
||||||
|
if err = yaml.Unmarshal(b, &cfg); err != nil {
|
||||||
|
if err = toml.Unmarshal(b, &cfg); err != nil {
|
||||||
|
if err = toml.Unmarshal(b, &cfg); err != nil {
|
||||||
|
err = ErrUnkownSyntax
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = defaults.Set(cfg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.CacheDB != ":memory:" {
|
||||||
|
if err = paths.RealPath(&cfg.CacheDB); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = validate.Struct(cfg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range cfg.Tunnels {
|
||||||
|
t.cfg = cfg
|
||||||
|
if t.Username == nil {
|
||||||
|
if cfg.Username == nil {
|
||||||
|
err = ErrMissingUser
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
t.Username = cfg.Username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.TemplateConfigs != nil && len(t.TemplateConfigs) > 0 {
|
||||||
|
for _, tpl := range t.TemplateConfigs {
|
||||||
|
if err = paths.RealPath(&tpl.Template); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = paths.RealPath(&tpl.Dest); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
50
conf/funcs_test.go
Normal file
50
conf/funcs_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
`embed`
|
||||||
|
`encoding/xml`
|
||||||
|
`fmt`
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
`github.com/BurntSushi/toml`
|
||||||
|
`github.com/goccy/go-yaml`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed "_testdata"
|
||||||
|
testData embed.FS
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConf(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
var cfg *Config
|
||||||
|
var b []byte
|
||||||
|
|
||||||
|
if b, err = testData.ReadFile("_testdata/test.json"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg, err = NewConfigFromBytes(b); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
spew.Dump(cfg)
|
||||||
|
return
|
||||||
|
*/
|
||||||
|
|
||||||
|
if b, err = toml.Marshal(cfg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
|
if b, err = xml.MarshalIndent(cfg, "", " "); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
|
if b, err = yaml.Marshal(cfg); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
|
}
|
1
conf/funcs_tunnel.go
Normal file
1
conf/funcs_tunnel.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package conf
|
129
conf/types.go
Normal file
129
conf/types.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
`net`
|
||||||
|
`os`
|
||||||
|
|
||||||
|
`r00t2.io/gobroke/tplCmd`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config represents a configuration file.
|
||||||
|
type Config struct {
|
||||||
|
XMLName xml.Name `json:"-" toml:"-" xml:"config" yaml:"-" `
|
||||||
|
/*
|
||||||
|
Username, if provided, will be the default username used for any Tunnel that does not specify one (via Tunnel.Username).
|
||||||
|
This should be the username used when authenticating to tunnelbroker.net.
|
||||||
|
It is optional, as the username can be specified/overridden for each Tunnel, but at least one or the other *must* be provided.
|
||||||
|
This makes it easier if you have multiple tunnels under the same account.
|
||||||
|
If a username is specified in Tunnel.Username, it will be used.
|
||||||
|
If not (and, of course, Config.Username is specified), then Config.Username will be used for that Tunnel.
|
||||||
|
*/
|
||||||
|
Username *string `json:"default_username,omitempty" toml:"DefaultUsername,omitempty" xml:"defaultUser,attr,omitempty" yaml:"Default Username,omitempty"`
|
||||||
|
// SingleTunnel, if true, will suppress goroutine-management of tunnels and instead execute them sequentially instead.
|
||||||
|
SingleTunnel bool `json:"1tun,omitempty" toml:"SingleTunnel,omitempty" xml:"oneTun,attr,omitempty" yaml:"NoGoTunnel,omitempty"`
|
||||||
|
// CacheDB, if specified, is a path to a SQLite3 DB on-disk to make cached information persistent across reboots.
|
||||||
|
CacheDB string `json:"cache_db,omitempty" toml:"CacheDbPath,omitempty" xml:"cacheDb,attr,omitempty" yaml:"Cache Database Path,omitempty" default:":memory:" validate:"omitempty,filepath|eq=:memory:"`
|
||||||
|
// CacheDbPerms specifies the optional permissions for the file and parent directory for CacheDB; only used if persistent cache.
|
||||||
|
CacheDbPerms *Perms `json:"cache_perms,omitempty" toml:"CacheDbPerms,omitempty" xml:"cachePerms,omitempty" yaml:"Cache Database Permissions,omitempty"`
|
||||||
|
// Tunnels contains one or more tunnel configurations.
|
||||||
|
Tunnels []*Tunnel `json:"tunnels" toml:"Tunnel" xml:"tunnels>tunnel" yaml:"Tunnels" validate:"required"`
|
||||||
|
/*
|
||||||
|
Cmds are executed, in order, *after* all Tunnel configurations have been run.
|
||||||
|
Unlike in Tunnel and ConfigTemplate, no templating on these commands is performed.
|
||||||
|
*/
|
||||||
|
Cmds []tplCmd.Cmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tunnel represents a single tunnel configuration from tunnelbroker.net.
|
||||||
|
type Tunnel struct {
|
||||||
|
XMLName xml.Name `json:"-" toml:"-" xml:"tunnel" yaml:"-"`
|
||||||
|
/*
|
||||||
|
TunnelID can be found by logging into https://tunnelbroker.net/ and, at the "Main Page" that loads
|
||||||
|
when logging in, clicking on the desired tunnel name. The tunnel ID is then displayed in both the URL bar:
|
||||||
|
https://tunnelbroker.net/tunnel_detail.php?tid=<TunnelID>
|
||||||
|
And as the first line on the first tab ("IPv6 Tunnel" tab), labeled "Tunnel ID".
|
||||||
|
*/
|
||||||
|
TunnelID uint `json:"tun_id" toml:"TunnelID" xml:"id,attr" yaml:"Tunnel ID" validate:"required,ge=1"`
|
||||||
|
/*
|
||||||
|
ExplicitAddr, if provided, will be used as the tunnelbroker.FetchedTunnel.CurrentIPv4.
|
||||||
|
If not provided, this will be fetched dynamically from an external source.
|
||||||
|
*/
|
||||||
|
ExplicitAddr *net.IP `json:"addr,omitempty" toml:"ExplicitClientIP,omitempty" xml:"addr,attr,omitempty" yaml:"Explicit Client IP Address,omitempty"`
|
||||||
|
/*
|
||||||
|
MTU should be specified if you have defined a custom one (under the "Advanced" tab for this tunnel at tunnlebroker.net).
|
||||||
|
If you did not change this, the default is 1480 (the maximum allowed), and the default value of this struct field
|
||||||
|
on configuration parsing will reflect this.
|
||||||
|
*/
|
||||||
|
MTU uint `json:"mtu,omitempty" toml:"MTU,omitempty" xml:"mtu,attr,omitempty" yaml:"MTU,omitempty" default:"1480" validate:"required,gt=0,le=1480"`
|
||||||
|
/*
|
||||||
|
Username field is optional IF DefaultUsername was specified.
|
||||||
|
This also allows you to specify tunnels from different accounts
|
||||||
|
by providing a tunnel-specific username.
|
||||||
|
*/
|
||||||
|
Username *string `json:"username,omitempty" toml:"Username,omitempty" xml:"username,attr,omitempty" yaml:"Username,omitempty"`
|
||||||
|
/*
|
||||||
|
UpdateKey can be found under the "Advanced" tab on your tunnelbroker.net tunnel's page, labeled "Update Key".
|
||||||
|
This token is used to not only update the client-side tunnel IP but also to query the HE Tunnelbroker "API"
|
||||||
|
(it's really just a single endpoint) to get the tunnel information necessary for local configuration.
|
||||||
|
*/
|
||||||
|
UpdateKey string `json:"update_key" toml:"UpdateKey" xml:"key,attr" yaml:"Update Key" validate:"required"`
|
||||||
|
// TemplateConfgs is optional. It holds templates that will be executed in order given. See ConfigTemplate.
|
||||||
|
TemplateConfigs []ConfigTemplate `json:"cfg_tpls" toml:"ConfigTemplate" xml:"config>tpl" yaml:"Configuration File Templates"`
|
||||||
|
/*
|
||||||
|
Cmds are executed, in order, *after* all tunnel updates/fetching and the templating has completed (if any specified).
|
||||||
|
Each command will also have tunnelbroker.FetchedTunnel templated to it like TemplateConfigs/ConfigTemplate.Commands,
|
||||||
|
so they may be templated as necessary.
|
||||||
|
*/
|
||||||
|
Cmds []tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"commands>cmd,omitempty" yaml:"Commands,omitempty"`
|
||||||
|
// cfg is the parent Config.
|
||||||
|
cfg *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ConfigTemplate allows the templating of configuration files, etc. from the tunnel information.
|
||||||
|
Templates are executed *after* the IP update (if an update was necessary), but are always *run*.
|
||||||
|
ConfigTemplate.Dest will only be written to if:
|
||||||
|
|
||||||
|
* The file does not exist (yet), or
|
||||||
|
* The templated content differs from the file on disk
|
||||||
|
*/
|
||||||
|
type ConfigTemplate struct {
|
||||||
|
XMLName xml.Name `json:"-" toml:"-" xml:"tpl" yaml:"-"`
|
||||||
|
/*
|
||||||
|
Template is the path to the template file on disk.
|
||||||
|
It must follow the syntax, rules, etc. of a Golang (text/)template.Template (https://pkg.go.dev/text/template#Template).
|
||||||
|
The struct passed to it is a tunnelbroker.FetchedTunnel.
|
||||||
|
*/
|
||||||
|
Template string `json:"tpl" toml:"Template" xml:"tpl,attr" yaml:"Template File Path" validate:"required,filepath"`
|
||||||
|
// Dest contains the filepath that the Template should be written out to.
|
||||||
|
Dest string `json:"dest" toml:"Destination" xml:"dest,attr" yaml:"Destination File Path" validate:"required,filepath"`
|
||||||
|
// Perms allows specifying permissions/ownerships, if the curent user has the capability to do so.
|
||||||
|
Perms *Perms `json:"perms,omitempty" toml:"Permissions,omitempty" xml:"perms,omitempty" yaml:"Permissions and Ownership,omitempty"`
|
||||||
|
// Commands specifiies commands to run after this ConfigTemplate run.
|
||||||
|
Commands []tplCmd.TemplateCmd `json:"cmds,omitempty" toml:"Command,omitempty" xml:"cmds>cmd,omitempty" yaml:"Commands,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Perms struct {
|
||||||
|
// File specifies the desired permissions/ownership of the target file.
|
||||||
|
File *PermSpec `json:"file,omitempty" toml:"File,omitempty" xml:"file,omitempty" yaml:"File,omitempty"`
|
||||||
|
// ParentDir specifies the desired permissions/ownership of the parent ("dirname") of File.
|
||||||
|
ParentDir *PermSpec `json:"dir,omitempty" toml:"Dir,omitempty" xml:"dir,omitempty" yaml:"Directory,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermSpec struct {
|
||||||
|
/*
|
||||||
|
User is the username or UID (tried in that order) to chown.
|
||||||
|
If specified as an empty string, the current/runtime UID will be used.
|
||||||
|
If unspecified, UID will not be enforced.
|
||||||
|
*/
|
||||||
|
User *string `json:"user,omitempty" toml:"User,omitempty" xml:"user,attr,omitempty" yaml:"User,omitempty"`
|
||||||
|
/*
|
||||||
|
Group is the groupname or GID (tried in that order) to chown.
|
||||||
|
If specified as an empty string, the current/runtime GID will be used.
|
||||||
|
If unspecified, GID will not be enforced.
|
||||||
|
*/
|
||||||
|
Group *string `json:"group,omitempty" toml:"Group,omitempty" xml:"group,attr,omitempty" yaml:"Group,omitempty"`
|
||||||
|
// Mode is the permission mode bitset. If unspecified, mode will not be enforced.
|
||||||
|
Mode *os.FileMode `json:"mode,omitempty" toml:"Mode,omitempty" xml:"mode,attr,omitempty" yaml:"Mode,omitempty"`
|
||||||
|
}
|
27
go.mod
Normal file
27
go.mod
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
module r00t2.io/gobroke
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.4.0
|
||||||
|
github.com/creasty/defaults v1.8.0
|
||||||
|
github.com/go-playground/validator/v10 v10.23.0
|
||||||
|
github.com/go-resty/resty/v2 v2.16.2
|
||||||
|
github.com/goccy/go-yaml v1.15.7
|
||||||
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
r00t2.io/sysutils v1.12.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/djherbis/times v1.6.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.27.0 // indirect
|
||||||
|
golang.org/x/sync v0.9.0 // indirect
|
||||||
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
r00t2.io/goutils v1.7.1 // indirect
|
||||||
|
)
|
61
go.sum
Normal file
61
go.sum
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||||
|
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||||
|
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||||
|
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98=
|
||||||
|
github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc=
|
||||||
|
r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
|
||||||
|
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
|
||||||
|
r00t2.io/sysutils v1.12.0 h1:Ce3qUOyLixE1ZtFT/+SVwOT5kSkzg5+l1VloGeGugrU=
|
||||||
|
r00t2.io/sysutils v1.12.0/go.mod h1:bNTKNBk9MnUhj9coG9JBNicSi5FrtJHEM645um85pyw=
|
34
tplCmd/funcs_cmd.go
Normal file
34
tplCmd/funcs_cmd.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package tplCmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
`os`
|
||||||
|
`os/exec`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
ToCmd returns an (os/)exec.Cmd from a Cmd.
|
||||||
|
|
||||||
|
err will always be nil for now, but is still returned and should be handled
|
||||||
|
for future-proofing.
|
||||||
|
*/
|
||||||
|
func (c *Cmd) ToCmd() (cmd *exec.Cmd, err error) {
|
||||||
|
|
||||||
|
var envs []string
|
||||||
|
|
||||||
|
if c.Args != nil && len(c.Args) > 0 {
|
||||||
|
cmd = exec.Command(c.Program, c.Args...)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command(c.Program)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.IsolateEnv {
|
||||||
|
envs = os.Environ()
|
||||||
|
}
|
||||||
|
if c.EnvVars != nil && len(c.EnvVars) > 0 {
|
||||||
|
envs = append(envs, c.EnvVars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Env = envs
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
73
tplCmd/funcs_templatecmd.go
Normal file
73
tplCmd/funcs_templatecmd.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package tplCmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
`bytes`
|
||||||
|
`os`
|
||||||
|
`os/exec`
|
||||||
|
`text/template`
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToCmd returns an (os/)exec.Cmd from a TemplateCmd. t should be a tunnelbroker.FetchedTunnel, generally.
|
||||||
|
func (c *TemplateCmd) ToCmd(t any) (cmd *exec.Cmd, err error) {
|
||||||
|
|
||||||
|
var progName string
|
||||||
|
var envs []string
|
||||||
|
var tpl *template.Template
|
||||||
|
var args []string
|
||||||
|
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
if tpl, err = template.New("").Parse(c.Program); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = tpl.Execute(buf, t); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
progName = buf.String()
|
||||||
|
|
||||||
|
if c.Args != nil && len(c.Args) > 0 {
|
||||||
|
args = make([]string, len(c.Args))
|
||||||
|
for idx, arg := range c.Args {
|
||||||
|
buf.Reset()
|
||||||
|
if tpl, err = template.New("").Parse(arg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = tpl.Execute(buf, t); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
args[idx] = buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.EnvVars != nil && len(c.EnvVars) > 0 {
|
||||||
|
envs = make([]string, len(c.EnvVars))
|
||||||
|
for idx, env := range c.EnvVars {
|
||||||
|
buf.Reset()
|
||||||
|
if tpl, err = template.New("").Parse(env); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = tpl.Execute(buf, t); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
envs[idx] = buf.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !c.IsolateEnv {
|
||||||
|
if envs != nil && len(envs) > 0 {
|
||||||
|
envs = append(os.Environ(), envs...)
|
||||||
|
} else {
|
||||||
|
envs = os.Environ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if args != nil && len(args) > 0 {
|
||||||
|
cmd = exec.Command(progName, args...)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command(progName)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Env = envs
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
21
tplCmd/types.go
Normal file
21
tplCmd/types.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package tplCmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
`encoding/xml`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cmd is a command that is executed without any templating.
|
||||||
|
type Cmd struct {
|
||||||
|
XMLName xml.Name `json:"-" toml:"-" xml:"cmd" yaml:"-"`
|
||||||
|
Program string `json:"bin" toml:"ProgramPath" xml:"bin,attr" yaml:"Program Path" validate:"required,file"`
|
||||||
|
Args []string `json:"args" toml:"Argument" xml:"args>arg" yaml:"Arguments"`
|
||||||
|
IsolateEnv bool `json:"isol8_env" toml:"IsolatedEnv" xml:"isol8Env,attr" yaml:"Isolated Environment"`
|
||||||
|
EnvVars []string `json:"env" toml:"EnvVars" xml:"envs>env" yaml:"Environment Variables"`
|
||||||
|
OnChanges *bool `json:"on_change,omitempty" toml:"OnChange,omitempty" xml:"onChange,attr,omitempty" yaml:"On Change,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateCmd is a command that supports templating. (It's in its own package to avoid a cyclic dependency.)
|
||||||
|
type TemplateCmd struct {
|
||||||
|
*Cmd `yaml:",inline"`
|
||||||
|
IsTemplate bool `json:"is_tpl" toml:"IsTemplate" xml:"isTpl,attr" yaml:"Is Template"`
|
||||||
|
}
|
23
tunnelbroker/consts.go
Normal file
23
tunnelbroker/consts.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package tunnelbroker
|
||||||
|
|
||||||
|
const (
|
||||||
|
wanIpUrl string = "https://c4.r00t2.io/ip"
|
||||||
|
// https://forums.he.net/index.php?topic=3153.0
|
||||||
|
// If no TID is provided, all tunnels are returned.
|
||||||
|
/*
|
||||||
|
All-tunnels mode can *only* use the account's password,
|
||||||
|
the TID-specified can *only* use the Update Key.
|
||||||
|
*/
|
||||||
|
infoBaseUrl string = "https://tunnelbroker.net/tunnelInfo.php"
|
||||||
|
updateBaseUrl string = "https://ipv4.tunnelbroker.net/nic/update"
|
||||||
|
infoTidParam string = "tid"
|
||||||
|
updateTidParam string = "hostname"
|
||||||
|
/*
|
||||||
|
NOTE:
|
||||||
|
This parameter is only required if the client's WAN IP
|
||||||
|
does not match the desired tunnel address.
|
||||||
|
If left off, it defaults to client IP (as seen by the webserver).
|
||||||
|
*/
|
||||||
|
updateIpParam string = "myip"
|
||||||
|
noTunBody string = "No tunnels found"
|
||||||
|
)
|
9
tunnelbroker/funcs.go
Normal file
9
tunnelbroker/funcs.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package tunnelbroker
|
||||||
|
|
||||||
|
// NewClient reuturns a Client.
|
||||||
|
func NewClient() (c *Client, err error) {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
49
tunnelbroker/types.go
Normal file
49
tunnelbroker/types.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package tunnelbroker
|
||||||
|
|
||||||
|
import (
|
||||||
|
`encoding/xml`
|
||||||
|
`net`
|
||||||
|
`net/netip`
|
||||||
|
|
||||||
|
`github.com/go-resty/resty/v2`
|
||||||
|
`r00t2.io/gobroke/conf`
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
tunCfg *conf.Config
|
||||||
|
myAddr net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
type TunnelList struct {
|
||||||
|
XMLName xml.Name `json:"-" xml:"tunnels" yaml:"-"`
|
||||||
|
Tunnels []*Tunnel `json:"tunnels" xml:"tunnel" yaml:"Tunnels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tunnel struct {
|
||||||
|
XMLName xml.Name `json:"-" xml:"tunnel" yaml:"-"`
|
||||||
|
ID uint `json:"id" xml:"id,attr" yaml:"ID" db:"tun_id"`
|
||||||
|
Description string `json:"desc" xml:"description" yaml:"Description" db:"desc"`
|
||||||
|
ServerIPv4 net.IP `json:"tgt_v4" xml:"serverv4" yaml:"IPv4 Tunnel Target" db:"server_v4"`
|
||||||
|
ClientIPv4 net.IP `json:"client_v4" xml:"clientv4" yaml:"Configured IPv4 Client Address" db:"current_client_v4"`
|
||||||
|
ServerIPv6 net.IP `json:"server_v6" xml:"serverv6" yaml:"IPv6 Endpoint" db:"tunnel_server_v6"`
|
||||||
|
ClientIPv6 net.IP `json:"client_v6" xml:"clientv6" yaml:"IPv6 Tunnel Client Address" db:"tunnel_client_v6"`
|
||||||
|
Routed64 netip.Prefix `json:"routed_64" xml:"routed64" yaml:"Routed /64" db:"prefix_64"`
|
||||||
|
Routed48 *netip.Prefix `json:"routed_48,omitempty" xml:"routed48,omitempty" yaml:"Routed /48,omitempty" db:"prefix_48"`
|
||||||
|
RDNS1 *string `json:"rdns_1,omitempty" xml:"rdns1,omitempty" yaml:"RDNS #1,omitempty" db:"rdns_1"`
|
||||||
|
RDNS2 *string `json:"rdns_2,omitempty" xml:"rdns2,omitempty" yaml:"RDNS #2,omitempty" db:"rdns_2"`
|
||||||
|
RDNS3 *string `json:"rdns_3,omitempty" xml:"rdns3,omitempty" yaml:"RDNS #3,omitempty" db:"rdns_3"`
|
||||||
|
RDNS4 *string `json:"rdns_4,omitempty" xml:"rdns4,omitempty" yaml:"RDNS #4,omitempty" db:"rdns_4"`
|
||||||
|
RDNS5 *string `json:"rdns_5,omitempty" xml:"rdns5,omitempty" yaml:"RDNS #5,omitempty" db:"rdns_5"`
|
||||||
|
client *Client
|
||||||
|
heClient *resty.Client
|
||||||
|
tunCfg *conf.Tunnel
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchedIP struct {
|
||||||
|
NewClientIPv4 net.IP `json:"new_client_v4" xml:"newClientv4,attr" yaml:"Evaluated IPv4 Client Address" db:"client_ip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FetchedTunnel struct {
|
||||||
|
*Tunnel
|
||||||
|
*FetchedIP
|
||||||
|
}
|
32
version/consts.go
Normal file
32
version/consts.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
These variables are automatically handled by the build script.
|
||||||
|
|
||||||
|
DO NOT MODIFY THESE VARIABLES.
|
||||||
|
Refer to /build.sh for how these are generated at build time and populated.
|
||||||
|
*/
|
||||||
|
var (
|
||||||
|
sourceControl string = "git"
|
||||||
|
version string = "(unknown)"
|
||||||
|
commitHash string
|
||||||
|
commitShort string
|
||||||
|
numCommitsAfterTag string
|
||||||
|
isDirty string
|
||||||
|
buildTime string
|
||||||
|
buildUser string
|
||||||
|
buildSudoUser string
|
||||||
|
buildHost string
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
patchRe *regexp.Regexp = regexp.MustCompile(`^(?P<patch>[0-9+])(?P<pre>-[0-9A-Za-z.-]+)?(?P<build>\+[0-9A-Za-z.-]+)?$`)
|
||||||
|
patchReIsolated *regexp.Regexp = regexp.MustCompile(`^([0-9]+)(?:[-+](.*)?)?$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ver is populated by main() from the build script and used in other places.
|
||||||
|
var Ver *BuildInfo
|
144
version/funcs.go
Normal file
144
version/funcs.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version returns the build information. See build.sh.
|
||||||
|
func Version() (b *BuildInfo, err error) {
|
||||||
|
|
||||||
|
var n int
|
||||||
|
var s string
|
||||||
|
var sb strings.Builder
|
||||||
|
var ok bool
|
||||||
|
var canonical string
|
||||||
|
var build strings.Builder
|
||||||
|
// Why a map?
|
||||||
|
// I forget but I had a reason for it once upon a time.
|
||||||
|
var raw map[string]string = map[string]string{
|
||||||
|
"sourceControl": sourceControl,
|
||||||
|
"tag": version,
|
||||||
|
"hash": commitHash,
|
||||||
|
"shortHash": commitShort,
|
||||||
|
"postTagCommits": numCommitsAfterTag,
|
||||||
|
"dirty": isDirty,
|
||||||
|
"time": buildTime,
|
||||||
|
"user": buildUser,
|
||||||
|
"sudoUser": buildSudoUser,
|
||||||
|
"host": buildHost,
|
||||||
|
}
|
||||||
|
var i BuildInfo = BuildInfo{
|
||||||
|
SourceControl: raw["sourceControl"],
|
||||||
|
TagVersion: raw["tag"],
|
||||||
|
// PostTagCommits: 0,
|
||||||
|
CommitHash: raw["hash"],
|
||||||
|
CommitId: raw["shortHash"],
|
||||||
|
BuildUser: raw["user"],
|
||||||
|
RealBuildUser: raw["sudoUser"],
|
||||||
|
// BuildTime: time.Time{},
|
||||||
|
BuildHost: raw["host"],
|
||||||
|
Dirty: false,
|
||||||
|
isDefined: false,
|
||||||
|
raw: raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok = raw["postTagCommits"]; ok && strings.TrimSpace(s) != "" {
|
||||||
|
if n, err = strconv.Atoi(s); err == nil {
|
||||||
|
i.PostTagCommits = uint(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok = raw["time"]; ok && strings.TrimSpace(s) != "" {
|
||||||
|
if n, err = strconv.Atoi(s); err == nil {
|
||||||
|
i.BuildTime = time.Unix(int64(n), 0).UTC()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(raw["dirty"]) {
|
||||||
|
case "1":
|
||||||
|
i.Dirty = true
|
||||||
|
case "0", "":
|
||||||
|
i.Dirty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the short form. We use this for both BuildInfo.short and BuildInfo.verSplit.
|
||||||
|
if i.TagVersion == "" {
|
||||||
|
sb.WriteString(i.SourceControl)
|
||||||
|
} else {
|
||||||
|
sb.WriteString(i.TagVersion)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Now the mess. In order to conform to SemVer 2.0 (the spec this code targets):
|
||||||
|
|
||||||
|
1.) MAJOR.
|
||||||
|
2.) MINOR.
|
||||||
|
3.) PATCH
|
||||||
|
4.) -PRERELEASE (OPTIONAL)
|
||||||
|
(git commit, if building against a commit made past 1-3. Always included if untagged.)
|
||||||
|
5.) +BUILDINFO (OPTIONAL)
|
||||||
|
("+x[.y]", where x is # of commits past 4, or tag commit if 4 is empty. 0 is valid.
|
||||||
|
y is optional, and is the string "dirty" if it is a "dirty" build - that is, uncommitted/unstaged changes.
|
||||||
|
if x and y would be 0 and empty, respectively, then 5 is not included.)
|
||||||
|
|
||||||
|
1-3 are already written, or the source control software used if not.
|
||||||
|
|
||||||
|
Technically 4 and 5 are only included if 3 is present. We force patch to 0 if it's a tagged release and patch isn't present --
|
||||||
|
so this is not relevant.
|
||||||
|
*/
|
||||||
|
// PRERELEASE
|
||||||
|
if i.TagVersion == "" || i.PostTagCommits > 0 {
|
||||||
|
// We use the full commit hash for git versions, short identifier for tagged releases.
|
||||||
|
if i.TagVersion == "" {
|
||||||
|
i.Pre = i.CommitHash
|
||||||
|
} else {
|
||||||
|
i.Pre = i.CommitId
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("-%v", i.Pre))
|
||||||
|
}
|
||||||
|
// BUILD
|
||||||
|
if i.PostTagCommits > 0 || i.Dirty {
|
||||||
|
build.WriteString(strconv.Itoa(int(i.PostTagCommits)))
|
||||||
|
if i.Dirty {
|
||||||
|
build.WriteString(".dirty")
|
||||||
|
}
|
||||||
|
i.Build = build.String()
|
||||||
|
sb.WriteString(fmt.Sprintf("+%v", i.Build))
|
||||||
|
}
|
||||||
|
|
||||||
|
i.short = sb.String()
|
||||||
|
if semver.IsValid(i.short) {
|
||||||
|
// DON'T DO THIS. It strips the prerelease and build info.
|
||||||
|
// i.short = semver.Canonical(i.short)
|
||||||
|
// Do this instead.
|
||||||
|
canonical = semver.Canonical(i.short)
|
||||||
|
// Numeric versions...
|
||||||
|
if n, err = strconv.Atoi(strings.TrimPrefix(semver.Major(canonical), "v")); err != nil {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
i.Major = uint(n)
|
||||||
|
}
|
||||||
|
if n, err = strconv.Atoi(strings.Split(semver.MajorMinor(canonical), ".")[1]); err != nil {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
i.Minor = uint(n)
|
||||||
|
}
|
||||||
|
if n, err = strconv.Atoi(patchReIsolated.FindStringSubmatch(strings.Split(canonical, ".")[2])[1]); err != nil {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
i.Patch = uint(n)
|
||||||
|
}
|
||||||
|
// The other tag assignments were performed above.
|
||||||
|
}
|
||||||
|
// The default is 0 for the numerics, so no big deal.
|
||||||
|
|
||||||
|
i.isDefined = true
|
||||||
|
|
||||||
|
b = &i
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
103
version/funcs_buildinfo.go
Normal file
103
version/funcs_buildinfo.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Detail returns a multiline string containing every possible piece of information we collect.
|
||||||
|
func (b *BuildInfo) Detail() (ver string) {
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
sb.WriteString(fmt.Sprintf("%v\n\n", b.short))
|
||||||
|
sb.WriteString(fmt.Sprintf("====\nSource Control: %v\n", b.SourceControl))
|
||||||
|
if b.TagVersion != "" {
|
||||||
|
if b.PostTagCommits > 0 {
|
||||||
|
sb.WriteString(fmt.Sprintf("Version Base: %v\nCommit Hash: %v\n", b.TagVersion, b.CommitHash))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("Version: %v\n", b.TagVersion))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("Version: (Unversioned)\nCommit Hash: %v\n", b.CommitHash))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-commits
|
||||||
|
if b.TagVersion != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf("# of Commits Since %v: %v\n", b.TagVersion, b.PostTagCommits))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("# of Commits Since %v: %v\n", b.CommitId, b.PostTagCommits))
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString("Uncommitted/Unstaged Changes: ")
|
||||||
|
if b.Dirty {
|
||||||
|
sb.WriteString("yes (dirty/monkeypatched build)\n")
|
||||||
|
} else {
|
||||||
|
sb.WriteString("no (clean build)\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.TagVersion != "" {
|
||||||
|
sb.WriteString(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"====\nMajor: %v\nMinor: %v\nPatch: %v\n",
|
||||||
|
b.Major, b.Minor, b.Patch,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sb.WriteString("====\n")
|
||||||
|
sb.WriteString(b.Meta())
|
||||||
|
|
||||||
|
ver = sb.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short returns a uniquely identifiable version string.
|
||||||
|
func (b *BuildInfo) Short() (ver string) {
|
||||||
|
|
||||||
|
ver = b.short
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta returns the build/compile-time info.
|
||||||
|
func (b *BuildInfo) Meta() (meta string) {
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
if b.RealBuildUser != b.BuildUser && b.RealBuildUser != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf("Real Build User: %v\n", b.RealBuildUser))
|
||||||
|
sb.WriteString(fmt.Sprintf("Sudo Build User: %v\n", b.BuildUser))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("Build User: %v\n", b.BuildUser))
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("Build Time: %v\nBuild Host: %v\n", b.BuildTime, b.BuildHost))
|
||||||
|
|
||||||
|
meta = sb.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getReMap gets a regex map of map[pattern]match.
|
||||||
|
func (b *BuildInfo) getReMap() (matches map[string]string) {
|
||||||
|
|
||||||
|
var s string = b.Short()
|
||||||
|
var sections []string
|
||||||
|
|
||||||
|
if !semver.IsValid(s) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sections = strings.Split(s, ".")
|
||||||
|
|
||||||
|
// The split should contain everything in the third element.
|
||||||
|
// Or, if using a "simplified" semver, the last element.
|
||||||
|
matches = make(map[string]string)
|
||||||
|
for idx, str := range patchRe.FindStringSubmatch(sections[len(sections)-1]) {
|
||||||
|
matches[patchRe.SubexpNames()[idx]] = str
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
51
version/types.go
Normal file
51
version/types.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildInfo contains nativized version information.
|
||||||
|
type BuildInfo struct {
|
||||||
|
// TagVersion is the most recent tag name on the current branch.
|
||||||
|
TagVersion string
|
||||||
|
// PostTagCommits is the number of commits after BuildInfo.TagVersion's commit on the current branch.
|
||||||
|
PostTagCommits uint
|
||||||
|
// CommitHash is the full commit hash.
|
||||||
|
CommitHash string
|
||||||
|
// CommitId is the "short" version of BuildInfo.CommitHash.
|
||||||
|
CommitId string
|
||||||
|
// BuildUser is the user the program was compiled under.
|
||||||
|
BuildUser string
|
||||||
|
// If compiled under sudo, BuildInfo.RealBuildUser is the user that called sudo.
|
||||||
|
RealBuildUser string
|
||||||
|
// BuildTime is the time and date of the program's build time.
|
||||||
|
BuildTime time.Time
|
||||||
|
// BuildHost is the host the binary was compiled on.
|
||||||
|
BuildHost string
|
||||||
|
// Dirty specifies if the source was "dirty" (uncommitted/unstaged etc. files) at the time of compilation.
|
||||||
|
Dirty bool
|
||||||
|
// SourceControl is the source control version used. Only relevant if not a "clean" build or untagged.
|
||||||
|
SourceControl string
|
||||||
|
// Major is the major version, expressed as an uint per spec.
|
||||||
|
Major uint
|
||||||
|
// Minor is the minor version, expressed as an uint per spec.
|
||||||
|
Minor uint
|
||||||
|
// Patch is the patch version, expressed as an uint per spec.
|
||||||
|
Patch uint
|
||||||
|
// Pre
|
||||||
|
Pre string
|
||||||
|
// Build
|
||||||
|
Build string
|
||||||
|
// isDefined specifies if this version was retrieved from the built-in values.
|
||||||
|
isDefined bool
|
||||||
|
// raw is the raw variable values.
|
||||||
|
raw map[string]string
|
||||||
|
/*
|
||||||
|
verSplit is a slice of []string{Major, Minor, Patch, PreRelease, Build}
|
||||||
|
|
||||||
|
If using an actual point release, PreRelease and Build are probably blank.
|
||||||
|
*/
|
||||||
|
verSplit [5]string
|
||||||
|
// short is the condensed version of verSplit.
|
||||||
|
short string
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user