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
Original file line number Diff line number Diff line change
Expand Up @@ -555,15 +555,11 @@ function ServerSettingsDetail(props: { readonly page: SettingsPage }) {
: !supportsAutomaticUpdates
? "Update older servers to control automatic updates."
: uniform("enableProviderUpdateChecks") === false
? "Turn on Provider update checks to update automatically."
: "Install updates for provider CLIs that support one-click updates on the selected environments while nothing is running."
? "Update background-service installs on the selected environments while nothing is running. Provider CLIs update only while Provider update checks is on."
: "Install provider CLI updates, and update background-service installs, on the selected environments while nothing is running."
}
value={uniform("automaticUpdates")}
disabled={
disabledFor("automaticUpdates") ||
!supportsAutomaticUpdates ||
uniform("enableProviderUpdateChecks") === false
}
disabled={disabledFor("automaticUpdates") || !supportsAutomaticUpdates}
onValueChange={(value) => write({ automaticUpdates: value })}
/>
</View>
Expand Down
51 changes: 48 additions & 3 deletions apps/server/src/cli/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { assert, it } from "@effect/vitest";
import type { ManagedUpdatesPolicy } from "@t3tools/contracts";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as Duration from "effect/Duration";
import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as FileSystem from "effect/FileSystem";
Expand Down Expand Up @@ -42,6 +43,11 @@ const unrelatedLsof = ProcessRunner.ProcessRunner.of({
stderrInvalidUtf8: false,
}),
});
import {
SERVICE_LAUNCHER_PROTOCOL,
SERVICE_RESTART_PENDING_FILE,
} from "../cloud/serviceProtocol.ts";
import { pruneServiceRuntimes } from "../serviceLauncher.ts";

afterEach(() => vi.restoreAllMocks());

Expand Down Expand Up @@ -193,17 +199,21 @@ it.layer(NodeServices.layer)("t3 update under a managed policy", (it) => {
});

it.layer(NodeServices.layer)("t3 update launcher", (it) => {
it.effect("repoints a symlink that lives in a runtime versions tree", () =>
it.effect("keeps a repointed CLI runtime after subsequent service updates", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-update-" });
const oldExe = path.join(root, "runtime/versions/1.0.0/t3");
const newExe = path.join(root, "runtime/versions/2.0.0/t3");
const launcher = path.join(root, "bin/t3");
for (const file of [oldExe, newExe]) {
const twoHoursAgo = DateTime.toDate(DateTime.subtract(yield* DateTime.now, { hours: 2 }));
for (const version of ["1.0.0", "2.0.0"]) {
const file = path.join(root, "runtime/versions", version, "t3");
yield* fs.makeDirectory(path.dirname(file), { recursive: true });
yield* fs.writeFileString(file, "");
yield* fs.writeFileString(file, version);
yield* fs.writeFileString(path.join(path.dirname(file), ".install-complete"), version);
yield* fs.utimes(path.dirname(file), twoHoursAgo, twoHoursAgo);
}
yield* fs.makeDirectory(path.dirname(launcher), { recursive: true });
yield* fs.symlink(oldExe, launcher);
Expand All @@ -216,6 +226,41 @@ it.layer(NodeServices.layer)("t3 update launcher", (it) => {

assert.deepStrictEqual(Option.getOrUndefined(repointed), launcher);
assert.equal(yield* fs.readLink(launcher), newExe);
assert.isFalse(yield* fs.exists(path.join(root, "runtime", SERVICE_RESTART_PENDING_FILE)));
for (const version of [3, 4]) {
const versionDir = path.join(root, "runtime/versions", `${version}.0.0`);
yield* fs.makeDirectory(versionDir);
yield* fs.writeFileString(path.join(versionDir, "t3"), `${version}.0.0`);
yield* fs.writeFileString(path.join(versionDir, ".install-complete"), `${version}.0.0`);
yield* fs.utimes(versionDir, twoHoursAgo, twoHoursAgo);
if (version === 4) {
const tempLink = `${launcher}.${process.pid}.tmp`;
yield* fs.writeFileString(tempLink, "");
yield* repointLauncher({
launchedAs: launcher,
baseDir: root,
targetEntryPath: path.join(versionDir, "t3"),
}).pipe(Effect.flip);
yield* fs.remove(tempLink);
}
yield* Effect.promise(() =>
pruneServiceRuntimes(
root,
{
protocol: SERVICE_LAUNCHER_PROTOCOL,
activeVersion: `${version}.0.0`,
update: {
id: `update-${version}`,
fromVersion: `${version - 1}.0.0`,
targetVersion: `${version}.0.0`,
status: "committed",
},
},
"1.0.0",
),
);
}
assert.equal(yield* fs.readFileString(launcher), "2.0.0");
}).pipe(Effect.scoped, Effect.provideService(HostProcess.Platform, "linux")),
);

Expand Down
1 change: 1 addition & 0 deletions apps/server/src/cli/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const ReleaseIndex = Schema.Array(
Schema.Struct({
tag_name: Schema.String,
draft: Schema.optional(Schema.Boolean),
prerelease: Schema.optional(Schema.Boolean),
}),
);
const decodeReleaseIndex = Schema.decodeUnknownEffect(Schema.fromJsonString(ReleaseIndex));
Expand Down
9 changes: 6 additions & 3 deletions apps/server/src/cloud/pinnedRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
pinnedRuntimeCommand,
pinnedRuntimePaths,
PinnedRuntimeInstallError,
PinnedRuntimePreflightBlockedError,
type PinnedRuntimeProgress,
prunePinnedRuntimes,
} from "./pinnedRuntime.ts";
Expand Down Expand Up @@ -249,7 +250,10 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
}),
);

it.effect("removes staging and leaves no final runtime when validation fails", () =>
it.effect.each([
new PinnedRuntimeInstallError({ step: "validating the staged runtime" }),
new PinnedRuntimePreflightBlockedError({ version, reason: "Different client protocol." }),
])("removes staging and leaves no final runtime when validation fails: %s", (error) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
Expand All @@ -265,8 +269,7 @@ it.layer(NodeServices.layer)("ensurePinnedRuntimeInstalled", (it) => {
arch: "x64",
httpClient: releaseHttpClient(yield* validChecksums),
runner: extractingRunner(fs, path),
validate: () =>
Effect.fail(new PinnedRuntimeInstallError({ step: "validating the staged runtime" })),
validate: () => Effect.fail(error),
}).pipe(Effect.flip);

assert.isFalse(yield* fs.exists(finalPaths.versionDir));
Expand Down
Loading
Loading