@jiayunxie/aerial 0.1.11 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/docs/usage.md +24 -1
- package/package.json +9 -4
- package/src/auth.js +4 -3
- package/src/cli.js +199 -98
- package/src/config.js +22 -1
- package/src/copilot.js +18 -12
- package/src/http-utils.js +9 -0
- package/src/model-selection.js +7 -32
- package/src/model-utils.js +20 -0
- package/src/probe.js +7 -24
- package/src/prompt-utils.js +8 -0
- package/src/proxy-config.js +51 -0
- package/src/responses-websocket.js +4 -2
- package/src/service.js +305 -214
- package/src/setup-selection.js +2 -9
- package/src/setup.js +41 -22
- package/src/socks5-bridge.js +189 -0
- package/src/upstream-fetch.js +354 -0
package/src/service.js
CHANGED
|
@@ -16,6 +16,8 @@ const DEFAULT_WRAPPER_LOG_BACKUPS = 3;
|
|
|
16
16
|
const HEALTH_TIMEOUT_MS = 1500;
|
|
17
17
|
const HEALTH_START_TIMEOUT_MS = 5000;
|
|
18
18
|
const HEALTH_POLL_INTERVAL_MS = 250;
|
|
19
|
+
const MIN_SERVICE_NODE_MAJOR = 24;
|
|
20
|
+
const DARWIN_CODEX_NODE = "/Applications/Codex.app/Contents/Resources/node";
|
|
19
21
|
|
|
20
22
|
function wrapperLogConfig() {
|
|
21
23
|
const out = { maxBytes: DEFAULT_WRAPPER_LOG_MAX_BYTES, backups: DEFAULT_WRAPPER_LOG_BACKUPS };
|
|
@@ -76,8 +78,43 @@ function defaultRunCommand(file, args, opts = {}) {
|
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
function parseNodeMajor(versionText) {
|
|
82
|
+
const match = /^v?(\d+)\./.exec(String(versionText || "").trim());
|
|
83
|
+
return match ? Number(match[1]) : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function nodeMajorOfBinary(file) {
|
|
87
|
+
if (!file || !fs.existsSync(file)) return undefined;
|
|
88
|
+
const result = spawnSync(file, ["--version"], {
|
|
89
|
+
stdio: "pipe",
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
timeout: 3000,
|
|
92
|
+
windowsHide: true
|
|
93
|
+
});
|
|
94
|
+
if (result.status !== 0) return undefined;
|
|
95
|
+
return parseNodeMajor(result.stdout || result.stderr);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function selectServiceNodeBinary({ requested, current, candidates = [], versionOf = nodeMajorOfBinary } = {}) {
|
|
99
|
+
if (requested) return requested;
|
|
100
|
+
if (versionOf(current) >= MIN_SERVICE_NODE_MAJOR) return current;
|
|
101
|
+
for (const candidate of candidates) {
|
|
102
|
+
if (versionOf(candidate) >= MIN_SERVICE_NODE_MAJOR) return candidate;
|
|
103
|
+
}
|
|
104
|
+
return current;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function serviceNodeCandidates() {
|
|
108
|
+
if (process.platform === "darwin") return [DARWIN_CODEX_NODE];
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
79
112
|
function nodeBinary() {
|
|
80
|
-
return
|
|
113
|
+
return selectServiceNodeBinary({
|
|
114
|
+
requested: process.env.AERIAL_SERVICE_NODE,
|
|
115
|
+
current: process.execPath,
|
|
116
|
+
candidates: serviceNodeCandidates()
|
|
117
|
+
});
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
function cliEntry() {
|
|
@@ -622,8 +659,8 @@ function windowsServiceState(ctx) {
|
|
|
622
659
|
}
|
|
623
660
|
|
|
624
661
|
function serviceState(ctx) {
|
|
625
|
-
|
|
626
|
-
if (
|
|
662
|
+
const adapter = serviceAdapter(ctx);
|
|
663
|
+
if (adapter) return adapter.state();
|
|
627
664
|
return { installed: false, loaded: false, reason: "unsupported_platform" };
|
|
628
665
|
}
|
|
629
666
|
|
|
@@ -700,162 +737,237 @@ function windowsWriteDefinition(ctx) {
|
|
|
700
737
|
return { wrapper, create };
|
|
701
738
|
}
|
|
702
739
|
|
|
703
|
-
|
|
740
|
+
function serviceAdapter(ctx) {
|
|
741
|
+
if (process.platform === "darwin") {
|
|
742
|
+
return {
|
|
743
|
+
platform: "darwin",
|
|
744
|
+
wrapperPath: darwinWrapperPath,
|
|
745
|
+
state: () => darwinServiceState(ctx),
|
|
746
|
+
writeDefinition: () => {
|
|
747
|
+
const written = darwinWriteDefinition();
|
|
748
|
+
return {
|
|
749
|
+
ok: true,
|
|
750
|
+
info: { file: written.file, wrapper: written.wrapper, label: SERVICE_LABEL }
|
|
751
|
+
};
|
|
752
|
+
},
|
|
753
|
+
triggerStart: () => darwinBootstrap(ctx),
|
|
754
|
+
triggerStop: () => darwinBootout(ctx),
|
|
755
|
+
startFailureReason: "bootstrap_failed",
|
|
756
|
+
startResultKey: "bootstrap",
|
|
757
|
+
uninstall: (state) => darwinUninstall(ctx, state)
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (process.platform === "win32") {
|
|
761
|
+
return {
|
|
762
|
+
platform: "win32",
|
|
763
|
+
wrapperPath: winWrapperPath,
|
|
764
|
+
state: () => windowsServiceState(ctx),
|
|
765
|
+
writeDefinition: () => {
|
|
766
|
+
const written = windowsWriteDefinition(ctx);
|
|
767
|
+
const info = {
|
|
768
|
+
taskName: WIN_TASK_NAME,
|
|
769
|
+
wrapper: written.wrapper,
|
|
770
|
+
create: { status: written.create.status, stderr: written.create.stderr }
|
|
771
|
+
};
|
|
772
|
+
return { ok: written.create.status === 0, info };
|
|
773
|
+
},
|
|
774
|
+
triggerStart: () => ctx.run("schtasks.exe", buildSchtasksArgs("run")),
|
|
775
|
+
triggerStop: () => ctx.run("schtasks.exe", buildSchtasksArgs("end")),
|
|
776
|
+
startFailureReason: "run_failed",
|
|
777
|
+
startResultKey: "run",
|
|
778
|
+
uninstall: (state) => windowsUninstall(ctx, state)
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function requireServiceAdapter(ctx, action) {
|
|
785
|
+
const adapter = serviceAdapter(ctx);
|
|
786
|
+
if (!adapter) throw unsupportedError(action);
|
|
787
|
+
return adapter;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function commandResultBlock(key, result) {
|
|
791
|
+
return { [key]: { status: result.status, stderr: result.stderr } };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function buildDefinitionRefreshFailure({ adapter, supervisor, config, definition }) {
|
|
795
|
+
const isManaged = supervisor === "service-managed";
|
|
796
|
+
const status = definition.info.create?.status;
|
|
797
|
+
const reason = isManaged ? "managed_definition_refresh_failed" : "foreground_definition_refresh_failed";
|
|
798
|
+
const message = isManaged
|
|
799
|
+
? `schtasks /Create failed (status ${status}) while refreshing the managed-service definition. The running service was not disturbed, but wrapper/env changes were NOT applied. Resolve the underlying schtasks error and rerun \`aerial service install\`.`
|
|
800
|
+
: `Aerial is already running in the foreground on port ${config.port}. The wrapper was rewritten but schtasks /Create failed (status ${status}), so the Task Scheduler definition was NOT refreshed. Resolve the underlying schtasks error and rerun \`aerial service install\`.`;
|
|
801
|
+
return {
|
|
802
|
+
ok: false,
|
|
803
|
+
action: "install",
|
|
804
|
+
platform: adapter.platform,
|
|
805
|
+
reason,
|
|
806
|
+
...(isManaged ? {} : { definitionUpdated: false }),
|
|
807
|
+
message,
|
|
808
|
+
warning: tokenWarning(),
|
|
809
|
+
...definition.info
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function buildRunningDefinitionResult({ adapter, supervisor, config, definition }) {
|
|
814
|
+
const managed = supervisor === "service-managed";
|
|
815
|
+
return {
|
|
816
|
+
ok: managed,
|
|
817
|
+
action: "install",
|
|
818
|
+
platform: adapter.platform,
|
|
819
|
+
...(managed
|
|
820
|
+
? {
|
|
821
|
+
note: "already running (service-managed); definition refreshed; run `aerial service restart` to apply wrapper/env changes"
|
|
822
|
+
}
|
|
823
|
+
: {
|
|
824
|
+
reason: "foreground_running",
|
|
825
|
+
message: `Aerial is already running in the foreground on port ${config.port}. The service definition has been updated, but the service was NOT started to avoid running two instances. Next step: stop the foreground process, then run \`aerial service start\`.`
|
|
826
|
+
}),
|
|
827
|
+
definitionUpdated: true,
|
|
828
|
+
warning: tokenWarning(),
|
|
829
|
+
...definition.info
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async function confirmStarted({ action, result, config, wrapper, healthFetch, healthDeadlineMs }) {
|
|
834
|
+
if (!result.ok) return result;
|
|
835
|
+
if (process.env.AERIAL_SERVICE_DRYRUN === "1") {
|
|
836
|
+
return { ...result, health: { ok: true, attempts: 0, elapsedMs: 0, dryRun: true } };
|
|
837
|
+
}
|
|
838
|
+
const poll = await pollForAerialUp(config.host, config.port, healthFetch, healthDeadlineMs);
|
|
839
|
+
if (poll.cls.mode === "aerial_running") {
|
|
840
|
+
return { ...result, health: { ok: true, attempts: poll.attempts, elapsedMs: poll.elapsedMs } };
|
|
841
|
+
}
|
|
842
|
+
const diagnostics = healthFailedDiagnostics({
|
|
843
|
+
wrapper,
|
|
844
|
+
probe: poll.probe,
|
|
845
|
+
attempts: poll.attempts,
|
|
846
|
+
elapsedMs: poll.elapsedMs
|
|
847
|
+
});
|
|
848
|
+
if (poll.cls.mode === "port_conflict") {
|
|
849
|
+
const prefix = action === "install" ? "After install" : "After start";
|
|
850
|
+
return {
|
|
851
|
+
...result,
|
|
852
|
+
ok: false,
|
|
853
|
+
reason: "port_conflict",
|
|
854
|
+
message: `${prefix}, port ${config.port} is responding as a non-Aerial process: ${poll.cls.reason}. Free the port and rerun.`,
|
|
855
|
+
diagnostics
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const message = action === "install"
|
|
859
|
+
? `Service definition was written and start was triggered, but /health did not become Aerial within ${poll.elapsedMs}ms (${poll.attempts} attempts). Inspect logs and rerun \`aerial service status --json\`.`
|
|
860
|
+
: `Start was triggered, but /health did not become Aerial within ${poll.elapsedMs}ms (${poll.attempts} attempts). Inspect logs and rerun \`aerial service status --json\`.`;
|
|
861
|
+
return {
|
|
862
|
+
...result,
|
|
863
|
+
ok: false,
|
|
864
|
+
reason: "health_check_failed",
|
|
865
|
+
...(action === "install" ? { definitionWritten: true } : {}),
|
|
866
|
+
startAttempted: true,
|
|
867
|
+
message,
|
|
868
|
+
diagnostics
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
async function describeRunning(adapter, host, port, healthFetch, knownState) {
|
|
704
873
|
const probe = await (healthFetch || defaultHealthFetch)(host, port);
|
|
705
874
|
const cls = classifyHealth(probe);
|
|
706
875
|
if (cls.mode !== "aerial_running") return { cls, probe };
|
|
707
|
-
const state =
|
|
876
|
+
const state = knownState || adapter.state();
|
|
708
877
|
const supervisor = state.installed && state.loaded ? "service-managed" : "foreground";
|
|
709
878
|
return { cls, probe, supervisor };
|
|
710
879
|
}
|
|
711
880
|
|
|
712
881
|
export async function serviceInstall({ run = defaultRunCommand, healthFetch, healthDeadlineMs } = {}) {
|
|
713
|
-
if (isUnsupportedPlatform()) throw unsupportedError("install");
|
|
714
882
|
const ctx = { run };
|
|
883
|
+
const adapter = requireServiceAdapter(ctx, "install");
|
|
715
884
|
const config = loadConfig();
|
|
716
|
-
const { cls, supervisor } = await describeRunning(
|
|
885
|
+
const { cls, supervisor } = await describeRunning(adapter, config.host, config.port, healthFetch);
|
|
717
886
|
if (cls.mode === "port_conflict") {
|
|
718
|
-
logEvent("service_install", { platform:
|
|
887
|
+
logEvent("service_install", { platform: adapter.platform, ok: false, reason: "port_conflict" });
|
|
719
888
|
return {
|
|
720
889
|
ok: false,
|
|
721
890
|
action: "install",
|
|
722
|
-
platform:
|
|
891
|
+
platform: adapter.platform,
|
|
723
892
|
reason: "port_conflict",
|
|
724
893
|
message: `Port ${config.port} is already in use by a non-Aerial process: ${cls.reason}. Free the port and rerun.`
|
|
725
894
|
};
|
|
726
895
|
}
|
|
727
|
-
if (cls.mode === "aerial_running"
|
|
728
|
-
|
|
729
|
-
if (
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
ok: false,
|
|
739
|
-
action: "install",
|
|
740
|
-
platform: "win32",
|
|
741
|
-
reason: "managed_definition_refresh_failed",
|
|
742
|
-
message: `schtasks /Create failed (status ${written.create.status}) while refreshing the managed-service definition. The running service was not disturbed, but wrapper/env changes were NOT applied. Resolve the underlying schtasks error and rerun \`aerial service install\`.`,
|
|
743
|
-
warning: tokenWarning(),
|
|
744
|
-
...writeInfo
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
logEvent("service_install", { platform: process.platform, ok: true, note: "managed_refreshed", definitionUpdated: true });
|
|
749
|
-
return {
|
|
750
|
-
ok: true,
|
|
751
|
-
action: "install",
|
|
752
|
-
platform: process.platform,
|
|
753
|
-
note: "already running (service-managed); definition refreshed; run `aerial service restart` to apply wrapper/env changes",
|
|
754
|
-
definitionUpdated: true,
|
|
755
|
-
warning: tokenWarning(),
|
|
756
|
-
...writeInfo
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
if (cls.mode === "aerial_running" && supervisor === "foreground") {
|
|
760
|
-
let writeInfo;
|
|
761
|
-
if (process.platform === "darwin") {
|
|
762
|
-
const written = darwinWriteDefinition();
|
|
763
|
-
writeInfo = { file: written.file, wrapper: written.wrapper, label: SERVICE_LABEL };
|
|
764
|
-
} else {
|
|
765
|
-
const written = windowsWriteDefinition(ctx);
|
|
766
|
-
writeInfo = { taskName: WIN_TASK_NAME, wrapper: written.wrapper, create: { status: written.create.status, stderr: written.create.stderr } };
|
|
767
|
-
if (written.create.status !== 0) {
|
|
768
|
-
logEvent("service_install", { platform: "win32", ok: false, reason: "foreground_definition_refresh_failed", status: written.create.status });
|
|
769
|
-
return {
|
|
770
|
-
ok: false,
|
|
771
|
-
action: "install",
|
|
772
|
-
platform: "win32",
|
|
773
|
-
reason: "foreground_definition_refresh_failed",
|
|
774
|
-
definitionUpdated: false,
|
|
775
|
-
message: `Aerial is already running in the foreground on port ${config.port}. The wrapper was rewritten but schtasks /Create failed (status ${written.create.status}), so the Task Scheduler definition was NOT refreshed. Resolve the underlying schtasks error and rerun \`aerial service install\`.`,
|
|
776
|
-
warning: tokenWarning(),
|
|
777
|
-
...writeInfo
|
|
778
|
-
};
|
|
779
|
-
}
|
|
896
|
+
if (cls.mode === "aerial_running") {
|
|
897
|
+
const definition = adapter.writeDefinition();
|
|
898
|
+
if (!definition.ok) {
|
|
899
|
+
const failure = buildDefinitionRefreshFailure({ adapter, supervisor, config, definition });
|
|
900
|
+
logEvent("service_install", {
|
|
901
|
+
platform: adapter.platform,
|
|
902
|
+
ok: false,
|
|
903
|
+
reason: failure.reason,
|
|
904
|
+
status: definition.info.create?.status
|
|
905
|
+
});
|
|
906
|
+
return failure;
|
|
780
907
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
definitionUpdated: true
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
...writeInfo
|
|
791
|
-
};
|
|
908
|
+
const result = buildRunningDefinitionResult({ adapter, supervisor, config, definition });
|
|
909
|
+
logEvent("service_install", {
|
|
910
|
+
platform: adapter.platform,
|
|
911
|
+
ok: result.ok,
|
|
912
|
+
reason: result.reason,
|
|
913
|
+
note: supervisor === "service-managed" ? "managed_refreshed" : undefined,
|
|
914
|
+
definitionUpdated: true
|
|
915
|
+
});
|
|
916
|
+
return result;
|
|
792
917
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
918
|
+
|
|
919
|
+
const definition = adapter.writeDefinition();
|
|
920
|
+
let result = {
|
|
921
|
+
ok: definition.ok,
|
|
922
|
+
action: "install",
|
|
923
|
+
platform: adapter.platform,
|
|
924
|
+
...definition.info
|
|
925
|
+
};
|
|
926
|
+
if (!definition.ok) {
|
|
927
|
+
result.reason = "create_failed";
|
|
800
928
|
} else {
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
result
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
if (!ok) result.reason = "run_failed";
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
if (result.ok) {
|
|
813
|
-
if (process.env.AERIAL_SERVICE_DRYRUN === "1") {
|
|
814
|
-
result.health = { ok: true, attempts: 0, elapsedMs: 0, dryRun: true };
|
|
815
|
-
} else {
|
|
816
|
-
const poll = await pollForAerialUp(config.host, config.port, healthFetch, healthDeadlineMs);
|
|
817
|
-
if (poll.cls.mode === "aerial_running") {
|
|
818
|
-
result.health = { ok: true, attempts: poll.attempts, elapsedMs: poll.elapsedMs };
|
|
819
|
-
} else if (poll.cls.mode === "port_conflict") {
|
|
820
|
-
result.ok = false;
|
|
821
|
-
result.reason = "port_conflict";
|
|
822
|
-
result.message = `After install, port ${config.port} is responding as a non-Aerial process: ${poll.cls.reason}. Free the port and rerun.`;
|
|
823
|
-
result.diagnostics = healthFailedDiagnostics({ wrapper: result.wrapper, probe: poll.probe, attempts: poll.attempts, elapsedMs: poll.elapsedMs });
|
|
824
|
-
} else {
|
|
825
|
-
result.ok = false;
|
|
826
|
-
result.reason = "health_check_failed";
|
|
827
|
-
result.definitionWritten = true;
|
|
828
|
-
result.startAttempted = true;
|
|
829
|
-
result.message = `Service definition was written and start was triggered, but /health did not become Aerial within ${poll.elapsedMs}ms (${poll.attempts} attempts). Inspect logs and rerun \`aerial service status --json\`.`;
|
|
830
|
-
result.diagnostics = healthFailedDiagnostics({ wrapper: result.wrapper, probe: poll.probe, attempts: poll.attempts, elapsedMs: poll.elapsedMs });
|
|
831
|
-
}
|
|
832
|
-
}
|
|
929
|
+
const start = adapter.triggerStart();
|
|
930
|
+
const triggerOk = start.status === 0;
|
|
931
|
+
result = {
|
|
932
|
+
...result,
|
|
933
|
+
ok: triggerOk,
|
|
934
|
+
...commandResultBlock(adapter.startResultKey, start),
|
|
935
|
+
...(triggerOk ? {} : { reason: adapter.startFailureReason })
|
|
936
|
+
};
|
|
833
937
|
}
|
|
938
|
+
result = await confirmStarted({
|
|
939
|
+
action: "install",
|
|
940
|
+
result,
|
|
941
|
+
config,
|
|
942
|
+
wrapper: result.wrapper,
|
|
943
|
+
healthFetch,
|
|
944
|
+
healthDeadlineMs
|
|
945
|
+
});
|
|
834
946
|
result.warning = tokenWarning();
|
|
835
|
-
logEvent("service_install", { platform:
|
|
947
|
+
logEvent("service_install", { platform: adapter.platform, ok: result.ok, reason: result.reason });
|
|
836
948
|
return result;
|
|
837
949
|
}
|
|
838
950
|
|
|
839
951
|
export async function serviceStart({ run = defaultRunCommand, healthFetch, healthDeadlineMs } = {}) {
|
|
840
|
-
if (isUnsupportedPlatform()) throw unsupportedError("start");
|
|
841
952
|
const ctx = { run };
|
|
953
|
+
const adapter = requireServiceAdapter(ctx, "start");
|
|
842
954
|
const config = loadConfig();
|
|
843
|
-
const state =
|
|
955
|
+
const state = adapter.state();
|
|
844
956
|
if (!state.installed) {
|
|
845
957
|
return {
|
|
846
958
|
ok: false,
|
|
847
959
|
action: "start",
|
|
848
|
-
platform:
|
|
960
|
+
platform: adapter.platform,
|
|
849
961
|
reason: "not_installed",
|
|
850
962
|
message: "Service is not installed. Run `aerial service install` first."
|
|
851
963
|
};
|
|
852
964
|
}
|
|
853
|
-
const { cls, supervisor } = await describeRunning(
|
|
965
|
+
const { cls, supervisor } = await describeRunning(adapter, config.host, config.port, healthFetch, state);
|
|
854
966
|
if (cls.mode === "port_conflict") {
|
|
855
967
|
return {
|
|
856
968
|
ok: false,
|
|
857
969
|
action: "start",
|
|
858
|
-
platform:
|
|
970
|
+
platform: adapter.platform,
|
|
859
971
|
reason: "port_conflict",
|
|
860
972
|
message: `Port ${config.port} is already in use by a non-Aerial process: ${cls.reason}. Free the port and rerun.`
|
|
861
973
|
};
|
|
@@ -864,7 +976,7 @@ export async function serviceStart({ run = defaultRunCommand, healthFetch, healt
|
|
|
864
976
|
return {
|
|
865
977
|
ok: false,
|
|
866
978
|
action: "start",
|
|
867
|
-
platform:
|
|
979
|
+
platform: adapter.platform,
|
|
868
980
|
reason: "foreground_running",
|
|
869
981
|
message: `Aerial is already running in the foreground on port ${config.port}. Stop the foreground process before starting the service.`
|
|
870
982
|
};
|
|
@@ -873,82 +985,51 @@ export async function serviceStart({ run = defaultRunCommand, healthFetch, healt
|
|
|
873
985
|
return {
|
|
874
986
|
ok: true,
|
|
875
987
|
action: "start",
|
|
876
|
-
platform:
|
|
988
|
+
platform: adapter.platform,
|
|
877
989
|
note: "already running (service-managed)",
|
|
878
990
|
warning: tokenWarning()
|
|
879
991
|
};
|
|
880
992
|
}
|
|
881
|
-
|
|
882
|
-
if (process.platform === "darwin") {
|
|
883
|
-
r = darwinBootstrap(ctx);
|
|
884
|
-
} else {
|
|
885
|
-
r = ctx.run("schtasks.exe", buildSchtasksArgs("run"));
|
|
886
|
-
}
|
|
993
|
+
const r = adapter.triggerStart();
|
|
887
994
|
const triggerOk = r.status === 0;
|
|
888
|
-
|
|
995
|
+
let result = {
|
|
889
996
|
ok: triggerOk,
|
|
890
997
|
action: "start",
|
|
891
|
-
platform:
|
|
998
|
+
platform: adapter.platform,
|
|
892
999
|
status: r.status,
|
|
893
1000
|
stderr: r.stderr,
|
|
894
1001
|
warning: tokenWarning(),
|
|
895
|
-
...(triggerOk ? {} : { reason:
|
|
1002
|
+
...(triggerOk ? {} : { reason: adapter.startFailureReason })
|
|
896
1003
|
};
|
|
897
1004
|
if (!triggerOk) {
|
|
898
|
-
logEvent("service_start", { platform:
|
|
899
|
-
return
|
|
900
|
-
}
|
|
901
|
-
if (process.env.AERIAL_SERVICE_DRYRUN === "1") {
|
|
902
|
-
base.health = { ok: true, attempts: 0, elapsedMs: 0, dryRun: true };
|
|
903
|
-
logEvent("service_start", { platform: process.platform, status: r.status, ok: true, dryRun: true });
|
|
904
|
-
return base;
|
|
905
|
-
}
|
|
906
|
-
const poll = await pollForAerialUp(config.host, config.port, healthFetch, healthDeadlineMs);
|
|
907
|
-
if (poll.cls.mode === "aerial_running") {
|
|
908
|
-
base.health = { ok: true, attempts: poll.attempts, elapsedMs: poll.elapsedMs };
|
|
909
|
-
logEvent("service_start", { platform: process.platform, status: r.status, ok: true });
|
|
910
|
-
return base;
|
|
911
|
-
}
|
|
912
|
-
const wrapper = process.platform === "darwin" ? darwinWrapperPath() : winWrapperPath();
|
|
913
|
-
if (poll.cls.mode === "port_conflict") {
|
|
914
|
-
logEvent("service_start", { platform: process.platform, ok: false, reason: "port_conflict" });
|
|
915
|
-
return {
|
|
916
|
-
...base,
|
|
917
|
-
ok: false,
|
|
918
|
-
reason: "port_conflict",
|
|
919
|
-
message: `After start, port ${config.port} is responding as a non-Aerial process: ${poll.cls.reason}. Free the port and rerun.`,
|
|
920
|
-
diagnostics: healthFailedDiagnostics({ wrapper, probe: poll.probe, attempts: poll.attempts, elapsedMs: poll.elapsedMs })
|
|
921
|
-
};
|
|
1005
|
+
logEvent("service_start", { platform: adapter.platform, status: r.status, reason: result.reason });
|
|
1006
|
+
return result;
|
|
922
1007
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
};
|
|
1008
|
+
result = await confirmStarted({
|
|
1009
|
+
action: "start",
|
|
1010
|
+
result,
|
|
1011
|
+
config,
|
|
1012
|
+
wrapper: adapter.wrapperPath(),
|
|
1013
|
+
healthFetch,
|
|
1014
|
+
healthDeadlineMs
|
|
1015
|
+
});
|
|
1016
|
+
logEvent("service_start", { platform: adapter.platform, status: r.status, ok: result.ok, reason: result.reason, dryRun: result.health?.dryRun });
|
|
1017
|
+
return result;
|
|
932
1018
|
}
|
|
933
1019
|
|
|
934
1020
|
export function serviceStop({ run = defaultRunCommand } = {}) {
|
|
935
|
-
if (isUnsupportedPlatform()) throw unsupportedError("stop");
|
|
936
1021
|
const ctx = { run };
|
|
937
|
-
const
|
|
1022
|
+
const adapter = requireServiceAdapter(ctx, "stop");
|
|
1023
|
+
const state = adapter.state();
|
|
938
1024
|
if (!state.installed) {
|
|
939
|
-
return { ok: true, action: "stop", platform:
|
|
1025
|
+
return { ok: true, action: "stop", platform: adapter.platform, note: "not installed" };
|
|
940
1026
|
}
|
|
941
1027
|
if (!state.loaded) {
|
|
942
|
-
return { ok: true, action: "stop", platform:
|
|
1028
|
+
return { ok: true, action: "stop", platform: adapter.platform, note: "not running" };
|
|
943
1029
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
} else {
|
|
948
|
-
r = ctx.run("schtasks.exe", buildSchtasksArgs("end"));
|
|
949
|
-
}
|
|
950
|
-
logEvent("service_stop", { platform: process.platform, status: r.status });
|
|
951
|
-
return { ok: r.status === 0, action: "stop", platform: process.platform, status: r.status, stderr: r.stderr };
|
|
1030
|
+
const r = adapter.triggerStop();
|
|
1031
|
+
logEvent("service_stop", { platform: adapter.platform, status: r.status });
|
|
1032
|
+
return { ok: r.status === 0, action: "stop", platform: adapter.platform, status: r.status, stderr: r.stderr };
|
|
952
1033
|
}
|
|
953
1034
|
|
|
954
1035
|
export async function serviceRestart(opts = {}) {
|
|
@@ -960,54 +1041,52 @@ export async function serviceRestart(opts = {}) {
|
|
|
960
1041
|
return { ok: start.ok, action: "restart", platform: process.platform, stop, start, warning: start.warning };
|
|
961
1042
|
}
|
|
962
1043
|
|
|
963
|
-
|
|
964
|
-
if (
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1044
|
+
function removeFileIfExists(file) {
|
|
1045
|
+
if (!fs.existsSync(file)) return false;
|
|
1046
|
+
try {
|
|
1047
|
+
fs.unlinkSync(file);
|
|
1048
|
+
return true;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return false;
|
|
969
1051
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
if (fs.existsSync(wrapper)) {
|
|
990
|
-
try { fs.unlinkSync(wrapper); } catch {}
|
|
991
|
-
}
|
|
992
|
-
logEvent("service_uninstall", { platform: "darwin", ok: true });
|
|
993
|
-
return { ok: true, action: "uninstall", platform: "darwin", file, wrapper, bootout: { status: bootout.status, stderr: bootout.stderr } };
|
|
994
|
-
}
|
|
995
|
-
try { fs.unlinkSync(file); } catch {}
|
|
996
|
-
if (fs.existsSync(wrapper)) {
|
|
997
|
-
try { fs.unlinkSync(wrapper); } catch {}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function darwinUninstall(ctx, state) {
|
|
1055
|
+
const file = plistPath();
|
|
1056
|
+
const wrapper = darwinWrapperPath();
|
|
1057
|
+
if (state.loaded) {
|
|
1058
|
+
const bootout = darwinBootout(ctx);
|
|
1059
|
+
if (bootout.status !== 0) {
|
|
1060
|
+
logEvent("service_uninstall", { platform: "darwin", ok: false, reason: "bootout_failed", status: bootout.status });
|
|
1061
|
+
return {
|
|
1062
|
+
ok: false,
|
|
1063
|
+
action: "uninstall",
|
|
1064
|
+
platform: "darwin",
|
|
1065
|
+
reason: "bootout_failed",
|
|
1066
|
+
file,
|
|
1067
|
+
wrapper,
|
|
1068
|
+
bootout: { status: bootout.status, stderr: bootout.stderr },
|
|
1069
|
+
message: `launchctl bootout failed (status ${bootout.status}). Service is still loaded; plist and wrapper were preserved. Retry with \`aerial service uninstall\`.`
|
|
1070
|
+
};
|
|
998
1071
|
}
|
|
1072
|
+
removeFileIfExists(file);
|
|
1073
|
+
removeFileIfExists(wrapper);
|
|
999
1074
|
logEvent("service_uninstall", { platform: "darwin", ok: true });
|
|
1000
|
-
return { ok: true, action: "uninstall", platform: "darwin", file, wrapper, bootout: { status:
|
|
1075
|
+
return { ok: true, action: "uninstall", platform: "darwin", file, wrapper, bootout: { status: bootout.status, stderr: bootout.stderr } };
|
|
1001
1076
|
}
|
|
1077
|
+
removeFileIfExists(file);
|
|
1078
|
+
removeFileIfExists(wrapper);
|
|
1079
|
+
logEvent("service_uninstall", { platform: "darwin", ok: true });
|
|
1080
|
+
return { ok: true, action: "uninstall", platform: "darwin", file, wrapper, bootout: { status: 0, skipped: "not_loaded" } };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function windowsUninstall(ctx, state) {
|
|
1002
1084
|
if (state.loaded) {
|
|
1003
1085
|
ctx.run("schtasks.exe", buildSchtasksArgs("end"));
|
|
1004
1086
|
}
|
|
1005
1087
|
const del = ctx.run("schtasks.exe", buildSchtasksArgs("delete"));
|
|
1006
1088
|
const wrapper = winWrapperPath();
|
|
1007
|
-
|
|
1008
|
-
if (del.status === 0 && fs.existsSync(wrapper)) {
|
|
1009
|
-
try { fs.unlinkSync(wrapper); wrapperRemoved = true; } catch {}
|
|
1010
|
-
}
|
|
1089
|
+
const wrapperRemoved = del.status === 0 ? removeFileIfExists(wrapper) : false;
|
|
1011
1090
|
logEvent("service_uninstall", { platform: "win32", ok: del.status === 0 });
|
|
1012
1091
|
return {
|
|
1013
1092
|
ok: del.status === 0,
|
|
@@ -1021,6 +1100,16 @@ export function serviceUninstall({ run = defaultRunCommand } = {}) {
|
|
|
1021
1100
|
};
|
|
1022
1101
|
}
|
|
1023
1102
|
|
|
1103
|
+
export function serviceUninstall({ run = defaultRunCommand } = {}) {
|
|
1104
|
+
const ctx = { run };
|
|
1105
|
+
const adapter = requireServiceAdapter(ctx, "uninstall");
|
|
1106
|
+
const state = adapter.state();
|
|
1107
|
+
if (!state.installed) {
|
|
1108
|
+
return { ok: true, action: "uninstall", platform: adapter.platform, note: "no service installed" };
|
|
1109
|
+
}
|
|
1110
|
+
return adapter.uninstall(state);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1024
1113
|
export async function serviceStatus({ run = defaultRunCommand, healthFetch } = {}) {
|
|
1025
1114
|
const config = loadConfig();
|
|
1026
1115
|
const ctx = { run };
|
|
@@ -1087,5 +1176,7 @@ export const _internal = {
|
|
|
1087
1176
|
wrapperBlock,
|
|
1088
1177
|
buildSchtasksArgs,
|
|
1089
1178
|
quoteSchtasksTR,
|
|
1090
|
-
classifyHealth
|
|
1179
|
+
classifyHealth,
|
|
1180
|
+
parseNodeMajor,
|
|
1181
|
+
selectServiceNodeBinary
|
|
1091
1182
|
};
|