add linux software support #7846
Signed-off-by: si458 <simonsmith5521@gmail.com>
si458 committed
May 30, 2026 at 15:15 UTC
14d2e603eb895260c8648540cbf7b40195257b21
4 files changed
+228
-8
agents/meshcore.js
+38
-2
@@ -1717,18 +1717,36 @@ function handleServerCommand(data) {
1717
});
1718
};
1719
if (data.value == 'installedapps') {
1720
+ if (process.platform == 'win32') {
1721
try {
1722
if (require('win-info').installedApps) {
1723
require('win-info').installedApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1724
} else { sendSoftwareResponse({ error: "Not supported" }); }
1725
} catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1726
+ } else if (process.platform == 'linux') {
1727
+ try {
1728
+ if (require('linux-info').packages) {
1729
+ require('linux-info').packages().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1730
+ } else { sendSoftwareResponse({ error: "Not supported" }); }
1731
+ } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1732
+ } else {
1733
+ sendSoftwareResponse({ success: false, error: "Not supported" });
1734
+ }
1735
} else if (data.value == 'installedstoreapps') {
1736
+ if (process.platform != 'win32') {
1737
+ sendSoftwareResponse({ success: false, error: "Installed Store Apps is only supported on Windows devices" });
1738
+ return;
1739
+ }
1740
try {
1741
if (require('win-info').installedStoreApps) {
1742
require('win-info').installedStoreApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1743
}
1744
} catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1745
} else if (typeof data.value === 'string' && data.value.indexOf('uninstallapp ') === 0) {
1746
+ if (process.platform != 'win32') {
1747
+ sendSoftwareResponse({ success: false, error: "Uninstall is only supported on Windows devices" });
1748
+ return;
1749
+ }
1750
var base64Cmd = data.value.substring(13).trim();
1751
var uninstallCmd = '';
1752
try {
@@ -1784,6 +1802,10 @@ function handleServerCommand(data) {
1802
}
1803
}
1804
} else if (typeof data.value === 'string' && data.value.indexOf('uninstallstoreapp ') === 0) {
1805
+ if (process.platform != 'win32') {
1806
+ sendSoftwareResponse({ success: false, error: "Uninstall is only supported on Windows devices" });
1807
+ return;
1808
+ }
1809
var rawName = data.value.substring(18).trim();
1810
var packageName = rawName.replace(/"/g, "").replace(/'/g, "");
1811
var logDir = (process.env['ProgramData'] || 'C:\\ProgramData') + '\\MeshAgent';
@@ -4406,7 +4428,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
4428
var fin = '', f = '', availcommands = 'domain,translations,agentupdate,errorlog,msh,timerinfo,coreinfo,coreinfoupdate,coredump,service,fdsnapshot,fdcount,startupoptions,';
4429
availcommands += 'alert,agentsize,versions,help,info,osinfo,args,print,type,dbkeys,dbget,dbset,dbdelete,dbcompact,eval,parseuri,httpget,wslist,plugin,wsconnect,wssend,wsclose,notify,';
4430
availcommands += 'ls,ps,kill,netinfo,location,power,wakeonlan,setdebug,smbios,rawsmbios,toast,lock,users,openurl,getscript,getclip,setclip,log,cpuinfo,sysinfo,';
4409
- availcommands += 'apf,scanwifi,wallpaper,agentmsg,task,uninstallagent,display,openfile';
4431
+ availcommands += 'apf,scanwifi,wallpaper,agentmsg,task,uninstallagent,display,openfile,installedapps';
4432
if (require('os').dns != null) { availcommands += ',dnsinfo'; }
4433
try { require('linux-dhcp'); availcommands += ',dhcp'; } catch (ex) { }
4434
if (process.platform == 'win32') {
@@ -4414,7 +4436,7 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
4436
if (bcdOK()) { availcommands += ',safemode'; }
4437
if (require('notifybar-desktop').DefaultPinned != null) { availcommands += ',privacybar'; }
4438
try { require('win-utils'); availcommands += ',taskbar'; } catch (ex) { }
4417
- try { require('win-info'); availcommands += ',installedapps,qfe,defender,av,installedstoreapps'; } catch (ex) { }
4439
+ try { require('win-info'); availcommands += ',qfe,defender,av,installedstoreapps'; } catch (ex) { }
4440
try { require('win-deskutils'); availcommands += ',mousetrails,idletime,deskbackground'; } catch (ex) { }
4441
}
4442
if (amt != null) { availcommands += ',amt,amtconfig,amtevents'; }
@@ -6020,6 +6042,18 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
6042
} catch (ex) {
6043
sendConsoleText("Module win-info error: " + ex, sessionid);
6044
}
6045
+ } else if (process.platform == 'linux') {
6046
+ try {
6047
+ require('linux-info').packages().then(function (apps) {
6048
+ sendConsoleText(JSON.stringify(apps, null, 1), sessionid);
6049
+ }).catch(function(e) {
6050
+ sendConsoleText("Error: " + e, sessionid);
6051
+ });
6052
+ } catch (ex) {
6053
+ sendConsoleText("Module linux-info error: " + ex, sessionid);
6054
+ }
6055
+ } else {
6056
+ sendConsoleText("Installed apps not supported on this platform.", sessionid);
6057
}
6058
break;
6059
}
@@ -6038,6 +6072,8 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
6072
} catch (ex) {
6073
sendConsoleText("Module win-info error: " + ex, sessionid);
6074
}
6075
+ } else {
6076
+ sendConsoleText("Installed Store Apps not supported on this platform.", sessionid);
6077
}
6078
break;
6079
}
agents/modules_meshcore/linux-info.js
+180
-4
@@ -1,4 +1,6 @@
1
2
+var promise = require('promise');
3
+
4
function dataHandler(data) { this.str += data.toString(); }
5
6
function av()
@@ -89,12 +91,186 @@ function firewall()
91
});
92
}
93
92
-if (process.platform == 'linux')
93
-{
94
- module.exports = { av: av, firewall: firewall };
94
+function packages() {
95
+ var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; });
96
+ var fs = require('fs');
97
+
98
+ function runSh(script) {
99
+ try {
100
+ var child = require('child_process').execFile('/bin/sh', ['sh']);
101
+ child.stdout.str = '';
102
+ child.stdout.on('data', function (d) { child.stdout.str += d; });
103
+ child.stdin.write(script + '\nexit\n');
104
+ child.waitExit();
105
+ return child.stdout.str.trim();
106
+ } catch (ex) { return ''; }
107
+ }
108
+
109
+ function dirExists(path) {
110
+ try { return fs.existsSync(path); } catch (ex) { return false; }
111
+ }
112
+
113
+ function binExists(paths) {
114
+ for (var i = 0; i < paths.length; ++i) {
115
+ try { if (fs.existsSync(paths[i])) return true; } catch (ex) { }
116
+ }
117
+ return false;
118
+ }
119
+
120
+ // ---------- dpkg (Debian/Ubuntu) ----------
121
+ function collectDpkg() {
122
+ if (!binExists(['/usr/bin/dpkg-query', '/bin/dpkg-query'])) return [];
123
+ if (!dirExists('/var/lib/dpkg/info')) return [];
124
+
125
+ var out = runSh('dpkg-query -W -f=\'${binary:Package}\\t${Version}\\t${Architecture}\\t${db:Status-Abbrev}\\t${Maintainer}\\n\' 2>/dev/null');
126
+ if (!out) return [];
127
+
128
+ var dateMap = {};
129
+ var dateOut = runSh('find /var/lib/dpkg/info -name \'*.list\' -printf \'%f\\t%TY-%Tm-%Td\\n\' 2>/dev/null');
130
+ if (dateOut) {
131
+ dateOut.split('\n').forEach(function (line) {
132
+ var p = line.split('\t');
133
+ if (p.length >= 2) { dateMap[p[0].replace(/\.list$/, '')] = p[1]; }
134
+ });
135
+ }
136
+
137
+ var results = [];
138
+ out.split('\n').forEach(function (line) {
139
+ var p = line.split('\t');
140
+ if (p.length < 4) return;
141
+ if (p[3].indexOf('ii') !== 0) return;
142
+ results.push({ name: p[0], version: p[1], arch: p[2], publisher: p[4] || '', date: dateMap[p[0]] || '', location: 'dpkg' });
143
+ });
144
+ return results;
145
+ }
146
+
147
+ // ---------- rpm (RHEL/Fedora/CentOS/SUSE) ----------
148
+ function collectRpm() {
149
+ if (!binExists(['/usr/bin/rpm', '/bin/rpm'])) return [];
150
+ if (!dirExists('/var/lib/rpm') && !dirExists('/usr/lib/sysimage/rpm')) return [];
151
+
152
+ var out = runSh('rpm -qa --qf \'%{NAME}\\t%{VERSION}\\t%{RELEASE}\\t%{ARCH}\\t%{VENDOR}\\t%{INSTALLTIME:date}\\n\' 2>/dev/null');
153
+ if (!out) return [];
154
+
155
+ var results = [];
156
+ out.split('\n').forEach(function (line) {
157
+ var p = line.split('\t');
158
+ if (p.length < 5 || !p[0]) return;
159
+ results.push({ name: p[0], version: p[1] + (p[2] ? '-' + p[2] : ''), arch: p[3], publisher: p[4] || '', date: p[5] || '', location: 'rpm' });
160
+ });
161
+ return results;
162
+ }
163
+
164
+ // ---------- pacman (Arch/Manjaro) ----------
165
+ function collectPacman() {
166
+ if (!binExists(['/usr/bin/pacman', '/bin/pacman'])) return [];
167
+ if (!dirExists('/var/lib/pacman/local')) return [];
168
+
169
+ var out = runSh('pacman -Qi 2>/dev/null');
170
+ if (!out) return [];
171
+
172
+ var results = [];
173
+ var current = {};
174
+ out.split('\n').forEach(function (line) {
175
+ var m = line.match(/^([A-Za-z ]+?)\s*:\s(.+)$/);
176
+ if (m) {
177
+ current[m[1].trim()] = m[2].trim();
178
+ } else if (!line.trim() && current['Name']) {
179
+ results.push({ name: current['Name'], version: current['Version'] || '', arch: current['Architecture'] || '', publisher: current['Packager'] || '', date: current['Install Date'] || '', location: 'pacman' });
180
+ current = {};
181
+ }
182
+ });
183
+ if (current['Name']) {
184
+ results.push({ name: current['Name'], version: current['Version'] || '', arch: current['Architecture'] || '', publisher: current['Packager'] || '', date: current['Install Date'] || '', location: 'pacman' });
185
+ }
186
+ return results;
187
+ }
188
+
189
+ // ---------- apk (Alpine) ----------
190
+ function collectApk() {
191
+ if (!binExists(['/sbin/apk', '/usr/bin/apk'])) return [];
192
+ if (!dirExists('/lib/apk/db') && !dirExists('/var/lib/apk/db')) return [];
193
+
194
+ var out = runSh('apk info -v 2>/dev/null');
195
+ if (!out) return [];
196
+
197
+ var results = [];
198
+ out.split('\n').forEach(function (line) {
199
+ line = line.trim();
200
+ if (!line) return;
201
+ var match = line.match(/^(.+)-(\d[^-]*)(-r\d+)?$/);
202
+ if (match) {
203
+ results.push({ name: match[1], version: match[2] + (match[3] || ''), arch: '', publisher: '', location: 'apk' });
204
+ } else {
205
+ results.push({ name: line, version: '', arch: '', publisher: '', location: 'apk' });
206
+ }
207
+ });
208
+ return results;
209
+ }
210
+
211
+ // ---------- flatpak ----------
212
+ function collectFlatpak() {
213
+ if (!binExists(['/usr/bin/flatpak', '/bin/flatpak'])) return [];
214
+ if (!dirExists('/var/lib/flatpak')) return [];
215
+
216
+ var results = [];
217
+
218
+ var sys = runSh('flatpak list --system --columns=application,version,origin 2>/dev/null');
219
+ sys.split('\n').forEach(function (line) {
220
+ var p = line.split('\t');
221
+ if (!p[0] || !p[0].trim()) return;
222
+ results.push({ name: p[0].trim(), version: (p[1] || '').trim(), arch: '', publisher: (p[2] || '').trim(), location: 'flatpak', scope: 'system' });
223
+ });
224
+
225
+ var usr = runSh('flatpak list --user --columns=application,version,origin 2>/dev/null');
226
+ usr.split('\n').forEach(function (line) {
227
+ var p = line.split('\t');
228
+ if (!p[0] || !p[0].trim()) return;
229
+ results.push({ name: p[0].trim(), version: (p[1] || '').trim(), arch: '', publisher: (p[2] || '').trim(), location: 'flatpak', scope: 'user' });
230
+ });
231
+
232
+ return results;
233
+ }
234
+
235
+ // ---------- snap ----------
236
+ function collectSnap() {
237
+ if (!binExists(['/usr/bin/snap', '/bin/snap'])) return [];
238
+ if (!dirExists('/var/lib/snapd/snaps')) return [];
239
+
240
+ var out = runSh('snap list --all 2>/dev/null');
241
+ if (!out) return [];
242
+
243
+ var results = [];
244
+ out.split('\n').forEach(function (line) {
245
+ if (!line || line.indexOf('Name') === 0) return;
246
+ var p = line.trim().split(/\s+/);
247
+ if (p.length < 5 || !p[0]) return;
248
+ results.push({ name: p[0], version: p[1], arch: '', publisher: p[4] || '', location: 'snap' });
249
+ });
250
+ return results;
251
+ }
252
+
253
+ try {
254
+ var all = [].concat(
255
+ collectDpkg(),
256
+ collectRpm(),
257
+ collectPacman(),
258
+ collectApk(),
259
+ collectFlatpak(),
260
+ collectSnap()
261
+ );
262
+ ret._res(all);
263
+ } catch (e) {
264
+ ret._rej(e);
265
+ }
266
+ return (ret);
267
+}
268
+
269
+if (process.platform == 'linux') {
270
+ module.exports = { av: av, firewall: firewall, packages: packages };
271
}
272
else
273
{
274
var not_supported = function () { throw (process.platform + ' not supported'); };
99
- module.exports = { av: not_supported, firewall: not_supported };
275
+ module.exports = { av: not_supported, firewall: not_supported, packages: not_supported };
276
}
\ No newline at end of file
views/default.handlebars
+5
-1
@@ -2781,6 +2781,10 @@
2781
handleInstalledAppsResponse(softwareData);
2782
} else if (typeof softwareData === 'string' && (softwareData.indexOf('"success"') >= 0 || softwareData.indexOf('"status"') >= 0 || softwareData.indexOf('"error"') >= 0)) {
2783
handleUninstallResponse(softwareData);
2784
+ } else {
2785
+ installedAppsLoading = false;
2786
+ installedAppsLoadingType = 0;
2787
+ displayInstalledApps();
2788
}
2789
} catch (xx) { }
2790
break;
@@ -8150,7 +8154,7 @@
8154
QV('MainDevTerminal', (((node.agent == null) && (node.intelamt != null)) || ((node.agent) && (node.agent.caps == null)) || ((node.agent) && ((node.agent.caps & 2) != 0)) || (node.intelamt && (node.intelamt.state == 2))) && (meshrights & 8) && terminalAccess);
8155
QV('MainDevFiles', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 4) != 0) && (meshrights & 8) && fileAccess);
8156
QV('MainDevInfo', (node.mtype < 3) && ((meshrights & 1048576) != 0));
8153
- QV('MainDevApps', (serverinfo.softwareinventory === true && isWindowsNode(node) && (meshrights & 1048576) != 0));
8157
+ QV('MainDevApps', (node.mtype == 2) && serverinfo.softwareinventory === true && (meshrights & 1048576) != 0);
8158
QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess);
8159
QV('MainDevConsole', (consoleRights && ((node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 8) != 0))) && (meshrights & 8));
8160
QV('MainDevPlugins', false);
views/default3.handlebars
+5
-1
@@ -3669,6 +3669,10 @@
3669
handleInstalledAppsResponse(softwareData);
3670
} else if (typeof softwareData === 'string' && (softwareData.indexOf('"success"') >= 0 || softwareData.indexOf('"status"') >= 0 || softwareData.indexOf('"error"') >= 0)) {
3671
handleUninstallResponse(softwareData);
3672
+ } else {
3673
+ installedAppsLoading = false;
3674
+ installedAppsLoadingType = 0;
3675
+ displayInstalledApps();
3676
}
3677
} catch (xx) { }
3678
break;
@@ -9264,7 +9268,7 @@
9268
QV('MainDevTerminal', (((node.agent == null) && (node.intelamt != null)) || ((node.agent) && (node.agent.caps == null)) || ((node.agent) && ((node.agent.caps & 2) != 0)) || (node.intelamt && (node.intelamt.state == 2))) && (meshrights & 8) && terminalAccess);
9269
QV('MainDevFiles', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 4) != 0) && (meshrights & 8) && fileAccess);
9270
QV('MainDevInfo', (node.mtype < 3) && ((meshrights & 1048576) != 0));
9267
- QV('MainDevApps', (serverinfo.softwareinventory === true && isWindowsNode(node) && (meshrights & 1048576) != 0));
9271
+ QV('MainDevApps', (node.mtype == 2) && (serverinfo.softwareinventory === true && (meshrights & 1048576) != 0));
9272
QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess);
9273
QV('MainDevConsole', (consoleRights && ((node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 8) != 0))) && (meshrights & 8));
9274
QV('MainDevPlugins', false);