diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e39a3d9..241d934 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,5 +14,13 @@ jobs: - run: sudo add-apt-repository ppa:inkscape.dev/stable - run: sudo apt-get update - run: sudo apt-get install inkscape -y + - name: Set up MariaDB + id: mariadb + uses: rusher/action-setup-mariadb@v1.6 + with: + tag: '11.4' + local: true # Force local installation + root-password: 'myRootPassword' + database: 'myDb' - run: go build ./cmd/violet/ - - run: go test ./... + - run: DB="root:myRootPassword@tcp(127.0.0.1)/myDb" go test ./... diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e52087d --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +.PHONY: all sqlc + +all: sqlc + +sqlc: + sqlc generate diff --git a/certs/certs.go b/certs/certs.go index 87ea70d..66fb8a9 100644 --- a/certs/certs.go +++ b/certs/certs.go @@ -7,7 +7,6 @@ import ( "github.com/1f349/violet/logger" "github.com/1f349/violet/utils" "github.com/mrmelon54/certgen" - "github.com/mrmelon54/rescheduler" "io/fs" "math/big" "os" @@ -28,13 +27,12 @@ type Certs struct { m map[string]*tls.Certificate ca *certgen.CertGen sn atomic.Int64 - r *rescheduler.Rescheduler t *time.Ticker ts chan struct{} } // New creates a new cert list -func New(certDir fs.FS, keyDir fs.FS, selfCert bool) *Certs { +func New(certDir fs.FS, keyDir fs.FS, selfCert bool, gap time.Duration) *Certs { c := &Certs{ cDir: certDir, kDir: keyDir, @@ -45,15 +43,13 @@ func New(certDir fs.FS, keyDir fs.FS, selfCert bool) *Certs { } if !selfCert { - // the rescheduler isn't even used in self cert mode so why initialise it - c.r = rescheduler.NewRescheduler(c.threadCompile) - - c.t = time.NewTicker(2 * time.Hour) + // the refresh loop isn't even used in self cert mode so why initialise it + c.t = time.NewTicker(gap) go func() { for { select { case <-c.t.C: - c.Compile() + c.threadCompile() case <-c.ts: return } @@ -121,18 +117,6 @@ func (c *Certs) GetCertForDomain(domain string) *tls.Certificate { return nil } -// Compile loads the certificates and keys from the directories. -// -// This method makes use of the rescheduler instead of just ignoring multiple -// calls. -func (c *Certs) Compile() { - // don't bother compiling in self-signed mode - if c.ss { - return - } - c.r.Run() -} - func (c *Certs) Stop() { if c.t != nil { c.t.Stop() diff --git a/certs/certs_test.go b/certs/certs_test.go index 4326751..bfbcbb7 100644 --- a/certs/certs_test.go +++ b/certs/certs_test.go @@ -52,7 +52,7 @@ func TestCertsNew_Lookup(t *testing.T) { }, } - certs := New(certDir, keyDir, false) + certs := New(certDir, keyDir, false, 5*time.Second) assert.NoError(t, certs.internalCompile(certs.m)) cc := certs.GetCertForDomain("example.com") leaf := certgen.TlsLeaf(cc) @@ -67,7 +67,7 @@ func TestCertsNew_SelfSigned(t *testing.T) { return } - certs := New(nil, nil, true) + certs := New(nil, nil, true, 5*time.Second) cc := certs.GetCertForDomain("example.com") leaf := certgen.TlsLeaf(cc) assert.Equal(t, []string{"example.com"}, leaf.DNSNames) diff --git a/cmd/violet/conf.go b/cmd/violet/conf.go index d002389..f8f481e 100644 --- a/cmd/violet/conf.go +++ b/cmd/violet/conf.go @@ -1,12 +1,16 @@ package main +import "github.com/1f349/violet/utils" + type startUpConfig struct { - SelfSigned bool `json:"self_signed"` - ErrorPagePath string `json:"error_page_path"` - Listen listenConfig `json:"listen"` - InkscapeCmd string `json:"inkscape"` - RateLimit uint64 `json:"rate_limit"` - MetricsToken string `json:"metrics_token"` + SelfSigned bool `json:"self_signed"` + ErrorPagePath string `json:"error_page_path"` + Listen listenConfig `json:"listen"` + InkscapeCmd string `json:"inkscape"` + RateLimit uint64 `json:"rate_limit"` + MetricsToken string `json:"metrics_token"` + TableRefresh utils.DurationText `json:"table_refresh"` + CertRefresh utils.DurationText `json:"cert_refresh"` } type listenConfig struct { diff --git a/cmd/violet/serve.go b/cmd/violet/serve.go index 00706ef..5c33b57 100644 --- a/cmd/violet/serve.go +++ b/cmd/violet/serve.go @@ -127,14 +127,20 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) certDir := os.DirFS(filepath.Join(wd, "certs")) keyDir := os.DirFS(filepath.Join(wd, "keys")) + dynamicErrorPages, err := errorPages.New(errorPageDir) + if err != nil { + logger.Logger.Fatal("Failed to load error pages", "err", err) + } + + serviceCtx, cancelService := context.WithCancel(context.Background()) + ws := websocket.NewServer() - allowedDomains := domains.New(db) // load allowed domains - acmeChallenges := utils.NewAcmeChallenge() // load acme challenge store - allowedCerts := certs.New(certDir, keyDir, config.SelfSigned) // load certificate manager - hybridTransport := proxy.NewHybridTransport(ws) // load reverse proxy - dynamicFavicons := favicons.New(db, config.InkscapeCmd) // load dynamic favicon provider - dynamicErrorPages := errorPages.New(errorPageDir) // load dynamic error page provider - dynamicRouter := router.NewManager(db, hybridTransport) // load dynamic router manager + allowedDomains := domains.New(serviceCtx, db, time.Duration(config.TableRefresh)) // load allowed domains + acmeChallenges := utils.NewAcmeChallenge() // load acme challenge store + allowedCerts := certs.New(certDir, keyDir, config.SelfSigned, time.Duration(config.CertRefresh)) // load certificate manager + hybridTransport := proxy.NewHybridTransport(ws) // load reverse proxy + dynamicFavicons := favicons.New(db, config.InkscapeCmd) // load dynamic favicon provider + dynamicRouter := router.NewManager(serviceCtx, db, hybridTransport, time.Duration(config.TableRefresh)) // load dynamic router manager // struct containing config for the http servers srvConf := &conf.Conf{ @@ -150,7 +156,7 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) } // create the compilable list and run a first time compile - allCompilables := utils.MultiCompilable{allowedDomains, allowedCerts, dynamicFavicons, dynamicErrorPages, dynamicRouter} + allCompilables := utils.MultiCompilable{dynamicFavicons} allCompilables.Compile() _, httpsPort, ok := utils.SplitDomainPort(config.Listen.Https, 443) @@ -219,6 +225,8 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) os.Exit(1) }) + cancelService() + // stop updating certificates allowedCerts.Stop() diff --git a/cmd/violet/setup.go b/cmd/violet/setup.go index d622cbc..8e91368 100644 --- a/cmd/violet/setup.go +++ b/cmd/violet/setup.go @@ -21,6 +21,7 @@ import ( "path" "path/filepath" "strconv" + "time" ) type setupCmd struct { @@ -154,7 +155,7 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) // domain manager to add a domain, no need to compile here as the program needs // to be run again with the serve subcommand - allowedDomains := domains.New(db) + allowedDomains := domains.New(context.Background(), db, 5*time.Second) allowedDomains.Put(answers.FirstDomain, true) // don't bother with this part is the api won't be listening @@ -181,7 +182,7 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) // add with the route manager, no need to compile as this will run when opened // with the serve subcommand - routeManager := router.NewManager(db, proxy.NewHybridTransportWithCalls(&nilTransport{}, &nilTransport{}, &websocket.Server{})) + routeManager := router.NewManager(context.Background(), db, proxy.NewHybridTransportWithCalls(&nilTransport{}, &nilTransport{}, &websocket.Server{}), 5*time.Minute) err = routeManager.InsertRoute(target.RouteWithActive{ Route: target.Route{ Src: path.Join(apiUrl.Host, apiUrl.Path), diff --git a/database/db.go b/database/db.go index 61f5bf4..85d4b8c 100644 --- a/database/db.go +++ b/database/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.25.0 +// sqlc v1.30.0 package database diff --git a/database/domain.sql.go b/database/domain.sql.go index 4cad183..54dd709 100644 --- a/database/domain.sql.go +++ b/database/domain.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.25.0 +// sqlc v1.30.0 // source: domain.sql package database @@ -10,7 +10,6 @@ import ( ) const addDomain = `-- name: AddDomain :exec -INSERT OR REPLACE INTO domains (domain, active) VALUES (?, ?) @@ -27,7 +26,6 @@ func (q *Queries) AddDomain(ctx context.Context, arg AddDomainParams) error { } const deleteDomain = `-- name: DeleteDomain :exec -INSERT OR REPLACE INTO domains(domain, active) VALUES (?, false) diff --git a/database/favicon.sql.go b/database/favicon.sql.go index 1513cce..c41284d 100644 --- a/database/favicon.sql.go +++ b/database/favicon.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.25.0 +// sqlc v1.30.0 // source: favicon.sql package database @@ -51,8 +51,8 @@ func (q *Queries) GetFavicons(ctx context.Context) ([]GetFaviconsRow, error) { } const updateFaviconCache = `-- name: UpdateFaviconCache :exec -INSERT OR -REPLACE INTO favicons (host, svg, png, ico) +REPLACE +INTO favicons (host, svg, png, ico) VALUES (?, ?, ?, ?) ` diff --git a/database/migrations/20240308125121_domains.down.sql b/database/migrations/20240308125121_domains.down.sql new file mode 100644 index 0000000..2fdc4e3 --- /dev/null +++ b/database/migrations/20240308125121_domains.down.sql @@ -0,0 +1 @@ +DROP TABLE domains; diff --git a/database/migrations/20240308125121_domains.up.sql b/database/migrations/20240308125121_domains.up.sql new file mode 100644 index 0000000..cf2ab37 --- /dev/null +++ b/database/migrations/20240308125121_domains.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS domains +( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + domain TEXT UNIQUE NOT NULL, + active BOOLEAN NOT NULL DEFAULT 1 +); diff --git a/database/migrations/20240308125121_init.down.sql b/database/migrations/20240308125121_init.down.sql deleted file mode 100644 index faf92e0..0000000 --- a/database/migrations/20240308125121_init.down.sql +++ /dev/null @@ -1,4 +0,0 @@ -DROP TABLE domains; -DROP TABLE favicons; -DROP TABLE routes; -DROP TABLE redirects; diff --git a/database/migrations/20240308125121_init.up.sql b/database/migrations/20240308125121_init.up.sql deleted file mode 100644 index e9469f4..0000000 --- a/database/migrations/20240308125121_init.up.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE TABLE IF NOT EXISTS domains -( - id INTEGER PRIMARY KEY AUTOINCREMENT, - domain TEXT UNIQUE NOT NULL, - active BOOLEAN NOT NULL DEFAULT 1 -); - -CREATE TABLE IF NOT EXISTS favicons -( - id INTEGER PRIMARY KEY AUTOINCREMENT, - host VARCHAR NOT NULL, - svg VARCHAR, - png VARCHAR, - ico VARCHAR -); - -CREATE TABLE IF NOT EXISTS routes -( - id INTEGER PRIMARY KEY AUTOINCREMENT, - source TEXT UNIQUE NOT NULL, - destination TEXT NOT NULL, - description TEXT NOT NULL, - flags INTEGER NOT NULL DEFAULT 0, - active BOOLEAN NOT NULL DEFAULT 1 -); - -CREATE TABLE IF NOT EXISTS redirects -( - id INTEGER PRIMARY KEY AUTOINCREMENT, - source TEXT UNIQUE NOT NULL, - destination TEXT NOT NULL, - description TEXT NOT NULL, - flags INTEGER NOT NULL DEFAULT 0, - code INTEGER NOT NULL DEFAULT 0, - active BOOLEAN NOT NULL DEFAULT 1 -); diff --git a/database/migrations/20251207020207_favicons.down.sql b/database/migrations/20251207020207_favicons.down.sql new file mode 100644 index 0000000..567fee6 --- /dev/null +++ b/database/migrations/20251207020207_favicons.down.sql @@ -0,0 +1 @@ +DROP TABLE favicons; diff --git a/database/migrations/20251207020207_favicons.up.sql b/database/migrations/20251207020207_favicons.up.sql new file mode 100644 index 0000000..5c83a60 --- /dev/null +++ b/database/migrations/20251207020207_favicons.up.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS favicons +( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + host TEXT NOT NULL, + svg TEXT, + png TEXT, + ico TEXT +); diff --git a/database/migrations/20251207020211_routes.down.sql b/database/migrations/20251207020211_routes.down.sql new file mode 100644 index 0000000..a71676d --- /dev/null +++ b/database/migrations/20251207020211_routes.down.sql @@ -0,0 +1 @@ +DROP TABLE routes; diff --git a/database/migrations/20251207020211_routes.up.sql b/database/migrations/20251207020211_routes.up.sql new file mode 100644 index 0000000..14a5694 --- /dev/null +++ b/database/migrations/20251207020211_routes.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS routes +( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + source TEXT UNIQUE NOT NULL, + destination TEXT NOT NULL, + description TEXT NOT NULL, + flags INTEGER NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT 1 +); diff --git a/database/migrations/20251207020214_redirects.down.sql b/database/migrations/20251207020214_redirects.down.sql new file mode 100644 index 0000000..93de988 --- /dev/null +++ b/database/migrations/20251207020214_redirects.down.sql @@ -0,0 +1 @@ +DROP TABLE redirects; diff --git a/database/migrations/20251207020214_redirects.up.sql b/database/migrations/20251207020214_redirects.up.sql new file mode 100644 index 0000000..eca40eb --- /dev/null +++ b/database/migrations/20251207020214_redirects.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS redirects +( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + source TEXT UNIQUE NOT NULL, + destination TEXT NOT NULL, + description TEXT NOT NULL, + flags INTEGER NOT NULL DEFAULT 0, + code INTEGER NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT 1 +); diff --git a/database/models.go b/database/models.go index 836327b..94eec3f 100644 --- a/database/models.go +++ b/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.25.0 +// sqlc v1.30.0 package database @@ -11,13 +11,13 @@ import ( ) type Domain struct { - ID int64 `json:"id"` + ID int32 `json:"id"` Domain string `json:"domain"` Active bool `json:"active"` } type Favicon struct { - ID int64 `json:"id"` + ID int32 `json:"id"` Host string `json:"host"` Svg sql.NullString `json:"svg"` Png sql.NullString `json:"png"` @@ -25,17 +25,17 @@ type Favicon struct { } type Redirect struct { - ID int64 `json:"id"` + ID int32 `json:"id"` Source string `json:"source"` Destination string `json:"destination"` Description string `json:"description"` Flags target.Flags `json:"flags"` - Code int64 `json:"code"` + Code uint16 `json:"code"` Active bool `json:"active"` } type Route struct { - ID int64 `json:"id"` + ID int32 `json:"id"` Source string `json:"source"` Destination string `json:"destination"` Description string `json:"description"` diff --git a/database/querier.go b/database/querier.go new file mode 100644 index 0000000..7ae1862 --- /dev/null +++ b/database/querier.go @@ -0,0 +1,27 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package database + +import ( + "context" +) + +type Querier interface { + AddDomain(ctx context.Context, arg AddDomainParams) error + AddRedirect(ctx context.Context, arg AddRedirectParams) error + AddRoute(ctx context.Context, arg AddRouteParams) error + DeleteDomain(ctx context.Context, domain string) error + GetActiveDomains(ctx context.Context) ([]string, error) + GetActiveRedirects(ctx context.Context) ([]GetActiveRedirectsRow, error) + GetActiveRoutes(ctx context.Context) ([]GetActiveRoutesRow, error) + GetAllRedirects(ctx context.Context) ([]GetAllRedirectsRow, error) + GetAllRoutes(ctx context.Context) ([]GetAllRoutesRow, error) + GetFavicons(ctx context.Context) ([]GetFaviconsRow, error) + RemoveRedirect(ctx context.Context, source string) error + RemoveRoute(ctx context.Context, source string) error + UpdateFaviconCache(ctx context.Context, arg UpdateFaviconCacheParams) error +} + +var _ Querier = (*Queries)(nil) diff --git a/database/queries/domain.sql b/database/queries/domain.sql index 7c4df8b..fe82b17 100644 --- a/database/queries/domain.sql +++ b/database/queries/domain.sql @@ -4,13 +4,11 @@ FROM domains WHERE active = 1; -- name: AddDomain :exec -INSERT OR REPLACE INTO domains (domain, active) VALUES (?, ?); -- name: DeleteDomain :exec -INSERT OR REPLACE INTO domains(domain, active) VALUES (?, false); diff --git a/database/queries/favicon.sql b/database/queries/favicon.sql index bb208e6..911235d 100644 --- a/database/queries/favicon.sql +++ b/database/queries/favicon.sql @@ -3,6 +3,6 @@ SELECT host, svg, png, ico FROM favicons; -- name: UpdateFaviconCache :exec -INSERT OR -REPLACE INTO favicons (host, svg, png, ico) +REPLACE +INTO favicons (host, svg, png, ico) VALUES (?, ?, ?, ?); diff --git a/database/queries/routing.sql b/database/queries/routing.sql index 0972c3b..e83afe3 100644 --- a/database/queries/routing.sql +++ b/database/queries/routing.sql @@ -17,13 +17,11 @@ SELECT source, destination, description, flags, code, active FROM redirects; -- name: AddRoute :exec -INSERT OR REPLACE INTO routes (source, destination, description, flags, active) VALUES (?, ?, ?, ?, ?); -- name: AddRedirect :exec -INSERT OR REPLACE INTO redirects (source, destination, description, flags, code, active) VALUES (?, ?, ?, ?, ?, ?); diff --git a/database/routing.sql.go b/database/routing.sql.go index e747cb5..0109c21 100644 --- a/database/routing.sql.go +++ b/database/routing.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.25.0 +// sqlc v1.30.0 // source: routing.sql package database @@ -12,7 +12,6 @@ import ( ) const addRedirect = `-- name: AddRedirect :exec -INSERT OR REPLACE INTO redirects (source, destination, description, flags, code, active) VALUES (?, ?, ?, ?, ?, ?) @@ -23,7 +22,7 @@ type AddRedirectParams struct { Destination string `json:"destination"` Description string `json:"description"` Flags target.Flags `json:"flags"` - Code int64 `json:"code"` + Code uint16 `json:"code"` Active bool `json:"active"` } @@ -40,7 +39,6 @@ func (q *Queries) AddRedirect(ctx context.Context, arg AddRedirectParams) error } const addRoute = `-- name: AddRoute :exec -INSERT OR REPLACE INTO routes (source, destination, description, flags, active) VALUES (?, ?, ?, ?, ?) @@ -75,7 +73,7 @@ type GetActiveRedirectsRow struct { Source string `json:"source"` Destination string `json:"destination"` Flags target.Flags `json:"flags"` - Code int64 `json:"code"` + Code uint16 `json:"code"` } func (q *Queries) GetActiveRedirects(ctx context.Context) ([]GetActiveRedirectsRow, error) { @@ -151,7 +149,7 @@ type GetAllRedirectsRow struct { Destination string `json:"destination"` Description string `json:"description"` Flags target.Flags `json:"flags"` - Code int64 `json:"code"` + Code uint16 `json:"code"` Active bool `json:"active"` } diff --git a/domains/domains.go b/domains/domains.go index bd4843a..511c406 100644 --- a/domains/domains.go +++ b/domains/domains.go @@ -6,9 +6,9 @@ import ( "github.com/1f349/violet/database" "github.com/1f349/violet/logger" "github.com/1f349/violet/utils" - "github.com/mrmelon54/rescheduler" "strings" "sync" + "time" ) var Logger = logger.Logger.WithPrefix("Violet Domains") @@ -18,17 +18,16 @@ type Domains struct { db *database.Queries s *sync.RWMutex m map[string]struct{} - r *rescheduler.Rescheduler } // New creates a new domain list -func New(db *database.Queries) *Domains { +func New(ctx context.Context, db *database.Queries, gap time.Duration) *Domains { a := &Domains{ db: db, s: &sync.RWMutex{}, m: make(map[string]struct{}), } - a.r = rescheduler.NewRescheduler(a.threadCompile) + go a.refreshTable(ctx, gap) return a } @@ -54,30 +53,38 @@ func (d *Domains) IsValid(host string) bool { return false } -// Compile downloads the list of domains from the database and loads them into -// memory for faster lookups. -// -// This method makes use of the rescheduler instead of just ignoring multiple -// calls. -func (d *Domains) Compile() { - d.r.Run() +func (d *Domains) refreshTable(ctx context.Context, gap time.Duration) { + for { + select { + case <-ctx.Done(): + Logger.Info("Shutting down domain table refresher") + return + + case <-time.After(gap): + err := d.compile() + if err != nil { + Logger.Error("Domain table compilation failed", "err", err) + } + } + } } -func (d *Domains) threadCompile() { +func (d *Domains) compile() error { // new map domainMap := make(map[string]struct{}) // compile map and check errors err := d.internalCompile(domainMap) if err != nil { - Logger.Info("Compile faile", "err", err) - return + return err } // lock while replacing the map d.s.Lock() d.m = domainMap d.s.Unlock() + + return nil } // internalCompile is a hidden internal method for querying the database during diff --git a/domains/domains_test.go b/domains/domains_test.go index 86484e7..4ef76b3 100644 --- a/domains/domains_test.go +++ b/domains/domains_test.go @@ -6,17 +6,20 @@ import ( "github.com/1f349/violet/database" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" + "os" "testing" + "time" ) func TestDomainsNew(t *testing.T) { - db, err := violet.InitDB("file:TestDomainsNew?mode=memory&cache=shared") + db, err := violet.InitDB(os.Getenv("DB")) assert.NoError(t, err) - domains := New(db) + domains := New(context.Background(), db, 5*time.Second) err = db.AddDomain(context.Background(), database.AddDomainParams{Domain: "example.com", Active: true}) assert.NoError(t, err) - domains.Compile() + + _ = domains.compile() if _, ok := domains.m["example.com"]; ok { assert.True(t, ok) @@ -29,10 +32,10 @@ func TestDomainsNew(t *testing.T) { func TestDomains_IsValid(t *testing.T) { // open sqlite database - db, err := violet.InitDB("file:TestDomains_IsValid?mode=memory&cache=shared") + db, err := violet.InitDB(os.Getenv("DB")) assert.NoError(t, err) - domains := New(db) + domains := New(context.Background(), db, 5*time.Second) err = db.AddDomain(context.Background(), database.AddDomainParams{Domain: "example.com", Active: true}) assert.NoError(t, err) diff --git a/error-pages/error-pages.go b/error-pages/error-pages.go index 46b6fe4..cb0dae9 100644 --- a/error-pages/error-pages.go +++ b/error-pages/error-pages.go @@ -3,7 +3,6 @@ package error_pages import ( "fmt" "github.com/1f349/violet/logger" - "github.com/mrmelon54/rescheduler" "io/fs" "net/http" "path/filepath" @@ -21,11 +20,10 @@ type ErrorPages struct { m map[int]func(rw http.ResponseWriter) generic func(rw http.ResponseWriter, code int) dir fs.FS - r *rescheduler.Rescheduler } // New creates a new error pages generator -func New(dir fs.FS) *ErrorPages { +func New(dir fs.FS) (*ErrorPages, error) { e := &ErrorPages{ s: &sync.RWMutex{}, m: make(map[int]func(rw http.ResponseWriter)), @@ -44,8 +42,7 @@ func New(dir fs.FS) *ErrorPages { }, dir: dir, } - e.r = rescheduler.NewRescheduler(e.threadCompile) - return e + return e, e.threadCompile() } // ServeError writes the error page for the given code to the response writer @@ -64,15 +61,7 @@ func (e *ErrorPages) ServeError(rw http.ResponseWriter, code int) { e.generic(rw, code) } -// Compile loads the error pages the certificates and keys from the directories. -// -// This method makes use of the rescheduler instead of just ignoring multiple -// calls. -func (e *ErrorPages) Compile() { - e.r.Run() -} - -func (e *ErrorPages) threadCompile() { +func (e *ErrorPages) threadCompile() error { // new map errorPageMap := make(map[int]func(rw http.ResponseWriter)) @@ -81,7 +70,7 @@ func (e *ErrorPages) threadCompile() { err := e.internalCompile(errorPageMap) if err != nil { Logger.Info("Compile failed", "err", err) - return + return err } } @@ -89,6 +78,8 @@ func (e *ErrorPages) threadCompile() { e.s.Lock() e.m = errorPageMap e.s.Unlock() + + return nil } func (e *ErrorPages) internalCompile(m map[int]func(rw http.ResponseWriter)) error { diff --git a/error-pages/error-pages_test.go b/error-pages/error-pages_test.go index 5f92178..33304d7 100644 --- a/error-pages/error-pages_test.go +++ b/error-pages/error-pages_test.go @@ -10,7 +10,8 @@ import ( ) func TestErrorPages_ServeError(t *testing.T) { - errorPages := New(nil) + errorPages, err := New(nil) + assert.NoError(t, err) rec := httptest.NewRecorder() errorPages.ServeError(rec, http.StatusTeapot) @@ -41,8 +42,8 @@ func TestErrorPagesWithCustom(t *testing.T) { }, } - errorPages := New(fs) - assert.NoError(t, errorPages.internalCompile(errorPages.m)) + errorPages, err := New(fs) + assert.NoError(t, err) rec := httptest.NewRecorder() errorPages.ServeError(rec, http.StatusTeapot) diff --git a/favicons/favicons_test.go b/favicons/favicons_test.go index 6faceea..02067e5 100644 --- a/favicons/favicons_test.go +++ b/favicons/favicons_test.go @@ -10,6 +10,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" "image/png" + "os" "testing" ) @@ -25,7 +26,7 @@ var ( func TestFaviconsNew(t *testing.T) { getFaviconViaRequest = func(_ string) ([]byte, error) { return exampleSvg, nil } - db, err := violet.InitDB("file:TestFaviconsNew?mode=memory&cache=shared") + db, err := violet.InitDB(os.Getenv("DB")) assert.NoError(t, err) favicons := New(db, "inkscape") diff --git a/go.mod b/go.mod index 8ffb5af..8d986ab 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-jose/go-jose/v4 v4.1.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect diff --git a/go.sum b/go.sum index 943836c..02874a0 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/1f349/rsa-helper v0.0.2 h1:N/fLQqg5wrjIzG6G4zdwa5Xcv9/jIPutCls9YekZr9 github.com/1f349/rsa-helper v0.0.2/go.mod h1:VUQ++1tYYhYrXeOmVFkQ82BegR24HQEJHl5lHbjg7yg= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -29,10 +33,30 @@ github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8= +github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= @@ -77,6 +101,12 @@ github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrmelon54/certgen v0.0.3 h1:9pF8iZz6woJeiczhT0ku9mkA8sqbhzxvDo3aHgow0xg= github.com/mrmelon54/certgen v0.0.3/go.mod h1:By4NSWvlke0ENybeJvz+1Vwky5PxdFVPxjOqTdSCCPA= github.com/mrmelon54/png2ico v1.0.2 h1:KyJd3ATmDjxAJS28MTSf44GxzYnlZ+7KT8SXzGb3sN8= @@ -87,6 +117,10 @@ github.com/mrmelon54/trie v0.0.3 h1:wZmws84FiGNBZJ00garLyQ2EQhtx0SipVoV7fK8+kZE= github.com/mrmelon54/trie v0.0.3/go.mod h1:d3hl0YUBSWR3XN4S9BDLkGVzLT4VgwP2mZkBJM6uFpw= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -109,6 +143,14 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/initdb.go b/initdb.go index 62f8793..de5f75d 100644 --- a/initdb.go +++ b/initdb.go @@ -6,7 +6,7 @@ import ( "errors" "github.com/1f349/violet/database" "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/golang-migrate/migrate/v4/database/mysql" "github.com/golang-migrate/migrate/v4/source/iofs" ) @@ -18,15 +18,15 @@ func InitDB(p string) (*database.Queries, error) { if err != nil { return nil, err } - dbOpen, err := sql.Open("sqlite3", p) + dbOpen, err := sql.Open("mysql", p) if err != nil { return nil, err } - dbDrv, err := sqlite3.WithInstance(dbOpen, &sqlite3.Config{}) + dbDrv, err := mysql.WithInstance(dbOpen, &mysql.Config{}) if err != nil { return nil, err } - mig, err := migrate.NewWithInstance("iofs", migDrv, "sqlite3", dbDrv) + mig, err := migrate.NewWithInstance("iofs", migDrv, "mysql", dbDrv) if err != nil { return nil, err } diff --git a/router/manager.go b/router/manager.go index f343525..e232379 100644 --- a/router/manager.go +++ b/router/manager.go @@ -11,6 +11,7 @@ import ( "net/http" "strings" "sync" + "time" ) var Logger = logger.Logger.WithPrefix("Violet Manager") @@ -27,14 +28,20 @@ type Manager struct { // NewManager create a new manager, initialises the routes and redirects tables // in the database and runs a first time compile. -func NewManager(db *database.Queries, proxy *proxy.HybridTransport) *Manager { +func NewManager(ctx context.Context, db *database.Queries, proxy *proxy.HybridTransport, tableRefreshGap time.Duration) *Manager { m := &Manager{ db: db, s: &sync.RWMutex{}, r: New(proxy), p: proxy, } - m.z = rescheduler.NewRescheduler(m.threadCompile) + + err := m.compile() + if err != nil { + Logger.Info("First time routing/redirect table compilation failed", "err", err) + } + + go m.refreshTables(ctx, tableRefreshGap) return m } @@ -45,25 +52,38 @@ func (m *Manager) ServeHTTP(rw http.ResponseWriter, req *http.Request) { r.ServeHTTP(rw, req) } -func (m *Manager) Compile() { - m.z.Run() +func (m *Manager) refreshTables(ctx context.Context, gap time.Duration) { + for { + select { + case <-ctx.Done(): + Logger.Info("Shutting down routing/redirect table refresher") + return + + case <-time.After(gap): + err := m.compile() + if err != nil { + Logger.Error("Routing/Redirect table compilation failed", "err", err) + } + } + } } -func (m *Manager) threadCompile() { +func (m *Manager) compile() error { // new router router := New(m.p) // compile router and check errors err := m.internalCompile(router) if err != nil { - Logger.Info("Compile failed", "err", err) - return + return err } // lock while replacing router m.s.Lock() m.r = router m.s.Unlock() + + return nil } // internalCompile is a hidden internal method for querying the database during diff --git a/router/manager_test.go b/router/manager_test.go index 50b0a9d..f14d448 100644 --- a/router/manager_test.go +++ b/router/manager_test.go @@ -11,7 +11,9 @@ import ( "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" + "os" "testing" + "time" ) type fakeTransport struct{ req *http.Request } @@ -24,12 +26,12 @@ func (f *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) { } func TestNewManager(t *testing.T) { - db, err := violet.InitDB("file:TestNewManager?mode=memory&cache=shared") + db, err := violet.InitDB(os.Getenv("DB")) assert.NoError(t, err) ft := &fakeTransport{} ht := proxy.NewHybridTransportWithCalls(ft, ft, &websocket.Server{}) - m := NewManager(db, ht) + m := NewManager(context.Background(), db, ht, 5*time.Second) assert.NoError(t, m.internalCompile(m.r)) rec := httptest.NewRecorder() @@ -60,9 +62,9 @@ func TestNewManager(t *testing.T) { } func TestManager_GetAllRoutes(t *testing.T) { - db, err := violet.InitDB("file:TestManager_GetAllRoutes?mode=memory&cache=shared") + db, err := violet.InitDB(os.Getenv("DB")) assert.NoError(t, err) - m := NewManager(db, nil) + m := NewManager(context.Background(), db, nil, 5*time.Second) a := []error{ m.InsertRoute(target.RouteWithActive{Route: target.Route{Src: "example.com"}, Active: true}), m.InsertRoute(target.RouteWithActive{Route: target.Route{Src: "test.example.com"}, Active: true}), @@ -91,9 +93,9 @@ func TestManager_GetAllRoutes(t *testing.T) { } func TestManager_GetAllRedirects(t *testing.T) { - db, err := violet.InitDB("file:TestManager_GetAllRedirects?mode=memory&cache=shared") + db, err := violet.InitDB(os.Getenv("DB")) assert.NoError(t, err) - m := NewManager(db, nil) + m := NewManager(context.Background(), db, nil, 5*time.Second) a := []error{ m.InsertRedirect(target.RedirectWithActive{Redirect: target.Redirect{Src: "example.com"}, Active: true}), m.InsertRedirect(target.RedirectWithActive{Redirect: target.Redirect{Src: "test.example.com"}, Active: true}), diff --git a/servers/api/api.go b/servers/api/api.go index a91c919..1494657 100644 --- a/servers/api/api.go +++ b/servers/api/api.go @@ -66,7 +66,6 @@ func domainManage(keyStore *mjwt.KeyStore, domains utils.DomainProvider) httprou return checkAuthWithPerm(keyStore, "violet:domains", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params, b AuthClaims) { // add domain with active state domains.Put(params.ByName("domain"), req.Method == http.MethodPut) - domains.Compile() rw.WriteHeader(http.StatusAccepted) }) } diff --git a/servers/api/target.go b/servers/api/target.go index cc7000b..2d27036 100644 --- a/servers/api/target.go +++ b/servers/api/target.go @@ -35,7 +35,6 @@ func SetupTargetApis(r *httprouter.Router, keyStore *mjwt.KeyStore, manager *rou apiError(rw, http.StatusInternalServerError, "Failed to insert route into database", err) return } - manager.Compile() rw.WriteHeader(http.StatusOK) _ = json.NewEncoder(rw).Encode(route) @@ -47,7 +46,6 @@ func SetupTargetApis(r *httprouter.Router, keyStore *mjwt.KeyStore, manager *rou apiError(rw, http.StatusInternalServerError, "Failed to delete route from database", err) return } - manager.Compile() })) // Endpoint for redirects @@ -71,7 +69,6 @@ func SetupTargetApis(r *httprouter.Router, keyStore *mjwt.KeyStore, manager *rou apiError(rw, http.StatusInternalServerError, "Failed to insert redirect into database", err) return } - manager.Compile() rw.WriteHeader(http.StatusOK) _ = json.NewEncoder(rw).Encode(redirect) @@ -83,7 +80,6 @@ func SetupTargetApis(r *httprouter.Router, keyStore *mjwt.KeyStore, manager *rou apiError(rw, http.StatusInternalServerError, "Failed to delete redirect from database", err) return } - manager.Compile() })) } diff --git a/servers/https_test.go b/servers/https_test.go index 8394392..716a554 100644 --- a/servers/https_test.go +++ b/servers/https_test.go @@ -1,6 +1,7 @@ package servers import ( + "context" "github.com/1f349/violet" "github.com/1f349/violet/certs" "github.com/1f349/violet/proxy" @@ -12,8 +13,10 @@ import ( "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" + "os" "sync" "testing" + "time" ) type fakeTransport struct{} @@ -25,16 +28,16 @@ func (f *fakeTransport) RoundTrip(_ *http.Request) (*http.Response, error) { } func TestNewHttpsServer_RateLimit(t *testing.T) { - db, err := violet.InitDB("file:TestNewHttpsServer_RateLimit?mode=memory&cache=shared") + db, err := violet.InitDB(os.Getenv("DB")) assert.NoError(t, err) ft := &fakeTransport{} httpsConf := &conf.Conf{ RateLimit: 5, Domains: &fake.Domains{}, - Certs: certs.New(nil, nil, true), + Certs: certs.New(nil, nil, true, 5*time.Second), Signer: fake.SnakeOilProv.KeyStore(), - Router: router.NewManager(db, proxy.NewHybridTransportWithCalls(ft, ft, &websocket.Server{})), + Router: router.NewManager(context.Background(), db, proxy.NewHybridTransportWithCalls(ft, ft, &websocket.Server{}), 5*time.Second), } srv := NewHttpsServer(httpsConf) diff --git a/sqlc.yaml b/sqlc.yaml index 953e616..d4bf334 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -1,6 +1,6 @@ version: "2" sql: - - engine: sqlite + - engine: mysql queries: database/queries schema: database/migrations gen: @@ -8,8 +8,11 @@ sql: package: "database" out: "database" emit_json_tags: true + emit_interface: true overrides: - column: "routes.flags" go_type: "github.com/1f349/violet/target.Flags" - column: "redirects.flags" go_type: "github.com/1f349/violet/target.Flags" + - column: "redirects.code" + go_type: "uint16" diff --git a/target/redirect.go b/target/redirect.go index 752d376..d82b4df 100644 --- a/target/redirect.go +++ b/target/redirect.go @@ -16,7 +16,7 @@ type Redirect struct { Dst string `json:"dst"` // redirect destination Desc string `json:"desc"` // description for admin panel use Flags Flags `json:"flags"` // extra flags - Code int64 `json:"code"` // status code used to redirect + Code uint16 `json:"code"` // status code used to redirect } type RedirectWithActive struct { diff --git a/target/redirect_test.go b/target/redirect_test.go index 51bd280..e50a636 100644 --- a/target/redirect_test.go +++ b/target/redirect_test.go @@ -35,7 +35,7 @@ func TestRedirect_ServeHTTP(t *testing.T) { res := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "https://www.example.com/hello/world", nil) i.ServeHTTP(res, req) - assert.Equal(t, i.Code, int64(res.Code)) + assert.Equal(t, i.Code, uint16(res.Code)) assert.Equal(t, i.target, res.Header().Get("Location")) } } diff --git a/utils/duration-text.go b/utils/duration-text.go new file mode 100644 index 0000000..9e70b0f --- /dev/null +++ b/utils/duration-text.go @@ -0,0 +1,24 @@ +package utils + +import ( + "encoding" + "time" +) + +type DurationText time.Duration + +var _ encoding.TextMarshaler = DurationText(0) +var _ encoding.TextUnmarshaler = (*DurationText)(nil) + +func (d DurationText) MarshalText() (text []byte, err error) { + return []byte(time.Duration(d).String()), nil +} + +func (d *DurationText) UnmarshalText(text []byte) error { + duration, err := time.ParseDuration(string(text)) + if err != nil { + return err + } + *d = DurationText(duration) + return nil +} diff --git a/utils/interfaces.go b/utils/interfaces.go index 749879d..ab89344 100644 --- a/utils/interfaces.go +++ b/utils/interfaces.go @@ -6,7 +6,6 @@ type DomainProvider interface { IsValid(host string) bool Put(domain string, active bool) Delete(domain string) - Compile() } type AcmeChallengeProvider interface { @@ -17,5 +16,4 @@ type AcmeChallengeProvider interface { type CertProvider interface { GetCertForDomain(domain string) *tls.Certificate - Compile() }