A Flask website that stores and scans Kometa and ImageMaid log files, presents recommendations, creates opaque shareable result URLs, and maintains a unified People Poster processing queue. Each scan result has a separate deletion token.
Log type is detected from content, including mixed Kometa/ImageMaid archives. ImageMaid results include versions, modes, runtime, errors, and reported cleanup counts and recovered space. Analytics groups default to closed accordions, and the main page shows cumulative ImageMaid cleanup totals.
Identical extracted log contents reuse an existing, unexpired scan across web and Discord uploads. Each uploader receives a new private management token and can see their own signed-in upload. Removing an upload retains a shared scan for its other uploaders; support can remove the entire scan. Expired or fully deleted logs may be uploaded again. Reused scans do not increase aggregate processed-log or cleanup totals. Anonymous aggregate totals survive retention cleanup; log content and identities do not.
Quickstart checks /api/capabilities before uploading ImageMaid logs. Update the
web service before enabling the updated Quickstart and Discord integration.
- Python 3.13
- Large archive members are extracted to disk and scanned with bounded memory; allow enough disk space for the 1 GiB extracted limit
- Supported archives: .zip, .7z, .tar, .tar.gz, .tgz, .gz, .tar.bz2, .tbz2, .bz2, .tar.xz, .txz, .xz, .tar.zst, and .zst
- Windows, Linux, or macOS
Open PowerShell in the cloned Logscan directory:
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
Copy-Item .env.example .env
python -m logscan_web.appThis starts Flask's development server. Open http://127.0.0.1:5000.
Stop it with Ctrl+C.
To test with the same Windows WSGI server used for production-like deployments:
waitress-serve --listen=127.0.0.1:5010 --threads=1 logscan_web.app:appThen open http://127.0.0.1:5010. Using port 5010 avoids conflicts when
another local service already owns port 5000.
If PowerShell blocks virtual-environment activation, run this once in the current PowerShell window:
Set-ExecutionPolicy -Scope Process Bypasspython3.13 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
cp .env.example .env
python -m logscan_web.appOpen http://127.0.0.1:5000 and stop the development server with Ctrl+C.
The application loads .env from the current working directory for local
development. Real process environment variables take precedence.
LOGSCAN_API_KEYauthenticates automated log uploads. Generate a long random value.DISCORD_CLIENT_IDandDISCORD_CLIENT_SECRETidentify a Discord application used for account sign-in.DISCORD_BOT_TOKENenables live support-role revalidation on privileged requests. The bot must belong to the configured guild and its token must remain secret.DISCORD_REDIRECT_URImust exactly match the application's OAuth redirect; production useshttps://logscan.kometa.team/support/callback.DISCORD_GUILD_IDis the Kometa Discord server ID.DISCORD_SUPPORT_ROLE_IDSis a comma-separated allowlist of support or administrator role IDs. Holding any listed role grants access.LOGSCAN_SECRET_KEYsigns browser sessions. Generate a separate long random value and keep it stable across restarts.LOGSCAN_SECURE_COOKIES=truerestricts support sessions to HTTPS in production.TMDB_API_KEYenables TMDb identity resolution, profile images, and trending people.DISCORD_PEOPLE_WEBHOOK_URLoptionally announces newly discovered missing people.SCAN_STOREselects persistent storage. For local testing,./data/scanskeeps data inside the checkout; containers use/data/scans.
The unified People page is available at http://127.0.0.1:5000/people when using Flask, or the equivalent path on the selected Waitress port.
The protected support inventory is available at /support/logs. In the Discord
Developer Portal, register the configured callback under OAuth2 Redirects.
Enable Discord Developer Mode to copy the server and role IDs. Sign-in requests
only identify and guilds.members.read and store no Discord OAuth access token.
Account sessions persist for 30 days. When DISCORD_BOT_TOKEN is configured,
support membership is revalidated against Discord on each request (including users
who gained a support role after signing in), and access is denied if validation
fails; repeated checks within one request share a single lookup. Without a bot
token, the role assertion expires after eight hours and requires a fresh OAuth
sign-in.
Do not use Flask's development server for a public deployment.
Linux production command:
gunicorn --workers 1 --threads 4 --timeout 900 --bind 0.0.0.0:8000 logscan_web.app:appWindows production command:
waitress-serve --listen=0.0.0.0:8000 --threads=4 logscan_web.app:appOne process is intentional because scan job status is held in memory. Four threads keep status polling responsive while the single background scan worker serializes memory-intensive scans. Put a reverse proxy and rate limiting in front of a public instance.
docker build -t kometa-logscan-web .
docker run --rm -p 8000:8000 \
-e LOGSCAN_API_KEY=replace-me \
-e TMDB_API_KEY=your-tmdb-api-key \
-v logscan-data:/data \
kometa-logscan-webOpen http://127.0.0.1:8000.
From the directory containing the repository and Compose file, pull the latest application version and recreate the container with a newly built image:
cd /opt/logscan
git pull
docker compose up -d --build --remove-orphans
docker compose logs -fThe persistent /opt/logscan/data directory is not recreated or removed by
this process. Press Ctrl+C to stop following logs; it does not stop the
container.
The included compose.saltbox.yml follows Saltbox's Traefik template and
publishes the app at https://logscan.kometa.team.
sudo mkdir -p /opt/logscan/data
sudo chown -R 10001:10001 /opt/logscan/data
cd /opt/logscan
cp .env.example .env
openssl rand -hex 32
# Put that value in .env, then:
docker compose -f compose.saltbox.yml up -d --buildCreate an A/AAAA record for logscan.kometa.team pointing to the Saltbox
server (or use Saltbox DDNS/wildcard DNS). The compose file expects the
external saltbox Docker network and Saltbox's standard Traefik middlewares.
If the domain is not managed through the Cloudflare account configured in
Saltbox, change cfdns to the certificate resolver used by your installation.
Logs and result JSON are stored below /opt/logscan/data, so all persistent
application data remains inside /opt/logscan on the host. Scans are
automatically deleted 48 hours after upload. The service checks immediately at
startup and hourly thereafter.
The /people page combines missing People Posters found in uploaded logs with
IMDb StarMeter and TMDb trending people. Each person carries missing, trending,
or both source tags and is de-duplicated by TMDb person ID. People already present
in the primary Kometa People Images repository automatically leave the actionable
queue. Filters and exports use the same union so automation processes both sources.
Set TMDB_API_KEY so the service can resolve people and load profile images.
For website uploads, set DISCORD_PEOPLE_WEBHOOK_URL to a webhook created in
the destination Discord channel. A non-embedding notice is posted only when a
person is first added to the queue, preventing repeat notifications for the
same person. The notification links back to the filtered People queue and the
source scan when available.
The Missing and Trending filters use OR behavior and are both enabled by
default. Export downloads the currently selected source union as
tmdbid|name. Marking a person complete clears both sources; candidates also
leave the queue automatically after their image appears in the primary Kometa
People Images repository.
The application accepts uploads and extracted archive contents up to 1 GiB. Large archive members are streamed to temporary disk storage while scanning. Browser and authenticated Discord uploads return 202 Accepted after intake and continue as background jobs, so long scans and queue waits are not tied to the proxy read timeout. Your reverse proxy must accept the compressed upload plus multipart overhead.
For Nginx, include this in the applicable server or location block:
client_max_body_size 1025M;
proxy_read_timeout 900s;
proxy_send_timeout 900s;
proxy_pass http://127.0.0.1:8000;If Cloudflare proxies the hostname, its plan-specific request-body limit still applies to the compressed upload. Use a higher-limit plan or DNS-only routing when uploads must exceed that limit.
Scan results retain the findings count and separately show Log occurrences: the number of critical, error, warning, info, debug, and trace log entries. Repeated entries count separately, while one entry may produce more than one finding. These counts are calculated from the full log, including large logs, rather than from the displayed evidence lines. Critical, error, and warning counts share their colors with findings and log viewer highlights; info, debug, and trace have distinct colors. The support console displays the same six levels in a separate Log counts column beside Findings, sortable by total tagged entries. Counts unavailable before backfill are shown as unavailable rather than zero.
Entry-backed findings additionally report occurrence_count: the number of
distinct log lines satisfying that detector's complete predicate. Multiple
keywords on the same line count once. Word boundaries, case-insensitive matches,
and same-line requirements are applied before counting. all_of remains a
log-wide prerequisite; qualifying lines are counted only after it is satisfied.
Detectors can overlap, so their occurrences are never summed into total errors
or findings counts. Contextual advice without a matching-entry count reports
occurrence_count: null.
Normal and streaming scans count the entire log. Evidence retains the first
10,000 matching lines while counting continues; evidence_truncated is true
when further evidence was omitted. Results show the count beside each applicable
finding and explain truncation, such as "10,005 occurrences; showing the first
10,000 matching entries." Older records without these fields remain viewable.
Large-file text rules match arbitrary mixed case, just like the normal scanner, without lowercasing or decoding the entire log into memory.
Validation-only Kometa runs are complete when validator.py logs a terminal
Result:. The result (including PASSED, FAILED, or SKIPPED) and validation
level are displayed separately. A completed validation command can have a
failed outcome; a report without a terminal result remains incomplete. No
normal runtime footer is required, and no runtime is fabricated.
Analysis version 6 reprocesses retained scans through the existing startup backfill, preserving scan IDs, source logs, deletion tokens and upload attribution. It adds per-detector counts and truncation flags to stored results, preserves existing schema metadata, and leaves raw log-level counts and findings totals separate. Quickstart's scanner and analytics are unchanged.
Install the application dependencies and development tools from the repository root (Python 3.13):
py -3.13 -m pip install -r requirements.txt -r requirements-dev.txt
npm ci
py -3.13 -m pre_commit installRun the same checks used by CI:
py -3.13 -m pre_commit run --all-files
py -3.13 -m pytest
node --test tests/test_occurrence_ui.cjsOn Linux or macOS, replace py -3.13 with python from an activated Python 3.13
virtual environment. The JavaScript tools require Node.js 22.18 or newer.
Pre-commit checks YAML and JSON syntax, merge conflicts, filename case conflicts,
whitespace, and requirements ordering, then runs Ruff and Black. Ruff uses the
initial Quickstart rule set (E4, E7, E9, F); Black and Ruff share a
180-character line length. ESLint checks browser scripts and Node tests, and
CSpell checks Markdown prose, excluding code blocks and inline code. Project
names are listed in cspell.json; add legitimate technical terms there instead
of ignoring misspelled prose. Hooks can fix files; review their changes before
committing. Run either Python check separately with:
py -3.13 -m ruff check .
py -3.13 -m black --check .
npm run lint:js
npm run spellcheckCI runs these file checks and both regression suites on pull requests and pushes
to main. Dependabot checks Python dependencies, JavaScript development tools,
and GitHub Actions daily.
logscan_web/app.py— Flask routes and upload handlinglogscan_web/storage.py— filesystem persistence and deletion tokenslogscan_web/scanner.py— input validation and result normalizationlogscan_web/models.pyandlogscan_web/rules/— structured scan data and modular recommendation ruleslogscan_web/templates/— HTML interfacelogscan_web/static/— styles and browser behaviortests/— scanner and API regression testsrequirements.txt— complete Python dependenciesDockerfile— container deploymentcompose.saltbox.yml— Saltbox/Traefik deployment