From 638eef37922eb29294ba4c4c20d157f49698fea5 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Wed, 25 Feb 2026 17:40:39 +0000 Subject: [PATCH 1/4] os: Support arbitrary IP address --- backend/planktoscope-org.backend.service | 1 + backend/src/network.js | 28 +++++++++++++ backend/src/service.js | 1 + controller/display/main.py | 35 ++++++++-------- controller/display/test.js | 13 ++---- frontend/.gitignore | 1 + frontend/src/pages/preview/reader.js | 2 - lib/cockpit.js | 40 +++++++++++++++++++ lib/mediamtx.js | 29 ++++++++++++++ lib/network.js | 38 ++++++++++++++++++ lib/package-lock.json | 16 ++++++++ os/cockpit/cockpit.ini | 2 +- os/cockpit/justfile | 4 +- os/justfile | 1 + os/mediamtx/.gitignore | 1 + os/mediamtx/justfile | 11 ++--- os/mediamtx/mediamtx.service | 2 +- .../{mediamtx.yml => mediamtx.template.yml} | 11 ++++- 18 files changed, 196 insertions(+), 40 deletions(-) create mode 100644 backend/src/network.js create mode 100644 lib/cockpit.js create mode 100644 lib/mediamtx.js create mode 100644 os/mediamtx/.gitignore rename os/mediamtx/{mediamtx.yml => mediamtx.template.yml} (83%) diff --git a/backend/planktoscope-org.backend.service b/backend/planktoscope-org.backend.service index 448bf1af3..2179bc59f 100644 --- a/backend/planktoscope-org.backend.service +++ b/backend/planktoscope-org.backend.service @@ -2,6 +2,7 @@ Description=PlanktoScope backend service Wants=mosquitto.service After=mosquitto.service +After=planktoscope-org.controller.display.service [Service] Type=simple diff --git a/backend/src/network.js b/backend/src/network.js new file mode 100644 index 000000000..4f7372c6f --- /dev/null +++ b/backend/src/network.js @@ -0,0 +1,28 @@ +import { configureDisplay } from "../../lib/scope.js" +import { + getWiredIPAddress, + onWiredConnectivityChange, +} from "../../lib/network.js" +import { reconfigureMediaMTX } from "../../lib/mediamtx.js" +import { reconfigureCockpit } from "../../lib/cockpit.js" +import os from "os" + +async function updateDisplay(address) { + let status = "" + if (!address) status = "Offline" + else if (address) status = `http://${address}` + await configureDisplay({ status }) +} + +async function update() { + const hostname = os.hostname() + const address = await getWiredIPAddress() + await Promise.all([ + updateDisplay(address), + reconfigureMediaMTX({ hostname, address }), + ]) + await reconfigureCockpit({ hostname, address }) +} + +update() +onWiredConnectivityChange(update) diff --git a/backend/src/service.js b/backend/src/service.js index a7b5cf4a0..52a18397d 100755 --- a/backend/src/service.js +++ b/backend/src/service.js @@ -8,6 +8,7 @@ import cors from "cors" import "./factory.js" import "./config.js" import "./led-operating-time.js" +import "./network.js" import { readSoftwareConfig, removeConfig } from "../../lib/file-config.js" import { capture } from "../../lib/scope.js" diff --git a/controller/display/main.py b/controller/display/main.py index 786e30fde..9605b2cb6 100644 --- a/controller/display/main.py +++ b/controller/display/main.py @@ -37,6 +37,7 @@ if os.path.exists(libdir): sys.path.append(libdir) +machine_name = None epd = None fontsmall = ImageFont.truetype(os.path.join(picdir, "Font.ttc"), 18) fontnormal = ImageFont.truetype(os.path.join(picdir, "Font.ttc"), 19) @@ -54,7 +55,7 @@ BAR_HEIGHT = 26 -def drawURL(url): +def drawStatus(status=""): assert draw is not None assert width is not None assert height is not None @@ -62,18 +63,20 @@ def drawURL(url): draw.rectangle((0, height - BAR_HEIGHT, width, height), fill=0) # White text centered in the bar draw.text( - (width // 2, height - BAR_HEIGHT // 2), text=url, anchor="mm", font=fontnormal, fill=255 + (width // 2, height - BAR_HEIGHT // 2), text=status, anchor="mm", font=fontnormal, fill=255 ) -def drawHostname(hostname): +def drawMachineName(machine_name): assert width is not None assert height is not None assert draw is not None # Black bar across the top draw.rectangle((0, 0, width, BAR_HEIGHT), fill=0) # White text centered in the bar - draw.text((width // 2, BAR_HEIGHT // 2 + 2), text=hostname, anchor="mm", font=fontbig, fill=255) + draw.text( + (width // 2, BAR_HEIGHT // 2 + 2), text=machine_name, anchor="mm", font=fontbig, fill=255 + ) def drawBrand(): @@ -89,7 +92,7 @@ def drawBrand(): image.paste(logo, (x, y)) -def render(url="", hostname=""): +def render(status=""): assert epd is not None assert width is not None assert height is not None @@ -103,12 +106,12 @@ def render(url="", hostname=""): # # TODO: only clear relevant area ? draw.rectangle((0, 0, height, width), fill=255) - # top black bar with hostname - drawHostname(hostname) + # top black bar with machine_name + drawMachineName(machine_name) # center logo drawBrand() - # bottom black bar with URL - drawURL(url) + # bottom black bar with status + drawStatus(status) epd.init() epd.Clear(0xFF) @@ -118,9 +121,8 @@ def render(url="", hostname=""): async def configure(config): - url = config.get("url", "") - machine_name = config.get("machine-name", "") - render(url, machine_name) + status = config.get("status", "") + render(status) async def clear(): @@ -142,7 +144,7 @@ async def start() -> None: if (await helpers.get_hat_version()) != 3.3: sys.exit() - global epd, epd2in9_V2, width, height + global epd, epd2in9_V2, width, height, machine_name from waveshare_epd import epd2in9_V2 # type: ignore epd = epd2in9_V2.EPD() @@ -150,9 +152,9 @@ async def start() -> None: width = epd.height height = epd.width - url = "http://192.168.4.1" machine_name = helpers.get_machine_name() - render(url=url, hostname=machine_name) + + render(status="http://192.168.4.1") global client client = aiomqtt.Client(hostname="localhost", port=1883, protocol=aiomqtt.ProtocolVersion.V5) @@ -188,9 +190,8 @@ async def handle_action(action: str, payload) -> None: async def stop() -> None: - machine_name = helpers.get_machine_name() if epd is not None: - render(url="OFF", hostname=machine_name) + render(status="OFF") loop.stop() diff --git a/controller/display/test.js b/controller/display/test.js index 805bfe52d..f4a62258d 100644 --- a/controller/display/test.js +++ b/controller/display/test.js @@ -1,5 +1,4 @@ -import { configureDisplay, clearDisplay, watch } from "../../lib/scope.js" -import { setTimeout } from "timers/promises" +import { configureDisplay, watch } from "../../lib/scope.js" watch("display").then(async (messages) => { for await (const message of messages) { @@ -7,13 +6,7 @@ watch("display").then(async (messages) => { } }) -const url = `http://planktoscope-sponge-bob` -const hostname = "sponge-bob" - +const status = `http://planktoscope-sponge-bob` await configureDisplay({ - url, - hostname, + status, }) - -await setTimeout(5000) -await clearDisplay() diff --git a/frontend/.gitignore b/frontend/.gitignore index 6845695d7..7f5d8fbd0 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,2 +1,3 @@ dist .vite +src/pages/preview/reader.js diff --git a/frontend/src/pages/preview/reader.js b/frontend/src/pages/preview/reader.js index bc2155e55..d6b4348be 100644 --- a/frontend/src/pages/preview/reader.js +++ b/frontend/src/pages/preview/reader.js @@ -1,5 +1,3 @@ -// https://github.com/bluenviron/mediamtx/blob/v1.16.1/internal/servers/webrtc/reader.js - 'use strict'; /** diff --git a/lib/cockpit.js b/lib/cockpit.js new file mode 100644 index 000000000..e3092c930 --- /dev/null +++ b/lib/cockpit.js @@ -0,0 +1,40 @@ +import { readFile, writeFile } from "node:fs/promises" +import os from "os" +import { $ } from "execa" +import { Systemctl } from "systemctl.js" + +const config_template_path = "/home/pi/PlanktoScope/os/cockpit/cockpit.ini" +const config_tmp_path = "/tmp/cockpit.conf" +const config_path = "/etc/cockpit/cockpit.conf" + +async function configureCockpit({ hostname, address } = {}) { + let content = await readFile(config_template_path, "utf8") + if (address) { + content = content.replaceAll("192.0.2.1", address) + } + if (hostname) { + content = content.replaceAll("raspberrypi", hostname) + } + await writeFile(config_tmp_path, content) + // FIXME: We should not need sudo, let's figure out + // how we can write cockpit config without root + await $`sudo mv ${config_tmp_path} ${config_path}` +} + +async function restartCockpit() { + const systemctl = new Systemctl() + await systemctl.init() + await systemctl.restart("cockpit") + await systemctl.deinit() +} + +export async function reconfigureCockpit(config) { + await configureCockpit(config) + await restartCockpit() +} + +/* eslint-disable n/no-top-level-await */ +if (import.meta.main) { + const hostname = os.hostname() + await reconfigureCockpit({ hostname, address: "10.42.0.94" }) +} diff --git a/lib/mediamtx.js b/lib/mediamtx.js new file mode 100644 index 000000000..4e75005ee --- /dev/null +++ b/lib/mediamtx.js @@ -0,0 +1,29 @@ +import { readFile, writeFile } from "node:fs/promises" +import os from "os" + +const config_template_path = + "/home/pi/PlanktoScope/os/mediamtx/mediamtx.template.yml" +const config_path = "/home/pi/PlanktoScope/os/mediamtx/mediamtx.yml" + +async function configureMediaMTX({ hostname, address } = {}) { + let content = await readFile(config_template_path, "utf8") + if (address) { + content = content.replaceAll("192.0.2.1", address) + } + if (hostname) { + content = content.replaceAll("raspberrypi", hostname) + } + await writeFile(config_path, content) +} + +export async function reconfigureMediaMTX(config) { + await configureMediaMTX(config) + // mediamtx watches for file change on the config file + // so we don't need to reload/restart the service +} + +/* eslint-disable n/no-top-level-await */ +if (import.meta.main) { + const hostname = os.hostname() + await reconfigureMediaMTX({ hostname, address: "192.0.2.1" }) +} diff --git a/lib/network.js b/lib/network.js index 00f1eddeb..689fd21af 100644 --- a/lib/network.js +++ b/lib/network.js @@ -91,3 +91,41 @@ export async function getWifis() { export async function connectToWifi(path) { await NetworkManager.AddAndActivateConnection([], device_path, path) } + +const [wired_device_path] = await NetworkManager.GetDeviceByIpIface("eth0") +const [wired_device, wired_device_Properties] = await Promise.all([ + service.getInterface( + wired_device_path, + "org.freedesktop.NetworkManager.Device", + ), + service.getInterface(wired_device_path, "org.freedesktop.DBus.Properties"), +]) + +export async function getWiredIPAddress() { + const IP4Config_path = await readProp(wired_device, "Ip4Config") + const IP4Config = await service.getInterface( + IP4Config_path, + "org.freedesktop.NetworkManager.IP4Config", + ) + const addressData = await readProp(IP4Config, "AddressData") + const address = addressData?.[0]?.[0]?.[1]?.[1]?.[0] + return address +} + +let wired_connectivity_change_callback +export function onWiredConnectivityChange(handler) { + wired_connectivity_change_callback = handler +} +await wired_device_Properties.subscribe( + "PropertiesChanged", + function handler(interface_name, changed_properties) { + if (interface_name === "org.freedesktop.NetworkManager.Device") { + const Ip4Connectivity = changed_properties.find((changed_property) => { + const [property_name] = changed_property + return property_name === "Ip4Connectivity" + }) + if (!Ip4Connectivity) return + wired_connectivity_change_callback?.() + } + }, +) diff --git a/lib/package-lock.json b/lib/package-lock.json index 04f3a3db6..c0d291ef8 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -17,6 +17,7 @@ "p-event": "^7.0.0", "systemctl.js": "^0.1.0", "wait-port": "^1.1.0", + "yaml": "3.0.0-0", "zod": "^4.1.13" }, "devDependencies": { @@ -2890,6 +2891,21 @@ "node": ">=4.0" } }, + "node_modules/yaml": { + "version": "3.0.0-0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-3.0.0-0.tgz", + "integrity": "sha512-PHPfns7F4FYUxfWzghNC/VYvFANsZkNSEZ7s7TbL9Aabf781lJ0wq88Hqx7+2gLBnBWUIy/YYaE+AIXJ0cs6Dg==", + "license": "ISC", + "bin": { + "yaml": "bin.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/os/cockpit/cockpit.ini b/os/cockpit/cockpit.ini index 751783d96..63f481e30 100644 --- a/os/cockpit/cockpit.ini +++ b/os/cockpit/cockpit.ini @@ -1,7 +1,7 @@ [WebService] AllowUnencrypted = true # Cannot use "*://" or "*" -Origins = http://localhost http://planktoscope.local http://192.168.4.1 http://raspberrypi http://raspberrypi.local +Origins = http://localhost http://planktoscope.local http://192.168.4.1 http://raspberrypi http://raspberrypi.local http://raspberrypi.local http://192.0.2.1 ProtocolHeader = X-Forwarded-Proto ForwardedForHeader = X-Forwarded-For UrlRoot = /admin/cockpit/ diff --git a/os/cockpit/justfile b/os/cockpit/justfile index 3f0174d8b..595c678a4 100644 --- a/os/cockpit/justfile +++ b/os/cockpit/justfile @@ -16,8 +16,8 @@ setup: cockpit-files # https://github.com/cockpit-project/cockpit-files/issues/635 cockpit-files: sudo apt install -y gettext - wget https://github.com/cockpit-project/cockpit-files/releases/download/30/cockpit-files-30.tar.xz -P /tmp - cd /tmp && tar -xf cockpit-files-30.tar.xz + wget https://github.com/cockpit-project/cockpit-files/releases/download/36/cockpit-files-36.tar.xz -P /tmp + cd /tmp && tar -xf cockpit-files-36.tar.xz rm -rf ~/.local/share/cockpit/files sudo rm -rf /usr/local/share/cockpit/files cd /tmp/cockpit-files && make diff --git a/os/justfile b/os/justfile index c3b512b77..b5ccca717 100644 --- a/os/justfile +++ b/os/justfile @@ -1,4 +1,5 @@ setup: + # Should be first just --justfile machine-name/justfile setup just --justfile localization/justfile setup just --justfile caddy/justfile setup diff --git a/os/mediamtx/.gitignore b/os/mediamtx/.gitignore new file mode 100644 index 000000000..bdd15dee1 --- /dev/null +++ b/os/mediamtx/.gitignore @@ -0,0 +1 @@ +mediamtx.yml diff --git a/os/mediamtx/justfile b/os/mediamtx/justfile index 6f6b5df74..fb5c7f85d 100644 --- a/os/mediamtx/justfile +++ b/os/mediamtx/justfile @@ -1,12 +1,13 @@ setup: sudo ./setup_h264_sysctl.sh - wget https://github.com/bluenviron/mediamtx/releases/download/v1.16.1/mediamtx_v1.16.1_linux_arm64.tar.gz -P /tmp - cd /tmp && tar -xf /tmp/mediamtx_v1.16.1_linux_arm64.tar.gz + wget https://github.com/bluenviron/mediamtx/releases/download/v1.16.2/mediamtx_v1.16.2_linux_arm64.tar.gz -P /tmp + wget https://raw.githubusercontent.com/bluenviron/mediamtx/refs/tags/v1.16.2/internal/servers/webrtc/reader.js -O /home/pi/PlanktoScope/frontend/src/pages/preview/reader.js + cd /tmp && tar -xf /tmp/mediamtx_v1.16.2_linux_arm64.tar.gz -sudo systemctl stop mediamtx sudo cp /tmp/mediamtx /usr/local/bin/mediamtx sudo cp mediamtx.service /etc/systemd/system/ - sudo cp mediamtx.yml /usr/local/etc/ - sudo sed -i "s/raspberrypi/$(hostname)/g" /usr/local/etc/mediamtx.yml + cp mediamtx.template.yml mediamtx.yml + # sudo sed -i "s/raspberrypi/$(hostname)/g" mediamtx.yml sudo systemctl reenable mediamtx sudo systemctl restart mediamtx @@ -15,4 +16,4 @@ test: dev: -sudo systemctl stop mediamtx - mediamtx /usr/local/etc/mediamtx.yml + mediamtx mediamtx.yml diff --git a/os/mediamtx/mediamtx.service b/os/mediamtx/mediamtx.service index fbbb3cef7..a8d9d55cd 100644 --- a/os/mediamtx/mediamtx.service +++ b/os/mediamtx/mediamtx.service @@ -5,7 +5,7 @@ After=network.target Wants=network.target [Service] -ExecStart=/usr/local/bin/mediamtx /usr/local/etc/mediamtx.yml +ExecStart=/usr/local/bin/mediamtx /home/pi/PlanktoScope/os/mediamtx/mediamtx.yml [Install] WantedBy=multi-user.target diff --git a/os/mediamtx/mediamtx.yml b/os/mediamtx/mediamtx.template.yml similarity index 83% rename from os/mediamtx/mediamtx.yml rename to os/mediamtx/mediamtx.template.yml index 73324d0c2..6f9d1cf6c 100644 --- a/os/mediamtx/mediamtx.yml +++ b/os/mediamtx/mediamtx.template.yml @@ -1,8 +1,15 @@ -# https://github.com/bluenviron/mediamtx/blob/v1.16.1/mediamtx.yml +# https://github.com/bluenviron/mediamtx/blob/v1.16.2/mediamtx.yml # https://mediamtx.org/docs/usage/webrtc-specific-features#solving-webrtc-connectivity-issues webrtcAdditionalHosts: - [localhost, planktoscope.local, 192.168.4.1, raspberrypi, raspberrypi.local] + [ + localhost, + planktoscope.local, + 192.168.4.1, + raspberrypi, + raspberrypi.local, + 192.0.2.1, + ] # https://mediamtx.org/docs/usage/decrease-packet-loss writeQueueSize: 1024 From 34be6454c61cadd9fe65e6e16df6b6fc4e42259e Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Thu, 26 Feb 2026 10:06:18 +0000 Subject: [PATCH 2/4] f --- os/cockpit/justfile | 10 +++------- os/filebrowser/justfile | 2 +- os/machine-name/generate-hostname.sh | 2 -- os/mediamtx/justfile | 3 +-- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/os/cockpit/justfile b/os/cockpit/justfile index 595c678a4..3274d5b16 100644 --- a/os/cockpit/justfile +++ b/os/cockpit/justfile @@ -1,24 +1,20 @@ setup: cockpit-files # Install cockpit sudo apt install -y --no-install-recommends cockpit cockpit-networkmanager cockpit-storaged cockpit-system cockpit-sosreport pcp - sudo cp cockpit.ini /etc/cockpit/cockpit.conf # https://cockpit-project.org/guide/latest/feature-pcp sudo systemctl reenable pmlogger sudo systemctl restart pmlogger + node ../../lib/cockpit.js sudo systemctl reenable cockpit.socket sudo systemctl restart cockpit.socket - # TODO consider - # https://github.com/gbraad-cockpit/cockpit-tailscale - # https://github.com/gbraad-cockpit/cockpit-headscale -# unavailable in debian +# cockpit-files is unavailable in debian for now -# https://github.com/cockpit-project/cockpit-files/issues/635 +# watch out for https://github.com/cockpit-project/cockpit-files/issues/635 cockpit-files: sudo apt install -y gettext wget https://github.com/cockpit-project/cockpit-files/releases/download/36/cockpit-files-36.tar.xz -P /tmp cd /tmp && tar -xf cockpit-files-36.tar.xz - rm -rf ~/.local/share/cockpit/files sudo rm -rf /usr/local/share/cockpit/files cd /tmp/cockpit-files && make cd /tmp/cockpit-files && sudo make install diff --git a/os/filebrowser/justfile b/os/filebrowser/justfile index 90958ada1..d941fbc99 100644 --- a/os/filebrowser/justfile +++ b/os/filebrowser/justfile @@ -1,5 +1,5 @@ setup: - wget https://github.com/filebrowser/filebrowser/releases/download/v2.44.2/linux-arm64-filebrowser.tar.gz -P /tmp + wget https://github.com/filebrowser/filebrowser/releases/download/v2.60.0/linux-arm64-filebrowser.tar.gz -P /tmp cd /tmp && tar -xzf /tmp/linux-arm64-filebrowser.tar.gz filebrowser -sudo systemctl stop filebrowser sudo cp /tmp/filebrowser /usr/local/bin/filebrowser diff --git a/os/machine-name/generate-hostname.sh b/os/machine-name/generate-hostname.sh index 1e81cd7bc..b951f9ac4 100755 --- a/os/machine-name/generate-hostname.sh +++ b/os/machine-name/generate-hostname.sh @@ -9,6 +9,4 @@ echo "Hostname: $hostname" printf "%s" "$hostname" > /etc/hostname sed -i "s/raspberrypi/$hostname/g" /etc/hosts || true -sed -i "s/raspberrypi/$hostname/g" /etc/cockpit/cockpit.conf || true sed -i "s/raspberrypi/PlanktoScope $machine_name/g" /etc/NetworkManager/system-connections/wlan0-hotspot.nmconnection || true -sed -i "s/raspberrypi/$hostname/g" /usr/local/etc/mediamtx.yml || true diff --git a/os/mediamtx/justfile b/os/mediamtx/justfile index fb5c7f85d..27a4279db 100644 --- a/os/mediamtx/justfile +++ b/os/mediamtx/justfile @@ -6,8 +6,7 @@ setup: -sudo systemctl stop mediamtx sudo cp /tmp/mediamtx /usr/local/bin/mediamtx sudo cp mediamtx.service /etc/systemd/system/ - cp mediamtx.template.yml mediamtx.yml - # sudo sed -i "s/raspberrypi/$(hostname)/g" mediamtx.yml + node ../../lib/mediamtx.js sudo systemctl reenable mediamtx sudo systemctl restart mediamtx From 1a35362d5de916ca73ec40ec4f185b11716fa92d Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Mon, 2 Mar 2026 10:25:09 +0000 Subject: [PATCH 3/4] investigate dhcp soft offline --- lib/network.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/network.js b/lib/network.js index 689fd21af..836f5919e 100644 --- a/lib/network.js +++ b/lib/network.js @@ -119,6 +119,7 @@ export function onWiredConnectivityChange(handler) { await wired_device_Properties.subscribe( "PropertiesChanged", function handler(interface_name, changed_properties) { + // console.dir(changed_properties, { colors: true, depth: null }) if (interface_name === "org.freedesktop.NetworkManager.Device") { const Ip4Connectivity = changed_properties.find((changed_property) => { const [property_name] = changed_property @@ -129,3 +130,16 @@ await wired_device_Properties.subscribe( } }, ) + +// If the DHCP server goes offline "softly", for example disabling ICS +// NetworkManager won't notice one possible solution is to use connectivity checking and +// await wired_device.subscribe( +// "StateChanged", +// function handler(new_state, old_state) { +// // NM_DEVICE_STATE_ACTIVATED +// if (new_state !== "ACTIVATED") { +// wired_connectivity_change_callback?.() +// } +// }, +// ) +// an proably better solution is to open a TCP connection to the host and watch for state changes From 0c5d77247e2027052abbcc3e04242fe89b7b4ae8 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Mon, 2 Mar 2026 10:27:03 +0000 Subject: [PATCH 4/4] f --- lib/package-lock.json | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/package-lock.json b/lib/package-lock.json index c0d291ef8..04f3a3db6 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -17,7 +17,6 @@ "p-event": "^7.0.0", "systemctl.js": "^0.1.0", "wait-port": "^1.1.0", - "yaml": "3.0.0-0", "zod": "^4.1.13" }, "devDependencies": { @@ -2891,21 +2890,6 @@ "node": ">=4.0" } }, - "node_modules/yaml": { - "version": "3.0.0-0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-3.0.0-0.tgz", - "integrity": "sha512-PHPfns7F4FYUxfWzghNC/VYvFANsZkNSEZ7s7TbL9Aabf781lJ0wq88Hqx7+2gLBnBWUIy/YYaE+AIXJ0cs6Dg==", - "license": "ISC", - "bin": { - "yaml": "bin.js" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",