aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralex <alex@pdp7.net>2026-01-25 19:20:36 +0100
committeralex <alex@pdp7.net>2026-01-25 19:20:36 +0100
commite649a976fa4d7bc3e70beda0576a8946ef38de4a (patch)
treee4874e2dee7bd17fe7c9e45c2556f4c520e1cd63
parent26a87fd4a71f695e47ff8c5ee143ab1f037fdc9d (diff)
Add systemd-credential certificate loading and docs
-rw-r--r--README.md24
-rwxr-xr-xproxy.py31
2 files changed, 42 insertions, 13 deletions
diff --git a/README.md b/README.md
index ed1393c..2a21b42 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,32 @@
`proxy.py` is a Gemini server that proxies all content to an http or https server.
-## Notes
+`proxy.py` loads certificates following the structure of Apache mod_md.
+
+## Providing the certificates via systemd credentials
+
+With `/etc/systemd/system/gemini-from-http.service`:
+
+```
+[Service]
+LoadCredential=certificates:/etc/apache2/md/domains/
+ExecStart=.../proxy.py --certificates-from-credential certificates
+PrivateUsers=self
+```
+
+Systemd injects the certificates to a private path than only `proxy.py` can read.
+The injection is a one off, so you must restart the service to get updated certificates.
+
+## Providing the certificates manually
+
+To run `proxy.py` as a regular user, you can run the `package-mod-md-certs` script as root to copy the certificates to your user:
```
su -c ./package-mod-md-certs | tar x
```
+Then you can run:
+
```
-./proxy.py domains/
+./proxy.py --certificates-from-path domains/
```
diff --git a/proxy.py b/proxy.py
index 854446b..9bdb76f 100755
--- a/proxy.py
+++ b/proxy.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import argparse
import logging
+import os
import pathlib
import ssl
import socketserver
@@ -8,7 +9,6 @@ import urllib.request
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
-domains_path = None
class Handler(socketserver.BaseRequestHandler):
def handle(self):
@@ -30,23 +30,32 @@ class Handler(socketserver.BaseRequestHandler):
sock.sendall(response.encode("UTF8"))
-def sni_callback(socket: ssl.SSLSocket, server_name, _context):
- context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
- domain_path = domains_path / server_name
- context.load_cert_chain(domain_path / "pubcert.pem", domain_path / "privkey.pem")
- socket.context = context
-
-
def main():
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(message)s")
parser = argparse.ArgumentParser()
parser.add_argument("--host", default="0.0.0.0")
parser.add_argument("--port", type=int, default=1965)
- parser.add_argument("domains_path", type=pathlib.Path)
+
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument("--certificates-from-path", type=pathlib.Path)
+ group.add_argument("--certificates-from-credential")
args = parser.parse_args()
- global domains_path
- domains_path = args.domains_path
+ if args.certificates_from_path:
+ def domain_to_path(server_name):
+ domain_path = args.certificates_from_path / server_name
+ return (domain_path / "pubcert.pem" , domain_path / "privkey.pem")
+
+ if args.certificates_from_credential:
+ def domain_to_path(server_name):
+ credentials_directory = pathlib.Path(os.environ["CREDENTIALS_DIRECTORY"])
+ return (credentials_directory / f"{args.certificates_from_credential}_{server_name}_pubcert.pem", credentials_directory / f"{args.certificates_from_credential}_{server_name}_privkey.pem")
+
+ def sni_callback(socket: ssl.SSLSocket, server_name, _context):
+ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ certfile, keyfile = domain_to_path(server_name)
+ context.load_cert_chain(certfile, keyfile)
+ socket.context = context
context.sni_callback = sni_callback