chore: add proper-lockfile for safe database read/write operations and implement retry logic for file access

This commit is contained in:
decolua 2026-03-27 10:31:35 +07:00
parent 3059df4014
commit 8759545260
15 changed files with 648 additions and 57 deletions

View file

@ -0,0 +1,25 @@
import { NextResponse } from "next/server";
import { exec } from "child_process";
import { join, dirname } from "path";
// Use npm from the same Node.js that runs Next.js — ensures 9remote
// lands in the correct global bin (nvm or system, whichever is active)
const npmBin = join(dirname(process.execPath), "npm");
function installPackage() {
return new Promise((resolve, reject) => {
exec(`"${npmBin}" install -g 9remote`, { windowsHide: true }, (err, stdout, stderr) => {
if (err) reject(new Error(stderr || err.message));
else resolve(stdout);
});
});
}
export async function POST() {
try {
await installPackage();
return NextResponse.json({ ok: true });
} catch (error) {
return NextResponse.json({ ok: false, error: error.message }, { status: 500 });
}
}

View file

@ -0,0 +1,43 @@
import { NextResponse } from "next/server";
import { spawn } from "child_process";
import { join, dirname } from "path";
import os from "os";
import { setRemoteProcess } from "@/lib/9remoteManager";
const bin9remote = join(dirname(process.execPath), "9remote");
export async function POST() {
try {
const nodeDir = dirname(process.execPath);
const existingPath = process.env.PATH || "";
const path = existingPath.includes(nodeDir)
? existingPath
: `${nodeDir}:${existingPath}`;
const env = {
HOME: os.homedir(),
PATH: path,
USER: process.env.USER || process.env.LOGNAME,
LANG: process.env.LANG || "en_US.UTF-8",
TERM: process.env.TERM || "xterm-256color",
TMPDIR: process.env.TMPDIR || os.tmpdir(),
SHELL: process.env.SHELL,
};
const home = os.homedir();
// Spawn without detached - process will be child of Next.js and receive SIGTERM
const child = spawn(bin9remote, ["ui", "--start"], {
cwd: home,
stdio: "ignore",
env,
windowsHide: process.platform === "win32",
});
// Store child process for manual cleanup if needed
setRemoteProcess(child);
return NextResponse.json({ ok: true });
} catch (error) {
return NextResponse.json({ ok: false, error: error.message }, { status: 500 });
}
}

View file

@ -0,0 +1,25 @@
import { NextResponse } from "next/server";
import { existsSync } from "fs";
import { join, dirname } from "path";
const bin9remote = join(dirname(process.execPath), "9remote");
const AGENT_URL = "http://localhost:2208";
async function isRunning() {
try {
const res = await fetch(`${AGENT_URL}/api/health`, {
signal: AbortSignal.timeout(1500),
});
return res.ok;
} catch {
return false;
}
}
export async function GET() {
const running = await isRunning();
if (running) return NextResponse.json({ installed: true, running: true });
const installed = existsSync(bin9remote);
return NextResponse.json({ installed, running: false });
}