@lazycatcloud/lzc-cli 1.1.9 → 1.1.11
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/api.js +6 -21
- package/lib/app/index.js +12 -13
- package/lib/app/lpk_build.js +19 -0
- package/lib/app/lpk_devshell.js +19 -164
- package/lib/app/lpk_devshell_docker.js +2 -2
- package/lib/app/lpk_installer.js +17 -7
- package/lib/appstore/publish.js +3 -1
- package/lib/key.js +1 -1
- package/lib/lzc_sdk.js +25 -0
- package/lib/sdk.js +1 -1
- package/package.json +2 -2
- package/template/_lpk/exec.sh +4 -1
- package/lib/autologin.js +0 -4
- package/template/_lpk/local_devshell/Dockerfile +0 -16
- package/template/_lpk/local_devshell/build.sh +0 -5
- package/template/_lpk/local_devshell/entrypoint.sh +0 -87
- package/template/_lpk/local_devshell/sshd_config +0 -117
package/lib/api.js
CHANGED
|
@@ -7,9 +7,10 @@ import fetch from "node-fetch";
|
|
|
7
7
|
//
|
|
8
8
|
// sdk 提供的API
|
|
9
9
|
export default class API {
|
|
10
|
-
constructor(appId, host) {
|
|
10
|
+
constructor(appId, host, uid = "") {
|
|
11
11
|
this.appId = appId;
|
|
12
12
|
this.host = host;
|
|
13
|
+
this.uid = uid;
|
|
13
14
|
}
|
|
14
15
|
async install(zipPath) {
|
|
15
16
|
if (!fs.existsSync(zipPath)) {
|
|
@@ -35,7 +36,7 @@ export default class API {
|
|
|
35
36
|
|
|
36
37
|
async resume() {
|
|
37
38
|
const resp = await fetch(
|
|
38
|
-
`${this.host}/api/v1/app/resume?id=${this.appId}`,
|
|
39
|
+
`${this.host}/api/v1/app/resume?id=${this.appId}&userId=${this.uid}`,
|
|
39
40
|
{
|
|
40
41
|
method: "POST",
|
|
41
42
|
}
|
|
@@ -46,24 +47,6 @@ export default class API {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
async hasMount() {
|
|
50
|
-
const resp = await fetch(
|
|
51
|
-
`${this.host}/api/v1/app/hasMount?id=${this.appId}`,
|
|
52
|
-
{
|
|
53
|
-
method: "GET",
|
|
54
|
-
}
|
|
55
|
-
);
|
|
56
|
-
if (resp.status == 200) {
|
|
57
|
-
return true;
|
|
58
|
-
} else {
|
|
59
|
-
const text = await resp.text();
|
|
60
|
-
if (text) {
|
|
61
|
-
logger.debug("hasMount request failed", text);
|
|
62
|
-
}
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
50
|
async isDevshell() {
|
|
68
51
|
const resp = await fetch(
|
|
69
52
|
`${this.host}/api/v1/app/isDevshell?id=${this.appId}`,
|
|
@@ -83,7 +66,9 @@ export default class API {
|
|
|
83
66
|
}
|
|
84
67
|
|
|
85
68
|
async status() {
|
|
86
|
-
const resp = await fetch(
|
|
69
|
+
const resp = await fetch(
|
|
70
|
+
`${this.host}/api/v1/app/status?id=${this.appId}&userId=${this.uid}`
|
|
71
|
+
);
|
|
87
72
|
if (resp.status == 200) {
|
|
88
73
|
const status = await resp.json();
|
|
89
74
|
return status;
|
package/lib/app/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import lpkCreate from "./lpk_create.js";
|
|
3
3
|
import { LpkBuild } from "./lpk_build.js";
|
|
4
|
-
import { AppDevShell
|
|
4
|
+
import { AppDevShell } from "./lpk_devshell.js";
|
|
5
5
|
import { LpkInstaller } from "./lpk_installer.js";
|
|
6
6
|
import { LpkUninstaller } from "./lpk_uninstall.js";
|
|
7
7
|
import { LpkStatuser } from "./lpk_status.js";
|
|
@@ -53,32 +53,31 @@ export function lpkProjectCommand(program) {
|
|
|
53
53
|
alias: "rsync",
|
|
54
54
|
deprecate: true,
|
|
55
55
|
type: "boolean",
|
|
56
|
-
default:
|
|
56
|
+
default: true,
|
|
57
57
|
});
|
|
58
|
-
const shell = process.env.SHELL
|
|
59
|
-
? path.basename(process.env.SHELL)
|
|
60
|
-
: "sh";
|
|
61
58
|
args.option("s", {
|
|
62
59
|
alias: "shell",
|
|
63
60
|
describe: "指定你最喜欢的shell",
|
|
64
61
|
type: "string",
|
|
65
|
-
default:
|
|
62
|
+
default: "bash",
|
|
66
63
|
});
|
|
67
64
|
args.option("f", {
|
|
68
65
|
alias: "force",
|
|
69
66
|
describe: "强制重新构建",
|
|
70
67
|
type: "boolean",
|
|
71
68
|
});
|
|
69
|
+
args.option("c", {
|
|
70
|
+
alias: "config",
|
|
71
|
+
describe: "devshell配置文件",
|
|
72
|
+
type: "string",
|
|
73
|
+
default: "lzc-build.yml",
|
|
74
|
+
});
|
|
72
75
|
},
|
|
73
|
-
handler: async ({ context, shell,
|
|
74
|
-
const app = new AppDevShell(context,
|
|
76
|
+
handler: async ({ context, shell, force, config }) => {
|
|
77
|
+
const app = new AppDevShell(context, config, force);
|
|
75
78
|
await app.init();
|
|
76
79
|
await app.build();
|
|
77
|
-
|
|
78
|
-
await app.rsyncShell(shell);
|
|
79
|
-
} else {
|
|
80
|
-
await app.sshfsShell(shell);
|
|
81
|
-
}
|
|
80
|
+
await app.rsyncShell(shell);
|
|
82
81
|
},
|
|
83
82
|
},
|
|
84
83
|
];
|
package/lib/app/lpk_build.js
CHANGED
|
@@ -14,6 +14,8 @@ import { LpkManifest } from "./lpk_create.js";
|
|
|
14
14
|
import archiver from "archiver";
|
|
15
15
|
import yaml from "js-yaml";
|
|
16
16
|
|
|
17
|
+
const isMacos = process.platform == "darwin";
|
|
18
|
+
|
|
17
19
|
async function tarContentDir(from, to, cwd = "./") {
|
|
18
20
|
const dest = fs.createWriteStream(to);
|
|
19
21
|
tar
|
|
@@ -90,9 +92,26 @@ async function fetchIconTo(options, cwd, destDir) {
|
|
|
90
92
|
// 提供一些方便的环境变量,可以在 lzc-build.yml 中直接使用
|
|
91
93
|
// - LocalIP 本地局域网ip
|
|
92
94
|
function localIp() {
|
|
95
|
+
if (isMacos) {
|
|
96
|
+
const result = spawnSync("sh", [
|
|
97
|
+
"-c",
|
|
98
|
+
"ifconfig | awk '/inet /&&!/127.0.0.1/{print $2;exit}'",
|
|
99
|
+
]);
|
|
100
|
+
if (result.error) {
|
|
101
|
+
logger.debug("macos get current ip is error", result.error);
|
|
102
|
+
return "";
|
|
103
|
+
} else {
|
|
104
|
+
const ip = result.stdout.toString().trim();
|
|
105
|
+
return ip;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
93
108
|
const output = spawnSync("ip", ["route", "get", "1.1.1.1"], {
|
|
94
109
|
encoding: "utf-8",
|
|
95
110
|
});
|
|
111
|
+
if (output.status !== 0) {
|
|
112
|
+
logger.debug("run ip route error", output.status, output.stderr);
|
|
113
|
+
return "";
|
|
114
|
+
}
|
|
96
115
|
const match = output.stdout.match(/src\ (.+)\ uid/i);
|
|
97
116
|
if (match) {
|
|
98
117
|
return match[1];
|
package/lib/app/lpk_devshell.js
CHANGED
|
@@ -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 {
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
6
|
import { LpkBuild } from "./lpk_build.js";
|
|
7
7
|
import { LpkInstaller } from "./lpk_installer.js";
|
|
8
8
|
import debounce from "lodash.debounce";
|
|
@@ -13,14 +13,12 @@ import {
|
|
|
13
13
|
isFileExist,
|
|
14
14
|
GitIgnore,
|
|
15
15
|
sleep,
|
|
16
|
-
envTemplateFile,
|
|
17
16
|
md5String,
|
|
18
17
|
md5File,
|
|
19
18
|
createTemplateFileCommon,
|
|
20
19
|
loadFromYaml,
|
|
21
20
|
FileLocker,
|
|
22
21
|
} from "../utils.js";
|
|
23
|
-
import yaml from "js-yaml";
|
|
24
22
|
import Key from "../key.js";
|
|
25
23
|
import os from "node:os";
|
|
26
24
|
import { sdkEnv } from "../env.js";
|
|
@@ -28,8 +26,8 @@ import commandExists from "command-exists";
|
|
|
28
26
|
import chokidar from "chokidar";
|
|
29
27
|
import _ from "lodash";
|
|
30
28
|
import BoxAPI from "../api.js";
|
|
31
|
-
import inquirer from "inquirer";
|
|
32
29
|
import { SdkDocker } from "./lpk_devshell_docker.js";
|
|
30
|
+
import { getUidByManifest } from "../lzc_sdk.js";
|
|
33
31
|
|
|
34
32
|
function sdkSSHHost() {
|
|
35
33
|
const sdkUrl = sdkEnv.sdkUrl;
|
|
@@ -41,17 +39,6 @@ function sdkSSHPort() {
|
|
|
41
39
|
return 2222;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
function sdkSSHAddress() {
|
|
45
|
-
const host = sdkSSHHost();
|
|
46
|
-
const port = sdkSSHPort();
|
|
47
|
-
return `${host}:${port}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 如果本地的机器没有 docker, 将会使用 rsync 同步
|
|
51
|
-
export function fallbackToRsync() {
|
|
52
|
-
return !commandExists.sync("docker");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
42
|
// 判断是否需要重新构建
|
|
56
43
|
// - 先判断 lzc-build.yml 是否发生改变
|
|
57
44
|
// - 再判断 lzc-build.yml 中的 manifest 中指定的文件是否发生改变
|
|
@@ -59,12 +46,11 @@ export function fallbackToRsync() {
|
|
|
59
46
|
// - 根据 backend api 判断一个 appid 是否属于 running
|
|
60
47
|
// - 根据在 backend api 中判断同步的目录下是否存在文件 判断当前运行的 app 是否已经有一个挂载的实例,避免重复挂载
|
|
61
48
|
class AppDevShellMonitor {
|
|
62
|
-
constructor(cwd, pkgId,
|
|
49
|
+
constructor(cwd, pkgId, uid = "") {
|
|
63
50
|
this.pwd = cwd ? path.resolve(cwd) : process.cwd();
|
|
64
|
-
this.rsyncMode = rsyncMode;
|
|
65
51
|
|
|
66
52
|
this.pkgId = pkgId;
|
|
67
|
-
this.boxapi = new BoxAPI(pkgId, sdkEnv.sdkUrl);
|
|
53
|
+
this.boxapi = new BoxAPI(pkgId, sdkEnv.sdkUrl, uid);
|
|
68
54
|
|
|
69
55
|
this.optionsFilePath = path.join(this.pwd, "lzc-build.yml");
|
|
70
56
|
this.options = loadFromYaml(this.optionsFilePath);
|
|
@@ -99,21 +85,10 @@ class AppDevShellMonitor {
|
|
|
99
85
|
|
|
100
86
|
async shouldBuild() {
|
|
101
87
|
return (
|
|
102
|
-
this.change() ||
|
|
103
|
-
(await this.notRunning()) ||
|
|
104
|
-
(await this.noMount()) ||
|
|
105
|
-
(await this.noDevshell())
|
|
88
|
+
this.change() || (await this.notRunning()) || (await this.noDevshell())
|
|
106
89
|
);
|
|
107
90
|
}
|
|
108
91
|
|
|
109
|
-
async shouldRemount() {
|
|
110
|
-
try {
|
|
111
|
-
return await this.noMount();
|
|
112
|
-
} catch {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
92
|
change() {
|
|
118
93
|
logger.debug("oldHash", this.oldHash);
|
|
119
94
|
logger.debug("newHash", this.newHash);
|
|
@@ -148,43 +123,31 @@ class AppDevShellMonitor {
|
|
|
148
123
|
}
|
|
149
124
|
}
|
|
150
125
|
|
|
151
|
-
async noMount() {
|
|
152
|
-
let hasMount = await this.boxapi.hasMount();
|
|
153
|
-
if (hasMount && this.rsyncMode) {
|
|
154
|
-
throw "sshfs docker 进程还在运行中,如果需要使用rsync模式,请先停止;或者继续使用sshfs模式";
|
|
155
|
-
}
|
|
156
|
-
if (this.rsyncMode) {
|
|
157
|
-
// rsync mode skip mount check
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
return !hasMount;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
126
|
async noDevshell() {
|
|
164
127
|
return !(await this.boxapi.isDevshell());
|
|
165
128
|
}
|
|
166
129
|
}
|
|
167
130
|
|
|
168
131
|
export class AppDevShell {
|
|
169
|
-
constructor(cwd,
|
|
132
|
+
constructor(cwd, config, forceBuild = false) {
|
|
170
133
|
this.cwd = cwd ? path.resolve(cwd) : process.cwd();
|
|
171
|
-
this.lpkBuild =
|
|
134
|
+
this.lpkBuild = undefined;
|
|
172
135
|
this.monitor = undefined;
|
|
173
|
-
this.sshfsProcess = undefined;
|
|
174
|
-
this.rsyncMode = rsyncMode;
|
|
175
136
|
this.forceBuild = forceBuild;
|
|
137
|
+
this.lpkBuildConfig = config;
|
|
176
138
|
}
|
|
177
139
|
|
|
178
140
|
async init() {
|
|
179
141
|
if (!this.lpkBuild) {
|
|
180
|
-
this.lpkBuild = new LpkBuild(this.cwd);
|
|
142
|
+
this.lpkBuild = new LpkBuild(this.cwd, this.lpkBuildConfig);
|
|
181
143
|
await this.lpkBuild.init();
|
|
182
144
|
}
|
|
183
145
|
const manifest = await this.lpkBuild.getManifest();
|
|
146
|
+
const uid = await getUidByManifest(manifest);
|
|
184
147
|
this.monitor = await new AppDevShellMonitor(
|
|
185
148
|
this.cwd,
|
|
186
149
|
manifest["package"],
|
|
187
|
-
|
|
150
|
+
uid
|
|
188
151
|
).init();
|
|
189
152
|
}
|
|
190
153
|
|
|
@@ -194,11 +157,6 @@ export class AppDevShell {
|
|
|
194
157
|
logger.debug("build...");
|
|
195
158
|
await this.devshellBuild();
|
|
196
159
|
}
|
|
197
|
-
// 判断是否需要重新启动 sshfs 容器
|
|
198
|
-
if (!this.rsyncMode && (await this.monitor.shouldRemount())) {
|
|
199
|
-
logger.debug("mount...");
|
|
200
|
-
this.sshfsProcess = await this.sshfs();
|
|
201
|
-
}
|
|
202
160
|
}
|
|
203
161
|
|
|
204
162
|
async devshellBuild() {
|
|
@@ -438,7 +396,8 @@ export class AppDevShell {
|
|
|
438
396
|
logger.debug(err);
|
|
439
397
|
}
|
|
440
398
|
|
|
441
|
-
const
|
|
399
|
+
const uid = await getUidByManifest(manifest);
|
|
400
|
+
const devshell = new DevShell(pairs["private"], pkgId, runShell, uid);
|
|
442
401
|
if (isSync) {
|
|
443
402
|
await devshell.shell();
|
|
444
403
|
} else {
|
|
@@ -449,55 +408,16 @@ export class AppDevShell {
|
|
|
449
408
|
// 现在会一直卡在退出状态后,必须要另外手动的指定 pkill node
|
|
450
409
|
process.exit(0);
|
|
451
410
|
}
|
|
452
|
-
|
|
453
|
-
async sshfs() {
|
|
454
|
-
const k = new Key();
|
|
455
|
-
const pairs = await k.getKeyPair();
|
|
456
|
-
const manifest = await this.lpkBuild.getManifest();
|
|
457
|
-
const localDevshell = new LocalDevshell(pairs, sdkSSHAddress());
|
|
458
|
-
const syncP = localDevshell.run(manifest.package, this.cwd);
|
|
459
|
-
await syncP.ready();
|
|
460
|
-
return syncP;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
async sshfsShell(runShell) {
|
|
464
|
-
const manifest = await this.lpkBuild.getManifest();
|
|
465
|
-
const sdk = new SdkDocker();
|
|
466
|
-
await sdk.interactiveShell(manifest.package, runShell);
|
|
467
|
-
|
|
468
|
-
if (this.sshfsProcess) {
|
|
469
|
-
// already exit
|
|
470
|
-
if (this.sshfsProcess.p.exitCode) {
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const questions = [
|
|
475
|
-
{
|
|
476
|
-
name: "stop",
|
|
477
|
-
type: "input",
|
|
478
|
-
default: "n",
|
|
479
|
-
message: "是否停止 sshfs 同步容器(y/n): ",
|
|
480
|
-
},
|
|
481
|
-
];
|
|
482
|
-
const answers = await inquirer.prompt(questions);
|
|
483
|
-
if (answers.stop.toLowerCase() === "y") {
|
|
484
|
-
this.sshfsProcess.p.kill("SIGTERM");
|
|
485
|
-
} else {
|
|
486
|
-
this.sshfsProcess.p.unref();
|
|
487
|
-
// FIXME: why must process.exit()
|
|
488
|
-
process.exit(0);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
411
|
}
|
|
493
412
|
|
|
494
413
|
class DevShell {
|
|
495
|
-
constructor(keyFile, appId, runShell = "sh") {
|
|
414
|
+
constructor(keyFile, appId, runShell = "sh", uid = "") {
|
|
496
415
|
logger.debug("keyFile", keyFile);
|
|
497
416
|
logger.debug("appid", appId);
|
|
498
417
|
|
|
499
418
|
this.keyFile = keyFile;
|
|
500
419
|
this.appId = appId;
|
|
420
|
+
this.uid = uid;
|
|
501
421
|
this.runShell = runShell;
|
|
502
422
|
}
|
|
503
423
|
|
|
@@ -523,11 +443,12 @@ class DevShell {
|
|
|
523
443
|
}
|
|
524
444
|
|
|
525
445
|
let rsyncDebug = process.env.RSYNCDEBUG ? "-P" : "";
|
|
526
|
-
|
|
446
|
+
const userId = this.uid ? `/${this.uid}` : "";
|
|
447
|
+
let storePath = `/run/lzc_boot/data/cache/${appId}${userId}/devshell`;
|
|
527
448
|
// FIXME: 下方执行命令不确定是否有兼容性问题
|
|
528
449
|
try {
|
|
529
450
|
execSync(
|
|
530
|
-
`rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update . ${host}:${storePath} --filter=':- .gitignore' --exclude='node_modules' --exclude='.git' --
|
|
451
|
+
`rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update . ${host}:${storePath} --filter=':- .gitignore' --exclude='node_modules' --exclude='.git' --ignore-errors --usermap=:nobody --groupmap=*:nobody`,
|
|
531
452
|
{ stdio: ["ignore", "inherit", "inherit"] }
|
|
532
453
|
);
|
|
533
454
|
} catch (err) {
|
|
@@ -615,72 +536,6 @@ class DevShell {
|
|
|
615
536
|
|
|
616
537
|
async connectShell() {
|
|
617
538
|
const sdk = new SdkDocker();
|
|
618
|
-
await sdk.interactiveShell(this.appId, this.runShell);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// 本地的 devshell 容器
|
|
623
|
-
// - 将当前项目目录通过 volumes 的方式绑定到 local_devshell 中
|
|
624
|
-
// - local_devshell 中通过 ssh 反代本地的 22 端口到 sdk 中 30222
|
|
625
|
-
// - sdk 中的 sshfs 通过连接 30222 端口进行同步
|
|
626
|
-
// - 结合 local_devshell 容器中的 entrypoint 脚本
|
|
627
|
-
class LocalDevshell {
|
|
628
|
-
constructor(pairs, sdkHost) {
|
|
629
|
-
this.publicKey = pairs["public"];
|
|
630
|
-
this.privateKey = pairs["private"];
|
|
631
|
-
this.sdkHost = sdkHost;
|
|
632
|
-
this.log = logger.getLogger("LocalDevshell");
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
run(pkgId, projectPath) {
|
|
636
|
-
const host = this.sdkHost.split(":")[0];
|
|
637
|
-
const port = this.sdkHost.split(":")[1];
|
|
638
|
-
// prettier-ignore
|
|
639
|
-
const syncP = spawn(`docker`, [
|
|
640
|
-
`run`, `--rm`,
|
|
641
|
-
`--network=host`,
|
|
642
|
-
`-v`, `${projectPath}:/project/${pkgId}`,
|
|
643
|
-
`-v`, `${this.privateKey}:/sdk/privateKey`,
|
|
644
|
-
`-v`, `${this.publicKey}:/sdk/publicKey`,
|
|
645
|
-
`-e`, `PKGID=${pkgId}`,
|
|
646
|
-
`-e`, `SDK_HOST=${host}`,
|
|
647
|
-
`-e`, `SDK_PORT=${port}`,
|
|
648
|
-
`-e`, `HOSTUID=${process.getuid()}`,
|
|
649
|
-
`-e`, `HOSTGID=${process.getgid()}`,
|
|
650
|
-
`registry.lazycat.cloud/lzc-cli/local_devshell:0.0.4`,
|
|
651
|
-
], { detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
652
|
-
syncP.stdout.on("data", (data) => {
|
|
653
|
-
this.log.debug(data.toString());
|
|
654
|
-
});
|
|
655
|
-
syncP.stderr.on("data", (data) => {
|
|
656
|
-
this.log.debug(data.toString());
|
|
657
|
-
});
|
|
658
|
-
return {
|
|
659
|
-
p: syncP,
|
|
660
|
-
ready: async () => {
|
|
661
|
-
return new Promise((_resolve, _reject) => {
|
|
662
|
-
syncP.stdout.on("data", (data) => {
|
|
663
|
-
if (data.toString().search(/sshfs running/g) > -1) {
|
|
664
|
-
this.log.debug("sshfs ready");
|
|
665
|
-
_resolve();
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
syncP.on("error", (err) => {
|
|
670
|
-
this.log.error(err);
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
syncP.on("close", (code) => {
|
|
674
|
-
if (code != 0) {
|
|
675
|
-
this.log.debug(`docker exited with code ${code}`);
|
|
676
|
-
_reject();
|
|
677
|
-
} else {
|
|
678
|
-
this.log.debug("sshfs close");
|
|
679
|
-
_resolve();
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
});
|
|
683
|
-
},
|
|
684
|
-
};
|
|
539
|
+
await sdk.interactiveShell(this.appId, this.runShell, this.uid);
|
|
685
540
|
}
|
|
686
541
|
}
|
|
@@ -135,14 +135,14 @@ export class SdkDocker {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
async interactiveShell(appId, shell) {
|
|
138
|
+
async interactiveShell(appId, shell, uid = "") {
|
|
139
139
|
const host = new URL(sdkEnv.sdkUrl).hostname;
|
|
140
140
|
const opts = await connectOptions(host);
|
|
141
141
|
const client = new SSHClient(opts);
|
|
142
142
|
await client.connect();
|
|
143
143
|
try {
|
|
144
144
|
const replacedAppId = appId.replaceAll(".", "_").replaceAll("-", "__");
|
|
145
|
-
const containerName = `lzc
|
|
145
|
+
const containerName = `lzc-${uid}-${replacedAppId}-app-1`;
|
|
146
146
|
const stream = await client.exec(
|
|
147
147
|
`docker exec -e SHELL=${shell} -e PKGID=${appId} -ti ${containerName} /lzcapp/pkg/content/devshell/exec.sh`,
|
|
148
148
|
{
|
package/lib/app/lpk_installer.js
CHANGED
|
@@ -6,6 +6,8 @@ import path from "node:path";
|
|
|
6
6
|
import Key from "../key.js";
|
|
7
7
|
import { loadFromYaml, Downloader, sleep } from "../utils.js";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { getUidByManifest } from "../lzc_sdk.js";
|
|
10
|
+
import fetch from "node-fetch";
|
|
9
11
|
|
|
10
12
|
export class LpkInstaller {
|
|
11
13
|
constructor() {}
|
|
@@ -25,22 +27,30 @@ export class LpkInstaller {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
let manifest = await builder.getManifest();
|
|
28
|
-
let
|
|
30
|
+
let uid = await getUidByManifest(manifest);
|
|
31
|
+
let api = new BoxAPI(manifest["package"], sdkEnv.sdkUrl, uid);
|
|
29
32
|
|
|
30
33
|
let pkgPath = await builder.exec("");
|
|
31
34
|
logger.info("开始部署应用");
|
|
32
35
|
await api.install(pkgPath);
|
|
33
36
|
|
|
37
|
+
const appUrl = sdkEnv.sdkUrl.replace(
|
|
38
|
+
/sdk/,
|
|
39
|
+
manifest["application"]["subdomain"]
|
|
40
|
+
);
|
|
41
|
+
// 如果是多实例的应用,需要先访问下,才会创建对应的容器
|
|
42
|
+
if (manifest["application"]["user_app"]) {
|
|
43
|
+
const res = await fetch(appUrl);
|
|
44
|
+
if (res.status >= 400) {
|
|
45
|
+
throw `访问 ${appUrl} 失败`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
// 新安装的应用默认为休眠的状态,需要先等容器创建成功,等成为 paused 后 resume 下
|
|
35
50
|
await api.checkStatus(true);
|
|
36
51
|
|
|
37
52
|
await sleep(2000);
|
|
38
|
-
logger.info(
|
|
39
|
-
`👉 请在浏览器中访问 ${sdkEnv.sdkUrl.replace(
|
|
40
|
-
/sdk/,
|
|
41
|
-
manifest["application"]["subdomain"]
|
|
42
|
-
)}`
|
|
43
|
-
);
|
|
53
|
+
logger.info(`👉 请在浏览器中访问 ${appUrl}`);
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
async install(pkgUrl) {
|
package/lib/appstore/publish.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { request } from "./login.js";
|
|
1
|
+
import { request, autoLogin } from "./login.js";
|
|
2
2
|
import logger from "loglevel";
|
|
3
3
|
import FormData from "form-data";
|
|
4
4
|
import fs from "node:fs";
|
|
@@ -35,6 +35,8 @@ export class Publish {
|
|
|
35
35
|
fs.rmSync(tempDir, { recursive: true });
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
await autoLogin();
|
|
39
|
+
|
|
38
40
|
if (!changelog) {
|
|
39
41
|
const answer = await askChangeLog();
|
|
40
42
|
changelog = answer.changelog;
|
package/lib/key.js
CHANGED
package/lib/lzc_sdk.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import logger from "loglevel";
|
|
5
|
+
|
|
6
|
+
const lzcConfigFile = path.resolve(
|
|
7
|
+
os.homedir(),
|
|
8
|
+
".config",
|
|
9
|
+
"lzc-client-desktop",
|
|
10
|
+
"defaultBox.json"
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
let lzcConfig = undefined;
|
|
14
|
+
export async function getUidByManifest(manifest) {
|
|
15
|
+
if (manifest["application"]["user_app"]) {
|
|
16
|
+
if (!lzcConfig) {
|
|
17
|
+
lzcConfig = JSON.parse(
|
|
18
|
+
fs.readFileSync(lzcConfigFile, { encoding: "utf8" })
|
|
19
|
+
);
|
|
20
|
+
logger.debug("loginUser: ", lzcConfig["loginUser"]);
|
|
21
|
+
}
|
|
22
|
+
return lzcConfig["loginUser"];
|
|
23
|
+
}
|
|
24
|
+
return "";
|
|
25
|
+
}
|
package/lib/sdk.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazycatcloud/lzc-cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "lazycat cloud developer kit",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"postinstall": "cp .githooks/* .git/hooks/",
|
|
7
6
|
"test": "tap",
|
|
8
7
|
"snap": "tap"
|
|
9
8
|
},
|
|
@@ -32,6 +31,7 @@
|
|
|
32
31
|
"@balena/dockerignore": "^1.0.2",
|
|
33
32
|
"@improbable-eng/grpc-web": "^0.15.0",
|
|
34
33
|
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
|
|
34
|
+
"@lazycatcloud/sdk": "^0.1.120",
|
|
35
35
|
"archiver": "^5.3.0",
|
|
36
36
|
"browser-headers": "^0.4.1",
|
|
37
37
|
"chalk": "^4.1.2",
|
package/template/_lpk/exec.sh
CHANGED
package/lib/autologin.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
FROM alpine:3.16
|
|
2
|
-
|
|
3
|
-
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
|
4
|
-
|
|
5
|
-
RUN apk add --no-cache openssh openssl inotify-tools curl tzdata \
|
|
6
|
-
&& echo "root:root" | chpasswd
|
|
7
|
-
|
|
8
|
-
COPY sshd_config /etc/ssh/sshd_config
|
|
9
|
-
COPY entrypoint.sh /entrypoint.sh
|
|
10
|
-
|
|
11
|
-
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
|
12
|
-
echo "Asia/Shanghai" > /etc/timezone && \
|
|
13
|
-
date && \
|
|
14
|
-
chmod +x /entrypoint.sh
|
|
15
|
-
|
|
16
|
-
CMD ["./entrypoint.sh"]
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
ssh-keygen -A
|
|
5
|
-
|
|
6
|
-
free_port(){
|
|
7
|
-
# $1 最小端口
|
|
8
|
-
local port="$1"
|
|
9
|
-
port="${port:-8192}"
|
|
10
|
-
local ports
|
|
11
|
-
ports="$(cut -c 16-19 /proc/net/tcp)"
|
|
12
|
-
while echo "$ports" | grep -q "$(printf '%.4X' "$port")"; do
|
|
13
|
-
port=$((port+1))
|
|
14
|
-
done
|
|
15
|
-
echo $port
|
|
16
|
-
}
|
|
17
|
-
local_ssh_port="$(free_port 22812)"
|
|
18
|
-
|
|
19
|
-
# do not detach (-D), log to stderr (-e), passthrough other arguments
|
|
20
|
-
/usr/sbin/sshd -e -p $local_ssh_port
|
|
21
|
-
|
|
22
|
-
OPTIONS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=3 -o ConnectTimeout=30 -o ServerAliveInterval=5"
|
|
23
|
-
|
|
24
|
-
# 生成key
|
|
25
|
-
cat >/tmp/prepare.sh <<EOF
|
|
26
|
-
#!/bin/sh
|
|
27
|
-
mkdir -p /tmp/$PKGID/.ssh
|
|
28
|
-
|
|
29
|
-
if [ ! -f /tmp/$PKGID/.ssh/id_ed25519 ]; then
|
|
30
|
-
ssh-keygen -t ed25519 -f /tmp/$PKGID/.ssh/id_ed25519 -P ""
|
|
31
|
-
fi
|
|
32
|
-
EOF
|
|
33
|
-
ssh $OPTIONS -p $SDK_PORT -i /sdk/privateKey $SDK_HOST "$(cat /tmp/prepare.sh)"
|
|
34
|
-
|
|
35
|
-
# 复制生成的 key 到本地的 devshell 容器中
|
|
36
|
-
scp -O -i /sdk/privateKey -P $SDK_PORT $OPTIONS $SDK_HOST:/tmp/$PKGID/.ssh/id_ed25519.pub /tmp/id_ed25519.pub
|
|
37
|
-
# 输出到本地 devshell 容器中的 authorized_keys 中
|
|
38
|
-
mkdir -p /root/.ssh
|
|
39
|
-
cat /tmp/id_ed25519.pub >> /root/.ssh/authorized_keys
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# 生成 ssh port
|
|
43
|
-
cat >/tmp/sdk_port.sh <<\EOF
|
|
44
|
-
#!/bin/sh
|
|
45
|
-
free_port(){
|
|
46
|
-
# $1 最小端口
|
|
47
|
-
local port="$1"
|
|
48
|
-
port="${port:-8192}"
|
|
49
|
-
local ports
|
|
50
|
-
ports="$(cut -c 16-19 /proc/net/tcp)"
|
|
51
|
-
while echo "$ports" | grep -q "$(printf '%.4X' "$port")"; do
|
|
52
|
-
port=$((port+1))
|
|
53
|
-
done
|
|
54
|
-
echo $port
|
|
55
|
-
}
|
|
56
|
-
echo "ssh_port:$(free_port 12282)"
|
|
57
|
-
EOF
|
|
58
|
-
ssh_port=$(ssh $OPTIONS -p $SDK_PORT -i /sdk/privateKey $SDK_HOST "$(cat /tmp/sdk_port.sh)" 2>&1 |\
|
|
59
|
-
grep ssh_port | awk -F: '{ print $2 }')
|
|
60
|
-
echo "ssh port: $ssh_port"
|
|
61
|
-
|
|
62
|
-
# inotify script
|
|
63
|
-
cat >/tmp/inotify.sh <<EOF
|
|
64
|
-
#!/bin/sh
|
|
65
|
-
cd /project
|
|
66
|
-
while [ -d /project/$PKGID ]
|
|
67
|
-
do
|
|
68
|
-
cd /project/$PKGID
|
|
69
|
-
inotifywait -q -m -r -e "modify,move,create,delete" --timefmt "%Y-%m-%d %X" --format "%T,%:e,%w%f,$PKGID" .
|
|
70
|
-
done
|
|
71
|
-
EOF
|
|
72
|
-
chmod +x /tmp/inotify.sh
|
|
73
|
-
|
|
74
|
-
# 反代本地的 $local_ssh_port 端口到 sdk 容器中的端口 ssh_port
|
|
75
|
-
cat >/tmp/sshfs.sh <<EOF
|
|
76
|
-
#!/bin/sh
|
|
77
|
-
set -ex
|
|
78
|
-
|
|
79
|
-
mkdir -p /var/lib/box-server/lzcapps/$PKGID/lzcapp/cache/devshell
|
|
80
|
-
|
|
81
|
-
sshfs -v root@localhost:/project/$PKGID /var/lib/box-server/lzcapps/$PKGID/lzcapp/cache/devshell -o allow_other,default_permissions -o direct_io -o dir_cache=no -o IdentityFile=/tmp/$PKGID/.ssh/id_ed25519 -o auto_unmount $OPTIONS -p $ssh_port -o uid=$HOSTUID -o gid=$HOSTGID
|
|
82
|
-
|
|
83
|
-
echo "sshfs running"
|
|
84
|
-
ssh root@localhost $OPTIONS -p $ssh_port -i /tmp/$PKGID/.ssh/id_ed25519 sh -c '/tmp/inotify.sh' | /lzc-cli-backend -inotify
|
|
85
|
-
EOF
|
|
86
|
-
|
|
87
|
-
exec ssh -R 127.0.0.1:$ssh_port:127.0.0.1:$local_ssh_port -p $SDK_PORT $OPTIONS -i /sdk/privateKey $SDK_HOST "$(cat /tmp/sshfs.sh)"
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# $OpenBSD: sshd_config,v 1.104 2021/07/02 05:11:21 dtucker Exp $
|
|
2
|
-
|
|
3
|
-
# This is the sshd server system-wide configuration file. See
|
|
4
|
-
# sshd_config(5) for more information.
|
|
5
|
-
|
|
6
|
-
# This sshd was compiled with PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
7
|
-
|
|
8
|
-
# The strategy used for options in the default sshd_config shipped with
|
|
9
|
-
# OpenSSH is to specify options with their default value where
|
|
10
|
-
# possible, but leave them commented. Uncommented options override the
|
|
11
|
-
# default value.
|
|
12
|
-
|
|
13
|
-
# Port 22456
|
|
14
|
-
#AddressFamily any
|
|
15
|
-
ListenAddress 127.0.0.1
|
|
16
|
-
#ListenAddress ::
|
|
17
|
-
|
|
18
|
-
#HostKey /etc/ssh/ssh_host_rsa_key
|
|
19
|
-
#HostKey /etc/ssh/ssh_host_ecdsa_key
|
|
20
|
-
#HostKey /etc/ssh/ssh_host_ed25519_key
|
|
21
|
-
|
|
22
|
-
# Ciphers and keying
|
|
23
|
-
#RekeyLimit default none
|
|
24
|
-
|
|
25
|
-
# Logging
|
|
26
|
-
#SyslogFacility AUTH
|
|
27
|
-
#LogLevel INFO
|
|
28
|
-
|
|
29
|
-
# Authentication:
|
|
30
|
-
|
|
31
|
-
#LoginGraceTime 2m
|
|
32
|
-
PermitRootLogin yes
|
|
33
|
-
#StrictModes yes
|
|
34
|
-
#MaxAuthTries 6
|
|
35
|
-
#MaxSessions 10
|
|
36
|
-
|
|
37
|
-
PubkeyAuthentication yes
|
|
38
|
-
|
|
39
|
-
# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
|
|
40
|
-
# but this is overridden so installations will only check .ssh/authorized_keys
|
|
41
|
-
AuthorizedKeysFile .ssh/authorized_keys
|
|
42
|
-
|
|
43
|
-
#AuthorizedPrincipalsFile none
|
|
44
|
-
|
|
45
|
-
#AuthorizedKeysCommand none
|
|
46
|
-
#AuthorizedKeysCommandUser nobody
|
|
47
|
-
|
|
48
|
-
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
|
|
49
|
-
#HostbasedAuthentication no
|
|
50
|
-
# Change to yes if you don't trust ~/.ssh/known_hosts for
|
|
51
|
-
# HostbasedAuthentication
|
|
52
|
-
IgnoreUserKnownHosts yes
|
|
53
|
-
# Don't read the user's ~/.rhosts and ~/.shosts files
|
|
54
|
-
IgnoreRhosts yes
|
|
55
|
-
|
|
56
|
-
# To disable tunneled clear text passwords, change to no here!
|
|
57
|
-
PasswordAuthentication no
|
|
58
|
-
PermitEmptyPasswords no
|
|
59
|
-
|
|
60
|
-
# Change to no to disable s/key passwords
|
|
61
|
-
#KbdInteractiveAuthentication yes
|
|
62
|
-
|
|
63
|
-
# Kerberos options
|
|
64
|
-
#KerberosAuthentication no
|
|
65
|
-
#KerberosOrLocalPasswd yes
|
|
66
|
-
#KerberosTicketCleanup yes
|
|
67
|
-
#KerberosGetAFSToken no
|
|
68
|
-
|
|
69
|
-
# GSSAPI options
|
|
70
|
-
#GSSAPIAuthentication no
|
|
71
|
-
#GSSAPICleanupCredentials yes
|
|
72
|
-
|
|
73
|
-
# Set this to 'yes' to enable PAM authentication, account processing,
|
|
74
|
-
# and session processing. If this is enabled, PAM authentication will
|
|
75
|
-
# be allowed through the KbdInteractiveAuthentication and
|
|
76
|
-
# PasswordAuthentication. Depending on your PAM configuration,
|
|
77
|
-
# PAM authentication via KbdInteractiveAuthentication may bypass
|
|
78
|
-
# the setting of "PermitRootLogin without-password".
|
|
79
|
-
# If you just want the PAM account and session checks to run without
|
|
80
|
-
# PAM authentication, then enable this but set PasswordAuthentication
|
|
81
|
-
# and KbdInteractiveAuthentication to 'no'.
|
|
82
|
-
# UsePAM no
|
|
83
|
-
|
|
84
|
-
AllowAgentForwarding yes
|
|
85
|
-
# Feel free to re-enable these if your use case requires them.
|
|
86
|
-
AllowTcpForwarding yes
|
|
87
|
-
GatewayPorts yes
|
|
88
|
-
X11Forwarding no
|
|
89
|
-
#X11DisplayOffset 10
|
|
90
|
-
#X11UseLocalhost yes
|
|
91
|
-
#PermitTTY yes
|
|
92
|
-
#PrintMotd yes
|
|
93
|
-
#PrintLastLog yes
|
|
94
|
-
#TCPKeepAlive yes
|
|
95
|
-
#PermitUserEnvironment no
|
|
96
|
-
#Compression delayed
|
|
97
|
-
ClientAliveInterval 5
|
|
98
|
-
ClientAliveCountMax 3
|
|
99
|
-
#UseDNS no
|
|
100
|
-
#PidFile /run/sshd.pid
|
|
101
|
-
#MaxStartups 10:30:100
|
|
102
|
-
#PermitTunnel no
|
|
103
|
-
#ChrootDirectory none
|
|
104
|
-
#VersionAddendum none
|
|
105
|
-
|
|
106
|
-
# no default banner path
|
|
107
|
-
#Banner none
|
|
108
|
-
|
|
109
|
-
# override default of no subsystems
|
|
110
|
-
Subsystem sftp /usr/lib/ssh/sftp-server
|
|
111
|
-
|
|
112
|
-
# Example of overriding settings on a per-user basis
|
|
113
|
-
#Match User anoncvs
|
|
114
|
-
# X11Forwarding no
|
|
115
|
-
# AllowTcpForwarding no
|
|
116
|
-
# PermitTTY no
|
|
117
|
-
# ForceCommand cvs server
|