diff --git a/Makefile b/Makefile index 825cd34..37e2296 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ install: bin/geml-server --owner geml \ --group geml \ --mode 644 \ - geml.ini + geml.example.ini uninstall: id -u geml &>/dev/null && userdel geml diff --git a/README.md b/README.md index b1a8314..2679be7 100644 --- a/README.md +++ b/README.md @@ -4,53 +4,40 @@ Gemini server written in Common Lisp ## /etc/geml/geml.ini -geml requires `cert` and `key` to be configured before it will run. And will -have nothing to serve until you configure at least one domain and root. See -[`geml.ini`](./geml.ini) as an example. +geml will have nothing to serve until you configure at least one domain and +root. ``` -cert = /var/lib/geml/localhost.crt -key = /var/lib/geml/localhost.key - [my.gmi.capsule] root = /srv/gmi ``` -### Generate Self-Signed SSL Certificate - -- [ ] include/write helper script for this - -```sh -openssl req -x509 \ - -out localhost.crt \ - -keyout localhost.key \ - -newkey rsa:2048 \ - -nodes \ - -sha256 \ - -subj '/CN=localhost' \ - -extensions EXT \ - -config <(printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") -``` - ## Usage -### Standalone Executable - To get an executable `bin/geml-server` run `make`. ```sh make && bin/geml-server -h ``` - ### Systemd -Install the build to `/usr/local/bin/geml-server` with -`sudo make install` which will also wire up systemd so you can `sudo systemctl -enable --now geml` +To install the build to `/usr/local/bin/geml-server` use `sudo make install` +which will also wire up systemd so you can `sudo systemctl enable --now geml` You'll need to `sudo systemctl restart geml` whenever you update `/etc/geml/geml.ini`... you did do that didn't you? +```sh +sudo cp /etc/geml/geml.example.ini /etc/geml/geml.ini +``` + +`make install` also sets up a geml system user and you may want to add yourself +to that group. + +```sh +usermod -a -G geml $USER +``` + ### yeet it To undo all that run these: @@ -59,12 +46,5 @@ To undo all that run these: sudo make uninstall make clean ``` - -### SBCL - -Start sbcl with proper readline support: `rlwrap sbcl` - -```lisp -(ql:quickload "geml") -(gemini.server:start-server "/etc/geml/geml.ini") -``` +**NOTE**: The automatically created TLS certs will still be at `/var/lib/geml` +you can (re)use those elsewhere or also just delete them. diff --git a/geml.ini b/geml.ini index 5d58b8d..bdb9716 100644 --- a/geml.ini +++ b/geml.ini @@ -2,9 +2,7 @@ ;; see usocket: https://github.com/usocket/usocket#api-definition ;; host = 0.0.0.0 ;; port = 1965 - -cert = /var/lib/geml/localhost.crt -key = /var/lib/geml/localhost.key +;; certs-dir = /var/lib/geml/ [localhost] root = /srv/gmi diff --git a/src/bin.lisp b/src/bin.lisp index ead2919..5e61bb1 100644 --- a/src/bin.lisp +++ b/src/bin.lisp @@ -24,19 +24,22 @@ (defun bin () (multiple-value-bind (options free-args) (handler-case - (handler-bind ((opts:unknown-option #'unknown-option)) - (opts:get-opts)) - (opts:missing-arg (condition) - (format t "option ~s needs an argument!~%" - (opts:option condition))) - (opts:arg-parser-failed (condition) - (format t "cannot parse ~s as argument of ~s~%" - (opts:raw-arg condition) - (opts:option condition))) - (opts:missing-required-option (con) - (format t "~a~%" con) - (opts:exit 1))) - (cond - ((getf options :help) (opts:describe :usage-of "geml-server")) - ((getf options :version) (format t "~a~%" (asdf:component-version (asdf:find-system 'geml)))) - (t (start-server (getf options :config)))))) + (handler-bind ((unix-opts:unknown-option #'unknown-option)) + (unix-opts:get-opts)) + (unix-opts:missing-arg (condition) + (format t "option ~s needs an argument!~%" + (unix-opts:option condition))) + (unix-opts:arg-parser-failed (condition) + (format t "cannot parse ~s as argument of ~s~%" + (unix-opts:raw-arg condition) + (unix-opts:option condition))) + (unix-opts:missing-required-option (con) + (format t "~a~%" con) + (unix-opts:exit 1))) + (cond + ((getf options :help) (unix-opts:describe :usage-of "geml-server")) + ((getf options :version) + (format t "~a~%" (asdf:component-version (asdf:find-system 'geml)))) + (t (let ((settings (cl-ini:parse-ini (getf options :config)))) + (start-server settings)))))) + diff --git a/src/server.lisp b/src/server.lisp index 7521565..52d7ced 100644 --- a/src/server.lisp +++ b/src/server.lisp @@ -26,8 +26,6 @@ :root :section (intern (string-upcase domain) :keyword))) - ;; TODO use path library? - ;; http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_the_filen_s_dictionary.html (filename (probe-file (concatenate 'string root (resolve-index path))))) @@ -38,29 +36,62 @@ (format conn "51 ~a does not exist for ~a~%" path domain))) (force-output conn)) -(defun start-server (settings-file) - (let* ((settings (cl-ini:parse-ini settings-file)) - (host (or (cl-ini:ini-value settings :host) +(defun ensure-certs (settings certs-dir) + (mapcar #'(lambda (setting) + (unless (eq (car setting) :global) + (let* ((domain (string-downcase (symbol-name (car setting)))) + (key (concatenate 'string + (namestring certs-dir) + domain ".key")) + (cert (concatenate 'string + (namestring certs-dir) + domain ".crt")) + (missing-certs (or (not (probe-file key)) + (not (probe-file cert)))) + (openssl-cmd (format nil + "openssl req -x509 ~ + -newkey rsa:4096 ~ + -sha256 ~ + -days 3650 -nodes ~ + -subj \"/CN=~a\" ~ + -keyout ~a ~ + -out ~a" + ;; TODO -addext \"subjectAltName=DNS:~a\"" + domain + key + cert))) + (cond + (missing-certs + (progn (format t "geml is creating TLS certs for ~a (~a)~%" + domain openssl-cmd) + (uiop:run-program openssl-cmd))) + (t + (format t "geml found TLS certs for ~a~%" domain))) + setting))) + settings)) + +(defun start-server (settings) + (let* ((host (or (cl-ini:ini-value settings :host) "0.0.0.0")) (port (or (cl-ini:ini-value settings :port) 1965)) - (key (probe-file (cl-ini:ini-value settings :key))) - (cert (probe-file (cl-ini:ini-value settings :cert)))) + (certs-dir (probe-file (or (cl-ini:ini-value settings :certs-dir) + "/var/lib/geml")))) + (ensure-certs settings certs-dir) (cond - ((not cert) (format t "geml cannot read cert using path defined in ~a~%" settings-file)) - ((not key) (format t "geml cannot read key using path defined in ~a~%" settings-file)) (t (let ((server (usocket:socket-listen host port))) - (format t "geml booted on ~a:~a (~a)~%" + (format t "geml booted on ~a:~a~%" host - port - settings-file) + port) (unwind-protect (loop (let* ((socket (usocket:socket-accept server)) + (cert (concatenate 'string certs-dir domain ".crt")) + (key (concatenate 'string certs-dir domain ".key")) (conn (cl+ssl:make-ssl-server-stream (usocket:socket-stream socket) :external-format '(:utf-8 :eol-style :crlf) - :certificate (namestring cert) - :key (namestring key)))) + :certificate cert + :key key))) (unwind-protect (handle-request conn settings) (close conn)))) (format t "geml is shutting down...~%")