Examples
example.ini
; By default, configuration is read from ~/.gmcapsulerc. Use the -c option
; to specify some other configuration file.
[server]
;host = localhost
;address = 0.0.0.0
;port = 1965
;certs = .certs
;modules =
;threads = 5
;processes = 2
[static]
root = .
[titan]
;upload_limit = 10485760
[cgi]
bin_root = ./cgi-bin
[cgi.booster]
protocol = titan
host = localhost
path = /gemlog/* /plan.gmi
command = /usr/bin/python3 ../booster/booster.py
[cgi.printenv]
path = /cgienv
command = printenv
[rewrite.test]
path = ^/altenv$
status = 30 gemini://localhost/cgienv${QUERY_STRING}
[misfin]
email.cmd = stdout
;email.cmd = /usr/sbin/sendmail
email.from = admin@example.com
;reject = list of SHA-256 hashes
;
[misfin.jk]
cert = /Users/jaakko/misfin-localhost.pem
key = /Users/jaakko/misfin.key
email = address@example.com
;--------------------------------------------------------------------------
[gitview]
git = /usr/bin/git
cache_path = /Users/jaakko/Library/Caches/gmgitview
[gitview.lagrange]
title = Lagrange
brief = A Beautiful Gemini Client
clone_url = https://git.skyjake.fi/gemini/lagrange.git
tag_url = https://git.skyjake.fi/gemini/lagrange/releases/tag/{tag}
path = /Users/jaakko/src/lagrange
url_root = lagrange
default_branch = release
[gitview.gitview]
title = GmGitView
brief = Git Repository Viewer for Gemini
clone_url = https://git.skyjake.fi/gemini/gitview.git
path = /Users/jaakko/src/gmgitview
url_root = gmgitview
default_branch = main
[gitview.bubble]
title = Bubble
brief = Bulletin Boards for Gemini
clone_url = https://git.skyjake.fi/gemini/bubble.git
path = /Users/jaakko/src/bubble
url_root = bubble
default_branch = main
Caching
class GitViewCache(Cache):
"""
File-based cache that stores content using ``pickle``.
File paths inside the cache directory are based on a SHA-256 hash
of the original URL path.
Args:
hostname (str): Hostname that this cache belongs to.
file_root (str): Directory where the cached content is stored.
A large number of hash-prefix subdirectories are created.
"""
def __init__(self, hostname, file_root):
super().__init__()
self.hostname = hostname
self.file_root = file_root
os.makedirs(file_root, exist_ok=True)
self.ptn_static_path = \
re.compile('.*/(tags|commits|patch|cdiff|pcdiff)/[0-9a-f]+')
def storage_path(self, path):
digest = hashlib.sha256(path.encode('utf-8')).hexdigest()
return digest[0:2] + '/' + digest[2:]
def max_age(self, path):
if self.ptn_static_path.match(path):
return 60 * 24 * 3600 # two months
return 3600 # one hour for dynamic content
def try_load(self, path):
if not path.startswith(self.hostname + '/'):
return None, None
storage_path = pjoin(self.file_root, self.storage_path(path))
if not os.path.exists(storage_path):
return None, None
now_time = time.time()
storage_time = os.path.getmtime(storage_path)
if now_time - storage_time > self.max_age(path):
return None, None
return pickle.load(open(storage_path, 'rb'))
def save(self, path, media_type, content):
if not path.startswith(self.hostname + '/'):
return False
storage_path = pjoin(self.file_root, self.storage_path(path))
os.makedirs(os.path.dirname(storage_path), exist_ok=True)
pickle.dump((media_type, content), open(storage_path, 'wb'))
return True