@the-seeker/server-agent 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/collectors/command.d.ts +6 -1
- package/dist/collectors/command.js +18 -6
- package/dist/install/systemd.d.ts +2 -0
- package/dist/install/systemd.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,6 +84,19 @@ capability: `available`=조회 성공, `unsupported`=OS 미지원 또는 nginx
|
|
|
84
84
|
`not_installed`=실행 파일 없음, `permission_denied`=권한 부족, `error`=기타 조회/파싱 실패.
|
|
85
85
|
호스트 조회 실패는 호출자가 처리하고 host capability를 `error`로 지정합니다.
|
|
86
86
|
|
|
87
|
+
## 문제 해결
|
|
88
|
+
|
|
89
|
+
systemd의 PATH에서 nvm으로 설치한 PM2가 보이지 않으면 capability가 `not_installed`가 될 수 있습니다. `@the-seeker/server-agent@0.1.1`부터는 실행 중인 node의 `dirname(process.execPath)/pm2`를 자동으로 찾고, `install`이 systemd unit에 `Environment=PATH=<node bin dir>:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`을 기록합니다. 업그레이드 후 호스트에서 `install`을 다시 실행하면 됩니다.
|
|
90
|
+
|
|
91
|
+
자동 설정을 쓰지 않을 때는 systemd drop-in으로 PM2 실행 파일을 지정합니다.
|
|
92
|
+
|
|
93
|
+
```ini
|
|
94
|
+
[Service]
|
|
95
|
+
Environment=PM2_BIN=/home/<user>/.nvm/versions/node/<ver>/bin/pm2
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
drop-in 저장 후 `sudo systemctl restart theseeker-agent`를 실행합니다.
|
|
99
|
+
|
|
87
100
|
## 상태 확인과 제거
|
|
88
101
|
|
|
89
102
|
Ubuntu:
|
|
@@ -4,7 +4,12 @@ export declare function run(file: string, args: readonly string[], options?: Omi
|
|
|
4
4
|
stdout: string;
|
|
5
5
|
stderr: string;
|
|
6
6
|
}>;
|
|
7
|
-
export
|
|
7
|
+
export interface LocateDeps {
|
|
8
|
+
readonly execPath?: string;
|
|
9
|
+
readonly access?: (path: string, mode: number) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** systemd units run with a minimal PATH, so a nvm-installed `pm2` is invisible to `which` but sits next to the node binary in ExecStart. */
|
|
12
|
+
export declare function locate(binary: string, deps?: LocateDeps): Promise<string | null>;
|
|
8
13
|
export declare class CollectionError extends Error {
|
|
9
14
|
readonly code: ResponseError["code"];
|
|
10
15
|
constructor(code: ResponseError["code"]);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { access, constants } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
2
4
|
export function run(file, args, options = {}) {
|
|
3
5
|
return new Promise((resolve, reject) => {
|
|
4
6
|
execFile(file, [...args], { timeout: 8000, maxBuffer: 8 * 1024 * 1024, ...options, shell: false, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
@@ -9,15 +11,25 @@ export function run(file, args, options = {}) {
|
|
|
9
11
|
});
|
|
10
12
|
});
|
|
11
13
|
}
|
|
12
|
-
|
|
14
|
+
/** systemd units run with a minimal PATH, so a nvm-installed `pm2` is invisible to `which` but sits next to the node binary in ExecStart. */
|
|
15
|
+
export async function locate(binary, deps = {}) {
|
|
13
16
|
try {
|
|
14
|
-
const
|
|
15
|
-
|
|
17
|
+
const found = (await run("which", [binary])).stdout.trim();
|
|
18
|
+
if (found)
|
|
19
|
+
return found;
|
|
16
20
|
}
|
|
17
21
|
catch (error) {
|
|
18
|
-
if (error instanceof Error && "code" in error && (error.code === 1 || error.code === "ENOENT"))
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
if (!(error instanceof Error && "code" in error && (error.code === 1 || error.code === "ENOENT")))
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const candidate = join(dirname(deps.execPath ?? process.execPath), binary);
|
|
26
|
+
const isExecutable = deps.access ?? access;
|
|
27
|
+
try {
|
|
28
|
+
await isExecutable(candidate, constants.X_OK);
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
21
33
|
}
|
|
22
34
|
}
|
|
23
35
|
export class CollectionError extends Error {
|
|
@@ -3,6 +3,8 @@ export declare const SERVICE_NAME = "theseeker-agent";
|
|
|
3
3
|
export declare const UNIT_PATH = "/etc/systemd/system/theseeker-agent.service";
|
|
4
4
|
export declare const UNIT_FILE_MODE = 420;
|
|
5
5
|
export declare const SUPPLEMENTARY_GROUPS: readonly ["adm", "systemd-journal"];
|
|
6
|
+
/** systemd gives a unit no PATH at all, so the node bin directory (nvm keeps `pm2` there) is prepended to this system default. */
|
|
7
|
+
export declare const SYSTEM_PATH_TAIL = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
6
8
|
export interface UnitOptions {
|
|
7
9
|
readonly user: string;
|
|
8
10
|
readonly group: string;
|
package/dist/install/systemd.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
1
2
|
import { SYSTEM_CONFIG_DIR } from "../config.js";
|
|
2
3
|
import { skipsServiceManager } from "./detect.js";
|
|
3
4
|
export const SERVICE_NAME = "theseeker-agent";
|
|
4
5
|
export const UNIT_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
5
6
|
export const UNIT_FILE_MODE = 0o644;
|
|
6
7
|
export const SUPPLEMENTARY_GROUPS = ["adm", "systemd-journal"];
|
|
8
|
+
/** systemd gives a unit no PATH at all, so the node bin directory (nvm keeps `pm2` there) is prepended to this system default. */
|
|
9
|
+
export const SYSTEM_PATH_TAIL = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
7
10
|
/** `ProtectSystem=strict` still hides `/home`; the agent reads PM2 logs there, so a `/home` PM2_HOME disables the guard. */
|
|
8
11
|
export function protectHomeValue(pm2Home) {
|
|
9
12
|
return pm2Home.startsWith("/home/") || pm2Home === "/home" ? "false" : "read-only";
|
|
@@ -23,7 +26,7 @@ export function buildUnitFile(options) {
|
|
|
23
26
|
];
|
|
24
27
|
if (options.addGroups)
|
|
25
28
|
lines.push(`SupplementaryGroups=${SUPPLEMENTARY_GROUPS.join(" ")}`);
|
|
26
|
-
lines.push(`Environment=PM2_HOME=${options.pm2Home}`, `ExecStart=${options.nodePath} ${options.binPath} run`, "Restart=always", "RestartSec=5", "NoNewPrivileges=true", "ProtectSystem=strict", `ProtectHome=${protectHomeValue(options.pm2Home)}`, `ReadWritePaths=${SYSTEM_CONFIG_DIR}`, "", "[Install]", "WantedBy=multi-user.target", "");
|
|
29
|
+
lines.push(`Environment=PATH=${dirname(options.nodePath)}:${SYSTEM_PATH_TAIL}`, `Environment=PM2_HOME=${options.pm2Home}`, `ExecStart=${options.nodePath} ${options.binPath} run`, "Restart=always", "RestartSec=5", "NoNewPrivileges=true", "ProtectSystem=strict", `ProtectHome=${protectHomeValue(options.pm2Home)}`, `ReadWritePaths=${SYSTEM_CONFIG_DIR}`, "", "[Install]", "WantedBy=multi-user.target", "");
|
|
27
30
|
return lines.join("\n");
|
|
28
31
|
}
|
|
29
32
|
export function unitOptionsFrom(detection, addGroups) {
|