mop-agent 0.1.0 → 0.1.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 +24 -6
- package/apps/web/.env.example +2 -0
- package/apps/web/lib/memory/local-embedder.ts +3 -0
- package/installer/bootstrap.mjs +9 -5
- package/installer/mop-agent.mjs +60 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@ through MOP-FLOW. It stores project memory, performs semantic recall and
|
|
|
5
5
|
consolidation, serves grounded chat, and can request approved actions from a
|
|
6
6
|
linked FLOW node.
|
|
7
7
|
|
|
8
|
-
> **Release status:** npm package `mop-agent@0.1.
|
|
9
|
-
>
|
|
8
|
+
> **Release status:** npm package `mop-agent@0.1.1` contains the root/VPS
|
|
9
|
+
> installer fix. After publishing 0.1.1, the canonical installation command is
|
|
10
10
|
> exactly `npx mop-agent`.
|
|
11
11
|
|
|
12
12
|
## Current status
|
|
@@ -44,7 +44,7 @@ Prerequisites:
|
|
|
44
44
|
- A domain with an `A`/`AAAA` record pointing to the server
|
|
45
45
|
- Inbound ports 80 and 443 allowed by the firewall/security group
|
|
46
46
|
|
|
47
|
-
Run as your normal user:
|
|
47
|
+
Run as either your normal sudo user or directly as root on a VPS:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
npx mop-agent
|
|
@@ -56,9 +56,26 @@ The first run copies the npm-packaged runtime from the temporary npx cache into
|
|
|
56
56
|
SQLite database, HTTPS, and systemd service. The menu remains open between
|
|
57
57
|
steps.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
During `setup`, choose one deployment mode:
|
|
60
|
+
|
|
61
|
+
- `public` — enter a public domain and optionally obtain a Let's Encrypt HTTPS
|
|
62
|
+
certificate. Use this for an internet-facing server with public IP/DNS.
|
|
63
|
+
- `local` — use a LAN hostname such as `mop-agent.local`; the installer uses
|
|
64
|
+
HTTP and does not invoke Certbot.
|
|
65
|
+
|
|
66
|
+
For a LAN-only test, map the selected hostname to the server IP in your router
|
|
67
|
+
DNS or client `/etc/hosts`. Let's Encrypt public mode requires a real public
|
|
68
|
+
domain and reachable ports 80/443.
|
|
69
|
+
|
|
70
|
+
When launched by a normal user, the installer requests `sudo` only when it
|
|
71
|
+
needs to write under `/opt` or `/etc`, install OS packages, or control
|
|
72
|
+
nginx/systemd. When launched as root, it creates a locked-down `mop-agent`
|
|
73
|
+
system account and runs the web service under that account—not as root.
|
|
74
|
+
|
|
75
|
+
During the `install` step, MOP-AGENT checks the installed npm version. If a
|
|
76
|
+
newer npm is available it displays the version and Node.js requirement, then
|
|
77
|
+
asks before running the global npm update. Set `MOP_AGENT_SKIP_NPM_UPDATE=1` to
|
|
78
|
+
skip this check.
|
|
62
79
|
|
|
63
80
|
Subsequent operations use the same command:
|
|
64
81
|
|
|
@@ -83,6 +100,7 @@ so it uses `/opt/mop-agent` rather than `/var/www` for application code.
|
|
|
83
100
|
| systemd unit | `/etc/systemd/system/mop-agent.service` | same |
|
|
84
101
|
| TLS certificates | `/etc/letsencrypt/live/<domain>/` | same |
|
|
85
102
|
| Service logs | `journalctl -u mop-agent -f` | same |
|
|
103
|
+
| Root-install service account | `mop-agent` | `mop-agent` |
|
|
86
104
|
|
|
87
105
|
`MOP_AGENT_DIR` can override `/opt/mop-agent`. Updates preserve
|
|
88
106
|
`apps/web/.env` and `data/`; uninstall preserves SQLite brain data unless the
|
package/apps/web/.env.example
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# MOP-AGENT web — copy to .env and fill in.
|
|
2
2
|
PORT=3000
|
|
3
|
+
MOP_AGENT_DEPLOY_MODE=public # public | local
|
|
3
4
|
|
|
4
5
|
# --- Fasa 2 (auth + db + secrets) ---
|
|
5
6
|
# BETTER_AUTH_SECRET=
|
|
6
7
|
# BETTER_AUTH_URL=http://localhost:3000
|
|
7
8
|
# MOP_AGENT_SECRET= # AES-GCM key for provider keys at rest (32 bytes hex)
|
|
8
9
|
# MOP_AGENT_DATA_DIR= # defaults to OS data dir if unset
|
|
10
|
+
# MOP_AGENT_MODEL_CACHE= # local MiniLM model cache; installer puts it under data/models
|
|
9
11
|
|
|
10
12
|
# --- providers (Fasa 2/3) ---
|
|
11
13
|
# ANTHROPIC_API_KEY=
|
|
@@ -15,6 +15,9 @@ export function localEmbedder(model = "Xenova/all-MiniLM-L6-v2"): Embedder {
|
|
|
15
15
|
_extractor = (async () => {
|
|
16
16
|
const t = await import("@xenova/transformers");
|
|
17
17
|
t.env.allowLocalModels = false;
|
|
18
|
+
if (process.env.MOP_AGENT_MODEL_CACHE) {
|
|
19
|
+
t.env.cacheDir = process.env.MOP_AGENT_MODEL_CACHE;
|
|
20
|
+
}
|
|
18
21
|
return t.pipeline("feature-extraction", model);
|
|
19
22
|
})();
|
|
20
23
|
}
|
package/installer/bootstrap.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
constants,
|
|
13
13
|
cpSync,
|
|
14
14
|
existsSync,
|
|
15
|
+
mkdirSync,
|
|
15
16
|
readFileSync,
|
|
16
17
|
writeFileSync,
|
|
17
18
|
} from "node:fs";
|
|
@@ -53,9 +54,10 @@ Usage:
|
|
|
53
54
|
Environment:
|
|
54
55
|
MOP_AGENT_DIR Durable app directory (default: ${DEFAULT_DIR})
|
|
55
56
|
|
|
56
|
-
Run this command as
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
Run this command as either a normal sudo user or root. When launched by a normal
|
|
58
|
+
user, MOP-AGENT asks for sudo only when an OS operation requires it. The web
|
|
59
|
+
service itself never runs as root. Native Windows/macOS production installation
|
|
60
|
+
is not yet supported; use WSL2 Ubuntu on Windows or a Linux host.
|
|
59
61
|
`);
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -86,7 +88,10 @@ function writable(path) {
|
|
|
86
88
|
|
|
87
89
|
function ensureDestination() {
|
|
88
90
|
if (existsSync(APP_DIR) && writable(APP_DIR)) return;
|
|
89
|
-
if (isRoot)
|
|
91
|
+
if (isRoot) {
|
|
92
|
+
mkdirSync(APP_DIR, { recursive: true });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
90
95
|
const uid = String(process.getuid());
|
|
91
96
|
const gid = String(process.getgid());
|
|
92
97
|
console.log(`\n▸ Preparing ${APP_DIR} (sudo is required once for this system directory)`);
|
|
@@ -151,7 +156,6 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
151
156
|
} else {
|
|
152
157
|
assertPlatform();
|
|
153
158
|
assertSafeDestination();
|
|
154
|
-
if (isRoot) fail("Do not run `npx mop-agent` with sudo/root. Run it as your normal user.");
|
|
155
159
|
ensureDestination();
|
|
156
160
|
deployPackage();
|
|
157
161
|
run(process.execPath, [resolve(APP_DIR, "installer/mop-agent.mjs"), ...argv], {
|
package/installer/mop-agent.mjs
CHANGED
|
@@ -105,10 +105,11 @@ function q(value) {
|
|
|
105
105
|
|
|
106
106
|
// ---- commands ----------------------------------------------------------
|
|
107
107
|
|
|
108
|
-
function cmdInstall() {
|
|
108
|
+
async function cmdInstall() {
|
|
109
109
|
banner();
|
|
110
110
|
const os = detectOS();
|
|
111
111
|
if (!printInstallLocations(os)) return;
|
|
112
|
+
await maybeUpdateNpm();
|
|
112
113
|
console.log(c("bold", "Installing system dependencies (nginx and Certbot)…\n"));
|
|
113
114
|
runSteps(planInstallDeps(os), { privileged: true });
|
|
114
115
|
console.log(c("green", "\n✓ dependencies step complete. Next: mop-agent setup\n"));
|
|
@@ -121,10 +122,22 @@ async function cmdSetup() {
|
|
|
121
122
|
const rl = createInterface({ input, output });
|
|
122
123
|
const ask = async (q, def) => (await rl.question(c("cyan", ` ${q}${def ? c("gray", ` [${def}]`) : ""}: `))).trim() || def || "";
|
|
123
124
|
|
|
124
|
-
const
|
|
125
|
+
const deployMode = (await ask("Deployment mode (public/local)", "public")).toLowerCase();
|
|
126
|
+
if (!new Set(["public", "local"]).has(deployMode)) {
|
|
127
|
+
rl.close();
|
|
128
|
+
throw new Error(`Invalid deployment mode: ${deployMode}. Use public or local.`);
|
|
129
|
+
}
|
|
130
|
+
const domain = await ask(
|
|
131
|
+
deployMode === "public" ? "Public domain (e.g. agent.mydomain.com)" : "LAN hostname",
|
|
132
|
+
deployMode === "local" ? "mop-agent.local" : "",
|
|
133
|
+
);
|
|
125
134
|
const port = await ask("App port", "3000");
|
|
126
|
-
const
|
|
127
|
-
|
|
135
|
+
const wantSsl = deployMode === "public"
|
|
136
|
+
? (await ask("Obtain HTTPS cert now? (y/n)", "y")).toLowerCase().startsWith("y")
|
|
137
|
+
: false;
|
|
138
|
+
const email = wantSsl
|
|
139
|
+
? await ask("Email for Let's Encrypt", domain ? `admin@${domain.split(".").slice(-2).join(".")}` : "")
|
|
140
|
+
: "";
|
|
128
141
|
rl.close();
|
|
129
142
|
|
|
130
143
|
if (!isValidDomain(domain)) throw new Error(`Invalid domain: ${domain || "(empty)"}`);
|
|
@@ -133,12 +146,15 @@ async function cmdSetup() {
|
|
|
133
146
|
|
|
134
147
|
// 1) .env
|
|
135
148
|
const secret = (n) => randomToken(n);
|
|
149
|
+
const protocol = wantSsl ? "https" : "http";
|
|
136
150
|
const env = [
|
|
137
151
|
`PORT=${port}`,
|
|
138
|
-
`
|
|
152
|
+
`MOP_AGENT_DEPLOY_MODE=${deployMode}`,
|
|
153
|
+
`BETTER_AUTH_URL=${protocol}://${domain}`,
|
|
139
154
|
`BETTER_AUTH_SECRET=${secret(48)}`,
|
|
140
155
|
`MOP_AGENT_SECRET=${secret(64).replace(/[^0-9a-f]/g, "").padEnd(64, "0").slice(0, 64)}`,
|
|
141
156
|
`MOP_AGENT_DATA_DIR=${APP_DIR}/data`,
|
|
157
|
+
`MOP_AGENT_MODEL_CACHE=${APP_DIR}/data/models`,
|
|
142
158
|
`MOP_AGENT_CONSOLIDATE_CRON=0 3 * * *`,
|
|
143
159
|
].join("\n") + "\n";
|
|
144
160
|
console.log(c("cyan", "\n▸ Write apps/web/.env"));
|
|
@@ -172,8 +188,13 @@ async function cmdSetup() {
|
|
|
172
188
|
{ label: "Reload nginx", cmd: "systemctl reload nginx" },
|
|
173
189
|
], { privileged: true });
|
|
174
190
|
|
|
175
|
-
// 4) systemd —
|
|
176
|
-
const serviceUser = isRoot
|
|
191
|
+
// 4) systemd — root installs get a dedicated locked-down service account.
|
|
192
|
+
const serviceUser = isRoot
|
|
193
|
+
? ensureRootServiceUser(os)
|
|
194
|
+
: process.env.USER || String(process.getuid?.() ?? "root");
|
|
195
|
+
if (isRoot) {
|
|
196
|
+
run(`mkdir -p ${q(`${APP_DIR}/data`)} && chown -R ${serviceUser}:${serviceUser} ${q(`${APP_DIR}/data`)} ${q(`${APP_DIR}/apps/web/.env`)}`);
|
|
197
|
+
}
|
|
177
198
|
const unit = renderSystemdUnit({ appDir: APP_DIR, port, user: serviceUser });
|
|
178
199
|
console.log(c("cyan", "▸ systemd service (auto-restart on boot)"));
|
|
179
200
|
writeConf("/etc/systemd/system/mop-agent.service", unit, { privileged: true });
|
|
@@ -200,7 +221,7 @@ async function cmdSetup() {
|
|
|
200
221
|
}
|
|
201
222
|
}
|
|
202
223
|
|
|
203
|
-
console.log(c("green", `\n✓
|
|
224
|
+
console.log(c("green", `\n✓ ${deployMode} setup complete. Visit ${protocol}://${domain}/setup to create the owner.\n`));
|
|
204
225
|
printInstallLocations(os);
|
|
205
226
|
}
|
|
206
227
|
|
|
@@ -282,6 +303,37 @@ function randomToken(n) {
|
|
|
282
303
|
return randomBytes(Math.ceil(n / 2)).toString("hex").slice(0, n);
|
|
283
304
|
}
|
|
284
305
|
|
|
306
|
+
async function maybeUpdateNpm() {
|
|
307
|
+
if (args["skip-npm-update"] || process.env.MOP_AGENT_SKIP_NPM_UPDATE === "1") return;
|
|
308
|
+
const current = run("npm --version", { capture: true }).stdout.trim();
|
|
309
|
+
const latestResult = run("npm view npm version", { capture: true, allowFailure: true });
|
|
310
|
+
const latest = latestResult.stdout.trim();
|
|
311
|
+
if (!latest || latest === current) {
|
|
312
|
+
console.log(c("green", `✓ npm ${current || "unknown"} is current\n`));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const engineResult = run("npm view npm@latest engines.node", { capture: true, allowFailure: true });
|
|
316
|
+
const engine = engineResult.stdout.trim();
|
|
317
|
+
const rl = createInterface({ input, output });
|
|
318
|
+
const answer = (await rl.question(c("cyan", ` Update npm ${current} → ${latest}${engine ? ` (Node ${engine})` : ""}? [Y/n]: `))).trim().toLowerCase();
|
|
319
|
+
rl.close();
|
|
320
|
+
if (answer && !answer.startsWith("y")) {
|
|
321
|
+
console.log(c("gray", " npm update skipped.\n"));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
run("npm install -g npm@latest", { privileged: true });
|
|
325
|
+
console.log(c("green", `✓ npm updated to ${latest}\n`));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function ensureRootServiceUser(os) {
|
|
329
|
+
const user = "mop-agent";
|
|
330
|
+
const create = os.family === "alpine"
|
|
331
|
+
? `id -u ${user} >/dev/null 2>&1 || adduser -S -H -h ${q(APP_DIR)} -s /sbin/nologin ${user}`
|
|
332
|
+
: `id -u ${user} >/dev/null 2>&1 || useradd --system --home-dir ${q(APP_DIR)} --shell /usr/sbin/nologin ${user}`;
|
|
333
|
+
run(create);
|
|
334
|
+
return user;
|
|
335
|
+
}
|
|
336
|
+
|
|
285
337
|
async function tui() {
|
|
286
338
|
banner();
|
|
287
339
|
if (!supportedLinux()) return;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mop-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "mop-agent",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.1",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"packages/*",
|
package/package.json
CHANGED