Domain and favicon compilable with inkscape to convert svg to png
ci/woodpecker/push/build Pipeline was successful Details

This commit is contained in:
Melon 2023-01-21 16:33:05 +00:00
parent 3362df508e
commit 5d7f23fb29
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
23 changed files with 458 additions and 58 deletions

View File

@ -0,0 +1,20 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GoUnhandledErrorResult" enabled="true" level="WARNING" enabled_by_default="true">
<methods>
<method importPath="hash" receiver="Hash" name="Write" />
<method importPath="strings" receiver="*Builder" name="Write" />
<method importPath="strings" receiver="*Builder" name="WriteByte" />
<method importPath="bytes" receiver="*Buffer" name="WriteRune" />
<method importPath="bytes" receiver="*Buffer" name="Write" />
<method importPath="bytes" receiver="*Buffer" name="WriteString" />
<method importPath="strings" receiver="*Builder" name="WriteString" />
<method importPath="bytes" receiver="*Buffer" name="WriteByte" />
<method importPath="strings" receiver="*Builder" name="WriteRune" />
<method importPath="math/rand" receiver="*Rand" name="Read" />
<method importPath="github.com/galihrivanto/go-inkscape" receiver="*Proxy" name="Close" />
</methods>
</inspection_tool>
</profile>
</component>

View File

@ -26,7 +26,7 @@ test:
$(CC) test ./... -tags TEST
dev:
docker compose -f docker-compose.development.yml build --no-cache
docker compose -f docker-compose.development.yml build
docker compose -f docker-compose.development.yml up
dev-down:

View File

@ -6,4 +6,4 @@ COPY ../.. .
RUN go get -d -v ./...
RUN ./scripts/build.sh "/bin/azalea" "./cmd/azalea/"
CMD ["azalea"]
CMD ["/bin/azalea"]

View File

