home-hosted 0.7.20 → 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 +6 -3
- package/README.md +12 -1
- package/dist/cli.js +315 -435
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/uis/stock/dist/assets/{index-DpddvXhj.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.
|
|
@@ -1077,7 +1090,14 @@ maxRssBytes: "number.integer >= 0 = 0" }).onUndeclaredKey("reject");
|
|
|
1077
1090
|
logBufferLines: logBufferLinesSchema.optional()
|
|
1078
1091
|
}).onUndeclaredKey("reject");
|
|
1079
1092
|
editableFields = {
|
|
1080
|
-
|
|
1093
|
+
/**
|
|
1094
|
+
* `null` clears it, the same spelling `serverSchema` accepts and every display's `label ?? id`
|
|
1095
|
+
* fallback needs — an empty string satisfies neither. `serverPatchSchema` is what the editor
|
|
1096
|
+
* validates its payload against and what `PATCH /api/servers/:id` checks, so without this a
|
|
1097
|
+
* cleared Label was refused with "label must be a string (was null)" and could not be saved at all
|
|
1098
|
+
* (`1f58a5d` fixed `applyPatch` and the *control* patch schema, and cited that one in its message).
|
|
1099
|
+
*/
|
|
1100
|
+
label: labelOrNullSchema.optional(),
|
|
1081
1101
|
enabled: "boolean?",
|
|
1082
1102
|
autostart: "boolean?",
|
|
1083
1103
|
persistent: "boolean?",
|
|
@@ -1888,7 +1908,11 @@ function isSealedDdns(value) {
|
|
|
1888
1908
|
function isPlainDdns(value) {
|
|
1889
1909
|
return "plain" in value;
|
|
1890
1910
|
}
|
|
1891
|
-
/**
|
|
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
|
+
*/
|
|
1892
1916
|
function readStoredDdns(value) {
|
|
1893
1917
|
if (!isRecord(value)) return {};
|
|
1894
1918
|
const out = {};
|
|
@@ -1897,12 +1921,12 @@ function readStoredDdns(value) {
|
|
|
1897
1921
|
out[accountId] = entry;
|
|
1898
1922
|
continue;
|
|
1899
1923
|
}
|
|
1900
|
-
if (!isRecord(entry)) continue;
|
|
1901
|
-
const values = readDdnsValues(
|
|
1924
|
+
if (!isRecord(entry) || typeof entry.provider !== "string" || entry.provider.length === 0) continue;
|
|
1925
|
+
const values = readDdnsValues(entry.values);
|
|
1902
1926
|
if (Object.keys(values).length === 0) continue;
|
|
1903
1927
|
out[accountId] = {
|
|
1904
1928
|
plain: true,
|
|
1905
|
-
provider:
|
|
1929
|
+
provider: entry.provider,
|
|
1906
1930
|
values
|
|
1907
1931
|
};
|
|
1908
1932
|
}
|
|
@@ -1970,6 +1994,7 @@ var COST, KEYLEN, SALT_BYTES, DDNS_KEYLEN, DDNS_IV_BYTES, DDNS_AAD_PREFIX, DDNS_
|
|
|
1970
1994
|
var init_secrets = __esmMin((() => {
|
|
1971
1995
|
init_atomic();
|
|
1972
1996
|
init_logger();
|
|
1997
|
+
init_error_message();
|
|
1973
1998
|
init_shape();
|
|
1974
1999
|
COST = {
|
|
1975
2000
|
N: 16384,
|
|
@@ -1989,6 +2014,7 @@ var init_secrets = __esmMin((() => {
|
|
|
1989
2014
|
scope;
|
|
1990
2015
|
cache = null;
|
|
1991
2016
|
cacheKey = "";
|
|
2017
|
+
error = null;
|
|
1992
2018
|
ddnsKeyCache = null;
|
|
1993
2019
|
warnedAccounts = /* @__PURE__ */ new Set();
|
|
1994
2020
|
warnedDefaultSecret = false;
|
|
@@ -2113,7 +2139,7 @@ var init_secrets = __esmMin((() => {
|
|
|
2113
2139
|
if (stored === void 0) return null;
|
|
2114
2140
|
const record = this.openStored(accountId, stored);
|
|
2115
2141
|
if (record === null) return null;
|
|
2116
|
-
return record.provider
|
|
2142
|
+
return record.provider === provider ? record : null;
|
|
2117
2143
|
}
|
|
2118
2144
|
/** An empty field is dropped, so clearing every field forgets the account. */
|
|
2119
2145
|
setDdnsCredentials(accountId, provider, credentials) {
|
|
@@ -2184,9 +2210,22 @@ var init_secrets = __esmMin((() => {
|
|
|
2184
2210
|
this.warnedAccounts.add(accountId);
|
|
2185
2211
|
logger.warn(`ddns: cannot read the credentials for "${accountId}" — ${reason}`);
|
|
2186
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
|
+
}
|
|
2187
2226
|
read() {
|
|
2227
|
+
this.error = null;
|
|
2188
2228
|
const empty = {
|
|
2189
|
-
version: 3,
|
|
2190
2229
|
password: null,
|
|
2191
2230
|
apiToken: null,
|
|
2192
2231
|
telegram: null,
|
|
@@ -2194,17 +2233,24 @@ var init_secrets = __esmMin((() => {
|
|
|
2194
2233
|
ddnsKdf: null
|
|
2195
2234
|
};
|
|
2196
2235
|
if (!fs.existsSync(this.file)) return this.inScope(empty);
|
|
2236
|
+
let text;
|
|
2197
2237
|
try {
|
|
2198
|
-
|
|
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);
|
|
2199
2245
|
return this.inScope({
|
|
2200
|
-
version: 3,
|
|
2201
2246
|
password: parsed?.password ?? null,
|
|
2202
2247
|
apiToken: parsed?.apiToken?.hash ? parsed.apiToken : null,
|
|
2203
2248
|
telegram: parsed?.telegram?.botToken ? { botToken: parsed.telegram.botToken } : null,
|
|
2204
2249
|
ddns: readStoredDdns(parsed?.ddns),
|
|
2205
2250
|
ddnsKdf: readDdnsKdf(parsed?.ddnsKdf)
|
|
2206
2251
|
});
|
|
2207
|
-
} catch {
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
this.error = `${this.file} is not readable JSON (${errorMessage(error)}) — it was left untouched`;
|
|
2208
2254
|
return this.inScope(empty);
|
|
2209
2255
|
}
|
|
2210
2256
|
}
|
|
@@ -2224,6 +2270,7 @@ var init_secrets = __esmMin((() => {
|
|
|
2224
2270
|
return contents;
|
|
2225
2271
|
}
|
|
2226
2272
|
save(contents) {
|
|
2273
|
+
if (this.configError !== null) throw new Error(`refusing to write ${this.file}: ${this.error}`);
|
|
2227
2274
|
const sealed = this.sealStored(this.inScope(contents));
|
|
2228
2275
|
writeFileAtomic(this.file, `${JSON.stringify(sealed, null, 2)}\n`, { mode: 384 });
|
|
2229
2276
|
this.cache = sealed;
|
|
@@ -2912,7 +2959,11 @@ function workspaceIdsOnDisk() {
|
|
|
2912
2959
|
return [];
|
|
2913
2960
|
}
|
|
2914
2961
|
}
|
|
2915
|
-
/**
|
|
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
|
+
*/
|
|
2916
2967
|
function hasLegacyLayout() {
|
|
2917
2968
|
if (fs.existsSync(workspacesPath)) return false;
|
|
2918
2969
|
return fs.existsSync(legacy.configPath) || fs.existsSync(legacy.secretsPath);
|
|
@@ -2948,15 +2999,7 @@ var init_paths = __esmMin((() => {
|
|
|
2948
2999
|
proxyChallengeAuthPath = path.join(proxyStateDir, "challenge.json");
|
|
2949
3000
|
legacy = {
|
|
2950
3001
|
configPath: path.join(dataRoot, "servers.config.json"),
|
|
2951
|
-
|
|
2952
|
-
secretsPath: path.join(dataRoot, ".control-secrets.json"),
|
|
2953
|
-
tlsDir: path.join(dataRoot, ".tls"),
|
|
2954
|
-
logsDir: path.join(dataRoot, ".logs"),
|
|
2955
|
-
historyPath: path.join(dataRoot, ".logs", "history.json"),
|
|
2956
|
-
stateDir: path.join(dataRoot, ".state"),
|
|
2957
|
-
backupsDir: path.join(dataRoot, ".backups"),
|
|
2958
|
-
uiDir: path.join(dataRoot, ".ui"),
|
|
2959
|
-
runtimePath: path.join(dataRoot, "run.json")
|
|
3002
|
+
secretsPath: path.join(dataRoot, ".control-secrets.json")
|
|
2960
3003
|
};
|
|
2961
3004
|
}));
|
|
2962
3005
|
//#endregion
|
|
@@ -3617,7 +3660,7 @@ function planConfigMigrations(from, options = {}) {
|
|
|
3617
3660
|
to,
|
|
3618
3661
|
steps,
|
|
3619
3662
|
tooNew: from > to,
|
|
3620
|
-
reached: steps.
|
|
3663
|
+
reached: steps.at(-1)?.to ?? from
|
|
3621
3664
|
};
|
|
3622
3665
|
}
|
|
3623
3666
|
/** Applies the plan in order; the caller owns writing the result. */
|
|
@@ -3685,6 +3728,35 @@ function parseTolerant(value, schema, prefix, unknownKeys) {
|
|
|
3685
3728
|
};
|
|
3686
3729
|
}
|
|
3687
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
|
+
/**
|
|
3688
3760
|
* Reads one settings file from any release: schema guard, unrecognized keys
|
|
3689
3761
|
* reported and left on disk, blocking problems returned for the caller to refuse on.
|
|
3690
3762
|
*/
|
|
@@ -3703,18 +3775,11 @@ function parseGrouped(raw, label, definition, options) {
|
|
|
3703
3775
|
errors.push(`the ${label} must contain a JSON object`);
|
|
3704
3776
|
return result;
|
|
3705
3777
|
}
|
|
3706
|
-
const
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
if (schemaVersion > 1) {
|
|
3712
|
-
errors.push(`written by home-hosted ${writtenBy ?? "a newer release"} (config schema ${schemaVersion}); this release understands schema 1`);
|
|
3713
|
-
return result;
|
|
3714
|
-
}
|
|
3715
|
-
const { steps } = planConfigMigrations(schemaVersion, options);
|
|
3716
|
-
if (steps.length > 0) {
|
|
3717
|
-
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);
|
|
3718
3783
|
return result;
|
|
3719
3784
|
}
|
|
3720
3785
|
for (const key of Object.keys(raw)) if (!definition.keys.includes(key)) unknownKeys.push(key);
|
|
@@ -3726,8 +3791,8 @@ function parseGrouped(raw, label, definition, options) {
|
|
|
3726
3791
|
}
|
|
3727
3792
|
if (errors.length > 0) return result;
|
|
3728
3793
|
result.config = definition.build({
|
|
3729
|
-
writtenBy: writtenBy ?? "",
|
|
3730
|
-
schema: schemaVersion
|
|
3794
|
+
writtenBy: stamp.writtenBy ?? "",
|
|
3795
|
+
schema: stamp.schemaVersion
|
|
3731
3796
|
}, groups);
|
|
3732
3797
|
return result;
|
|
3733
3798
|
}
|
|
@@ -3789,18 +3854,11 @@ function parseServersFile(raw, defaults, options = {}) {
|
|
|
3789
3854
|
errors.push("the servers config must contain a JSON object");
|
|
3790
3855
|
return result;
|
|
3791
3856
|
}
|
|
3792
|
-
const
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
if (schemaVersion > 1) {
|
|
3798
|
-
errors.push(`written by home-hosted ${writtenBy ?? "a newer release"} (config schema ${schemaVersion}); this release understands schema 1`);
|
|
3799
|
-
return result;
|
|
3800
|
-
}
|
|
3801
|
-
const { steps } = planConfigMigrations(schemaVersion, options);
|
|
3802
|
-
if (steps.length > 0) {
|
|
3803
|
-
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);
|
|
3804
3862
|
return result;
|
|
3805
3863
|
}
|
|
3806
3864
|
for (const key of Object.keys(raw)) if (!SERVERS_FILE_KEYS.includes(key)) unknownKeys.push(key);
|
|
@@ -4195,6 +4253,18 @@ function failed(error) {
|
|
|
4195
4253
|
error
|
|
4196
4254
|
};
|
|
4197
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
|
+
}
|
|
4198
4268
|
/** Missing required credential keys, as a sentence for the settings page. */
|
|
4199
4269
|
function missingFields(credentials, keys) {
|
|
4200
4270
|
const missing = keys.filter((key) => (credentials?.[key] ?? "").trim().length === 0);
|
|
@@ -4272,7 +4342,7 @@ async function call$1(path, headers, context, init = {}) {
|
|
|
4272
4342
|
return {
|
|
4273
4343
|
data: null,
|
|
4274
4344
|
status: 0,
|
|
4275
|
-
error:
|
|
4345
|
+
error: errorMessage(error)
|
|
4276
4346
|
};
|
|
4277
4347
|
}
|
|
4278
4348
|
}
|
|
@@ -4331,6 +4401,7 @@ async function findRecord(zoneId, record, headers, context, cached) {
|
|
|
4331
4401
|
var API$5, cloudflareProvider;
|
|
4332
4402
|
var init_cloudflare = __esmMin((() => {
|
|
4333
4403
|
init_types();
|
|
4404
|
+
init_error_message();
|
|
4334
4405
|
API$5 = "https://api.cloudflare.com/client/v4";
|
|
4335
4406
|
cloudflareProvider = {
|
|
4336
4407
|
id: "cloudflare",
|
|
@@ -4407,7 +4478,7 @@ var init_cloudflare = __esmMin((() => {
|
|
|
4407
4478
|
}
|
|
4408
4479
|
};
|
|
4409
4480
|
} catch (error) {
|
|
4410
|
-
return failed(
|
|
4481
|
+
return failed(errorMessage(error));
|
|
4411
4482
|
}
|
|
4412
4483
|
},
|
|
4413
4484
|
/**
|
|
@@ -4474,7 +4545,7 @@ var init_cloudflare = __esmMin((() => {
|
|
|
4474
4545
|
} catch (error) {
|
|
4475
4546
|
return {
|
|
4476
4547
|
ok: false,
|
|
4477
|
-
message:
|
|
4548
|
+
message: errorMessage(error)
|
|
4478
4549
|
};
|
|
4479
4550
|
}
|
|
4480
4551
|
}
|
|
@@ -4605,6 +4676,7 @@ var ENDPOINT$7, desecProvider;
|
|
|
4605
4676
|
var init_desec = __esmMin((() => {
|
|
4606
4677
|
init_dyndns2();
|
|
4607
4678
|
init_types();
|
|
4679
|
+
init_error_message();
|
|
4608
4680
|
ENDPOINT$7 = "https://update.dedyn.io/";
|
|
4609
4681
|
desecProvider = {
|
|
4610
4682
|
id: "desec",
|
|
@@ -4634,7 +4706,7 @@ var init_desec = __esmMin((() => {
|
|
|
4634
4706
|
});
|
|
4635
4707
|
return describeDyndnsResponse(response, await response.text(), "desec");
|
|
4636
4708
|
} catch (error) {
|
|
4637
|
-
return failed(
|
|
4709
|
+
return failed(errorMessage(error));
|
|
4638
4710
|
}
|
|
4639
4711
|
}
|
|
4640
4712
|
};
|
|
@@ -4702,6 +4774,7 @@ var API$4, digitaloceanProvider;
|
|
|
4702
4774
|
var init_digitalocean = __esmMin((() => {
|
|
4703
4775
|
init_types();
|
|
4704
4776
|
init_zone();
|
|
4777
|
+
init_error_message();
|
|
4705
4778
|
API$4 = "https://api.digitalocean.com/v2/domains";
|
|
4706
4779
|
digitaloceanProvider = {
|
|
4707
4780
|
id: "digitalocean",
|
|
@@ -4735,7 +4808,7 @@ var init_digitalocean = __esmMin((() => {
|
|
|
4735
4808
|
if (!listed.ok) return failed(await errorText(listed));
|
|
4736
4809
|
const existing = ((await listed.json()).domain_records ?? []).find((entry) => entry.name === name && entry.type === record.type);
|
|
4737
4810
|
if (existing !== void 0 && existing.data === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
4738
|
-
|
|
4811
|
+
return await wrote(existing === void 0 ? await context.fetch(collection, {
|
|
4739
4812
|
method: "POST",
|
|
4740
4813
|
headers,
|
|
4741
4814
|
body: JSON.stringify({
|
|
@@ -4755,11 +4828,9 @@ var init_digitalocean = __esmMin((() => {
|
|
|
4755
4828
|
ttl
|
|
4756
4829
|
}),
|
|
4757
4830
|
signal: context.signal
|
|
4758
|
-
});
|
|
4759
|
-
if (!written.ok) return failed(await errorText(written));
|
|
4760
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
4831
|
+
}), record);
|
|
4761
4832
|
} catch (error) {
|
|
4762
|
-
return failed(
|
|
4833
|
+
return failed(errorMessage(error));
|
|
4763
4834
|
}
|
|
4764
4835
|
}
|
|
4765
4836
|
};
|
|
@@ -4774,6 +4845,7 @@ function subdomain(host) {
|
|
|
4774
4845
|
var ENDPOINT$6, duckdnsProvider;
|
|
4775
4846
|
var init_duckdns = __esmMin((() => {
|
|
4776
4847
|
init_types();
|
|
4848
|
+
init_error_message();
|
|
4777
4849
|
ENDPOINT$6 = "https://www.duckdns.org/update";
|
|
4778
4850
|
duckdnsProvider = {
|
|
4779
4851
|
id: "duckdns",
|
|
@@ -4802,7 +4874,7 @@ var init_duckdns = __esmMin((() => {
|
|
|
4802
4874
|
if (body.toUpperCase() === "OK") return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
4803
4875
|
return failed(`duckdns answered "${body.slice(0, 120) || `HTTP ${response.status}`}"`);
|
|
4804
4876
|
} catch (error) {
|
|
4805
|
-
return failed(
|
|
4877
|
+
return failed(errorMessage(error));
|
|
4806
4878
|
}
|
|
4807
4879
|
}
|
|
4808
4880
|
};
|
|
@@ -4813,6 +4885,7 @@ var ENDPOINT$5, dynuProvider;
|
|
|
4813
4885
|
var init_dynu = __esmMin((() => {
|
|
4814
4886
|
init_dyndns2();
|
|
4815
4887
|
init_types();
|
|
4888
|
+
init_error_message();
|
|
4816
4889
|
ENDPOINT$5 = "https://api.dynu.com/nic/update";
|
|
4817
4890
|
dynuProvider = {
|
|
4818
4891
|
id: "dynu",
|
|
@@ -4848,7 +4921,7 @@ var init_dynu = __esmMin((() => {
|
|
|
4848
4921
|
});
|
|
4849
4922
|
return describeDyndnsResponse(response, await response.text(), "dynu");
|
|
4850
4923
|
} catch (error) {
|
|
4851
|
-
return failed(
|
|
4924
|
+
return failed(errorMessage(error));
|
|
4852
4925
|
}
|
|
4853
4926
|
}
|
|
4854
4927
|
};
|
|
@@ -4867,6 +4940,7 @@ function updateToken(credentials) {
|
|
|
4867
4940
|
var ENDPOINT$4, freednsProvider;
|
|
4868
4941
|
var init_freedns = __esmMin((() => {
|
|
4869
4942
|
init_types();
|
|
4943
|
+
init_error_message();
|
|
4870
4944
|
ENDPOINT$4 = "https://freedns.afraid.org/dynamic/update.php";
|
|
4871
4945
|
freednsProvider = {
|
|
4872
4946
|
id: "freedns",
|
|
@@ -4893,7 +4967,7 @@ var init_freedns = __esmMin((() => {
|
|
|
4893
4967
|
if (!response.ok || /^ERROR/i.test(body)) return failed(`freedns answered "${body.slice(0, 160) || `HTTP ${response.status}`}"`);
|
|
4894
4968
|
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
4895
4969
|
} catch (error) {
|
|
4896
|
-
return failed(
|
|
4970
|
+
return failed(errorMessage(error));
|
|
4897
4971
|
}
|
|
4898
4972
|
}
|
|
4899
4973
|
};
|
|
@@ -4912,6 +4986,7 @@ var API$3, gandiProvider;
|
|
|
4912
4986
|
var init_gandi = __esmMin((() => {
|
|
4913
4987
|
init_types();
|
|
4914
4988
|
init_zone();
|
|
4989
|
+
init_error_message();
|
|
4915
4990
|
API$3 = "https://api.gandi.net/v5/livedns/domains";
|
|
4916
4991
|
gandiProvider = {
|
|
4917
4992
|
id: "gandi",
|
|
@@ -4948,7 +5023,7 @@ var init_gandi = __esmMin((() => {
|
|
|
4948
5023
|
const body = await current.json();
|
|
4949
5024
|
if (body.rrset_values?.length === 1 && body.rrset_values[0] === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
4950
5025
|
}
|
|
4951
|
-
|
|
5026
|
+
return await wrote(await context.fetch(url, {
|
|
4952
5027
|
method: "PUT",
|
|
4953
5028
|
headers,
|
|
4954
5029
|
body: JSON.stringify({
|
|
@@ -4956,11 +5031,9 @@ var init_gandi = __esmMin((() => {
|
|
|
4956
5031
|
rrset_ttl: ttl
|
|
4957
5032
|
}),
|
|
4958
5033
|
signal: context.signal
|
|
4959
|
-
});
|
|
4960
|
-
if (!written.ok) return failed(await errorText(written));
|
|
4961
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
5034
|
+
}), record);
|
|
4962
5035
|
} catch (error) {
|
|
4963
|
-
return failed(
|
|
5036
|
+
return failed(errorMessage(error));
|
|
4964
5037
|
}
|
|
4965
5038
|
}
|
|
4966
5039
|
};
|
|
@@ -4975,6 +5048,7 @@ var API$2, godaddyProvider;
|
|
|
4975
5048
|
var init_godaddy = __esmMin((() => {
|
|
4976
5049
|
init_types();
|
|
4977
5050
|
init_zone();
|
|
5051
|
+
init_error_message();
|
|
4978
5052
|
API$2 = "https://api.godaddy.com/v1/domains";
|
|
4979
5053
|
godaddyProvider = {
|
|
4980
5054
|
id: "godaddy",
|
|
@@ -5023,7 +5097,7 @@ var init_godaddy = __esmMin((() => {
|
|
|
5023
5097
|
const body = await current.json();
|
|
5024
5098
|
if (body.length === 1 && body[0]?.data === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
5025
5099
|
}
|
|
5026
|
-
|
|
5100
|
+
return await wrote(await context.fetch(url, {
|
|
5027
5101
|
method: "PUT",
|
|
5028
5102
|
headers,
|
|
5029
5103
|
body: JSON.stringify([{
|
|
@@ -5031,11 +5105,9 @@ var init_godaddy = __esmMin((() => {
|
|
|
5031
5105
|
ttl
|
|
5032
5106
|
}]),
|
|
5033
5107
|
signal: context.signal
|
|
5034
|
-
});
|
|
5035
|
-
if (!written.ok) return failed(await errorText(written));
|
|
5036
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
5108
|
+
}), record);
|
|
5037
5109
|
} catch (error) {
|
|
5038
|
-
return failed(
|
|
5110
|
+
return failed(errorMessage(error));
|
|
5039
5111
|
}
|
|
5040
5112
|
}
|
|
5041
5113
|
};
|
|
@@ -5046,6 +5118,7 @@ var ENDPOINT$3, henetProvider;
|
|
|
5046
5118
|
var init_henet = __esmMin((() => {
|
|
5047
5119
|
init_dyndns2();
|
|
5048
5120
|
init_types();
|
|
5121
|
+
init_error_message();
|
|
5049
5122
|
ENDPOINT$3 = "https://dyn.dns.he.net/nic/update";
|
|
5050
5123
|
henetProvider = {
|
|
5051
5124
|
id: "henet",
|
|
@@ -5071,7 +5144,7 @@ var init_henet = __esmMin((() => {
|
|
|
5071
5144
|
const response = await context.fetch(url, { signal: context.signal });
|
|
5072
5145
|
return describeDyndnsResponse(response, await response.text(), "hurricane electric");
|
|
5073
5146
|
} catch (error) {
|
|
5074
|
-
return failed(
|
|
5147
|
+
return failed(errorMessage(error));
|
|
5075
5148
|
}
|
|
5076
5149
|
}
|
|
5077
5150
|
};
|
|
@@ -5085,6 +5158,7 @@ var ENDPOINT$2, namecheapProvider;
|
|
|
5085
5158
|
var init_namecheap = __esmMin((() => {
|
|
5086
5159
|
init_types();
|
|
5087
5160
|
init_zone();
|
|
5161
|
+
init_error_message();
|
|
5088
5162
|
ENDPOINT$2 = "https://dynamicdns.park-your-domain.com/update";
|
|
5089
5163
|
namecheapProvider = {
|
|
5090
5164
|
id: "namecheap",
|
|
@@ -5119,7 +5193,7 @@ var init_namecheap = __esmMin((() => {
|
|
|
5119
5193
|
if (ip === null) return failed(`namecheap answered without an IP: ${body.replace(/\s+/g, " ").slice(0, 160)}`);
|
|
5120
5194
|
return succeeded(`${host === "@" ? zone : `${host}.${zone}`} now ${ip}`, ip !== record.ip);
|
|
5121
5195
|
} catch (error) {
|
|
5122
|
-
return failed(
|
|
5196
|
+
return failed(errorMessage(error));
|
|
5123
5197
|
}
|
|
5124
5198
|
}
|
|
5125
5199
|
};
|
|
@@ -5211,7 +5285,7 @@ async function call(command, params, context) {
|
|
|
5211
5285
|
body
|
|
5212
5286
|
};
|
|
5213
5287
|
} catch (error) {
|
|
5214
|
-
return
|
|
5288
|
+
return errorMessage(error);
|
|
5215
5289
|
}
|
|
5216
5290
|
}
|
|
5217
5291
|
/** The registered domain split into the `SLD`/`TLD` pair the API asks for. */
|
|
@@ -5258,6 +5332,7 @@ var ENDPOINT$1, RECORD_LIMIT, CHALLENGE_TTL, XML_ENTITIES, namecheapApiProvider;
|
|
|
5258
5332
|
var init_namecheap_api = __esmMin((() => {
|
|
5259
5333
|
init_types();
|
|
5260
5334
|
init_zone();
|
|
5335
|
+
init_error_message();
|
|
5261
5336
|
ENDPOINT$1 = "https://api.namecheap.com/xml.response";
|
|
5262
5337
|
RECORD_LIMIT = 150;
|
|
5263
5338
|
CHALLENGE_TTL = 60;
|
|
@@ -5372,6 +5447,7 @@ var ENDPOINT, DEFAULT_AGENT, noipProvider;
|
|
|
5372
5447
|
var init_noip = __esmMin((() => {
|
|
5373
5448
|
init_dyndns2();
|
|
5374
5449
|
init_types();
|
|
5450
|
+
init_error_message();
|
|
5375
5451
|
ENDPOINT = "https://dynupdate.no-ip.com/nic/update";
|
|
5376
5452
|
DEFAULT_AGENT = "home-hosted/1.0 (+https://github.com/NamesMT/home-hosted)";
|
|
5377
5453
|
noipProvider = {
|
|
@@ -5420,7 +5496,7 @@ var init_noip = __esmMin((() => {
|
|
|
5420
5496
|
});
|
|
5421
5497
|
return describeDyndnsResponse(response, await response.text(), "no-ip");
|
|
5422
5498
|
} catch (error) {
|
|
5423
|
-
return failed(
|
|
5499
|
+
return failed(errorMessage(error));
|
|
5424
5500
|
}
|
|
5425
5501
|
}
|
|
5426
5502
|
};
|
|
@@ -5455,7 +5531,7 @@ async function post(path, context, body) {
|
|
|
5455
5531
|
} catch (error) {
|
|
5456
5532
|
return {
|
|
5457
5533
|
data: null,
|
|
5458
|
-
error:
|
|
5534
|
+
error: errorMessage(error)
|
|
5459
5535
|
};
|
|
5460
5536
|
}
|
|
5461
5537
|
}
|
|
@@ -5463,6 +5539,7 @@ var API$1, porkbunProvider;
|
|
|
5463
5539
|
var init_porkbun = __esmMin((() => {
|
|
5464
5540
|
init_types();
|
|
5465
5541
|
init_zone();
|
|
5542
|
+
init_error_message();
|
|
5466
5543
|
API$1 = "https://api.porkbun.com/api/json/v3";
|
|
5467
5544
|
porkbunProvider = {
|
|
5468
5545
|
id: "porkbun",
|
|
@@ -5511,6 +5588,7 @@ var API, PAGE_SIZE, spaceshipProvider;
|
|
|
5511
5588
|
var init_spaceship = __esmMin((() => {
|
|
5512
5589
|
init_types();
|
|
5513
5590
|
init_zone();
|
|
5591
|
+
init_error_message();
|
|
5514
5592
|
API = "https://spaceship.dev/api/v1/dns/records";
|
|
5515
5593
|
PAGE_SIZE = 500;
|
|
5516
5594
|
spaceshipProvider = {
|
|
@@ -5555,7 +5633,7 @@ var init_spaceship = __esmMin((() => {
|
|
|
5555
5633
|
if (found !== void 0 || items.length < PAGE_SIZE) break;
|
|
5556
5634
|
}
|
|
5557
5635
|
if (found?.address === record.ip) return succeeded(`already ${record.ip}`, false);
|
|
5558
|
-
|
|
5636
|
+
return await wrote(await context.fetch(`${API}/${encodeURIComponent(zone)}`, {
|
|
5559
5637
|
method: "PUT",
|
|
5560
5638
|
headers,
|
|
5561
5639
|
body: JSON.stringify({
|
|
@@ -5568,11 +5646,9 @@ var init_spaceship = __esmMin((() => {
|
|
|
5568
5646
|
}]
|
|
5569
5647
|
}),
|
|
5570
5648
|
signal: context.signal
|
|
5571
|
-
});
|
|
5572
|
-
if (!written.ok) return failed(await errorText(written));
|
|
5573
|
-
return succeeded(`set ${record.host} ${record.type} to ${record.ip}`, true);
|
|
5649
|
+
}), record);
|
|
5574
5650
|
} catch (error) {
|
|
5575
|
-
return failed(
|
|
5651
|
+
return failed(errorMessage(error));
|
|
5576
5652
|
}
|
|
5577
5653
|
}
|
|
5578
5654
|
};
|
|
@@ -6553,7 +6629,7 @@ function describeTelegramError(error) {
|
|
|
6553
6629
|
const description = candidate.description ?? candidate.message;
|
|
6554
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`}`;
|
|
6555
6631
|
}
|
|
6556
|
-
return
|
|
6632
|
+
return errorMessage(error);
|
|
6557
6633
|
}
|
|
6558
6634
|
async function sendTelegramMessage(token, chatId, html) {
|
|
6559
6635
|
try {
|
|
@@ -6621,6 +6697,7 @@ async function listTelegramChats(token) {
|
|
|
6621
6697
|
}
|
|
6622
6698
|
var bots;
|
|
6623
6699
|
var init_telegram = __esmMin((() => {
|
|
6700
|
+
init_error_message();
|
|
6624
6701
|
bots = /* @__PURE__ */ new Map();
|
|
6625
6702
|
}));
|
|
6626
6703
|
//#endregion
|
|
@@ -7779,15 +7856,15 @@ function acmeIssuers(acme, challenge = {}) {
|
|
|
7779
7856
|
}, { module: "internal" }];
|
|
7780
7857
|
}
|
|
7781
7858
|
/**
|
|
7782
|
-
* The page
|
|
7783
|
-
*
|
|
7784
|
-
* 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.
|
|
7785
7862
|
*/
|
|
7786
|
-
function
|
|
7863
|
+
function htmlPage(title, heading, body) {
|
|
7787
7864
|
return `<!doctype html>
|
|
7788
7865
|
<html lang="en">
|
|
7789
7866
|
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7790
|
-
<title
|
|
7867
|
+
<title>${title}</title>
|
|
7791
7868
|
<style>
|
|
7792
7869
|
:root { color-scheme: light dark }
|
|
7793
7870
|
body { margin: 0; min-height: 100dvh; display: grid; place-items: center; font: 15px/1.6 system-ui, sans-serif }
|
|
@@ -7798,13 +7875,21 @@ function notConfiguredPage() {
|
|
|
7798
7875
|
footer { color: #888; font-size: .85em }
|
|
7799
7876
|
</style></head>
|
|
7800
7877
|
<body><main>
|
|
7801
|
-
<h1
|
|
7802
|
-
|
|
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
|
|
7803
7889
|
<code>{http.request.host}</code>, so there is nothing here to serve.</p>
|
|
7804
7890
|
<p>Add a route for it under <strong>Others → Reverse Proxy</strong>, or check that you
|
|
7805
7891
|
reached the hostname you meant to.</p>
|
|
7806
|
-
<footer>home-hosted reverse proxy</footer
|
|
7807
|
-
</main></body></html>`;
|
|
7892
|
+
<footer>home-hosted reverse proxy</footer>`);
|
|
7808
7893
|
}
|
|
7809
7894
|
/** The catch-all: it answers last, so every configured route wins over it. */
|
|
7810
7895
|
function notConfiguredRoute() {
|
|
@@ -7817,30 +7902,14 @@ function notConfiguredRoute() {
|
|
|
7817
7902
|
}
|
|
7818
7903
|
/** The page a visitor gets when a name has no certificate yet and HTTPS cannot work. */
|
|
7819
7904
|
function noticePage(host, httpPort, httpsPort) {
|
|
7820
|
-
return
|
|
7821
|
-
<html lang="en">
|
|
7822
|
-
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7823
|
-
<title>Certificate not ready — ${host}</title>
|
|
7824
|
-
<style>
|
|
7825
|
-
:root { color-scheme: light dark }
|
|
7826
|
-
body { margin: 0; min-height: 100dvh; display: grid; place-items: center; font: 15px/1.6 system-ui, sans-serif }
|
|
7827
|
-
main { max-width: 34rem; padding: 2rem }
|
|
7828
|
-
h1 { font-size: 1.15rem; margin: 0 0 .75rem }
|
|
7829
|
-
p { margin: 0 0 .75rem }
|
|
7830
|
-
code { font-family: ui-monospace, monospace; font-size: .9em }
|
|
7831
|
-
footer { color: #888; font-size: .85em }
|
|
7832
|
-
</style></head>
|
|
7833
|
-
<body><main>
|
|
7834
|
-
<h1>No certificate yet for ${host}</h1>
|
|
7835
|
-
<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
|
|
7836
7906
|
for it yet, so HTTPS cannot complete a handshake. The engine keeps trying in the
|
|
7837
7907
|
background and will serve the name as soon as it succeeds.</p>
|
|
7838
7908
|
<p>The usual cause is that the CA cannot reach this machine: with the proxy on
|
|
7839
7909
|
<code>${httpPort}</code>/<code>${httpsPort}</code>, forward <code>80 → ${httpPort}</code>
|
|
7840
7910
|
and <code>443 → ${httpsPort}</code> on your router. Otherwise <code>http://${host}</code>
|
|
7841
7911
|
will keep landing here.</p>
|
|
7842
|
-
<footer>Details, and the engine's own reason, are in the panel: Others → Reverse Proxy.</footer
|
|
7843
|
-
</main></body></html>`;
|
|
7912
|
+
<footer>Details, and the engine's own reason, are in the panel: Others → Reverse Proxy.</footer>`);
|
|
7844
7913
|
}
|
|
7845
7914
|
/**
|
|
7846
7915
|
* The same match, minus the ACME challenge path: the CA has to be able to read it on
|
|
@@ -8026,7 +8095,7 @@ function validatePair(certificate, privateKey) {
|
|
|
8026
8095
|
} catch (error) {
|
|
8027
8096
|
return {
|
|
8028
8097
|
ok: false,
|
|
8029
|
-
error: `certificate is not a valid PEM: ${
|
|
8098
|
+
error: `certificate is not a valid PEM: ${errorMessage(error)}`
|
|
8030
8099
|
};
|
|
8031
8100
|
}
|
|
8032
8101
|
try {
|
|
@@ -8037,7 +8106,7 @@ function validatePair(certificate, privateKey) {
|
|
|
8037
8106
|
} catch (error) {
|
|
8038
8107
|
return {
|
|
8039
8108
|
ok: false,
|
|
8040
|
-
error: `private key is not a valid PEM: ${
|
|
8109
|
+
error: `private key is not a valid PEM: ${errorMessage(error)}`
|
|
8041
8110
|
};
|
|
8042
8111
|
}
|
|
8043
8112
|
const invalid = validityError(x509);
|
|
@@ -8051,6 +8120,7 @@ var TlsStore;
|
|
|
8051
8120
|
var init_tls$1 = __esmMin((() => {
|
|
8052
8121
|
init_atomic();
|
|
8053
8122
|
init_logger();
|
|
8123
|
+
init_error_message();
|
|
8054
8124
|
TlsStore = class {
|
|
8055
8125
|
dir;
|
|
8056
8126
|
name;
|
|
@@ -8177,7 +8247,7 @@ var init_tls$1 = __esmMin((() => {
|
|
|
8177
8247
|
} catch (error) {
|
|
8178
8248
|
return {
|
|
8179
8249
|
...base,
|
|
8180
|
-
error: `invalid certificate: ${
|
|
8250
|
+
error: `invalid certificate: ${errorMessage(error)}`
|
|
8181
8251
|
};
|
|
8182
8252
|
}
|
|
8183
8253
|
}
|
|
@@ -8336,9 +8406,11 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8336
8406
|
init_types();
|
|
8337
8407
|
init_nanny$2();
|
|
8338
8408
|
init_port();
|
|
8409
|
+
init_proc();
|
|
8339
8410
|
init_proxy$2();
|
|
8340
8411
|
init_proxy_config();
|
|
8341
8412
|
init_tls$1();
|
|
8413
|
+
init_error_message();
|
|
8342
8414
|
init_proxy_form();
|
|
8343
8415
|
PROXY_ID = "proxy";
|
|
8344
8416
|
ADMIN_TIMEOUT_MS = 5e3;
|
|
@@ -8380,8 +8452,6 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8380
8452
|
failureCache = null;
|
|
8381
8453
|
/** The installed binary's modules, read once per process; an install clears it. */
|
|
8382
8454
|
moduleCache = null;
|
|
8383
|
-
/** A legacy `engine.json` is reported once, not on every state frame. */
|
|
8384
|
-
legacyChecksumWarned = false;
|
|
8385
8455
|
/** Why the engine's nanny could not be spawned, cleared on the next start. */
|
|
8386
8456
|
nannyError = null;
|
|
8387
8457
|
certCache = null;
|
|
@@ -8670,7 +8740,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8670
8740
|
} catch (error) {
|
|
8671
8741
|
return {
|
|
8672
8742
|
...base,
|
|
8673
|
-
error: `unreadable certificate: ${
|
|
8743
|
+
error: `unreadable certificate: ${errorMessage(error)}`
|
|
8674
8744
|
};
|
|
8675
8745
|
}
|
|
8676
8746
|
});
|
|
@@ -8882,14 +8952,14 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
8882
8952
|
if (this.live()) {
|
|
8883
8953
|
logger.info(`proxy: reattached to the running ${this.config.engine} engine`);
|
|
8884
8954
|
await this.apply().catch((error) => {
|
|
8885
|
-
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)}`);
|
|
8886
8956
|
});
|
|
8887
8957
|
return;
|
|
8888
8958
|
}
|
|
8889
8959
|
try {
|
|
8890
8960
|
await this.start();
|
|
8891
8961
|
} catch (error) {
|
|
8892
|
-
this.lastError =
|
|
8962
|
+
this.lastError = errorMessage(error);
|
|
8893
8963
|
logger.error(`proxy: could not start: ${this.lastError}`);
|
|
8894
8964
|
}
|
|
8895
8965
|
}
|
|
@@ -9117,7 +9187,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9117
9187
|
*/
|
|
9118
9188
|
async postConfiguration(rendered) {
|
|
9119
9189
|
const result = await this.request("POST", "/load", rendered).catch((error) => {
|
|
9120
|
-
this.lastError = `the engine did not accept the configuration: ${
|
|
9190
|
+
this.lastError = `the engine did not accept the configuration: ${errorMessage(error)}`;
|
|
9121
9191
|
throw new DetailedError(this.lastError, {
|
|
9122
9192
|
statusCode: 502,
|
|
9123
9193
|
code: "ENGINE_UNREACHABLE"
|
|
@@ -9185,7 +9255,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9185
9255
|
this.certificateSignature = signature;
|
|
9186
9256
|
if (first) return;
|
|
9187
9257
|
await this.apply().catch((error) => {
|
|
9188
|
-
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)}`);
|
|
9189
9259
|
});
|
|
9190
9260
|
}
|
|
9191
9261
|
/**
|
|
@@ -9392,15 +9462,7 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9392
9462
|
try {
|
|
9393
9463
|
const parsed = JSON.parse(fs.readFileSync(path.join(this.options.binDir, "engine.json"), "utf8"));
|
|
9394
9464
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
9395
|
-
const
|
|
9396
|
-
if ("sha256" in record) {
|
|
9397
|
-
delete record.sha256;
|
|
9398
|
-
if (!this.legacyChecksumWarned) {
|
|
9399
|
-
this.legacyChecksumWarned = true;
|
|
9400
|
-
logger.warn("proxy: dropped the recorded engine checksum; nothing verifies it");
|
|
9401
|
-
}
|
|
9402
|
-
}
|
|
9403
|
-
const parsedRecord = engineRecordSchema(record);
|
|
9465
|
+
const parsedRecord = engineRecordSchema(parsed);
|
|
9404
9466
|
return parsedRecord instanceof type.errors ? null : parsedRecord;
|
|
9405
9467
|
} catch {
|
|
9406
9468
|
return null;
|
|
@@ -9731,7 +9793,9 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9731
9793
|
const statePath = nannyStatePath(this.options.stateDir, PROXY_ID);
|
|
9732
9794
|
const state = readNannyState(statePath);
|
|
9733
9795
|
if (state !== null && state.serverId === PROXY_ID) {
|
|
9734
|
-
const
|
|
9796
|
+
const nanny = await nannyIsAlive(state, PROXY_ID);
|
|
9797
|
+
const child = state.childPid !== null && state.childPid > 0 && await processCarriesServerId(state.childPid, PROXY_ID);
|
|
9798
|
+
const pids = [...nanny ? [state.nannyPid] : [], ...nanny || child ? [state.childPid] : []].filter((pid) => pid !== null && pid > 0);
|
|
9735
9799
|
for (const pid of pids) {
|
|
9736
9800
|
if (!isProcessAlive(pid)) continue;
|
|
9737
9801
|
try {
|
|
@@ -9742,10 +9806,12 @@ var init_proxy$1 = __esmMin((() => {
|
|
|
9742
9806
|
}
|
|
9743
9807
|
const deadline = Date.now() + (this.options.stopGraceMs ?? 1e4);
|
|
9744
9808
|
while (Date.now() < deadline && pids.some((pid) => isProcessAlive(pid))) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
9745
|
-
|
|
9809
|
+
const survivors = pids.filter(isProcessAlive);
|
|
9810
|
+
for (const pid of survivors) try {
|
|
9746
9811
|
process.kill(pid, "SIGKILL");
|
|
9747
9812
|
} catch {}
|
|
9748
|
-
|
|
9813
|
+
if (survivors.some(isProcessAlive)) logger.warn(`proxy: pid ${survivors.join(", ")} ignored SIGKILL — keeping ${path.basename(statePath)} so a later boot can still stop it`);
|
|
9814
|
+
else fs.rmSync(statePath, { force: true });
|
|
9749
9815
|
}
|
|
9750
9816
|
this.nanny = null;
|
|
9751
9817
|
this.admin = null;
|
|
@@ -9947,7 +10013,7 @@ function createProxyRoute(deps) {
|
|
|
9947
10013
|
if (proxy().status().state === "running") await proxy().apply();
|
|
9948
10014
|
else await proxy().start();
|
|
9949
10015
|
} catch (error) {
|
|
9950
|
-
proxy().recordError(
|
|
10016
|
+
proxy().recordError(errorMessage(error));
|
|
9951
10017
|
throw error;
|
|
9952
10018
|
}
|
|
9953
10019
|
};
|
|
@@ -10106,6 +10172,7 @@ var init_proxy = __esmMin((() => {
|
|
|
10106
10172
|
init_exposure();
|
|
10107
10173
|
init_proxy$1();
|
|
10108
10174
|
init_contracts();
|
|
10175
|
+
init_error_message();
|
|
10109
10176
|
certificateParam = type({ id: "/^[a-z0-9][a-z0-9_-]*$/" });
|
|
10110
10177
|
}));
|
|
10111
10178
|
//#endregion
|
|
@@ -11011,237 +11078,26 @@ var init_app = __esmMin((() => {
|
|
|
11011
11078
|
}));
|
|
11012
11079
|
//#endregion
|
|
11013
11080
|
//#region src/config/layout.ts
|
|
11014
|
-
var layout_exports = /* @__PURE__ */ __exportAll({
|
|
11015
|
-
ensureLayout: () => ensureLayout,
|
|
11016
|
-
migrateLayout: () => migrateLayout
|
|
11017
|
-
});
|
|
11018
|
-
function readJson(file) {
|
|
11019
|
-
try {
|
|
11020
|
-
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
11021
|
-
return isRecord(parsed) ? parsed : null;
|
|
11022
|
-
} catch {
|
|
11023
|
-
return null;
|
|
11024
|
-
}
|
|
11025
|
-
}
|
|
11081
|
+
var layout_exports = /* @__PURE__ */ __exportAll({ ensureLayout: () => ensureLayout });
|
|
11026
11082
|
/**
|
|
11027
|
-
*
|
|
11083
|
+
* Refuses a state directory this release cannot use, before any command resolves a
|
|
11084
|
+
* path into it.
|
|
11028
11085
|
*
|
|
11029
|
-
*
|
|
11030
|
-
*
|
|
11031
|
-
*
|
|
11032
|
-
*
|
|
11033
|
-
*
|
|
11034
|
-
*/
|
|
11035
|
-
function readLegacy(file) {
|
|
11036
|
-
if (!fs.existsSync(file)) return {
|
|
11037
|
-
parsed: null,
|
|
11038
|
-
unreadable: false
|
|
11039
|
-
};
|
|
11040
|
-
const parsed = readJson(file);
|
|
11041
|
-
return {
|
|
11042
|
-
parsed,
|
|
11043
|
-
unreadable: parsed === null
|
|
11044
|
-
};
|
|
11045
|
-
}
|
|
11046
|
-
function writeJson(file, value, mode) {
|
|
11047
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
11048
|
-
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, mode === void 0 ? {} : { mode });
|
|
11049
|
-
}
|
|
11050
|
-
/** `renameSync` across devices fails with EXDEV; fall back to a copy. */
|
|
11051
|
-
function move(source, destination, warnings) {
|
|
11052
|
-
if (!fs.existsSync(source)) return false;
|
|
11053
|
-
if (fs.existsSync(destination)) {
|
|
11054
|
-
warnings.push(`${destination} already exists — left ${source} alone`);
|
|
11055
|
-
return false;
|
|
11056
|
-
}
|
|
11057
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
11058
|
-
try {
|
|
11059
|
-
fs.renameSync(source, destination);
|
|
11060
|
-
} catch {
|
|
11061
|
-
fs.cpSync(source, destination, {
|
|
11062
|
-
recursive: true,
|
|
11063
|
-
force: true
|
|
11064
|
-
});
|
|
11065
|
-
fs.rmSync(source, {
|
|
11066
|
-
recursive: true,
|
|
11067
|
-
force: true
|
|
11068
|
-
});
|
|
11069
|
-
}
|
|
11070
|
-
return true;
|
|
11071
|
-
}
|
|
11072
|
-
function metaOf(raw) {
|
|
11073
|
-
const meta = raw?.meta;
|
|
11074
|
-
if (typeof meta !== "object" || meta === null) return void 0;
|
|
11075
|
-
return meta;
|
|
11076
|
-
}
|
|
11077
|
-
/** The old config split three ways: global settings, workspace settings, servers. */
|
|
11078
|
-
function splitConfig(raw) {
|
|
11079
|
-
const meta = metaOf(raw);
|
|
11080
|
-
const unknown = {};
|
|
11081
|
-
for (const [key, value] of Object.entries(raw)) if (!KNOWN_TOP_LEVEL.has(key)) unknown[key] = value;
|
|
11082
|
-
return {
|
|
11083
|
-
global: {
|
|
11084
|
-
$schema: "./settings.schema.json",
|
|
11085
|
-
...meta === void 0 ? {} : { meta },
|
|
11086
|
-
control: raw.control ?? {},
|
|
11087
|
-
host: raw.host ?? {},
|
|
11088
|
-
backups: raw.backups ?? {},
|
|
11089
|
-
...unknown
|
|
11090
|
-
},
|
|
11091
|
-
settings: {
|
|
11092
|
-
$schema: "./settings.schema.json",
|
|
11093
|
-
...meta === void 0 ? {} : { meta },
|
|
11094
|
-
defaults: raw.defaults ?? {},
|
|
11095
|
-
logs: raw.logs ?? {},
|
|
11096
|
-
notifications: raw.notifications ?? {},
|
|
11097
|
-
ddns: raw.ddns ?? {}
|
|
11098
|
-
},
|
|
11099
|
-
servers: {
|
|
11100
|
-
$schema: "./servers.config.schema.json",
|
|
11101
|
-
...meta === void 0 ? {} : { meta },
|
|
11102
|
-
servers: Array.isArray(raw.servers) ? raw.servers : []
|
|
11103
|
-
}
|
|
11104
|
-
};
|
|
11105
|
-
}
|
|
11106
|
-
/** The old secrets file split global (password, token) vs. workspace (Telegram, DDNS). */
|
|
11107
|
-
function splitSecrets(raw) {
|
|
11108
|
-
return {
|
|
11109
|
-
global: {
|
|
11110
|
-
version: 3,
|
|
11111
|
-
password: raw?.password ?? null,
|
|
11112
|
-
apiToken: raw?.apiToken ?? null
|
|
11113
|
-
},
|
|
11114
|
-
workspace: {
|
|
11115
|
-
version: 3,
|
|
11116
|
-
telegram: raw?.telegram ?? null,
|
|
11117
|
-
ddns: raw?.ddns ?? {},
|
|
11118
|
-
ddnsKdf: raw?.ddnsKdf ?? null
|
|
11119
|
-
}
|
|
11120
|
-
};
|
|
11121
|
-
}
|
|
11122
|
-
/**
|
|
11123
|
-
* Moves the panel's console log apart from the per-server logs: both used to
|
|
11124
|
-
* live in `<root>/.logs`, and only the console log is global now.
|
|
11125
|
-
*/
|
|
11126
|
-
function splitLogsDir(warnings) {
|
|
11127
|
-
if (!fs.existsSync(legacy.logsDir)) return;
|
|
11128
|
-
const workspaceLogs = path.join(hhDir, "default", ".logs");
|
|
11129
|
-
for (const name of fs.readdirSync(legacy.logsDir)) {
|
|
11130
|
-
const destination = name === "home-hosted.log" || name.startsWith("home-hosted.log.") ? path.join(hhDir, ".logs", name) : path.join(workspaceLogs, name);
|
|
11131
|
-
move(path.join(legacy.logsDir, name), destination, warnings);
|
|
11132
|
-
}
|
|
11133
|
-
try {
|
|
11134
|
-
if (fs.readdirSync(legacy.logsDir).length === 0) fs.rmdirSync(legacy.logsDir);
|
|
11135
|
-
else warnings.push(`${legacy.logsDir} still holds files that could not be moved — left it in place`);
|
|
11136
|
-
} catch {}
|
|
11137
|
-
}
|
|
11138
|
-
function migrateLayout() {
|
|
11139
|
-
if (!hasLegacyLayout()) return EMPTY;
|
|
11140
|
-
const moved = [];
|
|
11141
|
-
const warnings = [];
|
|
11142
|
-
fs.mkdirSync(hhDir, { recursive: true });
|
|
11143
|
-
const legacyConfig = readLegacy(legacy.configPath);
|
|
11144
|
-
let configSplit = false;
|
|
11145
|
-
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`);
|
|
11146
|
-
if (legacyConfig.parsed !== null) {
|
|
11147
|
-
const split = splitConfig(legacyConfig.parsed);
|
|
11148
|
-
const destinations = [
|
|
11149
|
-
path.join(hhDir, "settings.json"),
|
|
11150
|
-
path.join(hhDir, "default", "settings.json"),
|
|
11151
|
-
path.join(hhDir, "default", "servers.config.json")
|
|
11152
|
-
];
|
|
11153
|
-
const taken = destinations.filter((file) => fs.existsSync(file));
|
|
11154
|
-
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`);
|
|
11155
|
-
else {
|
|
11156
|
-
writeJson(destinations[0], split.global);
|
|
11157
|
-
fs.mkdirSync(path.join(hhDir, "default"), { recursive: true });
|
|
11158
|
-
writeJson(destinations[1], split.settings);
|
|
11159
|
-
writeJson(destinations[2], split.servers);
|
|
11160
|
-
moved.push("servers.config.json → settings.json + default/settings.json + default/servers.config.json");
|
|
11161
|
-
configSplit = true;
|
|
11162
|
-
}
|
|
11163
|
-
}
|
|
11164
|
-
const legacySecrets = readLegacy(legacy.secretsPath);
|
|
11165
|
-
let secretsSplit = false;
|
|
11166
|
-
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`);
|
|
11167
|
-
if (legacySecrets.parsed !== null) {
|
|
11168
|
-
const split = splitSecrets(legacySecrets.parsed);
|
|
11169
|
-
const destinations = [path.join(hhDir, ".control-secrets.json"), path.join(hhDir, "default", ".secrets.json")];
|
|
11170
|
-
const taken = destinations.filter((file) => fs.existsSync(file));
|
|
11171
|
-
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`);
|
|
11172
|
-
else {
|
|
11173
|
-
writeJson(destinations[0], split.global, 384);
|
|
11174
|
-
fs.mkdirSync(path.join(hhDir, "default"), { recursive: true });
|
|
11175
|
-
writeJson(destinations[1], split.workspace, 384);
|
|
11176
|
-
moved.push(".control-secrets.json → .hh/.control-secrets.json + .hh/default/.secrets.json");
|
|
11177
|
-
secretsSplit = true;
|
|
11178
|
-
}
|
|
11179
|
-
}
|
|
11180
|
-
if (move(legacy.tlsDir, path.join(hhDir, ".tls"), warnings)) moved.push(".tls/ → .hh/.tls/");
|
|
11181
|
-
if (move(legacy.uiDir, path.join(hhDir, ".ui"), warnings)) moved.push(".ui/ → .hh/.ui/");
|
|
11182
|
-
if (move(legacy.backupsDir, path.join(hhDir, ".backups"), warnings)) moved.push(".backups/ → .hh/.backups/");
|
|
11183
|
-
if (move(legacy.runtimePath, path.join(hhDir, "run.json"), warnings)) moved.push("run.json → .hh/run.json");
|
|
11184
|
-
if (move(legacy.stateDir, path.join(hhDir, "default", ".state"), warnings)) moved.push(".state/ → .hh/default/.state/");
|
|
11185
|
-
splitLogsDir(warnings);
|
|
11186
|
-
if (configSplit) fs.rmSync(legacy.configPath, { force: true });
|
|
11187
|
-
if (secretsSplit) fs.rmSync(legacy.secretsPath, { force: true });
|
|
11188
|
-
fs.rmSync(legacy.configSchemaPath, { force: true });
|
|
11189
|
-
if (!fs.existsSync(workspacesPath)) {
|
|
11190
|
-
writeJson(workspacesPath, {
|
|
11191
|
-
$schema: "./workspaces.schema.json",
|
|
11192
|
-
workspaces: [{
|
|
11193
|
-
id: "default",
|
|
11194
|
-
label: "Default"
|
|
11195
|
-
}]
|
|
11196
|
-
});
|
|
11197
|
-
moved.push("workspaces.json created with the `default` workspace");
|
|
11198
|
-
} else warnings.push(`${workspacesPath} already exists — left the workspace registry alone`);
|
|
11199
|
-
writeJson(path.join(hhDir, ".layout-migration.json"), {
|
|
11200
|
-
version: 1,
|
|
11201
|
-
at: Date.now(),
|
|
11202
|
-
moved
|
|
11203
|
-
});
|
|
11204
|
-
return {
|
|
11205
|
-
migrated: true,
|
|
11206
|
-
moved,
|
|
11207
|
-
warnings
|
|
11208
|
-
};
|
|
11209
|
-
}
|
|
11210
|
-
/**
|
|
11211
|
-
* Runs the relocation and reports it through the logger. Every CLI entry point
|
|
11212
|
-
* calls this before it resolves a state path, so an existing instance keeps
|
|
11213
|
-
* 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.
|
|
11214
11091
|
*/
|
|
11215
11092
|
function ensureLayout() {
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
}
|
|
11221
|
-
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.`);
|
|
11222
11097
|
}
|
|
11223
|
-
var EMPTY, KNOWN_TOP_LEVEL;
|
|
11224
11098
|
var init_layout = __esmMin((() => {
|
|
11225
|
-
init_logger();
|
|
11226
11099
|
init_paths();
|
|
11227
|
-
|
|
11228
|
-
EMPTY = {
|
|
11229
|
-
migrated: false,
|
|
11230
|
-
moved: [],
|
|
11231
|
-
warnings: []
|
|
11232
|
-
};
|
|
11233
|
-
KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
11234
|
-
"$schema",
|
|
11235
|
-
"meta",
|
|
11236
|
-
"control",
|
|
11237
|
-
"defaults",
|
|
11238
|
-
"logs",
|
|
11239
|
-
"notifications",
|
|
11240
|
-
"host",
|
|
11241
|
-
"backups",
|
|
11242
|
-
"ddns",
|
|
11243
|
-
"servers"
|
|
11244
|
-
]);
|
|
11100
|
+
init_version();
|
|
11245
11101
|
}));
|
|
11246
11102
|
//#endregion
|
|
11247
11103
|
//#region src/helpers/daemon.ts
|
|
@@ -11573,7 +11429,7 @@ function loadEnvFile(file) {
|
|
|
11573
11429
|
return {
|
|
11574
11430
|
env: {},
|
|
11575
11431
|
path: file,
|
|
11576
|
-
error:
|
|
11432
|
+
error: errorMessage(error)
|
|
11577
11433
|
};
|
|
11578
11434
|
}
|
|
11579
11435
|
}
|
|
@@ -11593,6 +11449,7 @@ function resolveEnvFilePath(file, cwd) {
|
|
|
11593
11449
|
}
|
|
11594
11450
|
var VARIABLE;
|
|
11595
11451
|
var init_env_file = __esmMin((() => {
|
|
11452
|
+
init_error_message();
|
|
11596
11453
|
VARIABLE = /\$\{([A-Z_]\w*)\}/gi;
|
|
11597
11454
|
}));
|
|
11598
11455
|
//#endregion
|
|
@@ -11853,7 +11710,7 @@ async function probeHttp(host, port, options) {
|
|
|
11853
11710
|
return {
|
|
11854
11711
|
healthy: false,
|
|
11855
11712
|
ms: Date.now() - started,
|
|
11856
|
-
detail: `request failed: ${
|
|
11713
|
+
detail: `request failed: ${errorMessage(error)}`
|
|
11857
11714
|
};
|
|
11858
11715
|
}
|
|
11859
11716
|
}
|
|
@@ -11875,6 +11732,7 @@ async function probeHealth(options) {
|
|
|
11875
11732
|
}
|
|
11876
11733
|
var init_health_check = __esmMin((() => {
|
|
11877
11734
|
init_port();
|
|
11735
|
+
init_error_message();
|
|
11878
11736
|
}));
|
|
11879
11737
|
//#endregion
|
|
11880
11738
|
//#region src/providers/process.ts
|
|
@@ -11962,28 +11820,24 @@ function signalPid(pid, signal, killGroup) {
|
|
|
11962
11820
|
process.kill(pid, signal);
|
|
11963
11821
|
} catch {}
|
|
11964
11822
|
}
|
|
11965
|
-
function alive(pid) {
|
|
11966
|
-
try {
|
|
11967
|
-
process.kill(pid, 0);
|
|
11968
|
-
return true;
|
|
11969
|
-
} catch {
|
|
11970
|
-
return false;
|
|
11971
|
-
}
|
|
11972
|
-
}
|
|
11973
11823
|
/**
|
|
11974
11824
|
* The same shutdown a supervised child gets, for a process we adopted instead of
|
|
11975
11825
|
* spawned: a detached successor is not our child, so it is signalled by pid (and
|
|
11976
11826
|
* by process group when asked) and its exit is polled rather than awaited.
|
|
11827
|
+
*
|
|
11828
|
+
* Liveness comes from `isProcessAlive`, never a local signal-0 copy: a second definition returned
|
|
11829
|
+
* `false` for `EPERM`, so a process that was there but not ours to signal read as "exited" and
|
|
11830
|
+
* `'force-killed'` was returned for one still running. One predicate, one answer.
|
|
11977
11831
|
*/
|
|
11978
11832
|
async function terminatePid(pid, options) {
|
|
11979
|
-
if (!
|
|
11833
|
+
if (!isProcessAlive(pid)) return "exited";
|
|
11980
11834
|
signalPid(pid, options.signal, options.killGroup);
|
|
11981
11835
|
const deadline = Date.now() + Math.max(0, options.graceMs);
|
|
11982
|
-
while (Date.now() < deadline &&
|
|
11983
|
-
if (!
|
|
11836
|
+
while (Date.now() < deadline && isProcessAlive(pid)) await delay$1(50);
|
|
11837
|
+
if (!isProcessAlive(pid)) return "exited";
|
|
11984
11838
|
signalPid(pid, "SIGKILL", options.killGroup);
|
|
11985
11839
|
const hardDeadline = Date.now() + 2e3;
|
|
11986
|
-
while (Date.now() < hardDeadline &&
|
|
11840
|
+
while (Date.now() < hardDeadline && isProcessAlive(pid)) await delay$1(50);
|
|
11987
11841
|
return "force-killed";
|
|
11988
11842
|
}
|
|
11989
11843
|
function delay$1(ms) {
|
|
@@ -12007,6 +11861,7 @@ function waitForExit$1(child, timeoutMs) {
|
|
|
12007
11861
|
var execFileAsync$1, WINDOWS_SHIM_EXTENSIONS;
|
|
12008
11862
|
var init_process = __esmMin((() => {
|
|
12009
11863
|
init_paths();
|
|
11864
|
+
init_port();
|
|
12010
11865
|
execFileAsync$1 = promisify(execFile);
|
|
12011
11866
|
WINDOWS_SHIM_EXTENSIONS = [
|
|
12012
11867
|
".cmd",
|
|
@@ -12296,6 +12151,7 @@ var TAIL_INTERVAL_MS, LogRelay;
|
|
|
12296
12151
|
var init_log_relay = __esmMin((() => {
|
|
12297
12152
|
init_logger();
|
|
12298
12153
|
init_log_tail();
|
|
12154
|
+
init_error_message();
|
|
12299
12155
|
TAIL_INTERVAL_MS = 250;
|
|
12300
12156
|
LogRelay = class {
|
|
12301
12157
|
options;
|
|
@@ -12356,7 +12212,7 @@ var init_log_relay = __esmMin((() => {
|
|
|
12356
12212
|
try {
|
|
12357
12213
|
this.poll();
|
|
12358
12214
|
} catch (error) {
|
|
12359
|
-
logger.warn(`tail failed: ${
|
|
12215
|
+
logger.warn(`tail failed: ${errorMessage(error)}`);
|
|
12360
12216
|
}
|
|
12361
12217
|
}, this.options.intervalMs ?? TAIL_INTERVAL_MS);
|
|
12362
12218
|
this.timer.unref();
|
|
@@ -12823,6 +12679,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
12823
12679
|
nannyExit: null,
|
|
12824
12680
|
starting: false,
|
|
12825
12681
|
stopping: false,
|
|
12682
|
+
removed: false,
|
|
12826
12683
|
bootstrapDone: !config.bootstrap,
|
|
12827
12684
|
logs: new LogBuffer(config.logBufferLines),
|
|
12828
12685
|
echoed: /* @__PURE__ */ new Set(),
|
|
@@ -12841,6 +12698,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
12841
12698
|
this.relay.unfollow(id);
|
|
12842
12699
|
this.options.logFiles.clear(id);
|
|
12843
12700
|
this.options.history.forget(id);
|
|
12701
|
+
entry.removed = true;
|
|
12844
12702
|
this.stopEntry(entry).catch((error) => {
|
|
12845
12703
|
logger.error(`could not stop the removed server ${id}`, error);
|
|
12846
12704
|
});
|
|
@@ -12938,21 +12796,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
12938
12796
|
return { kind: "free" };
|
|
12939
12797
|
}
|
|
12940
12798
|
this.log(entry, "system", `port ${port} is held by pid ${foreign.join(", ")} — onPortConflict: kill, stopping the holder`);
|
|
12941
|
-
|
|
12942
|
-
if (forced.length > 0) this.log(entry, "system", `pid ${forced.join(", ")} ignored SIGTERM and was killed`);
|
|
12943
|
-
await delay(PORT_RELEASE_RECHECK_MS);
|
|
12944
|
-
if (await freeOnAll()) {
|
|
12945
|
-
entry.portState = "free";
|
|
12946
|
-
return { kind: "free" };
|
|
12947
|
-
}
|
|
12948
|
-
entry.status = "conflict";
|
|
12949
|
-
entry.lastError = `port ${port} is still in use after killing pid ${foreign.join(", ")}`;
|
|
12950
|
-
this.log(entry, "system", entry.lastError);
|
|
12951
|
-
this.publishServer(entry);
|
|
12952
|
-
return {
|
|
12953
|
-
kind: "blocked",
|
|
12954
|
-
error: entry.lastError
|
|
12955
|
-
};
|
|
12799
|
+
return await this.stopPortHolders(entry, foreign, port, "killing", freeOnAll);
|
|
12956
12800
|
}
|
|
12957
12801
|
if (own !== null && entry.config.onPortConflict === "follow") return {
|
|
12958
12802
|
kind: "adopt",
|
|
@@ -12960,21 +12804,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
12960
12804
|
};
|
|
12961
12805
|
if (own !== null && entry.config.onPortConflict === "reclaim") {
|
|
12962
12806
|
this.log(entry, "system", `port ${port} is held by pid ${own}, a detached restart of this entry — replacing it with a supervised process`);
|
|
12963
|
-
|
|
12964
|
-
if (forced.length > 0) this.log(entry, "system", `pid ${forced.join(", ")} ignored SIGTERM and was killed`);
|
|
12965
|
-
await delay(PORT_RELEASE_RECHECK_MS);
|
|
12966
|
-
if (await freeOnAll()) {
|
|
12967
|
-
entry.portState = "free";
|
|
12968
|
-
return { kind: "free" };
|
|
12969
|
-
}
|
|
12970
|
-
entry.status = "conflict";
|
|
12971
|
-
entry.lastError = `port ${port} is still in use after replacing pid ${own}`;
|
|
12972
|
-
this.log(entry, "system", entry.lastError);
|
|
12973
|
-
this.publishServer(entry);
|
|
12974
|
-
return {
|
|
12975
|
-
kind: "blocked",
|
|
12976
|
-
error: entry.lastError
|
|
12977
|
-
};
|
|
12807
|
+
return await this.stopPortHolders(entry, [own], port, "replacing", freeOnAll);
|
|
12978
12808
|
}
|
|
12979
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` : "";
|
|
12980
12810
|
if (entry.config.onPortConflict !== "warn") {
|
|
@@ -13000,6 +12830,31 @@ var init_supervisor = __esmMin((() => {
|
|
|
13000
12830
|
* persistent entry whose state file was lost would show its history but never
|
|
13001
12831
|
* another live line.
|
|
13002
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
|
+
}
|
|
13003
12858
|
adoptEntry(entry, pid) {
|
|
13004
12859
|
entry.adopted = true;
|
|
13005
12860
|
entry.pid = pid;
|
|
@@ -13026,7 +12881,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
13026
12881
|
entry.responseMs = null;
|
|
13027
12882
|
entry.exitCode = null;
|
|
13028
12883
|
entry.exitSignal = null;
|
|
13029
|
-
this.options.history.record(entry.config.id, {
|
|
12884
|
+
if (!entry.removed) this.options.history.record(entry.config.id, {
|
|
13030
12885
|
type: "exit",
|
|
13031
12886
|
detail,
|
|
13032
12887
|
runtimeMs: ranForMs
|
|
@@ -13425,7 +13280,7 @@ var init_supervisor = __esmMin((() => {
|
|
|
13425
13280
|
at: Date.now()
|
|
13426
13281
|
});
|
|
13427
13282
|
const ranForMs = nannyExit?.runtimeMs ?? (entry.startedAt === null ? 0 : Date.now() - entry.startedAt);
|
|
13428
|
-
this.options.history.record(entry.config.id, {
|
|
13283
|
+
if (!entry.removed) this.options.history.record(entry.config.id, {
|
|
13429
13284
|
type: "exit",
|
|
13430
13285
|
detail,
|
|
13431
13286
|
runtimeMs: ranForMs
|
|
@@ -13616,6 +13471,10 @@ var init_supervisor = __esmMin((() => {
|
|
|
13616
13471
|
};
|
|
13617
13472
|
}
|
|
13618
13473
|
log(entry, stream, text) {
|
|
13474
|
+
if (entry.removed) {
|
|
13475
|
+
logger.debug(`[${entry.config.id}] ${text}`);
|
|
13476
|
+
return;
|
|
13477
|
+
}
|
|
13619
13478
|
const line = {
|
|
13620
13479
|
ts: Date.now(),
|
|
13621
13480
|
stream,
|
|
@@ -13888,6 +13747,7 @@ var init_backups = __esmMin((() => {
|
|
|
13888
13747
|
init_template();
|
|
13889
13748
|
init_archive();
|
|
13890
13749
|
init_supervisor();
|
|
13750
|
+
init_error_message();
|
|
13891
13751
|
init_generated();
|
|
13892
13752
|
init_shape();
|
|
13893
13753
|
MANIFEST = "manifest.json";
|
|
@@ -14042,7 +13902,7 @@ var init_backups = __esmMin((() => {
|
|
|
14042
13902
|
fs.rmSync(destination, { force: true });
|
|
14043
13903
|
return {
|
|
14044
13904
|
ok: false,
|
|
14045
|
-
error:
|
|
13905
|
+
error: errorMessage(error)
|
|
14046
13906
|
};
|
|
14047
13907
|
}
|
|
14048
13908
|
}
|
|
@@ -14083,7 +13943,7 @@ var init_backups = __esmMin((() => {
|
|
|
14083
13943
|
} catch (error) {
|
|
14084
13944
|
return {
|
|
14085
13945
|
...plan,
|
|
14086
|
-
error: `the archive could not be read: ${
|
|
13946
|
+
error: `the archive could not be read: ${errorMessage(error)}`
|
|
14087
13947
|
};
|
|
14088
13948
|
}
|
|
14089
13949
|
plan.encrypted = entries.some((entry) => entry.encrypted);
|
|
@@ -14368,7 +14228,7 @@ var init_backups = __esmMin((() => {
|
|
|
14368
14228
|
const done = plan.applied.length > 0 ? ` — already applied: ${plan.applied.join(", ")}` : "";
|
|
14369
14229
|
return {
|
|
14370
14230
|
...plan,
|
|
14371
|
-
error: `${
|
|
14231
|
+
error: `${errorMessage(error)}${done}`
|
|
14372
14232
|
};
|
|
14373
14233
|
} finally {
|
|
14374
14234
|
fs.rmSync(staging, {
|
|
@@ -14633,6 +14493,7 @@ var ControlServer;
|
|
|
14633
14493
|
var init_control_server = __esmMin((() => {
|
|
14634
14494
|
init_bind();
|
|
14635
14495
|
init_port();
|
|
14496
|
+
init_error_message();
|
|
14636
14497
|
ControlServer = class {
|
|
14637
14498
|
options;
|
|
14638
14499
|
endpoint;
|
|
@@ -14666,7 +14527,7 @@ var init_control_server = __esmMin((() => {
|
|
|
14666
14527
|
} catch (error) {
|
|
14667
14528
|
return {
|
|
14668
14529
|
ok: false,
|
|
14669
|
-
error: `restart failed: ${
|
|
14530
|
+
error: `restart failed: ${errorMessage(error)}`
|
|
14670
14531
|
};
|
|
14671
14532
|
}
|
|
14672
14533
|
}
|
|
@@ -14690,7 +14551,7 @@ var init_control_server = __esmMin((() => {
|
|
|
14690
14551
|
await this.listenWithRetry(next.host, next.port);
|
|
14691
14552
|
return { ok: true };
|
|
14692
14553
|
} catch (error) {
|
|
14693
|
-
const message =
|
|
14554
|
+
const message = errorMessage(error);
|
|
14694
14555
|
try {
|
|
14695
14556
|
await this.listenWithRetry(previous.host, previous.port);
|
|
14696
14557
|
} catch {}
|
|
@@ -15195,7 +15056,7 @@ async function detectPublicAddress(family, overrideUrl, fetchImpl, signal) {
|
|
|
15195
15056
|
};
|
|
15196
15057
|
lastError = `${endpoint} did not answer with a public ${family === "A" ? "IPv4" : "IPv6"} address`;
|
|
15197
15058
|
} catch (error) {
|
|
15198
|
-
lastError = `${endpoint}: ${
|
|
15059
|
+
lastError = `${endpoint}: ${errorMessage(error)}`;
|
|
15199
15060
|
}
|
|
15200
15061
|
return {
|
|
15201
15062
|
ip: null,
|
|
@@ -15204,6 +15065,7 @@ async function detectPublicAddress(family, overrideUrl, fetchImpl, signal) {
|
|
|
15204
15065
|
}
|
|
15205
15066
|
var DEFAULT_IPV4_ENDPOINTS, DEFAULT_IPV6_ENDPOINTS;
|
|
15206
15067
|
var init_ip = __esmMin((() => {
|
|
15068
|
+
init_error_message();
|
|
15207
15069
|
DEFAULT_IPV4_ENDPOINTS = [
|
|
15208
15070
|
"https://api4.ipify.org",
|
|
15209
15071
|
"https://ipv4.icanhazip.com",
|
|
@@ -15251,6 +15113,7 @@ var init_ddns = __esmMin((() => {
|
|
|
15251
15113
|
init_ddns$2();
|
|
15252
15114
|
init_ip();
|
|
15253
15115
|
init_types();
|
|
15116
|
+
init_error_message();
|
|
15254
15117
|
RUN_TIMEOUT_MS = 6e4;
|
|
15255
15118
|
DdnsService = class {
|
|
15256
15119
|
deps;
|
|
@@ -15487,7 +15350,7 @@ var init_ddns = __esmMin((() => {
|
|
|
15487
15350
|
try {
|
|
15488
15351
|
writeFileAtomic(this.deps.statePath, `${JSON.stringify(this.persisted, null, 2)}\n`);
|
|
15489
15352
|
} catch (error) {
|
|
15490
|
-
logger.warn(`ddns: could not save its state (${
|
|
15353
|
+
logger.warn(`ddns: could not save its state (${errorMessage(error)})`);
|
|
15491
15354
|
}
|
|
15492
15355
|
}
|
|
15493
15356
|
dispose() {
|
|
@@ -15695,6 +15558,7 @@ var init_log_files = __esmMin((() => {
|
|
|
15695
15558
|
return lines.slice(-tail);
|
|
15696
15559
|
}
|
|
15697
15560
|
clear(serverId) {
|
|
15561
|
+
this.pending.delete(serverId);
|
|
15698
15562
|
for (const file of this.rotateTargets(serverId)) try {
|
|
15699
15563
|
fs.rmSync(file, { force: true });
|
|
15700
15564
|
} catch {}
|
|
@@ -15797,6 +15661,7 @@ var REASON_LABEL, TITLE, NotificationService;
|
|
|
15797
15661
|
var init_notifications = __esmMin((() => {
|
|
15798
15662
|
init_logger();
|
|
15799
15663
|
init_telegram();
|
|
15664
|
+
init_error_message();
|
|
15800
15665
|
REASON_LABEL = {
|
|
15801
15666
|
"crash": "gave up restarting",
|
|
15802
15667
|
"unhealthy": "health check failing",
|
|
@@ -15822,14 +15687,12 @@ var init_notifications = __esmMin((() => {
|
|
|
15822
15687
|
NotificationService = class {
|
|
15823
15688
|
secrets;
|
|
15824
15689
|
getConfig;
|
|
15825
|
-
getLogsConfig;
|
|
15826
15690
|
cooldowns = /* @__PURE__ */ new Map();
|
|
15827
15691
|
lastResult = null;
|
|
15828
15692
|
lastResultAt = null;
|
|
15829
|
-
constructor(secrets, getConfig
|
|
15693
|
+
constructor(secrets, getConfig) {
|
|
15830
15694
|
this.secrets = secrets;
|
|
15831
15695
|
this.getConfig = getConfig;
|
|
15832
|
-
this.getLogsConfig = getLogsConfig;
|
|
15833
15696
|
}
|
|
15834
15697
|
get telegramTokenSet() {
|
|
15835
15698
|
return this.secrets.telegramTokenSet;
|
|
@@ -15876,7 +15739,7 @@ var init_notifications = __esmMin((() => {
|
|
|
15876
15739
|
/** Fire-and-forget by design: supervision must never wait on a chat API. */
|
|
15877
15740
|
notify(event) {
|
|
15878
15741
|
this.dispatch(event).catch((error) => {
|
|
15879
|
-
logger.warn(`notification failed: ${
|
|
15742
|
+
logger.warn(`notification failed: ${errorMessage(error)}`);
|
|
15880
15743
|
});
|
|
15881
15744
|
}
|
|
15882
15745
|
async dispatch(event) {
|
|
@@ -16009,6 +15872,7 @@ var init_panel = __esmMin((() => {
|
|
|
16009
15872
|
init_notifications();
|
|
16010
15873
|
init_state$1();
|
|
16011
15874
|
init_supervisor();
|
|
15875
|
+
init_error_message();
|
|
16012
15876
|
TICK_MS = 1e3;
|
|
16013
15877
|
PanelService = class {
|
|
16014
15878
|
options;
|
|
@@ -16218,7 +16082,7 @@ var init_panel = __esmMin((() => {
|
|
|
16218
16082
|
const secrets = new SecretsStore(workspaceSecretsPath(id), void 0, "workspace");
|
|
16219
16083
|
const logFiles = new LogFiles(workspaceLogsDir(id), () => store.logs);
|
|
16220
16084
|
const history = new HistoryStore(workspaceHistoryPath(id));
|
|
16221
|
-
const notifications = new NotificationService(secrets, () => store.notifications
|
|
16085
|
+
const notifications = new NotificationService(secrets, () => store.notifications);
|
|
16222
16086
|
const ddns = new DdnsService({
|
|
16223
16087
|
getConfig: () => store.ddns,
|
|
16224
16088
|
getCredentials: (accountId, provider) => secrets.getDdnsCredentials(accountId, provider)?.values ?? null,
|
|
@@ -16261,11 +16125,11 @@ var init_panel = __esmMin((() => {
|
|
|
16261
16125
|
runtime.watches = [new ConfigWatch({
|
|
16262
16126
|
file: store.path,
|
|
16263
16127
|
onChange: () => this.handleConfigChange(id),
|
|
16264
|
-
onError: (error) => logger.warn(`cannot watch ${path.basename(store.path)}: ${
|
|
16128
|
+
onError: (error) => logger.warn(`cannot watch ${path.basename(store.path)}: ${errorMessage(error)}`)
|
|
16265
16129
|
}), new ConfigWatch({
|
|
16266
16130
|
file: store.settingsPath,
|
|
16267
16131
|
onChange: () => this.handleConfigChange(id),
|
|
16268
|
-
onError: (error) => logger.warn(`cannot watch ${path.basename(store.settingsPath)}: ${
|
|
16132
|
+
onError: (error) => logger.warn(`cannot watch ${path.basename(store.settingsPath)}: ${errorMessage(error)}`)
|
|
16269
16133
|
})];
|
|
16270
16134
|
for (const watch of runtime.watches) watch.start();
|
|
16271
16135
|
return runtime;
|
|
@@ -16466,7 +16330,7 @@ function readDataRoot(dataRoot) {
|
|
|
16466
16330
|
}
|
|
16467
16331
|
}
|
|
16468
16332
|
function describeError$1(error) {
|
|
16469
|
-
return
|
|
16333
|
+
return errorMessage(error);
|
|
16470
16334
|
}
|
|
16471
16335
|
/**
|
|
16472
16336
|
* Where the site actually starts: the archive root, or a single wrapper directory
|
|
@@ -16500,6 +16364,7 @@ var init_ui = __esmMin((() => {
|
|
|
16500
16364
|
init_atomic();
|
|
16501
16365
|
init_archive();
|
|
16502
16366
|
init_contracts();
|
|
16367
|
+
init_error_message();
|
|
16503
16368
|
META = "ui.json";
|
|
16504
16369
|
MAX_ENTRIES = 2e4;
|
|
16505
16370
|
MAX_BYTES = 536870912;
|
|
@@ -16561,7 +16426,7 @@ var init_ui = __esmMin((() => {
|
|
|
16561
16426
|
} catch (error) {
|
|
16562
16427
|
return {
|
|
16563
16428
|
ok: false,
|
|
16564
|
-
error:
|
|
16429
|
+
error: errorMessage(error)
|
|
16565
16430
|
};
|
|
16566
16431
|
} finally {
|
|
16567
16432
|
fs.rmSync(staging, {
|
|
@@ -16586,7 +16451,7 @@ var init_ui = __esmMin((() => {
|
|
|
16586
16451
|
} catch (error) {
|
|
16587
16452
|
return {
|
|
16588
16453
|
ok: false,
|
|
16589
|
-
error: `the archive could not be read: ${
|
|
16454
|
+
error: `the archive could not be read: ${errorMessage(error)}`
|
|
16590
16455
|
};
|
|
16591
16456
|
}
|
|
16592
16457
|
if (entries.length === 0) return {
|
|
@@ -16898,7 +16763,7 @@ function formatBytes(bytes) {
|
|
|
16898
16763
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
16899
16764
|
}
|
|
16900
16765
|
function describeError(error) {
|
|
16901
|
-
return
|
|
16766
|
+
return errorMessage(error);
|
|
16902
16767
|
}
|
|
16903
16768
|
/**
|
|
16904
16769
|
* Tag ordering: `v1.2.3` beats `v1.2.2`, a prerelease loses to its release, and anything
|
|
@@ -16946,6 +16811,7 @@ function parseTag(tag) {
|
|
|
16946
16811
|
}
|
|
16947
16812
|
var DEFAULT_REPO, MAX_DOWNLOAD_BYTES, REQUEST_TIMEOUT_MS, DOWNLOAD_TIMEOUT_MS;
|
|
16948
16813
|
var init_ui_release = __esmMin((() => {
|
|
16814
|
+
init_error_message();
|
|
16949
16815
|
DEFAULT_REPO = "NamesMT/home-hosted";
|
|
16950
16816
|
MAX_DOWNLOAD_BYTES = 536870912;
|
|
16951
16817
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -17023,7 +16889,7 @@ async function syncOfficialUi(ui, runningVersion = appVersion()) {
|
|
|
17023
16889
|
} catch (error) {
|
|
17024
16890
|
return {
|
|
17025
16891
|
kind: "failed",
|
|
17026
|
-
error:
|
|
16892
|
+
error: errorMessage(error)
|
|
17027
16893
|
};
|
|
17028
16894
|
}
|
|
17029
16895
|
}
|
|
@@ -17042,13 +16908,14 @@ function autoUpdateOfficialUi(ui, runningVersion = appVersion()) {
|
|
|
17042
16908
|
if (result.kind === "updated") logger.info(`ui: updated to ${result.tag} — refresh the browser`);
|
|
17043
16909
|
else if (result.kind === "failed") logger.warn(`ui: could not update to ${plan.tag}: ${result.error}`);
|
|
17044
16910
|
}).catch((error) => {
|
|
17045
|
-
logger.warn(`ui: could not update: ${
|
|
16911
|
+
logger.warn(`ui: could not update: ${errorMessage(error)}`);
|
|
17046
16912
|
});
|
|
17047
16913
|
}
|
|
17048
16914
|
var init_ui_update$1 = __esmMin((() => {
|
|
17049
16915
|
init_logger();
|
|
17050
16916
|
init_version();
|
|
17051
16917
|
init_ui_release();
|
|
16918
|
+
init_error_message();
|
|
17052
16919
|
}));
|
|
17053
16920
|
//#endregion
|
|
17054
16921
|
//#region src/index.ts
|
|
@@ -17074,7 +16941,12 @@ function probeHostFor(bindHost) {
|
|
|
17074
16941
|
* directly.
|
|
17075
16942
|
*/
|
|
17076
16943
|
async function runControlPlane(options) {
|
|
17077
|
-
|
|
16944
|
+
try {
|
|
16945
|
+
ensureLayout();
|
|
16946
|
+
} catch (error) {
|
|
16947
|
+
logger.error(errorMessage(error));
|
|
16948
|
+
process.exit(1);
|
|
16949
|
+
}
|
|
17078
16950
|
const existing = readRuntime();
|
|
17079
16951
|
if (existing !== null && existing.pid !== process.pid && await runtimeIsAlive(existing)) {
|
|
17080
16952
|
logger.error(`already running (pid ${existing.pid}) at ${existing.url} — run \`home-hosted down\` first`);
|
|
@@ -17101,6 +16973,11 @@ async function runControlPlane(options) {
|
|
|
17101
16973
|
process.exit(1);
|
|
17102
16974
|
}
|
|
17103
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
|
+
}
|
|
17104
16981
|
const auth = new AuthService(secrets, () => settings.control.auth);
|
|
17105
16982
|
const tls = new TlsStore(tlsDir);
|
|
17106
16983
|
const hub = new EventHub();
|
|
@@ -17247,6 +17124,11 @@ async function runControlPlane(options) {
|
|
|
17247
17124
|
logger.info(`fix ${store.path} and ${store.settingsPath}, or install the release that wrote them`);
|
|
17248
17125
|
process.exit(1);
|
|
17249
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
|
+
}
|
|
17250
17132
|
if (store.pendingMigrations.length > 0) {
|
|
17251
17133
|
logger.error(`refusing to start: workspace "${workspace.id}" needs ${store.pendingMigrations.length} migration(s) before ${appVersion()} can use it`);
|
|
17252
17134
|
logger.info("run `home-hosted migrate` to see and apply them");
|
|
@@ -17394,6 +17276,7 @@ var init_src = __esmMin((() => {
|
|
|
17394
17276
|
init_ui();
|
|
17395
17277
|
init_ui_update$1();
|
|
17396
17278
|
init_contracts();
|
|
17279
|
+
init_error_message();
|
|
17397
17280
|
packageRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
17398
17281
|
}));
|
|
17399
17282
|
//#endregion
|
|
@@ -17957,6 +17840,7 @@ async function runLogs(input) {
|
|
|
17957
17840
|
var logsArgs, DEFAULT_LINES, logsCommand;
|
|
17958
17841
|
var init_logs = __esmMin((() => {
|
|
17959
17842
|
init_io();
|
|
17843
|
+
init_error_message();
|
|
17960
17844
|
logsArgs = {
|
|
17961
17845
|
lines: {
|
|
17962
17846
|
type: "string",
|
|
@@ -17983,7 +17867,7 @@ var init_logs = __esmMin((() => {
|
|
|
17983
17867
|
json: args.json
|
|
17984
17868
|
});
|
|
17985
17869
|
} catch (error) {
|
|
17986
|
-
fail(
|
|
17870
|
+
fail(errorMessage(error));
|
|
17987
17871
|
}
|
|
17988
17872
|
}
|
|
17989
17873
|
});
|
|
@@ -18150,12 +18034,6 @@ function targets(configOverride) {
|
|
|
18150
18034
|
return list;
|
|
18151
18035
|
}
|
|
18152
18036
|
async function runMigrate(config, dryRun, yes) {
|
|
18153
|
-
const relocated = migrateLayout();
|
|
18154
|
-
if (relocated.migrated) {
|
|
18155
|
-
process.stdout.write(`${green("state layout migrated")} into the current .hh layout\n`);
|
|
18156
|
-
for (const step of relocated.moved) process.stdout.write(` ${dim(step)}\n`);
|
|
18157
|
-
for (const warning of relocated.warnings) process.stdout.write(` ${dim(`warning: ${warning}`)}\n`);
|
|
18158
|
-
}
|
|
18159
18037
|
const existing = targets(config).filter((target) => fs.existsSync(target.file));
|
|
18160
18038
|
if (existing.length === 0) fail(`no home-hosted state to migrate — nothing at ${globalSettingsPath} or any workspace`);
|
|
18161
18039
|
const consented = yes || (process.env.HHOSTED_MIGRATE ?? "").toLowerCase() === "allow";
|
|
@@ -18165,7 +18043,7 @@ async function runMigrate(config, dryRun, yes) {
|
|
|
18165
18043
|
try {
|
|
18166
18044
|
raw = JSON.parse(fs.readFileSync(target.file, "utf8"));
|
|
18167
18045
|
} catch (error) {
|
|
18168
|
-
fail(`cannot read ${target.file}: ${
|
|
18046
|
+
fail(`cannot read ${target.file}: ${errorMessage(error)}`);
|
|
18169
18047
|
}
|
|
18170
18048
|
const meta = typeof raw.meta === "object" && raw.meta !== null ? raw.meta : {};
|
|
18171
18049
|
const from = typeof meta.schema === "number" ? meta.schema : 1;
|
|
@@ -18210,13 +18088,13 @@ async function runMigrate(config, dryRun, yes) {
|
|
|
18210
18088
|
var migrateArgs, migrateCommand;
|
|
18211
18089
|
var init_migrate = __esmMin((() => {
|
|
18212
18090
|
init_io();
|
|
18213
|
-
init_layout();
|
|
18214
18091
|
init_migrations();
|
|
18215
18092
|
init_parse();
|
|
18216
18093
|
init_workspaces();
|
|
18217
18094
|
init_atomic();
|
|
18218
18095
|
init_paths();
|
|
18219
18096
|
init_version();
|
|
18097
|
+
init_error_message();
|
|
18220
18098
|
migrateArgs = {
|
|
18221
18099
|
config: {
|
|
18222
18100
|
type: "string",
|
|
@@ -18380,7 +18258,7 @@ async function runInit(options) {
|
|
|
18380
18258
|
process.stdout.write(`${green("project created")} in ${result.dir}\n`);
|
|
18381
18259
|
for (const file of result.files) process.stdout.write(` ${dim(file)}\n`);
|
|
18382
18260
|
} catch (error) {
|
|
18383
|
-
fail(
|
|
18261
|
+
fail(errorMessage(error));
|
|
18384
18262
|
}
|
|
18385
18263
|
const target = path.resolve(dir);
|
|
18386
18264
|
if (git) {
|
|
@@ -18411,6 +18289,7 @@ var initArgs, initCommand;
|
|
|
18411
18289
|
var init_init = __esmMin((() => {
|
|
18412
18290
|
init_io();
|
|
18413
18291
|
init_init$1();
|
|
18292
|
+
init_error_message();
|
|
18414
18293
|
initArgs = {
|
|
18415
18294
|
dir: {
|
|
18416
18295
|
type: "string",
|
|
@@ -19057,6 +18936,7 @@ var init_nanny = __esmMin((() => {
|
|
|
19057
18936
|
}));
|
|
19058
18937
|
//#endregion
|
|
19059
18938
|
//#region src/cli.ts
|
|
18939
|
+
init_error_message();
|
|
19060
18940
|
init_args();
|
|
19061
18941
|
init_io();
|
|
19062
18942
|
init_nearest();
|
|
@@ -19353,7 +19233,7 @@ async function main() {
|
|
|
19353
19233
|
try {
|
|
19354
19234
|
await runCommand(nannyCommand, { rawArgs: dirFlags.rest });
|
|
19355
19235
|
} catch (error) {
|
|
19356
|
-
fail(
|
|
19236
|
+
fail(errorMessage(error));
|
|
19357
19237
|
}
|
|
19358
19238
|
return;
|
|
19359
19239
|
}
|
|
@@ -19382,11 +19262,11 @@ async function main() {
|
|
|
19382
19262
|
try {
|
|
19383
19263
|
await runCommand(rootCommand, { rawArgs: invocation.argv });
|
|
19384
19264
|
} catch (error) {
|
|
19385
|
-
fail(
|
|
19265
|
+
fail(errorMessage(error));
|
|
19386
19266
|
}
|
|
19387
19267
|
}
|
|
19388
19268
|
main().catch((error) => {
|
|
19389
|
-
fail(
|
|
19269
|
+
fail(errorMessage(error));
|
|
19390
19270
|
});
|
|
19391
19271
|
//#endregion
|
|
19392
19272
|
export { commandHelp, suggestCommand };
|