lopata 0.10.2 → 0.11.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/dist/dashboard/{chunk-jpd3vqkt.js → chunk-5h0jr7cs.js} +177 -0
- package/dist/dashboard/{chunk-jzyhpjad.css → chunk-n1xkdmxt.css} +31 -0
- package/dist/dashboard/index.html +1 -1
- package/package.json +1 -1
- package/src/api/dispatch.ts +2 -0
- package/src/api/handlers/hosts.ts +15 -0
- package/src/api/types.ts +7 -0
- package/src/cli/hosts.ts +78 -0
- package/src/cli.ts +6 -0
- package/src/hosts-check.ts +75 -0
|
@@ -7196,6 +7196,180 @@ function HomeView() {
|
|
|
7196
7196
|
}, undefined, true, undefined, this);
|
|
7197
7197
|
}
|
|
7198
7198
|
|
|
7199
|
+
// src/dashboard/views/hosts.tsx
|
|
7200
|
+
var STATUS_COLORS = {
|
|
7201
|
+
ok: "bg-emerald-500/15 text-emerald-500",
|
|
7202
|
+
missing: "bg-red-500/15 text-red-500",
|
|
7203
|
+
wrong_address: "bg-red-500/15 text-red-500",
|
|
7204
|
+
wildcard: "bg-yellow-500/15 text-yellow-500"
|
|
7205
|
+
};
|
|
7206
|
+
var STATUS_LABELS = {
|
|
7207
|
+
ok: "ok",
|
|
7208
|
+
missing: "missing",
|
|
7209
|
+
wrong_address: "wrong address",
|
|
7210
|
+
wildcard: "wildcard"
|
|
7211
|
+
};
|
|
7212
|
+
function CopyButton({ text, label }) {
|
|
7213
|
+
const [copied, setCopied] = d2(false);
|
|
7214
|
+
const timerRef = A2(null);
|
|
7215
|
+
y2(() => {
|
|
7216
|
+
return () => {
|
|
7217
|
+
if (timerRef.current)
|
|
7218
|
+
clearTimeout(timerRef.current);
|
|
7219
|
+
};
|
|
7220
|
+
}, []);
|
|
7221
|
+
const handleCopy = () => {
|
|
7222
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
7223
|
+
setCopied(true);
|
|
7224
|
+
if (timerRef.current)
|
|
7225
|
+
clearTimeout(timerRef.current);
|
|
7226
|
+
timerRef.current = setTimeout(() => setCopied(false), 2000);
|
|
7227
|
+
});
|
|
7228
|
+
};
|
|
7229
|
+
return /* @__PURE__ */ u3("button", {
|
|
7230
|
+
onClick: handleCopy,
|
|
7231
|
+
class: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-mono rounded-md border border-border bg-panel hover:bg-panel-hover text-text-secondary hover:text-ink transition-colors",
|
|
7232
|
+
children: copied ? /* @__PURE__ */ u3(k, {
|
|
7233
|
+
children: [
|
|
7234
|
+
/* @__PURE__ */ u3("svg", {
|
|
7235
|
+
class: "w-3.5 h-3.5 text-emerald-500",
|
|
7236
|
+
viewBox: "0 0 16 16",
|
|
7237
|
+
fill: "none",
|
|
7238
|
+
stroke: "currentColor",
|
|
7239
|
+
"stroke-width": "2",
|
|
7240
|
+
children: /* @__PURE__ */ u3("path", {
|
|
7241
|
+
d: "M3 8.5l3 3 7-7",
|
|
7242
|
+
"stroke-linecap": "round",
|
|
7243
|
+
"stroke-linejoin": "round"
|
|
7244
|
+
}, undefined, false, undefined, this)
|
|
7245
|
+
}, undefined, false, undefined, this),
|
|
7246
|
+
"Copied!"
|
|
7247
|
+
]
|
|
7248
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ u3(k, {
|
|
7249
|
+
children: [
|
|
7250
|
+
/* @__PURE__ */ u3("svg", {
|
|
7251
|
+
class: "w-3.5 h-3.5",
|
|
7252
|
+
viewBox: "0 0 16 16",
|
|
7253
|
+
fill: "none",
|
|
7254
|
+
stroke: "currentColor",
|
|
7255
|
+
"stroke-width": "1.5",
|
|
7256
|
+
children: [
|
|
7257
|
+
/* @__PURE__ */ u3("rect", {
|
|
7258
|
+
x: "5",
|
|
7259
|
+
y: "5",
|
|
7260
|
+
width: "8",
|
|
7261
|
+
height: "8",
|
|
7262
|
+
rx: "1",
|
|
7263
|
+
"stroke-linecap": "round",
|
|
7264
|
+
"stroke-linejoin": "round"
|
|
7265
|
+
}, undefined, false, undefined, this),
|
|
7266
|
+
/* @__PURE__ */ u3("path", {
|
|
7267
|
+
d: "M11 5V3.5A1.5 1.5 0 009.5 2h-6A1.5 1.5 0 002 3.5v6A1.5 1.5 0 003.5 11H5",
|
|
7268
|
+
"stroke-linecap": "round",
|
|
7269
|
+
"stroke-linejoin": "round"
|
|
7270
|
+
}, undefined, false, undefined, this)
|
|
7271
|
+
]
|
|
7272
|
+
}, undefined, true, undefined, this),
|
|
7273
|
+
label
|
|
7274
|
+
]
|
|
7275
|
+
}, undefined, true, undefined, this)
|
|
7276
|
+
}, undefined, false, undefined, this);
|
|
7277
|
+
}
|
|
7278
|
+
function HostsView() {
|
|
7279
|
+
const { data } = useQuery("hosts.check");
|
|
7280
|
+
const results = data?.results ?? [];
|
|
7281
|
+
const failing = results.filter((r3) => r3.status === "missing" || r3.status === "wrong_address");
|
|
7282
|
+
const missingHostnames = [...new Set(failing.map((r3) => r3.hostname))];
|
|
7283
|
+
const hostsLine = `127.0.0.1 ${missingHostnames.join(" ")}`;
|
|
7284
|
+
const fixCommand = data?.hostsFilePath ? `sudo sh -c 'echo "${hostsLine}" >> ${data.hostsFilePath}'` : "";
|
|
7285
|
+
return /* @__PURE__ */ u3("div", {
|
|
7286
|
+
class: "p-4 sm:p-8",
|
|
7287
|
+
children: [
|
|
7288
|
+
/* @__PURE__ */ u3(PageHeader, {
|
|
7289
|
+
title: "Hosts Check",
|
|
7290
|
+
subtitle: data?.hostsFilePath ? `Reading ${data.hostsFilePath}` : "Checking host routing setup"
|
|
7291
|
+
}, undefined, false, undefined, this),
|
|
7292
|
+
data?.error ? /* @__PURE__ */ u3("div", {
|
|
7293
|
+
class: "rounded-lg border border-red-500/30 bg-red-500/10 p-4 text-sm text-red-400",
|
|
7294
|
+
children: data.error
|
|
7295
|
+
}, undefined, false, undefined, this) : !results.length ? /* @__PURE__ */ u3(EmptyState, {
|
|
7296
|
+
message: "No host routing configured. Add hosts to workers in lopata.config.ts."
|
|
7297
|
+
}, undefined, false, undefined, this) : /* @__PURE__ */ u3(k, {
|
|
7298
|
+
children: [
|
|
7299
|
+
/* @__PURE__ */ u3(Table, {
|
|
7300
|
+
headers: ["Hostname", "Worker", "Address", "Status"],
|
|
7301
|
+
rows: results.map((r3) => [
|
|
7302
|
+
/* @__PURE__ */ u3("span", {
|
|
7303
|
+
class: "font-mono text-xs font-medium",
|
|
7304
|
+
children: r3.hostname
|
|
7305
|
+
}, undefined, false, undefined, this),
|
|
7306
|
+
/* @__PURE__ */ u3("span", {
|
|
7307
|
+
class: "text-text-secondary",
|
|
7308
|
+
children: r3.workerName
|
|
7309
|
+
}, undefined, false, undefined, this),
|
|
7310
|
+
/* @__PURE__ */ u3("span", {
|
|
7311
|
+
class: "font-mono text-xs text-text-secondary",
|
|
7312
|
+
children: r3.status === "ok" ? r3.address : r3.status === "wrong_address" ? r3.address : "—"
|
|
7313
|
+
}, undefined, false, undefined, this),
|
|
7314
|
+
/* @__PURE__ */ u3(StatusBadge, {
|
|
7315
|
+
status: STATUS_LABELS[r3.status] ?? r3.status,
|
|
7316
|
+
colorMap: STATUS_COLORS
|
|
7317
|
+
}, undefined, false, undefined, this)
|
|
7318
|
+
])
|
|
7319
|
+
}, undefined, false, undefined, this),
|
|
7320
|
+
missingHostnames.length > 0 && /* @__PURE__ */ u3("div", {
|
|
7321
|
+
class: "mt-6 rounded-lg border border-yellow-500/30 bg-yellow-500/5 p-4",
|
|
7322
|
+
children: [
|
|
7323
|
+
/* @__PURE__ */ u3("div", {
|
|
7324
|
+
class: "text-sm font-medium text-yellow-500 mb-2",
|
|
7325
|
+
children: "Missing hosts file entries"
|
|
7326
|
+
}, undefined, false, undefined, this),
|
|
7327
|
+
/* @__PURE__ */ u3("div", {
|
|
7328
|
+
class: "text-xs text-text-secondary mb-3",
|
|
7329
|
+
children: "Modifying the hosts file requires root privileges. Copy the command below and run it in your terminal:"
|
|
7330
|
+
}, undefined, false, undefined, this),
|
|
7331
|
+
/* @__PURE__ */ u3("div", {
|
|
7332
|
+
class: "flex items-stretch gap-2",
|
|
7333
|
+
children: [
|
|
7334
|
+
/* @__PURE__ */ u3("pre", {
|
|
7335
|
+
class: "flex-1 text-xs font-mono bg-surface p-3 rounded border border-border select-all overflow-x-auto",
|
|
7336
|
+
children: fixCommand
|
|
7337
|
+
}, undefined, false, undefined, this),
|
|
7338
|
+
/* @__PURE__ */ u3(CopyButton, {
|
|
7339
|
+
text: fixCommand,
|
|
7340
|
+
label: "Copy command"
|
|
7341
|
+
}, undefined, false, undefined, this)
|
|
7342
|
+
]
|
|
7343
|
+
}, undefined, true, undefined, this),
|
|
7344
|
+
/* @__PURE__ */ u3("div", {
|
|
7345
|
+
class: "mt-3 text-[11px] text-text-muted",
|
|
7346
|
+
children: [
|
|
7347
|
+
"Or manually add this line to ",
|
|
7348
|
+
data?.hostsFilePath,
|
|
7349
|
+
":"
|
|
7350
|
+
]
|
|
7351
|
+
}, undefined, true, undefined, this),
|
|
7352
|
+
/* @__PURE__ */ u3("div", {
|
|
7353
|
+
class: "flex items-stretch gap-2 mt-1",
|
|
7354
|
+
children: [
|
|
7355
|
+
/* @__PURE__ */ u3("pre", {
|
|
7356
|
+
class: "flex-1 text-xs font-mono bg-surface p-3 rounded border border-border select-all",
|
|
7357
|
+
children: hostsLine
|
|
7358
|
+
}, undefined, false, undefined, this),
|
|
7359
|
+
/* @__PURE__ */ u3(CopyButton, {
|
|
7360
|
+
text: hostsLine,
|
|
7361
|
+
label: "Copy line"
|
|
7362
|
+
}, undefined, false, undefined, this)
|
|
7363
|
+
]
|
|
7364
|
+
}, undefined, true, undefined, this)
|
|
7365
|
+
]
|
|
7366
|
+
}, undefined, true, undefined, this)
|
|
7367
|
+
]
|
|
7368
|
+
}, undefined, true, undefined, this)
|
|
7369
|
+
]
|
|
7370
|
+
}, undefined, true, undefined, this);
|
|
7371
|
+
}
|
|
7372
|
+
|
|
7199
7373
|
// src/dashboard/views/kv.tsx
|
|
7200
7374
|
function KvView({ route }) {
|
|
7201
7375
|
const parts = route.split("/").filter(Boolean);
|
|
@@ -9982,6 +10156,7 @@ var NAV_GROUPS = [
|
|
|
9982
10156
|
items: [
|
|
9983
10157
|
{ path: "/workers", label: "Workers", icon: "workers" },
|
|
9984
10158
|
{ path: "/routes", label: "Routes", icon: "routes" },
|
|
10159
|
+
{ path: "/hosts", label: "Hosts Check", icon: "routes" },
|
|
9985
10160
|
{ path: "/do", label: "Durable Objects", icon: "do" },
|
|
9986
10161
|
{ path: "/containers", label: "Containers", icon: "containers" },
|
|
9987
10162
|
{ path: "/workflows", label: "Workflows", icon: "workflows" },
|
|
@@ -10120,6 +10295,8 @@ function App() {
|
|
|
10120
10295
|
return /* @__PURE__ */ u3(WorkersView, {}, undefined, false, undefined, this);
|
|
10121
10296
|
if (route.startsWith("/routes"))
|
|
10122
10297
|
return /* @__PURE__ */ u3(RoutesView, {}, undefined, false, undefined, this);
|
|
10298
|
+
if (route.startsWith("/hosts"))
|
|
10299
|
+
return /* @__PURE__ */ u3(HostsView, {}, undefined, false, undefined, this);
|
|
10123
10300
|
if (route.startsWith("/generations"))
|
|
10124
10301
|
return /* @__PURE__ */ u3(GenerationsView, {}, undefined, false, undefined, this);
|
|
10125
10302
|
if (route.startsWith("/kv"))
|
|
@@ -564,6 +564,10 @@
|
|
|
564
564
|
margin-top: calc(var(--spacing) * 4);
|
|
565
565
|
}
|
|
566
566
|
|
|
567
|
+
.mt-6 {
|
|
568
|
+
margin-top: calc(var(--spacing) * 6);
|
|
569
|
+
}
|
|
570
|
+
|
|
567
571
|
.mt-8 {
|
|
568
572
|
margin-top: calc(var(--spacing) * 8);
|
|
569
573
|
}
|
|
@@ -684,6 +688,10 @@
|
|
|
684
688
|
height: calc(var(--spacing) * 2.5);
|
|
685
689
|
}
|
|
686
690
|
|
|
691
|
+
.h-3\.5 {
|
|
692
|
+
height: calc(var(--spacing) * 3.5);
|
|
693
|
+
}
|
|
694
|
+
|
|
687
695
|
.h-4 {
|
|
688
696
|
height: calc(var(--spacing) * 4);
|
|
689
697
|
}
|
|
@@ -760,6 +768,10 @@
|
|
|
760
768
|
width: calc(var(--spacing) * 2.5);
|
|
761
769
|
}
|
|
762
770
|
|
|
771
|
+
.w-3\.5 {
|
|
772
|
+
width: calc(var(--spacing) * 3.5);
|
|
773
|
+
}
|
|
774
|
+
|
|
763
775
|
.w-4 {
|
|
764
776
|
width: calc(var(--spacing) * 4);
|
|
765
777
|
}
|
|
@@ -969,6 +981,10 @@
|
|
|
969
981
|
align-items: flex-start;
|
|
970
982
|
}
|
|
971
983
|
|
|
984
|
+
.items-stretch {
|
|
985
|
+
align-items: stretch;
|
|
986
|
+
}
|
|
987
|
+
|
|
972
988
|
.justify-between {
|
|
973
989
|
justify-content: space-between;
|
|
974
990
|
}
|
|
@@ -1607,6 +1623,16 @@
|
|
|
1607
1623
|
background-color: var(--color-yellow-400);
|
|
1608
1624
|
}
|
|
1609
1625
|
|
|
1626
|
+
.bg-yellow-500\/5 {
|
|
1627
|
+
background-color: #edb2000d;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1631
|
+
.bg-yellow-500\/5 {
|
|
1632
|
+
background-color: color-mix(in oklab, var(--color-yellow-500) 5%, transparent);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1610
1636
|
.bg-yellow-500\/15 {
|
|
1611
1637
|
background-color: #edb20026;
|
|
1612
1638
|
}
|
|
@@ -2149,6 +2175,11 @@
|
|
|
2149
2175
|
outline-style: none;
|
|
2150
2176
|
}
|
|
2151
2177
|
|
|
2178
|
+
.select-all {
|
|
2179
|
+
-webkit-user-select: all;
|
|
2180
|
+
user-select: all;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2152
2183
|
.select-none {
|
|
2153
2184
|
-webkit-user-select: none;
|
|
2154
2185
|
user-select: none;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
10
10
|
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/__dashboard/assets/chunk-
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/__dashboard/assets/chunk-n1xkdmxt.css"><script type="module" crossorigin src="/__dashboard/assets/chunk-5h0jr7cs.js"></script></head>
|
|
12
12
|
<body class="h-full bg-surface text-ink" style="font-family: system-ui, -apple-system, sans-serif;">
|
|
13
13
|
<script>
|
|
14
14
|
// Apply saved theme before first paint to prevent flash
|
package/package.json
CHANGED
package/src/api/dispatch.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { handlers as durableObjects } from './handlers/do'
|
|
|
9
9
|
import { handlers as email } from './handlers/email'
|
|
10
10
|
import { handlers as errors } from './handlers/errors'
|
|
11
11
|
import { handlers as generations } from './handlers/generations'
|
|
12
|
+
import { handlers as hosts } from './handlers/hosts'
|
|
12
13
|
import { handlers as kv } from './handlers/kv'
|
|
13
14
|
import { handlers as overview } from './handlers/overview'
|
|
14
15
|
import { handlers as queue } from './handlers/queue'
|
|
@@ -42,6 +43,7 @@ const allHandlers = {
|
|
|
42
43
|
...analyticsEngine,
|
|
43
44
|
...warnings,
|
|
44
45
|
...routes,
|
|
46
|
+
...hosts,
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export type Procedures = {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { checkHostPatterns, readSystemHostsFile } from '../../hosts-check'
|
|
2
|
+
import type { HandlerContext, HostsCheckResponse } from '../types'
|
|
3
|
+
|
|
4
|
+
export const handlers = {
|
|
5
|
+
'hosts.check'(_input: {}, ctx: HandlerContext): HostsCheckResponse {
|
|
6
|
+
const hostsFile = readSystemHostsFile()
|
|
7
|
+
|
|
8
|
+
if ('error' in hostsFile) {
|
|
9
|
+
return { results: [], hostsFilePath: hostsFile.path, error: hostsFile.error }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const results = checkHostPatterns(hostsFile.entries, ctx.lopataConfig)
|
|
13
|
+
return { results, hostsFilePath: hostsFile.path }
|
|
14
|
+
},
|
|
15
|
+
}
|
package/src/api/types.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
export type { GenerationInfo } from '../generation'
|
|
4
4
|
import type { GenerationInfo } from '../generation'
|
|
5
|
+
import type { HostCheckResult } from '../hosts-check'
|
|
5
6
|
|
|
6
7
|
export interface Paginated<T> {
|
|
7
8
|
items: T[]
|
|
@@ -396,6 +397,12 @@ export interface HandlerContext {
|
|
|
396
397
|
hostRoutes: HostRouteInfo[]
|
|
397
398
|
}
|
|
398
399
|
|
|
400
|
+
export interface HostsCheckResponse {
|
|
401
|
+
results: HostCheckResult[]
|
|
402
|
+
hostsFilePath: string
|
|
403
|
+
error?: string
|
|
404
|
+
}
|
|
405
|
+
|
|
399
406
|
export interface RouteInfo {
|
|
400
407
|
pattern: string
|
|
401
408
|
workerName: string
|
package/src/cli/hosts.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { checkHostPatterns, readSystemHostsFile } from '../hosts-check'
|
|
2
|
+
import { loadLopataConfig } from '../lopata-config'
|
|
3
|
+
import type { CliContext } from './context'
|
|
4
|
+
|
|
5
|
+
export async function run(_ctx: CliContext, args: string[]) {
|
|
6
|
+
const action = args[0]
|
|
7
|
+
|
|
8
|
+
if (action !== 'check') {
|
|
9
|
+
console.error('Usage: lopata hosts check')
|
|
10
|
+
process.exit(1)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const baseDir = process.cwd()
|
|
14
|
+
const lopataConfig = await loadLopataConfig(baseDir)
|
|
15
|
+
|
|
16
|
+
if (!lopataConfig?.workers?.some(w => w.hosts?.length)) {
|
|
17
|
+
console.log('No host routing configured. Nothing to check.')
|
|
18
|
+
console.log('Host routing is configured via the "hosts" field in lopata.config.ts workers.')
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const hostsFile = readSystemHostsFile()
|
|
23
|
+
if ('error' in hostsFile) {
|
|
24
|
+
console.error(hostsFile.error)
|
|
25
|
+
console.error('Make sure you have read permissions.')
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const results = checkHostPatterns(hostsFile.entries, lopataConfig)
|
|
30
|
+
const hostsPath = hostsFile.path
|
|
31
|
+
|
|
32
|
+
console.log(`Hosts file: ${hostsPath}`)
|
|
33
|
+
console.log('')
|
|
34
|
+
|
|
35
|
+
for (const r of results) {
|
|
36
|
+
switch (r.status) {
|
|
37
|
+
case 'ok':
|
|
38
|
+
console.log(` ✓ ${r.hostname} (worker: ${r.workerName}) → ${r.address}`)
|
|
39
|
+
break
|
|
40
|
+
case 'missing':
|
|
41
|
+
console.log(` ✗ ${r.hostname} (worker: ${r.workerName}) — not found in hosts file`)
|
|
42
|
+
break
|
|
43
|
+
case 'wrong_address':
|
|
44
|
+
console.log(` ✗ ${r.hostname} (worker: ${r.workerName}) — points to ${r.address}, expected 127.0.0.1`)
|
|
45
|
+
break
|
|
46
|
+
case 'wildcard':
|
|
47
|
+
console.log(` ⚠ ${r.hostname} (worker: ${r.workerName}) — wildcard pattern, cannot be checked in hosts file`)
|
|
48
|
+
console.log(` You need to add specific subdomains to your hosts file manually.`)
|
|
49
|
+
if (process.platform === 'darwin') {
|
|
50
|
+
console.log(` Alternatively, use dnsmasq or a local DNS resolver to handle wildcard domains.`)
|
|
51
|
+
}
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log('')
|
|
57
|
+
|
|
58
|
+
const failing = results.filter(r => r.status === 'missing' || r.status === 'wrong_address')
|
|
59
|
+
if (failing.length > 0) {
|
|
60
|
+
console.log('Some hostnames are missing or misconfigured.')
|
|
61
|
+
console.log('')
|
|
62
|
+
console.log('Add the missing entries to your hosts file:')
|
|
63
|
+
console.log('')
|
|
64
|
+
|
|
65
|
+
const uniqueHostnames = [...new Set(failing.map(m => m.hostname))]
|
|
66
|
+
console.log(` ${hostsPath}:`)
|
|
67
|
+
console.log(` 127.0.0.1 ${uniqueHostnames.join(' ')}`)
|
|
68
|
+
console.log('')
|
|
69
|
+
|
|
70
|
+
if (process.platform !== 'win32') {
|
|
71
|
+
console.log(`Run: sudo sh -c 'echo "127.0.0.1 ${uniqueHostnames.join(' ')}" >> ${hostsPath}'`)
|
|
72
|
+
} else {
|
|
73
|
+
console.log('Open Notepad as Administrator and add the line above to the hosts file.')
|
|
74
|
+
}
|
|
75
|
+
} else if (!results.some(r => r.status === 'wildcard')) {
|
|
76
|
+
console.log('All host routes are correctly configured.')
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -55,6 +55,11 @@ switch (command) {
|
|
|
55
55
|
await mod.run(ctx, commandArgs.slice(1))
|
|
56
56
|
break
|
|
57
57
|
}
|
|
58
|
+
case 'hosts': {
|
|
59
|
+
const mod = await import('./cli/hosts')
|
|
60
|
+
await mod.run(ctx, commandArgs.slice(1))
|
|
61
|
+
break
|
|
62
|
+
}
|
|
58
63
|
case 'trace': {
|
|
59
64
|
const mod = await import('./cli/traces')
|
|
60
65
|
await mod.run(ctx, commandArgs.slice(1))
|
|
@@ -91,6 +96,7 @@ Commands:
|
|
|
91
96
|
queues message purge <queue> Purge queue messages
|
|
92
97
|
cache list List cache names
|
|
93
98
|
cache purge [--name CACHE] Purge cache entries
|
|
99
|
+
hosts check Check hosts file for configured host routes
|
|
94
100
|
trace list [options] List traces (--limit, --since, --search, --cursor)
|
|
95
101
|
trace get <traceId> Get trace detail as JSON
|
|
96
102
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import type { LopataConfig } from './lopata-config'
|
|
3
|
+
|
|
4
|
+
export interface HostsEntry {
|
|
5
|
+
address: string
|
|
6
|
+
hostnames: string[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface HostCheckResult {
|
|
10
|
+
hostname: string
|
|
11
|
+
workerName: string
|
|
12
|
+
status: 'ok' | 'missing' | 'wrong_address' | 'wildcard'
|
|
13
|
+
address?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const LOCALHOST_ADDRESSES = new Set(['127.0.0.1', '::1', 'localhost'])
|
|
17
|
+
|
|
18
|
+
export function getHostsFilePath(): string {
|
|
19
|
+
if (process.platform === 'win32') {
|
|
20
|
+
return 'C:\\Windows\\System32\\drivers\\etc\\hosts'
|
|
21
|
+
}
|
|
22
|
+
return '/etc/hosts'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseHostsFile(content: string): HostsEntry[] {
|
|
26
|
+
const entries: HostsEntry[] = []
|
|
27
|
+
for (const line of content.split('\n')) {
|
|
28
|
+
const trimmed = line.trim()
|
|
29
|
+
if (trimmed === '' || trimmed.startsWith('#')) continue
|
|
30
|
+
const parts = trimmed.split(/\s+/)
|
|
31
|
+
if (parts.length < 2) continue
|
|
32
|
+
entries.push({ address: parts[0]!, hostnames: parts.slice(1) })
|
|
33
|
+
}
|
|
34
|
+
return entries
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readSystemHostsFile(): { path: string; entries: HostsEntry[] } | { path: string; error: string } {
|
|
38
|
+
const path = getHostsFilePath()
|
|
39
|
+
try {
|
|
40
|
+
const content = readFileSync(path, 'utf-8')
|
|
41
|
+
return { path, entries: parseHostsFile(content) }
|
|
42
|
+
} catch {
|
|
43
|
+
return { path, error: `Could not read hosts file at ${path}` }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function checkHostPatterns(
|
|
48
|
+
hostsEntries: HostsEntry[],
|
|
49
|
+
lopataConfig: LopataConfig | null,
|
|
50
|
+
): HostCheckResult[] {
|
|
51
|
+
const results: HostCheckResult[] = []
|
|
52
|
+
|
|
53
|
+
if (!lopataConfig?.workers) return results
|
|
54
|
+
|
|
55
|
+
for (const worker of lopataConfig.workers) {
|
|
56
|
+
if (!worker.hosts) continue
|
|
57
|
+
for (const host of worker.hosts) {
|
|
58
|
+
if (host.startsWith('*.')) {
|
|
59
|
+
results.push({ hostname: host, workerName: worker.name, status: 'wildcard' })
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const entry = hostsEntries.find(e => e.hostnames.includes(host))
|
|
64
|
+
if (!entry) {
|
|
65
|
+
results.push({ hostname: host, workerName: worker.name, status: 'missing' })
|
|
66
|
+
} else if (!LOCALHOST_ADDRESSES.has(entry.address)) {
|
|
67
|
+
results.push({ hostname: host, workerName: worker.name, status: 'wrong_address', address: entry.address })
|
|
68
|
+
} else {
|
|
69
|
+
results.push({ hostname: host, workerName: worker.name, status: 'ok', address: entry.address })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return results
|
|
75
|
+
}
|