@ -12,6 +12,7 @@ type AzaleaConfig struct {
RateLimit uint64 `yaml:"rateLimit"`
Logs LogConfig `yaml:"logs"`
Code418 string `yaml:"418"`
Inkscape string `yaml:"inkscape"`
}
type ListenConfig struct {

View File

@ -1,4 +1,4 @@
FROM golang:1.19
FROM golang:1.19 AS build
WORKDIR /go/src/app
COPY ../.. .
@ -6,4 +6,10 @@ COPY ../.. .
RUN go get -d -v ./...
RUN ./scripts/build.sh "/bin/azalea" "./cmd/azalea/" "DEBUG"
CMD ["azalea"]
FROM debian:latest
RUN apt-get update && apt-get install -y inkscape \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /root
COPY --from=build /bin/azalea .
CMD ["./azalea"]

View File

@ -0,0 +1,74 @@
package domain_checker
import (
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"log"
"strings"
"sync"
"xorm.io/xorm"
)
func New(db *xorm.Engine) *DomainChecker {
d := &DomainChecker{
db: db,
domainSync: &sync.RWMutex{},
domainMap: make(map[string]struct{}),
}
d.Compile()
return d
}
type DomainChecker struct {
db *xorm.Engine
domainSync *sync.RWMutex
domainMap map[string]struct{}
}
func (d *DomainChecker) Compile() {
go func() {
domainMap := make(map[string]struct{})
err := d.internalCompile(domainMap)
if err != nil {
// log compile errors
log.Printf("[DomainChecker] Compile failed: %s\n", err)
return
}
// lock while replacing the map
d.domainSync.Lock()
d.domainMap = domainMap
d.domainSync.Unlock()
}()
}
func (d *DomainChecker) internalCompile(domainMap map[string]struct{}) error {
log.Println("[DomainChecker] Pulling from database")
var domains []web.Domain
err := d.db.Find(&domains)
if err != nil {
return err
}
for _, i := range domains {
domainMap[i.Domain] = struct{}{}
}
// well I guess we are done
log.Printf("[DomainChecker] Finished compiling, %d domains will be available shortly\n", len(domains))
return nil
}
func (d *DomainChecker) IsValid(host string) bool {
domain, ok := utils.GetDomainWithoutPort(host)
if !ok {
return false
}
d.domainSync.RLock()
defer d.domainSync.RUnlock()
n := strings.Split(domain, ".")
for i := 0; i < len(n); i++ {
if _, ok := d.domainMap[strings.Join(n[i:], ".")]; ok {
return true
}
}
return false
}

View File

@ -0,0 +1,18 @@
package domain_checker
import (
"github.com/stretchr/testify/assert"
"sync"
"testing"
)
func TestDomainChecker_IsValid(t *testing.T) {
d := &DomainChecker{
domainSync: &sync.RWMutex{},
domainMap: map[string]struct{}{"summer.test": {}},
}
assert.True(t, d.IsValid("this.is.a.test.summer.test"))
assert.True(t, d.IsValid("summer.test"))
assert.False(t, d.IsValid("test"))
assert.False(t, d.IsValid("not-summer.test"))
}

View File

@ -0,0 +1,207 @@
package favicons
import (
"bytes"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"github.com/mrmelon54/png2ico"
"golang.org/x/sync/errgroup"
"image/png"
"io"
"log"
"net/http"
"sync"
"xorm.io/xorm"
)
var ErrFaviconNotFound = errors.New("favicon not found")
func New(db *xorm.Engine, inkscapeCmd string) *Favicons {
f := &Favicons{
db: db,
cmd: inkscapeCmd,
cLock: &sync.RWMutex{},
faviconMap: make(map[string]*FaviconList),
}
f.Compile()
return f
}
type Favicons struct {
db *xorm.Engine
cmd string
cLock *sync.RWMutex
faviconMap map[string]*FaviconList
}
func (f *Favicons) Compile() {
go func() {
favicons := make(map[string]*FaviconList)
err := f.internalCompile(favicons)
if err != nil {
// log compile errors
log.Printf("[Favicons] Compile failed: %s\n", err)
return
}
// lock while replacing the map
f.cLock.Lock()
f.faviconMap = favicons
f.cLock.Unlock()
}()
}
func (f *Favicons) GetIcons(host string) (*FaviconList, bool) {
if a, ok := f.faviconMap[host]; ok {
return a, true
}
return nil, false
}
func (f *Favicons) internalCompile(faviconMap map[string]*FaviconList) error {
var faviconList []web.HttpFavicon
err := f.db.Find(&faviconList)
if err != nil {
return err
}
var g errgroup.Group
for _, i := range faviconList {
l := &FaviconList{
Ico: CreateFaviconImage(i.Ico),
Png: CreateFaviconImage(i.Png),
Svg: CreateFaviconImage(i.Svg),
}
faviconMap[i.Host] = l
g.Go(func() error {
return l.PreProcess(f.convertSvgToPng)
})
}
return g.Wait()
}
func (f *Favicons) convertSvgToPng(in []byte) ([]byte, error) {
return svg2png(f.cmd, in)
}
type FaviconList struct {
Ico *FaviconImage // can be generated from png with wrapper
Png *FaviconImage // can be generated from svg with inkscape
Svg *FaviconImage
}
func (l *FaviconList) ProduceIco() ([]byte, error) {
if l.Ico == nil {
return nil, ErrFaviconNotFound
}
return l.Ico.Raw, nil
}
func (l *FaviconList) ProducePng() ([]byte, error) {
if l.Png == nil {
return nil, ErrFaviconNotFound
}
return l.Png.Raw, nil
}
func (l *FaviconList) ProduceSvg() ([]byte, error) {
if l.Svg == nil {
return nil, ErrFaviconNotFound
}
return l.Svg.Raw, nil
}
func (l *FaviconList) PreProcess(convert func(in []byte) ([]byte, error)) error {
var err error
// SVG
if l.Svg != nil {
// download SVG
l.Svg.Raw, err = getFaviconViaRequest(l.Svg.Url)
if err != nil {
return fmt.Errorf("[Favicons] Failed to fetch SVG icon: %w", err)
}
l.Svg.Hash = hex.EncodeToString(sha256.New().Sum(l.Svg.Raw))
}
// PNG
if l.Png != nil {
// download PNG
l.Png.Raw, err = getFaviconViaRequest(l.Png.Url)
if err != nil {
return fmt.Errorf("[Favicons] Failed to fetch PNG icon: %w", err)
}
} else if l.Svg != nil {
// generate PNG from SVG
l.Png = &FaviconImage{}
l.Png.Raw, err = convert(l.Svg.Raw)
if err != nil {
return fmt.Errorf("[Favicons] Failed to generate PNG icon: %w", err)
}
}
// ICO
if l.Ico != nil {
// download ICO
l.Ico.Raw, err = getFaviconViaRequest(l.Ico.Url)
if err != nil {
return fmt.Errorf("[Favicons] Failed to fetch ICO icon: %w", err)
}
} else if l.Png != nil {
// generate ICO from PNG
l.Ico = &FaviconImage{}
decode, err := png.Decode(bytes.NewReader(l.Png.Raw))
if err != nil {
return fmt.Errorf("[Favicons] Failed to decode PNG icon: %w", err)
}
b := decode.Bounds()
l.Ico.Raw, err = png2ico.ConvertPngToIco(l.Png.Raw, b.Dx(), b.Dy())
if err != nil {
return fmt.Errorf("[Favicons] Failed to generate ICO icon: %w", err)
}
}
l.Svg.Hash = genSha256(l.Svg.Raw)
l.Png.Hash = genSha256(l.Png.Raw)
l.Ico.Hash = genSha256(l.Ico.Raw)
return nil
}
func getFaviconViaRequest(url string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("[Favicons] Failed to send request '%s': %w", url, err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("[Favicons] Failed to do request '%s': %w", url, err)
}
rawBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[Favicons] Failed to read response '%s': %w", url, err)
}
return rawBody, nil
}
func genSha256(in []byte) string {
h := sha256.New()
_, err := h.Write(in)
if err != nil {
return ""
}
return hex.EncodeToString(h.Sum(nil))
}
type FaviconImage struct {
Url string
Hash string
Raw []byte
}
func CreateFaviconImage(url string) *FaviconImage {
if url == "" {
return nil
}
return &FaviconImage{Url: url}
}

