@sksoftofficial/ocduet 0.2.1 → 0.2.2
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/package.json +1 -1
- package/src/commands/install.js +75 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sksoftofficial/ocduet",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Duet for opencode: pair your phone with a local opencode TUI. Type on mobile, see it live on both screens. LAN-first, QR pairing, zero config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/install.js
CHANGED
|
@@ -88,11 +88,40 @@ function unitFlag(argv, name) {
|
|
|
88
88
|
return i !== -1 && argv[i + 1] ? argv[i + 1] : null;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
const SYS_UNIT_PATH = "/etc/systemd/system/ocduet-relayer.service";
|
|
92
|
+
|
|
93
|
+
function shellQuote(a) {
|
|
94
|
+
return /^[\w\/.:=-]+$/.test(a) ? a : JSON.stringify(a);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function unitText(entry, root, dir, serveArgs, system) {
|
|
98
|
+
return [
|
|
99
|
+
"[Unit]",
|
|
100
|
+
"Description=ocduet relayer — public tunnel for the ocduet daemon",
|
|
101
|
+
"After=network-online.target",
|
|
102
|
+
"Wants=network-online.target",
|
|
103
|
+
"",
|
|
104
|
+
"[Service]",
|
|
105
|
+
`WorkingDirectory=${root}`,
|
|
106
|
+
`ExecStart=${[process.execPath, ...serveArgs].map(shellQuote).join(" ")}`,
|
|
107
|
+
`Environment=OCDUET_RELAYER_DIR=${dir}`,
|
|
108
|
+
"Restart=always",
|
|
109
|
+
"RestartSec=3",
|
|
110
|
+
"",
|
|
111
|
+
"[Install]",
|
|
112
|
+
`WantedBy=${system ? "multi-user.target" : "default.target"}`,
|
|
113
|
+
"",
|
|
114
|
+
].join("\n");
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
// `ocduet install relayer` — set the relayer up as a boot-persistent service
|
|
92
|
-
// on a public box.
|
|
93
|
-
//
|
|
118
|
+
// on a public box, no manual steps. Prefers a system systemd unit (the usual
|
|
119
|
+
// VPS case: root over ssh, or passwordless sudo), then a systemd user unit
|
|
120
|
+
// (enable-linger keeps it alive without a login session), and finally a
|
|
121
|
+
// detached process + pid file.
|
|
94
122
|
export async function installRelayer(argv = []) {
|
|
95
123
|
const root = pkgRoot();
|
|
124
|
+
const entry = path.join(root, "bin/ocduet.js");
|
|
96
125
|
const dir = relayerDir();
|
|
97
126
|
const port = unitFlag(argv, "--port") || process.env.OCDUET_RELAYER_PORT || "4290";
|
|
98
127
|
const cert = unitFlag(argv, "--cert");
|
|
@@ -100,44 +129,55 @@ export async function installRelayer(argv = []) {
|
|
|
100
129
|
fs.mkdirSync(dir, { recursive: true });
|
|
101
130
|
|
|
102
131
|
// make sure the store (and the enroll secret) exists before we print it
|
|
103
|
-
const secret = spawnSync(process.execPath, [
|
|
132
|
+
const secret = spawnSync(process.execPath, [entry, "relay", "secret"], { encoding: "utf8", env: { ...process.env, OCDUET_RELAYER_DIR: dir } });
|
|
104
133
|
const enroll = (secret.stdout || "").trim() || "(unknown — run `ocduet relayer secret`)";
|
|
105
134
|
|
|
106
|
-
const serveArgs = [
|
|
135
|
+
const serveArgs = [entry, "relay", "serve", "--port", port];
|
|
107
136
|
if (cert && key) serveArgs.push("--cert", cert, "--key", key);
|
|
108
137
|
|
|
138
|
+
const hasSystemd = fs.existsSync("/run/systemd/system");
|
|
139
|
+
const euid = typeof process.geteuid === "function" ? process.geteuid() : -1;
|
|
140
|
+
const canSudo = () => spawnSync("sudo", ["-n", "true"]).status === 0;
|
|
141
|
+
|
|
109
142
|
let managedBy = null;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"[
|
|
116
|
-
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"RestartSec=3",
|
|
126
|
-
"",
|
|
127
|
-
"[Install]",
|
|
128
|
-
"WantedBy=default.target",
|
|
129
|
-
"",
|
|
130
|
-
].join("\n"));
|
|
131
|
-
const ctl = spawnSync("systemctl", ["--user", "daemon-reload"], { encoding: "utf8" });
|
|
132
|
-
if (ctl.status === 0) {
|
|
133
|
-
spawnSync("systemctl", ["--user", "enable", "--now", "ocduet-relayer.service"], { encoding: "utf8" });
|
|
134
|
-
managedBy = "systemd --user";
|
|
143
|
+
if (hasSystemd && (euid === 0 || canSudo())) {
|
|
144
|
+
// root, or passwordless sudo: real system unit — starts now, survives reboots
|
|
145
|
+
const unit = unitText(entry, root, dir, serveArgs, true);
|
|
146
|
+
const wrote = euid === 0
|
|
147
|
+
? (() => { try { fs.writeFileSync(SYS_UNIT_PATH, unit); return true; } catch { return false; } })()
|
|
148
|
+
: spawnSync("sudo", ["-n", "tee", SYS_UNIT_PATH], { input: unit, encoding: "utf8" }).status === 0;
|
|
149
|
+
const sys = (args) => euid === 0
|
|
150
|
+
? spawnSync("systemctl", args, { encoding: "utf8" })
|
|
151
|
+
: spawnSync("sudo", ["-n", "systemctl", ...args], { encoding: "utf8" });
|
|
152
|
+
if (wrote && sys(["daemon-reload"]).status === 0) {
|
|
153
|
+
sys(["enable", "--now", "ocduet-relayer.service"]);
|
|
154
|
+
sys(["restart", "ocduet-relayer.service"]); // pick up unit changes on re-install
|
|
155
|
+
if (sys(["is-active", "--quiet", "ocduet-relayer.service"]).status === 0) {
|
|
156
|
+
managedBy = `systemd system unit (${SYS_UNIT_PATH})`;
|
|
157
|
+
}
|
|
135
158
|
}
|
|
136
|
-
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!managedBy) {
|
|
162
|
+
const unitDir = path.join(os.homedir(), ".config/systemd/user");
|
|
163
|
+
const unitPath = path.join(unitDir, "ocduet-relayer.service");
|
|
164
|
+
try {
|
|
165
|
+
fs.mkdirSync(unitDir, { recursive: true });
|
|
166
|
+
fs.writeFileSync(unitPath, unitText(entry, root, dir, serveArgs, false));
|
|
167
|
+
const ctl = spawnSync("systemctl", ["--user", "daemon-reload"], { encoding: "utf8" });
|
|
168
|
+
if (ctl.status === 0) {
|
|
169
|
+
spawnSync("systemctl", ["--user", "enable", "--now", "ocduet-relayer.service"], { encoding: "utf8" });
|
|
170
|
+
spawnSync("systemctl", ["--user", "restart", "ocduet-relayer.service"], { encoding: "utf8" });
|
|
171
|
+
if (spawnSync("systemctl", ["--user", "is-active", "--quiet", "ocduet-relayer.service"], { encoding: "utf8" }).status === 0) {
|
|
172
|
+
managedBy = "systemd --user";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch {}
|
|
176
|
+
}
|
|
137
177
|
|
|
138
178
|
if (!managedBy) {
|
|
139
179
|
const log = fs.openSync(path.join(dir, "relayer.log"), "a", 0o600);
|
|
140
|
-
const child = spawn(process.execPath,
|
|
180
|
+
const child = spawn(process.execPath, serveArgs, {
|
|
141
181
|
env: { ...process.env, OCDUET_RELAYER_DIR: dir },
|
|
142
182
|
detached: true,
|
|
143
183
|
stdio: ["ignore", log, log],
|
|
@@ -161,7 +201,10 @@ export async function installRelayer(argv = []) {
|
|
|
161
201
|
console.log("on your DESKTOP, link it:");
|
|
162
202
|
console.log(` ocduet relay link https://${host}:${port} ${enroll}`);
|
|
163
203
|
if (ips.length > 1) console.log(` (this box has multiple IPs: ${ips.join(", ")} — use the public one)`);
|
|
164
|
-
if (managedBy.startsWith("systemd")) {
|
|
204
|
+
if (managedBy.startsWith("systemd system")) {
|
|
205
|
+
console.log("");
|
|
206
|
+
console.log("logs: journalctl -u ocduet-relayer -f");
|
|
207
|
+
} else if (managedBy === "systemd --user") {
|
|
165
208
|
console.log("");
|
|
166
209
|
console.log("to survive reboots without a login session, run once (may need root):");
|
|
167
210
|
console.log(` sudo loginctl enable-linger $USER`);
|