Skip to content
Open
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
12 changes: 10 additions & 2 deletions apps/server/src/environment/ServerEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,20 @@ it.layer(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))("ServerEnvironmen
const first = yield* Effect.gen(function* () {
const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
return yield* serverEnvironment.getDescriptor;
}).pipe(Effect.provide(layerServerEnvironment(baseDir)));
}).pipe(
Effect.provide(layerServerEnvironment(baseDir)),
Effect.provideService(HostProcess.Environment, { T3CODE_ENVIRONMENT_LABEL: "Personal" }),
);
const second = yield* Effect.gen(function* () {
const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
return yield* serverEnvironment.getDescriptor;
}).pipe(Effect.provide(layerServerEnvironment(baseDir)));
}).pipe(
Effect.provide(layerServerEnvironment(baseDir)),
Effect.provideService(HostProcess.Environment, { T3CODE_ENVIRONMENT_LABEL: "Work" }),
);

expect(first.label).not.toBe(second.label);
expect({ ...first, label: second.label }).toEqual(second);
expect(first.environmentId).toBe(second.environmentId);
expect(first.orchestrationProtocolVersion).toBe(ORCHESTRATION_PROTOCOL_VERSION);
expect(second.capabilities.repositoryIdentity).toBe(true);
Expand Down
94 changes: 94 additions & 0 deletions apps/server/src/environment/ServerEnvironmentLabel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { expect, it } from "@effect/vitest";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";

import * as ProcessRunner from "../processRunner.ts";
import { resolveServerEnvironmentLabel } from "./ServerEnvironmentLabel.ts";

it.effect.each(
[undefined, "", " ", "\t\r\n", "  "].flatMap((override) =>
[
{
machineInfo: 'PRETTY_HOSTNAME=" Friendly host "\n',
command: "Other host",
hostname: "host",
cwd: "project",
expected: "Friendly host",
},
{
machineInfo: "",
command: " Command host\n",
hostname: "host",
cwd: "project",
expected: "Command host",
},
{ machineInfo: "", command: "", hostname: " host ", cwd: "project", expected: "host" },
{ machineInfo: "", command: "", hostname: " \t", cwd: " project ", expected: "project" },
{ machineInfo: "", command: "", hostname: "", cwd: "\t", expected: "T3 environment" },
].map((host) => ({ ...host, override })),
),
)("keeps fallback $expected for unset or blank override '$override'", (host) =>
Effect.gen(function* () {
const label = yield* resolveServerEnvironmentLabel({ cwdBaseName: host.cwd }).pipe(
Effect.provideService(HostProcess.Environment, { T3CODE_ENVIRONMENT_LABEL: host.override }),
Effect.provideService(HostProcess.Platform, "linux"),
Effect.provideService(HostProcess.Hostname, host.hostname),
Effect.provideService(
FileSystem.FileSystem,
FileSystem.makeNoop({
exists: () => Effect.succeed(host.machineInfo !== ""),
readFileString: () => Effect.succeed(host.machineInfo),
}),
),
Effect.provideService(ProcessRunner.ProcessRunner, {
run: () =>
Effect.succeed({
stdout: host.command,
stderr: "",
code: ChildProcessSpawner.ExitCode(0),
timedOut: false,
stdoutTruncated: false,
stderrTruncated: false,
stdoutInvalidUtf8: false,
stderrInvalidUtf8: false,
}),
}),
);

expect(label).toBe(host.expected);
}),
);

it.effect.each(
(["linux", "darwin", "win32"] as const).flatMap((platform) =>
[
{ username: "personal", override: " Personal workspace \t", expected: "Personal workspace" },
{ username: "work", override: "\n仕事用 🛠️ ", expected: "仕事用 🛠️" },
].map((account) => ({ ...account, platform })),
),
)("uses the explicit label for $username on $platform without host probes", (account) =>
Effect.gen(function* () {
const label = yield* resolveServerEnvironmentLabel({ cwdBaseName: "same-project" }).pipe(
Effect.provideService(HostProcess.Environment, {
T3CODE_ENVIRONMENT_LABEL: account.override,
}),
Effect.provideService(HostProcess.Platform, account.platform),
Effect.provideService(HostProcess.Hostname, "same-host"),
Effect.provideService(HostProcess.Username, account.username),
Effect.provideService(
FileSystem.FileSystem,
FileSystem.makeNoop({
exists: () => Effect.die("Explicit labels must not inspect host files"),
readFileString: () => Effect.die("Explicit labels must not read host files"),
}),
),
Effect.provideService(ProcessRunner.ProcessRunner, {
run: () => Effect.die("Explicit labels must not spawn host probes"),
}),
);

expect(label).toBe(account.expected);
}),
);
7 changes: 7 additions & 0 deletions apps/server/src/environment/ServerEnvironmentLabel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,16 @@ const resolveFriendlyHostLabel = Effect.fn("resolveFriendlyHostLabel")(function*
return null;
});

/** A nonblank display-only override skips host probes; otherwise retain the host/directory fallback. */
export const resolveServerEnvironmentLabel = Effect.fn("resolveServerEnvironmentLabel")(function* (
input: ResolveServerEnvironmentLabelInput,
) {
const environment = yield* HostProcess.Environment;
const environmentLabel = normalizeLabel(environment.T3CODE_ENVIRONMENT_LABEL);
if (environmentLabel) {
return environmentLabel;
}

const friendlyHostLabel = yield* resolveFriendlyHostLabel();
if (friendlyHostLabel) {
return friendlyHostLabel;
Expand Down
16 changes: 16 additions & 0 deletions docs/user/background-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ and updates. For example:
```dotenv
T3CODE_PORT=3773
T3CODE_HOST=127.0.0.1
T3CODE_ENVIRONMENT_LABEL="Build server"
```

Use `KEY=VALUE` entries, quoting values that contain spaces or `#`. Values are
Expand All @@ -81,6 +82,21 @@ A missing file is ignored; other read failures stop service startup.
If the file contains credentials, restrict access to that file with `chmod 600`
(for the default T3 home, `chmod 600 ~/.t3/service.env`).

### Name an environment

Set `T3CODE_ENVIRONMENT_LABEL` in the server's environment to distinguish multiple
T3 servers on one machine. The server trims surrounding whitespace. An unset or
blank value keeps the automatically detected label.

This changes the server's display label, not its environment ID, OS hostname, or
network address. Existing client-side aliases are unchanged. For a background
service, put the variable in `service.env` and restart the service after running
agent work has finished. For a server in a terminal:

```sh
T3CODE_ENVIRONMENT_LABEL="Build server" t3 serve
```

## Platform support

Linux needs systemd user services. Setup enables lingering so T3 Code starts at
Expand Down
Loading