// servidor gemini basado en el ejemplo de go-gemini y algo del código de flounder // https://git.sr.ht/~adnano/go-gemini // https://git.alexwennerberg.com/flounder/ package main import ( "context" "log" "os" "os/signal" "time" "io" "bufio" "path" "path/filepath" "strings" "git.sr.ht/~adnano/go-gemini" "git.sr.ht/~adnano/go-gemini/certificate" ) // de flounder: https://git.alexwennerberg.com/flounder/file/utils.go.html func cleanPath(thepath string) string { res := filepath.FromSlash(path.Clean("/" + strings.Trim(thepath, "/"))) if strings.Contains(res, "..") { // sanity check return "" } return res } // mapa de hosts : directorios var mapa map[string]string // inspirada por gmiPage de flounder: https://git.alexwennerberg.com/flounder/file/gemini.go.html func gmiHandler(_ context.Context, w gemini.ResponseWriter, r *gemini.Request) { fileName := cleanPath(r.URL.Path) // obtiene el hostname sin el puerto host := strings.SplitN(r.URL.Host, ":", 2)[0] // utiliza el directorio indicado por el mapa dir := mapa[host] fullPath := path.Join(dir, fileName) if strings.Contains(fileName, "atom.xml") { _, err := os.Stat(fullPath) if err == nil { // inspirado de ServeFile en fs.go: f, _ := os.Open(fullPath) defer f.Close() w.SetMediaType("application/atom+xml") io.Copy(w, f) return } } gemini.ServeFile(w, os.DirFS(dir), fileName) } func main() { config, err := os.Open("config.txt") log.Println("cargando configuración...") if err != nil { log.Fatal(err) } defer config.Close() scanner := bufio.NewScanner(config) mapa = make(map[string]string) for scanner.Scan(){ campos := strings.SplitN( scanner.Text(), " ", 2 ) mapa[ campos[0] ] = campos[1] } if err := scanner.Err(); err != nil { log.Fatal(err) } log.Println("configuración cargada!") if err := os.MkdirAll("certs",0700); err != nil { log.Fatal(err) } log.Println("registrando certificados...") certificates := &certificate.Store{} for host,_ := range mapa { certificates.Register(host) } if err := certificates.Load("certs"); err != nil { log.Fatal(err) } log.Println("certificados listos!") log.Println("iniciando servidor...") mux := &gemini.Mux{} mux.HandleFunc("/", gmiHandler) server := &gemini.Server{ Addr: ":1965", Handler: gemini.LoggingMiddleware(mux), ReadTimeout: 30 * time.Second, WriteTimeout: 1 * time.Minute, GetCertificate: certificates.Get, } // Listen for interrupt signal c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) errch := make(chan error) go func() { ctx := context.Background() errch <- server.ListenAndServe(ctx) }() select { case err := <-errch: log.Fatal(err) case <-c: // Shutdown the server log.Println("Shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() err := server.Shutdown(ctx) if err != nil { log.Fatal(err) } } }