@avelor/mesh 0.2.1 → 0.3.0
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 +2 -0
- package/bin/mesh.js +19 -21
- package/package.json +1 -1
- package/src/certs.js +18 -4
package/README.md
CHANGED
package/bin/mesh.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { watch, existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs'
|
|
3
|
-
import { spawn }
|
|
3
|
+
import { spawn, spawnSync } from 'child_process'
|
|
4
4
|
import { dirname, resolve } from 'path'
|
|
5
5
|
import { createRequire } from 'module'
|
|
6
6
|
import { loadConfig } from '../src/config.js'
|
|
@@ -11,6 +11,14 @@ import { init } from '../src/init.js'
|
|
|
11
11
|
|
|
12
12
|
const STATE_FILE = '/tmp/.mesh.json'
|
|
13
13
|
|
|
14
|
+
// Re-exec the current command under sudo if not already root.
|
|
15
|
+
// Uses sudo -E to preserve PATH so mkcert and other tools remain findable.
|
|
16
|
+
function autoSudo() {
|
|
17
|
+
if (process.getuid?.() === 0) return
|
|
18
|
+
const result = spawnSync('sudo', ['-E', process.execPath, ...process.argv.slice(1)], { stdio: 'inherit' })
|
|
19
|
+
process.exit(result.status ?? 1)
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
const RESET = '\x1b[0m'
|
|
15
23
|
const DIM = '\x1b[2m'
|
|
16
24
|
const GREEN = '\x1b[32m'
|
|
@@ -42,16 +50,12 @@ if (cmd === 'start') {
|
|
|
42
50
|
let alive = false
|
|
43
51
|
try { process.kill(state.pid, 0); alive = true } catch (e) { if (e.code === 'EPERM') alive = true }
|
|
44
52
|
if (alive) {
|
|
45
|
-
console.error(`mesh: already running (pid ${state.pid}) — run
|
|
53
|
+
console.error(`mesh: already running (pid ${state.pid}) — run mesh stop first`)
|
|
46
54
|
process.exit(1)
|
|
47
55
|
}
|
|
48
56
|
} catch { /* not running */ }
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
console.error('mesh: requires sudo to bind ports 80/443')
|
|
52
|
-
console.error(' sudo mesh start')
|
|
53
|
-
process.exit(1)
|
|
54
|
-
}
|
|
58
|
+
autoSudo()
|
|
55
59
|
|
|
56
60
|
const forwardArgs = args.filter(a => a !== 'start')
|
|
57
61
|
const child = spawn(process.execPath, [process.argv[1], 'route', ...forwardArgs], {
|
|
@@ -103,8 +107,7 @@ if (cmd === 'stop') {
|
|
|
103
107
|
console.log(`mesh: stopped (pid ${state.pid})`)
|
|
104
108
|
} catch (err) {
|
|
105
109
|
if (err.code === 'EPERM') {
|
|
106
|
-
|
|
107
|
-
process.exit(1)
|
|
110
|
+
autoSudo()
|
|
108
111
|
}
|
|
109
112
|
if (err.code === 'ESRCH') {
|
|
110
113
|
try { unlinkSync(STATE_FILE) } catch {}
|
|
@@ -165,21 +168,16 @@ if (cmd === 'status') {
|
|
|
165
168
|
|
|
166
169
|
if (cmd !== 'route') {
|
|
167
170
|
console.error('Usage:')
|
|
168
|
-
console.error(' mesh init
|
|
169
|
-
console.error('
|
|
170
|
-
console.error('
|
|
171
|
-
console.error('
|
|
172
|
-
console.error(' mesh status
|
|
173
|
-
console.error('
|
|
171
|
+
console.error(' mesh init create mesh.yml in current directory')
|
|
172
|
+
console.error(' mesh start start proxy in background')
|
|
173
|
+
console.error(' mesh start --config <path> use a specific config file')
|
|
174
|
+
console.error(' mesh stop stop the background proxy')
|
|
175
|
+
console.error(' mesh status show running services')
|
|
176
|
+
console.error(' mesh route start proxy in foreground (debug)')
|
|
174
177
|
process.exit(1)
|
|
175
178
|
}
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
console.error('mesh: requires sudo to write /etc/hosts and bind ports 80/443')
|
|
179
|
-
console.error(' both are cleaned up automatically on exit')
|
|
180
|
-
console.error(' sudo mesh route')
|
|
181
|
-
process.exit(1)
|
|
182
|
-
}
|
|
180
|
+
autoSudo()
|
|
183
181
|
|
|
184
182
|
// ── Load config ───────────────────────────────────────────────────────────────
|
|
185
183
|
|
package/package.json
CHANGED
package/src/certs.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from 'child_process'
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
2
|
+
import { chownSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
3
3
|
import { resolve } from 'path'
|
|
4
4
|
|
|
5
|
+
// When running under sudo, restore file ownership to the invoking user
|
|
6
|
+
// so that .mesh/ doesn't end up owned by root.
|
|
7
|
+
function fixOwnership(...paths) {
|
|
8
|
+
const uid = parseInt(process.env.SUDO_UID ?? '')
|
|
9
|
+
const gid = parseInt(process.env.SUDO_GID ?? '')
|
|
10
|
+
if (!uid || !gid) return
|
|
11
|
+
for (const p of paths) {
|
|
12
|
+
try { chownSync(p, uid, gid) } catch {}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
function mkcertInstalled() {
|
|
6
17
|
return spawnSync('which', ['mkcert']).status === 0
|
|
7
18
|
}
|
|
@@ -37,13 +48,15 @@ export function ensureCerts(services, cwd = process.cwd()) {
|
|
|
37
48
|
const keyFile = resolve(dir, 'key.pem')
|
|
38
49
|
|
|
39
50
|
mkdirSync(dir, { recursive: true })
|
|
51
|
+
fixOwnership(dir)
|
|
40
52
|
|
|
41
53
|
if (!caInstalled()) {
|
|
42
54
|
execFileSync('mkcert', ['-install'], { stdio: 'ignore' })
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
const domains
|
|
46
|
-
const cached
|
|
57
|
+
const domains = Object.keys(services).map(n => `${n}.test`).sort()
|
|
58
|
+
const cached = cachedDomains(dir).sort()
|
|
59
|
+
const domainsFile = resolve(dir, 'domains.json')
|
|
47
60
|
|
|
48
61
|
const needsRegen = JSON.stringify(domains) !== JSON.stringify(cached)
|
|
49
62
|
|| !existsSync(certFile)
|
|
@@ -51,7 +64,8 @@ export function ensureCerts(services, cwd = process.cwd()) {
|
|
|
51
64
|
|
|
52
65
|
if (needsRegen) {
|
|
53
66
|
execFileSync('mkcert', ['-cert-file', certFile, '-key-file', keyFile, ...domains], { stdio: 'ignore' })
|
|
54
|
-
writeFileSync(
|
|
67
|
+
writeFileSync(domainsFile, JSON.stringify(domains))
|
|
68
|
+
fixOwnership(certFile, keyFile, domainsFile)
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
return { certFile, keyFile }
|