@lazycatcloud/lzc-cli 1.2.37 → 1.2.41

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 CHANGED
@@ -3,9 +3,8 @@
3
3
  #### 依赖
4
4
 
5
5
  1. `ssh`
6
- 2. `ssh-copy-id`
7
- 3. `rsync`
8
- 4. 盒子中安装开发者工具应用
6
+ 2. `Linux` 和 `MacOS` 下需要安装 `rsync`
7
+ 3. 盒子中安装开发者工具应用
9
8
 
10
9
  #### 快速上手
11
10
 
@@ -32,9 +31,3 @@ lzc-cli project devshell
32
31
  # 经过测试后,将包发布到懒猫云商店中去
33
32
  lzc-cli appstore publish
34
33
  ```
35
-
36
- ### FAQ
37
-
38
- #### 1. 开发者工具 ssh 的帐号密码是多少?
39
-
40
- 现在帐号密码设置为 box:box,后面会使用 pam 模块对接盒子帐号体系。
@@ -17,14 +17,16 @@ export async function triggerApk(id, name, iconPath) {
17
17
  }
18
18
  const resp = await api.post(
19
19
  "https://appstore.lazycat.cloud/api/trigger_latest_for_app",
20
- form
20
+ form,
21
+ { timeout: 5000 }
21
22
  )
22
23
  if (resp.status == 304) {
23
- } else if (resp.status == 201) {
24
+ logger.debug(`APK构建任务已创建成功,如需使用安卓端,请耐心等待1分钟左右`)
25
+ } else if (resp.status <= 201) {
24
26
  logger.info(`APK构建任务已创建成功,如需使用安卓端,请耐心等待1分钟左右`)
25
27
  } else if (resp.status >= 400) {
26
28
  logger.debug("请求按钮应用出错:", resp)
27
- throw "请求生成应用出错!"
29
+ throw `请求生成应用出错! 使用 --apk=n 停止生成APK`
28
30
  }
29
31
  } catch (error) {
30
32
  logger.error(error)
@@ -9,9 +9,12 @@ import {
9
9
  envTemplateFile,
10
10
  isValidPackageName,
11
11
  tarContentDir,
12
- isPngWithFile
12
+ isPngWithFile,
13
+ isMacOs,
14
+ isWindows,
15
+ isLinux
13
16
  } from "../utils.js"
14
- import { spawnSync } from "child_process"
17
+ import spawn from "cross-spawn"
15
18
  import { LpkManifest } from "./lpk_create.js"
16
19
  import archiver from "archiver"
17
20
  import yaml from "js-yaml"
@@ -79,8 +82,8 @@ function localIp() {
79
82
  const regex = /inet6 (fc03:1136:[0-9a-fA-F:]+)[?:\/ ]/
80
83
 
81
84
  let output = ""
82
- if (process.platform == "darwin") {
83
- const result = spawnSync("sh", ["-c", "ifconfig"], {
85
+ if (isMacOs) {
86
+ const result = spawn.sync("sh", ["-c", "ifconfig"], {
84
87
  encoding: "utf-8"
85
88
  })
86
89
  if (result.status != 0 || result.error) {
@@ -88,11 +91,11 @@ function localIp() {
88
91
  return ""
89
92
  }
90
93
  output = result.stdout
91
- } else if (process.platform == "win32") {
94
+ } else if (isWindows) {
92
95
  logger.debug(`当前 Windows 不支持使用 LocalIP 反代`)
93
96
  return ""
94
97
  } else {
95
- const result = spawnSync("ip", ["addr", "show", "heiyu-0"], {
98
+ const result = spawn.sync("ip", ["addr", "show", "heiyu-0"], {
96
99
  encoding: "utf-8"
97
100
  })
98
101
  if (result.status !== 0 || result.error) {
@@ -198,7 +201,9 @@ export class LpkBuild {
198
201
  }
199
202
 
200
203
  if (this.options["buildscript"]) {
201
- let p = spawnSync("sh", ["-c", this.options["buildscript"]], {
204
+ const cmd = isWindows ? "cmd" : "sh"
205
+ const cmdArgs = isWindows ? "/c" : "-c"
206
+ let p = spawn.sync(cmd, [cmdArgs, this.options["buildscript"]], {
202
207
  cwd: this.pwd,
203
208
  stdio: "inherit"
204
209
  })
@@ -4,7 +4,7 @@ import fs from "fs"
4
4
  import { isBinaryFileSync } from "isbinaryfile"
5
5
  import { contextDirname, isFileExist } from "../utils.js"
6
6
  import chalk from "chalk"
7
- import { spawnSync } from "node:child_process"
7
+ import spawn from "cross-spawn"
8
8
  import logger from "loglevel"
9
9
 
10
10
  const __dirname = contextDirname(import.meta.url)
@@ -75,10 +75,10 @@ function vueTemplate(type) {
75
75
  const v = type == "vue3" ? "vue@3" : "vue@2"
76
76
  return {
77
77
  build: async (to, option) => {
78
- spawnSync("npm", ["init", v, path.basename(to)], { stdio: "inherit" })
78
+ spawn.sync("npm", ["init", v, path.basename(to)], { stdio: "inherit" })
79
79
  },
80
80
  after: async function (name, cwd) {
81
- spawnSync("npm", ["install"], {
81
+ spawn.sync("npm", ["install"], {
82
82
  cwd: path.resolve(cwd, name),
83
83
  stdio: "inherit"
84
84
  })
@@ -182,7 +182,7 @@ export const TemplateConfig = {
182
182
  appendREADME(path.resolve(cwd, name))
183
183
 
184
184
  console.log(chalk.green("开始安装依赖"))
185
- spawnSync("npm", ["install"], {
185
+ spawn.sync("npm", ["install"], {
186
186
  cwd: path.join(cwd, name),
187
187
  stdio: "inherit"
188
188
  })
@@ -1,26 +1,48 @@
1
- import { spawn, spawnSync } from "child_process"
1
+ import spawn from "cross-spawn"
2
2
  import fs from "node:fs"
3
3
  import shellApi from "../shellapi.js"
4
- import { isDebugMode, resolveDomain, sleep, sshCmd } from "../utils.js"
4
+ import {
5
+ isDebugMode,
6
+ isTraceMode,
7
+ resolveDomain,
8
+ sleep,
9
+ findSshPublicKey,
10
+ isWindows,
11
+ contextDirname
12
+ } from "../utils.js"
5
13
  import logger from "loglevel"
6
14
  import commandExists from "command-exists"
15
+ import path from "node:path"
16
+
17
+ export function sshBinary() {
18
+ if (isWindows) {
19
+ return "ssh"
20
+ }
21
+ return "ssh"
22
+ }
23
+
24
+ export function sshCmdArgs(...args) {
25
+ const defaultOptions = [
26
+ `-o "StrictHostKeyChecking=no"`,
27
+ `-o "UserKnownHostsFile=/dev/null"`,
28
+ "-q",
29
+ "-p 22222",
30
+ `${isTraceMode() ? "-v" : ""}`
31
+ ]
32
+ return [...defaultOptions, ...args]
33
+ }
7
34
 
8
35
  export class DebugBridge {
9
36
  constructor() {
10
37
  this.uid = shellApi.uid
11
38
  this.boxname = shellApi.boxname
12
39
  this.domain = `dev.${this.boxname}.heiyu.space`
13
- this.sshCmd = sshCmd(
14
- `${isDebugMode() ? "-vv" : ""}`,
15
- "-p 22222",
16
- `box@${this.domain}`
17
- )
18
40
  }
19
41
 
20
42
  async common(cmd, args) {
21
43
  const resolvedIp = await resolveDomain(this.domain)
22
44
  const resolvedCmd = cmd.replace(this.domain, resolvedIp)
23
- const ssh = spawnSync(resolvedCmd, args, {
45
+ const ssh = spawn.sync(resolvedCmd, args, {
24
46
  shell: true,
25
47
  encoding: "utf-8",
26
48
  stdio: ["pipe", "pipe", "pipe"]
@@ -43,8 +65,11 @@ export class DebugBridge {
43
65
  const stream = fs.createReadStream(lpkPath)
44
66
  const resolvedIp = await resolveDomain(this.domain)
45
67
  const ssh = spawn(
46
- sshCmd("-p 22222", `box@${resolvedIp}`),
47
- [`install --uid ${this.uid} --pkgId ${pkgId}`],
68
+ sshBinary(),
69
+ [
70
+ ...sshCmdArgs(`box@${resolvedIp}`),
71
+ `install --uid ${this.uid} --pkgId ${pkgId}`
72
+ ],
48
73
  {
49
74
  shell: true,
50
75
  stdio: ["pipe", "inherit", "inherit"]
@@ -64,25 +89,44 @@ export class DebugBridge {
64
89
  }
65
90
 
66
91
  async sshCopyId() {
67
- // 检查rsync工具是否存在:提示用户
68
- const existed = commandExists.sync("ssh-copy-id")
69
- if (!existed) {
70
- logger.error("请检查 ssh-copy-id 是否安装,路径是否正确!")
71
- process.exit(1)
72
- }
92
+ const sshInfo = await findSshPublicKey()
93
+ logger.debug("ssh public key info", sshInfo)
73
94
 
74
95
  const resolvedIp = await resolveDomain(this.domain)
75
- return this.common(`ssh-copy-id`, [
76
- `-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -f -p 22222 box@${resolvedIp}`
77
- ])
96
+ logger.debug("ip", resolvedIp)
97
+
98
+ // 将当前机器上的ssh public key 写到开发者工具中去,并过滤到重复的,管道在执行前会被清空,需要使用一个中间文件
99
+ const stream = spawn.sync(
100
+ sshBinary(),
101
+ [...sshCmdArgs(`box@${resolvedIp}`), `add-ssh-public-key`],
102
+ {
103
+ input: sshInfo.content.trimLeft(),
104
+ shell: true,
105
+ encoding: "utf-8",
106
+ stdio: "pipe"
107
+ }
108
+ )
109
+ if (stream.status != 0) {
110
+ const stderr = stream.stderr.toString("utf-8")
111
+ const stdout = stream.stdout.toString("utf-8")
112
+ throw stdout + stderr
113
+ }
114
+ if (stream.error) {
115
+ throw stream.error
116
+ }
78
117
  }
79
118
 
80
119
  async status(appId) {
81
- return this.common(this.sshCmd, [`status --uid ${this.uid}`, appId])
120
+ return this.common(sshBinary(), [
121
+ ...sshCmdArgs(`box@${this.domain}`),
122
+ `status --uid ${this.uid}`,
123
+ appId
124
+ ])
82
125
  }
83
126
 
84
127
  async isDevshell(appId) {
85
- const stdout = await this.common(this.sshCmd, [
128
+ const stdout = await this.common(sshBinary(), [
129
+ ...sshCmdArgs(`box@${this.domain}`),
86
130
  `isDevshell --uid ${this.uid}`,
87
131
  appId
88
132
  ])
@@ -90,11 +134,19 @@ export class DebugBridge {
90
134
  }
91
135
 
92
136
  async resume(appId) {
93
- return this.common(this.sshCmd, [`resume --uid ${this.uid}`, appId])
137
+ return this.common(sshBinary(), [
138
+ ...sshCmdArgs(`box@${this.domain}`),
139
+ `resume --uid ${this.uid}`,
140
+ appId
141
+ ])
94
142
  }
95
143
 
96
144
  async uninstall(appId) {
97
- return this.common(this.sshCmd, [`uninstall --uid ${this.uid}`, appId])
145
+ return this.common(sshBinary(), [
146
+ ...sshCmdArgs(`box@${this.domain}`),
147
+ `uninstall --uid ${this.uid}`,
148
+ appId
149
+ ])
98
150
  }
99
151
 
100
152
  async devshell(appId, isUserApp, onconnect = null) {
@@ -111,13 +163,15 @@ export class DebugBridge {
111
163
  const resolvedIp = await resolveDomain(this.domain)
112
164
 
113
165
  const stream = spawn(
114
- sshCmd("-p 22222", `box@${resolvedIp}`),
166
+ sshBinary(),
115
167
  [
168
+ ...sshCmdArgs(`box@${resolvedIp}`),
116
169
  "-t",
117
170
  "devshell",
118
171
  `--uid ${this.uid}`,
119
172
  isUserApp ? "--userapp" : "",
120
173
  appId,
174
+ "/bin/sh",
121
175
  "/lzcapp/pkg/content/devshell/exec.sh"
122
176
  ],
123
177
  {
@@ -142,17 +196,17 @@ export class DebugBridge {
142
196
  const resolvedIp = await resolveDomain(this.domain)
143
197
  const stream = fs.createReadStream(contextTar)
144
198
 
145
- const ssh = spawn(
146
- sshCmd("-p 22222", `box@${resolvedIp}`),
147
- [`build --tag ${tag}`],
199
+ const buildStream = spawn(
200
+ sshBinary(),
201
+ [...sshCmdArgs(`box@${resolvedIp}`), `build --tag ${tag}`],
148
202
  {
149
203
  shell: true,
150
204
  stdio: ["pipe", "inherit", "inherit"]
151
205
  }
152
206
  )
153
- stream.pipe(ssh.stdin)
207
+ stream.pipe(buildStream.stdin)
154
208
  return new Promise((resolve, reject) => {
155
- ssh.on("close", (code) => {
209
+ buildStream.on("close", (code) => {
156
210
  code == 0
157
211
  ? resolve(`dev.${this.boxname}.heiyu.space/${tag}`)
158
212
  : reject("在盒子中构建 image 失败")
@@ -2,7 +2,7 @@
2
2
  import path from "node:path"
3
3
  import fs from "node:fs"
4
4
  import logger from "loglevel"
5
- import { execSync } from "node:child_process"
5
+ import spawn from "cross-spawn"
6
6
  import { LpkInstaller } from "./lpk_installer.js"
7
7
  import debounce from "lodash.debounce"
8
8
  import {
@@ -18,7 +18,10 @@ import {
18
18
  isUserApp,
19
19
  createTemplateFileCommon,
20
20
  isDebugMode,
21
- resolveDomain
21
+ resolveDomain,
22
+ isWindows,
23
+ isMacOs,
24
+ isLinux
22
25
  } from "../utils.js"
23
26
  import os from "node:os"
24
27
  import commandExists from "command-exists"
@@ -152,36 +155,60 @@ export class AppDevShell {
152
155
  this.lpkBuild.onBeforeTarContent(async (contentdir) => {
153
156
  const busyboxPath = path.join(
154
157
  contextDirname(import.meta.url),
155
- "../../template/_lpk/busybox-1.35.0"
158
+ "..",
159
+ "..",
160
+ "template",
161
+ "_lpk",
162
+ "busybox-1.35.0"
156
163
  )
157
164
  let dest = path.join(contentdir, "devshell", "busybox")
158
165
  ensureDir(dest)
159
166
  fs.copyFileSync(busyboxPath, dest)
160
- fs.chmodSync(dest, 0o775)
161
167
  })
162
168
 
163
169
  // 复制 init_debug_bridge.sh 到 devshell 中去
164
170
  this.lpkBuild.onBeforeTarContent(async (contentdir) => {
165
171
  const initPath = path.join(
166
172
  contextDirname(import.meta.url),
167
- "../../template/_lpk/init_debug_bridge.sh"
173
+ "..",
174
+ "..",
175
+ "template",
176
+ "_lpk",
177
+ "init_debug_bridge.sh"
168
178
  )
169
179
  let dest = path.join(contentdir, "devshell", "init_debug_bridge.sh")
170
180
  ensureDir(dest)
171
181
  fs.copyFileSync(initPath, dest)
172
- fs.chmodSync(dest, 0o775)
173
182
  })
174
183
 
175
184
  // 复制 exec.sh 到 devshell 中去
176
185
  this.lpkBuild.onBeforeTarContent(async (contentdir) => {
177
186
  const execScriptPath = path.join(
178
187
  contextDirname(import.meta.url),
179
- "../../template/_lpk/exec.sh"
188
+ "..",
189
+ "..",
190
+ "template",
191
+ "_lpk",
192
+ "exec.sh"
180
193
  )
181
194
  let dest = path.join(contentdir, "devshell", "exec.sh")
182
195
  ensureDir(dest)
183
196
  fs.copyFileSync(execScriptPath, dest)
184
- fs.chmodSync(dest, 0o775)
197
+ })
198
+
199
+ // 复制 rsyncd.conf 到 devshell 中去
200
+ this.lpkBuild.onBeforeTarContent(async (contentdir) => {
201
+ const execScriptPath = path.join(
202
+ contextDirname(import.meta.url),
203
+ "..",
204
+ "..",
205
+ "template",
206
+ "_lpk",
207
+ "rsyncd.conf"
208
+ )
209
+ let dest = path.join(contentdir, "devshell", "rsyncd.conf")
210
+ ensureDir(dest)
211
+ fs.copyFileSync(execScriptPath, dest)
185
212
  })
186
213
 
187
214
  // 复制 setupscript 脚本
@@ -202,7 +229,6 @@ export class AppDevShell {
202
229
  } else {
203
230
  fs.writeFileSync(dest, `#!/bin/sh\nset -ex\n${devshell["setupscript"]}`)
