Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added notifications.yaml
Empty file.
1 change: 0 additions & 1 deletion packages/admin-ui/server-mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ startHttpApi({
env: () => {},
fileDownload: () => {},
globalEnvs: () => {},
notificationSend: () => {},
packageManifest: () => {},
metrics: () => {},
publicPackagesData: () => {},
Expand Down
1 change: 1 addition & 0 deletions packages/daemons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@dappnode/hostscriptsservices": "workspace:^0.1.0",
"@dappnode/installer": "workspace:^0.1.0",
"@dappnode/logger": "workspace:^0.1.0",
"@dappnode/notifications": "workspace:^0.1.0",
"@dappnode/params": "workspace:^0.1.0",
"@dappnode/types": "workspace:^0.1.0",
"@dappnode/upnpc": "workspace:^0.1.0",
Expand Down
51 changes: 28 additions & 23 deletions packages/daemons/src/autoUpdates/sendUpdateNotification.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { valid, lte } from "semver";
import { params } from "@dappnode/params";
import * as db from "@dappnode/db";
import { eventBus } from "@dappnode/eventbus";
import { DappnodeInstaller } from "@dappnode/installer";
import { prettyDnpName } from "@dappnode/utils";
import { CoreUpdateDataAvailable, upstreamVersionToString } from "@dappnode/types";
import { CoreUpdateDataAvailable, NotificationCategory, upstreamVersionToString } from "@dappnode/types";
import { formatPackageUpdateNotification, formatSystemUpdateNotification } from "./formatNotificationBody.js";
import { isCoreUpdateEnabled } from "./isCoreUpdateEnabled.js";
import { isDnpUpdateEnabled } from "./isDnpUpdateEnabled.js";
import { notifications } from "@dappnode/notifications";
import { logs } from "@dappnode/logger";

export async function sendUpdatePackageNotificationMaybe({
dappnodeInstaller,
Expand All @@ -31,19 +32,21 @@ export async function sendUpdatePackageNotificationMaybe({
upstream: release.manifest.upstream
});

// Emit notification about new version available
eventBus.notification.emit({
id: `update-available-${dnpName}-${newVersion}`,
type: "info",
title: `Update available for ${prettyDnpName(dnpName)}`,
body: formatPackageUpdateNotification({
dnpName: dnpName,
newVersion,
upstreamVersion,
currentVersion,
autoUpdatesEnabled: isDnpUpdateEnabled(dnpName)
// Send notification about new version available
await notifications
.sendNotification({
title: `Update available for ${prettyDnpName(dnpName)}`,
dnpName,
body: formatPackageUpdateNotification({
dnpName,
currentVersion,
newVersion,
upstreamVersion,
autoUpdatesEnabled: isDnpUpdateEnabled(dnpName)
}),
category: NotificationCategory.CORE
})
});
.catch((e) => logs.error("Error sending package update notification", e));

// Register version to prevent sending notification again
db.packageLatestKnownVersion.set(dnpName, { newVersion, upstreamVersion });
Expand All @@ -58,16 +61,18 @@ export async function sendUpdateSystemNotificationMaybe(data: CoreUpdateDataAvai
const lastEmittedVersion = db.notificationLastEmitVersion.get(dnpName);
if (lastEmittedVersion && valid(lastEmittedVersion) && lte(newVersion, lastEmittedVersion)) return; // Already emitted update available for this version

// Emit notification about new version available
eventBus.notification.emit({
id: `update-available-${dnpName}-${newVersion}`,
type: "info",
title: "System update available",
body: formatSystemUpdateNotification({
packages: data.packages,
autoUpdatesEnabled: isCoreUpdateEnabled()
// Send notification about new version available
await notifications
.sendNotification({
title: `System update available`,
dnpName,
body: formatSystemUpdateNotification({
packages: data.packages,
autoUpdatesEnabled: isCoreUpdateEnabled()
}),
category: NotificationCategory.CORE
})
});
.catch((e) => logs.error("Error sending system update notification", e));

data.packages;

Expand Down
26 changes: 15 additions & 11 deletions packages/daemons/src/diskUsage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { shell, runAtMostEvery, prettyDnpName } from "@dappnode/utils";
import { params } from "@dappnode/params";
import { eventBus } from "@dappnode/eventbus";
import { logs } from "@dappnode/logger";
import { notifications } from "@dappnode/notifications";
import { NotificationCategory } from "@dappnode/types";

/**
* Commands
Expand Down Expand Up @@ -93,17 +95,19 @@ async function monitorDiskUsage(): Promise<void> {
`WARNING: DAppNode has stopped ${threshold.containersDescription} (${stoppedDnpNameList}) after the disk space reached a ${threshold.id}`
);

eventBus.notification.emit({
id: "diskSpaceRanOut-stoppedPackages",
type: "danger",
title: `Disk space is running out, ${threshold.id.split(" ")[0]}`,
body: [
`Available disk space is less than a ${threshold.id}.`,
`To prevent your DAppNode from becoming unusable ${threshold.containersDescription} where stopped.`,
stoppedDnpNames.map((dnpName) => ` - ${prettyDnpName(dnpName)}`).join("\n"),
`Please, free up enough disk space and start them again.`
].join("\n\n")
});
await notifications
.sendNotification({
title: `Disk space is running out, ${threshold.id.split(" ")[0]}`,
dnpName: "dappmanager.dnp.dappnode.eth",
body: [
`Available disk space is less than a ${threshold.id}.`,
`To prevent your DAppNode from becoming unusable ${threshold.containersDescription} where stopped.`,
stoppedDnpNames.map((dnpName) => ` - ${prettyDnpName(dnpName)}`).join("\n"),
`Please, free up enough disk space and start them again.`
].join("\n\n"),
category: NotificationCategory.CORE
})
.catch((e) => logs.error("Error sending disk usage notification", e));

// Emit packages update
eventBus.requestPackages.emit();
Expand Down
1 change: 1 addition & 0 deletions packages/dappmanager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@dappnode/installer": "workspace:^0.1.0",
"@dappnode/logger": "workspace:^0.1.0",
"@dappnode/migrations": "workspace:^0.1.0",
"@dappnode/notifications": "workspace:^0.1.0",
"@dappnode/optimism": "workspace:^0.1.0",
"@dappnode/params": "workspace:^0.1.0",
"@dappnode/stakers": "workspace:^0.1.0",
Expand Down
1 change: 0 additions & 1 deletion packages/dappmanager/src/api/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ export * from "./publicPackagesData.js";
export * from "./sign.js";
export * from "./upload.js";
export * from "./downloadWireguardConfig.js";
export * from "./notificationSend.js";
export * from "./metrics.js";
export * from "./env.js";
49 changes: 0 additions & 49 deletions packages/dappmanager/src/api/routes/notificationSend.ts

This file was deleted.

2 changes: 0 additions & 2 deletions packages/dappmanager/src/api/startHttpApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export interface HttpRoutes {
env: RequestHandler<{ dnpName: string; envName: string }>;
fileDownload: RequestHandler<{ containerName: string }>;
globalEnvs: RequestHandler<{ name: string }>;
notificationSend: RequestHandler;
packageManifest: RequestHandler<{ dnpName: string }>;
metrics: RequestHandler;
publicPackagesData: RequestHandler<{ containerName: string }>;
Expand Down Expand Up @@ -165,7 +164,6 @@ export function startHttpApi({
app.get("/metrics", routes.metrics);
app.post("/sign", routes.sign);
app.post("/data-send", routes.dataSend);
app.post("/notification-send", routes.notificationSend);

// Rest of RPC methods
// prettier-ignore
Expand Down
60 changes: 5 additions & 55 deletions packages/dappmanager/src/calls/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { listPackages } from "@dappnode/dockerapi";
import { CustomEndpoint, GatusEndpoint, Manifest, Notification, NotificationsConfig } from "@dappnode/types";
import { getManifestPath } from "@dappnode/utils";
import fs from "fs";

const BASE_URL = "http://notifier.notifications.dappnode";
import { notifications } from "@dappnode/notifications";
import { CustomEndpoint, GatusEndpoint, Notification, NotificationsConfig } from "@dappnode/types";

/**
* Get all the notifications
* @returns all the notifications
*/
export async function notificationsGetAll(): Promise<Notification[]> {
const response = await fetch(new URL("/api/v1/notifications", `${BASE_URL}:8080`).toString());
return response.json();
return await notifications.getAll();
}

/**
Expand All @@ -20,19 +15,7 @@ export async function notificationsGetAll(): Promise<Notification[]> {
export async function notificationsGetEndpoints(): Promise<{
[dnpName: string]: { endpoints: GatusEndpoint[]; customEndpoints: CustomEndpoint[] };
}> {
const packages = await listPackages();

// Read all manifests files and retrieve the gatus config
const endpoints: { [dnpName: string]: { endpoints: GatusEndpoint[]; customEndpoints: CustomEndpoint[] } } = {};
for (const pkg of packages) {
const manifestPath = getManifestPath(pkg.dnpName, pkg.isCore);
const manifest: Manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
if (manifest.notifications?.endpoints) endpoints[pkg.dnpName].endpoints = manifest.notifications.endpoints;
if (manifest.notifications?.customEndpoints)
endpoints[pkg.dnpName].customEndpoints = manifest.notifications.customEndpoints;
}

return endpoints;
return await notifications.getEndpoints();
}

/**
Expand All @@ -45,38 +28,5 @@ export async function notificationsUpdateEndpoints({
dnpName: string;
notificationsConfig: NotificationsConfig;
}): Promise<void> {
const { endpoints: updatedEndpoints, customEndpoints: updatedCustomEndpoints } = notificationsConfig;

// Get current endpoint status
const manifest: Manifest = JSON.parse(fs.readFileSync(getManifestPath(dnpName, false), "utf8"));
if (!manifest.notifications) throw new Error("No notifications found in manifest");

// Update endpoints
if (updatedEndpoints) {
const endpoints = manifest.notifications.endpoints;
if (!endpoints) throw new Error(`No endpoints found in manifest`);

// Update endpoint
Object.assign(endpoints, updatedEndpoints);
}

// Update custom endpoints
if (updatedCustomEndpoints) {
const customEndpoints = manifest.notifications.customEndpoints;
if (!customEndpoints) throw new Error(`No custom endpoints found in manifest`);

// Update custom endpoint
Object.assign(customEndpoints, updatedCustomEndpoints);
}

// Save manifest
fs.writeFileSync(getManifestPath(dnpName, false), JSON.stringify(manifest, null, 2));

// Trigger reload. Gatus will execute reload at a minimum interval of x seconds
await fetch(new URL("/api/v1/gatus/endpoints/reload", `${BASE_URL}:8082`).toString(), {
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
await notifications.updateEndpoints(dnpName, notificationsConfig);
}
16 changes: 10 additions & 6 deletions packages/dappmanager/src/calls/setStaticIp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as db from "@dappnode/db";
import { updateDyndnsIp } from "@dappnode/dyndns";
import { eventBus } from "@dappnode/eventbus";
import { logs } from "@dappnode/logger";
import { notifications } from "@dappnode/notifications";
import { NotificationCategory } from "@dappnode/types";

/**
* Sets the static IP
Expand All @@ -25,12 +27,14 @@ export async function setStaticIp({ staticIp }: { staticIp: string }): Promise<v
logs.info(`Updated static IP: ${staticIp}`);
}

eventBus.notification.emit({
id: "staticIpUpdated",
type: "warning",
title: "Update connection profiles",
body: "Your static IP was changed, please download and install your VPN connection profile again. Instruct your users to do so also."
});
await notifications
.sendNotification({
title: "Static IP updated",
body: `Your static IP was changed to ${staticIp}.`,
dnpName: "dappmanager.dnp.dappnode.eth",
category: NotificationCategory.CORE
})
.catch((e) => logs.error("Error sending static IP updated notification", e));

// Dynamic update with the new staticIp
eventBus.requestSystemInfo.emit();
Expand Down
7 changes: 0 additions & 7 deletions packages/installer/src/ethClient/syncedNotification.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as db from "@dappnode/db";
import { eventBus } from "@dappnode/eventbus";
import { Eth2ClientTarget, EthClientStatus } from "@dappnode/types";

/**
Expand Down Expand Up @@ -30,11 +29,5 @@ export function emitSyncedNotification(target: Eth2ClientTarget, status: EthClie
execClientTarget: target.execClient,
status: "Synced"
});
eventBus.notification.emit({
id: `eth-client-synced-${target}`,
type: "success",
title: "Ethereum node synced",
body: `Your DAppNode's Ethereum node ${target} is synced.`
});
}
}
8 changes: 8 additions & 0 deletions packages/notifications/.mocharc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
colors: true
exit: true
extension: [ts]
require:
- dotenv/config
node-option:
- experimental-specifier-resolution=node
- import=tsx/esm
9 changes: 9 additions & 0 deletions packages/notifications/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# notifications package

## Overview

## Testing

## Todo

## Contact
28 changes: 28 additions & 0 deletions packages/notifications/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@dappnode/notifications",
"type": "module",
"version": "0.1.0",
"license": "GPL-3.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "tsc -p tsconfig.json",
"dev": "tsc -w"
},
"dependencies": {
"@dappnode/dockerapi": "workspace:^0.1.0",
"@dappnode/types": "workspace:^0.1.0",
"@dappnode/utils": "workspace:^0.1.0"
},
"devDependencies": {
"@types/mocha": "^10",
"mocha": "^10.7.0"
}
}
Loading
Loading