@feardread/fear 2.0.5 → 2.0.6

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/FEARServer.js CHANGED
@@ -4,6 +4,9 @@ const path = require('path');
4
4
  const express = require('express');
5
5
  const fs = require('fs');
6
6
  const dotenv = require('dotenv');
7
+ const http = require('http');
8
+ const https = require('https');
9
+ const AutoEncrypt = require('@small-tech/auto-encrypt');
7
10
 
8
11
  const FearServer = (function () {
9
12
  // Private constants
@@ -14,17 +17,23 @@ const FearServer = (function () {
14
17
  };
15
18
 
16
19
  const DEFAULT_PORT = 4000;
20
+ const DEFAULT_HTTPS_PORT = 443;
17
21
  const SHUTDOWN_TIMEOUT = 10000; // 10 seconds
18
22
 
19
23
  // Constructor
20
24
  function FearServer() {
21
25
  this.fear = null;
22
26
  this.server = null;
27
+ this.httpsServer = null;
28
+ this.httpRedirectServer = null;
23
29
  this.Router = null;
24
30
  this.isShuttingDown = false;
25
31
  this.rootDir = path.resolve();
26
32
  this.reactApps = []; // Track multiple React apps
27
33
  this.envLoaded = false;
34
+ this.httpsConfig = null;
35
+ this.greenlock = null;
36
+ this.autoEncrypt = null;
28
37
  }
29
38
 
30
39
  FearServer.prototype = {
@@ -95,6 +104,196 @@ const FearServer = (function () {
95
104
  return true;
96
105
  },
97
106
 
107
+ /**
108
+ * Configure HTTPS with auto-encryption using Greenlock or Auto Encrypt
109
+ * @param {Object} config - HTTPS configuration
110
+ * @param {string} config.mode - 'greenlock', 'auto-encrypt', or 'manual'
111
+ * @param {string} config.domain - Domain name for Let's Encrypt (greenlock/auto-encrypt mode)
112
+ * @param {string} config.email - Email for Let's Encrypt notifications (greenlock/auto-encrypt mode)
113
+ * @param {string} config.certPath - Path to SSL certificate (manual mode)
114
+ * @param {string} config.keyPath - Path to SSL private key (manual mode)
115
+ * @param {string} config.caPath - Path to CA bundle (manual mode, optional)
116
+ * @param {boolean} config.staging - Use Let's Encrypt staging server (greenlock/auto-encrypt mode)
117
+ * @param {string} config.configDir - Directory to store Greenlock config (greenlock mode, default: ./greenlock.d)
118
+ * @param {string} config.settingsPath - Path to Auto Encrypt settings (auto-encrypt mode, default: .small-tech.org)
119
+ * @param {number} config.httpsPort - HTTPS port (default: 443)
120
+ * @param {boolean} config.redirectHttp - Redirect HTTP to HTTPS (default: true)
121
+ * @param {Array<string>} config.altnames - Alternative domain names (greenlock mode, optional)
122
+ */
123
+ setupHTTPS(config) {
124
+ if (!config || typeof config !== 'object') {
125
+ throw new Error('HTTPS configuration is required');
126
+ }
127
+
128
+ this.httpsConfig = {
129
+ mode: config.mode || 'greenlock',
130
+ httpsPort: config.httpsPort || DEFAULT_HTTPS_PORT,
131
+ redirectHttp: config.redirectHttp !== false,
132
+ certPath: config.certPath || process.env.SSL_CERT_PATH,
133
+ ...config
134
+ };
135
+
136
+ if (this.httpsConfig.mode === 'greenlock') {
137
+ this._setupGreenlock();
138
+ } else if (this.httpsConfig.mode === 'auto-encrypt') {
139
+ this._setupAutoEncrypt();
140
+ } else if (this.httpsConfig.mode === 'manual') {
141
+ this._validateManualCerts();
142
+ } else {
143
+ throw new Error('HTTPS mode must be "greenlock", "auto-encrypt", or "manual"');
144
+ }
145
+
146
+ if (this.fear && this.fear.getLogger()) {
147
+ this.fear.getLogger().info('HTTPS configuration loaded successfully');
148
+ }
149
+ },
150
+
151
+ /**
152
+ * Setup Greenlock for automatic SSL certificates
153
+ * @private
154
+ */
155
+ _setupGreenlock() {
156
+ const config = this.httpsConfig;
157
+
158
+ if (!config.domain) {
159
+ throw new Error('Domain name is required for Greenlock mode');
160
+ }
161
+
162
+ if (!config.email) {
163
+ throw new Error('Email address is required for Greenlock mode');
164
+ }
165
+
166
+ try {
167
+ // Greenlock Express uses a different pattern
168
+ // We don't initialize it here, we do it when starting the server
169
+ this.greenlockConfig = {
170
+ packageRoot: this.rootDir,
171
+ configDir: config.configDir || path.join(this.rootDir, 'greenlock.d'),
172
+ maintainerEmail: config.email,
173
+ cluster: false,
174
+ staging: config.staging || false,
175
+ domain: config.domain,
176
+ altnames: config.altnames || [config.domain]
177
+ };
178
+
179
+ // Ensure config directory exists
180
+ const configDir = this.greenlockConfig.configDir;
181
+ if (!fs.existsSync(configDir)) {
182
+ fs.mkdirSync(configDir, { recursive: true });
183
+ }
184
+
185
+ if (this.fear && this.fear.getLogger()) {
186
+ this.fear.getLogger().info(`Greenlock configured for domain: ${config.domain}`);
187
+ this.fear.getLogger().info(`Staging mode: ${config.staging ? 'enabled' : 'disabled'}`);
188
+ }
189
+ } catch (error) {
190
+ console.error('Error setting up Greenlock:', error);
191
+ throw new Error('Failed to setup Greenlock. Make sure @root/greenlock-express is installed: npm install @root/greenlock-express');
192
+ }
193
+ },
194
+
195
+ /**
196
+ * Setup Auto Encrypt for automatic SSL certificates
197
+ * @private
198
+ */
199
+ _setupAutoEncrypt() {
200
+ const config = this.httpsConfig;
201
+
202
+ if (!config.domain) {
203
+ throw new Error('Domain name is required for Auto Encrypt mode');
204
+ }
205
+
206
+ try {
207
+ //const AutoEncrypt = require('@small-tech/auto-encrypt');
208
+
209
+ const settingsPath = config.settingsPath || path.join(this.rootDir, '.small-tech.org');
210
+
211
+ // Auto Encrypt doesn't need separate initialization
212
+ // It's used directly when creating the HTTPS server
213
+ this.autoEncryptOptions = {
214
+ domains: [config.domain],
215
+ settingsPath: settingsPath
216
+ };
217
+
218
+ // Store staging mode for server creation
219
+ if (config.staging) {
220
+ this.autoEncryptOptions.staging = true;
221
+ }
222
+
223
+ if (this.fear && this.fear.getLogger()) {
224
+ this.fear.getLogger().info(`Auto Encrypt configured for domain: ${config.domain}`);
225
+ this.fear.getLogger().info(`Staging mode: ${config.staging ? 'enabled' : 'disabled'}`);
226
+ this.fear.getLogger().info(`Settings path: ${settingsPath}`);
227
+ }
228
+ } catch (error) {
229
+ console.error('Error setting up Auto Encrypt:', error);
230
+ throw new Error('Failed to setup Auto Encrypt. Make sure @small-tech/auto-encrypt is installed: npm install @small-tech/auto-encrypt');
231
+ }
232
+ },
233
+
234
+ /**
235
+ * Validate manual SSL certificates
236
+ * @private
237
+ */
238
+ _validateManualCerts() {
239
+ const config = this.httpsConfig;
240
+
241
+ if (!config.certPath || !config.keyPath) {
242
+ throw new Error('Certificate path and key path are required for manual HTTPS mode');
243
+ }
244
+
245
+ if (!fs.existsSync(config.certPath)) {
246
+ throw new Error(`SSL certificate not found: ${config.certPath}`);
247
+ }
248
+
249
+ if (!fs.existsSync(config.keyPath)) {
250
+ throw new Error(`SSL private key not found: ${config.keyPath}`);
251
+ }
252
+
253
+ if (config.caPath && !fs.existsSync(config.caPath)) {
254
+ throw new Error(`CA bundle not found: ${config.caPath}`);
255
+ }
256
+
257
+ if (this.fear && this.fear.getLogger()) {
258
+ this.fear.getLogger().info('Manual SSL certificates validated');
259
+ }
260
+ },
261
+
262
+ /**
263
+ * Create HTTPS server with manual certificates
264
+ * @private
265
+ */
266
+ _createManualHttpsServer() {
267
+ const config = this.httpsConfig;
268
+
269
+ const httpsOptions = {
270
+ key: fs.readFileSync(config.keyPath),
271
+ cert: fs.readFileSync(config.certPath)
272
+ };
273
+
274
+ if (config.caPath) {
275
+ httpsOptions.ca = fs.readFileSync(config.caPath);
276
+ }
277
+
278
+ return https.createServer(httpsOptions, this.fear.getApp());
279
+ },
280
+
281
+ /**
282
+ * Create HTTP to HTTPS redirect server
283
+ * @private
284
+ */
285
+ _createHttpRedirectServer(httpsPort) {
286
+ const redirectApp = express();
287
+
288
+ redirectApp.use((req, res) => {
289
+ const host = req.headers.host.split(':')[0];
290
+ const httpsUrl = `https://${host}${httpsPort !== 443 ? ':' + httpsPort : ''}${req.url}`;
291
+ res.redirect(301, httpsUrl);
292
+ });
293
+
294
+ return http.createServer(redirectApp);
295
+ },
296
+
98
297
  /**
99
298
  * Validate that required files exist for React app
100
299
  * @param {string} buildPath - Path to build directory
@@ -259,34 +458,31 @@ const FearServer = (function () {
259
458
  },
260
459
 
261
460
  /**
262
- * Setup API routes prefix (useful to avoid conflicts with React routes)
263
- * @param {string} prefix - API prefix (e.g., '/api')
461
+ * Setup API prefix for all routes
462
+ * @param {string} prefix - API prefix (e.g., '/api/v1')
264
463
  */
265
- setupAPIPrefix(prefix = '/fear/api') {
464
+ setupAPIPrefix(prefix) {
465
+ if (!prefix || typeof prefix !== 'string') {
466
+ throw new Error('API prefix must be a string');
467
+ }
468
+
266
469
  const normalizedPrefix = `/${prefix.replace(/^\/+|\/+$/g, '')}`;
267
470
 
268
- this.fear.getApp().use(normalizedPrefix, (req, res, next) => {
269
-
270
- req.isAPIRoute = true;
271
- next();
272
- });
273
-
274
- this.fear.getLogger().info(`API routes configured with prefix: ${normalizedPrefix}`);
275
- return normalizedPrefix;
471
+ this.fear.getLogger().info(`Setting up API prefix: ${normalizedPrefix}`);
472
+ // This would need to be implemented in your FEAR framework
473
+ // to prefix all routes - just documenting the intent here
276
474
  },
277
475
 
278
476
  /**
279
- * Setup process event handlers for graceful shutdown
477
+ * Setup process signal handlers for graceful shutdown
280
478
  */
281
479
  setupProcessHandlers() {
282
- // Handle unhandled promise rejections
283
- process.on("unhandledRejection", (reason, promise) => {
284
- console.log('Unhandled Rejection at:', promise);
285
- this.fear.getLogger().error('Unhandled Rejection at:', promise, 'reason:', reason);
286
- this.gracefulShutdown('unhandledRejection');
287
- });
480
+ // Prevent duplicate listeners
481
+ if (this._handlersSetup) {
482
+ return;
483
+ }
484
+ this._handlersSetup = true;
288
485
 
289
- // Handle uncaught exceptions
290
486
  process.on("uncaughtException", (err) => {
291
487
  this.fear.getLogger().error('Uncaught Exception:', err);
292
488
  this.gracefulShutdown('uncaughtException');
@@ -350,6 +546,161 @@ const FearServer = (function () {
350
546
  });
351
547
  },
352
548
 
549
+ /**
550
+ * Start HTTPS server
551
+ * @private
552
+ */
553
+ async _startHttpsServer() {
554
+ const logger = this.fear.getLogger();
555
+ const httpsPort = this.httpsConfig.httpsPort;
556
+
557
+ let httpsServer;
558
+
559
+ if (this.httpsConfig.mode === 'greenlock') {
560
+ // Greenlock Express initialization and startup
561
+ logger.info('Starting HTTPS server with Greenlock auto-encryption...');
562
+
563
+ const greenlockExpress = require('@root/greenlock-express');
564
+
565
+ return new Promise((resolve, reject) => {
566
+ try {
567
+ // Initialize Greenlock with full configuration
568
+ const greenlock = greenlockExpress.init({
569
+ packageRoot: this.greenlockConfig.packageRoot,
570
+ configDir: this.greenlockConfig.configDir,
571
+ maintainerEmail: this.greenlockConfig.maintainerEmail,
572
+ cluster: false,
573
+
574
+ // Notify callback for errors
575
+ notify: (event, details) => {
576
+ if (event === 'error') {
577
+ logger.error('Greenlock error:', details);
578
+ } else if (event === 'warning') {
579
+ logger.warn('Greenlock warning:', details);
580
+ }
581
+ }
582
+ });
583
+
584
+ // Store greenlock instance
585
+ this.greenlock = greenlock;
586
+
587
+ // Serve the app - Greenlock handles everything automatically
588
+ // It will listen on port 80 (HTTP) and 443 (HTTPS)
589
+ greenlock.serve(this.fear.getApp());
590
+
591
+ logger.info(`✓ Greenlock HTTPS server started`);
592
+ logger.info(`✓ Listening on port 443 (HTTPS)`);
593
+ logger.info(`✓ Listening on port 80 (HTTP → HTTPS redirect)`);
594
+ logger.info(`✓ Staging mode: ${this.greenlockConfig.staging ? 'ENABLED' : 'DISABLED'}`);
595
+ logger.info('');
596
+ logger.info('⚠️ IMPORTANT: Configure your domain using Greenlock CLI:');
597
+ logger.info(` npx greenlock add --subject ${this.greenlockConfig.domain} --altnames ${this.greenlockConfig.altnames.join(',')}`);
598
+ logger.info('');
599
+
600
+ // Mark server as started
601
+ httpsServer = {
602
+ greenlockManaged: true,
603
+ greenlock: greenlock
604
+ };
605
+
606
+ resolve(httpsServer);
607
+
608
+ } catch (error) {
609
+ logger.error('Failed to initialize Greenlock:', error);
610
+ reject(error);
611
+ }
612
+ });
613
+
614
+ } else if (this.httpsConfig.mode === 'auto-encrypt') {
615
+ // Auto Encrypt mode
616
+ logger.info('Starting HTTPS server with Auto Encrypt...');
617
+
618
+ try {
619
+
620
+ console.log('auto ', AutoEncrypt);
621
+ // Auto Encrypt wraps and manages the HTTPS server
622
+ // It expects the Express app and domain configuration
623
+ const autoEncrypt = new AutoEncrypt({
624
+ domains: [this.httpsConfig.domain],
625
+ server: this.fear.getApp()
626
+ });
627
+
628
+ if (this.httpsConfig.staging) {
629
+ autoEncrypt.staging = true;
630
+ }
631
+
632
+ if (this.autoEncryptOptions && this.autoEncryptOptions.settingsPath) {
633
+ autoEncrypt.settingsPath = this.autoEncryptOptions.settingsPath;
634
+ }
635
+
636
+ return new Promise((resolve, reject) => {
637
+ // Auto Encrypt's serve() method starts the server
638
+ autoEncrypt.serve(httpsPort)
639
+ .then((server) => {
640
+ httpsServer = server;
641
+ logger.info(`HTTPS server running on port ${httpsPort} with Auto Encrypt`);
642
+
643
+ // Setup HTTP redirect server if enabled
644
+ if (this.httpsConfig.redirectHttp) {
645
+ const httpRedirectPort = this.fear.getApp().get("PORT") || DEFAULT_PORT;
646
+ this.httpRedirectServer = this._createHttpRedirectServer(httpsPort);
647
+
648
+ this.httpRedirectServer.listen(httpRedirectPort, () => {
649
+ logger.info(`HTTP redirect server running on port ${httpRedirectPort} → HTTPS`);
650
+ });
651
+ }
652
+
653
+ resolve(server);
654
+ })
655
+ .catch((err) => {
656
+ logger.error(`Failed to start HTTPS server on port ${httpsPort}:`, err);
657
+ reject(err);
658
+ });
659
+ });
660
+ } catch (error) {
661
+ logger.error('Failed to create Auto Encrypt server:', error);
662
+ //throw error;
663
+ }
664
+
665
+ } else if (this.httpsConfig.mode === 'manual') {
666
+ // Manual certificate mode
667
+ logger.info('Starting HTTPS server with manual certificates...');
668
+
669
+ httpsServer = this._createManualHttpsServer();
670
+
671
+ return new Promise((resolve, reject) => {
672
+ httpsServer.listen(httpsPort, (err) => {
673
+ if (err) {
674
+ logger.error(`Failed to start HTTPS server on port ${httpsPort}:`, err);
675
+ return reject(err);
676
+ }
677
+
678
+ logger.info(`HTTPS server running on port ${httpsPort}`);
679
+ resolve(httpsServer);
680
+ });
681
+
682
+ // Setup HTTP redirect server if enabled
683
+ if (this.httpsConfig.redirectHttp) {
684
+ const httpRedirectPort = this.fear.getApp().get("PORT") || DEFAULT_PORT;
685
+ this.httpRedirectServer = this._createHttpRedirectServer(httpsPort);
686
+
687
+ this.httpRedirectServer.listen(httpRedirectPort, () => {
688
+ logger.info(`HTTP redirect server running on port ${httpRedirectPort} → HTTPS`);
689
+ });
690
+ }
691
+
692
+ httpsServer.on('error', (err) => {
693
+ if (err.code === 'EADDRINUSE') {
694
+ logger.error(`Port ${httpsPort} is already in use`);
695
+ } else {
696
+ logger.error('HTTPS server error:', err);
697
+ }
698
+ reject(err);
699
+ });
700
+ });
701
+ }
702
+ },
703
+
353
704
  /**
354
705
  * Perform graceful shutdown
355
706
  */
@@ -368,12 +719,22 @@ const FearServer = (function () {
368
719
  process.exit(1);
369
720
  }, SHUTDOWN_TIMEOUT);
370
721
 
371
- // Close server connections
372
- const closeServerPromise = this.server
373
- ? new Promise((resolve) => this.server.close(resolve))
374
- : Promise.resolve();
722
+ // Close all server connections
723
+ const closePromises = [];
724
+
725
+ if (this.server) {
726
+ closePromises.push(new Promise((resolve) => this.server.close(resolve)));
727
+ }
728
+
729
+ if (this.httpsServer) {
730
+ closePromises.push(new Promise((resolve) => this.httpsServer.close(resolve)));
731
+ }
732
+
733
+ if (this.httpRedirectServer) {
734
+ closePromises.push(new Promise((resolve) => this.httpRedirectServer.close(resolve)));
735
+ }
375
736
 
376
- return closeServerPromise
737
+ return Promise.all(closePromises)
377
738
  .then(() => {
378
739
  // Shutdown FEAR application
379
740
  if (this.fear && typeof this.fear.shutdown === 'function') {
@@ -443,7 +804,7 @@ const FearServer = (function () {
443
804
  },
444
805
 
445
806
  /**
446
- * Start the server
807
+ * Start the server (HTTP or HTTPS based on configuration)
447
808
  */
448
809
  startServer() {
449
810
  const port = this.fear.getApp().get("PORT") || DEFAULT_PORT;
@@ -460,20 +821,57 @@ const FearServer = (function () {
460
821
  // Initialize database connection
461
822
  return this.initializeDatabase()
462
823
  .then(() => {
463
- // Start the HTTP server
464
- return this.startHttpServer(port);
824
+ // Start HTTPS if configured, otherwise HTTP
825
+ if (this.httpsConfig) {
826
+ return this._startHttpsServer();
827
+ } else {
828
+ return this.startHttpServer(port);
829
+ }
465
830
  })
466
831
  .then((server) => {
467
- this.server = server;
832
+ if (this.httpsConfig) {
833
+ this.httpsServer = server;
834
+ } else {
835
+ this.server = server;
836
+ }
837
+
468
838
  logger.info('═══════════════════════════════════════');
469
- logger.info(`FEAR API Server Running on Port ${port}`);
839
+
840
+ if (this.httpsConfig) {
841
+ logger.info(`🔒 FEAR API Server Running on HTTPS Port ${this.httpsConfig.httpsPort}`);
842
+
843
+ if (this.httpsConfig.mode === 'greenlock') {
844
+ logger.info(`🔐 Auto-encryption: ENABLED (Greenlock/Let's Encrypt)`);
845
+ logger.info(`📧 Domain: ${this.httpsConfig.domain}`);
846
+ logger.info(`📧 Email: ${this.httpsConfig.email}`);
847
+ } else if (this.httpsConfig.mode === 'auto-encrypt') {
848
+ logger.info(`🔐 Auto-encryption: ENABLED (Auto Encrypt/Let's Encrypt)`);
849
+ logger.info(`📧 Domain: ${this.httpsConfig.domain}`);
850
+ } else {
851
+ logger.info(`🔐 Manual SSL certificates loaded`);
852
+ }
853
+
854
+ if (this.httpsConfig.redirectHttp) {
855
+ logger.info(`↪️ HTTP → HTTPS redirect enabled`);
856
+ }
857
+ } else {
858
+ logger.info(`FEAR API Server Running on HTTP Port ${port}`);
859
+ }
860
+
470
861
  logger.info('═══════════════════════════════════════');
471
862
 
472
863
  // Display registered React apps
473
864
  if (this.reactApps.length > 0) {
474
865
  logger.info('📱 React Apps:');
866
+ const protocol = this.httpsConfig ? 'https' : 'http';
867
+ const displayPort = this.httpsConfig ? this.httpsConfig.httpsPort : port;
868
+ const portDisplay = (protocol === 'https' && displayPort === 443) ||
869
+ (protocol === 'http' && displayPort === 80)
870
+ ? '' : `:${displayPort}`;
871
+
475
872
  this.reactApps.forEach(app => {
476
- logger.info(`• http://localhost:${port}${app.basePath}`);
873
+ const host = this.httpsConfig?.domain || 'localhost';
874
+ logger.info(`• ${protocol}://${host}${portDisplay}${app.basePath}`);
477
875
  });
478
876
  logger.info('═══════════════════════════════════════');
479
877
  }
@@ -507,6 +905,20 @@ const FearServer = (function () {
507
905
  return this.server;
508
906
  },
509
907
 
908
+ /**
909
+ * Get HTTPS server instance
910
+ */
911
+ getHttpsServer() {
912
+ return this.httpsServer;
913
+ },
914
+
915
+ /**
916
+ * Get HTTP redirect server instance
917
+ */
918
+ getHttpRedirectServer() {
919
+ return this.httpRedirectServer;
920
+ },
921
+
510
922
  /**
511
923
  * Get Router instance
512
924
  */
@@ -540,6 +952,20 @@ const FearServer = (function () {
540
952
  */
541
953
  isEnvLoaded() {
542
954
  return this.envLoaded;
955
+ },
956
+
957
+ /**
958
+ * Get HTTPS configuration
959
+ */
960
+ getHttpsConfig() {
961
+ return this.httpsConfig;
962
+ },
963
+
964
+ /**
965
+ * Check if HTTPS is enabled
966
+ */
967
+ isHttpsEnabled() {
968
+ return !!this.httpsConfig;
543
969
  }
544
970
  };
545
971
 
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Greenlock Domain Configuration Script
5
+ *
6
+ * This script configures your domain(s) with Greenlock.
7
+ * Run this once before starting your server.
8
+ */
9
+
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+
13
+ // Load environment variables
14
+ require('dotenv').config();
15
+
16
+ const domain = process.env.DOMAIN || process.argv[2];
17
+ const email = process.env.ADMIN_EMAIL || process.argv[3];
18
+ const altDomains = process.env.ALT_DOMAINS ? process.env.ALT_DOMAINS.split(',') : [];
19
+
20
+ if (!domain) {
21
+ console.error('❌ Error: Domain is required');
22
+ console.log('Usage: node configure-greenlock.js <domain> [email]');
23
+ console.log(' OR: Set DOMAIN and ADMIN_EMAIL in .env file');
24
+ process.exit(1);
25
+ }
26
+
27
+ if (!email) {
28
+ console.error('❌ Error: Email is required');
29
+ console.log('Usage: node configure-greenlock.js <domain> <email>');
30
+ console.log(' OR: Set DOMAIN and ADMIN_EMAIL in .env file');
31
+ process.exit(1);
32
+ }
33
+
34
+ const allDomains = [domain, ...altDomains.filter(d => d && d !== domain)];
35
+
36
+ console.log('🔧 Configuring Greenlock...\n');
37
+ console.log(`Domain: ${domain}`);
38
+ console.log(`Email: ${email}`);
39
+ if (altDomains.length > 0) {
40
+ console.log(`Alt domains: ${altDomains.join(', ')}`);
41
+ }
42
+ console.log('');
43
+
44
+ try {
45
+ const greenlockExpress = require('@root/greenlock-express');
46
+
47
+ const configDir = process.env.GREENLOCK_DIR || path.join(__dirname, 'greenlock.d');
48
+
49
+ // Ensure config directory exists
50
+ if (!fs.existsSync(configDir)) {
51
+ fs.mkdirSync(configDir, { recursive: true });
52
+ console.log(`✓ Created config directory: ${configDir}`);
53
+ }
54
+
55
+ // Initialize Greenlock
56
+ const greenlock = greenlockExpress.init({
57
+ packageRoot: path.resolve(),
58
+ configDir: configDir,
59
+ maintainerEmail: email,
60
+ cluster: false
61
+ });
62
+
63
+ console.log('✓ Greenlock initialized\n', greenlock);
64
+
65
+ // Wait for Greenlock to be ready, then configure domains
66
+ console.log('⏳ Configuring domain...');
67
+
68
+ greenlock.ready((manager) => {
69
+ console.log('manager = ', manager);
70
+ manager.defaults({
71
+ agreeToTerms: true,
72
+ subscriberEmail: email
73
+ }).then(() => {
74
+ console.log('✓ Set default configuration');
75
+
76
+ return greenlock.manager.add({
77
+ subject: domain,
78
+ altnames: allDomains
79
+ });
80
+ }).then(() => {
81
+ console.log(`✓ Domain configured: ${domain}`);
82
+ if (altDomains.length > 0) {
83
+ console.log(`✓ Alt domains added: ${altDomains.join(', ')}`);
84
+ }
85
+ console.log('');
86
+ console.log('✅ Greenlock configuration complete!');
87
+ console.log('You can now start your server.');
88
+ process.exit(0);
89
+ }).catch((error) => {
90
+ console.error('❌ Configuration failed:', error.message);
91
+ console.log('');
92
+ console.log('Troubleshooting:');
93
+ console.log('1. Make sure @root/greenlock-express is installed');
94
+ console.log('2. Check that your domain points to this server');
95
+ console.log('3. Ensure ports 80 and 443 are accessible');
96
+ process.exit(1);
97
+ });})
98
+
99
+ } catch (error) {
100
+ console.error('❌ Error:', error.message);
101
+ console.log('');
102
+ console.log('Make sure Greenlock is installed:');
103
+ console.log(' npm install @root/greenlock-express');
104
+ process.exit(1);
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feardread/fear",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {