Skip to content

Commit a98b10c

Browse files
authored
chore: 🔧 env vars in yaml config (apotdevin#194)
1 parent bd3b2d3 commit a98b10c

File tree

6 files changed

+189
-14
lines changed

6 files changed

+189
-14
lines changed

.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
# Account Configs
3131
# -----------
3232
# ACCOUNT_CONFIG_PATH='/path/to/config/thubConfig.yaml'
33+
# YML_ENV_1=''
34+
# YML_ENV_2=''
35+
# YML_ENV_3=''
36+
# YML_ENV_4=''
3337

3438
# -----------
3539
# SSO Account Configs

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,22 @@ accounts:
213213
214214
Notice you can specify either `macaroonPath` and `certificatePath` or `macaroon` and `certificate`.
215215

216+
#### Account with environment variables
217+
218+
It's possible to set different parts of the accounts based on environment variables.
219+
220+
You can use the following environment variables: `YML_ENV_1`, `YML_ENV_2`, `YML_ENV_3`, `YML_ENV_4` and fill your accounts in the following way:
221+
222+
```yaml
223+
accounts:
224+
- name: '{YML_ENV_1}'
225+
serverUrl: '{YML_ENV_2}'
226+
macaroon: 'macaroonforthisaccount'
227+
certificate: '{YML_ENV_4}'
228+
```
229+
230+
ThunderHub will take care of replacing the fields with the correct environment variables. The `{YML_ENV_[1-4]}` can only be used for fields inside the accounts. So for example using it for the `masterPassword` will not work.
231+
216232
#### Account with LND directory
217233

218234
You can also specify the main LND directory and ThunderHub will look for the certificate and the macaroon in the default folders (based on the network).

next.config.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,42 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
33
enabled: process.env.ANALYZE === 'true',
44
});
55

