Skip to content

Repository files navigation

SReader

SReader は、公開 RSS/Atom feed を取得し、記事 URL/タイトルと本文抽出結果を PostgreSQL に保存する lightweight feed reader です。

テックスタックは Java 25 + Spring Boot + jOOQ + Flyway + PostgreSQL 18.x です。

ディレクトリ構成

  • app/: Spring Boot application
  • app/src/generated/java/: Git-managed jOOQ generated sources
  • db/migration/: Flyway migration。fresh development DB 前提で feed_urlcontent_headercontent_full_texteft_rules だけを作成
  • docker-compose.yml: postgresmavenapp
  • k8s/: Kubernetes manifests (Kustomize base/overlays)。詳細は k8s/README.md

build/runtime path は app/ module を対象にします。

Docker Compose 経由で操作可能です。ホスト OS に Java / Maven / PostgreSQL client / Flyway を入れる必要はありません。Kubernetes へのデプロイは k8s/README.md を参照してください。

セットアップ

docker compose config
docker compose down -v
docker compose up -d --wait postgres
docker compose ps
docker compose exec postgres psql -U sreader -d sreader -c "SELECT version();"

docker compose down -v はローカル PostgreSQL volume を削除します。既存の開発 DB データが必要な場合は実行しないでください。

Flyway

Flyway のバージョンは Spring Boot Parent が管理します。実行経路は次の 2 つです。

  1. 開発 / CI(jOOQ 生成用): Maven Flyway Plugin(generate-jooq profile)
  2. 利用環境(runtime): Spring Boot 組み込み Flyway(app 起動時)

独立した Flyway コンテナは使いません。

開発用 DB への migration 状態確認:

docker compose up -d --wait postgres
docker compose run --rm maven \
  mvn -Pgenerate-jooq -pl app flyway:info
docker compose exec postgres psql -U sreader -d sreader -c "\dt"

migration だけ適用する場合:

docker compose up -d --wait postgres
docker compose run --rm maven \
  mvn -Pgenerate-jooq -pl app flyway:migrate

Flyway の goal を直接実行する場合は、親 POM でも goal が実行されないよう -am を付けません。jOOQ 生成の generate-sources では、通常どおり -am を付けて親 POM と app module をまとめて実行します。

Spring Boot app 起動時にも Flyway auto migration が有効です(classpath:db/migration)。Maven Flyway Plugin は jOOQ 生成用であり、runtime migration の代わりではありません。

jOOQ generated sources policy

jOOQ generated sources は Git 管理します。通常の app 実装変更では generated sources を再生成・手編集しないでください。

