2024-09-28 19:41:34 +01:00
package main
import (
2024-10-15 19:06:05 +01:00
library "git.ailur.dev/ailur/fg-library/v2"
2024-11-03 14:59:05 +00:00
"github.com/andybalholm/brotli"
2024-10-29 11:20:01 +00:00
"errors"
2024-09-28 19:41:34 +01:00
"io"
"log"
2024-11-01 13:09:35 +00:00
"mime"
2024-09-28 19:41:34 +01:00
"os"
"plugin"
2024-11-01 13:09:35 +00:00
"strconv"
2024-10-04 18:30:17 +01:00
"strings"
2024-10-03 18:33:41 +01:00
"sync"
2024-09-28 19:41:34 +01:00
"time"
2024-10-29 11:20:01 +00:00
"compress/gzip"
2024-11-01 13:09:35 +00:00
"crypto/tls"
2024-09-28 19:41:34 +01:00
"database/sql"
"log/slog"
"net/http"
2024-11-01 13:09:35 +00:00
"net/http/httputil"
"net/url"
2024-09-28 19:41:34 +01:00
"path/filepath"
2024-09-29 16:06:28 +01:00
"github.com/go-chi/chi/v5"
2024-09-28 19:41:34 +01:00
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
2024-10-29 11:20:01 +00:00
"github.com/klauspost/compress/zstd"
2024-11-03 11:58:33 +00:00
"gopkg.in/yaml.v3"
2024-09-28 19:41:34 +01:00
_ "github.com/lib/pq"
2024-10-20 18:39:20 +01:00
_ "github.com/mattn/go-sqlite3"
2024-09-28 19:41:34 +01:00
)
type Config struct {
Global struct {
2024-11-03 11:58:33 +00:00
IP string ` yaml:"ip" validate:"required,ip_addr" `
HTTPPort string ` yaml:"httpPort" validate:"required" `
HTTPSPort string ` yaml:"httpsPort" validate:"required" `
ServiceDirectory string ` yaml:"serviceDirectory" validate:"required" `
ResourceDirectory string ` yaml:"resourceDirectory" validate:"required" `
2024-11-01 13:09:35 +00:00
Compression struct {
2024-11-03 11:58:33 +00:00
Algorithm string ` yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd" `
Level float64 ` yaml:"level" validate:"omitempty,min=1,max=22" `
} ` yaml:"compression" `
2024-11-03 13:03:49 +00:00
HTTPS struct {
2024-11-03 13:05:44 +00:00
CertificatePath string ` yaml:"certificate" validate:"required" `
KeyPath string ` yaml:"key" validate:"required" `
2024-11-03 13:20:09 +00:00
} ` yaml:"https" `
2024-11-03 15:33:12 +00:00
Logging struct {
2024-11-03 11:58:33 +00:00
Enabled bool ` yaml:"enabled" `
File string ` yaml:"file" validate:"required_if=Enabled true" `
} ` yaml:"logging" `
2024-11-01 13:09:35 +00:00
Database struct {
2024-11-03 11:58:33 +00:00
Type string ` yaml:"type" validate:"required,oneof=sqlite postgres" `
ConnectionString string ` yaml:"connectionString" validate:"required_if=Type postgres" `
Path string ` yaml:"path" validate:"required_if=Type sqlite" `
} ` yaml:"database" validate:"required" `
2024-11-03 14:26:02 +00:00
Stealth struct {
Enabled bool ` yaml:"enabled" `
Server string ` yaml:"server" validate:"required_if=Enabled true" `
PHP struct {
Enabled bool ` yaml:"enabled" `
Version string ` yaml:"version" validate:"required_if=Enabled true" `
} ` yaml:"php" `
ASPNet bool ` yaml:"aspNet" `
}
2024-11-03 11:58:33 +00:00
} ` yaml:"global" validate:"required" `
2024-11-01 13:09:35 +00:00
Routes [ ] struct {
2024-11-03 11:58:33 +00:00
Subdomain string ` yaml:"subdomain" validate:"required" `
Services [ ] string ` yaml:"services" `
2024-11-01 13:09:35 +00:00
Paths [ ] struct {
2024-11-03 13:20:09 +00:00
Paths [ ] string ` yaml:"paths" validate:"required" `
2024-11-01 13:09:35 +00:00
Proxy struct {
2024-11-03 11:58:33 +00:00
URL string ` yaml:"url" validate:"required" `
StripPrefix bool ` yaml:"stripPrefix" `
2024-11-03 13:20:09 +00:00
} ` yaml:"proxy" validate:"required_without=Static Redirect" `
2024-11-01 13:09:35 +00:00
Static struct {
2024-11-03 11:58:33 +00:00
Root string ` yaml:"root" validate:"required,isDirectory" `
DirectoryListing bool ` yaml:"directoryListing" `
2024-11-03 13:20:09 +00:00
} ` yaml:"static" validate:"required_without_all=Proxy Redirect" `
Redirect struct {
URL string ` yaml:"url" validate:"required" `
Permanent bool ` yaml:"permanent" `
} ` yaml:"redirect" validate:"required_without_all=Proxy Static" `
2024-11-03 11:58:33 +00:00
} ` yaml:"paths" `
2024-11-01 13:09:35 +00:00
HTTPS struct {
2024-11-03 13:05:44 +00:00
CertificatePath string ` yaml:"certificate" validate:"required" `
KeyPath string ` yaml:"key" validate:"required" `
2024-11-03 11:58:33 +00:00
} ` yaml:"https" `
2024-11-01 13:09:35 +00:00
Compression struct {
2024-11-03 11:58:33 +00:00
Algorithm string ` yaml:"algorithm" validate:"omitempty,oneof=gzip brotli zstd" `
Level float64 ` yaml:"level" validate:"omitempty,min=1,max=22" `
} ` yaml:"compression" `
} ` yaml:"routes" `
Services map [ string ] interface { } ` yaml:"services" `
2024-09-28 19:41:34 +01:00
}
2024-10-04 19:37:05 +01:00
type Service struct {
2024-10-15 19:17:29 +01:00
ServiceID uuid . UUID
ServiceMetadata library . Service
2024-11-03 11:58:33 +00:00
ServiceMainFunc func ( library . ServiceInitializationInformation )
2024-10-15 19:17:29 +01:00
Inbox chan library . InterServiceMessage
2024-09-28 19:41:34 +01:00
}
2024-10-29 11:20:01 +00:00
type ResponseWriterWrapper struct {
http . ResponseWriter
io . Writer
}
2024-11-01 13:09:35 +00:00
type CompressionSettings struct {
Level int
Algorithm string
}
2024-10-29 11:20:01 +00:00
func ( w * ResponseWriterWrapper ) WriteHeader ( statusCode int ) {
w . ResponseWriter . WriteHeader ( statusCode )
}
func ( w * ResponseWriterWrapper ) Write ( p [ ] byte ) ( int , error ) {
return w . Writer . Write ( p )
}
2024-11-01 13:09:35 +00:00
func checkCompressionAlgorithm ( algorithm string , handler http . Handler ) http . Handler {
switch algorithm {
case "gzip" :
return gzipHandler ( handler )
case "brotli" :
return brotliHandler ( handler )
case "zstd" :
return zStandardHandler ( handler )
default :
return handler
2024-10-24 19:39:45 +01:00
}
2024-11-01 13:09:35 +00:00
}
func logger ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
next . ServeHTTP ( w , r )
slog . Info ( r . Method + " " + r . URL . Path )
} )
}
func serverChanger ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2024-11-03 14:26:02 +00:00
if ! config . Global . Stealth . Enabled {
w . Header ( ) . Set ( "Server" , "Fulgens HTTP Server" )
w . Header ( ) . Set ( "X-Powered-By" , "Go net/http" )
2024-11-03 15:33:12 +00:00
w . Header ( ) . Set ( "Alt-Svc" , "Alt-Svc: h2=\":443\"; ma=3600" )
2024-11-03 14:26:02 +00:00
} else {
switch config . Global . Stealth . Server {
case "nginx" :
w . Header ( ) . Set ( "Server" , "nginx" )
}
var poweredBy strings . Builder
if config . Global . Stealth . PHP . Enabled {
poweredBy . WriteString ( "PHP/" + config . Global . Stealth . PHP . Version )
}
if config . Global . Stealth . ASPNet {
if poweredBy . Len ( ) > 0 {
poweredBy . WriteString ( ", " )
}
poweredBy . WriteString ( "ASP.NET" )
}
w . Header ( ) . Set ( "X-Powered-By" , poweredBy . String ( ) )
}
2024-11-01 13:09:35 +00:00
next . ServeHTTP ( w , r )
} )
}
func gzipHandler ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if strings . Contains ( r . Header . Get ( "Accept-Encoding" ) , "gzip" ) {
var compressionLevel int
var host string
if r . Header . Get ( "Host" ) != "" {
host = r . Header . Get ( "Host" )
} else {
host = "none"
}
compressionSettings , ok := compression [ host ]
if ! ok {
compressionLevel = int ( config . Global . Compression . Level )
} else {
compressionLevel = compressionSettings . Level
}
gzipWriter , err := gzip . NewWriterLevel ( w , compressionLevel )
if err != nil {
slog . Error ( "Error creating gzip writer: " + err . Error ( ) )
next . ServeHTTP ( w , r )
return
}
defer func ( ) {
w . Header ( ) . Del ( "Content-Length" )
err := gzipWriter . Close ( )
if errors . Is ( err , http . ErrBodyNotAllowed ) {
// This is fine, all it means is that they have it cached, and we don't need to send it
2024-10-29 11:20:01 +00:00
return
2024-11-01 13:09:35 +00:00
} else if err != nil {
slog . Error ( "Error closing gzip writer: " + err . Error ( ) )
2024-10-29 11:20:01 +00:00
}
2024-11-01 13:09:35 +00:00
} ( )
gzipResponseWriter := & ResponseWriterWrapper { ResponseWriter : w , Writer : gzipWriter }
if w . Header ( ) . Get ( "Content-Encoding" ) != "" {
w . Header ( ) . Set ( "Content-Encoding" , w . Header ( ) . Get ( "Content-Encoding" ) + ", gzip" )
} else {
w . Header ( ) . Set ( "Content-Encoding" , "gzip" )
}
next . ServeHTTP ( gzipResponseWriter , r )
} else {
next . ServeHTTP ( w , r )
}
} )
}
2024-11-03 14:59:05 +00:00
2024-11-01 13:09:35 +00:00
func brotliHandler ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if strings . Contains ( r . Header . Get ( "Accept-Encoding" ) , "br" ) {
var compressionLevel int
var host string
if r . Header . Get ( "Host" ) != "" {
host = r . Header . Get ( "Host" )
} else {
host = "none"
}
compressionSettings , ok := compression [ host ]
if ! ok {
compressionLevel = int ( config . Global . Compression . Level )
} else {
compressionLevel = compressionSettings . Level
}
brotliWriter := brotli . NewWriterV2 ( w , compressionLevel )
defer func ( ) {
w . Header ( ) . Del ( "Content-Length" )
err := brotliWriter . Close ( )
if errors . Is ( err , http . ErrBodyNotAllowed ) {
// This is fine, all it means is that they have it cached, and we don't need to send it
return
} else if err != nil {
slog . Error ( "Error closing Brotli writer: " + err . Error ( ) )
2024-10-29 11:20:01 +00:00
}
2024-11-01 13:09:35 +00:00
} ( )
brotliResponseWriter := & ResponseWriterWrapper { ResponseWriter : w , Writer : brotliWriter }
if w . Header ( ) . Get ( "Content-Encoding" ) != "" {
w . Header ( ) . Set ( "Content-Encoding" , w . Header ( ) . Get ( "Content-Encoding" ) + ", br" )
2024-10-29 11:20:01 +00:00
} else {
2024-11-01 13:09:35 +00:00
w . Header ( ) . Set ( "Content-Encoding" , "br" )
}
next . ServeHTTP ( brotliResponseWriter , r )
} else {
next . ServeHTTP ( w , r )
}
} )
}
func zStandardHandler ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if strings . Contains ( r . Header . Get ( "Accept-Encoding" ) , "zstd" ) {
var compressionLevel int
var host string
if r . Header . Get ( "Host" ) != "" {
host = r . Header . Get ( "Host" )
} else {
host = "none"
}
compressionSettings , ok := compression [ host ]
if ! ok {
compressionLevel = int ( config . Global . Compression . Level )
} else {
compressionLevel = compressionSettings . Level
}
zStandardWriter , err := zstd . NewWriter ( w , zstd . WithEncoderLevel ( zstd . EncoderLevelFromZstd ( compressionLevel ) ) )
if err != nil {
slog . Error ( "Error creating ZStandard writer: " + err . Error ( ) )
2024-10-29 11:20:01 +00:00
next . ServeHTTP ( w , r )
2024-11-01 13:09:35 +00:00
return
2024-10-29 11:20:01 +00:00
}
2024-11-01 13:09:35 +00:00
defer func ( ) {
w . Header ( ) . Del ( "Content-Length" )
err := zStandardWriter . Close ( )
if err != nil {
2024-10-29 11:20:01 +00:00
if errors . Is ( err , http . ErrBodyNotAllowed ) {
// This is fine, all it means is that they have it cached, and we don't need to send it
return
2024-11-01 13:09:35 +00:00
} else {
slog . Error ( "Error closing ZStandard writer: " + err . Error ( ) )
2024-10-29 11:20:01 +00:00
}
2024-11-01 13:09:35 +00:00
}
} ( )
gzipResponseWriter := & ResponseWriterWrapper { ResponseWriter : w , Writer : zStandardWriter }
if w . Header ( ) . Get ( "Content-Encoding" ) != "" {
w . Header ( ) . Set ( "Content-Encoding" , w . Header ( ) . Get ( "Content-Encoding" ) + ", zstd" )
2024-10-29 11:20:01 +00:00
} else {
2024-11-01 13:09:35 +00:00
w . Header ( ) . Set ( "Content-Encoding" , "zstd" )
2024-10-29 11:20:01 +00:00
}
2024-11-01 13:09:35 +00:00
next . ServeHTTP ( gzipResponseWriter , r )
} else {
next . ServeHTTP ( w , r )
}
} )
}
2024-11-03 12:21:45 +00:00
func listDirectory ( w http . ResponseWriter , r * http . Request , root string , path string ) {
2024-11-01 13:09:35 +00:00
// Provide a directory listing
w . WriteHeader ( 200 )
w . Header ( ) . Set ( "Content-Type" , "text/html" )
_ , err := w . Write ( [ ] byte ( "<html><body><h2>Directory listing</h2><ul>" ) )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error writing directory listing: " + err . Error ( ) )
return
2024-10-29 11:20:01 +00:00
}
2024-11-03 12:07:53 +00:00
entries , err := os . ReadDir ( filepath . Join ( root , filepath . FromSlash ( r . URL . Path ) ) )
2024-11-01 13:09:35 +00:00
if err != nil {
serverError ( w , 500 )
2024-11-03 12:07:53 +00:00
slog . Error ( "Error listing directory: " + err . Error ( ) )
2024-11-01 13:09:35 +00:00
return
}
2024-11-03 12:07:53 +00:00
for _ , entry := range entries {
2024-11-03 12:21:45 +00:00
relPath , err := filepath . Rel ( root , filepath . Join ( root , filepath . FromSlash ( r . URL . Path ) , entry . Name ( ) ) )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error getting relative path: " + err . Error ( ) )
return
}
_ , err = w . Write ( [ ] byte ( "<li><a href=\"" + path + strings . TrimPrefix ( relPath , "./" ) + "\">" + entry . Name ( ) + "</a></li>" ) )
2024-11-03 12:07:53 +00:00
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error writing directory listing: " + err . Error ( ) )
return
}
}
2024-11-01 13:09:35 +00:00
_ , err = w . Write ( [ ] byte ( "</ul></body></html>" ) )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error writing directory listing: " + err . Error ( ) )
return
}
}
func parseEndRange ( w http . ResponseWriter , file * os . File , end string ) {
endI64 , err := strconv . ParseInt ( end , 10 , 64 )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error parsing range: " + err . Error ( ) )
return
}
_ , err = file . Seek ( - endI64 , io . SeekEnd )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error seeking file: " + err . Error ( ) )
return
}
_ , err = io . Copy ( w , file )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error writing file: " + err . Error ( ) )
return
}
}
func parseBeginningRange ( w http . ResponseWriter , file * os . File , beginning string ) {
beginningI64 , err := strconv . ParseInt ( beginning , 10 , 64 )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error parsing range: " + err . Error ( ) )
return
}
_ , err = file . Seek ( beginningI64 , io . SeekStart )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error seeking file: " + err . Error ( ) )
return
}
_ , err = io . Copy ( w , file )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error writing file: " + err . Error ( ) )
return
}
}
func parsePartRange ( w http . ResponseWriter , file * os . File , beginning , end string ) {
beginningI64 , err := strconv . ParseInt ( beginning , 10 , 64 )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error parsing range: " + err . Error ( ) )
return
}
endI64 , err := strconv . ParseInt ( end , 10 , 64 )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error parsing range: " + err . Error ( ) )
return
}
_ , err = file . Seek ( beginningI64 , io . SeekStart )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error seeking file: " + err . Error ( ) )
return
}
_ , err = io . CopyN ( w , file , endI64 - beginningI64 )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error writing file: " + err . Error ( ) )
return
}
}
2024-11-03 12:21:45 +00:00
func newFileServer ( root string , directoryListing bool , path string ) http . Handler {
2024-11-01 13:09:35 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
stat , err := os . Stat ( filepath . Join ( root , filepath . FromSlash ( r . URL . Path ) ) )
if err != nil {
serverError ( w , 404 )
return
}
if stat . IsDir ( ) {
2024-11-03 12:07:53 +00:00
// See if index.html exists
_ , err := os . Stat ( filepath . Join ( root , filepath . FromSlash ( r . URL . Path ) , "index.html" ) )
if err != nil {
if directoryListing {
2024-11-03 12:21:45 +00:00
listDirectory ( w , r , root , path )
2024-11-03 12:07:53 +00:00
} else {
serverError ( w , 403 )
}
return
2024-11-01 13:09:35 +00:00
} else {
2024-11-03 12:07:53 +00:00
// Serve the index.html file
r . URL . Path = filepath . Join ( r . URL . Path , "index.html" )
2024-11-01 13:09:35 +00:00
}
}
file , err := os . Open ( filepath . Join ( root , filepath . FromSlash ( r . URL . Path ) ) )
if err != nil {
serverError ( w , 500 )
return
}
w . Header ( ) . Set ( "Content-Type" , mime . TypeByExtension ( filepath . Ext ( r . URL . Path ) ) )
if strings . HasPrefix ( r . Header . Get ( "Range" ) , "bytes=" ) {
// Parse the range header. If there is an int-int, seek to the first int then return a limitedReader.
// If there is an int-, seek to the first int and return the rest of the file.
// If there is an -int, seek to the end of the file minus int and return the last int bytes.
for _ , item := range strings . Split ( strings . TrimPrefix ( r . Header . Get ( "Range" ) , "bytes=" ) , ", " ) {
if strings . Contains ( item , "-" ) {
beginning := strings . Split ( item , "-" ) [ 0 ]
end := strings . Split ( item , "-" ) [ 1 ]
if beginning == "" {
parseEndRange ( w , file , end )
} else if end == "" {
parseBeginningRange ( w , file , beginning )
} else {
parsePartRange ( w , file , beginning , end )
}
2024-10-29 11:20:01 +00:00
} else {
2024-11-01 13:09:35 +00:00
serverError ( w , 416 )
return
2024-10-29 11:20:01 +00:00
}
}
2024-11-01 13:09:35 +00:00
} else {
_ , err = io . Copy ( w , file )
if err != nil {
serverError ( w , 500 )
slog . Error ( "Error writing file: " + err . Error ( ) )
return
}
err = file . Close ( )
if err != nil {
slog . Error ( "Error closing file: " + err . Error ( ) )
}
}
} )
}
func serverError ( w http . ResponseWriter , status int ) {
w . Header ( ) . Set ( "Content-Type" , "text/html" )
w . WriteHeader ( status )
2024-11-03 14:26:02 +00:00
if ! config . Global . Stealth . Enabled {
_ , err := w . Write ( [ ] byte ( "<html><body><h2>" + strconv . Itoa ( status ) + " " + http . StatusText ( status ) + "</h2><span>Fulgens HTTP Server</span></body></html>" ) )
if err != nil {
slog . Error ( "Error writing " + strconv . Itoa ( status ) + ": " + err . Error ( ) )
return
}
} else {
switch config . Global . Stealth . Server {
case "nginx" :
_ , err := w . Write ( [ ] byte ( "<html><head><title>" + strconv . Itoa ( status ) + " " + http . StatusText ( status ) + "</title></head>\n<body>\n<center><h1>" + strconv . Itoa ( status ) + " " + http . StatusText ( status ) + "</h1></center>\n<hr><center>nginx/1.27.2</center>\n\n\n</body></html>" ) )
if err != nil {
slog . Error ( "Error writing " + strconv . Itoa ( status ) + ": " + err . Error ( ) )
return
}
case "net/http" :
_ , err := w . Write ( [ ] byte ( strconv . Itoa ( status ) + " " + http . StatusText ( status ) ) )
if err != nil {
slog . Error ( "Error writing " + strconv . Itoa ( status ) + ": " + err . Error ( ) )
return
}
}
2024-11-01 13:09:35 +00:00
}
}
func hostRouter ( w http . ResponseWriter , r * http . Request ) {
host := strings . Split ( r . Host , ":" ) [ 0 ]
router , ok := subdomains [ host ]
if ! ok {
router , ok = subdomains [ "none" ]
if ! ok {
serverError ( w , 404 )
slog . Error ( "No subdomain found for " + host )
}
2024-10-29 11:20:01 +00:00
}
2024-11-01 13:09:35 +00:00
compressionSettings , ok := compression [ host ]
if ! ok {
checkCompressionAlgorithm ( config . Global . Compression . Algorithm , router ) . ServeHTTP ( w , r )
} else {
checkCompressionAlgorithm ( compressionSettings . Algorithm , router ) . ServeHTTP ( w , r )
}
}
var (
2024-11-03 11:58:33 +00:00
validate * validator . Validate
lock sync . RWMutex
config Config
registeredServices = make ( map [ string ] Service )
activeServices = make ( map [ uuid . UUID ] Service )
certificates = make ( map [ string ] * tls . Certificate )
compression = make ( map [ string ] CompressionSettings )
subdomains = make ( map [ string ] * chi . Mux )
2024-09-28 19:41:34 +01:00
)
2024-11-01 13:09:35 +00:00
func loadTLSCertificate ( certificatePath , keyPath string ) ( * tls . Certificate , error ) {
certificate , err := tls . LoadX509KeyPair ( certificatePath , keyPath )
if err != nil {
return nil , err
} else {
return & certificate , nil
}
}
func getTLSCertificate ( hello * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
cert , ok := certificates [ hello . ServerName ]
if ! ok {
2024-11-03 13:03:49 +00:00
if config . Global . HTTPS . CertificatePath == "" || config . Global . HTTPS . KeyPath == "" {
return nil , errors . New ( "no certificate found" )
} else {
return certificates [ "none" ] , nil
}
2024-11-01 13:09:35 +00:00
} else {
return cert , nil
}
}
func svInit ( message library . InterServiceMessage ) {
// Service database initialization message
// Check if the service has the necessary permissions
2024-11-03 11:58:33 +00:00
if activeServices [ message . ServiceID ] . ServiceMetadata . Permissions . Database {
2024-11-01 13:09:35 +00:00
// Check if we are using sqlite or postgres
if config . Global . Database . Type == "sqlite" {
// Open the database and return the connection
pluginConn , err := sql . Open ( "sqlite3" , filepath . Join ( config . Global . Database . Path , message . ServiceID . String ( ) + ".db" ) )
if err != nil {
// Report an error
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-11-01 13:09:35 +00:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : err ,
}
} else {
// Report a successful activation
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-11-01 13:09:35 +00:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 2 ,
SentAt : time . Now ( ) ,
Message : library . Database {
DB : pluginConn ,
DBType : library . Sqlite ,
} ,
}
2024-09-28 19:41:34 +01:00
}
2024-11-01 13:09:35 +00:00
} else if config . Global . Database . Type == "postgres" {
// Connect to the database
conn , err := sql . Open ( "postgres" , config . Global . Database . ConnectionString )
if err != nil {
// Report an error
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-09-28 19:41:34 +01:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
2024-11-01 13:09:35 +00:00
MessageType : 1 ,
2024-09-28 19:41:34 +01:00
SentAt : time . Now ( ) ,
2024-11-01 13:09:35 +00:00
Message : err ,
2024-09-28 19:41:34 +01:00
}
2024-11-01 13:09:35 +00:00
} else {
// Try to create the schema
_ , err = conn . Exec ( "CREATE SCHEMA IF NOT EXISTS \"" + message . ServiceID . String ( ) + "\"" )
if err != nil {
// Report an error
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-11-01 13:09:35 +00:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : err ,
}
} else {
// Create a new connection to the database
var connectionString string
if strings . Contains ( config . Global . Database . ConnectionString , "?" ) {
connectionString = config . Global . Database . ConnectionString + "&search_path=\"" + message . ServiceID . String ( ) + "\""
} else {
connectionString = config . Global . Database . ConnectionString + "?search_path=\"" + message . ServiceID . String ( ) + "\""
}
pluginConn , err := sql . Open ( "postgres" , connectionString )
if err != nil {
// Report an error
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-11-01 13:09:35 +00:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : err ,
}
} else {
// Test the connection
err = pluginConn . Ping ( )
2024-09-28 19:41:34 +01:00
if err != nil {
// Report an error
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-09-28 19:41:34 +01:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : err ,
}
} else {
// Report a successful activation
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-09-28 19:41:34 +01:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 2 ,
SentAt : time . Now ( ) ,
2024-10-13 19:20:19 +01:00
Message : library . Database {
DB : pluginConn ,
2024-11-01 13:09:35 +00:00
DBType : library . Postgres ,
2024-10-13 19:20:19 +01:00
} ,
2024-09-28 19:41:34 +01:00
}
}
}
}
}
2024-11-01 13:09:35 +00:00
}
} else {
// Report an error
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-11-01 13:09:35 +00:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "database access not permitted" ) ,
}
}
}
func tryAuthAccess ( message library . InterServiceMessage ) {
// We need to check if the service is allowed to access the Authentication service
2024-11-03 11:58:33 +00:00
serviceMetadata , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok && serviceMetadata . ServiceMetadata . Permissions . Authenticate {
// Send message to Authentication service
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ uuid . MustParse ( "00000000-0000-0000-0000-000000000004" ) ]
2024-11-01 13:09:35 +00:00
if ok {
service . Inbox <- message
} else if ! ok {
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-09-28 19:41:34 +01:00
if ok {
2024-11-01 13:09:35 +00:00
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "authentication service not found" ) ,
2024-09-28 19:41:34 +01:00
}
2024-11-01 13:09:35 +00:00
} else {
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
} else {
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok {
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "authentication service not yet available" ) ,
2024-09-28 19:41:34 +01:00
}
} else {
2024-11-01 13:09:35 +00:00
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
}
} else {
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok {
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "authentication not permitted" ) ,
}
} else {
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
}
}
func tryStorageAccess ( message library . InterServiceMessage ) {
// We need to check if the service is allowed to access the Blob Storage service
2024-11-03 11:58:33 +00:00
serviceMetadata , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok && serviceMetadata . ServiceMetadata . Permissions . BlobStorage {
// Send message to Blob Storage service
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ uuid . MustParse ( "00000000-0000-0000-0000-000000000003" ) ]
2024-11-01 13:09:35 +00:00
if ok {
service . Inbox <- message
} else if ! ok {
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok {
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "blob storage service not found" ) ,
}
} else {
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
} else {
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok {
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "blob storage is not yet available" ) ,
}
} else {
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
}
} else {
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok {
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "blob storage is not permitted" ) ,
}
} else {
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
}
}
func tryLogger ( message library . InterServiceMessage ) {
// Logger service
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-11-01 13:09:35 +00:00
if ok {
switch message . MessageType {
case 0 :
// Log message
2024-11-03 11:58:33 +00:00
slog . Info ( strings . ToLower ( service . ServiceMetadata . Name ) + " says: " + message . Message . ( string ) )
2024-11-01 13:09:35 +00:00
case 1 :
// Warn message
2024-11-03 11:58:33 +00:00
slog . Warn ( strings . ToLower ( service . ServiceMetadata . Name ) + " warns: " + message . Message . ( string ) )
2024-11-01 13:09:35 +00:00
case 2 :
// Error message
2024-11-03 11:58:33 +00:00
slog . Error ( strings . ToLower ( service . ServiceMetadata . Name ) + " complains: " + message . Message . ( string ) )
2024-11-01 13:09:35 +00:00
case 3 :
// Fatal message
2024-11-03 11:58:33 +00:00
slog . Error ( strings . ToLower ( service . ServiceMetadata . Name ) + "'s dying wish: " + message . Message . ( string ) )
2024-11-01 13:09:35 +00:00
os . Exit ( 1 )
}
}
}
func processInterServiceMessage ( channel chan library . InterServiceMessage ) {
for {
message := <- channel
if message . ForServiceID == uuid . MustParse ( "00000000-0000-0000-0000-000000000000" ) {
// Broadcast message
2024-11-03 11:58:33 +00:00
for _ , service := range activeServices {
2024-11-01 13:09:35 +00:00
service . Inbox <- message
}
} else if message . ForServiceID == uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) {
// Service initialization service
switch message . MessageType {
case 0 :
// This has been deprecated, ignore it
// Send "true" back
2024-11-03 11:58:33 +00:00
activeServices [ message . ServiceID ] . Inbox <- library . InterServiceMessage {
2024-11-01 13:09:35 +00:00
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 0 ,
SentAt : time . Now ( ) ,
Message : true ,
2024-09-28 19:41:34 +01:00
}
2024-11-01 13:09:35 +00:00
case 1 :
svInit ( message )
2024-09-28 19:41:34 +01:00
}
2024-11-01 13:09:35 +00:00
} else if message . ForServiceID == uuid . MustParse ( "00000000-0000-0000-0000-000000000002" ) {
tryLogger ( message )
} else if message . ForServiceID == uuid . MustParse ( "00000000-0000-0000-0000-000000000003" ) {
tryStorageAccess ( message )
} else if message . ForServiceID == uuid . MustParse ( "00000000-0000-0000-0000-000000000004" ) {
tryAuthAccess ( message )
2024-09-28 19:41:34 +01:00
} else {
2024-11-03 11:58:33 +00:00
serviceMetadata , ok := activeServices [ message . ServiceID ]
2024-10-04 19:37:05 +01:00
if ok && serviceMetadata . ServiceMetadata . Permissions . InterServiceCommunication {
2024-09-28 19:41:34 +01:00
// Send message to specific service
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ForServiceID ]
2024-10-04 19:37:05 +01:00
if ! ok {
2024-09-28 19:41:34 +01:00
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-09-28 19:41:34 +01:00
if ok {
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "requested service not found" ) ,
}
} else {
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
}
2024-10-04 19:37:05 +01:00
service . Inbox <- message
2024-09-28 19:41:34 +01:00
} else {
// Send error message
2024-11-03 11:58:33 +00:00
service , ok := activeServices [ message . ServiceID ]
2024-09-28 19:41:34 +01:00
if ok {
service . Inbox <- library . InterServiceMessage {
ServiceID : uuid . MustParse ( "00000000-0000-0000-0000-000000000001" ) ,
ForServiceID : message . ServiceID ,
MessageType : 1 ,
SentAt : time . Now ( ) ,
Message : errors . New ( "inter-service communication not permitted" ) ,
}
} else {
// This should never happen
slog . Error ( "Bit flip error: Impossible service ID. Move away from radiation or use ECC memory." )
os . Exit ( 1 )
}
}
}
}
}
func parseConfig ( path string ) Config {
// Register the custom validators
validate = validator . New ( )
// Register the custom isDirectory validator
err := validate . RegisterValidation ( "isDirectory" , func ( fl validator . FieldLevel ) bool {
// Check if it exists
fileInfo , err := os . Stat ( fl . Field ( ) . String ( ) )
if err != nil {
return false
}
// Check if it is a directory
return fileInfo . IsDir ( )
} )
2024-11-01 13:09:35 +00:00
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-01 13:09:35 +00:00
slog . Error ( "Error registering custom validator: " + err . Error ( ) )
2024-09-28 19:41:34 +01:00
os . Exit ( 1 )
}
// Parse the configuration file
2024-11-03 11:58:33 +00:00
configFile , err := os . Open ( path )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-01 13:09:35 +00:00
slog . Error ( "Error reading configuration file: " + err . Error ( ) )
2024-09-28 19:41:34 +01:00
os . Exit ( 1 )
}
// Parse the configuration file
2024-11-03 11:58:33 +00:00
decoder := yaml . NewDecoder ( configFile )
2024-10-29 11:20:01 +00:00
err = decoder . Decode ( & config )
2024-09-28 19:41:34 +01:00
if err != nil {
2024-11-01 13:09:35 +00:00
slog . Error ( "Error parsing configuration file: " + err . Error ( ) )
2024-09-28 19:41:34 +01:00
os . Exit ( 1 )
}
// Validate the configuration
err = validate . Struct ( config )
if err != nil {
2024-11-01 13:09:35 +00:00
slog . Error ( "Invalid configuration: " + err . Error ( ) )
2024-09-28 19:41:34 +01:00
os . Exit ( 1 )
}
// Check if we are logging to a file
2024-11-01 13:09:35 +00:00
if config . Global . Logging != ( Config { } . Global . Logging ) && config . Global . Logging . Enabled {
2024-09-28 19:41:34 +01:00
// Check if the log file is set
2024-11-01 13:09:35 +00:00
logFilePath := config . Global . Logging . File
2024-09-28 19:41:34 +01:00
// Set the log file
logFile , err := os . OpenFile ( logFilePath , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0644 )
if err != nil {
2024-11-01 13:09:35 +00:00
slog . Error ( "Error opening log file: " + err . Error ( ) )
2024-09-28 19:41:34 +01:00
os . Exit ( 1 )
}
log . SetOutput ( io . MultiWriter ( os . Stdout , logFile ) )
}
return config
}
2024-11-03 11:58:33 +00:00
func iterateThroughSubdomains ( globalOutbox chan library . InterServiceMessage ) {
2024-11-01 13:09:35 +00:00
for _ , route := range config . Routes {
var subdomainRouter * chi . Mux
// Create the subdomain router
if route . Compression . Level != 0 {
compression [ route . Subdomain ] = CompressionSettings {
Level : int ( route . Compression . Level ) ,
Algorithm : route . Compression . Algorithm ,
}
} else {
subdomainRouter = chi . NewRouter ( )
subdomainRouter . NotFound ( func ( w http . ResponseWriter , r * http . Request ) {
serverError ( w , 404 )
} )
}
subdomains [ route . Subdomain ] = subdomainRouter
subdomains [ route . Subdomain ] . Use ( logger )
subdomains [ route . Subdomain ] . Use ( serverChanger )
// Check the services
if route . Services != nil {
// Iterate through the services
for _ , service := range route . Services {
2024-11-03 11:58:33 +00:00
// Check if the service is registered
registeredService , ok := registeredServices [ service ]
if ok {
// Check if the service is already active
_ , ok := activeServices [ registeredService . ServiceMetadata . ServiceID ]
if ok {
slog . Error ( "Service with ID " + service + " is already active, will not activate again" )
os . Exit ( 1 )
}
// Initialize the service
initializeService ( registeredService , globalOutbox , subdomainRouter )
2024-11-01 13:09:35 +00:00
} else {
2024-11-03 11:58:33 +00:00
slog . Warn ( "Service with ID " + service + " is not registered" )
2024-11-01 13:09:35 +00:00
}
}
}
// Iterate through the paths
2024-11-03 13:20:09 +00:00
for _ , pathBlock := range route . Paths {
for _ , path := range pathBlock . Paths {
if pathBlock . Static . Root != "" {
// Serve the static directory
rawPath := strings . TrimSuffix ( path , "*" )
subdomainRouter . Handle ( path , http . StripPrefix ( rawPath , newFileServer ( pathBlock . Static . Root , pathBlock . Static . DirectoryListing , rawPath ) ) )
slog . Info ( "Serving static directory " + pathBlock . Static . Root + " on subdomain " + route . Subdomain + " with pattern " + path )
} else if pathBlock . Proxy . URL != "" {
// Parse the URL
proxyUrl , err := url . Parse ( pathBlock . Proxy . URL )
if err != nil {
slog . Error ( "Error parsing URL: " + err . Error ( ) )
os . Exit ( 1 )
}
// Create the proxy
if pathBlock . Proxy . StripPrefix {
subdomainRouter . Handle ( path , http . StripPrefix ( strings . TrimSuffix ( path , "*" ) , httputil . NewSingleHostReverseProxy ( proxyUrl ) ) )
} else {
subdomainRouter . Handle ( path , httputil . NewSingleHostReverseProxy ( proxyUrl ) )
}
} else if pathBlock . Redirect . URL != "" {
// Set the code
code := http . StatusFound
if pathBlock . Redirect . Permanent {
code = http . StatusMovedPermanently
}
// Create the redirect
subdomainRouter . Handle ( path , http . RedirectHandler ( pathBlock . Redirect . URL , code ) )
2024-11-01 13:09:35 +00:00
}
}
}
// Add the TLS certificate
if route . HTTPS . CertificatePath != "" && route . HTTPS . KeyPath != "" {
certificate , err := loadTLSCertificate ( route . HTTPS . CertificatePath , route . HTTPS . KeyPath )
if err != nil {
2024-11-03 13:20:09 +00:00
slog . Error ( "Error loading TLS certificate: " + err . Error ( ) + ", TLS will not be available for subdomain " + route . Subdomain )
2024-11-01 13:09:35 +00:00
}
certificates [ route . Subdomain ] = certificate
}
}
}
2024-11-03 13:03:49 +00:00
func registerServices ( ) ( err error ) {
err = filepath . Walk ( config . Global . ServiceDirectory , func ( path string , info os . FileInfo , err error ) error {
if err != nil {
return err
}
if info . IsDir ( ) || filepath . Ext ( path ) != ".fgs" {
return nil
}
// Open the service
service , err := plugin . Open ( path )
if err != nil {
return err
}
// Load the service information
serviceInformation , err := service . Lookup ( "ServiceInformation" )
if err != nil {
return errors . New ( "service lacks necessary information" )
}
// Load the main function
mainFunc , err := service . Lookup ( "Main" )
if err != nil {
return errors . New ( "service lacks necessary main function" )
}
// Register the service
var inbox = make ( chan library . InterServiceMessage , 1 )
lock . Lock ( )
registeredServices [ strings . ToLower ( serviceInformation . ( * library . Service ) . Name ) ] = Service {
ServiceID : serviceInformation . ( * library . Service ) . ServiceID ,
Inbox : inbox ,
ServiceMetadata : * serviceInformation . ( * library . Service ) ,
ServiceMainFunc : mainFunc . ( func ( library . ServiceInitializationInformation ) ) ,
}
lock . Unlock ( )
// Log the service registration
slog . Info ( "Service " + strings . ToLower ( serviceInformation . ( * library . Service ) . Name ) + " registered with ID " + serviceInformation . ( * library . Service ) . ServiceID . String ( ) )
return nil
} )
return err
}
2024-11-03 11:58:33 +00:00
func initializeService ( service Service , globalOutbox chan library . InterServiceMessage , subdomainRouter * chi . Mux ) {
// Get the plugin from the map
slog . Info ( "Activating service " + strings . ToLower ( service . ServiceMetadata . Name ) + " with ID " + service . ServiceMetadata . ServiceID . String ( ) )
2024-11-01 13:09:35 +00:00
2024-11-03 11:58:33 +00:00
serviceInitializationInformation := library . ServiceInitializationInformation {
Domain : strings . ToLower ( service . ServiceMetadata . Name ) ,
Configuration : config . Services [ strings . ToLower ( service . ServiceMetadata . Name ) ] . ( map [ string ] interface { } ) ,
Outbox : globalOutbox ,
Inbox : service . Inbox ,
Router : subdomainRouter ,
}
2024-11-01 13:09:35 +00:00
2024-11-03 11:58:33 +00:00
// Check if they want a resource directory
if service . ServiceMetadata . Permissions . Resources {
serviceInitializationInformation . ResourceDir = os . DirFS ( filepath . Join ( config . Global . ResourceDirectory , service . ServiceMetadata . ServiceID . String ( ) ) )
}
2024-11-01 13:09:35 +00:00
2024-11-03 11:58:33 +00:00
// Add the service to the active services
lock . Lock ( )
activeServices [ service . ServiceMetadata . ServiceID ] = service
lock . Unlock ( )
2024-11-01 13:09:35 +00:00
2024-11-03 11:58:33 +00:00
// Call the main function
service . ServiceMainFunc ( serviceInitializationInformation )
2024-11-01 13:09:35 +00:00
2024-11-03 11:58:33 +00:00
// Log the service activation
slog . Info ( "Service " + strings . ToLower ( service . ServiceMetadata . Name ) + " activated with ID " + service . ServiceMetadata . ServiceID . String ( ) )
2024-11-01 13:09:35 +00:00
}
2024-09-28 19:41:34 +01:00
func main ( ) {
// Parse the configuration file
if len ( os . Args ) < 2 {
2024-11-03 11:58:33 +00:00
info , err := os . Stat ( "config.yaml" )
2024-09-28 19:41:34 +01:00
if err != nil {
if errors . Is ( err , os . ErrNotExist ) {
slog . Error ( "No configuration file provided" )
os . Exit ( 1 )
} else {
2024-11-01 13:09:35 +00:00
slog . Error ( "Error reading configuration file: " + err . Error ( ) )
2024-09-28 19:41:34 +01:00
os . Exit ( 1 )
}
}
if info . IsDir ( ) {
slog . Error ( "No configuration file provided" )
os . Exit ( 1 )
}
2024-11-03 11:58:33 +00:00
config = parseConfig ( "config.yaml" )
2024-09-28 19:41:34 +01:00
} else {
config = parseConfig ( os . Args [ 1 ] )
}
2024-10-20 19:51:16 +01:00
// If we are using sqlite, create the database directory if it does not exist
2024-11-01 13:09:35 +00:00
if config . Global . Database . Type == "sqlite" {
err := os . MkdirAll ( config . Global . Database . Path , 0755 )
2024-10-20 19:51:16 +01:00
if err != nil {
2024-11-01 13:09:35 +00:00
slog . Error ( "Error creating database directory: " + err . Error ( ) )
2024-10-20 19:51:16 +01:00
os . Exit ( 1 )
}
}
2024-11-03 13:03:49 +00:00
// Check if the root TLS certificate exists
if config . Global . HTTPS . CertificatePath != "" && config . Global . HTTPS . KeyPath != "" {
certificate , err := loadTLSCertificate ( config . Global . HTTPS . CertificatePath , config . Global . HTTPS . KeyPath )
2024-11-03 11:58:33 +00:00
if err != nil {
2024-11-03 13:20:09 +00:00
slog . Error ( "Error loading TLS certificate: " + err . Error ( ) + ", TLS will not be available unless specified in the subdomains" )
2024-11-03 11:58:33 +00:00
}
2024-11-03 13:03:49 +00:00
certificates [ "none" ] = certificate
}
2024-09-28 19:41:34 +01:00
2024-11-03 13:03:49 +00:00
// Walk through the service directory and load the plugins
err := registerServices ( )
if err != nil {
slog . Error ( "Error registering services: " + err . Error ( ) )
os . Exit ( 1 )
}
2024-11-01 13:09:35 +00:00
2024-11-03 11:58:33 +00:00
var globalOutbox = make ( chan library . InterServiceMessage , 1 )
2024-09-28 19:41:34 +01:00
2024-11-03 11:58:33 +00:00
// Initialize the service discovery, health-check, and logging services
// Since these are core services, always allocate them the service IDs 0, 1, and 2
// These are not dynamically loaded, as they are integral to the system functioning
go processInterServiceMessage ( globalOutbox )
2024-09-28 19:41:34 +01:00
2024-11-03 11:58:33 +00:00
// Start the storage service
initializeService ( registeredServices [ "storage" ] , globalOutbox , nil )
2024-09-28 19:41:34 +01:00
2024-11-03 11:58:33 +00:00
// Iterate through the subdomains and create the routers as well as the compression levels and service maps
iterateThroughSubdomains ( globalOutbox )
2024-09-28 19:41:34 +01:00
2024-11-01 13:09:35 +00:00
// Start the server
slog . Info ( "Starting server on " + config . Global . IP + " with ports " + config . Global . HTTPPort + " and " + config . Global . HTTPSPort )
go func ( ) {
2024-11-03 15:33:12 +00:00
// Create the TLS server
server := & http . Server {
Addr : config . Global . IP + ":" + config . Global . HTTPSPort ,
Handler : http . HandlerFunc ( hostRouter ) ,
TLSConfig : & tls . Config {
GetCertificate : getTLSCertificate ,
} ,
2024-11-03 14:59:05 +00:00
}
2024-11-03 15:33:12 +00:00
// Start the TLS server
err = server . ListenAndServeTLS ( "" , "" )
slog . Error ( "Error starting HTTPS server: " + err . Error ( ) )
os . Exit ( 1 )
2024-11-01 13:09:35 +00:00
} ( )
// Start the HTTP server
err = http . ListenAndServe ( config . Global . IP + ":" + config . Global . HTTPPort , http . HandlerFunc ( hostRouter ) )
slog . Error ( "Error starting server: " + err . Error ( ) )
os . Exit ( 1 )
2024-09-28 19:41:34 +01:00
}