6+
const ymlEnv = {
7+
YML_ENV_1: process.env.YML_ENV_1 || '',
8+
YML_ENV_2: process.env.YML_ENV_2 || '',
9+
YML_ENV_3: process.env.YML_ENV_3 || '',
10+
YML_ENV_4: process.env.YML_ENV_4 || '',
11+
};
12+
13+
const ssoEnv = {
14+
cookiePath: process.env.COOKIE_PATH || '',
15+
lnServerUrl: process.env.SSO_SERVER_URL || '',
16+
lnCertPath: process.env.SSO_CERT_PATH || '',
17+
macaroonPath: process.env.SSO_MACAROON_PATH || '',
18+
dangerousNoSSOAuth:
19+
process.env.DANGEROUS_NO_SSO_AUTH === 'true' ? true : false,
20+
};
21+
22+
const sslEnv = {
23+
publicUrl: process.env.PUBLIC_URL || '',
24+
sslPort: process.env.SSL_PORT || '',
25+
sslSave: process.env.SSL_SAVE || '',
26+
};
27+
28+
const accountConfig = {
29+
accountConfigPath: process.env.ACCOUNT_CONFIG_PATH || '',
30+
};
31+
632
module.exports = withBundleAnalyzer({
733
poweredByHeader: false,
834
basePath: process.env.BASE_PATH || '',
935
serverRuntimeConfig: {
1036
nodeEnv: process.env.NODE_ENV || 'development',
1137
logLevel: process.env.LOG_LEVEL || 'info',
12-
cookiePath: process.env.COOKIE_PATH || '',
13-
lnServerUrl: process.env.SSO_SERVER_URL || '',
14-
lnCertPath: process.env.SSO_CERT_PATH || '',
15-
macaroonPath: process.env.SSO_MACAROON_PATH || '',
16-
accountConfigPath: process.env.ACCOUNT_CONFIG_PATH || '',
17-
publicUrl: process.env.PUBLIC_URL || '',
18-
sslPort: process.env.SSL_PORT || '',
19-
sslSave: process.env.SSL_SAVE || '',
20-
dangerousNoSSOAuth:
21-
process.env.DANGEROUS_NO_SSO_AUTH === 'true' ? true : false,
38+
...ssoEnv,
39+
...accountConfig,
40+
...sslEnv,
41+
...ymlEnv,
2242
},
2343
publicRuntimeConfig: {
2444
nodeEnv: process.env.NODE_ENV || 'development',
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { resolveEnvVarsInAccount } from '../env';
2+
import { AccountType, UnresolvedAccountType } from '../fileHelpers';
3+
4+
const vars = {
5+
YML_ENV_1: 'firstEnv',
6+
YML_ENV_2: 'macaroonString',
7+
YML_ENV_3: 'false',
8+
YML_ENV_4: 'true',
9+
};
10+
11+
jest.mock('next/config', () => () => ({
12+
serverRuntimeConfig: vars,
13+
}));
14+
15+
describe('resolveEnvVarsInAccount', () => {
16+
it('returns resolved account', () => {
17+
const account: UnresolvedAccountType = {
18+
name: '{YML_ENV_1}',
19+
serverUrl: 'server.url:10009',
20+
macaroon: '{YML_ENV_2}',
21+
};
22+
23+
const resolved = resolveEnvVarsInAccount(account);
24+
25+
const result: AccountType = {
26+
name: 'firstEnv',
27+
serverUrl: 'server.url:10009',
28+
macaroon: 'macaroonString',
29+
};
30+
31+
expect(resolved).toStrictEqual(result);
32+
});
33+
it('resolves false boolean values', () => {
34+
const account: UnresolvedAccountType = {
35+
name: '{YML_ENV_1}',
36+
serverUrl: 'server.url:10009',
37+
encrypted: '{YML_ENV_3}',
38+
};
39+
40+
const resolved = resolveEnvVarsInAccount(account);
41+
42+
const result: AccountType = {
43+
name: 'firstEnv',
44+
serverUrl: 'server.url:10009',
45+
encrypted: false,
46+
};
47+
48+
expect(resolved).toStrictEqual(result);
49+
});
50+
it('resolves true boolean values', () => {
51+
const account: UnresolvedAccountType = {
52+
name: '{YML_ENV_1}',
53+
serverUrl: 'server.url:10009',
54+
encrypted: '{YML_ENV_4}',
55+
};
56+
57+
const resolved = resolveEnvVarsInAccount(account);
58+
59+
const result: AccountType = {
60+
name: 'firstEnv',
61+
serverUrl: 'server.url:10009',
62+
encrypted: true,
63+
};
64+
65+
expect(resolved).toStrictEqual(result);
66+
});
67+
it('does not resolve non existing env vars', () => {
68+
const account: UnresolvedAccountType = {
69+
macaroon: '{YML_ENV_NONE}',
70+
};
71+
72+
const resolved = resolveEnvVarsInAccount(account);
73+
74+
expect(resolved).toStrictEqual({
75+
macaroon: '{YML_ENV_NONE}',
76+
});
77+
});
78+
});

server/helpers/env.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import getConfig from 'next/config';
2+
import { AccountType, UnresolvedAccountType } from './fileHelpers';
3+
4+
const { serverRuntimeConfig } = getConfig() || { serverRuntimeConfig: {} };
5+
const { YML_ENV_1, YML_ENV_2, YML_ENV_3, YML_ENV_4 } = serverRuntimeConfig;
6+
7+
const env: { [key: string]: string } = {
8+
YML_ENV_1,
9+
YML_ENV_2,
10+
YML_ENV_3,
11+
YML_ENV_4,
12+
};
13+
14+
export const resolveEnvVarsInAccount = (
15+
account: UnresolvedAccountType
16+
): AccountType => {
17+
const regex = /(?<=\{)(.*?)(?=\})/;
18+
19+
const resolved = Object.fromEntries(
20+
Object.entries(account).map(([k, v]) => {
21+
if (typeof v !== 'string') {
22+
return [k, v];
23+
}
24+
25+
const match: string | boolean =
26+
env[v.toString().match(regex)?.[0] || ''] || v;
27+
28+
if (match === 'true') {
29+
return [k, true];
30+
}
31+
32+
if (match === 'false') {
33+
return [k, false];
34+
}
35+
36+
return [k, match];
37+
})
38+
);
39+
40+
return resolved;
41+
};

server/helpers/fileHelpers.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { logger } from 'server/helpers/logger';
66
import yaml from 'js-yaml';
77
import { getUUID } from './auth';
88
import { hashPassword } from './crypto';
9+
import { resolveEnvVarsInAccount } from './env';
910

1011
type EncodingType = 'hex' | 'utf-8';
1112
type BitcoinNetwork = 'mainnet' | 'regtest' | 'testnet';
@@ -23,6 +24,19 @@ export type AccountType = {
2324
encrypted?: boolean;
2425
};
2526

27+
export type UnresolvedAccountType = {
28+
name?: string;
29+
serverUrl?: string;
30+
lndDir?: string;
31+
network?: BitcoinNetwork;
32+
macaroonPath?: string;
33+
certificatePath?: string;
34+
password?: string | null;
35+
macaroon?: string;
36+
certificate?: string;
37+
encrypted?: boolean | string;
38+
};
39+
2640
export type ParsedAccount = {
2741
name: string;
2842
id: string;
@@ -228,11 +242,13 @@ export const getAccounts = (filePath: string): ParsedAccount[] => {
228242
};
229243

230244
export const getParsedAccount = (
231-
account: AccountType,
245+
account: UnresolvedAccountType,
232246
index: number,
233247
masterPassword: string | null,
234248
defaultNetwork: BitcoinNetwork
235249
): ParsedAccount | null => {
250+
const resolvedAccount = resolveEnvVarsInAccount(account);
251+
236252
const {
237253
name,
238254
serverUrl,
@@ -242,7 +258,7 @@ export const getParsedAccount = (
242258
macaroon: macaroonValue,
243259
password,
244260
encrypted,
245-
} = account;
261+
} = resolvedAccount;
246262

247263
const missingFields: string[] = [];
248264
if (!name) missingFields.push('name');
@@ -269,14 +285,14 @@ export const getParsedAccount = (
269285
return null;
270286
}
271287

272-
const cert = getCertificate(account);
288+
const cert = getCertificate(resolvedAccount);
273289
if (!cert) {
274290
logger.warn(
275291
`No certificate for account ${name}. Make sure you don't need it to connect.`
276292
);
277293
}
278294

279-
const macaroon = getMacaroon(account, defaultNetwork);
295+
const macaroon = getMacaroon(resolvedAccount, defaultNetwork);
280296
if (!macaroon) {
281297
logger.error(
282298
`Account ${name} has neither lnd directory, macaroon nor macaroon path specified.`

0 commit comments

Comments
 (0)