- docs/deployment.md: step-by-step Docker deployment for anyone (setup, configuration, LAN/HTTPS/VPN access, adding manga, operation, prebuilt images, backups, peering, troubleshooting, rules for agents). - deploy/circus.sh: host helper without Node (status, up/down with queue pause, logs, pause/resume, update, rescan, restore-test); deploy/compose.https.yml + Caddyfile for automatic HTTPS. - README rewritten for public readers (Docker first, sources/copyright note, docs index); AGENTS.md + CLAUDE.md for contributors and coding agents. - Removed notes about one particular installation (Windows host, handoffs, audits of a private library, benchmarks with local paths, wishlist, reading map) and the unused ops/proxmox templates; generalized remaining mentions of hosts, VPN and Windows-only commands. - Defaults: TZ and reading time zone UTC; .gitattributes keeps shell/YAML LF. - Personal agent coordination files are git-ignored (CLAUDE.local.md, .claude/skills/manga-org/). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
7.5 KiB
Markdown
120 lines
7.5 KiB
Markdown
# Runtime resilience and agent operations
|
|
|
|
## Ownership and failure boundaries
|
|
|
|
```text
|
|
Browser → Reader ──private API──→ Scraper → library (atomic chapter publication)
|
|
└─ stall watchdog: bounded checkpoint recovery only
|
|
|
|
Supervisor(reader) Supervisor(scraper)
|
|
↑
|
|
Coding agent: inspect incidents → reproduce → patch → test → release
|
|
```
|
|
|
|
The reader is the only writer of users/progress and the catalog. The scraper is the
|
|
only queue owner and content publisher, and it records its own failures and incidents.
|
|
Each service's supervisor owns only that child process. The coding agent is an
|
|
**escalation/maintenance operator**, not a necessary part of normal downloading.
|
|
A periodic check by an operator or coding agent is an additional backstop, not the runtime watchdog.
|
|
|
|
## Automatic recovery implemented
|
|
|
|
| Failure | Action | Automatic limit |
|
|
|---|---|---|
|
|
| Process exit or repeated failed liveness checks | Supervisor restarts only that service, with backoff | Three failures per rolling hour; then parked for operator action |
|
|
| Network/HTTP 5xx | Preserve checkpoint; open source circuit, wait five minutes, retry job | One job-level retry; request-level retries remain bounded |
|
|
| Title unavailable/no English chapters | Record unavailable, continue unrelated work | No blind retry |
|
|
| Parser/local adapter failure | Quarantine job, keep partial data, continue other titles | Two different affected jobs in an hour pause source as systemic failure |
|
|
| Storage/permission failure | Pause all downloads and report | No automatic retry |
|
|
| HTTP 401/403/429 or access challenge | Pause affected source lane | No automatic retry/bypass |
|
|
| Active job with no progress for 15 minutes | Scraper stall watchdog stops it at its next checkpoint and requeues it | One stall recovery; next stall quarantined |
|
|
|
|
The stall watchdog runs inside the scraper (`ScraperService.checkStalls`, every 30 s
|
|
from the work loop). A job is stalled when its checkpoint fingerprint (completed
|
|
chapters/pages) has not moved for 15 minutes, or for one chapter delay plus three
|
|
minutes if that is longer. Time spent paused does not count, and a deliberately
|
|
paused queue is not a stall. It only treats a job as stalled when its checkpoint can
|
|
be read: the scraper resolves source-specific work folders (including MangaPill's
|
|
`mangapill-` prefix), and a missing checkpoint is not evidence that downloads stopped.
|
|
Each stall records an incident with `failure.kind: 'stall'` and an `events.log` entry.
|
|
An operator retry resets the job's stall recovery count without deleting its partial
|
|
download. Timeouts bound source fetches to 60 seconds and metadata calls to 30 seconds.
|
|
Publication remains one complete chapter at a time, on the same filesystem.
|
|
No recovery action raises pacing, creates another worker for the same source,
|
|
changes identities or deletes partial images.
|
|
|
|
The publisher retains validated pages in `.staging-mcscraper-resume-<sha256>` folders
|
|
on pause/failure. It checks the full chapter/source/page-manifest fingerprint before
|
|
reuse and revalidates each image; completed chapters still publish with one rename.
|
|
A changed manifest creates separate staging rather than mixing releases. Old random
|
|
stages remain untouched for manual review: they cannot be safely associated from
|
|
page filenames alone. Hidden pending image files are never treated as completed pages.
|
|
|
|
`retry_wait`, `unavailable` and `quarantined` are explicit queue states in schema 4.
|
|
`ready` is a healthy inspected job awaiting operator download confirmation, not a
|
|
stall. Inspection shares the source lane; it never downloads images or bypasses a
|
|
source circuit. Source coverage and job completion are separate observations.
|
|
Legacy unavailable-title errors are migrated; only pauses solely attributable to
|
|
these unavailable titles are released. Access and unknown failures remain paused.
|
|
Nightly discovery avoids duplicating outstanding, unavailable or quarantined jobs;
|
|
use an explicit operator retry after fixing their cause.
|
|
|
|
## Observability
|
|
|
|
**Settings → Service health** shows scraper reachability ("Scraper: unreachable" when
|
|
the reader cannot reach it), active checkpoints, failed jobs and the scraper's incident
|
|
history. Process crashes are handled by the supervisor and Docker restart policy, not
|
|
recorded as incidents. **Settings → Scraper** retains queue, pacing and discovery controls.
|
|
Service errors are classified rather than buried in a single historical `lastError`.
|
|
`npm run check` performs read-only checks of reader and scraper.
|
|
|
|
Scraper status is independent of expensive library scans. Concurrent admin/health
|
|
probes share one background inventory refresh and receive the previous summary
|
|
immediately. The dashboard summary refreshes at most once per minute unless a
|
|
publication or metadata change invalidates it; duplicate-sensitive decisions still
|
|
request a fresh inventory. `libraryStatus` reports ready/refreshing/stale/updated/error, and failed
|
|
background refreshes do not spawn a new scan on every UI poll. Authoritative
|
|
discovery waits for the current inventory; publication/metadata invalidation during
|
|
a scan forces another scan before it can be used for those decisions. Cold start
|
|
may temporarily show no library targets, but queue and rate controls remain usable.
|
|
|
|
Storage:
|
|
|
|
- `data/catalog.json`, `data/collection-covers/`: reader catalog and covers.
|
|
- `data/scraper/queue.json`, `events.log`: queue, circuits, failures and incidents
|
|
(including stalls).
|
|
- `data/runtime-reader.json`, `data/scraper/runtime.json`: supervisor status and
|
|
persisted crash budget.
|
|
- `data/sentinel/`: only on installs from before 2026-10-22; unused, safe to delete.
|
|
- `data/scraper/supervisor.lock`, `daemon.lock`: native process ownership; transient.
|
|
|
|
Scraper and supervisor emit structured logs. The queue history retains
|
|
200 incidents; the UI shows a bounded subset. Docker logs are rotated. Native supervisor logs
|
|
(`tools/run-supervisor.ps1`) remain append-only: archive them during stopped maintenance if needed.
|
|
Never export user databases, tokens, cookies, signed query strings or unredacted environments.
|
|
|
|
## What self-repair intentionally does not mean
|
|
|
|
The stall watchdog cannot execute a shell, patch adapters, access a Docker socket,
|
|
or restart processes; it only requeues or quarantines a job. Nothing at runtime
|
|
repairs broken markup or solves source challenges. The agent handles those escalations in the repository:
|
|
|
|
1. Inspect one bounded incident and relevant checkpoint; preserve the active lane.
|
|
2. Add a fixture reproducing the failure, patch the smallest adapter/logic surface.
|
|
3. Run `npm test`; test UI/API in isolated data if affected.
|
|
4. Build a traceable image, deploy checkpoint-safely, verify resumed progress.
|
|
5. Record release, backup and rollback compatibility. Stop after repeated identical failures.
|
|
|
|
A VM-local monitor cannot alert on total VM/network failure. Before VM cutover choose
|
|
an external health check and notification destination (e.g. an existing monitoring
|
|
service and private ntfy/Gotify). No notification credentials or destination are invented.
|
|
|
|
## Crash-budget operation
|
|
|
|
A parked supervisor stays alive and reports `repair-required`; Docker's restart policy
|
|
does not create an unlimited process restart storm. Its health check fails while the
|
|
child is absent. Investigate first. Once repaired, restart the wrapper after its
|
|
rolling one-hour budget has expired. For an intentional earlier reset, first stop the
|
|
service, back up its `runtime*.json`, and clear only that service's `failures` array;
|
|
never clear the queue or daemon checkpoint. Explicit deployments do not erase budgets.
|