Skip to content

Commit

Permalink
Replace node-dbus with @balena/systemd
Browse files Browse the repository at this point in the history
The node-dbus module is unmaintained and a blocker for the update to
Node 18. Switching to our own node bindings for systemd solves this
issue.

Relates-to: Shouqun/node-dbus#241
Change-type: patch
  • Loading branch information
pipex committed Aug 3, 2023
1 parent 393260d commit 3aefd28
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 174 deletions.
10 changes: 5 additions & 5 deletions Dockerfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ ARG FATRW_LOCATION="https://github.com/balena-os/fatrw/releases/download/v${FATR
WORKDIR /usr/src/app

RUN apk add --update --no-cache \
g++ \
make \
build-base \
python3 \
curl \
$NODE \
$NPM \
libuv \
sqlite-dev \
dbus-dev && \
cargo \
rust && \
npm install -g npm@8

COPY package*.json ./
Expand Down Expand Up @@ -170,8 +170,8 @@ RUN npm run build
# Run the production install here, to avoid the npm dependency on
# the later stage
RUN npm ci \
--production \
--no-optional \
--omit=dev \
--omit=optional \
--unsafe-perm \
--build-from-source \
--sqlite=/usr/lib \
Expand Down
63 changes: 14 additions & 49 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"private": true,
"dependencies": {
"@balena/happy-eyeballs": "0.0.6",
"dbus": "^1.0.7",
"@balena/systemd": "^0.4.0",
"got": "^12.5.3",
"mdns-resolver": "^1.0.0",
"semver": "^7.3.2",
Expand All @@ -54,7 +54,6 @@
"@types/chai-things": "0.0.35",
"@types/common-tags": "^1.8.1",
"@types/copy-webpack-plugin": "^10.1.0",
"@types/dbus": "^1.0.3",
"@types/dockerode": "^2.5.34",
"@types/event-stream": "^3.3.34",
"@types/express": "^4.17.14",
Expand Down
164 changes: 49 additions & 115 deletions src/lib/dbus.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,21 @@
import { getBus, Error as DBusError } from 'dbus';
import { promisify } from 'util';
import { TypedError } from 'typed-error';
import * as _ from 'lodash';

import log from './supervisor-console';
import DBus = require('dbus');

export class DbusError extends TypedError {}

let bus: DBus.DBusConnection;
let getInterfaceAsync: <T = DBus.AnyInterfaceMethod>(
serviceName: string,
objectPath: string,
ifaceName: string,
) => Promise<DBus.DBusInterface<T>>;

export const initialized = _.once(async () => {
bus = getBus('system');
getInterfaceAsync = promisify(bus.getInterface.bind(bus));
});

async function getSystemdInterface() {
await initialized();
try {
return await getInterfaceAsync(
'org.freedesktop.systemd1',
'/org/freedesktop/systemd1',
'org.freedesktop.systemd1.Manager',
);
} catch (e) {
throw new DbusError(e as DBusError);
}
}

async function getLoginManagerInterface() {
await initialized();
try {
return await getInterfaceAsync(
'org.freedesktop.login1',
'/org/freedesktop/login1',
'org.freedesktop.login1.Manager',
);
} catch (e) {
throw new DbusError(e as DBusError);
}
}
import { singleton, ServiceManager, LoginManager } from '@balena/systemd';
import { setTimeout } from 'timers/promises';

async function startUnit(unitName: string) {
const systemd = await getSystemdInterface();
const bus = await singleton();
const systemd = new ServiceManager(bus);
const unit = systemd.getUnit(unitName);
log.debug(`Starting systemd unit: ${unitName}`);
try {
systemd.StartUnit(unitName, 'fail');
} catch (e) {
throw new DbusError(e as DBusError);
}
await unit.start('fail');
}

export async function restartService(serviceName: string) {
const systemd = await getSystemdInterface();
const bus = await singleton();
const systemd = new ServiceManager(bus);
const unit = systemd.getUnit(`${serviceName}.service`);
log.debug(`Restarting systemd service: ${serviceName}`);
try {
systemd.RestartUnit(`${serviceName}.service`, 'fail');
} catch (e) {
throw new DbusError(e as DBusError);
}
await unit.restart('fail');
}

export async function startService(serviceName: string) {
Expand All @@ -75,13 +27,11 @@ export async function startSocket(socketName: string) {
}

async function stopUnit(unitName: string) {
const systemd = await getSystemdInterface();
const bus = await singleton();
const systemd = new ServiceManager(bus);
const unit = systemd.getUnit(unitName);
log.debug(`Stopping systemd unit: ${unitName}`);
try {
systemd.StopUnit(unitName, 'fail');
} catch (e) {
throw new DbusError(e as DBusError);
}
await unit.stop('fail');
}

export async function stopService(serviceName: string) {
Expand All @@ -92,60 +42,44 @@ export async function stopSocket(socketName: string) {
return stopUnit(`${socketName}.socket`);
}

export const reboot = async () =>
setTimeout(async () => {
try {
const logind = await getLoginManagerInterface();
logind.Reboot(false);
} catch (e) {
log.error(`Unable to reboot: ${e}`);
}
}, 1000);

export const shutdown = async () =>
setTimeout(async () => {
try {
const logind = await getLoginManagerInterface();
logind.PowerOff(false);
} catch (e) {
log.error(`Unable to shutdown: ${e}`);
}
}, 1000);

async function getUnitProperty(
unitName: string,
property: string,
): Promise<string> {
const systemd = await getSystemdInterface();
return new Promise((resolve, reject) => {
systemd.GetUnit(unitName, async (err: Error, unitPath: string) => {
if (err) {
return reject(err);
}
const iface = await getInterfaceAsync(
'org.freedesktop.systemd1',
unitPath,
'org.freedesktop.DBus.Properties',
);
export async function reboot() {
// No idea why this timeout is here, my guess
// is that it is to allow the API reboot endpoint to be able
// to send a response before the event happens
await setTimeout(1000);
const bus = await singleton();
const logind = new LoginManager(bus);
try {
await logind.reboot();
} catch (e) {
log.error(`Unable to reboot: ${e}`);
}
}

iface.Get(
'org.freedesktop.systemd1.Unit',
property,
(e: Error, value: string) => {
if (e) {
return reject(new DbusError(e));
}
resolve(value);
},
);
});
});
export async function shutdown() {
// No idea why this timeout is here, my guess
// is that it is to allow the API shutdown endpoint to be able
// to send a response before the event happens
await setTimeout(1000);
const bus = await singleton();
const logind = new LoginManager(bus);
try {
await logind.powerOff();
} catch (e) {
log.error(`Unable to shutdown: ${e}`);
}
}

export function serviceActiveState(serviceName: string) {
return getUnitProperty(`${serviceName}.service`, 'ActiveState');
export async function serviceActiveState(serviceName: string) {
const bus = await singleton();
const systemd = new ServiceManager(bus);
const unit = systemd.getUnit(`${serviceName}.service`);
return await unit.activeState;
}

export function servicePartOf(serviceName: string) {
return getUnitProperty(`${serviceName}.service`, 'PartOf');
export async function servicePartOf(serviceName: string) {
const bus = await singleton();
const systemd = new ServiceManager(bus);
const unit = systemd.getUnit(`${serviceName}.service`);
return await unit.partOf;
}
1 change: 1 addition & 0 deletions src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const startConnectivityCheck = _.once(
blink.pattern.stop();
} else {
log.info('Waiting for connectivity...');
//
blink.pattern.start(networkPattern);
}
},
Expand Down
Loading

0 comments on commit 3aefd28

Please sign in to comment.