Skip to content

owncast/owncast-directory-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Owncast directory example

This is an AI-built proof of concept, not a product made by people. It was written by an AI coding assistant to show what is possible when you build your own directories and stream listings on top of Owncast. It is a demonstration and a starting point, nothing more. Do not treat it as an official, supported, or production-ready piece of software.

A small reference application that follows Owncast servers over ActivityPub, tracks which ones are live, accepts operator submissions, and serves a single web page listing them. Live servers appear first with their thumbnail, name, and title. Offline servers are listed below.

It is built to be read and forked. The whole thing is a handful of short Python files with one SQLite table behind them.

The goal is to hopefully have people build a far more productized solution for people to use in the future, so the idea of personal and communal directories of Owncast live streams can become a thing.

This is a minimal reference, not a production service. It has no accounts, no admin UI, no rate limiting, and no scaling story. It keeps everything in one process and one SQLite file. Read it, learn from it, and build your real directory on top of the ideas here rather than shipping this as is.

How it works

The directory is itself a Fediverse actor. It publishes an ActivityStreams actor document with an RSA public key, served over HTTPS, plus WebFinger and NodeInfo, so an Owncast server can resolve it and verify the requests it sends.

For each server it lists, the flow is:

  1. Follow. The directory sends a signed Follow to the server's actor.
  2. Accept. The server posts an Accept back to the directory's inbox. The entry is now followed.
  3. Offer. While the server is live it posts an Offer to the inbox about every five minutes, carrying Owncast's custom metadata (title, description, server name, thumbnail, tags). The directory marks the entry online and fills it in from those fields. The logo is not taken from the activity: the page derives it from the server URL (/logo/external), a value it trusts, rather than the logo URL the remote server sends.
  4. Leave. When the stream ends the server posts a Leave, and the entry goes offline.
  5. Staleness sweep. If an online entry stops receiving Offer pings without a Leave (a crash or a network drop), a once-a-minute sweep marks it offline after about eleven minutes.

Owncast has no built-in way for a server to ask to be listed, so the directory provides a submission form at /submit. An operator pastes their server URL, the directory checks it is a featurable Owncast server through NodeInfo, and the submission waits for review. The directory owner approves or rejects it at /review. On approval the directory follows the server and the flow above takes over.

Remote metadata is treated as untrusted. URL fields are confirmed to be http or https before they are rendered, text is length-clamped, and the templates escape everything. The one value the directory trusts is the server URL it chose to follow, not the display name the server sends.

Layout

File Responsibility
directory/config.py Configuration from environment variables
directory/store.py SQLite storage, one row per server
directory/owncast.py NodeInfo validation and Owncast metadata parsing and sanitizing
directory/federation.py Keypair, actor document, signed requests, signature verification, inbox dispatch
directory/app.py Quart routes, the submission and review flow, the page, the sweep
directory/templates.py The HTML
directory/cli.py A tiny review command
directory/__main__.py Entry point

The ActivityPub layer is built on bovine, which does the cryptography, the signing, and the signature verification. The web framework is Quart, which bovine's inbound signature validator is designed to work with directly.

Requirements

  • Python 3.11 or newer.
  • uv for dependency management. The dependencies (bovine, quart, hypercorn) are pinned in pyproject.toml and locked in uv.lock.

Install

uv sync

That creates a .venv and installs the exact locked versions. uv will fetch a suitable Python for you if you do not have one.

If you would rather not use uv, the project is a standard pyproject.toml, so pip install . into a virtual environment works too.

Configure

Everything is an environment variable. See env.example for the full list with defaults and comments. The ones you are most likely to set:

  • DIRECTORY_DOMAIN: required, no default. The public host the directory runs on, with no scheme. The actor is always served over HTTPS using this host, and it is the keyId other servers fetch to verify the directory's signatures, so set it to a host they can reach. The app refuses to start without it.
  • DIRECTORY_STALE_SECONDS: how long a live entry may go without an Offer ping before it is marked offline (default 660, about 11 minutes).

Run

uv run owncast-directory

That console script is the entry point. uv run python -m directory does the same thing. The app serves plain HTTP on DIRECTORY_BIND_HOST:DIRECTORY_BIND_PORT (default 127.0.0.1:8000). It does not terminate TLS itself. Put an HTTPS reverse proxy in front of it on the host named by DIRECTORY_DOMAIN, because Owncast signs every request and will only deliver to an HTTPS inbox. On first run it generates an RSA keypair under DIRECTORY_KEY_DIR and reuses it afterwards. Keep that keypair: it is the actor's identity.

Endpoints:

  • GET / the public directory page.
  • GET /submit and POST /submit the submission form.
  • GET /review and POST /review the review page (no auth, since this is a demo).
  • GET /activitypub/actor the actor document.
  • POST /activitypub/actor/inbox the inbox.
  • GET /.well-known/webfinger, GET /.well-known/nodeinfo, GET /nodeinfo/2.0 discovery.

Listing a server

This assumes a publicly reachable Owncast server (v0.3.0 or newer, with featured streams enabled) and a publicly reachable directory, so the two can sign and deliver requests to each other over HTTPS. Below, replace https://directory.example.com with your directory's own domain.

Every server is added by hand and approved by hand. There are no seeds and no auto-accept. Listing a server is two explicit steps on two pages.

1. Submit the server

Open the submission form and enter the server's URL:

https://directory.example.com/submit

The directory checks, through NodeInfo, that the URL is a featurable Owncast server, and rejects anything that is not Owncast or is unreachable with a clear message. A valid submission is stored as pending and is not listed yet.

2. Approve it

Open the review page. It lists every pending submission with Approve and Reject buttons:

https://directory.example.com/review

This page has no password, because this is a demo. A real directory would put it behind a login, an allowlist, or a private network. (If you prefer the command line, it edits the same database the running app reads: uv run python -m directory.cli list, then approve <url> or reject <url>.)

On approval the directory sends the server a signed Follow carrying the https://owncast.online/ns#directory marker.

3. The server's operator opts in

That marker tells Owncast the follower is a directory, so Owncast does not auto-accept it. It holds the request for the operator to approve in their Owncast admin under Featured Streams. Being listed is opt-in on their side by design, so the entry stays pending and unlisted until they approve.

4. It appears and tracks live status

Once the operator approves, the server shows on the directory page, offline at first. While it is live it sends periodic Offer pings and the entry shows live with its title, thumbnail, and tags. When the stream ends it sends a Leave and the entry returns to offline. If the server drops off without a clean Leave, the staleness sweep ages the entry out (see DIRECTORY_STALE_SECONDS).

If the operator later removes the directory from their Featured Streams admin, Owncast sends the directory a Reject and the entry is dropped from the listing.

License

MIT. See LICENSE. bovine is also MIT licensed.

About

This is a very basic, but runnable, proof of concept for building your own directories and live stream listings for Owncast live broadcasts.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages