improve flag config and docs

This commit is contained in:
dre 2021-07-10 11:43:59 +08:00
parent bd61363009
commit e58ec93de3
4 changed files with 80 additions and 33 deletions

View file

@ -8,7 +8,7 @@ server? Because it's educational and that's the spirit of the protocol.
**Features**
- **zero conf**, if no certificate is available, gmifs generates a self-signed cert
- **zero dependencies**, Go standard library only
- directory listing support `-autoindex`
- directory listing support through the auto index flag
- reloads ssl certs and reopens log files on SIGHUP, e.g. after Let's Encrypt renewal
- response writer interceptor and middleware support
- simple middleware for lru document cache
@ -16,44 +16,81 @@ server? Because it's educational and that's the spirit of the protocol.
- KISS, single file gemini implementation, handler func in main
- modern tls ciphers (from [Mozilla's TLS ciphers recommendations](https://statics.tls.security.mozilla.org/server-side-tls-conf.json))
This tool is used alongside the markdown to gemtext converter
[md2gmi](https://github.com/n0x1m/md2gmi).
## Usage
### Installation
Currently only supported through the go toolchain, either check out the repot and build it or use:
```
go install github.com/n0x1m/gmifs
```
### Dev & Tests
Test it locally by serving e.g. a `./public` directory on localhost with directory listing turned on
```
./gmifs -root ./public -host localhost -autoindex
./gmifs -root ./public -autoindex
```
If no key pair with the flags `-cert` and `-key` is provided, like in this example, gmifs will auto
provision a self-signed certificate for the hostname `localhost` with 1 day validity.
### Production
In the real world generate a self-signed server certificate with OpenSSL or use a Let's Encrypt
key pair
key pair. Generate example:
```bash
openssl req -x509 -newkey rsa:4096 -keyout key.rsa -out cert.pem \
-days 3650 -nodes -subj "/CN=nox.im"
```
start gmifs with a key pair
start gmifs with a Let's Encrypt key pair on OpenBSD:
```
gmifs -addr 0.0.0.0:1965 -root /var/www/htdocs/nox.im/gemini \
-host nox.im -max-conns 256 -timeout 5 -cache 256 \
-logs /var/gemini/logs/ \
-logs /var/www/logs/gemini \
-cert /etc/ssl/nox.im.fullchain.pem \
-key /etc/ssl/private/nox.im.key
```
if need be, send SIGHUP to reload the certificate without cold start, e.g. after a Let's Encrypt
renewal
if need be, send SIGHUP to reload the certificate without cold start, e.g. after certificate renewal
```
pgrep gmifs | awk '{print "kill -1 " $1}' | sh
```
If debug logs are enabled, the certificate rotation will be confirmed.
### Supported flags
```
Usage of ./gmifs:
-addr string
address to listen on, e.g. 127.0.0.1:1965 (default ":1965")
-autocertvalidity int
valid days when using a gmifs auto provisioned self-signed certificate (default 1)
-autoindex
enables auto indexing, directory listings
-cache int
simple lru document cache for n items. Disabled when zero.
-cert string
TLS chain of one or more certificates
-debug
enable verbose logging of the gemini server
-host string
hostname for sni and x509 CN when using temporary self-signed certs (default "localhost")
-key string
TLS private key
-logs string
enables file based logging and specifies the directory
-max-conns int
maximum number of concurrently open connections (default 128)
-root string
server root directory to serve from (default "public")
-timeout int
connection timeout in seconds (default 5)
```

View file

@ -8,6 +8,7 @@ import (
"os"
"path"
"path/filepath"
"strings"
"github.com/n0x1m/gmifs/gemini"
)
@ -102,7 +103,8 @@ func listDirectory(fullpath, relpath string) ([]byte, string, error) {
var out []byte
parent := filepath.Dir(relpath)
if relpath != "/" {
out = append(out, []byte(fmt.Sprintf("Index of %s/\n\n", relpath))...)
idx := strings.TrimRight(relpath, "/")
out = append(out, []byte(fmt.Sprintf("Index of %s/\n\n", idx))...)
out = append(out, []byte(fmt.Sprintf("=> %s ..\n", parent))...)
} else {
out = append(out, []byte(fmt.Sprintf("Index of %s\n\n", relpath))...)

42
main.go
View file

@ -20,33 +20,36 @@ import (
const (
defaultAddress = ":1965"
defaultMaxConns = 256
defaultTimeout = 10
defaultRootPath = "/var/www/htdocs/gemini"
defaultHost = ""
defaultMaxConns = 128
defaultTimeout = 5
defaultCacheObjects = 0
defaultRootPath = "public"
defaultHost = "localhost"
defaultCertPath = ""
defaultKeyPath = ""
autoCertDaysValid = 7
shutdownTimeout = 10 * time.Second
defaultLogsDir = ""
defaultDebugMode = false
defaultAutoIndex = false
defaultAutoCertValidity = 1
)
func main() {
var addr, root, crt, key, host, logs string
var maxconns, timeout, cache int
var maxconns, timeout, cache, autocertvalidity int
var debug, autoindex bool
flag.StringVar(&addr, "addr", defaultAddress, "address to listen on. E.g. 127.0.0.1:1965")
flag.StringVar(&addr, "addr", defaultAddress, "address to listen on, e.g. 127.0.0.1:1965")
flag.IntVar(&maxconns, "max-conns", defaultMaxConns, "maximum number of concurrently open connections")
flag.IntVar(&timeout, "timeout", defaultTimeout, "connection timeout in seconds")
flag.IntVar(&cache, "cache", 0, "simple lru document cache for n items. Disabled when zero.")
flag.IntVar(&cache, "cache", defaultCacheObjects, "simple lru document cache for n items. Disabled when zero.")
flag.StringVar(&root, "root", defaultRootPath, "server root directory to serve from")
flag.StringVar(&host, "host", defaultHost, "hostname / x509 Common Name when using temporary self-signed certs")
flag.StringVar(&host, "host", defaultHost, "hostname for sni and x509 CN when using temporary self-signed certs")
flag.StringVar(&crt, "cert", defaultCertPath, "TLS chain of one or more certificates")
flag.StringVar(&key, "key", defaultKeyPath, "TLS private key")
flag.StringVar(&logs, "logs", "", "directory for file based logging")
flag.BoolVar(&debug, "debug", false, "enable verbose logging of the gemini server")
flag.BoolVar(&autoindex, "autoindex", false, "enables or disables the directory listing output")
flag.IntVar(&autocertvalidity, "autocertvalidity", defaultAutoCertValidity, "valid days when using a gmifs auto provisioned self-signed certificate")
flag.StringVar(&logs, "logs", defaultLogsDir, "enables file based logging and specifies the directory")
flag.BoolVar(&debug, "debug", defaultDebugMode, "enable verbose logging of the gemini server")
flag.BoolVar(&autoindex, "autoindex", defaultAutoIndex, "enables auto indexing, directory listings")
flag.Parse()
var err error
@ -80,7 +83,7 @@ func main() {
server := &gemini.Server{
Addr: addr,
Hostname: host,
TLSConfigLoader: setupCertificate(crt, key, host),
TLSConfigLoader: setupCertificate(crt, key, host, autocertvalidity),
Handler: mux,
MaxOpenConns: maxconns,
ReadTimeout: time.Duration(timeout) * time.Second,
@ -101,7 +104,7 @@ func main() {
signal.Notify(stop, os.Interrupt)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
if err := server.Shutdown(ctx); err != nil {
cancel()
log.Fatalf("ListenAndServe shutdown with error: %v", err)
@ -111,7 +114,7 @@ func main() {
cancel()
}
func setupCertificate(crt, key, host string) func() (*tls.Config, error) {
func setupCertificate(crt, key, host string, validdays int) func() (*tls.Config, error) {
return func() (*tls.Config, error) {
if crt != "" && key != "" {
cert, err := tls.LoadX509KeyPair(crt, key)
@ -121,8 +124,9 @@ func setupCertificate(crt, key, host string) func() (*tls.Config, error) {
return gemini.TLSConfig(host, cert), nil
}
log.Println("generating self-signed temporary certificate")
cert, err := gemini.GenX509KeyPair(host, autoCertDaysValid)
// only used for testing
log.Printf("generating a self-signed temporary certificate, valid for %d days\n", validdays)
cert, err := gemini.GenX509KeyPair(host, validdays)
if err != nil {
return nil, fmt.Errorf("generate x509 keypair: %w", err)
}

View file

@ -39,6 +39,10 @@ func (c *cache) housekeeping(key string) {
}
func (c *cache) Write(key string, mimeType string, doc []byte) {
// protect against crashes when initialized and chained with zero size.
if c.size <= 0 {
return
}
c.Lock()
c.housekeeping(key)
c.documents[key] = doc