A Bearblog-to-Gemini proxy showcasing Xitzin
Project description
UrsaProxy
A Bearblog-to-Gemini proxy built with Xitzin. It fetches content from a Bearblog RSS feed and HTML pages, converts them to Gemtext format, and serves them over the Gemini protocol.
Features
- Proxies Bearblog content to Gemini protocol
- Converts HTML to Gemtext via Markdown intermediate format
- Generates Atom feeds with Gemini URLs
- Configurable TTL caching for feed and post data
- Support for static pages not in RSS feed
Installation
Requires Python 3.13+.
# Using uv
uv add ursaproxy
# Using pip
pip install ursaproxy
Configuration
UrsaProxy is configured via environment variables:
Required
| Variable | Description |
|---|---|
BEARBLOG_URL |
The Bearblog URL to proxy (e.g., https://example.bearblog.dev) |
BLOG_NAME |
Display name for the blog |
CERT_FILE |
Path to TLS certificate file |
KEY_FILE |
Path to TLS private key file |
Optional
| Variable | Default | Description |
|---|---|---|
PAGES |
{} |
JSON dict of static pages {"slug": "Title"} |
GEMINI_HOST |
None |
Hostname for Gemini URLs in feed |
CACHE_TTL_FEED |
300 |
Feed cache TTL in seconds (5 min) |
CACHE_TTL_POST |
1800 |
Post cache TTL in seconds (30 min) |
HOST |
localhost |
Server bind address |
PORT |
1965 |
Server port (Gemini default) |
Example
export BEARBLOG_URL="https://example.bearblog.dev"
export BLOG_NAME="My Gemini Blog"
export CERT_FILE="/path/to/cert.pem"
export KEY_FILE="/path/to/key.pem"
export PAGES='{"about": "About Me", "now": "What I am doing now"}'
export GEMINI_HOST="gemini.example.com"
Usage
ursaproxy
The server will start on gemini://localhost:1965/ by default.
Routes
| Route | Description |
|---|---|
/ |
Landing page with recent posts and page links |
/post/{slug} |
Individual blog post with date |
/page/{slug} |
Static page (without date) |
/about |
About page from feed metadata |
/feed |
Atom feed with Gemini URLs |
Development
For contributing, clone the repository and install with dev dependencies:
git clone https://github.com/alanbato/ursaproxy.git
cd ursaproxy
uv sync --group dev --group test
Commands
# Run linting
uv run ruff check .
# Run linting with auto-fix
uv run ruff check --fix .
# Format code
uv run ruff format .
# Type check
uv run ty check
# Run all pre-commit hooks
uv run pre-commit run --all-files
# Run tests
uv run pytest
# Run tests with verbose output
uv run pytest -v
Project Structure
src/ursaproxy/
├── __init__.py # Xitzin app, routes, and entry point
├── config.py # Pydantic settings for environment config
├── fetcher.py # HTTP client for fetching Bearblog content
├── converter.py # HTML -> Markdown -> Gemtext pipeline
├── cache.py # Simple TTL cache implementation
└── templates/ # Jinja2 templates
├── index.gmi # Landing page template
├── post.gmi # Post/page template
├── about.gmi # About page template
└── feed.xml # Atom feed template
Testing
The test suite uses pytest with fixtures for offline testing:
# Run all 111 tests
uv run pytest
# Run specific test file
uv run pytest tests/test_converter.py
# Run with coverage (if installed)
uv run pytest --cov=ursaproxy
HTTP requests are mocked using respx, so tests run completely offline.
How It Works
- Feed Fetching: Fetches RSS feed from
{BEARBLOG_URL}/feed/?type=rss - HTML Fetching: Fetches individual pages from
{BEARBLOG_URL}/{slug}/ - Conversion Pipeline:
- Parse HTML with BeautifulSoup
- Extract content from
<main>element - Remove nav, footer, scripts, styles
- Convert to Markdown with markdownify
- Convert to Gemtext with md2gemini
- Caching: Feed and posts are cached with configurable TTLs
- Serving: Content served via Gemini protocol using Xitzin
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ursaproxy-0.2.0.tar.gz.
File metadata
- Download URL: ursaproxy-0.2.0.tar.gz
- Upload date:
- Size: 7.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebcbd3961038e31ecc996f822a9bbffaa2520dd62b343382d62f73bc65dee627
|
|
| MD5 |
1789f685a5e201efc988deb11da9746d
|
|
| BLAKE2b-256 |
81a5bc266749c9cda17aba36c30935e0cacd998cdc2b404ee8092679643e2f56
|
Provenance
The following attestation bundles were made for ursaproxy-0.2.0.tar.gz:
Publisher:
release-pypi.yml on alanbato/ursaproxy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursaproxy-0.2.0.tar.gz -
Subject digest:
ebcbd3961038e31ecc996f822a9bbffaa2520dd62b343382d62f73bc65dee627 - Sigstore transparency entry: 919555972
- Sigstore integration time:
-
Permalink:
alanbato/ursaproxy@88665d41ba0c2a27f9a27648bb0d411080df7898 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/alanbato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@88665d41ba0c2a27f9a27648bb0d411080df7898 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ursaproxy-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ursaproxy-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3026406a7fc8c2a59cf7a17cb0a87e71c832a11dae62ea4ac3626752c2a2ac49
|
|
| MD5 |
ad06f8eb9b7be19b690ffc52fa63a6d6
|
|
| BLAKE2b-256 |
ae93f7509029e6c02168420ac291b63e9258ca452d90658056c5d8603b0cc623
|
Provenance
The following attestation bundles were made for ursaproxy-0.2.0-py3-none-any.whl:
Publisher:
release-pypi.yml on alanbato/ursaproxy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ursaproxy-0.2.0-py3-none-any.whl -
Subject digest:
3026406a7fc8c2a59cf7a17cb0a87e71c832a11dae62ea4ac3626752c2a2ac49 - Sigstore transparency entry: 919555974
- Sigstore integration time:
-
Permalink:
alanbato/ursaproxy@88665d41ba0c2a27f9a27648bb0d411080df7898 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/alanbato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@88665d41ba0c2a27f9a27648bb0d411080df7898 -
Trigger Event:
push
-
Statement type: