mirror of
https://github.com/tale/headplane.git
synced 2026-07-28 00:28:56 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90f0bf2555 | |||
| ea2ffdf0c1 | |||
| 9aedd9baad | |||
| 690b52d8c6 | |||
| a72a3d6e5f | |||
| c4c1fd8aab | |||
| 9801ef453d |
@@ -1,3 +1,9 @@
|
|||||||
|
### 0.2.4 (August 24, 2024)
|
||||||
|
- Removed ACL management from the integration since Headscale 0.23-beta2 now supports it natively.
|
||||||
|
- Removed the `ACL_FILE` environment variable since it's no longer needed.
|
||||||
|
- Introduce a `COOKIE_SECURE=false` environment variable to disable HTTPS requirements for cookies.
|
||||||
|
- Fixed a bug where removing Split DNS configurations would crash the UI.
|
||||||
|
|
||||||
### 0.2.3 (August 23, 2024)
|
### 0.2.3 (August 23, 2024)
|
||||||
- Change the minimum required version of Headscale to 0.23-beta2
|
- Change the minimum required version of Headscale to 0.23-beta2
|
||||||
- Support the new API policy mode for Headscale 0.23-beta1
|
- Support the new API policy mode for Headscale 0.23-beta1
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import TabLink from './TabLink'
|
|||||||
|
|
||||||
interface Properties {
|
interface Properties {
|
||||||
readonly data?: {
|
readonly data?: {
|
||||||
acl: HeadplaneContext['acl']
|
|
||||||
config: HeadplaneContext['config']
|
config: HeadplaneContext['config']
|
||||||
user?: SessionData['user']
|
user?: SessionData['user']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,36 +88,6 @@ export default createIntegration<Context>({
|
|||||||
return context.client !== undefined
|
return context.client !== undefined
|
||||||
},
|
},
|
||||||
|
|
||||||
onAclChange: async (context) => {
|
|
||||||
if (!context.client || !context.container) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('INTG', 'Sending SIGHUP to Headscale via Docker')
|
|
||||||
|
|
||||||
let attempts = 0
|
|
||||||
while (attempts <= context.maxAttempts) {
|
|
||||||
const response = await context.client.request({
|
|
||||||
method: 'POST',
|
|
||||||
path: `/v1.30/containers/${context.container}/kill?signal=SIGHUP`,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.statusCode !== 204) {
|
|
||||||
if (attempts < context.maxAttempts) {
|
|
||||||
attempts++
|
|
||||||
await setTimeout(1000)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const stringCode = response.statusCode.toString()
|
|
||||||
const body = await response.body.text()
|
|
||||||
throw new Error(`API request failed: ${stringCode} ${body}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onConfigChange: async (context) => {
|
onConfigChange: async (context) => {
|
||||||
if (!context.client || !context.container) {
|
if (!context.client || !context.container) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ export interface IntegrationFactory<T = any> {
|
|||||||
name: string
|
name: string
|
||||||
context: T
|
context: T
|
||||||
isAvailable: (context: T) => Promise<boolean> | boolean
|
isAvailable: (context: T) => Promise<boolean> | boolean
|
||||||
onAclChange?: (context: T) => Promise<void> | void
|
|
||||||
onConfigChange?: (context: T) => Promise<void> | void
|
onConfigChange?: (context: T) => Promise<void> | void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,15 +177,6 @@ export default createIntegration<Context>({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onAclChange: (context) => {
|
|
||||||
if (!context.pid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('INTG', 'Sending SIGHUP to Headscale')
|
|
||||||
kill(context.pid, 'SIGHUP')
|
|
||||||
},
|
|
||||||
|
|
||||||
onConfigChange: (context) => {
|
onConfigChange: (context) => {
|
||||||
if (!context.pid) {
|
if (!context.pid) {
|
||||||
return
|
return
|
||||||
|
|||||||
+1
-10
@@ -70,14 +70,5 @@ export default createIntegration<Context>({
|
|||||||
log.error('INTG', 'Failed to read /proc')
|
log.error('INTG', 'Failed to read /proc')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
onAclChange: (context) => {
|
|
||||||
if (!context.pid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('INTG', 'Sending SIGHUP to Headscale')
|
|
||||||
kill(context.pid, 'SIGHUP')
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import Notice from '~/components/Notice'
|
|||||||
import Spinner from '~/components/Spinner'
|
import Spinner from '~/components/Spinner'
|
||||||
import { toast } from '~/components/Toaster'
|
import { toast } from '~/components/Toaster'
|
||||||
import { cn } from '~/utils/cn'
|
import { cn } from '~/utils/cn'
|
||||||
import { loadAcl, loadContext, patchAcl } from '~/utils/config/headplane'
|
import { loadContext } from '~/utils/config/headplane'
|
||||||
import { HeadscaleError, pull, put } from '~/utils/headscale'
|
import { HeadscaleError, pull, put } from '~/utils/headscale'
|
||||||
import { getSession } from '~/utils/sessions'
|
import { getSession } from '~/utils/sessions'
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
session.get('hsApiKey')!,
|
session.get('hsApiKey')!,
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(policy)
|
|
||||||
try {
|
try {
|
||||||
// We have read access, now do we have write access?
|
// We have read access, now do we have write access?
|
||||||
// Attempt to set the policy to what we just got
|
// Attempt to set the policy to what we just got
|
||||||
@@ -37,7 +36,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
hasAclWrite: true,
|
hasAclWrite: true,
|
||||||
isPolicyApi: true,
|
|
||||||
currentAcl: policy,
|
currentAcl: policy,
|
||||||
aclType: 'json',
|
aclType: 'json',
|
||||||
} as const
|
} as const
|
||||||
@@ -49,35 +47,15 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
if (error.status === 500) {
|
if (error.status === 500) {
|
||||||
return {
|
return {
|
||||||
hasAclWrite: false,
|
hasAclWrite: false,
|
||||||
isPolicyApi: true,
|
|
||||||
currentAcl: policy,
|
currentAcl: policy,
|
||||||
aclType: 'json',
|
aclType: 'json',
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {}
|
||||||
// Propagate our errors through normal error handling
|
|
||||||
if (!(error instanceof HeadscaleError)) {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not on 0.23-beta1 or later
|
|
||||||
if (error.status === 404) {
|
|
||||||
const { data, type, read, write } = await loadAcl()
|
|
||||||
return {
|
|
||||||
hasAclWrite: write,
|
|
||||||
isPolicyApi: false,
|
|
||||||
currentAcl: read ? data : '',
|
|
||||||
aclType: type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasAclWrite: true,
|
hasAclWrite: true,
|
||||||
isPolicyApi: true,
|
|
||||||
currentAcl: '',
|
currentAcl: '',
|
||||||
aclType: 'json',
|
aclType: 'json',
|
||||||
} as const
|
} as const
|
||||||
@@ -91,32 +69,17 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await request.json() as { acl: string, api: boolean }
|
const { acl } = await request.json() as { acl: string, api: boolean }
|
||||||
if (data.api) {
|
try {
|
||||||
try {
|
await put('v1/policy', session.get('hsApiKey')!, {
|
||||||
await put('v1/policy', session.get('hsApiKey')!, {
|
policy: acl,
|
||||||
policy: data.acl,
|
|
||||||
})
|
|
||||||
|
|
||||||
return json({ success: true })
|
|
||||||
} catch (error) {
|
|
||||||
return json({ success: false }, {
|
|
||||||
status: error instanceof HeadscaleError ? error.status : 500,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = await loadContext()
|
|
||||||
if (!context.acl.write) {
|
|
||||||
return json({ success: false }, {
|
|
||||||
status: 403,
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
await patchAcl(data.acl)
|
return json({ success: true })
|
||||||
|
} catch (error) {
|
||||||
if (context.integration?.onAclChange) {
|
return json({ success: false }, {
|
||||||
await context.integration.onAclChange(context.integration.context)
|
status: error instanceof HeadscaleError ? error.status : 500,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return json({ success: true })
|
return json({ success: true })
|
||||||
@@ -175,23 +138,6 @@ export function ErrorBoundary() {
|
|||||||
<Code>database</Code>
|
<Code>database</Code>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
<p className="mb-2 text-md">
|
|
||||||
If you are running an older version of Headscale, the
|
|
||||||
{' '}
|
|
||||||
<Code>ACL_FILE</Code>
|
|
||||||
{' '}
|
|
||||||
environment variable is not set. Refer to the
|
|
||||||
{' '}
|
|
||||||
<Link
|
|
||||||
to="https://github.com/tale/headplane/blob/main/docs/Configuration.md"
|
|
||||||
name="Headplane Configuration"
|
|
||||||
>
|
|
||||||
Headplane Configuration
|
|
||||||
</Link>
|
|
||||||
{' '}
|
|
||||||
documentation for more information on how to set the
|
|
||||||
ACL file and integrate it with Headscale.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,25 +171,13 @@ export default function Page() {
|
|||||||
? undefined
|
? undefined
|
||||||
: (
|
: (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
{data.isPolicyApi
|
<Notice className="w-fit">
|
||||||
? (
|
The ACL policy is read-only. You can view the current policy
|
||||||
<Notice className="w-fit">
|
but you cannot make changes to it.
|
||||||
The ACL policy is read-only. You can view the current policy
|
<br />
|
||||||
but you cannot make changes to it.
|
To resolve this, you need to set the ACL policy mode to
|
||||||
<br />
|
database in your Headscale configuration.
|
||||||
To resolve this, you need to set the ACL policy mode to
|
</Notice>
|
||||||
database in your Headscale configuration.
|
|
||||||
</Notice>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<Notice className="w-fit">
|
|
||||||
The ACL policy is read-only. You can view the current policy
|
|
||||||
but you cannot make changes to it.
|
|
||||||
<br />
|
|
||||||
To resolve this, you need to configure a Headplane integration
|
|
||||||
or make the ACL_FILE environment variable available.
|
|
||||||
</Notice>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -357,7 +291,6 @@ export default function Page() {
|
|||||||
setToasted(false)
|
setToasted(false)
|
||||||
fetcher.submit({
|
fetcher.submit({
|
||||||
acl,
|
acl,
|
||||||
api: data.isPolicyApi,
|
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json',
|
encType: 'application/json',
|
||||||
|
|||||||
@@ -91,9 +91,9 @@ function NameserverList({ isGlobal, isDisabled, nameservers, name }: ListProps)
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const key = `dns.nameservers.split."${name}"`
|
const key = `dns.nameservers.split."${name}"`
|
||||||
|
const list = nameservers.filter((_, i) => i !== index)
|
||||||
submit({
|
submit({
|
||||||
[key]: nameservers
|
[key]: list.length ? list : null,
|
||||||
.filter((_, i) => i !== index),
|
|
||||||
}, {
|
}, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
encType: 'application/json',
|
encType: 'application/json',
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
const context = await loadContext()
|
const context = await loadContext()
|
||||||
return {
|
return {
|
||||||
acl: context.acl,
|
|
||||||
config: context.config,
|
config: context.config,
|
||||||
user: session.get('user'),
|
user: session.get('user'),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,6 @@ export interface HeadplaneContext {
|
|||||||
write: boolean
|
write: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
acl: {
|
|
||||||
read: boolean
|
|
||||||
write: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
oidc?: {
|
oidc?: {
|
||||||
issuer: string
|
issuer: string
|
||||||
client: string
|
client: string
|
||||||
@@ -69,7 +64,6 @@ export async function loadContext(): Promise<HeadplaneContext> {
|
|||||||
cookieSecret,
|
cookieSecret,
|
||||||
integration: await loadIntegration(),
|
integration: await loadIntegration(),
|
||||||
config: contextData,
|
config: contextData,
|
||||||
acl: await checkAcl(config),
|
|
||||||
oidc: await checkOidc(config),
|
oidc: await checkOidc(config),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,75 +75,10 @@ export async function loadContext(): Promise<HeadplaneContext> {
|
|||||||
: 'Unavailable',
|
: 'Unavailable',
|
||||||
)
|
)
|
||||||
|
|
||||||
log.info('CTXT', 'ACL: %s', context.acl.read
|
|
||||||
? `Found ${context.acl.write ? '' : '(Read Only)'}`
|
|
||||||
: 'Unavailable',
|
|
||||||
)
|
|
||||||
|
|
||||||
log.info('CTXT', 'OIDC: %s', context.oidc ? 'Configured' : 'Unavailable')
|
log.info('CTXT', 'OIDC: %s', context.oidc ? 'Configured' : 'Unavailable')
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadAcl(): Promise<{
|
|
||||||
data: string
|
|
||||||
type: 'json' | 'yaml'
|
|
||||||
read: boolean
|
|
||||||
write: boolean
|
|
||||||
}> {
|
|
||||||
let path = process.env.ACL_FILE
|
|
||||||
if (!path) {
|
|
||||||
try {
|
|
||||||
const config = await loadConfig()
|
|
||||||
path = config.acl_policy_path
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
throw new Error('No ACL file defined')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for attributes
|
|
||||||
let read = false
|
|
||||||
let write = false
|
|
||||||
|
|
||||||
try {
|
|
||||||
await access(path, constants.R_OK)
|
|
||||||
read = true
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await access(path, constants.W_OK)
|
|
||||||
write = true
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
const data = await readFile(path, 'utf8')
|
|
||||||
|
|
||||||
// Naive check for YAML over JSON
|
|
||||||
// This is because JSON.parse doesn't support comments
|
|
||||||
try {
|
|
||||||
parse(data)
|
|
||||||
return { data, type: 'yaml', read, write }
|
|
||||||
} catch {
|
|
||||||
return { data, type: 'json', read, write }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function patchAcl(data: string) {
|
|
||||||
let path = process.env.ACL_FILE
|
|
||||||
if (!path) {
|
|
||||||
try {
|
|
||||||
const config = await loadConfig()
|
|
||||||
path = config.acl_policy_path
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path) {
|
|
||||||
throw new Error('No ACL file defined')
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile(path, data, 'utf8')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkConfig(path: string) {
|
async function checkConfig(path: string) {
|
||||||
let config: HeadscaleConfig | undefined
|
let config: HeadscaleConfig | undefined
|
||||||
try {
|
try {
|
||||||
@@ -179,32 +108,6 @@ async function checkConfig(path: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkAcl(config?: HeadscaleConfig) {
|
|
||||||
let path = process.env.ACL_FILE
|
|
||||||
if (!path && config) {
|
|
||||||
path = config.acl_policy_path
|
|
||||||
}
|
|
||||||
|
|
||||||
let read = false
|
|
||||||
let write = false
|
|
||||||
if (path) {
|
|
||||||
try {
|
|
||||||
await access(path, constants.R_OK)
|
|
||||||
read = true
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await access(path, constants.W_OK)
|
|
||||||
write = true
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
read,
|
|
||||||
write,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkOidc(config?: HeadscaleConfig) {
|
async function checkOidc(config?: HeadscaleConfig) {
|
||||||
const disableKeyLogin = process.env.DISABLE_API_KEY_LOGIN === 'true'
|
const disableKeyLogin = process.env.DISABLE_API_KEY_LOGIN === 'true'
|
||||||
const rootKey = process.env.ROOT_API_KEY ?? process.env.API_KEY
|
const rootKey = process.env.ROOT_API_KEY ?? process.env.API_KEY
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ const HeadscaleConfig = z.object({
|
|||||||
disable_check_updates: goBool.default(false),
|
disable_check_updates: goBool.default(false),
|
||||||
ephemeral_node_inactivity_timeout: goDuration.default('120s'),
|
ephemeral_node_inactivity_timeout: goDuration.default('120s'),
|
||||||
randomize_client_port: goBool.default(false),
|
randomize_client_port: goBool.default(false),
|
||||||
acl_policy_path: z.string().optional(),
|
|
||||||
|
|
||||||
acme_email: z.string().optional(),
|
acme_email: z.string().optional(),
|
||||||
acme_url: z.string().optional(),
|
acme_url: z.string().optional(),
|
||||||
@@ -309,6 +308,11 @@ export async function patchConfig(partial: Record<string, unknown>) {
|
|||||||
|
|
||||||
// Push the remaining element
|
// Push the remaining element
|
||||||
path.push(temp.replaceAll('"', ''))
|
path.push(temp.replaceAll('"', ''))
|
||||||
|
if (value === null) {
|
||||||
|
configYaml.deleteIn(path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
configYaml.setIn(path, value)
|
configYaml.setIn(path, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ export const {
|
|||||||
maxAge: 60 * 60 * 24, // 24 hours
|
maxAge: 60 * 60 * 24, // 24 hours
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
secrets: [process.env.COOKIE_SECRET!],
|
secrets: [process.env.COOKIE_SECRET!],
|
||||||
secure: true
|
secure: process.env.COOKIE_SECURE !== 'false',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ networks:
|
|||||||
driver: 'bridge'
|
driver: 'bridge'
|
||||||
services:
|
services:
|
||||||
headscale:
|
headscale:
|
||||||
image: 'headscale/headscale:0.23.0-beta1'
|
image: 'headscale/headscale:0.23.0-beta2'
|
||||||
container_name: 'headscale'
|
container_name: 'headscale'
|
||||||
restart: 'unless-stopped'
|
restart: 'unless-stopped'
|
||||||
command: 'serve'
|
command: 'serve'
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ When the ACL file is available for editing, the `Access Controls` tab will
|
|||||||
become available. All of the integrations support automatic reloading of the
|
become available. All of the integrations support automatic reloading of the
|
||||||
ACLs when the file is changed.
|
ACLs when the file is changed.
|
||||||
|
|
||||||
> By default, the ACL file is read from `/etc/headscale/acl_policy.json`. This
|
> By default, the ACL file is read from `/etc/headscale/acl_policy.json`.
|
||||||
can be overridden by setting the `ACL_FILE` environment variable and is also
|
> If `policy.path` is set and `policy.mode` is set to `file`, the ACL file will
|
||||||
overriden by the `acl_policy_path` key in the configuration file if set.
|
> be read from the path specified in the configuration file instead.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ services:
|
|||||||
OIDC_ISSUER: 'https://sso.example.com'
|
OIDC_ISSUER: 'https://sso.example.com'
|
||||||
OIDC_CLIENT_SECRET: 'super_secret_client_secret'
|
OIDC_CLIENT_SECRET: 'super_secret_client_secret'
|
||||||
DISABLE_API_KEY_LOGIN: 'true'
|
DISABLE_API_KEY_LOGIN: 'true'
|
||||||
|
COOKIE_SECURE: 'false'
|
||||||
|
|
||||||
# These are the default values
|
# These are the default values
|
||||||
HOST: '0.0.0.0'
|
HOST: '0.0.0.0'
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ You can configure Headplane using environment variables.
|
|||||||
- **`HOST`**: The host to bind the server to (default: `0.0.0.0`).
|
- **`HOST`**: The host to bind the server to (default: `0.0.0.0`).
|
||||||
- **`PORT`**: The port to bind the server to (default: `3000`).
|
- **`PORT`**: The port to bind the server to (default: `3000`).
|
||||||
- **`CONFIG_FILE`**: The path to the Headscale `config.yaml` (default: `/etc/headscale/config.yaml`).
|
- **`CONFIG_FILE`**: The path to the Headscale `config.yaml` (default: `/etc/headscale/config.yaml`).
|
||||||
- **`ACL_FILE`**: The path to the ACL file (default: `/etc/headscale/acl_policy.json`, not needed if you have `acl_policy_path` in your config).
|
|
||||||
- **`HEADSCALE_CONFIG_UNSTRICT`**: This will disable the strict configuration loader (default: `false`).
|
- **`HEADSCALE_CONFIG_UNSTRICT`**: This will disable the strict configuration loader (default: `false`).
|
||||||
|
- **`COOKIE_SECURE`**: This option enables the `Secure` flag for cookies, ensuring they are sent only over HTTPS, which helps prevent interception and enhances data security. It should be disabled when using HTTP instead of HTTPS (default: `true`).
|
||||||
|
|
||||||
#### Docker Integration
|
#### Docker Integration
|
||||||
The Docker integration allows Headplane to manage the Headscale docker container.
|
The Docker integration allows Headplane to manage the Headscale docker container.
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ services:
|
|||||||
HOST: '0.0.0.0'
|
HOST: '0.0.0.0'
|
||||||
PORT: '3000'
|
PORT: '3000'
|
||||||
|
|
||||||
|
# Only set this to false if you aren't behind a reverse proxy
|
||||||
|
COOKIE_SECURE: 'false'
|
||||||
|
|
||||||
# Overrides the configuration file values if they are set in config.yaml
|
# Overrides the configuration file values if they are set in config.yaml
|
||||||
# If you want to share the same OIDC configuration you do not need this
|
# If you want to share the same OIDC configuration you do not need this
|
||||||
OIDC_CLIENT_ID: 'headscale'
|
OIDC_CLIENT_ID: 'headscale'
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ spec:
|
|||||||
valueFrom:
|
valueFrom:
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: metadata.name
|
fieldPath: metadata.name
|
||||||
|
|
||||||
|
# Only set this to false if you aren't behind a reverse proxy
|
||||||
|
- name: COOKIE_SECURE
|
||||||
|
value: 'false'
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: headscale-config
|
- name: headscale-config
|
||||||
mountPath: /etc/headscale
|
mountPath: /etc/headscale
|
||||||
|
|||||||
Reference in New Issue
Block a user