feat: implement context sanity checks
This commit is contained in:
parent
e216d4171f
commit
4e129d527a
@ -18,8 +18,12 @@ import RenameModal from './rename'
|
||||
|
||||
// We do not want to expose every config value
|
||||
export async function loader() {
|
||||
const config = await getConfig()
|
||||
const context = await getContext()
|
||||
if (!context.hasConfig) {
|
||||
throw new Error('No configuration is available')
|
||||
}
|
||||
|
||||
const config = await getConfig()
|
||||
|
||||
const dns = {
|
||||
prefixes: config.prefixes,
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { Cog8ToothIcon, CpuChipIcon, GlobeAltIcon, LockClosedIcon, ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline'
|
||||
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
||||
import { Outlet, useRouteError } from '@remix-run/react'
|
||||
import { Outlet, useLoaderData, useRouteError } from '@remix-run/react'
|
||||
|
||||
import { ErrorPopup } from '~/components/Error'
|
||||
import TabLink from '~/components/TabLink'
|
||||
import { getContext } from '~/utils/config'
|
||||
import { HeadscaleError, pull } from '~/utils/headscale'
|
||||
import { destroySession, getSession } from '~/utils/sessions'
|
||||
|
||||
@ -32,11 +33,12 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
return null
|
||||
const context = await getContext()
|
||||
return context
|
||||
}
|
||||
|
||||
export default function Layout() {
|
||||
const data = useLoaderData<typeof loader>()
|
||||
return (
|
||||
<>
|
||||
<header className='mb-16 bg-gray-800 text-white dark:bg-gray-700'>
|
||||
@ -48,9 +50,13 @@ export default function Layout() {
|
||||
<div className='flex items-center gap-x-4'>
|
||||
<TabLink to='/machines' name='Machines' icon={<ServerStackIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/users' name='Users' icon={<UsersIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
|
||||
{data.hasAcl ? <TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/> : undefined}
|
||||
{data.hasConfig ? (
|
||||
<>
|
||||
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
|
||||
</>
|
||||
) : undefined}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@ -63,6 +69,12 @@ export default function Layout() {
|
||||
}
|
||||
|
||||
export function ErrorBoundary() {
|
||||
const data = useLoaderData<typeof loader>()
|
||||
const error = useRouteError()
|
||||
if (!data) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className='mb-16 bg-gray-800 text-white dark:bg-gray-700'>
|
||||
@ -74,9 +86,13 @@ export function ErrorBoundary() {
|
||||
<div className='flex items-center gap-x-4'>
|
||||
<TabLink to='/machines' name='Machines' icon={<ServerStackIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/users' name='Users' icon={<UsersIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
|
||||
{data.hasAcl ? <TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/> : undefined}
|
||||
{data.hasConfig ? (
|
||||
<>
|
||||
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
|
||||
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
|
||||
</>
|
||||
) : undefined}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@ -142,7 +142,10 @@ export async function patchConfig(partial: Record<string, unknown>) {
|
||||
|
||||
type Context = {
|
||||
hasDockerSock: boolean;
|
||||
hasConfig: boolean;
|
||||
hasConfigWrite: boolean;
|
||||
hasAcl: boolean;
|
||||
hasAclWrite: boolean;
|
||||
}
|
||||
|
||||
export let context: Context
|
||||
@ -151,24 +154,16 @@ export async function getContext() {
|
||||
if (!context) {
|
||||
context = {
|
||||
hasDockerSock: await checkSock(),
|
||||
hasConfigWrite: await checkConfigWrite()
|
||||
hasConfig: await hasConfig(),
|
||||
hasConfigWrite: await hasConfigW(),
|
||||
hasAcl: await hasAcl(),
|
||||
hasAclWrite: await hasAclW()
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
async function checkConfigWrite() {
|
||||
try {
|
||||
await getConfig()
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to read config file', error)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async function checkSock() {
|
||||
try {
|
||||
await access('/var/run/docker.sock', constants.R_OK)
|
||||
@ -181,3 +176,68 @@ async function checkSock() {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async function hasConfig() {
|
||||
try {
|
||||
await getConfig()
|
||||
return true
|
||||
} catch {}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async function hasConfigW() {
|
||||
const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml')
|
||||
try {
|
||||
await access(path, constants.W_OK)
|
||||
return true
|
||||
} catch {}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async function hasAcl() {
|
||||
let path = process.env.ACL_FILE
|
||||
if (!path) {
|
||||
try {
|
||||
const config = await getConfig()
|
||||
path = config.acl_policy_path
|
||||
} catch {}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await access(path, constants.R_OK)
|
||||
return true
|
||||
} catch {}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async function hasAclW() {
|
||||
let path = process.env.ACL_FILE
|
||||
if (!path) {
|
||||
try {
|
||||
const config = await getConfig()
|
||||
path = config.acl_policy_path
|
||||
} catch {}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await access(path, constants.W_OK)
|
||||
return true
|
||||
} catch {}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user