db/migration/*.sql を変更した場合の標準フロー:

  1. db/migration に versioned migration を追加する
  2. ./scripts/generate-jooq.sh を実行する(PostgreSQL 起動 → Flyway migrate → jOOQ generate)
  3. app/src/generated/java の差分を確認する
  4. migration、生成コード、手書きコードを同じ PR に含める
  5. CI が fresh DB で migration を再生し、生成コードの一致を検証する

同等の手動コマンド:

docker compose up -d --wait postgres
docker compose run --rm maven \
  mvn -B -Pgenerate-jooq -pl app -am generate-sources

jOOQ の version または codegen 設定を変更した場合も、同じ generate-jooq profile を実行して generated source の diff を確認・commit します。

通常の mvn clean verify / mvn package では migration も jOOQ 生成も実行しません。

generated sources の配置先は app/src/generated/java/ です。

jOOQ / build / test

通常 build は checked-in generated sources を compile し、jOOQ code generation は実行しません。 package -DskipTests は build-time PostgreSQL なしで実行できます。

docker compose run --rm maven mvn -version
docker compose run --rm maven java -version
docker compose run --rm maven mvn -pl app -am package -DskipTests

test は PostgreSQL を使うため、先に DB を起動してから実行してください。

docker compose up -d postgres
docker compose run --rm maven mvn test

カバレッジを含む正式確認では verify を実行します。JaCoCo は HTML、XML、CSV の 各形式でレポートを生成し、アプリケーション全体の BUNDLE 分岐カバレッジが 90% 未満の場合はビルドを失敗させます。

docker compose up -d postgres
docker compose run --rm maven mvn clean verify

HTML レポートは app/target/site/jacoco/index.html に出力されます。

master の CI が成功すると、engineering reports site を GitHub Pages へ公開します。

Database schema report は、master の CI で fresh PostgreSQL に Flyway migration を 全適用した到達結果を SchemaSpy が解析して生成します。行データや行数は公開せず、 DB 構造(テーブル定義・制約・ER diagram)のみを文書化します。 flyway_schema_history は application schema report から除外します。

この report は派生ドキュメントであり、DB schema の変更契約(source of truth)は 引き続き db/migration/*.sql です。SchemaSpy は migration の代替ではありません。

jOOQ generated sources(net/sasasin/sreader/jooq/**)は集計対象外で、手書き プロダクションコードは原則としてすべて集計します。このゲートはローカルと GitHub Actions の双方で mvn clean verify により実行されます。

ゲート導入前の初回計測値は docs/testing/coverage-baseline.md を参照してください。ゲート有効化直前の参考値は、BUNDLE 分岐カバレッジ 622/667(93.25%)です。

jOOQ sources を再生成する場合は scripts/generate-jooq.sh(または上記の mvn -Pgenerate-jooq ... generate-sources)を使います。generate-jooq profile は同一 Maven lifecycle 内で Flyway migrate の後に jOOQ Code Generator を実行します。

maven service は jOOQ codegen 用に SREADER_JOOQ_JDBC_* 環境変数経由で sreader DB へ接続し、test 実行時は sreadertest DB に接続します。docker/postgres/init/00-create-test-database.sql が test DB/role を作成します。

Renovate

依存関係と Docker image の更新検知には Renovate を使います。設定は repository root の renovate.json です。

現在の設定:

  • config:recommended を継承します。
  • release 直後の不安定な更新を避けるため、minimumReleaseAge3 days にしています。
  • Kubernetes manifest、target k3s version(k8s/ops/versions.env)、Kubernetes CI 補助ツールも更新検出の対象です。互換性の判定は Kubernetes manifests CI が担当します。

Renovate GitHub App を有効化する手順:

  1. GitHub の sasasin/sreader repository を開きます。
  2. Settings -> Integrations -> GitHub Apps を開きます。
  3. Renovate が未インストールの場合は Renovate GitHub App を開き、Install または Configure から sasasin/sreader を対象 repository に追加します。
  4. 初回 onboarding PR が作られた場合は、PR の内容が root の renovate.json と矛盾しないことを確認して close するか、必要に応じて取り込みます。
  5. 以後の Renovate PR では、この README の Docker Compose コマンドで検証します。

app 起動

docker compose build app
docker compose up app

app container は /app/app.jar を実行します。

Container image publishing (GHCR)

master への push ごとに、GitHub Actions が application container image を build して GitHub Container Registry (GHCR) へ publish します。手動実行は workflow_dispatch でも可能です。

  • Workflow: .github/workflows/publish-container.yml
  • Image: ghcr.io/sasasin/sreader
  • Tags:
    • sha-<full-commit-sha> (immutable。40 文字の full commit SHA。home-server への deploy に推奨)
    • master (mutable。動作確認用)

sha-<full-commit-sha>sha- に続けて 40 文字の full Git commit SHA を付けた tag です。例: sha-412ddade0cf36492fb52ec2060f5afce81c0de10。deploy 対象の SHA は git rev-parse HEAD や GitHub commit ページの full SHA と対応させてください。rollback と再現性のため、home-server の k3s Deployment ではこの tag を使ってください。master tag は毎回上書きされるため、本番相当の運用では使わないでください。

GHCR package は public 想定です。初回 publish 後、GitHub の repository Packages から sreader container package を開き、visibility が public であることを確認してください。public image の pull に追加 credential は不要です。

GitHub Actions から home-server への自動 deploy は行いません。deploy は手動です。

Home-server deployment (Ubuntu + k3s)

通常の home-server deploy では、以下は不要です。

  • home-server 上での Maven package
  • home-server 上での Docker image build
  • ローカル build した tarball の k3s containerd image import

代わりに、GHCR から publish 済み image を pull し、k3s Deployment を更新します。deploy する full commit SHA を確認し、sha-<full-commit-sha> tag を指定してください。

kubectl -n sreader scale deployment/sreader --replicas=0

kubectl -n sreader set image deployment/sreader \
  sreader=ghcr.io/sasasin/sreader:sha-<full-commit-sha>

kubectl -n sreader scale deployment/sreader --replicas=1

kubectl -n sreader rollout status deployment/sreader --timeout=300s
kubectl -n sreader logs deployment/sreader --tail=120

Deployment manifest では、通常は SHA-based tag を使います。

containers:
  - name: sreader
    image: ghcr.io/sasasin/sreader:sha-<full-commit-sha>
    imagePullPolicy: IfNotPresent

master tag で試す場合は imagePullPolicy: Always が必要になることがありますが、本番相当の home-server 運用では master tag は使わないでください。

DB migration と backup

app 起動時の Flyway auto migration は、従来どおり有効です。

db/migration/*.sql を含む更新は、通常の app image 更新より慎重に扱ってください。home-server へ deploy する前に、PostgreSQL backup を取ることを検討してください。例:

mkdir -p /srv/sreader/backups

pg_dump -Fc \
  -h 127.0.0.1 \
  -U sreader \
  -d sreader \
  -f "/srv/sreader/backups/sreader-$(date +%Y%m%d-%H%M%S).dump"

host、user、database 名は環境に合わせて読み替えてください。

Scheduler

Spring Scheduler がアプリケーションコンテナ内で job を実行します。

主な環境変数:

  • SREADER_SCHEDULER_ENABLED=true
  • SREADER_SCHEDULER_CRON=0 */15 * * * *
  • SREADER_HTTP_USER_AGENT=SReader/0.1
  • SREADER_HTTP_CONNECT_TIMEOUT=5s
  • SREADER_HTTP_READ_TIMEOUT=20s
  • SREADER_HTTP_RETRY_COUNT=1
  • SREADER_PLAYWRIGHT_ENABLED=false
  • SREADER_SEED_FEED_URLS= comma separated feed URLs

