openilink-app-runner 0.1.0 → 0.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.
@@ -6,6 +6,7 @@ const config_1 = require("../src/config");
6
6
  const sync_1 = require("../src/sync");
7
7
  const hub_1 = require("../src/hub");
8
8
  const handler_1 = require("../src/handler");
9
+ const daemon_1 = require("../src/daemon");
9
10
  const program = new commander_1.Command();
10
11
  program
11
12
  .name("openilink-app-runner")
@@ -131,6 +132,19 @@ program
131
132
  });
132
133
  hub.connect();
133
134
  });
135
+ program
136
+ .command("install")
137
+ .description("注册为系统服务(systemd / launchd),开机自启")
138
+ .option("-c, --config <path>", "配置文件路径", "runner.yaml")
139
+ .action((opts) => {
140
+ (0, daemon_1.install)((0, config_1.getConfigPath)(opts.config));
141
+ });
142
+ program
143
+ .command("uninstall")
144
+ .description("卸载系统服务")
145
+ .action(() => {
146
+ (0, daemon_1.uninstall)();
147
+ });
134
148
  // Default: show help
135
149
  if (process.argv.length <= 2) {
136
150
  program.help();
@@ -38,10 +38,28 @@ exports.loadConfig = loadConfig;
38
38
  exports.saveConfig = saveConfig;
39
39
  exports.initConfig = initConfig;
40
40
  const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
41
43
  const yaml = __importStar(require("js-yaml"));
42
- const DEFAULT_PATH = "runner.yaml";
44
+ function defaultConfigDir() {
45
+ const platform = os.platform();
46
+ const home = os.homedir();
47
+ if (process.getuid?.() === 0) {
48
+ return "/etc/openilink-app-runner";
49
+ }
50
+ if (platform === "darwin") {
51
+ return path.join(home, "Library/Application Support/openilink-app-runner");
52
+ }
53
+ return path.join(home, ".config/openilink-app-runner");
54
+ }
43
55
  function getConfigPath(custom) {
44
- return custom || DEFAULT_PATH;
56
+ if (custom)
57
+ return custom;
58
+ // Check current directory first
59
+ if (fs.existsSync("runner.yaml"))
60
+ return "runner.yaml";
61
+ // Then standard directory
62
+ return path.join(defaultConfigDir(), "runner.yaml");
45
63
  }
46
64
  function loadConfig(configPath) {
47
65
  if (!fs.existsSync(configPath)) {
@@ -60,6 +78,10 @@ function loadConfig(configPath) {
60
78
  return config;
61
79
  }
62
80
  function saveConfig(configPath, config) {
81
+ const dir = path.dirname(configPath);
82
+ if (!fs.existsSync(dir)) {
83
+ fs.mkdirSync(dir, { recursive: true });
84
+ }
63
85
  const raw = yaml.dump(config, { lineWidth: -1, noRefs: true });
64
86
  fs.writeFileSync(configPath, raw, "utf-8");
65
87
  }
@@ -0,0 +1,2 @@
1
+ export declare function install(configPath: string): void;
2
+ export declare function uninstall(): void;
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.install = install;
37
+ exports.uninstall = uninstall;
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const child_process_1 = require("child_process");
42
+ const SERVICE_NAME = "openilink-app-runner";
43
+ function getBinPath() {
44
+ try {
45
+ return (0, child_process_1.execSync)("which openilink-app-runner", { encoding: "utf-8" }).trim();
46
+ }
47
+ catch {
48
+ // Fallback: resolve from node_modules
49
+ return path.resolve(__dirname, "../bin/runner.js");
50
+ }
51
+ }
52
+ function getNodePath() {
53
+ return process.execPath;
54
+ }
55
+ // ========== Linux (systemd) ==========
56
+ function getRealUser() {
57
+ const sudoUser = process.env.SUDO_USER;
58
+ if (sudoUser) {
59
+ try {
60
+ const home = (0, child_process_1.execSync)(`eval echo ~${sudoUser}`, { encoding: "utf-8", shell: "/bin/sh" }).trim();
61
+ return { name: sudoUser, home };
62
+ }
63
+ catch { }
64
+ }
65
+ return { name: os.userInfo().username, home: os.homedir() };
66
+ }
67
+ function systemdUnit(configPath) {
68
+ const absConfig = path.resolve(configPath);
69
+ const binPath = getBinPath();
70
+ const workDir = path.dirname(absConfig);
71
+ const user = getRealUser();
72
+ return `[Unit]
73
+ Description=OpeniLink App Runner
74
+ After=network.target
75
+
76
+ [Service]
77
+ Type=simple
78
+ User=${user.name}
79
+ ExecStart=${binPath} start --config ${absConfig}
80
+ WorkingDirectory=${workDir}
81
+ Restart=always
82
+ RestartSec=5
83
+ Environment=NODE_ENV=production
84
+
85
+ [Install]
86
+ WantedBy=multi-user.target
87
+ `;
88
+ }
89
+ function installSystemd(configPath) {
90
+ const unitPath = `/etc/systemd/system/${SERVICE_NAME}.service`;
91
+ const unit = systemdUnit(configPath);
92
+ fs.writeFileSync(unitPath, unit);
93
+ (0, child_process_1.execSync)("systemctl daemon-reload");
94
+ (0, child_process_1.execSync)(`systemctl enable ${SERVICE_NAME}`);
95
+ (0, child_process_1.execSync)(`systemctl start ${SERVICE_NAME}`);
96
+ console.log(`✓ 已安装 systemd 服务`);
97
+ console.log(` 服务文件: ${unitPath}`);
98
+ console.log(` 配置文件: ${path.resolve(configPath)}`);
99
+ console.log(` 查看状态: systemctl status ${SERVICE_NAME}`);
100
+ console.log(` 查看日志: journalctl -u ${SERVICE_NAME} -f`);
101
+ }
102
+ function uninstallSystemd() {
103
+ try {
104
+ (0, child_process_1.execSync)(`systemctl stop ${SERVICE_NAME}`, { stdio: "ignore" });
105
+ }
106
+ catch { }
107
+ try {
108
+ (0, child_process_1.execSync)(`systemctl disable ${SERVICE_NAME}`, { stdio: "ignore" });
109
+ }
110
+ catch { }
111
+ const unitPath = `/etc/systemd/system/${SERVICE_NAME}.service`;
112
+ if (fs.existsSync(unitPath)) {
113
+ fs.unlinkSync(unitPath);
114
+ (0, child_process_1.execSync)("systemctl daemon-reload");
115
+ }
116
+ console.log(`✓ 已卸载 systemd 服务`);
117
+ }
118
+ // ========== macOS (launchd) ==========
119
+ function launchdPlist(configPath) {
120
+ const absConfig = path.resolve(configPath);
121
+ const binPath = getBinPath();
122
+ const logDir = path.join(os.homedir(), "Library/Logs");
123
+ return `<?xml version="1.0" encoding="UTF-8"?>
124
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
125
+ <plist version="1.0">
126
+ <dict>
127
+ <key>Label</key>
128
+ <string>com.openilink.app-runner</string>
129
+ <key>ProgramArguments</key>
130
+ <array>
131
+ <string>${binPath}</string>
132
+ <string>start</string>
133
+ <string>--config</string>
134
+ <string>${absConfig}</string>
135
+ </array>
136
+ <key>RunAtLoad</key>
137
+ <true/>
138
+ <key>KeepAlive</key>
139
+ <true/>
140
+ <key>StandardOutPath</key>
141
+ <string>${logDir}/openilink-app-runner.log</string>
142
+ <key>StandardErrorPath</key>
143
+ <string>${logDir}/openilink-app-runner.err</string>
144
+ </dict>
145
+ </plist>
146
+ `;
147
+ }
148
+ function installLaunchd(configPath) {
149
+ const plistPath = path.join(os.homedir(), `Library/LaunchAgents/com.openilink.app-runner.plist`);
150
+ const plist = launchdPlist(configPath);
151
+ fs.mkdirSync(path.dirname(plistPath), { recursive: true });
152
+ fs.writeFileSync(plistPath, plist);
153
+ try {
154
+ (0, child_process_1.execSync)(`launchctl unload ${plistPath}`, { stdio: "ignore" });
155
+ }
156
+ catch { }
157
+ (0, child_process_1.execSync)(`launchctl load ${plistPath}`);
158
+ console.log(`✓ 已安装 launchd 服务`);
159
+ console.log(` 配置文件: ${plistPath}`);
160
+ console.log(` 日志: ~/Library/Logs/openilink-app-runner.log`);
161
+ }
162
+ function uninstallLaunchd() {
163
+ const plistPath = path.join(os.homedir(), `Library/LaunchAgents/com.openilink.app-runner.plist`);
164
+ try {
165
+ (0, child_process_1.execSync)(`launchctl unload ${plistPath}`, { stdio: "ignore" });
166
+ }
167
+ catch { }
168
+ if (fs.existsSync(plistPath)) {
169
+ fs.unlinkSync(plistPath);
170
+ }
171
+ console.log(`✓ 已卸载 launchd 服务`);
172
+ }
173
+ // ========== Public API ==========
174
+ function install(configPath) {
175
+ let absConfig = path.resolve(configPath);
176
+ // If running under sudo and config not found, try the real user's config path
177
+ if (!fs.existsSync(absConfig) && process.env.SUDO_USER) {
178
+ const user = getRealUser();
179
+ const userConfig = path.join(user.home, ".config/openilink-app-runner/runner.yaml");
180
+ if (fs.existsSync(userConfig)) {
181
+ absConfig = userConfig;
182
+ }
183
+ }
184
+ if (!fs.existsSync(absConfig)) {
185
+ console.error(`配置文件不存在: ${absConfig}`);
186
+ console.error(`请先运行: openilink-app-runner init --hub-url <url> --token <token>`);
187
+ process.exit(1);
188
+ }
189
+ if (os.platform() === "darwin") {
190
+ installLaunchd(configPath);
191
+ }
192
+ else if (os.platform() === "linux") {
193
+ installSystemd(configPath);
194
+ }
195
+ else {
196
+ console.error(`不支持的平台: ${os.platform()}`);
197
+ console.error(`请手动运行: openilink-app-runner start --config ${absConfig}`);
198
+ process.exit(1);
199
+ }
200
+ }
201
+ function uninstall() {
202
+ if (os.platform() === "darwin") {
203
+ uninstallLaunchd();
204
+ }
205
+ else if (os.platform() === "linux") {
206
+ uninstallSystemd();
207
+ }
208
+ else {
209
+ console.error(`不支持的平台: ${os.platform()}`);
210
+ process.exit(1);
211
+ }
212
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openilink-app-runner",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Run local commands as OpeniLink Hub App tools — bridge CLI to WeChat",
5
5
  "bin": {
6
6
  "openilink-app-runner": "dist/bin/runner.js"