feat: button-driven Telegram menu (reply keyboard + commands), reachability diagnostic, How-To update

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
2026-07-23 20:12:27 +00:00
parent 5105c7d529
commit 7608bc62da
4 changed files with 148 additions and 29 deletions
+56
View File
@@ -0,0 +1,56 @@
import type { APIRoute } from 'astro';
import net from 'node:net';
import dns from 'node:dns/promises';
import { one } from '../../../lib/db';
export const prerender = false;
const json = (b: unknown, s = 200) =>
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
/** TCP-Verbindungstest aus dem Container heraus (mit Timeout). */
function tcpProbe(host: string, port: number, timeoutMs = 6000): Promise<{ ok: boolean; ms: number; error?: string }> {
return new Promise((resolve) => {
const start = Date.now();
const sock = new net.Socket();
let done = false;
const finish = (ok: boolean, error?: string) => {
if (done) return; done = true;
try { sock.destroy(); } catch { /* egal */ }
resolve({ ok, ms: Date.now() - start, error });
};
sock.setTimeout(timeoutMs);
sock.once('connect', () => finish(true));
sock.once('timeout', () => finish(false, 'Timeout — keine Antwort (Firewall/Route?)'));
sock.once('error', (e: any) => finish(false, e?.code || e?.message || 'Verbindungsfehler'));
sock.connect(port, host);
});
}
/** Prüft, ob ein Host:Port aus dem Container erreichbar ist. Body {host,port} oder
* ohne Angabe der konfigurierte NAS-Host. Testet zusätzlich DNS-Auflösung. */
export const POST: APIRoute = async ({ request }) => {
const b = await request.json().catch(() => ({} as any));
let host = (b.host || '').trim();
let port = Number(b.port) || 0;
if (!host) {
const s = await one<{ nas_host: string | null; nas_port: number | null; nas_protocol: string | null }>(
'SELECT nas_host, nas_port, nas_protocol FROM settings WHERE id=1');
host = (s?.nas_host || '').trim();
port = s?.nas_port || (s?.nas_protocol === 'ftps' ? 21 : 22);
}
if (!host) return json({ error: 'Kein Host angegeben.' }, 400);
if (!port) port = 22;
const out: any = { host, port };
// DNS: nur wenn kein reines IP.
const isIp = net.isIP(host) !== 0;
if (!isIp) {
try { const a = await dns.lookup(host); out.resolve = { ok: true, address: a.address }; }
catch (e: any) { out.resolve = { ok: false, error: e?.code || 'DNS fehlgeschlagen' }; }
} else {
out.resolve = { ok: true, address: host, note: 'IP-Adresse' };
}
out.tcp = await tcpProbe(host, port);
out.reachable = out.tcp.ok;
return json(out);
};