Initial commit.

This commit is contained in:
moony 2022-01-11 13:27:08 -08:00
commit 92266ae01e
12 changed files with 579 additions and 0 deletions

16
.editorconfig Normal file
View File

@ -0,0 +1,16 @@
root = true
[*.{py,c,cpp,h,rst,md,yml}]
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
[*.{py,c,cpp,h}]
indent_size = 4
[*.yml]
indent_size = 2
[*.{go}]
indent_size=4
indent_style=tab

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/bin
/.go
/.push-*
/.container-*
/.dockerfile-*
/vendor

8
Dockerfile.in Normal file
View File

@ -0,0 +1,8 @@
FROM {ARG_FROM}
ADD bin/{ARG_OS}_{ARG_ARCH}/{ARG_BIN} /{ARG_BIN}
# This would be nicer as `nobody:nobody` but distroless has no such entries.
USER 65535:65535
ENTRYPOINT ["/{ARG_BIN}"]

188
Makefile Normal file
View File

@ -0,0 +1,188 @@
# The binary to build (just the basename).
BIN := mullfast
# Where to push the docker image.
REGISTRY ?= thockin
# This version-strategy uses git tags to set the version string
VERSION := $(shell git describe --tags --always --dirty)
#
# This version-strategy uses a manual value to set the version string
#VERSION := 1.2.3
###
### These variables should not need tweaking.
###
SRC_DIRS := cmd pkg # directories which hold app source (not vendored)
ALL_PLATFORMS := linux/amd64 linux/arm linux/arm64 linux/ppc64le linux/s390x
# Used internally. Users should pass GOOS and/or GOARCH.
OS := $(if $(GOOS),$(GOOS),$(shell go env GOOS))
ARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH))
BASEIMAGE ?= gcr.io/distroless/static
IMAGE := $(REGISTRY)/$(BIN)
TAG := $(VERSION)__$(OS)_$(ARCH)
BUILD_IMAGE ?= golang:latest
# If you want to build all binaries, see the 'all-build' rule.
# If you want to build all containers, see the 'all-container' rule.
# If you want to build AND push all containers, see the 'all-push' rule.
all: build
# For the following OS/ARCH expansions, we transform OS/ARCH into OS_ARCH
# because make pattern rules don't match with embedded '/' characters.
build-%:
@$(MAKE) build \
--no-print-directory \
GOOS=$(firstword $(subst _, ,$*)) \
GOARCH=$(lastword $(subst _, ,$*))
container-%:
@$(MAKE) container \
--no-print-directory \
GOOS=$(firstword $(subst _, ,$*)) \
GOARCH=$(lastword $(subst _, ,$*))
push-%:
@$(MAKE) push \
--no-print-directory \
GOOS=$(firstword $(subst _, ,$*)) \
GOARCH=$(lastword $(subst _, ,$*))
all-build: $(addprefix build-, $(subst /,_, $(ALL_PLATFORMS)))
all-container: $(addprefix container-, $(subst /,_, $(ALL_PLATFORMS)))
all-push: $(addprefix push-, $(subst /,_, $(ALL_PLATFORMS)))
build: bin/$(OS)_$(ARCH)/$(BIN)
# Directories that we need created to build/test.
BUILD_DIRS := bin/$(OS)_$(ARCH) \
.go/bin/$(OS)_$(ARCH) \
.go/cache
# The following structure defeats Go's (intentional) behavior to always touch
# result files, even if they have not changed. This will still run `go` but
# will not trigger further work if nothing has actually changed.
OUTBIN = bin/$(OS)_$(ARCH)/$(BIN)
$(OUTBIN): .go/$(OUTBIN).stamp
@true
# This will build the binary under ./.go and update the real binary iff needed.
.PHONY: .go/$(OUTBIN).stamp
.go/$(OUTBIN).stamp: $(BUILD_DIRS)
@echo "making $(OUTBIN)"
@docker run \
-i \
--rm \
-u $$(id -u):$$(id -g) \
-v $$(pwd):/src \
-w /src \
-v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \
-v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \
-v $$(pwd)/.go/cache:/.cache \
--env HTTP_PROXY=$(HTTP_PROXY) \
--env HTTPS_PROXY=$(HTTPS_PROXY) \
$(BUILD_IMAGE) \
/bin/sh -c " \
ARCH=$(ARCH) \
OS=$(OS) \
VERSION=$(VERSION) \
./build/build.sh \
"
@if ! cmp -s .go/$(OUTBIN) $(OUTBIN); then \
mv .go/$(OUTBIN) $(OUTBIN); \
date >$@; \
fi
# Example: make shell CMD="-c 'date > datefile'"
shell: $(BUILD_DIRS)
@echo "launching a shell in the containerized build environment"
@docker run \
-ti \
--rm \
-u $$(id -u):$$(id -g) \
-v $$(pwd):/src \
-w /src \
-v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \
-v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \
-v $$(pwd)/.go/cache:/.cache \
--env HTTP_PROXY=$(HTTP_PROXY) \
--env HTTPS_PROXY=$(HTTPS_PROXY) \
$(BUILD_IMAGE) \
/bin/sh $(CMD)
# Used to track state in hidden files.
DOTFILE_IMAGE = $(subst /,_,$(IMAGE))-$(TAG)
container: .container-$(DOTFILE_IMAGE) say_container_name
.container-$(DOTFILE_IMAGE): bin/$(OS)_$(ARCH)/$(BIN) Dockerfile.in
@sed \
-e 's|{ARG_BIN}|$(BIN)|g' \
-e 's|{ARG_ARCH}|$(ARCH)|g' \
-e 's|{ARG_OS}|$(OS)|g' \
-e 's|{ARG_FROM}|$(BASEIMAGE)|g' \
Dockerfile.in > .dockerfile-$(OS)_$(ARCH)
@docker build -t $(IMAGE):$(TAG) -f .dockerfile-$(OS)_$(ARCH) .
@docker images -q $(IMAGE):$(TAG) > $@
say_container_name:
@echo "container: $(IMAGE):$(TAG)"
push: .push-$(DOTFILE_IMAGE) say_push_name
.push-$(DOTFILE_IMAGE): .container-$(DOTFILE_IMAGE)
@docker push $(IMAGE):$(TAG)
say_push_name:
@echo "pushed: $(IMAGE):$(TAG)"
manifest-list: all-push
platforms=$$(echo $(ALL_PLATFORMS) | sed 's/ /,/g'); \
manifest-tool \
--username=oauth2accesstoken \
--password=$$(gcloud auth print-access-token) \
push from-args \
--platforms "$$platforms" \
--template $(REGISTRY)/$(BIN):$(VERSION)__OS_ARCH \
--target $(REGISTRY)/$(BIN):$(VERSION)
version:
@echo $(VERSION)
test: $(BUILD_DIRS)
@docker run \
-i \
--rm \
-u $$(id -u):$$(id -g) \
-v $$(pwd):/src \
-w /src \
-v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin \
-v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \
-v $$(pwd)/.go/cache:/.cache \
--env HTTP_PROXY=$(HTTP_PROXY) \
--env HTTPS_PROXY=$(HTTPS_PROXY) \
$(BUILD_IMAGE) \
/bin/sh -c " \
ARCH=$(ARCH) \
OS=$(OS) \
VERSION=$(VERSION) \
./build/test.sh $(SRC_DIRS) \
"
$(BUILD_DIRS):
@mkdir -p $@
clean: container-clean bin-clean
container-clean:
rm -rf .container-* .dockerfile-* .push-*
bin-clean:
rm -rf .go bin

