# Gemini

Applications and libraries for building applications on Gemini (see https://gemini.circumlunar.space/).

## Gemini CLI

### Run a server

```sh
gemini serve --domain=example.com --certFile=a.crt --keyFile=a.key --path=.
```

### Request content

curl for Gemini.

```sh
gemini request --insecure --verbose gemini://example.com/pass
```

## Gemini Server Docker image

### Run a server with Docker

```sh
docker run \
    -v /path_to_your_cert_files:/certs \
    -e PORT=1965 \
    -e DOMAIN=localhost \
    -v /path_to_your_content:/content \
    -p 1965:1965 \
    adrianhesketh/gemini:latest
```

## Quick start

Check out https://github.com/a-h/gemini/releases for the latest version of the `gemini` command line tool to run locally, or use Docker:

```sh
# Create a server certificate.
openssl ecparam -genkey -name secp384r1 -out server.key
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
# Make a Gemini file.
mkdir content
echo "# Hello, World!" > content/index.gmi
# Run the container.
docker pull adrianhesketh/gemini:latest
docker run -v `pwd`:/certs -e PORT=1965 -e DOMAIN=localhost -v `pwd`/content:/content -p 1965:1965 adrianhesketh/gemini:latest
```

## Libraries

### Serve

Use `gemini.Server` / `gemini.ListenAndServe` to build your own custom servers. 

Supports hosting multiple Gemini servers on a single IP address.

These are used to build a Gemini application that supports dynamic content.

```go
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/a-h/gemini"
	"github.com/a-h/gemini/mux"
)

func main() {
	// Create the handlers for a domain (a.gemini).
	okHandler := gemini.HandlerFunc(func(w gemini.ResponseWriter, r *gemini.Request) {
		w.Write([]byte("OK"))
	})

	helloHandler := gemini.HandlerFunc(func(w gemini.ResponseWriter, r *gemini.Request) {
		w.Write([]byte("# Hello, user!\n"))
		if r.Certificate.ID == "" {
			w.Write([]byte("You're not authenticated"))
			return
		}
		w.Write([]byte(fmt.Sprintf("Certificate: %v\n", r.Certificate.ID)))
	})

	// Create a router for gemini://a.gemini/require_cert and gemini://a.gemini/public
	routerA := mux.NewMux()
	// Let's make /require_cert require the client to be authenticated.
	routerA.AddRoute("/require_cert", gemini.RequireCertificateHandler(helloHandler, nil))
	routerA.AddRoute("/public", okHandler)

	// Create a file system handler gemini://b.gemini/{path}
	handlerB := gemini.FileSystemHandler(gemini.Dir("./content"))

	// Set up the domain handlers.
	ctx := context.Background()
	a, err := gemini.NewDomainHandler("a.gemini", "a.crt", "a.key", routerA)
	if err != nil {
		log.Fatal("error creating domain handler A:", err)
	}
	b, err := gemini.NewDomainHandler("b.gemini", "b.crt", "b.key", handlerB)
	if err != nil {
		log.Fatal("error creating domain handler B:", err)
	}

	// Start the server for two domains (a.gemini / b.gemini).
	err = gemini.ListenAndServe(ctx, ":1965", a, b)
	if err != nil {
		log.Fatal("error:", err)
	}
}
```

### Route

Use `github.com/a-h/gemini/mux` to provide routing between Gemini handlers and extract variables from URL paths.

### Built-in utility handlers

* `RequireCertificateHandler` a handler that ensures that users present certificates.
* `FileSystemHandler` to support hosting static content.


### Gemini client

```go
client := gemini.NewClient()

// Make a request to the server without accepting its certificate.
r, certificates, authenticated, ok, err := client.Request("gemini://a.gemini/require_cert")
if err != nil {
	log.Printf("Request failed: %v", err)
	return
}
```

Configure allowed server certificates for trust-on-first-use certificate support:

```
client.AddAlllowedCertificateForHost("a.gemini", " ")
```

## Tasks

### test

Test the project.

```sh
go test ./... -short
```

### test-integration

Integration test the project.

```sh
go test ./...
```

### build

Build the CLI.

```sh
go build -o gemini ./cmd/main.go
```

### build-docker

Build the Docker image.

```sh
docker build . -t adrianhesketh/gemini
```

### build-snapshot

Build a snapshot release using goreleaser.

```sh
goreleaser build --snapshot --rm-dist
```

### serve-local-tests

Run a local Gemini server.

```sh
echo add '127.0.0.1       a-h.gemini' to your /etc/hosts file
openssl ecparam -genkey -name secp384r1 -out server.key
openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650 -subj "/C=/ST=/L=/O=/OU=/CN=a-h.gemini"
go run ./cmd/main.go serve --domain=a-h.gemini --certFile=server.crt --keyFile=server.key --path=./tests
```

### release

Push a release to Github.

```
if [ "${GITHUB_TOKEN}" == "" ]; then echo "Set the GITHUB_TOKEN environment variable"; fi
./push-tag.sh
goreleaser --rm-dist
```
