koishi-plugin-freekill-u 1.0.0 → 1.0.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/lib/index.js +99 -67
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -37,12 +37,14 @@ var Config = import_koishi.Schema.object({
|
|
|
37
37
|
updateCommand: import_koishi.Schema.string().description("\u66F4\u65B0\u547D\u4EE4\uFF08\u5728\u65B0\u6708\u6740\u547D\u4EE4\u884C\u8F93\u5165\u7684\u6307\u4EE4\uFF09").default("u"),
|
|
38
38
|
attachDelay: import_koishi.Schema.number().description("screen -r \u540E\u7B49\u5F85 attach \u7684\u6BEB\u79D2\u6570").default(3e3).min(500).max(3e4),
|
|
39
39
|
updateWait: import_koishi.Schema.number().description("\u53D1\u9001\u66F4\u65B0\u547D\u4EE4\u540E\u7B49\u5F85\u8F93\u51FA\u7684\u6BEB\u79D2\u6570").default(3e4).min(5e3).max(3e5),
|
|
40
|
-
authority: import_koishi.Schema.number().description("\u4F7F\u7528\u8BE5\u547D\u4EE4\u6240\u9700\u7684\u6700\u4F4E\u6743\u9650\u7B49\u7EA7").default(4).min(1).max(5)
|
|
40
|
+
authority: import_koishi.Schema.number().description("\u4F7F\u7528\u8BE5\u547D\u4EE4\u6240\u9700\u7684\u6700\u4F4E\u6743\u9650\u7B49\u7EA7").default(4).min(1).max(5),
|
|
41
|
+
debugMode: import_koishi.Schema.boolean().description("\u8C03\u8BD5\u6A21\u5F0F\uFF1A\u663E\u793A\u5B8C\u6574 SSH \u8F93\u51FA\uFF08\u542B\u654F\u611F\u5185\u5BB9\uFF0C\u4EC5\u8C03\u8BD5\u65F6\u5F00\u542F\uFF09").default(false)
|
|
41
42
|
});
|
|
42
43
|
var usage = `
|
|
43
44
|
# \u65B0\u6708\u6740\u8FDC\u7A0B\u66F4\u65B0\u63D2\u4EF6
|
|
44
45
|
|
|
45
46
|
\u901A\u8FC7 SSH \u8FDE\u63A5\u670D\u52A1\u5668\uFF0C\u81EA\u52A8\u8FDB\u5165 screen \u4F1A\u8BDD\u6267\u884C\u66F4\u65B0\u547D\u4EE4\u3002
|
|
47
|
+
\u5371\u9669\u64CD\u4F5C\uFF0C\u8BF7\u52A1\u5FC5\u8BBE\u7F6E\u8DB3\u591F\u7684\u6743\u9650\u7B49\u7EA7\u3002
|
|
46
48
|
|
|
47
49
|
## \u4F7F\u7528\u65B9\u6CD5
|
|
48
50
|
|
|
@@ -62,6 +64,9 @@ var usage = `
|
|
|
62
64
|
|
|
63
65
|
- screen \u4F1A\u8BDD\u5FC5\u987B\u662F **detached** \u72B6\u6001\uFF0C\u5982\u679C\u5DF2\u88AB\u4EBA attach\uFF0C\u9700\u8981\u5148\u7528 \`screen -d -r\`
|
|
64
66
|
- \u672C\u63D2\u4EF6\u6D89\u53CA\u670D\u52A1\u5668\u5B89\u5168\uFF0C\u8BF7\u52A1\u5FC5\u8BBE\u7F6E\u8DB3\u591F\u7684\u6743\u9650\u7B49\u7EA7
|
|
67
|
+
|
|
68
|
+
## \u53CD\u9988\u6E20\u9053
|
|
69
|
+
QQ \u7FA4\uFF1A1078815356
|
|
65
70
|
`;
|
|
66
71
|
function apply(ctx, config) {
|
|
67
72
|
const logger = ctx.logger("freekill-u");
|
|
@@ -73,23 +78,36 @@ function apply(ctx, config) {
|
|
|
73
78
|
if (userAuthority < config.authority) {
|
|
74
79
|
return `\u26A0\uFE0F \u6743\u9650\u4E0D\u8DB3\uFF0C\u9700\u8981\u7B49\u7EA7 ${config.authority} \u4EE5\u4E0A\u3002`;
|
|
75
80
|
}
|
|
81
|
+
const startTime = Date.now();
|
|
76
82
|
await session.send("\u{1F50C} \u6B63\u5728\u8FDE\u63A5\u670D\u52A1\u5668\u2026");
|
|
77
83
|
try {
|
|
78
|
-
const result = await executeUpdate(config
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
const result = await executeUpdate(config);
|
|
85
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
86
|
+
const lines = ["\u2705 \u65B0\u6708\u6740\u66F4\u65B0\u5B8C\u6210\uFF01", `\u23F1 \u8017\u65F6 ${elapsed} \u79D2`];
|
|
87
|
+
if (result.hasError) {
|
|
88
|
+
lines.push("\u26A0\uFE0F \u68C0\u6D4B\u5230\u53EF\u80FD\u7684\u9519\u8BEF\uFF0C\u5EFA\u8BAE\u68C0\u67E5\u670D\u52A1\u5668\u65E5\u5FD7\u3002");
|
|
82
89
|
}
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
if (config.debugMode && result.output) {
|
|
91
|
+
let output = filterSensitive(result.output);
|
|
92
|
+
if (output.length > 2e3) {
|
|
93
|
+
output = output.slice(-2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u4EC5\u663E\u793A\u672B\u5C3E 2000 \u5B57\u7B26\uFF09";
|
|
94
|
+
}
|
|
95
|
+
lines.push(`
|
|
96
|
+
\u{1F4CB} \u66F4\u65B0\u8F93\u51FA\uFF1A
|
|
85
97
|
${output}`);
|
|
98
|
+
}
|
|
99
|
+
if (result.hasError) {
|
|
100
|
+
logger.warn("\u66F4\u65B0\u53EF\u80FD\u5931\u8D25", { elapsed, lines: result.lineCount });
|
|
101
|
+
}
|
|
102
|
+
await session.send(lines.join("\n"));
|
|
86
103
|
} catch (e) {
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
105
|
+
logger.warn("\u66F4\u65B0\u5931\u8D25", { error: e?.message || String(e), elapsed });
|
|
106
|
+
await session.send(`\u274C \u66F4\u65B0\u5931\u8D25\uFF08\u8017\u65F6 ${elapsed} \u79D2\uFF09\uFF1A${e?.message || String(e)}`);
|
|
89
107
|
}
|
|
90
108
|
});
|
|
91
109
|
}
|
|
92
|
-
function executeUpdate(config
|
|
110
|
+
function executeUpdate(config) {
|
|
93
111
|
return new Promise((resolve, reject) => {
|
|
94
112
|
const conn = new Client();
|
|
95
113
|
const totalTimeout = config.attachDelay + config.updateWait + 3e4;
|
|
@@ -101,8 +119,7 @@ function executeUpdate(config, logger) {
|
|
|
101
119
|
reject(new Error("SSH \u64CD\u4F5C\u8D85\u65F6"));
|
|
102
120
|
}, totalTimeout);
|
|
103
121
|
conn.on("ready", () => {
|
|
104
|
-
|
|
105
|
-
conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, async (err, stream) => {
|
|
122
|
+
conn.shell({ term: "xterm-256color", cols: 200, rows: 50 }, (err, stream) => {
|
|
106
123
|
if (err) {
|
|
107
124
|
clearTimeout(timer);
|
|
108
125
|
try {
|
|
@@ -112,70 +129,82 @@ function executeUpdate(config, logger) {
|
|
|
112
129
|
reject(new Error(`\u6253\u5F00 shell \u5931\u8D25: ${err.message}`));
|
|
113
130
|
return;
|
|
114
131
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
logger.info(`\u53D1\u9001\u66F4\u65B0\u547D\u4EE4: ${config.updateCommand}`);
|
|
141
|
-
output += await sendAndWait(config.updateCommand + "\n", config.updateWait);
|
|
142
|
-
logger.info("\u5206\u79BB screen \u4F1A\u8BDD");
|
|
143
|
-
stream.write("d");
|
|
144
|
-
output += await collectOutput(1e3);
|
|
145
|
-
stream.write("exit\n");
|
|
146
|
-
output += await collectOutput(500);
|
|
147
|
-
clearTimeout(timer);
|
|
148
|
-
try {
|
|
149
|
-
stream.close();
|
|
150
|
-
} catch {
|
|
132
|
+
let output = "";
|
|
133
|
+
let hasError = false;
|
|
134
|
+
const collectOutput = (duration) => {
|
|
135
|
+
return new Promise((resolveCollected) => {
|
|
136
|
+
let buf = "";
|
|
137
|
+
const onData = (data) => {
|
|
138
|
+
buf += data.toString("utf8");
|
|
139
|
+
};
|
|
140
|
+
stream.on("data", onData);
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
stream.removeListener("data", onData);
|
|
143
|
+
resolveCollected(buf);
|
|
144
|
+
}, duration);
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
const sendAndWait = async (cmd, waitMs) => {
|
|
148
|
+
stream.write(cmd);
|
|
149
|
+
return collectOutput(waitMs);
|
|
150
|
+
};
|
|
151
|
+
const checkError = (text) => {
|
|
152
|
+
const lower = text.toLowerCase();
|
|
153
|
+
if (/error|fail|fatal|exception|拒绝|失败|错误/.test(lower)) {
|
|
154
|
+
if (!/0 errors?|no error/i.test(lower)) {
|
|
155
|
+
hasError = true;
|
|
156
|
+
}
|
|
151
157
|
}
|
|
158
|
+
};
|
|
159
|
+
(async () => {
|
|
152
160
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
stream.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
output += await collectOutput(1e3);
|
|
162
|
+
const screenCmd = config.screenSession ? `screen -r ${config.screenSession}
|
|
163
|
+
` : `screen -r
|
|
164
|
+
`;
|
|
165
|
+
output += await sendAndWait(screenCmd, config.attachDelay);
|
|
166
|
+
const updateOutput = await sendAndWait(config.updateCommand + "\n", config.updateWait);
|
|
167
|
+
output += updateOutput;
|
|
168
|
+
checkError(updateOutput);
|
|
169
|
+
stream.write("d");
|
|
170
|
+
output += await collectOutput(1e3);
|
|
171
|
+
stream.write("exit\n");
|
|
172
|
+
output += await collectOutput(500);
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
try {
|
|
175
|
+
stream.close();
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
conn.end();
|
|
180
|
+
} catch {
|
|
181
|
+
}
|
|
182
|
+
const cleanOutput = stripAnsi(output);
|
|
183
|
+
const lines = cleanOutput.split("\n").filter((l) => l.trim()).length;
|
|
184
|
+
resolve({
|
|
185
|
+
output: cleanOutput,
|
|
186
|
+
lineCount: lines,
|
|
187
|
+
hasError
|
|
188
|
+
});
|
|
189
|
+
} catch (e) {
|
|
190
|
+
clearTimeout(timer);
|
|
191
|
+
try {
|
|
192
|
+
stream.close();
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
conn.end();
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
reject(e);
|
|
167
200
|
}
|
|
168
|
-
|
|
169
|
-
}
|
|
201
|
+
})();
|
|
170
202
|
});
|
|
171
203
|
});
|
|
172
204
|
conn.on("error", (err) => {
|
|
173
205
|
clearTimeout(timer);
|
|
174
206
|
reject(new Error(`SSH \u8FDE\u63A5\u9519\u8BEF: ${err.message}`));
|
|
175
207
|
});
|
|
176
|
-
conn.on("close", () => {
|
|
177
|
-
logger.info("SSH \u8FDE\u63A5\u5DF2\u5173\u95ED");
|
|
178
|
-
});
|
|
179
208
|
conn.connect({
|
|
180
209
|
host: config.host,
|
|
181
210
|
port: config.port,
|
|
@@ -185,6 +214,9 @@ function executeUpdate(config, logger) {
|
|
|
185
214
|
});
|
|
186
215
|
});
|
|
187
216
|
}
|
|
217
|
+
function filterSensitive(text) {
|
|
218
|
+
return text.replace(/\/home\/[^/\s]+/g, "/home/***").replace(/\/root\//g, "/root/***/").replace(/(token|secret|password|passwd|key|apikey)\s*[=:]\s*\S+/gi, "$1=***").replace(/\b(\d{1,3})\.(\d{1,3})\.\d{1,3}\.\d{1,3}\b/g, "$1.$2.*.*");
|
|
219
|
+
}
|
|
188
220
|
function stripAnsi(text) {
|
|
189
221
|
return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\x1b[()][AB012]/g, "").replace(/\r/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
190
222
|
}
|