flounder

A simple gemini site builder
Log | Files | Refs | README | LICENSE

commit f282a3b485f2bbe813913fc8e61edc5cc6370161
parent 12355a343f5989021dbad85cff9c72d42e33c017
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Mon, 19 Oct 2020 21:16:21 -0700

add basic templating

Diffstat:
Mgemini.go | 121++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mmain.go | 7++++++-
Atemplates/index.gmi | 15+++++++++++++++
3 files changed, 138 insertions(+), 5 deletions(-)

diff --git a/gemini.go b/gemini.go @@ -1,19 +1,132 @@ package main import ( - "fmt" + "bytes" + "crypto/tls" + "crypto/x509" // todo move into cert file + "encoding/pem" + // "fmt" "git.sr.ht/~adnano/gmi" + "log" + "os" + // "path" + "text/template" + "time" ) func gmiIndex(w *gmi.ResponseWriter, r *gmi.Request) { - fmt.Fprintf(w, "index") + t, err := template.ParseFiles("templates/index.gmi") + if err != nil { + log.Fatal(err) + } + files, _ := getIndexFiles() + users, _ := getUsers() + data := struct { + Domain string + Files []*File + Users []string + }{ + Domain: "flounder.online", + Files: files, + Users: users, + } + t.Execute(w, data) } func gmiPage(w *gmi.ResponseWriter, r *gmi.Request) { } -func runGeminiServer() { +func runGeminiServer(config *Config) { var server gmi.Server - server.HandleFunc("flounder.online", gmiIndex) + + if err := server.CertificateStore.Load("./tmpcerts"); err != nil { + log.Fatal(err) + } + server.GetCertificate = func(hostname string, store *gmi.CertificateStore) *tls.Certificate { + cert, err := store.Lookup(hostname) + if err != nil { + switch err { + case gmi.ErrCertificateExpired: + // Generate a new certificate if the current one is expired. + log.Print("Old certificate expired, creating new one") + fallthrough + case gmi.ErrCertificateUnknown: + // Generate a certificate if one does not exist. + cert, err := gmi.NewCertificate(hostname, time.Minute) + if err != nil { + // Failed to generate new certificate, abort + return nil + } + // Store and return the new certificate + err = writeCertificate("./tmpcerts/"+hostname, cert) + if err != nil { + return nil + } + store.Add(hostname, cert) + return &cert + } + } + return cert + } + + // replace with wildcard cert + server.HandleFunc("localhost", gmiIndex) + server.ListenAndServe() } + +// writeCertificate writes the provided certificate and private key +// to path.crt and path.key respectively. +func writeCertificate(path string, cert tls.Certificate) error { + crt, err := marshalX509Certificate(cert.Leaf.Raw) + if err != nil { + return err + } + key, err := marshalPrivateKey(cert.PrivateKey) + if err != nil { + return err + } + + // Write the certificate + crtPath := path + ".crt" + crtOut, err := os.OpenFile(crtPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + if _, err := crtOut.Write(crt); err != nil { + return err + } + + // Write the private key + keyPath := path + ".key" + keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + if _, err := keyOut.Write(key); err != nil { + return err + } + return nil +} + +// marshalX509Certificate returns a PEM-encoded version of the given raw certificate. +func marshalX509Certificate(cert []byte) ([]byte, error) { + var b bytes.Buffer + if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil { + return nil, err + } + return b.Bytes(), nil +} + +// marshalPrivateKey returns PEM encoded versions of the given certificate and private key. +func marshalPrivateKey(priv interface{}) ([]byte, error) { + var b bytes.Buffer + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, err + } + if err := pem.Encode(&b, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return nil, err + } + return b.Bytes(), nil +} diff --git a/main.go b/main.go @@ -18,6 +18,10 @@ type File struct { UpdatedTime string } +func getUsers() ([]string, error) { + return []string{"me", "other guy"}, nil +} + func getIndexFiles() ([]*File, error) { // cache this function result := []*File{} err := filepath.Walk(userFilesPath, func(path string, info os.FileInfo, err error) error { @@ -58,10 +62,11 @@ func getUserFiles(user string) ([]*File, error) { } func main() { + config := Config{} // http functions // go serve gemini // go serve http -- not // runHTTPServer() - runGeminiServer() + runGeminiServer(&config) // go log.Fatal(gmi.ListenAndServe(":8080", nil)) } diff --git a/templates/index.gmi b/templates/index.gmi @@ -0,0 +1,15 @@ +{{$domain := .Domain}} +# ­čÉčFlounder! + +Welcome to flounder, a home for Gemini sites. Flounder hosts small Gemini web pages over https and Gemini. Right now, the only way to make an account is via the https portal, but I'm working on adding alternatives. Feel free to make an account and join if you'd like! + +=> gemini://admin.{{$domain}} Admin page +=> https://{{$domain}} View on HTTPS + +## All Users: +{{range .Users}}=> gemini://{{.}}.{{$domain}} +{{end}} + +## Recently updated files: +{{range .Files}}=> gemini://{{.Creator}}.{{$domain}}/{{.Name}} {{.Name}} ({{.UpdatedTime}}) +{{end}}