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 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.0` is prepared but may not have
9
- > been published yet. After publication, the canonical installation command is
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 the TUI. Choose
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
- The installer requests `sudo` only when it needs to write under `/opt` or
60
- `/etc`, install OS packages, or control nginx/systemd. Do not run the entire
61
- npm/npx process with `sudo`.
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
@@ -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
  }
@@ -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 nginx and Certbot
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 uninstall Remove service and nginx config
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 your normal user. MOP-AGENT asks for sudo only when an OS
57
- operation requires it. Native Windows/macOS production installation is not yet
58
- supported; use WSL2 Ubuntu on Windows or a Linux host.
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) fail("Do not run `npx mop-agent` with sudo/root. Run it as your normal user.");
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], {
@@ -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 # install system deps (nginx/certbot)
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 uninstall # remove service + nginx vhost (keeps data unless --purge)
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 step complete. Next: mop-agent setup\n"));
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 domain = await ask("Domain (e.g. agent.mydomain.com)");
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 email = await ask("Email for Let's Encrypt", domain ? `admin@${domain.split(".").slice(-2).join(".")}` : "");
127
- const wantSsl = (await ask("Obtain HTTPS cert now? (y/n)", "y")).toLowerCase().startsWith("y");
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
- `BETTER_AUTH_URL=https://${domain}`,
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 — run the app as the invoking user, never as root by default.
176
- const serviceUser = isRoot ? process.env.SUDO_USER || "root" : process.env.USER || String(process.getuid?.() ?? "root");
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✓ Setup complete. Visit ${domain ? `https://${domain}` : `http://localhost:${port}`}/setup to create the owner.\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 system deps (nginx and Certbot)"],
292
- ["2", "setup", "Configure domain, SQLite, SSL, systemd service"],
320
+ ["1", "install", "Install (dependencies + complete setup)"],
321
+ ["2", "update", "Update MOP-AGENT + restart"],
293
322
  ["3", "status", "Show service health"],
294
- ["4", "update", "Update to latest + restart"],
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}`);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mop-agent",
3
- "version": "0.1.0",
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.0",
9
+ "version": "0.1.2",
10
10
  "license": "UNLICENSED",
11
11
  "workspaces": [
12
12
  "packages/*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mop-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Self-hosted AI brain and control plane for MOP-FLOW projects, installed with npx mop-agent.",
5
5
  "author": "BURHANDEV ENTERPRISE",
6
6
  "license": "UNLICENSED",