無効化起動:

docker compose run --rm -e SREADER_SCHEDULER_ENABLED=false app

1 回だけ job を実行:

docker compose run --rm app --sreader.scheduler.enabled=false run-once

既存の --sreader.job.run-once=true も後方互換のため利用できます。

全文取得 method

feed_url.full_text_method は以下の 9 方式を指定できます。省略時は従来通り http です。

  • feed: RSS/Atom entry 本文を保存します。
  • http: 記事 URL を HTTP GET し、XPath rule、失敗時 body text で抽出します。
  • http_readability: 記事 URL を HTTP GET し、取得した HTML を Readability4J で抽出します。
  • http_autopagerize: 記事 URL を HTTP GET し、active AutoPagerize rule で複数ページを追跡します。ページごとに XPath rule、失敗時は AutoPagerize の pageElement で抽出します。
  • http_autopagerize_readability: active AutoPagerize rule で複数ページを追跡し、ページごとに Readability4J、失敗時は pageElement で抽出します。
  • playwright: Chromium で JS 実行後の DOM HTML から XPath rule、失敗時 body text で抽出します。
  • playwright_readability: Chromium で JS 実行後の DOM HTML を Readability4J で抽出します。
  • playwright_autopagerize: Chromium で JS 実行後の DOM を page ごとに capture し、active AutoPagerize rule で複数ページを追跡します。ページごとに XPath rule、失敗時は AutoPagerize の pageElement で抽出します。
  • playwright_autopagerize_readability: 同上の Playwright AutoPagerize 追跡のうえ、ページごとに Readability4J、失敗時は pageElement で抽出します。

playwright_infy_scroll / playwright_infy_scroll_readability は廃止済みです。既存 DB の該当値は migration で playwright_autopagerize / playwright_autopagerize_readability へ変換されます。

Playwright 系 method はデフォルトでは無効です。使う場合は SREADER_PLAYWRIGHT_ENABLED=true を設定してください。無効時に Playwright 系 method の feed がある場合、その記事の全文取得は warning log を出して skip します。

