package gopkgserver import ( "context" _ "embed" "errors" "flag" "fmt" "io" "log/slog" "net" "net/http" "os" "github.com/pelletier/go-toml/v2" "go-pkg-server/logging" "go-pkg-server/logging/handler" "go-pkg-server/repo" ) type ServerConfig struct { Proto string `toml:"proto"` Address string `toml:"address"` } type Config struct { Server ServerConfig `toml:"server"` Log logging.Config `toml:"log"` Repos map[string]repo.Repo `toml:"repo"` } func Run(ctx context.Context, fs *flag.FlagSet, args []string) error { var cfgFile string fs.StringVar(&cfgFile, "config", "config.toml", "Path to config file") fs.Parse(args) cfg, err := LoadConfig(cfgFile) if err != nil { return err } logger := logging.SetupLogger(cfg.Log) ctx = logging.WithLogger(ctx, logger) h, err := repo.New(cfg.Repos) if err != nil { return fmt.Errorf(": %v", err) } mux := http.NewServeMux() mux.Handle("GET /robots.txt", Robot()) mux.Handle("GET /", h) return RunServer(ctx, handler.New(mux), cfg.Server) } func LoadConfig(cfgFile string) (Config, error) { data, err := os.ReadFile(cfgFile) if err != nil { return Config{}, fmt.Errorf("read config: %w", err) } var cfg Config if err := toml.Unmarshal(data, &cfg); err != nil { return Config{}, fmt.Errorf("parse config: %w", err) } if cfg.Server.Proto == "" { cfg.Server.Proto = "tcp" } return cfg, nil } func RunServer(ctx context.Context, mux http.Handler, config ServerConfig) error { logger := logging.FromContext(ctx) srv := &http.Server{ Handler: mux, BaseContext: func(l net.Listener) context.Context { logger := logger.With("address", l.Addr()) ctx = logging.WithLogger(ctx, logger) return ctx }, ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), } ln, err := net.Listen(config.Proto, config.Address) if err != nil { return err } logger.Log(ctx, slog.LevelError, "Server Started on", "address", ln.Addr()) go func() { <-ctx.Done() shutdownCtx := context.Background() // We received an interrupt signal, shut down. if err := srv.Shutdown(shutdownCtx); err != nil { slog.Log(shutdownCtx, slog.LevelError, "HTTP server Shutdown", "error", err) } }() if err := srv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } return nil } func Robot() http.HandlerFunc { robots := `User-agent: * Disallow: / ` return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "public, max-age=86400, immutable") w.Header().Set("Content-Type", "text/plain") io.WriteString(w, robots) } }