Add reverse proxy boilerplate with configuration management
- Introduced the main entry point `main.go` with basic configuration loading and error handling. - Added `config` package to handle environment variables, help printing, and default settings. - Configured required dependencies in `go.mod` and included their checksums in `go.sum`. - Created `.gitignore` file for common Go development scenarios.
This commit is contained in:
110
.gitignore
vendored
Normal file
110
.gitignore
vendored
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Allowlisting gitignore template for GO projects prevents us
|
||||||
|
# from adding various unwanted local files, such as generated
|
||||||
|
# files, developer configurations or IDE-specific files etc.
|
||||||
|
#
|
||||||
|
# Recommended: Go.AllowList.gitignore
|
||||||
|
|
||||||
|
# Ignore everything
|
||||||
|
*
|
||||||
|
|
||||||
|
# Especially these
|
||||||
|
.vscode
|
||||||
|
.vscode/
|
||||||
|
.vscode/**
|
||||||
|
**/.vscode
|
||||||
|
**/.vscode/**
|
||||||
|
node_modules
|
||||||
|
node_modules/
|
||||||
|
node_modules/**
|
||||||
|
**/node_modules
|
||||||
|
**/node_modules/
|
||||||
|
**/node_modules/**
|
||||||
|
/test*
|
||||||
|
.idea
|
||||||
|
.idea/
|
||||||
|
.idea/**
|
||||||
|
/.idea/
|
||||||
|
/.idea/**
|
||||||
|
/.idea
|
||||||
|
# and others
|
||||||
|
/go.work.sum
|
||||||
|
/secp256k1/
|
||||||
|
|
||||||
|
# But not these files...
|
||||||
|
!/.gitignore
|
||||||
|
!*.go
|
||||||
|
!go.sum
|
||||||
|
!go.mod
|
||||||
|
!*.md
|
||||||
|
!LICENSE
|
||||||
|
!*.sh
|
||||||
|
!Makefile
|
||||||
|
!*.json
|
||||||
|
!*.pdf
|
||||||
|
!*.csv
|
||||||
|
!*.py
|
||||||
|
!*.mediawiki
|
||||||
|
!*.did
|
||||||
|
!*.rs
|
||||||
|
!*.toml
|
||||||
|
!*.file
|
||||||
|
!.gitkeep
|
||||||
|
!pkg/eth/**
|
||||||
|
!*.h
|
||||||
|
!*.c
|
||||||
|
!*.proto
|
||||||
|
!bundleData
|
||||||
|
!*.item
|
||||||
|
!*.bin
|
||||||
|
!*.yml
|
||||||
|
!*.yaml
|
||||||
|
!*.tmpl
|
||||||
|
!*.s
|
||||||
|
!*.asm
|
||||||
|
!.gitmodules
|
||||||
|
!*.txt
|
||||||
|
!*.sum
|
||||||
|
!pkg/version
|
||||||
|
!*.service
|
||||||
|
!*.benc
|
||||||
|
!*.png
|
||||||
|
!*.adoc
|
||||||
|
!*.js
|
||||||
|
!*.bash
|
||||||
|
!PATENTS
|
||||||
|
!*.css
|
||||||
|
!*.ts
|
||||||
|
!*.html
|
||||||
|
!Dockerfile
|
||||||
|
!*.lock
|
||||||
|
!*.nix
|
||||||
|
!license
|
||||||
|
!readme
|
||||||
|
!*.ico
|
||||||
|
!.idea/*
|
||||||
|
!*.xml
|
||||||
|
!.name
|
||||||
|
!.gitignore
|
||||||
|
!version
|
||||||
|
!out.jsonl
|
||||||
|
# ...even if they are in subdirectories
|
||||||
|
!*/
|
||||||
|
/blocklist.json
|
||||||
|
/gui/gui/main.wasm
|
||||||
|
/gui/gui/index.html
|
||||||
|
pkg/database/testrealy
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/dictionaries/project.xml
|
||||||
|
/.idea/shelf/Add_tombstone_handling__enhance_event_ID_logic__update_imports.xml
|
||||||
|
/.idea/.gitignore
|
||||||
|
/.idea/misc.xml
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/orly.dev.iml
|
||||||
|
/.idea/vcs.xml
|
||||||
|
/.idea/codeStyles/codeStyleConfig.xml
|
||||||
|
/.idea/material_theme_project_new.xml
|
||||||
|
/.idea/orly.iml
|
||||||
|
/.idea/go.imports.xml
|
||||||
|
/.idea/inspectionProfiles/Project_Default.xml
|
||||||
|
/.idea/.name
|
||||||
|
/.idea/reverse.iml
|
||||||
225
config/config.go
Normal file
225
config/config.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mleku/lol/chk"
|
||||||
|
"go-simpler.org/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
type C struct {
|
||||||
|
LogLevel string `env:"REVERSE_LOG_LEVEL" default:"info" usage:"Log level: fatal error warn info debug trace"`
|
||||||
|
Listen string `env:"REVERSE_LISTEN" usage:"Listen address for reverse proxy" default:":443"`
|
||||||
|
Mapping string `env:"REVERSE_MAPPING" usage:"file containing domain/target mappings for reverse proxy" default:"~/.config/reverse/mapping.conf"`
|
||||||
|
Cache string `env:"REVERSE_CERTCACHE" usage:"directory where certificates from letsencrypt are stored" default:"~/.cache/reverse"`
|
||||||
|
HSTS bool `env:"REVERSE_HSTS" usage:"add Strict-TransportSecurity header" default:"false"`
|
||||||
|
Email string `env:"REVERSE_EMAIL" usage:"email address presented to letsencrypt CA"`
|
||||||
|
HTTP string `env:"REVERSE_HTTP" usage:"http address for HTTP->HTTPS upgrades" default:":80"`
|
||||||
|
Idle time.Duration `env:"REVERSE_IDLE" usage:"how long idle connection is kept before closing" default:"1m"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (cfg *C, err error) {
|
||||||
|
cfg = new(C)
|
||||||
|
if err = env.Load(cfg, &env.Options{SliceSep: ","}); chk.T(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if GetEnv() {
|
||||||
|
PrintEnv(cfg, os.Stdout)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
if HelpRequested() {
|
||||||
|
PrintHelp(cfg, os.Stderr)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelpRequested determines if the command line arguments indicate a request for help
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - help: A boolean value indicating true if a help flag was detected in the
|
||||||
|
// command line arguments, false otherwise
|
||||||
|
//
|
||||||
|
// # Expected Behaviour
|
||||||
|
//
|
||||||
|
// The function checks the first command line argument for common help flags and
|
||||||
|
// returns true if any of them are present. Returns false if no help flag is found
|
||||||
|
func HelpRequested() (help bool) {
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
switch strings.ToLower(os.Args[1]) {
|
||||||
|
case "help", "-h", "--h", "-help", "--help", "?":
|
||||||
|
help = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnv checks if the first command line argument is "env" and returns
|
||||||
|
// whether the environment configuration should be printed.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - requested: A boolean indicating true if the 'env' argument was
|
||||||
|
// provided, false otherwise.
|
||||||
|
//
|
||||||
|
// # Expected Behaviour
|
||||||
|
//
|
||||||
|
// The function returns true when the first command line argument is "env"
|
||||||
|
// (case-insensitive), signalling that the environment configuration should be
|
||||||
|
// printed. Otherwise, it returns false.
|
||||||
|
func GetEnv() (requested bool) {
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
switch strings.ToLower(os.Args[1]) {
|
||||||
|
case "env":
|
||||||
|
requested = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// KV is a key/value pair.
|
||||||
|
type KV struct{ Key, Value string }
|
||||||
|
|
||||||
|
// KVSlice is a sortable slice of key/value pairs, designed for managing
|
||||||
|
// configuration data and enabling operations like merging and sorting based on
|
||||||
|
// keys.
|
||||||
|
type KVSlice []KV
|
||||||
|
|
||||||
|
func (kv KVSlice) Len() int { return len(kv) }
|
||||||
|
func (kv KVSlice) Less(i, j int) bool { return kv[i].Key < kv[j].Key }
|
||||||
|
func (kv KVSlice) Swap(i, j int) { kv[i], kv[j] = kv[j], kv[i] }
|
||||||
|
|
||||||
|
// Compose merges two KVSlice instances into a new slice where key-value pairs
|
||||||
|
// from the second slice override any duplicate keys from the first slice.
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - kv2: The second KVSlice whose entries will be merged with the receiver.
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - out: A new KVSlice containing all entries from both slices, with keys
|
||||||
|
// from kv2 taking precedence over keys from the receiver.
|
||||||
|
//
|
||||||
|
// # Expected Behaviour
|
||||||
|
//
|
||||||
|
// The method returns a new KVSlice that combines the contents of the receiver
|
||||||
|
// and kv2. If any key exists in both slices, the value from kv2 is used. The
|
||||||
|
// resulting slice remains sorted by keys as per the KVSlice implementation.
|
||||||
|
func (kv KVSlice) Compose(kv2 KVSlice) (out KVSlice) {
|
||||||
|
// duplicate the initial KVSlice
|
||||||
|
for _, p := range kv {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
for i, p := range kv2 {
|
||||||
|
for j, q := range out {
|
||||||
|
// if the key is repeated, replace the value
|
||||||
|
if p.Key == q.Key {
|
||||||
|
out[j].Value = kv2[i].Value
|
||||||
|
continue out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvKV generates key/value pairs from a configuration object's struct tags
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - cfg: A configuration object whose struct fields are processed for env tags
|
||||||
|
//
|
||||||
|
// # Return Values
|
||||||
|
//
|
||||||
|
// - m: A KVSlice containing key/value pairs derived from the config's env tags
|
||||||
|
//
|
||||||
|
// # Expected Behaviour
|
||||||
|
//
|
||||||
|
// Processes each field of the config object, extracting values tagged with
|
||||||
|
// "env" and converting them to strings. Skips fields without an "env" tag.
|
||||||
|
// Handles various value types including strings, integers, booleans, durations,
|
||||||
|
// and string slices by joining elements with commas.
|
||||||
|
func EnvKV(cfg any) (m KVSlice) {
|
||||||
|
t := reflect.TypeOf(cfg)
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
k := t.Field(i).Tag.Get("env")
|
||||||
|
v := reflect.ValueOf(cfg).Field(i).Interface()
|
||||||
|
var val string
|
||||||
|
switch v.(type) {
|
||||||
|
case string:
|
||||||
|
val = v.(string)
|
||||||
|
case int, bool, time.Duration:
|
||||||
|
val = fmt.Sprint(v)
|
||||||
|
case []string:
|
||||||
|
arr := v.([]string)
|
||||||
|
if len(arr) > 0 {
|
||||||
|
val = strings.Join(arr, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this can happen with embedded structs
|
||||||
|
if k == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m = append(m, KV{k, val})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintEnv outputs sorted environment key/value pairs from a configuration object
|
||||||
|
// to the provided writer
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - cfg: Pointer to the configuration object containing env tags
|
||||||
|
//
|
||||||
|
// - printer: Destination for the output, typically an io.Writer implementation
|
||||||
|
//
|
||||||
|
// # Expected Behaviour
|
||||||
|
//
|
||||||
|
// Outputs each environment variable derived from the config's struct tags in
|
||||||
|
// sorted order, formatted as "key=value\n" to the specified writer
|
||||||
|
func PrintEnv(cfg *C, printer io.Writer) {
|
||||||
|
kvs := EnvKV(*cfg)
|
||||||
|
sort.Sort(kvs)
|
||||||
|
for _, v := range kvs {
|
||||||
|
_, _ = fmt.Fprintf(printer, "%s=%s\n", v.Key, v.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintHelp prints help information including application version, environment
|
||||||
|
// variable configuration, and details about .env file handling to the provided
|
||||||
|
// writer
|
||||||
|
//
|
||||||
|
// # Parameters
|
||||||
|
//
|
||||||
|
// - cfg: Configuration object containing app name and config directory path
|
||||||
|
//
|
||||||
|
// - printer: Output destination for the help text
|
||||||
|
//
|
||||||
|
// # Expected Behaviour
|
||||||
|
//
|
||||||
|
// Prints application name and version followed by environment variable
|
||||||
|
// configuration details, explains .env file behaviour including automatic
|
||||||
|
// loading and custom path options, and displays current configuration values
|
||||||
|
// using PrintEnv. Outputs all information to the specified writer
|
||||||
|
func PrintHelp(cfg *C, printer io.Writer) {
|
||||||
|
_, _ = fmt.Fprintf(printer, "reverse usage:\n\n")
|
||||||
|
env.Usage(cfg, printer, &env.Options{SliceSep: ","})
|
||||||
|
_, _ = fmt.Fprintf(
|
||||||
|
printer,
|
||||||
|
"\nCLI parameter 'help' also prints this information\n"+
|
||||||
|
"use the parameter 'env' to print out the current configuration to the terminal\n\n",
|
||||||
|
)
|
||||||
|
_, _ = fmt.Fprintf(printer, "current configuration:\n\n")
|
||||||
|
PrintEnv(cfg, printer)
|
||||||
|
return
|
||||||
|
}
|
||||||
16
go.mod
Normal file
16
go.mod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module github.com/mleku/reverse
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mleku/lol v1.0.1
|
||||||
|
go-simpler.org/env v0.12.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
)
|
||||||
15
go.sum
Normal file
15
go.sum
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mleku/lol v1.0.1 h1:CMGpeMTh2iE7Xc4/dtJxs0vZqwRmPiGxsqpWxdmOZKc=
|
||||||
|
github.com/mleku/lol v1.0.1/go.mod h1:daW3rL0XP4ZKscvWn990AJCrJs2Lsu+sdrI9cWbacWE=
|
||||||
|
go-simpler.org/env v0.12.0 h1:kt/lBts0J1kjWJAnB740goNdvwNxt5emhYngL0Fzufs=
|
||||||
|
go-simpler.org/env v0.12.0/go.mod h1:cc/5Md9JCUM7LVLtN0HYjPTDcI3Q8TDaPlNTAlDU+WI=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
21
main.go
Normal file
21
main.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mleku/lol/chk"
|
||||||
|
"github.com/mleku/reverse/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
var cfg *config.C
|
||||||
|
if cfg, err = config.New(); chk.T(err) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err)
|
||||||
|
}
|
||||||
|
config.PrintHelp(cfg, os.Stderr)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user