Fixed the container port in the `Example Frontend mode` section.
The inner container port should be `8080` instead of `80` to correctly map the frontend service.
fix: preserve the modern frontend layout in deployments (#834)
Serve the modern UI assets from frontend/ in both repository and Docker
deployments, matching the paths used by index-modern.html.
Move settings.json and server-list.json to the web root, where the modern
page resolves its configuration, and keep Docker, installation guidance, and
design-switch documentation consistent with that layout.
Add E2E coverage for:
- serving the repository unchanged with Python's static HTTP server;
- required modern UI assets and a real 404 for missing paths;
- Alpine standalone settings and modern frontend rendering.
Fixes#804
index-modern.html refers to styling/, javascript/ and images/ as if they sat
next to it, but they live under frontend/. Only docker/entrypoint.sh reconciles
that, by flattening those directories into the web root, so serving the
repository as it stands renders the page without stylesheet, script or logo.
Point the HTML at frontend/, have the entrypoint copy that directory as-is, and
move settings.json and server-list.json to the root that the page fetches them
from, so the repository layout and the deployed layout are the same. Update the
install instructions, which described the flattening.
formatSpeedtestDataForImage() assigned the timestamp to itself, so the value
reached the image exactly as the database returned it. Every other field on
that list is passed through format(); this one reads like a placeholder that
was never filled in, and static analysis flags it as a self-assignment.
How much precision the column carries is decided by the backend, and the three
schemas shipped here disagree: MySQL's `timestamp` keeps none, PostgreSQL's
`timestamp without time zone DEFAULT now()` keeps microseconds and MSSQL's
`datetime` keeps milliseconds. A PostgreSQL deployment therefore drew
"2026-08-10 02:04:13.957789" where a MySQL one drew "2026-08-10 02:04:13".
- xhr.onload now owns the complete lifecycle: checks x.status on
the captured local variable, restarts only on 2xx.
- xhr.upload.onload no longer calls testStream() to avoid race
with xhr.onload overwriting xhr[i] before status is read.
- xhr.upload.onerror also uses the captured x for consistency.
- speedtest_worker.js: moved HTTP status check from xhr.upload.onload
(body sent, response may not be available) to xhr.onload
(full HTTP transaction complete, xhr.status is reliable).
- doc.md: added full IIS web.config example with maxAllowedContentLength.
- speedtest_worker.js: xhr.upload.onload now checks xhr.status
before restarting the upload stream. Non-2xx responses (e.g.
HTTP 413 from undersized client_max_body_size) are treated as
failures instead of inflating upload speed measurements.
- doc.md: added explicit nginx/Apache/IIS configuration examples
for client_max_body_size / LimitRequestBody to prevent HTTP 413
errors on the 20MB upload test blobs.
Drop the Debian COPY comment (it only restated the code) and the
version-fragility framing in the Alpine comment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous build-time discovery via PHP_CONFIG_FILE_SCAN_DIR worked
but invoked a PHP binary just to learn a path that's already a stable
contract of each base image:
* php:8-apache exports PHP_INI_DIR=/usr/local/etc/php as part of the
docker-library image template, stable across PHP majors. Reference
it directly in the COPY destination — no RUN, no validation needed.
* Alpine's apk php-apache2 always installs mod_php's conf.d at
/etc/phpXX/conf.d (currently /etc/php84). The FROM php:8-alpine
image also ships its own PHP at /usr/local/etc/php/conf.d, but
mod_php doesn't read from there. Glob /etc/php*/conf.d to track
the apk-installed PHP major automatically; explicit error if the
glob matches nothing.
Net effect: Debian goes from a 5-line RUN block to a 1-line COPY.
Alpine keeps a small RUN block but no longer invokes a PHP binary,
so it doesn't matter which of the two PHP installs `php` resolves
to via $PATH.
Verified: both variants build, install the override at the right
conf.d, mod_php reports post_max_size=32M, and a 20 MB POST to
/backend/empty.php returns HTTP 200 / 0 bytes with no warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
speedtest_worker.js uploads in 20 MB chunks by default
(xhr_ul_blob_megabytes: 20) but the official Docker images inherit
PHP's stock post_max_size = 8M / upload_max_filesize = 2M, so every
upload chunk:
* triggers a "POST Content-Length ... exceeds the limit" warning
(leaked into the response body of /backend/empty.php on the Debian
variant where display_errors is on; suppressed but still emitted on
Alpine where it's off);
* causes empty.php's subsequent header() calls to fail with "Cannot
modify header information - headers already sent", leaving the
response without proper status, cache, or CORS directives.
Ship a small docker/librespeed-php.ini with post_max_size = 32M,
upload_max_filesize = 32M, memory_limit = 256M and COPY it into the
right conf.d for each base image (/usr/local/etc/php/conf.d on
Debian, /etc/php84/conf.d on Alpine). 99- prefix follows the
NN-name.ini packaging convention so this loads after distro defaults
but never silently shadows operator overrides.
Verified post-fix on both variants: a 20 MB POST to /backend/empty.php
returns HTTP 200 / 0 bytes with no warning leakage.