204
231
  }
205
- fs.chmodSync(dest, 0o775)
206
232
  })
207
233
 
208
234
  // 在生成 manifest.yml 之前合并 devshell manifest 模板字段
@@ -214,9 +240,10 @@ export class AppDevShell {
214
240
  logger.debug("options devshell delete 'routes' field")
215
241
  delete options["devshell"]["routes"]
216
242
 
217
- // 添加 devshell 必要路由
243
+ // 添加 devshell 必要路由,这里需要使用 /bin/sh 启动后面的脚本,因为在
244
+ // Windows 上打包的文件将会丢失可执行权限
218
245
  routes.push(
219
- "/__debug.bridge=exec://80,/lzcapp/pkg/content/devshell/init_debug_bridge.sh"
246
+ "/__debug.bridge=exec://80,/bin/sh /lzcapp/pkg/content/devshell/init_debug_bridge.sh"
220
247
  )
221
248
  routes.push("/__isdevshell=file:///lzcapp/pkg/devshell")
222
249
 
@@ -281,7 +308,11 @@ export class AppDevShell {
281
308
  try {
282
309
  const dockerfilePath = path.join(
283
310
  contextDirname(import.meta.url),
284
- "../../template/_lpk/Dockerfile.in"
311
+ "..",
312
+ "..",
313
+ "template",
314
+ "_lpk",
315
+ "Dockerfile.in"
285
316
  )
286
317
  await createTemplateFileCommon(
287
318
  dockerfilePath,
@@ -413,42 +444,52 @@ class DevShell {
413
444
  }
414
445
 
415
446
  async syncProject(appId) {
416
- // prettier-ignore
417
- const resolvedIp = await resolveDomain(`dev.${shellApi.boxname}.heiyu.space`);
418
- let rsh = [
419
- "ssh",
420
- "-o",
421
- `"ProxyCommand ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q -W %h:%p -p 22222 box@${resolvedIp}"`,
422
- "-p",
423
- `22222`,
424
- "-o",
425
- `"StrictHostKeyChecking=no"`,
426
- "-o",
427
- `"UserKnownHostsFile=/dev/null"`,
428
- "-o",
429
- `"ConnectionAttempts=3"`,
430
- "-o",
431
- `"ConnectTimeout=30"`,
432
- "-o",
433
- `${isDebugMode() ? '"LogLevel=DEBUG"' : '"LogLevel=ERROR"'}`
434
- ].join(" ")
435
- // 检查rsync工具是否存在:提示用户
436
- const rsyncExisted = commandExists.sync("rsync")
437
- if (!rsyncExisted) {
438
- logger.error("请检查 rsync 是否安装,路径是否正确!")
439
- process.exit(1)
447
+ if (isLinux || isMacOs) {
448
+ // 检查rsync工具是否存在:提示用户
449
+ const rsyncExisted = commandExists.sync("rsync")
450
+ if (!rsyncExisted) {
451
+ logger.error("请检查 rsync 是否安装,路径是否正确!")
452
+ process.exit(1)
453
+ }
440
454
  }
441
455
 
456
+ const resolvedIp = await resolveDomain(
457
+ `dev.${shellApi.boxname}.heiyu.space`
458
+ )
442
459
  const rsyncDebug = isDebugMode() ? "-P" : ""
443
460
  const host = this.isUserApp
444
461
  ? `${shellApi.uid}.app.${appId}.lzcapp`
445
462
  : `app.${appId}.lzcapp`
446
- const dest = `root@${host}:/lzcapp/cache/devshell`
463
+ const dest = `rsync://${host}@[${resolvedIp}]:873/lzcapp/cache/devshell`
464
+ const rsyncCmd = isWindows
465
+ ? path.join(
466
+ contextDirname(import.meta.url),
467
+ "..",
468
+ "..",
469
+ "template",
470
+ "_lpk",
471
+ "win-rsync",
472
+ "rsync.exe"
473
+ )
474
+ : `rsync`
475
+
447
476
  try {
448
- execSync(
449
- `rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update -F --filter=':- .gitignore' --ignore-errors . ${dest}`,
450
- { stdio: ["ignore", isDebugMode() ? "inherit" : "ignore", "inherit"] }
451
- )
477
+ const rsyncArgs = [
478
+ `${rsyncDebug}`,
479
+ `--recursive`,
480
+ `--relative`,
481
+ `--perms`,
482
+ `--update`,
483
+ `-F --filter=':- .gitignore'`,
484
+ `--ignore-errors`,
485
+ `. ${dest}`
486
+ ]
487
+ logger.debug("同步代码: ", rsyncCmd, rsyncArgs.join(" "))
488
+ const rsyncStream = spawn.sync(rsyncCmd, rsyncArgs, {
489
+ env: { RSYNC_PASSWORD: "fakefakefake" },
490
+ shell: true,
491
+ stdio: ["ignore", isDebugMode() ? "inherit" : "ignore", "inherit"]
492
+ })
452
493
  } catch (err) {
453
494
  logger.error("rsync 同步失败")
454
495
  logger.debug(err)
package/lib/shellapi.js CHANGED
@@ -1,6 +1,12 @@
1
1
  import grpc from "@grpc/grpc-js"
2
2
  import protoLoader from "@grpc/proto-loader"
3
- import { contextDirname } from "./utils.js"
3
+ import {
4
+ contextDirname,
5
+ isMacOs,
6
+ isWindows,
7
+ isLinux,
8
+ isFileExist
9
+ } from "./utils.js"
4
10
  import path from "node:path"
5
11
  import fs from "node:fs"
6
12
  import os from "node:os"
@@ -15,9 +21,9 @@ const bannerfileContent = `˄=ᆽ=ᐟ \\`
15
21
  function getShellAPIConfigDir() {
16
22
  const home = os.homedir()
17
23
  let suffix = "/.config/hportal-client"
18
- if (process.platform === "darwin") {
24
+ if (isMacOs) {
19
25
  suffix = "/Library/Application Support/hportal-client"
20
- } else if (process.platform === "win32") {
26
+ } else if (isWindows) {
21
27
  suffix = "\\AppData\\Roaming\\hportal-client"
22
28
  }
23
29
  const SHELLAPI_CONFIG_DIR = path.join(home, suffix)
@@ -28,19 +34,49 @@ class ShellApi {
28
34
  constructor() {}
29
35
 
30
36
  async init() {
31
- const pbShell = this.initShell()
32
- const { addr, cred } = this.readShellApiInfo()
33
- this.client = new pbShell.ShellCore(addr, grpc.credentials.createInsecure())
34
-
35
37
  // 检查当前 shell 环境上下文是否配置 HTTP_PROXY
36
38
  if (this.checkEnvProxy()) {
37
- logger.warn(`WARN:: 当前终端环境已配置 HTTP_PROXY 代理,可能会导致访问懒猫微服失败,如果未影响功能可以忽略该警告`)
39
+ logger.warn(
40
+ `WARN:: 当前终端环境已配置 HTTP_PROXY 代理,可能会导致访问懒猫微服失败,如果未影响功能可以忽略该警告`
41
+ )
38
42
  }
39
43
 
40
- const md = new grpc.Metadata()
41
- md.add("lzc-shellapi-cred", cred)
42
- this.metadata = md
43
- this.info = await this.initBoxInfo()
44
+ const SHELLAPI_CONFIG_DIR = getShellAPIConfigDir()
45
+ const addrFile = path.resolve(SHELLAPI_CONFIG_DIR, "shellapi_addr")
46
+ const credFile = path.resolve(SHELLAPI_CONFIG_DIR, "shellapi_cred")
47
+ if (!isFileExist(addrFile) || !isFileExist(credFile)) {
48
+ logger.warn(`WARN:: 读取不到以下路径中的文件,无法连接客户端自动判读盒子名称和盒子用户,
49
+
50
+ ${addrFile} 盒子的连接地址
51
+ ${credFile} 盒子的连接凭证
52
+
53
+ 但可使用环境变量指定:
54
+ - BOX_NAME=foo 指定盒子的名称
55
+ - BOX_UID=bar 指定盒子的用户
56
+
57
+ NOTE:在指定环境变量的模式下,有些接口依旧不能访问,为了更好的开发体验,建议尽量在同主机的环境下启动客户端`)
58
+ const md = new grpc.Metadata()
59
+ md.add("lzc-shellapi-cred", "fakecred")
60
+ this.metadata = md
61
+ const boxUid = process.env.BOX_UID
62
+ const boxName = process.env.BOX_NAME
63
+ if (!boxUid || !boxName) {
64
+ process.exit(1)
65
+ return
66
+ }
67
+ this.info = { uid: boxUid, boxname: boxName }
68
+ } else {
69
+ const pbShell = this.initShell()
70
+ const { addr, cred } = this.readShellApiInfo()
71
+ this.client = new pbShell.ShellCore(
72
+ addr,
73
+ grpc.credentials.createInsecure()
74
+ )
75
+ const md = new grpc.Metadata()
76
+ md.add("lzc-shellapi-cred", cred)
77
+ this.metadata = md
78
+ this.info = await this.initBoxInfo()
79
+ }
44
80
  this.checkDevTools()
45
81
  }
46
82
 
@@ -121,14 +157,15 @@ class ShellApi {
121
157
  }
122
158
 
123
159
  checkEnvProxy() {
124
- if(process.env.HTTPS_PROXY ||
125
- process.env.HTTP_PROXY ||
126
- process.env.ALL_PROXY ||
127
- process.env.https_proxy ||
128
- process.env.http_proxy ||
129
- process.env.all_proxy
130
- ){
131
- return true
160
+ if (
161
+ process.env.HTTPS_PROXY ||
162
+ process.env.HTTP_PROXY ||
163
+ process.env.ALL_PROXY ||
164
+ process.env.https_proxy ||
165
+ process.env.http_proxy ||
166
+ process.env.all_proxy
167
+ ) {
168
+ return true
132
169
  }
133
170
  return false
134
171
  }
package/lib/utils.js CHANGED
@@ -12,7 +12,7 @@ import https from "node:https"
12
12
  import http from "node:http"
13
13
  import zlib from "node:zlib"
14
14
  import process from "node:process"
15
- import { spawnSync } from "node:child_process"
15
+ import spawn from "cross-spawn"
16
16
  import logger from "loglevel"
17
17
  import * as tar from "tar"
18
18
  import dns from "node:dns/promises"
@@ -33,6 +33,10 @@ api.interceptors.response.use(
33
33
  }
34
34
  )
35
35
 
36
+ export const isMacOs = process.platform == "darwin"
37
+ export const isLinux = process.platform == "linux"
38
+ export const isWindows = process.platform == "win32"
39
+
36
40
  export const envsubstr = async (templateContents, args) => {
37
41
  const parse = await importDefault("envsub/js/envsub-parser.js")
38
42
  return parse(templateContents, args)
@@ -350,7 +354,7 @@ export class FileLocker {
350
354
  }
351
355
  }
352
356
  withOpen(filename) {
353
- const p = spawnSync("lsof", ["-w", "-t", filename])
357
+ const p = spawn.sync("lsof", ["-w", "-t", filename])
354
358
  if (p.status === 0) {
355
359
  return true
356
360
  } else {
@@ -438,6 +442,10 @@ export function isDebugMode() {
438
442
  return logger.getLevel() <= logger.levels.DEBUG
439
443
  }
440
444
 
445
+ export function isTraceMode() {
446
+ return logger.getLevel() <= logger.levels.TRACE
447
+ }
448
+
441
449
  export async function resolveDomain(domain) {
442
450
  try {
443
451
  // Set machine's dns server as defalut dns server
@@ -466,17 +474,6 @@ export async function resolveDomain(domain) {
466
474
  }
467
475
  }
468
476
 
469
- export function sshCmd(...args) {
470
- const baseCommand = "ssh"
471
- const defaultOptions = [
472
- "-o StrictHostKeyChecking=no",
473
- "-o UserKnownHostsFile=/dev/null",
474
- "-q"
475
- ]
476
- const options = [...defaultOptions, ...args].join(" ")
477
- return `${baseCommand} ${options}`
478
- }
479
-
480
477
  export function unzipSync(zipPath, destPath) {
481
478
  // 确保目标目录存在
482
479
  fs.mkdirSync(destPath, { recursive: true })
@@ -486,3 +483,94 @@ export function unzipSync(zipPath, destPath) {
486
483
  // 同步解压所有文件
487
484
  zip.extractAllTo(destPath, true)
488
485
  }
486
+
487
+ export function findSshPublicKey() {
488
+ // 获取用户 HOME 目录
489
+ let homeDir
490
+ switch (process.platform) {
491
+ case "win32":
492
+ // Windows: 通常是 C:\Users\username
493
+ homeDir = process.env.USERPROFILE || os.homedir()
494
+ break
495
+ case "darwin":
496
+ case "linux":
497
+ // macOS 和 Linux: 通常是 /Users/username 或 /home/username
498
+ homeDir = process.env.HOME || os.homedir()
499
+ break
500
+ default:
501
+ throw new Error("Unsupported operating system")
502
+ }
503
+
504
+ const sshDir = path.join(homeDir, ".ssh")
505
+
506
+ // 检查 .ssh 目录是否存在
507
+ try {
508
+ const dirStat = fs.statSync(sshDir)
509
+ if (!dirStat.isDirectory()) {
510
+ throw new Error(
511
+ ".ssh 目前存在,但不是一个正常的目录,请确保 ssh 已经安装,以及 ssh-keygen 生成公钥"
512
+ )
513
+ }
514
+ } catch (error) {
515
+ if (error.code === "ENOENT") {
516
+ throw new Error(
517
+ ".ssh 目录不存在, 请确保 ssh 已经安装,以及 ssh-keygen 生成公钥"
518
+ )
519
+ }
520
+ throw error
521
+ }
522
+
523
+ // 定义公钥优先级列表
524
+ const priorityKeys = [
525
+ "id_ed25519.pub",
526
+ "id_rsa.pub",
527
+ "id_ecdsa.pub",
528
+ "id_dsa.pub"
529
+ ]
530
+
531
+ try {
532
+ // 获取 .ssh 目录下所有文件
533
+ const files = fs.readdirSync(sshDir)
534
+
535
+ // 首先检查优先级列表中的公钥
536
+ for (const keyName of priorityKeys) {
537
+ if (files.includes(keyName)) {
538
+ const keyPath = path.join(sshDir, keyName)
539
+ // 验证文件是否可读
540
+ try {
541
+ fs.accessSync(keyPath, fs.constants.R_OK)
542
+ return {
543
+ path: keyPath,
544
+ content: fs.readFileSync(keyPath, "utf8")
545
+ }
546
+ } catch (error) {
547
+ console.warn(`Found ${keyName} but cannot read it:`, error.message)
548
+ continue
549
+ }
550
+ }
551
+ }
552
+
553
+ // 如果没找到优先级列表中的公钥,查找任何 .pub 结尾的文件
554
+ const pubKey = files.find((file) => file.endsWith(".pub"))
555
+ if (pubKey) {
556
+ const keyPath = path.join(sshDir, pubKey)
557
+ try {
558
+ fs.accessSync(keyPath, fs.constants.R_OK)
559
+ return {
560
+ path: keyPath,
561
+ content: fs.readFileSync(keyPath, "utf8")
562
+ }
563
+ } catch (error) {
564
+ throw new Error(`Found ${pubKey} but cannot read it: ${error.message}`)
565
+ }
566
+ }
567
+ throw new Error(
568
+ ".ssh 目录没有没有找到任何 .pub 公钥. 请使用 ssh-keygen 生成"
569
+ )
570
+ } catch (error) {
571
+ if (error.code === "ENOENT") {
572
+ throw new Error("不能查询 .ssh 目录")
573
+ }
574
+ throw error
575
+ }
576
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazycatcloud/lzc-cli",
3
- "version": "1.2.37",
3
+ "version": "1.2.41",
4
4
  "description": "lazycat cloud developer kit",
5
5
  "files": [
6
6
  "template",
@@ -20,12 +20,14 @@
20
20
  "@balena/dockerignore": "^1.0.2",
21
21
  "@grpc/grpc-js": "^1.11.1",
22
22
  "@grpc/proto-loader": "^0.7.13",
23
+ "@lazycatcloud/sdk": "^0.1.423",
23
24
  "adm-zip": "^0.5.16",
24
25
  "archiver": "^7.0.1",
25
26
  "axios": "^1.7.7",
26
27
  "chalk": "^5.3.0",
27
28
  "chokidar": "^3.6.0",
28
29
  "command-exists": "^1.2.9",
30
+ "cross-spawn": "^7.0.3",
29
31
  "dockerfile-ast": "^0.6.1",
30
32
  "envsub": "^4.1.0",
31
33
  "fast-glob": "^3.3.2",
File without changes
@@ -4,7 +4,7 @@
4
4
  # 从1号进程读取环境变量
5
5
  cat > /tmp/lzcapp_env.sh << EOF
6
6
  #!/bin/sh
7
- $(/lzcapp/pkg/content/devshell/busybox tr '\0' '\n' < /proc/1/environ | /lzcapp/pkg/content/devshell/busybox xargs -I{} echo export {})
7
+ $(/usr/lib/debug.bridge/busybox tr '\0' '\n' < /proc/1/environ | /usr/lib/debug.bridge/busybox xargs -I{} echo export {})
8
8
  EOF
9
9
  . /tmp/lzcapp_env.sh
10
10
 
@@ -2,30 +2,38 @@
2
2
  set -e
3
3
 
4
4
  DEBUG_BRIDGE_LZCAPP=http://app.cloud.lazycat.developer.tools.lzcapp
5
- BUSYBOX=/lzcapp/pkg/content/devshell/busybox
5
+
6
+ mkdir -p /usr/lib/debug.bridge/
7
+ # 不能直接对 pkg content 中的 busybox chmod(Read-Only权限)
8
+ LZCBUSYBOX=/usr/lib/debug.bridge/busybox
9
+ cp /lzcapp/pkg/content/devshell/busybox ${LZCBUSYBOX}
10
+ chmod +x ${LZCBUSYBOX}
6
11
 
7
12
  mkdir -p /root/.ssh
8
13
  mkdir -p /etc/debug.bridge
9
14
 
10
15
  if ! [ -f /usr/bin/dropbearmulti ]; then
11
- ${BUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/resources/dropbearmulti -O /usr/bin/dropbearmulti
16
+ ${LZCBUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/resources/dropbearmulti -O /usr/bin/dropbearmulti
12
17
  chmod +x /usr/bin/dropbearmulti
13
18
  fi
14
19
 
15
20
  if ! [ -f /usr/bin/rsync ]; then
16
- ${BUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/resources/rsync -O /usr/bin/rsync
21
+ ${LZCBUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/resources/rsync -O /usr/bin/rsync
17
22
  chmod +x /usr/bin/rsync
18
23
  fi
19
24
 
20
- ${BUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/resources/authorized_keys -O /root/.ssh/authorized_keys
25
+ ${LZCBUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/resources/authorized_keys -O /root/.ssh/authorized_keys
21
26
  chmod 0644 /root/.ssh/authorized_keys
22
27
 
23
- ${BUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/bannerfile -O /etc/debug.bridge/bannerfile
28
+ ${LZCBUSYBOX} wget ${DEBUG_BRIDGE_LZCAPP}/bannerfile -O /etc/debug.bridge/bannerfile
24
29
 
25
- if ${BUSYBOX} netstat -tln | grep ':22222' >/dev/null; then
30
+ if ${LZCBUSYBOX} netstat -tln | grep ':22222' >/dev/null; then
26
31
  echo "端口22222正在监听"
27
- ${BUSYBOX} sleep infinity
32
+ ${LZCBUSYBOX} sleep infinity
28
33
  else
34
+ echo "启动 rsync daemon."
35
+ rsync --daemon --no-detach --config=/lzcapp/pkg/content/devshell/rsyncd.conf &
36
+
29
37
  mkdir -p /etc/dropbear
30
38
  exec dropbearmulti dropbear -R -F -E -B -p 22222
31
39
  fi
@@ -0,0 +1,28 @@
1
+ # 全局参数
2
+ uid = root
3
+ gid = root
4
+ use chroot = no
5
+ max connections = 4
6
+ timeout = 300
7
+ pid file = /var/run/rsyncd.pid
8
+ lock file = /var/run/rsync.lock
9
+
10
+ # 实时输出设置
11
+ log file = /dev/stdout
12
+ transfer logging = yes
13
+ log format = %t rsync [%p] %o %h [%a] %m %f %l
14
+
15
+ [lzcapp]
16
+ path = /lzcapp
17
+ read only = no
18
+ dont compress = *.gz *.bz2 *.zip
19
+
20
+ [root]
21
+ path = /root/
22
+ read only = no
23
+ dont compress = *.gz *.bz2 *.zip
24
+
25
+ [tmp]
26
+ path = /tmp/
27
+ read only = no
28
+ dont compress = *.gz *.bz2 *.zip