AutoPagerize method の前提

HTTP / Playwright の AutoPagerize method は、利用者が local に用意した AutoPagerize SITEINFO(items_all.json 相当)を CLI で import し、active dataset を切替えたうえで動作します。

  • SReader は JSON をソースツリーに同梱しません。
  • SReader は URL から AutoPagerize DB を自動 download しません。
  • 対象は AutoPagerize の WeData DB です。InfyScroll 独自の items_all.json は使いません。
  • active dataset が無い状態で AutoPagerize method を指定すると、該当記事の抽出は configuration failure になります。

AutoPagerize の対応範囲

  • 通常 URL の next link を追って複数ページを結合します。
  • click / AJAX の「もっと見る」やブラウザ拡張固有の action は対象外です。
  • 途中ページの取得失敗時は部分記事を保存せず strict completion とします。
  • same-origin 制約とページ数 / バイト / 時間の上限があります。

再現性

  • --source-uri は provenance metadata のみで、fetch には使いません。
  • dataset identity は raw file の SHA-256 と importer version で管理します。
  • 記事成功行には dataset / rule / page metadata が保存されます。
  • 旧 dataset を残すことで調査と rollback が可能です。

ページネーションの主な環境変数:

  • SREADER_AUTOPAGERIZE_MAX_PAGES=20
  • SREADER_AUTOPAGERIZE_MAX_PAGE_BYTES=5242880
  • SREADER_AUTOPAGERIZE_MAX_TOTAL_BYTES=20971520
  • SREADER_AUTOPAGERIZE_TOTAL_TIMEOUT=120s
  • SREADER_AUTOPAGERIZE_SAME_ORIGIN_ONLY=true

Playwright 用の主な環境変数:

  • SREADER_PLAYWRIGHT_HEADLESS=true
  • SREADER_PLAYWRIGHT_VIEWPORT_WIDTH=1280
  • SREADER_PLAYWRIGHT_VIEWPORT_HEIGHT=1600
  • SREADER_PLAYWRIGHT_NAVIGATION_TIMEOUT=60s
  • SREADER_PLAYWRIGHT_NETWORK_IDLE_TIMEOUT=5s

Docker app image は Playwright の bundled Chromium と、その Chromium 専用の OS 依存ライブラリを含みます。ブラウザは /ms-playwright に固定して配置され、実行時のブラウザ自動ダウンロードは無効です。Playwright のバージョンを更新した場合は、app image を再ビルドしてください。

Maven service で Playwright の bundled browser を使う場合は、Docker Compose 経由で以下を実行してください。

docker compose run --rm maven mvn -pl app exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install chromium"

一度 content_full_text に保存された記事は insertIfAbsent のため、full_text_method を変更しても自動再抽出されません。再抽出が必要な場合は対象行の扱いを別途検討してください。

Content text export

全文取得済みの記事をテキストファイルへ出力できます。デフォルトでは無効です。

有効化する場合は Docker Compose の環境変数で指定します。

SREADER_TEXT_EXPORT_ENABLED=true
SREADER_TEXT_EXPORT_HOST_DIR=./var/sreader/content-text
SREADER_TEXT_EXPORT_BATCH_SIZE=100

app container 内の出力先は以下です。Docker Compose ではこの path が host directory に bind mount されます。

/var/lib/sreader/content-text

ファイルはディレクトリ分散せず、content_header.id を使った相対ファイル名で作成されます。

/var/lib/sreader/content-text/<content_header.id>.txt

各ファイルの内容は UTF-8 で以下の形式です。

URL: <content_header.canonical_url>
TITLE: <content_header.title>

<content_full_text.full_text>

出力対象は、content_full_text.full_text が空でなく、まだ content_text_file_export に記録されていない content_header + content_full_text です。ファイル書き込みに成功したものだけ content_text_file_export に記録され、記録済みの記事は再出力されません。書き込みに失敗した記事は出力済み扱いにせず、次回 job で再試行されます。

