f8625170971fe17f3e406298ab6271947490dc67 — Sebastien Binet 2 years ago 1e7c0c1
2021/2021-03-30-gmni: first import

Signed-off-by: Sebastien Binet <binet@cern.ch>
A 2021/2021-03-30-gmni/README.md => 2021/2021-03-30-gmni/README.md +3 -0
@@ 0,0 1,3 @@
# 2021-03-30-gmni

`go-present` link: [slides](https://talks.godoc.org/github.com/sbinet/talks/2021/2021-03-30-gmni/talk.slide)

A 2021/2021-03-30-gmni/_code/gen-cert.go => 2021/2021-03-30-gmni/_code/gen-cert.go +23 -0
@@ 0,0 1,23 @@
package main

import (


func main() {
	opts := certificate.CreateOptions{
		DNSNames: []string{"localhost"}, Duration: 10 * 24 * time.Hour,
	cert, err := certificate.Create(opts)
	if err != nil {
		log.Fatalf("could not create certificate: %+v", err)

	err = certificate.Write(cert, "./certs/cert.pem", "key.pem")
	if err != nil {
		log.Fatalf("could not save certificate: %+v", err)

A 2021/2021-03-30-gmni/_code/gmni.go => 2021/2021-03-30-gmni/_code/gmni.go +37 -0
@@ 0,0 1,37 @@
package main

import (


func main() {
	certs := &certificate.Store{}
	if err := certs.Load("./certs"); err != nil {

	mux := &gemini.Mux{}
	mux.HandleFunc("/", func(ctx context.Context, w gemini.ResponseWriter, r *gemini.Request) {
		fmt.Fprint(w, fmt.Sprintf(`# Heading 1
You have requested (at %s):
=> %s
`, time.Now().UTC().Format(time.RFC3339Nano), r.URL.Path))

	server := &gemini.Server{
		Handler:        gemini.LoggingMiddleware(mux),
		GetCertificate: certs.Get,

	err := server.ListenAndServe(context.Background())
	if err != nil {
		log.Fatalf("could not run gmni server: %+v", err)

A 2021/2021-03-30-gmni/_code/hello-1.gmni => 2021/2021-03-30-gmni/_code/hello-1.gmni +18 -0
@@ 0,0 1,18 @@
Hello (Gemini) World

=> gemini.circumlunar.space/capcom/ CAPCOM: Gemini space aggregator
=> gemini://example.org/
=> gemini://example.org/ An example link
=> gemini://example.org/foo	Another example link at the same host
=> foo/bar/baz.txt	A relative link
=> 	gopher://example.org:70/1 A gopher link

Here is some "code":

package main

func main() {
	println("hello gemini!")

A 2021/2021-03-30-gmni/_code/hello-2.gmni => 2021/2021-03-30-gmni/_code/hello-2.gmni +24 -0
@@ 0,0 1,24 @@
# H1 heading

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

## H2 heading

Suscipit tellus mauris a diam. Mauris pharetra et ultrices neque.

### H3 heading

Tempus iaculis urna id volutpat lacus laoreet. Vitae purus faucibus ornare suspendisse sed nisi. Senectus et netus et malesuada fames. Amet nulla facilisi morbi tempus. Mattis nunc sed blandit libero. Eget est lorem ipsum dolor sit amet consectetur. Augue ut lectus arcu bibendum at varius vel pharetra vel. Varius sit amet mattis vulputate enim. Vel orci porta non pulvinar neque. Leo in vitae turpis massa. Pulvinar pellentesque habitant morbi tristique senectus et. Proin sagittis nisl rhoncus mattis rhoncus urna neque viverra. Nisl suscipit adipiscing bibendum est ultricies. Et tortor consequat id porta nibh venenatis cras sed felis. Accumsan lacus vel facilisis volutpat est velit egestas dui. Aliquam faucibus purus in massa tempor nec. Tincidunt dui ut ornare lectus sit amet est placerat.

Unordered list items:

* First item
* Second item
* Third item

Quoted text:

> This is some quoted text.
> With another line of quoted text.

And... back to just text and prose.

A 2021/2021-03-30-gmni/_code/web.go => 2021/2021-03-30-gmni/_code/web.go +18 -0
@@ 0,0 1,18 @@
package main

import (

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)

	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatalf("error while running web server: %+v", err)

A 2021/2021-03-30-gmni/_figs/lagrange.png => 2021/2021-03-30-gmni/_figs/lagrange.png +0 -0
A 2021/2021-03-30-gmni/talk.slide => 2021/2021-03-30-gmni/talk.slide +197 -0
@@ 0,0 1,197 @@
Gemini protocol
LPC-dev info, 2021-03-30

Sebastien Binet

* Gemini: why?


- the Web (`www`, `http{,s}`, `HTML`) is *too*heavy*
- browsing w/ JS disabled is an "interesting" experience
- browsing w/ JS enabled is an attack vector
- implementing a web browser is a multi-hundred developers endeavour (only big companies can afford it

"Sophistication" brings endless new ways for bugs or attack vectors.
Its footprint isn't completely eco-friendly either.


.link https://gemini.circumlunar.space/docs/faq.html

* Gemini

From the official web site (but also available on the Gemini space):

  Gemini is a new internet protocol which:
  - Is heavier than gopher
  - Is lighter than the web
  - Will not replace either
  - Strives for maximum power to weight ratio
  - Takes user privacy very seriously

.link https://gemini.circumlunar.space

In a nutshell, it's a protocol for serving and receiving hypertext documents (called `text/gemini` like `text/html` for the web), via [[https://en.wikipedia.org/wiki/Transport_Layer_Security][TLS]].

Gemini resources are served via _URIs_ behind the `gemini://` scheme.
There are not equivalent of `http{,s}`: everything is served via `TLS` using self-signed certificates (`TOFU` -- _Trust_on_first_use_).

_Default_port:_ `1965` "for some reason".

* Gemini - specs

.link https://gemini.circumlunar.space/docs/specification.html

Typical session:

  C: Opens connection
  S: Accepts connection
  C/S: Complete TLS handshake
  C: Validates server certificate
  C: Sends request (one CRLF terminated line)
  S: Sends response header (one CRLF terminated line), closes connection
     under non-success conditions
  S: Sends response body (text or binary data)
  S: Closes connection
  C: Handles response


  C: gemini://example.com/
  S: 20 text/gemini
  S: # Example Title
  S: Welcome to my Gemini capsule. [etc]

* Simplicity

A Gemini client can just be implemented via `openssl+awk`:

  $> openssl s_client -quiet -crlf           \
      -servername gemini.circumlunar.space   \
      -connect gemini.circumlunar.space:1965 \
    | awk '{ print "response: " $0 }'
  depth=0 CN = gemini.circumlunar.space
  verify error:num=18:self signed certificate
  verify return:1
  depth=0 CN = gemini.circumlunar.space
  verify return:1
  > gemini://gemini.circumlunar.space/capcom/
  response: 20 text/gemini
  response: # CAPCOM Geminispace aggregator
  response: CAPCOM is an aggregator for Atom feeds of Gemini content.  It was inspired by:
  response: => gopher://i-logout.cz:70/1/bongusta/	The Bongusta aggregator for Gopherspace
  response: Each month, CAPCOM randomly selects 100 distinct URLs from its list of known feeds, and includes their content in its output.  This makes it a nice way to discover new content in Geminispace.  If you're enjoying content from one feed, you should subscribe to it yourself in some way, because that feed is not guaranteed to be one of the 100 feeds chosen next month!
  response: => submit	Submit your Atom feed's URL for inclusion in CAPCOM's list
  response: => feeds.txt	See this month's active feeds

* Gemini clients

.link https://github.com/makeworld-the-better-one/amfora Amfora (TUI, Go)
.link https://git.sr.ht/~acdw/bollux/ Bollux (term, bash)
.link https://bombadillo.colorfield.space/ Bombadillo (term, Go)
.link https://git.sr.ht/~julienxx/castor Castor (GUI, GTK+Rust)
.link https://play.google.com/store/apps/details?id=ca.snoe.deedum Deedum (Android,iOS, GUI)
.link https://git.sr.ht/~sircmpwn/gmni Gmni (term, C)
.link https://gmi.skyjake.fi/lagrange/ Lagrange (GUI, C)

(I am using `Lagrange` and `deedum`.)

* Gemini servers

.link https://github.com/spc476/GLV-1.12556 The first Gemini server (Lua)
.link https://sr.ht/~sircmpwn/gmnisrv/ gmnisrv (C)
.link https://sr.ht/~adnano/go-gemini/ go-gemini (Go)
.link https://git.sr.ht/~julienxx/pollux Pollux (Rust)
.link https://gitlab.com/lambdatronic/space-age space-age (Clojure)
.link https://coding.openguide.co.uk/git/gemini-php/ Gemini-PHP (PHP)
.link https://github.com/jfmcbrayer/germinal Germinal (Common Lisp)

(not surprisingly, I use `go-gemini`.)

* text/gemini

The markup language of Gemini documents is `text/gemini` (like `text/html`, `text/plain`, etc...).

It can be viewed as a "line oriented" subset of [[https://en.wikipedia.org/wiki/Markdown][Markdown]]:

- text lines
- link lines
- pre-formatted text lines
- headings lines
- unordered list items
- quote lines

* text/gemini example

.code _code/hello-1.gmni

* text/gemini example - II

.code _code/hello-2.gmni

* text/gemini example - III


.image _figs/lagrange.png 500 _

* Web server

.code _code/web.go

  $> go run ./main.go
  $> curl http://localhost:8080/web/is/great/but/heavy
  Hello, you've requested: /web/is/great/but/heavy

* Gemini server - generate certificates

.code _code/gen-cert.go

* Gemini server

.code _code/gmni.go /^func main/,/^}/

* (My opinionated) Conclusions


- fun to develop with and for
- very simple to bootstrap (generating self-signed certificates is rather easy)
- `text/gemini` is easy to learn
- no more tracking from web sites
- attack surface drastically shrinked


- no way to serve simple images (_e.g._ how to display a blueprint, a plot? "solution" is to put a link-line to that image)
- no way to display `LaTeX` equations (same "solution" or just "display" it "raw")



- serve the [[https://golang.org][Go]] documentation on `Gemini`
- write a little [[https://gioui.org][Gio-based]] GUI client
- add support for displaying `LaTeX` equations to that mock-Gemini client

* Bibliography

.link https://en.wikipedia.org/wiki/Gemini_(protocol)
.link https://gemini.circumlunar.space/
.link https://www.bortzmeyer.org/gemini.html
.link https://linuxfr.org/users/hellpe/journaux/gemini-et-solid-deux-alternatives-au-web-qu-il-faut-qu-on-m-explique
.link https://drewdevault.com/2020/11/01/What-is-Gemini-anyway.html
.link https://en.wikipedia.org/wiki/Trust_on_first_use