Skip to content

RSS Workshop v1.0.0

Choose a tag to compare

@Ldogg123 Ldogg123 released this 12 Sep 00:46
· 180 commits to main since this release
f9346bd

First stable release. RSS Workshop turns pages that publish no feed into RSS and Atom feeds you keep, and 1.0 is the point at which its upgrade path, configuration surface and reader URLs become commitments rather than implementation details. See upgrades before installing over an existing deployment.

Upgrading from v0.2.0

  • Take a backup first. Starting this version migrates the database to schema 4 in a single transaction. Every released schema upgrades directly, but an older release cannot open a newer database, so rolling back needs a backup taken before the upgrade. See backup and restore.
  • Readers refetch once. Published feeds carry more than they did, so every feed's bytes change once and subscribed readers fetch a full copy on their next poll. Stories keep their identities and publication dates, so nobody sees a duplicate.
  • Container logs now rotate, at three 10 MiB files per service. docker compose logs reaches back only as far as the retained files.

Feeds readers actually want

  • Full article content. Follow each story's own link, extract the article body, and publish that instead of the list page's one-line teaser. Off by default and configured per feed. Article pages are fetched with the same guarded fetcher as list pages, at most 10 per refresh so a long feed fills in over several cycles, and an article that cannot be fetched keeps its teaser rather than dropping the story or failing the refresh.
  • Better published feeds. RSS keeps the article in description and repeats it in content:encoded; Atom carries the list-page description as summary and the article as content. The extracted image is published as media:content and as an Atom enclosure, where readers look for a thumbnail, and is no longer repeated when the article body already contains it. The RSS channel gains atom:link rel="self", ttl, lastBuildDate and generator.
  • Conditional requests. Reader responses honour If-Modified-Since alongside the existing ETag, so a reader polling from several devices costs validators rather than full copies.
  • OPML export. Download a subscription list of every feed and import it into your reader in one step. It contains every private reader link, so it requires an admin session and is never cached.

Knowing when something breaks

  • Log levels. LOG_LEVEL selects debug, info, warn or error, and LOG_FORMAT switches to JSON for a log collector. The startup line summarizes the running deployment, failed refreshes report their reason and feed, rejected sign-ins are reported with their remote address, and shutdown is logged.
  • Prometheus metrics. Setting METRICS_TOKEN enables /metrics, scraped with that token as a bearer credential; blank leaves the endpoint returning 404. Per-feed series carry item counts, consecutive failures and last-success timestamps, so an alert can name the feed that broke.
  • A failed database write no longer hammers the source. Such a refresh rolls back the feed's schedule along with everything else, so the source was refetched on every scheduler tick for as long as writing failed, while the log reported success. The feed now backs off, and the failure reports its reason.
  • Retention is visible. MAX_ITEMS applies to stories already saved, so lowering it permanently deletes the excess at each feed's next refresh. That now appears in the log and is documented as destructive.

Running it

  • Reverse proxy support. compose.proxy.yaml and a guide for a proxy running in Docker, which cannot reach the default loopback publication.
  • Container log rotation, configurable through LOG_MAX_SIZE and LOG_MAX_FILES.
  • Stable image aliases. latest and latest-browser include Chromium; latest-static selects the lightweight runtime. Compose follows these aliases, with version and digest pins still available.
  • Direct upgrades from every released schema, with frozen per-backend fixtures and tests comparing all saved feed, item and run data across upgrade and reopening. Backups support schemas 1 through 4.

Editor and examples

  • Full article content is inspectable and repairable. Previews show the fetched body, so a selector matching the wrong block is visible before saving, and changing or clearing the selector discards the stored bodies so later refreshes fetch them again.
  • New example recipes. The previous examples targeted five real news sites that all publish their own feeds, extracted only a title and link, and two relied on generated class names that change whenever those sites deploy. They are replaced by a demo site included in the repository, with recipes covering every field, CSS and XPath, and filtering with full article content. A test imports each example and extracts from that site.
  • Reorganized documentation. The README follows install, first feed, and diagnosing a broken one, with installation variants collected below; .env.example is grouped by purpose. Screenshots are refreshed and now include full article content and per-feed diagnostics.

Internal

  • Security headers applied to every response are pinned by a test; the content policy is the second layer behind sanitization for extracted markup and previously had no coverage of its own.
  • A refresh no longer loads every stored article body to decide which article pages to fetch.

Verifying a download

Native archives ship with a .sha256 sidecar:

sha256sum -c rss-workshop-v1.0.0-linux-amd64.tar.gz.sha256

Corresponding Debian source archives for both image architectures are attached to this release. Large archives are split; reassemble before verifying:

cat debian-sources-v1.0.0-linux-amd64.tar.gz.part-* > debian-sources-v1.0.0-linux-amd64.tar.gz
sha256sum -c debian-sources-v1.0.0-linux-amd64.tar.gz.sha256

Images

Variant Tag
Browser (default) ghcr.io/ldogg123/rss-workshop:v1.0.0-browser
Static ghcr.io/ldogg123/rss-workshop:v1.0.0-static

The latest, latest-browser and latest-static aliases advance to this release. Pin a version tag or digest with RSS_IMAGE to stay put.