Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,11 +688,13 @@ describe("DesktopBackendConfiguration", () => {
const previousWslEnv = process.env.WSLENV;
const previousDisabled = process.env.OTEL_SDK_DISABLED;
const previousTelemetry = process.env.T3CODE_TELEMETRY_ENABLED;
const previousConnect = process.env.T3CODE_CONNECT_ENABLED;
const previousResourceAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES;
try {
delete process.env.WSLENV;
process.env.OTEL_SDK_DISABLED = "true";
process.env.T3CODE_TELEMETRY_ENABLED = "false";
process.env.T3CODE_CONNECT_ENABLED = "false";
process.env.OTEL_RESOURCE_ATTRIBUTES = "deployment.environment.name=staging";

yield* Effect.gen(function* () {
Expand All @@ -703,6 +705,8 @@ describe("DesktopBackendConfiguration", () => {
assert.include((config.env.WSLENV ?? "").split(":"), "OTEL_SDK_DISABLED");
assert.equal(config.env.T3CODE_TELEMETRY_ENABLED, "false");
assert.include((config.env.WSLENV ?? "").split(":"), "T3CODE_TELEMETRY_ENABLED");
assert.equal(config.env.T3CODE_CONNECT_ENABLED, "false");
assert.include((config.env.WSLENV ?? "").split(":"), "T3CODE_CONNECT_ENABLED");
assert.equal(config.env.OTEL_RESOURCE_ATTRIBUTES, "deployment.environment.name=staging");
assert.include((config.env.WSLENV ?? "").split(":"), "OTEL_RESOURCE_ATTRIBUTES");
}).pipe(
Expand All @@ -726,6 +730,7 @@ describe("DesktopBackendConfiguration", () => {
restoreEnv("WSLENV", previousWslEnv);
restoreEnv("OTEL_SDK_DISABLED", previousDisabled);
restoreEnv("T3CODE_TELEMETRY_ENABLED", previousTelemetry);
restoreEnv("T3CODE_CONNECT_ENABLED", previousConnect);
restoreEnv("OTEL_RESOURCE_ATTRIBUTES", previousResourceAttributes);
}
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const WSL_FORWARDED_ENV_NAMES = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"T3CODE_TELEMETRY_ENABLED",
"T3CODE_CONNECT_ENABLED",
"T3CODE_GITHUB_APP_CLIENT_ID",
// Otherwise the WSL server keeps exporting to endpoints from the bootstrap.
"T3CODE_OTEL_SDK_DISABLED",
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/ipc/DesktopIpcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
} from "./methods/updates.ts";
import {
getAppBranding,
getConnectEnabled,
getLocalEnvironmentBootstraps,
getLocalEnvironmentBearerToken,
getRelayTelemetryEnabled,
Expand Down Expand Up @@ -92,6 +93,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
yield* ipc.handle(completeLegacyLocalStorage);
yield* ipc.handleSync(getAppBranding);
yield* ipc.handleSync(getRelayTelemetryEnabled);
yield* ipc.handleSync(getConnectEnabled);
yield* ipc.handleSync(getSystemLocale);
yield* ipc.handleSync(getWindowFullscreenState);
yield* ipc.handleSync(getLocalEnvironmentBootstraps);
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const CLI_COMMAND_INSTALL_CHANNEL = "desktop:cli-command-install";
export const CLI_COMMAND_UNINSTALL_CHANNEL = "desktop:cli-command-uninstall";
export const GET_APP_BRANDING_CHANNEL = "desktop:get-app-branding";
export const GET_RELAY_TELEMETRY_ENABLED_CHANNEL = "desktop:get-relay-telemetry-enabled";
export const GET_CONNECT_ENABLED_CHANNEL = "desktop:get-connect-enabled";
export const GET_SYSTEM_LOCALE_CHANNEL = "desktop:get-system-locale";
export const GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL = "desktop:get-local-environment-bootstraps";
export const GET_LOCAL_ENVIRONMENT_ENABLED_CHANNEL = "desktop:get-local-environment-enabled";
Expand Down
10 changes: 9 additions & 1 deletion apps/desktop/src/ipc/methods/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { PROJECT_FAVICON_EXTENSIONS } from "@t3tools/shared/projectFavicon";
import { resolveEditorCommand } from "@t3tools/shared/editor";
import * as HostProcess from "@t3tools/shared/HostProcess";
import { isRelayTracingEnabled } from "@t3tools/shared/otelEnvironment";
import { isConnectEnabled, isRelayTracingEnabled } from "@t3tools/shared/otelEnvironment";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -80,6 +80,14 @@ export const getRelayTelemetryEnabled = DesktopIpc.makeSyncIpcMethod({
}),
});

export const getConnectEnabled = DesktopIpc.makeSyncIpcMethod({
channel: IpcChannels.GET_CONNECT_ENABLED_CHANNEL,
result: Schema.Boolean,
handler: Effect.fn("desktop.ipc.window.getConnectEnabled")(function* () {
return isConnectEnabled((yield* HostProcess.Environment).T3CODE_CONNECT_ENABLED);
}),
});

export const getSystemLocale = DesktopIpc.makeSyncIpcMethod({
channel: IpcChannels.GET_SYSTEM_LOCALE_CHANNEL,
result: Schema.String,
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ function unwrapEnsureSshEnvironmentResult(result: unknown) {
return result as Awaited<ReturnType<DesktopBridge["ensureSshEnvironment"]>>;
}

const connectEnabled = ipcRenderer.sendSync(IpcChannels.GET_CONNECT_ENABLED_CHANNEL) === true;

contextBridge.exposeInMainWorld("desktopBridge", {
getAppBranding: () => {
const result = ipcRenderer.sendSync(IpcChannels.GET_APP_BRANDING_CHANNEL);
Expand All @@ -82,6 +84,7 @@ contextBridge.exposeInMainWorld("desktopBridge", {
},
getRelayTelemetryEnabled: () =>
ipcRenderer.sendSync(IpcChannels.GET_RELAY_TELEMETRY_ENABLED_CHANNEL) === true,
getConnectEnabled: () => connectEnabled,
getPathForFile: (file: File) => webUtils.getPathForFile(file),
getClientPlatform: () => clientPlatform,
setNotificationBadge: (badge) =>
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/shell/DesktopShellEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const LOGIN_SHELL_ENV_NAMES = [
"SSL_CERT_DIR",
"NODE_EXTRA_CA_CERTS",
"T3CODE_TELEMETRY_ENABLED",
"T3CODE_CONNECT_ENABLED",
"T3CODE_GITHUB_APP_CLIENT_ID",
"__MISE_DIFF",
] as const;
Expand Down Expand Up @@ -451,6 +452,7 @@ const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosix
"SSL_CERT_DIR",
"NODE_EXTRA_CA_CERTS",
"T3CODE_TELEMETRY_ENABLED",
"T3CODE_CONNECT_ENABLED",
"T3CODE_GITHUB_APP_CLIENT_ID",
] as const) {
if (!config.env[name] && shellEnvironment[name]) {
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/binCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { connectCommand, statusCommand, upCommand } from "./cli/up.ts";
const layerCliRuntime = Layer.mergeAll(NodeServices.layer, NetService.layer);

const connectPublicConfigMissingMessage =
"T3 Connect commands are unavailable: this build is missing T3 Connect public configuration.";
"T3 Connect commands are unavailable: T3CODE_CONNECT_ENABLED=false, or this build is missing T3 Connect public configuration.";

class ConnectPublicConfigMissingError extends CliError.UserError {
override get message() {
Expand All @@ -46,7 +46,7 @@ class ConnectPublicConfigMissingError extends CliError.UserError {
const connectUnavailableCommand = Command.make("connect", {
command: Argument.String("command").pipe(Argument.variadic),
}).pipe(
Command.withDescription("T3 Connect is unavailable in builds without public configuration."),
Command.withDescription("T3 Connect is turned off or missing from this build."),
Command.unlisted,
Command.withHandler(() =>
Effect.fail(
Expand Down
26 changes: 26 additions & 0 deletions apps/server/src/cli/connect.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as NetService from "@t3tools/shared/Net";
import * as RelayClient from "@t3tools/shared/relayClient";
import { assert, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as ConfigProvider from "effect/ConfigProvider";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import { Command } from "effect/cli";

import { acquireRelayClientForLink } from "./connect.ts";
import { connectCommand } from "./up.ts";

const managedExecutable = {
status: "available",
Expand Down Expand Up @@ -40,3 +47,22 @@ it.effect("does not install the relay client when the user declines the managed
assert.equal(installCalls, 0);
}),
);

it.layer(Layer.mergeAll(NodeServices.layer, NetService.layer))("disabled service", (it) => {
it.effect.each([
{ args: ["login", "--headless"] },
{ args: ["link", "--publish-only", "--headless"] },
{ args: ["link", "--headless"] },
{ args: ["publish"] },
])("refuses enabling Connect through $args", ({ args }) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-connect-disabled-" });
yield* fs.writeFileString(`${baseDir}/service.env`, "T3CODE_CONNECT_ENABLED=false\n");
const error = yield* Effect.flip(
Command.runWith(connectCommand, { version: "0.0.0" })([...args, "--base-dir", baseDir]),
);
assert.include(String(error), "T3CODE_CONNECT_ENABLED=false");
}).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),
);
});
18 changes: 17 additions & 1 deletion apps/server/src/cli/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "@t3tools/contracts";
import { RelayOkResponse } from "@t3tools/contracts/relay";
import * as RelayClient from "@t3tools/shared/relayClient";
import { isConnectEnabled } from "@t3tools/shared/otelEnvironment";
import { withRelayClientTracing } from "@t3tools/shared/relayTracing";
import * as Cause from "effect/Cause";
import * as Clock from "effect/Clock";
Expand All @@ -26,6 +27,7 @@ import * as Path from "effect/Path";
import * as References from "effect/References";
import * as Schema from "effect/Schema";
import { Command, Flag, GlobalFlag, Prompt } from "effect/cli";
import * as CliError from "effect/cli/CliError";
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
import * as HttpApiClient from "effect/http-api/HttpApiClient";

Expand All @@ -43,7 +45,7 @@ import {
PUBLISH_AGENT_ACTIVITY_SECRET,
RELAY_URL_SECRET,
} from "../cloud/config.ts";
import { relayUrlConfig } from "../cloud/publicConfig.ts";
import { readConnectEnabled, relayUrlConfig } from "../cloud/publicConfig.ts";
import * as RelayTracing from "../cloud/relayTracing.ts";
import * as ServerConfig from "../config.ts";
import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
Expand Down Expand Up @@ -94,6 +96,17 @@ function formatDeviceAuthorizationPrompt(
].join("\n");
}

const requireConnectEnabled = Effect.fn("cloud.cli.require_connect_enabled")(function* () {
const { baseDir } = yield* ServerConfig.ServerConfig;
const { entries } = yield* CliService.readServiceEnv(baseDir);
if (!(yield* readConnectEnabled) || !isConnectEnabled(entries.T3CODE_CONNECT_ENABLED)) {
return yield* new CliError.UserError({
cause: "connect",
userMessage: "T3 Connect is turned off for this environment (T3CODE_CONNECT_ENABLED=false).",
});
}
});

const authorizeCli = Effect.fn("cloud.cli.authorize")(function* (options: {
readonly headless: boolean;
}) {
Expand Down Expand Up @@ -561,6 +574,7 @@ const linkEnvironmentForConnect = Effect.fn("cloud.cli.link_environment")(functi
/** For `t3 up`, where the user already chose T3 Connect: install it quietly, without asking. */
readonly installRelayClient?: boolean;
}) {
yield* requireConnectEnabled();
const publishOnly = options.publishOnly ?? false;
if (!publishOnly) {
const relayClient = yield* RelayClient.RelayClient;
Expand Down Expand Up @@ -605,6 +619,7 @@ const connectLoginCommand = Command.make("login", {
runCloudCommand(
flags,
Effect.gen(function* () {
yield* requireConnectEnabled();
yield* Console.log("T3 Connect\n");
const identity = yield* authorizeCli(flags);
yield* Console.log(`✓ Signed in${connectedAs(identity)}`);
Expand Down Expand Up @@ -722,6 +737,7 @@ const connectPublishCommand = Command.make("publish", {
const secrets = yield* ServerSecretStore.ServerSecretStore;
const tokens = yield* CliTokenManager.CloudCliTokenManager;
const enabled = !flags.disable;
if (enabled) yield* requireConnectEnabled();
yield* secrets.set(
PUBLISH_AGENT_ACTIVITY_SECRET,
stringToBytes(enabled ? "true" : "false"),
Expand Down
19 changes: 19 additions & 0 deletions apps/server/src/cli/up.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import * as BootService from "../cloud/bootService.ts";
import { upsertServiceEnv } from "./service.ts";
import { connectEnabled, planRoutes, upCommand } from "./up.ts";

vi.mock("../cloud/publicConfig.ts", async (importOriginal) => ({
...(await importOriginal<typeof import("../cloud/publicConfig.ts")>()),
hasCloudPublicConfig: true,
}));

const none = Option.none<boolean>();

it.each([
Expand Down Expand Up @@ -163,12 +168,23 @@ it.layer(Layer.mergeAll(NodeServices.layer, NetService.layer))("t3 up", (it) =>
/** Runs as if in a terminal, answering each question with this text. */
readonly answer?: string;
readonly prints?: string;
readonly omits?: string;
readonly env: string;
readonly args: ReadonlyArray<string>;
readonly installs: number;
readonly restarts: number;
readonly envAfter: string;
}>([
{
label: "the service disables T3 Connect",
installed: true,
env: "T3CODE_CONNECT_ENABLED=false\nT3CODE_LAN=false\n",
args: ["--no-lan"],
installs: 0,
restarts: 0,
envAfter: "T3CODE_CONNECT_ENABLED=false\nT3CODE_LAN=false\n",
omits: "T3 Connect",
},
{
label: "nothing changed",
installed: true,
Expand Down Expand Up @@ -337,6 +353,9 @@ it.layer(Layer.mergeAll(NodeServices.layer, NetService.layer))("t3 up", (it) =>
if (input.prints !== undefined) {
expect(lines.join("\n")).toContain(input.prints);
}
if (input.omits !== undefined) {
expect(lines.join("\n")).not.toContain(input.omits);
}
expect(exit._tag).toBe(input.restartFails === true ? "Failure" : "Success");
expect({ installs, restarts }).toEqual({
installs: input.installs,
Expand Down
10 changes: 6 additions & 4 deletions apps/server/src/cli/up.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as HostProcess from "@t3tools/shared/HostProcess";
import { isConnectEnabled } from "@t3tools/shared/otelEnvironment";
import * as Config from "effect/Config";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Console from "effect/Console";
Expand Down Expand Up @@ -66,9 +67,9 @@ const readSetup = Effect.fn("cli.up.read_setup")(function* () {
.pipe(Config.option)
.parse(ConfigProvider.fromEnv({ env: entries }));
const secrets = yield* ServerSecretStore.ServerSecretStore;
const connectRevoked =
hasCloudPublicConfig && Option.isSome(yield* secrets.get(CLOUD_LINK_REVOKED));
const connect = hasCloudPublicConfig
const connectAvailable = hasCloudPublicConfig && isConnectEnabled(entries.T3CODE_CONNECT_ENABLED);
const connectRevoked = connectAvailable && Option.isSome(yield* secrets.get(CLOUD_LINK_REVOKED));
const connect = connectAvailable
? connectEnabled({
cliDesired: yield* CliState.readCliDesiredCloudLink,
cliMode: yield* CliState.readCliDesiredLinkMode,
Expand Down Expand Up @@ -198,7 +199,8 @@ const setUp = Effect.fn("cli.up")(function* (flags: {
if (before.connect === null && Option.getOrElse(flags.connect, () => false)) {
return yield* new CliError.UserError({
cause: "connect",
userMessage: "T3 Connect is unavailable in this build.",
userMessage:
"T3 Connect is unavailable: T3CODE_CONNECT_ENABLED=false, or this build is missing T3 Connect public configuration.",
});
}

Expand Down
Loading
Loading