diff --git a/app/routes/ssh/page.tsx b/app/routes/ssh/page.tsx index 1332f8c..1f9e9b8 100644 --- a/app/routes/ssh/page.tsx +++ b/app/routes/ssh/page.tsx @@ -5,7 +5,13 @@ import { data, isRouteErrorResponse, type ShouldRevalidateFunction } from "react import Button from "~/components/button"; import Card from "~/components/card"; import Code from "~/components/code"; -import { agentsContext, appConfigContext, requestApiContext } from "~/server/context"; +import StatusBanner from "~/components/status-banner"; +import { + agentsContext, + appConfigContext, + headscaleContext, + requestApiContext, +} from "~/server/context"; import { findHeadscaleUserBySubject } from "~/server/web/headscale-identity"; import type { Route } from "./+types/page"; @@ -26,7 +32,9 @@ export const shouldRevalidate: ShouldRevalidateFunction = () => { export async function loader({ request, params, context, url }: Route.LoaderArgs) { const agents = context.get(agentsContext); const config = context.get(appConfigContext); + const headscale = context.get(headscaleContext); const getRequestApi = context.get(requestApiContext); + const compatibilityWarning = getBrowserSSHCompatibilityWarning(headscale.version); const origin = url.origin; const assets = [WASM_HELPER_URL, WASM_MODULE_URL]; @@ -62,11 +70,17 @@ export async function loader({ request, params, context, url }: Route.LoaderArgs } if (!node.online) { - return { hostname, username, offline: true, node: undefined }; + return { hostname, username, offline: true, node: undefined, compatibilityWarning }; } if (!username) { - return { hostname, username: undefined, offline: false, node: undefined }; + return { + hostname, + username: undefined, + offline: false, + node: undefined, + compatibilityWarning, + }; } // The user must exist within Headscale to generate a pre-auth key @@ -98,9 +112,24 @@ export async function loader({ request, params, context, url }: Route.LoaderArgs preAuthKey: preAuthKey.key, ephemeralHostname: generateHostname(username), }, + compatibilityWarning, }; } +function getBrowserSSHCompatibilityWarning(version: { + unknown: boolean; + major: number; + minor: number; + patch: number; + raw: string; +}) { + if (version.unknown) return null; + if (version.major === 0 && version.minor === 29 && version.patch < 2) { + return { version: version.raw }; + } + return null; +} + function generateHostname(username: string) { const hex = crypto.randomUUID().replace(/-/g, "").slice(0, 8); return `ssh-${hex}-${username}`; @@ -117,32 +146,66 @@ export const links: Route.LinksFunction = () => [ ]; export default function Page({ loaderData }: Route.ComponentProps) { - const { hostname, username, offline, node } = loaderData; + const { hostname, username, offline, node, compatibilityWarning } = loaderData; if (offline) { return ( -
{hostname} is not currently connected to the Tailnet.
- {hostname} is not currently connected to the Tailnet.
+ /ts2021 WebSocket request with 405 Method Not Allowed. Upgrade
+ Headscale to 0.29.2 or newer, or use Headscale 0.28.x.
+