fix: handle configs better and propagate errors
This commit is contained in:
parent
e19dbda5ed
commit
ed50c48965
62
app/entry.server.tsx
Normal file
62
app/entry.server.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { PassThrough } from 'node:stream'
|
||||
|
||||
import type { AppLoadContext, EntryContext } from '@remix-run/node'
|
||||
import { createReadableStreamFromReadable } from '@remix-run/node'
|
||||
import { RemixServer } from '@remix-run/react'
|
||||
import { isbot } from 'isbot'
|
||||
import { renderToPipeableStream } from 'react-dom/server'
|
||||
|
||||
import { loadContext } from './utils/config/headplane'
|
||||
|
||||
await loadContext()
|
||||
|
||||
export default function handleRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_loadContext: AppLoadContext,
|
||||
) {
|
||||
const ua = request.headers.get('user-agent')
|
||||
const isBot = ua ? isbot(ua) : false
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let shellRendered = false
|
||||
const { pipe, abort } = renderToPipeableStream(
|
||||
<RemixServer
|
||||
context={remixContext}
|
||||
url={request.url}
|
||||
abortDelay={5000}
|
||||
/>,
|
||||
{
|
||||
[isBot ? 'onAllReady' : 'onShellReady']() {
|
||||
shellRendered = true
|
||||
const body = new PassThrough()
|
||||
const stream = createReadableStreamFromReadable(body)
|
||||
responseHeaders.set('Content-Type', 'text/html')
|
||||
|
||||
resolve(
|
||||
new Response(stream, {
|
||||
headers: responseHeaders,
|
||||
status: responseStatusCode,
|
||||
}),
|
||||
)
|
||||
|
||||
pipe(body)
|
||||
},
|
||||
onShellError(error: unknown) {
|
||||
reject(error as Error)
|
||||
},
|
||||
onError(error: unknown) {
|
||||
responseStatusCode = 500
|
||||
if (shellRendered) {
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
setTimeout(abort, 5000)
|
||||
})
|
||||
}
|
||||
@ -43,12 +43,8 @@ export async function loadContext(): Promise<HeadplaneContext> {
|
||||
return context
|
||||
}
|
||||
|
||||
let config: HeadscaleConfig | undefined
|
||||
try {
|
||||
config = await loadConfig()
|
||||
} catch {}
|
||||
|
||||
const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml')
|
||||
const { config, contextData } = await checkConfig(path)
|
||||
|
||||
let headscaleUrl = process.env.HEADSCALE_URL
|
||||
if (!headscaleUrl && !config) {
|
||||
@ -72,7 +68,7 @@ export async function loadContext(): Promise<HeadplaneContext> {
|
||||
headscaleUrl,
|
||||
cookieSecret,
|
||||
integration: await checkIntegration(),
|
||||
config: await checkConfig(path, config),
|
||||
config: contextData,
|
||||
acl: await checkAcl(config),
|
||||
oidc: await checkOidc(config),
|
||||
}
|
||||
@ -121,7 +117,20 @@ export async function patchAcl(data: string) {
|
||||
await writeFile(path, data, 'utf8')
|
||||
}
|
||||
|
||||
async function checkConfig(path: string, config?: HeadscaleConfig) {
|
||||
async function checkConfig(path: string) {
|
||||
let config: HeadscaleConfig | undefined
|
||||
try {
|
||||
config = await loadConfig(path)
|
||||
} catch {
|
||||
return {
|
||||
config: undefined,
|
||||
contextData: {
|
||||
read: false,
|
||||
write: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let write = false
|
||||
try {
|
||||
await access(path, constants.W_OK)
|
||||
@ -129,8 +138,11 @@ async function checkConfig(path: string, config?: HeadscaleConfig) {
|
||||
} catch {}
|
||||
|
||||
return {
|
||||
read: config ? true : false,
|
||||
write,
|
||||
config,
|
||||
contextData: {
|
||||
read: true,
|
||||
write,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -158,16 +158,40 @@ export type HeadscaleConfig = z.infer<typeof HeadscaleConfig>
|
||||
export let configYaml: Document | undefined
|
||||
export let config: HeadscaleConfig | undefined
|
||||
|
||||
export async function loadConfig() {
|
||||
export async function loadConfig(path?: string) {
|
||||
if (config) {
|
||||
return config
|
||||
}
|
||||
|
||||
const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml')
|
||||
const data = await readFile(path, 'utf8')
|
||||
if (!path) {
|
||||
throw new Error('Path is required to lazy load config')
|
||||
}
|
||||
|
||||
const data = await readFile(path, 'utf8')
|
||||
configYaml = parseDocument(data)
|
||||
config = await HeadscaleConfig.parseAsync(configYaml.toJSON())
|
||||
|
||||
try {
|
||||
config = await HeadscaleConfig.parseAsync(configYaml.toJSON())
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.log('Failed to parse the Headscale configuration file!')
|
||||
console.log('The following schema issues were found:')
|
||||
for (const issue of error.issues) {
|
||||
const path = issue.path.map(String).join('.')
|
||||
const message = issue.message
|
||||
|
||||
console.log(`- '${path}': ${message}`)
|
||||
}
|
||||
|
||||
console.log('')
|
||||
console.log('Please fix the configuration file and try again.')
|
||||
console.log('Headplane will operate as if no config is present.')
|
||||
console.log('')
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user