neoagent 2.3.1-beta.84 → 2.3.1-beta.85

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.
@@ -5,6 +5,16 @@ const { DATA_DIR } = require('../../../runtime/paths');
5
5
 
6
6
  const VM_ROOT = path.join(DATA_DIR, 'runtime-vms');
7
7
  const GUEST_BOOTSTRAP_ROOT = path.join(VM_ROOT, 'guest-bootstrap');
8
+ const REPO_ROOT = path.resolve(__dirname, '../../..');
9
+ const GUEST_PAYLOAD_ENTRIES = Object.freeze([
10
+ { source: 'server/guest-agent.package.json', target: 'package.json' },
11
+ { source: 'runtime/env.js', target: 'runtime/env.js' },
12
+ { source: 'runtime/paths.js', target: 'runtime/paths.js' },
13
+ { source: 'server/guest_agent.js', target: 'server/guest_agent.js' },
14
+ { source: 'server/services/cli', target: 'server/services/cli' },
15
+ { source: 'server/services/browser', target: 'server/services/browser' },
16
+ { source: 'server/services/android', target: 'server/services/android' },
17
+ ]);
8
18
 
9
19
  fs.mkdirSync(GUEST_BOOTSTRAP_ROOT, { recursive: true });
10
20
 
@@ -12,16 +22,50 @@ function encodeGuestToken(value) {
12
22
  return Buffer.from(String(value || ''), 'utf8').toString('base64');
13
23
  }
14
24
 
