improve flag config and docs
This commit is contained in:
parent
bd61363009
commit
e58ec93de3
4 changed files with 80 additions and 33 deletions
57
README.md
57
README.md
|
@ -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)
|
||||
```
|
||||
|
|
|
@ -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
42
main.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue