home-hosted 0.7.21 → 0.8.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/AGENTS.md +3 -2
- package/README.md +12 -1
- package/dist/cli.js +281 -417
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/uis/stock/dist/assets/{index-3hQv8lsr.js → index-Mcvn1u4C.js} +7 -7
- package/uis/stock/dist/index.html +1 -1
- package/uis/stock/dist/ui.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,19 @@ var __exportAll = (all, no_symbols) => {
|
|
|
49
49
|
return target;
|
|
50
50
|
};
|
|
51
51
|
//#endregion
|
|
52
|
+
//#region src/shared/error-message.ts
|
|
53
|
+
/**
|
|
54
|
+
* One rendering for a thrown value, so a caught error reads the same everywhere.
|
|
55
|
+
*
|
|
56
|
+
* `catch` binds `unknown`, and the same ternary was written out in ~60 places across the
|
|
57
|
+
* panel and both UIs. Two idioms in one codebase is how they drift, and the UIs are
|
|
58
|
+
* separate bundles: a fix applied to one copy silently misses the others.
|
|
59
|
+
*/
|
|
60
|
+
function errorMessage(caught) {
|
|
61
|
+
return caught instanceof Error ? caught.message : String(caught);
|
|
62
|
+
}
|
|
63
|
+
var init_error_message = __esmMin((() => {}));
|
|
64
|
+
//#endregion
|
|
52
65
|
//#region src/cli/nearest.ts
|
|
53
66
|
/**
|
|
54
67
|
* The nearest word to a typo, or null when nothing is close.
|
|
@@ -1895,7 +1908,11 @@ function isSealedDdns(value) {
|
|
|
1895
1908
|
function isPlainDdns(value) {
|
|
1896
1909
|
return "plain" in value;
|
|
1897
1910
|
}
|
|
1898
|
-
/**
|
|
1911
|
+
/**
|
|
1912
|
+
* Sealed on disk, or `{ provider, values }` waiting for the sealing write. A bare
|
|
1913
|
+
* field map — written before the provider was recorded — no longer reads, because
|
|
1914
|
+
* there is nothing to say which provider those fields belong to.
|
|
1915
|
+
*/
|
|
1899
1916
|
function readStoredDdns(value) {
|
|
1900
1917
|
if (!isRecord(value)) return {};
|
|
1901
1918
|
const out = {};
|
|
@@ -1904,12 +1921,12 @@ function readStoredDdns(value) {
|
|
|
1904
1921
|
out[accountId] = entry;
|
|
1905
1922
|
continue;
|
|
1906
1923
|
}
|
|
1907
|
-
if (!isRecord(entry)) continue;
|
|
1908
|
-
const values = readDdnsValues(
|
|
1924
|
+
if (!isRecord(entry) || typeof entry.provider !== "string" || entry.provider.length === 0) continue;
|
|
1925
|
+
const values = readDdnsValues(entry.values);
|
|
1909
1926
|
if (Object.keys(values).length === 0) continue;
|
|
1910
1927
|
out[accountId] = {
|
|
1911
1928
|
plain: true,
|
|
1912
|
-
provider:
|
|
1929
|
+
provider: entry.provider,
|
|
1913
1930
|
values
|
|
1914
1931
|
};
|
|
1915
1932
|
}
|
|
@@ -1977,6 +1994,7 @@ var COST, KEYLEN, SALT_BYTES, DDNS_KEYLEN, DDNS_IV_BYTES, DDNS_AAD_PREFIX, DDNS_
|
|
|
1977
1994
|
var init_secrets = __esmMin((() => {
|
|
1978
1995
|
init_atomic();
|
|
1979
1996
|
init_logger();
|
|
1997
|
+
init_error_message();
|
|
1980
1998
|
init_shape();
|
|
1981
1999
|
COST = {
|
|
1982
2000
|
N: 16384,
|
|
@@ -1996,6 +2014,7 @@ var init_secrets = __esmMin((() => {
|
|
|
1996
2014
|
scope;
|
|
1997
2015
|
cache = null;
|
|
1998
2016
|
cacheKey = "";
|
|
2017
|
+
error = null;
|
|
1999
2018
|
ddnsKeyCache = null;
|
|
2000
2019
|
warnedAccounts = /* @__PURE__ */ new Set();
|
|
2001
2020
|
warnedDefaultSecret = false;
|
|
@@ -2120,7 +2139,7 @@ var init_secrets = __esmMin((() => {
|
|
|
2120
2139
|
if (stored === void 0) return null;
|
|
2121
2140
|
const record = this.openStored(accountId, stored);
|
|
2122
2141
|
if (record === null) return null;
|
|
2123
|
-
return record.provider
|
|
2142
|
+
return record.provider === provider ? record : null;
|
|
2124
2143
|
}
|
|
2125
2144
|
/** An empty field is dropped, so clearing every field forgets the account. */
|
|
2126
2145
|
setDdnsCredentials(accountId, provider, credentials) {
|
|
@@ -2191,9 +2210,22 @@ var init_secrets = __esmMin((() => {
|
|
|
2191
2210
|
this.warnedAccounts.add(accountId);
|
|
2192
2211
|
logger.warn(`ddns: cannot read the credentials for "${accountId}" — ${reason}`);
|
|
2193
2212
|
}
|
|
2213
|
+
/**
|
|
2214
|
+
* A secrets file that exists but cannot be parsed, or null when it is fine (or absent).
|
|
2215
|
+
*
|
|
2216
|
+
* `read()` still returns the empty shape — a corrupt file must not authenticate anyone — but
|
|
2217
|
+
* "unreadable" and "absent" have to stay distinguishable: treated as absent, a truncated
|
|
2218
|
+
* `.control-secrets.json` reads as a fresh install, and `runControlPlane` then writes a
|
|
2219
|
+
* **default-password** file over it, destroying the password hash, the API token and every
|
|
2220
|
+
* Telegram and DDNS credential. Only the caller can refuse the boot, so this is its surface.
|
|
2221
|
+
*/
|
|
2222
|
+
get configError() {
|
|
2223
|
+
this.load();
|
|
2224
|
+
return this.error;
|
|
2225
|
+
}
|
|
2194
2226
|
read() {
|
|
2227
|
+
this.error = null;
|
|
2195
2228
|
const empty = {
|
|
2196
|
-
version: 3,
|
|
2197
2229
|
password: null,
|
|
2198
2230
|
apiToken: null,
|
|
2199
2231
|
telegram: null,
|
|
@@ -2201,17 +2233,24 @@ var init_secrets = __esmMin((() => {
|
|
|
2201
2233
|
ddnsKdf: null
|
|
2202
2234
|
};
|
|
2203
2235
|
if (!fs.existsSync(this.file)) return this.inScope(empty);
|
|
2236
|
+
let text;
|
|
2204
2237
|
try {
|
|
2205
|
-
|
|
2238
|
+
text = fs.readFileSync(this.file, "utf8");
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
this.error = `cannot read ${this.file}: ${errorMessage(error)}`;
|
|
2241
|
+
return this.inScope(empty);
|
|
2242
|
+
}
|
|
2243
|
+
try {
|
|
2244
|
+
const parsed = JSON.parse(text);
|
|
2206
2245
|
return this.inScope({
|
|
2207
|
-
version: 3,
|
|
2208
2246
|
password: parsed?.password ?? null,
|
|
2209
2247
|
apiToken: parsed?.apiToken?.hash ? parsed.apiToken : null,
|
|
2210
2248
|
telegram: parsed?.telegram?.botToken ? { botToken: parsed.telegram.botToken } : null,
|
|
2211
2249
|
ddns: readStoredDdns(parsed?.ddns),
|
|
2212
2250
|
ddnsKdf: readDdnsKdf(parsed?.ddnsKdf)
|
|
2213
2251
|
});
|
|
2214
|
-
} catch {
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
this.error = `${this.file} is not readable JSON (${errorMessage(error)}) — it was left untouched`;
|
|
2215
2254
|
return this.inScope(empty);
|
|
2216
2255
|
}
|
|
2217
2256
|
}
|
|
@@ -2231,6 +2270,7 @@ var init_secrets = __esmMin((() => {
|
|
|
2231
2270
|
return contents;
|
|
2232
2271
|
}
|
|
2233
2272
|
save(contents) {
|
|
2273
|
+
if (this.configError !== null) throw new Error(`refusing to write ${this.file}: ${this.error}`);
|
|
2234
2274
|
const sealed = this.sealStored(this.inScope(contents));
|
|
2235
2275
|
writeFileAtomic(this.file, `${JSON.stringify(sealed, null, 2)}\n`, { mode: 384 });
|
|
2236
2276
|
this.cache = sealed;
|
|
@@ -2919,7 +2959,11 @@ function workspaceIdsOnDisk() {
|
|
|
2919
2959
|
return [];
|
|
2920
2960
|
}
|
|
2921
2961
|
}
|
|
2922
|
-
/**
|
|
2962
|
+
/**
|
|
2963
|
+
* True when a pre-workspace instance left state at the old paths **and** never
|
|
2964
|
+
* crossed into `.hh`: `workspaces.json` means the layout is current, so a stray
|
|
2965
|
+
* root-level copy beside it is not an old instance.
|
|
2966
|
+
*/
|
|
2923
2967
|
function hasLegacyLayout() {
|
|
2924
2968
|
if (fs.existsSync(workspacesPath)) return false;
|
|
2925
2969
|
return fs.existsSync(legacy.configPath) || fs.existsSync(legacy.secretsPath);
|
|
@@ -2955,15 +2999,7 @@ var init_paths = __esmMin((() => {
|
|
|
2955
2999
|
proxyChallengeAuthPath = path.join(proxyStateDir, "challenge.json");
|
|
2956
3000
|
legacy = {
|
|
2957
3001
|
configPath: path.join(dataRoot, "servers.config.json"),
|
|
2958
|
-
|
|
2959
|
-
secretsPath: path.join(dataRoot, ".control-secrets.json"),
|
|
2960
|
-
tlsDir: path.join(dataRoot, ".tls"),
|
|
2961
|
-
logsDir: path.join(dataRoot, ".logs"),
|
|
2962
|
-
historyPath: path.join(dataRoot, ".logs", "history.json"),
|
|
2963
|
-
stateDir: path.join(dataRoot, ".state"),
|
|
2964
|
-
backupsDir: path.join(dataRoot, ".backups"),
|
|
2965
|
-
uiDir: path.join(dataRoot, ".ui"),
|
|
2966
|
-
runtimePath: path.join(dataRoot, "run.json")
|
|
3002
|
+
secretsPath: path.join(dataRoot, ".control-secrets.json")
|
|
2967
3003
|
};
|
|
2968
3004
|
}));
|
|
2969
3005
|
//#endregion
|
|
@@ -3624,7 +3660,7 @@ function planConfigMigrations(from, options = {}) {
|
|
|
3624
3660
|
to,
|
|
3625
3661
|
steps,
|
|
3626
3662
|
tooNew: from > to,
|
|
3627
|
-
reached: steps.
|
|
3663
|
+
reached: steps.at(-1)?.to ?? from
|
|
3628
3664
|
};
|
|
3629
3665
|
}
|
|
3630
3666
|
/** Applies the plan in order; the caller owns writing the result. */
|
|
@@ -3692,6 +3728,35 @@ function parseTolerant(value, schema, prefix, unknownKeys) {
|
|
|
3692
3728
|
};
|
|
3693
3729
|
}
|
|
3694
3730
|
/**
|
|
3731
|
+
* Reads `meta`, then refuses a file this release cannot use.
|
|
3732
|
+
*
|
|
3733
|
+
* Both readers need this and the two messages have to stay in step: a file written by a
|
|
3734
|
+
* newer release, and one whose migration has not been applied, are different problems with
|
|
3735
|
+
* different fixes (`install that release` vs. `run migrate`). Written once here, so a change
|
|
3736
|
+
* to either wording cannot land on one path only.
|
|
3737
|
+
*/
|
|
3738
|
+
function readStamp(raw, options) {
|
|
3739
|
+
const meta = isRecord(raw.meta) ? raw.meta : null;
|
|
3740
|
+
const schemaVersion = typeof meta?.schema === "number" ? meta.schema : 1;
|
|
3741
|
+
const writtenBy = typeof meta?.writtenBy === "string" && meta.writtenBy.length > 0 ? meta.writtenBy : null;
|
|
3742
|
+
if (schemaVersion > 1) return {
|
|
3743
|
+
schemaVersion,
|
|
3744
|
+
writtenBy,
|
|
3745
|
+
error: `written by home-hosted ${writtenBy ?? "a newer release"} (config schema ${schemaVersion}); this release understands schema 1`
|
|
3746
|
+
};
|
|
3747
|
+
const { steps } = planConfigMigrations(schemaVersion, options);
|
|
3748
|
+
if (steps.length > 0) return {
|
|
3749
|
+
schemaVersion,
|
|
3750
|
+
writtenBy,
|
|
3751
|
+
error: `config schema ${schemaVersion} needs ${steps.length} migration${steps.length === 1 ? "" : "s"} before this release can use it`
|
|
3752
|
+
};
|
|
3753
|
+
return {
|
|
3754
|
+
schemaVersion,
|
|
3755
|
+
writtenBy,
|
|
3756
|
+
error: null
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
/**
|
|
3695
3760
|
* Reads one settings file from any release: schema guard, unrecognized keys
|
|
3696
3761
|
* reported and left on disk, blocking problems returned for the caller to refuse on.
|
|
3697
3762
|
*/
|
|
@@ -3710,18 +3775,11 @@ function parseGrouped(raw, label, definition, options) {
|
|
|
3710
3775
|
errors.push(`the ${label} must contain a JSON object`);
|
|
3711
3776
|
return result;
|
|
3712
3777
|
}
|
|
3713
|
-
const
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
if (schemaVersion > 1) {
|
|
3719
|
-
errors.push(`written by home-hosted ${writtenBy ?? "a newer release"} (config schema ${schemaVersion}); this release understands schema 1`);
|
|
3720
|
-
return result;
|
|
3721
|
-
}
|
|
3722
|
-
const { steps } = planConfigMigrations(schemaVersion, options);
|
|
3723
|
-
if (steps.length > 0) {
|
|
3724
|
-
errors.push(`config schema ${schemaVersion} needs ${steps.length} migration${steps.length === 1 ? "" : "s"} before this release can use it`);
|
|
3778
|
+
const stamp = readStamp(raw, options);
|
|
3779
|
+
result.schemaVersion = stamp.schemaVersion;
|
|
3780
|
+
result.writtenBy = stamp.writtenBy;
|
|
3781
|
+
if (stamp.error !== null) {
|
|
3782
|
+
errors.push(stamp.error);
|
|
3725
3783
|
return result;
|
|
3726
3784
|
}
|
|
3727
3785
|
for (const key of Object.keys(raw)) if (!definition.keys.includes(key)) unknownKeys.push(key);
|
|
@@ -3733,8 +3791,8 @@ function parseGrouped(raw, label, definition, options) {
|
|
|
3733
3791
|
}
|
|
3734
3792
|
if (errors.length > 0) return result;
|
|
3735
3793
|
result.config = definition.build({
|
|
3736
|
-
writtenBy: writtenBy ?? "",
|
|
3737
|
-
schema: schemaVersion
|
|
3794
|
+
writtenBy: stamp.writtenBy ?? "",
|
|
3795
|
+
schema: stamp.schemaVersion
|
|
3738
3796
|
}, groups);
|
|
3739
3797
|
return result;
|
|
3740
3798
|
}
|
|
@@ -3796,18 +3854,11 @@ function parseServersFile(raw, defaults, options = {}) {
|
|
|
3796
3854
|
errors.push("the servers config must contain a JSON object");
|
|
3797
3855
|
return result;
|
|
3798
3856
|
}
|
|
3799
|
-
const
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
if (schemaVersion > 1) {
|
|
3805
|
-
errors.push(`written by home-hosted ${writtenBy ?? "a newer release"} (config schema ${schemaVersion}); this release understands schema 1`);
|
|
3806
|
-
return result;
|
|
3807
|
-
}
|
|
3808
|
-
const { steps } = planConfigMigrations(schemaVersion, options);
|
|
3809
|
-
if (steps.length > 0) {
|
|
3810
|
-
errors.push(`config schema ${schemaVersion} needs ${steps.length} migration${steps.length === 1 ? "" : "s"} before this release can use it`);
|
|
3857
|
+
const stamp = readStamp(raw, options);
|
|
3858
|
+
result.schemaVersion = stamp.schemaVersion;
|
|
3859
|
+
result.writtenBy = stamp.writtenBy;
|
|
3860
|
+
if (stamp.error !== null) {
|
|
3861
|
+
errors.push(stamp.error);
|
|
3811
3862
|
return result;
|
|
3812
3863
|
}
|
|
3813
3864
|
for (const key of Object.keys(raw)) if (!SERVERS_FILE_KEYS.includes(key)) unknownKeys.push(key);
|
|
@@ -4202,6 +4253,18 @@ function failed(error) {
|
|
|
4202
4253
|
error
|
|
4203
4254
|
};
|
|
4204
4255
|
}
|
|
4256
|
+
/**
|
|
4257
|
+
* The result of the write six providers end their `update()` with.
|
|
4258
|
+
*
|
|
4259
|
+
* They repeated the same two lines after their own write call, and they have to agree: a
|
|
4260
|
+
* non-ok response is a provider error with its body attached, and an ok one is a change.
|
|
4261
|
+
* The message names the record, because a settings page shows it as the account's last
|
|
4262
|
+
* result and "ok" would not say what actually moved.
|
|
4263
|
+
*/
|
|
4264
|
+
async function wrote(response, record) {
|
|
4265
|
+
if (!response.ok) return failed(await errorText(response));
|
|
4266
|
+
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
4267
|
+
}
|
|
4205
4268
|
/** Missing required credential keys, as a sentence for the settings page. */
|
|
4206
4269
|
function missingFields(credentials, keys) {
|
|
4207
4270
|
const missing = keys.filter((key) => (credentials?.[key] ?? "").trim().length === 0);
|
|
@@ -4279,7 +4342,7 @@ async function call$1(path, headers, context, init = {}) {
|
|
|
4279
4342
|
return {
|
|
4280
4343
|
data: null,
|
|
4281
4344
|
status: 0,
|
|
4282
|
-
error:
|
|
4345
|
+
error: errorMessage(error)
|
|
4283
4346
|
};
|
|
4284
4347
|
}
|
|
4285
4348
|
}
|
|
@@ -4338,6 +4401,7 @@ async function findRecord(zoneId, record, headers, context, cached) {
|
|
|
4338
4401
|
var API$5, cloudflareProvider;
|
|
4339
4402
|
var init_cloudflare = __esmMin((() => {
|
|
4340
4403
|
init_types();
|
|
4404
|
+
init_error_message();
|
|
4341
4405
|
API$5 = "https://api.cloudflare.com/client/v4";
|
|
4342
4406
|
cloudflareProvider = {
|
|
4343
4407
|
id: "cloudflare",
|
|
@@ -4414,7 +4478,7 @@ var init_cloudflare = __esmMin((() => {
|
|
|
4414
4478
|
}
|
|
4415
4479
|
};
|
|
4416
4480
|
} catch (error) {
|
|
4417
|
-
return failed(
|
|
4481
|
+
return failed(errorMessage(error));
|
|
4418
4482
|
}
|
|
4419
4483
|
},
|
|
4420
4484
|
/**
|
|
@@ -4481,7 +4545,7 @@ var init_cloudflare = __esmMin((() => {
|
|
|
4481
4545
|
} catch (error) {
|
|
4482
4546
|
return {
|
|
4483
4547
|
ok: false,
|
|
4484
|
-
message:
|
|
4548
|
+
message: errorMessage(error)
|
|
4485
4549
|
};
|
|
4486
4550
|
}
|
|
4487
4551
|
}
|
|
@@ -4612,6 +4676,7 @@ var ENDPOINT$7, desecProvider;
|
|
|
4612
4676
|
var init_desec = __esmMin((() => {
|
|
4613
4677
|
init_dyndns2();
|
|
4614
4678
|
init_types();
|
|
4679
|
+
init_error_message();
|
|
4615
4680
|
ENDPOINT$7 = "https://update.dedyn.io/";
|
|
4616
4681
|
desecProvider = {
|
|
4617
4682
|
id: "desec",
|
|
@@ -4641,7 +4706,7 @@ var init_desec = __esmMin((() => {
|
|
|
4641
4706
|
});
|
|
4642
4707
|
return describeDyndnsResponse(response, await response.text(), "desec");
|
|
4643
4708
|
} catch (error) {
|
|
4644
|
-
return failed(
|
|
4709
|
+
return failed(errorMessage(error));
|
|
4645
4710
|
}
|
|
4646
4711
|
}
|
|
4647
4712
|
};
|
|
@@ -4709,6 +4774,7 @@ var API$4, digitaloceanProvider;
|
|
|
4709
4774
|
var init_digitalocean = __esmMin((() => {
|
|
4710
4775
|
init_types();
|
|
4711
4776
|
init_zone();
|
|
4777
|
+
init_error_message();
|
|
4712
4778
|
API$4 = "https://api.digitalocean.com/v2/domains";
|
|
4713
4779
|
digitaloceanProvider = {
|
|
4714
4780
|
id: "digitalocean",
|
|
@@ -4742,7 +4808,7 @@ var init_digitalocean = __esmMin((() => {
|
|
|
4742
4808
|
if (!listed.ok) return failed(await errorText(listed));
|
|
4743
4809
|
const existing = ((await listed.json()).domain_records ?? []).find((entry) => entry.name === name && entry.type === record.type);
|
|
4744
4810
|
if (existing !== void 0 && existing.data === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
4745
|
-
|
|
4811
|
+
return await wrote(existing === void 0 ? await context.fetch(collection, {
|
|
4746
4812
|
method: "POST",
|
|
4747
4813
|
headers,
|
|
4748
4814
|
body: JSON.stringify({
|
|
@@ -4762,11 +4828,9 @@ var init_digitalocean = __esmMin((() => {
|
|
|
4762
4828
|
ttl
|
|
4763
4829
|
}),
|
|
4764
4830
|
signal: context.signal
|
|
4765
|
-
});
|
|
4766
|
-
if (!written.ok) return failed(await errorText(written));
|
|
4767
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
4831
|
+
}), record);
|
|
4768
4832
|
} catch (error) {
|
|
4769
|
-
return failed(
|
|
4833
|
+
return failed(errorMessage(error));
|
|
4770
4834
|
}
|
|
4771
4835
|
}
|
|
4772
4836
|
};
|
|
@@ -4781,6 +4845,7 @@ function subdomain(host) {
|
|
|
4781
4845
|
var ENDPOINT$6, duckdnsProvider;
|
|
4782
4846
|
var init_duckdns = __esmMin((() => {
|
|
4783
4847
|
init_types();
|
|
4848
|
+
init_error_message();
|
|
4784
4849
|
ENDPOINT$6 = "https://www.duckdns.org/update";
|
|
4785
4850
|
duckdnsProvider = {
|
|
4786
4851
|
id: "duckdns",
|
|
@@ -4809,7 +4874,7 @@ var init_duckdns = __esmMin((() => {
|
|
|
4809
4874
|
if (body.toUpperCase() === "OK") return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
4810
4875
|
return failed(`duckdns answered "${body.slice(0, 120) || `HTTP ${response.status}`}"`);
|
|
4811
4876
|
} catch (error) {
|
|
4812
|
-
return failed(
|
|
4877
|
+
return failed(errorMessage(error));
|
|
4813
4878
|
}
|
|
4814
4879
|
}
|
|
4815
4880
|
};
|
|
@@ -4820,6 +4885,7 @@ var ENDPOINT$5, dynuProvider;
|
|
|
4820
4885
|
var init_dynu = __esmMin((() => {
|
|
4821
4886
|
init_dyndns2();
|
|
4822
4887
|
init_types();
|
|
4888
|
+
init_error_message();
|
|
4823
4889
|
ENDPOINT$5 = "https://api.dynu.com/nic/update";
|
|
4824
4890
|
dynuProvider = {
|
|
4825
4891
|
id: "dynu",
|
|
@@ -4855,7 +4921,7 @@ var init_dynu = __esmMin((() => {
|
|
|
4855
4921
|
});
|
|
4856
4922
|
return describeDyndnsResponse(response, await response.text(), "dynu");
|
|
4857
4923
|
} catch (error) {
|
|
4858
|
-
return failed(
|
|
4924
|
+
return failed(errorMessage(error));
|
|
4859
4925
|
}
|
|
4860
4926
|
}
|
|
4861
4927
|
};
|
|
@@ -4874,6 +4940,7 @@ function updateToken(credentials) {
|
|
|
4874
4940
|
var ENDPOINT$4, freednsProvider;
|
|
4875
4941
|
var init_freedns = __esmMin((() => {
|
|
4876
4942
|
init_types();
|
|
4943
|
+
init_error_message();
|
|
4877
4944
|
ENDPOINT$4 = "https://freedns.afraid.org/dynamic/update.php";
|
|
4878
4945
|
freednsProvider = {
|
|
4879
4946
|
id: "freedns",
|
|
@@ -4900,7 +4967,7 @@ var init_freedns = __esmMin((() => {
|
|
|
4900
4967
|
if (!response.ok || /^ERROR/i.test(body)) return failed(`freedns answered "${body.slice(0, 160) || `HTTP ${response.status}`}"`);
|
|
4901
4968
|
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
4902
4969
|
} catch (error) {
|
|
4903
|
-
return failed(
|
|
4970
|
+
return failed(errorMessage(error));
|
|
4904
4971
|
}
|
|
4905
4972
|
}
|
|
4906
4973
|
};
|
|
@@ -4919,6 +4986,7 @@ var API$3, gandiProvider;
|
|
|
4919
4986
|
var init_gandi = __esmMin((() => {
|
|
4920
4987
|
init_types();
|
|
4921
4988
|
init_zone();
|
|
4989
|
+
init_error_message();
|
|
4922
4990
|
API$3 = "https://api.gandi.net/v5/livedns/domains";
|
|
4923
4991
|
gandiProvider = {
|
|
4924
4992
|
id: "gandi",
|
|
@@ -4955,7 +5023,7 @@ var init_gandi = __esmMin((() => {
|
|
|
4955
5023
|
const body = await current.json();
|
|
4956
5024
|
if (body.rrset_values?.length === 1 && body.rrset_values[0] === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
4957
5025
|
}
|
|
4958
|
-
|
|
5026
|
+
return await wrote(await context.fetch(url, {
|
|
4959
5027
|
method: "PUT",
|
|
4960
5028
|
headers,
|
|
4961
5029
|
body: JSON.stringify({
|
|
@@ -4963,11 +5031,9 @@ var init_gandi = __esmMin((() => {
|
|
|
4963
5031
|
rrset_ttl: ttl
|
|
4964
5032
|
}),
|
|
4965
5033
|
signal: context.signal
|
|
4966
|
-
});
|
|
4967
|
-
if (!written.ok) return failed(await errorText(written));
|
|
4968
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
5034
|
+
}), record);
|
|
4969
5035
|
} catch (error) {
|
|
4970
|
-
return failed(
|
|
5036
|
+
return failed(errorMessage(error));
|
|
4971
5037
|
}
|
|
4972
5038
|
}
|
|
4973
5039
|
};
|
|
@@ -4982,6 +5048,7 @@ var API$2, godaddyProvider;
|
|
|
4982
5048
|
var init_godaddy = __esmMin((() => {
|
|
4983
5049
|
init_types();
|
|
4984
5050
|
init_zone();
|
|
5051
|
+
init_error_message();
|
|
4985
5052
|
API$2 = "https://api.godaddy.com/v1/domains";
|
|
4986
5053
|
godaddyProvider = {
|
|
4987
5054
|
id: "godaddy",
|
|
@@ -5030,7 +5097,7 @@ var init_godaddy = __esmMin((() => {
|
|
|
5030
5097
|
const body = await current.json();
|
|
5031
5098
|
if (body.length === 1 && body[0]?.data === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
5032
5099
|
}
|
|
5033
|
-
|
|
5100
|
+
return await wrote(await context.fetch(url, {
|
|
5034
5101
|
method: "PUT",
|
|
5035
5102
|
headers,
|
|
5036
5103
|
body: JSON.stringify([{
|
|
@@ -5038,11 +5105,9 @@ var init_godaddy = __esmMin((() => {
|
|
|
5038
5105
|
ttl
|
|
5039
5106
|
}]),
|
|
5040
5107
|
signal: context.signal
|
|
5041
|
-
});
|
|
5042
|
-
if (!written.ok) return failed(await errorText(written));
|
|
5043
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
5108
|
+
}), record);
|
|
5044
5109
|
} catch (error) {
|
|
5045
|
-
return failed(
|
|
5110
|
+
return failed(errorMessage(error));
|
|
5046
5111
|
}
|
|
5047
5112
|
}
|
|
5048
5113
|
};
|
|
@@ -5053,6 +5118,7 @@ var ENDPOINT$3, henetProvider;
|
|
|
5053
5118
|
var init_henet = __esmMin((() => {
|
|
5054
5119
|
init_dyndns2();
|
|
5055
5120
|
init_types();
|
|
5121
|
+
init_error_message();
|
|
5056
5122
|
ENDPOINT$3 = "https://dyn.dns.he.net/nic/update";
|
|
5057
5123
|
henetProvider = {
|
|
5058
5124
|
id: "henet",
|
|
@@ -5078,7 +5144,7 @@ var init_henet = __esmMin((() => {
|
|
|
5078
5144
|
const response = await context.fetch(url, { signal: context.signal });
|
|
5079
5145
|
return describeDyndnsResponse(response, await response.text(), "hurricane electric");
|
|
5080
5146
|
} catch (error) {
|
|
5081
|
-
return failed(
|
|
5147
|
+
return failed(errorMessage(error));
|
|
5082
5148
|
}
|
|
5083
5149
|
}
|
|
5084
5150
|
};
|
|
@@ -5092,6 +5158,7 @@ var ENDPOINT$2, namecheapProvider;
|
|
|
5092
5158
|
var init_namecheap = __esmMin((() => {
|
|
5093
5159
|
init_types();
|
|
5094
5160
|
init_zone();
|
|
5161
|
+
init_error_message();
|
|
5095
5162
|
ENDPOINT$2 = "https://dynamicdns.park-your-domain.com/update";
|
|
5096
5163
|
namecheapProvider = {
|
|
5097
5164
|
id: "namecheap",
|
|
@@ -5126,7 +5193,7 @@ var init_namecheap = __esmMin((() => {
|
|
|
5126
5193
|
if (ip === null) return failed(`namecheap answered without an IP: ${body.replace(/\s+/g, " ").slice(0, 160)}`);
|
|
5127
5194
|
return succeeded(`${host === "@" ? zone : `${host}.${zone}`} now ${ip}`, ip !== record.ip);
|
|
5128
5195
|
} catch (error) {
|
|
5129
|
-
return failed(
|
|
5196
|
+
return failed(errorMessage(error));
|
|
5130
5197
|
}
|
|
5131
5198
|
}
|
|
5132
5199
|
};
|
|
@@ -5218,7 +5285,7 @@ async function call(command, params, context) {
|
|
|
5218
5285
|
body
|
|
5219
5286
|
};
|
|
5220
5287
|
} catch (error) {
|
|
5221
|
-
return
|
|
5288
|
+
return errorMessage(error);
|
|
5222
5289
|
}
|
|
5223
5290
|
}
|
|
5224
5291
|
/** The registered domain split into the `SLD`/`TLD` pair the API asks for. */
|
|
@@ -5265,6 +5332,7 @@ var ENDPOINT$1, RECORD_LIMIT, CHALLENGE_TTL, XML_ENTITIES, namecheapApiProvider;
|
|
|
5265
5332
|
var init_namecheap_api = __esmMin((() => {
|
|
5266
5333
|
init_types();
|
|
5267
5334
|
init_zone();
|
|
5335
|
+
init_error_message();
|
|
5268
5336
|
ENDPOINT$1 = "https://api.namecheap.com/xml.response";
|
|
5269
5337
|
RECORD_LIMIT = 150;
|
|
5270
5338
|
CHALLENGE_TTL = 60;
|
|
@@ -5379,6 +5447,7 @@ var ENDPOINT, DEFAULT_AGENT, noipProvider;
|
|
|
5379
5447
|
var init_noip = __esmMin((() => {
|
|
5380
5448
|
init_dyndns2();
|
|
5381
5449
|
init_types();
|
|
5450
|
+
init_error_message();
|
|
5382
5451
|
ENDPOINT = "https://dynupdate.no-ip.com/nic/update";
|
|
5383
5452
|
DEFAULT_AGENT = "home-hosted/1.0 (+https://github.com/NamesMT/home-hosted)";
|
|
5384
5453
|
noipProvider = {
|
|
@@ -5427,7 +5496,7 @@ var init_noip = __esmMin((() => {
|
|
|
5427
5496
|
});
|
|
5428
5497
|
return describeDyndnsResponse(response, await response.text(), "no-ip");
|
|
5429
5498
|
} catch (error) {
|
|
5430
|
-
return failed(
|
|
5499
|
+
return failed(errorMessage(error));
|
|
5431
5500
|
}
|
|
5432
5501
|
}
|
|
5433
5502
|
};
|
|
@@ -5462,7 +5531,7 @@ async function post(path, context, body) {
|
|
|
5462
5531
|
} catch (error) {
|
|
5463
5532
|
return {
|
|
5464
5533
|
data: null,
|
|
5465
|
-
error:
|
|
5534
|
+
error: errorMessage(error)
|
|
5466
5535
|
};
|
|
5467
5536
|
}
|
|
5468
5537
|
}
|
|
@@ -5470,6 +5539,7 @@ var API$1, porkbunProvider;
|
|
|
5470
5539
|
var init_porkbun = __esmMin((() => {
|
|
5471
5540
|
init_types();
|
|
5472
5541
|
init_zone();
|
|
5542
|
+
init_error_message();
|
|
5473
5543
|
API$1 = "https://api.porkbun.com/api/json/v3";
|
|
5474
5544
|
porkbunProvider = {
|
|
5475
5545
|
id: "porkbun",
|
|
@@ -5518,6 +5588,7 @@ var API, PAGE_SIZE, spaceshipProvider;
|
|
|
5518
5588
|
var init_spaceship = __esmMin((() => {
|
|
5519
5589
|
init_types();
|
|
5520
5590
|
init_zone();
|
|
5591
|
+
init_error_message();
|
|
5521
5592
|
API = "https://spaceship.dev/api/v1/dns/records";
|
|
5522
5593
|
PAGE_SIZE = 500;
|
|
5523
5594
|
spaceshipProvider = {
|
|
@@ -5562,7 +5633,7 @@ var init_spaceship = __esmMin((() => {
|
|
|
5562
5633
|
if (found !== void 0 || items.length < PAGE_SIZE) break;
|
|
5563
5634
|
}
|
|
5564
5635
|
if (found?.address === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
5565
|
-
|
|
5636
|
+
return await wrote(await context.fetch(`${API}/${encodeURIComponent(zone)}`, {
|
|
5566
5637
|
method: "PUT",
|
|
5567
5638
|
headers,
|
|
5568
5639
|
body: JSON.stringify({
|
|
@@ -5575,11 +5646,9 @@ var init_spaceship = __esmMin((() => {
|
|
|
5575
5646
|
}]
|
|
5576
5647
|
}),
|
|
5577
5648
|
signal: context.signal
|
|
5578
|
-
});
|
|
5579
|
-
if (!written.ok) return failed(await errorText(written));
|
|
5580
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
5649
|
+
}), record);
|
|
5581
5650
|
} catch (error) {
|
|
5582
|
-
return failed(
|
|
5651
|
+
return failed(errorMessage(error));
|
|
5583
5652
|
}
|
|
5584
5653
|
}
|
|
5585
5654
|
};
|
|
@@ -6560,7 +6629,7 @@ function describeTelegramError(error) {
|
|
|
6560
6629
|
const description = candidate.description ?? candidate.message;
|
|
6561
6630
|
if (description) return `${description}${candidate.error_code === void 0 ? "" : ` (${candidate.error_code})`}${candidate.parameters?.retry_after === void 0 ? "" : `, retry in ${candidate.parameters.retry_after}s`}`;
|
|
6562
6631
|
}
|
|
6563
|
-
return
|
|
6632
|
+
return errorMessage(error);
|
|
6564
6633
|
}
|
|
6565
6634
|
async function sendTelegramMessage(token, chatId, html) {
|
|
6566
6635
|
try {
|
|
@@ -6628,6 +6697,7 @@ async function listTelegramChats(token) {
|
|
|
6628
6697
|
}
|
|
6629
6698
|
var bots;
|
|
6630
6699
|
var init_telegram = __esmMin((() => {
|
|
6700
|
+
init_error_message();
|
|
6631
6701
|
bots = /* @__PURE__ */ new Map();
|
|
6632
6702
|
}));
|
|
6633
6703
|
//#endregion
|
|
@@ -7786,15 +7856,15 @@ function acmeIssuers(acme, challenge = {}) {
|
|
|
7786
7856
|
}, { module: "internal" }];
|
|
7787
7857
|
}
|
|
7788
7858
|
/**
|
|
7789
|
-
* The page
|
|
7790
|
-
*
|
|
7791
|
-
* that
|
|
7859
|
+
* The one page shell both visitor-facing pages use. They are the same document with
|
|
7860
|
+
* different words, and the stylesheet has to stay identical — a page the engine serves
|
|
7861
|
+
* on a port a person reaches by hand is a page that has to look like this panel.
|
|
7792
7862
|
*/
|
|
7793
|
-
function
|
|
7863
|
+
function htmlPage(title, heading, body) {
|
|
7794
7864
|
return `<!doctype html>
|
|
7795
7865
|
<html lang="en">
|
|
7796
7866
|
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7797
|
-
<title
|
|
7867
|
+
<title>${title}</title>
|
|
7798
7868
|
<style>
|
|
7799
7869
|
:root { color-scheme: light dark }
|
|
7800
7870
|
body { margin: 0; min-height: 100dvh; display: grid; place-items: center; font: 15px/1.6 system-ui, sans-serif }
|
|
@@ -7805,13 +7875,21 @@ function notConfiguredPage() {
|
|
|
7805
7875
|
footer { color: #888; font-size: .85em }
|
|
7806
7876
|
</style></head>
|
|
7807
7877
|
<body><main>
|
|
7808
|
-
<h1
|
|
7809
|
-
|
|
7878
|
+
<h1>${heading}</h1>
|
|
7879
|
+
${body}
|
|
7880
|
+
</main></body></html>`;
|
|
7881
|
+
}
|
|
7882
|
+
/**
|
|
7883
|
+
* The page a visitor gets when the hostname they reached is not routed at all. It is
|
|
7884
|
+
* the answer on both ports, so a request never lands on a redirect into a handshake
|
|
7885
|
+
* that cannot finish.
|
|
7886
|
+
*/
|
|
7887
|
+
function notConfiguredPage() {
|
|
7888
|
+
return htmlPage("No route for this hostname", "This route is not configured for any target yet", ` <p>No route in <strong>home-hosted</strong>'s reverse proxy claims the hostname
|
|
7810
7889
|
<code>{http.request.host}</code>, so there is nothing here to serve.</p>
|
|
7811
7890
|
<p>Add a route for it under <strong>Others → Reverse Proxy</strong>, or check that you
|
|
7812
7891
|
reached the hostname you meant to.</p>
|
|
7813
|
-
<footer>home-hosted reverse proxy</footer
|
|
7814
|
-
</main></body></html>`;
|
|
7892
|
+
<footer>home-hosted reverse proxy</footer>`);
|
|
7815
7893
|
}
|
|
7816
7894
|
/** The catch-all: it answers last, so every configured route wins over it. */
|
|
7817
7895
|
function notConfiguredRoute() {
|
|
@@ -7824,30 +7902,14 @@ function notConfiguredRoute() {
|
|
|
7824
7902
|
}
|
|
7825
7903
|
/** The page a visitor gets when a name has no certificate yet and HTTPS cannot work. */
|
|
7826
7904
|
function noticePage(host, httpPort, httpsPort) {
|
|
7827
|
-
return
|
|
7828
|
-
<html lang="en">
|
|
7829
|
-
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7830
|
-
<title>Certificate not ready — ${host}</title>
|
|
7831
|
-
<style>
|
|
7832
|
-
:root { color-scheme: light dark }
|
|
7833
|
-
body { margin: 0; min-height: 100dvh; display: grid; place-items: center; font: 15px/1.6 system-ui, sans-serif }
|
|
7834
|
-
main { max-width: 34rem; padding: 2rem }
|
|
7835
|
-
h1 { font-size: 1.15rem; margin: 0 0 .75rem }
|
|
7836
|
-
p { margin: 0 0 .75rem }
|
|
7837
|
-
code { font-family: ui-monospace, monospace; font-size: .9em }
|
|
7838
|
-
footer { color: #888; font-size: .85em }
|
|
7839
|
-
</style></head>
|
|
7840
|
-
<body><main>
|
|
7841
|
-
<h1>No certificate yet for ${host}</h1>
|
|
7842
|
-
<p>This hostname is routed, but the certificate authority has not issued a certificate
|
|
7905
|
+
return htmlPage(`Certificate not ready — ${host}`, `No certificate yet for ${host}`, ` <p>This hostname is routed, but the certificate authority has not issued a certificate
|
|
7843
7906
|
for it yet, so HTTPS cannot complete a handshake. The engine keeps trying in the
|
|
7844
7907
|
background and will serve the name as soon as it succeeds.</p>
|
|
7845
7908
|
<p>The usual cause is that the CA cannot reach this machine: with the proxy on
|
|
7846
7909
|
<code>${httpPort}</code>/<code>${httpsPort}</code>, forward <code>80 → ${httpPort}</code>
|
|
7847
7910
|
and <code>443 → ${httpsPort}</code> on your router. Otherwise <code>http://${host}</code>
|
|
7848
7911
|
will keep landing here.</p>
|
|
7849
|
-
<footer>Details, and the engine's own reason, are in the panel: Others → Reverse Proxy.</footer
|
|
7850
|
-
</main></body></html>`;
|
|
7912
|
+
<footer>Details, and the engine's own reason, are in the panel: Others → Reverse Proxy.</footer>`);
|
|
7851
7913
|
}
|
|
7852
7914
|
/**
|
|
7853
7915
|
* The same match, minus the ACME challenge path: the CA has to be able to read it on
|
|
@@ -8033,7 +8095,7 @@ function validatePair(certificate, privateKey) {
|
|
|
8033
8095
|
} catch (error) {
|
|
8034
8096
|
return {
|
|
8035
8097
|
ok: false,
|
|
8036
|
-
error: `certificate is not a valid PEM: ${
|
|
8098
|
+
error: `certificate is not a valid PEM: ${errorMessage(error)}`
|
|
8037
8099
|
};
|
|
8038
8100
|
}
|
|
8039
8101
|
try {
|
|
@@ -8044,7 +8106,7 @@ function validatePair(certificate, privateKey) {
|
|
|
8044
8106
|
} catch (error) {
|
|
8045
8107
|
return {
|
|
8046
8108
|
ok: false,
|
|
8047
|
-
error: `private key is not a valid PEM: ${
|
|
8109
|
+
error: `private key is not a valid PEM: ${errorMessage(error)}`
|
|
8048
8110
|
};
|
|
8049
8111
|
}
|
|
8050
8112
|
const invalid = validityError(x509);
|
|
@@ -8058,6 +8120,7 @@ var TlsStore;
|
|
|
8058
8120
|
var init_tls$1 = __esmMin((() => {
|
|
8059
8121
|
init_atomic();
|
|
8060
8122
|
init_logger();
|
|
8123
|
+
init_error_message();
|
|
8061
8124
|
TlsStore = class {
|
|
8062
8125
|
dir;
|
|
8063
8126
|
name;
|
|
@@ -8184,7 +8247,7 @@ var init_tls$1 = __esmMin((() => {
|
|
|
8184
8247
|
} catch (error) {
|
|
8185
8248
|
return {
|
|
8186
8249
|
...base,
|
|
8187
|
-
error: `invalid certificate: ${
|
|
8250
|
+
error: `invalid certificate: ${errorMessage(error)}`
|
|
8188
8251
|
};
|
|
8189
8252
|
}
|
|
8190
8253
|
}
|
|
@@ -8347,6 +8410,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8347
8410
|
init_proxy$2();
|
|
8348
8411
|
init_proxy_config();
|
|
8349
8412
|
init_tls$1();
|
|
8413
|
+
init_error_message();
|
|
8350
8414
|
init_proxy_form();
|
|
8351
8415
|
PROXY_ID = "proxy";
|
|
8352
8416
|
ADMIN_TIMEOUT_MS = 5e3;
|
|
@@ -8388,8 +8452,6 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8388
8452
|
failureCache = null;
|
|
8389
8453
|
/** The installed binary's modules, read once per process; an install clears it. */
|
|
8390
8454
|
moduleCache = null;
|
|
8391
|
-
/** A legacy `engine.json` is reported once, not on every state frame. */
|
|
8392
|
-
legacyChecksumWarned = false;
|
|
8393
8455
|
/** Why the engine's nanny could not be spawned, cleared on the next start. */
|
|
8394
8456
|
nannyError = null;
|
|
8395
8457
|
certCache = null;
|
|
@@ -8678,7 +8740,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8678
8740
|
} catch (error) {
|
|
8679
8741
|
return {
|
|
8680
8742
|
...base,
|
|
8681
|
-
error: `unreadable certificate: ${
|
|
8743
|
+
error: `unreadable certificate: ${errorMessage(error)}`
|
|
8682
8744
|
};
|
|
8683
8745
|
}
|
|
8684
8746
|
});
|
|
@@ -8890,14 +8952,14 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8890
8952
|
if (this.live()) {
|
|
8891
8953
|
logger.info(`proxy: reattached to the running ${this.config.engine} engine`);
|
|
8892
8954
|
await this.apply().catch((error) => {
|
|
8893
|
-
logger.warn(`proxy: could not apply the current configuration to the reattached engine: ${
|
|
8955
|
+
logger.warn(`proxy: could not apply the current configuration to the reattached engine: ${errorMessage(error)}`);
|
|
8894
8956
|
});
|
|
8895
8957
|
return;
|
|
8896
8958
|
}
|
|
8897
8959
|
try {
|
|
8898
8960
|
await this.start();
|
|
8899
8961
|
} catch (error) {
|
|
8900
|
-
this.lastError =
|
|
8962
|
+
this.lastError = errorMessage(error);
|
|
8901
8963
|
logger.error(`proxy: could not start: ${this.lastError}`);
|
|
8902
8964
|
}
|
|
8903
8965
|
}
|
|
@@ -9125,7 +9187,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9125
9187
|
*/
|
|
9126
9188
|
async postConfiguration(rendered) {
|
|
9127
9189
|
const result = await this.request("POST", "/load", rendered).catch((error) => {
|
|
9128
|
-
this.lastError = `the engine did not accept the configuration: ${
|
|
9190
|
+
this.lastError = `the engine did not accept the configuration: ${errorMessage(error)}`;
|
|
9129
9191
|
throw new DetailedError(this.lastError, {
|
|
9130
9192
|
statusCode: 502,
|
|
9131
9193
|
code: "ENGINE_UNREACHABLE"
|
|
@@ -9193,7 +9255,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9193
9255
|
this.certificateSignature = signature;
|
|
9194
9256
|
if (first) return;
|
|
9195
9257
|
await this.apply().catch((error) => {
|
|
9196
|
-
logger.warn(`proxy: could not re-apply after a certificate change: ${
|
|
9258
|
+
logger.warn(`proxy: could not re-apply after a certificate change: ${errorMessage(error)}`);
|
|
9197
9259
|
});
|
|
9198
9260
|
}
|
|
9199
9261
|
/**
|
|
@@ -9400,15 +9462,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9400
9462
|
try {
|
|
9401
9463
|
const parsed = JSON.parse(fs.readFileSync(path.join(this.options.binDir, "engine.json"), "utf8"));
|
|
9402
9464
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
9403
|
-
const
|
|
9404
|
-
if ("sha256" in record) {
|
|
9405
|
-
delete record.sha256;
|
|
9406
|
-
if (!this.legacyChecksumWarned) {
|
|
9407
|
-
this.legacyChecksumWarned = true;
|
|
9408
|
-
logger.warn("proxy: dropped the recorded engine checksum; nothing verifies it");
|
|
9409
|
-
}
|
|
9410
|
-
}
|
|
9411
|
-
const parsedRecord = engineRecordSchema(record);
|
|
9465
|
+
const parsedRecord = engineRecordSchema(parsed);
|
|
9412
9466
|
return parsedRecord instanceof type.errors ? null : parsedRecord;
|
|
9413
9467
|
} catch {
|
|
9414
9468
|
return null;
|
|
@@ -9959,7 +10013,7 @@ function createProxyRoute(deps) {
|
|
|
9959
10013
|
if (proxy().status().state === "running") await proxy().apply();
|
|
9960
10014
|
else await proxy().start();
|
|
9961
10015
|
} catch (error) {
|
|
9962
|
-
proxy().recordError(
|
|
10016
|
+
proxy().recordError(errorMessage(error));
|
|
9963
10017
|
throw error;
|
|
9964
10018
|
}
|
|
9965
10019
|
};
|
|
@@ -10118,6 +10172,7 @@ var init_proxy = __esmMin((() => {
|
|
|
10118
10172
|
init_exposure();
|
|
10119
10173
|
init_proxy$1();
|
|
10120
10174
|
init_contracts();
|
|
10175
|
+
init_error_message();
|
|
10121
10176
|
certificateParam = type({ id: "/^[a-z0-9][a-z0-9_-]*$/" });
|
|
10122
10177
|
}));
|
|
10123
10178
|
//#endregion
|
|
@@ -11023,237 +11078,26 @@ var init_app = __esmMin((() => {
|
|
|
11023
11078
|
}));
|
|
11024
11079
|
//#endregion
|
|
11025
11080
|
//#region src/config/layout.ts
|
|
11026
|
-
var layout_exports = /* @__PURE__ */ __exportAll({
|
|
11027
|
-
ensureLayout: () => ensureLayout,
|
|
11028
|
-
migrateLayout: () => migrateLayout
|
|
11029
|
-
});
|
|
11030
|
-
function readJson(file) {
|
|
11031
|
-
try {
|
|
11032
|
-
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
11033
|
-
return isRecord(parsed) ? parsed : null;
|
|
11034
|
-
} catch {
|
|
11035
|
-
return null;
|
|
11036
|
-
}
|
|
11037
|
-
}
|
|
11081
|
+
var layout_exports = /* @__PURE__ */ __exportAll({ ensureLayout: () => ensureLayout });
|
|
11038
11082
|
/**
|
|
11039
|
-
*
|
|
11083
|
+
* Refuses a state directory this release cannot use, before any command resolves a
|
|
11084
|
+
* path into it.
|
|
11040
11085
|
*
|
|
11041
|
-
*
|
|
11042
|
-
*
|
|
11043
|
-
*
|
|
11044
|
-
*
|
|
11045
|
-
*
|
|
11046
|
-
*/
|
|
11047
|
-
function readLegacy(file) {
|
|
11048
|
-
if (!fs.existsSync(file)) return {
|
|
11049
|
-
parsed: null,
|
|
11050
|
-
unreadable: false
|
|
11051
|
-
};
|
|
11052
|
-
const parsed = readJson(file);
|
|
11053
|
-
return {
|
|
11054
|
-
parsed,
|
|
11055
|
-
unreadable: parsed === null
|
|
11056
|
-
};
|
|
11057
|
-
}
|
|
11058
|
-
function writeJson(file, value, mode) {
|
|
11059
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
11060
|
-
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, mode === void 0 ? {} : { mode });
|
|
11061
|
-
}
|
|
11062
|
-
/** `renameSync` across devices fails with EXDEV; fall back to a copy. */
|
|
11063
|
-
function move(source, destination, warnings) {
|
|
11064
|
-
if (!fs.existsSync(source)) return false;
|
|
11065
|
-
if (fs.existsSync(destination)) {
|
|
11066
|
-
warnings.push(`${destination} already exists — left ${source} alone`);
|
|
11067
|
-
return false;
|
|
11068
|
-
}
|
|
11069
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
11070
|
-
try {
|
|
11071
|
-
fs.renameSync(source, destination);
|
|
11072
|
-
} catch {
|
|
11073
|
-
fs.cpSync(source, destination, {
|
|
11074
|
-
recursive: true,
|
|
11075
|
-
force: true
|
|
11076
|
-
});
|
|
11077
|
-
fs.rmSync(source, {
|
|
11078
|
-
recursive: true,
|
|
11079
|
-
force: true
|
|
11080
|
-
});
|
|
11081
|
-
}
|
|
11082
|
-
return true;
|
|
11083
|
-
}
|
|
11084
|
-
function metaOf(raw) {
|
|
11085
|
-
const meta = raw?.meta;
|
|
11086
|
-
if (typeof meta !== "object" || meta === null) return void 0;
|
|
11087
|
-
return meta;
|
|
11088
|
-
}
|
|
11089
|
-
/** The old config split three ways: global settings, workspace settings, servers. */
|
|
11090
|
-
function splitConfig(raw) {
|
|
11091
|
-
const meta = metaOf(raw);
|
|
11092
|
-
const unknown = {};
|
|
11093
|
-
for (const [key, value] of Object.entries(raw)) if (!KNOWN_TOP_LEVEL.has(key)) unknown[key] = value;
|
|
11094
|
-
return {
|
|
11095
|
-
global: {
|
|
11096
|
-
$schema: "./settings.schema.json",
|
|
11097
|
-
...meta === void 0 ? {} : { meta },
|
|
11098
|
-
control: raw.control ?? {},
|
|
11099
|
-
host: raw.host ?? {},
|
|
11100
|
-
backups: raw.backups ?? {},
|
|
11101
|
-
...unknown
|
|
11102
|
-
},
|
|
11103
|
-
settings: {
|
|
11104
|
-
$schema: "./settings.schema.json",
|
|
11105
|
-
...meta === void 0 ? {} : { meta },
|
|
11106
|
-
defaults: raw.defaults ?? {},
|
|
11107
|
-
logs: raw.logs ?? {},
|
|
11108
|
-
notifications: raw.notifications ?? {},
|
|
11109
|
-
ddns: raw.ddns ?? {}
|
|
11110
|
-
},
|
|
11111
|
-
servers: {
|
|
11112
|
-
$schema: "./servers.config.schema.json",
|
|
11113
|
-
...meta === void 0 ? {} : { meta },
|
|
11114
|
-
servers: Array.isArray(raw.servers) ? raw.servers : []
|
|
11115
|
-
}
|
|
11116
|
-
};
|
|
11117
|
-
}
|
|
11118
|
-
/** The old secrets file split global (password, token) vs. workspace (Telegram, DDNS). */
|
|
11119
|
-
function splitSecrets(raw) {
|
|
11120
|
-
return {
|
|
11121
|
-
global: {
|
|
11122
|
-
version: 3,
|
|
11123
|
-
password: raw?.password ?? null,
|
|
11124
|
-
apiToken: raw?.apiToken ?? null
|
|
11125
|
-
},
|
|
11126
|
-
workspace: {
|
|
11127
|
-
version: 3,
|
|
11128
|
-
telegram: raw?.telegram ?? null,
|
|
11129
|
-
ddns: raw?.ddns ?? {},
|
|
11130
|
-
ddnsKdf: raw?.ddnsKdf ?? null
|
|
11131
|
-
}
|
|
11132
|
-
};
|
|
11133
|
-
}
|
|
11134
|
-
/**
|
|
11135
|
-
* Moves the panel's console log apart from the per-server logs: both used to
|
|
11136
|
-
* live in `<root>/.logs`, and only the console log is global now.
|
|
11137
|
-
*/
|
|
11138
|
-
function splitLogsDir(warnings) {
|
|
11139
|
-
if (!fs.existsSync(legacy.logsDir)) return;
|
|
11140
|
-
const workspaceLogs = path.join(hhDir, "default", ".logs");
|
|
11141
|
-
for (const name of fs.readdirSync(legacy.logsDir)) {
|
|
11142
|
-
const destination = name === "home-hosted.log" || name.startsWith("home-hosted.log.") ? path.join(hhDir, ".logs", name) : path.join(workspaceLogs, name);
|
|
11143
|
-
move(path.join(legacy.logsDir, name), destination, warnings);
|
|
11144
|
-
}
|
|
11145
|
-
try {
|
|
11146
|
-
if (fs.readdirSync(legacy.logsDir).length === 0) fs.rmdirSync(legacy.logsDir);
|
|
11147
|
-
else warnings.push(`${legacy.logsDir} still holds files that could not be moved — left it in place`);
|
|
11148
|
-
} catch {}
|
|
11149
|
-
}
|
|
11150
|
-
function migrateLayout() {
|
|
11151
|
-
if (!hasLegacyLayout()) return EMPTY;
|
|
11152
|
-
const moved = [];
|
|
11153
|
-
const warnings = [];
|
|
11154
|
-
fs.mkdirSync(hhDir, { recursive: true });
|
|
11155
|
-
const legacyConfig = readLegacy(legacy.configPath);
|
|
11156
|
-
let configSplit = false;
|
|
11157
|
-
if (legacyConfig.unreadable) warnings.push(`${legacy.configPath} could not be read — it was NOT migrated and is still there; fix or remove it, then move the file in by hand`);
|
|
11158
|
-
if (legacyConfig.parsed !== null) {
|
|
11159
|
-
const split = splitConfig(legacyConfig.parsed);
|
|
11160
|
-
const destinations = [
|
|
11161
|
-
path.join(hhDir, "settings.json"),
|
|
11162
|
-
path.join(hhDir, "default", "settings.json"),
|
|
11163
|
-
path.join(hhDir, "default", "servers.config.json")
|
|
11164
|
-
];
|
|
11165
|
-
const taken = destinations.filter((file) => fs.existsSync(file));
|
|
11166
|
-
if (taken.length > 0) warnings.push(`${legacy.configPath} could not be split — ${taken.map((file) => path.relative(hhDir, file)).join(", ")} already exist(s); left it in place`);
|
|
11167
|
-
else {
|
|
11168
|
-
writeJson(destinations[0], split.global);
|
|
11169
|
-
fs.mkdirSync(path.join(hhDir, "default"), { recursive: true });
|
|
11170
|
-
writeJson(destinations[1], split.settings);
|
|
11171
|
-
writeJson(destinations[2], split.servers);
|
|
11172
|
-
moved.push("servers.config.json → settings.json + default/settings.json + default/servers.config.json");
|
|
11173
|
-
configSplit = true;
|
|
11174
|
-
}
|
|
11175
|
-
}
|
|
11176
|
-
const legacySecrets = readLegacy(legacy.secretsPath);
|
|
11177
|
-
let secretsSplit = false;
|
|
11178
|
-
if (legacySecrets.unreadable) warnings.push(`${legacy.secretsPath} could not be read — it was NOT migrated and is still there; a panel without it starts on the default password`);
|
|
11179
|
-
if (legacySecrets.parsed !== null) {
|
|
11180
|
-
const split = splitSecrets(legacySecrets.parsed);
|
|
11181
|
-
const destinations = [path.join(hhDir, ".control-secrets.json"), path.join(hhDir, "default", ".secrets.json")];
|
|
11182
|
-
const taken = destinations.filter((file) => fs.existsSync(file));
|
|
11183
|
-
if (taken.length > 0) warnings.push(`${legacy.secretsPath} could not be split — ${taken.map((file) => path.relative(hhDir, file)).join(", ")} already exist(s); left it in place`);
|
|
11184
|
-
else {
|
|
11185
|
-
writeJson(destinations[0], split.global, 384);
|
|
11186
|
-
fs.mkdirSync(path.join(hhDir, "default"), { recursive: true });
|
|
11187
|
-
writeJson(destinations[1], split.workspace, 384);
|
|
11188
|
-
moved.push(".control-secrets.json → .hh/.control-secrets.json + .hh/default/.secrets.json");
|
|
11189
|
-
secretsSplit = true;
|
|
11190
|
-
}
|
|
11191
|
-
}
|
|
11192
|
-
if (move(legacy.tlsDir, path.join(hhDir, ".tls"), warnings)) moved.push(".tls/ → .hh/.tls/");
|
|
11193
|
-
if (move(legacy.uiDir, path.join(hhDir, ".ui"), warnings)) moved.push(".ui/ → .hh/.ui/");
|
|
11194
|
-
if (move(legacy.backupsDir, path.join(hhDir, ".backups"), warnings)) moved.push(".backups/ → .hh/.backups/");
|
|
11195
|
-
if (move(legacy.runtimePath, path.join(hhDir, "run.json"), warnings)) moved.push("run.json → .hh/run.json");
|
|
11196
|
-
if (move(legacy.stateDir, path.join(hhDir, "default", ".state"), warnings)) moved.push(".state/ → .hh/default/.state/");
|
|
11197
|
-
splitLogsDir(warnings);
|
|
11198
|
-
if (configSplit) fs.rmSync(legacy.configPath, { force: true });
|
|
11199
|
-
if (secretsSplit) fs.rmSync(legacy.secretsPath, { force: true });
|
|
11200
|
-
fs.rmSync(legacy.configSchemaPath, { force: true });
|
|
11201
|
-
if (!fs.existsSync(workspacesPath)) {
|
|
11202
|
-
writeJson(workspacesPath, {
|
|
11203
|
-
$schema: "./workspaces.schema.json",
|
|
11204
|
-
workspaces: [{
|
|
11205
|
-
id: "default",
|
|
11206
|
-
label: "Default"
|
|
11207
|
-
}]
|
|
11208
|
-
});
|
|
11209
|
-
moved.push("workspaces.json created with the `default` workspace");
|
|
11210
|
-
} else warnings.push(`${workspacesPath} already exists — left the workspace registry alone`);
|
|
11211
|
-
writeJson(path.join(hhDir, ".layout-migration.json"), {
|
|
11212
|
-
version: 1,
|
|
11213
|
-
at: Date.now(),
|
|
11214
|
-
moved
|
|
11215
|
-
});
|
|
11216
|
-
return {
|
|
11217
|
-
migrated: true,
|
|
11218
|
-
moved,
|
|
11219
|
-
warnings
|
|
11220
|
-
};
|
|
11221
|
-
}
|
|
11222
|
-
/**
|
|
11223
|
-
* Runs the relocation and reports it through the logger. Every CLI entry point
|
|
11224
|
-
* calls this before it resolves a state path, so an existing instance keeps
|
|
11225
|
-
* working after the upgrade without anyone having to run a command first.
|
|
11086
|
+
* Through 0.7.x this relocated a pre-workspace `$HHOSTED_HOME` automatically. 0.8.0
|
|
11087
|
+
* dropped the mover, so an instance that never crossed `.hh` is stopped instead of
|
|
11088
|
+
* being read as an empty one — its `servers.config.json` would otherwise sit unread
|
|
11089
|
+
* beside a panel supervising nothing, with no error anywhere. The migration itself is
|
|
11090
|
+
* unchanged and still shipped: the 0.7 line performs it on its first run.
|
|
11226
11091
|
*/
|
|
11227
11092
|
function ensureLayout() {
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
}
|
|
11233
|
-
return result;
|
|
11093
|
+
if (!hasLegacyLayout()) return;
|
|
11094
|
+
const found = [legacy.configPath, legacy.secretsPath].filter((file) => fs.existsSync(file));
|
|
11095
|
+
throw new Error(`${found.join(", ")} belongs to home-hosted 0.6.x, which ${appVersion()} no longer relocates.\n Install the latest 0.7.x once — it moves that state into .hh/ on its first run — then start this release.
|
|
11096
|
+
Nothing has been changed or deleted.`);
|
|
11234
11097
|
}
|
|
11235
|
-
var EMPTY, KNOWN_TOP_LEVEL;
|
|
11236
11098
|
var init_layout = __esmMin((() => {
|
|
11237
|
-
init_logger();
|
|
11238
11099
|
init_paths();
|
|
11239
|
-
|
|
11240
|
-
EMPTY = {
|
|
11241
|
-
migrated: false,
|
|
11242
|
-
moved: [],
|
|
11243
|
-
warnings: []
|
|
11244
|
-
};
|
|
11245
|
-
KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
11246
|
-
"$schema",
|
|
11247
|
-
"meta",
|
|
11248
|
-
"control",
|
|
11249
|
-
"defaults",
|
|
11250
|
-
"logs",
|
|
11251
|
-
"notifications",
|
|
11252
|
-
"host",
|
|
11253
|
-
"backups",
|
|
11254
|
-
"ddns",
|
|
11255
|
-
"servers"
|
|
11256
|
-
]);
|
|
11100
|
+
init_version();
|
|
11257
11101
|
}));
|
|
11258
11102
|
//#endregion
|
|
11259
11103
|
//#region src/helpers/daemon.ts
|
|
@@ -11585,7 +11429,7 @@ function loadEnvFile(file) {
|
|
|
11585
11429
|
return {
|
|
11586
11430
|
env: {},
|
|
11587
11431
|
path: file,
|
|
11588
|
-
error:
|
|
11432
|
+
error: errorMessage(error)
|
|
11589
11433
|
};
|
|
11590
11434
|
}
|
|
11591
11435
|
}
|
|
@@ -11605,6 +11449,7 @@ function resolveEnvFilePath(file, cwd) {
|
|
|
11605
11449
|
}
|
|
11606
11450
|
var VARIABLE;
|
|
11607
11451
|
var init_env_file = __esmMin((() => {
|
|
11452
|
+
init_error_message();
|
|
11608
11453
|
VARIABLE = /\$\{([A-Z_]\w*)\}/gi;
|
|
11609
11454
|
}));
|
|
11610
11455
|
//#endregion
|
|
@@ -11865,7 +11710,7 @@ async function probeHttp(host, port, options) {
|
|
|
11865
11710
|
return {
|
|
11866
11711
|
healthy: false,
|
|
11867
11712
|
ms: Date.now() - started,
|
|
11868
|
-
detail: `request failed: ${
|
|
11713
|
+
detail: `request failed: ${errorMessage(error)}`
|
|
11869
11714
|
};
|
|
11870
11715
|
}
|
|
11871
11716
|
}
|
|
@@ -11887,6 +11732,7 @@ async function probeHealth(options) {
|
|
|
11887
11732
|
}
|
|
11888
11733
|
var init_health_check = __esmMin((() => {
|
|
11889
11734
|
init_port();
|
|
11735
|
+
init_error_message();
|
|
11890
11736
|
}));
|
|
11891
11737
|
//#endregion
|
|
11892
11738
|
//#region src/providers/process.ts
|
|
@@ -12305,6 +12151,7 @@ var TAIL_INTERVAL_MS, LogRelay;
|
|
|
12305
12151
|
var init_log_relay = __esmMin((() => {
|
|
12306
12152
|
init_logger();
|
|
12307
12153
|
init_log_tail();
|
|
12154
|
+
init_error_message();
|
|
12308
12155
|
TAIL_INTERVAL_MS = 250;
|
|
12309
12156
|
LogRelay = class {
|
|
12310
12157
|
options;
|
|
@@ -12365,7 +12212,7 @@ var init_log_relay = __esmMin((() => {
|
|
|
12365
12212
|
try {
|
|
12366
12213
|
this.poll();
|
|
12367
12214
|
} catch (error) {
|
|
12368
|
-
logger.warn(`tail failed: ${
|
|
12215
|
+
logger.warn(`tail failed: ${errorMessage(error)}`);
|
|
12369
12216
|
}
|
|
12370
12217
|
}, this.options.intervalMs ?? TAIL_INTERVAL_MS);
|
|
12371
12218
|
this.timer.unref();
|
|
@@ -12949,21 +12796,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
12949
12796
|
return { kind: "free" };
|
|
12950
12797
|
}
|
|
12951
12798
|
this.log(entry, "system", `port ${port} is held by pid ${foreign.join(", ")} — onPortConflict: kill, stopping the holder`);
|
|
12952
|
-
|
|
12953
|
-
if (forced.length > 0) this.log(entry, "system", `pid ${forced.join(", ")} ignored SIGTERM and was killed`);
|
|
12954
|
-
await delay(PORT_RELEASE_RECHECK_MS);
|
|
12955
|
-
if (await freeOnAll()) {
|
|
12956
|
-
entry.portState = "free";
|
|
12957
|
-
return { kind: "free" };
|
|
12958
|
-
}
|
|
12959
|
-
entry.status = "conflict";
|
|
12960
|
-
entry.lastError = `port ${port} is still in use after killing pid ${foreign.join(", ")}`;
|
|
12961
|
-
this.log(entry, "system", entry.lastError);
|
|
12962
|
-
this.publishServer(entry);
|
|
12963
|
-
return {
|
|
12964
|
-
kind: "blocked",
|
|
12965
|
-
error: entry.lastError
|
|
12966
|
-
};
|
|
12799
|
+
return await this.stopPortHolders(entry, foreign, port, "killing", freeOnAll);
|
|
12967
12800
|
}
|
|
12968
12801
|
if (own !== null && entry.config.onPortConflict === "follow") return {
|
|
12969
12802
|
kind: "adopt",
|
|
@@ -12971,21 +12804,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
12971
12804
|
};
|
|
12972
12805
|
if (own !== null && entry.config.onPortConflict === "reclaim") {
|
|
12973
12806
|
this.log(entry, "system", `port ${port} is held by pid ${own}, a detached restart of this entry — replacing it with a supervised process`);
|
|
12974
|
-
|
|
12975
|
-
if (forced.length > 0) this.log(entry, "system", `pid ${forced.join(", ")} ignored SIGTERM and was killed`);
|
|
12976
|
-
await delay(PORT_RELEASE_RECHECK_MS);
|
|
12977
|
-
if (await freeOnAll()) {
|
|
12978
|
-
entry.portState = "free";
|
|
12979
|
-
return { kind: "free" };
|
|
12980
|
-
}
|
|
12981
|
-
entry.status = "conflict";
|
|
12982
|
-
entry.lastError = `port ${port} is still in use after replacing pid ${own}`;
|
|
12983
|
-
this.log(entry, "system", entry.lastError);
|
|
12984
|
-
this.publishServer(entry);
|
|
12985
|
-
return {
|
|
12986
|
-
kind: "blocked",
|
|
12987
|
-
error: entry.lastError
|
|
12988
|
-
};
|
|
12807
|
+
return await this.stopPortHolders(entry, [own], port, "replacing", freeOnAll);
|
|
12989
12808
|
}
|
|
12990
12809
|
const hint = own !== null ? ` — pid ${own} is a detached restart of this entry: set onPortConflict to "follow" to adopt it, "reclaim" to replace it with a supervised process, or "kill" to stop whatever holds the port` : "";
|
|
12991
12810
|
if (entry.config.onPortConflict !== "warn") {
|
|
@@ -13011,6 +12830,31 @@ var init_supervisor = __esmMin((() => {
|
|
|
13011
12830
|
* persistent entry whose state file was lost would show its history but never
|
|
13012
12831
|
* another live line.
|
|
13013
12832
|
*/
|
|
12833
|
+
/**
|
|
12834
|
+
* Stops the listed port holders and reports whether the port came free.
|
|
12835
|
+
*
|
|
12836
|
+
* `kill` and `reclaim` are the same sequence over a different pid list — the foreign
|
|
12837
|
+
* holder versus this entry's own detached successor — and they have to stay the same:
|
|
12838
|
+
* a stop that does not re-probe, or that reports the port free without waiting, spawns
|
|
12839
|
+
* onto a listener that is still there.
|
|
12840
|
+
*/
|
|
12841
|
+
async stopPortHolders(entry, pids, port, word, freeOnAll) {
|
|
12842
|
+
const { forced } = await terminatePids(pids, { graceMs: entry.config.stop.graceMs });
|
|
12843
|
+
if (forced.length > 0) this.log(entry, "system", `pid ${forced.join(", ")} ignored SIGTERM and was killed`);
|
|
12844
|
+
await delay(PORT_RELEASE_RECHECK_MS);
|
|
12845
|
+
if (await freeOnAll()) {
|
|
12846
|
+
entry.portState = "free";
|
|
12847
|
+
return { kind: "free" };
|
|
12848
|
+
}
|
|
12849
|
+
entry.status = "conflict";
|
|
12850
|
+
entry.lastError = `port ${port} is still in use after ${word} pid ${pids.join(", ")}`;
|
|
12851
|
+
this.log(entry, "system", entry.lastError);
|
|
12852
|
+
this.publishServer(entry);
|
|
12853
|
+
return {
|
|
12854
|
+
kind: "blocked",
|
|
12855
|
+
error: entry.lastError
|
|
12856
|
+
};
|
|
12857
|
+
}
|
|
13014
12858
|
adoptEntry(entry, pid) {
|
|
13015
12859
|
entry.adopted = true;
|
|
13016
12860
|
entry.pid = pid;
|
|
@@ -13903,6 +13747,7 @@ var init_backups = __esmMin((() => {
|
|
|
13903
13747
|
init_template();
|
|
13904
13748
|
init_archive();
|
|
13905
13749
|
init_supervisor();
|
|
13750
|
+
init_error_message();
|
|
13906
13751
|
init_generated();
|
|
13907
13752
|
init_shape();
|
|
13908
13753
|
MANIFEST = "manifest.json";
|
|
@@ -14057,7 +13902,7 @@ var init_backups = __esmMin((() => {
|
|
|
14057
13902
|
fs.rmSync(destination, { force: true });
|
|
14058
13903
|
return {
|
|
14059
13904
|
ok: false,
|
|
14060
|
-
error:
|
|
13905
|
+
error: errorMessage(error)
|
|
14061
13906
|
};
|
|
14062
13907
|
}
|
|
14063
13908
|
}
|
|
@@ -14098,7 +13943,7 @@ var init_backups = __esmMin((() => {
|
|
|
14098
13943
|
} catch (error) {
|
|
14099
13944
|
return {
|
|
14100
13945
|
...plan,
|
|
14101
|
-
error: `the archive could not be read: ${
|
|
13946
|
+
error: `the archive could not be read: ${errorMessage(error)}`
|
|
14102
13947
|
};
|
|
14103
13948
|
}
|
|
14104
13949
|
plan.encrypted = entries.some((entry) => entry.encrypted);
|
|
@@ -14383,7 +14228,7 @@ var init_backups = __esmMin((() => {
|
|
|
14383
14228
|
const done = plan.applied.length > 0 ? ` — already applied: ${plan.applied.join(", ")}` : "";
|
|
14384
14229
|
return {
|
|
14385
14230
|
...plan,
|
|
14386
|
-
error: `${
|
|
14231
|
+
error: `${errorMessage(error)}${done}`
|
|
14387
14232
|
};
|
|
14388
14233
|
} finally {
|
|
14389
14234
|
fs.rmSync(staging, {
|
|
@@ -14648,6 +14493,7 @@ var ControlServer;
|
|
|
14648
14493
|
var init_control_server = __esmMin((() => {
|
|
14649
14494
|
init_bind();
|
|
14650
14495
|
init_port();
|
|
14496
|
+
init_error_message();
|
|
14651
14497
|
ControlServer = class {
|
|
14652
14498
|
options;
|
|
14653
14499
|
endpoint;
|
|
@@ -14681,7 +14527,7 @@ var init_control_server = __esmMin((() => {
|
|
|
14681
14527
|
} catch (error) {
|
|
14682
14528
|
return {
|
|
14683
14529
|
ok: false,
|
|
14684
|
-
error: `restart failed: ${
|
|
14530
|
+
error: `restart failed: ${errorMessage(error)}`
|
|
14685
14531
|
};
|
|
14686
14532
|
}
|
|
14687
14533
|
}
|
|
@@ -14705,7 +14551,7 @@ var init_control_server = __esmMin((() => {
|
|
|
14705
14551
|
await this.listenWithRetry(next.host, next.port);
|
|
14706
14552
|
return { ok: true };
|
|
14707
14553
|
} catch (error) {
|
|
14708
|
-
const message =
|
|
14554
|
+
const message = errorMessage(error);
|
|
14709
14555
|
try {
|
|
14710
14556
|
await this.listenWithRetry(previous.host, previous.port);
|
|
14711
14557
|
} catch {}
|
|
@@ -15210,7 +15056,7 @@ async function detectPublicAddress(family, overrideUrl, fetchImpl, signal) {
|
|
|
15210
15056
|
};
|
|
15211
15057
|
lastError = `${endpoint} did not answer with a public ${family === "A" ? "IPv4" : "IPv6"} address`;
|
|
15212
15058
|
} catch (error) {
|
|
15213
|
-
lastError = `${endpoint}: ${
|
|
15059
|
+
lastError = `${endpoint}: ${errorMessage(error)}`;
|
|
15214
15060
|
}
|
|
15215
15061
|
return {
|
|
15216
15062
|
ip: null,
|
|
@@ -15219,6 +15065,7 @@ async function detectPublicAddress(family, overrideUrl, fetchImpl, signal) {
|
|
|
15219
15065
|
}
|
|
15220
15066
|
var DEFAULT_IPV4_ENDPOINTS, DEFAULT_IPV6_ENDPOINTS;
|
|
15221
15067
|
var init_ip = __esmMin((() => {
|
|
15068
|
+
init_error_message();
|
|
15222
15069
|
DEFAULT_IPV4_ENDPOINTS = [
|
|
15223
15070
|
"https://api4.ipify.org",
|
|
15224
15071
|
"https://ipv4.icanhazip.com",
|
|
@@ -15266,6 +15113,7 @@ var init_ddns = __esmMin((() => {
|
|
|
15266
15113
|
init_ddns$2();
|
|
15267
15114
|
init_ip();
|
|
15268
15115
|
init_types();
|
|
15116
|
+
init_error_message();
|
|
15269
15117
|
RUN_TIMEOUT_MS = 6e4;
|
|
15270
15118
|
DdnsService = class {
|
|
15271
15119
|
deps;
|
|
@@ -15502,7 +15350,7 @@ var init_ddns = __esmMin((() => {
|
|
|
15502
15350
|
try {
|
|
15503
15351
|
writeFileAtomic(this.deps.statePath, `${JSON.stringify(this.persisted, null, 2)}\n`);
|
|
15504
15352
|
} catch (error) {
|
|
15505
|
-
logger.warn(`ddns: could not save its state (${
|
|
15353
|
+
logger.warn(`ddns: could not save its state (${errorMessage(error)})`);
|
|
15506
15354
|
}
|
|
15507
15355
|
}
|
|
15508
15356
|
dispose() {
|
|
@@ -15813,6 +15661,7 @@ var REASON_LABEL, TITLE, NotificationService;
|
|
|
15813
15661
|
var init_notifications = __esmMin((() => {
|
|
15814
15662
|
init_logger();
|
|
15815
15663
|
init_telegram();
|
|
15664
|
+
init_error_message();
|
|
15816
15665
|
REASON_LABEL = {
|
|
15817
15666
|
"crash": "gave up restarting",
|
|
15818
15667
|
"unhealthy": "health check failing",
|
|
@@ -15838,14 +15687,12 @@ var init_notifications = __esmMin((() => {
|
|
|
15838
15687
|
NotificationService = class {
|
|
15839
15688
|
secrets;
|
|
15840
15689
|
getConfig;
|
|
15841
|
-
getLogsConfig;
|
|
15842
15690
|
cooldowns = /* @__PURE__ */ new Map();
|
|
15843
15691
|
lastResult = null;
|
|
15844
15692
|
lastResultAt = null;
|
|
15845
|
-
constructor(secrets, getConfig
|
|
15693
|
+
constructor(secrets, getConfig) {
|
|
15846
15694
|
this.secrets = secrets;
|
|
15847
15695
|
this.getConfig = getConfig;
|
|
15848
|
-
this.getLogsConfig = getLogsConfig;
|
|
15849
15696
|
}
|
|
15850
15697
|
get telegramTokenSet() {
|
|
15851
15698
|
return this.secrets.telegramTokenSet;
|
|
@@ -15892,7 +15739,7 @@ var init_notifications = __esmMin((() => {
|
|
|
15892
15739
|
/** Fire-and-forget by design: supervision must never wait on a chat API. */
|
|
15893
15740
|
notify(event) {
|
|
15894
15741
|
this.dispatch(event).catch((error) => {
|
|
15895
|
-
logger.warn(`notification failed: ${
|
|
15742
|
+
logger.warn(`notification failed: ${errorMessage(error)}`);
|
|
15896
15743
|
});
|
|
15897
15744
|
}
|
|
15898
15745
|
async dispatch(event) {
|
|
@@ -16025,6 +15872,7 @@ var init_panel = __esmMin((() => {
|
|
|
16025
15872
|
init_notifications();
|
|
16026
15873
|
init_state$1();
|
|
16027
15874
|
init_supervisor();
|
|
15875
|
+
init_error_message();
|
|
16028
15876
|
TICK_MS = 1e3;
|
|
16029
15877
|
PanelService = class {
|
|
16030
15878
|
options;
|
|
@@ -16234,7 +16082,7 @@ var init_panel = __esmMin((() => {
|
|
|
16234
16082
|
const secrets = new SecretsStore(workspaceSecretsPath(id), void 0, "workspace");
|
|
16235
16083
|
const logFiles = new LogFiles(workspaceLogsDir(id), () => store.logs);
|
|
16236
16084
|
const history = new HistoryStore(workspaceHistoryPath(id));
|
|
16237
|
-
const notifications = new NotificationService(secrets, () => store.notifications
|
|
16085
|
+
const notifications = new NotificationService(secrets, () => store.notifications);
|
|
16238
16086
|
const ddns = new DdnsService({
|
|
16239
16087
|
getConfig: () => store.ddns,
|
|
16240
16088
|
getCredentials: (accountId, provider) => secrets.getDdnsCredentials(accountId, provider)?.values ?? null,
|
|
@@ -16277,11 +16125,11 @@ var init_panel = __esmMin((() => {
|
|
|
16277
16125
|
runtime.watches = [new ConfigWatch({
|
|
16278
16126
|
file: store.path,
|
|
16279
16127
|
onChange: () => this.handleConfigChange(id),
|
|
16280
|
-
onError: (error) => logger.warn(`cannot watch ${path.basename(store.path)}: ${
|
|
16128
|
+
onError: (error) => logger.warn(`cannot watch ${path.basename(store.path)}: ${errorMessage(error)}`)
|
|
16281
16129
|
}), new ConfigWatch({
|
|
16282
16130
|
file: store.settingsPath,
|
|
16283
16131
|
onChange: () => this.handleConfigChange(id),
|
|
16284
|
-
onError: (error) => logger.warn(`cannot watch ${path.basename(store.settingsPath)}: ${
|
|
16132
|
+
onError: (error) => logger.warn(`cannot watch ${path.basename(store.settingsPath)}: ${errorMessage(error)}`)
|
|
16285
16133
|
})];
|
|
16286
16134
|
for (const watch of runtime.watches) watch.start();
|
|
16287
16135
|
return runtime;
|
|
@@ -16482,7 +16330,7 @@ function readDataRoot(dataRoot) {
|
|
|
16482
16330
|
}
|
|
16483
16331
|
}
|
|
16484
16332
|
function describeError$1(error) {
|
|
16485
|
-
return
|
|
16333
|
+
return errorMessage(error);
|
|
16486
16334
|
}
|
|
16487
16335
|
/**
|
|
16488
16336
|
* Where the site actually starts: the archive root, or a single wrapper directory
|
|
@@ -16516,6 +16364,7 @@ var init_ui = __esmMin((() => {
|
|
|
16516
16364
|
init_atomic();
|
|
16517
16365
|
init_archive();
|
|
16518
16366
|
init_contracts();
|
|
16367
|
+
init_error_message();
|
|
16519
16368
|
META = "ui.json";
|
|
16520
16369
|
MAX_ENTRIES = 2e4;
|
|
16521
16370
|
MAX_BYTES = 536870912;
|
|
@@ -16577,7 +16426,7 @@ var init_ui = __esmMin((() => {
|
|
|
16577
16426
|
} catch (error) {
|
|
16578
16427
|
return {
|
|
16579
16428
|
ok: false,
|
|
16580
|
-
error:
|
|
16429
|
+
error: errorMessage(error)
|
|
16581
16430
|
};
|
|
16582
16431
|
} finally {
|
|
16583
16432
|
fs.rmSync(staging, {
|
|
@@ -16602,7 +16451,7 @@ var init_ui = __esmMin((() => {
|
|
|
16602
16451
|
} catch (error) {
|
|
16603
16452
|
return {
|
|
16604
16453
|
ok: false,
|
|
16605
|
-
error: `the archive could not be read: ${
|
|
16454
|
+
error: `the archive could not be read: ${errorMessage(error)}`
|
|
16606
16455
|
};
|
|
16607
16456
|
}
|
|
16608
16457
|
if (entries.length === 0) return {
|
|
@@ -16914,7 +16763,7 @@ function formatBytes(bytes) {
|
|
|
16914
16763
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
16915
16764
|
}
|
|
16916
16765
|
function describeError(error) {
|
|
16917
|
-
return
|
|
16766
|
+
return errorMessage(error);
|
|
16918
16767
|
}
|
|
16919
16768
|
/**
|
|
16920
16769
|
* Tag ordering: `v1.2.3` beats `v1.2.2`, a prerelease loses to its release, and anything
|
|
@@ -16962,6 +16811,7 @@ function parseTag(tag) {
|
|
|
16962
16811
|
}
|
|
16963
16812
|
var DEFAULT_REPO, MAX_DOWNLOAD_BYTES, REQUEST_TIMEOUT_MS, DOWNLOAD_TIMEOUT_MS;
|
|
16964
16813
|
var init_ui_release = __esmMin((() => {
|
|
16814
|
+
init_error_message();
|
|
16965
16815
|
DEFAULT_REPO = "NamesMT/home-hosted";
|
|
16966
16816
|
MAX_DOWNLOAD_BYTES = 536870912;
|
|
16967
16817
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -17039,7 +16889,7 @@ async function syncOfficialUi(ui, runningVersion = appVersion()) {
|
|
|
17039
16889
|
} catch (error) {
|
|
17040
16890
|
return {
|
|
17041
16891
|
kind: "failed",
|
|
17042
|
-
error:
|
|
16892
|
+
error: errorMessage(error)
|
|
17043
16893
|
};
|
|
17044
16894
|
}
|
|
17045
16895
|
}
|
|
@@ -17058,13 +16908,14 @@ function autoUpdateOfficialUi(ui, runningVersion = appVersion()) {
|
|
|
17058
16908
|
if (result.kind === "updated") logger.info(`ui: updated to ${result.tag} — refresh the browser`);
|
|
17059
16909
|
else if (result.kind === "failed") logger.warn(`ui: could not update to ${plan.tag}: ${result.error}`);
|
|
17060
16910
|
}).catch((error) => {
|
|
17061
|
-
logger.warn(`ui: could not update: ${
|
|
16911
|
+
logger.warn(`ui: could not update: ${errorMessage(error)}`);
|
|
17062
16912
|
});
|
|
17063
16913
|
}
|
|
17064
16914
|
var init_ui_update$1 = __esmMin((() => {
|
|
17065
16915
|
init_logger();
|
|
17066
16916
|
init_version();
|
|
17067
16917
|
init_ui_release();
|
|
16918
|
+
init_error_message();
|
|
17068
16919
|
}));
|
|
17069
16920
|
//#endregion
|
|
17070
16921
|
//#region src/index.ts
|
|
@@ -17090,7 +16941,12 @@ function probeHostFor(bindHost) {
|
|
|
17090
16941
|
* directly.
|
|
17091
16942
|
*/
|
|
17092
16943
|
async function runControlPlane(options) {
|
|
17093
|
-
|
|
16944
|
+
try {
|
|
16945
|
+
ensureLayout();
|
|
16946
|
+
} catch (error) {
|
|
16947
|
+
logger.error(errorMessage(error));
|
|
16948
|
+
process.exit(1);
|
|
16949
|
+
}
|
|
17094
16950
|
const existing = readRuntime();
|
|
17095
16951
|
if (existing !== null && existing.pid !== process.pid && await runtimeIsAlive(existing)) {
|
|
17096
16952
|
logger.error(`already running (pid ${existing.pid}) at ${existing.url} — run \`home-hosted down\` first`);
|
|
@@ -17117,6 +16973,11 @@ async function runControlPlane(options) {
|
|
|
17117
16973
|
process.exit(1);
|
|
17118
16974
|
}
|
|
17119
16975
|
const secrets = new SecretsStore(globalSecretsPath, void 0, "global");
|
|
16976
|
+
if (secrets.configError !== null) {
|
|
16977
|
+
logger.error(`refusing to start: ${secrets.configError}`);
|
|
16978
|
+
logger.info("fix or restore the file, or move it aside to start fresh — starting fresh discards it");
|
|
16979
|
+
process.exit(1);
|
|
16980
|
+
}
|
|
17120
16981
|
const auth = new AuthService(secrets, () => settings.control.auth);
|
|
17121
16982
|
const tls = new TlsStore(tlsDir);
|
|
17122
16983
|
const hub = new EventHub();
|
|
@@ -17263,6 +17124,11 @@ async function runControlPlane(options) {
|
|
|
17263
17124
|
logger.info(`fix ${store.path} and ${store.settingsPath}, or install the release that wrote them`);
|
|
17264
17125
|
process.exit(1);
|
|
17265
17126
|
}
|
|
17127
|
+
if (workspace.secrets.configError !== null) {
|
|
17128
|
+
logger.error(`refusing to start: workspace "${workspace.id}": ${workspace.secrets.configError}`);
|
|
17129
|
+
logger.info("fix or restore the file, or move it aside to start fresh — starting fresh discards it");
|
|
17130
|
+
process.exit(1);
|
|
17131
|
+
}
|
|
17266
17132
|
if (store.pendingMigrations.length > 0) {
|
|
17267
17133
|
logger.error(`refusing to start: workspace "${workspace.id}" needs ${store.pendingMigrations.length} migration(s) before ${appVersion()} can use it`);
|
|
17268
17134
|
logger.info("run `home-hosted migrate` to see and apply them");
|
|
@@ -17410,6 +17276,7 @@ var init_src = __esmMin((() => {
|
|
|
17410
17276
|
init_ui();
|
|
17411
17277
|
init_ui_update$1();
|
|
17412
17278
|
init_contracts();
|
|
17279
|
+
init_error_message();
|
|
17413
17280
|
packageRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
17414
17281
|
}));
|
|
17415
17282
|
//#endregion
|
|
@@ -17973,6 +17840,7 @@ async function runLogs(input) {
|
|
|
17973
17840
|
var logsArgs, DEFAULT_LINES, logsCommand;
|
|
17974
17841
|
var init_logs = __esmMin((() => {
|
|
17975
17842
|
init_io();
|
|
17843
|
+
init_error_message();
|
|
17976
17844
|
logsArgs = {
|
|
17977
17845
|
lines: {
|
|
17978
17846
|
type: "string",
|
|
@@ -17999,7 +17867,7 @@ var init_logs = __esmMin((() => {
|
|
|
17999
17867
|
json: args.json
|
|
18000
17868
|
});
|
|
18001
17869
|
} catch (error) {
|
|
18002
|
-
fail(
|
|
17870
|
+
fail(errorMessage(error));
|
|
18003
17871
|
}
|
|
18004
17872
|
}
|
|
18005
17873
|
});
|
|
@@ -18166,12 +18034,6 @@ function targets(configOverride) {
|
|
|
18166
18034
|
return list;
|
|
18167
18035
|
}
|
|
18168
18036
|
async function runMigrate(config, dryRun, yes) {
|
|
18169
|
-
const relocated = migrateLayout();
|
|
18170
|
-
if (relocated.migrated) {
|
|
18171
|
-
process.stdout.write(`${green("state layout migrated")} into the current .hh layout\n`);
|
|
18172
|
-
for (const step of relocated.moved) process.stdout.write(` ${dim(step)}\n`);
|
|
18173
|
-
for (const warning of relocated.warnings) process.stdout.write(` ${dim(`warning: ${warning}`)}\n`);
|
|
18174
|
-
}
|
|
18175
18037
|
const existing = targets(config).filter((target) => fs.existsSync(target.file));
|
|
18176
18038
|
if (existing.length === 0) fail(`no home-hosted state to migrate — nothing at ${globalSettingsPath} or any workspace`);
|
|
18177
18039
|
const consented = yes || (process.env.HHOSTED_MIGRATE ?? "").toLowerCase() === "allow";
|
|
@@ -18181,7 +18043,7 @@ async function runMigrate(config, dryRun, yes) {
|
|
|
18181
18043
|
try {
|
|
18182
18044
|
raw = JSON.parse(fs.readFileSync(target.file, "utf8"));
|
|
18183
18045
|
} catch (error) {
|
|
18184
|
-
fail(`cannot read ${target.file}: ${
|
|
18046
|
+
fail(`cannot read ${target.file}: ${errorMessage(error)}`);
|
|
18185
18047
|
}
|
|
18186
18048
|
const meta = typeof raw.meta === "object" && raw.meta !== null ? raw.meta : {};
|
|
18187
18049
|
const from = typeof meta.schema === "number" ? meta.schema : 1;
|
|
@@ -18226,13 +18088,13 @@ async function runMigrate(config, dryRun, yes) {
|
|
|
18226
18088
|
var migrateArgs, migrateCommand;
|
|
18227
18089
|
var init_migrate = __esmMin((() => {
|
|
18228
18090
|
init_io();
|
|
18229
|
-
init_layout();
|
|
18230
18091
|
init_migrations();
|
|
18231
18092
|
init_parse();
|
|
18232
18093
|
init_workspaces();
|
|
18233
18094
|
init_atomic();
|
|
18234
18095
|
init_paths();
|
|
18235
18096
|
init_version();
|
|
18097
|
+
init_error_message();
|
|
18236
18098
|
migrateArgs = {
|
|
18237
18099
|
config: {
|
|
18238
18100
|
type: "string",
|
|
@@ -18396,7 +18258,7 @@ async function runInit(options) {
|
|
|
18396
18258
|
process.stdout.write(`${green("project created")} in ${result.dir}\n`);
|
|
18397
18259
|
for (const file of result.files) process.stdout.write(` ${dim(file)}\n`);
|
|
18398
18260
|
} catch (error) {
|
|
18399
|
-
fail(
|
|
18261
|
+
fail(errorMessage(error));
|
|
18400
18262
|
}
|
|
18401
18263
|
const target = path.resolve(dir);
|
|
18402
18264
|
if (git) {
|
|
@@ -18427,6 +18289,7 @@ var initArgs, initCommand;
|
|
|
18427
18289
|
var init_init = __esmMin((() => {
|
|
18428
18290
|
init_io();
|
|
18429
18291
|
init_init$1();
|
|
18292
|
+
init_error_message();
|
|
18430
18293
|
initArgs = {
|
|
18431
18294
|
dir: {
|
|
18432
18295
|
type: "string",
|
|
@@ -19073,6 +18936,7 @@ var init_nanny = __esmMin((() => {
|
|
|
19073
18936
|
}));
|
|
19074
18937
|
//#endregion
|
|
19075
18938
|
//#region src/cli.ts
|
|
18939
|
+
init_error_message();
|
|
19076
18940
|
init_args();
|
|
19077
18941
|
init_io();
|
|
19078
18942
|
init_nearest();
|
|
@@ -19369,7 +19233,7 @@ async function main() {
|
|
|
19369
19233
|
try {
|
|
19370
19234
|
await runCommand(nannyCommand, { rawArgs: dirFlags.rest });
|
|
19371
19235
|
} catch (error) {
|
|
19372
|
-
fail(
|
|
19236
|
+
fail(errorMessage(error));
|
|
19373
19237
|
}
|
|
19374
19238
|
return;
|
|
19375
19239
|
}
|
|
@@ -19398,11 +19262,11 @@ async function main() {
|
|
|
19398
19262
|
try {
|
|
19399
19263
|
await runCommand(rootCommand, { rawArgs: invocation.argv });
|
|
19400
19264
|
} catch (error) {
|
|
19401
|
-
fail(
|
|
19265
|
+
fail(errorMessage(error));
|
|
19402
19266
|
}
|
|
19403
19267
|
}
|
|
19404
19268
|
main().catch((error) => {
|
|
19405
|
-
fail(
|
|
19269
|
+
fail(errorMessage(error));
|
|
19406
19270
|
});
|
|
19407
19271
|
//#endregion
|
|
19408
19272
|
export { commandHelp, suggestCommand };
|