feat(FN-2585): remove top-level remote access toggle
- Remove the standalone remoteEnabled setting from CLI, core settings defaults/types, and dashboard settings APIs/UI - Treat remote access as enabled when an active provider is selected and that provider is configured as enabled - Update remote auth and engine lifecycle checks to gate on provider activation instead of a global flag - Adjust tests and add a changeset documenting the remote access configuration simplification
This commit is contained in:
6
.changeset/fn-2585-simplify-remote-access.md
Normal file
6
.changeset/fn-2585-simplify-remote-access.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@fusion/cli": patch
|
||||
"@fusion/engine": patch
|
||||
---
|
||||
|
||||
Simplify remote access configuration: remove the separate top-level enable toggle. Remote access is now active when a provider (Tailscale or Cloudflare) is selected and configured.
|
||||
@@ -34,7 +34,7 @@ function makeInteractiveData(opts: {
|
||||
models?: ModelItem[];
|
||||
taskDetail?: TaskDetailData | null;
|
||||
remote?: Partial<{
|
||||
getSettings: () => Promise<{ remoteEnabled: boolean; activeProvider: "tailscale" | "cloudflare" | null; tailscaleEnabled: boolean; cloudflareEnabled: boolean; shortLivedEnabled: boolean; shortLivedTtlMs: number }>;
|
||||
getSettings: () => Promise<{ activeProvider: "tailscale" | "cloudflare" | null; tailscaleEnabled: boolean; cloudflareEnabled: boolean; shortLivedEnabled: boolean; shortLivedTtlMs: number }>;
|
||||
getStatus: () => Promise<{ provider: "tailscale" | "cloudflare" | null; state: "stopped" | "starting" | "running" | "error"; url: string | null; lastError: string | null }>;
|
||||
activateProvider: (provider: "tailscale" | "cloudflare") => Promise<void>;
|
||||
startTunnel: () => Promise<void>;
|
||||
@@ -58,7 +58,6 @@ function makeInteractiveData(opts: {
|
||||
pollIntervalMs: 60000,
|
||||
enginePaused: false,
|
||||
globalPause: false,
|
||||
remoteEnabled: false,
|
||||
remoteActiveProvider: null,
|
||||
remoteShortLivedEnabled: false,
|
||||
remoteShortLivedTtlMs: 900000,
|
||||
@@ -66,7 +65,6 @@ function makeInteractiveData(opts: {
|
||||
const models = opts.models ?? [];
|
||||
const remoteDefaults = {
|
||||
getSettings: async () => ({
|
||||
remoteEnabled: settings.remoteEnabled,
|
||||
activeProvider: settings.remoteActiveProvider,
|
||||
tailscaleEnabled: true,
|
||||
cloudflareEnabled: true,
|
||||
@@ -353,7 +351,6 @@ describe("Settings view", () => {
|
||||
pollIntervalMs: 60000,
|
||||
enginePaused: false,
|
||||
globalPause: false,
|
||||
remoteEnabled: true,
|
||||
remoteActiveProvider: "tailscale",
|
||||
remoteShortLivedEnabled: true,
|
||||
remoteShortLivedTtlMs: 600000,
|
||||
@@ -401,7 +398,6 @@ describe("Settings view", () => {
|
||||
pollIntervalMs: 60000,
|
||||
enginePaused: false,
|
||||
globalPause: false,
|
||||
remoteEnabled: true,
|
||||
remoteActiveProvider: "cloudflare",
|
||||
remoteShortLivedEnabled: true,
|
||||
remoteShortLivedTtlMs: 600000,
|
||||
|
||||
@@ -448,7 +448,6 @@ function SettingsPanel({ state, isFocused }: { state: DashboardState; isFocused:
|
||||
["pollMs", `${s.pollIntervalMs}`],
|
||||
["paused", s.enginePaused ? "yes" : "no"],
|
||||
["globalPause", s.globalPause ? "yes" : "no"],
|
||||
["remoteEnabled", s.remoteEnabled ? "enabled" : "disabled"],
|
||||
["remoteProvider", s.remoteActiveProvider ?? "none"],
|
||||
["remoteState", s.remoteStatus?.state ?? "unknown"],
|
||||
] as Array<[string, string]>
|
||||
@@ -2105,7 +2104,7 @@ function AgentsView({ state }: { state: DashboardState }) {
|
||||
|
||||
// ── Settings interactive view ─────────────────────────────────────────────────
|
||||
|
||||
type SettingKey = "maxConcurrent" | "maxWorktrees" | "autoMerge" | "mergeStrategy" | "pollIntervalMs" | "enginePaused" | "globalPause" | "remoteEnabled" | "remoteActiveProvider" | "remoteShortLivedEnabled" | "remoteShortLivedTtlMs";
|
||||
type SettingKey = "maxConcurrent" | "maxWorktrees" | "autoMerge" | "mergeStrategy" | "pollIntervalMs" | "enginePaused" | "globalPause" | "remoteActiveProvider" | "remoteShortLivedEnabled" | "remoteShortLivedTtlMs";
|
||||
|
||||
interface SettingDef {
|
||||
key: SettingKey;
|
||||
@@ -2122,7 +2121,6 @@ const SETTING_DEFS: SettingDef[] = [
|
||||
{ key: "pollIntervalMs", label: "Poll Interval (ms)", type: "number" },
|
||||
{ key: "enginePaused", label: "Engine Paused", type: "boolean" },
|
||||
{ key: "globalPause", label: "Global Pause", type: "boolean" },
|
||||
{ key: "remoteEnabled", label: "Remote Access", type: "boolean" },
|
||||
{ key: "remoteActiveProvider", label: "Remote Provider", type: "enum", options: ["tailscale", "cloudflare"] },
|
||||
{ key: "remoteShortLivedEnabled", label: "Short-Lived Tokens", type: "boolean" },
|
||||
{ key: "remoteShortLivedTtlMs", label: "Short-Lived TTL (ms)", type: "number" },
|
||||
@@ -2513,8 +2511,6 @@ function SettingsInteractiveView({ state, controller }: { state: DashboardState;
|
||||
<Text color={localSettings.remoteStatus?.state === "running" ? "green" : "yellow"}>{localSettings.remoteStatus?.state ?? "unknown"}</Text>
|
||||
</Box>
|
||||
<Box flexDirection="row" gap={1}>
|
||||
<Text dimColor>Enabled:</Text>
|
||||
<Text>{localSettings.remoteSettingsSnapshot?.remoteEnabled ? "yes" : "no"}</Text>
|
||||
<Text dimColor>Short-lived:</Text>
|
||||
<Text>{localSettings.remoteSettingsSnapshot?.shortLivedEnabled ? "on" : "off"}</Text>
|
||||
</Box>
|
||||
|
||||
@@ -75,7 +75,6 @@ export interface RemoteQrPayload {
|
||||
}
|
||||
|
||||
export interface RemoteSettingsSnapshot {
|
||||
remoteEnabled: boolean;
|
||||
activeProvider: RemoteProvider | null;
|
||||
tailscaleEnabled: boolean;
|
||||
cloudflareEnabled: boolean;
|
||||
@@ -91,7 +90,6 @@ export interface SettingsValues {
|
||||
pollIntervalMs: number;
|
||||
enginePaused: boolean;
|
||||
globalPause: boolean;
|
||||
remoteEnabled: boolean;
|
||||
remoteActiveProvider: RemoteProvider | null;
|
||||
remoteShortLivedEnabled: boolean;
|
||||
remoteShortLivedTtlMs: number;
|
||||
|
||||
@@ -722,7 +722,6 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
pollIntervalMs: fullSettings.pollIntervalMs ?? 60_000,
|
||||
enginePaused: fullSettings.enginePaused ?? false,
|
||||
globalPause: fullSettings.globalPause ?? false,
|
||||
remoteEnabled: Boolean(fullSettings.remoteEnabled),
|
||||
remoteActiveProvider: (fullSettings.remoteActiveProvider as "tailscale" | "cloudflare" | null) ?? null,
|
||||
remoteShortLivedEnabled: Boolean(fullSettings.remoteShortLivedEnabled),
|
||||
remoteShortLivedTtlMs: Number(fullSettings.remoteShortLivedTtlMs ?? 900_000),
|
||||
@@ -736,7 +735,6 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
pollIntervalMs: 60_000,
|
||||
enginePaused: paused,
|
||||
globalPause: false,
|
||||
remoteEnabled: false,
|
||||
remoteActiveProvider: null,
|
||||
remoteShortLivedEnabled: false,
|
||||
remoteShortLivedTtlMs: 900_000,
|
||||
@@ -874,7 +872,6 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
pollIntervalMs: settings.pollIntervalMs ?? 60_000,
|
||||
enginePaused: settings.enginePaused ?? false,
|
||||
globalPause: settings.globalPause ?? false,
|
||||
remoteEnabled: Boolean(settings.remoteEnabled),
|
||||
remoteActiveProvider: (settings.remoteActiveProvider as "tailscale" | "cloudflare" | null) ?? null,
|
||||
remoteShortLivedEnabled: Boolean(settings.remoteShortLivedEnabled),
|
||||
remoteShortLivedTtlMs: Number(settings.remoteShortLivedTtlMs ?? 900_000),
|
||||
@@ -1846,7 +1843,6 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
pollIntervalMs: settings.pollIntervalMs ?? 60_000,
|
||||
enginePaused: settings.enginePaused ?? false,
|
||||
globalPause: settings.globalPause ?? false,
|
||||
remoteEnabled: Boolean(settings.remoteEnabled),
|
||||
remoteActiveProvider: (settings.remoteActiveProvider as "tailscale" | "cloudflare" | null) ?? null,
|
||||
remoteShortLivedEnabled: Boolean(settings.remoteShortLivedEnabled),
|
||||
remoteShortLivedTtlMs: Number(settings.remoteShortLivedTtlMs ?? 900_000),
|
||||
@@ -1986,12 +1982,10 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
pollIntervalMs: s.pollIntervalMs ?? 60_000,
|
||||
enginePaused: s.enginePaused ?? false,
|
||||
globalPause: s.globalPause ?? false,
|
||||
remoteEnabled: Boolean(s.remoteEnabled),
|
||||
remoteActiveProvider: (s.remoteActiveProvider as "tailscale" | "cloudflare" | null) ?? null,
|
||||
remoteShortLivedEnabled: Boolean(s.remoteShortLivedEnabled),
|
||||
remoteShortLivedTtlMs: Number(s.remoteShortLivedTtlMs ?? 900_000),
|
||||
remoteSettingsSnapshot: {
|
||||
remoteEnabled: Boolean(s.remoteEnabled),
|
||||
activeProvider: (s.remoteActiveProvider as "tailscale" | "cloudflare" | null) ?? null,
|
||||
tailscaleEnabled: Boolean(s.remoteTailscaleEnabled),
|
||||
cloudflareEnabled: Boolean(s.remoteCloudflareEnabled),
|
||||
@@ -2010,7 +2004,6 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
if (partial.pollIntervalMs !== undefined) mapped.pollIntervalMs = partial.pollIntervalMs;
|
||||
if (partial.enginePaused !== undefined) mapped.enginePaused = partial.enginePaused;
|
||||
if (partial.globalPause !== undefined) mapped.globalPause = partial.globalPause;
|
||||
if (partial.remoteEnabled !== undefined) mapped.remoteEnabled = partial.remoteEnabled;
|
||||
if (partial.remoteActiveProvider !== undefined) mapped.remoteActiveProvider = partial.remoteActiveProvider;
|
||||
if (partial.remoteShortLivedEnabled !== undefined) mapped.remoteShortLivedEnabled = partial.remoteShortLivedEnabled;
|
||||
if (partial.remoteShortLivedTtlMs !== undefined) mapped.remoteShortLivedTtlMs = partial.remoteShortLivedTtlMs;
|
||||
@@ -2037,7 +2030,6 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
}
|
||||
const payload = await response.json();
|
||||
return {
|
||||
remoteEnabled: Boolean(payload?.settings?.remoteEnabled),
|
||||
activeProvider: (payload?.settings?.remoteActiveProvider as "tailscale" | "cloudflare" | null) ?? null,
|
||||
tailscaleEnabled: Boolean(payload?.settings?.remoteTailscaleEnabled),
|
||||
cloudflareEnabled: Boolean(payload?.settings?.remoteCloudflareEnabled),
|
||||
|
||||
@@ -2628,7 +2628,6 @@ describe("TaskStore", () => {
|
||||
|
||||
describe("remoteAccess settings", () => {
|
||||
const baseRemoteAccess = {
|
||||
enabled: true,
|
||||
activeProvider: "cloudflare" as const,
|
||||
providers: {
|
||||
tailscale: {
|
||||
@@ -2759,7 +2758,6 @@ describe("TaskStore", () => {
|
||||
await store.updateSettings({ remoteAccess: null as any });
|
||||
|
||||
const settings = await store.getSettings();
|
||||
expect(settings.remoteAccess?.enabled).toBe(false);
|
||||
expect(settings.remoteAccess?.activeProvider).toBeNull();
|
||||
expect(settings.remoteAccess?.tokenStrategy.persistent.token).toBeNull();
|
||||
|
||||
|
||||
@@ -157,7 +157,6 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
||||
agentPrompts: undefined,
|
||||
promptOverrides: undefined,
|
||||
remoteAccess: {
|
||||
enabled: false,
|
||||
activeProvider: null,
|
||||
providers: {
|
||||
tailscale: {
|
||||
|
||||
@@ -1214,7 +1214,6 @@ export interface RemoteAccessLifecycleConfig {
|
||||
}
|
||||
|
||||
export interface RemoteAccessProjectSettings {
|
||||
enabled: boolean;
|
||||
activeProvider: RemoteAccessProvider | null;
|
||||
providers: RemoteAccessProvidersConfig;
|
||||
tokenStrategy: RemoteAccessTokenStrategyConfig;
|
||||
|
||||
@@ -404,7 +404,6 @@ export function updateSettings(settings: Partial<Settings>, projectId?: string):
|
||||
}
|
||||
|
||||
export interface RemoteSettings {
|
||||
remoteEnabled: boolean;
|
||||
remoteActiveProvider: "tailscale" | "cloudflare" | null;
|
||||
remoteTailscaleEnabled: boolean;
|
||||
remoteTailscaleHostname: string;
|
||||
|
||||
@@ -1244,7 +1244,6 @@ export function SettingsModal({
|
||||
|
||||
const handleSaveRemoteSettings = useCallback(async () => {
|
||||
const nextSettings: Partial<RemoteSettings> = {
|
||||
remoteEnabled: Boolean((form as Record<string, unknown>).remoteEnabled),
|
||||
remoteActiveProvider: ((form as Record<string, unknown>).remoteActiveProvider as "tailscale" | "cloudflare" | null) ?? null,
|
||||
remoteTailscaleEnabled: Boolean((form as Record<string, unknown>).remoteTailscaleEnabled),
|
||||
remoteTailscaleHostname: String((form as Record<string, unknown>).remoteTailscaleHostname ?? ""),
|
||||
@@ -3411,19 +3410,6 @@ export function SettingsModal({
|
||||
{renderScopeBanner()}
|
||||
<h4 className="settings-section-heading">Remote Access</h4>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="remoteEnabled" className="checkbox-label">
|
||||
<input
|
||||
id="remoteEnabled"
|
||||
type="checkbox"
|
||||
checked={Boolean(remoteForm.remoteEnabled)}
|
||||
onChange={(e) => setForm((f) => ({ ...f, remoteEnabled: e.target.checked } as SettingsFormState))}
|
||||
/>
|
||||
Enable remote access controls
|
||||
</label>
|
||||
<small>Configure provider settings, tunnel lifecycle, and tokenized remote URLs.</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="remoteActiveProvider">Active provider</label>
|
||||
<select
|
||||
|
||||
@@ -200,7 +200,6 @@ describe("SettingsModal", () => {
|
||||
mockFetchDashboardHealth.mockResolvedValue({ status: "ok", version: "1.2.3", uptime: 123 });
|
||||
mockFetchRemoteSettings.mockResolvedValue({
|
||||
settings: {
|
||||
remoteEnabled: false,
|
||||
remoteActiveProvider: null,
|
||||
remoteTailscaleEnabled: false,
|
||||
remoteTailscaleHostname: "",
|
||||
@@ -221,7 +220,6 @@ describe("SettingsModal", () => {
|
||||
});
|
||||
mockUpdateRemoteSettings.mockResolvedValue({
|
||||
settings: {
|
||||
remoteEnabled: false,
|
||||
remoteActiveProvider: null,
|
||||
remoteTailscaleEnabled: false,
|
||||
remoteTailscaleHostname: "",
|
||||
|
||||
@@ -75,7 +75,6 @@ async function REQUEST(
|
||||
describe("remote access headless parity", () => {
|
||||
function buildRemoteAccessSettings() {
|
||||
return {
|
||||
enabled: true,
|
||||
activeProvider: "cloudflare" as const,
|
||||
providers: {
|
||||
tailscale: {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { request as performRequest } from "../test-request.js";
|
||||
|
||||
function buildRemoteAccessSettings(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
enabled: true,
|
||||
activeProvider: "cloudflare" as const,
|
||||
providers: {
|
||||
tailscale: {
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
|
||||
function createRemoteSettings(overrides: Partial<RemoteAccessProjectSettings> = {}): RemoteAccessProjectSettings {
|
||||
return {
|
||||
enabled: true,
|
||||
activeProvider: "tailscale",
|
||||
providers: {
|
||||
tailscale: {
|
||||
@@ -94,7 +93,7 @@ describe("remote-auth", () => {
|
||||
});
|
||||
|
||||
it("returns disabled when remote access or token strategy is disabled", () => {
|
||||
const disabledRemote = validateRemoteAuthToken("anything", createRemoteSettings({ enabled: false }));
|
||||
const disabledRemote = validateRemoteAuthToken("anything", createRemoteSettings({ activeProvider: null }));
|
||||
expect(disabledRemote).toEqual({ status: "disabled" });
|
||||
|
||||
const disabledStrategies = validateRemoteAuthToken(
|
||||
|
||||
@@ -8,7 +8,6 @@ import { request as performRequest } from "../test-request.js";
|
||||
|
||||
function buildRemoteAccessSettings() {
|
||||
return {
|
||||
enabled: true,
|
||||
activeProvider: "cloudflare" as const,
|
||||
providers: {
|
||||
tailscale: {
|
||||
|
||||
@@ -13670,7 +13670,6 @@ describe("GET /settings/scopes", () => {
|
||||
global: { themeMode: "dark" },
|
||||
project: {
|
||||
remoteAccess: {
|
||||
enabled: true,
|
||||
activeProvider: "tailscale",
|
||||
providers: {
|
||||
tailscale: {
|
||||
|
||||
@@ -110,12 +110,16 @@ function isRemoteAccessTokenStrategyEnabled(settings: RemoteAccessSettings): boo
|
||||
return settings.tokenStrategy.persistent.enabled || settings.tokenStrategy.shortLived.enabled;
|
||||
}
|
||||
|
||||
function isRemoteProviderEnabled(settings: RemoteAccessSettings): boolean {
|
||||
return Boolean(settings.activeProvider != null && settings.providers[settings.activeProvider]?.enabled);
|
||||
}
|
||||
|
||||
export function validateRemoteAuthToken(
|
||||
rt: string | null | undefined,
|
||||
settings: RemoteAccessSettings,
|
||||
nowMs: number = Date.now(),
|
||||
): RemoteTokenValidationResult {
|
||||
if (!settings.enabled || !isRemoteAccessTokenStrategyEnabled(settings)) {
|
||||
if (!isRemoteProviderEnabled(settings) || !isRemoteAccessTokenStrategyEnabled(settings)) {
|
||||
return { status: "disabled" };
|
||||
}
|
||||
|
||||
|
||||
@@ -122,8 +122,8 @@ export function registerSettingsMemoryRoutes(ctx: ApiRoutesContext, deps: Settin
|
||||
const settings = await scopedStore.getSettings();
|
||||
const remoteAccess = settings.remoteAccess;
|
||||
|
||||
if (!remoteAccess?.enabled) {
|
||||
throw new ApiError(409, "Remote access is disabled", { code: "REMOTE_ACCESS_DISABLED" });
|
||||
if (!remoteAccess || remoteAccess.activeProvider == null || !remoteAccess.providers[remoteAccess.activeProvider]?.enabled) {
|
||||
throw new ApiError(409, "No remote provider is enabled", { code: "REMOTE_ACCESS_DISABLED" });
|
||||
}
|
||||
|
||||
const baseUrl = resolveRemoteBaseUrl(remoteAccess);
|
||||
@@ -266,7 +266,8 @@ export function registerSettingsMemoryRoutes(ctx: ApiRoutesContext, deps: Settin
|
||||
function toRemoteSettingsPayload(remoteAccess: NonNullable<Awaited<ReturnType<typeof store.getSettings>>["remoteAccess"]>) {
|
||||
const persistentToken = remoteAccess.tokenStrategy.persistent.token?.trim() ?? "";
|
||||
return {
|
||||
remoteEnabled: Boolean(remoteAccess.enabled),
|
||||
remoteEnabled: remoteAccess.activeProvider != null &&
|
||||
(remoteAccess.providers[remoteAccess.activeProvider]?.enabled ?? false),
|
||||
remoteActiveProvider: remoteAccess.activeProvider ?? null,
|
||||
remoteTailscaleEnabled: Boolean(remoteAccess.providers.tailscale.enabled),
|
||||
remoteTailscaleHostname: remoteAccess.providers.tailscale.hostname,
|
||||
@@ -315,7 +316,6 @@ export function registerSettingsMemoryRoutes(ctx: ApiRoutesContext, deps: Settin
|
||||
const body = (req.body ?? {}) as Record<string, unknown>;
|
||||
const nextRemoteAccess = {
|
||||
...remoteAccess,
|
||||
enabled: body.remoteEnabled === undefined ? remoteAccess.enabled : Boolean(body.remoteEnabled),
|
||||
activeProvider: body.remoteActiveProvider === undefined
|
||||
? remoteAccess.activeProvider
|
||||
: (body.remoteActiveProvider as "tailscale" | "cloudflare" | null),
|
||||
|
||||
@@ -50,6 +50,9 @@ interface RemoteLifecycleEvaluation {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const isRemoteActive = (ra: Settings["remoteAccess"] | undefined): boolean =>
|
||||
ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
|
||||
|
||||
export interface ProjectEngineOptions {
|
||||
/** Project identifier for notification deep links */
|
||||
projectId?: string;
|
||||
@@ -419,8 +422,8 @@ export class ProjectEngine {
|
||||
const store = this.runtime.getTaskStore();
|
||||
const settings = await store.getSettings();
|
||||
const remoteAccess = settings.remoteAccess;
|
||||
if (!remoteAccess?.enabled) {
|
||||
throw new Error("invalid_config:remote access is disabled");
|
||||
if (!remoteAccess || !isRemoteActive(remoteAccess)) {
|
||||
throw new Error("invalid_config:no remote access provider enabled");
|
||||
}
|
||||
|
||||
const provider = remoteAccess.activeProvider;
|
||||
@@ -545,7 +548,7 @@ export class ProjectEngine {
|
||||
|
||||
const settings = await store.getSettings();
|
||||
const remoteAccess = settings.remoteAccess;
|
||||
if (!remoteAccess?.enabled) {
|
||||
if (!remoteAccess || !isRemoteActive(remoteAccess)) {
|
||||
this.setRestoreDiagnostics("skipped", "remote_access_disabled", null);
|
||||
return;
|
||||
}
|
||||
@@ -651,8 +654,8 @@ export class ProjectEngine {
|
||||
provider: TunnelProvider,
|
||||
): Promise<RemoteLifecycleEvaluation> {
|
||||
const remoteAccess = settings.remoteAccess;
|
||||
if (!remoteAccess?.enabled) {
|
||||
return { provider, reason: "remote_access_disabled", message: "Remote access is disabled" };
|
||||
if (!remoteAccess || !isRemoteActive(remoteAccess)) {
|
||||
return { provider, reason: "remote_access_disabled", message: "No remote provider is enabled" };
|
||||
}
|
||||
|
||||
if (provider === "tailscale") {
|
||||
|
||||
Reference in New Issue
Block a user