45
README.md Normal file
View File

@ -0,0 +1,45 @@
# Go app template build environment
This is a skeleton project for a Go application, which captures the best build
techniques I have learned to date. It uses a Makefile to drive the build (the
universal API to software projects) and a Dockerfile to build a docker image.
This has only been tested on Linux, and depends on Docker to build.
## Customizing it
To use this, simply copy these files and make the following changes:
Makefile:
- change `BIN` to your binary name
- rename `cmd/myapp` to `cmd/$BIN`
- change `REGISTRY` to the Docker registry you want to use
- maybe change `SRC_DIRS` if you use some other layout
- choose a strategy for `VERSION` values - git tags or manual
Dockerfile.in:
- maybe change or remove the `USER` if you need
## Go Modules
This assumes the use of go modules (which will be the default for all Go builds
as of Go 1.13) and vendoring (which reasonable minds might disagree about).
You will need to run `go mod vendor` to create a `vendor` directory when you
have dependencies.
## Building
Run `make` or `make build` to compile your app. This will use a Docker image
to build your app, with the current directory volume-mounted into place. This
will store incremental state for the fastest possible build. Run `make
all-build` to build for all architectures.
Run `make container` to build the container image. It will calculate the image
tag based on the most recent git tag, and whether the repo is "dirty" since
that tag (see `make version`). Run `make all-container` to build containers
for all architectures.
Run `make push` to push the container image to `REGISTRY`. Run `make all-push`
to push the container images for all architectures.
Run `make clean` to clean up.

