v0.1.4 — MaxMind Basic Auth + Account-ID Field, detailed download errors
This commit is contained in:
parent
c710d874b1
commit
2e412b61a7
2 changed files with 90 additions and 19 deletions
|
|
@ -29,6 +29,7 @@ export default function SettingsPage() {
|
|||
const [status, setStatus] = useState<UpdateStatus | null>(null);
|
||||
const [geo, setGeo] = useState<{ available: boolean; path: string } | null>(null);
|
||||
const [licenseKey, setLicenseKey] = useState("");
|
||||
const [accountId, setAccountId] = useState("");
|
||||
const [installingGeo, setInstallingGeo] = useState(false);
|
||||
const [geoMsg, setGeoMsg] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
|
@ -56,15 +57,23 @@ export default function SettingsPage() {
|
|||
const r = await fetch("/api/settings/geo", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ license_key: licenseKey.trim() }),
|
||||
body: JSON.stringify({
|
||||
license_key: licenseKey.trim(),
|
||||
...(accountId.trim() ? { account_id: accountId.trim() } : {}),
|
||||
}),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (r.ok) {
|
||||
setGeoMsg("GeoLite2-DB installiert.");
|
||||
setLicenseKey("");
|
||||
setAccountId("");
|
||||
load();
|
||||
} else {
|
||||
setGeoMsg(`Fehler: ${d.error || "Download fehlgeschlagen"}`);
|
||||
const parts = [`Fehler: ${d.error || "Download fehlgeschlagen"}`];
|
||||
if (d.status) parts.push(`HTTP ${d.status}`);
|
||||
if (d.detail) parts.push(d.detail);
|
||||
if (d.hint) parts.push(`→ ${d.hint}`);
|
||||
setGeoMsg(parts.join(" — "));
|
||||
}
|
||||
} finally {
|
||||
setInstallingGeo(false);
|
||||
|
|
@ -240,23 +249,37 @@ export default function SettingsPage() {
|
|||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="licenseKey">MaxMind License-Key</Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="accountId">MaxMind Account-ID</Label>
|
||||
<Input
|
||||
id="licenseKey"
|
||||
type="password"
|
||||
placeholder="xxxxxxxxxxxxxxxx"
|
||||
value={licenseKey}
|
||||
onChange={(e) => setLicenseKey(e.target.value)}
|
||||
id="accountId"
|
||||
type="text"
|
||||
placeholder="123456"
|
||||
value={accountId}
|
||||
onChange={(e) => setAccountId(e.target.value)}
|
||||
disabled={installingGeo}
|
||||
/>
|
||||
<Button onClick={installGeo} disabled={installingGeo || !licenseKey.trim()}>
|
||||
{installingGeo ? <Loader2 className="mr-2 h-3 w-3 animate-spin" /> : null}
|
||||
Installieren
|
||||
</Button>
|
||||
<p className="text-[11px] text-muted-foreground">Empfohlen — neue License-Keys brauchen die Account-ID (Basic Auth).</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="licenseKey">MaxMind License-Key</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="licenseKey"
|
||||
type="password"
|
||||
placeholder="xxxxxxxxxxxxxxxx"
|
||||
value={licenseKey}
|
||||
onChange={(e) => setLicenseKey(e.target.value)}
|
||||
disabled={installingGeo}
|
||||
/>
|
||||
<Button onClick={installGeo} disabled={installingGeo || !licenseKey.trim()}>
|
||||
{installingGeo ? <Loader2 className="mr-2 h-3 w-3 animate-spin" /> : null}
|
||||
Installieren
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">Lädt GeoLite2-Country.mmdb herunter. EULA muss im MaxMind-Account akzeptiert sein.</p>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">Lädt GeoLite2-Country.mmdb herunter und aktiviert Geo-Lookup.</p>
|
||||
</div>
|
||||
)}
|
||||
{geoMsg && <p className="text-xs text-muted-foreground">{geoMsg}</p>}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,25 @@ export async function GET() {
|
|||
|
||||
const schema = z.object({
|
||||
license_key: z.string().min(10),
|
||||
account_id: z.string().optional(),
|
||||
});
|
||||
|
||||
type DownloadResult = { ok: true; body: Buffer } | { ok: false; status: number; text: string };
|
||||
|
||||
async function tryDownload(url: string, headers: Record<string, string> = {}): Promise<DownloadResult> {
|
||||
const res = await fetch(url, { headers: { "User-Agent": "corex-nexredirect", ...headers } });
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => "");
|
||||
return { ok: false, status: res.status, text: text.slice(0, 500) };
|
||||
}
|
||||
const ct = res.headers.get("content-type") || "";
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
if (ct.includes("text/html") || buf.slice(0, 4).toString() === "<htm" || buf.slice(0, 5).toString() === "<!DOC") {
|
||||
return { ok: false, status: 200, text: "Server returned HTML, not tar.gz (likely auth failure or EULA not accepted)" };
|
||||
}
|
||||
return { ok: true, body: buf };
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
|
||||
|
|
@ -29,12 +46,43 @@ export async function POST(req: Request) {
|
|||
const parsed = schema.safeParse(body);
|
||||
if (!parsed.success) return NextResponse.json({ error: "invalid" }, { status: 400 });
|
||||
|
||||
const url = `https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${encodeURIComponent(parsed.data.license_key)}&suffix=tar.gz`;
|
||||
const { license_key, account_id } = parsed.data;
|
||||
|
||||
const attempts: Array<{ url: string; headers?: Record<string, string>; label: string }> = [];
|
||||
|
||||
if (account_id) {
|
||||
const auth = Buffer.from(`${account_id}:${license_key}`).toString("base64");
|
||||
attempts.push({
|
||||
url: "https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz",
|
||||
headers: { Authorization: `Basic ${auth}` },
|
||||
label: "basic-auth",
|
||||
});
|
||||
}
|
||||
attempts.push({
|
||||
url: `https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${encodeURIComponent(license_key)}&suffix=tar.gz`,
|
||||
label: "legacy",
|
||||
});
|
||||
|
||||
let lastErr: { status: number; text: string; label: string } | null = null;
|
||||
let buf: Buffer | null = null;
|
||||
for (const a of attempts) {
|
||||
const r = await tryDownload(a.url, a.headers);
|
||||
if (r.ok) { buf = r.body; break; }
|
||||
lastErr = { status: r.status, text: r.text, label: a.label };
|
||||
}
|
||||
|
||||
if (!buf) {
|
||||
return NextResponse.json({
|
||||
error: "download_failed",
|
||||
detail: lastErr?.text || "no detail",
|
||||
status: lastErr?.status,
|
||||
hint: account_id
|
||||
? "MaxMind hat das Tarball nicht ausgeliefert. Stimmen Account-ID und License-Key? GeoLite2 muss im Account aktiviert sein und EULA akzeptiert."
|
||||
: "Falls dein License-Key über das neue MaxMind-Account-System erstellt wurde, ist die Account-ID nötig (siehe Account → My License Keys oder unter Account-Details).",
|
||||
}, { status: 502 });
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) return NextResponse.json({ error: "download_failed", status: res.status }, { status: 502 });
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "geo-"));
|
||||
const tarPath = path.join(tmpDir, "geo.tgz");
|
||||
await fs.writeFile(tarPath, buf);
|
||||
|
|
|
|||
Loading…
Reference in a new issue