@simplens/onboard 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/__tests__/infra-prompts.test.js +3 -1
- package/dist/__tests__/infra-prompts.test.js.map +1 -1
- package/dist/__tests__/infra.test.js +13 -0
- package/dist/__tests__/infra.test.js.map +1 -1
- package/dist/__tests__/validators.test.js +21 -1
- package/dist/__tests__/validators.test.js.map +1 -1
- package/dist/index.js +138 -20
- package/dist/index.js.map +1 -1
- package/dist/infra.d.ts +12 -3
- package/dist/infra.d.ts.map +1 -1
- package/dist/infra.js +94 -22
- package/dist/infra.js.map +1 -1
- package/dist/services.d.ts +12 -0
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +52 -2
- package/dist/services.js.map +1 -1
- package/dist/templates.d.ts +5 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +66 -0
- package/dist/templates.js.map +1 -1
- package/dist/types/domain.d.ts +6 -0
- package/dist/types/domain.d.ts.map +1 -1
- package/dist/validators.d.ts +9 -0
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +38 -0
- package/dist/validators.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/infra-prompts.test.ts +3 -1
- package/src/__tests__/infra.test.ts +15 -0
- package/src/__tests__/validators.test.ts +27 -1
- package/src/index.ts +147 -19
- package/src/infra.ts +119 -23
- package/src/services.ts +74 -2
- package/src/templates.ts +70 -0
- package/src/types/domain.ts +6 -0
- package/src/validators.ts +51 -0
package/src/services.ts
CHANGED
|
@@ -19,6 +19,15 @@ async function execDockerCompose(args: string[], cwd: string): Promise<void> {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
type ComposeFile = 'docker-compose.yaml' | 'docker-compose.infra.yaml';
|
|
23
|
+
|
|
24
|
+
function withComposeFile(args: string[], composeFile?: ComposeFile): string[] {
|
|
25
|
+
if (!composeFile) {
|
|
26
|
+
return args;
|
|
27
|
+
}
|
|
28
|
+
return ['-f', composeFile, ...args];
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
/**
|
|
23
32
|
* Prompts user whether to start the services immediately after setup.
|
|
24
33
|
*
|
|
@@ -125,6 +134,69 @@ export async function startAppServices(targetDir: string): Promise<void> {
|
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
|
|
137
|
+
export function getSslManualCommands(options: {
|
|
138
|
+
composeFile: ComposeFile;
|
|
139
|
+
domain: string;
|
|
140
|
+
email: string;
|
|
141
|
+
}): string[] {
|
|
142
|
+
const composeFlag = options.composeFile === 'docker-compose.infra.yaml'
|
|
143
|
+
? '-f docker-compose.infra.yaml '
|
|
144
|
+
: '';
|
|
145
|
+
|
|
146
|
+
return [
|
|
147
|
+
`docker compose ${composeFlag}up -d nginx certbot certbot-renew`,
|
|
148
|
+
`docker compose ${composeFlag}exec -T certbot certbot certonly --webroot -w /var/www/certbot --email ${options.email} --agree-tos --no-eff-email -d ${options.domain} --non-interactive`,
|
|
149
|
+
`docker compose ${composeFlag}exec -T nginx nginx -s reload`,
|
|
150
|
+
`docker compose ${composeFlag}up -d certbot-renew`,
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function setupSslCertificates(targetDir: string, options: {
|
|
155
|
+
composeFile: ComposeFile;
|
|
156
|
+
domain: string;
|
|
157
|
+
email: string;
|
|
158
|
+
}): Promise<void> {
|
|
159
|
+
logInfo(`Setting up SSL certificate for ${options.domain}...`);
|
|
160
|
+
|
|
161
|
+
const s = spinner();
|
|
162
|
+
const composeArgs = (args: string[]) => withComposeFile(args, options.composeFile);
|
|
163
|
+
|
|
164
|
+
s.start('Ensuring nginx/certbot services are running...');
|
|
165
|
+
await execDockerCompose(composeArgs(['up', '-d', 'nginx', 'certbot']), targetDir);
|
|
166
|
+
s.stop('Nginx and certbot services are running');
|
|
167
|
+
|
|
168
|
+
s.start('Requesting initial certificate from Let\'s Encrypt...');
|
|
169
|
+
await execDockerCompose(
|
|
170
|
+
composeArgs([
|
|
171
|
+
'exec',
|
|
172
|
+
'-T',
|
|
173
|
+
'certbot',
|
|
174
|
+
'certbot',
|
|
175
|
+
'certonly',
|
|
176
|
+
'--webroot',
|
|
177
|
+
'-w',
|
|
178
|
+
'/var/www/certbot',
|
|
179
|
+
'--email',
|
|
180
|
+
options.email,
|
|
181
|
+
'--agree-tos',
|
|
182
|
+
'--no-eff-email',
|
|
183
|
+
'-d',
|
|
184
|
+
options.domain,
|
|
185
|
+
'--non-interactive',
|
|
186
|
+
]),
|
|
187
|
+
targetDir
|
|
188
|
+
);
|
|
189
|
+
s.stop('Initial certificate issued');
|
|
190
|
+
|
|
191
|
+
s.start('Reloading nginx to apply certificates...');
|
|
192
|
+
await execDockerCompose(composeArgs(['exec', '-T', 'nginx', 'nginx', '-s', 'reload']), targetDir);
|
|
193
|
+
s.stop('Nginx reloaded');
|
|
194
|
+
|
|
195
|
+
s.start('Starting automatic certificate renewal service...');
|
|
196
|
+
await execDockerCompose(composeArgs(['up', '-d', 'certbot-renew']), targetDir);
|
|
197
|
+
s.stop('Certificate auto-renewal service started');
|
|
198
|
+
}
|
|
199
|
+
|
|
128
200
|
/**
|
|
129
201
|
* Display service status and URLs
|
|
130
202
|
*/
|
|
@@ -170,8 +242,8 @@ export async function displayServiceStatus(): Promise<void> {
|
|
|
170
242
|
console.log('');
|
|
171
243
|
|
|
172
244
|
printCommandHints('Helpful commands', [
|
|
173
|
-
'docker
|
|
174
|
-
'docker
|
|
245
|
+
'docker compose logs -f',
|
|
246
|
+
'docker compose down',
|
|
175
247
|
]);
|
|
176
248
|
console.log(`${divider('green', '═')}\n`);
|
|
177
249
|
} catch (error) {
|
package/src/templates.ts
CHANGED
|
@@ -127,8 +127,11 @@ export const APP_COMPOSE_TEMPLATE = `services:
|
|
|
127
127
|
- 3000:3000
|
|
128
128
|
env_file:
|
|
129
129
|
- .env
|
|
130
|
+
environment:
|
|
131
|
+
SIMPLENS_CONFIG_PATH: \${SIMPLENS_CONFIG_PATH:-/app/simplens.config.yaml}
|
|
130
132
|
volumes:
|
|
131
133
|
- plugin-data:/app/.plugins
|
|
134
|
+
- logs-data:/app/logs
|
|
132
135
|
- ./simplens.config.yaml:/app/simplens.config.yaml:ro
|
|
133
136
|
command: [ "node", "dist/api/server.js" ]
|
|
134
137
|
restart: unless-stopped
|
|
@@ -143,6 +146,8 @@ export const APP_COMPOSE_TEMPLATE = `services:
|
|
|
143
146
|
image: ghcr.io/simplenotificationsystem/simplens-core:\${CORE_VERSION:-latest}
|
|
144
147
|
env_file:
|
|
145
148
|
- .env
|
|
149
|
+
volumes:
|
|
150
|
+
- logs-data:/app/logs
|
|
146
151
|
command: [ "node", "dist/workers/worker.js" ]
|
|
147
152
|
restart: unless-stopped
|
|
148
153
|
|
|
@@ -150,8 +155,11 @@ export const APP_COMPOSE_TEMPLATE = `services:
|
|
|
150
155
|
image: ghcr.io/simplenotificationsystem/simplens-core:\${CORE_VERSION:-latest}
|
|
151
156
|
env_file:
|
|
152
157
|
- .env
|
|
158
|
+
environment:
|
|
159
|
+
SIMPLENS_CONFIG_PATH: \${SIMPLENS_CONFIG_PATH:-/app/simplens.config.yaml}
|
|
153
160
|
volumes:
|
|
154
161
|
- plugin-data:/app/.plugins
|
|
162
|
+
- logs-data:/app/logs
|
|
155
163
|
- ./simplens.config.yaml:/app/simplens.config.yaml:ro
|
|
156
164
|
command: [ "node", "dist/processors/unified/unified.processor.js" ]
|
|
157
165
|
depends_on:
|
|
@@ -163,6 +171,8 @@ export const APP_COMPOSE_TEMPLATE = `services:
|
|
|
163
171
|
image: ghcr.io/simplenotificationsystem/simplens-core:\${CORE_VERSION:-latest}
|
|
164
172
|
env_file:
|
|
165
173
|
- .env
|
|
174
|
+
volumes:
|
|
175
|
+
- logs-data:/app/logs
|
|
166
176
|
command: [ "node", "dist/processors/delayed/delayed.processor.js" ]
|
|
167
177
|
restart: unless-stopped
|
|
168
178
|
|
|
@@ -170,6 +180,8 @@ export const APP_COMPOSE_TEMPLATE = `services:
|
|
|
170
180
|
image: ghcr.io/simplenotificationsystem/simplens-core:\${CORE_VERSION:-latest}
|
|
171
181
|
env_file:
|
|
172
182
|
- .env
|
|
183
|
+
volumes:
|
|
184
|
+
- logs-data:/app/logs
|
|
173
185
|
command: [ "node", "dist/workers/recovery/recovery.service.js" ]
|
|
174
186
|
restart: unless-stopped
|
|
175
187
|
|
|
@@ -189,6 +201,7 @@ export const APP_COMPOSE_TEMPLATE = `services:
|
|
|
189
201
|
|
|
190
202
|
volumes:
|
|
191
203
|
plugin-data:
|
|
204
|
+
logs-data:
|
|
192
205
|
|
|
193
206
|
networks:
|
|
194
207
|
default:
|
|
@@ -206,3 +219,60 @@ export const APP_NGINX_SERVICE_TEMPLATE = ` nginx:
|
|
|
206
219
|
- api
|
|
207
220
|
- dashboard
|
|
208
221
|
restart: unless-stopped`;
|
|
222
|
+
|
|
223
|
+
export const APP_NGINX_SSL_SERVICE_TEMPLATE = ` nginx:
|
|
224
|
+
image: nginx:alpine
|
|
225
|
+
container_name: nginx
|
|
226
|
+
ports:
|
|
227
|
+
- "80:80"
|
|
228
|
+
- "443:443"
|
|
229
|
+
volumes:
|
|
230
|
+
- "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
|
|
231
|
+
- certbot-etc:/etc/letsencrypt
|
|
232
|
+
- certbot-www:/var/www/certbot
|
|
233
|
+
depends_on:
|
|
234
|
+
- api
|
|
235
|
+
- dashboard
|
|
236
|
+
restart: unless-stopped`;
|
|
237
|
+
|
|
238
|
+
export const APP_CERTBOT_SERVICES_TEMPLATE = ` certbot:
|
|
239
|
+
image: certbot/certbot:latest
|
|
240
|
+
container_name: certbot
|
|
241
|
+
volumes:
|
|
242
|
+
- certbot-etc:/etc/letsencrypt
|
|
243
|
+
- certbot-www:/var/www/certbot
|
|
244
|
+
command: sh -c "trap exit TERM; while :; do sleep 86400; done"
|
|
245
|
+
restart: unless-stopped
|
|
246
|
+
|
|
247
|
+
certbot-renew:
|
|
248
|
+
image: docker:cli
|
|
249
|
+
container_name: certbot-renew
|
|
250
|
+
volumes:
|
|
251
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
252
|
+
depends_on:
|
|
253
|
+
- certbot
|
|
254
|
+
- nginx
|
|
255
|
+
command: sh -c "while :; do sleep 12h; docker exec certbot certbot renew --webroot -w /var/www/certbot --quiet && docker exec nginx nginx -s reload || true; done"
|
|
256
|
+
restart: unless-stopped`;
|
|
257
|
+
|
|
258
|
+
export const INFRA_CERTBOT_SERVICES_TEMPLATE = ` certbot:
|
|
259
|
+
image: certbot/certbot:latest
|
|
260
|
+
container_name: certbot
|
|
261
|
+
volumes:
|
|
262
|
+
- certbot-etc:/etc/letsencrypt
|
|
263
|
+
- certbot-www:/var/www/certbot
|
|
264
|
+
command: sh -c "trap exit TERM; while :; do sleep 86400; done"
|
|
265
|
+
restart: unless-stopped
|
|
266
|
+
|
|
267
|
+
certbot-renew:
|
|
268
|
+
image: docker:cli
|
|
269
|
+
container_name: certbot-renew
|
|
270
|
+
volumes:
|
|
271
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
272
|
+
depends_on:
|
|
273
|
+
- certbot
|
|
274
|
+
- nginx
|
|
275
|
+
command: sh -c "while :; do sleep 12h; docker exec certbot certbot renew --webroot -w /var/www/certbot --quiet && docker exec nginx nginx -s reload || true; done"
|
|
276
|
+
restart: unless-stopped`;
|
|
277
|
+
|
|
278
|
+
export const INFRA_CERTBOT_VOLUMES = ['certbot-etc', 'certbot-www'] as const;
|
package/src/types/domain.ts
CHANGED
|
@@ -52,6 +52,12 @@ export interface SetupOptions {
|
|
|
52
52
|
targetDir: string;
|
|
53
53
|
/** Dashboard base path (empty string means root) */
|
|
54
54
|
basePath: string;
|
|
55
|
+
/** Whether SSL automation with Certbot is enabled */
|
|
56
|
+
enableSsl?: boolean;
|
|
57
|
+
/** Public domain for SSL certificate */
|
|
58
|
+
sslDomain?: string;
|
|
59
|
+
/** Registration email for Let's Encrypt */
|
|
60
|
+
sslEmail?: string;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
/**
|
package/src/validators.ts
CHANGED
|
@@ -10,6 +10,57 @@ import { VALIDATION } from './config/constants.js';
|
|
|
10
10
|
|
|
11
11
|
export type OSType = 'windows' | 'linux' | 'darwin';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Validate a public domain used for ACME/Certbot.
|
|
15
|
+
* Accepts FQDN only (no protocol, path, query, port, or wildcard).
|
|
16
|
+
*/
|
|
17
|
+
export function validatePublicDomain(input: string): true | string {
|
|
18
|
+
const value = input.trim().toLowerCase();
|
|
19
|
+
|
|
20
|
+
if (!value) {
|
|
21
|
+
return 'Domain is required';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
value.includes('://') ||
|
|
26
|
+
value.includes('/') ||
|
|
27
|
+
value.includes('?') ||
|
|
28
|
+
value.includes('#') ||
|
|
29
|
+
value.includes(':') ||
|
|
30
|
+
value.includes('*')
|
|
31
|
+
) {
|
|
32
|
+
return 'Enter only a domain name (example: app.example.com)';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// RFC-ish practical FQDN validation
|
|
36
|
+
const domainRegex =
|
|
37
|
+
/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
|
|
38
|
+
|
|
39
|
+
if (!domainRegex.test(value)) {
|
|
40
|
+
return 'Invalid domain format (example: app.example.com)';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate email format for Let's Encrypt registration.
|
|
48
|
+
*/
|
|
49
|
+
export function validateEmailAddress(input: string): true | string {
|
|
50
|
+
const value = input.trim();
|
|
51
|
+
|
|
52
|
+
if (!value) {
|
|
53
|
+
return 'Email is required';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
57
|
+
if (!emailRegex.test(value)) {
|
|
58
|
+
return 'Invalid email format (example: admin@example.com)';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
13
64
|
/**
|
|
14
65
|
* Checks if Docker is installed on the system by running `docker --version`.
|
|
15
66
|
*
|