diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx
index 660e9f4..2a8dc7d 100644
--- a/app/routes/machines/machine.tsx
+++ b/app/routes/machines/machine.tsx
@@ -108,7 +108,10 @@ export default function Page() {
- {node.user.name || node.user.displayName || node.user.email || node.user.id}
+ {node.user.name ||
+ node.user.displayName ||
+ node.user.email ||
+ node.user.id}
@@ -253,7 +256,15 @@ export default function Page() {
variant="flat"
>
-
+
{
+export function interpolateEnvVars(str: string): string {
+ return str.replace(/\$\{([^}]+)\}/g, (_, varName) => {
const value = env[varName];
if (value === undefined) {
throw new ConfigError(`Environment variable "${varName}" not found`);
@@ -52,9 +52,9 @@ export async function loadConfig({ loadEnv, path }: EnvOverrides) {
if (!loadEnv) {
log.debug('config', 'Environment variable overrides are disabled');
log.debug('config', 'This also disables the loading of a .env file');
- config = await loadSecretsFromFiles(config);
+ const moddedConfig = await loadSecretsFromFiles(config);
log.debug('config', 'Loaded file-based secrets');
- return config;
+ return moddedConfig;
}
log.info('config', 'Loading a .env file (if available)');
@@ -67,10 +67,10 @@ export async function loadConfig({ loadEnv, path }: EnvOverrides) {
);
}
- config = await loadSecretsFromFiles(config);
+ const moddedConfig = await loadSecretsFromFiles(config);
log.debug('config', 'Loaded file-based secrets');
- return config;
+ return moddedConfig;
}
/**
@@ -78,13 +78,40 @@ export async function loadConfig({ loadEnv, path }: EnvOverrides) {
* reads that file and assigns its contents to the corresponding key
* without the suffix, then removes the "_path" property.
*/
-const SECRET_PATH_KEYS = new Set([
+const SECRET_PATH_KEYS = [
'pre_authkey_path',
'client_secret_path',
'headscale_api_key_path',
'cookie_secret_path',
-]);
-async function loadSecretsFromFiles(obj: T): Promise {
+] as const;
+
+// For fast set hashing lookups, but we still need the array for typings
+const SECRET_PATH_KEY_SET = new Set(SECRET_PATH_KEYS);
+
+type SecretPathKey = (typeof SECRET_PATH_KEYS)[number];
+type StripPath = S extends `${infer T}_path` ? T : never;
+type KeysToPromote = Extract;
+type MappedKeys = StripPath>;
+
+type NonNullablized = Omit | MappedKeys> & {
+ [K in MappedKeys]-?: string;
+};
+
+type NestedNonNullablized = T extends readonly (infer U)[]
+ ? readonly NestedNonNullablized[]
+ : T extends (infer U)[]
+ ? NestedNonNullablized[]
+ : T extends object
+ ? {
+ [K in keyof NonNullablized]: NestedNonNullablized<
+ NonNullablized[K]
+ >;
+ }
+ : T;
+
+async function loadSecretsFromFiles(
+ obj: T,
+): Promise> {
// Work with a Record so we can mutate/delete properties
const record = obj as Record;
@@ -97,7 +124,7 @@ async function loadSecretsFromFiles(obj: T): Promise {
continue;
}
- if (SECRET_PATH_KEYS.has(key) && typeof val === 'string') {
+ if (SECRET_PATH_KEY_SET.has(key) && typeof val === 'string') {
try {
const path = interpolateEnvVars(val);
const content = await readFile(path, 'utf8');
@@ -114,26 +141,7 @@ async function loadSecretsFromFiles(obj: T): Promise {
}
// Cast back to the original T so callers keep their precise type
- return record as T;
-}
-
-export async function hp_loadConfig() {
- // // OIDC Related Checks
- // if (config.oidc) {
- // if (!config.oidc.client_secret && !config.oidc.client_secret_path) {
- // log.error('CFGX', 'OIDC configuration is missing a secret, disabling');
- // log.error(
- // 'CFGX',
- // 'Please specify either `oidc.client_secret` or `oidc.client_secret_path`',
- // );
- // }
- // if (config.oidc?.strict_validation) {
- // const result = await testOidc(config.oidc);
- // if (!result) {
- // log.error('CFGX', 'OIDC configuration failed validation, disabling');
- // }
- // }
- // }
+ return record as NestedNonNullablized;
}
async function validateConfigPath(path: string) {