diff --git a/v3/.gitignore b/v3/.gitignore new file mode 100644 index 0000000..2d8ebac --- /dev/null +++ b/v3/.gitignore @@ -0,0 +1,5 @@ +# CrowdSec — generated bouncer API key; never commit +crowdsec/.crowdsec.env + +# Local env overrides +.env.local diff --git a/v3/crowdsec/acquis.yaml b/v3/crowdsec/acquis.yaml new file mode 100644 index 0000000..2af7d7f --- /dev/null +++ b/v3/crowdsec/acquis.yaml @@ -0,0 +1,12 @@ +# CrowdSec log acquisition — tells the CrowdSec agent which logs to parse. +# +# Caddy writes JSON access logs to /var/log/caddy/access.log (mounted from the +# caddy_logs Docker volume). CrowdSec reads the same volume at the same path. +# +# The `crowdsecurity/caddy` collection (installed via COLLECTIONS env var) +# provides the parser that understands Caddy's JSON log format. + +filenames: + - /var/log/caddy/access.log +labels: + type: caddy diff --git a/v3/docs/d2/api-routing.d2 b/v3/docs/d2/api-routing.d2 index 10966d9..db6e9b8 100644 --- a/v3/docs/d2/api-routing.d2 +++ b/v3/docs/d2/api-routing.d2 @@ -171,7 +171,7 @@ client -> caddy: HTTPS :443 caddy -> sk: "/* (catch-all)\n→ SvelteKit handles auth" caddy -> be: "/health /scrape*\n/api/browse /api/book-preview/*\n/api/chapter-text/* /api/chapter-markdown/*\n/api/reindex/* /api/cover/*\n/api/audio-proxy/* /api/catalogue /api/ranking" -caddy -> storage.mn: "/avatars/*\n(presigned GETs)" +caddy -> storage.mn: "/avatars/*\n/audio/*\n/chapters/*\n(presigned MinIO GETs)" # ─── SvelteKit → Backend (server-side proxy) ────────────────────────────────── diff --git a/v3/docs/d2/api-routing.svg b/v3/docs/d2/api-routing.svg index ae42f95..3f19df2 100644 --- a/v3/docs/d2/api-routing.svg +++ b/v3/docs/d2/api-routing.svg @@ -1,24 +1,24 @@ -LegendBrowser / iOS AppCaddy :443custom build · caddy-ratelimitsecurity headers · rate limitingstatic error pagesSvelteKit UI :3000Backend API :8080Storagepublicuser authadmin onlyPOST /api/auth/loginPOST /api/auth/registerPOST /api/auth/change-passwordGET /api/auth/sessionGET /api/catalogue-pageGET /api/searchGET /api/book/{slug}GET /api/chapter/{slug}/{n}GET /api/chapter-text-preview/{slug}/{n}GET /api/scrape/statusGET /api/scrape/tasksPOST /api/scrapePOST /api/scrape/rangePOST /api/scrape/cancel/{id}POST /api/audio/{slug}/{n}GET /api/audio/status/{slug}/{n}GET /api/voicesGET /api/presign/chapter/{slug}/{n}GET /api/presign/audio/{slug}/{n}GET /api/presign/voice-sample/{voice}GET /api/presign/avatar-upload/{userId}GET /api/presign/avatar/{userId}GET /api/progressPOST /api/progress/{slug}DELETE /api/progress/{slug}GET /api/libraryPOST /api/library/{slug}DELETE /api/library/{slug}GET /api/comments/{slug}POST /api/comments/{slug}GET /healthGET /api/versionPOST /scrapePOST /scrape/bookPOST /scrape/book/rangeGET /api/browseGET /api/catalogueGET /api/rankingGET /api/cover/{domain}/{slug}GET /api/book-preview/{slug}GET /api/chapter-text/{slug}/{n}GET /api/chapter-markdown/{slug}/{n}POST /api/reindex/{slug} ⚠ adminGET /api/audio-proxy/{slug}/{n}GET /api/voicesauth · books · progresscomments · libraryscrape_jobs · audio_cachechapters · audioavatars · browseindex: bookspresign URL cache HTTPS :443/* (catch-all)→ SvelteKit handles auth/health /scrape*/api/browse /api/book-preview/*/api/chapter-text/* /api/chapter-markdown/*/api/reindex/* /api/cover/*/api/audio-proxy/* /api/catalogue /api/ranking/avatars/*(presigned GETs)internal proxyinternal proxyinternal proxycheck cachegenerate presigngenerate presignsessions / usersscrape job recordsfull-text searchranking recordscover presignchapter objectsbook metadataaudio presignpresign cache - + .d2-4154178137 .fill-N1{fill:#0A0F25;} + .d2-4154178137 .fill-N2{fill:#676C7E;} + .d2-4154178137 .fill-N3{fill:#9499AB;} + .d2-4154178137 .fill-N4{fill:#CFD2DD;} + .d2-4154178137 .fill-N5{fill:#DEE1EB;} + .d2-4154178137 .fill-N6{fill:#EEF1F8;} + .d2-4154178137 .fill-N7{fill:#FFFFFF;} + .d2-4154178137 .fill-B1{fill:#0D32B2;} + .d2-4154178137 .fill-B2{fill:#0D32B2;} + .d2-4154178137 .fill-B3{fill:#E3E9FD;} + .d2-4154178137 .fill-B4{fill:#E3E9FD;} + .d2-4154178137 .fill-B5{fill:#EDF0FD;} + .d2-4154178137 .fill-B6{fill:#F7F8FE;} + .d2-4154178137 .fill-AA2{fill:#4A6FF3;} + .d2-4154178137 .fill-AA4{fill:#EDF0FD;} + .d2-4154178137 .fill-AA5{fill:#F7F8FE;} + .d2-4154178137 .fill-AB4{fill:#EDF0FD;} + .d2-4154178137 .fill-AB5{fill:#F7F8FE;} + .d2-4154178137 .stroke-N1{stroke:#0A0F25;} + .d2-4154178137 .stroke-N2{stroke:#676C7E;} + .d2-4154178137 .stroke-N3{stroke:#9499AB;} + .d2-4154178137 .stroke-N4{stroke:#CFD2DD;} + .d2-4154178137 .stroke-N5{stroke:#DEE1EB;} + .d2-4154178137 .stroke-N6{stroke:#EEF1F8;} + .d2-4154178137 .stroke-N7{stroke:#FFFFFF;} + .d2-4154178137 .stroke-B1{stroke:#0D32B2;} + .d2-4154178137 .stroke-B2{stroke:#0D32B2;} + .d2-4154178137 .stroke-B3{stroke:#E3E9FD;} + .d2-4154178137 .stroke-B4{stroke:#E3E9FD;} + .d2-4154178137 .stroke-B5{stroke:#EDF0FD;} + .d2-4154178137 .stroke-B6{stroke:#F7F8FE;} + .d2-4154178137 .stroke-AA2{stroke:#4A6FF3;} + .d2-4154178137 .stroke-AA4{stroke:#EDF0FD;} + .d2-4154178137 .stroke-AA5{stroke:#F7F8FE;} + .d2-4154178137 .stroke-AB4{stroke:#EDF0FD;} + .d2-4154178137 .stroke-AB5{stroke:#F7F8FE;} + .d2-4154178137 .background-color-N1{background-color:#0A0F25;} + .d2-4154178137 .background-color-N2{background-color:#676C7E;} + .d2-4154178137 .background-color-N3{background-color:#9499AB;} + .d2-4154178137 .background-color-N4{background-color:#CFD2DD;} + .d2-4154178137 .background-color-N5{background-color:#DEE1EB;} + .d2-4154178137 .background-color-N6{background-color:#EEF1F8;} + .d2-4154178137 .background-color-N7{background-color:#FFFFFF;} + .d2-4154178137 .background-color-B1{background-color:#0D32B2;} + .d2-4154178137 .background-color-B2{background-color:#0D32B2;} + .d2-4154178137 .background-color-B3{background-color:#E3E9FD;} + .d2-4154178137 .background-color-B4{background-color:#E3E9FD;} + .d2-4154178137 .background-color-B5{background-color:#EDF0FD;} + .d2-4154178137 .background-color-B6{background-color:#F7F8FE;} + .d2-4154178137 .background-color-AA2{background-color:#4A6FF3;} + .d2-4154178137 .background-color-AA4{background-color:#EDF0FD;} + .d2-4154178137 .background-color-AA5{background-color:#F7F8FE;} + .d2-4154178137 .background-color-AB4{background-color:#EDF0FD;} + .d2-4154178137 .background-color-AB5{background-color:#F7F8FE;} + .d2-4154178137 .color-N1{color:#0A0F25;} + .d2-4154178137 .color-N2{color:#676C7E;} + .d2-4154178137 .color-N3{color:#9499AB;} + .d2-4154178137 .color-N4{color:#CFD2DD;} + .d2-4154178137 .color-N5{color:#DEE1EB;} + .d2-4154178137 .color-N6{color:#EEF1F8;} + .d2-4154178137 .color-N7{color:#FFFFFF;} + .d2-4154178137 .color-B1{color:#0D32B2;} + .d2-4154178137 .color-B2{color:#0D32B2;} + .d2-4154178137 .color-B3{color:#E3E9FD;} + .d2-4154178137 .color-B4{color:#E3E9FD;} + .d2-4154178137 .color-B5{color:#EDF0FD;} + .d2-4154178137 .color-B6{color:#F7F8FE;} + .d2-4154178137 .color-AA2{color:#4A6FF3;} + .d2-4154178137 .color-AA4{color:#EDF0FD;} + .d2-4154178137 .color-AA5{color:#F7F8FE;} + .d2-4154178137 .color-AB4{color:#EDF0FD;} + .d2-4154178137 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-4154178137);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-4154178137);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-4154178137);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-4154178137);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-4154178137);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-4154178137);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-4154178137);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-4154178137);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>LegendBrowser / iOS AppCaddy :443custom build · caddy-ratelimitsecurity headers · rate limitingstatic error pagesSvelteKit UI :3000Backend API :8080Storagepublicuser authadmin onlyPOST /api/auth/loginPOST /api/auth/registerPOST /api/auth/change-passwordGET /api/auth/sessionGET /api/catalogue-pageGET /api/searchGET /api/book/{slug}GET /api/chapter/{slug}/{n}GET /api/chapter-text-preview/{slug}/{n}GET /api/scrape/statusGET /api/scrape/tasksPOST /api/scrapePOST /api/scrape/rangePOST /api/scrape/cancel/{id}POST /api/audio/{slug}/{n}GET /api/audio/status/{slug}/{n}GET /api/voicesGET /api/presign/chapter/{slug}/{n}GET /api/presign/audio/{slug}/{n}GET /api/presign/voice-sample/{voice}GET /api/presign/avatar-upload/{userId}GET /api/presign/avatar/{userId}GET /api/progressPOST /api/progress/{slug}DELETE /api/progress/{slug}GET /api/libraryPOST /api/library/{slug}DELETE /api/library/{slug}GET /api/comments/{slug}POST /api/comments/{slug}GET /healthGET /api/versionPOST /scrapePOST /scrape/bookPOST /scrape/book/rangeGET /api/browseGET /api/catalogueGET /api/rankingGET /api/cover/{domain}/{slug}GET /api/book-preview/{slug}GET /api/chapter-text/{slug}/{n}GET /api/chapter-markdown/{slug}/{n}POST /api/reindex/{slug} ⚠ adminGET /api/audio-proxy/{slug}/{n}GET /api/voicesauth · books · progresscomments · libraryscrape_jobs · audio_cachechapters · audioavatars · browseindex: bookspresign URL cache HTTPS :443/* (catch-all)→ SvelteKit handles auth/health /scrape*/api/browse /api/book-preview/*/api/chapter-text/* /api/chapter-markdown/*/api/reindex/* /api/cover/*/api/audio-proxy/* /api/catalogue /api/ranking/avatars/*/libnovel-audio/*/libnovel-chapters/*(presigned MinIO GETs)internal proxyinternal proxyinternal proxycheck cachegenerate presigngenerate presignsessions / usersscrape job recordsfull-text searchranking recordscover presignchapter objectsbook metadataaudio presignpresign cache + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/v3/docs/d2/architecture.d2 b/v3/docs/d2/architecture.d2 index 06b731b..470145b 100644 --- a/v3/docs/d2/architecture.d2 +++ b/v3/docs/d2/architecture.d2 @@ -46,7 +46,7 @@ storage: Storage { minio: MinIO { shape: cylinder - label: "MinIO :9000\n\nbuckets:\n libnovel-chapters\n libnovel-audio\n avatars\n libnovel-browse" + label: "MinIO :9000\n\nbuckets:\n chapters\n audio\n avatars\n catalogue" } pocketbase: PocketBase { @@ -135,7 +135,7 @@ app.ui -> app.backend: REST API calls (server-side)\n/api/catalogue /api/book-pr app.caddy -> app.ui: "/* (catch-all)\n/api/scrape/*\n/api/chapter-text-preview/*\n→ SvelteKit (auth enforced)" app.caddy -> app.backend: "/health /scrape*\n/api/browse /api/book-preview/*\n/api/chapter-text/*\n/api/reindex/* /api/cover/*\n/api/audio-proxy/*" -app.caddy -> storage.minio: "/avatars/*\n(presigned avatar GETs)" +app.caddy -> storage.minio: "/avatars/*\n/audio/*\n/chapters/*\n(presigned MinIO GETs)" # ─── External → App ─────────────────────────────────────────────────────────── diff --git a/v3/docs/d2/architecture.svg b/v3/docs/d2/architecture.svg index b5aa8d0..c1d9fcb 100644 --- a/v3/docs/d2/architecture.svg +++ b/v3/docs/d2/architecture.svg @@ -1,23 +1,23 @@ -novelfire.netKokoro-FastAPI TTSLet's EncryptBrowser / iOS AppInit containersStorageApplicationOpsminio-init(mc: create buckets)pb-init(bootstrap collections)MinIO :9000 buckets: libnovel-chapters libnovel-audio avatars libnovel-browsePocketBase :8090 collections: books chapters_idx audio_cache progress scrape_jobs app_users rankingValkey :6379 (presign URL cacheTTL-based, shared)Meilisearch :7700 indices: booksCaddy :443 / :80custom build + caddy-ratelimit features: auto-HTTPS (Let's Encrypt) security headers rate limiting (per-IP) static error pages (502/503/504)Backend API :8080(Go — HTTP API server)Runner :9091(Go — background workerscraping + TTS jobs/metrics endpoint)SvelteKit UI :3000(adapter-node)Watchtower(containrrr/watchtower) polls every 5 minautopulls + redeploys: backend · runner · ui create bucketsbootstrap schema blobs (chapters, audio,avatars, browse)structured records(books, progress, jobs…)cache presigned URLs(SET/GET with TTL)write chapter markdown& audio MP3sread/update scrape jobswrite book recordsindex books onscrape completionread presigned URL cacheauth, progress,comments, settingsREST API calls (server-side)/api/catalogue /api/book-preview/api/chapter-text /api/audio etc./* (catch-all)/api/scrape/*/api/chapter-text-preview/*→ SvelteKit (auth enforced)/health /scrape*/api/browse /api/book-preview/*/api/chapter-text/*/api/reindex/* /api/cover/*/api/audio-proxy/*/avatars/*(presigned avatar GETs)scrape(HTTP GET)TTS generation(HTTP POST)ACME certificate(TLS-ALPN-01)watch (label-enabled)watch (label-enabled)watch (label-enabled)HTTPS :443(single entry point) - - - - - - - - - - - - - - - - - + .d2-1763159692 .fill-N1{fill:#0A0F25;} + .d2-1763159692 .fill-N2{fill:#676C7E;} + .d2-1763159692 .fill-N3{fill:#9499AB;} + .d2-1763159692 .fill-N4{fill:#CFD2DD;} + .d2-1763159692 .fill-N5{fill:#DEE1EB;} + .d2-1763159692 .fill-N6{fill:#EEF1F8;} + .d2-1763159692 .fill-N7{fill:#FFFFFF;} + .d2-1763159692 .fill-B1{fill:#0D32B2;} + .d2-1763159692 .fill-B2{fill:#0D32B2;} + .d2-1763159692 .fill-B3{fill:#E3E9FD;} + .d2-1763159692 .fill-B4{fill:#E3E9FD;} + .d2-1763159692 .fill-B5{fill:#EDF0FD;} + .d2-1763159692 .fill-B6{fill:#F7F8FE;} + .d2-1763159692 .fill-AA2{fill:#4A6FF3;} + .d2-1763159692 .fill-AA4{fill:#EDF0FD;} + .d2-1763159692 .fill-AA5{fill:#F7F8FE;} + .d2-1763159692 .fill-AB4{fill:#EDF0FD;} + .d2-1763159692 .fill-AB5{fill:#F7F8FE;} + .d2-1763159692 .stroke-N1{stroke:#0A0F25;} + .d2-1763159692 .stroke-N2{stroke:#676C7E;} + .d2-1763159692 .stroke-N3{stroke:#9499AB;} + .d2-1763159692 .stroke-N4{stroke:#CFD2DD;} + .d2-1763159692 .stroke-N5{stroke:#DEE1EB;} + .d2-1763159692 .stroke-N6{stroke:#EEF1F8;} + .d2-1763159692 .stroke-N7{stroke:#FFFFFF;} + .d2-1763159692 .stroke-B1{stroke:#0D32B2;} + .d2-1763159692 .stroke-B2{stroke:#0D32B2;} + .d2-1763159692 .stroke-B3{stroke:#E3E9FD;} + .d2-1763159692 .stroke-B4{stroke:#E3E9FD;} + .d2-1763159692 .stroke-B5{stroke:#EDF0FD;} + .d2-1763159692 .stroke-B6{stroke:#F7F8FE;} + .d2-1763159692 .stroke-AA2{stroke:#4A6FF3;} + .d2-1763159692 .stroke-AA4{stroke:#EDF0FD;} + .d2-1763159692 .stroke-AA5{stroke:#F7F8FE;} + .d2-1763159692 .stroke-AB4{stroke:#EDF0FD;} + .d2-1763159692 .stroke-AB5{stroke:#F7F8FE;} + .d2-1763159692 .background-color-N1{background-color:#0A0F25;} + .d2-1763159692 .background-color-N2{background-color:#676C7E;} + .d2-1763159692 .background-color-N3{background-color:#9499AB;} + .d2-1763159692 .background-color-N4{background-color:#CFD2DD;} + .d2-1763159692 .background-color-N5{background-color:#DEE1EB;} + .d2-1763159692 .background-color-N6{background-color:#EEF1F8;} + .d2-1763159692 .background-color-N7{background-color:#FFFFFF;} + .d2-1763159692 .background-color-B1{background-color:#0D32B2;} + .d2-1763159692 .background-color-B2{background-color:#0D32B2;} + .d2-1763159692 .background-color-B3{background-color:#E3E9FD;} + .d2-1763159692 .background-color-B4{background-color:#E3E9FD;} + .d2-1763159692 .background-color-B5{background-color:#EDF0FD;} + .d2-1763159692 .background-color-B6{background-color:#F7F8FE;} + .d2-1763159692 .background-color-AA2{background-color:#4A6FF3;} + .d2-1763159692 .background-color-AA4{background-color:#EDF0FD;} + .d2-1763159692 .background-color-AA5{background-color:#F7F8FE;} + .d2-1763159692 .background-color-AB4{background-color:#EDF0FD;} + .d2-1763159692 .background-color-AB5{background-color:#F7F8FE;} + .d2-1763159692 .color-N1{color:#0A0F25;} + .d2-1763159692 .color-N2{color:#676C7E;} + .d2-1763159692 .color-N3{color:#9499AB;} + .d2-1763159692 .color-N4{color:#CFD2DD;} + .d2-1763159692 .color-N5{color:#DEE1EB;} + .d2-1763159692 .color-N6{color:#EEF1F8;} + .d2-1763159692 .color-N7{color:#FFFFFF;} + .d2-1763159692 .color-B1{color:#0D32B2;} + .d2-1763159692 .color-B2{color:#0D32B2;} + .d2-1763159692 .color-B3{color:#E3E9FD;} + .d2-1763159692 .color-B4{color:#E3E9FD;} + .d2-1763159692 .color-B5{color:#EDF0FD;} + .d2-1763159692 .color-B6{color:#F7F8FE;} + .d2-1763159692 .color-AA2{color:#4A6FF3;} + .d2-1763159692 .color-AA4{color:#EDF0FD;} + .d2-1763159692 .color-AA5{color:#F7F8FE;} + .d2-1763159692 .color-AB4{color:#EDF0FD;} + .d2-1763159692 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-1763159692);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-1763159692);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-1763159692);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1763159692);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1763159692);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1763159692);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1763159692);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1763159692);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>novelfire.netKokoro-FastAPI TTSLet's EncryptBrowser / iOS AppInit containersStorageApplicationOpsminio-init(mc: create buckets)pb-init(bootstrap collections)MinIO :9000 buckets: libnovel-chapters libnovel-audio avatars libnovel-browsePocketBase :8090 collections: books chapters_idx audio_cache progress scrape_jobs app_users rankingValkey :6379 (presign URL cacheTTL-based, shared)Meilisearch :7700 indices: booksCaddy :443 / :80custom build + caddy-ratelimit features: auto-HTTPS (Let's Encrypt) security headers rate limiting (per-IP) static error pages (502/503/504)Backend API :8080(Go — HTTP API server)Runner :9091(Go — background workerscraping + TTS jobs/metrics endpoint)SvelteKit UI :3000(adapter-node)Watchtower(containrrr/watchtower) polls every 5 minautopulls + redeploys: backend · runner · ui create bucketsbootstrap schema blobs (chapters, audio,avatars, browse)structured records(books, progress, jobs…)cache presigned URLs(SET/GET with TTL)write chapter markdown& audio MP3sread/update scrape jobswrite book recordsindex books onscrape completionread presigned URL cacheauth, progress,comments, settingsREST API calls (server-side)/api/catalogue /api/book-preview/api/chapter-text /api/audio etc./* (catch-all)/api/scrape/*/api/chapter-text-preview/*→ SvelteKit (auth enforced)/health /scrape*/api/browse /api/book-preview/*/api/chapter-text/*/api/reindex/* /api/cover/*/api/audio-proxy/*/avatars/*/libnovel-audio/*/libnovel-chapters/*(presigned MinIO GETs)scrape(HTTP GET)TTS generation(HTTP POST)ACME certificate(TLS-ALPN-01)watch (label-enabled)watch (label-enabled)watch (label-enabled)HTTPS :443(single entry point) + + + + + + + + + + + + + + + + + - - - + + + diff --git a/v3/docs/mermaid/data-flow.mermaid.md b/v3/docs/mermaid/data-flow.mermaid.md index 9880853..0536890 100644 --- a/v3/docs/mermaid/data-flow.mermaid.md +++ b/v3/docs/mermaid/data-flow.mermaid.md @@ -22,7 +22,7 @@ flowchart TD H --> I[Worker pool — N goroutines\nRUNNER_MAX_CONCURRENT_SCRAPE] I --> J[For each missing chapter:\nGET chapter HTML from novelfire.net] J --> K[Parse HTML → Markdown\nhtmlutil.NodeToMarkdown] - K --> L[PUT object to MinIO\nlibnovel-chapters/{slug}/{n}.md] + K --> L[PUT object to MinIO\nchapters/{slug}/{n}.md] L --> M[Upsert book doc\nto Meilisearch index: books] M --> Z F --> M @@ -87,7 +87,7 @@ flowchart TD H --> I["GET /api/chapter-text/{slug}/{n}\nfrom backend — plain text"] I --> J[POST /v1/audio/speech\nto Kokoro-FastAPI\nbody: text + voice] J --> K[Stream MP3 response] - K --> L[PUT object to MinIO\nlibnovel-audio/{slug}/{n}/{voice}.mp3] + K --> L[PUT object to MinIO\naudio/{slug}/{n}/{voice}.mp3] L --> M[UPDATE audio_task\nstatus=done] N(["Client polls\nGET /api/audio/status/{slug}/{n}"]) --> O{status?}