記事 URL は 3 種類を保存します。source_url は RSS/Atom の link、fetch_url はリダイレクト解決後に本文を取得する URL、canonical_url は記事の同一性判定とテキスト出力に使う正規化済み URL です。

関連する環境変数:

  • SREADER_TEXT_EXPORT_ENABLED=false
  • SREADER_TEXT_EXPORT_OUTPUT_DIR=/var/lib/sreader/content-text
  • SREADER_TEXT_EXPORT_HOST_DIR=./var/sreader/content-text
  • SREADER_TEXT_EXPORT_BATCH_SIZE=100

AutoPagerize dataset import / probe

AutoPagerize の WeData items_all.json 相当を、利用者がローカルに用意した JSON ファイルだけから PostgreSQL へ immutable dataset として import します。SReader は AutoPagerize DB を URL から自動取得しません。実データの JSON はソースツリーに同梱しません。

# 1. dry-run(検証のみ、DB 変更なし)
docker compose run --rm -v ./items_all.json:/tmp/items_all.json:ro app \
  --sreader.scheduler.enabled=false autopagerize import \
  --input /tmp/items_all.json --dry-run

# 2. import and activate(保存後に active を切替)
docker compose run --rm -v ./items_all.json:/tmp/items_all.json:ro app \
  --sreader.scheduler.enabled=false autopagerize import \
  --input /tmp/items_all.json

# 3. import without activate(保存するが active は変えない)
docker compose run --rm -v ./items_all.json:/tmp/items_all.json:ro app \
  --sreader.scheduler.enabled=false autopagerize import \
  --input /tmp/items_all.json --no-activate

# rejection が 1 件でもあれば DB を変更せず失敗
docker compose run --rm -v ./items_all.json:/tmp/items_all.json:ro app \
  --sreader.scheduler.enabled=false autopagerize import \
  --input /tmp/items_all.json --strict

# 4. dataset list
docker compose run --rm app --sreader.scheduler.enabled=false autopagerize datasets list

# 5. inactive dataset activate
docker compose run --rm app --sreader.scheduler.enabled=false \
  autopagerize datasets activate --dataset-id 1

# 6. probe with active dataset
docker compose run --rm app --sreader.scheduler.enabled=false probe article \
  --url https://example.com/article/1 \
  --method http_autopagerize \
  --verbose

# 7. probe with explicit dataset ID
docker compose run --rm app --sreader.scheduler.enabled=false probe article \
  --url https://example.com/article/1 \
  --method http_autopagerize \
  --autopagerize-dataset-id 1 \
  --verbose
  • identity は format + ファイル raw bytes の SHA-256 + importer version です。同一 identity の再 import は dataset を重複作成せず、必要なら既存を active にします。
  • 任意の --source-uri は provenance metadata のみで、fetch には使いません。
  • 必須 rule 項目や regex/XPath 構文不正は rule rejection です。任意 timestamp の不正は NULL + warning(rule は受理)です。

feed URL TOML import / export

feed_url は購読状態を持つ購読レコードです。

  • active: 記事取得対象です。
  • unsubscribed: 購読停止済みです。DELETE ではなく tombstone として残し、過去に停止した feed が別環境や古い TOML import で誤って復活しにくいようにします。

export はデフォルトで activeunsubscribed の両方を URL 昇順で出力します。

docker compose run --rm app --sreader.scheduler.enabled=false feeds export --output /tmp/feeds.toml
docker compose run --rm app --sreader.scheduler.enabled=false feeds export --active-only --output /tmp/active-feeds.toml

import は safe merge です。TOML に存在する feed だけを insert/update し、TOML に存在しない DB 側 feed はデフォルトでは変更しません。dry-run で件数と conflict を確認できます。

docker compose run --rm -v ./feeds.toml:/tmp/feeds.toml:ro app --sreader.scheduler.enabled=false feeds import --input /tmp/feeds.toml --dry-run
docker compose run --rm -v ./feeds.toml:/tmp/feeds.toml:ro app --sreader.scheduler.enabled=false feeds import --input /tmp/feeds.toml

DB 側が unsubscribed、TOML 側が active の場合、デフォルトでは復活させず conflict として報告します。明示的に復活させる場合だけ --resubscribe を付けます。

