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.
Files changed (2) hide show
  1. package/lib/index.js +99 -67
  2. 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, logger);
79
- let output = result.trim();
80
- if (output.length > 2e3) {
81
- output = output.slice(0, 2e3) + "\n\u2026\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF09";
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
- await session.send(`\u2705 \u66F4\u65B0\u5B8C\u6210\uFF01
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
- logger.error("\u66F4\u65B0\u5931\u8D25", e);
88
- await session.send(`\u274C \u66F4\u65B0\u5931\u8D25\uFF1A${e?.message || String(e)}`);
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, logger) {
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
- logger.info("SSH \u5DF2\u8FDE\u63A5");
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
- try {
116
- let output = "";
117
- const collectOutput = (duration) => {
118
- return new Promise((resolveCollected) => {
119
- let buf = "";
120
- const onData = (data) => {
121
- buf += data.toString("utf8");
122
- };
123
- stream.on("data", onData);
124
- setTimeout(() => {
125
- stream.removeListener("data", onData);
126
- resolveCollected(buf);
127
- }, duration);
128
- });
129
- };
130
- const sendAndWait = async (cmd, waitMs) => {
131
- stream.write(cmd);
132
- return collectOutput(waitMs);
133
- };
134
- output += await collectOutput(1e3);
135
- const screenCmd = config.screenSession ? `screen -r ${config.screenSession}
136
- ` : `screen -r
137
- `;
138
- logger.info("\u53D1\u9001\u547D\u4EE4: screen -r");
139
- output += await sendAndWait(screenCmd, config.attachDelay);
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
- conn.end();
154
- } catch {
155
- }
156
- const cleanOutput = stripAnsi(output);
157
- resolve(cleanOutput);
158
- } catch (e) {
159
- clearTimeout(timer);
160
- try {
161
- stream.close();
162
- } catch {
163
- }
164
- try {
165
- conn.end();
166
- } catch {
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
- reject(e);
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-freekill-u",
3
3
  "description": "新月杀远程更新 — 通过 SSH 连接服务器自动进入 screen 会话执行更新",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [