diff --git a/.env.example b/.env.example index 7a4a794..5d0a467 100644 --- a/.env.example +++ b/.env.example @@ -12,8 +12,9 @@ REDIRECT_URI=http://localhost:8081/api/auth/callback CLIENT_ID= CLIENT_SECRET= -# Listening port -PORT= +# Listen address (if the IP address is empty, it will default to 127.0.0.1) +# e.g. 127.0.0.1:8081 or :8081 +LISTEN_ADDRESS=:8081 # S3 S3_ENDPOINT= @@ -32,3 +33,14 @@ POSTGRES_DSN= # Random secret. Recommended length is 64 characters at minimum. JWT_SECRET= + +# URL to Boxy's API. +# e.g. http://localhost:8001 +# without ending forward slash (/) +BOXY_URL= + +# Address by which Boxy can reach us. If it is not set, it will be the address with which we'll reach Boxy's API. +BOXY_CLIENT_ADDRESS= + +BOXY_CLIENT_NAME= +BOXY_CLIENT_SECRET= diff --git a/internal/boxy/boxy.go b/internal/boxy/boxy.go new file mode 100644 index 0000000..4d5ea92 --- /dev/null +++ b/internal/boxy/boxy.go @@ -0,0 +1,97 @@ +package boxy + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strconv" +) + +type BoxyClient struct { + Name string + Secret string + ApiUrl string + Hostnames []string + + endpointId int +} + +func (b *BoxyClient) Register(address string, port int) error { + rawBody := map[string]any{ + "port": port, + "hostname": b.Hostnames, + } + + if address != "" { + rawBody["address"] = address + } + + jBody, err := json.Marshal(rawBody) + + if err != nil { + return err + } + + req, err := http.NewRequest("POST", b.ApiUrl+"/register", bytes.NewBuffer(jBody)) + + if err != nil { + return err + } + + req.SetBasicAuth(b.Name, b.Secret) + + resp, _ := http.DefaultClient.Do(req) + + if resp.StatusCode != 200 { + log.Fatal("Could not register with Boxy.\nStatus Code: " + strconv.Itoa(req.Response.StatusCode)) + } + + body, err := io.ReadAll(resp.Body) + + if err != nil { + return err + } + + b.endpointId, err = strconv.Atoi(string(body)) + + if err != nil { + return err + } + + return nil +} + +func (b *BoxyClient) Unregister() error { + rawBody := map[string]any{} + + jBody, err := json.Marshal(rawBody) + + if err != nil { + return err + } + + req, err := http.NewRequest("DELETE", b.ApiUrl+"/endpoint/"+strconv.Itoa(b.endpointId), bytes.NewBuffer(jBody)) + + if err != nil { + return err + } + + req.SetBasicAuth(b.Name, b.Secret) + + resp, _ := http.DefaultClient.Do(req) + + body, err := io.ReadAll(resp.Body) + + if err != nil { + return err + } + + if resp.StatusCode != 200 { + return fmt.Errorf("Could not delete endpoint.\nBody: %s", string(body)) + } + + return nil +} diff --git a/internal/types/types.go b/internal/types/types.go index 0395fcb..aef1f95 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -26,6 +26,7 @@ import ( "github.com/minio/minio-go/v7" "gorm.io/gorm" "stereo.cat/backend/internal/auth/client" + "stereo.cat/backend/internal/boxy" ) type Route struct { @@ -45,6 +46,7 @@ type StereoConfig struct { FrontendUri string Domain string Context context.Context + BoxyClient boxy.BoxyClient } type File struct { diff --git a/main.go b/main.go index bf5eb73..ec52dc5 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,8 @@ import ( "fmt" "log" "os" + "strconv" + "strings" "github.com/gin-gonic/gin" "github.com/joho/godotenv" @@ -34,6 +36,7 @@ import ( "stereo.cat/backend/internal/api" "stereo.cat/backend/internal/auth" "stereo.cat/backend/internal/auth/client" + "stereo.cat/backend/internal/boxy" "stereo.cat/backend/internal/types" ) @@ -56,6 +59,9 @@ func main() { databaseType := getEnv("DATABASE_TYPE", "sqlite") sqliteFile := getEnv("SQLITE_FILE", "stereo.db") + boxyApiUrl := getEnv("BOXY_URL", "") + listenAddr := getEnv("LISTEN_ADDRESS", ":8081") + imagePath := getEnv("IMAGE_PATH", os.TempDir()) if _, err := os.Stat(imagePath); err != nil { @@ -82,7 +88,7 @@ func main() { Router: gin.Default(), MinioClient: minioClient, Bucket: requireEnv("S3_BUCKET"), - Context: context.Background(), + Context: context.Background(), ImagePath: imagePath, Client: client.New( requireEnv("REDIRECT_URI"), @@ -122,7 +128,32 @@ func main() { c.Database.AutoMigrate(&auth.User{}, &types.File{}) + if boxyApiUrl != "" { + boxyClientName := requireEnv("BOXY_CLIENT_NAME") + boxyClientSecret := requireEnv("BOXY_CLIENT_SECRET") + boxyClientAddress := getEnv("BOXY_CLIENT_ADDRESS", "") + + port, err := strconv.Atoi(strings.Split(listenAddr, ":")[1]) + + if err != nil { + panic(err) + } + + c.BoxyClient = boxy.BoxyClient{ + Name: boxyClientName, + Secret: boxyClientSecret, + ApiUrl: boxyApiUrl, + Hostnames: []string{c.Domain}, + } + + err = c.BoxyClient.Register(boxyClientAddress, port) + + if err != nil { + panic(err) + } + } + api.Register(&c) - fmt.Printf("Running on port %s\n", getEnv("PORT", "8081")) - c.Router.Run() + fmt.Printf("Listening on %s\n", listenAddr) + c.Router.Run(listenAddr) }