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);