Vendor platform-tools: everything this site runs is now in one repo
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>
This commit is contained in:
1 parent
0e034232e9
commit
4d40adbf64
67 files changed
+17478
-5
No files matched your search
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
// Compila o índice de país por endereço, para a analítica de visitas.
|
||||
//
|
||||
// php bin/geo-build-index.php
|
||||
//
|
||||
// O bloqueio por país (functions_geo.php) só precisa das listas dos países
|
||||
// permitidos, que são pequenas e ficam versionadas. Isto é outra coisa: para
|
||||
// dizer DE ONDE veio uma visita é preciso o mundo inteiro -- 240 países e
|
||||
// ~263 mil ranges. Em texto são 4,7 MB e a compilar dá ~2,6 MB.
|
||||
//
|
||||
// Por isso o índice NÃO vai para o repositório: um binário desse tamanho
|
||||
// refrescado todos os meses inchava o histórico sem nada em troca. Gera-se aqui
|
||||
// e, se faltar, a analítica regista o país como desconhecido -- que é um
|
||||
// degradar aceitável, ao contrário do bloqueio, onde dados em falta seriam uma
|
||||
// decisão de segurança.
|
||||
//
|
||||
// Formato, escolhido para se poder pesquisar sem carregar nada para memória:
|
||||
// registos de largura fixa, ordenados, e busca binária por fseek(). São
|
||||
// centenas de milhares de ranges — lê-los todos a cada visita custaria mais do
|
||||
// que a própria página.
|
||||
//
|
||||
// .v4 → 10 bytes: início(4, big-endian) + fim(4) + código do país(2)
|
||||
// .v6 → 34 bytes: início(16) + fim(16) + código do país(2)
|
||||
//
|
||||
// Os bytes vêm de inet_pton(), por isso a ordem lexicográfica é a ordem
|
||||
// numérica e o strcmp() serve de comparador nas duas famílias.
|
||||
|
||||
const V4_URL = 'https://www.ipdeny.com/ipblocks/data/countries/all-zones.tar.gz';
|
||||
const V6_URL = 'https://www.ipdeny.com/ipv6/ipaddresses/blocks/ipv6-all-zones.tar.gz';
|
||||
|
||||
$dataDir = dirname(__DIR__) . '/data/geo';
|
||||
@mkdir($dataDir, 0775, true);
|
||||
|
||||
require_once dirname(__DIR__) . '/functions_geo.php'; // geo_cidr_to_range()
|
||||
|
||||
$tmp = sys_get_temp_dir() . '/geo-index-' . getmypid();
|
||||
@mkdir($tmp, 0700, true);
|
||||
register_shutdown_function(static function () use ($tmp) {
|
||||
foreach (glob($tmp . '/*') ?: [] as $f) { @unlink($f); }
|
||||
@rmdir($tmp);
|
||||
});
|
||||
|
||||
$status = 0;
|
||||
|
||||
foreach ([['v4', V4_URL, 4], ['v6', V6_URL, 16]] as [$fam, $url, $addrLen]) {
|
||||
fwrite(STDERR, "$fam: a descarregar...\n");
|
||||
|
||||
$tar = "$tmp/$fam.tar.gz";
|
||||
if (!download($url, $tar)) {
|
||||
fwrite(STDERR, " falhou o download -- o índice anterior mantém-se\n");
|
||||
$status = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
$work = "$tmp/$fam";
|
||||
@mkdir($work, 0700, true);
|
||||
exec('tar xzf ' . escapeshellarg($tar) . ' -C ' . escapeshellarg($work) . ' 2>&1', $out, $code);
|
||||
if ($code !== 0) {
|
||||
fwrite(STDERR, " o arquivo não abriu -- ignorado\n");
|
||||
$status = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nem toda a zona do ipdeny é um país. O zz.zone é um apanha-tudo de blocos
|
||||
// /8 inteiros (1.0.0.0/8, 8.0.0.0/8…) que se sobrepõem aos países reais, e
|
||||
// o eu.zone é um agregado continental. Deixá-los entrar fazia o 8.8.8.8 sair
|
||||
// como "ZZ" em vez de US, porque a busca binária cai no range que apanhar --
|
||||
// e com sobreposições não há garantia nenhuma de ser o mais específico.
|
||||
// Fora estes dois, sobram 2 sobreposições em 262 mil.
|
||||
$notCountries = ['ZZ', 'EU', 'AP'];
|
||||
|
||||
$ranges = [];
|
||||
foreach (glob("$work/*.zone") ?: [] as $file) {
|
||||
// ipdeny nomeia cada ficheiro pelo código ISO: pt.zone, br.zone…
|
||||
$cc = strtoupper(basename($file, '.zone'));
|
||||
if (!preg_match('/^[A-Z]{2}$/', $cc) || in_array($cc, $notCountries, true)) {
|
||||
continue;
|
||||
}
|
||||
foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
$r = geo_cidr_to_range($line);
|
||||
// Um ficheiro v4 não pode contaminar o índice v6 nem o contrário:
|
||||
// os registos são de largura fixa e um intruso desalinhava tudo a
|
||||
// partir dali, o que a busca binária não tem como detectar.
|
||||
if ($r === null || strlen($r[0]) !== $addrLen) {
|
||||
continue;
|
||||
}
|
||||
$ranges[] = [$r[0], $r[1], $cc];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($ranges) < 1000) {
|
||||
fwrite(STDERR, ' só ' . count($ranges) . " ranges — recusado, fica o índice anterior\n");
|
||||
$status = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
usort($ranges, static fn($a, $b) => strcmp($a[0], $b[0]));
|
||||
|
||||
// As poucas sobreposições que restam são descartadas, ficando a primeira.
|
||||
// A busca binária pressupõe ranges disjuntos: com sobreposições devolveria
|
||||
// um resultado que depende de onde a bissecção calhou aterrar, o que é pior
|
||||
// do que não responder -- daria países diferentes para o mesmo endereço
|
||||
// consoante o tamanho do índice.
|
||||
$clean = [];
|
||||
$lastEnd = null;
|
||||
$dropped = 0;
|
||||
foreach ($ranges as $r) {
|
||||
if ($lastEnd !== null && strcmp($r[0], $lastEnd) <= 0) {
|
||||
$dropped++;
|
||||
continue;
|
||||
}
|
||||
$clean[] = $r;
|
||||
$lastEnd = $r[1];
|
||||
}
|
||||
$ranges = $clean;
|
||||
if ($dropped > 0) {
|
||||
fwrite(STDERR, " $dropped ranges sobrepostos descartados\n");
|
||||
}
|
||||
|
||||
// Escrita para ficheiro temporário e rename: o rename é atómico, por isso
|
||||
// um processo a ler nunca apanha o índice a meio de ser escrito.
|
||||
$outFile = "$dataDir/country-index.$fam";
|
||||
$tmpOut = $outFile . '.tmp';
|
||||
$fh = fopen($tmpOut, 'wb');
|
||||
if ($fh === false) {
|
||||
fwrite(STDERR, " não consegui escrever em $tmpOut\n");
|
||||
$status = 1;
|
||||
continue;
|
||||
}
|
||||
foreach ($ranges as [$start, $end, $cc]) {
|
||||
fwrite($fh, $start . $end . $cc);
|
||||
}
|
||||
fclose($fh);
|
||||
@chmod($tmpOut, 0664);
|
||||
rename($tmpOut, $outFile);
|
||||
|
||||
printf(
|
||||
" %s: %d ranges, %.1f MB\n",
|
||||
$fam,
|
||||
count($ranges),
|
||||
filesize($outFile) / 1048576
|
||||
);
|
||||
}
|
||||
|
||||
exit($status);
|
||||
|
||||
function download(string $url, string $dest): bool
|
||||
{
|
||||
$fh = fopen($dest, 'wb');
|
||||
if ($fh === false) {
|
||||
return false;
|
||||
}
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_FILE => $fh,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 180,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
]);
|
||||
$ok = curl_exec($ch) !== false;
|
||||
curl_close($ch);
|
||||
fclose($fh);
|
||||
return $ok && filesize($dest) > 1024;
|
||||
}
|
||||
Reference in new issue
Block a user