import { ArrowRight } from 'lucide-react'; import { useEffect } from 'react'; import { GrApple } from 'react-icons/gr'; import { ImFinder } from 'react-icons/im'; import { MdAndroid } from 'react-icons/md'; import { PiTerminalFill, PiWindowsLogoFill } from 'react-icons/pi'; import { LoaderFunctionArgs, NavLink, redirect, useLoaderData, } from 'react-router'; import Button from '~/components/Button'; import Card from '~/components/Card'; import Link from '~/components/Link'; import Options from '~/components/Options'; import StatusCircle from '~/components/StatusCircle'; import { LoadContext } from '~/server'; import { Machine } from '~/types'; import cn from '~/utils/cn'; import { useLiveData } from '~/utils/live-data'; import log from '~/utils/log'; import toast from '~/utils/toast'; export async function loader({ request, context, }: LoaderFunctionArgs) { const session = await context.sessions.auth(request); const user = session.get('user'); if (!user) { return redirect('/login'); } // Try to determine the OS split between Linux, Windows, macOS, iOS, and Android // We need to convert this to a known value to return it to the client so we can // automatically tab to the correct download button. const userAgent = request.headers.get('user-agent'); const os = userAgent?.match(/(Linux|Windows|Mac OS X|iPhone|iPad|Android)/); let osValue = 'linux'; switch (os?.[0]) { case 'Windows': osValue = 'windows'; break; case 'Mac OS X': osValue = 'macos'; break; case 'iPhone': case 'iPad': osValue = 'ios'; break; case 'Android': osValue = 'android'; break; default: osValue = 'linux'; break; } let firstMachine: Machine | undefined = undefined; try { const { nodes } = await context.client.get<{ nodes: Machine[] }>( 'v1/node', session.get('api_key')!, ); const node = nodes.find((n) => { if (n.user.provider !== 'oidc') { return false; } // For some reason, headscale makes providerID a url where the // last component is the subject, so we need to strip that out const subject = n.user.providerId?.split('/').pop(); if (!subject) { return false; } const sessionUser = session.get('user'); if (!sessionUser) { return false; } if (subject !== sessionUser.subject) { return false; } return true; }); firstMachine = node; } catch (e) { // If we cannot lookup nodes, we cannot proceed log.debug('api', 'Failed to lookup nodes %o', e); } return { user, osValue, firstMachine, }; } export default function Page() { const { user, osValue, firstMachine } = useLoaderData(); const { pause, resume } = useLiveData(); useEffect(() => { if (firstMachine) { pause(); } else { resume(); } }, [firstMachine]); const subject = user.email ? ( <> as {user.email} ) : ( 'with your OIDC provider' ); return (
Welcome!
Let's get set up
Install Tailscale and sign in {subject}. Once you sign in on a device, it will be automatically added to your Headscale network. Linux
} >

Click this button to copy the command.{' '} View script source

Windows
} >

Requires Windows 10 or later.

macOS } >

Requires macOS Big Sur 11.0 or later.
You can also download Tailscale on the{' '} macOS App Store {'.'}

iOS } >

Requires iOS 15 or later.

Android } >

Requires Android 8 or later.

{firstMachine ? (
Success!
We found your first device

{firstMachine.givenName}

{firstMachine.name}

IP Addresses

{firstMachine.ipAddresses.map((ip) => (

{ip}

))}
) : (

Waiting for your first device...

)}
); }