@lazycatcloud/lzc-cli 1.1.13 → 1.2.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 +30 -62
- package/lib/app/index.js +33 -35
- package/lib/app/lpk_debug_bridge.js +101 -0
- package/lib/app/lpk_devshell.js +55 -114
- package/lib/app/lpk_installer.js +43 -40
- package/lib/appstore/login.js +1 -1
- package/lib/appstore/publish.js +18 -8
- package/lib/box/index.js +2 -86
- package/lib/env.js +18 -178
- package/lib/lzc_sdk.js +16 -2
- package/lib/shellapi.js +95 -0
- package/lib/shellapi.proto +301 -0
- package/lib/utils.js +2 -103
- package/package.json +4 -26
- package/template/_lpk/busybox-1.35.0 +0 -0
- package/template/_lpk/exec.sh +1 -10
- package/template/_lpk/init_debug_bridge.sh +24 -0
- package/template/_lpk/vue.lzc-build.yml.in +0 -16
- package/template/golang/lzc-build.yml +0 -15
- package/template/ionic_vue3/lzc-build.yml +0 -16
- package/template/ionic_vue3/package-lock.json +8100 -0
- package/template/lite/lzc-build.yml +0 -28
- package/lib/api.js +0 -155
- package/lib/app/lpk_devshell_docker.js +0 -211
- package/lib/app/lpk_log.js +0 -68
- package/lib/app/lpk_status.js +0 -18
- package/lib/app/lpk_uninstall.js +0 -19
- package/lib/box/api/clientapi.js +0 -1322
- package/lib/box/api/empty.js +0 -35
- package/lib/box/check_qemu.js +0 -51
- package/lib/box/qemu_vm_mgr.js +0 -608
- package/lib/box/schemes/vm_box_system_debian.json +0 -47
- package/lib/core.proto +0 -118
- package/lib/docker/promise.js +0 -91
- package/lib/docker-compose.js +0 -50
- package/lib/fetch.js +0 -50
- package/lib/git/git-commit.sh +0 -7
- package/lib/git/git-reset.sh +0 -15
- package/lib/key.js +0 -102
- package/lib/sdk.js +0 -135
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "home-cloud",
|
|
3
|
-
"uuid": "047dac31-b75b-46ed-aedb-a31fa46a6503",
|
|
4
|
-
"memory": 4096,
|
|
5
|
-
"kernel": "debian",
|
|
6
|
-
"uefi": true,
|
|
7
|
-
"path": "${HOME}/.cache/box-emulator",
|
|
8
|
-
"volume": {
|
|
9
|
-
"config": "/etc/home-cloud"
|
|
10
|
-
},
|
|
11
|
-
"disks": [
|
|
12
|
-
{
|
|
13
|
-
"id": "system",
|
|
14
|
-
"size": "8G",
|
|
15
|
-
"system": true,
|
|
16
|
-
"partitions": [
|
|
17
|
-
{
|
|
18
|
-
"fs_type": "vfat",
|
|
19
|
-
"size": "100M",
|
|
20
|
-
"mount_point": "/boot/ESP"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"fs_type": "ext4",
|
|
24
|
-
"size": "4G",
|
|
25
|
-
"mount_point": "/"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"fs_type": "ext4",
|
|
29
|
-
"size": "full",
|
|
30
|
-
"mount_point": "/mnt"
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"id": "data1",
|
|
36
|
-
"size": "64G",
|
|
37
|
-
"system": false,
|
|
38
|
-
"partitions": []
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"id": "data2",
|
|
42
|
-
"size": "64G",
|
|
43
|
-
"rebuild": false,
|
|
44
|
-
"partitions": []
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
package/lib/core.proto
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
|
|
3
|
-
package space.heiyu.hportal.shell;
|
|
4
|
-
|
|
5
|
-
import "google/protobuf/empty.proto";
|
|
6
|
-
|
|
7
|
-
// 此接口为hclient&hserver曝露给管理界面、命令行客户端等使用的API
|
|
8
|
-
service ShellCore {
|
|
9
|
-
// 查询当前盒子列表的状态
|
|
10
|
-
rpc QueryBoxList(google.protobuf.Empty) returns(BoxList) {}
|
|
11
|
-
// 添加、修改盒子的登陆信息、开启、关闭盒子等
|
|
12
|
-
rpc ModifyBox(BoxSetting) returns (google.protobuf.Empty);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 盒子只会处于以下四种状态之一
|
|
16
|
-
enum BoxStatus {
|
|
17
|
-
// 此盒子当前未激活
|
|
18
|
-
DISABLED = 0;
|
|
19
|
-
|
|
20
|
-
// 正在连接中,此过程持续时间无法评估,
|
|
21
|
-
// 可能瞬间结束也可能持续一两分钟(在确实可以连接上的前提下)
|
|
22
|
-
CONNECTING = 1;
|
|
23
|
-
|
|
24
|
-
// 成功连接上了
|
|
25
|
-
CONNECTED = 2;
|
|
26
|
-
|
|
27
|
-
// 确定无法连接,此时不会继续重试,需要客户端
|
|
28
|
-
// 修正环境后,重新调用ModifyBox后继续尝试。
|
|
29
|
-
FAILED = 3;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
enum ConnectingStatus {
|
|
33
|
-
AWAIT_NEW_DEVICE_AUTH = 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
enum FailedStatus {
|
|
37
|
-
// 未知原因失败
|
|
38
|
-
FAILED_UNKNOWN = 0;
|
|
39
|
-
|
|
40
|
-
// 当前盒子无管理员帐号,需要调用CreateUser创建管理员帐号
|
|
41
|
-
FAILED_NO_ADMINUSER = 1;
|
|
42
|
-
|
|
43
|
-
// 当前盒子未注册盒子名称,需要调用SetupNewBox向horigin申请盒子名称以及配套资源
|
|
44
|
-
FAILED_NO_BOXNAME = 2;
|
|
45
|
-
|
|
46
|
-
// 当前登陆信息不正确,需要调用ModifyBox设置新的auth信息
|
|
47
|
-
FAILED_INVALID_PASSWORD = 3;
|
|
48
|
-
|
|
49
|
-
// 当前客户端与服务器版本不匹配,无法连接,需要升级盒子系统或客户端软件
|
|
50
|
-
FAILED_MISMATCH_BOX_VERSION = 4;
|
|
51
|
-
|
|
52
|
-
// 当前设备被拒绝登录
|
|
53
|
-
FAILED_DEVICE_AUTH_REJECT = 5;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//------ 100+的失败状态一般为本机特有错误状态
|
|
57
|
-
|
|
58
|
-
//调用LoginBox的过程中,此盒子的状态被其他API请求转换为disabled了
|
|
59
|
-
FAILED_LOCAL_DISABLED_BOX = 100;
|
|
60
|
-
|
|
61
|
-
//调用LoginBox时使用了一个不存在的盒子名称。(若只是本地不盒子列表不存在此盒子,会自动添加到盒子列表)
|
|
62
|
-
FAILED_LOCAL_NO_SUCH_BOX_IN_THE_WORLD = 101;
|
|
63
|
-
|
|
64
|
-
//使用LoginBox时,登陆超时
|
|
65
|
-
FAILED_LOCAL_CONNECTING_TIMEOUT = 102;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
enum ExceptionStatus {
|
|
69
|
-
OK = 0;
|
|
70
|
-
Error = 1;
|
|
71
|
-
Booting = 2;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
message BoxInfo {
|
|
75
|
-
string box_id = 1;
|
|
76
|
-
string box_name = 2;
|
|
77
|
-
string box_home_url = 3 [deprecated = true];
|
|
78
|
-
string box_domain = 14;
|
|
79
|
-
string box_virtual_ip = 10;
|
|
80
|
-
|
|
81
|
-
BoxStatus status = 4;
|
|
82
|
-
string status_reason = 5;
|
|
83
|
-
optional FailedStatus failed_status = 6;
|
|
84
|
-
optional ConnectingStatus connecting_status = 12; // deprecated
|
|
85
|
-
ExceptionStatus exception_status = 11;
|
|
86
|
-
|
|
87
|
-
bool is_relay_connection = 13;
|
|
88
|
-
|
|
89
|
-
string login_user = 7;
|
|
90
|
-
bool is_admin_login = 8;
|
|
91
|
-
string auth_token = 9;
|
|
92
|
-
|
|
93
|
-
bool is_default_box = 15;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
message BoxList {
|
|
97
|
-
repeated BoxInfo boxes = 1;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
message BoxSetting {
|
|
101
|
-
string id = 1;
|
|
102
|
-
|
|
103
|
-
optional string name = 2;
|
|
104
|
-
|
|
105
|
-
// 若不设置则使用当前值
|
|
106
|
-
optional bool enabled = 3;
|
|
107
|
-
|
|
108
|
-
message AuthInfo {
|
|
109
|
-
string user = 3;
|
|
110
|
-
string password = 4;
|
|
111
|
-
optional string verification_code = 5;
|
|
112
|
-
}
|
|
113
|
-
optional AuthInfo auth = 4;
|
|
114
|
-
|
|
115
|
-
//设置此盒子为默认盒子,会覆盖其他(如果有)默认盒子的配置
|
|
116
|
-
//第一个添加的盒子会自动成为默认盒子
|
|
117
|
-
optional bool set_as_default_box = 5;
|
|
118
|
-
}
|
package/lib/docker/promise.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import Docker from "dockerode";
|
|
2
|
-
|
|
3
|
-
var wrappedProto = Docker.prototype;
|
|
4
|
-
|
|
5
|
-
function denodeify(func) {
|
|
6
|
-
return function (...args) {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
let input = [...args, (err, val) => (err ? reject(err) : resolve(val))];
|
|
9
|
-
func.apply(this, input);
|
|
10
|
-
});
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function proxyPromise(method) {
|
|
15
|
-
var promisey = denodeify(method);
|
|
16
|
-
return function () {
|
|
17
|
-
return promisey.apply(this.$subject, arguments);
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function promiseObj(target, input) {
|
|
22
|
-
for (var key in input) {
|
|
23
|
-
if (typeof input[key] !== "function") continue;
|
|
24
|
-
target[key] = proxyPromise(input[key]);
|
|
25
|
-
}
|
|
26
|
-
return target;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function PromiseProxy(subject) {
|
|
30
|
-
var result = Object.create(subject);
|
|
31
|
-
result.$subject = subject;
|
|
32
|
-
return promiseObj(result, subject);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function ContainerProxy(subject) {
|
|
36
|
-
var result = PromiseProxy(subject);
|
|
37
|
-
var exec = result.exec;
|
|
38
|
-
result.exec = function () {
|
|
39
|
-
return exec.apply(this, arguments).then(function (exec) {
|
|
40
|
-
return PromiseProxy(exec);
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function DockerProxy(options) {
|
|
47
|
-
this.$subject = new Docker(options);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
promiseObj(DockerProxy.prototype, wrappedProto);
|
|
51
|
-
|
|
52
|
-
// sadly we need to wrap run directly as a promise to consolidate both
|
|
53
|
-
// of the resulting arguments.
|
|
54
|
-
DockerProxy.prototype.run = function (image, command, stream) {
|
|
55
|
-
var subject = this.$subject;
|
|
56
|
-
return new Promise(function (accept, reject) {
|
|
57
|
-
subject.run(image, command, stream, function (err, result, container) {
|
|
58
|
-
if (err) return reject(err);
|
|
59
|
-
accept({
|
|
60
|
-
result: result,
|
|
61
|
-
// re-wrap
|
|
62
|
-
container: ContainerProxy(container),
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// We also have wrap createContainer manually as it returns
|
|
69
|
-
DockerProxy.prototype.createContainer = function (opts) {
|
|
70
|
-
var subject = this.$subject;
|
|
71
|
-
return new Promise(function (accept, reject) {
|
|
72
|
-
subject.createContainer(opts, function (err, container) {
|
|
73
|
-
if (err) return reject(err);
|
|
74
|
-
accept(ContainerProxy(container));
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
DockerProxy.prototype.getImage = function (id) {
|
|
80
|
-
return PromiseProxy(this.$subject.getImage(id));
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
DockerProxy.prototype.getContainer = function (id) {
|
|
84
|
-
return ContainerProxy(this.$subject.getContainer(id));
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
DockerProxy.prototype.getVolume = function (name) {
|
|
88
|
-
return PromiseProxy(this.$subject.getVolume(name));
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export default DockerProxy;
|
package/lib/docker-compose.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import yaml from "js-yaml";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import mergeWith from "lodash.mergewith";
|
|
4
|
-
|
|
5
|
-
export function load(filePath) {
|
|
6
|
-
return yaml.load(fs.readFileSync(filePath, "utf8"));
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// 用于合并多个 DockerCompose 文件
|
|
10
|
-
export default class DockerCompose {
|
|
11
|
-
constructor(basefile) {
|
|
12
|
-
this.basefile = basefile;
|
|
13
|
-
this.obj = load(basefile);
|
|
14
|
-
}
|
|
15
|
-
// 直接修改 yaml 内容
|
|
16
|
-
pipe(cb) {
|
|
17
|
-
cb.apply(this, [this.obj]);
|
|
18
|
-
return this;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// mergeFile({file: ""}) or merge({content: ""})
|
|
22
|
-
merge(context) {
|
|
23
|
-
let newObj;
|
|
24
|
-
if (context.file) {
|
|
25
|
-
newObj = load(context.file);
|
|
26
|
-
}
|
|
27
|
-
if (context.content) {
|
|
28
|
-
newObj = yaml.load(context.content);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
this.obj = mergeWith(this.obj, newObj, (objValue, srcValue) => {
|
|
32
|
-
if (Array.isArray(objValue)) {
|
|
33
|
-
return objValue.concat(srcValue);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
return this;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// to: optional
|
|
40
|
-
save(to) {
|
|
41
|
-
fs.writeFileSync(
|
|
42
|
-
to || this.basefile,
|
|
43
|
-
yaml.dump(this.obj, {
|
|
44
|
-
styles: {
|
|
45
|
-
"!!null": "empty", // dump null as ""
|
|
46
|
-
},
|
|
47
|
-
})
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
package/lib/fetch.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import nodeFetch from "node-fetch";
|
|
2
|
-
import makeFetchCookie from "fetch-cookie";
|
|
3
|
-
import { FileCookieStore } from "tough-cookie-file-store";
|
|
4
|
-
import inquirer from "inquirer";
|
|
5
|
-
import env from "./env.js";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { GLOBAL_CONFIG_DIR } from "./utils.js";
|
|
8
|
-
|
|
9
|
-
const cookieFetch = makeFetchCookie(
|
|
10
|
-
nodeFetch,
|
|
11
|
-
new makeFetchCookie.toughCookie.CookieJar(
|
|
12
|
-
new FileCookieStore(path.resolve(GLOBAL_CONFIG_DIR, "cookie.json"))
|
|
13
|
-
)
|
|
14
|
-
);
|
|
15
|
-
const fetch = async (url, options) => {
|
|
16
|
-
const resp = await cookieFetch(url, options);
|
|
17
|
-
if (resp.status == 401) {
|
|
18
|
-
await login();
|
|
19
|
-
}
|
|
20
|
-
return resp;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export async function login() {
|
|
24
|
-
const boxname = env.get("DEFAULT_BOXNAME");
|
|
25
|
-
const boxUrl = `https://${boxname}.heiyu.space`;
|
|
26
|
-
|
|
27
|
-
const questions = [
|
|
28
|
-
{
|
|
29
|
-
name: "username",
|
|
30
|
-
type: "input",
|
|
31
|
-
message: "请输入帐号名",
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
type: "password",
|
|
35
|
-
mask: "*",
|
|
36
|
-
name: "password",
|
|
37
|
-
message: "请输入登录密码",
|
|
38
|
-
},
|
|
39
|
-
];
|
|
40
|
-
const answers = await inquirer.prompt(questions);
|
|
41
|
-
await fetch(`${boxUrl}/sys/login?type=plain`, {
|
|
42
|
-
headers: {
|
|
43
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
44
|
-
},
|
|
45
|
-
body: `username=${answers.username}&password=${answers.password}`,
|
|
46
|
-
method: "POST",
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default fetch;
|
package/lib/git/git-commit.sh
DELETED
package/lib/git/git-reset.sh
DELETED
package/lib/key.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import inquirer from "inquirer";
|
|
2
|
-
import glob from "fast-glob";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { Client } from "ssh2";
|
|
5
|
-
import process from "process";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import execa from "execa";
|
|
8
|
-
import API from "./api.js";
|
|
9
|
-
import { ensureDir } from "./utils.js";
|
|
10
|
-
import os from "os";
|
|
11
|
-
|
|
12
|
-
const KEY_NAME = "id_ed25519";
|
|
13
|
-
const SSH_CONFIG_DIR = path.join(os.homedir(), ".ssh");
|
|
14
|
-
|
|
15
|
-
export default class Key {
|
|
16
|
-
// 确保该秘钥可以工作
|
|
17
|
-
async ensure(host) {
|
|
18
|
-
const pairs = await this.getKeyPair();
|
|
19
|
-
await this.validationCheck(host, pairs);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async generate() {
|
|
23
|
-
const privateKeyFile = path.join(SSH_CONFIG_DIR, KEY_NAME);
|
|
24
|
-
ensureDir(privateKeyFile);
|
|
25
|
-
await execa(
|
|
26
|
-
"ssh-keygen",
|
|
27
|
-
["-t", "ed25519", "-P", "", "-f", privateKeyFile],
|
|
28
|
-
{
|
|
29
|
-
stdio: "inherit",
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
return {
|
|
33
|
-
private: privateKeyFile,
|
|
34
|
-
public: privateKeyFile + ".pub",
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async validationCheck(host, pair) {
|
|
39
|
-
const hostname = new URL(host).hostname;
|
|
40
|
-
const client = new Client();
|
|
41
|
-
return new Promise((resolve, reject) => {
|
|
42
|
-
client
|
|
43
|
-
.on("ready", () => {
|
|
44
|
-
resolve(true);
|
|
45
|
-
client.end();
|
|
46
|
-
})
|
|
47
|
-
.on("error", (err) => {
|
|
48
|
-
if (err.level == "client-authentication") {
|
|
49
|
-
inquirer
|
|
50
|
-
.prompt([
|
|
51
|
-
{
|
|
52
|
-
name: "key",
|
|
53
|
-
type: "confirm",
|
|
54
|
-
message: "发布应用需要上传公钥, 确定上传?",
|
|
55
|
-
},
|
|
56
|
-
])
|
|
57
|
-
.then(({ key }) => {
|
|
58
|
-
if (key) {
|
|
59
|
-
const api = new API("", host);
|
|
60
|
-
api
|
|
61
|
-
.postPublicKey(fs.readFileSync(pair["public"]))
|
|
62
|
-
.then(() => {
|
|
63
|
-
resolve(true);
|
|
64
|
-
});
|
|
65
|
-
} else {
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
} else {
|
|
70
|
-
reject(err);
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
.connect({
|
|
74
|
-
host: hostname,
|
|
75
|
-
port: 2222,
|
|
76
|
-
username: "box",
|
|
77
|
-
retry: 3,
|
|
78
|
-
wait: 500,
|
|
79
|
-
authHandler: [
|
|
80
|
-
{
|
|
81
|
-
type: "publickey",
|
|
82
|
-
username: "box",
|
|
83
|
-
key: fs.readFileSync(pair["private"]),
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
keepaliveInterval: 60000, // 60s
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async getKeyPair() {
|
|
92
|
-
const privateKey = path.join(SSH_CONFIG_DIR, KEY_NAME);
|
|
93
|
-
const publicKey = path.join(SSH_CONFIG_DIR, `${KEY_NAME}.pub`);
|
|
94
|
-
if (fs.existsSync(privateKey) && fs.existsSync(publicKey)) {
|
|
95
|
-
return {
|
|
96
|
-
private: privateKey,
|
|
97
|
-
public: publicKey,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
return await this.generate();
|
|
101
|
-
}
|
|
102
|
-
}
|
package/lib/sdk.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import ssh from "docker-modem/lib/ssh.js";
|
|
2
|
-
// import Docker from "./docker/promise.js";
|
|
3
|
-
import Docker from "dockerode";
|
|
4
|
-
import process from "process";
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import { Client } from "ssh2";
|
|
7
|
-
import Key from "./key.js";
|
|
8
|
-
import logger from "loglevel";
|
|
9
|
-
|
|
10
|
-
const sshDebug = function () {
|
|
11
|
-
if (process.env["SSH2_DEBUG"]) {
|
|
12
|
-
return logger.debug;
|
|
13
|
-
} else {
|
|
14
|
-
return function () {};
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export async function connectOptions(host) {
|
|
19
|
-
const pairs = await new Key().getKeyPair();
|
|
20
|
-
return {
|
|
21
|
-
host: host,
|
|
22
|
-
port: 2222,
|
|
23
|
-
username: "box",
|
|
24
|
-
retry: 3,
|
|
25
|
-
wait: 500,
|
|
26
|
-
authHandler: [
|
|
27
|
-
{
|
|
28
|
-
type: "publickey",
|
|
29
|
-
username: "box",
|
|
30
|
-
key: fs.readFileSync(pairs["private"]),
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
type: "agent",
|
|
34
|
-
username: "box",
|
|
35
|
-
agent: process.env.SSH_AUTH_SOCK,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
keepaliveInterval: 60000, // 60s
|
|
39
|
-
debug: sshDebug(),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class DockerClient {
|
|
44
|
-
constructor(host) {
|
|
45
|
-
this.host = host;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async init() {
|
|
49
|
-
this.docker = !!this.host
|
|
50
|
-
? new Docker({
|
|
51
|
-
protocal: "ssh",
|
|
52
|
-
agent: ssh(await connectOptions(this.host)),
|
|
53
|
-
})
|
|
54
|
-
: new Docker();
|
|
55
|
-
|
|
56
|
-
const host = this.host;
|
|
57
|
-
|
|
58
|
-
async function onUncaughtException(e) {
|
|
59
|
-
if (e.code == "ECONNREFUSED") {
|
|
60
|
-
logger.error(`无法连接 sdk 服务, 请确保 ${host} 可以访问`);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
} else {
|
|
63
|
-
throw e;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
process.on("uncaughtException", onUncaughtException);
|
|
67
|
-
await this.docker.ping();
|
|
68
|
-
process.off("uncaughtException", onUncaughtException);
|
|
69
|
-
return this.docker;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export class SSHClient {
|
|
74
|
-
// arg can be a simple host name or a ssh connectOptions
|
|
75
|
-
constructor(arg) {
|
|
76
|
-
if (typeof arg == "string") {
|
|
77
|
-
this.opts = connectOptions(arg);
|
|
78
|
-
} else {
|
|
79
|
-
this.opts = arg;
|
|
80
|
-
}
|
|
81
|
-
this.con = new Client();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
connect() {
|
|
85
|
-
return new Promise((resolve, reject) => {
|
|
86
|
-
this.con
|
|
87
|
-
.on("ready", () => resolve(this.con))
|
|
88
|
-
.on("error", (err) => {
|
|
89
|
-
logger.error("ssh client ", err);
|
|
90
|
-
reject(err);
|
|
91
|
-
})
|
|
92
|
-
.on("close", () => resolve(null))
|
|
93
|
-
.connect(this.opts);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
close() {
|
|
98
|
-
this.con.end();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
exec(command, options = {}) {
|
|
102
|
-
return new Promise((resolve, reject) => {
|
|
103
|
-
this.con.exec(command, options, (err, chan) => {
|
|
104
|
-
if (err) return reject(err);
|
|
105
|
-
resolve(chan);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async shell() {
|
|
111
|
-
this.con.shell(
|
|
112
|
-
{
|
|
113
|
-
term: process.env.TERM,
|
|
114
|
-
rows: process.stdout.rows,
|
|
115
|
-
cols: process.stdout.columns,
|
|
116
|
-
},
|
|
117
|
-
(err, stream) => {
|
|
118
|
-
if (err) throw err;
|
|
119
|
-
|
|
120
|
-
stream.on("close", () => {
|
|
121
|
-
process.exit();
|
|
122
|
-
});
|
|
123
|
-
process.stdin.setRawMode(true);
|
|
124
|
-
process.stdin.pipe(stream);
|
|
125
|
-
|
|
126
|
-
// Connect remote output to local stdout
|
|
127
|
-
stream.pipe(process.stdout);
|
|
128
|
-
process.stdout.on("resize", () => {
|
|
129
|
-
// Let the remote end know when the local terminal has been resized
|
|
130
|
-
stream.setWindow(process.stdout.rows, process.stdout.columns, 0, 0);
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
}
|