Software: Apache/2.4.41 (Ubuntu). PHP/8.0.30 uname -a: Linux apirnd 5.4.0-204-generic #224-Ubuntu SMP Thu Dec 5 13:38:28 UTC 2024 x86_64 uid=33(www-data) gid=33(www-data) groups=33(www-data) Safe-mode: OFF (not secure) /usr/local/lib/node_modules/homebridge-config-ui-x/dist/bin/platforms/ drwxr-xr-x | |
| Viewing file: Select action/file-type: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinuxInstaller = void 0;
const os = require("os");
const path = require("path");
const child_process = require("child_process");
const fs = require("fs-extra");
const si = require("systeminformation");
const semver = require("semver");
class LinuxInstaller {
constructor(hbService) {
this.hbService = hbService;
}
get systemdServiceName() {
return this.hbService.serviceName.toLowerCase();
}
get systemdServicePath() {
return path.resolve('/etc/systemd/system', this.systemdServiceName + '.service');
}
get systemdEnvPath() {
return path.resolve('/etc/default', this.systemdServiceName);
}
get runPartsPath() {
return path.resolve('/etc/hb-service', this.hbService.serviceName.toLowerCase(), 'prestart.d');
}
async install() {
this.checkForRoot();
await this.checkUser();
this.setupSudo();
await this.hbService.portCheck();
await this.hbService.storagePathCheck();
await this.hbService.configCheck();
try {
await this.createSystemdEnvFile();
await this.createSystemdService();
await this.createRunPartsPath();
await this.reloadSystemd();
await this.enableService();
await this.createFirewallRules();
await this.start();
await this.hbService.printPostInstallInstructions();
}
catch (e) {
console.error(e.toString());
this.hbService.logger('ERROR: Failed Operation', 'fail');
}
}
async uninstall() {
this.checkForRoot();
await this.stop();
await this.disableService();
try {
if (fs.existsSync(this.systemdServicePath)) {
fs.unlinkSync(this.systemdServicePath);
}
if (fs.existsSync(this.systemdEnvPath)) {
fs.unlinkSync(this.systemdEnvPath);
}
await this.reloadSystemd();
this.hbService.logger(`Removed ${this.hbService.serviceName} Service`, 'succeed');
}
catch (e) {
console.error(e.toString());
this.hbService.logger('ERROR: Failed Operation', 'fail');
}
}
async start() {
this.checkForRoot();
this.fixPermissions();
try {
this.hbService.logger(`Starting ${this.hbService.serviceName} Service...`);
child_process.execSync(`systemctl start ${this.systemdServiceName}`);
this.hbService.logger(`${this.hbService.serviceName} Started`, 'succeed');
}
catch (e) {
this.hbService.logger(`Failed to start ${this.hbService.serviceName}`, 'fail');
}
}
async stop() {
this.checkForRoot();
try {
this.hbService.logger(`Stopping ${this.hbService.serviceName} Service...`);
child_process.execSync(`systemctl stop ${this.systemdServiceName}`);
this.hbService.logger(`${this.hbService.serviceName} Stopped`, 'succeed');
}
catch (e) {
this.hbService.logger(`Failed to stop ${this.systemdServiceName}`, 'fail');
}
}
async restart() {
this.checkForRoot();
this.fixPermissions();
try {
this.hbService.logger(`Restarting ${this.hbService.serviceName} Service...`);
child_process.execSync(`systemctl restart ${this.systemdServiceName}`);
this.hbService.logger(`${this.hbService.serviceName} Restarted`, 'succeed');
}
catch (e) {
this.hbService.logger(`Failed to restart ${this.hbService.serviceName}`, 'fail');
}
}
async rebuild(all = false) {
try {
this.checkForRoot();
const npmGlobalPath = child_process.execSync('/bin/echo -n "$(npm --no-update-notifier -g prefix)/lib/node_modules"').toString('utf8');
const targetNodeVersion = child_process.execSync('node -v').toString('utf8').trim();
child_process.execSync('npm rebuild --unsafe-perm', {
cwd: process.env.UIX_BASE_PATH,
stdio: 'inherit',
});
if (all === true) {
try {
child_process.execSync('npm rebuild --unsafe-perm', {
cwd: npmGlobalPath,
stdio: 'inherit',
});
}
catch (e) {
this.hbService.logger('Could not rebuild all modules - check Homebridge logs.', 'warn');
}
}
this.hbService.logger(`Rebuilt modules in ${process.env.UIX_BASE_PATH} for Node.js ${targetNodeVersion}.`, 'succeed');
}
catch (e) {
console.error(e.toString());
this.hbService.logger('ERROR: Failed Operation', 'fail');
}
}
async getId() {
if (process.getuid() === 0 && this.hbService.asUser) {
const uid = child_process.execSync(`id -u ${this.hbService.asUser}`).toString('utf8');
const gid = child_process.execSync(`id -g ${this.hbService.asUser}`).toString('utf8');
return {
uid: parseInt(uid, 10),
gid: parseInt(gid, 10),
};
}
else {
return {
uid: os.userInfo().uid,
gid: os.userInfo().gid,
};
}
}
getPidOfPort(port) {
try {
if (this.hbService.docker) {
return child_process.execSync('pidof homebridge').toString('utf8').trim();
}
else {
return child_process.execSync(`fuser ${port}/tcp 2>/dev/null`).toString('utf8').trim();
}
}
catch (e) {
return null;
}
}
async updateNodejs(job) {
this.checkForRoot();
const targetPath = path.dirname(path.dirname(process.execPath));
if (targetPath !== '/usr' && targetPath !== '/usr/local') {
this.hbService.logger(`Cannot update Node.js on your system. Non-standard installation path detected: ${targetPath}`, 'fail');
process.exit(1);
}
if (targetPath === '/usr' && await fs.pathExists('/etc/apt/sources.list.d/nodesource.list')) {
await this.updateNodeFromNodesource(job);
}
else {
await this.updateNodeFromTarball(job, targetPath);
}
if (job.rebuild) {
this.hbService.logger(`Rebuilding for Node.js ${job.target}...`);
await this.rebuild(true);
}
if (await fs.pathExists(this.systemdServicePath)) {
await this.restart();
}
else {
this.hbService.logger('Please restart Homebridge for the changes to take effect.', 'warn');
}
}
async updateNodeFromTarball(job, targetPath) {
try {
const glibcVersion = parseFloat(child_process.execSync('getconf GNU_LIBC_VERSION 2>/dev/null').toString().split('glibc')[1].trim());
if (glibcVersion < 2.24) {
this.hbService.logger('Your version of Linux does not meet the GLIBC version requirements to use this tool to upgrade Node.js. ' +
`Wanted: >=2.24. Installed: ${glibcVersion}`, 'fail');
process.exit(1);
}
}
catch (e) {
const osInfo = await si.osInfo();
if (osInfo.distro === 'Alpine Linux') {
this.hbService.logger('Updating Node.js on Alpine Linux / Docker is not supported by this command.', 'fail');
this.hbService.logger('To update Node.js you should pull down the latest version of the oznu/homebridge Docker image.', 'fail');
}
else {
this.hbService.logger('Updating Node.js using this tool is not supported on your version of Linux.');
}
process.exit(1);
}
const uname = child_process.execSync('uname -m').toString().trim();
let downloadUrl;
switch (uname) {
case 'x86_64':
downloadUrl = `https://nodejs.org/dist/${job.target}/node-${job.target}-linux-x64.tar.gz`;
break;
case 'aarch64':
downloadUrl = `https://nodejs.org/dist/${job.target}/node-${job.target}-linux-arm64.tar.gz`;
break;
case 'armv7l':
downloadUrl = `https://nodejs.org/dist/${job.target}/node-${job.target}-linux-armv7l.tar.gz`;
break;
case 'armv6l':
downloadUrl = `https://unofficial-builds.nodejs.org/download/release/${job.target}/node-${job.target}-linux-armv6l.tar.gz`;
break;
default:
this.hbService.logger(`Architecture not supported: ${process.arch}.`, 'fail');
process.exit(1);
break;
}
this.hbService.logger(`Target: ${targetPath}`);
try {
const archivePath = await this.hbService.downloadNodejs(downloadUrl);
const extractConfig = {
file: archivePath,
cwd: targetPath,
strip: 1,
preserveOwner: false,
unlink: true,
};
await this.hbService.removeNpmPackage(path.resolve(targetPath, 'lib', 'node_modules', 'npm'));
await this.hbService.extractNodejs(job.target, extractConfig);
await fs.remove(archivePath);
}
catch (e) {
this.hbService.logger(`Failed to update Node.js: ${e.message}`, 'fail');
process.exit(1);
}
}
async updateNodeFromNodesource(job) {
this.hbService.logger('Updating from NodeSource...');
try {
const majorVersion = semver.parse(job.target).major;
child_process.execSync(`curl -sL https://deb.nodesource.com/setup_${majorVersion}.x | bash -`, {
stdio: 'inherit',
});
if (majorVersion < semver.parse(process.version).major) {
child_process.execSync('apt-get remove -y nodejs', {
stdio: 'inherit',
});
}
child_process.execSync('apt-get install -y nodejs', {
stdio: 'inherit',
});
}
catch (e) {
this.hbService.logger(`Failed to update Node.js: ${e.message}`, 'fail');
process.exit(1);
}
}
async reloadSystemd() {
try {
child_process.execSync('systemctl daemon-reload');
}
catch (e) {
this.hbService.logger('WARNING: failed to run "systemctl daemon-reload"', 'warn');
}
}
async enableService() {
try {
child_process.execSync(`systemctl enable ${this.systemdServiceName} 2> /dev/null`);
}
catch (e) {
this.hbService.logger(`WARNING: failed to run "systemctl enable ${this.systemdServiceName}"`, 'warn');
}
}
async disableService() {
try {
child_process.execSync(`systemctl disable ${this.systemdServiceName} 2> /dev/null`);
}
catch (e) {
this.hbService.logger(`WARNING: failed to run "systemctl disable ${this.systemdServiceName}"`, 'warn');
}
}
checkForRoot() {
if (process.getuid() !== 0) {
this.hbService.logger('ERROR: This command must be executed using sudo on Linux', 'fail');
this.hbService.logger(`EXAMPLE: sudo hb-service ${this.hbService.action}`, 'fail');
process.exit(1);
}
if (this.hbService.action === 'install' && !this.hbService.asUser) {
this.hbService.logger('ERROR: User parameter missing. Pass in the user you want to run Homebridge as using the --user flag eg.', 'fail');
this.hbService.logger(`EXAMPLE: sudo hb-service ${this.hbService.action} --user your-user`, 'fail');
process.exit(1);
}
}
async checkUser() {
try {
child_process.execSync(`id ${this.hbService.asUser} 2> /dev/null`);
}
catch (e) {
child_process.execSync(`useradd -m --system ${this.hbService.asUser}`);
this.hbService.logger(`Created service user: ${this.hbService.asUser}`, 'info');
}
try {
const osInfo = await si.osInfo();
if (osInfo.distro === 'Raspbian GNU/Linux') {
child_process.execSync(`usermod -a -G audio,bluetooth,dialout,gpio,video ${this.hbService.asUser} 2> /dev/null`);
child_process.execSync(`usermod -a -G input,i2c,spi ${this.hbService.asUser} 2> /dev/null`);
}
}
catch (e) {
}
}
setupSudo() {
try {
const npmPath = child_process.execSync('which npm').toString('utf8').trim();
const shutdownPath = child_process.execSync('which shutdown').toString('utf8').trim();
const sudoersEntry = `${this.hbService.asUser} ALL=(ALL) NOPASSWD:SETENV: ${shutdownPath}, ${npmPath}, /usr/bin/npm, /usr/local/bin/npm`;
const sudoers = fs.readFileSync('/etc/sudoers', 'utf-8');
if (sudoers.includes(sudoersEntry)) {
return;
}
child_process.execSync(`echo '${sudoersEntry}' | sudo EDITOR='tee -a' visudo`);
}
catch (e) {
this.hbService.logger('WARNING: Failed to setup /etc/sudoers, you may not be able to shutdown/restart your server from the Homebridge UI.', 'warn');
}
}
fixPermissions() {
if (fs.existsSync(this.systemdServicePath) && fs.existsSync(this.systemdEnvPath)) {
try {
const serviceUser = child_process.execSync(`cat "${this.systemdServicePath}" | grep "User=" | awk -F'=' '{print $2}'`)
.toString('utf8').trim();
const storagePath = child_process.execSync(`cat "${this.systemdEnvPath}" | grep "UIX_STORAGE_PATH" | awk -F'=' '{print $2}' | sed -e 's/^"//' -e 's/"$//'`)
.toString('utf8').trim();
if (storagePath.length > 5 && fs.existsSync(storagePath)) {
child_process.execSync(`chown -R ${serviceUser}: "${storagePath}"`);
}
}
catch (e) {
this.hbService.logger('WARNING: Failed to set permissions', 'warn');
}
}
}
async createFirewallRules() {
if (await fs.pathExists('/usr/sbin/ufw')) {
return await this.createUfwRules();
}
if (await fs.pathExists('/usr/bin/firewall-cmd')) {
return await this.createFirewallCmdRules();
}
}
async createUfwRules() {
var _a;
try {
const status = child_process.execSync('/bin/echo -n "$(ufw status)" 2> /dev/null').toString('utf8');
if (!status.includes('Status: active')) {
return;
}
const currentConfig = await fs.readJson(process.env.UIX_CONFIG_PATH);
const bridgePort = (_a = currentConfig.bridge) === null || _a === void 0 ? void 0 : _a.port;
child_process.execSync(`ufw allow ${this.hbService.uiPort}/tcp 2> /dev/null`);
this.hbService.logger(`Added firewall rule to allow inbound traffic on port ${this.hbService.uiPort}/tcp`, 'info');
if (bridgePort) {
child_process.execSync(`ufw allow ${bridgePort}/tcp 2> /dev/null`);
this.hbService.logger(`Added firewall rule to allow inbound traffic on port ${bridgePort}/tcp`, 'info');
}
}
catch (e) {
this.hbService.logger('WARNING: failed to allow ports through firewall.', 'warn');
}
}
async createFirewallCmdRules() {
var _a;
try {
const status = child_process.execSync('/bin/echo -n "$(firewall-cmd --state)" 2> /dev/null').toString('utf8');
if (status !== 'running') {
return;
}
const currentConfig = await fs.readJson(process.env.UIX_CONFIG_PATH);
const bridgePort = (_a = currentConfig.bridge) === null || _a === void 0 ? void 0 : _a.port;
child_process.execSync(`firewall-cmd --permanent --add-port=${this.hbService.uiPort}/tcp 2> /dev/null`);
this.hbService.logger(`Added firewall rule to allow inbound traffic on port ${this.hbService.uiPort}/tcp`, 'info');
if (bridgePort) {
child_process.execSync(`firewall-cmd --permanent --add-port=${bridgePort}/tcp 2> /dev/null`);
this.hbService.logger(`Added firewall rule to allow inbound traffic on port ${bridgePort}/tcp`, 'info');
}
child_process.execSync('firewall-cmd --reload 2> /dev/null');
this.hbService.logger('Firewall reloaded', 'info');
}
catch (e) {
this.hbService.logger('WARNING: failed to allow ports through firewall.', 'warn');
}
}
async createRunPartsPath() {
await fs.mkdirp(this.runPartsPath);
const permissionScriptPath = path.resolve(this.runPartsPath, '10-fix-permissions');
const permissionScript = [
'#!/bin/sh',
'',
'# Ensure the storage path permissions are correct',
'if [ -n "$UIX_STORAGE_PATH" ] && [ -n "$USER" ]; then',
' echo "Ensuring $UIX_STORAGE_PATH is owned by $USER"',
' [ -d $UIX_STORAGE_PATH ] || mkdir -p $UIX_STORAGE_PATH',
' chown -R $USER: $UIX_STORAGE_PATH',
'fi',
].filter(x => x !== null).join('\n');
await fs.writeFile(permissionScriptPath, permissionScript);
await fs.chmod(permissionScriptPath, '755');
}
async createSystemdEnvFile() {
const envFile = [
`HOMEBRIDGE_OPTS=-I -U "${this.hbService.storagePath}"`,
`UIX_STORAGE_PATH="${this.hbService.storagePath}"`,
'',
'# To enable web terminals via homebridge-config-ui-x uncomment the following line',
'HOMEBRIDGE_CONFIG_UI_TERMINAL=1',
'',
'DISABLE_OPENCOLLECTIVE=true',
].filter(x => x !== null).join('\n');
await fs.writeFile(this.systemdEnvPath, envFile);
}
async createSystemdService() {
const serviceFile = [
'[Unit]',
`Description=${this.hbService.serviceName}`,
'Wants=network-online.target',
'After=syslog.target network-online.target',
'',
'[Service]',
'Type=simple',
`User=${this.hbService.asUser}`,
'PermissionsStartOnly=true',
`WorkingDirectory=${this.hbService.storagePath}`,
`EnvironmentFile=/etc/default/${this.systemdServiceName}`,
`ExecStartPre=-run-parts ${this.runPartsPath}`,
`ExecStartPre=-${this.hbService.selfPath} before-start $HOMEBRIDGE_OPTS`,
`ExecStart=${this.hbService.selfPath} run $HOMEBRIDGE_OPTS`,
'Restart=always',
'RestartSec=3',
'KillMode=process',
'CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_CHOWN CAP_FOWNER CAP_DAC_OVERRIDE CAP_AUDIT_WRITE CAP_SYS_ADMIN',
'AmbientCapabilities=CAP_NET_RAW CAP_NET_BIND_SERVICE',
'',
'[Install]',
'WantedBy=multi-user.target',
].filter(x => x !== null).join('\n');
await fs.writeFile(this.systemdServicePath, serviceFile);
}
}
exports.LinuxInstaller = LinuxInstaller;
//# sourceMappingURL=linux.js.map |
:: Command execute :: | |
--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.0052 ]-- |