From cd3f93bcc2d8e4a5934a19b13f7fdf30ee4e130c Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 22:34:19 +0100 Subject: web: serve latest.php through PHP and refuse stray PHP files nginx example now routes index.php and latest.php to php-fpm, sends the newest-version feed with no-store, keeps the stable installers revalidated, and returns 404 for every other .php request so releases.php cannot be downloaded as source. Documents Debian and Arch pool sockets. --- web/README.md | 38 +++++++++++++++++++++++++------------- web/nginx.conf.example | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 19 deletions(-) (limited to 'web') diff --git a/web/README.md b/web/README.md index 13f5f85..81346fd 100644 --- a/web/README.md +++ b/web/README.md @@ -71,34 +71,46 @@ Cargo itself cannot produce these names: it always writes ## nginx `web/nginx.conf.example` is a working server block for a subdomain with an -existing certificate. On Arch-based systems: +existing certificate. Debian/Ubuntu and Arch install it the same way: ```sh +# Debian / Ubuntu +sudo apt install nginx php-fpm +sudo install -d -o "$USER" -g www-data -m 0755 /srv/zagan.bet + +# Arch sudo pacman -S nginx php-fpm sudo install -d -o "$USER" -g http -m 0755 /srv/zagan.bet + sudo cp web/nginx.conf.example /etc/nginx/conf.d/zagan.bet.conf -# edit server_name, root and the certificate paths, then: +# edit server_name, root, socket and certificate paths, then: sudo nginx -t && sudo systemctl enable --now nginx php-fpm ``` -The pool socket and user are Arch defaults (`http`, -`/run/php-fpm/php-fpm.sock`). Check them and adjust `fastcgi_pass` if your -distribution differs: +The example uses Debian/Ubuntu's pool socket (`/run/php/php-fpm.sock`); Arch +uses `/run/php-fpm/php-fpm.sock`. Check the pool's user and socket and adjust +`fastcgi_pass` if your distribution differs: ```sh -grep -E '^(user|group|listen)' /etc/php/php-fpm.d/www.conf +grep -E '^(user|group|listen)' /etc/php/*/fpm/pool.d/www.conf /etc/php-fpm.d/www.conf 2>/dev/null ``` -`rsync` publishes files as `0755`/`0644`, so the `http` worker and the php-fpm -pool can read them without extra group juggling. `index.php` is the only -script; any other `.php` request 404s instead of executing. +`rsync` publishes files as `0755`/`0644`, so the PHP worker can read them +without extra group juggling. `index.php` and `latest.php` are the only +scripts; `releases.php` is an include, and the example returns 404 for every +other `.php` request instead of serving it as source. + +The example sends `/latest.php` with `no-store` and the two installers with +`no-cache`, so a new release is picked up immediately without giving up the +immutable caching on `/bin/`. PHP's opcache is enabled by default and revalidates timestamps every two -seconds, so a deployed `index.php` takes effect without reloading php-fpm. If -you ever set `opcache.validate_timestamps=0`, reload php-fpm after a deploy. +seconds, so a deployed `index.php` or `latest.php` takes effect without +reloading php-fpm. If you ever set `opcache.validate_timestamps=0`, reload +php-fpm after a deploy. -The php-fpm unit on Arch uses `PrivateTmp=`, so the hash cache in `/tmp` lives -in the service's private tmp. It still works; point `ZAGAN_HASH_CACHE` at a +The php-fpm unit often uses `PrivateTmp=`, so the hash cache in `/tmp` lives in +the service's private tmp. It still works; point `ZAGAN_HASH_CACHE` at a writable path such as `/var/cache/zagan-bet-hashes.json` if you want it to survive a service restart. diff --git a/web/nginx.conf.example b/web/nginx.conf.example index ad001d1..9fc942f 100644 --- a/web/nginx.conf.example +++ b/web/nginx.conf.example @@ -1,11 +1,12 @@ # zagan.bet download page — nginx + php-fpm example. # # Install at /etc/nginx/conf.d/zagan.bet.conf (the packaged nginx.conf includes -# conf.d/*.conf). Replace the server name, root and certificate paths. +# conf.d/*.conf). Replace the server name, root, socket and certificate paths. # -# Only index.php is PHP; everything under /bin/ is served as static files. -# The binaries are versioned, so they can be cached forever — which means you -# must bump the version (Cargo.toml) instead of overwriting a published file. +# Only index.php and latest.php are PHP. Everything else under the web root is +# static: /bin/ is immutable and versioned, while the installers are stable and +# revalidated. Any other .php request is refused, so releases.php (an include) +# and stray scripts cannot be downloaded as source. server { listen 80; @@ -17,7 +18,7 @@ server { server { listen 443 ssl; listen [::]:443 ssl; - http2 on; + http2 on; # nginx < 1.25.1: listen 443 ssl http2; server_name dl.example.com; root /srv/zagan.bet; @@ -39,15 +40,41 @@ server { # The page rescans bin/ on every request, so don't cache it. location = /index.php { include fastcgi_params; - fastcgi_pass unix:/run/php-fpm/php-fpm.sock; + # Debian/Ubuntu: /run/php/php-fpm.sock + # Arch: /run/php-fpm/php-fpm.sock + fastcgi_pass unix:/run/php/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; add_header Cache-Control "no-cache"; } + # The newest-version feed read by install.sh and install.ps1. + location = /latest.php { + include fastcgi_params; + fastcgi_pass unix:/run/php/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + add_header Cache-Control "no-store"; + } + + # Stable installers: always revalidate so a fix reaches the next download. + location = /install.sh { + default_type text/plain; + add_header Cache-Control "no-cache"; + } + + location = /install.ps1 { + default_type text/plain; + add_header Cache-Control "no-cache"; + } + location / { try_files $uri $uri/ =404; } + # releases.php is an include only; no other script is meant to be served. + location ~ \.php$ { + return 404; + } + # Never serve dotfiles (.gitignore, editor leftovers). location ~ /\. { deny all; -- cgit v1.2.3