mop-agent 0.1.0 → 0.1.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/README.md +30 -10
- package/apps/web/.env.example +2 -0
- package/apps/web/lib/memory/local-embedder.ts +3 -0
- package/installer/bootstrap.mjs +11 -8
- package/installer/mop-agent.mjs +47 -19
- 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.2` contains the corrected VPS
|
|
9
|
+
> installer flow. After publishing 0.1.2, the canonical installation command is
|
|
10
10
|
> exactly `npx mop-agent`.
|
|
11
11
|
|
|
12
12
|
## Current status
|
|
@@ -44,21 +44,40 @@ 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
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
The first run copies the npm-packaged runtime from the temporary npx cache into
|
|
54
|
-
`/opt/mop-agent`, installs its dependencies, and opens
|
|
55
|
-
`install` to install nginx/Certbot, then `setup` to configure the domain,
|
|
56
|
-
SQLite database, HTTPS, and systemd service. The menu remains open between
|
|
57
|
-
steps.
|
|
54
|
+
`/opt/mop-agent`, installs its dependencies, and opens a four-action TUI:
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
- `Install` — installs nginx/Certbot and immediately continues through the
|
|
57
|
+
complete domain, SQLite, HTTPS, and systemd setup.
|
|
58
|
+
- `Update` — updates only MOP-AGENT, migrates/builds, and restarts it.
|
|
59
|
+
- `Status` — reports service health and filesystem locations.
|
|
60
|
+
- `Delete` — removes the service and nginx configuration while preserving data
|
|
61
|
+
unless purge is explicitly requested.
|
|
62
|
+
|
|
63
|
+
During `setup`, choose one deployment mode:
|
|
64
|
+
|
|
65
|
+
- `public` — enter a public domain and optionally obtain a Let's Encrypt HTTPS
|
|
66
|
+
certificate. Use this for an internet-facing server with public IP/DNS.
|
|
67
|
+
- `local` — use a LAN hostname such as `mop-agent.local`; the installer uses
|
|
68
|
+
HTTP and does not invoke Certbot.
|
|
69
|
+
|
|
70
|
+
For a LAN-only test, map the selected hostname to the server IP in your router
|
|
71
|
+
DNS or client `/etc/hosts`. Let's Encrypt public mode requires a real public
|
|
72
|
+
domain and reachable ports 80/443.
|
|
73
|
+
|
|
74
|
+
When launched by a normal user, the installer requests `sudo` only when it
|
|
75
|
+
needs to write under `/opt` or `/etc`, install OS packages, or control
|
|
76
|
+
nginx/systemd. When launched as root, it creates a locked-down `mop-agent`
|
|
77
|
+
system account and runs the web service under that account—not as root.
|
|
78
|
+
|
|
79
|
+
MOP-AGENT never upgrades or modifies the system npm installation. npm and
|
|
80
|
+
Node.js upgrades remain an explicit server-administration task.
|
|
62
81
|
|
|
63
82
|
Subsequent operations use the same command:
|
|
64
83
|
|
|
@@ -83,6 +102,7 @@ so it uses `/opt/mop-agent` rather than `/var/www` for application code.
|
|
|
83
102
|
| systemd unit | `/etc/systemd/system/mop-agent.service` | same |
|
|
84
103
|
| TLS certificates | `/etc/letsencrypt/live/<domain>/` | same |
|
|
85
104
|
| Service logs | `journalctl -u mop-agent -f` | same |
|
|
105
|
+
| Root-install service account | `mop-agent` | `mop-agent` |
|
|
86
106
|
|
|
87
107
|
`MOP_AGENT_DIR` can override `/opt/mop-agent`. Updates preserve
|
|
88
108
|
`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";
|
|
@@ -44,18 +45,18 @@ MOP-AGENT ${VERSION}
|
|
|
44
45
|
|
|
45
46
|
Usage:
|
|
46
47
|
npx mop-agent Open the installer menu
|
|
47
|
-
npx mop-agent install Install
|
|
48
|
-
npx mop-agent setup Configure domain, HTTPS, app and systemd
|
|
48
|
+
npx mop-agent install Install dependencies and complete setup
|
|
49
49
|
npx mop-agent status Show service health and file locations
|
|
50
50
|
npx mop-agent update Apply the npm version selected by npx
|
|
51
|
-
npx mop-agent
|
|
51
|
+
npx mop-agent delete Remove service and nginx config
|
|
52
52
|
|
|
53
53
|
Environment:
|
|
54
54
|
MOP_AGENT_DIR Durable app directory (default: ${DEFAULT_DIR})
|
|
55
55
|
|
|
56
|
-
Run this command as
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
Run this command as either a normal sudo user or root. When launched by a normal
|
|
57
|
+
user, MOP-AGENT asks for sudo only when an OS operation requires it. The web
|
|
58
|
+
service itself never runs as root. Native Windows/macOS production installation
|
|
59
|
+
is not yet supported; use WSL2 Ubuntu on Windows or a Linux host.
|
|
59
60
|
`);
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -86,7 +87,10 @@ function writable(path) {
|
|
|
86
87
|
|
|
87
88
|
function ensureDestination() {
|
|
88
89
|
if (existsSync(APP_DIR) && writable(APP_DIR)) return;
|
|
89
|
-
if (isRoot)
|
|
90
|
+
if (isRoot) {
|
|
91
|
+
mkdirSync(APP_DIR, { recursive: true });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
90
94
|
const uid = String(process.getuid());
|
|
91
95
|
const gid = String(process.getgid());
|
|
92
96
|
console.log(`\n▸ Preparing ${APP_DIR} (sudo is required once for this system directory)`);
|
|
@@ -151,7 +155,6 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
151
155
|
} else {
|
|
152
156
|
assertPlatform();
|
|
153
157
|
assertSafeDestination();
|
|
154
|
-
if (isRoot) fail("Do not run `npx mop-agent` with sudo/root. Run it as your normal user.");
|
|
155
158
|
ensureDestination();
|
|
156
159
|
deployPackage();
|
|
157
160
|
run(process.execPath, [resolve(APP_DIR, "installer/mop-agent.mjs"), ...argv], {
|
package/installer/mop-agent.mjs
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
* MOP-AGENT installer / operator (TUI). Self-host with one command.
|
|
4
4
|
*
|
|
5
5
|
* npx mop-agent # interactive TUI
|
|
6
|
-
* npx mop-agent install #
|
|
7
|
-
* npx mop-agent setup # domain / SQLite / ssl / systemd
|
|
6
|
+
* npx mop-agent install # dependencies + complete setup
|
|
8
7
|
* npx mop-agent update # migrate + build + restart staged npm version
|
|
9
8
|
* npx mop-agent status # health
|
|
10
|
-
* npx mop-agent
|
|
9
|
+
* npx mop-agent delete # remove service + nginx vhost (keeps data unless --purge)
|
|
11
10
|
*
|
|
12
11
|
* Run as a normal user. Privileged OS operations request sudo individually.
|
|
13
12
|
*/
|
|
@@ -105,26 +104,39 @@ function q(value) {
|
|
|
105
104
|
|
|
106
105
|
// ---- commands ----------------------------------------------------------
|
|
107
106
|
|
|
108
|
-
function cmdInstall() {
|
|
107
|
+
async function cmdInstall() {
|
|
109
108
|
banner();
|
|
110
109
|
const os = detectOS();
|
|
111
110
|
if (!printInstallLocations(os)) return;
|
|
112
111
|
console.log(c("bold", "Installing system dependencies (nginx and Certbot)…\n"));
|
|
113
112
|
runSteps(planInstallDeps(os), { privileged: true });
|
|
114
|
-
console.log(c("green", "\n✓ dependencies
|
|
113
|
+
console.log(c("green", "\n✓ dependencies installed. Continuing to application setup…\n"));
|
|
114
|
+
await cmdSetup({ continuation: true });
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
async function cmdSetup() {
|
|
118
|
-
banner();
|
|
117
|
+
async function cmdSetup({ continuation = false } = {}) {
|
|
118
|
+
if (!continuation) banner();
|
|
119
119
|
const os = detectOS();
|
|
120
|
-
if (!printInstallLocations(os)) return;
|
|
120
|
+
if (!continuation && !printInstallLocations(os)) return;
|
|
121
121
|
const rl = createInterface({ input, output });
|
|
122
122
|
const ask = async (q, def) => (await rl.question(c("cyan", ` ${q}${def ? c("gray", ` [${def}]`) : ""}: `))).trim() || def || "";
|
|
123
123
|
|
|
124
|
-
const
|
|
124
|
+
const deployMode = (await ask("Deployment mode (public/local)", "public")).toLowerCase();
|
|
125
|
+
if (!new Set(["public", "local"]).has(deployMode)) {
|
|
126
|
+
rl.close();
|
|
127
|
+
throw new Error(`Invalid deployment mode: ${deployMode}. Use public or local.`);
|
|
128
|
+
}
|
|
129
|
+
const domain = await ask(
|
|
130
|
+
deployMode === "public" ? "Public domain (e.g. agent.mydomain.com)" : "LAN hostname",
|
|
131
|
+
deployMode === "local" ? "mop-agent.local" : "",
|
|
132
|
+
);
|
|
125
133
|
const port = await ask("App port", "3000");
|
|
126
|
-
const
|
|
127
|
-
|
|
134
|
+
const wantSsl = deployMode === "public"
|
|
135
|
+
? (await ask("Obtain HTTPS cert now? (y/n)", "y")).toLowerCase().startsWith("y")
|
|
136
|
+
: false;
|
|
137
|
+
const email = wantSsl
|
|
138
|
+
? await ask("Email for Let's Encrypt", domain ? `admin@${domain.split(".").slice(-2).join(".")}` : "")
|
|
139
|
+
: "";
|
|
128
140
|
rl.close();
|
|
129
141
|
|
|
130
142
|
if (!isValidDomain(domain)) throw new Error(`Invalid domain: ${domain || "(empty)"}`);
|
|
@@ -133,12 +145,15 @@ async function cmdSetup() {
|
|
|
133
145
|
|
|
134
146
|
// 1) .env
|
|
135
147
|
const secret = (n) => randomToken(n);
|
|
148
|
+
const protocol = wantSsl ? "https" : "http";
|
|
136
149
|
const env = [
|
|
137
150
|
`PORT=${port}`,
|
|
138
|
-
`
|
|
151
|
+
`MOP_AGENT_DEPLOY_MODE=${deployMode}`,
|
|
152
|
+
`BETTER_AUTH_URL=${protocol}://${domain}`,
|
|
139
153
|
`BETTER_AUTH_SECRET=${secret(48)}`,
|
|
140
154
|
`MOP_AGENT_SECRET=${secret(64).replace(/[^0-9a-f]/g, "").padEnd(64, "0").slice(0, 64)}`,
|
|
141
155
|
`MOP_AGENT_DATA_DIR=${APP_DIR}/data`,
|
|
156
|
+
`MOP_AGENT_MODEL_CACHE=${APP_DIR}/data/models`,
|
|
142
157
|
`MOP_AGENT_CONSOLIDATE_CRON=0 3 * * *`,
|
|
143
158
|
].join("\n") + "\n";
|
|
144
159
|
console.log(c("cyan", "\n▸ Write apps/web/.env"));
|
|
@@ -172,8 +187,13 @@ async function cmdSetup() {
|
|
|
172
187
|
{ label: "Reload nginx", cmd: "systemctl reload nginx" },
|
|
173
188
|
], { privileged: true });
|
|
174
189
|
|
|
175
|
-
// 4) systemd —
|
|
176
|
-
const serviceUser = isRoot
|
|
190
|
+
// 4) systemd — root installs get a dedicated locked-down service account.
|
|
191
|
+
const serviceUser = isRoot
|
|
192
|
+
? ensureRootServiceUser(os)
|
|
193
|
+
: process.env.USER || String(process.getuid?.() ?? "root");
|
|
194
|
+
if (isRoot) {
|
|
195
|
+
run(`mkdir -p ${q(`${APP_DIR}/data`)} && chown -R ${serviceUser}:${serviceUser} ${q(`${APP_DIR}/data`)} ${q(`${APP_DIR}/apps/web/.env`)}`);
|
|
196
|
+
}
|
|
177
197
|
const unit = renderSystemdUnit({ appDir: APP_DIR, port, user: serviceUser });
|
|
178
198
|
console.log(c("cyan", "▸ systemd service (auto-restart on boot)"));
|
|
179
199
|
writeConf("/etc/systemd/system/mop-agent.service", unit, { privileged: true });
|
|
@@ -200,7 +220,7 @@ async function cmdSetup() {
|
|
|
200
220
|
}
|
|
201
221
|
}
|
|
202
222
|
|
|
203
|
-
console.log(c("green", `\n✓
|
|
223
|
+
console.log(c("green", `\n✓ ${deployMode} setup complete. Visit ${protocol}://${domain}/setup to create the owner.\n`));
|
|
204
224
|
printInstallLocations(os);
|
|
205
225
|
}
|
|
206
226
|
|
|
@@ -282,17 +302,25 @@ function randomToken(n) {
|
|
|
282
302
|
return randomBytes(Math.ceil(n / 2)).toString("hex").slice(0, n);
|
|
283
303
|
}
|
|
284
304
|
|
|
305
|
+
function ensureRootServiceUser(os) {
|
|
306
|
+
const user = "mop-agent";
|
|
307
|
+
const create = os.family === "alpine"
|
|
308
|
+
? `id -u ${user} >/dev/null 2>&1 || adduser -S -H -h ${q(APP_DIR)} -s /sbin/nologin ${user}`
|
|
309
|
+
: `id -u ${user} >/dev/null 2>&1 || useradd --system --home-dir ${q(APP_DIR)} --shell /usr/sbin/nologin ${user}`;
|
|
310
|
+
run(create);
|
|
311
|
+
return user;
|
|
312
|
+
}
|
|
313
|
+
|
|
285
314
|
async function tui() {
|
|
286
315
|
banner();
|
|
287
316
|
if (!supportedLinux()) return;
|
|
288
317
|
if (DRY) console.log(c("yellow", " Running in DRY-RUN (no changes).\n"));
|
|
289
318
|
const rl = createInterface({ input, output });
|
|
290
319
|
const menu = [
|
|
291
|
-
["1", "install", "Install
|
|
292
|
-
["2", "
|
|
320
|
+
["1", "install", "Install (dependencies + complete setup)"],
|
|
321
|
+
["2", "update", "Update MOP-AGENT + restart"],
|
|
293
322
|
["3", "status", "Show service health"],
|
|
294
|
-
["4", "
|
|
295
|
-
["5", "uninstall", "Remove service + nginx vhost"],
|
|
323
|
+
["4", "delete", "Delete service + nginx config"],
|
|
296
324
|
["q", "quit", "Exit"],
|
|
297
325
|
];
|
|
298
326
|
for (const [k, , desc] of menu) console.log(` ${c("cyan", k)} ${desc}`);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mop-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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.2",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"packages/*",
|
package/package.json
CHANGED