No description
- Python 98.8%
- Dockerfile 1.2%
Replaces the self-hosted Gemini tinylog with real-time mirroring to Station (gemini://station.martinrue.com) via the Gemini protocol and TLS client certificates. Posts, self-replies, edits, and deletes are all kept in sync. Long posts are split into continuation self-replies to fit Station's 1024-byte URL limit. Media attachments are appended as gemtext link lines pointing back to the GTS media URLs. Removes tinylog.py, feed.py, all backfill/SYNC_SINCE logic, antenna pings, and simplifies db.py schema (drops created_at and state table). Assisted-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .env.example | ||
| .gitignore | ||
| .python-version | ||
| CLAUDE.md | ||
| compose.yml | ||
| db.py | ||
| Dockerfile | ||
| gts.py | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| station.py | ||
| uv.lock | ||
tinylog
Generates and keeps a tinylog-format .gmi file from public posts on a GoToSocial instance, for serving on a Gemini capsule. Connects to the GoToSocial streaming API for real-time updates and handles adds, edits, and deletes.
Features
- Real-time streaming via GoToSocial WebSocket API (no polling)
- Full backfill of existing public posts on first run
- Propagates edits and deletes
- Self-reply threads collapsed into a single tinylog entry with a
[continued]separator - HTML-to-gemtext conversion: lists, blockquotes, preformatted blocks, footnote-style links
- Skips posts with content warnings, replies to other accounts, non-public posts, local-only posts, and posts that mention other accounts
- Atomically writes the
.gmifile so Agate never serves a partial result - Regenerates the full file on every startup (safe to restart after config changes)
- Generates a
feed.gmicompatible with Gemini antenna aggregators and pings them on every update
Setup
cp .env.example .env
# fill in .env
docker compose up --build -d
Map the output file into your Agate content directory via a volume in compose.yml:
volumes:
- ./data:/app/data
- /var/gemini/capsule/tinylog.gmi:/app/tinylog.gmi
Configuration
All configuration is via environment variables (.env):
| Variable | Required | Default | Description |
|---|---|---|---|
GTS_BASE_URL |
yes | — | Your GoToSocial instance URL |
GTS_ACCESS_TOKEN |
yes | — | OAuth bearer token with read scope |
TINYLOG_TITLE |
no | Tinylog |
Level-1 heading in the .gmi file |
AUTHOR_NAME |
no | — | Author name for the author: metadata line |
AUTHOR_CAPSULE |
no | — | Gemini capsule URL for the author: metadata line |
AUTHOR_AVATAR |
no | — | Avatar emoji (optionally followed by a description) |
AUTHOR_DESCRIPTION |
no | — | Free-text description shown below the title |
TINYLOG_PATH |
no | /app/tinylog.gmi |
Output path inside the container |
FEED_PATH |
no | — | Output path for feed.gmi; required to enable feed generation |
FEED_TITLE |
no | — | Link label in feed.gmi (e.g. Microblog); required to enable feed generation |
FEED_URL |
no | — | Full Gemini URL of feed.gmi; sent to antenna endpoints |
ANTENNA_URLS |
no | — | Comma-separated Gemini antenna submit URLs to ping on each update |
SYNC_SINCE |
no | now |
First-run behaviour (see below) |
LOG_LEVEL |
no | INFO |
DEBUG, INFO, WARNING, ERROR |
GTS access token
Create an application on your GoToSocial instance and complete the OAuth flow to obtain a bearer token with read scope. No write scope is needed.
SYNC_SINCE (first run only)
Controls what happens the very first time the program runs against an empty database. Ignored on subsequent starts.
| Value | Behaviour |
|---|---|
now (default) |
Sets a watermark at the current latest post; no historical posts are included |
all |
Backfills everything GoToSocial can return (paginated) |
2024-05-01 |
Backfills posts from that date onward (ISO date or datetime) |
Development
# Run locally (requires .env)
uv run python main.py
# Override sync behaviour
SYNC_SINCE=all uv run python main.py
There are no automated tests in this project.