Skip to content
Merged
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
7 changes: 7 additions & 0 deletions changelog/unreleased/qoder-cn-runtime-info-noexec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### English

- Run the Qoder CN check-in fingerprint from an executable directory when the account runtime directory cannot run programs.

### 中文

- 账号运行目录不能执行程序时,Qoder 国内版签到会改到可执行目录读取机器指纹。
25 changes: 22 additions & 3 deletions worker/src/checkin.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import { readdirSync } from "node:fs";
import { copyFileSync, mkdirSync, readdirSync } from "node:fs";
import os from "node:os";
import path from "node:path";

Expand Down Expand Up @@ -104,7 +104,7 @@ function machineHostname() {
return printableHeader.test(shortened) ? shortened : "";
}

function runtimeInfoPath(home = process.env.QODER_HOME || process.env.HOME || "") {
function installedRuntimeInfo(home = process.env.QODER_HOME || process.env.HOME || "") {
if (!home) return "";
const directory = path.join(home, ".bin");
let names = [];
Expand All @@ -118,6 +118,23 @@ function runtimeInfoPath(home = process.env.QODER_HOME || process.env.HOME || ""
return match ? path.join(directory, match) : "";
}

function executableRuntimeInfo(source, prepare = prepareExecutableRuntimeInfo) {
if (!source) return "";
try {
return prepare(source) || source;
} catch {
return source;
}
}

function prepareExecutableRuntimeInfo(source) {
const directory = process.env.QODER_RUNTIME_INFO_DIR || os.tmpdir();
const executable = path.join(directory, path.basename(source));
mkdirSync(directory, { recursive: true });
copyFileSync(source, executable);
return executable;
}

function accountId(user) {
for (const value of [user?.uid, user?.user_id, user?.userId, user?.id]) {
if (typeof value === "string" && value.trim()) return value.trim();
Expand Down Expand Up @@ -196,7 +213,9 @@ async function machineHeaders(auth, user, options) {
headers["Cosy-MachineId"] = id;
headers["Cosy-MachineToken"] = id;
}
const executable = (options.runtimeInfoPath ?? runtimeInfoPath)();
const executable = options.runtimeInfoPath
? executableRuntimeInfo(options.runtimeInfoPath(), options.prepareRuntimeInfo)
: executableRuntimeInfo(installedRuntimeInfo());
const account = accountId(user);
if (executable && account) {
const identity = await (options.readRiskIdentity ?? readRiskIdentity)(executable, account);
Expand Down
23 changes: 23 additions & 0 deletions worker/test/checkin.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@ test("CN check-in sends the machine identity headers used by the desktop client"
}
});

test("CN check-in uses an executable copy when the account runtime directory cannot execute files", async () => {
const previous = process.env.QODER_RUNTIME_INFO_DIR;
process.env.QODER_RUNTIME_INFO_DIR = "/opt/qoder-runtime";
const reads = [];
try {
const { checkin } = fixture([json(listed(credit({ status: "CLAIMED" })))], {
machineId: "machine-test-id",
accountId: "account-1",
runtimeInfoPath: () => "/run/account/.bin/runtime-info-linux-x64-probe",
prepareRuntimeInfo: (source) => `/opt/qoder-runtime/${source.split("/").at(-1)}`,
readRiskIdentity: async (executable) => {
reads.push(executable);
return { machineToken: "risk-token", machineType: "risk-type", machineCode: "risk-code" };
},
});
assert.equal((await checkin()).status, "already");
assert.deepEqual(reads, ["/opt/qoder-runtime/runtime-info-linux-x64-probe"]);
} finally {
if (previous === undefined) delete process.env.QODER_RUNTIME_INFO_DIR;
else process.env.QODER_RUNTIME_INFO_DIR = previous;
}
});

test("CN check-in keeps the machine id when the risk identity bridge is unavailable", async () => {
const { checkin, calls } = fixture([json(listed(credit({ status: "CLAIMED" })))], {
machineId: "machine-test-id",
Expand Down
Loading