feat(ai-proxy): Update 9router button — shut down, npm update, relaunch hidden
Settings → AI Proxy gains an admin-only Update 9router button beside Config 9router. One in-memory job (server/lib/nine-router-update.js) stops 9router (its user unit, else this account's own 9router processes), runs npm install -g --prefix <its prefix> 9router@latest (sudo -n only when sudoers already allows it, probed before anything stops), then relaunches it hidden only if it was running. A failed update still relaunches the old version. Nothing in the request names a command, path or package.
This commit is contained in:
@@ -3281,6 +3281,12 @@ export const api = {
|
||||
list: () => request<{ items: AiProxy[] }>("/ai-proxies"),
|
||||
/** GET /api/ai-proxies/9router - whether a `9router` binary is installed on the server. */
|
||||
nineRouter: () => request<{ installed: boolean }>("/ai-proxies/9router"),
|
||||
/** GET /api/ai-proxies/9router/update - versions, whether it runs, why an update would be refused, the job. */
|
||||
nineRouterUpdateStatus: (fresh = false) =>
|
||||
request<NineRouterUpdateStatus>(`/ai-proxies/9router/update${fresh ? "?fresh=1" : ""}`),
|
||||
/** POST /api/ai-proxies/9router/update - shut 9router down, update it, start it again hidden (admin). */
|
||||
nineRouterUpdate: () =>
|
||||
request<NineRouterUpdateJob>("/ai-proxies/9router/update", { method: "POST" }),
|
||||
/** POST /api/ai-proxies - save a new profile. */
|
||||
create: (body: AiProxyInput) =>
|
||||
request<AiProxy>("/ai-proxies", { method: "POST", body: JSON.stringify(body) }),
|
||||
@@ -4836,6 +4842,38 @@ export interface AiProxy {
|
||||
* error is still a successful HTTP call: `ok` is the PROXY's verdict, `status`
|
||||
* is its HTTP code (null when nothing answered at all), and `message` is its
|
||||
* own words — truncated server-side, never the key. */
|
||||
/** One step of the 9router update job, in order: shutdown → update → relaunch. */
|
||||
export interface NineRouterUpdateStep {
|
||||
id: "shutdown" | "update" | "relaunch";
|
||||
status: "pending" | "running" | "done" | "failed" | "skipped";
|
||||
message: string | null;
|
||||
}
|
||||
|
||||
/** The server's one 9router update job (in memory; null before the first). */
|
||||
export interface NineRouterUpdateJob {
|
||||
status: "running" | "done" | "failed";
|
||||
startedAt: string;
|
||||
finishedAt: string | null;
|
||||
from: string | null;
|
||||
to: string | null;
|
||||
managedBy: "systemd" | "process" | null;
|
||||
install: "direct" | "sudo";
|
||||
steps: NineRouterUpdateStep[];
|
||||
log: string;
|
||||
}
|
||||
|
||||
/** GET /api/ai-proxies/9router/update. `blocker` is why a start would be refused now. */
|
||||
export interface NineRouterUpdateStatus {
|
||||
installed: boolean;
|
||||
version: string | null;
|
||||
latest: string | null;
|
||||
updateAvailable: boolean;
|
||||
running: boolean;
|
||||
managedBy: "systemd" | "process" | null;
|
||||
blocker: { code: string; message: string; command?: string } | null;
|
||||
job: NineRouterUpdateJob | null;
|
||||
}
|
||||
|
||||
export interface AiProxyProbe {
|
||||
ok: boolean;
|
||||
status: number | null;
|
||||
|
||||
Reference in New Issue
Block a user