25
+ function createGuestPayloadArchive(seedDir) {
26
+ const seedRoot = path.dirname(seedDir);
27
+ const stagingRoot = path.join(seedRoot, 'guest-payload');
28
+ const archivePath = path.join(seedRoot, 'guest-payload.tar.gz');
29
+ fs.rmSync(stagingRoot, { recursive: true, force: true });
30
+ fs.rmSync(archivePath, { force: true });
31
+ fs.mkdirSync(stagingRoot, { recursive: true });
32
+
33
+ for (const entry of GUEST_PAYLOAD_ENTRIES) {
34
+ const sourcePath = path.join(REPO_ROOT, entry.source);
35
+ const targetPath = path.join(stagingRoot, entry.target);
36
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
37
+ if (fs.statSync(sourcePath).isDirectory()) {
38
+ fs.cpSync(sourcePath, targetPath, { recursive: true, force: true });
39
+ } else {
40
+ fs.copyFileSync(sourcePath, targetPath);
41
+ }
42
+ }
43
+
44
+ const tarResult = spawnSync('tar', ['-czf', archivePath, '-C', stagingRoot, '.'], {
45
+ encoding: 'utf8',
46
+ stdio: ['ignore', 'pipe', 'pipe'],
47
+ });
48
+ if (tarResult.status !== 0 || !fs.existsSync(archivePath)) {
49
+ throw new Error(
50
+ String(tarResult.stderr || tarResult.stdout || tarResult.error?.message || 'Failed to create guest payload archive.')
51
+ .trim(),
52
+ );
53
+ }
54
+ fs.rmSync(stagingRoot, { recursive: true, force: true });
55
+ return archivePath;
56
+ }
57
+
15
58
  function createCloudInitScript({
16
59
  guestToken,
17
- hostShareMount,
18
- hostDataMount = '/mnt/neoagent-data',
60
+ guestPayloadPath = '/var/lib/neoagent/guest-payload.tar.gz',
19
61
  guestAgentPort = 8421,
20
62
  }) {
21
63
  const guestTokenB64 = encodeGuestToken(guestToken);
22
64
  const envFile = '/etc/neoagent/neoagent.env';
23
65
  const appDir = '/opt/neoagent';
24
66
  const bootstrapMarker = '/var/lib/neoagent/bootstrap-complete';
67
+ const browserReadyMarker = '/var/lib/neoagent/browser-runtime-ready';
68
+ const browserDepsMarker = '/var/lib/neoagent/browser-deps-installed';
25
69
  const nodeSourceSetupUrl = 'https://deb.nodesource.com/setup_20.x';
26
70
 
27
71
  return [
@@ -29,53 +73,20 @@ function createCloudInitScript({
29
73
  'set -uo pipefail', // Removed -e to handle non-critical failures gracefully
30
74
  '',
31
75
  'export DEBIAN_FRONTEND=noninteractive',
32
- `HOST_SHARE_MOUNT=${JSON.stringify(hostShareMount)}`,
33
- `HOST_DATA_MOUNT=${JSON.stringify(hostDataMount)}`,
34
- 'HOST_SHARE_TAG=neoagent-host',
35
- 'HOST_SHARE_TAG_FALLBACK=neoagent-host-pci',
36
- 'HOST_DATA_TAG=neoagent-data',
37
- 'HOST_DATA_TAG_FALLBACK=neoagent-data-pci',
38
76
  `APP_DIR=${JSON.stringify(appDir)}`,
39
77
  `BOOTSTRAP_MARKER=${JSON.stringify(bootstrapMarker)}`,
78
+ `BROWSER_READY_MARKER=${JSON.stringify(browserReadyMarker)}`,
79
+ `BROWSER_DEPS_MARKER=${JSON.stringify(browserDepsMarker)}`,
40
80
  `ENV_FILE=${JSON.stringify(envFile)}`,
81
+ `GUEST_PAYLOAD_PATH=${JSON.stringify(guestPayloadPath)}`,
41
82
  '',
42
- 'mkdir -p /etc/neoagent /var/lib/neoagent "$HOST_SHARE_MOUNT" "$HOST_DATA_MOUNT" "$APP_DIR"',
83
+ 'mkdir -p /etc/neoagent /var/lib/neoagent "$APP_DIR"',
43
84
  '',
44
- '# Ensure the 9p virtio filesystem driver is loaded',
45
- 'modprobe 9p 2>/dev/null || true',
46
- 'modprobe 9pnet_virtio 2>/dev/null || true',
47
- '',
48
- 'function mount_9p_tag() {',
49
- ' local tag="$1"',
50
- ' local target="$2"',
51
- ' local mode="$3"',
52
- ' mount -t 9p -o "trans=virtio,version=9p2000.L,msize=131072,${mode}" "$tag" "$target" >/dev/null 2>&1',
53
- '}',
54
- '',
55
- 'if ! mount_9p_tag "$HOST_SHARE_TAG" "$HOST_SHARE_MOUNT" ro; then',
56
- ' if mount_9p_tag "$HOST_SHARE_TAG_FALLBACK" "$HOST_SHARE_MOUNT" ro; then',
57
- ' HOST_SHARE_TAG="$HOST_SHARE_TAG_FALLBACK"',
58
- ' fi',
59
- 'fi',
60
- 'if ! mount_9p_tag "$HOST_DATA_TAG" "$HOST_DATA_MOUNT" rw; then',
61
- ' if mount_9p_tag "$HOST_DATA_TAG_FALLBACK" "$HOST_DATA_MOUNT" rw; then',
62
- ' HOST_DATA_TAG="$HOST_DATA_TAG_FALLBACK"',
63
- ' fi',
64
- 'fi',
65
- '',
66
- 'if ! grep -qs "${HOST_SHARE_MOUNT}" /etc/fstab; then',
67
- ' echo "${HOST_SHARE_TAG} ${HOST_SHARE_MOUNT} 9p trans=virtio,version=9p2000.L,msize=262144,ro 0 0" >> /etc/fstab',
68
- 'fi',
69
- 'if ! grep -qs "${HOST_DATA_MOUNT}" /etc/fstab; then',
70
- ' echo "${HOST_DATA_TAG} ${HOST_DATA_MOUNT} 9p trans=virtio,version=9p2000.L,msize=262144,rw 0 0" >> /etc/fstab',
71
- 'fi',
72
- '',
73
- 'mount -a >/dev/null 2>&1 || true',
74
- '',
75
- '# Redirect logs to both host-writable share and console',
76
- 'LOG_FILE="${HOST_DATA_MOUNT}/bootstrap.log"',
85
+ '# Redirect logs to a guest-local file and console',
86
+ 'LOG_FILE="/var/log/neoagent-bootstrap.log"',
77
87
  'exec > >(tee -a "$LOG_FILE" >/dev/console) 2>&1',
78
88
  'echo "NeoAgent guest bootstrap starting..."',
89
+ 'rm -f "$BOOTSTRAP_MARKER" "$BROWSER_READY_MARKER"',
79
90
  '',
80
91
  'function retry_cmd() {',
81
92
  ' local n=1',
@@ -95,49 +106,14 @@ function createCloudInitScript({
95
106
  ' done',
96
107
  '}',
97
108
  '',
98
- 'echo "Updating package lists..."',
99
- 'retry_cmd apt-get update || echo "Warning: apt-get update failed, proceeding with cached lists."',
100
- '',
101
- 'echo "Installing dependencies..."',
102
- 'retry_cmd apt-get install -y --no-install-recommends \\',
103
- ' curl ca-certificates gnupg openjdk-17-jre-headless git rsync build-essential \\',
104
- ' python3 unzip libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 libcups2 \\',
105
- ' libx11-xcb1 libgtk-3-0 libnss3 libnspr4 libxcomposite1 libxdamage1 \\',
106
- ' libxrandr2 libxkbcommon0 libasound2t64 libgbm1 libdrm2 libdbus-1-3 \\',
107
- ' libpango-1.0-0 libpangocairo-1.0-0 libxshmfence1 || echo "Warning: Some dependencies failed to install."',
108
- '',
109
- 'if [ -d "$HOST_SHARE_MOUNT" ]; then',
110
- ' echo "Syncing guest agent sources..."',
111
- ' SYNC_PATHS=(',
112
- ' server/guest-agent.package.json:package.json',
113
- ' runtime/env.js',
114
- ' runtime/paths.js',
115
- ' server/guest_agent.js',
116
- ' server/services/cli',
117
- ' server/services/browser',
118
- ' server/services/android',
119
- ' )',
120
- ' for relPath in "${SYNC_PATHS[@]}"; do',
121
- ' sourceRelPath="${relPath%%:*}"',
122
- ' targetRelPath="${relPath##*:}"',
123
- ' sourcePath="$HOST_SHARE_MOUNT/$sourceRelPath"',
124
- ' targetPath="$APP_DIR/$targetRelPath"',
125
- ' if [ -e "$sourcePath" ]; then',
126
- ' mkdir -p "$(dirname "$targetPath")"',
127
- ' if [ -d "$sourcePath" ]; then',
128
- ' mkdir -p "$targetPath"',
129
- ' rsync -a --delete "$sourcePath"/ "$targetPath"/',
130
- ' else',
131
- ' rsync -a "$sourcePath" "$targetPath"',
132
- ' fi',
133
- ' else',
134
- ' echo "Warning: Optional source path missing: $relPath"',
135
- ' fi',
136
- ' done',
137
- 'else',
138
- ' echo "Error: Host repo share is not available. Bootstrap cannot continue." >&2',
109
+ 'if [ ! -f "$GUEST_PAYLOAD_PATH" ]; then',
110
+ ' echo "Error: Guest payload archive is missing at $GUEST_PAYLOAD_PATH." >&2',
139
111
  ' exit 1',
140
112
  'fi',
113
+ 'echo "Extracting guest runtime payload..."',
114
+ 'rm -rf "$APP_DIR"',
115
+ 'mkdir -p "$APP_DIR"',
116
+ 'tar -xzf "$GUEST_PAYLOAD_PATH" -C "$APP_DIR" || { echo "Error: Failed to extract guest runtime payload." >&2; exit 1; }',
141
117
  '',
142
118
  'if ! command -v node >/dev/null 2>&1 || ! node -e "process.exit(Number(process.versions.node.split(\'.\')[0]) >= 20 ? 0 : 1)"; then',
143
119
  ' echo "Installing Node.js..."',
@@ -150,28 +126,50 @@ function createCloudInitScript({
150
126
  'chmod 0600 "$ENV_FILE"',
151
127
  '',
152
128
  'cd "$APP_DIR"',
129
+ 'export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1',
153
130
  'if [ ! -d node_modules ] || [ ! -f node_modules/.neoagent-bootstrap-stamp ] || [ package.json -nt node_modules/.neoagent-bootstrap-stamp ]; then',
154
131
  ' echo "Installing npm dependencies..."',
155
- ' export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1',
156
- ' retry_cmd npm install --omit=dev --no-audit --no-fund || echo "Warning: npm install failed."',
132
+ ' retry_cmd npm cache clean --force',
133
+ ' retry_cmd npm install --omit=dev --no-audit --no-fund || { echo "Error: npm install failed." >&2; exit 1; }',
157
134
  ' mkdir -p node_modules',
158
135
  ' date > node_modules/.neoagent-bootstrap-stamp',
159
136
  'fi',
160
137
  '',
161
- '# Install Playwright browser binaries',
138
+ 'if [ ! -f "$BROWSER_DEPS_MARKER" ]; then',
139
+ ' echo "Updating package lists..."',
140
+ ' retry_cmd apt-get update || echo "Warning: apt-get update failed, proceeding with cached lists."',
141
+ '',
142
+ ' echo "Installing browser runtime dependencies..."',
143
+ ' retry_cmd apt-get install -y --no-install-recommends \\',
144
+ ' curl ca-certificates gnupg git rsync unzip \\',
145
+ ' dbus-x11 \\',
146
+ ' xvfb \\',
147
+ ' libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 libcups2 \\',
148
+ ' libx11-xcb1 libgtk-3-0 libnss3 libnspr4 libxcomposite1 libxdamage1 \\',
149
+ ' libxrandr2 libxkbcommon0 libasound2t64 libgbm1 libdrm2 libdbus-1-3 \\',
150
+ ' libpango-1.0-0 libpangocairo-1.0-0 libxshmfence1 || echo "Warning: Some browser dependencies failed to install."',
151
+ ' touch "$BROWSER_DEPS_MARKER"',
152
+ 'else',
153
+ ' echo "Browser runtime dependencies already installed; skipping apt install."',
154
+ 'fi',
155
+ '',
156
+ 'systemctl daemon-reload',
157
+ 'systemctl enable neoagent-guest-agent.service || true',
158
+ 'if ! systemctl is-active --quiet neoagent-guest-agent.service; then',
159
+ ' systemctl start neoagent-guest-agent.service || true',
160
+ 'fi',
161
+ 'echo "NeoAgent guest agent is available; continuing browser runtime provisioning..."',
162
+ '',
162
163
  'PLAYWRIGHT_BROWSERS_PATH="$APP_DIR/.playwright-browsers"',
163
- 'PLAYWRIGHT_STAMP="$PLAYWRIGHT_BROWSERS_PATH/.chromium-installed"',
164
- 'if [ ! -f "$PLAYWRIGHT_STAMP" ]; then',
164
+ 'PLAYWRIGHT_STAMP="$PLAYWRIGHT_BROWSERS_PATH/.firefox-installed"',
165
+ 'mkdir -p "$PLAYWRIGHT_BROWSERS_PATH"',
166
+ 'if [ ! -f "$PLAYWRIGHT_STAMP" ] || [ package.json -nt "$PLAYWRIGHT_STAMP" ]; then',
165
167
  ' echo "Installing Playwright browsers..."',
166
- ' mkdir -p "$PLAYWRIGHT_BROWSERS_PATH"',
167
- ' PLAYWRIGHT_BROWSERS_PATH="$PLAYWRIGHT_BROWSERS_PATH" npx playwright install chromium --with-deps || \\',
168
- ' PLAYWRIGHT_BROWSERS_PATH="$PLAYWRIGHT_BROWSERS_PATH" node ./node_modules/playwright-chromium/install.js || true',
168
+ ' PLAYWRIGHT_BROWSERS_PATH="$PLAYWRIGHT_BROWSERS_PATH" retry_cmd npx playwright install firefox || { echo "Error: Playwright browser install failed." >&2; exit 1; }',
169
169
  ' date > "$PLAYWRIGHT_STAMP"',
170
170
  'fi',
171
171
  '',
172
- 'systemctl daemon-reload',
173
- 'systemctl enable neoagent-guest-agent.service || true',
174
- 'systemctl restart neoagent-guest-agent.service || true',
172
+ 'touch "$BROWSER_READY_MARKER"',
175
173
  'touch "$BOOTSTRAP_MARKER"',
176
174
  'echo "NeoAgent guest bootstrap completed."',
177
175
  '',
@@ -180,15 +178,13 @@ function createCloudInitScript({
180
178
 
181
179
  function createCloudInitUserData({
182
180
  guestToken,
183
- hostShareMount = '/mnt/neoagent-host',
184
- hostDataMount = '/mnt/neoagent-data',
181
+ guestPayloadBase64,
185
182
  guestAgentPort = 8421,
186
183
  }) {
187
184
  const guestTokenB64 = encodeGuestToken(guestToken);
188
185
  const bootstrapScript = createCloudInitScript({
189
186
  guestToken,
190
- hostShareMount,
191
- hostDataMount,
187
+ guestPayloadPath: '/var/lib/neoagent/guest-payload.tar.gz',
192
188
  guestAgentPort,
193
189
  });
194
190
 
@@ -200,8 +196,14 @@ function createCloudInitUserData({
200
196
  " permissions: '0600'",
201
197
  ' owner: root:root',
202
198
  ' content: |',
203
- ` NEOAGENT_VM_GUEST_TOKEN_B64=${guestTokenB64}`,
204
- ` NEOAGENT_GUEST_AGENT_PORT=${guestAgentPort}`,
199
+ ` NEOAGENT_VM_GUEST_TOKEN_B64=${guestTokenB64}`,
200
+ ` NEOAGENT_GUEST_AGENT_PORT=${guestAgentPort}`,
201
+ ' - path: /var/lib/neoagent/guest-payload.tar.gz',
202
+ " permissions: '0644'",
203
+ ' owner: root:root',
204
+ " encoding: 'b64'",
205
+ ' content: |',
206
+ ` ${guestPayloadBase64}`,
205
207
  ' - path: /usr/local/bin/neoagent-guest-bootstrap.sh',
206
208
  " permissions: '0755'",
207
209
  ' owner: root:root',
@@ -409,7 +411,6 @@ function createSeedIso(sourceDir, isoPath) {
409
411
  function ensureGuestBootstrapSeed({
410
412
  userRoot,
411
413
  guestToken,
412
- hostShareMount = '/mnt/neoagent-host',
413
414
  guestAgentPort = 8421,
414
415
  guestArch = 'x64',
415
416
  }) {
@@ -422,7 +423,13 @@ function ensureGuestBootstrapSeed({
422
423
  const userDataPath = path.join(seedDir, 'user-data');
423
424
  const metaDataPath = path.join(seedDir, 'meta-data');
424
425
  const startupNshPath = path.join(seedDir, 'startup.nsh');
425
- const userData = createCloudInitUserData({ guestToken, hostShareMount, guestAgentPort });
426
+ const guestPayloadArchivePath = createGuestPayloadArchive(seedDir);
427
+ const guestPayloadBase64 = fs.readFileSync(guestPayloadArchivePath).toString('base64');
428
+ const userData = createCloudInitUserData({
429
+ guestToken,
430
+ guestPayloadBase64,
431
+ guestAgentPort,
432
+ });
426
433
  const metaData = createCloudInitMetaData({
427
434
  instanceId: `neoagent-${path.basename(userRoot)}`,
428
435
  localHostName: `neoagent-${path.basename(userRoot)}`,
@@ -467,7 +474,6 @@ function ensureGuestBootstrapSeed({
467
474
  userDataPath,
468
475
  metaDataPath,
469
476
  startupNshPath,
470
- hostShareMount,
471
477
  };
472
478
  }
473
479