summaryrefslogtreecommitdiff
path: root/src/gmi
diff options
context:
space:
mode:
Diffstat (limited to 'src/gmi')
-rw-r--r--src/gmi/gmi.go122
1 files changed, 121 insertions, 1 deletions
diff --git a/src/gmi/gmi.go b/src/gmi/gmi.go
index 358d935..6e2d79f 100644
--- a/src/gmi/gmi.go
+++ b/src/gmi/gmi.go
@@ -1,2 +1,122 @@
-// Package gmi contains utilities for working with gemini and gemtext
+// Package gmi implements the gemini-based api for the mediocre-blog.
package gmi
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/url"
+ "os"
+ "strings"
+
+ "git.sr.ht/~adnano/go-gemini"
+ "git.sr.ht/~adnano/go-gemini/certificate"
+ "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
+ "github.com/mediocregopher/mediocre-go-lib/v2/mctx"
+ "github.com/mediocregopher/mediocre-go-lib/v2/mlog"
+)
+
+// Params are used to instantiate a new API instance. All fields are required
+// unless otherwise noted.
+type Params struct {
+ Logger *mlog.Logger
+ PublicURL *url.URL
+ ListenAddr string
+ CertificatesPath string
+}
+
+// SetupCfg implement the cfg.Cfger interface.
+func (p *Params) SetupCfg(cfg *cfg.Cfg) {
+
+ publicURLStr := cfg.String("gemini-public-url", "gemini://localhost:2065", "URL this service is accessible at")
+
+ cfg.StringVar(&p.ListenAddr, "gemini-listen-addr", ":2065", "Address to listen for HTTP requests on")
+
+ cfg.StringVar(&p.CertificatesPath, "gemini-certificates-path", "", "Path to directory where gemini certs should be created/stored")
+
+ cfg.OnInit(func(context.Context) error {
+
+ if p.CertificatesPath == "" {
+ return errors.New("-gemini-certificates-path is required")
+ }
+
+ var err error
+
+ *publicURLStr = strings.TrimSuffix(*publicURLStr, "/")
+
+ if p.PublicURL, err = url.Parse(*publicURLStr); err != nil {
+ return fmt.Errorf("parsing -gemini-public-url: %w", err)
+ }
+
+ return nil
+ })
+}
+
+// Annotate implements mctx.Annotator interface.
+func (p *Params) Annotate(a mctx.Annotations) {
+ a["geminiPublicURL"] = p.PublicURL
+ a["geminiListenAddr"] = p.ListenAddr
+ a["geminiCertificatesPath"] = p.CertificatesPath
+}
+
+// API will listen on the port configured for it, and serve gemini requests for
+// the mediocre-blog.
+type API interface {
+ Shutdown(ctx context.Context) error
+}
+
+type api struct {
+ params Params
+ srv *gemini.Server
+}
+
+// New initializes and returns a new API instance, including setting up all
+// listening ports.
+func New(params Params) (API, error) {
+
+ if err := os.MkdirAll(params.CertificatesPath, 0700); err != nil {
+ return nil, fmt.Errorf("creating certificate directory %q: %w", params.CertificatesPath, err)
+ }
+
+ certStore := new(certificate.Store)
+ certStore.Load(params.CertificatesPath)
+ certStore.Register(params.PublicURL.Hostname())
+
+ a := &api{
+ params: params,
+ }
+
+ a.srv = &gemini.Server{
+ Addr: params.ListenAddr,
+ Handler: a.handler(),
+ GetCertificate: certStore.Get,
+ }
+
+ go func() {
+
+ ctx := mctx.WithAnnotator(context.Background(), &a.params)
+
+ err := a.srv.ListenAndServe(ctx)
+ if err != nil && !errors.Is(err, context.Canceled) {
+ a.params.Logger.Fatal(ctx, "serving gemini server", err)
+ }
+ }()
+
+ return a, nil
+}
+
+func (a *api) Shutdown(ctx context.Context) error {
+ return a.srv.Shutdown(ctx)
+}
+
+func (a *api) handler() gemini.Handler {
+ return gemini.HandlerFunc(func(
+ ctx context.Context,
+ rw gemini.ResponseWriter,
+ r *gemini.Request,
+ ) {
+ fmt.Fprintf(rw, "# Test\n\n")
+ fmt.Fprintf(rw, "HELLO WORLD\n\n")
+ fmt.Fprintf(rw, "=> gemini://midnight.pub Hit the pub\n\n")
+ })
+}