9
banner.txt Normal file
View File

@ -0,0 +1,9 @@
* (
( ` ( ( )\ ) )
)\))( ( )\ )\(()/( ) ( /(
((_)()\ ))\ ((_)((_)/(_))( /( ( )\())
(_()((_) /((_) _ _ (_))_|)(_)) )\ (_))/
| \/ |(_))( | | | || |_ ((_)_ ((_)| |_
| |\/| || || || | | || __|/ _` |(_-<| _|
|_| |_| \_,_||_| |_||_| \__,_|/__/ \__|
moony <moony@tcp.direct> - @5ynACK

29
build/build.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/sh
set -o errexit
set -o nounset
set -o pipefail
if [ -z "${OS:-}" ]; then
echo "OS must be set"
exit 1
fi
if [ -z "${ARCH:-}" ]; then
echo "ARCH must be set"
exit 1
fi
if [ -z "${VERSION:-}" ]; then
echo "VERSION must be set"
exit 1
fi
export CGO_ENABLED=0
export GOARCH="${ARCH}"
export GOOS="${OS}"
export GO111MODULE=on
export GOFLAGS="-mod=vendor"
go install \
-installsuffix "static" \
-ldflags "-X $(go list -m)/pkg/version.VERSION=${VERSION}" \
./...

39
build/test.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/sh
set -o errexit
set -o nounset
set -o pipefail
export CGO_ENABLED=0
export GO111MODULE=on
export GOFLAGS="-mod=vendor"
TARGETS=$(for d in "$@"; do echo ./$d/...; done)
echo "Running tests:"
go test -installsuffix "static" ${TARGETS}
echo
echo -n "Checking gofmt: "
ERRS=$(find "$@" -type f -name \*.go | xargs gofmt -l 2>&1 || true)
if [ -n "${ERRS}" ]; then
echo "FAIL - the following files need to be gofmt'ed:"
for e in ${ERRS}; do
echo " $e"
done
echo
exit 1
fi
echo "PASS"
echo
echo -n "Checking go vet: "
ERRS=$(go vet ${TARGETS} 2>&1 || true)
if [ -n "${ERRS}" ]; then
echo "FAIL"
echo "${ERRS}"
echo
exit 1
fi
echo "PASS"
echo

187
cmd/mullfast/main.go Normal file
View File

@ -0,0 +1,187 @@
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"sort"
"strings"
"sync"
"time"
ct "github.com/daviddengcn/go-colortext"
"github.com/nuttapp/pinghist/ping"
"github.com/manifoldco/promptui"
"git.tcp.direct/moony/mullfast/version"
)
const BANNER = "ICAgKiAgICAgICAgICAgICAgICAgICgKICggIGAgICAgICAgICAgKCAgICggIClcICkgICAgICAgICAgICAgICkKIClcKSkoICAgICAoICAgKVwgIClcKCgpLyggICAgKSAgICAgICggLygKKChfKSgpXCAgICkpXCAoKF8pKChfKS8oXykpKCAvKCAgKCAgIClcKCkpCihfKCkoKF8pIC8oKF8pIF8gICBfIChfKSlffCkoXykpIClcIChfKSkvCnwgIFwvICB8KF8pKSggfCB8IHwgfHwgfF8gKChfKV8gKChfKXwgfF8KfCB8XC98IHx8IHx8IHx8IHwgfCB8fCBfX3wvIF9gIHwoXy08fCAgX3wKfF98ICB8X3wgXF8sX3x8X3wgfF98fF98ICBcX18sX3wvX18vIFxfX3wKICAgLSBDb2RlZCBieSBNb29ueSAtIFZlcnNpb246IDAuMS4wIC0K"
const URL = "https://api.mullvad.net/www/relays/all"
type Server struct {
List []float64
Last float64
URL string
Hostname string
Country_code string
Country_name string
City_name string
Active bool
Type string
}
var (
servers []*Server
active []*Server
)
type ByLast []*Server
// Length method
func (a ByLast) Len() int { return len(a) }
// Swap method
func (a ByLast) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
// Less method
func (a ByLast) Less(i, j int) bool { return a[i].Last < a[j].Last }
// Ping method
func (s *Server) Ping() (float64, error) {
// for i := 0; i < 3; i++ {
ct.ChangeColor(ct.Blue, false, ct.None, false)
fmt.Println("[*] Testing server latency", s.URL)
r, err := ping.Ping(s.URL)
if err != nil {
return 0, err
}
if r.Time < 1 {
r.Time = 999999999999
}
s.List = append(s.List, r.Time)
s.Last = r.Time
return s.Last, nil
}
// main function
func main() {
banner, err := base64.RawStdEncoding.DecodeString(BANNER)
if err != nil {
ct.ChangeColor(ct.Red, false, ct.None, false)
fmt.Println(err)
} else {
ct.ChangeColor(ct.Magenta, false, ct.None, false)
fmt.Printf("%s\n", banner)
}
prompt := promptui.Select{
Label: "Select Mullvad server type",
Items: []string{"WireGuard", "OpenVPN", "Cancel"},
}
_, promptResult, err := prompt.Run()
if err != nil {
ct.ChangeColor(ct.Red, false, ct.None, false)
fmt.Println("[!] User prompt failed", err)
return
} else if promptResult == "Cancel" {
ct.ChangeColor(ct.Red, false, ct.None, false)
fmt.Printf("[!] Server test canceled by user, exiting...\n")
os.Exit(0)
} else {
ct.ChangeColor(ct.Blue, true, ct.None, false)
fmt.Printf("[*] You selected %q servers for testing\n", promptResult)
}
ct.ChangeColor(ct.Blue, false, ct.None, false)
fmt.Println("[*] Retrieving list of Mullvad servers...")
response, err := http.Get(URL)
if err != nil {
ct.ChangeColor(ct.Red, false, ct.None, false)
fmt.Println("[!] Request Error\n", err.Error())
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
jsonErr := json.Unmarshal(body, &servers)
ct.ChangeColor(ct.Blue, false, ct.None, false)
fmt.Printf("[*] There are %d total servers\n\n", len(servers))
if jsonErr != nil {
ct.ChangeColor(ct.Red, false, ct.None, false)
fmt.Println("[!] Json Error", jsonErr)
} else {
for i := range servers {
servers[i].URL = servers[i].Hostname + ".mullvad.net"
if servers[i].Type == strings.ToLower(promptResult) && servers[i].Active {
// if servers[i].Type == "wireguard" && servers[i].Active {
active = append(active, servers[i])
}
}
}
ct.ChangeColor(ct.Blue, false, ct.None, false)
fmt.Printf("[*] There are %d active servers\n\n", len(active))
// Test the valid servers.
var wg sync.WaitGroup
wg.Add(len(active) - 1)
for _, server := range active {
time.Sleep(100 * time.Millisecond)
go func(wg *sync.WaitGroup, server *Server) {
for i := 0; i < 3; i++ {
_, err := server.Ping()
if err != nil {
ct.ChangeColor(ct.Red, false, ct.None, false)
fmt.Println("[!] Error on ping", server.URL, err)
continue
}
break
}
wg.Done()
}(&wg, server)
}
wg.Wait()
ct.ChangeColor(ct.Magenta, false, ct.None, false)
// fmt.Println("------------------------------------------")
// fmt.Println("------------------------------------------")
// fmt.Println("==========================================")
fmt.Println("==========================================")
ct.ResetColor()
sort.Sort(ByLast(active))
printed := 0
for _, server := range active {
if server.Last < 1 {
continue
}
ct.ChangeColor(ct.Green, false, ct.None, false)
fmt.Println("[*]", server.URL, "\t", server.Last, "ms")
ct.ChangeColor(ct.Magenta, false, ct.None, false)
fmt.Println("------------------------------------------")
ct.ResetColor()
if printed >= 9 {
break
}
printed++
}
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module git.tcp.direct/moony/go-build-template
go 1.17
require (
github.com/daviddengcn/go-colortext v1.0.0
github.com/manifoldco/promptui v0.9.0
github.com/nuttapp/pinghist v0.0.0-20150302033657-ced89847b939
)
require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a // indirect
)

32
go.sum Normal file
View File

@ -0,0 +1,32 @@
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE=
github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c=
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A=
github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE=
github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ=
github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/nuttapp/pinghist v0.0.0-20150302033657-ced89847b939 h1:YEwmcezTDf+/5pcBEY3M0OeOpw1L1wxOyvP5Grg9NTg=
github.com/nuttapp/pinghist v0.0.0-20150302033657-ced89847b939/go.mod h1:5EgZXoqhrbFCROj3y3DWVb7deKUJiyu35+VevqETBOA=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=

5
pkg/version/version.go Normal file
View File

@ -0,0 +1,5 @@
package version
// VERSION is the app-global version string, which should be substituted with a
// real value during build.
var VERSION = "0.1.0"