package server import ( "fmt" "net" "net/http" "os" "path/filepath" "strconv" "strings" "github.com/thomiceli/opengist/internal/validator" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" "github.com/thomiceli/opengist/internal/config" "github.com/thomiceli/opengist/internal/i18n" ) type Server struct { echo *echo.Echo dev bool } func NewServer(isDev bool) *Server { e := echo.New() e.HideBanner = true e.HidePort = true e.Validator = validator.NewValidator() s := &Server{echo: e, dev: isDev} s.useCustomContext() if err := i18n.Locales.LoadAll(); err != nil { log.Fatal().Err(err).Msg("Failed to load locales") } s.registerMiddlewares() s.setFuncMap() s.echo.HTTPErrorHandler = s.errorHandler if !s.dev { s.parseManifestEntries() } s.registerRoutes() return s } func isSocketPath(host string) bool { return strings.Contains(host, "/") || strings.Contains(host, "\\") } func (s *Server) Start() { if isSocketPath(config.C.HttpHost) { s.startUnixSocket() } else { s.startHTTP() } } func (s *Server) startHTTP() { addr := config.C.HttpHost + ":" + config.C.HttpPort log.Info().Msg("Starting HTTP server on http://" + addr) if err := s.echo.Start(addr); err != nil && err != http.ErrServerClosed { log.Fatal().Err(err).Msg("Failed to start HTTP server") } } func (s *Server) startUnixSocket() { socketPath := config.C.HttpHost if socketPath == "" { socketPath = "/tmp/opengist.sock" } if dir := filepath.Dir(socketPath); dir != "." { if err := os.MkdirAll(dir, 0755); err != nil { log.Warn().Err(err).Str("dir", dir).Msg("Failed to create socket directory") } } if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { log.Warn().Err(err).Str("socket", socketPath).Msg("Failed to remove existing socket file") } pidPath := strings.TrimSuffix(socketPath, filepath.Ext(socketPath)) + ".pid" if err := s.createPidFile(pidPath); err != nil { log.Warn().Err(err).Str("pid-file", pidPath).Msg("Failed to create PID file") } listener, err := net.Listen("unix", socketPath) if err != nil { log.Fatal().Err(err).Msg("Failed to start Unix socket server") } s.echo.Listener = listener if config.C.UnixSocketPermissions != "" { if perm, err := strconv.ParseUint(config.C.UnixSocketPermissions, 8, 32); err == nil { if err := os.Chmod(socketPath, os.FileMode(perm)); err != nil { log.Warn().Err(err).Str("socket", socketPath).Str("permissions", config.C.UnixSocketPermissions).Msg("Failed to set socket permissions") } } else { log.Warn().Err(err).Str("permissions", config.C.UnixSocketPermissions).Msg("Invalid socket permissions format") } } log.Info().Str("socket", socketPath).Msg("Starting Unix socket server") log.Info().Str("pid-file", pidPath).Msg("PID file created") server := new(http.Server) if err := s.echo.StartServer(server); err != nil && err != http.ErrServerClosed { log.Fatal().Err(err).Msg("Failed to start Unix socket server") } } func (s *Server) Stop() { if isSocketPath(config.C.HttpHost) { s.stopUnixSocket() } else { s.stopHTTP() } } func (s *Server) stopHTTP() { log.Info().Msg("Stopping HTTP server...") if err := s.echo.Close(); err != nil { log.Fatal().Err(err).Msg("Failed to stop HTTP server") } } func (s *Server) stopUnixSocket() { log.Info().Msg("Stopping Unix socket server...") var socketPath string if s.echo.Listener != nil { if unixListener, ok := s.echo.Listener.(*net.UnixListener); ok { socketPath = unixListener.Addr().String() } } if err := s.echo.Close(); err != nil { log.Error().Err(err).Msg("Failed to stop Unix socket server") } if socketPath != "" { if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { log.Error().Err(err).Str("socket", socketPath).Msg("Failed to remove socket file") } else { log.Info().Str("socket", socketPath).Msg("Socket file removed") } pidPath := strings.TrimSuffix(socketPath, filepath.Ext(socketPath)) + ".pid" if err := os.Remove(pidPath); err != nil && !os.IsNotExist(err) { log.Error().Err(err).Str("pid-file", pidPath).Msg("Failed to remove PID file") } else { log.Info().Str("pid-file", pidPath).Msg("PID file removed") } } } func (s *Server) createPidFile(pidPath string) error { pid := os.Getpid() pidContent := fmt.Sprintf("%d\n", pid) if err := os.WriteFile(pidPath, []byte(pidContent), 0644); err != nil { return err } return nil } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.echo.ServeHTTP(w, r) } func (s *Server) Use(middleware echo.MiddlewareFunc) { s.echo.Use(middleware) }