shared/platform-tools was a git submodule. On this branch it is a plain
directory of tracked files, frozen at ea3616abae30106df283e73a3e2f9e5bb0e6ae90
— the same commit fotografia.andmore.pt pins. Contents are byte-identical to
that tree, verified blob by blob.
The submodule caused every deployment failure this site has had:
- .gitmodules named no `branch`, so the update fell back to
refs/remotes/origin/HEAD — a ref a plain server-side clone never writes.
"Atualizar submódulo" failed with "Unable to find refs/remotes/origin/HEAD
revision in submodule path".
- Its URL was http://, which the origin answers with 523.
- This install is deployed by uploading files, and a submodule is invisible
to that. The tree moved forward while shared/platform-tools stayed on the
old pin, config/functions_secrets.php died on a missing compat_secrets.php,
and the whole site — public pages included — went down.
Each of those was separately fixable. Together they mean a second repository
nobody can see from the deployment path, gating whether the first one boots. A
plain directory deploys like every other file and cannot fall out of step.
The cost is that platform-tools updates no longer arrive by bumping a pin; they
are a copy-over-and-commit. shared/platform-tools/VENDORED.md records the
upstream SHA, the procedure, and which four files differ from upstream in line
endings only (social/*.js, .css, functions_publications.php — CRLF upstream,
normalised to LF here; content otherwise identical).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
44 lines
2.1 KiB
PHP
44 lines
2.1 KiB
PHP
<?php
|
|
// ─── Quem é o visitante, atrás do proxy ─────────────────────────────────────
|
|
//
|
|
// Isto vivia só dentro de functions_auth.php, como auth_client_ip(). Servia
|
|
// bem o throttle do login, mas mais nada lhe conseguia chegar: o antispam da
|
|
// fotografia e do mstudio precisava do mesmo cálculo e não podia carregar o
|
|
// módulo de auth, porque esses sites declaram o seu próprio auth_login() e o
|
|
// include rebentava com "Cannot redeclare function".
|
|
//
|
|
// Daí um ficheiro à parte, sem dependências e sem nada a executar no include.
|
|
// Quem quer só o IP inclui este; auth_client_ip() passou a ser um alias para
|
|
// aqui, para não haver duas versões da mesma regra a divergir com o tempo.
|
|
//
|
|
// A regra: toda a quinta está atrás do Nginx Proxy Manager, por isso o
|
|
// REMOTE_ADDR é sempre o contentor do proxy (172.18.0.2) e nunca o visitante.
|
|
// O X-Forwarded-For tem o valor certo, mas é um cabeçalho -- qualquer cliente o
|
|
// escreve. Só se acredita nele quando TRUSTED_PROXY=true diz que há mesmo um
|
|
// proxy à frente a reescrevê-lo; caso contrário era uma identidade forjável, e
|
|
// qualquer limite por IP passava a valer zero.
|
|
|
|
if (!function_exists('platform_client_ip')) {
|
|
/**
|
|
* IP do visitante, ou null se não houver nenhum utilizável (CLI, cron).
|
|
*/
|
|
function platform_client_ip(): ?string
|
|
{
|
|
$trusted = function_exists('env')
|
|
? filter_var(env('TRUSTED_PROXY', 'false'), FILTER_VALIDATE_BOOLEAN)
|
|
: filter_var(getenv('TRUSTED_PROXY') ?: 'false', FILTER_VALIDATE_BOOLEAN);
|
|
|
|
if ($trusted && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
// O primeiro da lista é o cliente; os seguintes são os proxies por
|
|
// onde passou, e o NPM acrescenta-se ao fim.
|
|
$first = trim(explode(',', (string) $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
|
|
if (filter_var($first, FILTER_VALIDATE_IP)) {
|
|
return $first;
|
|
}
|
|
}
|
|
|
|
$ip = $_SERVER['REMOTE_ADDR'] ?? null;
|
|
return ($ip && filter_var($ip, FILTER_VALIDATE_IP)) ? $ip : null;
|
|
}
|
|
}
|