View File

@ -0,0 +1,30 @@
package favicons
import (
"bytes"
"fmt"
"os/exec"
)
func svg2png(inkscapeCmd string, in []byte) (out []byte, err error) {
var stdout, stderr bytes.Buffer
cmd := exec.Command(inkscapeCmd, "--export-type", "png", "--export-filename", "-", "--export-background-opacity", "0", "--pipe")
cmd.Stdin = bytes.NewBuffer(in)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if e := cmd.Run(); e != nil {
err = fmt.Errorf("%s\nSTDERR:\n%s", e.Error(), stderr.String())
return
}
if stdout.Len() == 0 {
err = fmt.Errorf("got no data from inkscape")
return
}
out = stdout.Bytes()
return
}

View File

@ -2,9 +2,12 @@ package main
import (
"code.mrmelon54.com/melon/summer/cmd/azalea/config"
domainChecker "code.mrmelon54.com/melon/summer/cmd/azalea/domain-checker"
"code.mrmelon54.com/melon/summer/cmd/azalea/favicons"
quickDb "code.mrmelon54.com/melon/summer/cmd/azalea/quick-db"
"code.mrmelon54.com/melon/summer/cmd/azalea/routing"
"code.mrmelon54.com/melon/summer/cmd/azalea/servers"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/cli"
"code.mrmelon54.com/melon/summer/pkg/proxy"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
@ -33,6 +36,7 @@ var usedTables = []any{
&web.HttpRouteHeader{},
&web.HttpServiceHeader{},
&web.HttpDefaultHost{},
&web.HttpFavicon{},
// renewal
&renewal.AcmeContent{},
@ -85,11 +89,14 @@ func (azalea *Azalea) Init(runner *cli.Runner[config.AzaleaConfig]) {
azalea.reverseProxy = proxy.CreateHybridReverseProxy()
tableRouter, err := routing.New(runner.Database, azalea.reverseProxy, azalea.accessLog, fourEighteenErrorFunc)
domainCheck := domainChecker.New(runner.Database)
faviconMap := favicons.New(runner.Database, azalea.conf.Inkscape)
compileTargets := api.MultiCompilable{tableRouter, domainCheck}
utils.Check("[Azalea.Init()] Failed to load table router:", err)
azalea.httpServer = servers.NewHttpServer(azalea.conf.Listen.Http, addrPort, azalea.runner.Database)
azalea.httpsServer = servers.NewHttpsServer(azalea.conf.Listen.Https, azalea.runner.Database, tableRouter, azalea.conf, fourEighteenErrorFunc)
azalea.apiServer = servers.NewApiServer(azalea.conf.Listen.Api, azalea.runner.Database, azalea.verify, tableRouter)
azalea.httpServer = servers.NewHttpServer(azalea.conf.Listen.Http, addrPort, azalea.runner.Database, domainCheck)
azalea.httpsServer = servers.NewHttpsServer(azalea.conf.Listen.Https, azalea.runner.Database, tableRouter, domainCheck, faviconMap, azalea.conf, fourEighteenErrorFunc)
azalea.apiServer = servers.NewApiServer(azalea.conf.Listen.Api, azalea.runner.Database, azalea.verify, compileTargets)
}
func (azalea *Azalea) Destroy() {

View File

@ -1,7 +1,7 @@
package servers
import (
"code.mrmelon54.com/melon/summer/cmd/azalea/routing"
"code.mrmelon54.com/melon/summer/pkg/api"
"code.mrmelon54.com/melon/summer/pkg/api/crud"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
@ -13,13 +13,13 @@ import (
"xorm.io/xorm"
)
func NewApiServer(listen string, db *xorm.Engine, verify mjwt.Provider, tableRouter *routing.TableRouter) *http.Server {
func NewApiServer(listen string, db *xorm.Engine, verify mjwt.Provider, compileTarget api.MultiCompilable) *http.Server {
router := mux.NewRouter()
crud.NewCrudHandler[web.HttpService](router, verify, crud.NewSinglePermProvider[web.HttpService]("manage:http-service", crud.NewDatabaseProvider[web.HttpService](db, "id")), "/http-services", "/http-service/{id}")
crud.NewCrudHandler[web.HttpRedirect](router, verify, crud.NewSinglePermProvider[web.HttpRedirect]("manage:http-redirect", crud.NewDatabaseProvider[web.HttpRedirect](db, "id")), "/http-redirects", "/http-redirect/{id}")
crud.NewCrudHandler[web.ApiRoute](router, verify, crud.NewSinglePermProvider[web.ApiRoute]("manage:api-route", crud.NewDatabaseProvider[web.ApiRoute](db, "id")), "/api-routes", "/api-route/{id}")
router.HandleFunc("/compile", func(rw http.ResponseWriter, req *http.Request) {
tableRouter.Compile()
compileTarget.Compile()
rw.WriteHeader(http.StatusAccepted)
}).Methods(http.MethodPost)

View File

@ -1,8 +1,8 @@
package servers
import (
domainChecker "code.mrmelon54.com/melon/summer/cmd/azalea/domain-checker"
"code.mrmelon54.com/melon/summer/pkg/tables/renewal"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"fmt"
"github.com/gorilla/mux"
@ -12,12 +12,12 @@ import (
"xorm.io/xorm"
)
func NewHttpServer(listen string, httpsPort uint16, db *xorm.Engine) *http.Server {
func NewHttpServer(listen string, httpsPort uint16, db *xorm.Engine, domainCheck *domainChecker.DomainChecker) *http.Server {
router := mux.NewRouter()
router.HandleFunc("/.well-known/acme-challenge/{token}", func(rw http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
if hostname, ok := utils.GetDomainWithoutPort(req.Host); ok {
if !web.CheckDomainInDb(db, hostname) {
if !domainCheck.IsValid(req.Host) {
utils.RequestKiller(rw)
return
}
@ -41,10 +41,6 @@ func NewHttpServer(listen string, httpsPort uint16, db *xorm.Engine) *http.Serve
})
router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if h, ok := utils.GetDomainWithoutPort(req.Host); ok {
if !web.CheckDomainInDb(db, h) {
utils.RequestKiller(rw)
return
}
if httpsPort != 443 {
req.URL.Host = fmt.Sprintf("%s:%d", h, httpsPort)
} else {

View File

@ -2,9 +2,10 @@ package servers
import (
"code.mrmelon54.com/melon/summer/cmd/azalea/config"
domainChecker "code.mrmelon54.com/melon/summer/cmd/azalea/domain-checker"
"code.mrmelon54.com/melon/summer/cmd/azalea/favicons"
"code.mrmelon54.com/melon/summer/cmd/azalea/routing"
"code.mrmelon54.com/melon/summer/pkg/tables/certificate"
"code.mrmelon54.com/melon/summer/pkg/tables/web"
"code.mrmelon54.com/melon/summer/pkg/utils"
"crypto/tls"
"fmt"
@ -18,9 +19,9 @@ import (
"xorm.io/xorm"
)
func NewHttpsServer(listen string, db *xorm.Engine, tableRouter *routing.TableRouter, conf config.AzaleaConfig, fourEighteenError func(rw http.ResponseWriter, req *http.Request)) *http.Server {
func NewHttpsServer(listen string, db *xorm.Engine, tableRouter *routing.TableRouter, domainCheck *domainChecker.DomainChecker, faviconMap *favicons.Favicons, conf config.AzaleaConfig, fourEighteenError func(rw http.ResponseWriter, req *http.Request)) *http.Server {
router := mux.NewRouter()
setupHttpsRouter(router, db, tableRouter, conf, fourEighteenError)
setupHttpsRouter(router, tableRouter, domainCheck, faviconMap, conf, fourEighteenError)
certCache := utils.NewCertCache()
@ -29,7 +30,7 @@ func NewHttpsServer(listen string, db *xorm.Engine, tableRouter *routing.TableRo
Handler: router,
TLSConfig: &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
if !web.CheckDomainInDb(db, info.ServerName) {
if !domainCheck.IsValid(info.ServerName) {
return nil, fmt.Errorf("invalid hostname used: '%s'", info.ServerName)
}
@ -62,12 +63,18 @@ func NewHttpsServer(listen string, db *xorm.Engine, tableRouter *routing.TableRo
return s
}
func setupHttpsRouter(router *mux.Router, db *xorm.Engine, tableRouter *routing.TableRouter, conf config.AzaleaConfig, fourEighteenError func(rw http.ResponseWriter, req *http.Request)) {
func setupHttpsRouter(router *mux.Router, tableRouter *routing.TableRouter, domainCheck *domainChecker.DomainChecker, faviconMap *favicons.Favicons, conf config.AzaleaConfig, fourEighteenError func(rw http.ResponseWriter, req *http.Request)) {
faviconColor := favicon.NewColor()
router.Use(setupRateLimiter(conf.RateLimit))
router.HandleFunc("/favicon.{ext:(?:ico|png|svg)}", utils.CachedPage(func(rw http.ResponseWriter, req *http.Request) {
if !web.CheckDomainInDb(db, req.Host) {
if !domainCheck.IsValid(req.Host) {
fourEighteenError(rw, req)
return
}
domain, ok := utils.GetDomainWithoutPort(req.Host)
if !ok {
fourEighteenError(rw, req)
return
}
@ -76,7 +83,12 @@ func setupHttpsRouter(router *mux.Router, db *xorm.Engine, tableRouter *routing.
ext := vars["ext"]
h := rw.Header()
favSvg := favicon.NewSvg(req.Host, faviconColor)
var favSvg produceImg
if faviconList, ok := faviconMap.GetIcons(domain); ok {
favSvg = faviconList
} else {
favSvg = favicon.NewSvg(domain, faviconColor)
}
var b []byte
var err error
@ -102,7 +114,7 @@ func setupHttpsRouter(router *mux.Router, db *xorm.Engine, tableRouter *routing.
_, _ = rw.Write(b)
}))
router.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if !web.CheckDomainInDb(db, req.Host) {
if !domainCheck.IsValid(req.Host) {
fourEighteenError(rw, req)
return
}
@ -125,3 +137,9 @@ func setupRateLimiter(rateLimit uint64) mux.MiddlewareFunc {
}
return middleware.Handle
}
type produceImg interface {
ProduceIco() ([]byte, error)
ProducePng() ([]byte, error)
ProduceSvg() ([]byte, error)
}

View File

@ -20,5 +20,9 @@ func NewApiServer(listen string, db *xorm.Engine, renewal *renewal.Renewal, veri
handleGetPublic(router, db, "/domain-certificate/{domain}")
crud.NewCrudHandler[CertDbItem](router, verify, crud.NewSinglePermProvider[CertDbItem]("manage:certificate", NewCertDbProvider(db, renewal)), "/certificates", "/certificate/{id}")
crud.NewCrudHandler[CertPrivate](router, verify, crud.NewSinglePermProvider[CertPrivate]("manage:certificate:admin", certPrivateProvider{db: db}), "/certificate-pairs", "/certificate-pair/{domain}")
router.HandleFunc("/renew", func(rw http.ResponseWriter, req *http.Request) {
renewal.Renew()
rw.WriteHeader(http.StatusAccepted)
})
return api.RunApiServer(listen, router)
}

View File

@ -10,3 +10,4 @@ rateLimit: 300
logs:
accessLogDir: logs
418: '418.html'
inkscape: 'inkscape'

View File

@ -8,3 +8,4 @@ auth:
apiDomain: api.summer.test
rateLimit: 300
418: '/etc/melon-summer/418.html'
inkscape: 'inkscape'

View File

@ -40,7 +40,6 @@ services:
- "8443:443"
links:
- database
- cli
depends_on:
- database
buttercup:
@ -55,7 +54,6 @@ services:
links:
- database
- pebble
- cli
depends_on:
- database
- pebble
@ -70,7 +68,6 @@ services:
- "./config/marigold.config.yml:/etc/melon-summer/marigold.config.yml:ro"
links:
- database
- cli
depends_on:
- database
rose:
@ -89,10 +86,5 @@ services:
- "4321:4321/udp"
links:
- database
- cli
depends_on:
- database
cli:
restart: on-failure
image: ubuntu
entrypoint: sleep infinity

6
go.mod
View File

@ -12,11 +12,14 @@ require (
github.com/go-sql-driver/mysql v1.6.0
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/kjk/common v0.0.0-20220705191345-4e210bd3860d
github.com/mattn/go-sqlite3 v1.14.9
github.com/mrmelon54/favicon v1.0.0
github.com/mrmelon54/mjwt v0.0.1
github.com/mrmelon54/png2ico v1.0.0
github.com/mskrha/svg2png v0.0.0-20220111070911-b5983936965c
github.com/pkg/errors v0.9.1
github.com/sec51/twofactor v1.0.0
github.com/sethvargo/go-limiter v0.7.2
@ -24,6 +27,7 @@ require (
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
golang.org/x/exp v0.0.0-20220218215828-6cf2b201936e
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/yaml.v3 v3.0.1
xorm.io/xorm v1.3.1
)
@ -45,12 +49,10 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrmelon54/png2ico v1.0.0 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sec51/convert v1.0.2 // indirect

2
go.sum
View File

@ -398,6 +398,8 @@ github.com/mrmelon54/mjwt v0.0.1 h1:XgyWviTmgsbMiKXjxo+Jp/QSf7FF7/omkvrUag8/P5U=
github.com/mrmelon54/mjwt v0.0.1/go.mod h1:M+kZ6t9EArEQ2/CGjfgyNhAo542ot+S7gw5uJCK11Ms=
github.com/mrmelon54/png2ico v1.0.0 h1:YE20i0xao8rkuYaCq3Xj2hUkVkJ6xp412aGDMrGqufA=
github.com/mrmelon54/png2ico v1.0.0/go.mod h1:vp8Be9y5cz102ANon+BnsIzTUdet3VQRvOuWJTH9h0M=
github.com/mskrha/svg2png v0.0.0-20220111070911-b5983936965c h1:WJZ9Z0iwgh4GO0HXjXWtZn+v8UqcLlVu2tqWaqbEACk=
github.com/mskrha/svg2png v0.0.0-20220111070911-b5983936965c/go.mod h1:KFdfdIgpr48ODxdkxKvpcYwuyLpQ6rfkAsFB2UQ6jD4=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=

13
pkg/api/compilable.go Normal file
View File

@ -0,0 +1,13 @@
package api
type Compilable interface {
Compile()
}
type MultiCompilable []Compilable
func (m MultiCompilable) Compile() {
for _, i := range m {
i.Compile()
}
}

View File

@ -23,6 +23,8 @@ import (
"xorm.io/xorm"
)
var ErrAlreadyRenewing = errors.New("renewal: already renewing")
type Renewal struct {
db *xorm.Engine
httpProvider *CustomHTTPProvider
@ -30,6 +32,7 @@ type Renewal struct {
certDone chan struct{}
caAddr string
caCert []byte
renewLock *sync.Mutex
}
type MyAccount struct {
@ -51,7 +54,7 @@ func (u *MyAccount) GetPrivateKey() crypto.PrivateKey {
}
func NewRenewalService(wg *sync.WaitGroup, db *xorm.Engine, httpProvider *CustomHTTPProvider, config Config) *Renewal {
r := &Renewal{db: db, httpProvider: httpProvider}
r := &Renewal{db: db, httpProvider: httpProvider, renewLock: &sync.Mutex{}}
r.load(wg, config)
return r
}
@ -107,7 +110,10 @@ func (r *Renewal) renewalRoutine(wg *sync.WaitGroup) {
}()
log.Printf("[Renewal] Doing quick certificate check before starting...\n")
r.renewalCheck()
err := r.renewalCheck()
if err != nil {
}
log.Printf("[Renewal] Initial check complete, continually checking every 4 hours...\n")
for {
@ -115,12 +121,17 @@ func (r *Renewal) renewalRoutine(wg *sync.WaitGroup) {
case <-r.certDone:
return
case <-r.certTicker.C:
r.renewalCheck()
_ = r.renewalCheck()
}
}
}
func (r *Renewal) renewalCheck() {
func (r *Renewal) renewalCheck() error {
if !r.renewLock.TryLock() {
return ErrAlreadyRenewing
}
r.renewLock.Unlock()
var certs []certificate2.CertificateMetaDomainJoiner
//goland:noinspection SqlDialectInspection,SqlNoDataSourceInspection
err := r.db.SQL(`
@ -130,13 +141,15 @@ select * from certificate
`).Find(&certs)
if err != nil {
log.Printf("[Renewal::renewalCheck()] Failed to get certificates for a renewal check: %s\n", err)
return
return nil
}
for _, i := range certs {
log.Printf("[Renewal] Cert '%d' runs out in 30 days\n", i.Id)
r.RequestUpdate(i)
}
return nil
}
func (r *Renewal) RequestUpdate(c certificate2.CertificateMetaDomainJoiner) {
@ -315,3 +328,7 @@ func (r *Renewal) setupForRequest(c *certificate2.CertificateMetaDomainJoiner) (
myUser.Registration = qReg
return client, nil
}
func (r *Renewal) Renew() {
}

View File

@ -1,24 +1,6 @@
package web
import (
"code.mrmelon54.com/melon/summer/pkg/utils"
"xorm.io/xorm"
)
type Domain struct {
Id uint64 `xorm:"pk autoincr"`
Domain string
}
func CheckDomainInDb(db *xorm.Engine, domain string) bool {
if nDomain, ok := utils.GetDomainWithoutPort(domain); ok {
if bDomain, ok := utils.GetTopFqdn(nDomain); ok {
a, err := db.Where("domain = ?", bDomain).Count(&Domain{})
if err != nil {
return false
}
return a >= 1
}
}
return false
}

View File

@ -0,0 +1,9 @@
package web
type HttpFavicon struct {
Id uint64 `json:"id" xorm:"pk autoincr"`
Host string `json:"host"`
Ico string `json:"ico"` // auto convert from png if present
Png string `json:"png"` // auto convert from svg if present
Svg string `json:"svg"` // can't be auto converted
}