@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/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-compose logs -f',
174
- 'docker-compose down',
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;
@@ -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
  *