@metric-im/administrate 1.0.0 → 1.0.1

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 CHANGED
@@ -1,2 +1,28 @@
1
1
  # administrate
2
2
  Tools for site administration
3
+
4
+ ## Certify
5
+ Certify will detect the hostname of incoming ssl requests and present the certs. If the domain name does not have a valid cert it requests one from letsencrypt and saves the keys to $HOME/.metric-im/
6
+
7
+ ```javascript
8
+ import https from "https";
9
+ import { Certify } from '@metric-im/administrate';
10
+ const app = express();
11
+ const certify = await Certify.attach(app,{contactEmail:'me@there.com'});
12
+ // certify.SNI return {key: xxx, cert: ...} for the current domain and/or triggers a request for a cert
13
+ const https_server = https.createServer({...certify.SNI},app);
14
+ ```
15
+ >NOTE: Set the environment variable PROFILE=DEV to instruct acme to fetch test circuits. Use this when testing or you will be throttled.
16
+
17
+ ## Multisite
18
+ Multisite acts as a proxy service. It listens on port 80 (4080), 443 (4443) for all web traffic and routes to the designated service. A service is identified by domain name. multisite will look for a service matching the incoming domain name in the *sites* folder. It spawns the app found with npm start and assigns it an http port. All subsquent traffic for that domain are routed to this process.
19
+
20
+ Multisite can also launch a new instance of the current app with the sanitized domain name as the first argument. In this way the same code can run multiple named apps in separate silos.
21
+
22
+ ```javascript
23
+ const multiSite = MultiSite.attach(app);
24
+ ```
25
+ See the Roots project [Harness](https;//github.com/rootz-global/harness). This is a simple host for multisite. It expects symlinks to all the apps the server responds to by domain name.
26
+
27
+ ## Syncrhonize
28
+ Provides a web hook to github for manaing synchronization with a code branch.
package/config.mjs CHANGED
@@ -56,7 +56,7 @@ export class DomainConfig extends Config {
56
56
  this.domainConfigFile = join(this.domainConfigDir, 'config.ini');
57
57
  if (!fs.existsSync(this.domainConfigDir)) {
58
58
  fs.mkdirSync(this.domainConfigDir);
59
- fs.writeFileSync(this.domainConfigFile,JSON.stringify([]));
59
+ fs.writeFileSync(this.domainConfigFile,`name=${domain}`);
60
60
  }
61
61
  this.load();
62
62
  }
package/multisite.mjs CHANGED
@@ -18,6 +18,7 @@ export class MultiSite {
18
18
  this._spawnPort = (parseInt(this.options.spawnPort||0) || 53874);
19
19
  this.usedPorts = new Set();
20
20
  this.healthCheckIntervals = new Map();
21
+ this.isShuttingDown = false;
21
22
  this.setupCleanup();
22
23
  }
23
24
  get spawnPort() {
@@ -27,13 +28,58 @@ export class MultiSite {
27
28
  }
28
29
 
29
30
  setupCleanup() {
30
- process.on('SIGTERM', () => this.cleanup());
31
- process.on('SIGINT', () => this.cleanup());
32
- process.on('exit', () => this.cleanup());
31
+ // Set up centralized signal handlers that host applications can use
32
+ this.gracefulShutdown = this.gracefulShutdown.bind(this);
33
+
34
+ // Only handle the 'exit' event for emergency cleanup
35
+ process.on('exit', () => {
36
+ // Synchronous cleanup only - no async operations allowed in 'exit'
37
+ console.log('Emergency cleanup on exit...');
38
+ Object.values(this.sites).forEach((site) => {
39
+ if (site.proc && !site.proc.killed) {
40
+ site.proc.kill('SIGKILL');
41
+ }
42
+ });
43
+ });
44
+ }
45
+
46
+ async gracefulShutdown(signal) {
47
+ if (this.isShuttingDown) {
48
+ console.log(`Received ${signal} again, forcing exit...`);
49
+ process.exit(1);
50
+ }
51
+
52
+ this.isShuttingDown = true;
53
+ console.log(`Received ${signal}, shutting down gracefully...`);
54
+
55
+ try {
56
+ console.log("Cleaning up child processes...");
57
+ await this.cleanup();
58
+
59
+ console.log("Graceful shutdown complete");
60
+ process.exit(0);
61
+ } catch (error) {
62
+ console.error("Error during shutdown:", error);
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ setupSignalHandlers() {
68
+ // Method host applications can call to set up proper signal handling
69
+ process.on('SIGINT', () => this.gracefulShutdown('SIGINT'));
70
+ process.on('SIGTERM', () => this.gracefulShutdown('SIGTERM'));
71
+ console.log('MultiSite signal handlers installed');
33
72
  }
34
73
 
35
74
  async cleanup() {
36
75
  console.log('Cleaning up spawned processes...');
76
+
77
+ // Set a global timeout for the entire cleanup process
78
+ const cleanupTimeout = setTimeout(() => {
79
+ console.log('Cleanup taking too long, forcing exit...');
80
+ process.exit(1);
81
+ }, 15000); // 15 second total timeout
82
+
37
83
  const promises = Object.values(this.sites).map(async (site) => {
38
84
  if (site.proc && !site.proc.killed) {
39
85
  return new Promise((resolve) => {
@@ -43,7 +89,7 @@ export class MultiSite {
43
89
  site.proc.kill('SIGKILL');
44
90
  }
45
91
  resolve();
46
- }, 10000); // 10 second timeout for graceful shutdown
92
+ }, 5000); // Reduced to 5 second timeout per process
47
93
 
48
94
  site.proc.kill('SIGTERM');
49
95
 
@@ -62,6 +108,7 @@ export class MultiSite {
62
108
  this.healthCheckIntervals.forEach(interval => clearInterval(interval));
63
109
  this.healthCheckIntervals.clear();
64
110
 
111
+ clearTimeout(cleanupTimeout);
65
112
  console.log('All spawned processes cleaned up');
66
113
  }
67
114
 
@@ -135,7 +182,7 @@ export class MultiSite {
135
182
  // spawn declared sites
136
183
  if (fs.existsSync(resolve('./sites'))) {
137
184
  instance.sites = (fs.readdirSync(resolve('./sites'))).reduce((result,hostName)=>{
138
- const domainName = Site.GetId(hostName);
185
+ const domainName = Site.WashName(hostName);
139
186
  const options = {
140
187
  cwd: resolve(`./sites/${hostName}`),
141
188
  env: {PORT:instance.spawnPort,meta:instance.options}
@@ -151,7 +198,7 @@ export class MultiSite {
151
198
  const router = express.Router();
152
199
 
153
200
  router.all(/.*/, async (req, res) => {
154
- const domain = Site.GetId(req.hostname);
201
+ const domain = Site.WashName(req.hostname);
155
202
  const site = this.sites[domain];
156
203
  if (site) {
157
204
  let target = `http://127.0.0.1:${site.options.env.PORT}${req.url}`;
@@ -188,14 +235,14 @@ export class MultiSite {
188
235
  timeout: 30000,
189
236
  responseType: 'stream'
190
237
  });
191
-
238
+
192
239
  console.log(`${response.status} ${target} (binary stream)`);
193
-
240
+
194
241
  // Set headers without the problematic ones
195
242
  const cleanHeaders = {...response.headers};
196
243
  delete cleanHeaders['transfer-encoding'];
197
244
  delete cleanHeaders['content-encoding'];
198
-
245
+
199
246
  res.status(response.status).set(cleanHeaders);
200
247
  response.data.pipe(res);
201
248
  } else {
@@ -210,13 +257,13 @@ export class MultiSite {
210
257
  timeout: 30000,
211
258
  responseType: 'text'
212
259
  });
213
-
260
+
214
261
  console.log(`${response.status} ${target}`);
215
-
262
+
216
263
  const cleanHeaders = {...response.headers};
217
264
  delete cleanHeaders['transfer-encoding'];
218
265
  delete cleanHeaders['content-encoding'];
219
-
266
+
220
267
  res.status(response.status).set(cleanHeaders).send(response.data);
221
268
  }
222
269
  } catch (error) {
@@ -319,9 +366,12 @@ export class Site {
319
366
  console.error(`${this.name}: Exception while spawning:`, err);
320
367
  this.proc = null;
321
368
  }
322
- }
323
- static GetId(hostName="") {
324
- if (hostName.match(/^[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}$/)) return null
325
- return hostName.toLowerCase().replace(/[^a-z0-9-]+/g,'_');
369
+ }
370
+ static WashName(hostName="") {
371
+ if (!hostName || hostName.match(/^[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}$/)) return "";
372
+ else return hostName.toLowerCase();
373
+ }
374
+ static SafeName(hostName) {
375
+ return Site.WashName(hostName).replace(/[^a-z0-9-]+/g,'_');
326
376
  }
327
377
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metric-im/administrate",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Tools for site administration",
5
5
  "homepage": "https://github.com/metric-im/administrate#readme",
6
6
  "bugs": {