mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-13 03:58:45 +00:00
feat: add initial implementation of osiris, the TLS terminator for Anubis
Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
48
cmd/osiris/internal/config/bind.go
Normal file
48
cmd/osiris/internal/config/bind.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCantBindToPort = errors.New("bind: can't bind to host:port")
|
||||
)
|
||||
|
||||
type Bind struct {
|
||||
HTTP string `hcl:"http"`
|
||||
HTTPS string `hcl:"https"`
|
||||
Metrics string `hcl:"metrics"`
|
||||
}
|
||||
|
||||
func (b *Bind) Valid() error {
|
||||
var errs []error
|
||||
|
||||
ln, err := net.Listen("tcp", b.HTTP)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w %q: %w", ErrCantBindToPort, b.HTTP, err))
|
||||
} else {
|
||||
defer ln.Close()
|
||||
}
|
||||
|
||||
ln, err = net.Listen("tcp", b.HTTPS)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w %q: %w", ErrCantBindToPort, b.HTTPS, err))
|
||||
} else {
|
||||
defer ln.Close()
|
||||
}
|
||||
|
||||
ln, err = net.Listen("tcp", b.Metrics)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w %q: %w", ErrCantBindToPort, b.Metrics, err))
|
||||
} else {
|
||||
defer ln.Close()
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
55
cmd/osiris/internal/config/bind_test.go
Normal file
55
cmd/osiris/internal/config/bind_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBindValid(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
precondition func(t *testing.T)
|
||||
bind Bind
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
precondition: nil,
|
||||
bind: Bind{
|
||||
HTTP: ":8081",
|
||||
HTTPS: ":8082",
|
||||
Metrics: ":8083",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "reused ports",
|
||||
precondition: func(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", ":8081")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { ln.Close() })
|
||||
},
|
||||
bind: Bind{
|
||||
HTTP: ":8081",
|
||||
HTTPS: ":8081",
|
||||
Metrics: ":8081",
|
||||
},
|
||||
err: ErrCantBindToPort,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.precondition != nil {
|
||||
tt.precondition(t)
|
||||
}
|
||||
|
||||
if err := tt.bind.Valid(); !errors.Is(err, tt.err) {
|
||||
t.Logf("want: %v", tt.err)
|
||||
t.Logf("got: %v", err)
|
||||
t.Error("got wrong error from validation function")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
31
cmd/osiris/internal/config/config.go
Normal file
31
cmd/osiris/internal/config/config.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Toplevel struct {
|
||||
Bind Bind `hcl:"bind,block"`
|
||||
Domains []Domain `hcl:"domain,block"`
|
||||
}
|
||||
|
||||
func (t *Toplevel) Valid() error {
|
||||
var errs []error
|
||||
|
||||
if err := t.Bind.Valid(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid bind block:\n%w", err))
|
||||
}
|
||||
|
||||
for _, d := range t.Domains {
|
||||
if err := d.Valid(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("when parsing domain %s: %w", d.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("invalid configuration file:\n%w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
65
cmd/osiris/internal/config/domain.go
Normal file
65
cmd/osiris/internal/config/domain.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidDomainName = errors.New("domain: name is invalid")
|
||||
ErrInvalidDomainTLSConfig = errors.New("domain: TLS config is invalid")
|
||||
ErrInvalidURL = errors.New("invalid URL")
|
||||
ErrInvalidURLScheme = errors.New("URL has invalid scheme")
|
||||
)
|
||||
|
||||
type Domain struct {
|
||||
Name string `hcl:"name,label"`
|
||||
TLS TLS `hcl:"tls,block"`
|
||||
Target string `hcl:"target"`
|
||||
HealthTarget string `hcl:"health_target"`
|
||||
}
|
||||
|
||||
func (d Domain) Valid() error {
|
||||
var errs []error
|
||||
|
||||
if _, err := idna.Lookup.ToASCII(d.Name); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w %q: %w", ErrInvalidDomainName, d.Name, err))
|
||||
}
|
||||
|
||||
if err := d.TLS.Valid(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w: %w", ErrInvalidDomainTLSConfig, err))
|
||||
}
|
||||
|
||||
if err := isURLValid(d.Target); err != nil {
|
||||
errs = append(errs, fmt.Errorf("target has %w %q: %w", ErrInvalidURL, d.Target, err))
|
||||
}
|
||||
|
||||
if err := isURLValid(d.HealthTarget); err != nil {
|
||||
errs = append(errs, fmt.Errorf("health_target has %w %q: %w", ErrInvalidURL, d.HealthTarget, err))
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isURLValid(input string) error {
|
||||
u, err := url.Parse(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "http", "https", "h2c", "unix":
|
||||
// do nothing
|
||||
default:
|
||||
return fmt.Errorf("%w %s has scheme %s (want http, https, h2c, unix)", ErrInvalidURLScheme, input, u.Scheme)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
cmd/osiris/internal/config/domain_test.go
Normal file
89
cmd/osiris/internal/config/domain_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDomainValid(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
input Domain
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "simple happy path",
|
||||
input: Domain{
|
||||
Name: "anubis.techaro.lol",
|
||||
TLS: TLS{
|
||||
Cert: "./testdata/tls/selfsigned.crt",
|
||||
Key: "./testdata/tls/selfsigned.key",
|
||||
},
|
||||
Target: "http://localhost:3000",
|
||||
HealthTarget: "http://localhost:9091/healthz",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid domain name",
|
||||
input: Domain{
|
||||
Name: "\uFFFD.techaro.lol",
|
||||
TLS: TLS{
|
||||
Cert: "./testdata/tls/selfsigned.crt",
|
||||
Key: "./testdata/tls/selfsigned.key",
|
||||
},
|
||||
Target: "http://localhost:3000",
|
||||
HealthTarget: "http://localhost:9091/healthz",
|
||||
},
|
||||
err: ErrInvalidDomainName,
|
||||
},
|
||||
{
|
||||
name: "invalid tls config",
|
||||
input: Domain{
|
||||
Name: "anubis.techaro.lol",
|
||||
TLS: TLS{
|
||||
Cert: "./testdata/tls/invalid.crt",
|
||||
Key: "./testdata/tls/invalid.key",
|
||||
},
|
||||
Target: "http://localhost:3000",
|
||||
HealthTarget: "http://localhost:9091/healthz",
|
||||
},
|
||||
err: ErrInvalidDomainTLSConfig,
|
||||
},
|
||||
{
|
||||
name: "invalid URL",
|
||||
input: Domain{
|
||||
Name: "anubis.techaro.lol",
|
||||
TLS: TLS{
|
||||
Cert: "./testdata/tls/selfsigned.crt",
|
||||
Key: "./testdata/tls/selfsigned.key",
|
||||
},
|
||||
Target: "file://[::1:3000",
|
||||
HealthTarget: "file://[::1:9091/healthz",
|
||||
},
|
||||
err: ErrInvalidURL,
|
||||
},
|
||||
{
|
||||
name: "wrong URL scheme",
|
||||
input: Domain{
|
||||
Name: "anubis.techaro.lol",
|
||||
TLS: TLS{
|
||||
Cert: "./testdata/tls/selfsigned.crt",
|
||||
Key: "./testdata/tls/selfsigned.key",
|
||||
},
|
||||
Target: "file://localhost:3000",
|
||||
HealthTarget: "file://localhost:9091/healthz",
|
||||
},
|
||||
err: ErrInvalidURLScheme,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
|
||||
t.Logf("want: %v", tt.err)
|
||||
t.Logf("got: %v", err)
|
||||
t.Error("got wrong error from validation function")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1
cmd/osiris/internal/config/testdata/tls/invalid.crt
vendored
Normal file
1
cmd/osiris/internal/config/testdata/tls/invalid.crt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
aorsentaeiorsntoiearnstoieanrsoietnaioresntoeiar
|
||||
1
cmd/osiris/internal/config/testdata/tls/invalid.key
vendored
Normal file
1
cmd/osiris/internal/config/testdata/tls/invalid.key
vendored
Normal file
@@ -0,0 +1 @@
|
||||
aorsentaeiorsntoiearnstoieanrsoietnaioresntoeiar
|
||||
11
cmd/osiris/internal/config/testdata/tls/selfsigned.crt
vendored
Normal file
11
cmd/osiris/internal/config/testdata/tls/selfsigned.crt
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBnzCCAVGgAwIBAgIUAw8funCpiB3ZAAPoWdSCWnzbsFIwBQYDK2VwMEUxCzAJ
|
||||
BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l
|
||||
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjUwNzE4MTkwMjM1WhcNMjUwODE3MTkwMjM1
|
||||
WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwY
|
||||
SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMCowBQYDK2VwAyEAcXDHXV3vgpvjtTaz
|
||||
s0Oj/73rMr06bhyGGhleYS1MNoWjUzBRMB0GA1UdDgQWBBQwmfKPthucFHB6Wfgz
|
||||
2Nj5nkMQOjAfBgNVHSMEGDAWgBQwmfKPthucFHB6Wfgz2Nj5nkMQOjAPBgNVHRMB
|
||||
Af8EBTADAQH/MAUGAytlcANBALBYbULlGwB7Ro0UTgUoQDNxEvayn3qzVFHIt7lC
|
||||
/2/NzNBkk4yPT+a4mbRuydxLkv+JIvmQbarZxpksYnWlCAM=
|
||||
-----END CERTIFICATE-----
|
||||
3
cmd/osiris/internal/config/testdata/tls/selfsigned.key
vendored
Normal file
3
cmd/osiris/internal/config/testdata/tls/selfsigned.key
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIOHKoX22Mha6SnnpLm34fSSfTUDbRiDCi6N1nOgTOlds
|
||||
-----END PRIVATE KEY-----
|
||||
40
cmd/osiris/internal/config/tls.go
Normal file
40
cmd/osiris/internal/config/tls.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCantReadTLS = errors.New("tls: can't read TLS")
|
||||
ErrInvalidTLSKeypair = errors.New("tls: can't parse TLS keypair")
|
||||
)
|
||||
|
||||
type TLS struct {
|
||||
Cert string `hcl:"cert"`
|
||||
Key string `hcl:"key"`
|
||||
}
|
||||
|
||||
func (t TLS) Valid() error {
|
||||
var errs []error
|
||||
|
||||
if _, err := os.Stat(t.Cert); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w certificate %s: %w", ErrCantReadTLS, t.Cert, err))
|
||||
}
|
||||
|
||||
if _, err := os.Stat(t.Key); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w key %s: %w", ErrCantReadTLS, t.Key, err))
|
||||
}
|
||||
|
||||
if _, err := tls.LoadX509KeyPair(t.Cert, t.Key); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%w (%s, %s): %w", ErrInvalidTLSKeypair, t.Cert, t.Key, err))
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
48
cmd/osiris/internal/config/tls_test.go
Normal file
48
cmd/osiris/internal/config/tls_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTLSValid(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
input TLS
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "simple selfsigned",
|
||||
input: TLS{
|
||||
Cert: "./testdata/tls/selfsigned.crt",
|
||||
Key: "./testdata/tls/selfsigned.key",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "files don't exist",
|
||||
input: TLS{
|
||||
Cert: "./testdata/tls/nonexistent.crt",
|
||||
Key: "./testdata/tls/nonexistent.key",
|
||||
},
|
||||
err: ErrCantReadTLS,
|
||||
},
|
||||
{
|
||||
name: "invalid keypair",
|
||||
input: TLS{
|
||||
Cert: "./testdata/tls/invalid.crt",
|
||||
Key: "./testdata/tls/invalid.key",
|
||||
},
|
||||
err: ErrInvalidTLSKeypair,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
|
||||
t.Logf("want: %v", tt.err)
|
||||
t.Logf("got: %v", err)
|
||||
t.Error("got wrong error from validation function")
|
||||
} else {
|
||||
t.Log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
37
cmd/osiris/internal/entrypoint/entrypoint.go
Normal file
37
cmd/osiris/internal/entrypoint/entrypoint.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/TecharoHQ/anubis/cmd/osiris/internal/config"
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
ConfigFname string
|
||||
}
|
||||
|
||||
func Main(opts Options) error {
|
||||
internal.SetHealth("osiris", healthv1.HealthCheckResponse_NOT_SERVING)
|
||||
|
||||
var cfg config.Toplevel
|
||||
if err := hclsimple.DecodeFile(opts.ConfigFname, nil, &cfg); err != nil {
|
||||
return fmt.Errorf("can't read configuration file %s:\n\n%w", opts.ConfigFname, err)
|
||||
}
|
||||
|
||||
if err := cfg.Valid(); err != nil {
|
||||
return fmt.Errorf("configuration file %s is invalid:\n\n%w", opts.ConfigFname, err)
|
||||
}
|
||||
|
||||
rtr, err := NewRouter(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Info("listening on", "http", cfg.Bind.HTTP)
|
||||
return http.ListenAndServe(cfg.Bind.HTTP, rtr)
|
||||
}
|
||||
33
cmd/osiris/internal/entrypoint/h2c.go
Normal file
33
cmd/osiris/internal/entrypoint/h2c.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func newH2CReverseProxy(target *url.URL) *httputil.ReverseProxy {
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
req.Host = target.Host
|
||||
}
|
||||
|
||||
// Use h2c transport
|
||||
transport := &http2.Transport{
|
||||
AllowHTTP: true,
|
||||
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
// Just do plain TCP (h2c)
|
||||
return net.Dial(network, addr)
|
||||
},
|
||||
}
|
||||
|
||||
return &httputil.ReverseProxy{
|
||||
Director: director,
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
114
cmd/osiris/internal/entrypoint/router.go
Normal file
114
cmd/osiris/internal/entrypoint/router.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package entrypoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/TecharoHQ/anubis/cmd/osiris/internal/config"
|
||||
"github.com/lum8rjack/go-ja4h"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTargetInvalid = errors.New("[unexpected] target invalid")
|
||||
ErrNoHandler = errors.New("[unexpected] no handler for domain")
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
lock sync.RWMutex
|
||||
routes map[string]http.Handler
|
||||
}
|
||||
|
||||
func (rtr *Router) setConfig(c config.Toplevel) error {
|
||||
var errs []error
|
||||
newMap := map[string]http.Handler{}
|
||||
|
||||
for _, d := range c.Domains {
|
||||
var domainErrs []error
|
||||
|
||||
u, err := url.Parse(d.Target)
|
||||
if err != nil {
|
||||
domainErrs = append(domainErrs, fmt.Errorf("%w %q: %v", ErrTargetInvalid, d.Target, err))
|
||||
}
|
||||
|
||||
var h http.Handler
|
||||
|
||||
switch u.Scheme {
|
||||
case "http", "https":
|
||||
h = httputil.NewSingleHostReverseProxy(u)
|
||||
case "h2c":
|
||||
h = newH2CReverseProxy(u)
|
||||
case "unix":
|
||||
h = &httputil.ReverseProxy{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||
return net.Dial("unix", strings.TrimPrefix(d.Target, "unix://"))
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if h == nil {
|
||||
domainErrs = append(domainErrs, ErrNoHandler)
|
||||
}
|
||||
|
||||
newMap[d.Name] = h
|
||||
|
||||
if len(domainErrs) != 0 {
|
||||
errs = append(errs, fmt.Errorf("invalid domain %s: %w", d.Name, errors.Join(domainErrs...)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("can't compile config to routing map: %w", errors.Join(errs...))
|
||||
}
|
||||
|
||||
rtr.lock.Lock()
|
||||
rtr.routes = newMap
|
||||
rtr.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRouter(c config.Toplevel) (*Router, error) {
|
||||
result := &Router{
|
||||
routes: map[string]http.Handler{},
|
||||
}
|
||||
|
||||
if err := result.setConfig(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("%#v\n", result.routes)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (rtr *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var h http.Handler
|
||||
var ok bool
|
||||
|
||||
ja4hFP := ja4h.JA4H(r)
|
||||
|
||||
slog.Info("got request", "method", r.Method, "host", r.Host, "path", r.URL.Path)
|
||||
|
||||
rtr.lock.RLock()
|
||||
h, ok = rtr.routes[r.Host]
|
||||
rtr.lock.RUnlock()
|
||||
|
||||
if !ok {
|
||||
http.NotFound(w, r) // TODO(Xe): brand this
|
||||
return
|
||||
}
|
||||
|
||||
r.Header.Set("X-HTTP-JA4H-Fingerprint", ja4hFP)
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
37
cmd/osiris/main.go
Normal file
37
cmd/osiris/main.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/cmd/osiris/internal/entrypoint"
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
"github.com/facebookgo/flagenv"
|
||||
)
|
||||
|
||||
var (
|
||||
configFname = flag.String("config", "./osiris.hcl", "Configuration file (HCL), see docs")
|
||||
slogLevel = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)")
|
||||
versionFlag = flag.Bool("version", false, "if true, show version information then quit")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flagenv.Parse()
|
||||
flag.Parse()
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Println("Osiris", anubis.Version)
|
||||
return
|
||||
}
|
||||
|
||||
internal.InitSlog(*slogLevel)
|
||||
|
||||
if err := entrypoint.Main(entrypoint.Options{
|
||||
ConfigFname: *configFname,
|
||||
}); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
15
cmd/osiris/osiris.hcl
Normal file
15
cmd/osiris/osiris.hcl
Normal file
@@ -0,0 +1,15 @@
|
||||
bind {
|
||||
http = ":3004"
|
||||
https = ":3005"
|
||||
metrics = ":9091"
|
||||
}
|
||||
|
||||
domain "anubis.techaro.lol" {
|
||||
tls {
|
||||
cert = "./internal/config/testdata/tls/selfsigned.crt"
|
||||
key = "./internal/config/testdata/tls/selfsigned.key"
|
||||
}
|
||||
|
||||
target = "http://localhost:3000"
|
||||
health_target = "http://localhost:9091/healthz"
|
||||
}
|
||||
Reference in New Issue
Block a user