Simplify README structure and wording

This commit is contained in:
Dymas
2026-05-17 22:14:10 +02:00
parent 5c4f251fa6
commit 70a8acfb18
+162 -258
View File
@@ -1,63 +1,60 @@
# ani-cli web # ani-cli web
A local web UI for a system-wide `ani-cli` install. Small local web UI for a system-wide `ani-cli` install.
Current version: `0.32.2` Current version: `0.32.5`
## Project Layout ## Project Layout
```text ```text
ani-cli-web/ ani-cli-web/
app.py Public entrypoint, runtime wiring, watchlist domain logic, and CLI startup. app.py Main entrypoint and runtime wiring.
app_support.py Shared runtime/state/config helpers plus queue and library file utilities. app_support.py Shared config, state, and helper utilities.
Dockerfile Container image build for system-wide ani-cli plus /app ani-cli-web. Dockerfile Container image build.
docker-compose.yaml Compose service for building and running the container with persistent mounts. docker-compose.yaml Example Compose service.
queue_jobs.py Download queue and background watchlist-refresh workers. queue_jobs.py Download queue and watchlist refresh workers.
watchlist_identity.py Safer watchlist identity resolution for queued download jobs. watchlist_identity.py Download-to-watchlist identity matching.
title_matching.py Shared anime-title normalization and matching helpers. title_matching.py Shared title normalization and matching.
http_handler.py HTTP route handling, request parsing, and remote-access guard. http_handler.py HTTP routing and access control.
web_templates.py Shared Search, Config, and Watchlist page markup aggregation. web_templates.py Shared page aggregation.
docker-entrypoint.sh Container startup wrapper with optional ani-cli self-update.
search_page.py Search page template. search_page.py Search page template.
config_page.py Config page template. config_page.py Config page template.
watchlist_page.py Watchlist page template. watchlist_page.py Watchlist page template.
template_helpers.py Shared page-shell navigation and sidebar brand helpers. docker-entrypoint.sh Container startup wrapper.
ani-cli-web Launcher script. ani-cli-web Launcher script.
test_app.py Regression tests with isolated temp-state setup. test_app.py Regression tests.
VERSION Project version. VERSION Project version.
CHANGELOG.md Change history. CHANGELOG.md Change history.
README.md Project documentation. README.md Project documentation.
``` ```
## Run ## Quick Start
Start the web app with: Run the app:
```sh ```sh
./ani-cli-web ./ani-cli-web
``` ```
For thumbnail debugging, run: Run with debug logging:
```sh ```sh
./ani-cli-web --debug ./ani-cli-web --debug
``` ```
The launcher also accepts `---debug`. You can also enable debug logging with `ANI_CLI_WEB_DEBUG=1`. Open:
Then open:
```text ```text
http://127.0.0.1:8421/ http://127.0.0.1:8421/
``` ```
By default the server binds to `127.0.0.1:8421`. Default bind address is `127.0.0.1:8421`.
## Docker ## Docker
The container image installs `ani-cli` system-wide and clones `ani-cli-web` into `/app` during `docker build`. The image installs `ani-cli` system-wide and clones `ani-cli-web` into `/app` during build.
Default build arguments: Default build args:
```text ```text
ANI_CLI=https://github.com/pystardust/ani-cli.git ANI_CLI=https://github.com/pystardust/ani-cli.git
@@ -65,23 +62,23 @@ ANI_CLI_BRANCH=master
ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git
``` ```
Build the image: Build:
```sh ```sh
docker build -t ani-cli-web . docker build -t ani-cli-web .
``` ```
Override the `ani-cli` branch when needed: Build with a custom `ani-cli` branch:
```sh ```sh
docker build \ docker build \
--build-arg ANI_CLI_BRANCH=my-feature-branch \ --build-arg ANI_CLI_BRANCH=my-branch \
--build-arg ANI_CLI=https://github.com/pystardust/ani-cli.git \ --build-arg ANI_CLI=https://github.com/pystardust/ani-cli.git \
--build-arg ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git \ --build-arg ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git \
-t ani-cli-web . -t ani-cli-web .
``` ```
Run the container with the requested persistent mounts: Run:
```sh ```sh
docker run --rm \ docker run --rm \
@@ -94,318 +91,225 @@ docker run --rm \
ani-cli-web ani-cli-web
``` ```
Or start the same setup with Compose: Compose:
```sh ```sh
docker compose up --build -d docker compose up --build -d
``` ```
To keep mounted downloads owned by your host user, start Compose with: Use host ownership for mounted downloads:
```sh ```sh
USER_UID="$(id -u)" USER_GID="$(id -g)" docker compose up --build -d USER_UID="$(id -u)" USER_GID="$(id -g)" docker compose up --build -d
``` ```
The included `docker-compose.yaml` uses the same defaults as the `Dockerfile`, mounts `./downloads` to `/downloads`, mounts `./.ani-cli-web` to `/app/.ani-cli-web`, and lets you override build/runtime settings through environment variables such as: Container notes:
```text
ANI_CLI=https://github.com/pystardust/ani-cli.git
ANI_CLI_BRANCH=master
ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git
ANI_CLI_WEB_PORT=8421
UPDATE_ON_START=false
USER_UID=
USER_GID=
ANI_CLI_WEB_ALLOW_REMOTE=false
ANI_CLI_WEB_REMOTE_TOKEN=
```
Container defaults:
- `ani-cli` is installed at `/usr/local/bin/ani-cli`. - `ani-cli` is installed at `/usr/local/bin/ani-cli`.
- `ani-cli-web` is cloned into `/app` and started from there. - `ani-cli-web` runs from `/app`.
- Container startup now runs `python3 /app/app.py` directly, so it does not depend on the `ani-cli-web` launcher file having executable permissions inside the image. - Downloads go to `/downloads`.
- Downloads are written to `/downloads`, so bind-mount `./downloads:/downloads`. - App state lives in `/app/.ani-cli-web`.
- App state stays under `/app/.ani-cli-web`, so bind-mount `./.ani-cli-web:/app/.ani-cli-web`. - The container binds `0.0.0.0:8421`.
- The image sets `ANI_CLI_WEB_HOST=0.0.0.0` and exposes port `8421`. - `UPDATE_ON_START=true` runs `sudo ani-cli --update` before startup.
- `UPDATE_ON_START=true` runs `sudo ani-cli --update` before the web app starts. - `USER_UID` and `USER_GID` keep mounted files from being owned by `root`.
- Set `USER_UID` and `USER_GID` together to run the web app as a specific numeric host user/group, which keeps new files on mounted folders such as `./downloads` from being owned by `root`.
The installed `ani-cli` update URL is rewritten during the image build so `ani-cli --update` follows the same `ANI_CLI_BRANCH` you built into the image when the `ANI_CLI` remote is a GitHub repository. ## What It Does
## Overview - Search AllAnime in `sub` or `dub`.
- Queue downloads with editable folder, quality, season, and episode range.
- Save defaults on `/config`.
- Track shows on `/watchlist`.
- Refresh watchlist episode counts in the background.
- Schedule periodic watchlist refresh jobs.
- Cache watchlist thumbnails locally.
- Sync successful downloads back into the watchlist.
- Keep queue and watchlist data in SQLite under `.ani-cli-web/`.
`ani-cli-web` keeps search and download management on `/`, saved defaults on `/config`, and the watchlist on `/watchlist`, all with the same sidebar navigation layout and a Tsuki-inspired visual style. ## Pages
The app currently includes: ### Search Page
- Search against AllAnime with `sub` or `dub` mode selection. Path: `/`
- Episode loading for the selected result.
- Queueing downloads with editable library name, season, episode range, quality, and target folder.
- A dedicated Config page for saved default folder, mode, and quality settings.
- Malformed config save requests now return `400 Bad Request` when the JSON body is not an object, instead of resetting values back to defaults.
- A paged SQLite-backed download queue with retry, cancel, remove, retry-failed, and clear-finished actions.
- A paged SQLite-backed watchlist with summary cards, per-show refresh, bulk refresh, source links, and thumbnail tools.
- Duplicate watchlist adds now return `200 OK` with `created: false`, keeping the API status aligned with the no-op result.
- Malformed watchlist add requests now also return `400 Bad Request` when the JSON body is not an object.
- Watchlist category tabs for `Watching`, `Planned`, `Finished`, and `Dropped`, with per-anime category editing.
- AnimeSchedule-backed watchlist completion tracking that compares live sub/dub availability with the expected total episode count when known.
- Automatic watchlist sync after successful downloads, including downloaded-vs-manual finished markers.
- Download sync now also reconciles unique title matches when ani-cli resolves a different `show_id`, so existing watchlist entries keep their category and still get marked as downloaded.
- Per-show background watchlist refresh queueing so adding a show or syncing a finished download does not block the request on upstream episode, schedule, and thumbnail lookups.
- Queued per-show watchlist refresh work now survives app restarts and resumes from rows still marked `queued`.
- Per-show watchlist refresh queueing now also deduplicates in-flight refreshes, so the same show is not fetched twice back-to-back while one refresh is already running.
- The watchlist page now silently refreshes itself while queued per-show refresh work is still being processed, so queued entries settle into their updated state without a manual reload.
- The watchlist page now also avoids re-triggering the same unresolved thumbnail warm-up request on every silent poll cycle while a show's thumbnail state is unchanged.
- Watchlist cards with both sub and dub fully ready now get a thin green border for quicker visual scanning, regardless of which category tab they are in.
- Background `Refresh All` execution with status polling, no inline thumbnail downloads during the bulk job, and a configurable delay between shows to reduce upstream request bursts.
- Optional scheduled background watchlist refreshes with Config page controls for enable/disable and refresh period.
- Scheduled background refresh runs now log queue, start, and completion status to stdout so container logs show whether they finished successfully, with errors, or failed.
- Routine browser polling endpoints such as the search-page queue poll and watchlist refresh-status poll now stay quiet in stdout request logs to reduce log spam.
- Queue job logs on the Search page now render newest lines first for easier at-a-glance progress checking.
- Search and Watchlist polling now use a shared serial browser polling helper so slow responses do not cause overlapping poll requests to pile up in the background.
- That shared browser polling helper now also tears itself down on page unload and uses chained `setTimeout` scheduling instead of a bare repeating interval.
- Controlled shutdown and runtime reset now cancel active download workers when waiting for teardown, reducing the chance of orphaned background downloads outliving the web process.
- Runtime reset now also refuses to clear the active global runtime when background workers fail to stop cleanly, preventing stale workers from outliving the runtime object that created them.
- Watchlist refresh networking now uses a shorter dedicated timeout, which helps per-show and bulk watchlist shutdown paths settle more predictably when upstream metadata services are slow.
- Persistent watchlist refresh history that survives restarts and marks interrupted bulk refresh jobs clearly.
- Local thumbnail caching plus manual thumbnail upload when automatic cover lookup fails.
- A refreshed glassy dark UI inspired by the Tsuki frontend design language.
- Shared page templates and backend services split into dedicated modules while keeping `app.py` and `web_templates.py` as stable public aggregation points.
- Broader regression coverage for higher-risk queue, watchlist, remote-access, and handler flows.
- Search and watchlist requests guard against stale out-of-order async responses during rapid repeated actions.
- Search-page queue polling also guards against stale out-of-order refresh responses now.
- Queue log writes are batched during active downloads to reduce SQLite churn on noisy jobs.
- Queue persistence now stores core job metadata and logs in structured SQLite columns instead of treating the whole job as one large JSON blob.
- Queue worker startup failures now degrade into a normal failed job state instead of crashing the background queue thread.
- Unexpected post-launch queue worker failures now also fail the active job cleanly instead of leaving it stuck in `running`.
- Failed and canceled downloads now clean their project-local staging directories automatically so partial files do not accumulate under `.ani-cli-web/staging/`.
- Failed thumbnail refresh attempts now record a retry timestamp, so unresolved covers back off instead of immediately requerying upstream sources on every later warm-up.
- Download-to-watchlist fallback matching now keeps season and part identity intact while still tolerating equivalent season notation such as `2nd Season` vs `Season 2`.
- Project-local runtime state under `.ani-cli-web/`.
## Search Page
The main page at `/` is where you search and queue downloads.
Workflow:
1. Search by anime title or keyword.
2. Pick a result to load available episodes.
3. Adjust library name, season, episode range, quality, and download folder.
4. Add the selected show either to the watchlist or directly to the queue.
The page also includes:
- A live queue view that refreshes automatically.
## Config Page
The Config page at `/config` is where saved defaults now live.
Use it to: Use it to:
1. Set the default download folder. 1. Search for an anime.
2. Choose the default `sub` or `dub` search mode. 2. Load episodes.
3. Choose the default search quality used to prefill the Search page. 3. Adjust folder, quality, season, and episode range.
4. Enable or disable periodic watchlist refresh checks. 4. Add the show to the queue or watchlist.
5. Set the refresh period in minutes for that scheduled watchlist job.
6. Set the delay in seconds between shows during bulk watchlist refresh runs.
Those saved defaults are written into the project-local `config.json` and loaded automatically when the Search page opens. Highlights:
The Config page also shows the current app version plus dependency status in a compact centered layout. - Live queue view with automatic polling.
- Queue log shows newest lines first.
- Search polling avoids overlapping requests and stale responses.
## Access Guard ### Config Page
By default, `ani-cli-web` only serves loopback clients such as `127.0.0.1` and `::1`. Path: `/config`
If you intentionally want LAN or remote access, set: Use it to set:
```sh 1. Default download folder.
ANI_CLI_WEB_ALLOW_REMOTE=1 2. Default `sub` or `dub` mode.
ANI_CLI_WEB_REMOTE_TOKEN=choose-a-long-random-token 3. Default quality.
``` 4. Scheduled watchlist refresh enable or disable.
5. Scheduled refresh period in minutes.
6. Delay between shows during `Refresh All`.
Non-local clients must then send either: Saved settings are written to `.ani-cli-web/config.json`.
- `Authorization: Bearer <token>` ### Watchlist
- `X-AniCli-Web-Token: <token>`
For browser access from another machine, you can also open the app once with: Path: `/watchlist`
```text Categories:
http://YOUR-HOST:8421/?token=your-token-here
```
The server will set an HTTP-only cookie and redirect to the same page without the token in the URL, so normal browser navigation and API polling continue to work afterward.
When the app is placed behind a loopback reverse proxy, forwarded external client IP headers such as `X-Forwarded-For` are also treated as non-local, so proxied internet or LAN traffic still needs the token.
This keeps the queue, config, and watchlist APIs safer when the app is intentionally exposed beyond localhost.
Malformed watchlist action payloads now return `400 Bad Request` when required JSON fields are missing, instead of surfacing those client mistakes as `404 Not Found`.
## Download Queue
The queue is stored in SQLite and shown 10 jobs at a time.
Core queue fields and logs are now stored in structured SQLite columns, while JSON payload storage is kept only for extra fields that do not have dedicated columns yet.
Queue actions currently available:
- `Retry` for finished or failed jobs.
- `Cancel` for pending or running jobs.
- `Remove` for completed or failed jobs.
- `Retry failed` for all failed jobs.
- `Remove finished` for completed jobs.
Downloads are staged inside the project-local app state first. After `ani-cli` finishes, files are moved into a Plex/Jellyfin-friendly layout:
```text
ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4
```
## Watchlist
The watchlist stores one row per show in SQLite, keeps separate `sub` and `dub` episode counts, and assigns each show to one of four categories:
- `Watching` - `Watching`
- `Planned` - `Planned`
- `Finished` - `Finished`
- `Dropped` - `Dropped`
You can add entries in two ways: Ways to add shows:
1. From the Search page with `Add to watchlist`, choosing the category first. 1. From the Search page.
2. Manually on `/watchlist` with a title, AllAnime `show_id`, and category. 2. Manually on `/watchlist`.
Watchlist features: Main behavior:
1. `Refresh` updates one tracked show. 1. `Refresh` updates one show.
2. `Refresh All` runs in the background, rejects duplicate queueing while a refresh is already pending or running, preserves interrupted job status across restarts, and refreshes shows one by one with a configurable delay without doing inline thumbnail downloads for every entry. 2. `Refresh All` runs in the background.
3. `Open` jumps back to the Search page with the title pre-filled. 3. `Open` jumps back to Search.
4. Clicking the anime title opens the AllManga source page at `bangumi/<show_id>` in a new tab. 4. Clicking the title opens the source page.
5. `Remove` deletes the watchlist entry and clears its cached poster file. 5. `Remove` deletes the entry and cached poster.
6. Poster cards use a compact multi-column grid with pagination at 30 cards per page. 6. Each card has an inline category selector.
7. Summary cards show tracked shows plus total `sub` and `dub` episode counts. 7. Summary cards show total tracked shows plus `sub` and `dub` counts.
8. Episode pills show `current / total` progress when AnimeSchedule exposes the planned episode count. 8. Pagination shows 30 cards per page.
9. `Sub` and `Dub` pills turn green and gain `Sub ready` or `Dub ready` tags when that release track appears fully finished and ready for download.
10. Each category has its own tab on the watchlist page.
11. Every card includes an inline category selector so you can move tracked anime between tabs at any time.
12. Finished shows display a small check badge: green means the anime was downloaded through the app, gray means it was marked finished manually.
### Download Sync Status behavior:
Successful downloads now feed back into the watchlist automatically. - `Sub` and `Dub` pills turn green when that track looks complete.
- Cards with both `sub` and `dub` fully ready get a thin green border.
- Finished cards show a small check badge.
- Green check means downloaded through the app.
- Gray check means finished manually.
Current behavior: Download sync:
- If the anime is already on the watchlist, a successful download marks it as downloaded and keeps its current category. - Existing watchlist entries stay in their current category when a download succeeds.
- If the anime is not yet on the watchlist, `ani-cli-web` creates a new watchlist entry in `Finished` after the download succeeds. - Missing entries are added to `Finished`.
- Those add and download-sync paths now queue a background refresh for episode counts, AnimeSchedule metadata, and thumbnails instead of waiting for all upstream fetches before returning. - If ani-cli resolves a different `show_id`, the app can reconcile a unique title match instead of creating a duplicate finished row.
- If a queued job ever reaches the older search-based fallback path for `show_id` resolution, the app now only accepts that fallback when the queued title still matches the returned result confidently, preferring to fail safe over marking the wrong anime as downloaded.
- That fallback now also preserves season and part distinctions, so `Season 2` downloads do not silently sync against a different season with the same base title.
- When that safer fallback still cannot resolve a `show_id`, the failure now keeps the specific reason so logs and error messages are easier to diagnose.
- Existing watchlist entries from older versions are migrated into the `Watching` category automatically.
### Thumbnail Behavior Thumbnail behavior:
Watchlist thumbnails use the local `/api/watchlist/thumb/...` route, not direct remote image URLs. - Thumbnails are served from the local `/api/watchlist/thumb/...` route.
- Cached thumbnails are reused.
- Missing covers are warmed in small batches.
- Failed lookups back off before retrying.
- You can force `Reload` or use `Upload` when no thumbnail exists.
Current thumbnail behavior: Refresh behavior:
- Cached thumbnails render from the local project cache when available. - Per-show refreshes are queued in the background.
- The page adds a fresh nonce to thumbnail URLs to avoid stale browser-cached misses. - Queued refresh state survives restarts.
- Missing visible covers are warmed up in small batches instead of one request per card all at once. - Duplicate refresh queueing is avoided.
- The thumbnail warm-up API now requires `show_ids` to be a JSON array and accepts explicit boolean-style `force` values such as `true` and `false`. - Scheduled refresh runs log start, stop, success, and failure to stdout.
- The page only retries an unresolved cover warm-up once per thumbnail state change during the current browser session, so silent watchlist polling does not keep hammering the same missing poster. - `Refresh All` works one show at a time with a configurable delay.
- If a show disappears while a thumbnail warm-up batch is running, that entry is skipped cleanly instead of failing the whole batch request.
- Thumbnail image tags only hit the local route for already-cached files, so opening the watchlist no longer triggers a per-card remote lookup burst.
- AnimeSchedule is tried first for cover lookup.
- Stale stored AnimeSchedule routes and AniDB IDs now fall back to fresh title-based lookups instead of aborting the whole thumbnail refresh attempt.
- If a season-labeled title fails as-is, lookup retries with simplified base-title queries.
- AniDB is used as a fallback when AnimeSchedule does not resolve a cover.
- AniDB fallback reuses the cached parsed title list directly to keep repeated lookups lighter.
- Failed automatic fetch attempts now start the retry window, so bad lookups back off instead of hammering AnimeSchedule and AniDB on every warm-up pass.
### Watchlist Pagination ## Access Guard
Watchlist listing responses now clamp out-of-range page requests to the last available page before querying rows, so the returned `page` number and `items` stay consistent even when the browser or API client asks for a page past the end. By default the app only serves loopback clients such as `127.0.0.1` and `::1`.
Per-card thumbnail tools: To allow LAN or remote access:
1. `Reload` forces a fresh redownload attempt for that show's poster. ```sh
2. `Upload` appears when no thumbnail is currently available and lets you assign a local image manually. ANI_CLI_WEB_ALLOW_REMOTE=1
3. Hovering the poster tile or card shows the current thumbnail source hint. ANI_CLI_WEB_REMOTE_TOKEN=choose-a-long-random-token
```
Manual uploads are saved into the same local thumbnail cache used by automatic cover downloads. Non-local clients must send one of:
### Completion Detection - `Authorization: Bearer <token>`
- `X-AniCli-Web-Token: <token>`
Watchlist refresh still uses AllAnime, the same upstream source that `ani-cli` uses, to count which `sub` and `dub` episodes are actually available right now. Browser bootstrap also works:
To decide whether a release track looks complete, `ani-cli-web` also checks AnimeSchedule for: ```text
http://YOUR-HOST:8421/?token=your-token-here
```
- the anime's expected total episode count The app stores the token in an HTTP-only cookie and redirects to the clean URL.
- the upstream airing status such as `Finished` or `Ongoing`
When AnimeSchedule reports a known total and `Finished`, the watchlist compares that total with the live AllAnime `sub` and `dub` counts: ## Download Queue
- `Sub` becomes ready when available sub episodes reach the expected total. The queue is stored in SQLite and shown 10 jobs at a time.
- `Dub` becomes ready when available dub episodes reach the expected total.
If AnimeSchedule does not know the total yet, the watchlist keeps showing the plain available-count value without a completion highlight. Available actions:
- `Retry`
- `Cancel`
- `Remove`
- `Retry failed`
- `Remove finished`
Downloads are staged first, then moved into a media-friendly layout:
```text
ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4
```
## Runtime State ## Runtime State
All runtime state is stored under: All local state is stored under:
```text ```text
ani-cli-web/.ani-cli-web/ ani-cli-web/.ani-cli-web/
``` ```
That folder currently holds: Main contents:
- `config.json` - `config.json`
- `state.sqlite3` - `state.sqlite3`
- legacy `queue.json` migration input when present
- legacy `watchlist.json` migration input when present
- `thumbnails/` - `thumbnails/`
- AniDB title cache data - AniDB title cache data
- staging files for active downloads - staging files for active downloads
Legacy files from `~/.config/ani-cli-web/` and `~/.local/state/ani-cli-web/` are migrated into the project-local folder automatically on startup. Legacy state from older locations is migrated on startup.
The earlier project-local `queue.sqlite3` filename is also migrated automatically into `state.sqlite3`.
## Configuration ## Configuration
Environment variables: Environment variables:
- `ANI_CLI_BIN`: path or command name for `ani-cli`; defaults to `ani-cli` from `PATH`. - `ANI_CLI_BIN`: path or command name for `ani-cli`
- `ANI_CLI_DOWNLOAD_DIR`: initial default download folder. - `ANI_CLI_DOWNLOAD_DIR`: initial default download folder
- `ANI_CLI_MODE`: initial mode, `sub` or `dub`. - `ANI_CLI_MODE`: initial mode, `sub` or `dub`
- `ANI_CLI_QUALITY`: initial quality such as `best`, `1080`, or `720`. - `ANI_CLI_QUALITY`: initial quality such as `best`, `1080`, or `720`
- `ANI_CLI_WEB_HOST`: server host, default `127.0.0.1`. - `ANI_CLI_WEB_HOST`: server host, default `127.0.0.1`
- `ANI_CLI_WEB_PORT`: server port, default `8421`. - `ANI_CLI_WEB_PORT`: server port, default `8421`
- `ANI_CLI_WEB_ALLOW_REMOTE`: allow non-loopback clients when set to `1`, `true`, `yes`, or `on`. - `ANI_CLI_WEB_ALLOW_REMOTE`: allow non-loopback clients
- `ANI_CLI_WEB_REMOTE_TOKEN`: required shared secret for non-loopback clients when remote access is enabled. - `ANI_CLI_WEB_REMOTE_TOKEN`: shared secret for remote access
- `ANI_CLI_WEB_DEBUG`: enable debug logging when set to `1`, `true`, `yes`, or `on`. - `ANI_CLI_WEB_DEBUG`: enable debug logging
- `ANI_CLI_WEB_STATE_ROOT`: override the project-local state directory path; useful for isolated test runs or custom storage locations. - `ANI_CLI_WEB_STATE_ROOT`: override the project-local state directory
- `UPDATE_ON_START`: when set to `1`, `true`, `yes`, or `on` in the container, runs `sudo ani-cli --update` before `ani-cli-web` starts. - `UPDATE_ON_START`: run `sudo ani-cli --update` in the container before startup
- `USER_UID` and `USER_GID`: optional numeric UID/GID pair for running the container process as a specific host user/group so mounted files are created with that ownership. - `USER_UID` and `USER_GID`: run the container as a specific host user/group
Saved defaults from the UI are written into the project-local `config.json`. Saved config fields include:
The saved config now also includes `watchlist_auto_refresh_enabled`, `watchlist_auto_refresh_minutes`, and `watchlist_refresh_delay_seconds`, which control the periodic background watchlist refresh schedule and the per-show pacing used by bulk refresh jobs. - `watchlist_auto_refresh_enabled`
- `watchlist_auto_refresh_minutes`
- `watchlist_refresh_delay_seconds`
## Testing
Run the regression suite with:
```sh
python3 -m unittest test_app.py
```
## Notes ## Notes
- Importing `app.py` no longer boots the runtime worker threads automatically; runtime setup is deferred until startup or first use. - Runtime boot is lazy. Importing `app.py` does not immediately start worker threads.
- Importing `app_support.py` no longer migrates or creates runtime state on its own; migrations now happen during runtime bootstrap. - Queue and watchlist data share the same SQLite database: `.ani-cli-web/state.sqlite3`.
- Cheap routes such as the page shells, `/api/version`, `/api/dependencies`, and `/api/config` do not need to boot the full runtime before responding. - Runtime folders such as `.ani-cli-web/` and `downloads/` are ignored by git.
- The CLI startup path itself also stays lazy now; runtime bootstrap happens when the first runtime-backed request arrives. - The Docker image clones both upstream repositories during build.
- Watchlist and queue data are stored in the same project-local SQLite database file, `state.sqlite3`.
- Runtime folders such as `.ani-cli-web/`, `downloads/`, and Python bytecode cache directories at any depth are ignored by git.
- The Docker image clones both upstream repositories during build, so the app source inside the container comes from the configured git remotes rather than the local build context.
- Focused regression tests live in `test_app.py`, run against an isolated temporary state root, and can be started with `python3 -m unittest test_app.py`.