@fastybird/smart-panel-backend 1.1.0-alpha.41 → 1.1.0-alpha.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.module.js +1 -1
- package/dist/app.module.js.map +1 -1
- package/dist/common/utils/cancellable-exec.utils.d.ts +5 -0
- package/dist/common/utils/cancellable-exec.utils.js +34 -0
- package/dist/common/utils/cancellable-exec.utils.js.map +1 -0
- package/dist/common/utils/remote-access-observation.utils.d.ts +11 -0
- package/dist/common/utils/remote-access-observation.utils.js +89 -0
- package/dist/common/utils/remote-access-observation.utils.js.map +1 -0
- package/dist/modules/remote-access/controllers/remote-access.controller.js +9 -5
- package/dist/modules/remote-access/controllers/remote-access.controller.js.map +1 -1
- package/dist/modules/remote-access/models/provider.model.d.ts +3 -0
- package/dist/modules/remote-access/models/provider.model.js +23 -0
- package/dist/modules/remote-access/models/provider.model.js.map +1 -1
- package/dist/modules/remote-access/models/status.model.d.ts +2 -0
- package/dist/modules/remote-access/models/status.model.js +18 -1
- package/dist/modules/remote-access/models/status.model.js.map +1 -1
- package/dist/modules/remote-access/models/urls.model.d.ts +2 -0
- package/dist/modules/remote-access/models/urls.model.js +17 -0
- package/dist/modules/remote-access/models/urls.model.js.map +1 -1
- package/dist/modules/remote-access/platforms/remote-access-provider.platform.d.ts +18 -1
- package/dist/modules/remote-access/remote-access.constants.d.ts +2 -1
- package/dist/modules/remote-access/remote-access.constants.js +2 -1
- package/dist/modules/remote-access/remote-access.constants.js.map +1 -1
- package/dist/modules/remote-access/services/remote-access-proxy-contribution.service.d.ts +1 -0
- package/dist/modules/remote-access/services/remote-access-proxy-contribution.service.js +4 -1
- package/dist/modules/remote-access/services/remote-access-proxy-contribution.service.js.map +1 -1
- package/dist/modules/remote-access/services/remote-access-status.service.d.ts +40 -8
- package/dist/modules/remote-access/services/remote-access-status.service.js +314 -65
- package/dist/modules/remote-access/services/remote-access-status.service.js.map +1 -1
- package/dist/modules/remote-access/services/remote-access-url.service.d.ts +2 -2
- package/dist/modules/remote-access/services/remote-access-url.service.js +4 -2
- package/dist/modules/remote-access/services/remote-access-url.service.js.map +1 -1
- package/dist/modules/websocket/gateway/websocket.gateway.js +1 -0
- package/dist/modules/websocket/gateway/websocket.gateway.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.d.ts +5 -3
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.js +20 -8
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/setup.controller.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.d.ts +3 -5
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.js +11 -10
- package/dist/plugins/remote-access-cloudflare-tunnel/controllers/status.controller.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.d.ts +2 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.js +17 -0
- package/dist/plugins/remote-access-cloudflare-tunnel/models/status.model.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.d.ts +20 -2
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.js +105 -48
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-managed.service.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.d.ts +13 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.js +8 -2
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflare-tunnel-provider.service.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.d.ts +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.js +12 -24
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-cli.service.js.map +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.d.ts +1 -1
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.js +5 -2
- package/dist/plugins/remote-access-cloudflare-tunnel/services/cloudflared-metrics.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/controllers/setup.controller.d.ts +10 -5
- package/dist/plugins/remote-access-tailscale/controllers/setup.controller.js +105 -15
- package/dist/plugins/remote-access-tailscale/controllers/setup.controller.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/controllers/status.controller.d.ts +5 -4
- package/dist/plugins/remote-access-tailscale/controllers/status.controller.js +19 -9
- package/dist/plugins/remote-access-tailscale/controllers/status.controller.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/models/status.model.d.ts +16 -0
- package/dist/plugins/remote-access-tailscale/models/status.model.js +100 -1
- package/dist/plugins/remote-access-tailscale/models/status.model.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.d.ts +2 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.js +3 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.constants.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.d.ts +17 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.js +33 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.exceptions.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.d.ts +2 -2
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.js +1 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.openapi.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.plugin.js +2 -0
- package/dist/plugins/remote-access-tailscale/remote-access-tailscale.plugin.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.d.ts +8 -4
- package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.js +58 -14
- package/dist/plugins/remote-access-tailscale/services/tailscale-cli.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.d.ts +8 -3
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js +175 -151
- package/dist/plugins/remote-access-tailscale/services/tailscale-login.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.d.ts +51 -7
- package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.js +481 -101
- package/dist/plugins/remote-access-tailscale/services/tailscale-node-managed.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-operation-coordinator.service.d.ts +33 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-operation-coordinator.service.js +167 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-operation-coordinator.service.js.map +1 -0
- package/dist/plugins/remote-access-tailscale/services/tailscale-provider.service.d.ts +13 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-provider.service.js +8 -2
- package/dist/plugins/remote-access-tailscale/services/tailscale-provider.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.d.ts +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.js +7 -6
- package/dist/plugins/remote-access-tailscale/services/tailscale-serve.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.d.ts +1 -3
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js +9 -11
- package/dist/plugins/remote-access-tailscale/services/tailscale-setup.service.js.map +1 -1
- package/dist/plugins/remote-access-tailscale/utils/tailscale-control.utils.d.ts +5 -0
- package/dist/plugins/remote-access-tailscale/utils/tailscale-control.utils.js +36 -0
- package/dist/plugins/remote-access-tailscale/utils/tailscale-control.utils.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -8,20 +8,24 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
11
14
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
15
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
16
|
};
|
|
14
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
18
|
exports.TailscaleNodeManagedService = void 0;
|
|
16
19
|
exports.compareTailscaleVersions = compareTailscaleVersions;
|
|
17
|
-
const node_child_process_1 = require("node:child_process");
|
|
18
20
|
const os_1 = __importDefault(require("os"));
|
|
19
21
|
const path_1 = require("path");
|
|
20
22
|
const common_1 = require("@nestjs/common");
|
|
21
23
|
const config_1 = require("@nestjs/config");
|
|
22
24
|
const event_emitter_1 = require("@nestjs/event-emitter");
|
|
23
25
|
const logger_1 = require("../../../common/logger");
|
|
26
|
+
const cancellable_exec_utils_1 = require("../../../common/utils/cancellable-exec.utils");
|
|
24
27
|
const config_utils_1 = require("../../../common/utils/config.utils");
|
|
28
|
+
const remote_access_observation_utils_1 = require("../../../common/utils/remote-access-observation.utils");
|
|
25
29
|
const config_service_1 = require("../../../modules/config/services/config.service");
|
|
26
30
|
const base_managed_extension_service_1 = require("../../../modules/extensions/services/base-managed-extension.service");
|
|
27
31
|
const platform_constants_1 = require("../../../modules/platform/platform.constants");
|
|
@@ -31,9 +35,9 @@ const config_model_1 = require("../models/config.model");
|
|
|
31
35
|
const remote_access_tailscale_constants_1 = require("../remote-access-tailscale.constants");
|
|
32
36
|
const remote_access_tailscale_exceptions_1 = require("../remote-access-tailscale.exceptions");
|
|
33
37
|
const tailscale_cli_service_1 = require("./tailscale-cli.service");
|
|
38
|
+
const tailscale_operation_coordinator_service_1 = require("./tailscale-operation-coordinator.service");
|
|
34
39
|
const tailscale_serve_service_1 = require("./tailscale-serve.service");
|
|
35
40
|
const tailscale_status_mapper_service_1 = require("./tailscale-status-mapper.service");
|
|
36
|
-
const TAILSCALE_REQUIREMENTS_PERIODIC_REFRESH_MS = 5 * 60 * 1000;
|
|
37
41
|
const TAILSCALE_PRINT_PLAN_TIMEOUT_MS = 2_000;
|
|
38
42
|
const OPERATOR_GRANTED_SATISFIED_MESSAGE = 'The smart-panel operator is granted.';
|
|
39
43
|
const TAILSCALE_DOCUMENTATION_URL = 'https://smart-panel.fastybird.com/docs';
|
|
@@ -52,7 +56,7 @@ function compareTailscaleVersions(a, b) {
|
|
|
52
56
|
}
|
|
53
57
|
const UNEVALUATED_MESSAGE = 'Not evaluated: the platform requirement is not satisfied.';
|
|
54
58
|
let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base_managed_extension_service_1.BaseManagedExtensionService {
|
|
55
|
-
constructor(cli, mapper, configService, nestConfigService, platformService, eventEmitter, serveService) {
|
|
59
|
+
constructor(cli, mapper, configService, nestConfigService, platformService, eventEmitter, serveService, operations = new tailscale_operation_coordinator_service_1.TailscaleOperationCoordinatorService()) {
|
|
56
60
|
super();
|
|
57
61
|
this.cli = cli;
|
|
58
62
|
this.mapper = mapper;
|
|
@@ -61,22 +65,121 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
61
65
|
this.platformService = platformService;
|
|
62
66
|
this.eventEmitter = eventEmitter;
|
|
63
67
|
this.serveService = serveService;
|
|
68
|
+
this.operations = operations;
|
|
64
69
|
this.logger = (0, logger_1.createExtensionLogger)(remote_access_tailscale_constants_1.REMOTE_ACCESS_TAILSCALE_PLUGIN_NAME, 'TailscaleNodeManagedService');
|
|
65
70
|
this.owner = { kind: 'plugin', type: remote_access_tailscale_constants_1.REMOTE_ACCESS_TAILSCALE_PLUGIN_NAME };
|
|
66
71
|
this.serviceId = 'node';
|
|
67
72
|
this.activationPolicy = 'owner-enabled';
|
|
73
|
+
this.observations = new remote_access_observation_utils_1.RemoteAccessObservation();
|
|
74
|
+
this.requirementObservations = new remote_access_observation_utils_1.RemoteAccessObservation();
|
|
75
|
+
this.authentication = 'unknown';
|
|
76
|
+
this.identity = {};
|
|
77
|
+
this.connectPromise = null;
|
|
78
|
+
this.stopPromise = null;
|
|
68
79
|
this.pollTimer = null;
|
|
69
80
|
this.lastStatus = null;
|
|
70
81
|
this.lastError = null;
|
|
71
82
|
this.pluginConfig = null;
|
|
72
83
|
this.requirementsCache = null;
|
|
73
|
-
this.requirementsRefreshedAt = 0;
|
|
74
84
|
this.lastServeConvergeDenied = false;
|
|
75
85
|
this.reconnectAttempts = 0;
|
|
76
86
|
this.nextReconnectAttemptAt = 0;
|
|
87
|
+
this.operations.onSettled((operation) => {
|
|
88
|
+
if (operation !== 'serve' && operation !== 'reconnect') {
|
|
89
|
+
this.schedulePoll(0);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
getOperationCoordinator() {
|
|
94
|
+
return this.operations;
|
|
95
|
+
}
|
|
96
|
+
getControlState() {
|
|
97
|
+
return {
|
|
98
|
+
enabled: this.isPluginEnabled(),
|
|
99
|
+
serviceState: this.state,
|
|
100
|
+
authentication: this.authentication,
|
|
101
|
+
operation: this.operations.getOperation(),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
isPluginEnabled() {
|
|
105
|
+
try {
|
|
106
|
+
return this.configService.getPluginConfig(remote_access_tailscale_constants_1.REMOTE_ACCESS_TAILSCALE_PLUGIN_NAME).enabled;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
clearAuthentication() {
|
|
113
|
+
this.authentication = 'required';
|
|
114
|
+
this.identity = {};
|
|
115
|
+
}
|
|
116
|
+
observeAuthentication(status) {
|
|
117
|
+
if (status.BackendState === 'NeedsLogin') {
|
|
118
|
+
this.clearAuthentication();
|
|
119
|
+
}
|
|
120
|
+
else if (['Running', 'Starting', 'Stopped', 'NeedsMachineAuth'].includes(status.BackendState)) {
|
|
121
|
+
this.authentication = 'authenticated';
|
|
122
|
+
this.identity = this.mapper.map(status, { port: this.getBackendPort() }).details;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async onModuleDestroy() {
|
|
126
|
+
await this.stop();
|
|
127
|
+
}
|
|
128
|
+
async ensureSupervisorStartedForLogin() {
|
|
129
|
+
if (!this.isPluginEnabled()) {
|
|
130
|
+
throw new remote_access_tailscale_exceptions_1.TailscalePluginDisabledException();
|
|
131
|
+
}
|
|
132
|
+
if (this.state !== 'started') {
|
|
133
|
+
await this.startNode(false);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
assertRequirements(requirements) {
|
|
137
|
+
const failed = requirements.find((requirement) => !requirement.satisfied);
|
|
138
|
+
if (failed) {
|
|
139
|
+
throw new remote_access_tailscale_exceptions_1.TailscaleRequirementUnsatisfiedException(failed);
|
|
140
|
+
}
|
|
141
|
+
if (requirements.length === 0) {
|
|
142
|
+
throw new tailscale_cli_service_1.TailscaleCliError('unknown', 'Tailscale setup could not be verified. Re-check setup before connecting.');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async connect() {
|
|
146
|
+
if (!this.isPluginEnabled()) {
|
|
147
|
+
throw new remote_access_tailscale_exceptions_1.TailscalePluginDisabledException();
|
|
148
|
+
}
|
|
149
|
+
if (this.connectPromise !== null) {
|
|
150
|
+
return this.connectPromise;
|
|
151
|
+
}
|
|
152
|
+
this.connectPromise = (this.state === 'started'
|
|
153
|
+
? this.operations.run('connect', async (token) => {
|
|
154
|
+
const config = this.getPluginConfig();
|
|
155
|
+
const requirements = await this.refreshRequirements('start');
|
|
156
|
+
this.operations.assertCurrent(token);
|
|
157
|
+
this.assertRequirements(requirements);
|
|
158
|
+
const status = await this.cli.getStatus();
|
|
159
|
+
this.operations.assertCurrent(token);
|
|
160
|
+
this.observeAuthentication(status);
|
|
161
|
+
if (!this.mapper.hasExistingKey(status)) {
|
|
162
|
+
throw new tailscale_cli_service_1.TailscaleCliError('needs-login', 'Sign in to Tailscale before connecting.');
|
|
163
|
+
}
|
|
164
|
+
if (this.mapper.map(status, { port: this.getBackendPort() }).state === 'connected') {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
await this.cli.set(this.buildPreferenceFlags(config));
|
|
168
|
+
this.operations.assertCurrent(token);
|
|
169
|
+
await this.cli.up(this.buildUpFlags(config));
|
|
170
|
+
this.operations.assertCurrent(token);
|
|
171
|
+
this.schedulePoll(0);
|
|
172
|
+
})
|
|
173
|
+
: this.startNode(true)).finally(() => {
|
|
174
|
+
this.connectPromise = null;
|
|
175
|
+
});
|
|
176
|
+
return this.connectPromise;
|
|
77
177
|
}
|
|
78
178
|
async start() {
|
|
79
|
-
|
|
179
|
+
return this.startNode(false);
|
|
180
|
+
}
|
|
181
|
+
async startNode(strict) {
|
|
182
|
+
await this.operations.run('connect', async (token) => {
|
|
80
183
|
if (this.state === 'started') {
|
|
81
184
|
return;
|
|
82
185
|
}
|
|
@@ -88,15 +191,31 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
88
191
|
this.logger.log('Starting Tailscale node service');
|
|
89
192
|
try {
|
|
90
193
|
const requirements = await this.refreshRequirements('start');
|
|
194
|
+
this.operations.assertCurrent(token);
|
|
195
|
+
if (strict) {
|
|
196
|
+
this.assertRequirements(requirements);
|
|
197
|
+
}
|
|
91
198
|
if (this.requirementsSatisfied(requirements)) {
|
|
92
199
|
const status = await this.getStatusOrNull();
|
|
200
|
+
this.operations.assertCurrent(token);
|
|
201
|
+
if (status) {
|
|
202
|
+
this.observeAuthentication(status);
|
|
203
|
+
}
|
|
93
204
|
if (status && this.mapper.hasExistingKey(status)) {
|
|
94
205
|
await this.cli.set(this.buildPreferenceFlags(config));
|
|
206
|
+
this.operations.assertCurrent(token);
|
|
95
207
|
await this.cli.up(this.buildUpFlags(config));
|
|
96
208
|
}
|
|
97
209
|
}
|
|
98
210
|
}
|
|
99
211
|
catch (error) {
|
|
212
|
+
this.operations.assertCurrent(token);
|
|
213
|
+
if (strict) {
|
|
214
|
+
this.state = 'error';
|
|
215
|
+
this.lastError = error instanceof Error ? error.message : 'Unknown Tailscale failure.';
|
|
216
|
+
await this.emitStatus();
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
100
219
|
if (error instanceof tailscale_cli_service_1.TailscaleCliError && error.kind === 'permission-denied') {
|
|
101
220
|
await this.refreshRequirements('permission-denied').catch(() => undefined);
|
|
102
221
|
}
|
|
@@ -109,41 +228,65 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
109
228
|
});
|
|
110
229
|
});
|
|
111
230
|
}
|
|
112
|
-
this.
|
|
231
|
+
this.operations.assertCurrent(token);
|
|
113
232
|
this.state = 'started';
|
|
233
|
+
this.lastError = null;
|
|
234
|
+
this.schedulePoll(0);
|
|
114
235
|
this.logger.log('Tailscale node service started');
|
|
115
236
|
});
|
|
237
|
+
if (strict && this.authentication !== 'authenticated') {
|
|
238
|
+
throw new tailscale_cli_service_1.TailscaleCliError('needs-login', 'Sign in to Tailscale before connecting.');
|
|
239
|
+
}
|
|
116
240
|
}
|
|
117
241
|
async stop() {
|
|
118
|
-
|
|
119
|
-
|
|
242
|
+
if (this.stopPromise !== null) {
|
|
243
|
+
return this.stopPromise;
|
|
244
|
+
}
|
|
245
|
+
const shouldDown = this.state !== 'stopped' || this.operations.getOperation() !== null || this.operations.hasPendingChildren();
|
|
246
|
+
this.state = 'stopping';
|
|
247
|
+
this.observations.invalidate();
|
|
248
|
+
this.requirementObservations.invalidate();
|
|
249
|
+
this.clearPoll();
|
|
250
|
+
this.stopPromise = this.operations
|
|
251
|
+
.interrupt('disconnect', async (token) => {
|
|
252
|
+
if (!shouldDown) {
|
|
253
|
+
this.state = 'stopped';
|
|
254
|
+
await this.emitStatus();
|
|
120
255
|
return;
|
|
121
256
|
}
|
|
122
|
-
this.state = 'stopping';
|
|
123
|
-
this.clearPoll();
|
|
124
257
|
try {
|
|
125
258
|
await this.cli.down();
|
|
259
|
+
this.operations.assertCurrent(token);
|
|
126
260
|
this.state = 'stopped';
|
|
127
261
|
}
|
|
128
262
|
catch (error) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
137
|
-
this.state = 'error';
|
|
138
|
-
this.lastError = message;
|
|
139
|
-
this.logger.error('Failed to stop the Tailscale node service', { message });
|
|
140
|
-
await this.emitStatus();
|
|
141
|
-
throw new remote_access_tailscale_exceptions_1.TailscaleNodeStopFailedException(message);
|
|
263
|
+
this.operations.assertCurrent(token);
|
|
264
|
+
const tolerated = await this.isStopFailureTolerated(error);
|
|
265
|
+
this.operations.assertCurrent(token);
|
|
266
|
+
if (!tolerated) {
|
|
267
|
+
throw error;
|
|
142
268
|
}
|
|
269
|
+
this.state = 'stopped';
|
|
270
|
+
}
|
|
271
|
+
this.lastError = null;
|
|
272
|
+
await this.emitStatus();
|
|
273
|
+
})
|
|
274
|
+
.catch(async (error) => {
|
|
275
|
+
if (error instanceof remote_access_tailscale_exceptions_1.TailscaleOperationCancelledException) {
|
|
276
|
+
throw error;
|
|
143
277
|
}
|
|
278
|
+
this.state = 'error';
|
|
279
|
+
this.lastError = error instanceof Error ? error.message : 'Unknown Tailscale failure.';
|
|
144
280
|
await this.emitStatus();
|
|
145
|
-
|
|
281
|
+
if (error instanceof Error && 'code' in error) {
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
throw new remote_access_tailscale_exceptions_1.TailscaleNodeStopFailedException(this.lastError);
|
|
285
|
+
})
|
|
286
|
+
.finally(() => {
|
|
287
|
+
this.stopPromise = null;
|
|
146
288
|
});
|
|
289
|
+
return this.stopPromise;
|
|
147
290
|
}
|
|
148
291
|
async isStopFailureTolerated(error) {
|
|
149
292
|
if (error instanceof tailscale_cli_service_1.TailscaleCliError &&
|
|
@@ -158,8 +301,10 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
158
301
|
try {
|
|
159
302
|
return await Promise.race([
|
|
160
303
|
this.getStatusOrNull(),
|
|
161
|
-
new Promise((resolve) => {
|
|
162
|
-
timer = setTimeout(() =>
|
|
304
|
+
new Promise((resolve, reject) => {
|
|
305
|
+
timer = setTimeout(() => {
|
|
306
|
+
void this.operations.reapPendingChildren().then(() => resolve(null), reject);
|
|
307
|
+
}, timeoutMs);
|
|
163
308
|
}),
|
|
164
309
|
]);
|
|
165
310
|
}
|
|
@@ -170,14 +315,65 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
170
315
|
}
|
|
171
316
|
}
|
|
172
317
|
async emitStatus() {
|
|
173
|
-
const
|
|
318
|
+
const generation = this.operations.getGeneration();
|
|
319
|
+
const status = this.state === 'stopped' || this.state === 'stopping' || this.state === 'error'
|
|
320
|
+
? this.buildStatus(this.state === 'error' ? 'error' : 'disconnected', this.lastError ?? 'The node service is stopped.', this.identity)
|
|
321
|
+
: await this.computeStatus();
|
|
322
|
+
if (!this.operations.isCurrent(generation)) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
this.operations.assertCurrent();
|
|
174
326
|
this.lastStatus = status;
|
|
175
|
-
this.eventEmitter.emit(remote_access_constants_1.EventType.
|
|
327
|
+
this.eventEmitter.emit(remote_access_constants_1.EventType.PROVIDER_OBSERVATION, {
|
|
328
|
+
status,
|
|
329
|
+
metadata: { requirements: this.getRequirements(), control: this.getControlState() },
|
|
330
|
+
});
|
|
176
331
|
}
|
|
177
332
|
async onConfigChanged() {
|
|
333
|
+
this.observations.invalidate();
|
|
334
|
+
this.requirementObservations.invalidate();
|
|
335
|
+
if (this.stopPromise !== null) {
|
|
336
|
+
await this.stopPromise;
|
|
337
|
+
}
|
|
338
|
+
this.clearPoll();
|
|
339
|
+
return this.operations.interrupt('config', async (token) => {
|
|
340
|
+
let result;
|
|
341
|
+
try {
|
|
342
|
+
result = await this.applyConfigChange();
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
this.operations.assertCurrent(token);
|
|
346
|
+
this.state = 'error';
|
|
347
|
+
this.lastError = error instanceof Error ? error.message : 'Unknown Tailscale failure.';
|
|
348
|
+
await this.emitStatus();
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
this.operations.assertCurrent(token);
|
|
352
|
+
if (this.isPollable()) {
|
|
353
|
+
this.schedulePoll(0);
|
|
354
|
+
}
|
|
355
|
+
return result;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async applyConfigChange() {
|
|
178
359
|
const previous = this.pluginConfig;
|
|
179
360
|
this.pluginConfig = null;
|
|
180
361
|
const next = this.getPluginConfig();
|
|
362
|
+
if (!next.enabled) {
|
|
363
|
+
this.state = 'stopping';
|
|
364
|
+
try {
|
|
365
|
+
await this.cli.down();
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
if (!(await this.isStopFailureTolerated(error))) {
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
this.operations.assertCurrent();
|
|
373
|
+
this.state = 'stopped';
|
|
374
|
+
await this.emitStatus();
|
|
375
|
+
return { restartRequired: false };
|
|
376
|
+
}
|
|
181
377
|
if (!previous || previous.loginServer !== next.loginServer) {
|
|
182
378
|
this.logger.log('Tailscale login_server changed (or its prior value was unknown), restart required');
|
|
183
379
|
try {
|
|
@@ -194,14 +390,21 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
194
390
|
throw error;
|
|
195
391
|
}
|
|
196
392
|
}
|
|
393
|
+
this.operations.assertCurrent();
|
|
394
|
+
this.clearAuthentication();
|
|
197
395
|
return { restartRequired: true };
|
|
198
396
|
}
|
|
397
|
+
if (!this.isPollable()) {
|
|
398
|
+
return { restartRequired: false };
|
|
399
|
+
}
|
|
199
400
|
try {
|
|
200
401
|
const requirements = await this.refreshRequirements('start');
|
|
201
402
|
if (this.requirementsSatisfied(requirements)) {
|
|
202
403
|
const status = await this.getStatusOrNull();
|
|
203
404
|
if (status && this.mapper.hasExistingKey(status)) {
|
|
405
|
+
this.operations.assertCurrent();
|
|
204
406
|
await this.cli.set(this.buildPreferenceFlags(next));
|
|
407
|
+
this.operations.assertCurrent();
|
|
205
408
|
const port = this.getBackendPort();
|
|
206
409
|
if (this.mapper.map(status, { port }).state === 'connected') {
|
|
207
410
|
await this.convergeServe(next, port, status);
|
|
@@ -210,6 +413,7 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
210
413
|
}
|
|
211
414
|
}
|
|
212
415
|
catch (error) {
|
|
416
|
+
this.operations.assertCurrent();
|
|
213
417
|
if (error instanceof tailscale_cli_service_1.TailscaleCliError && error.kind === 'permission-denied') {
|
|
214
418
|
await this.refreshRequirements('permission-denied').catch(() => undefined);
|
|
215
419
|
}
|
|
@@ -220,52 +424,114 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
220
424
|
return { restartRequired: false };
|
|
221
425
|
}
|
|
222
426
|
async isHealthy() {
|
|
427
|
+
const generation = this.operations.getGeneration();
|
|
428
|
+
if (!this.isPollable()) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
223
431
|
if (!this.requirementsSatisfied(this.getRequirements())) {
|
|
224
432
|
return false;
|
|
225
433
|
}
|
|
226
434
|
try {
|
|
227
|
-
const status = await this.
|
|
228
|
-
return
|
|
435
|
+
const { raw: status } = await this.computeStatusWithRawStatus();
|
|
436
|
+
return (this.isPollable() &&
|
|
437
|
+
this.operations.isCurrent(generation) &&
|
|
438
|
+
status?.BackendState === 'Running' &&
|
|
439
|
+
status.Self?.Online === true);
|
|
229
440
|
}
|
|
230
441
|
catch {
|
|
231
442
|
return false;
|
|
232
443
|
}
|
|
233
444
|
}
|
|
234
445
|
async factoryReset() {
|
|
235
|
-
|
|
446
|
+
this.state = 'stopping';
|
|
447
|
+
this.observations.invalidate();
|
|
448
|
+
this.requirementObservations.invalidate();
|
|
449
|
+
this.clearPoll();
|
|
236
450
|
try {
|
|
237
|
-
await this.
|
|
238
|
-
|
|
451
|
+
return await this.operations.interrupt('reset', async (token) => {
|
|
452
|
+
await this.cli.down().catch((error) => this.isStopFailureTolerated(error).then((safe) => {
|
|
453
|
+
if (!safe) {
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
}));
|
|
457
|
+
this.operations.assertCurrent(token);
|
|
458
|
+
await this.cli.serveReset().catch(() => undefined);
|
|
459
|
+
this.operations.assertCurrent(token);
|
|
460
|
+
await this.cli.logout().catch((error) => {
|
|
461
|
+
if (!(error instanceof tailscale_cli_service_1.TailscaleCliError && ['not-installed', 'needs-login', 'daemon-down'].includes(error.kind))) {
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
this.operations.assertCurrent(token);
|
|
466
|
+
this.clearAuthentication();
|
|
467
|
+
this.state = 'stopped';
|
|
468
|
+
await this.emitStatus();
|
|
469
|
+
return { success: true };
|
|
470
|
+
});
|
|
239
471
|
}
|
|
240
472
|
catch (error) {
|
|
241
|
-
if (error instanceof
|
|
242
|
-
|
|
473
|
+
if (!(error instanceof remote_access_tailscale_exceptions_1.TailscaleOperationCancelledException)) {
|
|
474
|
+
this.state = 'error';
|
|
475
|
+
this.lastError = error instanceof Error ? error.message : 'Unknown error';
|
|
476
|
+
await this.emitStatus();
|
|
243
477
|
}
|
|
244
478
|
return { success: false, reason: error instanceof Error ? error.message : 'Unknown error' };
|
|
245
479
|
}
|
|
246
480
|
}
|
|
247
481
|
async evaluateRequirements() {
|
|
248
|
-
return this.refreshRequirements('
|
|
482
|
+
return this.refreshRequirements('setup-complete');
|
|
483
|
+
}
|
|
484
|
+
async reconcileSetup(generation) {
|
|
485
|
+
if (!this.operations.isCurrent(generation) || !this.getControlState().enabled) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
await this.operations.run('config', async (token) => {
|
|
489
|
+
this.operations.assertCurrent(token);
|
|
490
|
+
if (!this.operations.isCurrent(generation)) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (this.state !== 'started') {
|
|
494
|
+
await this.startNode(false);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
const status = await this.cli.getStatus();
|
|
498
|
+
this.operations.assertCurrent(token);
|
|
499
|
+
this.observeAuthentication(status);
|
|
500
|
+
if (this.mapper.hasExistingKey(status)) {
|
|
501
|
+
await this.cli.set(this.buildPreferenceFlags(this.getPluginConfig()));
|
|
502
|
+
this.operations.assertCurrent(token);
|
|
503
|
+
await this.cli.up(this.buildUpFlags(this.getPluginConfig()));
|
|
504
|
+
this.operations.assertCurrent(token);
|
|
505
|
+
}
|
|
506
|
+
this.schedulePoll(0);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
249
509
|
}
|
|
250
510
|
getRequirements() {
|
|
251
511
|
return this.requirementsCache ?? [];
|
|
252
512
|
}
|
|
253
|
-
async refreshRequirements(reason) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
513
|
+
async refreshRequirements(reason, signal) {
|
|
514
|
+
const generation = this.operations.getGeneration();
|
|
515
|
+
const readOnly = reason === 'periodic' || reason === 'status-read';
|
|
516
|
+
const requirements = await (readOnly
|
|
517
|
+
? this.requirementObservations.run((readSignal) => this.evaluateRequirementsLive(generation, false, readSignal), signal)
|
|
518
|
+
: this.evaluateRequirementsLive(generation, true, signal));
|
|
519
|
+
signal?.throwIfAborted();
|
|
520
|
+
if (!this.operations.isCurrent(generation)) {
|
|
521
|
+
return this.getRequirements();
|
|
258
522
|
}
|
|
259
|
-
const requirements = await this.evaluateRequirementsLive();
|
|
260
523
|
this.requirementsCache = requirements;
|
|
261
|
-
this.requirementsRefreshedAt = Date.now();
|
|
262
524
|
return requirements;
|
|
263
525
|
}
|
|
264
526
|
requirementsSatisfied(requirements) {
|
|
265
527
|
return requirements.length > 0 && requirements.every((requirement) => requirement.satisfied);
|
|
266
528
|
}
|
|
267
|
-
async evaluateRequirementsLive() {
|
|
529
|
+
async evaluateRequirementsLive(generation, allowProbe = false, signal) {
|
|
268
530
|
const platform = await this.evaluatePlatformSupported();
|
|
531
|
+
signal?.throwIfAborted();
|
|
532
|
+
if (!this.operations.isCurrent(generation)) {
|
|
533
|
+
return this.getRequirements();
|
|
534
|
+
}
|
|
269
535
|
if (!platform.satisfied) {
|
|
270
536
|
return [
|
|
271
537
|
await this.finalizeRequirement(platform),
|
|
@@ -275,20 +541,34 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
275
541
|
this.unevaluatedRequirement('version-supported'),
|
|
276
542
|
];
|
|
277
543
|
}
|
|
278
|
-
const
|
|
279
|
-
this.evaluateBinaryAndVersion(),
|
|
280
|
-
this.evaluateDaemonActive(),
|
|
281
|
-
this.evaluateOperatorGranted(),
|
|
544
|
+
const results = await Promise.allSettled([
|
|
545
|
+
this.evaluateBinaryAndVersion(signal),
|
|
546
|
+
this.evaluateDaemonActive(signal),
|
|
547
|
+
this.evaluateOperatorGranted(generation, allowProbe, signal),
|
|
282
548
|
]);
|
|
283
|
-
|
|
549
|
+
signal?.throwIfAborted();
|
|
550
|
+
const [binaryResult, daemonResult, operatorResult] = results;
|
|
551
|
+
if (binaryResult.status === 'rejected') {
|
|
552
|
+
throw binaryResult.reason;
|
|
553
|
+
}
|
|
554
|
+
if (daemonResult.status === 'rejected') {
|
|
555
|
+
throw daemonResult.reason;
|
|
556
|
+
}
|
|
557
|
+
if (operatorResult.status === 'rejected') {
|
|
558
|
+
throw operatorResult.reason;
|
|
559
|
+
}
|
|
560
|
+
const { binary, version } = binaryResult.value;
|
|
561
|
+
const daemonActive = daemonResult.value;
|
|
562
|
+
const operatorGranted = operatorResult.value;
|
|
563
|
+
return this.attachRemedies([platform, binary, daemonActive, operatorGranted, version], signal);
|
|
284
564
|
}
|
|
285
565
|
unevaluatedRequirement(code) {
|
|
286
566
|
return { code, satisfied: false, message: UNEVALUATED_MESSAGE, remedy: null };
|
|
287
567
|
}
|
|
288
|
-
async attachRemedies(requirements) {
|
|
568
|
+
async attachRemedies(requirements, signal) {
|
|
289
569
|
let installRemedyPromise = null;
|
|
290
570
|
const getInstallRemedy = () => {
|
|
291
|
-
installRemedyPromise ??= this.buildInstallRemedy();
|
|
571
|
+
installRemedyPromise ??= this.buildInstallRemedy(signal);
|
|
292
572
|
return installRemedyPromise;
|
|
293
573
|
};
|
|
294
574
|
return Promise.all(requirements.map((requirement) => this.finalizeRequirement(requirement, getInstallRemedy)));
|
|
@@ -317,9 +597,9 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
317
597
|
return { commands: [], note: TAILSCALE_DOCUMENTATION_URL };
|
|
318
598
|
}
|
|
319
599
|
}
|
|
320
|
-
async buildInstallRemedy() {
|
|
600
|
+
async buildInstallRemedy(signal) {
|
|
321
601
|
try {
|
|
322
|
-
const stdout = await this.runSetupScriptPrintPlan(
|
|
602
|
+
const stdout = await this.runSetupScriptPrintPlan(signal);
|
|
323
603
|
const lines = stdout
|
|
324
604
|
.split('\n')
|
|
325
605
|
.map((line) => line.trim())
|
|
@@ -339,37 +619,100 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
339
619
|
return { commands: [], note: TAILSCALE_VENDOR_DOWNLOAD_URL };
|
|
340
620
|
}
|
|
341
621
|
}
|
|
342
|
-
runSetupScriptPrintPlan(
|
|
622
|
+
async runSetupScriptPrintPlan(signal) {
|
|
343
623
|
const script = (0, path_1.join)(__dirname, '..', 'scripts', 'tailscale-setup.sh');
|
|
344
|
-
return
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
624
|
+
return (await (0, cancellable_exec_utils_1.cancellableExecFile)('bash', [script, '--print-plan', '--step=install'], signal, TAILSCALE_PRINT_PLAN_TIMEOUT_MS)).stdout;
|
|
625
|
+
}
|
|
626
|
+
async computeStatus(_requirementsReason = 'status-read', options) {
|
|
627
|
+
return (await this.computeStatusWithRawStatus(options)).status;
|
|
628
|
+
}
|
|
629
|
+
async awaitObservationIdle() {
|
|
630
|
+
await Promise.all([this.observations.awaitIdle(), this.requirementObservations.awaitIdle()]);
|
|
631
|
+
}
|
|
632
|
+
async getStatusSnapshot(options) {
|
|
633
|
+
const observation = await this.computeStatusWithRawStatus(options);
|
|
634
|
+
return {
|
|
635
|
+
status: observation.status,
|
|
636
|
+
metadata: { requirements: observation.requirements, control: this.getControlState() },
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
async computeStatusWithRawStatus(options) {
|
|
640
|
+
const generation = this.operations.getGeneration();
|
|
641
|
+
const revision = this.operations.getObservationRevision();
|
|
642
|
+
try {
|
|
643
|
+
const result = await this.observations.run(async (signal) => {
|
|
644
|
+
const requirements = await this.refreshRequirements('status-read', signal);
|
|
645
|
+
const collected = await this.collectStatusWithRawStatus(generation, signal, requirements);
|
|
646
|
+
signal.throwIfAborted();
|
|
647
|
+
return { ...collected, requirements };
|
|
648
|
+
}, options?.signal);
|
|
649
|
+
if (!this.operations.isCurrent(generation) || !this.operations.isObservationCurrent(revision)) {
|
|
650
|
+
return this.supersededObservation();
|
|
651
|
+
}
|
|
652
|
+
if (result.raw && this.isPollable()) {
|
|
653
|
+
this.observeAuthentication(result.raw);
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
if (!this.operations.isCurrent(generation) ||
|
|
659
|
+
!this.operations.isObservationCurrent(revision) ||
|
|
660
|
+
!this.isPollable()) {
|
|
661
|
+
return this.supersededObservation();
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
status: this.buildStatus('error', error instanceof Error ? error.message : 'The status observation failed.'),
|
|
665
|
+
raw: null,
|
|
666
|
+
requirements: this.getRequirements(),
|
|
667
|
+
};
|
|
668
|
+
}
|
|
353
669
|
}
|
|
354
|
-
|
|
355
|
-
return
|
|
670
|
+
supersededObservation() {
|
|
671
|
+
return {
|
|
672
|
+
status: this.buildStatus(this.state === 'error' ? 'error' : 'disconnected', this.lastError ?? 'The observation was superseded by a lifecycle action.'),
|
|
673
|
+
raw: null,
|
|
674
|
+
requirements: this.getRequirements(),
|
|
675
|
+
};
|
|
356
676
|
}
|
|
357
|
-
async
|
|
677
|
+
async collectStatusWithRawStatus(generation = this.operations.getGeneration(), signal, requirements = this.getRequirements()) {
|
|
358
678
|
if (this.state === 'stopped' || this.state === 'stopping') {
|
|
359
|
-
return { status: this.buildStatus('disconnected', 'The node service is stopped.'), raw: null };
|
|
679
|
+
return { status: this.buildStatus('disconnected', 'The node service is stopped.', this.identity), raw: null };
|
|
360
680
|
}
|
|
361
681
|
if (this.state === 'error') {
|
|
362
682
|
return { status: this.buildStatus('error', this.lastError ?? 'The Tailscale node service failed.'), raw: null };
|
|
363
683
|
}
|
|
364
|
-
const platform =
|
|
684
|
+
const platform = requirements.find((requirement) => requirement.code === 'platform-supported') ?? {
|
|
685
|
+
satisfied: false,
|
|
686
|
+
message: 'Platform detection did not complete.',
|
|
687
|
+
};
|
|
688
|
+
signal?.throwIfAborted();
|
|
689
|
+
if (!this.operations.isCurrent(generation)) {
|
|
690
|
+
return {
|
|
691
|
+
status: this.buildStatus('disconnected', 'The observation was superseded by a lifecycle action.', this.identity),
|
|
692
|
+
raw: null,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
365
695
|
if (!platform.satisfied) {
|
|
366
696
|
return { status: this.buildStatus('unsupported', platform.message), raw: null };
|
|
367
697
|
}
|
|
368
698
|
try {
|
|
369
|
-
const status = await this.cli.getStatus();
|
|
699
|
+
const status = await this.cli.getStatus(signal);
|
|
700
|
+
signal?.throwIfAborted();
|
|
701
|
+
if (!this.operations.isCurrent(generation)) {
|
|
702
|
+
return {
|
|
703
|
+
status: this.buildStatus('disconnected', 'The observation was superseded by a lifecycle action.', this.identity),
|
|
704
|
+
raw: null,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
370
707
|
const port = this.getBackendPort();
|
|
371
708
|
const postureAdvisories = this.buildPostureAdvisories(status);
|
|
372
|
-
|
|
709
|
+
signal?.throwIfAborted();
|
|
710
|
+
if (!this.operations.isCurrent(generation)) {
|
|
711
|
+
return {
|
|
712
|
+
status: this.buildStatus('disconnected', 'The observation was superseded by a lifecycle action.', this.identity),
|
|
713
|
+
raw: null,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
373
716
|
const operatorRequirement = requirements.find((requirement) => requirement.code === 'operator-granted');
|
|
374
717
|
if (operatorRequirement && !operatorRequirement.satisfied) {
|
|
375
718
|
return {
|
|
@@ -388,7 +731,7 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
388
731
|
};
|
|
389
732
|
}
|
|
390
733
|
const config = this.getPluginConfig();
|
|
391
|
-
const serveResult = await this.serveService.read(config, port, status);
|
|
734
|
+
const serveResult = await this.serveService.read(config, port, status, signal);
|
|
392
735
|
return {
|
|
393
736
|
status: this.buildStatus(mapped.state, mapped.message, mapped.details, [...mapped.proxyAddresses, ...serveResult.proxyAddresses], [...mapped.endpoints, ...serveResult.endpoints], [...postureAdvisories, ...serveResult.advisories]),
|
|
394
737
|
raw: status,
|
|
@@ -414,7 +757,11 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
414
757
|
}
|
|
415
758
|
}
|
|
416
759
|
async convergeServe(config, port, status) {
|
|
417
|
-
const result = await this.
|
|
760
|
+
const result = await this.operations.run('serve', async (token) => {
|
|
761
|
+
const converged = await this.serveService.converge(config, port, status);
|
|
762
|
+
this.operations.assertCurrent(token);
|
|
763
|
+
return converged;
|
|
764
|
+
}, false);
|
|
418
765
|
if (result.permissionDenied) {
|
|
419
766
|
if (!this.lastServeConvergeDenied) {
|
|
420
767
|
this.lastServeConvergeDenied = true;
|
|
@@ -453,9 +800,9 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
453
800
|
message: `Tailscale is not supported on the '${platformType}' platform.`,
|
|
454
801
|
};
|
|
455
802
|
}
|
|
456
|
-
async evaluateBinaryAndVersion() {
|
|
803
|
+
async evaluateBinaryAndVersion(signal) {
|
|
457
804
|
try {
|
|
458
|
-
const info = await this.cli.getVersion();
|
|
805
|
+
const info = await this.cli.getVersion(signal);
|
|
459
806
|
const supported = compareTailscaleVersions(info.version, remote_access_tailscale_constants_1.TAILSCALE_MIN_VERSION) >= 0;
|
|
460
807
|
return {
|
|
461
808
|
binary: { code: 'binary-installed', satisfied: true, message: `Tailscale ${info.version} is installed.` },
|
|
@@ -484,21 +831,24 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
484
831
|
};
|
|
485
832
|
}
|
|
486
833
|
}
|
|
487
|
-
async evaluateDaemonActive() {
|
|
488
|
-
const active = await this.isSystemdUnitActive('tailscaled');
|
|
834
|
+
async evaluateDaemonActive(signal) {
|
|
835
|
+
const active = await this.isSystemdUnitActive('tailscaled', signal);
|
|
489
836
|
return {
|
|
490
837
|
code: 'daemon-active',
|
|
491
838
|
satisfied: active,
|
|
492
839
|
message: active ? 'tailscaled is active.' : 'tailscaled is not active. Run setup or start the service.',
|
|
493
840
|
};
|
|
494
841
|
}
|
|
495
|
-
async evaluateOperatorGranted() {
|
|
842
|
+
async evaluateOperatorGranted(generation = this.operations.getGeneration(), allowProbe = false, signal) {
|
|
496
843
|
const serviceUser = os_1.default.userInfo().username;
|
|
497
844
|
try {
|
|
498
|
-
const prefs = await this.cli.getPrefs();
|
|
845
|
+
const prefs = await this.cli.getPrefs(signal);
|
|
499
846
|
return this.buildOperatorRequirement(prefs.OperatorUser === serviceUser, serviceUser);
|
|
500
847
|
}
|
|
501
848
|
catch (error) {
|
|
849
|
+
if (!this.operations.isCurrent(generation)) {
|
|
850
|
+
return { code: 'operator-granted', satisfied: false, message: 'The requirement observation was superseded.' };
|
|
851
|
+
}
|
|
502
852
|
if (error instanceof tailscale_cli_service_1.TailscaleCliError) {
|
|
503
853
|
if (error.kind === 'daemon-down') {
|
|
504
854
|
return {
|
|
@@ -515,12 +865,22 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
515
865
|
};
|
|
516
866
|
}
|
|
517
867
|
}
|
|
868
|
+
if (!allowProbe) {
|
|
869
|
+
const verified = this.getRequirements().find((requirement) => requirement.code === 'operator-granted');
|
|
870
|
+
return verified?.satisfied
|
|
871
|
+
? verified
|
|
872
|
+
: {
|
|
873
|
+
code: 'operator-granted',
|
|
874
|
+
satisfied: false,
|
|
875
|
+
message: 'The operator grant cannot be verified by this Tailscale version. Run Set up or Connect to verify management access.',
|
|
876
|
+
};
|
|
877
|
+
}
|
|
518
878
|
return this.evaluateOperatorGrantedViaProbe(serviceUser);
|
|
519
879
|
}
|
|
520
880
|
}
|
|
521
881
|
async evaluateOperatorGrantedViaProbe(serviceUser) {
|
|
522
882
|
try {
|
|
523
|
-
await this.cli.set([`--operator=${serviceUser}`]);
|
|
883
|
+
await this.operations.run('preferences', async () => this.cli.set([`--operator=${serviceUser}`]), false);
|
|
524
884
|
return this.buildOperatorRequirement(true, serviceUser);
|
|
525
885
|
}
|
|
526
886
|
catch (error) {
|
|
@@ -563,16 +923,15 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
563
923
|
return null;
|
|
564
924
|
}
|
|
565
925
|
}
|
|
566
|
-
isSystemdUnitActive(unit) {
|
|
567
|
-
|
|
568
|
-
(0,
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
});
|
|
926
|
+
async isSystemdUnitActive(unit, signal) {
|
|
927
|
+
try {
|
|
928
|
+
const result = await (0, cancellable_exec_utils_1.cancellableExecFile)('systemctl', ['is-active', unit], signal, remote_access_tailscale_constants_1.TAILSCALE_SYSTEMCTL_PROBE_TIMEOUT_MS);
|
|
929
|
+
return result.exitCode === 0 && result.stdout.trim() === 'active';
|
|
930
|
+
}
|
|
931
|
+
catch {
|
|
932
|
+
signal?.throwIfAborted();
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
576
935
|
}
|
|
577
936
|
isPollable() {
|
|
578
937
|
return this.state === 'starting' || this.state === 'started';
|
|
@@ -583,7 +942,7 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
583
942
|
return;
|
|
584
943
|
}
|
|
585
944
|
this.pollTimer = setTimeout(() => {
|
|
586
|
-
void this.pollTick();
|
|
945
|
+
this.operations.withoutOperation(() => void this.pollTick());
|
|
587
946
|
}, delayMs);
|
|
588
947
|
this.pollTimer.unref?.();
|
|
589
948
|
}
|
|
@@ -594,9 +953,13 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
594
953
|
}
|
|
595
954
|
}
|
|
596
955
|
async pollTick() {
|
|
956
|
+
const generation = this.operations.getGeneration();
|
|
957
|
+
let revision = this.operations.getObservationRevision();
|
|
597
958
|
try {
|
|
598
|
-
let { status, raw } = await this.computeStatusWithRawStatus(
|
|
599
|
-
if (!this.isPollable()
|
|
959
|
+
let { status, raw, requirements } = await this.computeStatusWithRawStatus();
|
|
960
|
+
if (!this.isPollable() ||
|
|
961
|
+
!this.operations.isCurrent(generation) ||
|
|
962
|
+
!this.operations.isObservationCurrent(revision)) {
|
|
600
963
|
return;
|
|
601
964
|
}
|
|
602
965
|
if (status.state === 'connected') {
|
|
@@ -609,27 +972,39 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
609
972
|
this.requirementsSatisfied(this.getRequirements()) &&
|
|
610
973
|
Date.now() >= this.nextReconnectAttemptAt) {
|
|
611
974
|
const reconnected = await this.attemptReconnect();
|
|
612
|
-
|
|
975
|
+
revision = this.operations.getObservationRevision();
|
|
976
|
+
if (!this.isPollable() || !this.operations.isCurrent(generation)) {
|
|
613
977
|
return;
|
|
614
978
|
}
|
|
615
979
|
if (reconnected) {
|
|
616
|
-
({ status, raw } = await this.computeStatusWithRawStatus(
|
|
617
|
-
if (!this.isPollable()) {
|
|
980
|
+
({ status, raw, requirements } = await this.computeStatusWithRawStatus());
|
|
981
|
+
if (!this.isPollable() || !this.operations.isCurrent(generation)) {
|
|
618
982
|
return;
|
|
619
983
|
}
|
|
620
984
|
}
|
|
621
985
|
}
|
|
622
|
-
if (
|
|
623
|
-
|
|
986
|
+
if (!this.isPollable() ||
|
|
987
|
+
!this.operations.isCurrent(generation) ||
|
|
988
|
+
!this.operations.isObservationCurrent(revision)) {
|
|
989
|
+
return;
|
|
624
990
|
}
|
|
625
991
|
if (this.hasStatusChanged(this.lastStatus, status)) {
|
|
626
992
|
this.lastStatus = status;
|
|
627
|
-
this.eventEmitter.emit(remote_access_constants_1.EventType.
|
|
993
|
+
this.eventEmitter.emit(remote_access_constants_1.EventType.PROVIDER_OBSERVATION, {
|
|
994
|
+
status,
|
|
995
|
+
metadata: { requirements, control: this.getControlState() },
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
if (raw && status.state === 'connected' && this.requirementsSatisfied(this.getRequirements())) {
|
|
999
|
+
await this.convergeServe(this.getPluginConfig(), this.getBackendPort(), raw);
|
|
1000
|
+
}
|
|
1001
|
+
if (!this.isPollable() || !this.operations.isCurrent(generation)) {
|
|
1002
|
+
return;
|
|
628
1003
|
}
|
|
629
1004
|
this.schedulePoll(status.state === 'connecting' ? remote_access_tailscale_constants_1.TAILSCALE_POLL_INTERVAL_TRANSITIONING_MS : remote_access_tailscale_constants_1.TAILSCALE_POLL_INTERVAL_STABLE_MS);
|
|
630
1005
|
}
|
|
631
1006
|
catch (error) {
|
|
632
|
-
if (!this.isPollable()) {
|
|
1007
|
+
if (!this.isPollable() || !this.operations.isCurrent(generation)) {
|
|
633
1008
|
return;
|
|
634
1009
|
}
|
|
635
1010
|
this.logger.error('Tailscale status poll failed', {
|
|
@@ -639,20 +1014,23 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
639
1014
|
}
|
|
640
1015
|
}
|
|
641
1016
|
async attemptReconnect() {
|
|
642
|
-
return this.
|
|
1017
|
+
return this.operations.run('reconnect', async (token) => {
|
|
643
1018
|
if (this.state !== 'started') {
|
|
644
1019
|
return false;
|
|
645
1020
|
}
|
|
646
1021
|
try {
|
|
647
1022
|
const config = this.getPluginConfig();
|
|
648
1023
|
await this.cli.set(this.buildPreferenceFlags(config));
|
|
1024
|
+
this.operations.assertCurrent(token);
|
|
649
1025
|
await this.cli.up(this.buildUpFlags(config));
|
|
1026
|
+
this.operations.assertCurrent(token);
|
|
650
1027
|
this.reconnectAttempts = 0;
|
|
651
1028
|
this.nextReconnectAttemptAt = 0;
|
|
652
1029
|
this.logger.log('Tailscale node reconnected automatically after being found unexpectedly disconnected.');
|
|
653
1030
|
return true;
|
|
654
1031
|
}
|
|
655
1032
|
catch (error) {
|
|
1033
|
+
this.operations.assertCurrent(token);
|
|
656
1034
|
this.reconnectAttempts += 1;
|
|
657
1035
|
const delayMs = Math.min(remote_access_tailscale_constants_1.TAILSCALE_RECONNECT_BASE_DELAY_MS * 2 ** (this.reconnectAttempts - 1), remote_access_tailscale_constants_1.TAILSCALE_RECONNECT_MAX_DELAY_MS);
|
|
658
1036
|
this.nextReconnectAttemptAt = Date.now() + delayMs;
|
|
@@ -746,12 +1124,14 @@ let TailscaleNodeManagedService = class TailscaleNodeManagedService extends base
|
|
|
746
1124
|
exports.TailscaleNodeManagedService = TailscaleNodeManagedService;
|
|
747
1125
|
exports.TailscaleNodeManagedService = TailscaleNodeManagedService = __decorate([
|
|
748
1126
|
(0, common_1.Injectable)(),
|
|
1127
|
+
__param(7, (0, common_1.Optional)()),
|
|
749
1128
|
__metadata("design:paramtypes", [tailscale_cli_service_1.TailscaleCliService,
|
|
750
1129
|
tailscale_status_mapper_service_1.TailscaleStatusMapperService,
|
|
751
1130
|
config_service_1.ConfigService,
|
|
752
1131
|
config_1.ConfigService,
|
|
753
1132
|
platform_service_1.PlatformService,
|
|
754
1133
|
event_emitter_1.EventEmitter2,
|
|
755
|
-
tailscale_serve_service_1.TailscaleServeService
|
|
1134
|
+
tailscale_serve_service_1.TailscaleServeService,
|
|
1135
|
+
tailscale_operation_coordinator_service_1.TailscaleOperationCoordinatorService])
|
|
756
1136
|
], TailscaleNodeManagedService);
|
|
757
1137
|
//# sourceMappingURL=tailscale-node-managed.service.js.map
|