docker compose run --rm -v ./feeds.toml:/tmp/feeds.toml:ro app --sreader.scheduler.enabled=false feeds import --input /tmp/feeds.toml --resubscribe

TOML import 前に全文取得方式を試す

feeds importfull_text_method を指定する前に、probe コマンドで実際の記事/フィードに対して方式を試せます。DB へは何も書き込みません。

docker compose run --rm app --sreader.scheduler.enabled=false probe article \
  --url https://example.com/article \
  --method http_readability
docker compose run -e SREADER_PLAYWRIGHT_ENABLED=true --rm app --sreader.scheduler.enabled=false probe feed \
  --feed-url https://example.com/feed.xml \
  --method playwright_readability
docker compose run --rm app --sreader.scheduler.enabled=false feeds discover \
  --site-url https://example.com/
  • probe article / probe feed は本文を STDOUT へ(--output 指定時はファイルへ)。--verbose 時は診断情報を STDERR へ。
  • --xpath で XPath 抽出を明示的にテストできます(DB rule / Readability をバイパス)。
  • feeds discover はページから RSS/Atom リンクを検出。--format toml で import 用の雛形を出力。
  • Playwright 系 method を使う場合は SREADER_PLAYWRIGHT_ENABLED=true(および関連設定)が必要です。

TOML schema:

schema_version = 2
generated_at = "2026-06-14T12:00:00+09:00"

[[feeds]]
url = "https://example.com/feed.xml"
status = "active"
full_text_method = "http"

[[feeds]]
url = "https://closed.example/rss.xml"
status = "unsubscribed"
unsubscribe_reason = "site_closed"
unsubscribed_at = "2026-06-14T12:00:00+09:00"
note = "サイト閉鎖を確認したため"
full_text_method = "http"

schema_version = 2(エクスポート時の現在のバージョン)と feeds[].url は必須です。import では schema_version = 1(後方互換)も受け付けます(full_text_method 未指定時は "http" として扱います)。status は省略時 active です。full_text_method は省略時 "http" です。

URL は trim され、http / https の absolute URI のみ許可します。userinfo を含む URL は拒否します。同一 TOML 内で正規化後 URL が重複した場合も validation error です。

unsubscribe_reasonunsubscribed 用の任意項目です。省略時は other として扱います。

  • not_interested: もう関心がない。
  • site_closed: サイト閉鎖。
  • feed_dead: feed が取得不能・壊れている。
  • moved: 移転済み。
  • other: その他。

既存 canonical URL 重複の統合

先行する canonical URL 導入後も、過去に保存された URL(たとえば追跡用 query parameter が異なる記事)が重複している場合があります。content canonicalize はそれらを canonical URL ごとに1件へ統合する明示的なメンテナンスコマンドです。通常の scheduler と同時に実行しないでください。

実行前に PostgreSQL のバックアップを取得し、まず対象 host に限定した dry-run を確認します。--apply を付けない場合も dry-run です。

docker compose run --rm app --sreader.scheduler.enabled=false \
  content canonicalize --host <target-host> --dry-run

summary と対象件数を確認したら、初回は小さい --limit または --batch-size で適用します。

docker compose run --rm app --sreader.scheduler.enabled=false \
  content canonicalize --host <target-host> --limit 10 --apply

統合では canonical URL の MD5 を survivor ID とし、空でない最長の本文を保持します。export history は削除され、旧 ID と survivor ID の .txt は削除されるため、次の text exporter 実行で survivor のファイルが再生成されます。DB 統合は group ごとに commit してからファイルを削除します。ファイル削除エラーは failed_files として表示され、コマンドは non-zero で終了します。

apply 後は同じ dry-run を再実行して候補がなくなったことを確認し、text exporter を実行して survivor のファイルが生成されたことと orphan file が残っていないことを確認してください。

配布条件

本プログラムはフリーソフトウェアです。LGPL (the GNU Lesser General Public License) バージョン 3、またはそれ以降のバージョンに示す条件で本プログラムを再配布できます。LGPL については LICENSE ファイルを参照してください。

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages